【正点原子STM32】串口(数据通信、串行/并行,单工/半双工/全双工,同步/异步、ST MCU选型手册、串口数据发送接收过程、设置USART波特率、UART异步通信配置步骤、IO引脚复用功能)

一、数据通信的基础概念

二、串口(RS-232)

三、STM32的USART

四、HAL库外设初始化MSP回调机制
五、HAL库中断回调机制
六、USART/UART异步通信配置步骤

  • HAL库相关函数介绍

七、IO引脚复用功能

八、编程实战:通过串口接收或者发送一个字符
九、解读例程源码:串口实验
十、总结

一、数据通信的基础概念

1.1、串行/并行通信

在这里插入图片描述
按数据通信方式分类,可以分为串行通信和并行通信:

  1. 串行通信:

    • 定义: 在串行通信中,数据位按照顺序一个接一个地传输,即逐位传输。
    • 特点: 逐位传输使得每个数据位都需要单独的通信路径,因此通常需要较少的线路。
    • 应用: 常见于长距离通信、串行接口设备(如串行端口、串行通信接口等)。
  2. 并行通信:

    • 定义: 在并行通信中,数据的各个位同时通过多条线路传输。
    • 特点: 并行通信相对于串行通信来说,通信路径较宽,可以在同一时刻传输多个数据位,提高了传输速率。
    • 应用: 常见于短距离通信、内部系统通信、高速数据传输领域,如并行接口设备(如并行端口、内存总线等)。

选择串行通信还是并行通信通常取决于具体的应用需求、通信距离、传输速率等因素。串行通信适用于长距离通信,而并行通信更适合在短距离内提高传输速率。

1.2、单工/半双工/全双工通信

在这里插入图片描述
按数据传输方向分类,通信可以分为单工通信、半双工通信和全双工通信:

  1. 单工通信:

    • 定义: 在单工通信中,数据只能沿一个方向传输,即只有一个方向的通信通道。
    • 特点: 数据只能从发送方向接收方传输,而不能反向传输。这类似于单向街道,只能在一个方向上行驶。
  2. 半双工通信:

    • 定义: 在半双工通信中,数据可以沿两个方向传输,但是不能同时进行,需要分时进行。
    • 特点: 通信双方可以在不同的时间段内进行数据传输,但在同一时间只能有一个方向的数据传输。类似于对讲机,一方说话时另一方只能听,不能同时说话。
  3. 全双工通信:

    • 定义: 在全双工通信中,数据可以同时进行双向传输,允许双方在同一时间内发送和接收数据。
    • 特点: 允许双方同时进行发送和接收操作,类似于双向街道,可以同时在两个方向上行驶。

选择单工、半双工或全双工通信通常取决于应用的需求。单工通信适用于只需要单向传输的场景,半双工通信适用于需要在不同时间段内进行双向通信的场景,而全双工通信则适用于需要同时进行双向通信的场景,提供更高的通信效率。

1.3、同步/异步通信

在这里插入图片描述
按数据同步方式分类,通信可以分为同步通信和异步通信:

  1. 同步通信:

    • 定义: 在同步通信中,通信的发送和接收端共用同一时钟信号来保持数据同步。
    • 特点: 数据的发送和接收都在预定的时钟信号的边缘或特定时刻发生,确保数据的同步性。同步通信通常需要精确的时钟同步。
  2. 异步通信:

    • 定义: 在异步通信中,没有共用的时钟信号,而是通过在数据信号中加入起始位、停止位等控制信息来实现同步。
    • 特点: 数据的发送和接收端在没有预定的时钟信号的情况下进行通信。异步通信通过在数据帧中加入控制信息,如起始位和停止位,来保持数据的同步性。

选择同步通信还是异步通信通常取决于具体的应用需求。同步通信适用于对时序要求较高的场景,而异步通信适用于更加灵活和宽松的时序要求的场景。例如,串口通信中的RS-232通常采用异步通信方式。

1.4、波特率

在这里插入图片描述

  1. 比特率: 表示每秒钟传送的比特数,单位是比特每秒(bit/s)。比特率用来衡量数字通信系统的数据传输速率,即在一秒内可以传输的二进制位数。

  2. 波特率: 表示每秒钟传送的码元数,单位是波特(Baud)。波特率是指信号的变化率,每秒内信号变化的次数。在数字通信中,波特率常用来描述调制和解调的速率,指的是在信号中每秒传送的符号(码元)数量。

  3. 比特率和波特率关系: 比特率与波特率之间的关系可以用以下公式表示:比特率 = 波特率 * log2(M),其中M表示每个码元承载的信息量。这个公式说明了在有多个离散的信号水平(或相位)时,一个码元可以携带更多的信息。

  4. 二进制系统中的波特率: 在二进制系统中,波特率数值上等于比特率。这是因为在二进制系统中,每个码元只能表示两个状态(0或1),因此每个码元携带的信息量(log2M)等于1,公式简化为比特率等于波特率。

总结:

  • 比特率表示传输速率,单位是比特每秒(bit/s)。
  • 波特率表示信号变化率,单位是波特(Baud)。
  • 在二进制系统中,波特率数值上等于比特率。

1.5、常见的串行通信接口

在这里插入图片描述
以下是一些常见的串行通信接口及其基本信息:

  1. UART (通用异步收发器):

    • 接口引脚: TXD (发送端), RXD (接收端), GND (公共地)
    • 数据同步方式: 异步通信
    • 数据传输方向: 全双工
  2. 1-wire:

    • 接口引脚: DQ (发送/接收端)
    • 数据同步方式: 异步通信
    • 数据传输方向: 半双工
  3. IIC (I2C):

    • 接口引脚: SCL (同步时钟), SDA (数据输入/输出端)
    • 数据同步方式: 同步通信
    • 数据传输方向: 半双工
  4. SPI (串行外设接口):

    • 接口引脚: SCK (同步时钟), MISO (主机输入,从机输出), MOSI (主机输出,从机输入), CS (片选信号)
    • 数据同步方式: 同步通信
    • 数据传输方向: 全双工

这些串行通信接口在不同的应用场景中被广泛使用。它们提供了不同的特性,如同步方式、传输方向和引脚配置,以满足各种通信需求。UART常用于通用的异步通信,1-wire适用于一根数据线的短距离通信,I2C适用于多个设备在同一总线上进行通信,而SPI适用于高速数据传输的应用。

二、串口(RS-232)

2.1、什么是串口?

在这里插入图片描述

  1. 什么是串口?

    • 串口是一种用于串行通信的接口,通过这个接口可以在设备之间进行数据的串行传输。串口通常用于连接计算机与外部设备,如串口设备、调制解调器、传感器等。串口通信是按位发送和接收数据的方式进行的。
  2. RS-232接口(DB9):

    • 数据:
      • TXD(pin 3):串口数据输出
      • RXD(pin 2):串口数据输入
    • 地线:
      • GND(pin 5):信号地
    • 握手:
      • RTS(pin 7):请求发送
      • CTS(pin 8):清除发送
      • DSR(pin 6):数据发送就绪
      • DCD(pin 1):数据载波检测
      • DTR(pin 4):数据终端就绪
    • 其他:
      • RI(pin 9):振铃指示

RS-232是一种常见的串行通信标准,它规定了串口接口的物理连接、电气特性以及通信协议。DB9是RS-232接口的一种常见连接形式,具有9个引脚。这些引脚包括数据线、地线、握手信号以及其他用于控制通信和状态指示的信号。每个引脚的功能在特定的应用中可能有不同的用途。

2.2、RS-232电平与COMS/TTL电平对比

在这里插入图片描述
RS-232、CMOS(Complementary Metal-Oxide-Semiconductor)和TTL(Transistor-Transistor Logic)三种不同的电平标准。这些电平标准之间确实存在差异,因此在进行串行通信时,需要进行适当的电平转换。

  1. RS-232电平:

    • 逻辑1:-15V ~ -3V
    • 逻辑0:+3V ~ +15V
  2. CMOS电平(3.3V):

    • 逻辑1:3.3V
    • 逻辑0:0V
  3. TTL电平(5V):

    • 逻辑1:5V
    • 逻辑0:0V

结论:
由于RS-232电平的范围相对较大,与CMOS或TTL电平不同,因此不能直接连接交换信息。需要使用电平转换器来将RS-232电平转换为适用于CMOS或TTL电平的电平,或者反之。这是因为不同的设备和芯片可能使用不同的电平标准,确保它们之间的兼容性需要适当的电平匹配。典型的RS-232转CMOS/TTL电平转换器会将RS-232的电平变换为适用于3.3V或5V逻辑的电平。

2.3、设备间的RS-232通信示意图

在这里插入图片描述
MAX3232SP3232 都是常用于电平转换的串口通信芯片,它们的主要功能是将RS-232电平转换为TTL/CMOS电平,从而实现不同设备之间的串口通信。

美信 MAX3232:

  1. 功能:

    • 提供RS-232到TTL/CMOS电平的双向电平转换。
    • 集成了发送和接收的电压调整电路,无需外部电容器。
    • 用于实现RS-232串口设备和微控制器等TTL/CMOS设备之间的通信。
  2. 电压范围:

    • 支持较广泛的电源电压范围,通常为3V至5.5V。
  3. 工作速率:

    • 通常支持较高的串口通信速率,最高可达 120 kbps。
  4. 引脚:

    • MAX3232一般有16个引脚。

西伯斯 SP3232:

  1. 功能:

    • 类似于MAX3232,提供RS-232到TTL/CMOS电平的电平转换。
    • 集成了电流限制电阻,可以提供对电缆驱动能力的提高。
  2. 电压范围:

    • 支持较广泛的电源电压范围,通常为3V至5.5V。
  3. 工作速率:

    • 通常支持较高的串口通信速率,最高可达 250 kbps。
  4. 引脚:

    • SP3232一般有16个引脚。

共同点:

  • 两者都是经典的RS-232到TTL/CMOS电平转换器,广泛用于嵌入式系统、通信设备和其他串口应用中。
  • 都是双向转换器,可用于同时处理发送和接收数据。

注意事项:

  • 在使用这些芯片时,用户需要根据具体的应用需求来选择适当的工作电压、工作速率等参数。
  • 芯片的引脚功能需按照 datasheet 中的说明正确连接。

在实际设计中,选择 MAX3232 或 SP3232 取决于特定应用的要求和设计偏好。

2.4、STM32串口与电脑USB口通信示意图

在这里插入图片描述
CH340C 是一款常用的 USB 转串口芯片,用于在计算机与其他设备之间实现串口通信。它可以将 USB 信号转换为 TTL/CMOS 电平,从而使得计算机可以与使用 TTL/CMOS 电平的设备进行串口通信。

(南京沁恒 WCH)CH340C 的主要特性:

  1. USB 转串口: CH340C 主要功能是将 USB 接口的信号转换为串口通信所需的 TTL/CMOS 电平信号。

  2. USB 2.0 支持: 符合 USB 2.0 标准,提供高速数据传输。

  3. 多种串口配置: 支持多种串口配置,如8数据位、1或2停止位、奇偶校验等。

  4. 内置晶振: 集成了内部晶振,减少了外部元件的需求,简化了设计。

  5. 驱动支持: 通常需要安装相应的驱动程序,驱动程序由 CH340 提供,可在官方网站上下载。

  6. 广泛应用: CH340C 通常应用于嵌入式系统、单片机开发板、串口转 USB 转接器等场合。

连接方式:

一般情况下,CH340C 的连接方式如下:

  • USB 连接: CH340C 通过 USB 接口与计算机连接。
  • 串口连接: CH340C 通过串口引脚与目标设备连接,提供 TTL/CMOS 电平信号。

注意事项:

  • 在使用 CH340C 时,需要确保计算机正确安装了相应的驱动程序。通常,驱动程序会提供给用户,用户可以从 CH340 官方网站或相应硬件供应商处获取。

  • 根据具体的设计需求,使用 CH340C 时,需要注意其引脚分配和电气特性,确保连接正确并符合目标设备的规格。

总体而言,CH340C 提供了方便的 USB 转串口解决方案,适用于许多嵌入式系统和开发板应用。

2.5、RS-232异步通信协议

在这里插入图片描述
RS-232异步通信协议定义了数据帧的格式,包括启动位、有效数据位、校验位和停止位。以下是数据帧的基本结构:

  1. 启动位(Start Bit):

    • 必须占1个位长,保持逻辑0电平。
    • 用于指示数据帧的开始,通常为逻辑0。
  2. 有效数据位(Data Bits):

    • 可选占5、6、7、8、9个位长,LSB(最低有效位)在前,MSB(最高有效位)在后。
    • 有效数据位包含要传输的二进制数据。
  3. 校验位(Parity Bit):

    • 可选占1个位长,也可以没有该位。
    • 如果启用,用于检测数据传输中的错误。通常有奇偶校验,偶校验时校验位被设置为使数据位总数为偶数,奇校验时校验位被设置为使数据位总数为奇数。
  4. 停止位(Stop Bit):

    • 必须有,可选占0.5、1、1.5、2个位长,保持逻辑1电平。
    • 用于指示数据帧的结束,通常为逻辑1。

这种异步通信的数据帧结构提供了一种灵活的方式,使得设备能够以相对简单的方式进行通信。在传统的RS-232通信中,常见的配置是8数据位、无校验(或奇偶校验)、1停止位。不同设备之间需要协商并配置相同的数据帧格式,以确保正确的数据传输。

三、STM32的USART

3.1、STM32的USART简介

在这里插入图片描述

USART 通用同步异步收发器

STM32的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块是一种通用的串行通信模块,支持同步和异步通信。它可以用于与外部设备进行全双工的串行通信。USART在STM32系列微控制器中广泛应用,提供了灵活、高性能的串行通信解决方案。

以下是USART的一些基本特点和概述:

  1. 通用性: USART的通用性体现在其支持同步和异步通信。它可以灵活地适应不同的通信要求,使其成为多种应用场景下的理想选择。

  2. 异步通信: 在实际应用中,USART通常以异步通信的形式工作。异步通信是指数据传输不需要共享时钟信号,而是通过引入起始位、停止位等控制信息来同步数据。

  3. 全双工通信: USART支持全双工通信,允许同时进行数据的发送和接收,从而实现双向通信。这对于与外部设备进行双向数据交换非常重要。

  4. 波特率可调: USART支持可调的波特率,用户可以根据具体的应用需求选择适当的波特率。波特率是指每秒传输的比特数,通常用bps(比特每秒)来表示。

  5. 帧格式灵活: USART允许用户灵活配置帧格式,包括数据位数、停止位数、奇偶校验等。这使得USART适用于多种通信标准和设备之间的通信。

  6. DMA支持: STM32的USART模块通常支持DMA(Direct Memory Access)功能,可以通过DMA来实现数据的高效传输,减轻CPU的负担,提高性能。

  7. 中断支持: USART模块支持中断,通过中断机制,可以实现异步通信时的数据接收和发送中断处理。

USART在STM32中是一个非常重要且常用的外设,它为微控制器提供了强大的串行通信能力,适用于各种应用场景,包括与传感器、显示器、通信模块等外部设备的通信。在使用USART时,用户需要配置相关的寄存器和参数,以适应具体的通信需求。

UART 通用异步收发器

UART(Universal Asynchronous Receiver/Transmitter),通用异步收发器,是一种串行通信协议和硬件设备。UART主要用于异步通信,允许设备在没有共享时钟信号的情况下进行串行数据传输。UART是一种通用的串行通信标准,常用于连接微控制器、传感器、通信模块、计算机等设备。

以下是UART的一些基本概念和特点:

  1. 异步通信: UART通信是异步的,这意味着数据传输不需要共享时钟信号。相反,每个数据帧包含一个起始位(Start Bit)、一个或多个数据位、一个可选的校验位和一个或多个停止位。这些元素一起形成了一个数据帧,用于同步数据传输。

  2. 全双工通信: UART支持全双工通信,允许同时进行数据的发送和接收。这使得UART非常适用于双向通信场景,如设备之间的实时数据传输。

  3. 波特率可调: 波特率是指每秒传输的比特数,通常用bps(比特每秒)表示。UART允许用户灵活设置波特率,以适应不同的通信需求。

  4. 帧格式: UART数据帧的格式包括起始位、数据位、校验位和停止位。用户可以根据需要配置这些参数,以满足特定通信标准和设备的要求。

  5. 简单性: UART相对简单,易于实现。它在许多嵌入式系统和通信设备中得到广泛应用,因为其实现相对容易且成本较低。

  6. 用途广泛: UART在各种设备和场景中得到广泛应用,包括但不限于嵌入式系统、计算机外设、通信模块、传感器、GPS模块等。

总体而言,UART是一种通用的串行通信协议,提供了简单而灵活的解决方案,适用于多种应用场景。在使用UART时,用户需要配置相关参数,并根据通信双方的设置确保正确的数据帧格式。

3.2、 STM32的USART主要特征

在这里插入图片描述
STM32系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块是一种功能强大、灵活的串口通信模块。以下是STM32中USART的一些主要特征:

  1. 全双工异步通信: USART模块支持全双工通信,允许设备同时进行数据的发送和接收。这种特性使得STM32能够实现双向的异步通信,适用于复杂的通信需求。

  2. 单线半双工通信: USART还支持单线半双工通信模式,这种模式适用于只能在一段时间内进行发送或接收的应用场景。常见的应用如USART的单线通信模式(单线半双工)。

  3. 单独的发送器和接收器使能位: USART模块具有独立的发送器和接收器使能位,这使得用户可以根据需求单独启用或禁用发送和接收功能,提高灵活性。

  4. 可配置使用DMA的多缓冲器通信: USART模块支持使用DMA(Direct Memory Access)的通信,允许通过DMA传输数据,减轻CPU的负担,提高性能。此外,USART模块还支持多缓冲器通信,使得在数据传输时能够更高效地进行处理。

  5. 多个带标志的中断源: USART提供多个带标志的中断源,这使得用户可以监测和响应不同的事件,如数据接收、发送完成等。通过中断,可以实现异步通信时的事件处理,提高系统的实时性。

USART模块的这些特征使得STM32微控制器在通信领域具有广泛的适用性,可用于连接各种外部设备、传感器、通信模块等。用户可以根据具体的应用需求,选择合适的配置和特性以实现所需的通信功能。

如何快速查看STM32某个外设的数量及其对应的引脚?

路径:资料 / 8,STM32参考资料 / ST MCU 最新选型手册_2022.pdf
   资料 / 7,硬件资料 / 2,芯片资料 / STM32F103ZET6(中文版).pdf

在这里插入图片描述

表头注释描述
Commercial Product Code产品商业编号STM32F103ZET6
Core核心型号Cortex-M3
Frequency (MHz)核心频率 (MHz)处理器核心时钟频率(MHz)
Flash (Kbytes)Flash 存储器 (K字节)Flash 存储器大小(K字节)
RAM (Kbytes)RAM 内存 (K字节)RAM 内存大小(K字节)
Package封装集成电路封装类型
IOIO 引脚数输入/输出引脚数量
VDD供电电压供电电压
Timer (16-bit)定时器 (16位) 数量16位定时器数量
Advanced Timer (16-bit)高级定时器 (16位) 数量高级16位定时器数量
ADC 12-bit UnitsADC 12位 单元数12位ADC单元数量
ADC 12-bit ChannelsADC 12位 通道数12位ADC通道数量
DAC 12-bit ChannelsDAC 12位 通道数12位DAC通道数量
SPISPI 接口是否支持串行外设接口
I2SI2S 接口是否支持I2S接口
I2CI2C 接口是否支持I2C接口
U(S)ARTU(S)ART 接口是否支持USART接口
CANCAN 总线接口是否支持CAN总线接口
SDIOSDIO 接口是否支持SDIO接口
F(S)MCF(S)MC 接口是否支持灵活存储器控制器
USB DeviceUSB 设备接口是否支持USB设备接口
USB FS HOST/OTGUSB FS HOST/OTG 接口是否支持USB全速主机/OTG接口
Ethernet以太网接口是否支持以太网接口
Segment LCD分段 LCD 控制器是否支持分段LCD控制器
T° Max (℃)最大工作温度 ℃最大工作温度(摄氏度)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3、 STM32F1/F4/F7的USART框图

在这里插入图片描述

发送/接收数据的流程

发送和接收数据的流程可以总结如下:

发送数据流程:

  1. 用户通过 PRDATA 总线写操作将数据写入数据寄存器 DR。
  2. 数据寄存器 DR 控制将数据传送到发送数据寄存器 TDR。
  3. 发送数据寄存器 TDR 将数据传送到发送移位寄存器,准备发送。
  4. 最后,数据经过编解码模块 IrDASIR,由 TX 发送出去。

接收数据流程:

  1. 数据从 RX 接收进入编解码模块 IrDASIR。
  2. 接收移位寄存器接收并存储从编解码模块 IrDASIR 过来的数据。
  3. 接收数据寄存器 RDR 从接收移位寄存器获取数据。
  4. 用户通过读操作数据寄存器 DR 将接收到的数据传送到 PRDATA 总线。

综合上述过程,用户可以通过写操作将数据加载到发送数据寄存器,然后通过 TX 发送出去;同时,接收到的数据经过编解码模块,存储在接收移位寄存器中,最终通过读操作传送到数据寄存器,供用户使用。这是一个基本的数据发送和接收的流程,具体细节可能会依赖于硬件设计和编程接口的实现。

设置波特率

在USART(Universal Synchronous Asynchronous Receiver Transmitter)模块中,波特率是一个关键的设置,它决定了数据传输的速率。在传统的USART设置中,常用的波特率发送器相关寄存器和设置包括 USART_BRR、TE、RE 等,同时需要设置 USART 模块的时钟源。

以下是相关的解释:

波特率设置相关寄存器和标志:

  1. USART_BRR: 波特率寄存器,用于设置波特率的具体值。通常,波特率的计算公式是: B a u d r a t e = U S A R T 模块的时钟源 16 × U S A R T _ B R R {Baud rate} = \frac{{USART 模块的时钟源}}{16 \times {USART\_BRR}} Baudrate=16×USART_BRRUSART模块的时钟源 具体的计算过程会涉及到时钟源的选择和实际的波特率要求。

  2. TE (Transmitter Enable): 发送器使能位。设置 TE 位允许 USART 发送器工作。

  3. RE (Receiver Enable): 接收器使能位。设置 RE 位允许 USART 接收器工作。

时钟源设置:

  1. USART 模块的时钟源: USART 模块需要一个时钟源来生成波特率。时钟源可以是外部时钟(如外部晶振),也可以是内部时钟。

  2. 通过发送器时钟和接收器时钟: USART 模块的发送器和接收器都需要时钟信号。时钟可以是外部时钟,也可以是内部时钟,具体的时钟源和时钟频率设置需要根据硬件设计和应用的要求进行选择。

  3. 发送器控制和接收器控制: 时钟源会通过发送器控制和接收器控制来分别控制发送器和接收器的时钟。

实现发送和接收:

  1. TE 和 RE 位: 发送器和接收器的使能位需要设置为使能状态,以启动发送和接收操作。

  2. 时钟源和时钟频率: 确保 USART 模块的时钟源和时钟频率设置正确,以满足波特率的要求。

USARTDIV 的计算:

通过使用公式 USARTDIV = DIV_Mantissa + (DIV_Fraction/16) 计算整体的波特率值。
U S A R T D I V = D I V _ M a n t i s s a + ( D I V _ F r a c t i o n / 16 ) USARTDIV = DIV\_Mantissa + (DIV\_Fraction/16) USARTDIV=DIV_Mantissa+(DIV_Fraction/16)

  1. USARTDIV: 波特率寄存器的整体值,由 Mantissa 和 Fraction 组成。

  2. DIV_Mantissa: 波特率寄存器的整数部分,用于设置波特率的主要部分。

  3. DIV_Fraction: 波特率寄存器的小数部分,用于微调波特率。

以上是一般情况下在USART模块中设置波特率的基本步骤。具体的寄存器名称和位定义可能会因不同的微控制器型号而有所不同,因此需要参考相应的芯片手册或数据手册以获取准确的信息。

3.4、STM32H7的USART框图

在这里插入图片描述

时钟域和时钟源

在STM32H7系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块中,存在与时钟相关的域和源。以下是关于时钟域和时钟源的基本信息:

时钟域:

  1. usart_ker_ck 时钟域:

    • usart_ker_ck 是 USART 模块的内部时钟域,它表示 USART 核心时钟。
    • 该时钟域为 USART 模块提供内部时钟,用于控制 USART 的操作。
  2. usart_pclk 时钟域:

    • usart_pclk 是 USART 模块的外设时钟域。
    • 该时钟域表示 USART 外设的时钟,通常由总线上的外设时钟源提供。

时钟源:

  1. usart_ker_ck_pres 时钟源:
    • usart_ker_ck_pres 是 USART 模块的时钟源。
    • 时钟源用于提供 usart_ker_ck,从而为 USART 模块的内部时钟域提供时钟。

在STM32H7系列中,时钟源 usart_ker_ck_pres 可能会与系统时钟(HCLK)、外部时钟源(例如晶振)或其他时钟源相关联,具体的配置和选择取决于用户的设置和系统的设计。时钟源的选择和配置通常在时钟初始化和配置中完成。

注意:具体的寄存器和位定义可能因不同的STM32H7芯片型号而有所不同,因此确切的信息需要参考相应型号的参考手册或数据手册。

发送/接收数据的流程

在STM32H7系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块中,发送和接收数据的流程涉及到寄存器和缓冲区。以下是简要的发送和接收数据的流程:

发送数据流程:

  1. 32位APB总线通过 USART_TDR 寄存器:

    • 用户通过32位APB总线(可能是AHB或APB总线)将数据写入 USART_TDR 寄存器,该寄存器是 USART 的传输数据寄存器。
  2. TxFIFO 控制 TX 移位寄存器:

    • 发送数据从 USART_TDR 寄存器进入 TxFIFO(发送FIFO)缓冲区。
    • 控制单元负责将数据从 TxFIFO 传送到 TX 移位寄存器,准备进行数据发送。
  3. TX 进行数据发送:

    • 数据从 TX 移位寄存器发送到 USART 的发送引脚,进入通信总线,最终发送到外部设备。

接收数据流程:

  1. RX 接收数据:

    • 外部设备通过通信总线将数据发送到 USART 的接收引脚,进入 RX 移位寄存器。
  2. RX 移位寄存器到 RxFIFO:

    • 接收数据从 RX 移位寄存器进入 RxFIFO(接收FIFO)缓冲区。
  3. RxFIFO 到 USART_RDR 寄存器读取数据:

    • 控制单元负责将数据从 RxFIFO 传送到 USART_RDR 寄存器,用户可以通过读取 USART_RDR 寄存器获取接收到的数据。

这是一个简化的流程描述,实际上,流程的细节和性能可能取决于配置,例如使用DMA进行数据传输的情况下,数据可能直接从/到内存中传递,而不经过寄存器。因此,具体的细节可能需要参考STM32H7系列的参考手册或数据手册,以确保正确的配置和理解数据传输的细节。

设置波特率

在STM32H7系列的USART模块中,设置波特率涉及到时钟域、时钟源、USART_PRESC寄存器、波特率发生器(USART_BRR),以及过采样的配置。以下是一个简要的描述:

  1. 时钟域(usart_ker_ck):

    • usart_ker_ck 是USART模块的内部时钟域,用于控制USART的核心时钟。
  2. 时钟源(usart_ker_ck_pres):

    • usart_ker_ck_pres 是USART模块的时钟源。
    • 时钟源用于提供 usart_ker_ck,从而为USART模块的内部时钟域提供时钟。
  3. 波特率发生器寄存器(USART_BRR):

    • 波特率发生器用于生成适当的波特率。
    • 波特率的计算公式通常为: B a u d r a t e = U S A R T 模块的时钟源 16 × U S A R T _ B R R Baud rate = \frac{USART 模块的时钟源}{16 \times USART\_BRR} Baudrate=16×USART_BRRUSART模块的时钟源
  4. USART_PRESC 寄存器 配置过采样率:

    • USART_PRESC 寄存器用于设置过采样率(OverSampling)。
    • 过采样率的选择可能为 8 或 16。

波特率设置的基本流程:

  1. 选择时钟源和时钟域:

    • 配置 usart_ker_ck_pres,确定 USART 模块的时钟源。
    • 确保 usart_ker_ck 的时钟域配置正确。
  2. 配置过采样率(OverSampling):

    • 通过 USART_PRESC 寄存器配置过采样率。
  3. 计算和配置波特率:

    • 通过计算确定合适的 USART_BRR 的值,确保波特率满足通信需求。
    • 将计算得到的值写入 USART_BRR 寄存器。
  4. 使能 USART 模块:

    • 设置相应的控制寄存器以启用 USART 模块。
    • 使能发送和接收器。
  5. 其他配置:

    • 根据需求,配置其他 USART 相关寄存器,例如数据位数、停止位数、校验等。

以上流程的具体实现可能会因芯片型号和具体的应用而有所不同,因此建议查阅STM32H7系列的参考手册和数据手册,以获取详细的寄存器定义和配置信息。

STM32F1/F4/F7/H7的USART框图简化版

在这里插入图片描述
串口数据的发送和接收过程在不同系列的STM32微控制器中有一些差异。以下是对STM32F1、F4、F7和H7系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块的串口数据发送和接收过程的简要描述:

串口数据接收过程:

STM32F1/F4系列:
  1. RXD引脚接收数据:

    • 外部设备通过 RXD 引脚将串行数据输入到 USART 模块。
  2. 接收数据到接收数据寄存器RDR:

    • 数据经过接收移位寄存器,最终存储在接收数据寄存器 RDR 中。
    • CPU 或 DMA 可以从 RDR 中读取接收到的数据。
STM32F7/H7系列:
  1. RXD引脚接收数据:

    • 外部设备通过 RXD 引脚将串行数据输入到 USART 模块。
  2. 接收数据到CPU或DMA:

    • 接收到的数据直接传递给 CPU 或 DMA,无需经过特定的接收数据寄存器。

串口数据发送过程:

STM32F1/F4系列:
  1. CPU或DMA数据到发送数据寄存器TDR:

    • CPU 或 DMA 将要发送的数据写入发送数据寄存器 TDR。
    • 数据寄存器 DR 也可用于存储数据,然后由 TDR 发送。
  2. 发送数据通过TXD引脚输出:

    • 数据经过发送移位寄存器,通过 TXD 引脚发送到外部设备。
STM32F7/H7系列:
  1. CPU或DMA数据到发送数据寄存器TDR:

    • CPU 或 DMA 将要发送的数据写入发送数据寄存器 TDR。
  2. 发送数据通过TXD引脚输出:

    • 数据经过发送移位寄存器,通过 TXD 引脚发送到外部设备。

在F1/F4系列中,数据可能会先经过数据寄存器 DR 再到发送数据寄存器 TDR。而在F7/H7系列中,数据直接由 TDR 发送。

以上流程是一个简化的描述,具体的实现可能因芯片型号和配置而有所不同。详细的信息需要查阅相应系列的参考手册或数据手册。

3.5、设置USART波特率(F1)

在这里插入图片描述
在STM32F1系列的微控制器中,要设置USART的波特率,需要使用波特率计算公式,并根据具体的时钟频率进行计算。以下是基本的步骤:

  1. 确定USART的时钟源:

    • 对于USART1,时钟源是PCLK2;对于其他串口(USART2至USART5),时钟源是PCLK1。
  2. 计算USART的时钟频率( f c k f_{ck} fck):

    • 时钟频率 f c k f_{ck} fck 可以使用相应的时钟源频率。对于PCLK2和PCLK1,可以查阅系统时钟配置。
  3. 使用波特率计算公式:

    • 波特率计算公式为 b a u d = f c k 16 × U S A R T D I V {baud} = \frac{f_{ck}}{16 \times{USARTDIV}} baud=16×USARTDIVfck
  4. 计算USARTDIV:

    • 从波特率计算公式中解出 U S A R T D I V = f c k 16 × b a u d {USARTDIV} = \frac{f_{ck}}{16 \times{baud}} USARTDIV=16×baudfck
  5. 设置USART的BRR寄存器:

    • 将计算得到的整数部分值写入USART的BRR寄存器。

以下是一个简单的示例代码,假设要设置USART1的波特率为9600:

#include "stm32f1xx.h"

void USART1_Init(uint32_t baudrate) {
    // 1. 选择USART1的时钟源为PCLK2
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    // 2. 计算USART的时钟频率(f_ck)
    uint32_t f_ck = SystemCoreClock / 2; // 假设PCLK2为系统时钟的一半

    // 3. 计算USARTDIV
    uint32_t USARTDIV = f_ck / (16 * baudrate);

    // 4. 设置BRR寄存器
    USART1->BRR = USARTDIV;

    // 其他设置,例如使能USART、设置数据位、停止位等
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // 使能USART、使能发送和接收
    USART1->CR2 = 0; // 默认配置
    USART1->CR3 = 0; // 默认配置
}

int main(void) {
    // 初始化USART1,波特率为9600
    USART1_Init(9600);

    while (1) {
        // 主循环
    }
}

这只是一个基本的设置波特率的例子,实际的配置可能还需要根据具体的应用要求进行调整。请参考相应的STM32F1系列参考手册或数据手册以获取更详细的信息。

波特比率寄存器(BRR)

在这里插入图片描述
在STM32F1系列的USART模块中,波特率寄存器(BRR,Baud Rate Register)是用于设置波特率的关键寄存器。BRR寄存器的位[15:4]用于存放USARTDIV的整数部分,而位[3:0]用于存放USARTDIV的小数部分。

以下是一些关键的细节:

  1. USARTDIV:

    • USARTDIV是波特率计算中的一个参数,表示波特率发生器的分频系数。
    • 波特率计算公式为: b a u d = f c k 16 × USARTDIV {baud} = \frac{f_{ck}}{16 \times \text{USARTDIV}} baud=16×USARTDIVfck
  2. BRR寄存器的位[15:4]:

    • BRR寄存器的位[15:4]用于存放USARTDIV的整数部分。
    • 将计算得到的整数部分写入BRR的相应位。
  3. BRR寄存器的位[3:0]:

    • BRR寄存器的位[3:0]用于存放USARTDIV的小数部分。
    • 通常,小数部分需要转换为4位的定点数,例如: 小数部分 = ( 小数 × 16 10000 ) × 16 {小数部分} = \left( \frac{小数 \times 16}{10000} \right) \times 16 小数部分=(10000小数×16)×16
    • 然后将计算得到的小数部分写入BRR的相应位。

如何使用寄存器操作的方式设置波特率 - USART1为例

在这里插入图片描述
设置串口的波特率,是针对STM32中的USART1模块。在STM32中,波特率的设置涉及到USART的波特率寄存器(BRR)。

根据代码,已经计算了Mantissa和Fraction,并将它们组合到BRR寄存器中。波特率的计算公式为:

Baud Rate = f c k 16 × USARTDIV \text{Baud Rate} = \frac{\text{f}_{ck}}{16 \times \text{USARTDIV}} Baud Rate=16×USARTDIVfck

其中, USARTDIV = Mantissa + Fraction 16 \text{USARTDIV} = \text{Mantissa} + \frac{\text{Fraction}}{16} USARTDIV=Mantissa+16Fraction

得到USARTDIV为39.0625。USART的波特率寄存器BRR要求整数部分和小数部分分开设置。

uint16_t mantissa; 
uint16_t fraction; 

mantissa = 39; 
fraction = 0.0625*16+0.5=0x01; /* USARTDIV = DIV_Mantissa + (DIV_Fraction/16) */
USART1->BRR = (mantissa << 4) + fraction;

设置STM32中USART1的波特率寄存器(BRR)

  1. mantissa = 39;: 这是Mantissa的设置,Mantissa代表波特率的整数部分。在这里,Mantissa被设置为39。

  2. fraction = 0.0625 * 16 + 0.5 = 0x01;: 这是Fraction的设置,Fraction代表波特率的小数部分。这里使用了0.0625(1/16)的分辨率,并添加了0.5进行四舍五入。最终,Fraction被计算为0x01。

  3. USART1->BRR = (mantissa << 4) + fraction;: 这一行将Mantissa左移4位(相当于乘以16),然后将Fraction加到结果中,最终设置了USART1的波特率寄存器BRR。

综合来看,您的代码在设置USART1的波特率时,使用了整数部分39(Mantissa)和小数部分0.0625(Fraction)。这样的计算方式适用于很多情况下,但请确保在具体的应用中,APB2 clock 的频率和其他USART1的配置参数都符合您的需求。

在实际应用中,确保APB2 clock 是您期望的时钟频率,通常是系统时钟频率。此外,确保USART1已经启用,并且适当配置了其它参数,如数据位、停止位等。

如果您有具体的问题或需要更多的帮助,可以提供更多上下文或问题的细节。

波特率设置通用公式推演(F1)

在这里插入图片描述
在STM32F1系列微控制器中,波特率的设置仍然涉及到USART的波特率寄存器(BRR)。下面是推导过程:

  1. 波特率计算公式: baud = f c k 16 × USARTDIV \text{baud} = \frac{\text{f}_{ck}}{16 \times \text{USARTDIV}} baud=16×USARTDIVfck

  2. 推导USARTDIV的表达式: USARTDIV = f c k 16 × baud \text{USARTDIV} = \frac{\text{f}_{ck}}{16 \times \text{baud}} USARTDIV=16×baudfck

  3. 将USARTDIV写入波特率寄存器BRR,考虑到STM32的BRR要求整数部分和小数部分的分开设置:

    USART1->BRR = USARTDIV × 16 + 0.5 \text{USART1->BRR} = \text{USARTDIV} \times 16 + 0.5 USART1->BRR=USARTDIV×16+0.5

    把 USARTDIV 代入再次推导

    USART1->BRR = f c k / baud + 0.5 \text{USART1->BRR} = \text{f}_{ck}/\text{baud} + 0.5 USART1->BRR=fck/baud+0.5

    这是一种通用的表示,其中 baud \text{baud} baud 是波特率, f c k \text{f}_{ck} fck 是时钟频率。

  4. 如果我们考虑具体的寄存器版本的写法,根据上述公式进行替换:

    USART1->BRR = f c k + b a u d 2 baud \text{USART1->BRR} = \frac{\text{f}_{ck} + \frac{baud}{2}}{\text{baud}} USART1->BRR=baudfck+2baud

    这是根据波特率计算的推导,最终用于配置USART1的波特率寄存器BRR。

请注意,这里的具体数值取决于你的具体应用中的时钟频率和波特率。希望这能帮助您理解STM32F1系列中波特率设置的推导过程。如果有其他问题,或者需要更多解释,请随时提出。

3.6、设置USART波特率(F4)

在这里插入图片描述
在STM32F4系列中,设置USART/UART的波特率涉及到计算USARTDIV值。波特率计算公式如下:

baud = f c k 8 × ( 2 − OVER8 ) × USARTDIV \text{baud} = \frac{f_{ck}}{8 \times (2 - \text{OVER8}) \times \text{USARTDIV}} baud=8×(2OVER8)×USARTDIVfck

其中:

  • f c k f_{ck} fck 是串口的时钟频率: f P C L K x ( x = 1 , 2 ) f_{PCLKx(x=1, 2)} fPCLKx(x=1,2),可以是 f P C L K 1 f_{PCLK1} fPCLK1 f P C L K 2 f_{PCLK2} fPCLK2 中的一个,具体取决于使用的USART/UART模块。
  • OVER8 \text{OVER8} OVER8 是USART寄存器中的一个配置位,它决定了波特率调制的方式,可以是0或1。
  • USARTDIV \text{USARTDIV} USARTDIV 是要设置的寄存器值,通过计算后写入USART的寄存器。

具体步骤如下:

  1. 确定 f c k f_{ck} fck 的值,选择使用 f P C L K 1 f_{PCLK1} fPCLK1 f P C L K 2 f_{PCLK2} fPCLK2
  2. 选择合适的 OVER8 \text{OVER8} OVER8 值,根据需求配置波特率调制方式。
  3. 使用上述公式计算出合适的 USARTDIV \text{USARTDIV} USARTDIV 值。
  4. 将计算得到的 USARTDIV \text{USARTDIV} USARTDIV 写入USART的相应寄存器,通常是BRR(波特率寄存器)。

需要注意的是,通常情况下, f P C L K 1 f_{PCLK1} fPCLK1 f P C L K 2 f_{PCLK2} fPCLK2 的值可以从RCC寄存器中获取。

如果你有具体的时钟频率和波特率要求,我可以帮你进行具体的计算。

波特率寄存器(BRR)

在这里插入图片描述
过采样模式是指在串口通信中,接收端对于输入信号的采样次数。在USART通信中,有两种过采样模式:8倍过采样和16倍过采样。

  1. 8倍过采样:

    • 在这种模式下,每个波形周期内进行8次采样。
    • 通过USART_CR1寄存器的OVER8位设置为1来选择8倍过采样。
  2. 16倍过采样:

    • 在这种模式下,每个波形周期内进行16次采样。
    • 通过USART_CR1寄存器的OVER8位设置为0来选择16倍过采样。

选择过采样模式的关键影响因素之一是通信环境的噪声水平。在高噪声环境中,使用16倍过采样模式可能更有利于准确地检测和还原信号。另一方面,8倍过采样模式在某些情况下可能更适用,特别是在较低噪声环境下,因为它会占用较少的系统资源。

总体而言,选择过采样模式通常是根据具体的通信需求和环境来决定的。

3.7、设置USART波特率(F7)

在这里插入图片描述
在STM32F7系列中,设置USART/UART的波特率需要根据时钟频率和过采样模式选择使用不同的计算公式。以下是波特率计算公式:

  1. 16倍过采样模式:
    baud = f c k USARTDIV \text{baud} = \frac{f_{ck}}{\text{USARTDIV}} baud=USARTDIVfck

  2. 8倍过采样模式:
    baud = 2 × f c k USARTDIV \text{baud} = \frac{2 \times f_{ck}}{\text{USARTDIV}} baud=USARTDIV2×fck

其中, f c k f_{ck} fck 是串口的时钟频率。在STM32F7系列中,你可以参考RCC专用时钟配置寄存器 (DCKCFGR)获取时钟频率。

具体步骤如下:

  1. 从RCC专用时钟配置寄存器 (DCKCFGR) 获取串口的时钟频率 f c k f_{ck} fck
  2. 根据需要选择过采样模式,如果选择16倍过采样,使用第一个公式;如果选择8倍过采样,使用第二个公式。
  3. 计算出合适的 USARTDIV \text{USARTDIV} USARTDIV
  4. 将计算得到的 USARTDIV \text{USARTDIV} USARTDIV 写入USART的相应寄存器。

如果你有具体的时钟频率和波特率要求,我可以帮你进行具体的计算。

波特率寄存器(BRR)

在这里插入图片描述

3.8、设置USART波特率(H7)

在这里插入图片描述
在STM32H7系列中,设置USART/UART的波特率需要根据时钟频率和过采样模式选择使用不同的计算公式。以下是波特率计算公式:

  1. 16倍过采样模式:
    baud = usart_ker_ckpres USARTDIV \text{baud} = \frac{\text{usart\_ker\_ckpres}}{\text{USARTDIV}} baud=USARTDIVusart_ker_ckpres

  2. 8倍过采样模式:
    baud = 2 × usart_ker_ckpres USARTDIV \text{baud} = \frac{2 \times \text{usart\_ker\_ckpres}}{\text{USARTDIV}} baud=USARTDIV2×usart_ker_ckpres

其中, usart_ker_ckpres \text{usart\_ker\_ckpres} usart_ker_ckpres 是串口的工作时钟,你可以从RCC域2内核时钟配置寄存器 (RCC_D2CCIP2R) 获取。

具体步骤如下:

  1. 从RCC域2内核时钟配置寄存器 (RCC_D2CCIP2R) 获取串口的工作时钟 usart_ker_ckpres \text{usart\_ker\_ckpres} usart_ker_ckpres
  2. 根据需要选择过采样模式,如果选择16倍过采样,使用第一个公式;如果选择8倍过采样,使用第二个公式。
  3. 计算出合适的 USARTDIV \text{USARTDIV} USARTDIV
  4. 将计算得到的 USARTDIV \text{USARTDIV} USARTDIV 写入USART的相应寄存器。

如果你有具体的时钟频率和波特率要求,我可以帮你进行具体的计算。

波特率寄存器(BRR)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.9、USART寄存器介绍(F1)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在STM32F1系列中,USART寄存器包括控制寄存器1(CR1)、控制寄存器2(CR2)、控制寄存器3(CR3)、数据寄存器(DR)以及状态寄存器(SR)。以下是对各寄存器的配置要求和功能介绍:

  1. 控制寄存器1(CR1):

    • 位13(UE): 使能USART,设置为1表示使能USART。
    • 位12(M): 配置数据位数,设置为0表示使用8个数据位。
    • 位10(PCE): 禁止检验控制,设置为0表示禁止检验。
    • 位5(RXNEIE): 使能接收缓冲区非空中断,设置为1表示使能该中断。
    • 位3(TE): 使能发送,设置为1表示使能发送。
    • 位2(RE): 使能接收,设置为1表示使能接收。
  2. 控制寄存器2(CR2):

    • 配置1个停止位,通常无需特殊配置,因为1个停止位是默认设置。
  3. 控制寄存器3(CR3):

    • 配置不选择半双工模式,通常无需特殊配置,因为全双工模式是默认设置。
  4. 数据寄存器(DR):

    • 在设置好控制寄存器1和波特率寄存器后,往该寄存器写入数据即可发送。
    • 读该寄存器可以接收数据。
  5. 状态寄存器(SR):

    • 根据TC位可以知道能否发数据,TC位表示发送完成。
    • 根据RXNE位知道是否收到数据,RXNE位表示接收缓冲区非空。

这些寄存器的配置和使用在初始化USART并进行数据收发时非常关键。确保根据需求正确配置这些位,以实现期望的USART功能。

需要配置的时序总结

在这里插入图片描述
总结配置时序如下:

  1. 启动位:

    • 必须占1个位长。
    • 保持逻辑0电平。
  2. 有效数据位:

    • 8个位长。
    • 数据位的顺序可以选择是LSB(Least Significant Bit)在前还是MSB(Most Significant Bit)在前。
  3. 校验位:

    • 校验位是可选的,可以选择是否启用。
    • 如果启用校验位,其位长取决于校验方式(如无校验、奇校验、偶校验等)。
  4. 停止位:

    • 1个位长。
    • 保持逻辑1电平。

在配置USART时,你需要根据具体的通信需求选择数据位的顺序、是否使用校验位以及选择何种校验方式。上述总结涵盖了基本的USART配置时序,确保按照这些要求进行配置可以实现稳定和可靠的串口通信。

四、HAL库外设初始化MSP回调机制

在这里插入图片描述
在STM32Cube HAL库中,MSP(Microcontroller Support Package)是外设初始化的回调机制。MSP回调函数允许用户在HAL库生成的代码中插入自定义的外设初始化操作。这对于在初始化外设之前或之后执行一些额外的配置或任务非常有用。

MSP回调涉及到两个函数:HAL_MspInit()HAL_MspDeInit()。这两个函数分别用于外设的初始化和去初始化。

  1. HAL_MspInit():

    • 该函数在HAL库的HAL_Init()函数中调用。
    • 用户可以在此函数中执行与外设初始化相关的自定义配置,例如时钟配置、引脚配置等。
    • 通过覆盖此函数,用户可以插入自定义的初始化代码。
    void HAL_MspInit(void)
    {
        // 用户自定义的外设初始化代码
        // 例如:时钟配置、引脚配置等
    }
    
  2. HAL_MspDeInit():

    • 该函数在HAL库的HAL_DeInit()函数中调用。
    • 用户可以在此函数中执行与外设去初始化相关的自定义配置,例如时钟配置、引脚配置等。
    • 通过覆盖此函数,用户可以插入自定义的去初始化代码。
    void HAL_MspDeInit(void)
    {
        // 用户自定义的外设去初始化代码
        // 例如:时钟配置、引脚配置等
    }
    

用户可以在工程的源代码中实现这两个函数,并根据具体需求插入相应的初始化和去初始化代码。这种机制提供了一种可靠的方式,使用户能够在HAL库生成的代码中添加自定义的外设配置。

HAL库外设初始化MSP回调机制- USART为例

在这里插入图片描述
在代码示例中,重写了HAL库生成的UART初始化函数的MSP回调函数 HAL_UART_MspInit(),并对USART1进行了初始化。这是一种典型的用法,允许在初始化UART之前执行一些自定义的配置。注释提到了三个步骤,我将解释一下:

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;
    if(huart->Instance == USART1)               /* 如果是串口1,进行串口1 MSP初始化 */
    {
        /* (1)使能USART1和对应IO时钟,(2)初始化IO,(3)使能USART1中断,设置优先级 */

        // 步骤(1):使能USART1和对应IO时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        // 步骤(2):初始化IO
        gpio_init_struct.Pin = GPIO_PIN_9 | GPIO_PIN_10;   // USART1 TX (PA9) and RX (PA10)
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;
        gpio_init_struct.Pull = GPIO_NOPULL;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
        gpio_init_struct.Alternate = GPIO_AF7_USART1;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        // 步骤(3):使能USART1中断,设置优先级
        HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
    }
}

上述代码在USART1初始化时执行了三个步骤:

  1. 使能USART1和对应IO时钟。
  2. 初始化IO,配置USART1的TX和RX引脚。
  3. 使能USART1中断,并设置中断优先级。

这样,可以在UART初始化之前根据需要自定义配置,确保UART的正常工作。
在这里插入图片描述

五、HAL库中断回调机制

在这里插入图片描述
在STM32Cube HAL库中,中断回调机制是通过注册回调函数来处理中断事件的一种方式。当中断事件发生时,HAL库会调用用户在代码中预先定义的回调函数。这使得用户可以在中断服务程序(ISR)内执行定制的操作。

以下是在HAL库中使用中断回调的基本步骤:

  1. 定义回调函数:
    在你的代码中定义回调函数。这个函数应该匹配特定外设的中断处理函数的原型。例如,对于USART,你可能需要定义一个函数,它的原型为 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)。在这个函数中,你可以处理接收完成中断的相关逻辑。

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
        // 处理接收完成中断的逻辑
    }
    
  2. 注册回调函数:
    在你的初始化代码中,使用HAL库提供的相应函数注册你的回调函数。例如,在初始化USART时,可以使用 HAL_UART_RegisterCallback() 函数注册回调。

    HAL_UART_RegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID, HAL_UART_RxCpltCallback);
    

    这样,当USART1接收完成中断发生时,HAL_UART_RxCpltCallback() 函数就会被调用。

这是一个简单的例子,实际的步骤可能会因外设和中断类型而有所不同。确保查阅相关的HAL库文档,以了解你所使用外设的中断回调机制的详细信息。

使用中断回调机制的好处之一是,它允许你将中断处理逻辑从主程序中分离出来,使得代码更加模块化和可维护。

HAL库中断回调机制 - USART为例(F1)

在这里插入图片描述
在STM32Cube HAL库中处理USART和UART中断的机制。以下是相关函数和回调函数的一些概念:

  1. USARTx_IRQHandler() 或 UARTx_IRQHandler():

    • 这是具体USART或UART外设中断的处理函数。对于每个USART或UART外设,都有相应的中断处理函数。例如,对于USART1,中断处理函数为USART1_IRQHandler()。
  2. 用户调用HAL库中断共用处理函数 HAL_USART_IRQHandler() 或 HAL_UART_IRQHandler():

    • 这是HAL库提供的中断处理函数,用于处理USART或UART的中断。用户在USARTx_IRQHandler() 或 UARTx_IRQHandler() 中调用这个函数,以便由HAL库处理中断事件。
  3. HAL库自己调用中断回调函数:

    • HAL库中,在处理中断时会调用相应的中断回调函数。根据中断类型,以下是可能被调用的回调函数:
      • HAL_UART_TxCpltCallback(): 发送完成回调函数
      • HAL_UART_TxHalfCpltCallback(): 半发送完成回调函数
      • HAL_UART_RxCpltCallback(): 接收完成回调函数
      • HAL_UART_RxHalfCpltCallback(): 半接收完成回调函数
      • HAL_UART_ErrorCallback(): UART错误回调函数
      • HAL_UART_AbortCpltCallback(): UART中止回调函数
      • HAL_UART_AbortTransmitCpltCallback(): UART发送中止回调函数
      • HAL_UART_AbortReceiveCpltCallback(): UART接收中止回调函数
  4. 用户选择自己需要的回调函数进行重新定义:

    • 用户可以选择根据需要重新定义上述提到的回调函数,以适应自己的应用。例如,如果你希望在接收完成时执行特定的逻辑,可以重新定义 HAL_UART_RxCpltCallback()。

总的来说,这种回调机制允许用户定制USART或UART中断的处理逻辑,使得代码更加灵活和可维护。
在这里插入图片描述
stm32f1xx_hal_uart.c
HAL_UART_IRQHandler( )函数

/**
  * @brief  This function handles UART interrupt request.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  uint32_t isrflags   = READ_REG(huart->Instance->SR);
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  uint32_t errorflags = 0x00U;
  uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if (errorflags == RESET)
  {
    /* UART in mode Receiver -------------------------------------------------*/
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      UART_Receive_IT(huart);
      return;
    }
  }

  /* If some errors occur */
  if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    /* UART parity error interrupt occurred ----------------------------------*/
    if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }

    /* UART noise error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }

    /* UART frame error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }

    /* UART Over-Run interrupt occurred --------------------------------------*/
    if (((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    /* Call UART Error Call back function if need be --------------------------*/
    if (huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
      /* UART in mode Receiver -----------------------------------------------*/
      if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
      {
        UART_Receive_IT(huart);
      }

      /* If Overrun error occurs, or if any error occurs in DMA mode reception,
         consider error as blocking */
      dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
      if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
      {
        /* Blocking error : transfer is aborted
           Set the UART state ready to be able to start again the process,
           Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
        UART_EndRxTransfer(huart);

        /* Disable the UART DMA Rx request if enabled */
        if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
        {
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

          /* Abort the UART DMA Rx channel */
          if (huart->hdmarx != NULL)
          {
            /* Set the UART DMA Abort callback :
               will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
            huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
            if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
            {
              /* Call Directly XferAbortCallback function in case of error */
              huart->hdmarx->XferAbortCallback(huart->hdmarx);
            }
          }
          else
          {
            /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
            /*Call registered error callback*/
            huart->ErrorCallback(huart);
#else
            /*Call legacy weak error callback*/
            HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
          }
        }
        else
        {
          /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
          /*Call registered error callback*/
          huart->ErrorCallback(huart);
#else
          /*Call legacy weak error callback*/
          HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
        }
      }
      else
      {
        /* Non Blocking error : transfer could go on.
           Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered error callback*/
        huart->ErrorCallback(huart);
#else
        /*Call legacy weak error callback*/
        HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

  /* UART in mode Transmitter ------------------------------------------------*/
  if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }

  /* UART in mode Transmitter end --------------------------------------------*/
  if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  {
    UART_EndTransmit_IT(huart);
    return;
  }
}

在这里插入图片描述

六、USART/UART异步通信配置步骤

在这里插入图片描述
配置USART/UART异步通信的主要步骤。以下是对每个步骤的简要说明:

  1. 配置串口工作参数:

    • 使用 HAL_UART_Init() 函数配置USART/UART的工作参数,例如波特率、数据位、停止位、校验位等。
    HAL_UART_Init(&huart1);  // 例如,初始化USART1
    
  2. 串口底层初始化:

    • 通过 HAL_UART_MspInit() 函数配置底层硬件相关的参数,包括GPIO配置、NVIC配置、时钟配置等。
    void HAL_UART_MspInit(UART_HandleTypeDef *huart)
    {
        // 配置GPIO、NVIC、CLOCK等
        // 例如,使能USART1和对应IO时钟,配置TX和RX引脚
    }
    
  3. 开启串口异步接收中断:

    • 使用 HAL_UART_Receive_IT() 函数启动异步接收中断,使USART/UART能够在接收到数据时触发中断。
    HAL_UART_Receive_IT(&huart1, rx_buffer, BUFFER_SIZE);
    
  4. 设置优先级,使能中断:

    • 使用 HAL_NVIC_SetPriority()HAL_NVIC_EnableIRQ() 函数设置中断优先级并使能中断。
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
    
  5. 编写中断服务函数:

    • 编写中断服务函数 USARTx_IRQHandler()UARTx_IRQHandler() 来处理中断事件。这个函数通常会调用HAL库生成的中断处理函数和用户自定义的中断回调函数。
    void USART1_IRQHandler(void)
    {
        HAL_UART_IRQHandler(&huart1);  // 处理中断,调用HAL库的中断处理函数
    }
    
  6. 串口数据发送:

    • 使用 HAL_UART_Transmit() 函数发送数据。你还可以直接访问 USART_DR 寄存器来发送数据。
    HAL_UART_Transmit(&huart1, tx_data, sizeof(tx_data), HAL_MAX_DELAY);
    // 或者直接访问寄存器
    USART1->DR = data;
    

这些步骤构成了USART/UART异步通信的基本配置流程。确保按照这些步骤进行配置,以确保串口的正常工作。

HAL库相关函数介绍

串口的初始化函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

开启串口接收中断函数

在这里插入图片描述

串口阻塞发送函数

在这里插入图片描述

七、IO引脚复用功能

IO引脚的复用功能是指一根物理引脚能够在不同的工作模式下执行不同的功能。这种特性使得单个引脚能够适应多种外设和功能,提高了硬件资源的灵活性。在微控制器中,通常通过将引脚配置为特定的复用功能来实现这一点。以下是一些常见的IO引脚复用功能:

  1. GPIO (通用输入/输出):

    • 引脚被配置为通用输入/输出模式,可以用于普通的数字输入或输出。
  2. USART (通用同步/异步收发器):

    • 引脚被配置为USART模式,用于串口通信。
  3. SPI (串行外围接口):

    • 引脚被配置为SPI模式,用于串行外围设备之间的通信。
  4. I2C (Inter-Integrated Circuit):

    • 引脚被配置为I2C模式,用于在集成电路之间进行通信。
  5. PWM (脉冲宽度调制):

    • 引脚被配置为PWM输出,用于控制电机、LED等。
  6. ADC (模数转换器):

    • 引脚被配置为ADC输入,用于模数转换。
  7. DAC (数字模拟转换器):

    • 引脚被配置为DAC输出,用于数字到模拟转换。
  8. CAN (控制器局域网):

    • 引脚被配置为CAN模式,用于控制器局域网通信。

在实际使用中,通过芯片厂商提供的工具或API,可以配置IO引脚的复用功能。通常,这些配置需要在初始化代码中进行,确保引脚在系统启动时按照预期的方式进行配置。在STM32系列微控制器中,使用STM32CubeMX和HAL库可以方便地配置IO引脚的复用功能。

7.1、何为复用?

在这里插入图片描述
通用和复用与IO端口的输入/输出与GPIO外设的控制关系相关。让我进一步解释这两个概念:

  1. 通用 (General Purpose):

    • 当IO端口配置为通用模式时,其输入和输出是由GPIO(通用输入/输出)外设进行控制。
    • 通用模式下,IO端口可以用作数字输入或输出,并由GPIO模块提供基本的通用IO功能。
    • 在通用模式下,IO端口可以灵活地用于不同的用途,因为它受到GPIO模块的控制。
  2. 复用 (Multiplexing, Alternate Function):

    • 当IO端口配置为复用模式时,其输入和输出是由非GPIO外设进行控制,这些外设可以包括USART、SPI、I2C等。
    • 复用模式允许相同的物理引脚在不同的工作模式下执行不同的功能。这种灵活性使得单个引脚能够适应多种外设和功能。
    • 在复用模式下,IO端口可以成为外设的引脚,例如,当配置为USART模式时,IO端口可用作串口的TX和RX引脚。

在现代的微控制器中,通过配置寄存器和外设设置,可以轻松切换IO端口的通用和复用模式。通常,使用开发工具和库(如STM32CubeMX和HAL库)可以更方便地配置和管理这些设置。这种灵活性使得设计者能够根据应用的需求有效地管理IO端口的功能。

7.2、STM32F1的IO引脚复用

在这里插入图片描述
在STM32F1系列微控制器中,IO引脚的复用功能可以根据引脚的定义和所属的端口进行配置。以下是一般的解释:

  1. 各IO支持的复用功能:

    • 要查看特定引脚支持的复用功能,需要参考STM32F1系列的数据手册。在数据手册的引脚定义部分,你会找到每个引脚支持的不同功能,包括复用功能,例如USART、SPI、I2C等。
  2. IO复用功能冲突问题:

    • 在STM32F1系列中,同一时间一个IO引脚只能配置为一种特定的复用功能。如果尝试同时使用多个复用功能,可能会导致冲突和不正确的行为。
    • 复用功能的冲突问题通常由硬件设计的灵活性带来。在数据手册中可以找到每个引脚支持的复用功能,并且在特定情况下,有可能会出现功能冲突的情况。
  3. 遇到IO复用功能冲突:

    • 当遇到复用功能冲突时,STM32F1系列提供了一些引脚重映射(Remap)功能,以解决这种冲突。通过重映射,可以将某个外设的引脚映射到其他支持相同功能的引脚上。
    • 重映射功能可以在相关的寄存器中进行配置。例如,对于USART外设,可以使用 USART1_REMAP 寄存器进行引脚重映射。

需要注意的是,使用重映射时,要确保引脚之间的电气特性和功能匹配,以免引起电气不匹配或不正确的信号传输。

总之,在设计和配置STM32F1系列的IO引脚时,查看相关的数据手册和参考手册是非常重要的,以确保正确配置引脚的复用功能,处理冲突,并使用重映射功能解决问题。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.3、STM32F4/F7/H7的IO引脚复用

在这里插入图片描述
在STM32F4/F7/H7系列微控制器中,引入了复用器(MUX)的概念来更有效地管理IO引脚的复用功能。以下是与复用器相关的特点:

  1. 每个 IO 引脚都有一个复用器:

    • 对于每个IO引脚,都有一个相应的复用器,允许配置该引脚用于不同的复用功能。
  2. 复用器采用 16 路复用功能输入(AF0 到 AF15):

    • 复用器允许将IO引脚连接到16个不同的复用功能输入,从AF0到AF15。每个复用功能对应于一个外设或特定的功能。
  3. 复用器一次仅允许一个外设的复用功能 (AF) 连接到 IO 引脚:

    • 在任何时候,一个IO引脚只能配置为与一个复用功能关联。这确保了在同一时间内,一个引脚只能连接到一个特定的外设或功能。
  4. 通过GPIOx_AFRL和GPIOx_AFRH寄存器进行配置:

    • 复用功能的配置是通过GPIOx_AFRL和GPIOx_AFRH寄存器进行的。这些寄存器分别用于配置引脚0到7和引脚8到15的复用功能。
  5. 复位后连接到系统的复用功能 0 (AF0):

    • 复位后,所有IO引脚都会连接到系统的复用功能 0 (AF0)。这是一个安全的默认设置,确保在初始化时引脚处于正常状态。

在STM32 HAL库或CubeMX等工具中,通常会提供图形化界面,帮助用户轻松配置IO引脚的复用功能。用户可以选择特定的外设或功能,工具会生成相应的配置代码。

总体而言,复用器的引入使得在STM32F4/F7/H7系列中更加方便地管理IO引脚的复用功能,减少了复杂的配置和冲突问题。

IO引脚复用映射示意图

在这里插入图片描述
在这里插入图片描述

GPIO 复用功能低位寄存器(AFRL)

在这里插入图片描述
GPIO复用功能低位寄存器(AFRL)是在STM32系列微控制器中用于配置引脚复用功能的寄存器之一。该寄存器用于配置引脚0到7的复用功能。

具体来说,AFRL寄存器的每个位域对应于一个引脚,用于选择该引脚的复用功能编号(AF0到AF15)。每个位域通常占用4位。例如,AFRL寄存器的最低4位用于配置引脚0的复用功能,接下来的4位用于配置引脚1的复用功能,以此类推。

在STM32 HAL库或CubeMX等工具中,通过图形化界面或配置文件,用户可以轻松地选择每个引脚的复用功能,这将生成相应的配置代码。

以下是AFRL寄存器的通用格式:

|  Bits  |    31-28    |    27-24    |    23-20    |    19-16    |    15-12    |    11-8     |    7-4      |    3-0      |
|--------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|
|  Name  |     AF7     |     AF6     |     AF5     |     AF4     |     AF3     |     AF2     |     AF1     |     AF0     |

上述表格中,AF7到AF0是每个引脚对应的4位域,用于配置该引脚的复用功能。

在实际的HAL库或CubeMX配置代码中,你可以看到对AFRL寄存器的设置,示例如下:

GPIO_InitTypeDef GPIO_InitStruct = {0};

// 配置引脚0到7的复用功能
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
                       GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1; // 选择复用功能 AF7(USART1)

HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

在这个例子中,通过Alternate字段选择了复用功能AF7(USART1),并应用到引脚0到7。这将配置AFRL寄存器相应的位域,以选择USART1的复用功能。

GPIO 复用功能高位寄存器(AFRH)

在这里插入图片描述
GPIO复用功能高位寄存器(AFRH)是在STM32系列微控制器中用于配置引脚复用功能的寄存器之一。该寄存器用于配置引脚8到15的复用功能。

与AFRL寄存器类似,AFRH寄存器的每个位域对应于一个引脚,用于选择该引脚的复用功能编号(AF0到AF15)。每个位域通常占用4位。

以下是AFRH寄存器的通用格式:

|  Bits  |    31-28    |    27-24    |    23-20    |    19-16    |    15-12    |    11-8     |    7-4      |    3-0      |
|--------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|
|  Name  |    -        |    -        |    -        |    -        |     AF11    |     AF10    |     AF9     |     AF8     |

上述表格中,AF11到AF8是每个引脚对应的4位域,用于配置该引脚的复用功能。

在STM32 HAL库或CubeMX等工具中,用户可以通过图形化界面或配置文件来选择每个引脚的复用功能,从而生成相应的配置代码。

以下是一个示例,演示如何使用AFRH寄存器进行引脚复用功能的配置:

GPIO_InitTypeDef GPIO_InitStruct = {0};

// 配置引脚8到15的复用功能
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 |
                       GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH; // 选择复用功能 AF11(Ethernet)

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

在这个例子中,通过Alternate字段选择了复用功能AF11(Ethernet),并应用到引脚8到15。这将配置AFRH寄存器相应的位域,以选择Ethernet的复用功能。

八、编程实战:通过串口接收或者发送一个字符

在这里插入图片描述
usart.c

/* usart.c文件 */

/* 包含必要的头文件,其中包括系统初始化和USART配置所需的文件 */
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"

/******************************************************************************************/
/* 以下代码段用于支持printf函数,不需要选择使用MicroLIB */
#if 1

#if (__ARMCC_VERSION >= 6010050)            
__asm(".global __use_no_semihosting\n\t");  
__asm(".global __ARM_use_no_argv \n\t");    

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}

/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0X40) == 0);    
    USART1->DR = (uint8_t)ch;             
    return ch;
}
#endif
/******************************************************************************************/

/* 定义一个1字节的接收缓冲区和一个接收标志位 */
uint8_t g_rx_buffer[1];          
uint8_t g_usart1_rx_flag = 0;    

/* 定义UART句柄 */
UART_HandleTypeDef g_uart1_handle;  

/* 串口1初始化函数 */
void usart_init(uint32_t baudrate)
{
    g_uart1_handle.Instance = USART1;
    g_uart1_handle.Init.BaudRate = baudrate;
    g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;
    g_uart1_handle.Init.StopBits = UART_STOPBITS_1;
    g_uart1_handle.Init.Parity = UART_PARITY_NONE;
    g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    g_uart1_handle.Init.Mode = UART_MODE_TX_RX;
    HAL_UART_Init(&g_uart1_handle);
    
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1);
}

/* 串口MSP回调函数 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;
    if(huart->Instance == USART1)                
    {
        /* 使能USART1和对应IO时钟 */
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        /* 配置串口1的TX引脚为推挽输出 */
        gpio_init_struct.Pin = GPIO_PIN_9;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;            
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);            

        /* 配置串口1的RX引脚为输入,上拉 */
        gpio_init_struct.Pin = GPIO_PIN_10;
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;         
        gpio_init_struct.Pull = GPIO_PULLUP;                
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);            

        /* 配置串口1中断优先级和使能中断 */
        HAL_NVIC_SetPriority(USART1_IRQn, 3, 3);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
    }
}

/* 串口1中断服务函数 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&g_uart1_handle);
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1);
}

/* 串口数据接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        g_usart1_rx_flag = 1;
    }
}

该文件是一个使用HAL库的STM32串口通信的示例。以下是文件中的各个部分的注释说明:

  1. 支持printf函数:

    • 通过宏定义实现了对printf函数的支持,这样可以通过串口输出调试信息。
  2. 串口初始化函数(usart_init):

    • 初始化了USART1串口,包括波特率、数据位、停止位、校验位等设置。
    • 使用HAL_UART_Init进行初始化,并通过HAL_UART_Receive_IT开启了串口接收中断。
  3. HAL_UART_MspInit回调函数:

    • 在HAL库中,MSP(Microcontroller Support Package)回调函数用于底层硬件初始化。
    • 在这里,对USART1的时钟和引脚进行了初始化配置,并设置了中断优先级。
  4. USART1_IRQHandler中断服务函数:

    • 处理USART1的中断,调用HAL_UART_IRQHandler进行中断处理,并通过HAL_UART_Receive_IT再次开启串口接收中断。
  5. HAL_UART_RxCpltCallback回调函数:

    • 在串口接收完成时被调用,设置g_usart1_rx_flag标志位,用于指示有数据接收到。
  6. fputc函数:

    • 通过HAL库实现了标准库中的fputc函数,使得printf函数可以通过串口输出。
  7. 其他:

    • 定义了全局变量g_rx_buffer和g_usart1_rx_flag,用于存储接收到的数据和标志是否有数据接收到。

上述文件主要实现了USART1的初始化、中断配置和中断服务函数,以及一个简单的printf函数的支持。USART1的初始化配置在usart_init函数中,MSP(MCU Support Package)回调函数在HAL_UART_MspInit中配置GPIO引脚和中断。USART1的中断服务函数在USART1_IRQHandler中实现,用于处理接收中断。USART1的接收完成回调函数在HAL_UART_RxCpltCallback中设置接收标志。

usart.h

#ifndef __USART_H
#define __USART_H

#include "stdio.h"
#include "./SYSTEM/sys/sys.h"


extern UART_HandleTypeDef g_uart1_handle;       /* HAL UART句柄 */
extern uint8_t g_rx_buffer[1];                  /* HAL库使用的串口接收数据缓冲区 */
extern uint8_t g_usart1_rx_flag;                /* 串口接收到数据标志 */

void usart_init(uint32_t bound);                /* 串口初始化函数 */

#endif

main.c
这是一个使用STM32 HAL库的USART1串口接收一个字符并通过串口发送该字符的简单程序。以下是对主函数中各部分的注释:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    usart_init(115200);                         /* 波特率设为115200 */
    
    printf("请输入一个英文字符:\r\n\r\n");     /* 输出提示信息 */

    while(1)
    {
        if(g_usart1_rx_flag == 1)
        {
            printf("您输入的字符为:\r\n");      /* 输出提示信息 */
            
            HAL_UART_Transmit(&g_uart1_handle, (uint8_t*)g_rx_buffer, 1, 1000);  /* 通过串口发送接收到的字符 */
            
            while(__HAL_UART_GET_FLAG(&g_uart1_handle, UART_FLAG_TC) != 1);     /* 等待发送完成 */
            
            printf("\r\n");   /* 换行 */
            
            g_usart1_rx_flag = 0;   /* 清除接收标志位 */
        }
        else
        {
            delay_ms(10);   /* 如果没有接收到字符,延时一段时间 */
        }
    }
}

在主循环中,程序不断检查g_usart1_rx_flag标志位,如果该标志位为1,表示已经接收到一个字符。程序会通过串口发送接收到的字符,并清除标志位。如果没有接收到字符,程序会延时一段时间。此外,程序一开始会输出提示信息,提示用户输入一个英文字符。

九、解读例程源码:串口实验

在这里插入图片描述
在这里插入图片描述
使用STM32的HAL库进行串口接收,并且在接收完成回调函数HAL_UART_RxCpltCallback()中处理接收到的数据。同时,引入了一个状态变量 g_usart_rx_sta 用于制定接收协议,并定义了一个长度为200的接收缓冲区 g_usart_rx_buf

下面是一个简单的伪代码示例,说明串口接收数据的过程:

// 定义状态变量和接收缓冲区
uint16_t g_usart_rx_sta = 0;
uint8_t g_usart_rx_buf[200];

// 定义接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USARTx)  // 请替换为你使用的USART实例
    {
        // 处理接收到的数据
        uint8_t received_byte = g_rx_buffer[0];

        // 判断是否接收到换行或回车
        if (received_byte == 0x0A)
        {
            // 设置接收完成标志位
            g_usart_rx_sta |= (1 << 15);
        }
        else if (received_byte == 0x0D)
        {
            // 设置回车标志位
            g_usart_rx_sta |= (1 << 14);
        }
        else
        {
            // 将接收到的数据存入接收缓冲区
            g_usart_rx_buf[g_usart_rx_sta & 0x0FFF] = received_byte;

            // 增加有效字节数
            g_usart_rx_sta++;

            // 如果超过缓冲区大小,溢出处理(这里你可以根据实际情况进行处理)
            if ((g_usart_rx_sta & 0x0FFF) >= sizeof(g_usart_rx_buf))
            {
                // 清除状态变量,表示溢出
                g_usart_rx_sta = 0;
            }
        }

        // 继续以中断方式接收下一个字节
        HAL_UART_Receive_IT(huart, g_rx_buffer, 1);
    }
}

在上述代码中,每当接收到一个字节数据时,会判断是否为换行或回车,并根据情况设置相应的标志位。同时,将接收到的数据存储到缓冲区中,并增加有效字节数。接收完成后,继续以中断方式接收下一个字节。请注意,实际应用中,你可能需要根据具体的协议进行更复杂的处理。
在这里插入图片描述
在串口接收数据过程中,通过状态变量 g_usart_rx_sta 获取数据长度,并在发送数据时使用 HAL_UART_Transmit(),等待发送完成(TC位置1),然后清除相应标志位。以下是一个简单的伪代码示例,演示串口发送数据的过程:

// 定义状态变量
uint16_t g_usart_rx_sta = 0;

// 假设 g_usart_rx_sta 中存储了接收到的有效字节数目

// 假设发送缓冲区为 g_usart_tx_buf,需要发送的数据长度为 g_tx_length

// 发送数据
HAL_UART_Transmit(&g_uart_handle, g_usart_tx_buf, g_tx_length, HAL_MAX_DELAY);

// 等待发送完成
while (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_TC) == 0);

// 清除发送完成标志位
__HAL_UART_CLEAR_FLAG(&g_uart_handle, UART_FLAG_TC);

上述代码中,HAL_UART_Transmit() 用于发送数据,通过 while 循环等待发送完成(TC位置1),然后通过 __HAL_UART_CLEAR_FLAG() 清除发送完成标志位。在实际应用中,你需要确保在发送数据之前,g_usart_rx_sta 中已经正确存储了接收到的有效字节数目,并根据具体的需求进行适当的处理。

usart.h

#ifndef __USART_H
#define __USART_H

#include "stdio.h"
#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 引脚 和 串口 定义 
 * 默认是针对USART1的.
 * 注意: 通过修改这几个宏定义,可以支持USART1~UART5任意一个串口.
 */
#define USART_TX_GPIO_PORT                  GPIOA
#define USART_TX_GPIO_PIN                   GPIO_PIN_9
#define USART_TX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_RX_GPIO_PORT                  GPIOA
#define USART_RX_GPIO_PIN                   GPIO_PIN_10
#define USART_RX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_UX                            USART1
#define USART_UX_IRQn                       USART1_IRQn
#define USART_UX_IRQHandler                 USART1_IRQHandler
#define USART_UX_CLK_ENABLE()               do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0)  /* USART1 时钟使能 */

/******************************************************************************************/

#define USART_REC_LEN               200         /* 定义最大接收字节数 200 */
#define USART_EN_RX                 1           /* 使能(1)/禁止(0)串口1接收 */
#define RXBUFFERSIZE   1                        /* 缓存大小 */

extern UART_HandleTypeDef g_uart1_handle;       /* HAL UART句柄 */

extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t g_usart_rx_sta;                 /* 接收状态标记 */
extern uint8_t g_rx_buffer[RXBUFFERSIZE];       /* HAL库USART接收Buffer */


void usart_init(uint32_t bound);                /* 串口初始化函数 */

#endif

usart.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"


/* 如果使用os,则包括下面的头文件即可. */
#if SYS_SUPPORT_OS
#include "os.h" /* os 使用 */
#endif

/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif
/******************************************************************************************/

#if USART_EN_RX /*如果使能了接收*/

/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t g_usart_rx_buf[USART_REC_LEN];

/*  接收状态
 *  bit15,      接收完成标志
 *  bit14,      接收到0x0d
 *  bit13~0,    接收到的有效字节数目
*/
uint16_t g_usart_rx_sta = 0;

uint8_t g_rx_buffer[RXBUFFERSIZE];  /* HAL库使用的串口接收缓冲 */

UART_HandleTypeDef g_uart1_handle;  /* UART句柄 */

/**
 * @brief       串口X初始化函数
 * @param       baudrate: 波特率, 根据自己需要设置波特率值
 * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
 *              这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
 * @retval      无
 */
void usart_init(uint32_t baudrate)
{
    /*UART 初始化设置*/
    g_uart1_handle.Instance = USART_UX;                                       /* USART_UX */
    g_uart1_handle.Init.BaudRate = baudrate;                                  /* 波特率 */
    g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;                      /* 字长为8位数据格式 */
    g_uart1_handle.Init.StopBits = UART_STOPBITS_1;                           /* 一个停止位 */
    g_uart1_handle.Init.Parity = UART_PARITY_NONE;                            /* 无奇偶校验位 */
    g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;                      /* 无硬件流控 */
    g_uart1_handle.Init.Mode = UART_MODE_TX_RX;                               /* 收发模式 */
    HAL_UART_Init(&g_uart1_handle);                                           /* HAL_UART_Init()会使能UART1 */

    /* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); 
}

/**
 * @brief       UART底层初始化函数
 * @param       huart: UART句柄类型指针
 * @note        此函数会被HAL_UART_Init()调用
 *              完成时钟使能,引脚配置,中断配置
 * @retval      无
 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;

    if (huart->Instance == USART_UX)                            /* 如果是串口1,进行串口1 MSP初始化 */
    {
        USART_TX_GPIO_CLK_ENABLE();                             /* 使能串口TX脚时钟 */
        USART_RX_GPIO_CLK_ENABLE();                             /* 使能串口RX脚时钟 */
        USART_UX_CLK_ENABLE();                                  /* 使能串口时钟 */

        gpio_init_struct.Pin = USART_TX_GPIO_PIN;               /* 串口发送引脚号 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* IO速度设置为高速 */
        HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct);
                
        gpio_init_struct.Pin = USART_RX_GPIO_PIN;               /* 串口RX脚 模式设置 */
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;    
        HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct);   /* 串口RX脚 必须设置成输入模式 */
        
#if USART_EN_RX
        HAL_NVIC_EnableIRQ(USART_UX_IRQn);                      /* 使能USART1中断通道 */
        HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3);              /* 组2,最低优先级:抢占优先级3,子优先级3 */
#endif
    }
}

/**
 * @brief       串口数据接收回调函数
                数据处理在这里进行
 * @param       huart:串口句柄
 * @retval      无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART_UX)                    /* 如果是串口1 */
    {
        if ((g_usart_rx_sta & 0x8000) == 0)             /* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)                /* 接收到了0x0d(即回车键) */
            {
                if (g_rx_buffer[0] != 0x0a)             /* 接收到的不是0x0a(即不是换行键) */
                {
                    g_usart_rx_sta = 0;                 /* 接收错误,重新开始 */
                }
                else                                    /* 接收到的是0x0a(即换行键) */
                {
                    g_usart_rx_sta |= 0x8000;           /* 接收完成了 */
                }
            }
            else                                        /* 还没收到0X0d(即回车键) */
            {
                if (g_rx_buffer[0] == 0x0d)
                    g_usart_rx_sta |= 0x4000;
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
                    g_usart_rx_sta++;

                    if (g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;             /* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }

        HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
    }
}

/**
 * @brief       串口1中断服务函数
 * @param       无
 * @retval      无
 */
void USART_UX_IRQHandler(void)
{
#if SYS_SUPPORT_OS                          /* 使用OS */
    OSIntEnter();    
#endif

    HAL_UART_IRQHandler(&g_uart1_handle);   /* 调用HAL库中断处理公用函数 */

#if SYS_SUPPORT_OS                          /* 使用OS */
    OSIntExit();
#endif

}

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"


int main(void)
{
    uint8_t len;
    uint16_t times = 0;

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟为72Mhz */
    delay_init(72);                         /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    while (1)
    {
        if (g_usart_rx_sta & 0x8000)        /* 接收到了数据? */
        {
            len = g_usart_rx_sta & 0x3fff;  /* 得到此次接收到的数据长度 */
            printf("\r\n您发送的消息为:\r\n");

            HAL_UART_Transmit(&g_uart1_handle,(uint8_t*)g_usart_rx_buf, len, 1000);    /* 发送接收到的数据 */
            while(__HAL_UART_GET_FLAG(&g_uart1_handle, UART_FLAG_TC) != SET);          /* 等待发送结束 */
            printf("\r\n\r\n");             /* 插入换行 */
            g_usart_rx_sta = 0;
        }
        else
        {
            times++;

            if (times % 5000 == 0)
            {
                printf("\r\n正点原子 STM32开发板 串口实验\r\n");
                printf("正点原子@ALIENTEK\r\n\r\n\r\n");
            }

            if (times % 200 == 0) printf("请输入数据,以回车键结束\r\n");

            if (times % 30  == 0) LED0_TOGGLE(); /* 闪烁LED,提示系统正在运行. */

            delay_ms(10);
        }
    }
}

HAL_UART_RxCpltCallback( )函数解析

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART_UX)                    /* 如果是串口1 */
    {
        if ((g_usart_rx_sta & 0x8000) == 0)             /* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)                /* 接收到了0x0d(即回车键) */
            {
                if (g_rx_buffer[0] != 0x0a)             /* 接收到的不是0x0a(即不是换行键) */
                {
                    g_usart_rx_sta = 0;                 /* 接收错误,重新开始 */
                }
                else                                    /* 接收到的是0x0a(即换行键) */
                {
                    g_usart_rx_sta |= 0x8000;           /* 接收完成了 */
                }
            }
            else                                        /* 还没收到0X0d(即回车键) */
            {
                if (g_rx_buffer[0] == 0x0d)
                    g_usart_rx_sta |= 0x4000;
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
                    g_usart_rx_sta++;

                    if (g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;             /* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }

        HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
    }
}

这段代码是在HAL_UART_RxCpltCallback回调函数中处理串口接收数据的逻辑。让我们详细分析每个部分:

  1. 判断串口实例

    if (huart->Instance == USART_UX)
    
    • 这一部分通过判断当前的串口实例是否为特定的 USART_UX 来确定是否是串口1(请确保USART_UX宏定义正确)。这是一个简单的条件判断。
  2. 检查接收状态

    if ((g_usart_rx_sta & 0x8000) == 0)
    
    • 如果g_usart_rx_sta最高位(bit15)为0,表示接收未完成。这个条件检查确保接收状态为未完成。
  3. 处理接收到的0x0D(回车键)

    if (g_usart_rx_sta & 0x4000)
    
    • 如果已经接收到了0x0D(回车键),则继续判断接收到的是不是0x0A(换行键)。
  4. 接收到0x0D且不是0x0A

    if (g_rx_buffer[0] != 0x0A)
    
    • 如果接收到的不是0x0A,表示接收错误,重新开始接收。
  5. 接收到0x0D且是0x0A

    else
    {
        g_usart_rx_sta |= 0x8000;  /* 接收完成了 */
    }
    
    • 如果接收到的是0x0A,表示接收完成,设置接收完成标志位(最高位)。
  6. 还未接收到0x0D(回车键)

    else
    {
        // 处理接收到的数据
    }
    
    • 如果还未接收到0x0D,进一步处理接收到的数据。
  7. 处理接收到的数据

    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
    g_usart_rx_sta++;
    
    if (g_usart_rx_sta > (USART_REC_LEN - 1))
    {
        g_usart_rx_sta = 0;  /* 接收数据错误,重新开始接收 */
    }
    
    • 将接收到的数据存储到接收缓冲区,并增加接收状态计数。如果接收状态超过了定义的最大长度(USART_REC_LEN - 1),则表示接收数据错误,重新开始接收。
  8. 重新以中断方式启动下一轮接收

    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
    
    • 重新以中断方式启动下一轮接收,这确保了连续的数据接收。

总体来说,这段代码的目的是实现对串口数据的特定处理逻辑,通过检测回车键(0x0D)和换行键(0x0A)来判断接收的数据是否有效,并在接收完成时进行相应的处理。

来自printf函数的疑问

在这里插入图片描述
在嵌入式系统中,特别是资源受限的系统(如嵌入式微控制器),使用标准库中的 printf 函数可能会导致一些性能和资源消耗方面的问题。以下是一些原因:

  1. 内存消耗: printf 函数通常需要使用大量的内存,包括格式化字符串和缓冲区。在资源受限的系统中,这可能导致内存不足的问题。

  2. 代码大小: printf 的实现可能相对庞大,占用较多的代码空间。在一些嵌入式系统中,代码空间也是有限的,过多的代码可能导致无法满足系统的存储空间要求。

  3. 性能开销: printf 函数通常是为了灵活性而设计的,这就使得它的实现相对较为复杂。在一些对性能要求较高的系统中,使用 printf 可能会引入较大的性能开销。

如果考虑性能最好,可以考虑以下替代方案:

  1. 使用专门的日志输出函数: 如果只需输出一些调试信息,可以考虑使用专门为此设计的日志输出函数。这些函数通常可以更好地适应嵌入式系统的需求,减少内存和代码占用。

  2. 自定义输出函数: 根据具体的需求,可以编写自定义的输出函数,只包含必要的功能,从而减少代码和内存的使用。这可以根据具体应用的需求进行优化。

  3. 使用串口输出: 在嵌入式系统中,常常使用串口进行调试输出。可以编写简单的串口输出函数,适应系统的资源限制,同时提供足够的信息以进行调试。

总体来说,选择是否使用 printf 取决于具体的嵌入式系统需求。在资源受限的环境中,通常更倾向于使用轻量级、专门设计的输出方式,以满足性能和资源消耗的要求。

十、总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 40
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值