完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
前面讲了Uart三种不同的方式接收数据,请参照《STM32 Uart及其配置》《STM32 Uart中断接收》《STM32 Uart DMA方式接收数据》,但是,它们都需要指定数据的长度,但实际应用中,会出现不定长度的数据,比如,某些模块的@命令,那么,如何接收不定长度的数据呢?今天,我们就来扒一扒STM32 Uart 变长数据的接收。
问题来了,变长数据包,我们如何确定数据包的长度? 带着问题思考,我们可以得出以下几种思路: 1. 制定严格的通信协议,带有一串特殊字符的数据包头、长度、校验、包尾等,比如,一开始收到:0xAA-0xBB-0xCC-0xDD-0xEE,表示数据包头,接下来两个字节是数据包长度,接下来是数据包内容,接下来是校验值,接下来是结尾。 2. 使用定时器,判定在指定时间内没有收到数据,就算一个数据包结束。可以在收到一个数据后,开启定时器,定时器的超时时间设定为1~2个数据之间的时间,若在此期间有数据,则判定为数据包未结束,重载定时器,若此期间无数据,则判定为数据包结束,关闭定时器。 这篇章里不讨论1和2,因为STM32有更方便的处理方式,STM32能够检测空闲。 看RM0033,空闲,可以理解为,下一个起始位之前的全部1,也就是Rx线上高电平。 会不会和数据0xFF冲突呢?不会,因为数据0xFF有起始位,空闲没有。 看下来这个手画的图,中间那一段高电平,就是空闲。 当空闲被检测到时,如果IDLEIE位设置,就会产生一个IDLE中断。 我们捕获这个中断,并处理它就行了。 在空闲中断产生之前,我们收到的数据,怎么处理呢?先收着,存进一个Buffer里面! 如前面讲的《STM32 Uart中断接收》《STM32 Uart DMA方式接收数据》,接收数据,也可以用中断和DMA两种不同的方式。我们把两种方式都写进代码里面,用两个不同的宏定义区分开来。 我们就写一个,收到数据,往串口发送出收到的数据长度,以及完整的数据吧。 理论讲完,就开始实践吧! 参照《STM32 Uart DMA方式接收数据》,建立工程,生成代码。 在 usart.c 里面写这样一段代码,#define RCV_RXNEIDLE_PROCESS 表示用RX中断方式接收数据,#define RCV_DMAIDLE_PROCESS 表示用DMA方式接收数据。这段代码主要用于选择接收数据的方式。同一时间只能使用一种方式,#error这里做了个限制,不允许同时打开这两个宏定义,否则会编译出错。 // select oneof two method //#define RCV_RXNEIDLE_PROCESS #define RCV_DMAIDLE_PROCESS #if defined(RCV_DMAIDLE_PROCESS)&&defined(RCV_RXNEIDLE_PROCESS) #error "Don't Allow #define Two Micro at the same time, Check RCV_RXNEIDLE_PROCESS && RCV_DMAIDLE_PROCESS" #endif 接收数据,就定义一个数组来存放数据,就申请1024个字节的数组吧; 数据要变长,就定义一个变量来表示接收到的数据长度; 最后,再定义一个指针,用来指向存放数据的数组。 在 usart.c 里面,这一断代码定义了所需要的数据。 uint8_t uart4Rx[UART4_BUF_MAX]; // 存放接收到的数据 uint8_t *pUart4Rx; // 指向存认数据的数组 uint16_t uart4RxLength; // 接收到数据的长度 整个事件的流程就是,接收一段数据后,产生IDLE中断,IDLE中断服务例程里处理数据。 那我们就看一下stm32f2xx_it.c里面UART4_IRQHandler()中断服务例程里面,对Idle中断是怎么处理的? 看一下 HAL_UART_IRQHandler() 这个函数,它对Idle中断没有处理!那怎么办呢?自己写一段咯。 在HAL_UART_IRQHandler() 函数加上这一段。 /* UART in mode Idle -------------------------------------------------*/ if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET)) { HAL_UART_IdleCpltCallback(huart); return; } 这个回调函数HAL_UART_IdleCpltCallback(),仿照着在stm32f2xx_hal_uart.c里面加一个回调函数。 后面,我们在 usart.c 里面重写它。 /** * @brief Idle callbacks. * @param huart pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @retval None */ __weak void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) { /* Prevent unused argument(s) compilation warning */ UNUSED(huart); /* NOTE: This function Should not be modified, when the callback is needed, the HAL_UART_TxCpltCallback could be implemented in the user file */ } 整个HAL_UART_IRQHandler()函数,是这样的。 void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { uint32_t isrflags = READ_REG(huart->Instance->SR); uint32_t cr1its = READ_REG(huart->Instance->CR1); uint32_t cr3its = READ_REG(huart->Instance->CR3); uint32_t errorflags = 0x00U; uint32_t dmarequest = 0x00U; /* If no error occurs */ errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE)); if(errorflags == RESET) { /* UART in mode Receiver -------------------------------------------------*/ if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { UART_Receive_IT(huart); return; } } /* If some errors occur */ if((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET))) { /* UART parity error interrupt occurred ----------------------------------*/ if(((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_PE; } /* UART noise error interrupt occurred -----------------------------------*/ if(((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_NE; } /* UART frame error interrupt occurred -----------------------------------*/ if(((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_FE; } /* UART Over-Run interrupt occurred --------------------------------------*/ if(((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_ORE; } /* Call UART Error Call back function if need be --------------------------*/ if(huart->ErrorCode != HAL_UART_ERROR_NONE) { /* UART in mode Receiver -----------------------------------------------*/ if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { UART_Receive_IT(huart); } /* If Overrun error occurs, or if any error occurs in DMA mode reception, consider error as blocking */ dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR); if(((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest) { /* Blocking error : transfer is aborted Set the UART state ready to be able to start again the process, Disable Rx Interrupts, and disable Rx DMA request, if ongoing */ UART_EndRxTransfer(huart); /* Disable the UART DMA Rx request if enabled */ if(HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR)) { CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); /* Abort the UART DMA Rx channel */ if(huart->hdmarx != NULL) { /* Set the UART DMA Abort callback : will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */ huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError; if(HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK) { /* Call Directly XferAbortCallback function in case of error */ huart->hdmarx->XferAbortCallback(huart->hdmarx); } } else { /* Call user error callback */ HAL_UART_ErrorCallback(huart); } } else { /* Call user error callback */ HAL_UART_ErrorCallback(huart); } } else { /* Non Blocking error : transfer could go on. Error is notified to user through user error callback */ HAL_UART_ErrorCallback(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; } } return; } /* End if some error occurs */ // 这里,就是对Idle中断的处理啊啊啊啊啊!!! /* UART in mode Idle -------------------------------------------------*/ if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET)) { HAL_UART_IdleCpltCallback(huart); return; } /* UART in mode Transmitter ------------------------------------------------*/ if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { UART_Transmit_IT(huart); return; } /* UART in mode Transmitter end --------------------------------------------*/ if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET)) { UART_EndTransmit_IT(huart); return; } } 如《STM32 Uart中断接收》所写,要响应RX中断接收,必须使能RX中断; 如《STM32 Uart DMA方式接收数据》所写,要响应DMA接收,必须使能DMA; 要响应IDLE中断,必须使能 IDLE 中断。 看这个USR_UartInit()函数: void USR_UartInit(void) { // 这里是RX中断处理 #ifdef RCV_RXNEIDLE_PROCESS pUart4Rx = uart4Rx; // 指针指向数组 uart4RxLength = 0; // 长度初始为0 HAL_UART_Receive_IT(&huart4, pUart4Rx, 1); // 使能Rx中断,并设置长度为1,数据置于uart4Rx中,准备接收第一个数据 #endi // 这里是DMA中断处理; #ifdef RCV_DMAIDLE_PROCESS uart4RxLength = 0; // 长度初始为0 HAL_UART_Receive_DMA(&huart4, uart4Rx, UART4_BUF_MAX); // 使能DMA,最长为1024字节,数据置于uart4Rx中,准备接收一串数据 #endif __HAL_UART_ENABLE_IT(&huart4, UART_IT_IDLE); // 使能 IDLE中断 } 这个函数在main()里面,初始化串口和DMA之后调用。 MX_DMA_Init(); MX_UART4_Init(); /* USER CODE BEGIN 2 */ USR_UartInit(); /* USER CODE END 2 */ 在《STM32 Uart中断接收》里面讲了,响应RX中断,最终会到HAL_UART_RxCpltCallback里面处理; 在《STM32 Uart DMA方式接收数据》里面讲了,响应DMA中断,最终也会到HAL_UART_RxCpltCallback里面处理。 RX中断接收:来一次中断,指针要后移,指向下一个数据,上一个数据已经接收,数据长度自加,然后再次使能Rx中断,准备接收下一个数据。 DMA接收:再次使能DMA,准备接收下一串数据。 DMA产生中断的条件,1.接收满UART_BUF_MAX,2.设置DMA Disable。 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { #ifdef RCV_RXNEIDLE_PROCESS pUart4Rx++; // 指针指向下一个数据,上一个数据接收了,才会产生这次中断 uart4RxLength++; // 接收到上一个数据,长度要+1 HAL_UART_Receive_IT(&huart4, pUart4Rx, 1); // 继续使能RX中断,准备接收。 #endif #ifdef RCV_DMAIDLE_PROCESS HAL_UART_Receive_DMA(&huart4, uart4Rx, UART4_BUF_MAX); // DMA产生Complete中断条件:1.接收满UART_BUF_MAX,2.设置DMA Disable。再次使能DMA #endif } 接收完一串数据,接下来,轮到 Idle 中断出手了。 Idle中断处理,会调用回调函数HAL_UART_IdleCpltCallback(),我们就实现它。 uint8_t RxLenHi, RxLenlo; // 长度高位,低位 void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) { __HAL_UART_CLEAR_IDLEFLAG(huart); // RX #ifdef RCV_RXNEIDLE_PROCESS RxLenHi = (uint8_t)(uart4RxLength>>8); RxLenlo = (uint8_t)(uart4RxLength); HAL_UART_Transmit(huart, &RxLenHi, 1, 1000); HAL_UART_Transmit(huart, &RxLenlo, 1, 1000); HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000); pUart4Rx = uart4Rx; // 接收完一条命令,指针指向数组头,重新接收 uart4RxLength = 0; // 长度设置为0 #endif // DMA #ifdef RCV_DMAIDLE_PROCESS uart4RxLength = UART4_BUF_MAX-__HAL_DMA_GET_COUNTER(&hdma_uart4_rx); // 获取长度 RxLenHi = (uint8_t)(uart4RxLength>>8); RxLenlo = (uint8_t)(uart4RxLength); HAL_UART_Transmit(huart, &RxLenHi, 1, 1000); HAL_UART_Transmit(huart, &RxLenlo, 1, 1000); HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000); __HAL_DMA_DISABLE(&hdma_uart4_rx); // DMA计数重载,会产生DMA完成中断, // 参考RM0033-9.3.13,当DMA关闭时,会产生DMA完成中断 // 参考RM0033-DMA_SxNDTR,当DMA关闭时,重载计数器 #endif } 现在,捋一下整个过程: RX变长数据接收: 1. 初始化时使能RX中断,时刻准备着接收数据。 2. 第一个数据接收,触发RX中断,执行HAL_UART_RxCpltCallback(),函数里,接收的数据指向数组下一个存储空间,接收的长度+1,使能RX中断,准备下一数据接收。 下一个数据接收,触发RX中断,执行HAL_UART_RxCpltCallback(),函数里,接收的数据指向数组下一个存储空间,接收的长度+1,使能RX中断,准备下一数据接收。 …… …… 3. 整条命令接收完成,触发空闲中断,执行行HAL_UART_IdleCpltCallback(),函数里,处理接收到的数据,指针指向uart4Rx[]的第一个数据。 DMA变长数据接收: 1. 初始化时使能DMA中断,时刻准备着接收数据。 2. 有数据,触发DMA事件,有数据,触发DMA事件,有数据,触发DMA事件... ... ...这个过程是全自动的,不需要我们参与,DMA自动把数据接收了,存进uart4Rx[]里。 3. 整条命令接收完成,触发空闲中断,执行行HAL_UART_IdleCpltCallback(),函数里,处理接收到的数据, __HAL_DMA_DISABLE()关闭DMA,关闭DMA会引起DMA计数重载,且触发DMA Complete 中断,中断服务例程会执行HAL_UART_IdleCpltCallback(),函数里,使能DMA,准备接收下一条命令。 重新编译,烧录,运行便可; 返回的第1个字节,长度高位 0x00,返回的第2个字节,长度低位 0x0B,0x000B = 11,我们发的,正好11字节; 后面紧跟着 11 22 33 44 55 66 77 88 ff ff 99,就是发送区发的数据。 STM32收到的数据,是这样处理的。 HAL_UART_Transmit(huart, &RxLenHi, 1, 1000); // 发送长度高位 HAL_UART_Transmit(huart, &RxLenlo, 1, 1000); // 发送长度低位 HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000); // 发送接收到的数据 等等!这样有没有感觉超麻烦?有没有一种简单的方法,像C语言的printf,直接在串口调试助手输出字符串呢? |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1771 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1619 浏览 1 评论
1070 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
724 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1673 浏览 2 评论
1935浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
728浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
568浏览 3评论
593浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
551浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 14:03 , Processed in 1.088708 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号