物联网实战--入门篇之(四)嵌入式-UART驱动

news2025/1/13 8:07:29

        

目录

一、串口简介

二、串口驱动设计

三、串口发送

四、串口接收处理

五、PM2.5数据接收处理

六、printf重定义

七、总结


一、串口简介

        串口在单片机的开发中属于非常常用的外设,最基本的都会预留一个调试串口用来输出调试信息,串口时序这里就不谈了,主要来看看STM32中串口是怎么运行的。

void UART1_Init(void) 
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;// GPIO_Mode_AF_OD
	GPIO_Init(GPIOA, &GPIO_InitStructure);    //TX

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &GPIO_InitStructure);    //RX	
	

	USART_InitStructure.USART_BaudRate = BAUD_UART1;//波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//奇偶校验
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收和发模式
  USART_Init(USART1, &USART_InitStructure); //初始化串口
  
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //使能串口中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //主优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
	NVIC_Init(&NVIC_InitStructure); //初始化NVIC
 
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//接收中断
	USART_Cmd(USART1, ENABLE);                    //使能串口	
	
	g_sUART1.USARTx=USART1;
	g_sUART1.PortNum=1;
	g_sUART1.pBuff=g_u8UART1_Buff;//把数据指针指向接收缓冲区
	g_sUART1.total_len=UART1_LEN;
}

        先从初始化开始,基本上就是打开对应的时钟,初始化引脚,配置串口参数,配置接收中断,最后使能串口。

/*		
================================================================================
描述 :串口中断函数
输入 : 
输出 : 
================================================================================
*/
void USART1_IRQHandler(void)
{
  if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//获取接收状态
	{
		g_sUART1.pBuff[g_sUART1.iRecv]=USART_ReceiveData(USART1);//缓存接收字节		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清理中断位
		USART_ClearFlag(USART1, USART_FLAG_RXNE);//清理标志位
		g_sUART1.iRecv++;//接收长度加1
		if(g_sUART1.iRecv>=UART1_LEN)
			g_sUART1.iRecv=0;
	}
	else if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)//接收错误
	{
		USART_ReceiveData(USART1);
//		USART_ClearITPendingBit(USART1, USART_IT_ORE);
		USART_ClearFlag(USART1, USART_FLAG_ORE);
	}	
}

        串口是接收一个字节产生一个中断,不要再中断函数里面有过多的操作,就是连续缓存数据就好,其他的操作在外部程序中去执行;另外,如果串口数据接收错误或者溢出经常会发生ORE错误,导致一直进中断,程序直接卡死,这里就要处理下这个错误,避免卡死。

二、串口驱动设计

        我这里要重点说明的是设计思想,根据下面的头文件可知,首先定义一个结构体,用来存放串口的配置信息;结合附图,在初始化阶段把串口地址、串口号、缓冲区指针和长度都赋值了;最后将定义的三个串口结构体用extern 暴露给外部文件使用。

#ifndef __DRV_UART_H__
#define __DRV_UART_H__


#include "drv_common.h"

typedef struct
{
	USART_TypeDef* USARTx;
	u8 PortNum;//串口号
	u8 *pBuff;//数据指针
	u16 iRecv;//已接收长度
	u16 iDone;//已处理长度
	u16 total_len;//总长度
}UART_Struct;


extern UART_Struct g_sUART1;
extern UART_Struct g_sUART2;
extern UART_Struct g_sUART3;

void UART_Init(void);
void UART1_Init(void); 
void UART2_Init(void); 
void UART3_Init(void);

void UART_Send(u8 PortNum, u8 *buf, u16 len);
void UART_Clear(UART_Struct *pUART);

void UART1_Send(u8 *buf, u16 len);
void UART2_Send(u8 *buf, u16 len);
void UART3_Send(u8 *buf, u16 len);

#endif

三、串口发送

        串口发送较为简单,注意点就是while状态检测,一个字节发送完成才能发送下一个字节;还有就是最后的延时2ms,这个主要是在实际测试中发现有时候最后一个字节发送不完整,特别是RS485发送的时候,加个延时就能解决了。

/*		
================================================================================
描述 :串口1发送
输入 : 
输出 : 
================================================================================
*/
void UART1_Send(u8 *buf, u16 len)
{
	u16 i;
 for(i=0;i<len;i++)
 {
	 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);	//发送完成
	 USART_SendData(USART1,buf[i]);
 }
 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);	//发送完成		
 delay_ms(2); 
}
四、串口接收处理

        串口在接收中断函数里不断缓存数据,在应用层只需要引用相应数据指针即可,这里以调试串口指令处理为例,具体代码如下。

        首先引用串口1指针,然后在while循环里不断检测是否有收到数据,如果iRecv>0,那么说明有数据,但是此时不一定已经接收完成了,所以定义了recv_len缓存当前的数据长度,经过5ms延时后,如果recv_len和iRecv相等,那么就说明接收完整了,可以做进一步的数据处理。

        接下来是数据处理,根据个人应用具体修改,这里我是复位、取消订阅和PWM设置指令,在开发过程中做调试使用,项目完成后可删除;具体就是使用strstr()函数去检索是否包含指定字符串,然后根据指令去执行对应的动作。

        最后就是要用UART_Clear(pUART);清理下缓冲区数据。

五、PM2.5数据接收处理

        以下是PM2.5传感器的说明书,波特率是9600,传感器每隔1秒主动输出数据,其数据流的第一字节固定是0xA5,中间两字节是浓度值,最后一个字节是校验码。先思考下这种数据流应该如何解析?

        因为我们对PM2.5的实时性要求不会很高,三四秒更新一次即可,也就是三四秒处理一次串口的缓冲数据即可,代码如下所示。判断下接收长度是否大于等于4个字节,是的话进入校验环节,校验通过后再根据说明书组合浓度值,这里要注意的是,原始浓度值只是粉尘浓度,并不是PM2.5,根据XM净化器的数值大概标定下,乘以系数0.04;最后就是清理下缓冲区即可。

        主程序或任务线程调用时就四秒调用一次,如下图所示,就是在每次上报时更新下数据即可。

六、printf重定义

        printf是一个很有用的格式化输出函数,在PC端编写C语言的时候经常会用到,它会将数据打印到控制台上。那么,在单片机上,我们要如何把它的内容输出到指定的串口呢?这里就涉及到了重定义函数了,具体如下所示:

        其中UART_DEBUG在user_opt.h中定义,这样可以决定是否需要输出、用哪个串口输出。

七、总结

        至此,串口驱动也就差不多了,串口作为常用外设,几乎在每个项目中都会用到,这里通过驱动函数的形式保证了代码的重复利用的能力,再利用宏定义的方式对具体参数进行自定义配置,从而保证了通用型和灵活性。

本项目的交流QQ群:701889554

   写于2024-3-30

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1565633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

视频监控/云存储/磁盘阵列/AI智能分析平台EasyCVR集成时调用接口报跨域错误是什么原因?

EasyCVR视频融合平台基于云边端架构&#xff0c;可支持海量视频汇聚管理&#xff0c;能提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、智能分析等视频服务。平台兼容性强&#xff0c;支持多协议、多类型设备接入&#xff0c;包括&#xff1a;国标G…

每日一题(leetcode2952):添加硬币最小数量 初识贪心算法

这道题如果整体去思考&#xff0c;情况会比较复杂。因此我们考虑使用贪心算法。 1 我们可以假定一个X&#xff0c;认为[1,X-1]区间的金额都可以取到&#xff0c;不断去扩张X直到大于target。&#xff08;这里为什么要用[1,X-1]而不是[1,X],总的来说是方便&#xff0c;潜在思想…

uniapp开发app使用谷歌地图(ios跟安卓)

前提条件&#xff1a; 谷歌地图需要翻墙&#xff0c;否则无法加载 谷歌地图说明 文档地址&#xff1a;概览 | Maps JavaScript API | Google for Developers 设置地图语言 <script asyncsrc"https://maps.googleapis.com/maps/api/js?keyYOUR_API_KEY&lang…

MySQL安装卸载-Linux

目录 1.概述 2.安装 2.1.上传 2.2.解压 ​​​​​​​2.3.安装 ​​​​​​​2.4.启动服务 ​​​​​​​2.5.查询临时密码 ​​​​​​​2.6.修改临时密码 ​​​​​​​2.7.创建用户 ​​​​​​​2.8.分配权限 ​​​​​​​2.9.重新链接 3.卸载 3.1.停…

YOLOV5 改进:更换主干网络为Resnet

1、前言 之前实现了yolov5更换主干网络为MobileNet和vgg网络 本章将继续将yolov5代码进行更改,通过引用官方实现的resnet网络,替换原有的yolov5主干网络 替换的效果如下: 2、resnet 网络结构 测试的代码为官方的resnet34 通过summary 打印的resnet网络结构如下 =======…

在哪申请免费IP地址证书

IP证书&#xff0c;也被称为IP SSL证书&#xff0c;是一种特殊的SSL证书&#xff0c;不同于传统的域名验证&#xff08;DV&#xff09;证书&#xff0c;它是通过验证公网IP地址而不是域名来确保安全连接。这种证书是用于保护IP地址&#xff0c;并在安装后起到加密作用。 申请条…

振弦式应变计:简单操作,方便实用的应变监测工具

在现代工程领域中&#xff0c;对于结构物的应变监测是一项至关重要的任务。振弦式应变计作为一种高精度、高稳定性的应变监测工具&#xff0c;因其简单操作、方便实用的特点&#xff0c;受到了广大工程师和技术人员的青睐。 振弦式应变计的工作原理基于振弦的振动特性。它通过将…

学代码是理解就行,还是全部背?

在我没接触编程以前&#xff0c;看到程序&#xff0c;觉得这玩意到底怎么写出来的&#xff0c;写出这些代码的人&#xff0c;也太厉害了吧&#xff1f; 不会很多都要背下来吧&#xff1f; 我小学背课本都费劲&#xff0c;背不出来&#xff0c;中午不准回家吃饭&#xff0c;我就…

人类研究人员通过反复提问来削弱人工智能伦理

你如何让人工智能回答一个它不应该回答的问题&#xff1f;有很多这样的“越狱”技术&#xff0c;Anthropic的研究人员刚刚发现了一种新的技术&#xff0c;在这种技术中&#xff0c;如果你先用几十个危害较小的问题来启动它&#xff0c;就可以说服一个大型语言模型&#xff08;L…

RuntimeError: Error compiling objects for extension虚拟环境和系统环境——添加、删除、修改环境变量

前言&#xff1a;因为一个报错RuntimeError: Error compiling objects for extension 没有配置cl.exe环境变量&#xff0c;我的应用场景是需要搞定虚拟环境变量配置 RuntimeError: Error compiling objects for extension手把手带你解决&#xff08;超详细&#xff09;-CSDN博…

Redis数据库——性能管理

目录 一、Redis性能管理 1.Info Memory——查看Redis内存使用 2.内存碎片率 3.内存使用率 4.内存回收key 二、Redis缓存雪崩、穿透、击穿、预热 1.缓存雪崩 1.1什么是缓存雪崩 1.2产生原因 1.3实际应用场景 1.4解决方案 1.4.1方案一设置redis的某些key永不过期 1.…

基于Zabbix 5.0 实现windows服务器上应用程序和主机端口的状态监控

基于Zabbix 5.0 实现windows服务器上应用程序和主机端口的状态监控 背景 用python开发的应用程序在服务器上运行,有时候会出现程序自动退出却收不到告警的情况 环境 zabbix服务器:Centos7 64位 Windows服务器: Windows 10 64位 软件 zabbix_server:zabbix5.0 zabbix_…

02 - 全加器和加法器

---- 整理自B站UP主 踌躇月光 的视频 1. 全加器 用门电路实现两个二进制数相加并求出和的组合线路&#xff0c;称为一位全加器。一位全加器可以处理低位进位&#xff0c;并输出本位加法进位。全加器比半加器多了一位进位。 1.1 实验 1&#xff1a;通过两个半加器设计全加器 1.…

10.图像高斯滤波的原理与FPGA实现思路

1.概念 高斯分布 图像滤波之高斯滤波介绍 图像处理算法|高斯滤波   高斯滤波(Gaussian filter)包含很多种&#xff0c;包括低通、高通、带通等&#xff0c;在图像上说的高斯滤波通常是指的高斯模糊(Gaussian Blur)&#xff0c;是一种高斯低通滤波。通常这个算法也可以用来模…

基本电路理论-电流和电压的参考方向

&#x1f308;个人主页&#xff1a;会编程的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” 电流及参考方向 电流&#xff1a;带电粒子有规则的定向移动 电流强度&#xff1a;单位时间内通过导体横截面的电荷量&#xff0c;即&#xff1a;idq/dt 单位&#xff1a…

解决Toad for Oracle显示乱中文码问题

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

leet hot 100-13 最大子数组和

53. 最大子数组和 原题链接思路代码 原题链接 leet hot 100-10 53. 最大子数组和 思路 生成一个数字来记录last 表示前面数字全部之和与0取最大值 如果大于0 就加上如果不大于0 就不管 从当前位置从新开始遍历计算 时间复杂度O(n) 空间复杂度(1) 代码 class Solution {…

补充:一起来从Solidworks中导出机械臂的URDF模型

关于上一篇博客&#xff1a;一起来从Solidworks中导出URDF模型-CSDN博客 我们一起完成了小车的URDF模型的导出与Rviz界面中的可视化&#xff0c;下面一起来继续从Solidworks中导出关于机械臂的URDF模型 3. 如何导出机械臂URDF模型 与之前的小车结构不同&#xff0c;机械臂的…

【Hello,PyQt】PyQt5中的一些对话框

QDialog类是一种特殊的窗口&#xff0c;它被设计出来作为和用户进行交换的对话框。QDialog上是可以包含其他的控件的&#xff0c;比如QLineEdit&#xff0c;QPushButton等。QDialog类的子类主要有QMessageBox&#xff0c;QFileDialog&#xff0c;QColorDialog&#xff0c;QFont…

如何制作一个微信小程序商城?

在这个数字化飞速发展的时代&#xff0c;微信小程序商城以其独特的便捷性和高效的用户连接能力&#xff0c;成为了电商领域的一颗新星。对于那些渴望在微信平台上开展业务的商家和企业来说&#xff0c;微信小程序商城不仅是一种新的尝试&#xff0c;更是一个充满无限可能的商机…