【stm32f4】UART串口通信(stm32hal库)

一、串口通信简介

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)

偏移地址: 0x00
复位值: 0x00C0 0000

2、数据寄存器 (USART_DR)

偏移地址: 0x04
复位值: 0xXXXX XXXX

3、波特率寄存器 (USART_BRR)

注意: 如果 TE RE 位分别被禁止,则波特计数器会停止计数。
偏移地址: 0x08
复位值: 0x0000 0000

4、控制寄存器 1 (USART_CR1)

偏移地址: 0x0C
复位值: 0x0000 0000

5、控制寄存器 2 (USART_CR2)

偏移地址: 0x10
复位值: 0x0000 0000

6、控制寄存器 3 (USART_CR3)

偏移地址: 0x14
复位值: 0x0000 0000

7、保护时间和预分频器寄存器 (USART_GTPR)

偏移地址: 0x18
复位值: 0x0000 0000

三、串口通信案例

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"文件中外部晶振频率设置错误

需要先用记事本打开才能更改

猜你喜欢

转载自blog.csdn.net/2301_77699699/article/details/146990512
今日推荐