当前位置: 首页 > article >正文

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的改写

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. 代码示例
#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. 代码示例
#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 */









http://www.kler.cn/a/313236.html

相关文章:

  • LLM - 使用 LLaMA-Factory 微调大模型 Qwen2-VL SFT(LoRA) 图像数据集 教程 (2)
  • PaaS云原生:分布式集群中如何构建自动化压测工具
  • 华为大咖说 | 浅谈智能运维技术
  • 《JavaEE进阶》----20.<基于Spring图书管理系统①(登录+添加图书)>
  • 番外:MySQL的一些事务处理
  • 介绍和安装及数据类型
  • 【RabbitMQ】RabbitMQ 概述
  • 在HTML中添加段落方法
  • 领域驱动DDD三种架构-分层架构、洋葱架构、六边形架构
  • ARM驱动学习之22 字符类GPIO
  • 智慧安防监控EasyCVR视频汇聚管理平台如何修改视频流分辨率?
  • 传输大咖44 | 云计算企业大数据迁移如何更安全高效?
  • docker-compose elasticsearch 集群搭建(用户登录+https访问)
  • Python 从入门到实战24(类的继承)
  • SCDN是服务器吗?SCDN防御服务器有什么特点?
  • 操作数据库
  • web前端-HTML常用标签-综合案例
  • kafka动态认证 自定义认证 安全认证-亲测成功
  • 【C#基础】函数传参大总结
  • 算法题总结(一)——二分查找专题
  • [深度学习]神经网络
  • 基于单片机热敏电阻PT100温度控制系统设计
  • SQL编程题复习(24/9/19)
  • 安装Kali Linux后8件需要马上安排的事
  • 门控循环单元(GRU)
  • MySQL高阶1873-计算特殊奖金