打印
[应用方案]

单片机程序架构—时间轮片法

[复制链接]
2454|55
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
jtracy3|  楼主 | 2024-9-19 08:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
很多人尤其是初学者在写代码的时候往往都是想一点写一点,最开始没有一个整体的规划,导致后面代码越写越乱,bug不断。最终代码跑起来看似没有问题(有可能也真的没有问题),但是要加一个功能的时候会浪费大量的时间,甚至导致整个代码的崩溃。

所以,在一个项目开始的时候多花一些时间在代码的架构设计上是十分有必要的。代码架构确定好了之后你会发现敲代码的时候会特别快,并且在后期调试的时候也不会像无头苍蝇一样胡乱找问题。当然,调试也是一门技术。

怎样写代码?
单片机代码都是从主函数开始的,很多人会在main函数中写很多代码,这样个人觉得十分不好,因为看起来太累,太丑。俗话说,颜值即正义!只要是能够写出漂亮代码的人,大概率来说他写的代码的bug也比较少。

一般来说,main函数的结构如下:

#include <xxx.h>
#include "xxx.h"
......

extern struct xxx;
extern int/unsigned int/char xxx;

int main()
{
    init_function_1();
    inti_function_2();
    ......
    while(1)
    {
        task_1();
        task_2();
        ......
    }
}
这样的结构看起来就十分的简洁,主函数中只作各种外设的或者函数、结构体等的初始化,当然初始化函数也在其它的文件中;然后进入while(1)死循环,处理各个任务。这些任务函数也是定义在其它文件中。

当然,如果一个函数需要间隔特定的时间执行的话怎么办?在while(1)中加延时函数?

答案是否定的,一旦在主循环中加入延时函数后整个主循环都会受到影响,会导致程序完全不能正常执行。还有就是主循环中的任务函数都不能有延时。那么这时应该怎么办?

使用操作系统
使用操作系统,如:ucos,freertos,rt_thread等可以解决这个问题,但是学习操作系统的话需要一定时间,有时代码又没有复杂到需要使用操作系统,有时单片机资源不足以使用操作系统。并且使用操作系统对开发 人员的要求比较高。

使用定时器加全局flag
这种方式是在定时中判断各个任务的是否到执行时间,如果到了,就将该任务的flag置为1,然后在主函数中判断flag是否为1,如果为1,则执行。这样做看起来确实不错,但是在程序设计中尽量少使用全局变量,而且这样做的话,主函数会看起来十分不美观。

时间轮片法
所谓时间轮片,其实和上面使用定时器加全局flag没有本质区别,只是将其包装起来,看起来更好看而已。当然,颜值上升了,其战斗力也变强了, 在很多情况下使用起来就十分舒爽。

时间论轮片法
定义任务结构体与任务
#define TASK_NUM xx   // 要执行的任务数量
typedef enum   
{
      TASK_STOP,
      TASK_RUN   
} TaskRunFlag;   
struct Task   
{
       void* (task_handle)(void);  // 任务函数体,还可以带参数void (task_handle)(void* para)
       TaskRunFlag task_run_flag;    // 任务运行状态
       unsigned int task_timer;  // 任务计时器,为0时该相应任务执行
       unsigned int task_timer_init_val;     // 任务计时器初值   
};   
typedef struct Task TaskInitTypedef;

static TaskInitTypedef tasks[TASK_NUM] =  // 定义一个结构体数组并初始化各个任务   
{
       {task_1, TASK_STOP, xx,xx}, // task_1为任务1,初始状态为停止状态,每隔xx时钟节拍运行一次
       {task_1, TASK_STOP, xx,xx},
       {task_1, TASK_STOP, xx,xx},
       ......
       ......   
};  
一个任务结构体就定义了一个任务,包括任务函数、任务运行状态、执行间隔。

时间轮片
时间轮片是整个时间轮片法的核心,时间轮片就是在每一个时钟节拍判断是否到了执行该相应任务的时候,如果是,则任务状态为运行,如果不是则计数器减一。

void task_rhythm() // 该函数放于定时器中断中,进一次中断执行一次,是整个任务系统的心跳(节奏)
{
    unsigned char i;
    for(i = 0; i < TASK_NUM; i++)
    {
      if(tasks[i].task_timer != 0) // 挨个判断任务是否到执行时间
      {
          tasks[i].task_timer --;
          if(tasks[i].task_timer == 0) // 如果到执行时间
          {
              tasks[i].task_run_flag = TASK_RUN;
              tasks[i].task_timer = tasks[i].task_timer_ini_val;
          }
      }
}
}  
该函数位于定时器的中断中,定时器的定时时间就是整个任务的运行节拍,前面的task_timer参数就是运行节拍的计数器。如:定时器中断时间为10ms,那么该系统中任务的最短间隔就是10ms,也就是隔一个节拍运行一次;如果一个任务想要1s运行一次的话,就将task_timer参数以及task_timer_ini_val参数值设为100即可。

任务处理
上面已经知道了什么时候该运行相应的任务(就是任务对应的run_flag为TASK_RUN),那么我们就在主函数的大循环中一直检测任务相应的标志位即可。

void task_process()   
{
    unsigned char i;
    for(i = 0; i < TASK_NUM; i ++)
    {
      if(tasks[i].task_run_flag == TASK_RUN)
      {
          tasks[i].task_handle;     // 调用任务函数
          tasks[i].task_run_flag = TASK_STOP;   // 结束任务函数
      }
    }
}  
至此,时间轮片法完成。

使用特权

评论回复
沙发
jonas222| | 2024-10-7 13:18 | 只看该作者
任务列表:所有需要执行的任务被组织成一个列表。
时间片计数器:用于跟踪当前任务已执行的时间片数量。
系统时钟:提供时间基准,通常与单片机的定时器/计数器配合使用。
任务调度器:根据时间片来调度任务的执行。

使用特权

评论回复
板凳
averyleigh| | 2024-10-7 15:01 | 只看该作者
通过定时器中断来产生系统的基准时间片。例如,可以设定定时器每4ms或10ms中断一次,这取决于系统对时间精度的要求。
每一次定时器中断,都视为一个时间片的开始或结束。

使用特权

评论回复
地板
loutin| | 2024-10-7 18:01 | 只看该作者
代码结构清晰:任务的定义和管理更加模块化,便于代码的阅读和维护。

使用特权

评论回复
5
ccook11| | 2024-10-7 18:54 | 只看该作者
时间轮片法提供了一种灵活的调度机制,可以根据任务的重要性和紧急程度动态调整时间片的分配,以优化系统性能。

使用特权

评论回复
6
mollylawrence| | 2024-10-7 20:02 | 只看该作者
时间轮片法通常与优先级调度相结合。具有高优先级的任务可以抢占低优先级任务的时间片,从而优先获得CPU资源。这确保了关键任务能够得到及时响应。

使用特权

评论回复
7
wilhelmina2| | 2024-10-8 12:04 | 只看该作者
通过合理分配时间片,可以保证每个任务都有机会得到及时处理,提高系统的响应性。

使用特权

评论回复
8
mattlincoln| | 2024-10-8 12:24 | 只看该作者
时间轮片法通过定时器和中断服务程序实现了任务的自动调度,无需手动管理任务的执行顺序和时间间隔。

使用特权

评论回复
9
cashrwood| | 2024-10-9 08:45 | 只看该作者
时间轮片法类似于操作系统中的时间片轮转调度算法。它将单片机的运行时间分割成许多小的时间片,每个任务在分配给它的时间片内执行。当时间片用尽时,无论任务是否完成,都会切换到下一个任务。

使用特权

评论回复
10
youtome| | 2024-10-9 09:49 | 只看该作者
尽管时间轮片法的原理相对简单,但它在实际系统中的实现需要考虑许多细节,如时间片的选择、任务切换的开销等。

使用特权

评论回复
11
iyoum| | 2024-10-9 10:31 | 只看该作者
首先,需要定义任务结构体,包括任务函数、任务运行状态、执行间隔等。
任务函数是任务的核心,它定义了任务需要执行的具体操作。
任务运行状态用于标识任务当前是否处于运行状态。
执行间隔用于设置任务执行的时间间隔,即时间片长度。

使用特权

评论回复
12
houjiakai| | 2024-10-9 12:32 | 只看该作者
时间轮片法是一种在单片机编程中实现多任务处理的架构。它将 CPU 的运行时间划分成固定长度的时间片,每个任务按照顺序在分配到的时间片内执行。当一个任务的时间片用完后,不管任务是否完成,都暂停该任务的执行,转而执行下一个任务,所有任务依次轮流执行,从而在单个 CPU 上模拟多个任务 “同时” 执行的效果。

使用特权

评论回复
13
macpherson| | 2024-10-9 13:33 | 只看该作者
适用于那些不需要操作系统级别的复杂调度,但又需要一定任务管理机制的应用。

使用特权

评论回复
14
pmp| | 2024-10-9 14:14 | 只看该作者
时间轮片法的核心在于将CPU的运行时间分割成一系列固定长度的时间片,每个时间片代表一个基本的执行单元。这些时间片按照一定的顺序循环出现,为系统中的每个任务提供执行机会。

使用特权

评论回复
15
lihuami| | 2024-10-9 14:54 | 只看该作者
时间轮片法是一种在单片机程序中实现多任务调度的技术。这种方法通过将时间划分为固定长度的片段(即时间片),并在每个时间片内执行不同的任务,从而实现对多个任务的有序管理和执行。

使用特权

评论回复
16
hilahope| | 2024-10-9 15:13 | 只看该作者
根据单片机的性能和任务的需求确定时间片的长度。时间片长度通常以定时器中断的周期为基础。例如,如果单片机的系统时钟为 16MHz,定时器可以设置为每 1 毫秒产生一次中断,那么时间片的长度就可以是 1 毫秒。时间片长度的选择要兼顾任务的执行需求和系统的整体响应速度。如果时间片过长,可能会导致实时性要求高的任务响应延迟;如果时间片过短,会增加任务切换的开销。

使用特权

评论回复
17
yorkbarney| | 2024-10-9 17:56 | 只看该作者
时间轮片法的关键在于合理分配时间片,确保每个任务都能在其时间片内得到执行,同时避免任务间的相互干扰。

使用特权

评论回复
18
robincotton| | 2024-10-9 19:38 | 只看该作者
初始化:在系统启动时,初始化任务列表和每个任务的时间片计数器。
任务调度:调度器按照任务列表顺序,为每个任务分配固定长度的时间片。
执行任务:当前任务在其时间片内运行。如果任务完成,它可以通知调度器,调度器可以立即切换到下一个任务,或者等待当前时间片结束。
时间片管理:当任务的时间片用尽时,调度器保存当前任务的上下文(如果需要),并切换到下一个任务。如果任务没有完成,它将在下一个时间轮中得到继续执行的机会。
循环:调度器不断循环这个过程,确保每个任务都有机会执行。

使用特权

评论回复
19
claretttt| | 2024-10-14 12:33 | 只看该作者
通过调整任务的时间间隔和执行顺序,可以灵活地适应不同的应用场景和需求。

使用特权

评论回复
20
mickit| | 2024-10-14 20:03 | 只看该作者
时间轮片法具有良好的可扩展性,可以轻松地添加新任务或删除现有任务,而不会影响系统的其他部分。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

7

主题

1492

帖子

0

粉丝