标签:
文件名 | 文件作用 | 备注 |
---|---|---|
atom.h | 系统API | 任务控制块、系统错误宏、系统API等 |
atomkernel.c | 系统内核源码 | 内核功能:上下文切换、中断、TCB控制、信号量等 |
atommutex.c | 互斥源码 | ** |
atomport-template.h | 系统宏、类型声明 | 时间片轮设定 |
atomqueue.c | 队列源码 | ** |
atomsem.c | 信号量源码 | ** |
atomtimer.c | 定时器源码 | 系统滴答时钟、应用性定时器 |
操作系统是一种管理软件,负责管理对象的信息之余还按照某种规则对这些对象进行分配、调度,而实现这些操作的前提是需要有一份关于对象的详细信息,我们称之为程序控制块。从代码上看,程序控制块就是一个结构体。在Atom系统里面,程序控制块的组成简化如下图。
Atom系统里把上图所示结构体称之为ATOM_TCB(atom.h 4行),系统依赖于此结构来执行系统任务程序。
*注意:任务堆栈使用记录,是由ATOM_STACK_CHECKING宏决定是否存在TCB当中,如果不需要使用堆栈检测功能,可以根据宏来设置。
生活中,我们习惯将大而复杂的事情“分而治之”,程序也会遇到类似的问题;也有时候,我们需要等待用户按下按键,同时也需要不断读取来自网络的数据。可见,多任务的好处就是用来解决并发性问题。一个问题,我们称之为任务,程序需要通过算法去将这个任务执行并解决。而多线程便解决多任务问题,所以,操作系统正是通过系统规则对任务进行分配资源、调度处理。Atom系统的任务及其内存结构如下图:
由此可得,如果多个任务,就存在一张链表保存着所有任务的信息,通过图上标号F链接前一个任务控制块指针,标号N链接后一个任务控制块指针。系统则可以通过任务控制块链表来找到任意一个存在的任务,从而执行多个任务的程序。
Atom系统将此链表称为tcbReadyQ(kernel.h 172行)
至此,系统运行的“地基”已经造好。
系统初始化函数:atomOSInit()(kernel.h 657行)
编写的代码中,除了有用户任务(处理应用程序逻辑)还需要有系统任务(处理系统逻辑),我们需要给予系统足够的堆栈空间运行,才能处理系统任务。Atom系统初始化系统自身的同时,也为自己创建了一个名为“空闲任务”的线程。 截取该函数主要代码解析:
/* 初始化系统全局变量 */
curr_tcb = NULL;//系统运行期间,该指针一直指向正在执行的任务控制块
tcbReadyQ = NULL;//系统运行期间,该指针指向了保存所有任务控制块信息的链表
atomOSStarted = FALSE;//作为系统的总开关变量,FALSE为停止系统调度任务,
//TRUE为开启系统调度任务
/* 创建空闲线程(idle thread) */
status = atomThreadCreate(&idle_tcb,//系统任务:空闲线程的程序控制块
IDLE_THREAD_PRIORITY,//空闲线程优先级,最低优先级255
atomIdleThread,//任务函数,空闲程序入口,不作用户任务。
0,
idle_thread_stack_top,//堆栈顶
idle_thread_stack_size);//堆栈大小
读者亲自查看代码,容易发现初始化系统函数仅接受两个参数(stack_top和stack_size),这意味着我们初始化系统只需要设置这连个参数,而函数内其他都是固定设置的,包括创建的idle_tcb。可见,系统启动必须存在“空闲任务”线程,它在系统没有其他用户任务需要处理的时候执行。当然,它只是一个死循环。
/* 中断入口函数,中断嵌套层数全局变量atomIntCnt加一 */
atomIntEnter();
/* 系统时钟处理函数,心跳次数加一,并启动定时器回调 */
atomTimerTick();
/* 清空标志位 */
TIM1->SR1 = (uint8_t)(~(uint8_t)TIM1_IT_UPDATE);
/* 中断退出函数, atomIntCnt减一,调用系统调度程序*/
atomIntExit(TRUE);
该程序,完成了系统自动调度任务功能,至此实时系统最基本的调度功能完成。
线程创建函数:atomThreadCreate()
- 创建一个新线程并根据函数输入参数设置初始化TCB参数(挂起状态、优先级、队列指针、任务入口、入口参数)
- 调用archThreadContextInit(),入栈操作。将thread_shell()返回信息记录在线程的TCB主中,使得下次调度后执行正确的线程开始位置。
- 该函数内调用thread_shell()函数,能够找到线程处理函数入口地址。
- 将该线程放到任务队列tcbReadyQ
- 调用系统调度函数根据优先级决定线程运行,如果系统没有启动,则不进行调度。
线程堆栈问题:
1. 线程被创建,随后由系统调度挂起或执行,都需要足够的堆栈区,用以保存线程的资源。任务的大小决定着线程堆栈大小,实际调试根据任务的复杂度来设置堆栈大小。
2. Atom为线程提供堆栈检测接口,除了直观地设置堆栈大小,在声明了ATOM_STACK_CHECKING的情况下,还可以调用atomThreadStackCheck()函数检测线程堆栈使用情况。
if(系统关闭)
return ;
进入临界区,关闭总中断;
if(当前任务被挂起)
从任务队列tcbReadyQ取出第一个线程调用atomThreadSwitch()切换任务;
else
从tcbReadyQ取出任务优先级大于或者等于当前任务优先级等待执行的线程,进行任务切换,将当前任务添加到tcbReadyQ中;
退出临界区,开启总中断;
启动系统函数:atomOSStart()(atomkernel.c 694行),该函数主要处理:
1. atomOSStarted全局系统标识开关,设置为TURE,允许启动系统
2. 从任务队列取出“空闲任务”作为当前TCB指针curr_tcb,然后调用(汇编程序)archFirstThreadRestore()使它作为系统的第一个线程开始执行。
一般,我们单片机的控制对象都是一个装置或设备(以下统称对象),任务的功能是相对固定的,典型的任务是一个无限循环结构,这样便可以不断处理事件。
void function ( void )
{
while(1)
{
user_code;//用户代码
}
}
那么,一般我们直接操作寄存器实现CPU片上设备的特定功能,然后响应被激活的中断,来实现对多个对象的控制。但这是单线程的处理,也是单片机常用的控制模式。随着功能愈复杂(对象量愈多),那么我们单线程的控制代码就会显得臃肿,难以维护,不利于产品迭代进化。
假设,现在产生两个或以上的对象的控制矛盾,我们单线程处理就有点显得力不从心了。所以,使用多线程处理,能够更大限度地使用CPU资源,并行地运行多个任务(同时操作多个对象)。典型的多线程如下:
void thread_first ( void )
{
while(1)
{
first_user_code;//第一个用户代码
}
}
void thread_second ( void )
{
while(1)
{
second_user_code;//第二个用户代码
}
}
这时候有同学可能会问:while(1)不是一个死循环吗?对的,确实是。但Atomthread系统通过任务调度,实现了两个while(1)之间的切换,也就是我们所说的两个线程之间的调度了。恰好,我们通过堆栈保存断点数据实现任务切换,若不明白回头看6.任务调度章节。所以,我们在具有操作系统的情况下,只需要简单而便捷地创建一个线程,就可以实现对新增的对象进行控制,而不是设定一个又一个的标志位或状态机去轮询控制。
让我们通过Atom系统,实现入门单片机实验的LED闪烁功能。
现在,先请读者回头翻看,使用指南 2.使用Atom基本流程;
除了包含必要的头文件外。首先,我们得对我们创建的线程声明必要的变量,为了方便修改。
/* 空闲(起始)任务:堆栈大小 */
#define IDLE_STACK_SIZE_BYTES 104
NEAR static uint8_t idle_thread_stack[IDLE_STACK_SIZE_BYTES];
/* 任务一:优先级、堆栈大小、堆栈区、任务块、任务函数体 */
#define FIRST_THREAD_PRIO 1
#define FIRST_STACK_SIZE_BYTES 208
NEAR static uint8_t first_thread_stack[FIRST_STACK_SIZE_BYTES];
static ATOM_TCB first_tcb;
static void first_thread_func (uint32_t param);
/* 任务二:优先级、堆栈大小、堆栈区、任务块、任务函数体 */
#define SECOND_THREAD_PRIO 2
#define SECOND_STACK_SIZE_BYTES 156
NEAR static uint8_t second_thread_stack[SECOND_STACK_SIZE_BYTES];
static ATOM_TCB second_tcb;
static void second_thread_func (uint32_t param);
解析点:
1. 空闲任务的堆栈不易过小,用户任务的堆栈根据实际分配。
2. NEAR为IAR编译器对STM8的变量内存分配空间管理的关键字,实际为IAR提供的编译模式,这里是微模式,简单理解为内存中放置程序代码及数据的方式。
接下来就是用户任务(应用线程)的编写,这里放置实际功能的代码。我们分别让开发板的LED0(PB0)和LED1(PB1)先后以一定时间间隔闪烁。
/*****************************************************************************
函 数 名 : first_thread_func
功能描述 : 任务一:led0闪烁
输入参数 : param
输出参数 : 无
返 回 值 :
调用函数 :
被调函数 : main
*****************************************************************************/
static void first_thread_func (uint32_t param)
{
GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_OUT_PP_LOW_FAST);
while (1)
{
GPIO_WriteReverse(GPIOB, GPIO_PIN_0);
atomTimerDelay(50);
}
}
/*****************************************************************************
函 数 名 : second_thread_func
功能描述 : 任务二:led1闪烁
输入参数 : param
输出参数 : 无
返 回 值 :
调用函数 :
被调函数 : main
*****************************************************************************/
static void second_thread_func (uint32_t param)
{
GPIO_Init(GPIOB, GPIO_PIN_1, GPIO_MODE_OUT_PP_LOW_FAST);
while (1)
{
atomTimerDelay(50);
GPIO_WriteReverse(GPIOB, GPIO_PIN_1);
}
}
解析点:
1. 这里调用了Atom系统的定时器延时函数atomTimerDelay()(atomtimer.c 328行),延时时间时基依赖于系统当前的心跳频率。
最后,编写主函数。
NO_REG_SAVE
void main ( void )
{
int8_t status;
/* 系统初始化,赋予系统空闲运行堆栈 */
status = atomOSInit(&idle_thread_stack[IDLE_STACK_SIZE_BYTES - 1], IDLE_STACK_SIZE_BYTES);
if (status == ATOM_OK)
{
archInitSystemTickTimer();/* 启动系统滴答时钟 */
atomThreadCreate(&first_tcb,
FIRST_THREAD_PRIO, first_thread_func, 0,
&first_thread_stack[FIRST_STACK_SIZE_BYTES - 1],
FIRST_STACK_SIZE_BYTES);
atomThreadCreate(&second_tcb,
SECOND_THREAD_PRIO, second_thread_func, 0,
&second_thread_stack[SECOND_STACK_SIZE_BYTES - 1],
SECOND_STACK_SIZE_BYTES);
atomOSStart();//运行系统
}
while (1){/* 系统不会进入这里 */
}
}
解析点:
1. 创建线程不分先后,因为存在优先级排序执行,系统是抢占式的。
注明,需要代码的朋友,留言或者发我私信。
标签:
原文地址:http://blog.csdn.net/bangdingshouji/article/details/51918418