目录
介绍
通信
USART
基本结构
串口硬件电路
串口帧结构
串口发送数据
串口接收数据
串口常用寄存器
状态寄存器(USART_SR)
重映射
其他
相关代码
通用
串口接收数据
介绍
一种异步通信接口,常用于设备间的数据传输。其核心特点是逐位(bit-by-bit)传输,通过TX(发送)和RX(接收)
通信
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

注意:差分信号抗干扰能力强,适合长距离传输
USART
基本结构

时钟频率一般为72MHZ
波特率:每秒钟最多传输多少位
常用的波特率:9600、115200、921600
移位寄存器移位寄存器是数字电路中实现 “并行→串行” 或 “串行→并行” 数据转换,就是在CPU 输出的并行字节,拆解成串行比特流
串口硬件电路

简单双向串口通信有两根通信线(发送端TX和接收端RX)
TX与RX要交叉连接
当只需单向的数据传输时,可以只接一根通信线当电平标准不一致时,需要加电平转换芯片
电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,0V表示0
RS232电平:-3~-15V表示1,+3~+15V表示0
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
串口帧结构

流程:空闲时为高电平,在发送数据时先发送低电平(起始位) ,高电平表示1,低电平表示0并且一个字节被拆分成二进制然后从最低位开始传输,数据位传输完成后,发送 高电平,作为一帧数据的 “结束信号”,告诉接收端 “这一帧数据已发完”(注意:二进制是从低位开始发送,不是从高位)。
其中7/8为检验位
1.奇校验 如果0-6/7有四个1那么校验位D7就变成1 变成奇数
2.偶校验 如果0-6/7有三个1那么校验位D7就变成1 变成偶数
3.无校验
并且数据位可以有8位,和9位,其中8位为默认,因为计算机和 MCU 的基本数据单位是 “字节”(1 字节 = 8 位),8 位可以完整传输一个字节的二进制数据,无需拆分或截断。9 位数据位在多机串口组网场景中非常实用,核心用途是用第 9 位作为 “地址标识位”,区分 “地址帧” 和 “数据帧”
停止位
长度可配置为 0.5 位、1 位、1.5 位或 2 位,常用的是 1 位
串口发送数据

串口接收数据

串口常用寄存器
状态寄存器
(USART_SR) 、
数据寄存器(USART_DR)、波特比率寄存器
(USART_BRR) 、
控制寄存器 1(USART_CR1) 等,其中状态寄存器
(USART_SR)常用到串口发送
状态寄存器(USART_SR)

CTS :用于硬件流控制,当 CTS 引脚为低电平时,该位置 1,表示接收方允许发送数据;高电平时置 0,需停止发送。
TXE:当发送数据寄存器(USART_DR)中的数据被移位寄存器取走后,该位置 1,表示 “发送寄存器已空,可以写入下一个字节”,会自动清0
TC:当一帧数据(包括起始位、数据位、停止位)完全发送完毕后,该位置 1,表示 “当前字节已完整发出”。可以由软件自动清0也可以通过写入’0’来清除,只有在多缓存通讯中才推荐这种清除程序
RXNE:当接收数据寄存器(USART_DR)中有新数据时置 1,表示 “可以读取接收到的字节”,是接收中断的核心触发源。
ORE:当接收缓冲区已满但又收到新数据时置 1,表示 “数据溢出丢失”,需处理错误(如清空缓冲区、重新通信) 可使用硬件流控制解决
NE:检测到接收数据中有噪声干扰时置 1,用于判断数据是否受电磁干扰影响
FE:检测到接收数据的帧格式错误时置 1(比如停止位不是高电平),表示 “帧同步失败”
PE:当启用奇偶校验时,检测到数据校验不匹配时置 1,表示 “校验错误,数据可能损坏”
重映射


查看引脚定义表可以发现 不仅PA9和PA10有USART的引脚接口,PB6和PB7也有,如果PA9和PA10被占用,就可以使用PB6和PB7引脚使能USART。
相关代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);// 使能AFIO模块的时钟
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE); // USART1的重映射开启
其他

同步模式:在全双工的基础上增加CK线,用于传输时钟信号,相当于双向发送信息时每发一个数据进行一下相应时间的更新,确定单位时间从而可以把两个设备同步起来

硬件流控:在标准的基础上增加了CTS和RTS 并交叉接入,核心是接收方通过硬件引脚 “主动告诉” 发送方 ——“我处理完了,你继续发”/“我忙不过来,你先停”,解决 “发送方速率> 接收方处理能力” 导致的数据丢失(也可也是使用DMA转运,将数据存到内存缓冲器中)
相关代码
通用
初始化
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//USART初始化
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 9600;//波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
USART_InitStruct.USART_Mode = USART_Mode_Tx;//TX发送使能 RX为接收使能
USART_InitStruct.USART_Parity = USART_Parity_No;//无校验 奇、偶、无
USART_InitStruct.USART_StopBits = 1;//停止位
USART_InitStruct.USART_WordLength = 8;//帧传输或帧接收的位数,相当于数据位
USART_Init(USART1, &USART_InitStruct );
开启USART
USART_Cmd(USART1,ENABLE);
串口接收数据
SendByte(发送字符)
void Serial_SendByte(uint16_t Byte){
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//等待发送数据寄存器为空
USART_SendData(USART1,Byte);//发送数据到串口
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);//等待 USART1 串口当前字节完全发送完成
}
SendArray(发送数组)
数组传参时会自动退化为指针,因此Array[i]等价于*(Array+i),故可以访问数组的第i个元素
void Serial_SendArray(uint16_t *Array,uint8_t Length){
for(uint16_t i = 0;i<Length;i++){
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//等待发送数据寄存器为空
USART_SendData(USART1,Array[i]);//通过for循环依次发送数据到串口
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);//等待 USART1 串口当前字节完全发送完成
}
}
SendString(发送字符串)
void Serial_SendString(char *String){
for(uint16_t i = 0;i!='\\0';i++){
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
USART_SendData(USART1,String[i]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
}
}
SendNum(发送数字)
发送数字就相当于在电脑上先转成字符然后再依次打印字符串
例如:
123 就先要拆分成 1 2 3 ,然后使用SendByte逐一发送
123
百位—-123/10^2%10 = 1 十位 123/10^1%10 = 2 个位123/10^0%10 =3
依次打印百位、十位、个位
12345
万位 12345/10^4%10 =1 千位12345/1^3%10 = 2 百位 12345/10^2%10 = 3
十位 12345/10^1%10 = 4 个位 12345/10^0%10 = 5
依次打印万位、千位、百位、十位、个位
//算10的n次方
uint32_t Num_Pow(uint32_t Num,uint16_t Length){
uint32_t result = 1;
for(int16_t i = 0;i<Length;i++){
result*=Num;
}
return result;
}
void Serial_SendNum(uint32_t Num,uint8_t Length){
for(int16_t i = Length;i>=0;i–){
Serial_SendByte(Num/Num_Pow(10,i)%10 + '0');//如果没有+ '0',根据ASCII表发送到串口的数据不会是正确的数据
}
}
如果想使用printf()函数
1.需要先打开魔术棒并且勾选Use MicroLIB


2.对fputc进行重定向到串口(固定流程)
由于printf()函数默认输出到屏幕,但是单片机没有屏幕,所以需要重定向,并且还要添加头文件#include<stdio.h>
int fputc(int ch,FILE *f){
Serial_SendByte((uint16_t)ch);
return ch;
}
接收
在通用配置中,要将RX和TX使能,并且还要配置PA10接收串口的引脚
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz ;
1.通过NVIC开启中断进行接收
在开始的通用配置中添加中断再添加NVIC的初始化和中断服务函数
//开启中断
USART_ITConfig( USART1,USART_IT_RXNE, ENABLE);
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//·Ö×é
//NVIC初始化
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStruct);
中断服务函数
void USART1_IRQHandler(void){
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) ==SET){
uint32_t Serial_RXData = USART_ReceiveData(USART1);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除NXNE标志位,会自动清除,可以不写
}
2.判断RXNE是否为1,如果为1则调用ReceiveData(),读取DR寄存器
主函数
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) ==SET){
RXData = USART_ReceiveData(USART1);
}

