单片机-STM32部分:10、串口UART
飞书文档https://x509p6c8to.feishu.cn/wiki/W7ZGwKJCeiGjqmkvTpJcjT2HnNf
串口说明
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,0V表示0
RS232电平:-3~ -15V表示1,+3~ +15V表示0
RS485电平:两线压差+2~+6V表示1,-2 ~-6V表示0(差分信号)
STM32F103RC系列芯片中,有五个串口
3个USART,2个UART |
IO口说明:
点击图片可查看完整电子表格
TX:发送数据输出引脚。 |
硬件流控说明,例如: |
创建工程,设置SWD,设置时钟。
配置USART1为异步通信方式,不需要硬件流控制。
Asynchronous(异步通讯)主要使用 |
然后设置波特率为115200bps 数据长度8bit 没有校验位 1位停止位。
串口中,每个字节都装载在一个数据帧(10或11位)里,每个数据帧都由起始位、数据位和停止位,数据位有8个代表一个字节的8位。参数如下: |
波特率9600代表1s发送9600个bit,也就是1个bit发送需要100us左右
这时,软件会自动选择PA9与PA10做为串口的发送与接收引脚。
这时,我们可以生成工程
main.c
MX_USART1_UART_Init();usart.c
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();}
}
那如何实现串口发送或接收数据呢?
stm32f1xx_hal_uart.h |
有多种方式,我们先来了解第一种,阻塞轮询模式
HAL_UART_Transmit (UART_HandleTypeDef *huart, const uint8 t *pData, uint16 t Size, uint32 t Timeout) |
现在,我们先实现发送功能,在main.c中添加发送代码
while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */uint8_t txbuf[]="Hello,world!";HAL_UART_Transmit(&huart1,txbuf,sizeof(txbuf),1000);HAL_Delay(500); }/* USER CODE END 3 */
- 编译烧录至板卡,然后接好串口线连接到电脑。
- 打开串口调试助手,选择COM口,例如下方是COM5,根据自己电脑设备管理器的COM选择,插拔USB线,会显示新COM,如果提示COM口有叹号,则需要自行搜索CH340驱动安装。
串口调试助手软件:自行安装即可:参考飞书文档
- 然后设置波特率115200 8 N 1,即可看到间隔500ms打印信息。
参考工程:
如果烧录完没打印,可以重启或复位下
/* USER CODE BEGIN 2 */uint8_t rxbuf[12];/* USER CODE END 2 */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(HAL_UART_Receive(&huart1,rxbuf,sizeof(rxbuf),1000) == HAL_OK){HAL_UART_Transmit(&huart1,rxbuf,sizeof(rxbuf),1000);}}/* USER CODE END 3 */
编译烧录至板卡,然后接好串口线连接到电脑,打开串口调试助手,设置波特率115200 8 N 1,发送ASCII码“Hello world”,
为什么“Hello world”是11个字符,我们需要接收rxbuf[12]是12个字节呢?
因为串口助手工具,会自动加上换行符,点击右侧的发送后,我们可以看到TX是12个字节。
串口中断方式
我们可以看到,上方的方式都是阻塞式发送,轮询接收的,简单的产品这样设计没有问题,但是做一些复杂的,对实时性有要求的产品时,就满足不了了,所以我们可以用到串口中断的功能,在CUBEMX中使能中断。
阻塞方式就好比你要拿快递,就一遍遍都前台询问快递到没到,在这期间你不能干别的, |
生成工程后,可以在stm32f1xx_it.c中看到生成了中断相关函数
/*** @brief This function handles USART1 global interrupt.*/
void USART1_IRQHandler(void)
{HAL_UART_IRQHandler(&huart1);
}void HAL_UART_IRQHandler(UART_HandleTypeDef *huart){xxxxUART_Receive_IT(huart);xxxx
}static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart){xxxxHAL_UART_RxCpltCallback(huart);xxxx
}
最终找到需要重写的虚函数
/*** @brief Rx Transfer completed callbacks.* @param huart Pointer to a UART_HandleTypeDef structure that contains* the configuration information for the specified UART module.* @retval None*/
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{/* Prevent unused argument(s) compilation warning */UNUSED(huart);/* NOTE: This function should not be modified, when the callback is needed,the HAL_UART_RxCpltCallback could be implemented in the user file*/
}
然后在
main.c/* USER CODE BEGIN 0 */uint8_t rxbuf[10];uint8_t ackbuf[] = "ack pack";/* USER CODE END 0 *//* USER CODE BEGIN 2 */HAL_UART_Receive_IT(&huart1,rxbuf,sizeof(rxbuf));/* USER CODE END 2 *//* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(huart == &huart1) //判断中断是否来自于串口1{HAL_UART_Transmit_IT(&huart1,ackbuf,sizeof(ackbuf)); //通过中断的方式发送应答数据出去HAL_UART_Receive_IT(&huart1,rxbuf,sizeof(rxbuf)); //开始接收下一轮数据}
}
下载完成,点击复位。打开串口助手,连接到相应的端口,设置波特率为115200,从串口助手向单片机发送10个字节的数据,单片机将会把发过去的数据在返回给串口助手。必须发够10个字节以上的数据,才能够触发中断。
参考工程:
如果烧录完没打印,可以重启或复位下
串口中断+DMA方式
这时候,如果我们在开发产品过程中,需要频繁收发数据,且通信波特率较高时,如果采用中断方式,每收发一个字节的数据,CPU都会被打断,造成CPU无法处理其他事务。 因此在批量数据传输,通信波特率较高时,建议采用DMA方式。
串口中断每收发一个字节数据,CPU都会被打断 | CPU只需要设置开始传输和处理传输结束的中断 |
DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
我们知道CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?
所以串口收发数据量大时可借助DMA,减轻CPU负担。即在内存与IO设备间传送一个数据块的过程中,不需要CPU的任何中间干涉,只需要CPU在过程开始时向设备发出“传送块数据”的命令,然后通过中断来得知过程是否结束和下次操作是否准备就绪。
整个过程只产生两次中断,第一次是进入DMAx_Streamy_IRQHandler;第二次进入USARTx_IRQHandler。
前文说过中断方式就好比你告诉前台,等快递到了给你打电话,让你亲自来取,假设你正在做着一些重要的事情,正好来了电话让你取快递,这样一来就会耽误事。 |
STM32F103RC有12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过 软件来配置。
这里,我们切换CUBEMX的USART1中,设置DMA,点击Add,把USART1_TX USART1_RX都添加进来。
注意,RX和TX下方的DMA Request Settings都需要设置为一样。
Channel:通道 |
这里有个需要注意的地方,就是函数调用顺序
MX_DMA_Init()函数需要在其他初始化前调用,特别是在这个串口初始化前,不然会发送使用DMA发送会发送失败,在如下图位置配置调用顺序,必须先配置时钟再配置外设,MX_DMA_Init()里面有DMA时钟初始化
设置完成上面步骤,生成工程后,我们会发现DMA初始化在USART1之前,如果不进行这步设置,可能会出现发送失败的情况哦。
/* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();
然后,我们可以使用DMA方式实现串口发送
main.cwhile (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */uint8_t txbuf[]="Hello,world!";HAL_UART_Transmit_DMA(&huart1,txbuf,sizeof(txbuf));HAL_Delay(500); }/* USER CODE END 3 */
也可以使用DMA方式实现串口收发
如果需要实时处理串口的数据,则需要打开串口全局中断。
UART一旦开启DMA之后,DMA通道全局中断都是强制开启的,DMA传输完整数据后,会触发HAL_UART_RxCpltCallback或HAL_UART_TxCpltCallback中断产生。
main.c/* USER CODE BEGIN 0 */uint8_t rxbuf[10];uint8_t ackbuf[] = "ack pack";/* USER CODE BEGIN 2 *///初始化DMA串口接收需要在串口初始化前?HAL_UART_Receive_DMA(&huart1,rxbuf,sizeof(rxbuf));/* USER CODE END 2 *//* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(huart == &huart1) //判断中断是否来自于串口1{HAL_UART_Transmit_DMA(&huart1,ackbuf,sizeof(ackbuf)); //通过中断的方式发送应答数据出去//如果接收使用循环模式,则不用重新开启HAL_UART_Receive_DMA(&huart1,rxbuf,sizeof(rxbuf)); //开始接收下一轮数据}
}
参考工程:参考飞书文档
使用USART+DMA接收中断不定长数据
可以使用STM32 IDLE空闲中断实现,IDLE的中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断
main.c
/* USER CODE BEGIN 0 */
extern DMA_HandleTypeDef hdma_usart1_rx;
#define BUFFER_SIZE 100
uint8_t rxbuf[BUFFER_SIZE];/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1,rxbuf,sizeof(rxbuf));/* USER CODE BEGIN 4 */
void UART_IDLEHandler(){if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET) //如果串口处于空闲状态{__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);//清空空闲状态标志HAL_UART_DMAStop(&huart1); //关闭DMA传输//计算接收到的数据长度 ,已接收长度=需要接收总长度-剩余待接收长度uint8_t rlen = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//发送数据到上位机,当然,这里可以把数据复制到其它位置进行处理 HAL_UART_Transmit_DMA(&huart1,rxbuf,rlen);//重新打开DMA接收HAL_UART_Receive_DMA(&huart1,rxbuf,sizeof(rxbuf)); }
}main.h
/* USER CODE BEGIN EFP */
void UART_IDLEHandler(void);
/* USER CODE END EFP */stm32f1xx_it.c
/*** @brief This function handles USART1 global interrupt.*/
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 */UART_IDLEHandler();/* USER CODE END USART1_IRQn 1 */
}
参考工程:参考飞书文档
端口复用
当然,USART1是支持复用功能的,可以重映像到其它IO上,如果我们在进行硬件设计时,发现PA9、PA10走线不好走,或者需要作为其它用途,我们可以把USART1映射到PB6 PB7,如何知道是否支持重映像,可以查看手册8.3章节。
可以在右侧的芯片图中找到PB6,设置为USART1_TX,PB7,设置为USART1_RX
串口重定向
在单片机中使用printf打印
使用HAL_UART_Transmit发送字符串很不方便,可以重定向printf()函数使printf通过串口打印字符串
使用串口重定向,必须勾选MicroLIB |
main.c
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes *//* USER CODE BEGIN 4 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);return ch;
}
相关文章:

单片机-STM32部分:10、串口UART
飞书文档https://x509p6c8to.feishu.cn/wiki/W7ZGwKJCeiGjqmkvTpJcjT2HnNf 串口说明 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种: TTL电平:3.3V或5V表示1&am…...

RabittMQ-高级特性2-应用问题
文章目录 前言延迟队列介绍ttl死信队列存在问题延迟队列插件安装延迟插件使用事务消息分发概念介绍限流非公平分发(负载均衡) 限流负载均衡RabbitMQ应用问题-幂等性保障顺序性保障介绍1顺序性保障介绍2消息积压总结 前言 延迟队列介绍 延迟队列(Delaye…...

React 播客专栏 Vol.5|从“显示”到“消失”:打造你的第一个交互式 Alert 组件!
👋 欢迎回到《前端达人 播客书单》第 5 期(正文内容为学习笔记摘要,音频内容是详细的解读,方便你理解),请点击下方收听 📌 今天我们不再停留在看代码,而是动手实现一个真正的 React…...
解决 MySQL 数据库无法远程连接的问题
在使用 MySQL 数据库时,遇到这样的问题: 本地可以连接 MySQL,但远程机器连接时,总是报错 Host ... is not allowed to connect to this MySQL server。 这通常是因为 MySQL 的用户权限或配置限制了远程访问。 1. 登录 MySQL 数据…...
互联网大厂Java求职面试:基于RAG的智能问答系统设计与实现
互联网大厂Java求职面试:基于RAG的智能问答系统设计与实现 场景背景 在某互联网大厂的技术面试中,技术总监张总正在面试一位名为郑薪苦的求职者。郑薪苦虽然对技术充满热情,但回答问题时总是带着幽默感,有时甚至让人哭笑不得。 …...

解密火星文:LeetCode 269 题详解与 Swift 实现
文章目录 摘要描述题解答案题解代码分析构建图(Graph)拓扑排序(Topological Sort) 示例测试及结果时间复杂度空间复杂度实际场景类比总结 摘要 这篇文章我们来聊聊 LeetCode 269 题:火星词典(Alien Dictio…...

动态规划-62.不同路径-力扣(LeetCode)
一、题目解析 机器人只能向下或向左,要从Start位置到Finish位置。 二、算法原理 1.状态表示 我们要求到Finish位置一共有多少种方法,记Finish为[i,j],此时dp[i,j]表示:到[i,j]位置时,一共有多少种方法,满…...

5月9号.
v-for: v-bind: v-if&v-show: v-model: v-on: Ajax: Axios: async&await: Vue生命周期: Maven: Maven坐标:...

从 Git 到 GitHub - 使用 Git 进行版本控制 - Git 常用命令
希望本贴能从零开始带您一起学习如何使用 Git 进行版本控制,并结合远程仓库 GitHub。这会是一个循序渐进的指南,我们开始吧! 学习 Git 和 GitHub 的路线图: 理解核心概念:什么是版本控制?Git 是什么&…...
何时需要import css文件?怎么知道需要导入哪些css文件?为什么webpack不提示CSS导入?(导入css导入规则、css导入规范)
文章目录 何时需要import css文件?**1. 使用模块化工具(如 Webpack、Vite、Rollup 等)****适用场景:****示例:****优点:** **2. 动态加载 CSS(按需加载)****适用场景:***…...

双指针算法详解(含力扣和蓝桥杯例题)
目录 一、双指针算法核心概念 二、常用的双指针类型: 2.1 对撞指针 例题1:盛最多水的容器 例题2:神奇的数组 2.2 快慢指针: 例题1:移动零 例题2:美丽的区间(蓝桥OJ1372) 3.总…...

【网络编程】二、UDP网络套接字编程详解
文章目录 前言Ⅰ. UDP服务端一、服务器创建流程二、创建套接字 -- socketsocket 属于什么类型的接口❓❓❓socket 是被谁调用的❓❓❓socket 底层做了什么❓❓❓和其函数返回值有没有什么关系❓❓❓ 三、绑定对应端口号、IP地址到套接字 -- bind四、数据的发送和接收 -- sendto…...

【应急响应】- 日志流量如何分析?
【应急响应】- 日志流量如何下手?https://mp.weixin.qq.com/s/dKl8ZLZ0wjuqUezKo4eUSQ...
虚拟机设置NAT没网笔记
查看任务管理器的时候发现,VMware NAT Service 进程都没有,貌似是因为之前我给禁了,emmmmmm 1确认虚拟机网络设置的NAT模式 打开 VMware Workstation,点击 编辑 > 虚拟网络编辑器 确保 VMnet8 配置为 NAT 模式: …...

djinn: 3靶场渗透
djinn: 3 来自 <https://www.vulnhub.com/entry/djinn-3,492/> 1,将两台虚拟机网络连接都改为NAT模式 2,攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182,靶场IP192.168.23.243 3࿰…...

VS Code配置指南:打造高效的QMK开发环境
VS Code配置指南:打造高效的QMK开发环境 前言 你是否曾为QMK固件开发环境的搭建而头疼不已?本文将手把手教你使用Visual Studio Code(简称VS Code)这款强大的代码编辑器来构建一个完美的QMK开发环境,让你的键盘固件开…...

服务器多客户端连接核心要点(1)
刷题 服务器多客户端连接核心要点 多进程服务器 实现原理 fork子进程:每次accept新客户端后,调用fork创建子进程。独立处理:子进程负责与客户端通信(如read/write),父进程继续监听新连接。 特点 隔离性…...
【Python-Day 11】列表入门:Python 中最灵活的数据容器 (创建、索引、切片)
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

Stagehand:AI驱动的下一代浏览器自动化框架
Stagehand 是一个结合了 AI 代理、AI 工具和 Playwright 的浏览器自动化框架。核心理念是:让自动化任务既可控又智能。与传统工具不同,Stagehand 不仅仅依赖 AI 代理的“黑箱操作”,而是通过与 Playwright 的深度结合,赋予开发者对…...
实现线程的4种方法
知识点详细说明 在Java中,实现线程的常用方法有以下四种: 1. 继承Thread类 核心要点: 定义一个类继承Thread,重写run()方法。通过调用start()启动线程(自动执行run())。关键细节: 单继承限制:Java不支持多继承,若类已继承其他类,无法再继承Thread。线程对象直接使用…...

爱普生FA-238在车身控制模块中的应用
在汽车智能化、电子化飞速发展的当下,车身控制模块(BCM)作为车辆的 “智能管家”,肩负着协调和控制众多车身功能的重任,从车门的解锁与锁定、车窗的升降,到车灯的智能点亮与熄灭,再到雨刮器的自…...
单片机嵌入式按键库
kw_btn库说明 本库主要满足嵌入式按键需求,集成了常用的按键响应事件:高电平、低电平、上升沿、下降沿、单击、双击、长按键事件。可以裸机运行,也可以配合实时操作系统运行。 本库开源连接地址:连接 实现思路 本库采用C语言进行…...

【A2A】管中窥豹,google源码python-demo介绍
前言 A2A(Agent2Agent)是 Google 推出的一项新协议,旨在解决多智能体(Multi-Agent)系统中跨平台、跨组织协作的难题。它为 AI 代理之间的通信、协作和任务分工提供了一个统一的标准,可以类比为网页世界的 H…...

004-nlohmann/json 快速认识-C++开源库108杰
了解 nlohmann/json 的特点;理解编程中 “数据战场”划分的概念;迅速上手多种方式构建一个JSON对象; 1 特点与安装 nlohmann/json 是一个在 github 长期霸占 “JSON” 热搜版第1的CJSON处理库。它的最大优点是与 C 标准库的容器数据…...

Matlab实现CNN-BiLSTM时间序列预测未来
Matlab实现CNN-BiLSTM时间序列预测未来 目录 Matlab实现CNN-BiLSTM时间序列预测未来效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CNN-BiLSTM时间序列预测未来; 2.运行环境Matlab2023b及以上,data为数据集,单变量时间序…...

C语言| sizeof(array)占多少字节
C语言| 数组名作为函数参数 sizeof(数组名); 可以求出整个数组在内存中所占的字节数。 被调函数Array_Sum()中,数组array使用sizeof会得到多少? 实参数组a占32字节,实参a传给形参array,只占4字节。 原因如下: 数组名做…...

【文件系统—散列结构文件】
文章目录 一、实验目的实验内容设计思路 三、实验代码实现四、总结 一、实验目的 理解linux文件系统的内部技术,掌握linux与文件有关的系统调用命令,并在此基础上建立面向随机检索的散列结构文件;## 二、实验内容与设计思想 实验内容 1.设…...

World of Warcraft [CLASSIC][80][Deluyia] [Fragment of Val‘anyr]
瓦兰奈尔的碎片 [Fragment of Valanyr] 有时候下个班打个游戏,没想到套路也这么多,唉,何况现实生活,这一个片版本末期才1000G,30个,也就30000G,时光徽章等同月卡15000G,折合一下也就…...

数组和指针典型例题合集(一维数组、字符数组、二维数组)
1.一维数组 数组名的理解 数组名是数组首元素(第一个元素)的地址 但是有两个例外: 1.sizeof (数组名)—— 数组名表示整个数组,就算的是整个数组的大小,单位是字节。 2.&数组名 —— 数…...

地级市-机器人、人工智能等未来产业水平(2009-2023年)-社科数据
地级市-机器人、人工智能等未来产业水平(2009-2023年)-社科数据https://download.csdn.net/download/paofuluolijiang/90623814 https://download.csdn.net/download/paofuluolijiang/90623814 此数据集统计了2009-2023年全国地级市在机器人、人工智能等…...