单片机学习笔记————51单片机实现在串口接收中断里即时解析数据头的特殊程序框架
程序员文章站
2022-06-08 21:34:23
...
proteus虚拟串口的实现:https://mp.csdn.net/console/editor/html/107251649
一、使用proteus绘制简单的电路图,用于后续仿真
二、编写程序
/********************************************************************************************************************
---- @Project: USART
---- @File: main.c
---- @Edit: ZHQ
---- @Version: V1.0
---- @CreationTime: 20200712
---- @ModifiedTime: 20200712
---- @Description:
---- 波特率是:9600 。
---- 通讯协议:EB GG XX XX XX XX ED
---- 其中第1位EB就是数据头.
---- 其中第2位GG就是数据类型。01代表驱动蜂鸣器,02代表驱动Led灯。
---- 其中第3,4,5,6位XX就是有效数据长度。高位在左,低位在右。
---- 其中第7位ED就是数据尾,在这里也起一部分校验的作用,虽然不是累加和的方式。
---- 在本程序中,
---- 当数据类型是01时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么蜂鸣器就鸣叫一声表示正确。
---- 当数据类型是02时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么LED灯就会闪烁一下表示正确。
---- 十进制的123456789等于十六进制的75bcd15 。
---- 发送以下测试数据,将会分别控制蜂鸣器Led灯。
---- 控制蜂鸣器发送:eb 01 07 5b cd 15 ed
---- 控制LED灯发送:eb 02 07 5b cd 15 ed
---- 单片机: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 /*如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小*/
#define const_voice_short 80 /*蜂鸣器短叫的持续时间*/
#define const_led_short 80 /*LED灯亮的持续时间*/
/*——————变量函数定义及声明——————*/
/*蜂鸣器的驱动IO口*/
sbit BEEP = P2^7;
/*LED*/
sbit LED = P3^5;
unsigned int uiRcregTotal = 0; /*代表当前缓冲区已经接收了多少个数据*/
unsigned char ucRcregBuf[const_rc_size]; /*接收串口中断数据的缓冲区数组*/
/*为串口计时器多增加一个原子锁,作为中断与主函数共享数据的保护*/
unsigned char ucVoiceLock = 0; /*蜂鸣器鸣叫的原子锁*/
unsigned char ucLedLock = 0; /*Led灯点亮时间的原子锁*/
unsigned int uiVoiceCnt = 0; /*蜂鸣器鸣叫的持续时间计数器*/
unsigned int uiRcVoiceTime = 0; /*蜂鸣器发出声音的持续时间*/
unsigned int uiLedCnt = 0; /*Led灯点亮的计时器*/
unsigned long ulBeepData = 0; /*蜂鸣器的数据*/
unsigned long ulLedData = 0; /*LED的数据*/
unsigned char ucUsartStep = 0; /*串口接收字节的步骤变量*/
/**
* @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 < const_led_short)
{
LED = 1; /*LED亮*/
}
else
{
LED = 0;
}
}
/**
* @brief 定时器0中断函数
* @param 无
* @retval 无
**/
void ISR_T0(void) interrupt 1
{
TF0 = 0; /*清除中断标志*/
TR0 = 0; /*关中断*/
/*
* 此处多增加一个原子锁,作为中断与主函数共享数据的保护
*/
if(ucVoiceLock == 0) /*原子锁判断*/
{
if(uiVoiceCnt != 0)
{
uiVoiceCnt --;
BEEP = 0;
}
else
{
;
BEEP = 1;
}
}
if(ucLedLock == 0) /*原子锁判断*/
{
if(uiLedCnt < const_led_short)
{
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;
switch(ucUsartStep) /*串口接收字节的步骤变量*/
{
case 0:
ucRcregBuf[0] = SBUF;
if(ucRcregBuf[0] == 0xeb) /*数据头判断*/
{
ucRcregBuf[0] = 0; /*数据头及时清零,为下一串数据的接受判断做准备*/
uiRcregTotal = 1; /*缓存数组的下标初始化*/
ucUsartStep = 1; /*如果数据头正确,则切换到下一步,依次把上位机来的数据存入数组缓冲区*/
}
break;
case 1:
ucRcregBuf[uiRcregTotal] = SBUF; /*依次把上位机来的数据存入数组缓冲区*/
uiRcregTotal ++;
if(uiRcregTotal >= 7) /*已经接收了7个字节*/
{
if(ucRcregBuf[6] == 0xed) /*数据尾判断,也起到一部分校验的作用,让数据更加可靠,虽然没有用到累加和的检验方法*/
{
ucRcregBuf[6] = 0; /*数据尾及时清零,为下一串数据的接受判断做准备*/
switch(ucRcregBuf[1]) /*根据不同的数据类型来做不同的数据处理*/
{
case 0x01: /*与蜂鸣器相关*/
ulBeepData = ucRcregBuf[2]; /*把四个字节的数据合并成一个long型的数据*/
ulBeepData = ulBeepData << 8;
ulBeepData += ucRcregBuf[3];
ulBeepData = ulBeepData << 8;
ulBeepData += ucRcregBuf[4];
ulBeepData = ulBeepData << 8;
ulBeepData += ucRcregBuf[5];
if(ulBeepData == 123456789) /*如果此数据等于十进制的123456789,表示数据正确*/
{
ucVoiceLock = 1; /*共享数据的原子锁加锁*/
uiVoiceCnt = const_voice_short; /*蜂鸣器发出声音*/
ucVoiceLock = 0; /*共享数据的原子锁解锁*/
}
break;
case 0x02: /*与Led灯相关*/
ulLedData = ucRcregBuf[2]; /*把四个字节的数据合并成一个long型的数据*/
ulLedData = ulLedData << 8;
ulLedData += ucRcregBuf[3];
ulLedData = ulLedData << 8;
ulLedData += ucRcregBuf[4];
ulLedData = ulLedData << 8;
ulLedData += ucRcregBuf[5];
if(ulLedData == 123456789) /*如果此数据等于十进制的123456789,表示数据正确*/
{
ucLedLock = 1; /*共享数据的原子锁加锁*/
uiLedCnt = 0; /*在本程序中,清零计数器就等于自动点亮Led灯*/
ucLedLock = 0; /*共享数据的原子锁解锁*/
}
break;
}
}
ucUsartStep = 0; /*返回上一步数据头判断,为下一次的新数据接收做准备*/
}
break;
}
}
else
{
TI = 0;
}
}
/*————————————主函数————————————*/
/**
* @brief 主函数
* @param 无
* @retval 实现LED灯闪烁
**/
void main()
{
/*单片机初始化*/
Init();
/*延时,延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定*/
Delay_Long(100);
/*单片机外围初始化*/
Init_Peripheral();
while(1)
{
led_service(); /*Led灯的服务程序*/
}
}
三、仿真实现
51单片机实现在串口接收中断里即时解析数据头的特殊程序框架
上一篇: PHP 提升优化效率的50个技巧!