一、数据通信的基础概念
二、串口(RS-232)
三、STM32的USART
四、HAL库外设初始化MSP回调机制
五、HAL库中断回调机制
六、USART/UART异步通信配置步骤
七、IO引脚复用功能
八、编程实战:通过串口接收或者发送一个字符
九、解读例程源码:串口实验
十、总结
串行通信:
并行通信:
选择串行通信还是并行通信通常取决于具体的应用需求、通信距离、传输速率等因素。串行通信适用于长距离通信,而并行通信更适合在短距离内提高传输速率。
按数据传输方向分类,通信可以分为单工通信、半双工通信和全双工通信:
单工通信:
半双工通信:
全双工通信:
选择单工、半双工或全双工通信通常取决于应用的需求。单工通信适用于只需要单向传输的场景,半双工通信适用于需要在不同时间段内进行双向通信的场景,而全双工通信则适用于需要同时进行双向通信的场景,提供更高的通信效率。
同步通信:
异步通信:
选择同步通信还是异步通信通常取决于具体的应用需求。同步通信适用于对时序要求较高的场景,而异步通信适用于更加灵活和宽松的时序要求的场景。例如,串口通信中的RS-232通常采用异步通信方式。
比特率: 表示每秒钟传送的比特数,单位是比特每秒(bit/s)。比特率用来衡量数字通信系统的数据传输速率,即在一秒内可以传输的二进制位数。
波特率: 表示每秒钟传送的码元数,单位是波特(Baud)。波特率是指信号的变化率,每秒内信号变化的次数。在数字通信中,波特率常用来描述调制和解调的速率,指的是在信号中每秒传送的符号(码元)数量。
比特率和波特率关系: 比特率与波特率之间的关系可以用以下公式表示:比特率 = 波特率 * log2(M),其中M表示每个码元承载的信息量。这个公式说明了在有多个离散的信号水平(或相位)时,一个码元可以携带更多的信息。
二进制系统中的波特率: 在二进制系统中,波特率数值上等于比特率。这是因为在二进制系统中,每个码元只能表示两个状态(0或1),因此每个码元携带的信息量(log2M)等于1,公式简化为比特率等于波特率。
总结:
UART (通用异步收发器):
1-wire:
IIC (I2C):
SPI (串行外设接口):
这些串行通信接口在不同的应用场景中被广泛使用。它们提供了不同的特性,如同步方式、传输方向和引脚配置,以满足各种通信需求。UART常用于通用的异步通信,1-wire适用于一根数据线的短距离通信,I2C适用于多个设备在同一总线上进行通信,而SPI适用于高速数据传输的应用。
什么是串口?
RS-232接口(DB9):
RS-232是一种常见的串行通信标准,它规定了串口接口的物理连接、电气特性以及通信协议。DB9是RS-232接口的一种常见连接形式,具有9个引脚。这些引脚包括数据线、地线、握手信号以及其他用于控制通信和状态指示的信号。每个引脚的功能在特定的应用中可能有不同的用途。
RS-232、CMOS(Complementary Metal-Oxide-Semiconductor)和TTL(Transistor-Transistor Logic)三种不同的电平标准。这些电平标准之间确实存在差异,因此在进行串行通信时,需要进行适当的电平转换。
RS-232电平:
CMOS电平(3.3V):
TTL电平(5V):
结论:
由于RS-232电平的范围相对较大,与CMOS或TTL电平不同,因此不能直接连接交换信息。需要使用电平转换器来将RS-232电平转换为适用于CMOS或TTL电平的电平,或者反之。这是因为不同的设备和芯片可能使用不同的电平标准,确保它们之间的兼容性需要适当的电平匹配。典型的RS-232转CMOS/TTL电平转换器会将RS-232的电平变换为适用于3.3V或5V逻辑的电平。
MAX3232
和 SP3232
都是常用于电平转换的串口通信芯片,它们的主要功能是将RS-232电平转换为TTL/CMOS电平,从而实现不同设备之间的串口通信。
功能:
电压范围:
工作速率:
引脚:
功能:
电压范围:
工作速率:
引脚:
在实际设计中,选择 MAX3232 或 SP3232 取决于特定应用的要求和设计偏好。
CH340C
是一款常用的 USB 转串口芯片,用于在计算机与其他设备之间实现串口通信。它可以将 USB 信号转换为 TTL/CMOS 电平,从而使得计算机可以与使用 TTL/CMOS 电平的设备进行串口通信。
USB 转串口: CH340C 主要功能是将 USB 接口的信号转换为串口通信所需的 TTL/CMOS 电平信号。
USB 2.0 支持: 符合 USB 2.0 标准,提供高速数据传输。
多种串口配置: 支持多种串口配置,如8数据位、1或2停止位、奇偶校验等。
内置晶振: 集成了内部晶振,减少了外部元件的需求,简化了设计。
驱动支持: 通常需要安装相应的驱动程序,驱动程序由 CH340 提供,可在官方网站上下载。
广泛应用: CH340C 通常应用于嵌入式系统、单片机开发板、串口转 USB 转接器等场合。
一般情况下,CH340C 的连接方式如下:
在使用 CH340C 时,需要确保计算机正确安装了相应的驱动程序。通常,驱动程序会提供给用户,用户可以从 CH340 官方网站或相应硬件供应商处获取。
根据具体的设计需求,使用 CH340C 时,需要注意其引脚分配和电气特性,确保连接正确并符合目标设备的规格。
总体而言,CH340C 提供了方便的 USB 转串口解决方案,适用于许多嵌入式系统和开发板应用。
RS-232异步通信协议定义了数据帧的格式,包括启动位、有效数据位、校验位和停止位。以下是数据帧的基本结构:
启动位(Start Bit):
有效数据位(Data Bits):
校验位(Parity Bit):
停止位(Stop Bit):
这种异步通信的数据帧结构提供了一种灵活的方式,使得设备能够以相对简单的方式进行通信。在传统的RS-232通信中,常见的配置是8数据位、无校验(或奇偶校验)、1停止位。不同设备之间需要协商并配置相同的数据帧格式,以确保正确的数据传输。
STM32的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块是一种通用的串行通信模块,支持同步和异步通信。它可以用于与外部设备进行全双工的串行通信。USART在STM32系列微控制器中广泛应用,提供了灵活、高性能的串行通信解决方案。
以下是USART的一些基本特点和概述:
通用性: USART的通用性体现在其支持同步和异步通信。它可以灵活地适应不同的通信要求,使其成为多种应用场景下的理想选择。
异步通信: 在实际应用中,USART通常以异步通信的形式工作。异步通信是指数据传输不需要共享时钟信号,而是通过引入起始位、停止位等控制信息来同步数据。
全双工通信: USART支持全双工通信,允许同时进行数据的发送和接收,从而实现双向通信。这对于与外部设备进行双向数据交换非常重要。
波特率可调: USART支持可调的波特率,用户可以根据具体的应用需求选择适当的波特率。波特率是指每秒传输的比特数,通常用bps(比特每秒)来表示。
帧格式灵活: USART允许用户灵活配置帧格式,包括数据位数、停止位数、奇偶校验等。这使得USART适用于多种通信标准和设备之间的通信。
DMA支持: STM32的USART模块通常支持DMA(Direct Memory Access)功能,可以通过DMA来实现数据的高效传输,减轻CPU的负担,提高性能。
中断支持: USART模块支持中断,通过中断机制,可以实现异步通信时的数据接收和发送中断处理。
USART在STM32中是一个非常重要且常用的外设,它为微控制器提供了强大的串行通信能力,适用于各种应用场景,包括与传感器、显示器、通信模块等外部设备的通信。在使用USART时,用户需要配置相关的寄存器和参数,以适应具体的通信需求。
UART(Universal Asynchronous Receiver/Transmitter),通用异步收发器,是一种串行通信协议和硬件设备。UART主要用于异步通信,允许设备在没有共享时钟信号的情况下进行串行数据传输。UART是一种通用的串行通信标准,常用于连接微控制器、传感器、通信模块、计算机等设备。
以下是UART的一些基本概念和特点:
异步通信: UART通信是异步的,这意味着数据传输不需要共享时钟信号。相反,每个数据帧包含一个起始位(Start Bit)、一个或多个数据位、一个可选的校验位和一个或多个停止位。这些元素一起形成了一个数据帧,用于同步数据传输。
全双工通信: UART支持全双工通信,允许同时进行数据的发送和接收。这使得UART非常适用于双向通信场景,如设备之间的实时数据传输。
波特率可调: 波特率是指每秒传输的比特数,通常用bps(比特每秒)表示。UART允许用户灵活设置波特率,以适应不同的通信需求。
帧格式: UART数据帧的格式包括起始位、数据位、校验位和停止位。用户可以根据需要配置这些参数,以满足特定通信标准和设备的要求。
简单性: UART相对简单,易于实现。它在许多嵌入式系统和通信设备中得到广泛应用,因为其实现相对容易且成本较低。
用途广泛: UART在各种设备和场景中得到广泛应用,包括但不限于嵌入式系统、计算机外设、通信模块、传感器、GPS模块等。
总体而言,UART是一种通用的串行通信协议,提供了简单而灵活的解决方案,适用于多种应用场景。在使用UART时,用户需要配置相关参数,并根据通信双方的设置确保正确的数据帧格式。
STM32系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块是一种功能强大、灵活的串口通信模块。以下是STM32中USART的一些主要特征:
全双工异步通信: USART模块支持全双工通信,允许设备同时进行数据的发送和接收。这种特性使得STM32能够实现双向的异步通信,适用于复杂的通信需求。
单线半双工通信: USART还支持单线半双工通信模式,这种模式适用于只能在一段时间内进行发送或接收的应用场景。常见的应用如USART的单线通信模式(单线半双工)。
单独的发送器和接收器使能位: USART模块具有独立的发送器和接收器使能位,这使得用户可以根据需求单独启用或禁用发送和接收功能,提高灵活性。
可配置使用DMA的多缓冲器通信: USART模块支持使用DMA(Direct Memory Access)的通信,允许通过DMA传输数据,减轻CPU的负担,提高性能。此外,USART模块还支持多缓冲器通信,使得在数据传输时能够更高效地进行处理。
多个带标志的中断源: USART提供多个带标志的中断源,这使得用户可以监测和响应不同的事件,如数据接收、发送完成等。通过中断,可以实现异步通信时的事件处理,提高系统的实时性。
USART模块的这些特征使得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 | 封装 | 集成电路封装类型 |
IO | IO 引脚数 | 输入/输出引脚数量 |
VDD | 供电电压 | 供电电压 |
Timer (16-bit) | 定时器 (16位) 数量 | 16位定时器数量 |
Advanced Timer (16-bit) | 高级定时器 (16位) 数量 | 高级16位定时器数量 |
ADC 12-bit Units | ADC 12位 单元数 | 12位ADC单元数量 |
ADC 12-bit Channels | ADC 12位 通道数 | 12位ADC通道数量 |
DAC 12-bit Channels | DAC 12位 通道数 | 12位DAC通道数量 |
SPI | SPI 接口 | 是否支持串行外设接口 |
I2S | I2S 接口 | 是否支持I2S接口 |
I2C | I2C 接口 | 是否支持I2C接口 |
U(S)ART | U(S)ART 接口 | 是否支持USART接口 |
CAN | CAN 总线接口 | 是否支持CAN总线接口 |
SDIO | SDIO 接口 | 是否支持SDIO接口 |
F(S)MC | F(S)MC 接口 | 是否支持灵活存储器控制器 |
USB Device | USB 设备接口 | 是否支持USB设备接口 |
USB FS HOST/OTG | USB FS HOST/OTG 接口 | 是否支持USB全速主机/OTG接口 |
Ethernet | 以太网接口 | 是否支持以太网接口 |
Segment LCD | 分段 LCD 控制器 | 是否支持分段LCD控制器 |
T° Max (℃) | 最大工作温度 ℃ | 最大工作温度(摄氏度) |
发送和接收数据的流程可以总结如下:
综合上述过程,用户可以通过写操作将数据加载到发送数据寄存器,然后通过 TX 发送出去;同时,接收到的数据经过编解码模块,存储在接收移位寄存器中,最终通过读操作传送到数据寄存器,供用户使用。这是一个基本的数据发送和接收的流程,具体细节可能会依赖于硬件设计和编程接口的实现。
在USART(Universal Synchronous Asynchronous Receiver Transmitter)模块中,波特率是一个关键的设置,它决定了数据传输的速率。在传统的USART设置中,常用的波特率发送器相关寄存器和设置包括 USART_BRR、TE、RE 等,同时需要设置 USART 模块的时钟源。
以下是相关的解释:
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模块的时钟源 具体的计算过程会涉及到时钟源的选择和实际的波特率要求。
TE (Transmitter Enable): 发送器使能位。设置 TE 位允许 USART 发送器工作。
RE (Receiver Enable): 接收器使能位。设置 RE 位允许 USART 接收器工作。
USART 模块的时钟源: USART 模块需要一个时钟源来生成波特率。时钟源可以是外部时钟(如外部晶振),也可以是内部时钟。
通过发送器时钟和接收器时钟: USART 模块的发送器和接收器都需要时钟信号。时钟可以是外部时钟,也可以是内部时钟,具体的时钟源和时钟频率设置需要根据硬件设计和应用的要求进行选择。
发送器控制和接收器控制: 时钟源会通过发送器控制和接收器控制来分别控制发送器和接收器的时钟。
TE 和 RE 位: 发送器和接收器的使能位需要设置为使能状态,以启动发送和接收操作。
时钟源和时钟频率: 确保 USART 模块的时钟源和时钟频率设置正确,以满足波特率的要求。
通过使用公式 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)
USARTDIV: 波特率寄存器的整体值,由 Mantissa 和 Fraction 组成。
DIV_Mantissa: 波特率寄存器的整数部分,用于设置波特率的主要部分。
DIV_Fraction: 波特率寄存器的小数部分,用于微调波特率。
以上是一般情况下在USART模块中设置波特率的基本步骤。具体的寄存器名称和位定义可能会因不同的微控制器型号而有所不同,因此需要参考相应的芯片手册或数据手册以获取准确的信息。
在STM32H7系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块中,存在与时钟相关的域和源。以下是关于时钟域和时钟源的基本信息:
usart_ker_ck 时钟域:
usart_ker_ck
是 USART 模块的内部时钟域,它表示 USART 核心时钟。usart_pclk 时钟域:
usart_pclk
是 USART 模块的外设时钟域。usart_ker_ck_pres
是 USART 模块的时钟源。usart_ker_ck
,从而为 USART 模块的内部时钟域提供时钟。在STM32H7系列中,时钟源 usart_ker_ck_pres
可能会与系统时钟(HCLK)、外部时钟源(例如晶振)或其他时钟源相关联,具体的配置和选择取决于用户的设置和系统的设计。时钟源的选择和配置通常在时钟初始化和配置中完成。
注意:具体的寄存器和位定义可能因不同的STM32H7芯片型号而有所不同,因此确切的信息需要参考相应型号的参考手册或数据手册。
在STM32H7系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块中,发送和接收数据的流程涉及到寄存器和缓冲区。以下是简要的发送和接收数据的流程:
32位APB总线通过 USART_TDR 寄存器:
TxFIFO 控制 TX 移位寄存器:
TX 进行数据发送:
RX 接收数据:
RX 移位寄存器到 RxFIFO:
RxFIFO 到 USART_RDR 寄存器读取数据:
这是一个简化的流程描述,实际上,流程的细节和性能可能取决于配置,例如使用DMA进行数据传输的情况下,数据可能直接从/到内存中传递,而不经过寄存器。因此,具体的细节可能需要参考STM32H7系列的参考手册或数据手册,以确保正确的配置和理解数据传输的细节。
在STM32H7系列的USART模块中,设置波特率涉及到时钟域、时钟源、USART_PRESC寄存器、波特率发生器(USART_BRR),以及过采样的配置。以下是一个简要的描述:
时钟域(usart_ker_ck):
usart_ker_ck
是USART模块的内部时钟域,用于控制USART的核心时钟。时钟源(usart_ker_ck_pres):
usart_ker_ck_pres
是USART模块的时钟源。usart_ker_ck
,从而为USART模块的内部时钟域提供时钟。波特率发生器寄存器(USART_BRR):
USART_PRESC 寄存器 配置过采样率:
USART_PRESC
寄存器用于设置过采样率(OverSampling)。选择时钟源和时钟域:
usart_ker_ck_pres
,确定 USART 模块的时钟源。usart_ker_ck
的时钟域配置正确。配置过采样率(OverSampling):
USART_PRESC
寄存器配置过采样率。计算和配置波特率:
USART_BRR
的值,确保波特率满足通信需求。USART_BRR
寄存器。使能 USART 模块:
其他配置:
以上流程的具体实现可能会因芯片型号和具体的应用而有所不同,因此建议查阅STM32H7系列的参考手册和数据手册,以获取详细的寄存器定义和配置信息。
串口数据的发送和接收过程在不同系列的STM32微控制器中有一些差异。以下是对STM32F1、F4、F7和H7系列的USART(Universal Synchronous Asynchronous Receiver Transmitter)模块的串口数据发送和接收过程的简要描述:
RXD引脚接收数据:
接收数据到接收数据寄存器RDR:
RXD引脚接收数据:
接收数据到CPU或DMA:
CPU或DMA数据到发送数据寄存器TDR:
发送数据通过TXD引脚输出:
CPU或DMA数据到发送数据寄存器TDR:
发送数据通过TXD引脚输出:
在F1/F4系列中,数据可能会先经过数据寄存器 DR 再到发送数据寄存器 TDR。而在F7/H7系列中,数据直接由 TDR 发送。
以上流程是一个简化的描述,具体的实现可能因芯片型号和配置而有所不同。详细的信息需要查阅相应系列的参考手册或数据手册。
在STM32F1系列的微控制器中,要设置USART的波特率,需要使用波特率计算公式,并根据具体的时钟频率进行计算。以下是基本的步骤:
确定USART的时钟源:
计算USART的时钟频率( f c k f_{ck} fck):
使用波特率计算公式:
计算USARTDIV:
设置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系列参考手册或数据手册以获取更详细的信息。
在STM32F1系列的USART模块中,波特率寄存器(BRR,Baud Rate Register)是用于设置波特率的关键寄存器。BRR寄存器的位[15:4]用于存放USARTDIV的整数部分,而位[3:0]用于存放USARTDIV的小数部分。
以下是一些关键的细节:
USARTDIV:
BRR寄存器的位[15:4]:
BRR寄存器的位[3:0]:
设置串口的波特率,是针对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)
mantissa = 39;
: 这是Mantissa的设置,Mantissa代表波特率的整数部分。在这里,Mantissa被设置为39。
fraction = 0.0625 * 16 + 0.5 = 0x01;
: 这是Fraction的设置,Fraction代表波特率的小数部分。这里使用了0.0625(1/16)的分辨率,并添加了0.5进行四舍五入。最终,Fraction被计算为0x01。
USART1->BRR = (mantissa << 4) + fraction;
: 这一行将Mantissa左移4位(相当于乘以16),然后将Fraction加到结果中,最终设置了USART1的波特率寄存器BRR。
综合来看,您的代码在设置USART1的波特率时,使用了整数部分39(Mantissa)和小数部分0.0625(Fraction)。这样的计算方式适用于很多情况下,但请确保在具体的应用中,APB2 clock 的频率和其他USART1的配置参数都符合您的需求。
在实际应用中,确保APB2 clock 是您期望的时钟频率,通常是系统时钟频率。此外,确保USART1已经启用,并且适当配置了其它参数,如数据位、停止位等。
如果您有具体的问题或需要更多的帮助,可以提供更多上下文或问题的细节。
在STM32F1系列微控制器中,波特率的设置仍然涉及到USART的波特率寄存器(BRR)。下面是推导过程:
波特率计算公式: baud = f c k 16 × USARTDIV \text{baud} = \frac{\text{f}_{ck}}{16 \times \text{USARTDIV}} baud=16×USARTDIVfck
推导USARTDIV的表达式: USARTDIV = f c k 16 × baud \text{USARTDIV} = \frac{\text{f}_{ck}}{16 \times \text{baud}} USARTDIV=16×baudfck
将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 是时钟频率。
如果我们考虑具体的寄存器版本的写法,根据上述公式进行替换:
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系列中波特率设置的推导过程。如果有其他问题,或者需要更多解释,请随时提出。
在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×(2−OVER8)×USARTDIVfck
其中:
具体步骤如下:
需要注意的是,通常情况下, f P C L K 1 f_{PCLK1} fPCLK1 和 f P C L K 2 f_{PCLK2} fPCLK2 的值可以从RCC寄存器中获取。
如果你有具体的时钟频率和波特率要求,我可以帮你进行具体的计算。
过采样模式是指在串口通信中,接收端对于输入信号的采样次数。在USART通信中,有两种过采样模式:8倍过采样和16倍过采样。
8倍过采样:
16倍过采样:
选择过采样模式的关键影响因素之一是通信环境的噪声水平。在高噪声环境中,使用16倍过采样模式可能更有利于准确地检测和还原信号。另一方面,8倍过采样模式在某些情况下可能更适用,特别是在较低噪声环境下,因为它会占用较少的系统资源。
总体而言,选择过采样模式通常是根据具体的通信需求和环境来决定的。
在STM32F7系列中,设置USART/UART的波特率需要根据时钟频率和过采样模式选择使用不同的计算公式。以下是波特率计算公式:
16倍过采样模式:
baud = f c k USARTDIV \text{baud} = \frac{f_{ck}}{\text{USARTDIV}} baud=USARTDIVfck
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)获取时钟频率。
具体步骤如下:
如果你有具体的时钟频率和波特率要求,我可以帮你进行具体的计算。
在STM32H7系列中,设置USART/UART的波特率需要根据时钟频率和过采样模式选择使用不同的计算公式。以下是波特率计算公式:
16倍过采样模式:
baud = usart_ker_ckpres USARTDIV \text{baud} = \frac{\text{usart\_ker\_ckpres}}{\text{USARTDIV}} baud=USARTDIVusart_ker_ckpres
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) 获取。
具体步骤如下:
如果你有具体的时钟频率和波特率要求,我可以帮你进行具体的计算。
在STM32F1系列中,USART寄存器包括控制寄存器1(CR1)、控制寄存器2(CR2)、控制寄存器3(CR3)、数据寄存器(DR)以及状态寄存器(SR)。以下是对各寄存器的配置要求和功能介绍:
控制寄存器1(CR1):
控制寄存器2(CR2):
控制寄存器3(CR3):
数据寄存器(DR):
状态寄存器(SR):
这些寄存器的配置和使用在初始化USART并进行数据收发时非常关键。确保根据需求正确配置这些位,以实现期望的USART功能。
启动位:
有效数据位:
校验位:
停止位:
在配置USART时,你需要根据具体的通信需求选择数据位的顺序、是否使用校验位以及选择何种校验方式。上述总结涵盖了基本的USART配置时序,确保按照这些要求进行配置可以实现稳定和可靠的串口通信。
在STM32Cube HAL库中,MSP(Microcontroller Support Package)是外设初始化的回调机制。MSP回调函数允许用户在HAL库生成的代码中插入自定义的外设初始化操作。这对于在初始化外设之前或之后执行一些额外的配置或任务非常有用。
MSP回调涉及到两个函数:HAL_MspInit()
和 HAL_MspDeInit()
。这两个函数分别用于外设的初始化和去初始化。
HAL_MspInit():
void HAL_MspInit(void)
{
// 用户自定义的外设初始化代码
// 例如:时钟配置、引脚配置等
}
HAL_MspDeInit():
void HAL_MspDeInit(void)
{
// 用户自定义的外设去初始化代码
// 例如:时钟配置、引脚配置等
}
用户可以在工程的源代码中实现这两个函数,并根据具体需求插入相应的初始化和去初始化代码。这种机制提供了一种可靠的方式,使用户能够在HAL库生成的代码中添加自定义的外设配置。
在代码示例中,重写了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初始化时执行了三个步骤:
这样,可以在UART初始化之前根据需要自定义配置,确保UART的正常工作。
在STM32Cube HAL库中,中断回调机制是通过注册回调函数来处理中断事件的一种方式。当中断事件发生时,HAL库会调用用户在代码中预先定义的回调函数。这使得用户可以在中断服务程序(ISR)内执行定制的操作。
以下是在HAL库中使用中断回调的基本步骤:
定义回调函数:
在你的代码中定义回调函数。这个函数应该匹配特定外设的中断处理函数的原型。例如,对于USART,你可能需要定义一个函数,它的原型为 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
。在这个函数中,你可以处理接收完成中断的相关逻辑。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 处理接收完成中断的逻辑
}
注册回调函数:
在你的初始化代码中,使用HAL库提供的相应函数注册你的回调函数。例如,在初始化USART时,可以使用 HAL_UART_RegisterCallback()
函数注册回调。
HAL_UART_RegisterCallback(&huart1, HAL_UART_RX_COMPLETE_CB_ID, HAL_UART_RxCpltCallback);
这样,当USART1接收完成中断发生时,HAL_UART_RxCpltCallback()
函数就会被调用。
这是一个简单的例子,实际的步骤可能会因外设和中断类型而有所不同。确保查阅相关的HAL库文档,以了解你所使用外设的中断回调机制的详细信息。
使用中断回调机制的好处之一是,它允许你将中断处理逻辑从主程序中分离出来,使得代码更加模块化和可维护。
在STM32Cube HAL库中处理USART和UART中断的机制。以下是相关函数和回调函数的一些概念:
USARTx_IRQHandler() 或 UARTx_IRQHandler():
用户调用HAL库中断共用处理函数 HAL_USART_IRQHandler() 或 HAL_UART_IRQHandler():
HAL库自己调用中断回调函数:
用户选择自己需要的回调函数进行重新定义:
总的来说,这种回调机制允许用户定制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异步通信的主要步骤。以下是对每个步骤的简要说明:
配置串口工作参数:
HAL_UART_Init()
函数配置USART/UART的工作参数,例如波特率、数据位、停止位、校验位等。HAL_UART_Init(&huart1); // 例如,初始化USART1
串口底层初始化:
HAL_UART_MspInit()
函数配置底层硬件相关的参数,包括GPIO配置、NVIC配置、时钟配置等。void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
// 配置GPIO、NVIC、CLOCK等
// 例如,使能USART1和对应IO时钟,配置TX和RX引脚
}
开启串口异步接收中断:
HAL_UART_Receive_IT()
函数启动异步接收中断,使USART/UART能够在接收到数据时触发中断。HAL_UART_Receive_IT(&huart1, rx_buffer, BUFFER_SIZE);
设置优先级,使能中断:
HAL_NVIC_SetPriority()
和 HAL_NVIC_EnableIRQ()
函数设置中断优先级并使能中断。HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
编写中断服务函数:
USARTx_IRQHandler()
或 UARTx_IRQHandler()
来处理中断事件。这个函数通常会调用HAL库生成的中断处理函数和用户自定义的中断回调函数。void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1); // 处理中断,调用HAL库的中断处理函数
}
串口数据发送:
HAL_UART_Transmit()
函数发送数据。你还可以直接访问 USART_DR
寄存器来发送数据。HAL_UART_Transmit(&huart1, tx_data, sizeof(tx_data), HAL_MAX_DELAY);
// 或者直接访问寄存器
USART1->DR = data;
这些步骤构成了USART/UART异步通信的基本配置流程。确保按照这些步骤进行配置,以确保串口的正常工作。
IO引脚的复用功能是指一根物理引脚能够在不同的工作模式下执行不同的功能。这种特性使得单个引脚能够适应多种外设和功能,提高了硬件资源的灵活性。在微控制器中,通常通过将引脚配置为特定的复用功能来实现这一点。以下是一些常见的IO引脚复用功能:
GPIO (通用输入/输出):
USART (通用同步/异步收发器):
SPI (串行外围接口):
I2C (Inter-Integrated Circuit):
PWM (脉冲宽度调制):
ADC (模数转换器):
DAC (数字模拟转换器):
CAN (控制器局域网):
在实际使用中,通过芯片厂商提供的工具或API,可以配置IO引脚的复用功能。通常,这些配置需要在初始化代码中进行,确保引脚在系统启动时按照预期的方式进行配置。在STM32系列微控制器中,使用STM32CubeMX和HAL库可以方便地配置IO引脚的复用功能。
通用和复用与IO端口的输入/输出与GPIO外设的控制关系相关。让我进一步解释这两个概念:
通用 (General Purpose):
复用 (Multiplexing, Alternate Function):
在现代的微控制器中,通过配置寄存器和外设设置,可以轻松切换IO端口的通用和复用模式。通常,使用开发工具和库(如STM32CubeMX和HAL库)可以更方便地配置和管理这些设置。这种灵活性使得设计者能够根据应用的需求有效地管理IO端口的功能。
在STM32F1系列微控制器中,IO引脚的复用功能可以根据引脚的定义和所属的端口进行配置。以下是一般的解释:
各IO支持的复用功能:
IO复用功能冲突问题:
遇到IO复用功能冲突:
需要注意的是,使用重映射时,要确保引脚之间的电气特性和功能匹配,以免引起电气不匹配或不正确的信号传输。
总之,在设计和配置STM32F1系列的IO引脚时,查看相关的数据手册和参考手册是非常重要的,以确保正确配置引脚的复用功能,处理冲突,并使用重映射功能解决问题。
在STM32F4/F7/H7系列微控制器中,引入了复用器(MUX)的概念来更有效地管理IO引脚的复用功能。以下是与复用器相关的特点:
每个 IO 引脚都有一个复用器:
复用器采用 16 路复用功能输入(AF0 到 AF15):
复用器一次仅允许一个外设的复用功能 (AF) 连接到 IO 引脚:
通过GPIOx_AFRL和GPIOx_AFRH寄存器进行配置:
复位后连接到系统的复用功能 0 (AF0):
在STM32 HAL库或CubeMX等工具中,通常会提供图形化界面,帮助用户轻松配置IO引脚的复用功能。用户可以选择特定的外设或功能,工具会生成相应的配置代码。
总体而言,复用器的引入使得在STM32F4/F7/H7系列中更加方便地管理IO引脚的复用功能,减少了复杂的配置和冲突问题。
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)是在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配置所需的文件 */
#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串口通信的示例。以下是文件中的各个部分的注释说明:
支持printf函数:
串口初始化函数(usart_init):
HAL_UART_MspInit回调函数:
USART1_IRQHandler中断服务函数:
HAL_UART_RxCpltCallback回调函数:
fputc函数:
其他:
上述文件主要实现了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
回调函数中处理串口接收数据的逻辑。让我们详细分析每个部分:
判断串口实例:
if (huart->Instance == USART_UX)
检查接收状态:
if ((g_usart_rx_sta & 0x8000) == 0)
g_usart_rx_sta
最高位(bit15)为0,表示接收未完成。这个条件检查确保接收状态为未完成。处理接收到的0x0D(回车键):
if (g_usart_rx_sta & 0x4000)
接收到0x0D且不是0x0A:
if (g_rx_buffer[0] != 0x0A)
接收到0x0D且是0x0A:
else
{
g_usart_rx_sta |= 0x8000; /* 接收完成了 */
}
还未接收到0x0D(回车键):
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; /* 接收数据错误,重新开始接收 */
}
USART_REC_LEN
- 1),则表示接收数据错误,重新开始接收。重新以中断方式启动下一轮接收:
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
总体来说,这段代码的目的是实现对串口数据的特定处理逻辑,通过检测回车键(0x0D)和换行键(0x0A)来判断接收的数据是否有效,并在接收完成时进行相应的处理。
在嵌入式系统中,特别是资源受限的系统(如嵌入式微控制器),使用标准库中的 printf
函数可能会导致一些性能和资源消耗方面的问题。以下是一些原因:
内存消耗: printf
函数通常需要使用大量的内存,包括格式化字符串和缓冲区。在资源受限的系统中,这可能导致内存不足的问题。
代码大小: printf
的实现可能相对庞大,占用较多的代码空间。在一些嵌入式系统中,代码空间也是有限的,过多的代码可能导致无法满足系统的存储空间要求。
性能开销: printf
函数通常是为了灵活性而设计的,这就使得它的实现相对较为复杂。在一些对性能要求较高的系统中,使用 printf
可能会引入较大的性能开销。
如果考虑性能最好,可以考虑以下替代方案:
使用专门的日志输出函数: 如果只需输出一些调试信息,可以考虑使用专门为此设计的日志输出函数。这些函数通常可以更好地适应嵌入式系统的需求,减少内存和代码占用。
自定义输出函数: 根据具体的需求,可以编写自定义的输出函数,只包含必要的功能,从而减少代码和内存的使用。这可以根据具体应用的需求进行优化。
使用串口输出: 在嵌入式系统中,常常使用串口进行调试输出。可以编写简单的串口输出函数,适应系统的资源限制,同时提供足够的信息以进行调试。
总体来说,选择是否使用 printf
取决于具体的嵌入式系统需求。在资源受限的环境中,通常更倾向于使用轻量级、专门设计的输出方式,以满足性能和资源消耗的要求。