一、前言
串口作为STM32的重要外设,对程序调试具有不可替代的作用。通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。其主要具备以下特性:
- 支持全双工的异步通信
- 支持LIN(局部互联网)
- 支持智能卡协议和IrDA(红外数据组织)SIR ENDEC规范
- 支持调制解调器(CTS/RTS)操作
- 支持多处理器通信
- 支持使用多缓冲器配置的DMA方式,实现高速数据通信。
STM32F103ZET6具备多达5路串口,本文将使用USART1(PA9/PA10)实现串口回环测试DEMO。
二、串口通信原理与时序
2.1 USART和UART的区别
STM32F103ZET6具备5个串口,其中串口1,2,3均为USART(通用同步异步收发传输器),串口4,5为UART(通用异步收发传输器)。他们的区别如下:
- 同步与异步:UART仅支持异步通信,数据在没有时钟信号的情况下进行传输,发送和接收双方必须在波特率等设置上保持一致;USART支持同步和异步两种通信模式。在同步模式下,它使用额外的时钟信号来同步数据的发送和接收。
- 信号引脚:USART1、USART2和USART3接口具有硬件的CTS和RTS信号管理、兼容ISO7816的智能卡模式和类SPI通信模式,除了UART5之外所有其他接口都可以使用DMA操作。
- 数据传输可靠性:UART数据传输仅依赖于预设的波特率,没有时钟信号,因此数据传输的可靠性可能受到干扰;USART在同步模式下,数据传输更加可靠,因为有时钟信号来确保数据的正确接收和发送。USART1接口通信速率可达4.5兆位/秒,其他接口的通信速率可达2.25兆位/秒。
2.2 串口通信的硬件组成
USART1由6根线组成,如下所示:
USART1线束 | 功能 |
---|---|
USART1_TX | 发送数据引脚 |
USART1_RX | 接受数据引脚 |
USART1_CK | 用于输出同步信号传输的时钟 |
USART1_CTS | 用于流量控制的清除发送引脚,若是高电平,在当前数据传输结束时阻断下一次的数据发送。 |
USART1_RTS | 用于流量控制的发送请求引脚,若是低电平,表明USART准备好接收数据。 |
GND | 接地 |
其中,同步模式下的CK引脚以及用于流量控制的CTS/RTS引脚不常用,而TX/RX/GND引脚是串口通信中不可或缺的,因此本文只关注这三个引脚。
2.3 串口通信的通讯方式
从传输数据的方向性和同时性上划分,串口通信主要被分为全双工、半双工和单工模式:
- 全双工模式:允许数据在两个通信设备之间同时双向传输。
- 半双工模式:数据可以在两个通信设备之间双向传输,但不能同时进行。
- 单工模式:数据只能单向传输,不能同时发送和接收。
STM32F103ZET6的USART1通讯方式是全双工的。
2.4 串口通信的时序要求和数据格式
串口通信的时序要求如下所示:
每一个字符帧都满足以下数据格式:
字符帧的位 | 含义 |
---|---|
起始位 | 每个数据帧的开始部分,通常是一个低电平信号。 |
数据位 | 实际传输的数据部分,可以是8位、7位或9位,具体取决于配置。每个位依次从最低有效位(LSB)到最高有效位(MSB)传输(小端传输)。 |
校验位 | 在数据位后添加一个校验位,可以帮助检测数据在传输过程中是否发生了错误。校验方式有奇偶校验。 |
停止位 | 数据帧的结束部分,通常是一个高电平信号。一般来讲,停止位有1,1.5,2个单位时间三种长度。 |
空闲位 | 处于逻辑1状态, 表示当前线路上没有数据传送。 |
2.5 串口通信的奇校验和偶校验
在标准ASCII码中,其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。
2.6 串口通信的波特率
波特率(Baud Rate)是串行通信中衡量数据传输速度的一个重要参数,定义为每秒钟传输的符号(信号变化)次数,通常以bps(bits per second,位/秒)为单位。
三、时钟树配置
从《STM32中文参考手册》可知,USART1是归属APB2总线下的外设,其时钟来源为APB2的外设时钟PCLK2。APB2时钟来源于AHB时钟,AHB时钟来源于经锁相环PLL倍频后的外部8MHz高速时钟HSE。具体时钟树配置流程如下所示。
时钟树配置代码在正点原子官方例程SYSTEM/sys.c下实现:
void Stm32_Clock_Init(u8 PLL)
{
unsigned char temp=0;
MYRCC_DeInit(); //复位并配置向量表
RCC->CR|=0x00010000; //外部高速时钟使能HSEON
while(!(RCC->CR>>17));//等待外部时钟就绪
RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
PLL-=2; //抵消2个单位(因为是从2开始的,设置0就是2)
RCC->CFGR|=PLL<<18; //设置PLL值 2~16
RCC->CFGR|=1<<16; //PLLSRC ON
FLASH->ACR|=0x32; //FLASH 2个延时周期
RCC->CR|=0x01000000; //PLLON
while(!(RCC->CR>>25));//等待PLL锁定
RCC->CFGR|=0x00000002;//PLL作为系统时钟
while(temp!=0x02) //等待PLL作为系统时钟设置成功
{
temp=RCC->CFGR>>2;
temp&=0x03;
}
}
调用该函数时,函数传参PLL倍频为9, 最终串口1获得的基础时钟频率PCLK2为72MHz。
四、寄存器介绍
实现USART1的功能主要涉及以下寄存器:
寄存器 | 功能 |
---|---|
APB2ENR |
APB2 外设时钟使能寄存器
|
GPIOx_CRH
| 端口配置高寄存器 |
APB2RSTR |
APB2 外设复位寄存器
|
USART_BRR |
波特比率寄存器
|
USART_CR1 |
控制寄存器 1
|
USART_SR |
状态寄存器
|
USART_DR |
数据寄存器
|
下面将对这些寄存器进行一一介绍。
4.1 APB2ENR外设时钟使能寄存器
《STM32中文参考手册》对APB2ENR寄存器的描述如下:
毋庸置疑,本文需要将USART1时钟使能,即将位14置1。
除此以外,本文用到的串口1的TX和RX分别是PA9和PA10,因此需要使能IO端口A的时钟,即将位2置1。
4.2 GPIOx_CRH端口配置高寄存器
《STM32中文参考手册》对GPIOx_CRH寄存器的描述如下:
本文使用PA9和PA10作为串口1的功能端口,因此需要按照如下要求进行配置:
GPIO端口 | 复用功能 | 配置 | GPIO配置 |
---|---|---|---|
PA9 |
USART1_TX
| 全双工模式 | 推挽复用输出 |
PA10 | USART1_RX | 全双工模式 | 浮空输入或上拉输入 |
因此,需要按照如下进行配置:
GPIO端口 寄存器配置 PA9 GPIOA_CRH[7:6] = 0b10 GPIOA_CRH[5:4] = 0b11 PA10 GPIOA_CRH[11:10] = 0b10 GPIOA_CRH[9:8] = 0b00
4.3 APB2RSTR外设复位寄存器
《STM32中文参考手册》对APB2RSTR寄存器的描述如下:
本次DEMO仅需将USART1复位即可,故仅需将 APB2RSTR的第14位 置1 后再 置0 即可复位。
4.4 USART_BRR波特比率寄存器
《STM32中文参考手册》对USART_BRR寄存器的描述如下:
STM32F103ZET6的USART1波特率计算方法如下:
若设置USART1的波特率为115200bps,按照如下方式求得USART_BRR寄存器的值:
- 由第三节时钟树配置可知,USART1的时钟来源PCLK2为72MHz。
- USARTDIV=fPCLK2/(16*bound)=72000000/(16*115200)=39.0625
- 故DIV_Mantissa[11:0]=39=0X27
- 故DIV_Fraction[3:0]=0.0625*16=1=0X1
- USART_BRR寄存器的值为(DIV_Mantissa[11:0]<<4)|(DIV_Fraction[3:0])=0X0271
4.5 USART_SR状态寄存器
《STM32中文参考手册》对USART_SR寄存器的描述如下:
其中,仅重点关注TXE、TC和RXNE标志位。当发送寄存器为空时,TXE为1;当发送完成时,TC为1,;当读寄存器非空,即接收到数据时,RXNE为1。
4.6 USART_CR1控制寄存器 1
《STM32中文参考手册》对USART_CR1寄存器的描述如下:
其中,仅需重点关注以下几位:
标志位 | 功能 | 用法 |
---|---|---|
13:UE | 串口使能位 | 置1 |
12:M | 字长选择位,当该位为 0 的时候设置串口为 8 个字长外加 n 个停止
位,停止位的个数(n)是根据 USART_CR2 的[13:12]位设置来决定的,默认为 0。
| 置0 |
10:PCE |
校验使能位,设置为 0,则禁止校验,否则使能校验。
| 置0 |
9:PS |
校验位选择,设置为 0 则为偶校验,否则为奇校验
| / |
7:TXEIE |
发送缓冲区空中断使能位,设置该位为 1,当 USART_SR 中的 TXE 位为1 时,将产生串口中断。
| 置1 |
6:TCIE |
发送完成中断使能位,设置该位为 1,当 USART_SR 中的 TC 位为 1 时,将产生串口中断。
| 置1 |
5:RXNEIE |
RXNEIE 为接收缓冲区非空中断使能,设置该位为 1,当USART_SR 中的 ORE 或者 RXNE 位为1时,将产生串口中断。
| 置1 |
3:TE |
发送使能位
| 置1 |
2:RE |
接收使能位
| 置1 |
4.7 USART_DR数据寄存器
《STM32中文参考手册》对USART_DR寄存器的描述如下:
将数据放入TDR中即可发送,同理,可以从RDR中读出接收到的数据。
五、硬件连接
- 将以上跳线帽接好,即将USART1接到板载CH340 USB转串口芯片上。
- 将USB线接到USB_SLAVE端口。
六、程序设计
6.1 USART1初始化
本函数位于SYSTEM/usart.c/uart_init(),该函数主要进行串口波特率设置(参考4.4)、使能A口和串口1外设时钟(参考4.1),设置端口复用(参考4.2),复位串口1(参考4.3)、使能接收中断(参考4.6)。具体代码如下所示:
void uart_init(u32 pclk2,u32 bound)
{
float temp;
u16 mantissa;
u16 fraction;
temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV
mantissa=temp; //得到整数部分
fraction=(temp-mantissa)*16; //得到小数部分
mantissa<<=4;
mantissa+=fraction;
RCC->APB2ENR|=1<<2; //使能PORTA口时钟
RCC->APB2ENR|=1<<14; //使能串口时钟
GPIOA->CRH&=0XFFFFF00F;//IO状态设置
GPIOA->CRH|=0X000008B0;//IO状态设置
RCC->APB2RSTR|=1<<14; //复位串口1
RCC->APB2RSTR&=~(1<<14);//停止复位
//波特率设置
USART1->BRR=mantissa; // 波特率设置
USART1->CR1|=0X200C; //1位停止,无校验位.
#if EN_USART1_RX //如果使能了接收
//使能接收中断
USART1->CR1|=1<<5; //接收缓冲区非空中断使能
MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级
#endif
}
6.2 USART1接收中断函数
本函数位于SYSTEM/usart.c/USART1_IRQHandler(),主要通过定义一个标志USART_RX_STA,其bit15为接收完成标志位,bit14为接收到0X0d标志位,bit13-bit0为Buf计数。接收到的数据需要以0X0d 0X0a结尾。具体流程如下图所示:
具体实现如下所示:
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void USART1_IRQHandler(void)
{
u8 res;
if(USART1->SR&(1<<5)) //接收到数据
{
res=USART1->DR;
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}else //还没收到0X0D
{
if(res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=res;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
6.3 printf函数重定向
本函数位于SYSTEM/usart.c/fputc(),主要是将fputc重定向到USART1上。
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//等待上一次串口数据发送完成
USART1->DR = (u8) ch; //写DR,串口1将发送数据
return ch;
}
6.4 轮询主函数
本函数位于USER/test.c,主要是通过USART_RX_STA获得接收到的信息长度,并将信息按照字节通过USART1->DR发送出去,利用USART_SR->TC判断是否发送完成。进而形成USART回环。具体代码如下所示:
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
int main(void)
{
u16 t;
u16 len;
u16 times=0;
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,115200); //串口初始化为115200
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n\r\n");
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\n串口USART1寄存器实验\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
}
}
完整版代码见正点原子官方:【正点原子】精英STM32F103开发板\【正点原子】精英STM32F103开发板资料 资料盘(A盘)\4,程序源码\1,标准例程-寄存器版本\1,标准例程-寄存器版本\实验4 串口实验
七、上机测试
使用XCOM查看串口发送的数据,并测试串口回环:
至此测试成功!