购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.3 第一个样例工程

为了更好地理解实时操作系统下的编程,基于同样的程序功能,分别通过NOS工程和Mbed OS工程来进行编程实现。

3.3.1 样例程序功能

样例程序的硬件是红、绿、蓝三色一体的发光二极管(小灯),由三个GPIO引脚控制其亮暗。

软件控制红灯每5s、绿灯每10s、蓝灯每20s变化一次,对外表现为三色灯的合成色,经过分析,三色灯的实际效果如图3-2所示,即开始时为暗,依次变化为红、绿、黄(红+绿)、蓝、紫(红+蓝)、青(蓝+绿)、白(红+蓝+绿),周而复始。

img

图3-2 三色灯的实际效果

3.3.2 工程框架设计原则

良好的工程框架是编程工作的重要一环,建立一个组织合理、易于理解的嵌入式软件工程框架需要较深入的思考与斟酌。

所谓工程框架是指工程内文件夹的命名、文件的存放位置、文件内容的分置规则。软件工程与一件建筑作品、一幅画作等是一致的,软件工程框架是整个工程的脊梁,其主要线程不是完成一个单独的模块功能,而是指出工程应该包含哪些文件夹,这些文件夹里面应该放置什么文件,各个文件的内容又是如何定位等。

因此,工程框架设计的基本原则应该是分门别类,各有归处,建立工程文件夹,并考虑随后内容安排及内容定位,建立其子文件夹。

人们常常可以看到一些工程框架混乱、子文件夹命名不规范、文件内容定位不清晰、文件包含冗余的样例工程,把学习者与开发者弄得一头雾水,这样的工程框架是不符合软件工程要求的。甚至在一些机构给出的底层驱动中,包含不少操作系统的内容,违背了底层驱动设计独立于上层软件的基本要求,一旦更换操作系统,该驱动难以使用,给软件开发工程师带来烦恼。

3.3.3 无操作系统的工程框架

1.无操作系统的工程框架的树形结构

表3-2给出了无操作系统的工程框架的树型结构,并对MCU文件夹、用户板文件夹、应用程序文件夹等进行了补充说明。

表3-2 无操作系统的工程框架的树型结构及补充说明

img

(1)MCU文件夹。把链接文件、MCU的启动文件、MCU底层驱动(MCU基础构件)放入这个文件夹中,分别建立linker_file、startup、MCU_drivers三个子文件夹。linker_file文件夹内的链接文件,给出了芯片存储器的基本信息;startup文件夹含有芯片的启动文件;MCU_drivers存放与MCU硬件直接相关的基础构件。

(2)用户板文件夹。开发者选好一款MCU,要做成产品总要设计自己的硬件板,这就是用户板。这个板上可能有LCD、传感器、开关等,这些硬件必须由软件干预才能工作,干预这些硬件的软件构件被称为应用构件。应用构件一般需要调用MCU基础构件,它被放置在该文件夹中。

(3)应用程序文件夹。其包含总头文件(includes.h)、中断服务程序源程序文件(isr.c)、主程序文件(main.c)等,这些文件是软件开发工程师进行编程的主要对象。总头文件includes.h是isr.c及main.c使用的头文件,包含用到的构件、全局变量声明、常数宏定义等。中断服务程序文件isr.c是中断处理函数编程的地方。主程序文件main.c是应用程序启动后的总入口,main函数即在该文件中实现。main函数包含一个永久循环,对具体事务过程的操作几乎都是添加在该主循环中的。应用程序的执行有两条独立的线路,一条是主循环运行路线,在main.c文件中编程;另一条是中断线,在isr.c文件中编程。若有操作系统,则可在main.c中启动操作系统调度器。

(4)此外,编译输出还会产生Debug文件夹,含有编译链接生成的.elf、.hex、.list、.map等文件。

.elf(Executable and Linking Format),即可执行链接格式,最初由UNIX系统实验室(UNIX System Laboratories,USL)作为应用程序二进制接口(Application Binary Interface,ABI)的一部分而制定和发布的,其最大的特点在于它有比较广泛的适用性,通用的二进制接口定义使之可以平滑地移植到多种不同的操作环境中。UltraEdit软件工具可查看.elf文件内容。

.hex(Intel HEX)文件是由一行行符合Intel HEX文件格式的文本所构成的ASCII文本文件。在Intel HEX文件中,每一行包含一个HEX记录,这些记录由对应机器语言码(含常量数据)的十六进制编码数字组成。

.list文件提供了函数编译后机器码与源代码的对应关系,用于程序分析。

.map文件提供了查看程序、堆栈设置、全局变量、常量等存放的地址信息。.map文件中给出的地址在一定程度上是动态分配的(由编译器决定),工程有任何修改,这些地址都可能发生变动。

2.无操作系统的样例工程的main函数及isr函数

基于无操作系统的样例工程(见“...\04-Software\CH3.3.3-NOS”文件夹)可用AHL-GECIDE打开。该程序有两条执行路线,一条是主循环线,为了衔接操作系统概念,可称为线程线;另一条为中断线,分别对应main.c中的for循环,以及isr.c中的中断处理程序。

线程线:程序通过判断全局变量gSec(秒累加变量)来控制三色小灯的开关状态,实现红灯每5s闪烁一次、绿灯每10s闪烁一次、蓝灯每20s闪烁一次,同时通过串口输出开关信息。

中断线:定时器TIM2定时周期为1s,时间每经过1s,TIM2会触发定时器的中断服务程序,在中断服务程序中对变量gSec进行累加。

程序运行时先执行线程线,在执行线程线的过程中若触发定时器中断(即计时时间达到1s),程序便会从线程线跳转到中断线执行定时器中断服务程序(实现对gSec的累加)。当中断服务程序执行完成后,程序会回到线程线从刚才跳转的地方继续执行下去。

1)线程线:main函数

可以从main函数起点开始理解执行过程 ,在for(;;)的永久循环体内,程序通过判断gSec来控制三色小灯的开关状态。

img
img
img

2)中断线:isr.c中断处理程序

当定时器到达定时时间1s时,会执行定时器中断服务程序。在定时器中断服务程序中,先判断是否由TIM2触发的中断,若是,则对变量gSec累加(即秒加1),清除中断标志位。

img
img
3.无操作系统的样例工程运行测试

将样例工程编译,通过Type-C线将AHL-STM32L431与个人计算机的USB接口进行连接,进入AHL-GEC-IDE中的“下载”→“串口更新”,单击“连接GEC”按钮,连接成功后,导入编译产生的机器码.hex文件,单击“一键自动更新”按钮将程序下载到目标板上,程序将自动运行 。图3-3所示为无操作系统的样例工程下载后图示。随后,观察AHL-STM32L431开发板上的红灯、蓝灯和绿灯的闪烁情况,若与图3-2所示的情况一致,则正确。

img

图3-3 NOS样例工程下载后图示

3.3.4 Mbed OS的工程框架

1.Mbed OS的工程框架的树形结构

Mbed OS的工程框架与无操作系统的工程框架完全一致,不同的地方如下。

(1)在工程的05_UserBoard文件夹中增加了Os_Self_API.h和Os_United_API.h两个头文件。其中,Os_Self_API.h头文件给出了Mbed OS对外接口函数API,如事件(mbed_event)、消息队列(mbed__messagequeue)、信号量(mbed__semaphore)、互斥量(mbed__mutex)等有关函数,实际函数代码驻留于BIOS中;Os_United_API.h头文件给出了实时操作系统的统一对外接口API,目的是实现不同实时操作系统的应用程序可移植,可以涵盖实时操作系统的基本要素函数。

(2)在工程的“...\07_AppPrg\includes.h”文件中,给出了线程函数声明。

img

(3)工程的“...\07_AppPrg\main.c”文件中,给出了操作系统的启动。

img

(4)工程的07_AppPrg文件夹中的threadauto_appinit.c文件是主线程文件,在该文件的main函数中调用OS_start函数启动操作系统。app_init函数是OS_start函数的入口参数,在实时操作系统启动过程中作为主线程的执行函数。因此,main_thread线程也被称为自启动线程,app_init函数被称为自启动线程函数,它的作用是将该文件夹中的其他三个文件中的函数(即thread_redlight、thread_greenlight和thread_bluelight)变成线程函数,被调度运行。

(5)工程的07_AppPrg文件夹中的三个功能性函数文件为thread_bluelight.c、thread_greenlight.c和thread_redlight.c,它们内部的函数由于变成了线程函数,因此可分别称为蓝灯线程、绿灯线程和红灯线程,它们在内核调度下运行。至此,可以认为有三个独立的主函数在操作系统的调度下独立地运行,一个大工程变成了三个独立运行的小工程。

2.Mbed OS的启动

基于Mbed OS的样例工程(见“...\04-Software\CH3.3.4-mbedOS”文件夹),可用AHLGEC-IDE导入。在该样例工程中,共创建了六个线程。表3-3所示为样例工程线程一览表。

表3-3 样例工程线程一览表

img

执行OS_start(app_init)进行Mbed OS的启动,在启动过程中依次创建了主线程(main_thread)、空闲线程(osRtxInfo.thread.idle)和定时器线程(osRtxInfo.timer.thread)。主线程执行函数app_init源代码是在本工程中直接给出的,空闲线程执行函数osRtxIdleThread和定时器线程执行函数osRtxTimerThread被驻留在BIOS中(这一内容将在第8章中进行解释)。

3.主线程的执行过程

1)主线程功能概要

主线程被内核调度先运行,过程概要如下。

(1)在主线程中依次创建蓝灯线程、绿灯线程和红灯线程,红灯线程实现红灯每5s闪烁一次,绿灯线程实现绿灯每10s闪烁一次,蓝灯线程实现蓝灯每20s闪烁一次,创建完这些用户线程之后主线程被终止。

(2)此时,在就绪列表中剩下红灯线程、绿灯线程、蓝灯线程和空闲线程四个线程。

(3)由于就绪列表优先级最高的一个线程是thd_redlight,它优先得到激活运行。thd_redlight线程每隔5000ms控制一次红灯的亮暗状态,当thd_redlight线程调用系统服务delay_ms执行延时,调度系统暂时剥夺该线程对CPU的使用权,将该线程从就绪列表中移出,放入延时列表中。

(4)系统开始依次调度执行thd_bluelight线程和thd_greenlight线程,将线程从就绪列表中移出,根据延时时长将线程放入延时列表中。

(5)当这三个线程都被放入延时列表时,就绪列表中就只剩下空闲线程,此时空闲线程会得到运行。

基于每1ms(时间嘀嗒)的SysTick中断,在SysTick中断处理程序中,查看延时列表中的线程是否到期,若有到期的线程,则将线程从延时列表中移出,并放入就绪列表中。同时,由于到期线程的优先级大于空闲线程的优先级,会抢占空闲线程,通过上下文切换激活,再次得到运行。

由于这蓝、绿、红三个小灯物理上对外表现是一盏灯,所以样例工程的对外表现应该达到图3-2的效果(与无操作系统的样例工程运行效果相同)。

2)主线程源代码剖析

主线程的运行函数app_init主要完成全局变量初始化、外设初始化、创建其他用户线程、启动用户线程等工作,它在07_AppPrg\threadauto_appinit.c文件中定义。

(1)创建用户线程。在threadauto_appinit.c文件中,首先创建三个用户线程,即红灯线程thd_redlight、蓝灯线程thd_bluelight和绿灯线程thd_greenlight,它们的优先级都设置为10 ,堆栈空间设置为512字节。

img
img

(2)启动用户线程。在07_AppPrg文件夹下创建thread_redlight.c、thread_bluelight.c和thread_greenlight.c三个文件,在这三个文件中分别定义三个用户线程执行函数,即thread_redlight、thread_bluelight和thread_greenlight。这三个用户线程执行函数在定义上与普通函数无差别,但是在使用上不是作为子函数进行调用的,而是由Mbed OS进行调度的。此外,这三个用户线程执行函数基本上是一个无限循环,在执行过程中由Mbed OS分配CPU使用权。

img

3)主函数app_init代码注释

img
img
4.红灯、绿灯、蓝灯线程

根据Mbed OS样例程序的功能,设计了红灯thd_redlight、蓝灯thd_bluelight和绿灯thd_greenlight三个小灯闪烁线程,对应工程07_AppPrg文件夹下的thread_redlight.c、thread_bluelight.c和thread_greenlight.c这三个文件。

小灯闪烁线程先将小灯初始设置为暗,然后在while(1)的永久循环体内,通过delay_ms函数实现延时,每隔指定的时间间隔切换灯的亮暗一次。delay_ms函数的延时操作并非停止其他操作的空跑等待,而是通过延时列表管理延时线程,从而实现对线程的延时,延时函数将在4.2节和9.2节中详细介绍。在延时期间,线程被放入延时列表中,实时操作系统可以调度执行其他的线程。下面给出红灯线程函数thread_redlight的具体实现代码,蓝灯线程函数thread_bluelight和绿灯线程函数thread_greenlight与红灯线程函数thread_redlight类似,读者可自行分析。

img
5.Mbed OS样例工程运行测试

测试过程可参照无操作系统的样例工程,从中可以观察到三色灯随时间的变化与图3-2一致。Mbed OS样例工程测试结果如图3-4所示。由此体会无操作系统下编程与实时操作系统下编程的异同点。至此,实时操作系统可以较好地服务于用户程序设计。

img

图3-4 Mbed OS样例工程测试结果 nGHQD1QeMP+5rZvh69GdEcNNdu1Sv9b+OoM9d2zlHqEA6V4VnAIjAFcH4k9yvou9

点击中间区域
呼出菜单
上一章
目录
下一章
×