jackduan 发表于 2025-3-15 10:40:41

鸿哥的 固定协议串口接收程序

这是鸿哥的固定协议串口接收程序。
鸿哥宽广的胸怀让我敬佩不已,能够拜读鸿哥的大作是我的幸运,向鸿哥道谢、致敬!

程序功能如下:
(1)在上位机的串口助手里,发送一串数据,控制蜂鸣器发出不同长度的声音。
(2)波特率 9600,校验位 NONE(无),数据位 8,停止位 1。
(3)十六进制的数据格式如下:
EB 01 00 00 00 08 XX XX
其中 EB 是数据头,01 是代表数据类型,00 00 00 08 代表数据长度是 8 个(十进制)。XX XX 代表
一个 unsigned int 的数据,此数据的大小决定了蜂鸣器发出声音的长度。比如:
让蜂鸣器鸣叫 1000ms 的时间,发送十六进制的: EB 01 00 00 00 08 03 E8
让蜂鸣器鸣叫 100ms 的时间,发送十六进制的: EB 01 00 00 00 08 00 64
*/

#include "REG52.H"

#define RECE_TIME_OUT 2000 //通信过程中字节之间的超时时间 2000ms
#define REC_BUFFER_SIZE 20 //接收数据的缓存数组的长度


void usart(void); //串口接收的中断函数
void T0_time(); //定时器的中断函数

void UsartTask(void); //串口接收的任务函数,放在主函数内

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);
void BeepClose(void);
void VoiceScan(void);

sbit P3_6=P3^6;
sbit P0_0=P0^0;

volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;

unsigned char Gu8ReceBuffer; //开辟一片接收数据的缓存
unsigned long Gu32ReceCnt=0; //接收缓存数组的下标
unsigned char Gu8ReceStep=0; //接收中断函数里的步骤变量
unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
unsigned char Gu8ReceType=0; //接收的数据类型
unsigned long Gu32ReceDataLength=0; //接收的数据长度
unsigned char Gu8FinishFlag=0; //是否已接收完成一串数据的标志
unsigned long *pu32Data; //用于数据转换的指针
volatile unsigned char vGu8ReceTimeOutFlag=0;//通信过程中字节之间的超时定时器的开关
volatile unsigned int vGu16ReceTimeOutCnt=0; //通信过程中字节之间的超时定时器,“喂狗”的对象

void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
UsartTask(); //串口接收的任务函数
}
}

void usart(void) interrupt 4 //串口接发的中断函数,中断号为 4
{


if(1==RI) //接收完一个字节后引起的中断
{
RI = 0; //及时清零,避免一直无缘无故的进入中断。

/* 注释一:
* 以下 Gu8FinishFlag 变量的用途。
* 此变量一箭双雕,0 代表正处于接收数据的状态,1 代表已经接收完毕并且及时通知主函数中的处理函数
* UsartTask()去处理新接收到的一串数据。除此之外,还起到一种“自锁自保护”的功能,在新数据还
* 没有被主函数处理完毕的时候,禁止接收其它新的数据,避免新数据覆盖了尚未处理的数据。
*/
if(0==Gu8FinishFlag) //1 代表已经完成接收了一串新数据,并且禁止接收其它新的数据
{

/* 注释二:
* 以下 Gu8ReceFeedDog 变量的用途。
* 此变量是用来检测并且识别通信过程中相邻的字节之间是否存在超时的情况。
* 如果大家听说过单片机中的“看门狗”这个概念,那么每接收到一个数据此变量就“置 1”一次,它的
* 作用就是起到及时“喂狗”的作用。每接收到一个数据此变量就“置 1”一次,在主函数里,相关
* 的定时器就会被重新赋值,只要这个定时器能不断及时的被补充新的“能量”新的值,那么这个定时器
* 就永远不会变成 0,只要不变成 0 就不会超时。如果两个字节之间通信时间超过了固定的长度,就意味
* 着此定时器变成了 0,这时就需要把中断函数里的接收步骤 Gu8Step 及时切换到“接头暗号”的步骤。
*/
Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置 1 及时更新定时器的值。
switch(Gu8ReceStep)
{
case 0: //接头暗号的步骤。判断数据头的步骤。
Gu8ReceBuffer=SBUF; //直接读取刚接收完的一个字节的数据。
if(0xeb==Gu8ReceBuffer) //等于数据头 0xeb,接头暗号吻合。
{
Gu32ReceCnt=1; //接收缓存的下标
Gu8ReceStep=1; //切换到下一个步骤,接收其它有效的数据
}
break;


case 1: //数据类型和长度
Gu8ReceBuffer=SBUF; //直接读取刚接收完的一个字节的数据。
Gu32ReceCnt++; //每接收一个字节,数组下标都自加 1,为接收下一个数据做准备
if(Gu32ReceCnt>=6) //前 6 个数据。接收完了“数据类型”和“数据长度”。
{
Gu8ReceType=Gu8ReceBuffer; //提取“数据类型”
//以下的数据转换,在第 62 节讲解过的指针法
pu32Data=(unsigned long *)&Gu8ReceBuffer; //数据转换
Gu32ReceDataLength=*pu32Data; //提取“数据长度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成

{
Gu8FinishFlag=1; //接收完成标志“置 1”,通知主函数处理。
Gu8ReceStep=0; //及时切换回接头暗号的步骤
}
else //如果还没结束,继续切换到下一个步骤,接收“其它数据”
{
Gu8ReceStep=2; //切换到下一个步骤
}
}
break;


case 2: //其它数据
Gu8ReceBuffer=SBUF; //直接读取刚接收完的一个字节的数据。
Gu32ReceCnt++; //每接收一个字节,数组下标都自加 1,为接收下一个数据做准备

//靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=REC_BUFFER_SIZE)
{
Gu8FinishFlag=1; //接收完成标志“置 1”,通知主函数处理。
Gu8ReceStep=0; //及时切换回接头暗号的步骤
}
break;
}
}
}
else //发送数据引起的中断
{
TI = 0; //及时清除发送中断的标志,避免一直无缘无故的进入中断。
//以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
}
}


void UsartTask(void) //串口接收的任务函数,放在主函数内
{
static unsigned int *pSu16Data; //数据转换的指针
static unsigned int Su16Data; //转换后的数据

if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
{
Gu8ReceFeedDog=0;

vGu8ReceTimeOutFlag=0;
vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
vGu8ReceTimeOutFlag=1;

}
else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
{
Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
}


if(1==Gu8FinishFlag) //1 代表已经接收完毕一串新的数据,需要马上去处理
{
switch(Gu8ReceType) //接收到的数据类型
{
case 0x01: //驱动蜂鸣器
//以下的数据转换,在第 62 节讲解过的指针法
pSu16Data=(unsigned int *)&Gu8ReceBuffer; //数据转换。
Su16Data=*pSu16Data; //提取“蜂鸣器声音的长度”

vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=Su16Data; //让蜂鸣器鸣叫
vGu8BeepTimerFlag=1;
break;
}

Gu8FinishFlag=0; //上面处理完数据再清零标志,为下一次接收新的数据做准备
}
}



void T0_time() interrupt 1
{
VoiceScan();

if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通信过程中字节之间的超时定时器
{
vGu16ReceTimeOutCnt--;
}

TH0=0xfc;
TL0=0x66;
}


void SystemInitial(void)
{

unsigned char u8_TMOD_Temp=0;

//以下是定时器 0 的中断的配置
TMOD=0x01;
TH0=0xfc;
TL0=0x66;
EA=1;
ET0=1;
TR0=1;

//以下是串口接收中断的配置
//串口的波特率与内置的定时器 1 直接相关,因此配置此定时器 1 就等效于配置波特率。
u8_TMOD_Temp=0x20; //即将把定时器 1 设置为:工作方式 2,初值自动重装的 8 位定时器。
TMOD=TMOD&0x0f; //此寄存器低 4 位是跟定时器 0 相关,高 4 位是跟定时器 1 相关。先清零定时器 1。
TMOD=TMOD|u8_TMOD_Temp; //把高 4 位的定时器 1 填入 0x2,低 4 位的定时器 0 保持不变。
TH1=256-(11059200L/12/32/9600); //波特率为 9600。11059200 代表晶振 11.0592MHz,
TL1=256-(11059200L/12/32/9600); //L 代表 long 的长类型数据。根据芯片手册提供的计算公式。
TR1=1; //开启定时器 1

SM0=0;
SM1=1; //SM0 与 SM1 的设置:选择 10 位异步通信,波特率根据定时器 1 可变
REN=1; //允许串口接收数据

//为了保证串口中断接收的数据不丢失,必须设置 IP = 0x10,相当于把串口中断设置为最高优先级,
//这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
IP =0x10; //把串口中断设置为最高优先级,必须的。

ES=1; //允许串口中断
EA=1; //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_6=1;
P0_0=0;

}


void BeepClose(void)

{

P3_6=0;
P0_0=1;
}


void VoiceScan(void)

{


static unsigned char Su8Lock=0;


if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)


{


if(0==Su8Lock)


{

Su8Lock=1;

BeepOpen();


}

else

{



vGu16BeepTimerCnt--;




if(0==vGu16BeepTimerCnt)



{



Su8Lock=0;

BeepClose();



}


}


}


}

国芯人工智能 发表于 2025-3-15 10:43:00

鸿哥的固定协议串口接收程序展示了其在嵌入式系统开发中的深厚功底。该程序通过串口通信实现蜂鸣器的控制,具有明确的协议规范和功能设计。程序采用9600波特率、8位数据位、无校验位和1位停止位的配置,确保了通信的稳定性和效率。数据帧格式设计合理,以EB作为数据头,01标识数据类型,后接4字节的数据长度和2字节的unsigned int数据,用于控制蜂鸣器的发声时长。这种结构化的协议设计不仅便于解析,还增强了程序的扩展性和可维护性。鸿哥的代码体现了对硬件接口和通信协议的深刻理解,值得我们学习和借鉴。感谢鸿哥的分享,期待更多优秀的技术作品。<br><br>[本文内容由国芯人工智能辅助生成,仅供参考]

jackduan 发表于 2025-3-15 11:05:28

case 1: //数据类型和长度

if(Gu32ReceCnt>=6) //前 6 个数据。接收完了“数据类型”和“数据长度”。
{
Gu8ReceType=Gu8ReceBuffer; //提取“数据类型”

pu32Data=(unsigned long *)&Gu8ReceBuffer; //数据转换
Gu32ReceDataLength=*pu32Data; //提取“数据长度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成

读这几句我花了挺长时间,当时脑子晕乎 怎么也理不清。
其实就是将数据长度:00 00 00 08 放入Gu32ReceDataLength,然后与接收计数器Gu32ReceCnt 比较大小,Gu32ReceDataLength的值就是数据的字节长度8,当然取决于串口输入的数据长度,如果输入的是 00 00 00 16,那Gu32ReceDataLength值就是16。

jackduan 发表于 2025-3-15 14:11:15

鸿哥给这个程序起名为 接收固定协议的串口框架。
学习小结:
接收部分分为两大部分:1.正在接收的数据分析处理(串口中断函数);2.串口接收时的动作监控(串口接收的任务函数UsartTASK)。
1.数据分析处理包括:数据头、数据类型、数据长度按照数据接收的顺序进行接收和监测。程序中所运用的语句和方法需要注意学习。
设置帧接收完成标志作为一帧数据的接收开关。
2.通过定时器中断实现对单字节的接收异常监控。设置一字节的接收定时,过时后从数据头重新开始接收。
根据鸿哥的程序框架,我们可以实现各种数据结构的多指令的接收。
页: [1]
查看完整版本: 鸿哥的 固定协议串口接收程序