【STM32】STM32单片机总目录
1、轮询、中断、多任务对比
2、什么是任务
如果您学过linux,那么任务可以理解为线程。在代码中的体现就是线程函数,一个函数中有个无限循环函数,并且永不返回。例如:
void Task (void *arg)
{
while(1)
{
。。。
}
}
3、任务栈
3.1 栈
栈stack是一块程序运行时用来存储临时变量的内存RAM空间。栈一般静态分配,并且后进先出,栈的生命周期从程序的起始直到程序结束。一个函数返回,其用到的栈空间就被释放给后续函数使用。
不带操作系统的裸机中,可以视为只有一个任务,任务栈也只有一个,可以在启动文件中看到相关代码:
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
Stack栈的大小为:0x400(1024Byte),一个函数中定义的所有局部变量,加起来不能大于工程的栈大小,否则程序肯定会出现内存溢出,导致复位。
3.2 堆
与栈相类似的还有堆空间,当工程中使用了malloc动态分配内存空间时,这时分配的空间就为堆的空间,同样在启动代码中也能看到堆空间的分配的代码:
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
Heap堆的大小为:0x200(512Byte)
3.3 多任务中栈分配
在操作系统,如ucosIII中,每个任务都需要分配堆栈(如果不需要动态分配内存,可以不分配堆)。
栈空间的分配其实就是,创建一个数组,即连续的内存分配,例如:
__align(8) CPU_STK STkTask1[512];
宏CPU_STK其实就是 unsigned int ,源码如下
typedef CPU_INT32U CPU_STK;
typedef unsigned int CPU_INT32U;
4、任务控制块TCB
任务函数写好后,uCOSIII系统如何调度我们写的任务函数?这就需要通过任务控制块TCB来让uCOSIII识别、调度任务。
任务控制块TCB相当于任务的身份证,里面存有任务的所有信息,比如任务的堆栈,任务名称,任务的形参等。
任务控制块TCB源码如下:
最主要的有两个任务函数指针 (CPU_STK *StkPtr)和任务栈大小(CPU_STK_SIZE StkSize;)
struct os_tcb {
CPU_STK *StkPtr;
void *ExtPtr;
CPU_STK *StkLimitPtr;
#if (OS_CFG_DBG_EN == DEF_ENABLED)
CPU_CHAR *NamePtr;
#endif
OS_TCB *NextPtr;
OS_TCB *PrevPtr;
#if (OS_CFG_TICK_EN == DEF_ENABLED)
OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;
#endif
#if ((OS_CFG_DBG_EN == DEF_ENABLED) || (OS_CFG_STAT_TASK_STK_CHK_EN == DEF_ENABLED) || (OS_CFG_TASK_STK_REDZONE_EN == DEF_ENABLED))
CPU_STK *StkBasePtr;
#endif
#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE];
#endif
#if (OS_CFG_DBG_EN == DEF_ENABLED)
OS_TASK_PTR TaskEntryAddr;
void *TaskEntryArg;
#endif
OS_TCB *PendNextPtr;
OS_TCB *PendPrevPtr;
OS_PEND_OBJ *PendObjPtr;
OS_STATE PendOn;
OS_STATUS PendStatus;
OS_STATE TaskState;
OS_PRIO Prio;
...
5、任务创建函数
任务控制块TCB就是一个结构体,需要封装了任务信息,uCOS提供一个函数将任务信息填充到TCP中,并将它注册到uCOS操作系统中去。让uCOS知道它的存在,并调度它。这个函数在uCOS中就是OSTaskCreate,函数原型如下:
void OSTaskCreate (OS_TCB *p_tcb,
CPU_CHAR *p_name,
OS_TASK_PTR p_task,
void *p_arg,
OS_PRIO prio,
CPU_STK *p_stk_base,
CPU_STK_SIZE stk_limit,
CPU_STK_SIZE stk_size,
OS_MSG_QTY q_size,
OS_TICK time_quanta,
void *p_ext,
OS_OPT opt,
OS_ERR *p_err)
6、任务就绪列表
所有任务都在一个列表中,供系统切换,这个列表叫做任务就绪列表:OSRdyList
任务就绪列表的定义:OS_RDY_LIST OSRdyList[OS_CFG_PRIO_MAX]
节点个数最大64个,也就是说,最多可以创建64个任务:#define OS_CFG_PRIO_MAX 64u
节点类型 OS_RDY_LIST 其实就是组成双向列表的结构体,原代码如下
typedef struct os_rdy_list OS_RDY_LIST;
struct os_rdy_list{
OS_TCB *HeadPtr;
OS_TCB *TailPtr;
};
7、任务初始化OSInit
系统初始化一般都是在硬件初始化完后再执行。代码如下,系统初始化函数OSInit在时钟初始化、外部设备初初始化以后运行
int main(void)
{
OS_ERR err;
System_Init();
MX_GPIO_Init();
OSInit(&err);
。。。
}
OSInit主要完成的工作:初始化全局变量、初始化任务就绪列表。
初始化的全局变量包括:
OSRunning 系统的运行状态,默认是停止状态OS_STATE_OS_STOPPED
OSTCBCurPtr:当前正在运行的任务TCB指针
OSTCBHighRdyPtr:任务就绪列表中,优先级最高的任务TCB指针
8、任务启动OSStart
先装载最高优先级任务到当前任务指针,然后执行任务切换函数:OSStartHighRdy
void OSStart (OS_ERR *p_err)
{
OS_OBJ_QTY kernel_task_cnt;
kernel_task_cnt = 0u;
if (OSRunning == OS_STATE_OS_STOPPED) {
OSPrioHighRdy = OS_PrioGetHighest();
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING;
OSStartHighRdy();
*p_err = OS_ERR_FATAL_RETURN;
} else {
*p_err = OS_ERR_OS_RUNNING;
}
}
9、任务切换OSStartHighRdy
任务切换函数OSStartHighRdy是汇编写的,在os_cpu_a.asm中,部分源码如下,本人不懂汇编,不在展开解释
主要完成的工作是:
保存上下文(将当前任务的各个CPU寄存器中值保存到任务栈中)
切换上下文(将下个任务栈中的内容加到CPU寄存器中)
OSStartHighRdy
CPSID I ; Prevent interruption during context switch
MOV32 R0, NVIC_SYSPRI14 ; Set the PendSV exception priority
MOV32 R1, NVIC_PENDSV_PRI
STRB R1, [R0]
。。。
10、任务调度
在每个任务函数的循环中会需要执行一个函数: OSTimeDly;
OSTimeDly中会调用OSSched;
OSSched会切换下一个任务
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)