STM32使用 :串口的接收与发送

一、串口

  1. 在 STM32 中,串口(UART,通用异步收发传输器)是用于串行通信的外设。它在嵌入式系统中的作用非常广泛,主要包括几个方面
  • 数据通信
    串口用于微控制器与其他设备 之间的数据传输。这些设备可以是其他微控制器、传感器、计算机或通信模块(例如蓝牙、Wi-Fi 模块等)。串口以异步方式传输数据,不需要时钟信号,因此实现起来相对简单。
  • 调试功能
    UART 在嵌入式开发中常用于打印调试信息。在 STM32 开发过程中,程序员可以使用 printf 函数将调试信息通过串口输出到计算机终端。
  • 固件升级
    在嵌入式设备的维护过程中,UART 可以用于通过串口线对设备进行固件升级。
  • 外部模块通信
    GPS模块,蓝牙模块
UART 串口的主要特点
  • 异步通信,全双工通信,易于实现,传输效率较快。
调试软件

www.mcuisp.com下载即可

串口的发送(调试功能)
  1. 设置RCC的high SPeed CLock的模式为Crystal/Ceramic

  2. 找到电路板的串口,看清楚串口连接的是哪一个,我的电路板串口启用的是UART1

  3. UART1挂载到APB2总线上,设置APB2为64MHZ即可。

  4. 对USART1模式设置为Asynchronous,即为异步通信方式,当然你也可以在这里设置串口的具体参数

  5. 通常将 printf 函数的输出重定向到 UART,这样你可以使用 printf 在终端上输出调试信息。需要实现 __io_putchar 函数来将字符发送到 UART

  6. 实际上是对__io_putchar的改写

cpp 复制代码
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 函数兼容,通常不用于实际逻辑。
  1. 代码示例
cpp 复制代码
#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)
  {
  }
}
串口的接收
  1. 设置RCC的High SPeed CLock为Crystal/Ceramic
  2. 设置USART1的模式为Asynchronous,并在NVIC开启中断,不用轮询访问
  3. 配置对应的总线上的时钟频率
  4. void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size)进行改写即可。
  5. 代码示例
cpp 复制代码
#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
c 复制代码
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);
  • 代码示例
cpp 复制代码
该代码的主要功能是可以对以下格式的数据进行处理
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 */
相关推荐
嵌入式大圣1 小时前
STM32 单片机最小系统全解析
stm32·单片机·嵌入式硬件
LN花开富贵6 小时前
stm32g431rbt6芯片中VREF+是什么?在电路中怎么设计?
笔记·stm32·单片机·嵌入式硬件·学习
qq21084629536 小时前
【stm32笔记】使用rtt-studio与stm32CubeMx联合创建项目
笔记·stm32·嵌入式硬件
CV金科6 小时前
蓝桥杯—STM32G431RBT6按键的多方式使用(包含软件消抖方法精讲)从原理层面到实际应用(一)
stm32·单片机·嵌入式硬件·蓝桥杯
2021.096 小时前
五、CAN总线
嵌入式硬件
luckyluckypolar6 小时前
STM32——输入捕获
stm32·单片机·嵌入式硬件·物联网
hong1616886 小时前
嵌入式硬件基础知识
嵌入式硬件
hai405876 小时前
单片机(Microcontroller)原理及应用
单片机·嵌入式硬件
jun7788957 小时前
嵌入式硬件基础知识
嵌入式硬件
Projectsauron8 小时前
STM32 芯片启动过程
stm32·单片机·芯片启动过程