架构的运行机制
(首先给大家看个例子,如果这个例子大家能够看得懂,那么后面的框架大家也一样可以看懂。)
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t* event_idx)
{
Flag1 = 1;
}
while(1)
{
If(1 == flag1 || 1==flag2)
{
flag1 = 0;
Task1;
}
}
(上面的程序标志位有两个一个是flag1另一个是flag2。下面的框架是多个任务,也就是多个标志位。每个标志位对应着一个任务。在调度的过程中有两类标志位,一类标志位时可变的,下面你会看到这类标志位有三个动作,置标志位->判断标志位->清楚标志位。然而另一类标志位只有两个动作,置标志位->判断标志位。
)
while (1)
{
task_handle();
}
void task_handle(void)
{
uint8_t i;
for (i = 0; i < ARRAY_SIZE(tasks); ++i)
{
if ((is_task_set(tasks[i].id))
|| (is_task_always_alive(tasks[i].flags)))
{
reset_task(tasks[i].id);
tasks[i].handle(tasks[i].args);
}
}
}
从代码上来看这是一个轮询机制。
调度函数的条件有两个如下:
- is_task_set(tasks[i].id) 相当于小程序的(if(flag1 == )
- is_task_always_alive(tasks[i].flags)((if(flag2 == 1)))
那么这个两个标志位的不同在哪里?
下面会详细讲解
第一种调度的原理:
标志位被置位的条件:
- 中断置位,在应用程序中调度任务函数(USB下发的测试,升级等任务)
- 定时置位,使应用函数在固定的时间间隔内被调度执行(读取touchpad,按键等任务)
- 在任务中调度另一个任务
我们先看第一种条件的调度:
- notify(tasks[i].id); 相当于 flag=1
- is_task_set(tasks[i].id) 相当于 if(1==flag)
- reset_task(tasks[i].id) 相当于 flag=0
struct task
{
uint8_t id;
uint8_t flags;
void *args;
void (*handle)(void *args);
};
对应于下列:
const struct task tasks[] =
{ uint8_t id; uint8_t flags; void *args; void (*handle)(void *args)
{EV_TICK_LITTLE, ALWAYS_ALIVE, NULL, sys_tick_little},
{EV_READ, ALWAYS_ALIVE, NULL, read_rf_status_cmd},
{EV_CONTROL_IR, 0, NULL, period_control_ir},
{EV_100MS, 0, NULL, task_100ms},//
{EV_SEC, 0, NULL, task_sec},
{EV_TICK, ALWAYS_ALIVE, NULL, sys_tick},
{EV_REPORT_DATA, 0, NULL, report_data_rf},
{EV_USB_TASK, 0, NULL, handle_usb_task},
{EV_3MS, 0, NULL, task_3ms},
{EV_6MS, 0, NULL, task_6ms},
{EV_12MS, 0, NULL, task_12ms},
{EV_CALIBRATE_GRY_ACC, 0, NULL, calibrate_gry_acc},
};
解读代码:
ARRAY_SIZE(tasks)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
计算出任务的个数。
第一种条件下的调度原理,先看这个函数的展开:
- notify(tasks[i].id);
#define notify(task_bit) set_bit(task_monitor, task_bit)
#define set_bit(x, bit) do{ __disable_irq();(x) |= 1 << (bit);__enable_irq();}while(0)
注:执行置位的时候开启了总中断,其实中断和应用程序在用同一个资源,要保证同一资源在一个时候是唯一被应用的。
- is_task_set(tasks[i].id)
#define is_task_set(task_bit) is_bit_set(task_monitor, task_bit)
#define is_bit_set(x, bit) ((x) & (1 << (bit)))
- reset_task(tasks[i].id)
#define reset_task(task_bit) reset_bit(task_monitor, task_bit)
#define reset_bit(x, bit) do{ __disable_irq();(x) &= ~(1 << (bit));__enable_irq();}while(0)
- is_task_always_alive(tasks[i].flags))
#define is_task_always_alive(flags) (flags & ALWAYS_ALIVE)
enum
{
ALWAYS_ALIVE = 0x01
};
上面这三个函数里,有一个共同的全局变量task_monitor,在代码里的定义为:
uint16_t task_monitor;
三个函数项目间的联系是通过这个全局变量task_monitor,从定义上看,这是一个16位的全局变量,也就意味着我们的框架最多可以调度16个任务,每一个任务占用一位。
16个任务对于一般的项目是够用了,如果不够用这个框架还可以扩展到32个任务,可以修改如下:
uint32_t task_monitor;
下面是定义的ID号:
enum
{
EV_TICK_LITTLE,
EV_READ,
EV_CONTROL_IR,
EV_100MS,
EV_SEC,
EV_TICK,
EV_USB_TASK,
EV_3MS,
EV_6MS,
EV_12MS,
EV_CALIBRATE_GRY_ACC,
};
目前用到的是14个任务,从左往右,从上往下,是对应16位task_monitor;的从低位到高位,如下图对应:
EV_TICK_LITTLE | EV_READ | EV_CONTROL_IR | EV_100MS | EV_SEC | EV_TICK | EV_MIN | EV_CALLBACK |
EV_USB_TASK | EV_3MS | EV_6MS | EV_12MS | EV_CALIBRATE_GRY_ACC | EV_REPORT_DATA |
|
|
第一种情况下函数调度的调度原理如下:
task_monitor |
is_task_set |
USB等中断 |
reset_task |
notify |
调度对应的函数 |
定时器 |
第二种调度的原理以及定时任务的建立:
is_task_always_alive(tasks[i].flags
如果任务对应的flag 是ALWAYS_ALIVE,那么轮询到这个flag就会调用对应的函数。
const struct task tasks[] =
{ uint8_t id; uint8_t flags; void *args; void (*handle)(void *args)
{EV_TICK_LITTLE, ALWAYS_ALIVE, NULL, sys_tick_little},
{EV_READ, ALWAYS_ALIVE, NULL, read_rf_status_cmd},
{EV_CONTROL_IR, 0, NULL, period_control_ir},
{EV_100MS, 0, NULL, task_100ms},//
{EV_SEC, 0, NULL, task_sec},
{EV_TICK, ALWAYS_ALIVE, NULL, sys_tick},
{EV_REPORT_DATA, 0, NULL, report_data_rf},
{EV_USB_TASK, 0, NULL, handle_usb_task},
{EV_3MS, 0, NULL, task_3ms},
{EV_6MS, 0, NULL, task_6ms},
{EV_12MS, 0, NULL, task_12ms},
{EV_CALIBRATE_GRY_ACC, 0, NULL, calibrate_gry_acc},
};
从上面的任务看使用ALWAYS_ALIVE 的任务有三个分别是sys_tick_little,read_rf_status_cmd,sys_tick 。
sys_tick_little和sys_tick是用来驱动定时任务的。
sys_tick函数驱动的任务:
sys_tick_little |
notify(EV_12MS)
|
notify(EV_3MS) |
notify(EV_6MS)
|
sys_tick |
notify(EV_MIN)
|
notify(EV_100MS) |
notify(EV_SEC)
|
定时调度任务是用time2来进行计时的。
notify(EV_100MS) |
TIME2 |
_sys_tick |
last_tick |
sys_tick |
notify(EV_MIN)
|
notify(EV_SEC)
|
可以讲解一个例子来说明
每一个事务的出现都有它的两面性,比如核能的出现,可以用来发电也可以用来制造武器。现在MCU(也就是单片机)常用的操作系统是ucoss系列和freertos,企业最常用的是freertos。用系统比框架有很多的优势,这里就不多说了。如果我们的产品功能不是太多就没有必要用系统,再者就是系统会占用flash空间。这个框架在一个小时以内就可以入门,系统需要的时间较长一点。系统还会有移植的问题,但是现在不比担心了,如果你想用freertos,用ST官方提供的Cube就可以自动生成。Cube是一个很好用的工具,可以有空学习一下。
这个框架的缺点:
- 里面的任务没有优先级。
- 任务最好碎片化处理,每个任务的执行时间最好不好超过你最小的定时任务。(假设你的最小定时任务是3ms,然而有一个任务是4ms的执行时间,那么你的定时任务最小应该是4ms,而不是3ms)。
- 假设最小的定时间隔是3ms,其他任务的执行时间也小于3ms,那么定时3ms的任务会出现不准确的现象,你的定时任务会出现:
3ms <= 定时任务时间 <= 3ms + (除3ms以外任务运行的时间)
read_rf_status_cmd |
handle_usb_task |
report_data_rf |
task_3ms |
sys_tick_little |
作者:albert