完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
阅读本文需要有一定的STM32基础。并对STM32 DMA和串口有一定的了解。
串口空闲中断是什么?看官方的解释(大致的意思就是RX总线在一定时间内没有活干了,就认为空闲了): ## 问题分析 我们都知道,在用STM32串口的时候,使用DMA传输和串口空闲中断很香。 原理大家都懂,就是发生串口空闲中断的时候,就表示数据传输完成。然后就执行数据处理或者使用就好了。 这样的想象说理想也可以,但是没有考虑完数据发送的时间差问题,也就是当接收方初始化完DMA后,发送方发送数据了没有。做个假设:
在做SBUS接收(25个8位数据)的时候,我参考别人的DMA+串口空闲中断的代码,都逃不掉数据错位的现象。偶尔会正确,但是还是有错位的现象,一旦错位,就会一直错位下去!!!! 本文将教你如何设置DMA和串口,并解决错位的现象。 ## DMA简介 预留。 ## 串口初始化代码 注:我使用UART2,使用UART1的朋友请修改UART的RCC初始化函数。 这里包括GPIO、串口初始化,注意,这里打开了串口空闲中断,打开了串口DMA接收。这里2步是很重要的。 USART_DMACmd(RC_RX_UART_Port,USART_DMAReq_Rx,ENABLE);//ENABLE DMA receive USART_ITConfig(RC_RX_UART_Port, USART_IT_IDLE, ENABLE);//ENABLE IDLE interrupt //GPIO init config RCC_APB2PeriphClockCmd(RC_RXIO_RCC,ENABLE); // GPIO Clock RCC_APB1PeriphClockCmd(RC_RX_UART_RCC, ENABLE); // UART Clock GPIO_InitStructure.GPIO_Pin = RC_RX_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(RC_RXIO_Port, &GPIO_InitStructure); //USART init config USART_InitStructure.USART_BaudRate = RC_BAUD; USART_InitStructure.USART_WordLength = USART_WordLength_9b;//8b Not identified by betaflight USART_InitStructure.USART_StopBits = USART_StopBits_2; USART_InitStructure.USART_Parity = USART_Parity_Even; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx ; USART_ITConfig(RC_RX_UART_Port, USART_IT_IDLE, ENABLE);//ENABLE IDLE interrupt USART_Init(RC_RX_UART_Port, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = RC_RX_UART_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_DMACmd(RC_RX_UART_Port,USART_DMAReq_Rx,ENABLE);//ENABLE DMA receive USART_Cmd(RC_RX_UART_Port, ENABLE); ## DMA初始化代码 这里包括DMA时钟初始化,DMA通道配置,DMA通道传输开启。 DMA_DIR(DMA传输方向)配置为DMA_DIR_PeripheralSRC,串口----》-----内存。 DMA_Mode(DMA传输模式)配置为DMA_Mode_Circular,循环模式,必须是这个。 DMA_BufferSize(目标接收长度)配置为SbusLength,这里是25。 RCC_AHBPeriphClockCmd(RC_DMA_Rx_RCC,ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&RC_RX_UART_Port->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (u32) SbusDataRx; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = SbusLength; 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_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(RC_DMA_Rx_Ch,&DMA_InitStructure); DMA_ClearFlag(RC_Dma_RxFlagTC); DMA_Cmd(RC_DMA_Rx_Ch,ENABLE); ## 串口空闲中断处理代码 这部分就是处理数据,解决接收错位的代码。写得很简单,我重点分析一下。 1. 关闭DMA传输通道。 2. 传输发生串口空闲中断后,首先需要清除SR和DR寄存器,因为他们还有数据的话,下一次启动DMA传输,他们会被DMA直接搬走。所以在发生DMA空闲中断后,要清除这2个寄存器,直接读取就可以清除。参考本文首图。 3. 清除DMA传输完成标志位。 4. 获取DMA接收剩余长度。这个是关键的第一步,这个剩余长度是和我们设定的DMA接收长度对比的。也就是用DMA_BufferSize值做自减,每接收到一个数据,就减1,当减到0时,接收完成。会自动设置为DMA_BufferSize值。 相关公式是:已接收长度=目标接收长度(DMA_BufferSize)- 剩余接收长度 5. 有效帧判断。通过获取的剩余长度和数据帧的特点进行判断接收到的数据的有效性。这里说的数据帧特点,也就是看数据帧是否含了帧头和CRC,如果有帧头和CRC,可以用做有效帧判断。我这里用的是帧头和帧尾。 6. 在检测到无效帧的时候需要把 剩余接收长度 设置为目标长度。使用这个函数 DMA_SetCurrDataCounter(RC_DMA_Rx_Ch,SbusLength); 这样一来,发生错位的时候(无效帧),就可以告诉DMA:你接收错误了,重新开始接收。 7. 清除接收缓冲区。我这里只要清除帧头和帧尾,避免他们影响下一次的判断。也不必要将所有缓冲区都清除(省CPU时间)。 8. 如果接收到的是有效帧,那就执行你的数据处理就好了。处理完毕,记得做清除接收缓冲区。也可以像我一样单纯的清除数据帧特点位。 9. 打开DMA传输通道。 经过这几部的处理,当检测到错位后,我们重新设置 剩余接收长度。这一来,DMA会重新开始接收数据,在发送方重新发送数据的时候接收方的DMA会从第0位开始转存。然后就可以达到解决接收错位的问题。 错误示例: ErrorRX:错误的帧数量 OkRX:正确的帧数量 IDLEIRQ:帧数量 仿真的时候就可以看到效果了,ErrorRX的值正常应该是0-1。 u16 DMA_Residual_length=0; uint32_t ErrorRX=0; uint32_t OkRX=0; uint32_t IDLEIRQ=0; void USART2_IRQHandler(void) { u8 RxTemp; if(USART_GetITStatus(RC_RX_UART_Port,USART_IT_IDLE)!=RESET) { IDLEIRQ++; DMA_Cmd(RC_DMA_Rx_Ch,DISABLE); // off DMA1_Channel6 RxTemp=RC_RX_UART_Port->SR; // USART2->SR RxTemp=RC_RX_UART_Port->DR; // Clear USART2->DR DMA_ClearFlag(RC_Dma_RxFlagTC); // Clear DMA1_FLAG_TC6 DMA_Residual_length=DMA_GetCurrDataCounter(RC_DMA_Rx_Ch);//Get the residual length if((DMA_Residual_length==SbusLength) && (SbusDataRx[0] == 0x0f && SbusDataRx[24]==0x00)) { OkRX++; UnSbusPack(ChannelRx);// Valid frame,The received ***us data is parsed and stored in ChannelRx } else { ErrorRX++; DMA_SetCurrDataCounter(RC_DMA_Rx_Ch,SbusLength); SbusDataRx[0]=0; SbusDataRx[24]=0; } DMA_Cmd(RC_DMA_Rx_Ch,ENABLE); } } ## 涉及的宏定义 #define RC_RX_UART_Port USART2 #define RC_RX_UART_IRQn USART2_IRQn #define RC_RX_UART_RCC RCC_APB1Periph_USART2 #define RC_RXIO_Port GPIOA #define RC_RXIO_RCC RCC_APB2Periph_GPIOA #define RC_RX_PIN GPIO_Pin_3 #define RC_DMA_Rx #define RC_DMA_Rx_RCC RCC_AHBPeriph_DMA1 #define RC_DMA_Rx_Ch DMA1_Channel6 #define RC_Dma_RxFlagTC DMA1_FLAG_TC6 #define RC_BAUD 100000 #define SbusLength 25 // Sbus Length TX and RX u8 SbusDataRx[SbusLength]; u16 ChannelRx[16]; void UnSbusPack(u16 *Array); 后记 如有错漏请通知修改和补充。上面的代码是可以实现的。如果需要代码工程,请看鄙人上传的资源。 |
|||
|
|||
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1780 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1621 浏览 1 评论
1081 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
728 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1679 浏览 2 评论
1938浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
731浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
570浏览 3评论
596浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
556浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-24 02:59 , Processed in 0.788495 second(s), Total 45, Slave 39 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号