历史上的今天
返回首页

历史上的今天

今天是:2024年11月08日(星期五)

正在发生

2021年11月08日 | 51单片机实现常用的自定义串口通讯协议

2021-11-08 来源:eefocus

一、使用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   {

      for(j=0;j<500;j++)  /*内嵌循环的空指令数量*/

          {

             ; /*一个分号相当于执行一条空语句*/

          }

   }

}

///**

//* @brief  延时函数

//* @param  无

//* @retval 无

//**/

//void Delay_Short(unsigned int uiDelayShort)

//{

//   unsigned int i;

//   for(i=0;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灯的服务程序*/

}

}

 

三、仿真实现


推荐阅读

史海拾趣

Galil Motion Control Inc公司的发展小趣事

Galil Motion Control Inc. 的五个发展故事

故事一:技术创新引领者

Galil Motion Control Inc.,成立于1983年,位于美国加州硅谷中心,是电子行业中运动控制技术的先驱。公司成立初期,便致力于研发基于微处理器的精密运动控制器。据《DESIGNNEWS》杂志报道,Galil是全球第一家推出数字运动控制器的公司,这一技术创新在业界引起了巨大反响。此后,Galil不断推陈出新,其产品已发展到第五代,采用RISC结构的DSP技术,结合最新控制理论及网络技术,使得伺服更新速率和指令执行周期显著提升,引领了运动控制领域的技术潮流。

故事二:全球市场的扩展

随着技术的不断成熟,Galil的产品逐渐在全球市场上占据了一席之地。目前,全球有超过500,000台Galil的运动控制器在稳定运行,应用领域覆盖了医疗、半导体、纺织、物料搬运、食品加工、机床、产业机械、航天、测试测量等多个行业。这一广泛的应用不仅证明了Galil产品的可靠性和稳定性,也为其在全球市场的扩展奠定了坚实的基础。

故事三:分布式运动控制器的诞生

在进入21世纪之初,Galil公司又开发出了DMC3425分布式运动控制器。这款控制器不仅提供了API函数支持高级应用开发,还引入了极其简单易学的2字符命令集,使得应用编程变得如同书写英文字母般简单。这一创新不仅降低了用户的学习成本,也极大地提高了系统的开发效率,进一步巩固了Galil在运动控制领域的领先地位。

故事四:与光纤激光切割技术的结合

近年来,随着激光切割技术的快速发展,Galil公司也紧跟这一趋势,将运动控制技术与光纤激光切割技术相结合。通过设计以Galil控制卡为核心的四轴联动伺服控制系统,实现了激光切割设备的高精度控制。这一结合不仅提升了激光切割设备的性能,也拓展了Galil运动控制技术的应用领域,为工业制造带来了更多的可能性。

故事五:全球销售与服务网络的建立

为了更好地服务全球客户,Galil公司在全球范围内建立了完善的销售和服务网络。目前,Galil拥有众多销售代理商,遍布世界各地。这些代理商不仅负责产品的销售工作,还为客户提供专业的技术支持和售后服务。通过这一网络,Galil能够迅速响应客户的需求,确保每位客户都能获得最佳的产品体验和服务保障。这种以客户为中心的服务理念,也为Galil赢得了良好的市场口碑和广泛的客户认可。

格莱尔(GLE)公司的发展小趣事

面对日益激烈的市场竞争,格莱尔积极推进数字化转型和智能化升级。公司引入了ERP、EHR、OA等信息化系统,实现了生产、管理、销售等各个环节的信息化和智能化。同时,格莱尔还加大了对自动化生产设备的投入和研发力度,提高了生产效率和产品质量。这些举措不仅提升了企业的竞争力,也为格莱尔在未来的发展中注入了新的动力。

Dionics Inc公司的发展小趣事

在经历了一段艰难的市场竞争后,Dionics Inc决定加大研发投入,寻求技术突破。经过数年的努力,公司成功研发出一款具有自主知识产权的高性能微处理器芯片,该芯片在性能上大幅超越同类产品,并在市场上取得了良好的口碑。随着这款芯片的成功上市,Dionics Inc的市场份额也逐渐扩大。

High Tech Chips Inc公司的发展小趣事

在20世纪90年代初,Dionics Inc由几位对电子技术充满热情的工程师创立。当时,电子市场正处于快速发展期,但也面临着激烈的竞争。Dionics Inc凭借其在电源管理领域的创新技术,成功开发出一款高效节能的电源管理芯片,赢得了市场的认可。然而,随着市场的进一步开放,来自国内外的竞争对手纷纷涌入,Dionics Inc面临着巨大的挑战。

Alorium Technology公司的发展小趣事

近年来,随着全球贸易环境的变化和市场竞争的加剧,Dionics Inc也面临着前所未有的挑战。然而,在公司管理层的坚强领导下,全体员工团结一心、共克时艰。公司不仅成功应对了各种挑战和困难,还通过一系列创新举措实现了业务的稳步增长。展望未来,Dionics Inc将继续坚持创新驱动的发展战略,努力成为电子行业的领军企业之一。

Agere System(LSI Logic)公司的发展小趣事

近年来,随着全球贸易环境的变化和市场竞争的加剧,Dionics Inc也面临着前所未有的挑战。然而,在公司管理层的坚强领导下,全体员工团结一心、共克时艰。公司不仅成功应对了各种挑战和困难,还通过一系列创新举措实现了业务的稳步增长。展望未来,Dionics Inc将继续坚持创新驱动的发展战略,努力成为电子行业的领军企业之一。

问答坊 | AI 解惑

伟福,没礼貌!

本人想买一台仿真器,打电话到南京公司,一男的接电话 以下是对话内容: \"我想买一台仿真器\" \"说\" \"我想买一台51的仿真器,哪里能买到?\" \"你是哪里的?\" \"深圳\" \"你找我们代理商吧\" \"好的,他们电话是多少呢?\" \"8XXX......\" .. ...…

查看全部问答>

低电压PLD/FPGA的供电设计

由于半导体制造工艺的原因,低电压器件的成本比传统5V器件更低,性能更优,加上多数器件的I/O脚可以兼容5v/3.3v TTL电平,可以直接使用在原有系统中,所以各大半导体公司都将3.3v,2.5v等低电压集成电路作为推广重点,如高端的DSP,PLD/FPGA产品已广 ...…

查看全部问答>

大家来找茬儿之——PLI程序编译装载后modelsim崩溃

今天写了个pli程序,编译、生成dll文件都没有问题,就是仿真装载过程中,modelsim崩溃,请大家给分析一下,这是什么病,怎么治!源码如下: top.v `timescale 1ns/1ns module top(clk,AM,AS,WRITE,IACK,LWORD,DS0,DS1,DTACK,BERR,RETRY,AB,DB,SY ...…

查看全部问答>

求TC35外围电路连接图及收发短信源代码 !!

求TC35外围电路连接图及收发短信源代码 !! 各位大虾帮帮忙吧,刚接触这东西,急需这些来熟悉熟悉,买了个TC35模块,想自己焊个板子玩下。 如果有的话,麻烦发我邮箱forjobforlife@163.com 谢谢了~ …

查看全部问答>

GPRS拨号,SOCKET能连通,IE及其它程序无法上网

HI,各位 最近在做一个WINCE5的项目,需要用到GPRS,使用SIM300模块做MODEM,设置都OK了 现在的问题是,使用我自己写的小程序,程序使用的是SOCKET,可以正常通讯,但是用IE不能上网 在CE下,所有IP都ping不通,但是用自己写的小程序连接过某个IP以后,就可 ...…

查看全部问答>

請USB高手推荐比較好的USB HOST 開發板

如題,我要用8051讀取USB 鼠標的數據,請USB高手推荐比較好的USB HOST 開發板,要有齊全的相關資料和詳細的原代碼說明,最好是中文的.…

查看全部问答>

请教完成如下的功能使用什么CPU

1、连接4个串口 2、用220V供电 3、访问桌面SQL Server数据库 操作系统采用windows ce请问使用什么CPU的嵌入式主板?相应厂家的联系方式?…

查看全部问答>

STM32在ucLinux环境中,能流畅驱动600*480彩屏么

                                  …

查看全部问答>