任务二:串口通信
一、stm32串⼝通信理论部分
1、通信协议大总结
- 串口通信即为其中一种,其可以实现两个设备间的互相通信
- 串口通信实现了各硬件模块的互相通信
2、串口通信硬件电路连接
其中:
- GND接地(严格意义上讲也是GND数据线,传输数据依靠的是电平差)
- VCC用于供电,若是两个设备均有单独供电,则不需要接这根线; 若需要供电,还要注意子系统的供电需求
- TX与RX要交叉接(很好理解)
- 若只需单工通讯,只接一根数据线即可
- 若电平标准不一致,需要加装电平转换芯片
附:串口常用的电平标准
- TTL电平:+3.3V或+5V表示1,0V表示0(一般用于单片机等小型设备)
TX对地为3.3V,则为逻辑1;对地为0V,则为逻辑0 - RS232电平:-3 ~ -15V表示1,+3 ~ +15V表示0(用于大型设备,所需电压高)
- RS485电平:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号)(抗干扰能力强)
3、串口软件部分(串口时序、参数)
(1)串口参数
-
数据帧:每个字节的存放地,由起始位、数据位和停止位组成
数据位有8个代表一个字节的8位(还可能在最后有一个奇偶校验位) -
波特率:串口通信的速率(两个设备协调的统一通信速率)
-
起始位:标志一个数据帧的开始,固定为低电平(空闲状态为高电平,起始位产生下降沿,来告诉设备要开始发送数据了)
-
停止位:用于数据帧间隔,固定为高电平(为下一个起始位做准备,切换到高电平空闲状态)
-
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
-
比如:发送一个字节为0x0F,首先转化为二进制0000 1111,再低位先行:依次传入1111 000,可将整个过程理解为蛇进洞
-
校验位:根据数据位计算得来,用于判断数据传输是否出错,出错可选择丢弃或者重传
-
奇校验:发送方发送数据后,包括校验位在内的9个数据位会出现奇数个1
原为0000 1111,则校验位补1,使1的个数为奇数 原为0000 1110,则校验位补0,使1的个数为偶数
接收方接收数据后,验证数据位和校验位中1的个数
-
偶校验类似(保证1个数为偶数)
-
CRC校验法(循环冗余校验码)(网上查过,看不懂一点。。。)
-
(2)串口收发时序
开端位->数据位->校验位->中止位
- 开端位,为一位逻辑0;
- 数据位,可设为5-8位,由低位开端逐位发送;
- 奇偶校验位,为一位,能够省掉;
- 中止位,能够挑选1,1.5或2位,为逻辑1;
- 闲暇时刻为逻辑1
二、学习并驱动stm32串口外设,实现串口发送和接受
1、USART简介
-
很少在该串口中使用同步功能,故基本上与UART差不多
同步功能多了一个时钟输出而已,甚至不支持时钟输入。。(不支持两个USART之间的同步通信)
-
可以看作为两部分:接受和发送
- 将数据寄存器中的一个字节的数据自动生成数据帧时序,从TX引脚发送出去
- 自动从RX引脚接收数据帧时序拼接成一个字节数据,存放于数据寄存器
-
自带波特率发生器最高达4.5Mbits/s(最常用:9600,115200)
-
可配置数据位(8,常用/9)和停止位长度(0.5/1,常用/1.5/2)
-
可选校验位(无校验、常用/奇校验/偶校验)
-
持同步模式、硬件流控制(控制数据流量)、DMA、智能卡、IrDA(红外通信)、LIN(后三者属于其他协议)
-
stm32F103C8T6上共有3根USART的挂载线
2、USART结构
(1)串口数据的收发过程
- 发送(TDR)/接收(RDR)数据寄存器:只读或只写
- 发送移位寄存器:把字节的数据一位一位地移出去
在发送移位寄存器中,要先等未发送的字节全部发送完毕,才能继续接入数据寄存器中的字节 - 接收移位寄存器:一位一位接收,全部接收完成后一起进入数据寄存器中(也就是发送的逆过程,比较好理解)
(2)串口的控制系统
-
发送器控制、接收器控制(顾名思义,很好理解)
-
硬件流控管脚:控制数据流流入速率(一般不用)
- nRTS:请求发送,为输出脚,接到对面的nCTS(告诉别人我能不能收)
- nCTS:清除发送,是输入脚,接到外部设备的RTS用于接收别人nRTS的信号(查看别人是否可以接收)
-
时钟引脚SCLK:产生同步的时钟信号,配合发送移位寄存器(一般不用)
发送寄存器每移位一次,同步时钟电平就跳变一个周期 -
唤醒单元:实现串口挂载多设备(一般不用)
在USART地址处给串口分配一个地址
当发送指定地址时此设备唤醒开始工作;
当发送别的设备地址时别的设备工作,没收到地址的设备不唤醒保持沉默 -
中断(输出)控制:TXE(发送寄存器空)和RXNE(接收寄存器非空)较重要
用于判断发送、接收状态的必要标志位
(3)波特率发生器
-
波特率发生器其实就是分频器
-
USART1挂载在APB2上(72MHz),其他的USART都挂载在APB1上(36MHz)
3、USART对应引脚
引脚模式 | USART1 | USART2 | USART3 |
---|---|---|---|
TX | PA9 | PA2 | PB10 |
RX | PA10 | PA3 | PB11 |
4、一些细节问题
(1)数据帧
- 8位字长也可以设置有无校验位,一般为了发送完整字节都选择无校验。9位字长常选择有校验
- 可配置停止位长度为0.5,1,1.5,2四种,本质是时长不同,一般选择1位
(2)输入数据策略
- 起始位侦测:
输入电路对采样时钟进行了细分,会以波特率的16倍频率进行采样,即在1位的时间里进行16次采样 - 数据采样
由于起始位侦测已经对齐了采样时钟,所以这里就直接在第8、9、10次采样数据位(为了保证数据可靠性连续采样3次)
5、串口的发送
(1)初始化流程
- 开启时钟:将USART和GPIO的时钟打开
- GPIO初始化:TX配置为复用输出,RX配置为输入
- 配置USART:直接使用一个结构体,即可配置所有参数
- 只发送:开启USART即可
还要接收的话:可能还要配置中断,需要在开启USART之前,再加上ITConfig和NVIC的代码
(2)认识库函数
USART使用函数
USART_DeInit(USARTx);
//将USART寄存器重置为默认值
USART_Init(USARTx,&USART_InitStruct);
//根据结构体的参数配置来对USARTx外设进行初始化
USART_StructInit(USART_InitStruct);
//将USART_InitStructure结构体初始化
USART_ClockInit(USARTx, USART_ClockInitStruct)
void USART_ClockStructInit(USART_ClockInitStruct)
//用于时钟输出(不常用)
USART_Cmd(USARTx, NewState)
//使能或者失能USART外设
//eg:USART_Cmd(USART1 , ENABLE)
USART_ITConfig(USARTx,USART_IT,NewState)
//配置指定的USART中断
//eg:USART_ITConfig(USART1 , USART_IT_RXNE , ENABLE)
USART_DMACmd(USARTx,USART_DMAReq, NewState)
//使能或者失能USART的DMA请求
//eg:USART_DMACmd(USART1 , USART_DMAReq_Tx , ENABLE)
USART_SendData(USARTx, uint16_t Data)
//通过USARTx外设传输单个字节数据
uint16_t USART_ReceiveData(USARTx)
//返回由USARTx外设接收的最新数据
USART结构体初始化函数
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//流控
USART_InitStructure.USART_Mode = USART_Mode_Tx;//串口模式
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位(ODD奇校验,EVEN偶校验)
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长
USART_Init(USART1,&USART_InitStructure);
USART_Cmd(USART1,ENABLE);
发送函数的书写
void Serial_SendByte(uint8_t Byte){
USART_SendData(USART1, Byte);
//将Byte变量写入TDR
//需要等待数据进入移位寄存器,所以需要等待一下标志位置1
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
//即等待发送数据寄存器空标志位变为SET(置1后)
//此时也不需要手动清零标志位,下一次SendData时会自动清零
}
(3)代码书写注意
- USART1是APB2的外设,其他都是APB1的(APB2性能高于APB1)
- TX引脚是USART外设控制的输出脚,应选用复用推挽输出模式
RX引脚是USART外设数据输入脚,应选择输入模式(一般浮空或上拉)
(4)不同数据模式
- HEX模式/十六进制模式/二进制模式:以原始数据形式显示(显示一个个十六进制数)
- 文本模式/字符模式:显示数据编码后的形式
eg:发送0x41,HEX模式显示41,文本模式显示为A(ASCII码)
先实现下发送吧。。。
(这里的发送案例是发送单个字节)
- serial.c
- main.c
接上(库函数认识)
(5)发送数组
void Serial_SendArray(uint8_t Array[], uint16_t Length){
uint16_t i;
for (i = 0; i < Length ; i ++){
Serial_SendByte(Array[i]);
}
}
(6)发送字符串
void Serial_SendString(char String[]){
uint8_t i;
for (i = 0; String[i] != 0;i++){
Serial_SendByte(String[i]);
}
}
(7)发送一个数字
void Serial_SendNumber(uint32_t Number,uint8_t Length){
uint8_t i;
for (i = 0 ; i < Length ; i++){
Serial_SendByte(Number / SerialPow(10,Length - i - 1) % 10 + '0');
}
}
(8)对printf()函数重定向
int fputc(int ch, FILE *f){
Serial_SendByte(ch);
return ch;
}
(9)对sprintf()函数的封装
void Serial_Printf(char *format, ...){
char String[100];
va_list arg;//定义一个参数列表变量
va_start(arg, format);//从format位置开始接受参数表,放在arg中
vsprintf(String, format, arg);//打印位置String,格式化字符串format,参数列表arg
va_end(arg);//释放参数表
Serial_SendString(String);//把String发送出去
}
6、串口的接收
(1)利用查询接收数据
- 流程:主函数中不断查询标志位(若置1,则说明收到数据)->调用recievedata读取DR即可
- 这里我还是将函数进行了封装
void Serial_RecieveByte(uint8_t Byte){
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){
Byte = USART_ReceiveData(USART1);
OLED_ShowHexNum(1, 1, Byte, 2);
}
}
(2)利用中断接收数据
(这里博主还没学,因为这个小项目暂时用不到)
三、学习python串口通信,⽤python实现串口发送和接受
1、为了使程序更简洁,我写了一个PC13.c和PC13.h方便函数调用
- 这里参照的是LED.c和LED.h进行的书写,通过书写可进一步熟悉相关函数的调用
2、主函数的书写及遇到的问题
- 关于不用中断读取DR中的值:使用输入寄存器标志位判断
- 由USART_RecieveData()函数读取的值是ASK码,因此判断时应当使用0/1/2对应的ASK码作为依据
- 注意OLED相关函数输出的进制数
3、利用python作为串口助手进行收发
-
关于python的串口通信可以参照 Python应用开发——串口通信
-
链接里有源码哦
-
串口发送
-
串口接收
-
剩下的就很简单啦,于是乎就有了:
芜湖!!任务二也完成辣!是不是很开心呢┗( ▔, ▔ )┛
接下来登场的是手势识别数字,期待与你的再次相遇!
特别声明:以上的图片部分来自于网络,感谢CSDN、知乎等平台上各位博主的分享,本文用作交流学习予以引用,在此一并表示感谢!