7.串口通信uart编写思路及自定义协议

news2025/3/5 2:22:07

前言:

        串口是很重要的,有许多模块通信接口就是串口,例如gps模块,蓝牙模块,wifi模块还有一些精度比较高的陀螺仪模块等等,所以学会了串口之后,这些听起来很牛批的模块都能够用起来了。此外,单片机的之间的通信,也大多用串口,如距离比较长的RS485,RS232,光纤通信等等有线通信,也只是电平转换芯片不一样,但是代码层面完成是一样的,作为单片机开发串口是很必要熟练的。在学习的第二阶段,尽量还是照着手册来编写代码,或者说,根据自己的思路来嫖代码,而不是像初次学习一样代码、思路都嫖别人的。这样才能最大限度的检验自己的能力,当然,做项目怎样都成,怎么方便怎么来。

思路:

       下面就来记录记录我个人的编码思路,首先由下图可以看到,串口的模式还是挺多的:

        这样相应的寄存器也就必然很多,所以从一开始就需要明确我们需要的是哪种模式,然后就只关注这个模式,与之无关的寄存器都可以忽略,如此编码就简单清晰了。以最常用的异步模式为例:

        手册没有讲初始化流程,所以只能按照经验来写代码了,回忆串口无非就是:串口时钟使能,配置数据位,停止位等,配置波特率,使能串口,从寄存器读出数据/向寄存器写入数据。一般为了方便数据处理,还加一个接收中断。但是~串口不只是串口,还涉及GPIO初始化,GPIO复用配置。

1.初始化GPIO相关配置

三部曲:时钟,IO,复用啥

        GPIOx->AFR这个寄存器就是将某个GPIO管脚复用成指定功能的。下面AF虽多,但是要根据数据手册引脚说明来选,芯片没有设计的当然选了也没用。我没有在手册找到AF对应的是什么,不过正点原子的代码有写,也不知哪里找的。

//AF0~15设置情况(这里仅是列出常用的,详细的请见407数据手册,56页Table 7):
//AF0:MCO/SWD/SWCLK/RTC   AF1:TIM1/TIM2;            AF2:TIM3~5;               AF3:TIM8~11
//AF4:I2C1~I2C3;          AF5:SPI1/SPI2;            AF6:SPI3;                 AF7:USART1~3;
//AF8:USART4~6;           AF9;CAN1/CAN2/TIM12~14    AF10:USB_OTG/USB_HS       AF11:ETH
//AF12:FSMC/SDIO/OTG/HS   AF13:DCIM                 AF14:                     AF15:EVENTOUT

 编码如下:

1.时钟
RCC->AHB1ENR|=1<<0;   	//GPIOA时钟附属于AHB1
2.IO  
GPIO_Set(GPIOA,PIN9|PIN10,GPIO_MODE_AF,0,0,0);//PA9,PA10,都配置为复用模式,其他电气属性如上下拉之类的可以根据需要配置,不配置也行的。
3.复用啥,直接用正点原子的函数,里面其实就是对GPIOx->AFRH和GPIOx->AFRL这两个寄存器进行编写,不过正点原子这个封装的挺好的,一目了然
GPIO_AF_Set(GPIOA,9,7);	//PA9,AF7
GPIO_AF_Set(GPIOA,10,7);//PA10,AF7  	   

2.串口相关初始化

目的是:串口时钟使能,配置数据位,停止位,接收中断,使能串口等,配置波特率,从寄存器读出数据/向寄存器写入数据。

由手册第66页可知,USART1时钟隶属于APB2

1.使能串口1时钟    

 RCC->APB2ENR|=1<<4;   

2.配置波特率:

根据公式算,然后填到对应的位里面去

	float temp;
	u16 mantissa;    //整数部分
	u16 fraction;	 //小数部分
	temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV@OVER8=0
	mantissa=temp;				 //得到整数部分
	fraction=(temp-mantissa)*16; //得到小数部分@OVER8=0 
    mantissa<<=4;
	mantissa+=fraction; 

 	USART1->BRR=mantissa; 	//波特率设置	 

这个是正点原子那嫖的,适用于多种时钟,多种波特率的情况,挺好用的。

3.配置数据位,停止位,中断等

USART_CR1检索:只看和异步通信有关的位,其他的不管

bit[2]:        接收使能

bit[3]:        发送使能

bit[5]:        接收中断使能

bit[10]:       关/开奇偶检验

bit[12]:       置零:1 起始位,8 数据位,n 停止位

bit[13]:        串口关闭/使能,后面记得给这个串口中断分组以及设置优先级

bit[15]:        0:16倍过采样率,1:8倍过采样率,这个是和波特率计算有关的,设为0

其他就无所谓了,好像这个寄存器就完全够配置我们所需了

USART1->CR1 = 0<<15 | 1<<13 | 0<<12 | 0<<10 | 1<<5 | 1<<3 | 1<<2 ;
//中断分组及优先级,中断后面有时间再讲(其核心思想就是分组,中断线,设优先级三部曲)
MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级 

4.完整的串口初始化代码如下:

void uart_init(u32 pclk2,u32 bound)
{ 
	float temp;
	u16 mantissa;
	u16 fraction;	   
    
    //1.GPIO初始化相关
	RCC->AHB1ENR|=1<<0;   	//使能PORTA口时钟  
	GPIO_Set(GPIOA,PIN9|PIN10,GPIO_MODE_AF,0,0,0);//PA9,PA10,复用功能
 	GPIO_AF_Set(GPIOA,9,7);	//PA9,AF7
	GPIO_AF_Set(GPIOA,10,7);//PA10,AF7  
	   
    //2.使能串口1时钟 
	RCC->APB2ENR|=1<<4;  	

	//3.波特率设置
	temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV@OVER8=0
	mantissa=temp;				 //得到整数部分
	fraction=(temp-mantissa)*16; //得到小数部分@OVER8=0 
    mantissa<<=4;
	mantissa+=fraction; 
 	USART1->BRR=mantissa; 	//波特率设置	 
    
    //4.配置数据位,停止位,中断等
    USART1->CR1 = 0<<15 | 1<<13 | 0<<12 | 0<<10 | 1<<5 | 1<<3 | 1<<2 ;
    //中断分组及优先级,中断后面有时间再讲(其核心思想就是分组,中断线,设优先级三部曲)
    MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级 
}

运行效果:

其他代码不改动,换上自己思路写的代码运行ok:

3.中断服务函数

a.它最原始的模样:
void USART1_IRQHandler(void)
{
	u8 res;	

	if(USART1->SR&(1<<5))//接收到数据的标志置1---->>有数据
	{	 
		res=USART1->DR; //取出接收到的数据---->>1B
								     
	} 

} 

如果要发送数据,可以编写如下:

u8 res;
USART1->DR = res;//要发送的数据  1B
while((USART1->SR&0X40)==0);//等待发送结束
b.正点原子给的:
u8 USART_RX_BUF[USART_REC_LEN];     //里面存着接收到的数据

u16 USART_RX_STA=0;                 //是否有数据标志+接收到的字节数

//下面这个不用改它,原封不动放代码里就能用
void USART1_IRQHandler(void)
{
	u8 res;	
	if(USART1->SR&(1<<5))//接收到数据
	{	 
		res=USART1->DR; 
		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;//接收数据错误,重新开始接收	  
				}		 
			}
		}  		 									     
	} 

} 
									 

其中:

判断有无数据接收:

		if(USART_RX_STA&0x8000){。。。。。。}

得知数据共有多少B:

int len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度

数据存放的数组:

USART_RX_BUF[]

相对来说以及挺方便的了~下面还有一个比赛常用的,自定义的协议

c.自定义协议:
首先在usart.c中加入变量:
/*更改变量 BEGIN-- */
uint8_t uart1_rxbuff;//引入该.h可使用
uint8_t uart2_rxbuff;//引入该.h可使用
uint8_t uart3_rxbuff;//引入该.h可使用

uint8_t sendBuf[1]; 
u8 uart1_sdbuffer[11]={0x2c,0x12,0x11,0x22,0x33,0x5b,0,0,0,0};//从索引2开始赋值
/*更改变量 END-- */
在usart.h中导出方便别的文件使用:
extern uint8_t uart1_rxbuff;
extern uint8_t uart2_rxbuff;
extern uint8_t uart3_rxbuff;

extern uint8_t uart1_sdbuffer[11];
下面是协议解析函数,自定义的协议是:

协议头0x2c,0x12

协议尾0x5b,想要让协议数据位变多,只需要修改变量RxBuffer1[]的定义即可

//解析接收的数据 最多11哥,两个帧头,一个帧尾,其他是数据位
void Portocol_Receive_Data(uint8_t com_data)
{
		uint8_t i;
		static uint8_t RxCounter1=0;//计数
		static uint8_t RxBuffer1[11]={0};
		static uint8_t RxState = 0;	
		static uint8_t RxFlag1 = 0;
        u8 pi=0;
        
        //printf("%x\t",com_data);//打印调试

		if(RxState==0&&com_data==0x2C)  //0x2c帧头 RxCounter1==1
		{
			
			RxState=1;
			RxBuffer1[RxCounter1++]=com_data;  
		}

		else if(RxState==1&&com_data==0x12)  //0x12帧头 RxCounter1==2
		{
			RxState=2;
			RxBuffer1[RxCounter1++]=com_data;
		}
		
		else if(RxState==2)//开始接收数据位
		{                     
			 
			RxBuffer1[RxCounter1++]=com_data;
			if(RxCounter1>=10||com_data == 0x5B)
			{
				//RxCounter1-1是帧尾
				if(RxBuffer1[RxCounter1-1] == 0x5B)//接收到贞结尾了
				{
                  /* USER CODE BEGIN 2 */
//                    for(i = 0; i <= 10; i++)
//                    {
//                        printf("%x\t",RxBuffer1[i]);
//                    }
//                    printf("\r\n");
                    USART1_Portocol_Send_Data();
//                    printf("\r\n");
                  /* USER CODE END 2 */
       
                    RxFlag1 = 0;
                    RxCounter1 = 0;
                    RxState = 0;
						
				}
				else   //接收错误
				{

                    RxState = 0;
                    RxCounter1=0;
                    for(i=0;i<11;i++)
                    {
                            RxBuffer1[i]=0x00;      //将存放数据数组清零
                    }
				}

			}
		}
		else   //接收异常
		{
				RxState = 0;
				RxCounter1=0;
				for(i=0;i<10;i++)
				{
						RxBuffer1[i]=0x00;      //将存放数据数组清零
				}
		}
}
中断服务函数是这样滴:
void USART1_IRQHandler(void)
{
    if(USART1->SR&(1<<5))//接收到数据
	{	
        uart1_rxbuff = USART1->DR;

        Portocol_Receive_Data(uart1_rxbuff);
    }
}
另外还有一个协议配套的发送函数:

要修改发送的内容只需修改uart1_sdbuffer数组的内容即可:

//串口X发送函数
void USART1_Portocol_Send_Data(void)
{
	   u8 i;
    
		for(i = 0; i <= 10; i++)
		{
            
            USART1->DR=uart1_sdbuffer[i];//要发送的数据  1B
            while((USART1->SR&0X40)==0);//等待发送结束

		}
}
效果如下:

在协议代码中,下面这部分就是给你自由发挥的,进到这段代码里说明成功接收到了按协议格式发来的信息;

4.拓展到其他串口:

复用到其他的串口也很简单,仿照把发送呀接收呀里面的寄存器改一改就行了

        比赛常用的还是自定义协议的串口,比如双车用蓝牙通讯呀,或者stm32和openmv通讯,几乎都要自己写一个协议去收发数据,这样才会可靠。

        完~

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

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

相关文章

Qt/QML编程学习之心得:在QML工程中添加库(十四)

实现库并且使用库&#xff0c;类似于vc中的静态库library、动态库dll、COM组件等方法一样&#xff0c;在Qt中也经常会使用库&#xff0c;或者将部分功能打包成库。 右击Qt项目&#xff0c;点击add library... 在linux中将.a文件导入&#xff0c;工程会自动在.pro温江中增加相应…

centos安装Jenkins并拉取git远程仓库的代码进行自动化构建部署

安装Jenkins并拉取git远程仓库的代码进行自动化构建部署 1 前置条件2 先安装jdk113 安装git4 安装maven5 安装jenkins5.1下载jenkins5.2启动jenkins 6 使用jenkins拉取git仓库代码并部署6.1 安装插件6.2 在jenkins中配置maven6.3在jenkins上构建maven项目6.4 配置拉取的git仓库…

成功案例分享:物业管理小程序如何助力打造智慧社区

随着科技的进步和互联网的普及&#xff0c;数字化转型已经渗透到各个行业&#xff0c;包括物业管理。借助小程序这一轻量级应用&#xff0c;物业管理可以实现线上线下服务的无缝对接&#xff0c;提升服务质量&#xff0c;优化用户体验。本文将详细介绍如何通过乔拓云网设计小程…

【vtkWidgetRepresentation】第十六期 vtkContourRepresentation(三)

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享vtkContourLineInterpolator接口的源码剖析和实例应用,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 目录 前言 …

csrf自动化检测调研

https://github.com/pillarjs/understanding-csrf/blob/master/README_zh.md CSRF 攻击者在钓鱼站点&#xff0c;可以通过创建一个AJAX按钮或者表单来针对你的网站创建一个请求&#xff1a; <form action"https://my.site.com/me/something-destructive" metho…

【Java基础】 一个空的Object对象到底占多少内存

对象头包括&#xff08;Markword、类元指针、数组长度&#xff09; 压缩指针ON&#xff1a;占用12字节&#xff0c;Markword占8字节、类元指针占4字节.但是为了避免伪共享问题&#xff0c;JVM会按照8字节的倍数填充&#xff0c;所以会在对其区填充4字节&#xff0c;变成16字节。…

PhysX——源码编译

从git下载源码 git主页 https://github.com/NVIDIA-Omniverse/PhysXclone地址 https://github.com/NVIDIA-Omniverse/PhysX.git源码编译 运行PhysX需要两个编译器的支持&#xff0c;CMake 3.12 或以上版本以及Python 2.7.6 版本 进入工程的 physx 目录&#xff0c;运行generate…

系列一、GitHub搜索技巧

一、GitHub搜索技巧 1.1、概述 作为程序员&#xff0c;GitHub大家应该都再熟悉不过了&#xff0c;很多时候当我们需要使用某一项技能而又无从下手时&#xff0c;通常会在百度&#xff08;面向百度编程&#xff09;或者在GitHub上通过关键字寻找相关案例&#xff0c;比如我想学…

贪心法之活动安排问题

问题: 给定n个活动&#xff0c;每个活动有一个开始时间si和结束时间fi&#xff0c;选择出最大的互不相容的活动 集合。 思路&#xff1a; 将所有活动按照结束时间从小到大排序。 选择第一个活动&#xff0c;并将其加入最终的选择集合中。 依次考虑剩余的活动&#x…

基于Linphone android sdk开发Android软话机

1.Linphone简介 1.1 简介 LinPhone是一个遵循GPL协议的开源网络电话或者IP语音电话&#xff08;VOIP&#xff09;系统&#xff0c;其主要如下。使用linphone&#xff0c;开发者可以在互联网上随意的通信&#xff0c;包括语音、视频、即时文本消息。linphone使用SIP协议&#…

2024年你的年度目标OKR制定好了吗?

标题2023年余额见底&#xff0c;2024年的FLAG都制定好了吗&#xff1f; 目标很明确&#xff0c;计划很丰满&#xff0c;执行起来又处处透着一点点乏力&#xff0c;怎么办&#xff1f; 2024年可以尝试用OKR制定目标。 OKR目标管理方法&#xff0c;既适用于企业&#xff0c;也…

连续色调图像的二维编码

连续色调图像的二维编码&#xff08;也被称为连续色调图像的矢量量化&#xff09;是一种图像压缩和数据储存的方法&#xff0c;适用于连续渐变的色彩信息。该编码方法有助于减小图像文件的体积&#xff0c;并被广泛应用于数字图像处理、存储和传输领域。本文将介绍连续色调图像…

【科学计算语言】实验四 科学计算与可视化

【目的和要求】 &#xff08;1&#xff09;理解科学计算实质并掌握Python语言的科学计算应用 &#xff08;2&#xff09;掌握常用科学计算库 &#xff08;3&#xff09;熟练运用numpy及scipy、matplotlib等计算库资源 【实验准备】 Python核心科学计算库的导入、配置并熟悉相关…

Hive-high Avaliabl

hive—high Avaliable ​ hive的搭建方式有三种&#xff0c;分别是 ​ 1、Local/Embedded Metastore Database (Derby) ​ 2、Remote Metastore Database ​ 3、Remote Metastore Server ​ 一般情况下&#xff0c;我们在学习的时候直接使用hive –service metastore的方式…

基于ChatGLM搭建专业领域问答机器人的思路

如果我们对ChatGLM进一步提出涉及专业领域的问题&#xff0c;而此方面知识是ChatGLM未经数据训练的&#xff0c;那么ChatGLM的回答效果如何呢&#xff1f;本节将考察ChatGLM在专业领域的问答水平&#xff0c;并尝试解决此方面的问题。 在使用ChatGLM制作专业领域问答机器人之前…

【Amazon 实验①】Amazon WAF功能增强之实验环境准备

文章目录 1. 实验介绍2. 实验环境准备 1. 实验介绍 在真实的网络空间中&#xff0c;攻击者会使用大量广泛分布的僵尸网络、肉机等发起对目标的攻击。 其来源分布一般比较分散&#xff0c;因此难以简单防范。 本实验联合使用有多种AWS服务&#xff1a;Cloudfront、 Lambdaedge…

服务器数据恢复-服务器断电导致linux操作系统数据丢失的数据恢复案例

linux操作系统服务器数据恢复环境&#xff1a; 某品牌R730服务器MD3200系列存储&#xff0c;linux操作系统。 服务器故障&#xff1a; 机房意外断电导致服务器linux操作系统部分文件丢失。 服务器数据恢复过程&#xff1a; 1、将故障服务器连接到北亚企安数据恢复中心备份服务器…

【开源工程及源码】超级经典开源项目实景三维数字孪生智慧港口

智慧港口可视化平台&#xff0c;旨在实现对港口运营的全面监测、智能管理和优化决策。飞渡科技利用数字化、模拟和仿真的技术&#xff0c;通过互联的传感器和设备&#xff0c;实现实时数据的采集、传输和分析&#xff0c;将港口内外的复杂数据以直观、易懂的方式呈现&#xff0…

前端学习——vuex的入门

学习一门技术最快捷的方式就是先了解其概念和使用场景&#xff0c;毕竟任何技术的出现都是为了解决某一个场景下的通用解决方案&#xff0c;并且使用最合理的方式去解决问题。 那么什么是vuex&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中…

Python---搭建Python自带静态Web服务器

1. 静态Web服务器是什么&#xff1f; 可以为发出请求的浏览器提供静态文档的程序。 平时我们浏览百度新闻数据的时候&#xff0c;每天的新闻数据都会发生变化&#xff0c;那访问的这个页面就是动态的&#xff0c;而我们开发的是静态的&#xff0c;页面的数据不会发生变化。 …