Skip to content
View wdxzf's full-sized avatar
Block or Report

Block or report wdxzf

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Please don't include any personal information such as legal names or email addresses. Maximum 100 characters, markdown supported. This note will be visible to only you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse
wdxzf/README.md

🙋 Hello

🤺 About Me

  大家好,我是小小白同学。

  目前就读于大学地理信息科学专业。

  热爱计算机科学和IT互联网事业,励志成为一名资深程序员!

  我们正在让这个世界变得更加美好,通过代码的重复使用和延展构建完美体系。

  We're making the world a better place. Through constructing elegant hierarchies for maximum code reuse and extensibility.

  长风破浪会有时,直挂云帆济沧海。我开始得太晚了,但总不算太迟。春华秋实,努力一定会有收获,一枚学渣正在悄悄蜕变...

readme
  • 👴 我叫 𝐉𝐮𝐢ç𝐞, 或者2meow
  • 💼 坐标成都, 双流一大学本科牲
  • 🚀 爱好摄影 | 倒装句患者 | 反射弧略长
  • 🎯 正在学习 Swift,快乐码原 是我的博客
  • 💁 欢迎加入 web前端养老院 摸鱼
  • ✨ 如果想找我🤺, 那↘ 就↗ 来↗ 吧↘

🔥 My Tooooys

🤖 Reach me

Visitor's count 👀

sunbin :: Visitor's Count

        串口作为单片机开发的一个常用的外设,应用范围非常广。大部分时候,串口需要接收处理的数据长度是不定的。那么怎么才能判断一帧数据是否结束呢,今天就以STM32单片机为例,介绍几种接收不定长数据的方法。

        首先,我们需要打开一个串口,使用STM32CubeMx来配置,如下:

        然后打开串口中断、添加发送和接收的DMA,DMA参数设置为默认即可,如下图。(DMA可根据自身需求选择是否打开)

         配置一下时钟等,点击生成代码,这样就可以使用串口了。首先我们定义一个串口接收的结构体,并定义一个结构体变量,如下:

  1. #define RX_MAXLEN 200 //最大接收数据长度
  2. typedef struct{
  3. uint8_t RxBuf[RX_MAXLEN];//接收缓存
  4. uint16_t RxCnt; //接收数据计数
  5. uint16_t RxLen; //接收数据长度
  6. uint8_t RxStart; //开始接收标志
  7. uint8_t RxFlag; //一帧数据接收完成标志
  8. }Uart_Tpye_t;
  9. Uart_Tpye_t Uart1;

        下面介绍几种接收数据的方法:

1.空闲中断

        空闲中断可以配合接收中断或DMA来使用。

        当使用DMA+空闲中断时,需要在初始化完成后手动打开空闲中断和DMA接收。

  1. __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//打开串口空闲中断
  2. HAL_UART_Receive_DMA(&huart1, Uart1.RxBuf, RX_MAXLEN); //串口DMA接收数据

        编写空闲中断函数,如下:

  1. //串口空闲中断
  2. void UART_IDLECallBack(UART_HandleTypeDef *huart)
  3. {
  4. uint32_t temp;
  5. /*uart1 idle processing function*/
  6. if(huart == &huart1)
  7. {
  8. if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
  9. {
  10. __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
  11. /*your own code*/
  12. HAL_UART_DMAStop(&huart1);//停止DMA
  13. Uart1.RxLen = RX_MAXLEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中传输的数据个数
  14. Uart1.RxFlag = 1;
  15. HAL_UART_Receive_DMA(&huart1,Uart1.RxBuf,RX_MAXLEN); //开启下次接收
  16. }
  17. }
  18. }

        在主程序中判断接收完成标志,并处理数据:

  1. if(Uart1.RxFlag == 1)//接收完一帧数据
  2. {
  3. printf("Rev %d Bytes\r\n",Uart1.RxLen);
  4. Uart1.RxFlag = 0;
  5. }

        最后,别忘了在串口中断函数中调用自己编写的空闲中断函数。

        运行程序测试,结果如下:

        使用接收中断+空闲中断与DMA类似,只不过需要打开接收中断:

  1. __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//打开串口空闲中断
  2. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据

        编写接收中断回调函数,每次接收一个字节:

  1. uint8_t RevByte;
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  3. {
  4. if(huart->Instance==USART1)
  5. {
  6. Uart1.RxBuf[Uart1.RxCnt]=RevByte;
  7. Uart1.RxCnt++;
  8. if(Uart1.RxCnt==RX_MAXLEN)
  9. {
  10. Uart1.RxCnt = RX_MAXLEN-1;
  11. }
  12. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据
  13. }
  14. }

        编写空闲中断回调函数,与DMA的方式类似,只是数据长度判断方式不一样:

  1. //串口空闲中断
  2. void UART_IDLECallBack(UART_HandleTypeDef *huart)
  3. {
  4. uint32_t temp;
  5. /*uart1 idle processing function*/
  6. if(huart == &huart1)
  7. {
  8. if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
  9. {
  10. __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
  11. Uart1.RxFlag = 1;
  12. Uart1.RxLen = Uart1.RxCnt;
  13. Uart1.RxCnt = 0;
  14. }
  15. }
  16. }

        同样,在主程序中判断一帧数据的接收完成并处理。

2.特点协议判断帧头帧尾及长度

        有时候我们需要自己定义协议传输数据,这时候就可以在通讯协议里添加特点的帧头帧尾以及数据长度字节,通过判断这些字节来判断数据的开始和结束。假设定义一个简单的传输协议如下:

帧头

数据长度,1字节

数据,N字节

0x5A,0xA5

数据部分的字节数

有效数据

        可以使用中断方式接收数据:

HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据

        接收中断函数如下:

  1. //串口接收中断回调函数
  2. uint8_t RevByte;
  3. uint16_t RevTick = 0;
  4. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  5. {
  6. static uint16_t Rx_len;
  7. if(huart->Instance==USART1)
  8. {
  9. Uart1.RxBuf[Uart1.RxCnt]=RevByte;
  10. switch(Uart1.RxCnt)
  11. {
  12. case 0:
  13. if(Uart1.RxBuf[Uart1.RxCnt] == 0x5A)//帧头1正确
  14. Uart1.RxCnt++;
  15. else
  16. Uart1.RxCnt = 0;
  17. break;
  18. case 1:
  19. if(Uart1.RxBuf[Uart1.RxCnt] == 0xA5)//帧头2正确
  20. Uart1.RxCnt++;
  21. else
  22. Uart1.RxCnt = 0;
  23. break;
  24. case 2:
  25. Rx_len = Uart1.RxBuf[Uart1.RxCnt];
  26. Uart1.RxCnt++;
  27. break;
  28. default:
  29. Uart1.RxCnt++;
  30. if((Rx_len+3) == Uart1.RxCnt)//数据接收完成
  31. {
  32. Uart1.RxFlag = 1;
  33. Uart1.RxLen = Uart1.RxCnt;
  34. Uart1.RxCnt = 0;
  35. }
  36. break;
  37. }
  38. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据
  39. }
  40. }

        同样,在主程序中判断一帧数据的接收完成并处理,运行测试结果如下:

3.超时判断

        超时判断其实与空闲中断的原理类似,只不过是通过定时器来取代空闲中断来判断一帧数据的结束,一般采样接收中断+超时判断的方式。之前的文章Freemodbus移植就是采样这种方式。

        超时判断的时间跟波特率有关,假设串口起始位和结束位各1位,那么接收一个字节就需要8+2=10位,在9600波特率下,一秒钟就能接收9600/10=960字节。也就是一个字节需要1.04ms,那么超时时间最小可以设置为1.5倍的单字节接收时间,或者更长。

        超时判断可以使用硬件定时器或软件定时器来实现。硬件定时器的方式可以参考之前的Freemodbus移植部分的程序。软件定时器定义一个计时变量,该变量在systick中断中+1实现计时,可以节省硬件资源,但计时最小分辨率跟systick中断有关。

        编写中断接收函数:

  1. //串口接收中断回调函数
  2. uint8_t RevByte;
  3. uint16_t RevTick = 0;
  4. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  5. {
  6. if(huart->Instance==USART1)
  7. {
  8. Uart1.RxBuf[Uart1.RxCnt]=RevByte;
  9. Uart1.RxCnt++;
  10. Uart1.RxStart = 1;//开始接收标志
  11. RevTick = 0;//计时清零
  12. if(Uart1.RxCnt==RX_MAXLEN)
  13. {
  14. Uart1.RxCnt = RX_MAXLEN-1;
  15. }
  16. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据
  17. }
  18. }

        编写超时判断函数,在Systick中断中调用:

  1. //串口接收超时判断,该函数在Systick中断(1ms中断一次)中调用
  2. void UartTimeOut()
  3. {
  4. if(Uart1.RxStart == 1)
  5. {
  6. RevTick++;
  7. if(RevTick > 2)
  8. {
  9. Uart1.RxLen = Uart1.RxCnt;
  10. Uart1.RxCnt = 0;
  11. Uart1.RxStart = 0;
  12. Uart1.RxFlag = 1;
  13. }
  14. }
  15. }

        使用时只要打开接收中断即可,不再需要空闲中断。

HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据

        同样,在主程序中判断一帧数据的接收完成并处理。测试结果就不贴了。

4.总结

        上面几种方式都可以实现串口接收不定长数据,各有优缺点,可根据实际需求选择用哪种。需要注意的是,上面的例程只是简单地接收数据,实际应用中,还需要考虑连续接收多帧数据的情况,是缓存之后处理,还是舍弃后面的数据,都需要自己写程序实现。

        串口作为单片机开发的一个常用的外设,应用范围非常广。大部分时候,串口需要接收处理的数据长度是不定的。那么怎么才能判断一帧数据是否结束呢,今天就以STM32单片机为例,介绍几种接收不定长数据的方法。

        首先,我们需要打开一个串口,使用STM32CubeMx来配置,如下:

        然后打开串口中断、添加发送和接收的DMA,DMA参数设置为默认即可,如下图。(DMA可根据自身需求选择是否打开)

         配置一下时钟等,点击生成代码,这样就可以使用串口了。首先我们定义一个串口接收的结构体,并定义一个结构体变量,如下:

  1. #define RX_MAXLEN 200 //最大接收数据长度
  2. typedef struct{
  3. uint8_t RxBuf[RX_MAXLEN];//接收缓存
  4. uint16_t RxCnt; //接收数据计数
  5. uint16_t RxLen; //接收数据长度
  6. uint8_t RxStart; //开始接收标志
  7. uint8_t RxFlag; //一帧数据接收完成标志
  8. }Uart_Tpye_t;
  9. Uart_Tpye_t Uart1;

        下面介绍几种接收数据的方法:

1.空闲中断

        空闲中断可以配合接收中断或DMA来使用。

        当使用DMA+空闲中断时,需要在初始化完成后手动打开空闲中断和DMA接收。

  1. __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//打开串口空闲中断
  2. HAL_UART_Receive_DMA(&huart1, Uart1.RxBuf, RX_MAXLEN); //串口DMA接收数据

        编写空闲中断函数,如下:

  1. //串口空闲中断
  2. void UART_IDLECallBack(UART_HandleTypeDef *huart)
  3. {
  4. uint32_t temp;
  5. /*uart1 idle processing function*/
  6. if(huart == &huart1)
  7. {
  8. if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
  9. {
  10. __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
  11. /*your own code*/
  12. HAL_UART_DMAStop(&huart1);//停止DMA
  13. Uart1.RxLen = RX_MAXLEN - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中传输的数据个数
  14. Uart1.RxFlag = 1;
  15. HAL_UART_Receive_DMA(&huart1,Uart1.RxBuf,RX_MAXLEN); //开启下次接收
  16. }
  17. }
  18. }

        在主程序中判断接收完成标志,并处理数据:

  1. if(Uart1.RxFlag == 1)//接收完一帧数据
  2. {
  3. printf("Rev %d Bytes\r\n",Uart1.RxLen);
  4. Uart1.RxFlag = 0;
  5. }

        最后,别忘了在串口中断函数中调用自己编写的空闲中断函数。

        运行程序测试,结果如下:

        使用接收中断+空闲中断与DMA类似,只不过需要打开接收中断:

  1. __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//打开串口空闲中断
  2. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据

        编写接收中断回调函数,每次接收一个字节:

  1. uint8_t RevByte;
  2. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  3. {
  4. if(huart->Instance==USART1)
  5. {
  6. Uart1.RxBuf[Uart1.RxCnt]=RevByte;
  7. Uart1.RxCnt++;
  8. if(Uart1.RxCnt==RX_MAXLEN)
  9. {
  10. Uart1.RxCnt = RX_MAXLEN-1;
  11. }
  12. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据
  13. }
  14. }

        编写空闲中断回调函数,与DMA的方式类似,只是数据长度判断方式不一样:

  1. //串口空闲中断
  2. void UART_IDLECallBack(UART_HandleTypeDef *huart)
  3. {
  4. uint32_t temp;
  5. /*uart1 idle processing function*/
  6. if(huart == &huart1)
  7. {
  8. if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
  9. {
  10. __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
  11. Uart1.RxFlag = 1;
  12. Uart1.RxLen = Uart1.RxCnt;
  13. Uart1.RxCnt = 0;
  14. }
  15. }
  16. }

        同样,在主程序中判断一帧数据的接收完成并处理。

2.特点协议判断帧头帧尾及长度

        有时候我们需要自己定义协议传输数据,这时候就可以在通讯协议里添加特点的帧头帧尾以及数据长度字节,通过判断这些字节来判断数据的开始和结束。假设定义一个简单的传输协议如下:

帧头

数据长度,1字节

数据,N字节

0x5A,0xA5

数据部分的字节数

有效数据

        可以使用中断方式接收数据:

HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据

        接收中断函数如下:

  1. //串口接收中断回调函数
  2. uint8_t RevByte;
  3. uint16_t RevTick = 0;
  4. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  5. {
  6. static uint16_t Rx_len;
  7. if(huart->Instance==USART1)
  8. {
  9. Uart1.RxBuf[Uart1.RxCnt]=RevByte;
  10. switch(Uart1.RxCnt)
  11. {
  12. case 0:
  13. if(Uart1.RxBuf[Uart1.RxCnt] == 0x5A)//帧头1正确
  14. Uart1.RxCnt++;
  15. else
  16. Uart1.RxCnt = 0;
  17. break;
  18. case 1:
  19. if(Uart1.RxBuf[Uart1.RxCnt] == 0xA5)//帧头2正确
  20. Uart1.RxCnt++;
  21. else
  22. Uart1.RxCnt = 0;
  23. break;
  24. case 2:
  25. Rx_len = Uart1.RxBuf[Uart1.RxCnt];
  26. Uart1.RxCnt++;
  27. break;
  28. default:
  29. Uart1.RxCnt++;
  30. if((Rx_len+3) == Uart1.RxCnt)//数据接收完成
  31. {
  32. Uart1.RxFlag = 1;
  33. Uart1.RxLen = Uart1.RxCnt;
  34. Uart1.RxCnt = 0;
  35. }
  36. break;
  37. }
  38. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据
  39. }
  40. }

        同样,在主程序中判断一帧数据的接收完成并处理,运行测试结果如下:

3.超时判断

        超时判断其实与空闲中断的原理类似,只不过是通过定时器来取代空闲中断来判断一帧数据的结束,一般采样接收中断+超时判断的方式。之前的文章Freemodbus移植就是采样这种方式。

        超时判断的时间跟波特率有关,假设串口起始位和结束位各1位,那么接收一个字节就需要8+2=10位,在9600波特率下,一秒钟就能接收9600/10=960字节。也就是一个字节需要1.04ms,那么超时时间最小可以设置为1.5倍的单字节接收时间,或者更长。

        超时判断可以使用硬件定时器或软件定时器来实现。硬件定时器的方式可以参考之前的Freemodbus移植部分的程序。软件定时器定义一个计时变量,该变量在systick中断中+1实现计时,可以节省硬件资源,但计时最小分辨率跟systick中断有关。

        编写中断接收函数:

  1. //串口接收中断回调函数
  2. uint8_t RevByte;
  3. uint16_t RevTick = 0;
  4. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  5. {
  6. if(huart->Instance==USART1)
  7. {
  8. Uart1.RxBuf[Uart1.RxCnt]=RevByte;
  9. Uart1.RxCnt++;
  10. Uart1.RxStart = 1;//开始接收标志
  11. RevTick = 0;//计时清零
  12. if(Uart1.RxCnt==RX_MAXLEN)
  13. {
  14. Uart1.RxCnt = RX_MAXLEN-1;
  15. }
  16. HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据
  17. }
  18. }

        编写超时判断函数,在Systick中断中调用:

  1. //串口接收超时判断,该函数在Systick中断(1ms中断一次)中调用
  2. void UartTimeOut()
  3. {
  4. if(Uart1.RxStart == 1)
  5. {
  6. RevTick++;
  7. if(RevTick > 2)
  8. {
  9. Uart1.RxLen = Uart1.RxCnt;
  10. Uart1.RxCnt = 0;
  11. Uart1.RxStart = 0;
  12. Uart1.RxFlag = 1;
  13. }
  14. }
  15. }

        使用时只要打开接收中断即可,不再需要空闲中断。

HAL_UART_Receive_IT(&huart1, &RevByte, 1); //串口中断接收数据

        同样,在主程序中判断一帧数据的接收完成并处理。测试结果就不贴了。

4.总结

        上面几种方式都可以实现串口接收不定长数据,各有优缺点,可根据实际需求选择用哪种。需要注意的是,上面的例程只是简单地接收数据,实际应用中,还需要考虑连续接收多帧数据的情况,是缓存之后处理,还是舍弃后面的数据,都需要自己写程序实现。

Popular repositories

  1. WDXZF WDXZF Public

  2. sun0225SUN sun0225SUN Public

    Forked from sun0225SUN/sun0225SUN

    sun0225SUN's profile with 75 stars and 153 forks 🎉

  3. 17661977890 17661977890 Public

    Forked from 17661977890/17661977890

  4. yesmore yesmore Public

    Forked from yesmore/yesmore

    芜湖

  5. meetbill meetbill Public

    Forked from meetbill/meetbill

    显示在首页内容