• STM32 学习8 USART串口通讯与printf重定向


    一、串口通信介绍

    STM32 F103ZET6包含多个UART、USART串口。

    1. USART介绍

    USART,全称:Universal Synchronous/Asynchronous Receiver/Transmitter,是通用同步/异步串行接收/发送器,主要特点有:

    • 同步和异步通信
    • 全双工通信
    • 支持硬件和软件流控制机制

    2. UART介绍

    UART,全称:Universal Asynchronous Receiver/Transmitter,是通用异步收发器,在USART功能的基础上,裁剪掉了同步通信功能,其主要特点:

    • 异步通信
    • 全双工通信
    • 无需外部时钟信号

    3. STM32 F103ZET6串口资源

    STM32 F103ZET6芯片,有5个USART接口,数据手册可在官网查询:
    https://www.st.com/zh/microcontrollers-microprocessors/stm32f103.html

    根据手册的描述:

    STM32F103xC、STM32F103xD和STM32F103xE性能型系列集成了:

    • 三个通用同步/异步串行收发器(USART1、USART2和USART3)
    • 两个通用异步串行收发器(UART4和UART5)。

    这五个接口提供了异步通信、IrDA SIR ENDEC支持、多处理器通信模式、单线半双工通信模式,并具有LIN主/从能力。USART1接口能够以高达4.5 Mbit/s的速度进行通信。其他可用的接口的通信速度为最高2.25 Mbit/s。

    USART1、USART2和USART3还提供CTS和RTS信号的硬件管理、智能卡模式(符合ISO 7816标准)以及类SPI通信功能。
    除了UART5外,所有接口都可以由DMA控制器服务。

    开发板原理图:
    在这里插入图片描述

    4. STM32 USART作用

    USART 一个常见应用是将printf 函数通过串口输出,方便程序调试。
    另外, USART还支持 LIN(域互连网络)、智能卡协议与红外IrDA协议 SIR ENDEC规范、调制解调器操作(CTS/RTS)、和DMA功能。

    5. STM32 USART框图

    在《stm3210x参考手册.pdf》P309可以看到STM32的USART框图:
    在这里插入图片描述

    引脚说明

    • TX:发送端口;
    • RX:接收端口;
    • nRTS、nCTS:硬件流控,不常使用,只针对异步串口通讯端口;
    • SCLK:时钟,只针对异步串口通讯端口;
    • IRDA_OUT、IRDA_IN:内部引脚。

    6. 寄存器

    这里简单列出常用的USART寄存器,详细使用方法可以参考《stm32中文参考手册.pdf》。

    USART_SR(Status Register,状态寄存器):

    用于存储USART的状态信息,包括发送完成、接收缓冲区非空、校验错误等。

    USART_DR(Data Register,数据寄存器):

    用于存储发送和接收的数据。写入此寄存器可以启动数据发送,读取此寄存器可以获取接收到的数据。

    USART_BRR(Baud Rate Register,波特率寄存器):

    用于设置USART的波特率,通常需要根据系统时钟和所需的波特率进行配置。

    USART_CR1(Control Register 1,控制寄存器1):

    用于配置USART的工作模式、数据格式、中断使能等。

    USART_CR2(Control Register 2,控制寄存器2):

    用于配置USART的硬件流控、时钟极性等特性。

    USART_CR3(Control Register 3,控制寄存器3):

    用于配置USART的其他特性,如DMA使能、多主机模式等。

    USART_GTPR(Guard Time and Prescaler Register,守护时间和预分频器寄存器):

    用于配置USART的守护时间和时钟预分频器,通常与同步通信相关。

    USART_IT(Interrupt Register,中断寄存器):

    用于配置USART的中断使能和中断标志位。

    7. 中断请求

    在《STM32中文参考手册》中,中断请求表:
    在这里插入图片描述

    二、开发板RS-232硬件连接

    在普中-F1开发板上,提供了 RS-232 母头,其线序:
    在这里插入图片描述

    可以使用一根 RS-232转TTL转USB的连接线,连接USB接电脑,电脑上使用串口调试工具进行开发实验。

    RS-232公头线序:
    在这里插入图片描述

    三、串口通信的配置步骤

    配置USART的步骤如下:

    1. 时钟使能

    首先需要使能USART所使用的时钟,确保其正常工作。USART挂接的系统总线:

    • USART1: APB2时钟总线
    • USART2~5:APB1时钟总线

    代码示例:

    // 使能 GPIOA 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    // 使能 USART1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    • 1
    • 2
    • 3
    • 4

    2. GPIO配置

    配置相关的GPIO引脚,将其设置为USART的TX(发送)和RX(接收)功能。此外,还需要配置引脚的模式(输入/输出)、速率、上拉/下拉等参数。
    代码示例:

    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
    // 接收设置输入浮空模式
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
    
    • 1
    • 2
    • 3

    3. USART参数配置

    配置USART的工作模式、波特率、数据位、停止位、校验位等参数。这些参数需要根据具体的通信需求进行设置。

    #include "stm32f10x.h"
    
    void USART1_UART_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStruct;
        USART_InitTypeDef USART_InitStruct;
    
        // 使能串口1时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
        // 串口1 GPIO初始化
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; // TX引脚
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出模式
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // GPIO速度为50MHz
        GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; // RX引脚
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式
        GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        // 串口1参数配置
        USART_InitStruct.USART_BaudRate = 115200; // 波特率为115200
        USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 数据位长度为8位
        USART_InitStruct.USART_StopBits = USART_StopBits_1; // 停止位为1位
        USART_InitStruct.USART_Parity = USART_Parity_No; // 无校验位
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控制
        USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 支持接收和发送
        USART_Init(USART1, &USART_InitStruct);
    
        // 使能USART1模块
        USART_Cmd(USART1, ENABLE);
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    4. 使能USART

    最后需要使能USART模块,开始其正常工作。这涉及到设置相应的控制寄存器来启动USART的发送和接收功能。

    void USART_Cmd(USAR_TypeDef* USARTx, FunctionalState NewState);
    USART_Cmd(USART1, ENABLE);
    
    • 1
    • 2

    5. 设置串口中断类型并使能

    本章使用使用到串口中断,关于中断具体概念和使用会在后续章节介绍。

    // 设置中断类型
    void USART_ITConfig(USART_TypeDef * USARTx,uint16_t USART_IT,FunctionalState NewState);
    // 串口1接收使能
    USART_ITConfig(USART1, USART_IT_RXNE,ENABLE);
    // 发送使能
    USART_ITConfig(USART1, USART_IT_TC,ENABLE);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6. 设置串口中断优先级、使能串口中断通道

    NVIC_Init()
    
    • 1

    7. 串口中断函数

    ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
      // 接收USART1 中断的处理
    }
    void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    四、代码实现

    下面代码实现的功能是:
    通过电脑串口给开发板的USART3发送数据0-9的数据,开发板的数码管显示对应的数值,并且回复同样的内容给开发板。

    1. 用 uart 库

    在这里插入图片描述

    2. usart_utils.c

    #include "usart_utils.h"
    
    #include "stm32f10x.h"
    #include "led_utils.h"
    
    // 初始化USART3
    void USART3_Init(u32 bound) {
        GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
    
        // 使能USART3和GPIOB时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
        // 配置USART3的GPIO引脚
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // TX引脚
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStructure);
    
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX引脚
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOB, &GPIO_InitStructure);
    
        // 配置USART3参数
        USART_InitStructure.USART_BaudRate = 9600;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_Init(USART3, &USART_InitStructure);
    
        // 使能USART3
        USART_Cmd(USART3, ENABLE);
    
        USART_ClearFlag(USART3, USART_FLAG_TC);
        USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
    
        NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
        
    }
    
    
    // USART3中断服务函数
    void USART3_IRQHandler(void) {
        if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
            // 读取接收到的数据
            u8 data = USART_ReceiveData(USART3);
            if(data<=9){
                // 收到什么,就发送什么
                USART3_SendData(data);
                // led也显示对应的值
                led_lightn(data);
            }
    
            // 清除接收中断标志位
            USART_ClearITPendingBit(USART3, USART_IT_RXNE);
        }
    }
    
    // 发送函数
    void USART3_SendData(u8 data) {
        USART_SendData(USART3, data);
        while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    3. usart_utils.h

    #ifndef __USART_UTILS_H__
    #define __USART_UTILS_H__
    #include "stm32f10x.h"
    
    void USART3_Init(u32 bound);
    void USART3_IRQHandler(void);
    // 发送函数
    void USART3_SendData(u8 data);
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4. main函数

    #include "gpio_utils.h"
    #include "rcc_utils.h"
    #include "stm32f10x.h"
    #include "sys_tick_utils.h"
    #include "led_utils.h"
    #include "key_utils.h"
    #include "usart_utils.h"
    
    // 主函数
    int main(void)
    {
    	GPIO_Configuration(); // 调用GPIO配置函数
    	sys_tick_init(72);
    	led_all_off();
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	USART3_Init(9600);
    
    	while (1) // 无限循环
    	{
    		delay_ms(20);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    五、printf重定向

    1. 实现方式

    通过printf的重定向 ,可以实现在打印printf内容时,通过串口将内容输出来,以方便调试。

    要在 STM32 上实现 printf 的重定向,通常需要重写 fputc 函数,以便将输出重定向到你所选择的串口。下面是一个基本的示例:

    #include 
    #include "usart_utils.h"
    
    // 重定向 fputc 函数,将输出重定向到 USART3
    int fputc(int ch, FILE *f) {
        USART_SendData(USART3, (uint8_t)ch);
        while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
        return ch;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这个示例中, fputc 函数会重定向到 USART3,并且通过调用 USART_SendData 函数发送一个字节到 USART3,然后等待发送完成。

    需要注意的是,使用这个功能时,要钩选microLIB功能。 钩选microLIB会增加程序的体积,需要权衡利弊。
    在这里插入图片描述

    2. 调用

    示例代码里,使用printf("HelloWorld");,就可以在串口看到输出的字符串。

    本文代码开源在:
    https://gitee.com/xundh/stm32_arm_learn

  • 相关阅读:
    Python与数据库存储
    VUE3 之 插件的使用 - 这个系列的教程通俗易懂,适合自学
    2013-2020年全国31省数字经济信息化基础数据
    java计算机毕业设计ssm易物小店交换系统-二手咸鱼交易系统
    科研宝典·工具篇 | CiteSpace: 科学文献分析
    1688往微信小程序自营商城铺货商品采集API接口
    前端基础建设与架构12 如何理解 AST 实现和编译原理?
    洛谷千题详解 | P1010 [NOIP1998 普及组] 幂次方【C++、Java、Python、Pascal语言】
    [OpenAirInterface-01]什么是OAI?OAI在github中源代码的存放结构
    STM32 与 ARM 的联系
  • 原文地址:https://blog.csdn.net/xundh/article/details/136162244