16.1 关于 USART/UART

在嵌入式中,很多MCU和外设模块都集成有UART外设。STM32F103有3个通用同步异步收发器(Universal synchronous asynchronous receiver transmitter,USART),2个通用同步异步收发器(Universal asynchronous receiver transmitter,UART)。USART和UART的主要区别在于,USART支持同步通信,该模式有一根时钟线提供时钟。串口在嵌入式中经常使用,一般使用UART就足够了,常见的用途如下:

  1. 作为调试口,打印程序运行的状态信息;
  2. 连接串口接口的模块(比如GPS模块),传输数据;
  3. 通过电平转换芯片变为RS232/RS485电平,连接工控设备;

STM32F103系列不同USART所支持的功能如表 16.1.1 所示。
在这里插入图片描述
USART内部结构的结构如图 16.1.1 所示。
在这里插入图片描述
可以把USART分成四部分:
①:USART引脚

  • TX:数据发送;
  • RX:数据接收;
  • SW_RX:在单线和智能卡模式下接收数据,属于内部引脚,没有具体外部引脚;
  • RTS:在硬件流控制时,用于指示本设备准备好可接收数据,低电平说明本设备可以接收数据;
  • CTS:在硬件流控制时,用于指示本设备准备好可发送数据,低电平说明本设备可以发送数据;
  • CK:在同步模式时,用于输出时钟;

②:波特率发生器
通过设置USART_BRR寄存器的值,实现串口通信数据传输速率的设置。由《参考手册》可知计算公式为:
在这里插入图片描述

其中“1”为波特率,“2”为该外设USART的时钟频率,3”为USART_BRR寄存器的值。

假设所需波特率为115200,当前USART时钟为72MHz,则USARTDIV=72000000/(115200*16)=39.0625。USART_BRR寄存器使用高12位[15:4]存放整数部分,低4位[3:0]存放小数部分,小数部分每一位对应1/2⁴=0.0625。因此,整数39对应16进制为0x27,左移4位为0x270,小数0.0625,对应0x1,USART_BRR=0x271即可。

在利用寄存器配置USART的波特率的时候需要依据此公式计算USART_BRR的值,而在HAL库中无需计算,只需传入所需波特率,自动写USART_BRR寄存器值,但是我们仍然要学习这个波特率的计算公式,也许的开发调试过程中会使用到。

前面计算波特率需要知道外设时钟“2”的值,由前面图 6.1.2 可知,USART1挂载APB1上,USART2/3和USART4/5挂载APB2上。由前面图 9.1.1 可知,APB1时钟最大为36MHz,APB2时钟最大为72MHz。因此,只有USART1的波特率计算中的“2”能取最大系统时钟72MHz,而其它的USART/UART只能取36MHz.

③:发送器/接收器控制单元
通过向控制寄存器CR1、CR2、CR3和状态寄存器SR写入相应的位,可实现对USART数据的发送和接收控制。其中CR1主要用于配置USART的数据位、校验位和中断使能,CR2用于配置USART的停止位和SCLK时钟控制,CR3用于CTS硬件流控制、DMA多缓冲控制等。通过读取状态寄存器SR的值,可查询USART的状态。

④:数据收发寄存器单元
该部分主要由发送数据寄存器(TDR)、发送移位寄存器、接收数据寄存器(RDR)、接收移位寄存器组成。发送移位寄存和接收移位寄存器,分别负责将发送数据并串转换和接收数据串并转换,从而实现数据在传输时,是一位一位的发送和接收。

16.2 硬件设计

如图 16.2.1 为开发板USB调试串口部分的原理图,最左边J12是图 3.3.1 中,编号为“37”的Micro USB接口;U13是一个UART转USB的芯片,可自动将UART信号转换为USB信号;U14是一个施密特触发器,主要用于防干扰和电路保护。

MCU的USART1_TX(PA9)和USART1_RX(PA10),经过U14接到U13,U13收到数据后自动将UART信号转换成USB信号,在从J12引出。

最后用户还需要使用Micro USB连接线,将开发板的J12和电脑USB连接,再打开“4.3.3 下载、安装MobaXterm”介绍的MobaXterm,具体使用介绍在本章“16.4 实验效果”。
在这里插入图片描述

16.3 软件设计

16.3.1 软件设计思路

实验目的:本实验使用USART1向电脑发送打印信息。

  1. 初始化USART:设置波特率,收发选择,有效数据位等
  2. 串口引脚初始化:USART使能、GPIO端口时钟使能、GPIO引脚设置为USART复用;
  3. 重定向printf和scanf;
  4. 主函数调用USRAT初始化函数,使用printf打印输出,使用scanf获取输入;

本实验配套代码位于“5_程序源码\8_通信—调试串口\”。

16.3.2 软件设计讲解

  1. GPIO引脚选择与串口选择

代码段 16.3.1 引脚宏定义(driver_usart.h)

/*********************
* 引脚宏定义
**********************/
#define USARTx USART1
#define USARTx_TX_PIN GPIO_PIN_9
#define USARTx_RX_PIN GPIO_PIN_10
#define USARTx_PORT GPIOA
#define USARTx_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
#define USARTx_CLK_EN() __HAL_RCC_USART1_CLK_ENABLE()
#define USARTx_CLK_DIS() __HAL_RCC_USART1_CLK_DISABLE()
  1. 初始化USART
    USART初始化包含两部分:协议部分和硬件部分。
    协议部分与硬件无关,比如USART的波特率、奇偶校验、停止位等,通过“HAL_UART_Init()”设置。硬件部分指承载的硬件载体,比如串口所使用的发送、接收引脚的复用,通过“HAL_UART_MspInit()”设置。函数名中的Msp(MCU Specific Package,MCU特定软件包),就是指MCU相关的硬件初始化。

代码段 16.3.2 USART 初始化(driver_usart.c)

/*
* 定义全局变量
*/
UART_HandleTypeDef husart;
/*
* 函数名:void UsartInit(uint32_t baudrate)
* 输入参数:baudrate-串口波特率
* 输出参数:无
* 返回值:无
* 函数作用:初始化 USART 的波特率,收发选择,有效数据位等
*/
void UsartInit(uint32_t baudrate)
{
husart.Instance = USARTx; // 选择 USART1
husart.Init.BaudRate = baudrate; // 配置波特率
husart.Init.WordLength = USART_WORDLENGTH_8B; // 配置数据有效位为 8bit
husart.Init.StopBits = USART_STOPBITS_1; // 配置一位停止位
husart.Init.Parity = USART_PARITY_NONE; // 不设校验位
husart.Init.Mode = USART_MODE_TX_RX; // 可收可发
husart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
// 使用库函数初始化 USART1 的参数
if (HAL_UART_Init(&husart) != HAL_OK)
{
Error_Handler(); } }
  • 4行:定义一个“UART_HandleTypeDef”结构体(官方也称句柄)变量husart,用于保存串口参数设置;
  • 15~21行:设置串口参数,也就是husart:
  1. Instance:USART寄存器及地址,即哪一个USART;
  2. Init.BaudRate:“UART_HandleTypeDef”结构体里“UART_InitTypeDef”结构体成员,该属性为波特率;
  3. Init.WordLength:每次发送的字节长度,通常设置为8bit;
  4. Init.StopBits:每次发送后停止位长度,通常设置为1bit;
  5. Init.Parity:奇偶校验,通常设置为不校验;
  6. Init.Mode:收发模式,通常设置为可收可发;
  7. Init.HwFlowCtl:流控设置,没有用到(CTS/RTS),通常设置为None;
  • 24行:调用“HAL_UART_Init()”,传入设置的husart,初始化串口;

“HAL_UART_Init()”会调用“HAL_UART_MspInit()”,从而实现对涉及的硬件初始化,用户需要覆 写(HAL库提供函数名,函数内容需要自己编写)该函数,完成使能串口时钟、初始化TX/RX的引脚、设置USART1的中断优先级且使能中断等。

代码段 16.3.3 USART MSP 初始化(driver_usart.c)

/*
* 函数名:void HAL_USART_MspInit(USART_HandleTypeDef* husart)
* 输入参数:husart-USART 句柄
* 输出参数:无
* 返回值:无
* 函数作用:使能 USART1 的时钟,使能引脚时钟,并配置引脚的复用功能
*/
void HAL_UART_MspInit(UART_HandleTypeDef* husart)
{
// 定义 GPIO 结构体对象
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(husart->Instance==USARTx) {
// 使能 USART1 的时钟
USARTx_CLK_EN();
// 使能 USART1 的输入输出引脚的时钟
USARTx_GPIO_CLK_EN();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = USARTx_TX_PIN; // 选择 USART1 的 TX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 配置为复用推挽功能
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 引脚翻转速率快
HAL_GPIO_Init(USARTx_PORT, &GPIO_InitStruct); // 初始化 TX 引脚
GPIO_InitStruct.Pin = USARTx_RX_PIN; // 选择 RX 引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT; // 配置为输入
HAL_GPIO_Init(USARTx_PORT, &GPIO_InitStruct); // 初始化 RX 引脚
} }
  • 11行:定义一个“GPIO_InitTypeDef”结构体变量GPIO_InitStruct,用于保存GPIO参数设置;
  • 12行:当系统中用到多个串口时,都会调用“HAL_USART_MspInit”进行硬件初始化,因此需要判断是否为USARTx(宏定义对应USART1);
  • 15行:使能USART1时钟;
  • 18行:使能USART1_TX(PA9)和USART1_RX(PA10)所在组时钟;
  • 23~26行:设置USART的发送引脚:
  1. Pin:指定引脚号;
  2. Mode:配置为复用推挽功能;
  3. Pull:默认上拉即可;
  4. Speed:作为通信信号引脚,设置为High;
  • 27行:使用“HAL_GPIO_Init()”初始化该引脚;
  • 29~31行:设置USART的接收引脚;
  1. 重定向打印函数
    以上初始化完成后,就可以使用HAL库提供的“HAL_UART_Transmit()”从串口发送数据,使用“HAL_UART_Receive()”接收数据,但这样使用不方便,需要自己处理数据类型。在学习C语言时,通常使用printf将数据格式化打印,比较方便。因此,这里需要重定向打印函数,在使用printf时调用“HAL_UART_Transmit()”打印。

代码段 16.3.4 重定向打印函数(driver_usart.c)

/*****************************************************
*function: 写字符文件函数
*param1: 输出的字符
*param2: 文件指针
*return: 输出字符的 ASCII 码
******************************************************/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&husart, (uint8_t*)&ch, 1, 10);
return ch;
}
/*****************************************************
*function: 读字符文件函数
*param1: 文件指针
*return: 读取字符的 ASCII 码
******************************************************/
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&husart, (uint8_t*)&ch, 1, 10);
return (int)ch;
}

printf和scanf会分别调用“fputc()”和“fgetc()”,因此这里覆写这两个函数,使用HAL提供的函数实现收发数据。

同时,还需要需要点击“工程设置”,打开工程选项界面,切换到“Target”标签,勾选上“Use MicroLIB”,如图 16.3.1 所示。

在这里插入图片描述
也可以添加如下代码,就不用勾选“Use MicroLIB”,两个方法二选其一即可。
代码段 16.3.5 可选添加内容(driver_usart.c)

/*
* 添加如下代码,可不在工程设置中勾选 Use MicroLIB
*/
#pragma import(__use_no_semihosting)
struct __FILE
{
int a;
};
FILE __stdout;
FILE __stdin;
void _sys_exit(int x)
{}
  1. 主函数测试
// 初始化 USART1,设置波特率为 115200 bps
UsartInit(115200);
// 在 windows 下字符串\n\r 表示回车
// 如果工程在编译下面这句中文的时候报错,请在魔术棒“Option for target”->"C/C++"->"Misc Controls"添加“--locale=english”
printf("百问科技 www.100ask.net\n\r");
printf("UART 实验\n\r");
printf("test char = %c,%c\n\r", 'H', 'c');
printf("test string1 = %s\n\r", "www.100ask.net");
printf("test string2 = %s\n\r", "深圳百问网科技有限公司");
printf("test decimal1 number = %d\n\r", 123456);
printf("test decimal2 number = %d\n\r", -123456);
printf("test hex1 number = 0x%x\n\r", 0x123456);
printf("test hex2 number = 0x%08x\n\r", 0x123456);
printf("test float = %.5f\n\r", 3.1415);
printf("test double = %.10lf\n\r", 3.141592653);
printf("\r\n 键盘输入‘C’或者‘c’控制串口打印‘Hello world’");
while(1) {
scanf("%c", &cmd);
if(cmd=='C' || cmd=='c') {
cmd = 0;
printf("\r\nHello World."); }
HAL_Delay(100); }

主函数直接使用“printf()”便可调用USART1打印,使用“scanf()”便可调用USART1接收数据。

  • 2行:初始化USART1,设置波特率为常用的115200;
  • 6~7行:中文打印测试;如果编译报错,需要点击“工程设置”,打开工程选项界面,切换到“C/C++”标签,在“Misc Controls”里加上“"–locale=english"”,如图 16.3.2 所示。
  • 8行:字符打印测试;
  • 9~10行:字符串打印测试;
  • 11~12行:正负整打印疑测试;
  • 13~14行:十六进制打印测试;
  • 15~16行:浮点数打印测试;
  • 21~29行:通过“scanf()”函数获取输入,如果输入的指定内容,则打印指定内容;
    在这里插入图片描述

16.4 实验效果

本实验对应配套资料的“5_程序源码\8_通信—调试串口\”。首先如图 16.4.1 所示将开发板调试串口与电脑USB口连接。打开MobaXterm,设置串口会话,如图 16.4.2 所示。

  1. 点击做上角“Session”,创建会话;
  2. 在中间弹出的窗口,选择“Serial”,即串口;
  3. 下拉选择串口端口,我这里是COM4,读者可能编号不一样,但后面显示的芯片型号是一样的,选择“Silicon Labs CP210x USB to UART…”即可;
  4. 波特率选择115200,与主函数中串口初始化设置的波特率保持一致;
  5. 以为串口是异步通信,需要双方统一传输规则,这里和代码段 16.3.2 中的设置保持一致。数据位8位,停止位1位,无校验,无流控;
  6. 设置完后,点击“OK”;

打开代码工程,使用Keil编译,下载程序,MobaXtrem显示如图 16.4.3 所示界面,所有打印正常显示。在电脑键盘输入“C”或“c”,即可控制串口打印“Hello World”。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


百问网技术论坛:
http://bbs.100ask.net/

百问网嵌入式视频官网:
https://www.100ask.net/index

百问网开发板:
淘宝:https://100ask.taobao.com/
天猫:https://weidongshan.tmall.com/

技术交流群2(鸿蒙开发/Linux/嵌入式/驱动/资料下载)
QQ群:752871361

单片机-嵌入式Linux交流群:
QQ群:536785813

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐