✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进
❤欢迎关注我的知乎:对error视而不见
代码获取、问题探讨及文章转载可私信。
☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。
🍎获取更多嵌入式资料可点击链接进群领取,谢谢支持!👇
一、引言
在嵌入式系统开发里,设备间的通信是关键环节。串口通信由于其实现简单、成本低廉等特性,被广泛应用于各类设备之间的数据传输。STM32系列微控制器具备强大的通信功能,其中USART(通用同步/异步收发传输器)是常用的串口通信接口,它能在同步和异步模式下工作,满足不同场景的通信需求。接下来,我们会详细阐述USART串口协议和STM32的串口外设,并给出相应代码示例。
二、USART串口协议概述
2.1 基本概念
USART是一种全双工的串行通信接口,它既支持异步通信,也支持同步通信。在异步通信中,通信双方不需要共享时钟信号,而是依靠预先约定好的波特率来实现数据的同步传输;而同步通信则需要一个额外的时钟信号来协调发送方和接收方的操作,确保数据准确无误地传输。
2.2 数据帧结构
在异步通信里,数据是以帧为单位进行传输的,一个典型的数据帧包含以下几个部分:
- 起始位:通常是一个逻辑低电平信号,它的作用是标志着一个新的数据帧的开始,接收方检测到起始位后,便开始准备接收数据。
- 数据位:数据位的数量可以根据需要进行设置,常见的有5位、6位、7位或8位,用于传输实际的有效数据。
- 校验位:这是一个可选的部分,用于检测数据在传输过程中是否发生错误。常见的校验方式有奇校验和偶校验。例如,奇校验会保证包括校验位在内的整个数据帧中“1”的个数为奇数;偶校验则保证“1”的个数为偶数。
- 停止位:停止位用于表示一个数据帧的结束,它可以是1位、1.5位或2位,通常为逻辑高电平信号。
2.3 波特率
波特率定义了数据传输的速率,即每秒传输的比特数。常见的波特率有9600、115200等。在进行串口通信时,发送方和接收方必须使用相同的波特率,否则会导致数据传输错误,接收方无法正确解析接收到的数据。
三、STM32的USART串口外设
3.1 硬件连接
STM32的USART外设主要有两个重要的引脚:TX(发送)和RX(接收)。在异步通信模式下,只需要将发送方的TX引脚连接到接收方的RX引脚,接收方的TX引脚连接到发送方的RX引脚,就可以实现数据的双向传输。如果使用同步通信模式,还需要一个时钟引脚(CK)来提供同步时钟信号。
3.2 寄存器配置
STM32通过一系列的寄存器来对USART外设进行配置和控制,主要的寄存器如下:
- USART_CR1(控制寄存器1):这个寄存器用于配置串口的基本功能,例如使能发送和接收功能、设置奇偶校验等。通过对该寄存器的不同位进行设置,可以实现不同的功能。
- USART_CR2(控制寄存器2):主要用于设置停止位的数量、时钟极性等参数。不同的应用场景可能需要不同的停止位和时钟极性设置,通过该寄存器可以灵活调整。
- USART_CR3(控制寄存器3):用于配置硬件流控制等功能。硬件流控制可以在数据传输过程中避免数据丢失,提高通信的可靠性。
- USART_BRR(波特率寄存器):用于设置串口的波特率。通过对该寄存器的值进行设置,可以实现不同的波特率,以满足不同的通信需求。
3.3 工作模式
STM32的USART外设支持多种工作模式,主要包括:
- 全双工模式:在这种模式下,发送和接收可以同时进行,允许设备在发送数据的同时接收来自其他设备的数据,大大提高了通信效率。
- 半双工模式:发送和接收不能同时进行,需要通过软件或硬件进行切换。在某些应用场景下,半双工模式可以满足需求,并且可以节省硬件资源。
- 同步模式:使用额外的时钟信号进行同步数据传输,这种模式可以保证数据传输的准确性和稳定性,适用于对数据传输精度要求较高的场景。
四、代码实现
以下是一个基于STM32Cube HAL库的USART串口通信代码示例,实现了简单的串口数据发送和接收功能。
#include "stm32f4xx_hal.h"
UART_HandleTypeDef huart1;
// 系统时钟配置函数
void SystemClock_Config(void);
// GPIO初始化函数
static void MX_GPIO_Init(void);
// USART1初始化函数
static void MX_USART1_UART_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
uint8_t tx_data[] = "Hello, STM32 USART!\r\n";
uint8_t rx_data[20];
while (1)
{
// 发送数据
HAL_UART_Transmit(&huart1, tx_data, sizeof(tx_data), HAL_MAX_DELAY);
// 接收数据
HAL_UART_Receive(&huart1, rx_data, sizeof(rx_data), HAL_MAX_DELAY);
// 可在此处添加对接收数据的处理逻辑
HAL_Delay(1000);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 初始化RCC振荡器
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
// 初始化CPU、AHB和APB总线时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO端口时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置GPIO引脚输出电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
// 配置GPIO引脚: PA5
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置GPIO引脚: PA9 PA10
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void Error_Handler(void)
{
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief 报告发生断言错误的源文件名和行号
* @param file: 指向源文件名的指针
* @param line: 断言错误发生的行号
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
// 用户可以添加自己的实现来报告文件名和行号
}
#endif
代码解释
- 主函数
main
:程序的入口点。首先进行系统初始化,包括HAL库的初始化、系统时钟的配置、GPIO引脚的初始化以及USART1的初始化。然后进入一个无限循环,在循环中不断发送数据“Hello, STM32 USART!”,并接收数据。接收到的数据可以在后续添加处理逻辑。每次发送和接收操作后,程序会延迟1秒。 - 系统时钟配置函数
SystemClock_Config
:该函数的作用是配置系统时钟。首先初始化RCC振荡器,选择内部高速时钟(HSI)作为时钟源,并设置相关参数。然后初始化CPU、AHB和APB总线时钟,设置时钟的分频系数。如果配置过程中出现错误,会调用Error_Handler
函数进入无限循环。 - USART初始化函数
MX_USART1_UART_Init
:初始化USART1外设。设置波特率为115200,数据位为8位,停止位为1位,无校验位,工作模式为发送和接收模式,不使用硬件流控制,过采样率为16。如果初始化过程中出现错误,会调用Error_Handler
函数。 - GPIO初始化函数
MX_GPIO_Init
:配置GPIO引脚。使能GPIOA端口的时钟,将PA5引脚配置为推挽输出模式,速度为低频。将PA9和PA10引脚配置为复用功能,复用为USART1的TX和RX引脚,速度为高频。
五、应用场景与注意事项
5.1 应用场景
USART串口通信在众多领域都有广泛的应用,例如:
- 工业控制:在工业自动化系统中,USART可用于PLC(可编程逻辑控制器)与传感器、执行器之间的数据通信,实现对工业生产过程的监控和控制。
- 智能家居:在智能家居系统中,USART可用于设备之间的互联互通,如智能开关、智能传感器等设备之间的数据传输,实现家居的智能化控制。
- 传感器数据采集:许多传感器都支持USART通信接口,通过USART可以将传感器采集到的数据传输到微控制器进行处理和分析。
5.2 注意事项
- 波特率设置:通信双方的波特率必须保持一致,否则会导致数据传输错误。在实际应用中,要根据具体的通信需求和硬件条件选择合适的波特率。
- 数据帧格式:发送方和接收方的数据帧格式(数据位、校验位、停止位)必须相同,否则接收方无法正确解析接收到的数据。
- 缓冲区管理:在实际应用中,要注意接收缓冲区的大小和溢出问题。如果接收缓冲区过小,可能会导致数据丢失;如果数据接收速度过快,也可能会导致缓冲区溢出。因此,需要合理设置缓冲区的大小,并采用适当的缓冲区管理策略。
六、总结
本文详细介绍了USART串口协议和STM32的串口外设。USART协议提供了一种简单、可靠的串行通信方式,而STM32的串口外设则为开发者提供了丰富的配置选项和强大的功能。通过代码示例,我们展示了如何使用STM32Cube HAL库实现基本的串口通信功能。开发者可以根据实际需求对代码进行扩展和优化,以满足不同的应用场景。在实际开发过程中,要注意波特率设置、数据帧格式和缓冲区管理等问题,确保串口通信的稳定性和可靠性。