Code Monkey home page Code Monkey logo

🙋 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.总结

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

w's Projects

sun0225sun icon sun0225sun

sun0225SUN's profile with 75 stars and 153 forks 🎉

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.