你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32与PC端、HC-06、ROS进行USART串口通信

[复制链接]
STMCU小助手 发布时间:2022-4-26 22:26
前言
串口通讯对于任何开发板都是非常重要的,也是必学知识之一,通过串口通信可以实现上位机与下位机之间、开发板之间的通讯,可以让我们实时掌握机器人的各个关机的运动状态和传感器的信息。

现在的通信协议有很多,比如:UART、USART、CAN、SPI等等,它们功能不同,适用于不同的场合,USART作为单片机之间、下位机与上位机之间最常用的通讯方式之一,它对于数据的收发十分方便,应用日益广泛。

一、USART通信原理
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)中文名叫:全双工通用同步/异步串行收发模块。

先对这个名字的各个部分进行解释:

4c96242ab38648818d029a7ea10cab27.jpg

在平时的工程项目中,我们常用的是全双工串行异步通信方式,虽然串行通信数据是一位一位的发送,但随着近几年科技的高速发展,串行通讯的速度已经逐渐赶超并行通讯了,而且串行通讯方式适用于远距离通讯,比较常用。

USART通讯的数据格式大致是这样:

起始位(0)+串行数据帧(从低位到高位传输)+停止位(1)

串行数据帧可以人为设置为8位或者9位,9位是8位数据加上1位校验位(奇偶校验)。

另外一个比较重要的概念是波特率,在任何通讯开始之前,通讯双方都要约定好波特率,波特率是每秒发送有效数据的位数(bit/s),双方如果没有约定好一致的波特率,在传输过程中则会出现乱码的情况。

在STM32中,有专门的数据寄存器和特定的引脚负责USART通讯,并配合有相应的标志位,用于帮我们判断数据是否发送/接收完毕,并且也有相关的库函数帮助我们对串口进行配置。

相关寄存器有:发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)、接受移位寄存器。

相关标志位有:

TEX标志位:1:数据寄存器无数据;0:数据寄存器有数据
TX标志位:1:发送完成;0:发送未完成
RXNE标志位:1:数据接收完成,可以读出;0:数据未收到
具体知识在中文参考手册P517,大家可以详细查看

二、STM32与PC通讯

STM32与PC通讯需要进行一些配置,这里实现由PC端向STM32发送一个数据,STM32接收到后再发回到PC端,该实验需要用到串口调试助手。

STM32可以作为串口通讯的引脚大家可以通过数据手册进行查看,比如PA9(TX)和PA10(RX)

94]6SASVE]E8VUPMKX_YD40.png

串口通讯一般都配合中断进行使用,下面讲解串口配置过程:

USART配置步骤

步骤 函数 1.串口时钟、GPIO时钟使能 RCC_APB2PeriphClockCmd 2.GPIO端口模式配置 GPIO_Init 3.串口参数初始化 USART_Init 4.开启终端并初始化NVIC USART_ITConfig、NVIC_Init 5.使能串口 USART_Cmd 6.编写中断服务函数 USART1_IRQHandler

my_usart.c 代码如下:

  1. void My_Usart1_Init(int bound)
  2. {
  3.         /*1.串口时钟、GPIOA使能*/
  4.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA,ENABLE);
  5.         
  6.         
  7.         /*2.GPIO端口模式设置*/
  8.         GPIO_InitTypeDef GPIO_InitStruct;
  9.         /*TX*/
  10.         GPIO_InitStruct.GPIO_Pin = Usart1_TX;
  11.         GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
  12.         GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  13.         GPIO_Init(GPIOA, &GPIO_InitStruct);
  14.         /*RX*/
  15.         GPIO_InitStruct.GPIO_Pin = Usart1_RX;
  16.         GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  17.         //GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  18.         GPIO_Init(GPIOA, &GPIO_InitStruct);
  19.         
  20.         
  21.         /*3.串口参数初始化*/
  22.         USART_InitTypeDef USART_InitStruct;
  23.         USART_InitStruct.USART_BaudRate = bound;//波特率
  24.         USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
  25.         USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//USART模式
  26.         USART_InitStruct.USART_Parity = USART_Parity_No;//校验位
  27.         USART_InitStruct.USART_StopBits = USART_StopBits_1;//终止位
  28.         USART_InitStruct.USART_WordLength = USART_WordLength_8b;//数据长度,如果有奇偶校验就设置为9位
  29.         USART_Init(USART1, &USART_InitStruct);
  30.         
  31.         /*4.开启中断并初始化NVIC*/
  32.         USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开中断,开启接受中断后,接收到数据则会进入中断服务函数
  33.         
  34.         NVIC_InitTypeDef NVIC_InitStruct;
  35.         NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
  36.         NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  37.         NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;
  38.         NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
  39.         NVIC_Init(&NVIC_InitStruct);//初始化NVIC
  40.         
  41.         /*5.使能串口*/
  42.         USART_Cmd(USART1, ENABLE);
  43.         
  44.         /*6.编写中断服务函数*/
  45.          
  46. }
复制代码


进行如上配置后,当STM32接收到PC发送的信息就会进入中断服务函数,中断服务函数接收到数据后再进行发送。

中断服务函数代码:

  1. void USART1_IRQHandler(void)
  2. {
  3.         u16 RX_From_PC;
  4.         if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
  5.         {
  6.                 USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清空中断标志位
  7.                 RX_From_PC = USART_ReceiveByte(USART1);//接收PC端发来的消息
  8.                 USART_SendByte(USART1,RX_From_PC);//将数据发回到PC端
  9.                
  10.                 //usartReceiveOneData(&TargetVelocity,&TargetVelocity,&RX_Cmd_Form_Ros);//接收ROS发来的消息
  11.         }
  12. }
复制代码

在这里我们没有使用官方固件库中提供的收发函数 USART_ReceiveData 和 USART_SendData,而是使用了我自己对他们进行重写的函数,在其中加入了相关标志位的判断,这样可以保证收发过程中不会发生数据覆盖。

重写后的函数:

  1. void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data)
  2. {
  3.   /* Check the parameters */
  4.   assert_param(IS_USART_ALL_PERIPH(USARTx));
  5.   assert_param(IS_USART_DATA(Data));

  6.   /* Transmit Data */
  7.   USARTx->DR = (Data & (uint16_t)0x01FF);
  8.   while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);
  9. }

  10. void USART_SendString(USART_TypeDef* USARTx,char* str)
  11. {
  12.         while((*str) != '\0')
  13.         {
  14.                 USART_SendByte(USARTx, *str);
  15.                 str++;
  16.         }
  17.         while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
  18. }



  19. uint16_t USART_ReceiveByte(USART_TypeDef* USARTx)
  20. {
  21.   /* Check the parameters */
  22.   assert_param(IS_USART_ALL_PERIPH(USARTx));
  23.         
  24.   while(USART_GetFlagStatus(USARTx,USART_FLAG_RXNE)==RESET);

  25.   /* Receive Data */
  26.   return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
  27. }
复制代码

在主函数中进行相应的初始化,我们就可以通过串口助手与STM32进行通讯了。

{[]G6A`}0`ZOOK8XC_%)6S9.png

另外,Keil5是没有终端的,但我们可以通过一些设置也使用printf函数,让数据收发更加方便,我们需要对fputs和fgets这两个函数进行重定向(其实只重定向fputs即可),需要在程序中加入如下代码:

  1. /*重定向这两个函数*/
  2. int fputc(int ch,FILE *f)
  3. {
  4.         while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
  5.         USART_SendData(USART1,(uint8_t) ch);
  6.         return ch;
  7. }

  8. int fgetc(FILE *f)
  9. {
  10.         while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);
  11.         return (int)USART_ReceiveData(USART1);
  12. }
复制代码

printf函数使用的是半主机工作模式,需要使用微控制器,需要进行如下配置:

{3{FUBC9AU]ED(F8UYKCYID.png

这玩意功能少一些,如果不想用,则需要在程序中再加入以下代码,这是printf函数的底层程序:

  1. #pragma import(__use_no_semihosting)

  2. struct __FILE
  3. {
  4.         int handle;
  5. };

  6. FILE __stdout;
  7. _sys_exit(int x)
  8. {
  9.         x = x;
  10. }
复制代码

编译后,我们就可以与PC端进行愉快的通讯了。

三、STM32与HC-06通讯
STM32与HC-06的通讯与PC端的通讯类似,知识接线方式的不同而已,只需要进行如下正确的接线,即可完成顺利地通讯。

3EIZ2B@ALCG6F}(CGE(VO`W.png

这里要注意的是,给HC-06供电时,需要提供3.6V-6V的电压;另外,要注意看一下STM32的引脚输出电压是多少,HC-06的输入输出电压都是3.3V,比如像Arduino的输出电压为5V,这时单片机的TX在接HC-06的RX时需要分压!

四、STM32与ROS通讯

STM32与ROS通讯时,需要定义一个协议,以保证数据传输的可靠性,这个协议是STM32与ROS通讯时最广泛使用的协议,协议格式如下:

数据头55aa + 控制命令 + 数据字节数size + 数据 + 校验crc8 + 数据尾0d0a

在通讯时,我们只要对接收到的信息进行解码,即可获得有效数据信息,比如:设定的速度值、航向角等,保证了数据收发的可靠性。

先定义相关的变量:

  1. //数据接收暂存区
  2. unsigned char  receiveBuff[Message_Length-4] = {0};         
  3. //通信协议常量
  4. const unsigned char header[2]  = {0x55, 0xaa};
  5. const unsigned char ender[2]   = {0x0d, 0x0a};

  6. //发送数据(左轮速、右轮速、角度)共用体(-32767 - +32768)
  7. union sendData
  8. {
  9.         float d;
  10.         char data[4];
  11. }leftVelNow,rightVelNow,angleNow;

  12. //左右轮速控制速度共用体
  13. union receiveData
  14. {
  15.         float d;
  16.         char data[4];
  17. }leftVelSet,rightVelSet;
复制代码


接收信息函数:

  1. int usartReceiveOneData(float *p_leftSpeedSet,float *p_rightSpeedSet,unsigned char *p_crtlFlag)
  2. {
  3.         unsigned char USART_Receiver              = 0;          //接收数据
  4.         static unsigned char checkSum             = 0;
  5.         static unsigned char USARTBufferIndex     = 0;
  6.         static short j=0,k=0;
  7.         static unsigned char USARTReceiverFront   = 0;
  8.         static unsigned char Start_Flag           = START;      //一帧数据传送开始标志位
  9.         static short dataLength                   = 0;

  10.         USART_Receiver = USART_ReceiveByte(USART1);
  11.         //接收消息头
  12.         if(Start_Flag == START)
  13.         {
  14.                 if(USART_Receiver == 0xaa)                             //buf[1]
  15.                 {  
  16.                         if(USARTReceiverFront == 0x55)        //数据头两位 //buf[0]
  17.                         {
  18.                                 Start_Flag = !START;              //收到数据头,开始接收数据
  19.                                 //printf("header ok\n");
  20.                                 receiveBuff[0]=header[0];         //buf[0]
  21.                                 receiveBuff[1]=header[1];         //buf[1]
  22.                                 USARTBufferIndex = 0;             //缓冲区初始化
  23.                                 checkSum = 0x00;                                  //校验和初始化
  24.                         }
  25.                 }
  26.                 else
  27.                 {
  28.                         USARTReceiverFront = USART_Receiver;  
  29.                 }
  30.         }
  31.         else
  32.     {
  33.                 switch(USARTBufferIndex)
  34.                 {
  35.                         case 0://接收控制指令
  36.                                 receiveBuff[2] = USART_Receiver;
  37.                                 *p_crtlFlag = receiveBuff[2];                                //buf[2]
  38.                                 USARTBufferIndex++;
  39.                         case 1://接收左右轮速度数据的长度
  40.                                 receiveBuff[3] = USART_Receiver;
  41.                                 dataLength     = receiveBuff[3];            //buf[3]
  42.                                 USARTBufferIndex++;
  43.                                 break;                                
  44.                         case 2://接收所有数据,并赋值处理
  45.                                 receiveBuff[j + 4] = USART_Receiver;        //buf[4] - buf[11]                                       
  46.                                 j++;
  47.                                 if(j >= dataLength)                        
  48.                                 {
  49.                                         j = 0;
  50.                                         USARTBufferIndex++;
  51.                                 }
  52.                                 break;
  53.                         case 3://接收校验值信息
  54.                                 receiveBuff[4 + dataLength] = USART_Receiver;
  55.                                 checkSum = getCrc8(receiveBuff, 4 + dataLength);
  56.                                   // 检查信息校验值
  57.                                 if (checkSum != receiveBuff[4 + dataLength]) //buf[12]
  58.                                 {
  59.                                         printf("Received data check sum error!");
  60.                                         return 0;
  61.                                 }
  62.                                 USARTBufferIndex++;
  63.                                 break;
  64.                                 
  65.                         case 4://接收信息尾
  66.                                 if(k==0)
  67.                                 {
  68.                                         //数据0d     buf[13]  无需判断
  69.                                         k++;
  70.                                 }
  71.                                 else if (k==1)
  72.                                 {
  73.                                         //数据0a     buf[14] 无需判断

  74.                                         //进行速度赋值操作                                       
  75.                                          for(k = 0; k < 4; k++)
  76.                                         {
  77.                                                 leftVelSet.data[k]  = receiveBuff[k + 4]; //buf[4] - buf[7]
  78.                                                 rightVelSet.data[k] = receiveBuff[k + 8]; //buf[8] - buf[11]
  79.                                         }                                
  80.                                        
  81.                                         //速度赋值操作
  82.                                         sscanf(leftVelSet.data,"%f",&(*p_leftSpeedSet));
  83.                                         sscanf(leftVelSet.data,"%f",&(*p_rightSpeedSet));
  84.                                        
  85.                                        
  86.                                         //-----------------------------------------------------------------
  87.                                         //完成一个数据包的接收,相关变量清零,等待下一字节数据
  88.                                         USARTBufferIndex   = 0;
  89.                                         USARTReceiverFront = 0;
  90.                                         Start_Flag         = START;
  91.                                         checkSum           = 0;
  92.                                         dataLength         = 0;
  93.                                         j = 0;
  94.                                         k = 0;
  95.                                         //-----------------------------------------------------------------                                       
  96.                                 }
  97.                                 break;
  98.                          default:break;
  99.                 }               
  100.         }
  101.         return 0;
  102. }
复制代码

发送信息函数:

  1. void usartSendData(float leftVel, float rightVel,float angle,unsigned char ctrlFlag)
  2. {
  3.         // 协议数据缓存数组
  4.         unsigned char buf[Message_Length] = {0};
  5.         int i, length = 0;

  6.         // 计算左右轮期望速度
  7.         leftVelNow.d  = leftVel;
  8.         rightVelNow.d = rightVel;
  9.         angleNow.d    = angle;
  10.         
  11.         // 设置消息头
  12.         for(i = 0; i < 2; i++)
  13.                 buf<i> = header<i>;                      // buf[0] buf[1]

  14.         //设置命令位
  15.         buf[2] = ctrlFlag;                                                         // buf[2]
  16.         
  17.         //设置数据长度
  18.         length = 3 * sizeof(float);
  19.         buf[3] = length;                                                         // buf[3]
  20.         
  21.         // 设置机器人左右轮速度、角度
  22.         for(i = 0; i < 4; i++)
  23.         {
  24.                 buf[i + 4] = leftVelNow.data<i>;         // buf[4] buf[5] buf[6] buf[7]
  25.                 buf[i + 8] = rightVelNow.data<i>;        // buf[8] buf[9] buf[10] buf[11]
  26.                 buf[i + 12] = angleNow.data<i>;           // buf[12] buf[13] buf[14] buf[15]
  27.         }
  28.         
  29.         // 设置校验值、消息尾
  30.         buf[Message_Length - 3] = getCrc8(buf, 4 + length);  // buf[16]
  31.         buf[Message_Length - 2] = ender[0];              // buf[17]
  32.         buf[Message_Length - 1] = ender[1];              // buf[18]
  33.         
  34.         //发送字符串数据
  35.         USART_Send_String(buf,sizeof(buf));
  36. }

  37. 循环冗余校验:

  38. unsigned char getCrc8(unsigned char *ptr, unsigned short len)
  39. {
  40.         unsigned char crc;
  41.         unsigned char i;
  42.         crc = 0;
  43.         while(len--)
  44.         {
  45.                 crc ^= *ptr++;
  46.                 for(i = 0; i < 8; i++)
  47.                 {
  48.                         if(crc&0x01)
  49.                 crc=(crc>>1)^0x8C;
  50.                         else
  51.                 crc >>= 1;
  52.                 }
  53.         }
  54.         return crc;
  55. }</i></i></i></i></i>
复制代码

然后我们只要在中断服务函数中调用收发信息函数,即可实现与ROS的通讯。

总结
串口通讯的ROS部分,使用到了boost::asio库,等我学会了再更。



收藏 评论0 发布时间:2022-4-26 22:26

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版