STM32使用 :串口的接收与发送
一、串口
- 在 STM32 中,串口(UART,通用异步收发传输器)是用于串行通信的外设。它在嵌入式系统中的作用非常广泛,主要包括几个方面
- 数据通信
串口用于微控制器与其他设备之间的数据传输。这些设备可以是其他微控制器、传感器、计算机或通信模块(例如蓝牙、Wi-Fi 模块等)。串口以异步方式传输数据,不需要时钟信号,因此实现起来相对简单。 - 调试功能
UART 在嵌入式开发中常用于打印调试信息。在 STM32 开发过程中,程序员可以使用 printf 函数将调试信息通过串口输出到计算机终端。 - 固件升级
在嵌入式设备的维护过程中,UART 可以用于通过串口线对设备进行固件升级。 - 外部模块通信
GPS模块,蓝牙模块
UART 串口的主要特点
- 异步通信,全双工通信,易于实现,传输效率较快。
调试软件
www.mcuisp.com下载即可
串口的发送(调试功能)
-
设置RCC的high SPeed CLock的模式为Crystal/Ceramic
-
找到电路板的串口,看清楚串口连接的是哪一个,我的电路板串口启用的是UART1
-
UART1挂载到APB2总线上,设置APB2为64MHZ即可。
-
对USART1模式设置为Asynchronous,即为异步通信方式,当然你也可以在这里设置串口的具体参数
-
通常将 printf 函数的输出重定向到 UART,这样你可以使用 printf 在终端上输出调试信息。需要实现 __io_putchar 函数来将字符发送到 UART
-
实际上是对__io_putchar的改写
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
参数:
int ch:传入的字符,将要通过串口输出的单个字符。
功能:
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY):这个函数使用 STM32 的 HAL 库,通过 huart1(UART1 的句柄)发送数据。
&huart1:表示使用 UART1,也可以换成串口2。
(uint8_t *)&ch:将字符 ch 的地址转换为 uint8_t* 类型,因为 UART 通常处理的是 8 位数据(字符)。
1:发送 1 个字节的数据。
HAL_MAX_DELAY:设置为最大等待时间,表示发送数据时系统会等待直到传输完成。
返回值:
return ch;:函数返回发送的字符 ch,这是为了和标准的 putchar 函数兼容,通常不用于实际逻辑。
- 代码示例
#include "main.h"
UART_HandleTypeDef huart1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
// 主函数
int main(void)
{
// 初始化HAL库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化GPIO
MX_GPIO_Init();
// 初始化USART1
MX_USART1_UART_Init();
// 无限循环,用于发送测试信息
while (1)
{
// 打印当前天气信息
printf("today is sunshine,temp=45,shidu=35.\r\n");
// 打印欢迎信息
printf("hello world---------------->");
// 延迟500毫秒
HAL_Delay(500);
}
}
// 重定向printf函数,使其可以通过USART1发送字符
int __io_putchar(int ch)
{
// 通过USART1发送字符
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
// 配置系统时钟
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置时钟源
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 启用外部高速时钟 (HSE)
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; // HSE预分频值为1
RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 启用内部高速时钟 (HSI)
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 启用锁相环 (PLL)
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL时钟源为HSE
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL8; // PLL倍频因子为8
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
// 如果时钟配置失败,则进入错误处理
Error_Handler();
}
// 配置时钟树
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟源为PLL
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟不分频
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1时钟分频为2
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2时钟不分频
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
// 如果时钟配置失败,则进入错误处理
Error_Handler();
}
}
// 初始化USART1
static void MX_USART1_UART_Init(void)
{
// 初始化USART1结构体
huart1.Instance = USART1; // USART1实例
huart1.Init.BaudRate = 115200; // 波特率为115200
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 数据字长为8位
huart1.Init.StopBits = UART_STOPBITS_1; // 停止位为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; // 过采样为16
if (HAL_UART_Init(&huart1) != HAL_OK)
{
// 如果USART1初始化失败,则进入错误处理
Error_Handler();
}
}
// 初始化GPIO
static void MX_GPIO_Init(void)
{
// 使能GPIOD和GPIOA的时钟
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
// 错误处理函数
void Error_Handler(void)
{
// 禁用中断
__disable_irq();
// 进入无限循环
while (1)
{
}
}
串口的接收
- 设置RCC的High SPeed CLock为Crystal/Ceramic
- 设置USART1的模式为Asynchronous,并在NVIC开启中断,不用轮询访问
- 配置对应的总线上的时钟频率
- 对
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size)
进行改写即可。 - 代码示例
#include "main.h"
UART_HandleTypeDef huart1; // UART1 句柄,用于配置和管理 UART1 外设
char rxbuf[64]; // 接收缓冲区,用于存储从 UART 接收到的数据
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* UART 接收中断回调函数 ---------------------------------------------*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
// 在接收到数据后,这里将接收到的数据打印到终端
printf("uart recv:%s\r\n", rxbuf);
// 清空接收缓冲区
memset(rxbuf, 0, sizeof(rxbuf));
// 重新启动 UART 接收,以便持续接收数据
HAL_UARTEx_ReceiveToIdle_IT(&huart1, rxbuf, sizeof(rxbuf));
}
/* 重定向 printf 到 UART1 --------------------------------------------*/
int __io_putchar(int ch)
{
// 通过 UART1 发送单个字符
HAL_UART_Transmit(&huart1, (unsigned char*)&ch, 1, 1);
return ch; // 返回发送的字符
}
int main(void)
{
// 初始化 HAL 库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化 GPIO 和 UART1 外设
MX_GPIO_Init();
MX_USART1_UART_Init();
// 启动 UART 接收中断,准备接收数据
HAL_UARTEx_ReceiveToIdle_IT(&huart1, rxbuf, sizeof(rxbuf));
while (1)
{
// 主循环,保持程序运行
}
}
/* 配置系统时钟 -------------------------------------------------------*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置振荡器和 PLL
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL8;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置时钟源和时钟分频
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
/* 初始化 USART1 ------------------------------------------------------*/
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200; // 波特率设置为 115200
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 8 数据位
huart1.Init.StopBits = UART_STOPBITS_1; // 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; // 16 倍采样
HAL_UART_Init(&huart1); // 初始化 UART1
}
/* 初始化 GPIO --------------------------------------------------------*/
static void MX_GPIO_Init(void)
{
// 启用 GPIOD 和 GPIOA 时钟
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
/* 错误处理函数 -------------------------------------------------------*/
void Error_Handler(void)
{
__disable_irq(); // 禁用所有中断
while (1)
{
// 在出现错误时,进入死循环
}
}
应用:串口协议解析用户命令
- 其实就是对收到的数据(该数据具有一定的格式)截取对应的关键字进行处理的过程。
- 主要应用函数strstr
strstr 函数是 C 标准库中的一个字符串处理函数,用于查找一个子字符串在另一个字符串中的第一次出现位置。它的函数原型在 <string.h> 头文件中定义。
函数原型:char *strstr(const char *haystack, const char *needle);
参数:haystack ->>指向要搜索的主字符串的指针。
needle ->>指向要查找的子字符串的指针。
返回值
如果找到子字符串 needle 在主字符串 haystack 中的第一次出现位置,strstr 返回指向子字符串首次出现位置的指针。
位置从0开始算
如果没有找到子字符串,strstr 返回 NULL。
atoi函数:用于将一个字符串转换成整数。定义在 <stdlib.h>
原型:int atoi(const char *str);
- 代码示例
该代码的主要功能是可以对以下格式的数据进行处理
cmd:ledr=on,usrname=xiaowang,passwd=123456,temp=23;
代码中有限制对应的字长
硬件操作LED灯,管脚在PC6,PC7,PC8,软件输出用户名,密码和温度
#include "string.h"
#include <stdlib.h>
#include "main.h"
// UART句柄声明,用于管理USART1的UART通信
UART_HandleTypeDef huart1;
// 函数声明
void SystemClock_Config(void); // 系统时钟配置
static void MX_GPIO_Init(void); // GPIO初始化
static void MX_USART1_UART_Init(void); // USART1 UART初始化
char rxbuf[64]; // UART接收缓冲区
char name[64]; // 用户名存储
char passwd[64]; // 密码存储
char temp[32]; // 临时存储温度数据
int itemp; // 存储温度的整数值
/**
* @brief UART接收回调函数
* @param huart: UART句柄
* @param Size: 接收的数据大小
* @retval None
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
// 确保这是USART1的事件
if (huart->Instance == USART1) {
// 查找命令 "ledr=on" 并开启红色LED
char *p = strstr(rxbuf, "ledr=on");
if (p) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET);
}
// 查找命令 "ledr=off" 并关闭红色LED
p = strstr(rxbuf, "ledr=off");
if (p) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET);
}
// 查找命令 "ledg=on" 并开启绿色LED
p = strstr(rxbuf, "ledg=on");
if (p) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_SET);
}
// 查找命令 "ledg=off" 并关闭绿色LED
p = strstr(rxbuf, "ledg=off");
if (p) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_RESET);
}
// 查找命令 "ledb=on" 并开启蓝色LED
p = strstr(rxbuf, "ledb=on");
if (p) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);
}
// 查找命令 "ledb=off" 并关闭蓝色LED
p = strstr(rxbuf, "ledb=off");
if (p) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);
}
// 解析 "usrname=" 后的用户名信息
p = strstr(rxbuf, "usrname=");
if (p) {
p += strlen("usrname=");
int i = 0;
while (p[i] != ',' && p[i] != ';') {
name[i] = p[i];
i++;
}
name[i] = '\0'; // 添加字符串终止符
}
// 解析 "passwd=" 后的密码信息
p = strstr(rxbuf, "passwd=");
if (p) {
p += strlen("passwd=");
int i = 0;
while (p[i] != ',' && p[i] != ';') {
passwd[i] = p[i];
i++;
}
passwd[i] = '\0'; // 添加字符串终止符
}
// 解析 "temp=" 后的温度数据
p = strstr(rxbuf, "temp=");
if (p) {
p += strlen("temp=");
int i = 0;
while (p[i] != ',' && p[i] != ';') {
temp[i] = p[i];
i++;
}
temp[i] = '\0'; // 添加字符串终止符
itemp = atoi(temp); // 将字符串转换为整数
}
// 打印调试信息,包括用户名、密码和温度值
printf("xuart recv username=%s passwd=%s temp=%d\n", name, passwd, itemp);
// 清空接收缓冲区
memset(rxbuf, 0, sizeof(rxbuf));
// 重新启用接收中断
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)rxbuf, sizeof(rxbuf));
}
}
/**
* @brief 重定向printf函数到UART
* @param ch: 字符
* @retval 传输的字符
*/
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart1,(unsigned char*)&ch,1,HAL_MAX_DELAY);
return ch;
}
/**
* @brief 主函数,系统的入口
* @retval None
*/
int main(void)
{
// 初始化HAL库
HAL_Init();
// 系统时钟配置
SystemClock_Config();
// 初始化GPIO和USART1
MX_GPIO_Init();
MX_USART1_UART_Init();
// 启动UART接收
HAL_UARTEx_ReceiveToIdle_IT(&huart1, rxbuf, sizeof(rxbuf));
// 无限循环,保持程序运行
while (1)
{
}
}
/**
* @brief 配置系统时钟
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置HSE时钟
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL8;
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_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
// 配置时钟
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1初始化函数
* @retval None
*/
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();
}
}
/**
* @brief GPIO初始化函数
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 启用GPIO时钟
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置PC6、PC7和PC8引脚为输出
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
/**
* @brief 错误处理函数
* @retval None
*/
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief 报告错误的文件名和行号
* @param file: 错误发生的文件名
* @param line: 错误发生的行号
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */