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

【STM32HAL开发】学习笔记(3)——串口

1.串口的含义

串口是一种设备通信的接口,包含发送与接收功能。串口通信以数据帧的形式进行,每一帧数据包含1位起始位、8/9位数据位以及0.5/1/1.5/2位停止位,加粗是常用形式。串口处于空闲状态(高电平),起始位则是将串口拉低,然后开始传输数据位,传输完成后将输出拉高(停止位,处于高电平)。有时候,数据位的最后一位会被拿来作为校验位,常见的校验有奇校验和偶校验。波特率是衡量通信速率的指标,表述每秒传输的位数。常见波特率为9600,115200。

UART:Universal Asynchronous Receiver / Transmitter,通用异步收/发器,只支持异步模式

USART:Universal Synchronous / Asynchronous Receiver / Transmitter,通用同步/异步收/发器,只支持同步和异步模式

同步模式两个串口之间多了一个CLK时钟线。

2.串口收发数据——无中断

先在STM32CubeMX上进行配置串口,在Connectiveity中找到USART1,Mode选择Asynchronous,波特率(Baud Rate)设置为115200,数据位长度(Word Length)设置为8,校验位(Parity)设置为None,即不使用校验位,停止位(Stop Bits)设置为1,Data Direction设置为Receive and Transmit(收发双向)。

生成工程,然后打开Keil,串口收发代码如下

uint8_t data; // 定义串口发送数组缓存区 uint8_t receive; // 定义串口接收区 //第一个参数为串口句柄,由初始化配置生成,第二个参数为发送数据的首地址, //第三个参数为一次发送数据的大小,第四个为超时时间 HAL_UART_Transmit(&huart1, (uint8_t *)&data, sizeof(data), HAL_MAX_DELAY); //第一个参数为串口句柄,由初始化配置生成,第二个参数为发接收数据的首地址, //第三个参数为一次接收数据的大小,第四个为超时时间 HAL_UART_Receive(&huart1, &receive, 1, HAL_MAX_DELAY);

3.串口收发数据——有中断

在STM32中,串口中断收发是一种高效的通信方式,能够在数据到达或发送完成时自动触发中断,避免CPU轮询等待,从而提升系统实时性与资源利用率。在Cube配置中,使能NVIC。

初始化代码

HAL_UART_Receive_IT(&huart1, rx_buf, 1); // 开启接收中断

通过回调函数处理中断

// 接收回调函数,产生中断后自动调用 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart == &huart1) // 判断串口 { // 处理串口数据 // 重新使能接政 HAL_UART_Receive_IT(&huart1, rx_buf, 5); } } // 发送回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // 无需额外发送数据操作,但可以通知主程序发送状态,如设置标志位 }

4.串口DMA收发数据

在STM32中,DMA可以用于减轻CPU的负担,特别是在需要移动大量数据时。例如,当需要将数据从一个外设传输到另一个外设,或者从外设传输到内存时,DMA可以直接建立数据通路,而无需CPU介入。这样,CPU就可以专注于其他更重要的任务,如计算和控制。

配置步骤:使用DMA的前置配置与使用中断的一样,然后再勾选上DMA,点击Add-->Select将TX和RX都选上,其他的保持默认即可。

在Keil中,只需要将HAL_UART_Receive_IT()换成HAL_UART_Receive_DMA(),将HAL_UART_Transmit_IT()换成HAL_UART_Transmit_DMA(),即可实现与串口中断收发数据一样的效果。

5.串口接收不定长数据

对于STM32串口接收不定长数据,常用的方法有两种:(1)普通空闲中断,每一个字节都进中断;(2)空闲中断+DMA。此外还有一种使用定时器超时判断的,但是需要额外的定时器资源。

(1)普通空闲中断

原理:每个接收到的字节都会触发接收中断(RXNE),在中断中手动存入缓冲区并计数。利用空闲中断判断帧结束。

(2)空闲中断+DMA

开启串口的空闲中断(IDLE),当接收线上空闲超过一个字节时间时触发中断。使用DMA将接收到的数据自动搬运到缓冲区,无需CPU干预每个字节。在空闲中断中,通过DMA剩余计数器计算出本次接收的数据长度。

在CubeMX中进行以下配置

  • 使能USART1的全局中断

  • 添加DMA通道,方向为Peripheral to Memory,模式Normal(或Circular,推荐Circular)

  • 设置数据宽度为Byte

  • 使能DMA传输完成中断(可选)

如果使用循环模式(Circular),则DMA会循环覆盖缓冲区,适合连续接收;如果使用正常模式(Normal),则每次接收完指定长度后停止,需要重新启动。

定义变量

#define RX_BUF_SIZE 256 // 定义一个数组接收大小的宏,数组大小不支持变量 uint8_t rx_dma_buf[RX_BUF_SIZE]; // DMA接收缓冲区 volatile uint16_t rx_len = 0; // 本次接收的数据长度 volatile uint8_t rx_complete = 0; // 接收完成标志

初始化UART和DMA

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能串口空闲中断 HAL_UART_Receive_DMA(&huart1, rx_dma_buf, RX_BUF_SIZE); //启用DMA接收数据(循环模式)

空闲中断处理接收数据,void USART1_IRQHandler(void)函数一般在stm32f1xx_it.c中,这个是专门管理中断的。

void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ //处理空闲中断 if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == RESET) { //先关闭DMA,防止这个时候收到数据,影响当前数据的处理 HAL_UART_DMAStop(&huart1); //接收数据到缓存区 rx_len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); //计算接收数据的长度 rx_comp_flag = 1; //接收完成将标志设为1,通知主循环 } //重新启用DMA接收数据 HAL_UART_Receive_DMA(&huart1, rx_dma_buf, RX_BUF_SIZE); /* USER CODE END USART1_IRQn 1 */ }

在主循环中对接收到的数据进行回发

while (1) { if(rx_complete) { rx_complete = 0; // 处理 rx_dma_buf 中的 rx_len 个字节 // 例如发送回去: HAL_UART_Transmit(&huart1, rx_dma_buf, rx_len, 100); // 注意:处理期间DMA可能正在接收新数据,若需保护可使用双缓冲或临界区 } }

归纳串口收发函数

HAL_UART_Transmit(); //串口发送数据,使用超时管理机制 HAL_UART_Receive(); //串口接收数据,使用超时管理机制 HAL_UART_Transmit_IT(); //串口中断模式发送 HAL_UART_Receive_IT(); //串口中断模式接收 HAL_UART_Transmit_DMA(); //串口DMA模式发送 HAL_UART_Transmit_DMA(); //串口DMA模式接收 HAL_UART_IRQHandler(UART_HandleTypeDef *huart); //串口中断处理函数 HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //串口发送中断回调函数 HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //串口发送一半中断回调函数(用的较少) HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断回调函数 HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); //串口接收一半回调函数(用的较少) HAL_UART_ErrorCallback(); //串口接收错误函数
http://www.jsqmd.com/news/1185487/

相关文章:

  • RUST编程学习-碎片2-Unsafe
  • 2026 年通辽优秀的叉车出租订做厂家哪家强,别再自己买!效率翻倍的秘密工具解析 - 企业官方推荐【认证】
  • 【Unity3D】TrailRenderer进阶:从导弹尾焰到武器挥砍的实战特效
  • 基于YOLOv8的道路坑洼检测系统:从模型训练到PyQT5界面开发全流程
  • C++ HTTP客户端库cpr:现代化网络请求的实践指南
  • Windows平台微信QQ防撤回工具RevokeMsgPatcher原理与实战指南
  • 【项目编号:project05966】SpringBoot/Vue 电影推荐系统:用户端推荐、电影资讯与后台数据管理全流程
  • Unity URP性能优化:SRP Batcher原理、配置与实战指南
  • 反应式Agent在智能控制系统中的设计与优化
  • UE5 RPG技能系统开发:从蓝图到C++的Gameplay Ability System实战指南
  • STM32与A3908驱动的高精度运动控制系统设计
  • C语言模块管理器设计与实现:从编译原理到工程实践
  • Unity 2D游戏开发入门:从基础到实战
  • C++集成学习实战:从原理到工程,掌握Bagging、Boosting与Stacking
  • LLM注意力可视化在医疗诊断中的实践与应用
  • postman 接口自动化
  • 如何解决Kimi到word的格式问题?AI 导出鸭把Markdown/LaTeX编译成docx的技术拆解
  • 【2013-11-27】《Java编程思想》读书笔记:synchronized
  • stable-diffusion.cpp:纯C/C++实现AI绘画推理,打造轻量高效的生成式AI运行时
  • 表格 deepseek 工具哪个好用无需纠结,AI 导出鸭适配全场景表格导出
  • Unity 2022.3中Spine动画导入失败排查与版本兼容性解决方案
  • 2026年版中国地下运矿车市场发展战略规划及投资方向研究报告
  • Cursor多文件上下文协同失效?97%开发者忽略的context.windowSize阈值与动态裁剪策略
  • ArduSub入门指南:水下ROV运动控制原理与实战调参
  • C++26契约编程正式入标:用前置后置条件将防御式检查从文档变成编译器强制行为
  • Windows网络管理利器:Net命令实战指南与场景解析
  • 元初混沌 6G 全域通感一体化体系架构 第一卷二阶第二十五篇 三才组网体系闭环总结
  • 从哈尔到D20:多贝西小波(DB小波函数)的演进与工程选型指南
  • 【目标检测--tricks】FPN与PAN:从结构演进到实战融合
  • Windows C盘清理终极指南:从原理到实践的完整解决方案