微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > 嵌入式系统设计讨论 > STM32 简单多任务调度的方法与程序例程

STM32 简单多任务调度的方法与程序例程

时间:10-02 整理:3721RD 点击:

STM32 简单多任务调度的方法与程序例程

http://bbs.edu118.com/forum.php?mod=viewthread&tid=557&fromuid=231

(出处: 信盈达IT技术社区)




多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务.Linux、windows就是支持多任务的操作系统,比起单任务系统它的功能增强了许多。当多任务操作系统使用某种任务调度策略允许两个或更多进程并发共享一个处理器时,事实上处理器在某一时刻只会给一件任务提供服务。因为任务调度机制保证不同任务之间的切换速度十分迅速,因此给人多个任务同时运行的错觉。多任务系统中有3个功能单位:任务、进程和线程。

实时多任务操作系统(RTOS)是嵌入式应用软件的基础和开发平台,它是根据操作系统的工作特性而言的。实时是指物理进程的真实时间。实时操作系统是指具有实时性,能支持实时控制系统工作的操作系统。首要任务是调度一切可利用的资源完成实时控制任务,其次才着眼于提高计算机系统的使用效率,重要特点是要满足对时间的限制和要求。目前在中国大多数嵌入式软件开发还是基于处理器直接编写,没有采用商品化的RTOS,不能将系统软件和应用软件分开处理。RTOS是一段嵌入在目标代码中的软件,用户的其它应用程序都建立在RTOS之上。不但如此,RTOS还是一个可靠性和可信性很高的实时内核,将CPU时间、中断、I/O、定时器等资源都包装起来,留给用户一个标准的API,并根据各个任务的优先级,合理地在不同任务之间分配CPU时间。

RTOS是针对不同处理器优化设计的高效率实时多任务内核,优秀商品化的RTOS可以面对几十个系列的嵌入式处理器MPU、MCU、DSP、SOC等提供类同的API接口,这是RTOS基于设备独立的应用程序开发基础。因此基于RTOS上的C语言程序具有极大的可移植性。据专家测算,优秀RTOS上跨处理器平台的程序移植只需要修改1~5%的内容。在RTOS基础上可以编写出各种硬件驱动程序、专家库函数、行业库函数、产品库函数,和通用性的应用程序一起,可以作为产品销售,促进行业内的知识产权交流,因此RTOS又是一个软件开发平台。RTOS是嵌入式系统的软件开发平台,RTOS最关键的部分是实时多任务内核,它的基本功能包括任务管理、定时器管理、存储器管理、资源管理、事件管理、系统管理、消息管理、队列管理、旗语管理等,这些管理功能是通过内核服务函数形式交给用户调用的,也就是RTOS的API。RTOS的引入,解决了嵌入式软件开发标准化的难题。随着嵌入式系统中软件比重不断上升、应用程序越来越大,对开发人员、应用程序接口、程序档案的组织管理成为一个大的课题。引入RTOS相当于引入了一种新的管理模式,对于开发单位和开发人员都是一个提高。基于RTOS开发出的程序,具有较高的可移植性,实现90%以上设备独立,一些成熟的通用程序可以作为专家库函数产品推向社会。嵌入式软件的函数化、产品化能够促进行业交流以及社会分工专业化,减少重复劳动,提高知识创新的效率。

在STM32的开发目前大多数还开处于“裸奔”的阶段,处于开发成本的考虑,可能还未嵌入任何的RTOS系统,由于没有操作系统的支持,因而不能方便的对多任务进行调度和管理,在main函数中你可能会写成如下方式:


  • int main(void)
  • {
  • while (1)
  • {
  • Task1(); // 调用任务1
  • Task2(); // 调用任务2
  • }
  • }

[color=rgb(51, 102, 153) !important]复制代码


但简单这样写的话会存在一个问题,假如任务1是一个很紧急的任务,如AD采样任务,需要不断的去执行,而任务2是一个不太紧急的任务,只要保证一段时间执行一次就行(如控制LED灯闪烁,只需要每1s钟闪烁一次),这样的话一是频繁的调用任务2占用了任务1执行的时间,二是任务2根本不需要这样频繁的执行,白白耗费了CPU的处理。因此可以考虑实现一个调度策略来解决这个问题。对于每个任务,我们可以定义这样一个结构:


  • typedef struct{
  • void (*fTask)(void);
  • int64u uNextTick;
  • int32u uLenTick;
  • }sTask;

[color=rgb(51, 102, 153) !important]复制代码

其中fTask为任务指针,指向具体的任务,uNextTick为该任务下一次执行的时间,uLenTick为任务的调度周期或叫调度频率,即每隔多长时间执行一次。

按照这个结构,可以预先定义一个结构体数组,然后将要调用的任务和任务的调度时间按照如下方式罗列出来:


  • // 任务列表
  • static sTask mTaskTab[] =
  • {
  •   {Task_SysTick,    0, 0}
  • ,{Task1,   0, 10}    // 10ms执行一次
  • ,{Task2,   0, 200}   // 200ms执行一次
  • };

[color=rgb(51, 102, 153) !important]复制代码


其中第一个任务Task_SysTick为计算系统时间的任务,用以获取上电后运行的时间(Task_SysTick任务相关代码附在文章后面)。这里默认任务下一次执行的时间为0,在main函数中,不断的轮询这个数组,然后将当前任务的下一次调用时间和当前时间比较,如果发现轮到该任务执行,就执行该任务,执行完成后,将该任务的下一次执行时间设为当前时间加任务的调度时间,然后按照此方法去执行下一个需要执行的任务,代码如下:


  • while (1)
  • {
  • // 任务循环
  • for (i = 0; i < ARRAYSIZE(mTaskTab); i++)
  • {
  • if (mTaskTab.uNextTick <= GetTimingTick())
  • {
  • mTaskTab.uNextTick += mTaskTab.uLenTick;
  • mTaskTab.fTask();
  • }
  • }
  • }

[color=rgb(51, 102, 153) !important]复制代码


这样,就可以对多个任务做一个简单的调度,以后添加任务时只需要在mTaskTab表中添加即可,需要强调的是,由于执行每个任务也需要耗费时间,就会导致一个任务的实际调度周期可能会比设定的调度周期要长,这样会存在时间不准的情况,当然这仅仅是适合于对轮询周期不是很严格的任务,如果想要任务在严格的时间周期内执行或者需要更精确的时间处理,则必须采用定时器的方式了。

完整的main文件代码:


  • #ifndef ARRAYSIZE
  • #define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0]))
  • #endif
  • // 任务结构
  • typedef struct{
  • void (*fTask)(void);
  • u64 uNextTick;
  • u32 uLenTick;
  • }sTask;
  • // 任务列表
  • static sTask mTaskTab[] =
  • {
  • {Task_SysTick,    0, 0}
  • ,{Task1,           0, 10}   // 10ms执行一次
  • ,{Task2,           0, 200}  // 200ms执行一次
  • // 在这之前添加任务
  • };
  • /*******************************************************************************
  • * Function Name  : main
  • * Description    : Main program.
  • *******************************************************************************/
  • int main(void)
  • {
  • int i = 0;
  • // 硬件初始化
  • HW_init();
  • // 初始化系统Tick任务
  • dev_SysTick_init(void);
  • // ...
  • while (1)
  • {
  • // 任务循环
  • for (i = 0; i < ARRAYSIZE(mTaskTab); i++)
  • {
  • if (mTaskTab.uNextTick <= GetTimingTick())
  • {
  • mTaskTab.uNextTick += mTaskTab.uLenTick;
  • mTaskTab.fTask();
  • }
  • }
  • }

[color=rgb(51, 102, 153) !important]复制代码


Task_SysTick任务相关代码:


  • volatile int64u g_TimingTick = 0;
  • volatile int64u g_TimingTickOld = 0;
  • //=============================
  • //【函 数 名 称】 void dev_SysTick_init(void)
  • //【参       数】
  • //【功       能】 初始化
  • //【返   回  值】 None
  • //============================
  • void dev_SysTick_init(void)
  • {
  • TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  • /* Time base configuration */
  • TIM_TimeBaseStructure.TIM_Period = 65535;
  • TIM_TimeBaseStructure.TIM_Prescaler = 36000-1;
  • TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  • TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  • TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  • TIM_SetCounter(TIM2, 0);
  • /* TIM enable counter */
  • TIM_Cmd(TIM2, ENABLE);
  • }
  • //========================
  • //【函 数 名 称】 void GetTimingTick(void)
  • //【参       数】
  • //【功       能】 获取MCU启动后的运行时间
  • //【返   回  值】 MCU启动后的运行时间,单位ms
  • //=========================
  • int64u GetTimingTick(void)
  • {
  • return g_TimingTick;
  • }
  • //===============================
  • //【函 数 名 称】 void Task_SysTick(void)
  • //【参       数】
  • //【功       能】 Tick任务,从TIM2获取系统时间
  • //【返   回  值】 None
  • //==================================
  • void Task_SysTick(void)
  • {
  • int16u temp = TIM_GetCounter(TIM2);
  • if (temp > 1000)
  • {
  • TIM_SetCounter(TIM2, 0);
  • g_TimingTickOld = g_TimingTickOld + temp;
  • temp = 0;
  • }
  • g_TimingTick = g_TimingTickOld + temp;
  • }

[color=rgb(51, 102, 153) !important]复制代码


加我qq:3208919269交流。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top