【STM32学习笔记】串口通讯 — USART / UART
通讯的基本概念
- 串行通讯和并行通讯
特性 | 串行通讯 | 并行通讯 |
---|---|---|
通讯距离 | 较远 | 较近 |
抗干扰能力 | 较强 | 较弱 |
传输速率 | 较慢 | 较高 |
成本 | 较低 | 较高 |
- 全双工、半双工及单工通讯
通讯方式 | 说明 |
---|---|
全双工 | 在同一时刻,两个设备之间可以同时收发数据 |
半双工 | 两个设备之间可以收发数据,但不能在同一时刻进行 |
单工 | 在任何时刻都只能进行一个方向的通讯,即一个固定为发送设备,另一个固定为接收设备 |
- 同步通讯与异步通讯
在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据。通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。
在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据。某些通讯中还需要双方约定数据的传输速率,以便更好地同步。
在同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小,而异步通讯双方的时钟允许误差较大。
- 通讯速率
Bitrate—比特率:每秒钟传输的二进制位数,单位为比特每秒(bit/s) 。(I2C、SPI)(同步)
Baudrate—波特率:表示每秒钟传输的码元个数。(USART)(异步)
一般一个码元用一个二进制表示,如:
但是当码元有多个时,需要多位的二进制表示,如:
串口通讯协议简介
对于通讯协议,我们以分层的方式来理解,分为物理层和协议层。
物理层
规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。(硬件)
RS-232标准
- RS232标准串口主要用于工业设备直接通信
- 电平转换芯片一般有MAX3232,SP3232
- DB9接口(COM口) 标准的公头及母头接法
USB转串口
- USB转串口主要用于设备跟电脑通信
- 电平转换芯片一般有CH340、PL2303、CP2102、FT232
- 使用的时候电脑端需要安装电平转换芯片的驱动
原生的串口到串口
- 原生的串口通信主要是控制器跟串口的设备或者传感器通信,不需要经过电平转换芯片来转换电平,直接就用TTL电平通信
- GPS模块、GSM模块、串口转WIFI模块、HC04蓝牙模块
TTL和232的区别
电平标准不同
TTL电平一般是芯片里面出来的电平
通讯标准 | 电平标准(发送端) |
---|---|
5V TTL | 逻辑1:2.4V ~ 5V; 逻辑0:0 ~ 0.5V |
RS-232 | 逻辑1:-15V ~ -3V ;逻辑0:+3V ~ +15V(增加抗干扰能力) |
协议层
主要规定通讯逻辑,统一收发双方的数据打包、解包标准。(软件)
串口数据包的基本组成
-
波特率
本章中主要讲解的是串口异步通讯,异步通讯中由于没有时钟信号(如前面讲解的DB9接口中是没有时钟信号的),所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码。上图中用虚线分开的每一格就是代表一个码元。常见的波特率为4800、9600、115200 等。
-
通讯的起始和停止信号
数据包的起始信号由一个逻辑0 的数据位表示,而数据包的停止信号可由0.5、1、1.5 或2 个逻辑1 的数据位表示,只要双方约定一致即可。
-
有效数据
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为5、6、7 或8 位长。
-
数据校验(可选)
由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、0 校验(space)、1 校验(mark)以及无校验(noparity)。
简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。
STM32—USART / UART
简介
STM32有5个串口,其中有三个通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter),简称USART(USART1、USART2、USART3),两个通用异步收发器(Universal Asynchronous Receiver and Transmitter),简称UART(UART4、UART5)。USART1在APB2上,计算波特率的时候,fck 为72MHZ,其他的在APB1上,计算波特率的时候,fck 为36MHZ。
USART 功能框图
功能引脚
TX: 发送数据输出引脚。
RX: 接收数据输入引脚。
SW_RX: 数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS: 请求以发送(Request To Send),n 表示低电平有效。如果使能RTS 流控制,当USART 接收器准备好接收新数据时就会将nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制。
nCTS: 清除以发送(Clear To Send),n 表示低电平有效。如果使能CTS 流控制,发送器在发送下一帧数据之前会检测nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
SCLK: 发送器时钟输出引脚。这个引脚仅适用于同步模式。
USART 引脚在STM32F103ZET6 芯片具体分布见下表:
引脚 | APB2 总线 | APB1 总线 | APB1 总线 | APB1 总线 | APB1 总线 |
---|---|---|---|---|---|
USART1 | USART2 | USART3 | UART4 | UART5 | |
TX | PA9 | PA2 | PB10 | PC10 | PC12 |
RX | PA10 | PA3 | PB11 | PC11 | PD2 |
SCLK | PA8 | PA4 | PB12 | ||
nCTS | PA11 | PA0 | PB13 | ||
nRTS | PA12 | PA1 | PB14 |
UART 只是异步传输功能,所以没有SCLK、nCTS 和nRTS 功能引脚。
数据寄存器
USART 数据寄存器(USART_DR)只有低9 位有效,并且第9 位数据是否有效要取决于USART 控制寄存器1(USART_CR1)的M位设置,当M位为0 时表示8 位数据字长,当M位为1 表示9 位数据字长,我们一般使用8 位数据字长。
USART_DR 包含了已发送的数据或者接收到的数据。USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写TDR,一个专门用于接收的可读RDR。当进行发送操作时,往USART_DR 写入数据会自动存储在TDR 内;当进行读取操作时,向USART_DR读取数据会自动提取RDR 数据。
TDR 和RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到RDR。
控制寄存器 和 状态寄存器
控制寄存器有三个,为USART_CR1、USART_CR2、USART_CR3,对于每一位的功能可查看STM32F10x-参考手册第25章第6节。
为了便于理解,从发送和接受的过程对控制寄存器和状态寄存器的相关位功能进行学习
发送
- USART_CR1 寄存器的发送使能位TE 置1 时,启动数据发送。(发送移位寄存器的数据会在TX 引脚输出,低位在前,高位在后。如果是同步模式SCLK 也输出时钟信号。)当发送使能位TE 置1 之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),接下来就可以往USART_DR 寄存器写入要发送的数据。
-
停止位时间长短是可以通过USART 控制寄存器2(USART_CR2)的STOP[1:0]位控制。
- 状态寄存器(USART_SR) TXE 位:当TDR寄存器中的数据被硬件转移到移位寄存器的时候,该位被硬件置位。如果USART_CR1寄存器中的TXEIE为1,则产生中断。对USART_DR的写操作,将该位清零。
- 状态寄存器(USART_SR) TC 位 :当包含有数据的一帧发送完成后,并且TXE=1时,由硬件将该位置’1’。如果USART_CR1中的TCIE为’1’,则产生中断。由软件序列清除该位(先读USART_SR,然后写入USART_DR)。TC位也可以通过写入’0’来清除,只有在多缓存通讯中才推荐这种清除程序。
接收
- 控制寄存器1(USART_CR1) RE 位 置1,接收使能并开始搜寻RX引脚上的起始位。
- 状态寄存器(USART_SR) RXNE 位 :当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果USART_CR1寄存器中的RXNEIE为1,则产生中断。对USART_DR的读操作可以将该位清零。RXNE位也可以通过写入0来清除,只有在多缓存通讯中才推荐这种清除程序。
波特率生成
-
波特比率寄存器(USART_BRR)的位15:4为DIV_Mantissa[11:0]:USARTDIV的整数部分,这12位定义了USART分频器除法因子(USARTDIV)的整数部分。
-
波特比率寄存器(USART_BRR)的位15:4为DIV_Fraction[3:0]:USARTDIV的小数部分,这4位定义了USART分频器除法因子(USARTDIV)的小数部分。
-
波特率计算公式:
其中,fCK 为USART 时钟, USARTDIV 是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数。波特率越大,传输速率越快。
例如: DIV_Mantissa=24(0x18) ,DIV_Fraction=10(0x0A) , 此时USART_BRR 值为0x18A;那么USARTDIV 的小数位10/16=0.625;整数位24,最终USARTDIV 的值为24.625。 -
求已知波特率的USARTDIV 值:
以USART1 为例,即fPLCK=72MHz。为得到115200bps 的波特率,此时:
解得USARTDIV=39.0625 , 可算得DIV_Fraction=0.0625*16=1=0x01 ,DIV_Mantissa=39=0x17,即应该设置USART_BRR 的值为0x1701。为什么DIV_Fraction=0.0625*16=1=0x01? 小数部分可以看成最大值为1,DIV_Fraction[3:0]有四位,当全为1时,为最大值16,所以DIV_Fraction[3:0]的精度为1/16。所以,在DIV_Fraction[3:0]中,当一个十进制数转二进制时,让十进制数除以 1/16 即可。
校验控制
当使用校验位时,串口传输的长度将是 8 位的数据帧加上 1 位的校验位总共 9 位,此时USART_CR1 寄存器的M位需要设置为 1,即 9 位数据位。
相关寄存器位:
控制寄存器1(USART_CR1)PCE位:检验控制使能,软件对它置’1’或清’0’。一旦设置了该位,当前字节传输完成后,校验控制才生效。0:禁止校验控制;1:使能校验控制。
控制寄存器1(USART_CR1)PS位:校验选择,当校验控制使能后,该位用来选择是采用偶校验还是奇校验。软件对它置’1’或清’0’。当前字节传输完成后,该选择生效。0:偶校验;1:奇校验。
控制寄存器1(USART_CR1)PEIE位:PE中断使能,该位由软件设置或清除。0:禁止产生中断;1:当USART_SR中的PE为’1’时,产生USART中断。
使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧+校验位+停止位。
中断控制
USART 有多个中断请求事件,具体见下表:
中断事件 | 事件标志 | 使能控制位 |
---|---|---|
发送数据寄存器为空 | TXE | TXEIE |
CTS 标志 | CTS | CTSIE |
发送完成 | TC | TCIE |
准备好读取接收到的数据 | RXNE | RXNEIE |
检测到上溢错误 | ORE | RXNEIE |
检测到空闲线路 | IDLE | IDLEIE |
奇偶校验错误 | PE | PEIE |
断路标志 | LBD | LBDIE |
多缓冲通信中的噪声标志、上溢错误和帧错误 | NF/ORE/FE | EIE |
编程代码
USART 初始化结构体详解
USART 初始化结构体
typedef struct {
uint32_t USART_BaudRate; // 波特率
uint16_t USART_WordLength; // 字长
uint16_t USART_StopBits; // 停止位
uint16_t USART_Parity; // 校验位
uint16_t USART_Mode; // USART 模式
uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;
- USART_BaudRate:波特率设置。一般设置为2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV 值,从而设置USART_BRR 寄存器值。
- USART_WordLength:数据帧字长,可选8 位或9 位。它设定USART_CR1 寄存器的M 位的值。如果没有使能奇偶校验控制,一般使用8 数据位;如果使能了奇偶校验则一般设置为9 数据位。
- USART_StopBits:停止位设置,可选0.5 个、1 个、1.5 个和2 个停止位,它设定USART_CR2 寄存器的STOP[1:0]位的值,一般我们选择1 个停止位
- USART_Parity : 奇偶校验控制选择, 可选USART_Parity_No( 无校验) 、USART_Parity_Even( 偶校验) 以及USART_Parity_Odd( 奇校验) , 它设定USART_CR1 寄存器的PCE 位和PS 位的值。
- USART_Mode:USART 模式选择,有USART_Mode_Rx 和USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定USART_CR1 寄存器的RE 位和TE 位的值。
- USART_HardwareFlowControl:硬件流控制选择,可选有使能RTS、使能CTS、同时使能RTS 和CTS、不使能硬件流。只有在硬件流控制模式才有效
USART 时钟初始化结构体
该结构体内容也只有在同步模式才需要设置。
typedef struct {
uint16_t USART_Clock; // 时钟使能控制
uint16_t USART_CPOL; // 时钟极性
uint16_t USART_CPHA; // 时钟相位
uint16_t USART_LastBit; // 最尾位时钟脉冲
} USART_ClockInitTypeDef;
-
USART_Clock:同步模式下SCLK 引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要开启时钟。它设定USART_CR2 寄存器的CLKEN 位的值。
-
USART_CPOL:同步模式下SCLK 引脚上输出时钟极性设置,可设置在空闲时SCLK 引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定USART_CR2 寄存器的CPOL 位的值。
-
USART_CPHA:同步模式下SCLK 引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2 寄存器的CPHA 位的值。
USART_CPHA 与USART_CPOL 配合使用可以获得多种(四种)模式时钟关系。
-
USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在SCLK 引脚输出, 可以是不输出脉冲(USART_LastBit_Disable) 、输出脉冲(USART_LastBit_Enable)。它设定USART_CR2 寄存器的LBCL 位的值。
USART1 接发通信实验
初始化串口需要用到的GPIO
/* USART GPIO 引脚宏定义 */
#define DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
/*-------------------------- USART 相关参数配置 --------------------------*/
void USART_Config(void){
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/*** 初始化串口需要用到的GPIO ***/
/* 打开串口GPIO的时钟 */
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
/* 将USART Tx的GPIO配置为推挽复用模式 */
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
/* 将USART Rx的GPIO配置为浮空输入模式 */
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
}
配置 USART 的工作参数
/*-------------------------- USART 相关参数配置 --------------------------*/
void USART_Config(void){
/*** 配置 USART 的工作参数 ***/
/* 打开串口外设的时钟 */
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置一帧数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 配置校验位
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 配置工作模式,收发一起
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 配置硬件流控制
USART_Init(DEBUG_USARTx, &USART_InitStructure); // 完成串口的初始化配置
}
中断配置(接收中断、中断优先级)
/*------------------------ 配置 USARTx 中断优先级 -----------------------*/
static void NVIC_Configuration(void){
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 配置中断优先级分组
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ; // 配置中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 配置抢占优先级(主优先级)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 配置子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure);
}
/*-------------------------- USART 相关参数配置 --------------------------*/
void USART_Config(void){
/*** 中断配置 ***/
NVIC_Configuration(); // 串口中断优先级配置
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); // 使能串口接收中断
}
使能串口
/*-------------------------- USART 相关参数配置 --------------------------*/
void USART_Config(void){
/*** 使能串口 ***/
USART_Cmd(DEBUG_USARTx, ENABLE);
}
发送函数
/*-------------- 发送数据,由上位机(串口调试助手)接受并显示 -------------*/
/*-------------------- 注意:数据以 字符 形式传输 -----------------------*/
/* 发送一个字节的数据 */
void Usart_SendByte(USART_TypeDef* USARTx, uint8_t data){
USART_SendData(USARTx, data); // 发送一个字节数据到USART
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET ); // 等待发送数据寄存器为空
}
/* 发送两个字节的数据 */
void Usart_SendHalfWord(USART_TypeDef* USARTx, uint16_t data){
uint8_t temp_h, temp_l;
temp_h = (data & 0xff00) >> 8; // 将 data 的高八位赋值给 temp_h
temp_l = data & 0xff; // 将 data 的低八位赋值给 temp_l
USART_SendData(USARTx, temp_h); // 发送 data 的高八位 到USART
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET ); // 等待发送数据寄存器为空
USART_SendData(USARTx, temp_l); // 发送 data 的低八位 到USART
while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET ); // 等待发送数据寄存器为空
}
/* 发送八位数据的数组 */
void Usart_SendArray(USART_TypeDef* USARTx, uint8_t *arr, uint8_t num){
uint8_t i;
/* 循环发送 */
for(i=0 ; i<num ; i++){
/* 注意:
* 此处调用的是 void Usart_SendByte(USART_TypeDef* USARTx, uint8_t data) 函数
* 若调用 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) 函数
* 还需 while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET ) 保证一个8位的数据传完后,再传下一个数据
*/
Usart_SendByte(USARTx, arr[i]);
}
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET ); // 等待发送完毕
}
/* 发送字符串 */
void Usart_SendString(USART_TypeDef* USARTx, uint8_t *str){
/* 循环发送 */
do{
Usart_SendByte(USARTx, *(str++));
}while(*str != '\0'); // 当读到字符串的结束符时,停止循环
while( USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET ); // 等待发送完毕
}
/* 重定向c库函数printf到串口,重定向后可使用printf、putchar函数 */
int fputc(int ch, FILE *f){
USART_SendData(DEBUG_USARTx, (uint8_t)ch); // 发送一个字节数据到USART
while( USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET ); // 等待发送数据寄存器为空
return (ch);
}
接收的相关函数
/* 重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 */
int fgetc(FILE *f){
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET); // 等待串口输入数据
return (int)USART_ReceiveData(DEBUG_USARTx);
}
/* 中断服务函数 */
void DEBUG_USART_IRQHandler(void){
uint8_t temp;
/* 再次确认是否产生中断,避免错误的中断 */
if(USART_GetFlagStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET) {
temp = USART_ReceiveData(DEBUG_USARTx); // 将接收到的数据赋值给 temp
USART_SendData(DEBUG_USARTx, temp); // 将 temp 的值发送给上位机,使其显示
}
}
USART1 指令控制RGB 彩灯实验
bsp_usart.c 和 bsp_usart.h 中的相关配置和 USART1 接发通信实验中的一样。
通过字符控制RGB灯
不用配置中断
int main(){
uint8_t ch;
LED_GPIO_Config();
USART_Config();
while(1){
ch=getchar();
switch (ch){
case '1': LED_RED; printf("红灯亮\n"); break;
case '2': LED_GREEN; printf("绿灯亮\n"); break;
case '3': LED_BLUE; printf("蓝灯亮\n"); break;
default: ALL_OFF; printf("全部熄灭\n"); break;
}
}
}
通过16进制数据控制RGB灯
需要配置中断
/* 中断服务函数 */
void DEBUG_USART_IRQHandler(void){
/* 再次确认是否产生中断,避免错误的中断 */
if(USART_GetFlagStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET) {
temp = USART_ReceiveData(DEBUG_USARTx); // 将接收到的数据赋值给 temp
switch (temp){
case 0x01: LED_RED; printf("红灯亮\n"); break;
case 0x02: LED_GREEN; printf("绿灯亮\n"); break;
case 0x03: LED_BLUE; printf("蓝灯亮\n"); break;
default: ALL_OFF; printf("全部熄灭\n"); break;
}
}
}
本文地址:https://blog.csdn.net/Brave_Runer/article/details/108141262