营销型 展示类网站,企业形象网站策划方案,网站关键词优化报价,电子商务网站的设计与实现串口DMA接收基本思路 串口DMA接收数据基本思路一、串口处理使用背景及常用处理方法二、串口DMA接收相关思路三、串口DMA发送相关思路 串口DMA接收数据基本思路
一、串口处理使用背景及常用处理方法 单片机经常有串口处理大量数据的场景#xff0c;常用的串口处理数据方式有如… 串口DMA接收基本思路 串口DMA接收数据基本思路一、串口处理使用背景及常用处理方法二、串口DMA接收相关思路三、串口DMA发送相关思路 串口DMA接收数据基本思路
一、串口处理使用背景及常用处理方法 单片机经常有串口处理大量数据的场景常用的串口处理数据方式有如下方式 a.串口接收中断接收 串口阻塞发送即使用串口接收中断对数据进行接收。然后处理完毕后在主程序中调用串口发送函数进行发送。该方式每接收一个数据便进入一次接收中断且发送是阻塞发送会阻碍其他处理操作运行。适合数据量不大的场景。 b.串口空闲中断接收中断 环形缓冲区 串口阻塞发送该方法使用串口空闲中断和接收中断配合环形缓冲区可以解决大部分的串口数据处理场景。适合数据量较大的场景。 c.串口中断 DMA接收 DMA发送该方法使用了DMA可以释放CPU的占用CPU由每次接收触发中断变成接收完毕触发或DMA发送完毕触发减少中断触发频率。适合数据量较大的场景。 以上三种方式各有自己的优缺点DMA串口尽管处理效率看起来是最高的但是耗费了DMA资源。而DMA资源往往是单片机较为稀缺的资源。因此还是需要根据项目需求合理选择。本次主要是在资源富裕的情况下使用DMA进行串口的收发测试。
二、串口DMA接收相关思路 DMA串口接收大致流程如上图所示因为本次使用的是STM32F1系列的DMA只有传送任务完成一半的中断(DMA1_IT_HTx)。因此借用HTx中断实现双缓冲区效果进行接收。[本篇只针对串口DMA进行解析流程DMA相关基础知识请参考其他文章]。 如上图中需要准备一个缓冲区即用户自己管理的数组这个数组需要在程序编译后有确定的地址不要使用会被析构掉的数组地址。原因是开启DMA传送后DMA传送是不经过CPU的直接地址之间的快速传送。此时CPU也会同步正常运行而如果该地址会因CPU程序运行用作别的功能使用则会使DMA传送数据出错。 简单点来说串口接收DMA传送过程类似CPU跟DMA控制器下发一个指令说等串口收到数据了你把收到的数据搬运到仓库里存着发送缓冲区这次就先搬运100个。CPU下完这个指令后就能去执行别的指令处理任务DMA同时也会开始搬运的工作这样就可以实现CPU等100个数据都接收完了再一次性处理降低了CPU一个一个数据处理的工作量。这里的等串口收到数据了就是DMA请求串口每接收完毕一个数据就会通知DMA我收到了一个数据你可以来搬运了。而100个就是DMA传送的数量即这次DMA只搬运100个数据就完成本次DMA传送。 这样的好处就是CPU只在下发DMA指令的时候占用一瞬间便可以去干别的工作等DMA传送完毕之后再进行数据批量处理极大减少了CPU占用的时间。那么想想一下如下的场景假如一次DMA传送完成了CPU正在处理数据的时间串口又来了新数据这时候因为数据还没处理完发送缓冲区还不能释放出来进行下一次DMA接收这个时候就会导致数据丢失。于是这种机制还需要进一步改进。 假如我们将接收缓冲区Buffer人工区分成两个片区CPU要一次性处理100个数据分为每次处理50个数据再借助半满中断就能实现如上图的过程。 1、CPU下发DMA传送指令传送100个数据。此时设置接收缓冲区大小为100个。 2、当接收到50个数据时即接收的数据填满接收缓冲区前50个数据时进入DMA半满中断。在半满中断中通知CPU将接收到的数据搬入FIFO中此时DMA仍旧在向后50个地址写入数据。 3、当接收到100个数据时进入DMA完成中断。在DMA完成中断中首先重置DMA继续进入下一轮的100个数据接收状态这时由于接收缓冲区前50个地址数据已经被搬入FIFO中可以直接使能DMA进行继续接收然后CPU将后50个数据搬入FIFO中。 4、CPU在前台对FIFO中数据进行逐一处理。 可以看到如果整体流程变为这样时CPU提前对数据进行搬运耗费一定CPU资源但能够做到串口丢数据最少的操作。当然这样能够持续进行的前提一定是CPU将数据搬入FIFO的时间要小于DMA将数据搬入数组的时间。以串口波特率为115200bit/s为例数据位8位停止位1位总计一个有效数据为9位。接收1个数据耗时1/115200 * 9 7.8e-5 该时间还未加上DMA搬运时间;而单片机假如按照1M的速率进行处理1Byte数据也能到达1e-6速率。因此CPU肯定是能够在DMA传送完毕之前完成前半个缓冲区域的数据处理。 即使这样目前还剩最后一个问题需要解决假设我们串口目前只有80个数据需要接收按照我们优化后的程序去执行想想会发生什么由于我们CPU给DMA下发的是接收100个数据才完成。在接收到50个数据时进入半满中断搬运然后继续接收后面的50个数据。显而易见DMA只能再接收到30个数据便再也等不到数据了。这时会一直等不到DMA完成中断等不到DMA完成中断DMA接收的数据便无法被存在入用户FIFO中。造成数据本次不能完整的接收只有再接收20个数据凑满100个才能触发搬运。 为了解决该问题需要加入串口空闲中断即在串口空闲时通知CPU来将数据搬运到FIFO中同时重置DMA接收。这样便能接收不定长的数据下一次数据变成30个也能完成接收。而串口空闲中断需要的信息有当前接收缓冲区的首地址是起始地址还是一半的地址和需要搬移的数量。缓冲区首地址可以通过变量进行记录DMA启动后起始地址为0DMA半满中断触发后起始地址为缓冲区的一半。搬移数据量可以通过DMA计数器获得DMA内部存在一个计数器比如我们这里给的是初值100DMA控制器每搬运一个该值就会自减1如变为999897……。计数器变为50触发半满中断直到计数器变为0则触发DMA完成中断。这样我们便可以通过获取该计数器值来计算本次DMA已经传送了多少数据。如上面所说的80个数据此时计算规则为100-20-50 30100为DMA总传送数量20为当前DMA计数器的值50为buffer的起始地址前50个数据已经传送完毕,程序部分如下
#define USART1_DMA_BUF_MAX_LEN 128
#define USART1_DMA_FIFO_MAX_LEN 512_fifo_t dma_rx_fifo_uart1;
uint8_t dma_uart1_fifo_buf[USART1_DMA_FIFO_MAX_LEN]{0};
uint8_t dma_uart1_rx_buf[USART1_DMA_BUF_MAX_LEN] {0};
uint8_t dma_uart1_tx_buf[USART1_DMA_BUF_MAX_LEN] {0x01,0x02};
uint8_t lock_state 0;uart_dev uart_dev_uart1
{USART1 , //串口外设1115200 , //buaddma_rx_fifo_uart1, //DMA_RX_FIFOdma_uart1_rx_buf , //DMA_RX_BUFdma_uart1_tx_buf //DMA_TX_BUF
};/*** brief 初始化串口GPIO* param * retval 0:成功; 1:失败
*/
uint32_t uart_dev_gpio_init()
{GPIO_InitTypeDef gpio_initstruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//PA9 TXDgpio_initstruct.GPIO_Mode GPIO_Mode_AF_PP;gpio_initstruct.GPIO_Pin GPIO_Pin_9;gpio_initstruct.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, gpio_initstruct);//PA10 RXDgpio_initstruct.GPIO_Mode GPIO_Mode_IN_FLOATING;gpio_initstruct.GPIO_Pin GPIO_Pin_10;gpio_initstruct.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, gpio_initstruct);return 0;
}/*** brief 初始化串口外设* param * retval 0:成功; 1:失败
*/
uint32_t uart_dev_uart_init (uart_dev* uart_dev)
{ USART_InitTypeDef usart_initstruct;NVIC_InitTypeDef nvic_initstruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);usart_initstruct.USART_BaudRate uart_dev -baud;usart_initstruct.USART_HardwareFlowControl USART_HardwareFlowControl_None; //无硬件流控usart_initstruct.USART_Mode USART_Mode_Rx | USART_Mode_Tx; //接收和发送usart_initstruct.USART_Parity USART_Parity_No; //无校验usart_initstruct.USART_StopBits USART_StopBits_1; //1位停止位usart_initstruct.USART_WordLength USART_WordLength_8b; //8位数据位USART_Init(uart_dev-USARTx, usart_initstruct);USART_ClearFlag(USART1, USART_FLAG_TC|USART_FLAG_RXNE); //清除默认标志位(主要是TC)USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //串口DMA接收使能USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //串口DMA发送使能USART_Cmd(USART1, ENABLE); //使能串口USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //使能空闲中断nvic_initstruct.NVIC_IRQChannel USART1_IRQn;nvic_initstruct.NVIC_IRQChannelCmd ENABLE;nvic_initstruct.NVIC_IRQChannelPreemptionPriority 0;nvic_initstruct.NVIC_IRQChannelSubPriority 1;NVIC_Init(nvic_initstruct);return 0;
}/*** brief 初始化串口DMA* param * retval 0:成功; 1:失败
*/
uint32_t uart_dev_DMA_init(uart_dev* uart_dev)
{NVIC_InitTypeDef nvic_initstruct;DMA_InitTypeDef DMA_USART1_InitStructure;RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1,ENABLE);DMA_DeInit(DMA1_Channel5);DMA_USART1_InitStructure.DMA_PeripheralBaseAddr (uint32_t) USART1-DR ; //USART1数据接收寄存器DMA_USART1_InitStructure.DMA_MemoryBaseAddr (uint32_t) uart_dev -dma_rx_buff; //接收寄存器区间地址DMA_USART1_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC ; //从外设读取 外设 —— 内存DMA_USART1_InitStructure.DMA_BufferSize USART1_DMA_BUF_MAX_LEN ; //传送长度DMA_USART1_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable ; //外设地址不增DMA_USART1_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable ; //内存地址自增DMA_USART1_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; //传送数据为8位DMA_USART1_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte ; //MemoryDataSizeDMA_USART1_InitStructure.DMA_Mode DMA_Mode_Normal ; //普通模式 单次DMA_USART1_InitStructure.DMA_Priority DMA_Priority_VeryHigh ; //传送优先级非常高DMA_USART1_InitStructure.DMA_M2M DMA_M2M_Disable ; //从外设触发DMA_Init(DMA1_Channel5,DMA_USART1_InitStructure);//开启DMADMA_Cmd(DMA1_Channel5,DISABLE);DMA_SetCurrDataCounter(DMA1_Channel5, USART1_DMA_BUF_MAX_LEN);DMA_Cmd(DMA1_Channel5,ENABLE);DMA_ClearFlag(DMA1_IT_TC5|DMA1_IT_HT5); DMA_ITConfig(DMA1_Channel5,DMA_IT_HT|DMA_IT_TC|DMA_IT_TE,ENABLE); //使能传送一半及全部传送完毕中断nvic_initstruct.NVIC_IRQChannel DMA1_Channel5_IRQn;nvic_initstruct.NVIC_IRQChannelCmd ENABLE;nvic_initstruct.NVIC_IRQChannelPreemptionPriority 0;nvic_initstruct.NVIC_IRQChannelSubPriority 2;NVIC_Init(nvic_initstruct); return 0;
}/*** brief 清除DMAx通道* param * retval 0:成功; 1:失败
*/uint32_t uart_dev_clear_dma_channel(DMA_Channel_TypeDef* DMAy_Channelx)
{DMA_Cmd(DMAy_Channelx,DISABLE);DMA_SetCurrDataCounter(DMAy_Channelx,USART1_DMA_BUF_MAX_LEN);DMA_Cmd(DMAy_Channelx,ENABLE);return 0;
}/*
************************************************************
* 函数名称 USART1_IRQHandler
*
* 函数功能 串口1收发中断
*
* 入口参数 无
*
* 返回参数 无
*
* 说明
************************************************************
*/
void USART1_IRQHandler(void)
{uint8_t temp 0;uint16_t recv_size 0;if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) //接收中断{temp USART1-DR;USART_ClearFlag(USART1, USART_FLAG_RXNE);}else if(USART_GetITStatus(USART1, USART_IT_IDLE) ! RESET) //空闲中断{temp USART1-SR; //清除中断标志temp USART1-DR;USART_ClearFlag(USART1, USART_IT_IDLE);recv_size USART1_DMA_BUF_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5) - uart_dev_uart1.dma_rx_buff_curr_addr; //重置DMAuart_dev_clear_dma_channel(DMA1_Channel5);fifo_write(uart_dev_uart1.dma_rx_fifo, uart_dev_uart1.dma_rx_buff[uart_dev_uart1.dma_rx_buff_curr_addr], recv_size);uart_dev_uart1.dma_rx_buff_curr_addr 0; }}/*
************************************************************
* 函数名称 DMA1_Channel5_IRQHandler串口接收中断
*
* 函数功能 DMA1_Channel5中断
*
* 入口参数 无
*
* 返回参数 无
*
* 说明
************************************************************
*/
void DMA1_Channel5_IRQHandler(void)
{uint16_t recv_size 0;if(DMA_GetITStatus(DMA1_IT_HT5) ! RESET) //DMA传送一半中断{DMA_ClearITPendingBit(DMA1_IT_HT5); // 清除传输传送一半中断中断标志位 //搬移前半个buf数据recv_size USART1_DMA_BUF_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); fifo_write(uart_dev_uart1.dma_rx_fifo, uart_dev_uart1.dma_rx_buff, recv_size); //更新当前地址uart_dev_uart1.dma_rx_buff_curr_addr recv_size;}else if(DMA_GetITStatus(DMA1_IT_TC5) ! RESET) //DMA传送完成中断{DMA_ClearITPendingBit(DMA1_IT_TC5); // 清除传输完成中断标志位 recv_size USART1_DMA_BUF_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5) - uart_dev_uart1.dma_rx_buff_curr_addr; //重置DMAuart_dev_clear_dma_channel(DMA1_Channel5);fifo_write(uart_dev_uart1.dma_rx_fifo, uart_dev_uart1.dma_rx_buff[uart_dev_uart1.dma_rx_buff_curr_addr], recv_size);uart_dev_uart1.dma_rx_buff_curr_addr 0;}else if(DMA_GetITStatus(DMA1_IT_TE5) ! RESET){UsartPrintf(USART2, DMA ERROR \r\n);}}
三、串口DMA发送相关思路 如果理解了发送原理接收原理相对来说便简单的多将需要发送的数据填入发送buff通知DMA将数据进行发送便可以实现。这里需要注意的是因为串口接收和发送DMA同属于一个DMA的不同通道两个是没法同时进行的如果是收发双工同时使用这一个DMA可能会地效率有所影响。 需要注意的是因为发送是非阻塞进行的一定要获取该次发送完成后再进行下一次发送当然若发送大量数据可以仿照接收也使用类似与双buffer的方式进行发送。
/*** brief 初始化串口DMA 并发送数据发送串口* param * retval
*/
void uart_dev_DMA_tx_data(uart_dev* uart_dev )
{NVIC_InitTypeDef nvic_initstruct;DMA_InitTypeDef DMA_USART1_InitStructure;//使能DMA1 时钟RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1,ENABLE);DMA_DeInit(DMA1_Channel4);DMA_USART1_InitStructure.DMA_PeripheralBaseAddr (uint32_t) USART1-DR ; //USART1数据接收寄存器DMA_USART1_InitStructure.DMA_MemoryBaseAddr (uint32_t) uart_dev -dma_tx_buff; //发送寄存器区间地址DMA_USART1_InitStructure.DMA_DIR DMA_DIR_PeripheralDST ; //往外设发送 内村 —— 外设DMA_USART1_InitStructure.DMA_BufferSize USART1_DMA_BUF_MAX_LEN ; //传送长度DMA_USART1_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable ; //外设地址不增DMA_USART1_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable ; //内存地址自增DMA_USART1_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; //传送数据为8位DMA_USART1_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte ; //MemoryDataSizeDMA_USART1_InitStructure.DMA_Mode DMA_Mode_Normal ; //普通模式 单次DMA_USART1_InitStructure.DMA_Priority DMA_Priority_High ; //传送优先级高DMA_USART1_InitStructure.DMA_M2M DMA_M2M_Disable ; //从外设触发DMA_Init(DMA1_Channel4,DMA_USART1_InitStructure);//开启DMADMA_Cmd(DMA1_Channel4,DISABLE);DMA_SetCurrDataCounter(DMA1_Channel4, USART1_DMA_BUF_MAX_LEN);DMA_Cmd(DMA1_Channel4,ENABLE);DMA_ClearFlag(DMA1_IT_TC4); DMA_ITConfig(DMA1_Channel4,DMA_IT_TC|DMA_IT_TE,ENABLE); //使能传送一半及全部传送完毕中断nvic_initstruct.NVIC_IRQChannel DMA1_Channel4_IRQn;nvic_initstruct.NVIC_IRQChannelCmd ENABLE;nvic_initstruct.NVIC_IRQChannelPreemptionPriority 0;nvic_initstruct.NVIC_IRQChannelSubPriority 3;NVIC_Init(nvic_initstruct); }/*
************************************************************
* 函数名称 DMA1_Channel4_IRQHandler
*
* 函数功能 DMA1_Channel4中断
*
* 入口参数 无
*
* 返回参数 无
*
* 说明
************************************************************
*/void DMA1_Channel4_IRQHandler(void)
{uint16_t recv_size 0;if(DMA_GetITStatus(DMA1_IT_TC4) ! RESET) //DMA传送完成{uart_dev_uart1.dma_tx_idle_state 1;DMA_ClearITPendingBit(DMA1_IT_TC4); // 清除传输完成 }else if(DMA_GetITStatus(DMA1_IT_TE4) ! RESET){UsartPrintf(USART2, DMA uart tx ERROR \r\n);}}