基础知识点
DMA
DMA(Direct Memory Access),即直接内存存储,在一些数据的传输中,如串口、SPI等,采用DMA方式,传输过程不需要CPU参与,可用让CPU有更多的时间处理其他的事情。
STM32F4的DMA通道选择如下:
接下来的程序思路如下:
编程要点
DMA发送
串口DMA发送配置
由于是发送不定长的数据,先不需要配置发送的长度,在每次的发送时,再配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
void dma_uart_tx_init() { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); DMA_DeInit(Uart_Tx_DMAStream); while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}
DMA_InitStructure.DMA_Channel = DMA_Channel_4; DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(Uart_Tx_DMAStream, &DMA_InitStructure); DMA_ITConfig(Uart_Tx_DMAStream,DMA_IT_TC,ENABLE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7; NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); DMA_Cmd (Uart_Tx_DMAStream,DISABLE); }
|
DMA发送完成中断
DMA发送完成后,触发DMA发送完成中断,这里可用释放自定义的DMA发送完成信号量,表明下次的DMA传输可用进行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
void DMA2_Stream7_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken; if(DMA_GetITStatus(Uart_Tx_DMAStream,DMA_IT_TCIF7)!= RESET) { DMA_ClearITPendingBit(Uart_Tx_DMAStream,DMA_IT_TCIF7); if(uartDMATCSemaphore!=NULL) { xSemaphoreGiveFromISR(uartDMATCSemaphore,&xHigherPriorityTaskWoken); } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }
|
DMA发送函数接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
void uart_DMA_send(u8 *str,u16 ndtr) { u8 i; u8 *p=str; while(xSemaphoreTake(uartDMATCSemaphore,2)!=pdTRUE); DMA_Cmd(Uart_Tx_DMAStream, DISABLE); while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){} DMA_SetCurrDataCounter(Uart_Tx_DMAStream,ndtr); for(i=0;i<ndtr;i++) { SendBuff[i]=*p++; } DMA_Cmd(Uart_Tx_DMAStream, ENABLE); }
|
DMA接收
串口DMA接收配置
需要配置一个接收地址和一个接收长度,用于DMA接收数据的暂存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
void dma_uart_rx_init() { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); DMA_DeInit(Uart_Rx_DMAStream); while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}
DMA_InitStructure.DMA_Channel = DMA_Channel_4; DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ReceiveBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(Uart_Rx_DMAStream, &DMA_InitStructure); USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); DMA_Cmd (Uart_Rx_DMAStream,ENABLE); }
|
串口空闲中断
串口空闲中断的作用与上一篇FreeRTOS例程3-串口中断接收不定长的数据与二值信号量的使用介绍的一样,都是在发送完一串字符后被触发,这次由于使用了DMA接收,所以接收的数据在DMA缓冲区,且接收的数据长度可用根DMA接收通道的总长度与剩余长度的差值来计算,将接收的数据复制出来使用即可,同时释放自定义的串口空闲信号量,以便其它任务可用及时获取串口接收到的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
void USART1_IRQHandler(void) { uint8_t data; BaseType_t xHigherPriorityTaskWoken; if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { data = USART1->SR; data = USART1->DR; DMA_Cmd(Uart_Rx_DMAStream,DISABLE); while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){} rx_cnt = BUF_SIZE - DMA_GetCurrDataCounter(Uart_Rx_DMAStream); DMA_SetCurrDataCounter(Uart_Rx_DMAStream,BUF_SIZE); memcpy(rxbuf,ReceiveBuff,rx_cnt); DMA_ClearFlag(Uart_Rx_DMAStream,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5); DMA_Cmd(Uart_Rx_DMAStream,ENABLE); if(uartRxIDLESemaphore!=NULL) { xSemaphoreGiveFromISR(uartRxIDLESemaphore,&xHigherPriorityTaskWoken); } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }
|
串口配置与测试任务
串口配置
基础的GPIO配置,以及串口空闲中断配置,并调用上面的串口DMA发送与接收配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
void uart_init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure);
dma_uart_tx_init(); dma_uart_rx_init(); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6; NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_Cmd(USART1, ENABLE); }
|
测试任务
创建DMA发送完成信号量和串口空闲信号量,并先释放DMA发送完成信号量,用于第一次DMA发送时获取信号量。然后测试两条DMA发送不定长字符串,最后测试DMA接收不定长字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void print_task(void *pvParameters) { uartDMATCSemaphore = xSemaphoreCreateBinary(); uartRxIDLESemaphore = xSemaphoreCreateBinary(); xSemaphoreGive(uartDMATCSemaphore); u8 str1[]="ma nong ai xue xi\r\n"; uart_DMA_send(str1,sizeof(str1)); u8 str2[]="xxpcb.github.io\r\n"; uart_DMA_send(str2,sizeof(str2)); BaseType_t err = pdFALSE; while(1) { err=xSemaphoreTake(uartRxIDLESemaphore,5); if(err==pdTRUE) { uart_DMA_send("receive:",sizeof("receive:")); uart_DMA_send(rxbuf,rx_cnt); uart_DMA_send("\r\n",sizeof("\r\n")); rx_cnt=0; } } }
|
实验结果
通过串口助手,可以先接收到DMA发送的两个字符串(第一条hello是测试串口的,不是DMA发的),然后通过串口调试助手发送两次nice to meet you,测试DMA接收。
1 2 3 4 5
| hello ma nong ai xue xi xxpcb.github.io receive:nice to meet you receive:nice to meet you
|
完整工程代码已保存至GitHub:https://github.com/xxpcb/FreeRTOS-STM32F407-examples