1. 嵌入式软件概述
1.1. 嵌入式软件开发路线图
- 系统越来越复杂,嵌入式系统的软件开发逐渐成为软件专家的工作。
- 了解电子硬件,愿意做一些与硬件密切相关的工作。
- 近10-15年来,随着嵌入式软件规模和复杂性的爆炸性增加,专门的软件开发团队进入了视野。软件团队正在成长,但这种增长不仅仅是因为完成所需的代码。嵌入式软件团队拥有不同领域的专家,包括网络、家庭界限设计、硬件设备、传统嵌入式软件设计等。
1.2. 嵌入式软件与桌面软件的对比
1.2.1. 内存
编程语言的选择和开发工具的使用有限
1.2.2. CPU处理能力
考虑成本和功耗,ES必须采用保守的设计案例,其中CPU通常只是为了满足要求。
1.2.3. 操作系统
OS(Windows/Linux)、RTOS、专有OS或裸机
1.2.4. 实时性
- 实时系统不会运行得很快,但它必须是可预测的。通常的术语称为确定性
- 对实时要求OS的选择和程序设计都有影响
1.2.5. 开发流程
- ES软件开发往往没有足够的资源。交叉开发对工具的选择影响很大。
- 开发过程也不同。编辑/编译/调试的周期是相同的,但执行程序的步骤往往是复杂的需要将代码转移到目标机器或在某种环境下运输。
1.2.6. 执行流程
大多数嵌入式设备从启动开始就运行某个程序,该程序将直接运输到系统关闭。该程序可以存储在ROM,也可能是从易失性存储器转移到RAM中执?。
1.2.7. 每个嵌入式设备都不一样
- 可能是技术层:不同的CPU架构、内存、外设、应?程序和操作系统
- 也可能是商业运营层,如机械开发和生产的商业模式与核磁共振扫描仪完全不同。
1.2.8. 嵌入式软件工具和组件
- 与桌面软件开发样品,嵌入式软件程师需要运输工具来构建和测试软件。同时,它也可能引用重型软件组件
1.2.9. 开发工具
- 从表上看,嵌入式软件的开发过程与桌面软件非常相似:编译代码模块,然后在调试器的控制下执行。然而,它并不那么简单,而且有明显的区别。
- 嵌入式软件的开发通常使用交叉编译器。
- 有时嵌入式应用程序直接包含部分汇编代码:考虑性能。
- 嵌入式链接器除了集成多个对象模块和函数库例程外,还负责在内存中正确定位代码和数据。嵌入式系统的内存映射分为复杂和严格的精度要求。链接器必须能够灵活地满足这些要求。
- 嵌入式调试器不是单个组件,而是对应不同运输环境的系列工具。通过在本地或指令集模拟器中运输代码,代码只能在宿主机上运输。调试器也可以连接到一个标板(JTAG或其他类型,如以太?),并执?代码。
- 典型的选项包括分析实时性能和代码执行时设备功耗的分析器。
1.2.10. 软件组件
- 最简单易重的软件组件是库。部分库由编译器提供,嵌入式编译器提供的库应适用于嵌入式环境:可重存储ROM中。(不能用printf:会有问题)
- 其他可库,特别是对于C 如标准模板库STL。这些库可能不适合嵌入式应,所以在使用时要多加。
- 与硬件交互的难点。OS内部设备驱动提供了这样的连接
- ES不断提及互联性,需要不同的网络持有,TCP/IP或总线CAN\I2C,其它网络技术SNMP、Zigbee、Bluetooth、WiFi和USB。最重要的是检查有效性,确保符合标准,并与其他系统互联。
- 嵌入式设备需要有组织的数据存储,如电源故障和多线程。
- LCD(通常是触摸式的)降低成本,使其能够应嵌入式设计。于是软件迎来了挑战:持有复杂的图像和户界。
2. 软件如何影响硬件设计?
2.1. 谁设计硬件?
- 嵌入式工程师可能同时负责软件和硬件
- 更有可能有两个团队:少数掌握这两个知识的工程师
- 硬件的决定可能会对软件产生持久的影响
2.2. 平衡软件/硬件
2.3. 调试硬件
2.4. 自检支持
- I/O电路:"回送"可能有用
- 板载开关:适用于配置或模式选择
- 状态显示:向用户提供额外信息
- 也许是字符或者只是LED
- LED至少有三种状态:开关、闪烁
3. 嵌入式系统软件架构
3.1. 软件分层
3.2. 嵌入式系统的可视化程序模型
3.3. 为什么要为实时系统建立模型?
- 辅助测试和完善最终系统
- 更重要的是,该模型利用其所知的系统属性来描述整个系统,并可以进一步研究系统特性
- 实时工程师利用程序模型开发软件和硬件,从而充分考虑整个实时系统
- 模型允许程师从满系统的性能和功能需求中预测程序的运输
3.4. 模型之间的差异
- ?有些模型容易编写,但调试不容易
- 很难写,但调试很容易
- ?有些模型使程序运输更快,但需要付出更多内存资源消耗的代价
- ?一些鲁棒性能更好,但维护简单的模型
3.4.1. 单线程序模型的优缺点
- 优点
- 编程和再编程通常快速简单
- 在改变系统响应特性的同时,也很容易在模型上添加新的功能插件
- 缺点
- 在于应领域的限制
- 很难安全地重编程
- 很难去不同或不同环境的运输系统。
3.4.2. 多线程序模型的优缺点
- 优点
- 允许将系统划分为逻辑阶段,然后编写相互独立的程序来处理各种工作。
- 所有处理过程。
- 如果需要更多的吞吐量,程师可以在任务中引入新的通信和合作模式
- 缺点:资源竞争的可能性,即必须付出代价
3.5. 嵌入式软件系统结构概述
- 轮询
- 有限状态机
- 带中断的轮询
- 仅有中断
- 功能队列调度
- 实时操作系统(RTOS)
3.5.1. 轮询
//Round Robin / Control Loop
//Everything is a function call from the main loop
void main(void) {
while(TRUE) {
if (device_A requires service)
service device_A
if (device_B requires service)
service device_B
if (device_C requires service)
service device_C
... and so on until all devices have been serviced, then start over again
}
}
- 可确定性:如果有任务不能确定时间,会导致其他所有的任务都被影响到。而这不符合嵌入式的实时性要求。
- 轮询的特点:
- 优先级:无,一切按顺序进行。
- 响应时间:所有任务的总和。
- 变更的影响:重大。改变执行任务时间或添加任务会影响所有其他任务。
- 简单,没有共享数据问题:线性并行执行
3.5.1.1. Arduino - 按下按钮时打开LED
const int LED = 13; // the pin for the LED
const int BUTTON = 7; // the input pin where the
// pushbutton is connected
int val = 0; // val will be used to store the state
// of the input pin
void setup() {
pinMode(LED, OUTPUT); // tell Arduino LED is an output
pinMode(BUTTON, INPUT); // and BUTTON is an input
}
void loop(){
val = digitalRead(BUTTON); // read input value and store
it
// check whether the input is HIGH (button pressed)
if (val == HIGH) {
digitalWrite(LED, HIGH); // turn LED ON
}else{
digitalWrite(LED, LOW);
}
}
3.5.1.2. Arduino - Watchdog
#include <avr/wdt.h>
#define TIMEOUT WDTO_8S // predefine time, refer avr/wdt.h
const int ledPin = 13; // the number of the LED pin
void setup(){
// disable the watchdog
//wdt_disable();
pinMode(ledPin,OUTPUT);
// LED light once after start or if timeout
digitalWrite(ledPin,HIGH);
delay(1000);
// enable the watchdog
wdt_enable(TIMEOUT);
}
void loop(){
// process runing
digitalWrite(ledPin,LOW);
delay(9000); //if timeout trig the reset
//feed dog
wdt_reset();
}
3.5.2. 有限状态机
while(1) {
switch(state) {
case IDLE:
check_buttons();
LEDisplay_hex(NUM1);
if (BUTTON1 | BUTTON2 | BUTTON3)
state=SHOW;
break;
case SHOW:
NUM1=0;
if (BUTTON1) NUM1 += 0x0001;
if (BUTTON2) NUM1 += 0x0010;
if (BUTTON3) NUM1 += 0x0100;
state=IDLE;
break;
}
}
- 与轮询类似,但只有当前状态被执行.
- 每个状态决定下一个状态(非顺序执行)。
- 优先级:每个状态确定下一个状态的优先级。
- 响应时间:所有任务的总和。
- 变更的影响:重大。更改任务的执行时间或添加任务会影响所有其他任务。
- 简单性:没有共享数据问题。
3.5.3. 带有中断的轮询
BOOL flag_A = FALSE; /* Flag for device_A follow-up processing */
/* Interrupt Service Routine for high priority device_A */
ISR_A(void) {
... handle urgent requirements for device_A in the ISR,
then set flag for follow-up processing in the main loop ...
flag_A = TRUE;
}
void main(void) {
while(TRUE) {
if (flag_A)
//中断任务优先
flag_A = FALSE
//... do follow-up processing with data from device_A
if (device_B requires service)
service device_B
if (device_C requires service)
service device_C
//... and so on until all high and low priority devices have been serviced
}
}
- 优先级-中断优先于主循环:优先中断
- 响应时间:所有任务的总和以及中断执行时间
- 更改的影响:对于中断服务程序而言意义不大。与轮询作为主循环相同。
- 共享数据-必须处理与中断服务程序共享的数据
- MCU中使用最多的
3.5.4. 仅有中断
SET_VECTOR(P3AD, button_isr);
SET_VECTOR(TIMER1, display_isr);
while(1) {
;
}
- 如果ISR过多,可能会有问题
- 如果高优先级的中断执行所需的时间比低优先级的中断所需的时间长,那么某些中断将被错过,或者您需要处理嵌套中断。
- 优先级-中断优先
- 响应时间:中断执行时间
- 变化的影响-对于中断服务程序而言意义不大。
- 共享数据-必须处理与中断服务程序共享的数据
3.5.5. 功能队列调度
- 函数指针被添加到队列中。
- 主循环在队列中循环并执行任务。
- 任务或中断将新任务添加到功能队列中。
- 最坏的时机
#define MAX_TASKS 20
typedef int(*FuncPtr)();
FuncPtr tasks[MAX_TASKS]
int current_task = 0;
void add_task(FuncPtr func) {
int n;
for(n=current_task+1;n<MAX_TASKS-1;n++) {
if(tasks[n]==NULL) {
tasks[n]=func;
return;
}
}
for(n=0;n<current_task;n++) {
if(tasks[n]==NULL) {
tasks[n]=func;
return;
}
}
id display_task() {
LEDisplay_hex(NUM1);
add_task(button_task);
}
void button_task() {
check_buttons();
NUM1=0;
if (BUTTON1) NUM1 += 0x0001;
if (BUTTON2) NUM1 += 0x0010;
if (BUTTON3) NUM1 += 0x0100;
add_task(display_task);
}
main() {
LEDisplay_init();
LEDisplay_clear();
init_buttons();
add_task(button_task);
while(1) {
if(tasks[current_task]==NULL) {
;
}
else {
(*tasks[current_task])();
tasks[current_task]=NULL;
}
current_task++;
if(current_task>=MAX_TASKS) current_task=0;
}
}
- 优先级:中断优先。任务按顺序执行
- 响应时间:最长任务的执行时间
- 变更的影响:低。中断管理优先级功能。队列管理较低的优先级。
- 共享数据:必须处理与中断服务程序共享的数据
3.5.5.1. 函数队列优化
// 添加时间队列
typedef int(*FuncPtr);
typedef struct {
long timer;
int status;
FuncPtr;
} Task;
Task task_list[MAX_TASKS];
// 添加任务优先级
typedef int(*FuncPtr);
typedef struct {
int priority;
FuncPtr;
} Task;
Task task_list[MAX_TASKS];
3.5.6. 抢占式多任务或多线程
- 在这种类型的系统中,低级代码段基于计时器(连接到中断)在任务或线程之间切换。这是通常认为系统具有"操作系统"内核的级别。根据所需的功能量,它或多或少地引入了管理在概念上并行运行的多个任务的复杂性。
- 要访问共享数据,必须通过某种同步策略来控制,例如消息队列,信号量或非阻塞同步方案。
- 由于这些复杂性,组织通常使用实时操作系统(RTOS),从而使应用程序程序员可以专注于设备功能而不是操作系统服务。
3.6. 内核分类
3.6.1. 实时内核
3.6.2. 微内核
- 微内核是从实时操作系统升级的逻辑选择,通常的安排是操作系统内核分配内存并将CPU切换到不同的执行线程。 用户模式进程实现主要功能,例如文件系统,网络接口等。
- 通常,微内核在任务切换和任务间通信快速时成功,而在速度慢时失败。
3.6.3. 单内核
- 具有复杂功能的相对较大的内核适用于嵌入式环境。
- 示例:嵌入式Linux和Windows CE
- 尽管硬件成本增加,但这种嵌入式系统却越来越受欢迎,尤其是在功能更强大的嵌入式设备(例如无线路由器和GPS导航系统)上。原因如下:
4. Real time operating systems (RTOS) 实时操作系统
4.1. 整体操作系统模型(和Linux相似)
4.2. 操作系统
- 操作系统控制资源:
- 谁获得CPU;
- I / O何时发生;
- 分配了多少内存。
- 流程如何通信。
- 最重要的资源是CPU本身:由调度程序控制的CPU访问。
4.3. RTOS and GPOS
- 相似的功能
- 多任务级别
- 软件和硬件资源管理
- 为应用提供基本的OS服务
- 从软件应用抽象硬件
4.4. RTOS从GPOS中分离出来的不同功能
- 嵌入式应用上下文中具有的更好的可靠性
- 满足应用需要的裁剪能力
- 更快的特性
- 减少内存需求
- 为实时嵌入式系统提供可裁剪的调度策略
- 支持无盘化嵌入式系统,允许从ROM或RAM上引导并运行
- 对不同硬件平台具有更好的可移植性。
4.5. Commercial RTOSs (partial)
4.6. Real-time Operating System (RTOS),实时操作系统
- 为什么要实现实时操作系统?
- 灵活性
- 响应时间
4.6.1. 基本单位
- 实时操作系统的基本组成部分是进程。
- 由于没有主循环,因此可以轻松添加新进程或删除过时的进程。
- RTOS根据进程的优先级安排运行时间。
4.6.2. 调度程序
- RTOS的调度程序用来跟踪每个进程的状态,并确定应该运行的进程。
- 调度基本单位是进程,调度是通过有限状态机完成的
4.7. 时序约束
- 执行计算以符合外部时序约束。
- 截止时间频率
- 周期性
- 非周期性的
- 截止时间类型:
- 硬性:未能按时完成会导致系统故障
- 软性:未按时完成进程会导致响应降低
- 偶发进程:具有一定时钟周期的进程
4.7.1. 进程时序的规范
- 释放时间:进程处于就绪态的时间
- 截止时间:进程必须结束的时间
4.7.2. 释放时间和截止时间
- 非周期性的进程
- 紫色表示占用CPU的时间,紫色右侧是结束时间
- 周期性进程
- 截止时间默认和周期时间相同
- 周期性进程
- 比较复杂,时序的截止时间小于其周期性
4.8. 进程速率要求
- 周期:进程激活之间的间隔。
- 速率:周期的倒数。
- 启动速率可能高于周期:一次运行多个副本。
4.9. 时间冲突
- 如果流程没有在截止时间之前完成,会发生什么?
- 硬截止时间:如果错过了系统就会失败。
- 软截止时间:用户可能会注意到,但系统不一定会出现故障。
- 我们希望通过自己的调度算法来尽可能避免这种问题
4.10. 示例:航天飞机软件错误
航天飞机的首次发射由于软件计时错误而延迟:
- 主控制系统PASS和备用系统BFS。
- BFS无法与PASS同步。
- 更改为一个例行增加的延迟,这会延迟开始时间的计算。
4.11. 进程执行特征
- 进程执行时间 T i T_i Ti
- 没有抢占的执行时间。
- 可能的时间单位:秒、时钟周期
- 在某些情况下,最坏、最好情况的执行时间可能会有用。
- 变化来源:
- 数据依赖性。
- 内存系统。
- CPU管道。
- 主备冗余、多机热备
4.12. 利用率
- CPU利用率
- CPU执行有用的工作的比率
- 通常在没有调度开销的情况下进行计算
U = C P U t i m e f o r u s e r f u l w o r k t o t a l a v a i l a b l e C P U t i m e = ∑ t 1 ≤ t ≤ t 2 T ( t ) t 2 − t 2 = T t U = \frac{CPU\ time\ for\ userful\ work}{total\ available\ CPU\ time} \\ \ \\ = \frac{\sum\limits_{t_1\leq t \leq t_2}T(t)}{t_2 - t_2} \\ \ \\ = \frac{T}{t} U=total available CPU timeCPU time for userful work =t2−t2t1≤t≤t2∑T(t) =tT
4.13. 过程状态
流程可以处于以下三种状态之一:
- 在CPU上执行;
- 准备执行
- 等待数据。
4.14. 调度问题
- 我们可以在所有截止日期之前完成吗?在所有情况下都必须能够按时完成进程。
- 我们需要多少个CPU才能按时完成进程?
4.14.1. 嵌入式与通用调度
- 调度工作尝试避免使CPU访问过程变得空闲:公平=访问CPU。
- 嵌入式系统必须符合所有截止时间:低优先级进程可能不会长时间运行。
4.14.2. 操作系统进程管理
- 操作系统需要跟踪:流程优先级、时序状态、进程激活记录
- 可以创建进程:系统启动前静态、在执行期间动态:比如电话
4.14.3. 多道程序操作系统
- 进程:程序的唯一执行者
- 代码 + 数据
- 多进程可能会共享代码
- 每一个进程都会有唯一的数据(CPU寄存器,栈空间、内存空间)
- 进程会被他们的激活记录定义
- 线程有他们自己CPU寄存器值,但是共享相同的内存空间,所以他们可以通过其他线程来影响数据
- 一个进程可能有不同的线程
- 线程可能会运行在划分出来的CPU核心栈上
4.14.4. 典型的进程/任务激活记录(任务控制块)
- 任务ID
- 任务状态(运行,就绪,已阻止)
- 任务优先级
- 任务起始地址
- 任务堆栈
- 任务CPU寄存器
- 任务数据指针
- 任务时间
4.14.5. 什么时候可以分派新线程?
- 在非抢占式调度中:当前线程完成时可以分派新线程
- 在抢先式调度下:
- 在计时器中断时
- 在I/O中断时可能会分派新县城
- 创建新线程或完成一个新线程时
- 当前线程阻塞或释放互斥锁时
- 当前线程阻塞信号量时
- 更改信号量状态时
- 当前线程进行任何OS调用时
- 文件系统访问
- 网络访问
4.14.6. 当前的焦点:如何决定要安排哪个线程?
- 需要关注的内容有
- 抢先式与非抢先式调度
- 定期与非定期任务
- 固定优先级与动态优先级
- 优先反转异常
- 其他计划异常
4.14.7. 指标(我们如何评估调度策略)
- 能够满足所有截止时间。
- CPU利用率-有效工作的时间百分比。
- 计划开销:制定计划所需的时间。
- 延迟
- 总完成时间。
4.15. 抢占式调度
- 假设所有线程都有优先级
- 静态分配的(在线程持续时间内恒定)
- 动态分配的(可能有所不同)
- 进一步假设内核跟踪"启用"了哪些线程(能够执行,例如在等待信号量或互斥量或时间到期之前不会被阻塞)。
- 抢占式调度:
- 在任何时候,具有最高优先级的已启用线程正在执行。
- 每当任何线程更改优先级或启用状态时,内核都可以分派新线程。
4.16. 速率单调调度(RMS, Rate Monotonic Scheduling)
- 假设使用以下命令定期调用n个任务:
- 周期T1,…,Tn(施加实时约束)
- 所有任务都是独立的。
- 最坏情况执行时间(WCET)C1,…,Cn:假定没有互斥请锁、信号量或阻塞的I/O
- 没有优先级限制
- 固定优先级
- 上下文切换所需的时间可以忽略不计
- 抢占式调度
- 定理:如果任何优先级分配产生可行的时间表,则按时段排序的优先级(最小时段具有最高优先级)也产生可行的时间表。
- 就可行性而言,RMS是最佳的。
4.16.1. RMS的灵活性
- 为RMS定义的可行性是指每个任务在其指定时间内执行一次完成。
4.16.2. 显示RMS的优越性:考虑两个不同周期的任务
非抢占式时序是否可行? | 非抢占式的时间表是不可行的。红色任务的某些实例(2)如果我们进行非抢占式调度,则不会在其期限内完成。 |
---|---|
如果我们对红色任务有优先级更高的抢占式调度该怎么办? | 具有较高优先级的红色任务的抢占式调度是可行的。请注意,抢占蓝色任务会延长其完成时间。 |
4.16.3. 显示RMS的优越性:任务调整
- 当低优先级任务的开始阶段与高优先级任务的开始阶段匹配时,其完成时间最差。
- 因此,在检查计划的可行性时,仅考虑最坏的情况就足够了:所有任务都同时开始其周期,如下就是最差情况
4.16.4. 显示RMS的优越:用于两个任务
- 足以表明,如果非RMS计划是可行的,则RMS计划是可行的。
- 考虑以下两个任务:
- 非RMS固定优先级时间表如下所示:我们可以观察到对于非RMS时序如果可行当且仅当 C 1 + C 2 ≤ T 2 C_1 + C_2 \leq T_2 C1+C2≤T2
- RMS时间表如下所示:周期较短的任务会提前移动,而对于RMS是可行的
4.16.5. 总结
- 该证明可以扩展到任意数量的任务(尽管它变得更加乏味)。
- 该证明仅具有最优性、可行性。它没有说其他最优标准。
- 实际实施:
- 定时器中断在周期的最大公约数
- 多个计时器
4.16.6. RMS的可行性调度分析
- 可调度性分析:如果需要,可以使用RM调度一组定期任务
∑ i = 1 n ≤ n ( 2 1 n − 1 ) \sum\limits^n\limits_{i=1} \leq n(2^{\frac{1}{n}} - 1) i=1∑n≤n(2n1−1)
- 此条件足够,但不是必需的。
- U = ∑ i = 1 n C i T i U = \sum\limits_{i=1}^{n}\frac{C_i}{T_i} U=i=1∑nTiCi,表示处理器利用率因子U,它是执行任务集所花费的处理器时间的一部分。
4.17. DDL驱动时序
4.17.1. Jackson’s Algorithm: EDD,Earliest Due Date (1955)
- 给定n个具有期限d1,…,dn的独立一次性任务,对它们进行调度以最大程度地减少最大延迟,定义为
L m a x = max 1 ≤ i ≤ n { f i − d i } L_{max} = \max\limits_{1 \leq i \leq n}\{f_i - d_i\} Lmax=1≤i≤nmax{ fi−di}
- 其中 f i f_i fi是任务i的完成时间。请注意,如果所有截止日期都得到满足,则这是负面的。
- 最早到期日期算法:按不递减的截止日期顺序执行它们。
- 请注意,这不需要抢占。
4.17.1.1. 定理
从最大延迟的意义上讲,EDD是最佳的
- 为了证明这一点,请使用互换参数。如果给定的时间表S不是EDD,则必须存在任务a和b,其中时间表中a紧接在b之前,但 d a d_a da> d b d_b db<