《时间触发嵌入式操作系统》这本书已经有一些年份了,他是以古老的51单片机(8051)作为控制器讲解的,本书涉及到一些硬件基础知识,这些可能在平时开发中已经熟悉,软件部分则介绍了时间触发的嵌入式系统的设计和原理。

本章主要概述了几种系统:

  • 信息系统:这类系统主要用来处理大数据,使用到了数据库
  • 桌面应用程序:这类系统主要用以开发桌面应用,电脑端的程序。
  • 实时系统:及时响应,典型应用飞机等需要立即响应的工程
  • 嵌入式系统:至少包含一个MCU的系统,各种家用电器,如录像机,微波炉等。
  • 时间触发系统:关注时间,固定的时间执行任务的调度
  • 事件触发系统:关注事件,事件发送时候触发事件处理

它们之间存在着重叠,比如本书的核心,时间触发嵌入式系统,它则属于时间触发系统,也属于嵌入式系统。

第一章还介绍了中断的概念。


什么是设计模式

假如用户使用51单片机来产生一个1Hz的PWM,那么这个程序是简单的,只需要设计以下函数:

void pwm_gera()
{
	io_pwm = !io_pwm;
	pwm_delay_ms(500);
}

假如用户使用51单片机设计一个按键与蜂鸣器的程序,功能是当按键按下时,蜂鸣器鸣叫1s,那么这个程序也是简单的:

void beep_btn_ctrl(void)
{
    if(io_btn == 0)                  //按键按下时候,io_btn 的值为 0,否则为 1
    {
    	io_beep = 1;
    	beep_delay_s(1);
    	io_beep = 0;
    }
}

接着,如果需要将这两个功能合在一起,简单的顺序调用以上两个函数,会遇到一些问题。如下所示:

void main(void{
	while(1)
	{
		pwm_gera();
		beep_btn_ctrl();
	}
}

那么,当程序运行的一开始, pwm 的输出是正常的,但是当按键按下时候,单片就无法产生正确的波形了,原因是按键按下以后,会调用 beep_delay_s(1) 语句,此时的 单片机将在此停留 1s 的时间。这个程序还有一个问题,用户会感觉到按键不够灵敏,这是因为 pwm_delay_ms(500) 的原因。

所以,以上程序有非常大的极限性。于是,我们需要使用这种新的程序结构,来避免这种问题,博主比较常使用的是以下程序结构:

void main(void)
{
    static int ms_cnt = 0;
	
	pwm_gera(ms_cnt);
	beep_btn_ctrl(ms_cnt);

	delay_ms(1);
	ms_cnt++;
}

这个程序保证每 1ms 都运行一次 pwm_gera()beep_btn_ctrl() 函数,并将 ms_cnt 传给别它们,这样做的好处就是,函数内部不需要去延时等待,只是根据 ms_cnt 就可以来判断动作——对于 pwm_gera() 只需要判断 ms_cnt 的值是否是 500 的整数倍,如果是就取反,这样就实现了每 500ms 翻转一次的效果,而对于 beep_btn_ctrl() ,只需要在按键按下的时候,打开蜂鸣器,并记录那个数值,之后不断的和 ms_cnt 对比,如果ms_cnt 比记录的值大1000,就关闭蜂鸣器即可。

这类型的代码,可以适用于这种多个功能需要延时的函数,按博主的理解,这种可以适用于某一类型问题的程序结构,被称为 设计模式,当然,不同的系统有不同的需求,所以有非常多的设计模式,特别的,由于不同的编程语言有自己的语法和规则,设计模式也未必是通用的,但是设计模式的思想,总是可以互相借鉴的。


从第三章开始,一直到第八章,讲的是51单片机硬件特性相关的内容:

  1. 8051 系列微控制器
  2. 振荡器硬件
  3. 硬件复位
  4. 存储器问题
  5. 直流负载驱动
  6. 交流负载驱动

这 6 章 比较针对性单片机所需要的电路,是一些非常实用的基础知识。这里略过。
第 9 章开始,讲述软件相关的内容。

超级循环

前文中,我们就是用了超级循环的程序结构,超级循环是一个简单,粗暴的死循环:

void main(void)
{
	hw_init();         //初始化
	while(1)
	{
		statement1;
		statement2;
	}
}

对于一个非常简单的功能,我们可以使用这个功能,它不断的执行 statement1statement2 ,有的程序员,也把 while(1) 写作 for(;;)

头文件

一段简单的真实的 51 程序,它控制一个 led 灯以 1 Hz 的频率闪烁:

#include <reg52.h>
#include <intrins.h>

sbit led = P2^1;

void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 22;
	j = 3;
	k = 227;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main(void)
{
    Delay500ms();
    while(1)
    {
        led = 0;
        Delay500ms();
        led = 1;    
        Delay500ms();
    }
    return;   
}

在 第 1,2 行中,我们使用到了头文件,头文件规定了各个寄存器的地址等,如果没有这个头文件,那么程序需要使用到51寄存器则需要在C文件中定义。一些脚本,比如 python 生成的文件,也可能是作为头文件,这样就可以和用户编写的代码区分开来。头文件是经常使用的,它的作用其实不言而喻。


第10章到第12章是51单片机常用的功能:
10. 使用端口:其实就是 IO 口的使用
11. 延时 :软件延时(常见的使用for循环来模糊延时),硬件延时(定时器延时)
12. 看门狗:分内部和外部看门狗

第 13 章开始,进入了本书的核心主题:调度器

合作式调度器

前文中,方波产生器和按键控制蜂鸣器的例子体现出了设计模式的必要性,本书所实现的调度器是非抢占式的,也就是一个任务执行完成以后,才会执行另一个任务,所以有一个重要的准则:每个任务执行的实际不能大于调度的时间。对于这一点,我们可以用状态机,超时机制,多级任务来将长任务(时间比较长的任务),拆分成短时间任务来规避这个问题。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐