一、串口通信简介
UART(Universal Asynchronous Receiver/Transmitter)即通用异步收发器,是一种常用的串行通信协议,在微控制器、嵌入式系统以及各种电子设备中应用广泛。
1、UART串口通信协议特点
UART是一种串行、异步、全双工的通信协议。串行意味着数据是一位一位顺序传输;异步表示通信双方不需要共享同一个时钟信号,每个设备有自己独立的时钟;全双工则允许两个设备在同一时间进行双向的数据传输。
2、硬件电路
UART硬件电路相对简单,双向传输数据一般有两根通信线,即发送端TX和接收端RX,主机和从机的TX与RX要交叉连接,也就是主机的TX接从机的RX。当只需单向传输数据时,可以只接一根通信线。当两台设备的电平标准不一致时,需要加电平转换芯片 。
3、电平标准
TTL电平:+3.3V或 +5V表示1,0V表示0,常用于一般的处理器。
RS232电平:(-3, -15)V表示1,(+3, +15)V表示0,抗静电干扰较强,多用在大型机器上。
RS485电平:两线压差(+2, +6)V表示1,(-2, -6)V表示0(差分信号),抗干扰能力非常强,通信距离可以达到上千米 。
4、数据帧格式
一个完整的UART数据帧通常由起始位、数据位、可选的校验位和停止位组成:
空闲位:当总线处于空闲状态时,信号线的状态为‘1’即高电平。
起始位:每开始一次通信时,发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的始。因为总线空闲时为高电平,所以起始位明显区别于空闲状态。
数据位:起始位之后就是要传输的数据,数据位可以是5、6、7、8位等,构成一个字符。数据先发送最低位,最后发送最高位,使用低电平表示‘0’,高电平表示‘1’完成数据位的传输。其中8位数据位是最常用的设置。
奇偶校验位:用于校验数据传送的正确性。数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)。例如,若校验位设置为偶校验,设备接收到数据帧后,会统计数值为1的位,若总数不是偶数,则认为数据帧中的位可能已改变;奇校验同理。当然,也可以选择不使用校验位。
停止位:它是一个字符数据的结束标志,可以是1位、1.5位、2位的高电平。由于数据在传输线上定时,且每个设备有自己的时钟,可能出现通信不同步的情况,因此停止位不仅表示传输的结束,还提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢,其中1个停止位是最常用的设置 。
5、波特率
在UART中使用波特率来表示数据的传输速度,单位是bit/s(每秒传输的二进制位数)。比如设置波特率为115200,表示每秒可以发送115200个bit。通信双方必须设置相同的波特率,才能保证数据的正确传输。
6、访问方式、(针对UART功能芯片)
电脑可以通过中断、轮询和DMA三种方式对UART功能芯片进行访问:
中断(Interrupt)方式:这是一种异步的访问方式。当UART接收到数据或发送完成时,它会触发一个中断信号,通知CPU进行相应的处理。CPU会立即暂停当前的任务,转而处理中断请求,并执行中断服务程序(ISR),该程序负责读取接收到的数据或发送下一个数据。
轮询方式:CPU定期检查UART的状态寄存器,看是否有数据到达或发送完成,这种方式相对简单,但效率较低,因为CPU需要不断地查询,会占用较多的CPU资源。
DMA(直接内存访问)方式:DMA控制器可以在不经过CPU干预的情况下,直接在内存和UART之间传输数据,大大提高了数据传输的效率,减轻了CPU的负担 。
二、寄存器介绍
1、状态寄存器 (USART_SR)
2、数据寄存器 (USART_DR)
3、波特率寄存器 (USART_BRR)
4、控制寄存器 1 (USART_CR1)
5、控制寄存器 2 (USART_CR2)
6、控制寄存器 3 (USART_CR3)
7、保护时间和预分频器寄存器 (USART_GTPR)
三、串口通信案例
1、串口打印helloworld
#include "UART.h"
#include "stdio.h"
UART_HandleTypeDef UART1_Handle;
uint8_t uart_rx_buf[128]; // 串口接收数据缓冲区
void UART1_Init(void)
{
UART1_Handle.Instance = USART1;
UART1_Handle.Init.BaudRate = 115200; // 波特率
UART1_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流
UART1_Handle.Init.Mode = UART_MODE_TX_RX; // 发送接收模式
UART1_Handle.Init.OverSampling = UART_OVERSAMPLING_16;
UART1_Handle.Init.Parity = UART_PARITY_NONE; // 无校验
UART1_Handle.Init.StopBits = UART_STOPBITS_1; // 停止位
UART1_Handle.Init.WordLength = UART_WORDLENGTH_8B; // 字长
HAL_UART_Init(&UART1_Handle);
}
/* 串口 MSP 回调函数 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// (1) 使能 USART 和对应 IO 口的时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
__HAL_RCC_USART1_CLK_ENABLE(); // 使能 USART1 的时钟
// (2) 初始化 IO 口
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_10; // TX: PA9, RX: PA10
GPIO_InitStructure.Pull = GPIO_PULLUP; // 上拉
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
GPIO_InitStructure.Alternate = GPIO_AF7_USART1; // 复用功能
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
}
}
/* 重定向 printf 函数为串口输出 */
int fputc(int ch, FILE *p)
{
char c = ch;
HAL_UART_Transmit(&UART1_Handle, (uint8_t *)&c, 1, HAL_MAX_DELAY);
return ch;
}
2、串口输入检测
#include "UART.h"
#include "stdio.h"
UART_HandleTypeDef UART1_Handle;
uint8_t uart_rx_buf[128]; // 串口接收数据缓冲区
void UART1_Init(void)
{
UART1_Handle.Instance = USART1;
UART1_Handle.Init.BaudRate = 115200; // 波特率
UART1_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流
UART1_Handle.Init.Mode = UART_MODE_TX_RX; // 发送接收模式
UART1_Handle.Init.OverSampling = UART_OVERSAMPLING_16;
UART1_Handle.Init.Parity = UART_PARITY_NONE; // 无校验
UART1_Handle.Init.StopBits = UART_STOPBITS_1; // 停止位
UART1_Handle.Init.WordLength = UART_WORDLENGTH_8B; // 字长
HAL_UART_Init(&UART1_Handle);
HAL_UART_Receive_IT(&UART1_Handle, uart_rx_buf, 1); // 设置串口中断缓冲区及中断阈值(当前为1)
}
/* 串口 MSP 回调函数 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
/* 1、使能 USART 和对应 IO 口的时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
__HAL_RCC_USART1_CLK_ENABLE(); // 使能 USART1 的时钟
/*2、初始化IO口*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_10; // TX: PA9, RX: PA10
GPIO_InitStructure.Pull = GPIO_PULLUP; // 上拉
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
GPIO_InitStructure.Alternate = GPIO_AF7_USART1; // 复用功能
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 3、设置中断优先级,使能串口中断 */
HAL_NVIC_SetPriority(USART1_IRQn,1,1);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
/* 串口接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
/*接收到的数据在uart_rx_buf内
*在这个回调函数内对接收到的数据进行处理
*/
printf("%c",*uart_rx_buf);
}
}
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&UART1_Handle);
HAL_UART_Receive_IT(&UART1_Handle, uart_rx_buf, 1); // 重新启用中断
}
/* 重定向 printf 函数为串口输出 */
int fputc(int ch, FILE *p)
{
char c = ch;
HAL_UART_Transmit(&UART1_Handle, (uint8_t *)&c, 1, HAL_MAX_DELAY);
return ch;
}
四、串口输出乱码问题
1、常见问题
波特率,字长啥的参数设置错误
2、时钟频率
在#include "stm32f4xx_hal_conf.h"文件中外部晶振频率设置错误
需要先用记事本打开才能更改