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

前言:

        串口是很重要的,有许多模块通信接口就是串口,例如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=0mantissa=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,AF7GPIO_AF_Set(GPIOA,10,7);//PA10,AF7  //2.使能串口1时钟 RCC->APB2ENR|=1<<4;  	//3.波特率设置temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV@OVER8=0mantissa=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];//要发送的数据  1Bwhile((USART1->SR&0X40)==0);//等待发送结束}
}
效果如下:

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

4.拓展到其他串口:

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

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

        完~

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

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

相关文章

MySQL 8.0 InnoDB Tablespaces之File-per-table tablespaces(单独表空间)

文章目录 MySQL 8.0 InnoDB Tablespaces之File-per-table tablespaces&#xff08;单独表空间&#xff09;File-per-table tablespaces&#xff08;单独表空间&#xff09;相关变量&#xff1a;innodb_file_per_table使用TABLESPACE子句指定表空间变量innodb_file_per_table设置…

Git系统有哪些优势

在现在的这个软件开发领域&#xff0c;版本控制是一项非常重要的工作。Git作为比较流行的分布式版本控制系统&#xff0c;他有着独特的优势成为了很多开发者们的首选。那Git系统都有哪些优势呢&#xff0c;下面我以自己的理解简单的介绍一下。 分布式版本控制的优势 Git用的是…

标准地址门牌管理系统:提升地址管理效率与准确性的关键

在信息化社会的今天&#xff0c;地址管理的重要性日益凸显。无论是商业活动、物流配送&#xff0c;还是公共安全&#xff0c;都需要精确、高效的地址管理。然而&#xff0c;传统地址管理方式往往存在地址不规范、信息不全等问题&#xff0c;这无疑增加了管理难度和工作量。为此…

linux 中 C++的环境搭建以及测试工具的简单介绍

文章目录 makefleCMakegdb调试 与 coredumpValgrind 内存检测gtest 单元测试 makefile 介绍 安装 : sudo apt install make makefile 的规则: 举例说明 包括&#xff1a;目标文件 、 依赖文件 、 生成规则 使用 &#xff1a; make make clean CMake : CMake是一个…

046.Python包和模块_导入相关

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

『番外篇五』SwiftUI 进阶之如何动态获取任意视图的 tag 和 id 值

概览 在某些场景下,我们需要用代码动态去探查 SwiftUI 视图的信息。比如任意视图的 id 或 tag 值: 如上图所示:我们通过动态探查技术在运行时将 SwiftUI 特定视图的 tag 和 id 值显示在了屏幕上。 这是如何做到的呢? 在本篇博文,您将学到如下内容: 概览1. “如意如意,…

手敲MyBatis(十三章)-返回Insert操作自增索引值

1.目的 这一章的目的主要是插入语句以后返回插入记录的id&#xff0c;因为插入语句可分为要返回记录id&#xff0c;不要返回记录id的以及不同数据源类型执行的时机也不同&#xff08;如&#xff1a;oracle不支持主键&#xff0c;需要先插入序列再增加&#xff0c;Mysql支持主键…

SQL指南:掌握日期函数来查询和管理数据

文章目录 1. 引言2. 建立数据库表2.1 建表语句2.2 数据插入 查询案例3.1 查询当前日期的订单3.2 查询过去一周内的订单3.3 查询明天的日期3.4 查询今年的订单3.5 查询特定月份的订单 总结 1. 引言 在数据库管理中&#xff0c;处理日期和时间是一项基本但重要的任务。本指南将通…

数智金融技术峰会|数新网络受邀分享《金融信创湖仓一体数据平台架构实践》,敬请期待

12月23日&#xff0c;数新网络参加DataFunSummit 2023&#xff1a;数智金融技术峰会。会上&#xff0c;数新CTO原攀峰将为大家带来《金融信创湖仓一体数据平台架构实践》 主题分享。 本次峰会由DataFun联合火山引擎、蓝驰等知名企业举办&#xff0c;将共同为大家带来一场数智金…

玩转Instagram Shop只需要学会这些功能

Instagram Shop作为Instagram下属的电商购物平台。用户可以通过浏览Instagram上的推荐产品和品牌&#xff0c;在无需离开应用的情况下了解并购买新的商品。对于经常使用Instagram的用户来说是个很便捷的购物渠道。面对这个新渠道&#xff0c;我们又该如何玩转它呢。这篇文章就会…

【沐风老师】3dMax篮球建模方法详解

3dMax足球、排球和篮球建模系列之&#xff1a;篮球建模。对于足球和排球建模&#xff0c;思路是从一个基础模型开始&#xff0c;利用这个基础模型与最终的足球&#xff08;或排球&#xff09;模型的某些相似之处&#xff0c;经过修改编辑&#xff0c;最终完成目标模型的建模。但…

ansible的playbook

1、playbook的组成部分 &#xff08;1&#xff09;task任务&#xff1a;在目标主机上执行的操作&#xff0c;使用模块定义这些操作&#xff0c;每个任务都是一个模块的调用 &#xff08;2&#xff09;variables变量&#xff1a;存储和传递数据&#xff08;变量可以自定义&…

Java可变参数(学习推荐版,通俗易懂)

定义 可变参数本质还是一个数组 示例代码 注意事项 1.形参列表中&#xff0c;可变参数只能有一个 2.可变参数必须放在形参列表的最后面 注意是最后面。 name也可以为int类型

【C#】TimeSpan

文章目录 概述属性时间计算拓展来源 概述 TimeSpan结构&#xff1a;表示一个时间间隔。 它含有以下四个构造函数&#xff1a; TimeSpan(Int64)将 TimeSpan结构的新实例初始化为指定的刻度数。&#xff08;DateTime.Tick:是计算机的一个计时周期&#xff0c;单位是一百纳秒&…

3. 行为模式 - 迭代器模式

亦称&#xff1a; Iterator 意图 迭代器模式是一种行为设计模式&#xff0c; 让你能在不暴露集合底层表现形式 &#xff08;列表、 栈和树等&#xff09; 的情况下遍历集合中所有的元素。 问题 集合是编程中最常使用的数据类型之一。 尽管如此&#xff0c; 集合只是一组对象的…

数据结构(八):图介绍及面试常考算法

一、图介绍 1、定义 图由结点的有穷集合V和边的集合E组成。其中&#xff0c;结点也称为顶点。一对结点&#xff08;x&#xff0c; y&#xff09;称为边&#xff08;edge&#xff09;&#xff0c;表示顶点x连接到顶点y。边可以包含权重/成本&#xff0c;显示从顶点x到y所需的成…

深入解析Python装饰器及*args, **kwargs的妙用

深入解析Python装饰器及*args, **kwargs的妙用 简介&#xff1a; ​ 装饰器&#xff08;Decorator&#xff09;是 Python 中一种强大的语法特性&#xff0c;它允许在不修改原始函数代码的情况下&#xff0c;动态地扩展函数的功能。装饰器是函数或类&#xff0c;用于包装其他函…

云上荆楚丨云轴科技ZStack成功实践精选(湖北)

湖北自古以来有九省通衢的美称&#xff0c;地处长江中游&#xff0c;富有荆楚之美誉&#xff0c;灵秀之蕴意。2022年湖北数字经济强省三年行动计划正式印发&#xff0c;计划到“十四五”末&#xff0c;数字经济核心产业增加值力争达到7000亿元&#xff0c;占GDP的比重超过12%。…

《每天一分钟学习C语言·七》指针、字节对齐等

1、 对于二维数组如a[3][4]可以当做有三个元素的一维数组&#xff0c;每个元素包含四个小元素。 2、 printf(“%-5d”, i); //负号表示左对齐&#xff0c;5d表示空五个光标的位置 3、 栈&#xff1a;先进后出&#xff0c;堆&#xff1a;先进先出 4、 &#xff08;1&#xff…

位运算:Leetcode137.只出现一次的数字(2)

题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 示例 1&#xff1a; 输入&#xff1a;nums [2,2,3,2] 输出&#xff1a;3示例 2&#xff1a; 输入&…