01 前言
串口通讯对于任何开发板都是非常重要的,也是必学知识之一,通过串口通信可以实现上位机与下位机之间、开发板之间的通讯,可以让我们实时掌握机器人的各个关机的运动状态和传感器的信息。
现在的通信协议有很多,比如:UART、USART、CAN、SPI等等,它们功能不同,适用于不同的场合,USART作为单片机之间、下位机与上位机之间最常用的通讯方式之一,它对于数据的收发十分方便,应用日益广泛。
(资料图)
02 USART通信原理
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)中文名叫:全双工通用同步/异步串行收发模块。
先对这个名字的各个部分进行解释:
在平时的工程项目中,我们常用的是全双工串行异步通信方式,虽然串行通信数据是一位一位的发送,但随着近几年科技的高速发展,串行通讯的速度已经逐渐赶超并行通讯了,而且串行通讯方式适用于远距离通讯,比较常用。
USART通讯的数据格式大致是这样:
起始位(0)+串行数据帧(从低位到高位传输)+停止位(1)
串行数据帧可以人为设置为8位或者9位,9位是8位数据加上1位校验位(奇偶校验)。
另外一个比较重要的概念是波特率,在任何通讯开始之前,通讯双方都要约定好波特率,波特率是每秒发送有效数据的位数(bit/s),双方如果没有约定好一致的波特率,在传输过程中则会出现乱码的情况。
在STM32中,有专门的数据寄存器和特定的引脚负责USART通讯,并配合有相应的标志位,用于帮我们判断数据是否发送/接收完毕,并且也有相关的库函数帮助我们对串口进行配置。
相关寄存器有:发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)、接受移位寄存器。
相关标志位有:
TEX标志位:1:数据寄存器无数据;0:数据寄存器有数据TX标志位:1:发送完成;0:发送未完成RXNE标志位:1:数据接收完成,可以读出;0:数据未收到具体知识在中文参考手册P517,大家可以详细查看
03 STM32与PC通讯
STM32与PC通讯需要进行一些配置,这里实现由PC端向STM32发送一个数据,STM32接收到后再发回到PC端,该实验需要用到串口调试助手。
STM32可以作为串口通讯的引脚大家可以通过数据手册进行查看,比如PA9(TX)和PA10(RX)
串口通讯一般都配合中断进行使用,下面讲解串口配置过程:
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 代码如下:
void My_Usart1_Init(int bound){ /*1.串口时钟、GPIOA使能*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA,ENABLE); /*2.GPIO端口模式设置*/ GPIO_InitTypeDef GPIO_InitStruct; /*TX*/ GPIO_InitStruct.GPIO_Pin = Usart1_TX; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); /*RX*/ GPIO_InitStruct.GPIO_Pin = Usart1_RX; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; //GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); /*3.串口参数初始化*/ USART_InitTypeDef USART_InitStruct; USART_InitStruct.USART_BaudRate = bound;//波特率 USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制 USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//USART模式 USART_InitStruct.USART_Parity = USART_Parity_No;//校验位 USART_InitStruct.USART_StopBits = USART_StopBits_1;//终止位 USART_InitStruct.USART_WordLength = USART_WordLength_8b;//数据长度,如果有奇偶校验就设置为9位 USART_Init(USART1, &USART_InitStruct); /*4.开启中断并初始化NVIC*/ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开中断,开启接受中断后,接收到数据则会进入中断服务函数 NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3; NVIC_Init(&NVIC_InitStruct);//初始化NVIC /*5.使能串口*/ USART_Cmd(USART1, ENABLE); /*6.编写中断服务函数*/}
进行如上配置后,当STM32接收到PC发送的信息就会进入中断服务函数,中断服务函数接收到数据后再进行发送。
中断服务函数代码:
void USART1_IRQHandler(void){ u16 RX_From_PC; if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET) { USART_ClearITPendingBit(USART1,USART_IT_RXNE);//清空中断标志位 RX_From_PC = USART_ReceiveByte(USART1);//接收PC端发来的消息 USART_SendByte(USART1,RX_From_PC);//将数据发回到PC端 //usartReceiveOneData(&TargetVelocity,&TargetVelocity,&RX_Cmd_Form_Ros);//接收ROS发来的消息 }}
在这里我们没有使用官方固件库中提供的收发函数 USART_ReceiveData 和 USART_SendData,而是使用了我自己对他们进行重写的函数,在其中加入了相关标志位的判断,这样可以保证收发过程中不会发生数据覆盖。
重写后的函数:
void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data){ /* Check the parameters*/ assert_param(IS_USART_ALL_PERIPH(USARTx)); assert_param(IS_USART_DATA(Data)); /* Transmit Data */ USARTx- >DR = (Data & (uint16_t)0x01FF); while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);}void USART_SendString(USART_TypeDef* USARTx,char* str){ while((*str) != "\\0") { USART_SendByte(USARTx, *str); str++; } while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);}uint16_t USART_ReceiveByte(USART_TypeDef* USARTx){ /* Check the parameters */ assert_param(IS_USART_ALL_PERIPH(USARTx)); while(USART_GetFlagStatus(USARTx,USART_FLAG_RXNE)==RESET); /* Receive Data */ return (uint16_t)(USARTx- >DR & (uint16_t)0x01FF);}
在主函数中进行相应的初始化,我们就可以通过串口助手与STM32进行通讯了。
另外,Keil5是没有终端的,但我们可以通过一些设置也使用printf函数,让数据收发更加方便,我们需要对fputs和fgets这两个函数进行重定向(其实只重定向fputs即可),需要在程序中加入如下代码:
/*重定向这两个函数*/int fputc(int ch,FILE *f){ while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET); USART_SendData(USART1,(uint8_t) ch); return ch;}int fgetc(FILE *f){ while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET); return (int)USART_ReceiveData(USART1);}
printf函数使用的是半主机工作模式,需要使用微控制器,需要进行如下配置:
这玩意功能少一些,如果不想用,则需要在程序中再加入以下代码,这是printf函数的底层程序:
#pragma import(__use_no_semihosting)struct __FILE{ int handle;};FILE __stdout;_sys_exit(int x){ x = x;}
编译后,我们就可以与PC端进行愉快的通讯了!
04 STM32与HC-06通讯
STM32与HC-06的通讯与PC端的通讯类似,知识接线方式的不同而已,只需要进行如下正确的接线,即可完成顺利地通讯。
这里要注意的是,给HC-06供电时,需要提供3.6V-6V的电压;另外,要注意看一下STM32的引脚输出电压是多少,HC-06的输入输出电压都是3.3V,比如像Arduino的输出电压为5V,这时单片机的TX在接HC-06的RX时需要分压!
05 STM32与ROS通讯
STM32与ROS通讯时,需要定义一个协议,以保证数据传输的可靠性,这个协议是STM32与ROS通讯时最广泛使用的协议,协议格式如下:
数据头55aa + 控制命令 + 数据字节数size + 数据 + 校验crc8 + 数据尾0d0a
在通讯时,我们只要对接收到的信息进行解码,即可获得有效数据信息,比如:设定的速度值、航向角等,保证了数据收发的可靠性。
先定义相关的变量:
//数据接收暂存区unsigned char receiveBuff[Message_Length-4] = {0}; //通信协议常量const unsigned char header[2] = {0x55, 0xaa};const unsigned char ender[2] = {0x0d, 0x0a};//发送数据(左轮速、右轮速、角度)共用体(-32767 - +32768)union sendData{ float d; char data[4];}leftVelNow,rightVelNow,angleNow;//左右轮速控制速度共用体union receiveData{ float d; char data[4];}leftVelSet,rightVelSet;
接收信息函数:
int usartReceiveOneData(float *p_leftSpeedSet,float *p_rightSpeedSet,unsigned char *p_crtlFlag){ unsigned char USART_Receiver = 0; //接收数据 static unsigned char checkSum = 0; static unsigned char USARTBufferIndex = 0; static short j=0,k=0; static unsigned char USARTReceiverFront = 0; static unsigned char Start_Flag = START; //一帧数据传送开始标志位 static short dataLength = 0; USART_Receiver = USART_ReceiveByte(USART1); //接收消息头 if(Start_Flag == START) { if(USART_Receiver == 0xaa) //buf[1] { if(USARTReceiverFront == 0x55) //数据头两位 //buf[0] { Start_Flag = !START; //收到数据头,开始接收数据 //printf("header ok\\n"); receiveBuff[0]=header[0]; //buf[0] receiveBuff[1]=header[1]; //buf[1] USARTBufferIndex = 0; //缓冲区初始化 checkSum = 0x00; //校验和初始化 } } else { USARTReceiverFront = USART_Receiver; } } else { switch(USARTBufferIndex) { case 0://接收控制指令 receiveBuff[2] = USART_Receiver; *p_crtlFlag = receiveBuff[2]; //buf[2] USARTBufferIndex++; case 1://接收左右轮速度数据的长度 receiveBuff[3] = USART_Receiver; dataLength = receiveBuff[3]; //buf[3] USARTBufferIndex++; break; case 2://接收所有数据,并赋值处理 receiveBuff[j + 4] = USART_Receiver; //buf[4] - buf[11] j++; if(j >= dataLength) { j = 0; USARTBufferIndex++; } break; case 3://接收校验值信息 receiveBuff[4 + dataLength] = USART_Receiver; checkSum = getCrc8(receiveBuff, 4 + dataLength); // 检查信息校验值 if (checkSum != receiveBuff[4 + dataLength]) //buf[12] { printf("Received data check sum error!"); return 0; } USARTBufferIndex++; break; case 4://接收信息尾 if(k==0) { //数据0d buf[13] 无需判断 k++; } else if (k==1) { //数据0a buf[14] 无需判断 //进行速度赋值操作 for(k = 0; k < 4; k++) { leftVelSet.data[k] = receiveBuff[k + 4]; //buf[4] - buf[7] rightVelSet.data[k] = receiveBuff[k + 8]; //buf[8] - buf[11] } //速度赋值操作 sscanf(leftVelSet.data,"%f",&(*p_leftSpeedSet)); sscanf(leftVelSet.data,"%f",&(*p_rightSpeedSet)); //----------------------------------------------------------------- //完成一个数据包的接收,相关变量清零,等待下一字节数据 USARTBufferIndex = 0; USARTReceiverFront = 0; Start_Flag = START; checkSum = 0; dataLength = 0; j = 0; k = 0; //----------------------------------------------------------------- } break; default:break; } } return 0;}
发送信息函数:
void usartSendData(float leftVel, float rightVel,float angle,unsigned char ctrlFlag){ // 协议数据缓存数组 unsigned char buf[Message_Length] = {0}; int i, length = 0; // 计算左右轮期望速度 leftVelNow.d = leftVel; rightVelNow.d = rightVel; angleNow.d = angle; // 设置消息头 for(i = 0; i < 2; i++) buf[i] = header[i]; // buf[0] buf[1] //设置命令位 buf[2] = ctrlFlag; // buf[2] //设置数据长度 length = 3 * sizeof(float); buf[3] = length; // buf[3] // 设置机器人左右轮速度、角度 for(i = 0; i < 4; i++) { buf[i + 4] = leftVelNow.data[i]; // buf[4] buf[5] buf[6] buf[7] buf[i + 8] = rightVelNow.data[i]; // buf[8] buf[9] buf[10] buf[11] buf[i + 12] = angleNow.data[i]; // buf[12] buf[13] buf[14] buf[15] } // 设置校验值、消息尾 buf[Message_Length - 3] = getCrc8(buf, 4 + length); // buf[16] buf[Message_Length - 2] = ender[0]; // buf[17] buf[Message_Length - 1] = ender[1]; // buf[18] //发送字符串数据 USART_Send_String(buf,sizeof(buf));}
循环冗余校验:
unsigned char getCrc8(unsigned char *ptr, unsigned short len){ unsigned char crc; unsigned char i; crc = 0; while(len--) { crc ^= *ptr++; for(i = 0; i < 8; i++) { if(crc&0x01) crc=(crc >>1)^0x8C; else crc > >= 1; } } return crc;}
然后我们只要在中断服务函数中调用收发信息函数,即可实现与ROS的通讯。
标签:
精彩推荐
2022年12月08日公告发布
2022年12月07日公告发布
5月20日是网络情人节,郑州陈寨花卉市场的一家花店看到一束与众不同的花,竟是用15个钢丝球包扎的。花店...
相较于火车站,机场的免费Wi-Fi速度要快了不少,而全球最快的免费Wi-Fi机场名单,也被统计了出来。美国...
近日,山东省高级人民法院向社会通报全省法院消费者权益司法保护工作情况及10起典型案例。据了解,五年...
南京市19日通报,公安部门在疫情防控期间依法打击各类涉疫违法犯罪行为,截至3月18日,全市共查处各类涉...
日前,北京市人民政府新闻办公室举行新闻发布会,解读《北京市全民科学素质行动规划纲要(2021—2035年)...
去年下半年以来,受多重因素影响,房地产市场出现下行态势。今年以来,各方共同努力持续稳地价、稳房价...
联合国人权理事会第49届会议新疆经济社会发展与人权保障边会18日在广州举办。会议由中国人权研究会、中...
人力资源和社会保障部近日印发《关于开展技术技能类山寨证书专项治理工作的通知》(以下简称《通知》),...
针对网络消费乱象,最高人民法院近期发布《最高人民法院关于审理网络消费纠纷案件适用法律若干问题的规...
当好农民工的“护薪人” 近日,罗某等7名农民工在收到被拖欠的工资后,纷纷打电话向江西省南昌市...
“通讯录里所有人都知道我欠钱了” □ 本报记者 韩丹东 □ 本报见习记者 张守坤 ...
大连宝马车撞人案肇事司机被判死刑 本报讯 记者韩宇 10月29日,辽宁省大连市中级人民法院一审...
医院财务迷上网络赌博输光5000万元公款 □ 本报记者 马维博 □ 本报通讯员 汪宇堂 曹...
辊环车削 雕琢毫厘(工匠绝活) 【绝活看点】 23年来,雷虎始终扎根一线,改进钢材轧制工艺...
交警严查超标电动自行车挪用“白牌” 截至昨晚6时,处罚电动自行车违法行为共计6585笔;下一步将...
明起寒潮来袭 北方气温普降10℃以上 中央气象台预计,本周日北京平原地区最低气温降至-4℃左右...
多种蔬菜价格降幅达五成 包括菠菜、蒿子秆等 预计本月中旬蔬菜恢复供需平衡 本报讯(记者...
北京周日最低气温或达-4℃ 本报讯(记者 赵婷婷)北京青年报记者昨天从中央气象台获悉,新一股...
资讯News
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
06-15
聚焦Policy
当好农民工的“护薪人” 近日,罗某等7名农民工在收到被拖欠的工资后,纷纷打电话向江西省南昌市...
“通讯录里所有人都知道我欠钱了” □ 本报记者 韩丹东 □ 本报见习记者 张守坤 ...
大连宝马车撞人案肇事司机被判死刑 本报讯 记者韩宇 10月29日,辽宁省大连市中级人民法院一审...
医院财务迷上网络赌博输光5000万元公款 □ 本报记者 马维博 □ 本报通讯员 汪宇堂 曹...
辊环车削 雕琢毫厘(工匠绝活) 【绝活看点】 23年来,雷虎始终扎根一线,改进钢材轧制工艺...
交警严查超标电动自行车挪用“白牌” 截至昨晚6时,处罚电动自行车违法行为共计6585笔;下一步将...
明起寒潮来袭 北方气温普降10℃以上 中央气象台预计,本周日北京平原地区最低气温降至-4℃左右...
多种蔬菜价格降幅达五成 包括菠菜、蒿子秆等 预计本月中旬蔬菜恢复供需平衡 本报讯(记者...
北京周日最低气温或达-4℃ 本报讯(记者 赵婷婷)北京青年报记者昨天从中央气象台获悉,新一股...
昌平一家四口确诊新冠肺炎 天通北苑第二社区升级为中风险地区 朝阳两涉疫校区及16所学校停课 ...