HAL库 STM32 串口通信
一、实验条件
将STM32的PA9复用为串口1的TX,PA10复用为串口1的RX。STM32芯片的输出TX和接收RX与CH340的接收RX和发送TX相连(收发交叉且PCB上默认没有相连,所以需要用P3跳线帽进行手动连接),CH340的另一端通过USB口引出与USB线相接。CH340作用:RS232电平标准转USB电平标准)。再使用USB转串口线实现PC与板子的通信。
PC端需要安装CH340虚拟串口驱动,目的是为了有CH340的通信协议。在使用串口调试助手进行通信时注意一下几点。
1.发送英文字符需要用一个字符即8位,发送汉字需要两个字符即16位,如上图,发送汉字“宋”实际是发送“CB(1100 1011)CE(1100 1110)”而发送英文字符S实际是发送“53(0101 0011)”,本质上没有太大区别;
2.勾选了下方“发送新行”后,XCOM就会再你输入的需要发送的数据后自动加上一个回车(0X0D+0X0A),如果不勾选则我们在手动输入完“宋S”后还需敲一个回车键只有这样点击发送后,调试助手上方窗体才能将其显示,这是因为我们在程序的串口中断中自定义了一个数据接收协议,即只有当接受的数据以回车结尾(0X0D+0X0A),串口才认可数据接受完毕。
二、例程软件实现
1、支持printf函数 这个函数大部分无需改动 但是针对不同的串口时:要记得改USART1为对应串口
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{ int handle; }; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{ x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{ while((USART1->SR&0X40)==0);//循环发送,直到发送完毕 USART1->DR = (u8) ch; return ch;
}
#endif
2、初始化
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 aRxBuffer[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound)
{ //UART 初始化设置UART1_Handler.Instance=USART1; //USART1 让Instance指向的寄存器基地址 等于串口1的外设基地址UART1_Handler.Init.BaudRate=bound; //波特率UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为8位数据格式UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()会使能UART1HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量}
void uart_init(u32 bound)函数需要注意的点
1、HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
(1)HAL_StatusTypeDef为一个枚举类型,也就是当返回值比较多,就全枚举到HAL_StatusTypeDef里
(2)huart是UART_HandleTypeDef类型的结构体指针变量,也称为串口句柄,指向结构体里第一个成员基地址。一开始就定义了UART_HandleTypeDef UART1_Handler;
UART1_Handler为UART_HandleTypeDef类型的结构体变量,可用于引用结构体里成员,取&为第一个成员基地址
即:huart = &UART1_Handler
所以调用HAL_UART_Init函数时,括号里应该填&UART1_Handler
(3)第二个形参的位置应该填一个指针,数组名可以也看作指向数组里第零个元素的指针,但是这里把aRxBuffer强制转换为指针类型,个人认为只是一种格式规范,已上板验证过,就算没有强制转换,也能实现串口通信。(为什么是u8?因为uint8_t *pData入口参数规定的)
(4)第三个形参,表示每次接收SIZE个字节后标示接收结束,然后会进入接收回调函数。
整个接收的过程卡了我一天......最终总结为以下:
串口调试助手每发送一个帧,接收移位寄存器把这一帧里有效位给接收数据寄存器(此观点为个人理解),然后接收寄存器(RDR)接收到了数据后,产生RXNE中断,进入中断处理函数,中断处理函数对里判断到是接收中断调用 UART_Receive_IT(huart)函数
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))//判断是否为读数据寄存器非空中断{UART_Receive_IT(huart); //调用接收函数return;}}
在接收函数里把接收到的值放pRxBuffPtr,同时RxXferCount--,直到RxXferCount减到零,调用接收回调函数。(当RxXferCount=0时表明这次已经传完,进行中断失能的处理)
if (--huart->RxXferCount == 0U){/* Disable the UART Data Register not empty Interrupt */ __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE); /* Disable the UART Parity Error Interrupt */__HAL_UART_DISABLE_IT(huart, UART_IT_PE);/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);/* Rx process is completed, restore huart->RxState to Ready */huart->RxState = HAL_UART_STATE_READY;#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)/*Call registered Rx complete callback*/huart->RxCpltCallback(huart);
#else/*Call legacy weak Rx complete callback*/HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */return HAL_OK;}
问题:RxXferCount、pRxBuffPtr、RxXferSize(这三个都是UART_HandleTypeDef结构体里的成员)在哪里被赋值?
在HAL_UART_Receive_IT()里,把形参给pRxBuffPtr、RxXferSize。(RxXferCount初始值为RxXferSize)。
我们可以看到例程里RXBUFFERSIZE=1,aRxBuffer[]容量也为1,所以RxXferCount=1,每收到1个字节就去一次接收回调函数,那难道说这个例程只能适用于接收1个字节的时候吗?
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);
答案是否定的,这里只是一个一个的接收,在回调函数里还要及时把接收的这个字节放在数组USART_RX_BUF[USART_REC_LEN]里,USART_REC_LEN为200。
但是这样的话,我没法知道接收完成没有,所以在串口回调函数里设计了小小的通信协议。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart->Instance==USART1)//如果是串口1{if((USART_RX_STA&0x8000)==0)//USART_RX_STA就是个自己定义的变量 16位{if(USART_RX_STA&0x4000)//接收到了0x0d{if(aRxBuffer[0]!=0x0a)USART_RX_STA=0;//接收错误,重新开始 字节else USART_RX_STA|=0x8000; //接收完成了 }else //还没收到0X0D{ if(aRxBuffer[0]==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } } //接收到的内容就放在USART_RX_BUF里 主函数里再决定怎么处理}}
}
当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2个字节组成: 0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待 0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到 0X0D,那么在接收数据超过 USART_REC_LEN 的时候,则会丢弃前面的数据,重新接收。
注意:USART_RX_STA[13:0]位用来装已接收的字节个数,可以有16384个,但是USART_RX_BUF[USART_REC_LEN]里,USART_REC_LEN为200,所以最多接收200个字节。
三、常用的串口中断处理函数
我们直接把中断控制逻辑写在中断服务函数内部,不适用中断回调函数
此时不用初始化HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);
直接在要开启中断的地方使用__HAL_UART_ENABLE_IT()单独开启中断即可。
调用了HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)对接收到的字符进行处理。
//串口1中断服务程序
void USART1_IRQHandler(void)
{ u8 Res;HAL_StatusTypeDef err;
#if SYSTEM_SUPPORT_OS //使用OSOSIntEnter();
#endifif((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET)) //接收中断(接收到的数据必须是0x0d 0x0a结尾){ //UART_FLAG_RXNE产生了 说明接收函数处理了数据HAL_UART_Receive(&USART1_Handler,&Res,1,1000);if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000; //接收完成了 }else //还没收到0X0D{ if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } }} }HAL_UART_IRQHandler(&UART1_Handler); //为什么还需要这句 不明确?
#if SYSTEM_SUPPORT_OS //使用OSOSIntExit();
#endif
}
#endif
UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout)
四、主函数
while(1){if(USART_RX_STA&0x8000){ len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度printf("\r\n您发送的消息为:\r\n");HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET); //等待发送结束printf("\r\n\r\n");//插入换行USART_RX_STA=0;}else{times++;if(times%5000==0){printf("\r\nALIENTEK 精英STM32开发板 串口实验\r\n");printf("正点原子@ALIENTEK\r\n\r\n\r\n");}if(times%200==0)printf("请输入数据,以回车键结束\r\n"); if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.delay_ms(10); } }
}
HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000);
1、第二个形参 为数组名USART_RX_BUF强制转换为的指针,指向数组第0个元素,指针实现自加的语法在这个发送函数里
if (huart->Init.Parity == UART_PARITY_NONE){pData += 2U;}else{pData += 1U;}
2、这个是超时,在设置的这个时间内没有发送完成,就返回超时(HAL_TIMEOUT)
如何取:
如果波特率为9600,发送一个位需要的时间为1/9600s=0.0001042s=0.1042ms,这里按数据位为8位,停止位为2位,
加起来就是10位,10个位发送所需的时间为:0.1042*10ms = 1.042ms,如果我要发送10个字节的数据,那发送这10个字节数据给接收方需要
的时间为:10*1.042ms = 10.42ms,这是算实际的发送10个字节的数据所需要的时间。我们在接收方接收数据时可以
把时间再加宽一些,让它有一点余量。让接收方能稳定的把数据从发送方接手过来,可以加个5ms,或更宽一点10ms,
加上发送10个字节所花的时间,就是15ms或20ms。
3、len 是取的USART_RX_STA的【13:0】位,总担心是个十六进制,不要担心,所有进制都会被化为二进制。
4、while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET);
这里的传输完成指的是USART_RX_BUF[len]传输完成,而不是发送一个字节寄存器产生那个传输完成。
相关文章:

HAL库 STM32 串口通信
一、实验条件将STM32的PA9复用为串口1的TX,PA10复用为串口1的RX。STM32芯片的输出TX和接收RX与CH340的接收RX和发送TX相连(收发交叉且PCB上默认没有相连,所以需要用P3跳线帽进行手动连接),CH340的另一端通过USB口引出与…...

2023-第十四届蓝桥杯冲刺计划!
💬前言 💡本文以目录形式列举大纲,可根据题目点击跳转 🌈冲刺阶段目的:把握高频重点,结合基础算法和常考题型总结,用真题进行模拟练习 根据自己的能力熟练目前已掌握的算法,不会的还可以暴力 ⏳最后三个星期大家一起冲…...

内网渗透基础知识
一、内网概述 内网也指局域网,是指在某一区域内又多台计算机互联成的计算机组。一般是方圆几千米内,局域网可以实现文件管理,应用软件共享,打印机共享,工作组内的历程安排,电子邮件和传真通信服务等功能。…...

鸟哥的Linux私房菜 正则表示法与文件格式化处理
第十一章、正则表示法与文件格式化处理 https://linux.vbird.org/linux_basic/centos7/0330regularex.php 简体版 http://cn.linux.vbird.org/linux_basic/0330regularex.php 11.2.2 grep的一些高级选项 例题一、搜索特定字符串 例题二、利用中括号 [] 来搜寻集合字符 例题四…...
1630.等差子数组
1630. 等差子数组 难度中等 如果一个数列由至少两个元素组成,且每两个连续元素之间的差值都相同,那么这个序列就是 等差数列 。更正式地,数列 s 是等差数列,只需要满足:对于每个有效的 i , s[i1] - s[i] …...

CSS 属性计算过程
CSS 属性计算过程 你是否了解 CSS 的属性计算过程呢? 有的同学可能会讲,CSS属性我倒是知道,例如: p{color : red; }上面的 CSS 代码中,p 是元素选择器,color 就是其中的一个 CSS 属性。 但是要说 CSS 属…...

ThinkPHP02:路由
ThinkPHP02:路由一、路由定义二、变量规则三、路由地址四、路由参数五、路由分组六、MISS七、资源路由八、注解路由九、URL生成一、路由定义 路由默认开启,在 config/app.php 中可以关闭路由。 路由配置在 config/route.php 中,路由定义在 r…...

制作简单进销存管理系统(C#)
实验三:制作简单进销存管理系统 任务要求: 在进销存管理系统中,商品的库存信息有很多种类,比如商品型号、商品名称、商品库存量等。在面向对象编程中,这些商品的信息可以存储到属性中,然后当需要使用这些…...

css总结9(过渡和2D变换)
目录 过渡 2D变换 3D变换 过渡 属性结构图 过渡补充 ### 过渡多个元素样式属性 transition:style1 duration , style2 duration,...; ### 过渡所有属性 transition: all duration; 简单示例 ### 移入时改变长度且加入过渡效果 div { width:100px; height:100px; …...

SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发 1 项目准备 SpringBoot 整合 RabbitMQ 消息队…...

【python进阶】序列切片还能这么用?切片的强大比你了解的多太多
📚引言 🙋♂️作者简介:生鱼同学,大数据科学与技术专业硕士在读👨🎓,曾获得华为杯数学建模国家二等奖🏆,MathorCup 数学建模竞赛国家二等奖🏅,…...

[数据结构]直接插入排序、希尔排序
文章目录排序的概念和运用排序的概念排序运用常见的排序算法常见的排序算法直接插入排序希尔排序性能对比排序的概念和运用 排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操…...

CNN、LeNet、AlexNet、VGG、GoogLeNet、RCNN、Fast RCNN、Faster RCNN、YOLO、YOLOv2、SSD等的关系
卷积神经网络的现状1943年美国数学家提出人工智能1949年心理学家建立神经元模型1957年弗兰克提出 感知器人工神经网络模型1980年建立多层感知器模型1984日本学者提出卷积神经网络原始模型神经感知机1998年提出LeNet-5卷积神经网络,并发展了其在音符和字符上的优势20…...

IO-day1-(fscanf、fprintf.........)
作业一、有一个usr.txt的文件,其中存储着用户的账户和密码,格式如下:zhangsan aaaalisi bbbbb空格前面是账户,空格后面是密码,一行一个账户、密码要求如下:从终端获取一个账户名和密码判断是否能够登录成功…...

C++类和对象(上篇)
目录 1.类的定义 2.类的访问限定符及封装 2.1类的访问限定符 2.2封装 3.类的作用域 4.类的实例化 5.类的大小 6.this 指针 1.类的定义 class className {// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号 class为定义类的关键字,Clas…...

解决Xshell无法连接Kali Linux 2020.1(2019.3)版本
使用Xshell远程终端工具连接虚拟机的Kali Linux却提示连接不上原因:Kali Linux默认没有打开SSH远程登录,SSH就是一种网络协议,用于加密的远程登录,所以在没有打开SSH协议之前是无法使用Xshell连接Kali Linux的。解决办法ÿ…...

项目文章 | 缓解高胆固醇血症 ,浒苔多糖如何相助?
文章标题:Polysaccharides from Enteromorpha prolifera alleviate hypercholesterolemia via modulating the gut microbiota and bile acid metabolism 发表期刊:Food & Function 影响因子:6.317 作者单位:福建医科大…...

Linux使用宝塔面板搭建网站,并内网穿透实现公网访问
文章目录前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4.固定http地址5. 配置二级子域名6.创建一个测试页面前言 宝塔面板作为简单好用的服务器运维管理面板,它支持Linux/Windows系统,我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…...

基于深度学习方法与张量方法的图像去噪相关研究
目录 1 研究现状 1.1 基于张量分解的高光谱图像去噪 1.2 基于深度学习的图像去噪算法 1.3 基于深度学习的高光谱去噪 1.4 小结 2 基于深度学习的图像去噪算法 2.1 深度神经网络基本知识 2.2 基于深度学习的图像去噪网络 2.3 稀疏编码 2.3.1 传统稀疏编码 2.3.2 群稀…...

Java基础知识之HashMap的使用
一、HashMap介绍 HashMap是Map接口的一个实现类(HashMap实现了Map的接口),它具有Map的特点。HashMap的底层是哈希表结构。 Map是用于保存具有映射关系的数据集合,它具有双列存储的特点,即一次必须添加两个元素…...

面试--每日一经
操作系统 死锁 死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 死锁的四个必要条件 互斥条件:一个资源每次只能被一个进…...

JavaSE进阶之(十六)枚举
十六、枚举16.1 背景16.2 枚举类型16.3 EnumSet 和 EnumMap01、EnumSet02、EnumMap16.1 背景 在 Java 语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组 int 类型的常量,常常用的就是: public static final int SPRING 1; …...

全同态加密:TFHE
参考文献: Cheon J H, Stehl D. Fully homomophic encryption over the integers revisited[C]//Advances in Cryptology–EUROCRYPT 2015: 34th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Sofia, Bulgaria, …...

【计算机二级】综合题目
计算机二级python真题 文章目录计算机二级python真题一、《大学慕课 两问 》二、综合应用题——价值链三、基本操作题 ——信息输出一、《大学慕课 两问 》 附件中的文件data.txt 是教育部爱课程网中国大学MOOC平台的某个 HTML页面源文件,里面包含了我国参与MOOC建设的一批大学…...

初识Kafka
介绍 Kafka Kafka 是一款基于发布与订阅的消息系统。 用生产者客户端 API 向 Kafka 生产消息,用消费者客户端 API 从 Kafka 读取这些消息。 Kafka 使用 Zookeeper 保存元数据信息。 Kafka 0.9 版本之前,除了 broker 之外, 消费者也会使用…...

【JavaEE】线程的状态
哈喽,大家好~我是保护小周ღ,本期为大家带来的是 Java 多线程的 线程的状态,New 新建状态,Runnable 运行状态,Blocked 阻塞状态,waiting 等待状态,Time_Waiting 超时等待状态,Termin…...

7个最受瞩目的 Python 库,提升你的开发效率
当今时代,数据分析和处理已经成为了各行各业中不可或缺的一环。Python作为一种非常流行的编程语言,为我们提供了许多强大的工具和库来处理不同类型的数据。 在这篇文章中,我将向您介绍七个非常有用的Python库,这些库各自有着独特…...

这些IT行业趋势,将改变2023
上一周,你被"AI"刷屏了吗? 打开任何一家科技媒体,人工智能都是不变的热门话题。周初大家还在用ChatGPT写论文、查资料、写代码,到周末的时候大家已经开始用GPT-4图像识别来做饭、Microsoft 365 Copilot 来写PPT了。 GP…...

蓝桥杯每日一真题——[蓝桥杯 2021 省 B] 杨辉三角形(二分+规律)
文章目录[蓝桥杯 2021 省 B] 杨辉三角形题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路:全部代码:[蓝桥杯 2021 省 B] 杨辉三角形 题目描述 下面的图形是著名的杨辉三角形: 如果我们按从上到下、从左到右的顺序把所有数排成一列&…...

<C++> 类和对象(下)
1.const成员函数将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。class A { public:void Print() //这里隐藏了A* this指针{cout <…...