个人笔记,主要内容均来自原文
一个例子来说明 RTOS 的好处。
假设编一个串行通信程序,通信协议为:数据包长度为 NBYTE,起始字节为 STARTBYTE1、STARTBYTE2,最后一个字节为校验和,中间不可能连续出现 STARTBYTE1、STARTBYTE2。
原文示例代码:【代码注释为个人理解,仅供参考】
unsigned char Buf[NBYTE - 2]; // NBYTE 为包的最大字节数,除去包头
bit GetRight = 0;
// 串行中断
void comm(void) interrupt 4
{
static unsigned char Sum, Flag = 0, i;
unsigned char temp;
if(R1 == 1)
{
R1 = 0;
temp = SBUF; // 获取串口数据
swtich(Flag)
{
case 0: // 状态0:开始接收并处理串口协议
if(temp == STARTBYTE1) //接收到的数据为包头第一个字节
{
Flag = 1; // 进入下一状态
}
break;
case 1: // 状态1:接收包头第二个字节
if(temp == STARTBYTE2) // 如果是目标字节
{
Sum = STARTBYTE1 + STARTBYTE2; // 更新数据包的和
i = 0; // "清空"串口接收数组
Flag = 2; // 进入下一状态
break;
}
if(temp == STARTBYTE1) // 如果依然收到的是包头第一个字节,保持当前的状态
break;
Flag = 0; // 如果接收到的数据不是包头数据,直接返回到状态0
break;
case 2: // 状态2:开始接收数据(非包头)
if(temp == STARTBYTE1) // 如果接收到包头第一个字节(丢弃之前接收到的数据)
{
Flag = 3; // 进入下一状态
break;
}
Sum += temp; // 更新校验和
if(i >= (NBYTE -3) && Sum == 0) // 如果接收到的是数据包最后一个字节(校验值),同时判断校验和是否为0,
{
GetRight = 1; // 表示数据包处理完毕
Flag = 0; // 状态复位
break;
}
Buf[i++] = temp; // 如果接收到的不是校验值也不是包头数据,则将其存入串口接收数组
break;
case 3: // 状态3:在状态2时接收到包头后才会进入此状态
if(temp == STARTBYTE2) // 接收到包头第二个字节
{
Sum = STARTBYTE1 + STARTBYTE2; // 计算校验和
Flag = 2; // 进入状态2
i = 0; // "清空"串口接收数组
break;
}
Sum += STARTBYTE1; // 如果上一个数据并不是包头,而是校验值(凑巧相等),则计算校验和
if((i >= (NBYTE - 3)) && Sum == 0) // 如果上一个接收的数据是数据包的最后一个字节,且校验和为0
{
GetRight=1; // 表示数据包处理完毕
Flag=0; // 状态复位
break;
}
}
}
}
通过串口中断这种方式处理串口协议,是比较直观和简单的一种处理方式,初学者(比如我)最先想到的就是这种方法了,直接在串口中断完成全部操作。
原文示例代码(原代码有些地方我不太理解,自己改了一点):【代码注释为个人理解,仅供参考】
// 串口中断中只完成数据入列
void comm(void) interrupt 4
{
if(R1 == 1)
{
R1 = 0;
SBUF 入队;
}
}
unsigned char Buf[NBYTE - 2]; // 串口接收数组,协议最大字节为 NBYTE(包括两个包头字节)
//串口处理函数,放在 main() 函数 while 循环的任意位置
unsigned char ReadSerial(unsigned char *cp)
{
unsigned char i;
unsigned char temp, Sum;
temp = 队列中数据个数;
if(temp < (NBYTE)) // 还没接收完成(一个数据包)
return 0;
出队 temp; // 从队列中取出一个字节
if(temp != STARTBYTE1) // 判断是否为队列首字节
return 0;
出队 temp; // 从队列中取出一个字节
if(temp != STARTBYTE2) // 判断是否为队列次首字节
return 0;
出队 temp;
Sum = STARTBYTE1 + STARTBYTE2; // 首字节的校验和
for(i = 0; i < NBYTE - 3; i++)
{
出队 temp;
*cp++= temp; // 将出队的数据存入串口缓存数组
Sum += temp; // 计算校验和
}
出队 temp; // 取出校验值
Sum += temp;
if(Sum != 0) // 校验不通过
return 0;
return 1; // 成功返回1
}
队列的方法可以降低中断执行时间,提高系统实时性。
原文示例代码:【代码注释为个人理解,仅供参考】
// 串口中断中只完成数据入列
void comm(void) interrupt 4
{
OS_INT_ENTER(); // 进入临界区
if(R1 == 1)
{
R1 = 0;
OSIntSendSignal(RECIVE_TASK_ID); // 发送一个信号(表示接收到的串口数据)
}
OSIntExit(); // 退出临界区
}
void Receive(void)
{
unsigned char temp, temp1, Sum, i;
OSWait(K_SIG, 0); // 等待串口中断发送信号
temp = SBUF; // 读取串口数据
while(1)
{
while(1)
{
OSWait(K_SIG, 0) // 等待串口中断发送信号
temp1 = SUBF; // 读取串口数据
if((temp == STARTBYTE1) && (temp1 == STARTBYTE2)) // 判断包头数据
break;
temp = temp1;
}
Sum = STARTBYTE1 + STARTBYTE2; // 计算校验和
OSWait(K_SIG, 0); // 等待串口中断发送信号
temp = SBUF; // 读取串口数据,存入 tmep
for(i = 0; i < NBYTE - 3; i++)
{
OSWait(K_SIG, 0);
temp1 = SBUF; // 读取串口数据,存入 temp1
if((temp == STARTBYTE1) && (temp1 == STARTBYTE2))
{
OSWait(K_SIG, 0)
temp = SBUF;
i = -1; // 重新开始循环
Sum = STARTBYTE1 + STARTBYTE2;
continue;
}
Buf[i] = temp; // 将 temp 存入串口接收数组
Sum += temp; // 更新校验和
temp = temp1; // 保存 temp1,下个循环再处理
}
Sum += temp1; // 加上校验值,获取校验和
if(Sum == 0)
OSSendSignal(GET_RIGHT_TASK_ID); // 串口命令处理完成,发送指定信号。
}
}
引入操作系统,那肯定是为了方便任务调度和提高系统实时性,也就是能在处理其他事情的同时,处理串口接收功能。
以下内容参考原文
如果串口数据发送得太快,第一种方法和第三种方法都会丢弃以前的数据,第二种方法则丢弃后面收到的数据。而且由于第二种方法是先获取整个包,所以在处理一个数据包期间(出现丢包)无法继续处理下一个包,也就是数据处理和接收时串行的关系,而第一种方法和第三种方法数据接收和处理为并行关系。
RTOS 使得实时应用程序的设计和扩展变得容易,不需要大的改动就可以增加新的功能。通过应用程序吧设计分割为若干独立的任务,RTOS 使得应用程序的设计过程大为简化。