CMSIS_RTOS学习笔记(一)

最近在学习CMSIS_RTOS,发现网上系统的中文教程很少,遂计划写个系列博客记录下,方便以后查阅,主要将ARM官方的《CMSIS_RTOS_Tutorial》文档进行了翻译和整理。

什么是CMSIS_RTOS

咱先解释下什么是CMSIS,ARM Cortex™ 微控制器软件接口标准(CMSIS:Cortex Microcontroller Software Interface Standard) 是 Cortex-M 处理器系列的与供应商无关的硬件抽象层。多家芯片供应商均采用ARM的处理器,而不同的芯片供应商制作出的芯片的外设却是不同的,CMSIS提供了一种在外设和芯片之间一致的接口,简化了软件的开发和开发人员的学习成本。而CMSIS_RTOS则为提供的,对操作系统层次的统一接口。目前,应用于嵌入式开发的操作系统种类繁多,比如火热的FreeRTOS,Keil自带的RTX操作系统等,而CMSIS_RTOS则是对各类操作系统提供了统一的接口,提供了操作系统的API,如此一来,大家可以按自己的需要选择所合适的操作系统,只要统一使用CMSIS作为接口标准,那么就可以很容易的读懂其他工程师用别的操作系统所写的程序,不用再费心思去学另一个操作系统的函数接口之类的。这使得不同操作系统间的移植变的容易,整合应用CMSIS_RTOS标准下的不同操作系统,将只需要修改有限的配置文件就行了。
CMSIS_RTOS学习笔记(一)_第1张图片

环境需求

是不是得手头上有块单片机,才能学习CMSIS_RTOS呢?
答案是否定的,你只需要有一个Keil IDE就足够了,Keil自带的仿真功能完全满足了我们的学习需求。笔者是参照官方文档,在STM32F103RB的平台上学习的,大部分人应该也都安装过STM32F1系列的固件库,没有也没关系,下面给出下载的链接,同样,你也可以选择在其他型号的平台上进行学习。
固件库下载链接:https://www.keil.com/dd2/Pack/
CMSIS_RTOS官方例程下载链接:https://www.keil.com/boards2/hitex/cmsis_rtos_tutorial/

总览

CMSIS_RTOS学习笔记(一)_第2张图片
上图截选自官方文档,我觉得操作系统就像一个大管家,起到了调度资源的作用:
1、任务管理,利用操作系统可以很方便的规划任务的优先级,同样这种各任务分块化的结构,对于调试也非常方便,相对于裸机,更容易定位到问题所在。
2、线程间的通讯,利用信号量,事件,邮箱等机制,可以很方便的进行任务间的信息交互和同步。
3、可以方便的进行时间管理和内存管理。
这也是用了操作系统后,就很难再乐意回去在裸机写程序的原因吧。。。

CMSIS_RTOS中的任务管理——Thread

首先,让我们先跑起来一个最最最最最简单的CMSIS_RTOS的程序

打开keil,新建一个项目,选择你的芯片,这儿采用STM32F103RB。
CMSIS_RTOS学习笔记(一)_第3张图片
点击OK,在接下来的配置中,把Keil RTX勾上,RTX是keil自带的一款操作系统,在RTX上把CMSIS的一些KPI走通后,换FREERTOS之类的其他操作系统,所需要的改动是很小的。
CMSIS_RTOS学习笔记(一)_第4张图片
点击,右下的Resolve,你会发现黄条变绿,再点击ok,生成了相应的工程。
在左侧生产的文件中,我们可以用左下角的Configuration Wizard,方便的进行相关配置。
比如,下图就是对RTX的一些配置,可以改一些堆栈大小之类的。CMSIS_RTOS学习笔记(一)_第5张图片
接下来我们在左侧项目工程中添加新文件
CMSIS_RTOS学习笔记(一)_第6张图片
这儿我们选择最后的用户模板,依次添加第一行的main模板和第七行的thread模板。CMSIS_RTOS学习笔记(一)_第7张图片
这样我们就愉快的左侧得到了两个新文件,main.c和Thread.c,
我们将Thread.c中的初始化函数在main.c中引用,代码如下:

#define osObjectsPublic                     // define objects in main module
#include "osObjects.h"                      // RTOS object definitions

extern int Init_Thread (void);            //需要添加的
/*
 * main: initialize and start the system
 */
int main (void) {
  osKernelInitialize ();                    // initialize CMSIS-RTOS

  // initialize peripherals here
	Init_Thread ();                             //需要添加的
  // create 'thread' functions that start executing,
  // example: tid_name = osThreadCreate (osThread(name), NULL);

  osKernelStart ();                         // start thread execution 
}

好了,最简单的一个操作系统应用程序就搭建好了,是不是急着想去跑一下了?
别急,还有最后一步,我们手头没有板子,所以需要做一些仿真的配置。
点击离编译按钮左侧不远处的魔法棒,在Debug选项块进行红圈内的更改。
CMSIS_RTOS学习笔记(一)_第8张图片
大功告成!ctrl+f5 debug走起!
在Debug选项中,添加OS Support的System and Thread Viewer便能看到在跑的线程,如下图。当我们按下run,会发现main线程消失,我们自个儿定义的Thread代替了main,开始活跃的奔跑,这儿就不放图了。
CMSIS_RTOS学习笔记(一)_第9张图片
好了,这是个很简单的工程,只是让我们快一点感受到操作系统的美妙~
接下来让我们进入到任务管理的学习!

Thread管理

概念

裸机程序中,如果有不同的任务块,那么我们将这些任务块都写在一个大的while循环里,再用定时器确定时间间隔,按照轮询的方式,去执行这些任务。然而,这种轮询的方式,无法保证任务之间的优先级,比如想要执行高优先级任务,也只能老老实实等高优先级任务前的低优先级任务跑完,才轮到高优先级,无法直接打断低优先级任务。任务就像是排着队一个个执行一样。
而操作系统中,可以把各任务看成是一个个并行的线程,也就是Thread,虽然同一时刻实际上只执行某一个任务,但因为cpu处理的速度极快,再加上各任务都是有执行的周期间隔的,大部分情况下,可以看成是各任务是并行的。
任务可大致分为三种状态(任务挂起也可以看成是一种状态,但我觉得可以等同于wait):
Running:运行状态,表明正在执行该任务;
Ready:就绪状态,万事俱备,只欠cpu,就等着cpu释放资源从而进入运行态;
Wait:阻塞状态,等待某些条件来进入就绪状态,比如 等待某个事件发生之类的。
同优先级的Thread会先轮到谁就运行谁,而高优先级的Thread可以打断低优先级的任务。如下图T4打断T3。CMSIS_RTOS学习笔记(一)_第10张图片

Thread相关KPI

一个Thread的创建,可以分成四步
我们拿创建一个Led灯闪烁的Thread来说明

osThreadId LED_TaskHandle; //创建一个thread handle
void LEDTask(void const * argument); //创建对应的function prototype 
osThreadDef(LEDTask, osPriorityNormal, 1, 0); //配置相关thread属性
LED_TaskHandle= osThreadCreate (osThread(LEDTask), NULL);//创建thread

接下来我们来挨个看这四步操作。
第一步,创建一个thread的句柄,没啥好说的。
第二步,声明一个thread的函数,这个函数是可以传参数进去的,这个结合后两步说。
第三步,配置thread的属性,这步的参数就多了,首先,
1)第一个参数,是配置thread的任务函数,也就是说thread跑的内容是在所配置的函数里跑的,这个任务函数也就是我们第二步所声明的函数。
2)第二个参数,是配置优先级,直接右键go to definition,可以很方便的看到别的优先级,按需求给你的thread合适的优先级。
3)第三个参数,是instance数,即实例数目,一般来说,都是1,也就是说你这个线程任务只对应一个线程,但也有存在对应多个实例的情况。比如说,我现在有两个LED灯,我想用两个线程来控制这两个灯闪烁,这两个线程的函数基本相同,只是灯的接口不同,那么我们可以用同一个thread模板,配置两个thread,这种情况下参数可以设置为2。
4)第四个参数,分配的stack大小,即分配的内存大小,0的话表示默认参数,在对应congfig配置文件中更改,也可以直接设置128,256,1024…之类的,自由分配内存的大小。
5)有的osThreadDef可能有第五个参数,线程名,开发的时候右键跳到头文件里去查看,根据实际要求配置一下就好了。
第四步,创建thread,第二个参数为传入参数,一般而言为NULL;如果在第三步中第三个参数不是1,配置了多个实例,则此处第二个参数可以传进去作为区别。

接下来放一段多个instance的thread创建代码,作用为创建两个led灯的函数。

osThreadId LED1_TaskHandle;
osThreadId LED2_TaskHandle;
void LEDTask(void const * argument)
{
	for (;;) 
	{
		LED_On((uint32_t)argument);              //根据argument区别所控制的LED            
		delay(500);
		LED_Off((uint32_t)argument);
		delay(500);
	}
}
osThreadDef(LEDTask, osPriorityNormal, 2, 128);    //实例2,内存分配128
main
{
	osKernelInitialize ();                    //初始化
	LED_Initialize();							//这个不具体写了,表示相关的初始化,LEDTask中的LED_On和Off也没具体列出,意思到了就行
	LED1_TaskHandleosThreadCreate(osThread(LEDTask),(void *) 1UL);  //创建第一个实例 控制LED1
	LED1_TaskHandleosThreadCreate(osThread(LEDTask),(void *) 2UL);  //创建第二个实例 控制LED2
	osKernelStart ();                         // 开启内核
	while(1);
}

其余会用到的KPI有:
osPriority osThreadGetPriority(threadID); //获取线程优先级
osStatus osThreadSetPriority(threadID, priority); //设定线程优先级
osStatus = osThreadTerminate (threadID1); //删除线程

Thread的基础介绍到此就结束了。
接下来,我们将去探索CMSIS_RTOS中的时间管理。

参考资料:《CMSIS_RTOS_Tutorial》

你可能感兴趣的:(嵌入式,操作系统,arm)