USART串口通信
一、串口物理层结构
1.1 RS232
RS232标准串口一般用于工业控制领域,在使用该标准时,数据传输使用该逻辑电平,在发送或接收到此电平时,会利用电平转换芯片将232电平转换为标准的TTL电平,最后传入到控制器当中处理。
1.2 原生的串口到串口通信(TTL to TTL)
两个设备之间直接使用TTL逻辑电平进行通信,不需要经过电平转换芯片转换电平,主要是控制器和串口设备或者传感器通信。
1.3 USB转TTL通信
单片机与电脑之间进行通信就需要USB转串口。
USB接口电源VCC是5V,一般而言为USB设备提供的最大电流为500mA。
其中D+和D-两根数据线在+400mV~-400mV之间变化,数据线传输的信号为差分信号:
- 传输高电平时:D+为高电平,D-为低电平
- 传输低电平时:D+为低电平,D-为高电平
二、STM32串口数据处理
2.1串口通信当中的数据处理
(图源自STM32F10x中文编程手册)
以发送字符“A”为例:
当要发送数据时,将字符“A”发送到发送数据寄存器(该寄存器只能写不能读),再将发送数据寄存器当中的"A"放入发送移位寄存器当中。通过添加起始位停止位等数据构成一个完整的数据帧,最后通过引脚将数据发送出去。当“A”送到移位寄存器时,发送数据寄存器没有数据,就可以将接下来的字符“B”放入到发送寄存器,以此类推就可以连续发送了,数据接收同理。
硬件数据“流控制”:
数据在两个串口之间传输常常会出现数据丢失的现象,或者两台计算机之间的处理速度不同时,接收端的缓冲区已满,则此时继续发送来的数据就会丢失。
当接收端的数据处理不过来时,就可以发出“不再接收”的信号,此时发送端就停止发送,直到接收端发出“可继续发送”信号时再发送数据。因此流控制可以控制两个串口之间的数据传输进程。PC中常用的两种流控制有硬件流控制(RTS/CTS、DTR/CTS)和软件流控制(xon/xoff(继续/停止))
2.2串口数据帧的基本组成
起始位:由1个逻辑0的数据位表示 有效数据位:8位 校验位:(可选)为的是数据的抗干扰性 停止位:由0.5、1、1.5或2个逻辑1的数据位表示
三、STM32串口编程
3.1 USART的代码实现:
1、在STM32F103C8T6上,USART1的GPIO管脚是在PA9和PA10上,可以从开发板的原理图可以找到:
2、USART1的时钟线也是在APB2,和GPIO在一条时钟总线上。(中文编程手册)
3、对串口数据收发的两个GPIO口初始化,可通过中文参考手册,设置对应的模式:
4、调用USART_Init();函数对串口进行初始化。
5、如果是主动发送数据的情况就不需要使用中断,如果是在接收到数据的情况下,就可以使用中断来处理受到数据的情况或处理接收到的数据
6、调用USART_Cmd();函数使能串口
7、清除发送完成标志位(TXE发送数据寄存器空/TC发送完成) USART发送端有两个寄存器,分别是USART_DR寄存器,另一个是移位寄存器。对应到USART数据发送有
两个标志:TXE发送数据寄存器空/TC发送完成
TC即Transmission Complete,发送完成。每发送完一个字节,该标志位会自动为1,如果有开中断的话,当这个标志位为1就会进入中断,也可以通过查询该标志位来查询是否发送完成。
所以当发送下一个字节时,就需要将这个标志位清零。单片机复位后,该标志位为1,所以需要手动清零,避免还没发送数据单片机就误以为发完,这样就有可能丢失数据。
调用USART_SendData();函数发送数据,并判断TC标志位是否为1来判断数据是否发送完成
(软件复位:先读取串口的状态寄存器,再写入数据寄存器)
根据程序理解:
发送完数据后读取标志位(读取状态寄存器),在下一次调用函数时,再次发送数据(写入数据寄存器)如此往复,这样单片机就会自动将TC清零。
#include "Usart.h"
void Usart_Init() {
GPIO_InitTypeDef GPIO_InitStruct_PA9;
GPIO_InitTypeDef GPIO_InitStruct_PA10;
//打开GPIO和串口时钟
//有些情况下使用复用功能还需要打开复用时钟(RCC_APB2Periph_AFIO):
//AFIO的事件控制寄存器、AFIO的重映射功能以及外部中断(EXTI)控制寄存器
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
//设置PA9,串口的发送引脚
GPIO_InitStruct_PA9.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct_PA9.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct_PA9.GPIO_Speed = GPIO_Speed_50MHz;
//设置PA10,串口的接收引脚
GPIO_InitStruct_PA10.GPIO_Mode = GPIO_Mode_IPU; //带上拉输入或浮空输入
GPIO_InitStruct_PA10.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct_PA10.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO初始化
GPIO_Init(GPIOA, &GPIO_InitStruct_PA9);
GPIO_Init(GPIOA, &GPIO_InitStruct_PA10);
USART_InitTypeDef SART_InitStruct;
SART_InitStruct.USART_BaudRate = 115200; //一般取1200整数倍
SART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //设置硬件流控制
SART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //同时开启发送和接收
SART_InitStruct.USART_Parity = USART_Parity_No; //设置串口数据校验位
SART_InitStruct.USART_StopBits = USART_StopBits_1; //设置停止位
SART_InitStruct.USART_WordLength = USART_WordLength_8b; //设置串口数据的字长
//USART1串口初始化
USART_Init(USART1, &SART_InitStruct);
//串口使能
USART_Cmd(USART1, ENABLE);
//清除标志位
USART_ClearFlag(USART1, USART_FLAG_TC);
}
//发送数据函数
void USART_SentByte(USART_TypeDef* USARTx, u8 Data) {
//发送串口数据
USART_SendData(USARTx, (u16)Data);
//获取标志位判断数据是否发送完成
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == 0);
}
//发送字符串函数
void USART_SentString(USART_TypeDef* USARTx, u8 *Str) {
u32 pos = 0;
while(*(Str+pos) != '\0') {
USART_SentByte(USARTx, *(Str+pos));
pos++;
}
}
四、STM32串口接收
串口接收需要用到中断来处理受到数据的情况或处理接收到的数据,因此需要做中断的配置。
4.1 设置中断分组
使用中断需要对中断进行分组,因此需要使用中断分组函数,在设置完中断分组后,所有中断都会依照设置的分组来运行。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
不同的分组其子优先级也不同,中断的优先级一共用4位二进制数来表示,4位二进制数一共有0-15种表示方式,将这16种表示方式分为0-4共5组。
- 主优先级也称为抢占优先级,子优先级也称为次占优先级(响应优先级)
- 分组值决定他们的取值范围,取值越小级别越高
- 优先级分组决定中断的取值范围,取值越小级别越高
- 主优先级高(值越小级别越高)的中断可以打断低优先级的中断
- 相同优先级中断不能相互打断,这时候才考虑子优先级,子优先级高的中断优先响应
总结:先用优先级分组为中断们分配中断数量,主优先级相同时不能相互打断,主优先级相同的中断同时请求时,用子优先级判断。
4.2 调用函数设置串口的中断优先级
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //中断通道号没有在函数定义中,可以在stm32f10x.h中找到,搜索“IRQn”,查找串口的中断通道号
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
//中断优先级的初始化
NVIC_Init(&NVIC_InitStruct);
4.3 打开串口中断
//打开串口中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
这里串口有很多中断,函数的第二个参数是选择打开串口中断类型,我们选择的是USART_IT_RXNE,即串口接收中断。
4.4 设置串口中断处理函数
#define size 256
u8 USART1_Word[size];
u16 pos = 0;
//该函数在启动文件当中查找,负责接收到数据后进入中断并存储数据
void USART1_IRQHandler(void) {
u8 Rdata;
//检查中断标志位以判断是否进入到串口接收中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) == 1) {
Rdata = (u8)USART_ReceiveData(USART1);
USART1_Word[pos++] = Rdata;
//清除中断标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
中断的处理函数都是固件库内有定义好的,可以直接到单片机的启动文件当中查询。
这里判断串口接收标志位来判断是否真的有进入中断,然后再用串口接收数据函数来处理接收的数据,最后用字符串数组来存储数据。
五、串口字符串收发
通过接收时间的间隔长短来判断字符串是否接收完成。
一个字符占八个位,当串口在发送一个字符时,除了发送字符数据,还需要加上起始位和停止位来构成一个完整的数据帧,8+2=10个位
除以设置的115200可以得到发送这一个字符所需要的时间
由此我们可以设置一个10ms以内的定时器,由计算可知接收一个字符的时间是0.087ms,如果在等待了10ms还没收到数据的话,就可以说明这整个字符串已经全部接收完成。
在中断处理函数中实现接收数据后将该数据重新发送出去:
//设置中断处理函数,在启动文件中查找
void USART1_IRQHandler() {
u16 Rdata;
//检查中断标志位以判断是否进入中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
Rdata = USART_ReceiveData(USART1); //接收收到的数据
UsartSendData(USART1, Rdata);
//清除中断标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
六、使用printf函数发送字符串
需要引入“stdio.h”头文件,并添加一下函数:
int fputc(int ch,FILE *f)
{
USART_SendData(USART1,(u16)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
return (ch);
}
七、串口功能的重映射
如果USART0对应的GPIO端口被占用作其他功能用,那么需要调用重映射函数将USART0的功能转移到其他的GPIO端口上
使用重映射功能还需要开启AFIO时钟
//串口重映射
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
//设置PB7,串口的发送引脚,全双工:推挽复用模式
GPIO_InitStruct_PA9.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct_PA9.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct_PA9.GPIO_Speed = GPIO_Speed_50MHz;
//设置PB8,串口的接收引脚,全双工:浮空输入或上拉输入
GPIO_InitStruct_PA10.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct_PA10.GPIO_Pin = GPIO_Pin_7;