单片机学习笔记————51单片机实现常用的自定义串口通讯协议

2023-05-16

proteus虚拟串口的实现:https://mp.csdn.net/console/editor/html/107251649

一、使用proteus绘制简单的电路图,用于后续仿真

 

二、编写程序

/********************************************************************************************************************
----	@Project:	USART
----	@File:	main.c
----	@Edit:	ZHQ
----	@Version:	V1.0
----	@CreationTime:	20200711
----	@ModifiedTime:	20200711
----	@Description:	
----	波特率是:9600 。
----	通讯协议:EB 00 55  GG HH HH XX XX …YY YY CY
----	其中第1,2,3位EB 00 55就是数据头
----	其中第4位GG就是数据类型。01代表驱动奉命,02代表驱动Led灯。
----	其中第5,6位HH就是有效数据长度。高位在左,低位在右。
----	其中第5,6位HH就是有效数据长度。高位在左,低位在右。
----	其中从第7位开始,到最后一个字节Cy之前,XX..YY都是具体的有效数据。
----	在本程序中,当数据类型是01时,有效数据代表蜂鸣器鸣叫的时间长度。当数据类型是02时,有效数据代表Led灯点亮的时间长度。
----	最后一个字节CY是累加和,前面所有字节的累加。
----	发送以下测试数据,将会分别控制蜂鸣器和Led灯的驱动时间长度。
----	蜂鸣器短叫发送:eb 00 55 01 00 02 00 28 6b  
----	蜂鸣器长叫发送:eb 00 55 01 00 02 00 fa 3d  
----	Led灯短亮发送:eb 00 55 02 00 02 00 28 6c
----	Led灯长亮发送:eb 00 55 02 00 02 00 fa 3e 
----	单片机:AT89C52
********************************************************************************************************************/
#include "reg52.h"
/*——————宏定义——————*/
#define FOSC 11059200L
#define BAUD 9600
#define T1MS (65536-FOSC/12/500)   /*0.5ms timer calculation method in 12Tmode*/

#define const_rc_size 20	/*接收串口中断数据的缓冲区数组大小*/

#define const_receive_time 5	/*如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小*/

/*——————变量函数定义及声明——————*/
/*蜂鸣器的驱动IO口*/
sbit BEEP = P2^7;
/*LED*/
sbit LED = P3^5;

unsigned int uiSendCnt = 0;	/*用来识别串口是否接收完一串数据的计时器*/
unsigned char ucSendLock = 1;	/*串口服务程序的自锁变量,每次接收完一串数据只处理一次*/
unsigned int uiRcregTotal = 0;	/*代表当前缓冲区已经接收了多少个数据*/
unsigned char ucRcregBuf[const_rc_size];	/*接收串口中断数据的缓冲区数组*/
unsigned int uiRcMoveIndex = 0;	/*用来解析数据协议的中间变量*/

/*为串口计时器多增加一个原子锁,作为中断与主函数共享数据的保护*/
unsigned char ucSendCntLock = 0;	/*串口计时器的原子锁*/
unsigned char ucVoiceLock = 0;	/*蜂鸣器鸣叫的原子锁*/
unsigned char ucLedLock = 0;	/*Led灯点亮时间的原子锁*/

unsigned char ucRcType = 0;	/*数据类型*/
unsigned int uiRcSize = 0;	/*数据长度*/
unsigned char ucRcCy = 0;	/*校验累加和*/

unsigned int uiVoiceCnt = 0;	/*蜂鸣器鸣叫的持续时间计数器*/
unsigned int uiRcVoiceTime = 0;	/*蜂鸣器发出声音的持续时间*/

unsigned int uiRcLedTime = 0;	/*在串口服务程序中,Led灯点亮时间长度的中间变量*/
unsigned int uiLedTime = 0;	/*Led灯点亮时间的长度*/
unsigned int uiLedCnt = 0;	/*Led灯点亮的计时器*/

/**
* @brief  定时器0初始化函数
* @param  无
* @retval 初始化T0
**/
void Init_T0(void)
{
	TMOD = 0x01;                    /*set timer0 as mode1 (16-bit)*/
	TL0 = T1MS;                     /*initial timer0 low byte*/
	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
}

/**
* @brief  串口初始化函数
* @param  无
* @retval 初始化T0
**/
void Init_USART(void)
{
	SCON = 0x50;
	TMOD = 0x21;                    
	TH1=TL1=-(FOSC/12/32/BAUD);
}

/**
* @brief  外围初始化函数
* @param  无
* @retval 初始化外围
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。
**/
void Init_Peripheral(void)
{
	ET0 = 1;/*允许定时中断*/
	TR0 = 1;/*启动定时中断*/
	TR1 = 1;
	ES = 1;	/*允许串口中断*/
	EA = 1;/*开总中断*/  
}

/**
* @brief  初始化函数
* @param  无
* @retval 初始化单片机
**/
void Init(void)
{
	LED  = 0;
	BEEP = 1;
	Init_T0();
	Init_USART();
}
/**
* @brief  延时函数
* @param  无
* @retval 无
**/
void Delay_Long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  /*内嵌循环的空指令数量*/
          {
             ; /*一个分号相当于执行一条空语句*/
          }
   }
}
///**
//* @brief  延时函数
//* @param  无
//* @retval 无
//**/
//void Delay_Short(unsigned int uiDelayShort)
//{
//   unsigned int i;
//   for(i=0;i<uiDelayShort;i++)
//   {
//		 ; /*一个分号相当于执行一条空语句*/
//   }
//}
/**

* @brief  Led灯的服务程序
* @param  无
* @retval 无
**/
void led_service(void)
{
	if(uiLedCnt < uiLedTime)
	{
		LED = 1;	/*LED亮*/
	}
	else
	{
		LED = 0;
	}
	
}

/**
* @brief  串口服务程序
* @param  无
* @retval 在main函数里
* 识别一串数据是否已经全部接收完了的原理:
* 在规定的时间里,如果没有接收到任何一个字节数据,那么就认为一串数据被接收完了,然后就进入数据协议
* 解析和处理的阶段。这个功能的实现要配合定时中断,串口中断的程序一起阅读,要理解他们之间的关系。
**/
void usart_service(void)
{
	/*局部变量定义*/
	unsigned int i;	
	/*如果超过了一定的时间内,再也没有新数据从串口来*/
	if(uiSendCnt >= const_receive_time && ucSendLock == 1)
	{
		ucSendLock = 0;	/*处理一次就锁起来,不用每次都进来,除非有新接收的数据*/
		/*下面的代码进入数据协议解析和数据处理的阶段*/
		uiRcMoveIndex = 0;	/*由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动*/
		/*
		* 判断数据头,进入循环解析数据协议必须满足两个条件:
		* 第一:最大接收缓冲数据必须大于一串数据的长度(这里是5。包括2个有效数据,3个数据头)
		* 第二:游标uiRcMoveIndex必须小于等于最大接收缓冲数据减去一串数据的长度(这里是5。包括2个有效数据,3个数据头)
		*/
		while(uiRcregTotal >= 5 && uiRcMoveIndex <= (uiRcregTotal - 5))	
		{
			/*数据头的判断*/
			if(ucRcregBuf[uiRcMoveIndex + 0] == 0xeb && ucRcregBuf[uiRcMoveIndex + 1] == 0x00 && ucRcregBuf[uiRcMoveIndex + 2] == 0x55)
			{
				ucRcType = ucRcregBuf[uiRcMoveIndex + 3];	/*数据类型  一个字节*/
				uiRcSize = ucRcregBuf[uiRcMoveIndex + 4];	/*数据长度  两个字节*/
				uiRcSize = uiRcSize << 8;
				uiRcSize = ucRcregBuf[uiRcMoveIndex + 5];
				ucRcCy = ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize];	/*记录最后一个字节的校验*/
				ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize] = 0;	/*清零最后一个字节的累加和变量*/
				/* 
				* 计算校验累加和的方法:除了最后一个字节,其它前面所有的字节累加起来,
				* 溢出的不用我们管,C语言编译器会按照固定的规则自动处理。
				* 以下for循环里的(3+1+2+uiRcSize),其中3代表3个字节数据头,1代表1个字节数据类型,
				* 2代表2个字节的数据长度变量,uiRcSize代表实际上一串数据中的有效数据个数。
				*/
				for(i = 0; i < (3+1+2+uiRcSize); i ++)	/*计算校验累加和*/
				{
					ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize] += ucRcregBuf[uiRcMoveIndex + i];
				}
				if(ucRcCy == ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize])	/*如果校验正确,则进入以下数据处理*/
				{
					switch(ucRcType)	/*根据不同的数据类型来做不同的数据处理*/
					{
						case 0x01:	/*驱动蜂鸣器发出声音,并且可以控制蜂鸣器持续发出声音的时间长度*/
							uiRcVoiceTime = ucRcregBuf[uiRcMoveIndex + 6];	/*把两个字节合并成一个int类型的数据*/
							uiRcVoiceTime = uiRcVoiceTime << 8;
							uiRcVoiceTime += ucRcregBuf[uiRcMoveIndex + 7];

							ucVoiceLock = 1;	/*共享数据的原子锁加锁*/
							uiVoiceCnt = uiRcVoiceTime;	/*蜂鸣器发出声音*/
							ucVoiceLock = 0;	/*共享数据的原子锁解锁*/
							break;
						case 0x02:	/*点亮一个LED灯,并且可以控制LED灯持续亮的时间长度*/
							uiRcLedTime = ucRcregBuf[uiRcMoveIndex + 6];	/*把两个字节合并成一个int类型的数据*/
							uiRcLedTime = uiRcLedTime << 8;
							uiRcLedTime += ucRcregBuf[uiRcMoveIndex + 7];

							ucLedLock = 1;	/*共享数据的原子锁加锁*/
							uiLedTime = uiRcLedTime;	/*更改点亮Led灯的时间长度*/
							uiLedCnt = 0;	/*在本程序中,清零计数器就等于自动点亮Led灯*/
							ucLedLock = 0;	/*共享数据的原子锁解锁*/						
							break;
					}
				}
				break;	/*退出循环*/
			}
			uiRcMoveIndex ++;	/*因为是判断数据头,游标向着数组最尾端的方向移动*/
		}
		uiRcregTotal = 0;	/*清空缓冲的下标,方便下次重新从0下标开始接受新数据*/
	}
}
/**
* @brief  定时器0中断函数
* @param  无
* @retval 无
**/
void ISR_T0(void)	interrupt 1
{
	TF0 = 0;  /*清除中断标志*/
	TR0 = 0; /*关中断*/
	/* 
	* 此处多增加一个原子锁,作为中断与主函数共享数据的保护
	*/
	if(ucSendCntLock == 0)	/*原子锁判断*/
	{
		ucSendCntLock = 1;	/*加锁*/
		if(uiSendCnt < const_receive_time)	/*如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完*/
		{
			uiSendCnt ++;	/*表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来*/
			ucSendLock = 1;	/*开自锁标志*/
		}	
		ucSendCntLock = 0;	/*解锁*/	
	}

	if(ucVoiceLock == 0)	/*原子锁判断*/
	{
		if(uiVoiceCnt != 0)
		{
			uiVoiceCnt --;
			BEEP = 0;
		}
		else
		{
			;
			BEEP = 1;
		}		
	}

	if(ucLedLock == 0)	/*原子锁判断*/
	{
		if(uiLedCnt < uiLedTime)
		{
			uiLedCnt ++;	/*Led灯点亮的时间计时器*/
		}
	}
	TL0 = T1MS;                     /*initial timer0 low byte*/
	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
  	TR0 = 1; /*开中断*/	
}

/**
* @brief  串口接收数据中断
* @param  无
* @retval 无
**/
void usart_receive(void)	interrupt 4
{
	if(RI == 1)
	{
		RI = 0;
		++ uiRcregTotal;
		if(uiRcregTotal > const_rc_size)
		{
			uiRcregTotal = const_rc_size;
		}
		ucRcregBuf[uiRcregTotal - 1] = SBUF;	/*将串口接收到的数据缓存到接收缓冲区里*/
		if(ucSendCntLock == 0)	/*原子锁判断*/
		{
			ucSendCntLock = 1;	/*加锁*/
			uiSendCnt = 0;	/*及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。*/
			ucSendCntLock = 0;	/*解锁*/
		}
	}
	else
	{
		TI = 0;
	}
}

/*————————————主函数————————————*/
/**
* @brief  主函数
* @param  无
* @retval 实现LED灯闪烁
**/
void main()
{
	/*单片机初始化*/
	Init();
	/*延时,延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定*/
	Delay_Long(100);
	/*单片机外围初始化*/	
	Init_Peripheral();
	while(1)
	{
		usart_service();	/*串口服务程序*/
		led_service();	/*Led灯的服务程序*/
	}
}

三、仿真实现

51单片机实现常用的自定义串口通讯协议

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单片机学习笔记————51单片机实现常用的自定义串口通讯协议 的相关文章

  • 解决“打开ArcGIS Server Manager”网页无反应为空白的情况

    问题 xff1a 装上arcgis serve 10后 xff0c 打开arcgis server manager页面返回空白 xff0c 用firefox显示 未找到元素 郁闷 xff0c 后来想尽各种办法 终于可以了 解决办法 xff1
  • 汇编语言教程-返回指令(RET)

    汇编语言教程 返回指令 RET 当子程序执行完时 xff0c 需要返回到调用它的程序之中 为实现此功能 xff0c 指令系统提供了一条专用的返回指令 其格式如下 xff1a RET RETN RETF Imm 子程序的返回在功能上是子程序调
  • 富斯/MC6接收机说明书

    正面 反面 1 PWM输出通道多达6个 xff0c 可以自由切换7种模式 xff0c 自由选择无刷 xff0c 有刷 xff0c 差速 xff0c 炫酷的RGB全彩灯带等 xff0c 自由玩耍 2 集成两个5A有刷电调 xff0c 通过模式
  • C++之struct构造函数(2010-10-19 15:04:47)

    C 43 43 之struct构造函数 2010 10 19 15 04 47 转载 标签 xff1a cpp struct 构造函数 校园 分类 xff1a C C PlusPlus 在网络协议 通信控制 嵌入式系统的C C 43 43
  • 汉字转拼音

    原创的兄弟 xff0c 看来是费了不少功夫 在此谢过了 代码如下 xff1a public class hanzi to pinyin1 private static readonly string Allhz 61 new string
  • 什么是功能性需求和非功能性需求

    需求定义 xff1a 需求 xff08 requirement xff09 就是系统 xff08 更广义的说法是项目 xff09 必须提供的能力和必须遵从的条件 需求分类 xff1a 1 在一般使用中 xff0c 需求按照功能性 xff08
  • 卷三、七言古诗

    卷三 七言古诗 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 登幽州台歌 作者 xff1a 陈子昂 前不见古人
  • 卷五、五言律诗

    卷五 五言律诗 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 经邹鲁祭孔子而叹之 作者 xff1a 唐玄宗 夫子
  • 卷六、七言律诗

    61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 黄鹤楼 作者 xff1a 崔颢 昔人已乘黄鹤去 xff0c 此地空
  • 计算两个经纬度间的距离(c++)

    double D jw double wd1 double jd1 double wd2 double jd2 double x y out double PI 61 3 14159265 double R 61 6 371229 1e6
  • C语言库函数及示例

    函数名 abort 功 能 异常终止一个进程 用 法 void abort void 程序例 include lt stdio h gt include lt stdlib h gt int main void printf 34 Call
  • Json风格指南

    英文版 xff1a http google styleguide googlecode com svn trunk jsoncstyleguide xml 翻译 xff1a Darcy Liu 简介 该风格指南是对在Google创建JSON
  • C中__FILE__ __LINE__的用法

    include lt stdio h gt void main void printf 34 File s Successfully reached line d n 34 FILE LINE Other statements here l
  • MC6C迈克/FLYSKY富斯/WFLY2天地飞二代接收机远程刷固件教程

    1 安装ch341的驱动程序 请找ch341卖家要或百度找 2 ch341的跳线跳到usb To ttl 如能本身只有TTL刷机的功能的板子 xff0c 像CH340一般只有usb to ttl的功能 xff0c 这一步可以不做 3 接收机
  • STM32入门系列-使用C语言封装寄存器

    前面介绍了存储器映射 寄存器和寄存器映射 xff0c 这些都是为了介绍使用 C语言封装寄存器做铺垫 这里我们通过一个实例来对 C 语言封装寄存器进行介绍 具体实例 xff1a 控制 GPIOC 端口的第 0 管脚输出一个低电平 首先我们需要
  • *** buffer overflow detected ***异常

    一次在linux上编译程序报错 xff1a buffer overflow detected TAppEncoderStaticSADBS terminated 排查原因发现是sprintf读取时数组长度不够 xff0c 将数组长度由50增
  • 利用火狐浏览器脚本功能_充分利用Firefox

    利用火狐浏览器脚本功能 Firefox 0 8的发布消息是凤凰网 Firebird Mozilla浏览器系列中的最新版本 xff0c 目前 xff0c Web开发社区对此感到非常关注 该发行版标志着Mozilla项目独立浏览器的第三个也是最
  • 串口接收无定长数据

    1 原理 xff1a 1 使能串口接收中断 定时器中断 xff1b 2 在串口第一次进入到中断后 xff0c 使能定时器计时 xff1b 3 在串口每次进入中断后 xff0c 清空定时器 xff1b 4 当定时器溢出时 xff0c 判定数据
  • OpenWRT 小记

    查看openwrt内核版本 xff1a cat proc version uname r 生成配置文件 xff1a config generate 查看DHCH 已经分配的IP cat var dhcp leases 分割cat tmp d
  • OpenWrt OpenMPTCProuter feed

    echo 34 src git OpenMPTCProuter https github com Ysurac openmptcprouter feeds git 34 gt gt feeds conf default echo 34 sr

随机推荐

  • openwrt路由器接华为E3372(E8372)网卡实现4G转有线和WIFI

    Hilink 在openwrt系统中安装kmod usb net rndis kmod usb net kmod usb2 usb modeswitch kmod usb net cdc ether 安装完成后 xff0c 把E3372 x
  • windows 10 内存居高不下,实际没开多少进程

    windows 10 内存居高不下 xff0c 实际没开多少进程 关闭快速启动 就好了
  • opkg list 报错

    opkg list Collected errors opkg conf load Could not lock var lock opkg lock Resource temporarily unavail echo 34 nameser
  • openwrt opkg install 强制替换安装

    查询 opkg list installed grep XXX opkg install XXX ipk force downgrade
  • stm32 硬件spi半双工三线的一些研究心得

    a7105可以使用四线spi 或者3线spi 但是之前都是使用3线的软件模拟的三线spi的 xff0c 所以不想改其它代码了 xff0c 就想可以提高一个spi的读写速度 xff0c 原来软件方式的读写速度 xff0c 在48Mhz的03x
  • Openwrt tftp刷机

    第一次写论坛 xff0c 今天早上才拿到路由器 开始学习openwrt 之前学过嵌入式Linux arm 移植 xff0c 开始正题 xff1a 拿到开发板后 xff0c 就开始烧写自己编译的 bin文件 xff0c 在烧写的过程中出现了问
  • openwrt ipk 安装 luci 界面

    试试看可行不 慢慢更新 opkg update 更新 opkg list grep svn
  • OpenWRT php 安装

    一 安装PHP opkg update opkg install php5 php5 mod apc opkg install php5 mod gd php5 mod session opkg install php5 mod pdo m
  • ESP8266 固件擦除

    折腾了两天 真是醉了 首先确认安装 python python2是否安装 python2 version sudo apt isntall python pip 安装pip和他的许多其他依赖 pip 9 0 1 from usr lib p
  • 第二次实验报告:使用Packet Tracer分析应用层协议

    姓名 xff1a 刘钰学号 xff1a 201821121036班级 xff1a 计算1812 1 实验目的 熟练使用Packet Tracer工具 分析抓到的应用层协议数据包 xff0c 深入理解应用层协议 xff0c 包括语法 语义 时
  • C++的类与C语言结构体比较

    C 43 43 的类与C语言结构体比较 C 43 43 的类与C语言结构体比较 一 结构体 xff0c 类的介绍二 结构体和类的具体区别1 C语言对结构体数组初始化 必须要在定义时初始化 xff1a 2 C 43 43 的类的初始化 构造函
  • CPP-网络/通信:经典HTTP协议详解

    2008 11 03 09 11 by Hundre 266688 阅读 23 评论 收藏 编辑 转自 xff1a http blog csdn net gueter archive 2007 03 08 1524447 aspx Auth
  • 串口编程3:使用串口读取GPS信息

    关于GPS的使用 xff0c 参考 本文主要参考的博客 xff0c 在此表示感谢 xff01 xff01 xff01 主函数 主函数gps main c xff0c 这里便涉及到了串口的打开 xff0c 读操作 xff0c 以及调用了串口设
  • 基于单片机语音智能导盲仪仿真设计-毕设课设资料

    资料下载地址 1110 xff08 百度网盘 xff09 xff1a 点击下载 包含超声波传感器检测障碍物 xff0c 温度传感器检测当前温度 可以通过按键设置距离报警范围 xff0c 报警装置通过声光报警 包含的电路有电源电路 显示电路
  • 宏定义详解

    宏定义有无参数宏定义和带参数宏定义两种 无参数的宏定义的一般形式为 define 标识符 字符序列 其中 define之后的标识符称为宏定义名 简称宏名 xff0c 要求宏名与字符序列之间用空格符分隔 这种宏定义要求编译预处理程序将源程序中
  • 一个无线鼠标的HID Report Desc

    HID设备是USB规范定义的设备类型之一 xff0c 其分类号为0x03 关于USB设备类型定义 xff0c 可参见本站 xff1a USB设备类型定义 USB中文网 HID设备除了用于专门的输入输出设备外 xff0c 有时也与其它的设备类
  • 虚拟机的三种网络连接方式

    1 NAT模式 xff1a 用于共享主机的IP地址 安装完VMware后在本地网络连接里会虚拟出两块网卡 xff08 VMnet1 xff0c VMnet8 xff09 如果选择的是NAT模式 xff0c 则会使用VMnet8这块网卡来和虚
  • 全局变量不能放在头文件当中

    看网上各种说法说 变量的声明和变量的定义 xff0c 但是还是没有讲清楚什么是声明什么是定义 xff0c 如果说定义要分配内存 xff0c 声明不分配 xff0c 这个谁都知道 刚我在VS2012中测试 xff1b 按理说 int i xf
  • 使用strcat连接字符串

    include lt iostream gt using namespace std int main int argc char argv char str1 61 34 hello 34 char str2 61 34 china 34
  • 单片机学习笔记————51单片机实现常用的自定义串口通讯协议

    proteus虚拟串口的实现 xff1a https mp csdn net console editor html 107251649 一 使用proteus绘制简单的电路图 xff0c 用于后续仿真 二 编写程序 64 Project