一、串口
- 在 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();// 初始化GPIOMX_GPIO_Init();// 初始化USART1MX_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预分频值为1RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 启用内部高速时钟 (HSI)RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 启用锁相环 (PLL)RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL时钟源为HSERCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL8; // PLL倍频因子为8if (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; // 系统时钟源为PLLRCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟不分频RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1时钟分频为2RCC_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; // 波特率为115200huart1.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; // 过采样为16if (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};// 配置振荡器和 PLLRCC_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; // 波特率设置为 115200huart1.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" 并开启红色LEDchar *p = strstr(rxbuf, "ledr=on");if (p) {HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET);}// 查找命令 "ledr=off" 并关闭红色LEDp = strstr(rxbuf, "ledr=off");if (p) {HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET);}// 查找命令 "ledg=on" 并开启绿色LEDp = strstr(rxbuf, "ledg=on");if (p) {HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_SET);}// 查找命令 "ledg=off" 并关闭绿色LEDp = strstr(rxbuf, "ledg=off");if (p) {HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_RESET);}// 查找命令 "ledb=on" 并开启蓝色LEDp = strstr(rxbuf, "ledb=on");if (p) {HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);}// 查找命令 "ledb=off" 并关闭蓝色LEDp = 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和USART1MX_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 */