百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

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

wptr33 2025-07-06 17:25 36 浏览


一、串口通信介绍

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);

2.GPIO配置:

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

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

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);
}

4.使能USART:

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

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

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);

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

NVIC_Init()

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);

四、代码实现

下面代码实现的功能是:
通过电脑串口给开发板的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);
}

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

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);
	}
}

五、printf重定向

1. 实现方式

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

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

#include <stdio.h>
#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;
}

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

需要注意的是,使用这个功能时,要钩选microLIB功能。 钩选microLIB会增加程序的体积,需要权衡利弊。

2. 调用

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

本文代码开源在:

https://gitee.com/xundh/stm32_arm_learn

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...