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

目录

一、串口简介

二、串口驱动设计

三、串口发送

四、串口接收处理

五、PM2.5数据接收处理

六、printf重定义

七、总结


一、串口简介

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

cpp 复制代码
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;
}

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

cpp 复制代码
/*		
================================================================================
描述 :串口中断函数
输入 : 
输出 : 
================================================================================
*/
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 暴露给外部文件使用。

cpp 复制代码
#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发送的时候,加个延时就能解决了。

cpp 复制代码
/*		
================================================================================
描述 :串口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

相关推荐
BY组态13 小时前
【技术分析】Ricon组态系统的模块化架构设计
物联网·iot·web组态·组态
BY组态14 小时前
【教程】如何使用Ricon组态系统快速构建监控画面
物联网·iot·web组态·组态
BY组态16 小时前
【对比分析】Ricon组态系统 vs 传统组态软件
运维·物联网·web组态·组态
zhaoshuzhaoshu1 天前
BLE(蓝牙低功耗)连接过程详解
物联网·蓝牙·无线
搜佛说1 天前
下一代跨语言原生操作系统商业计划书
物联网·软件工程
BY组态1 天前
Ricon组态系统在实际项目中的应用案例分享
物联网·web组态·组态
Zevalin爱灰灰2 天前
零基础入门学用物联网(ESP8266) 第一部分 基础知识篇(五)
单片机·物联网·嵌入式·esp8266
Web3_Daisy2 天前
Token 分红机制详解:实现逻辑、激励结构与风险分析
大数据·人工智能·物联网·web3·区块链
BY组态2 天前
从零开始:Ricon组态系统快速入门指南
运维·物联网·web组态·组态
次旅行的库2 天前
MQTT学习笔记
数据库·笔记·物联网·学习