前面我们学习了如何创建一个DSP/BIOS工程,那么我们这里就简单分析一个CCS自带的例程tsk example,一起来学习下这个tsk是如何执行的。
例程code:
#include
#include
#include
#include "tskcfg.h"
#define NLOOPS 5
Void task(Arg id_arg); /* Function for tasks created with Config Tool */
/*
* ======== main ========
*/
Void main()
{
}
/*
* ======== task ======== 程序在执行完main函数后,该如何执行task呢?
*/
Void task(Arg id_arg)
{
Int id = ArgToInt (id_arg);
Int i;
for (i = 0; i < NLOOPS ; i++) {
LOG_printf(&trace, "Loop %d: Task %d Working", i, id);
TSK_yield();
}
LOG_printf(&trace, "Task %d DONE", id);
}
程序在执行完main函数后,该如何执行task呢?
*/
Void task(Arg id_arg)
{
Int id = ArgToInt (id_arg);
Int i;
for (i = 0; i < NLOOPS ; i++) {
LOG_printf(&trace, "Loop %d: Task %d Working", i, id);
TSK_yield();
}
LOG_printf(&trace, "Task %d DONE", id);
}
通过阅读上面的代码,是不是觉得与单片机的程序开发是很相似的呢。的确,BIOS系统编程与单片机的编程是很相似的,不同之处在于BIOS编程在执行main函数时,并没有在main 函数中写一个死循环while(1)函数。那么肯定有人问,那在main 函数中不写一个死循环函数,程序执行完main函数不就结束了吗?那如何执行系统的任务线程呢?在这里就需要介绍下BIOS系统程序启动的顺序了。
DSP/BIOS工程的启动顺序
(1)初始化DSP:DSP/BIOS程序从C/C++环境入口c_int00开始运行。对于C6000平台,在c_int00开始处,系统栈指针(B15)和全局页指针(B14) 被分别设置在堆栈断的末尾和.bss断的开始。控制寄存器AMR、IER、CSR等被初始化;
(2)初始化.bss段:当堆栈被设置完成后,初始化任务被调用,利用.cinit的记录对.bss断的变量进行初始化;
(3)调用BIOS_init初始化用到的各个模块:BIOS_init调用MOD_init对配置用到的各个模块进行初始化,包括HWI_init、HST_init、IDL_init等;
(4)处理.pinit表:.pinit表包含一些指向初始化函数的指针,对C++程序,全局对象类的创建也在此时完成;
(5)调用用户程序的main函数:用户main函数可以是C/C++函数或者汇编语言函数,对于汇编函数,使用_main的函数名。由于此时的硬件、软件中断还没有被使能,所以在用户主函数的初始化中需要注意,可以使能单独的中断屏蔽位,但是不能调用类似HWI_enable的接口来使能全局中断;
(6)调用BIOS_start启动DSP/BIOS:BIOS_start在用户main函数退出后被调用,它负责使能使用的各个模块并调用MOD_startup启动每个模块。包括CLK_startup、PIP_startup、SWI_startup、HWI_startup等。当TSK管理模块在配置中被使用时,TSK_startup被执行,并且BIOS_start将不会结束返回;
(7)执行idle循环:有两种方式进入idle循环。当TSK管理模块使能时,任务调度器运行的TSK_idle任务调用IDL_loop在其它任务空闲时进入idle循环;当TSK模块未被使用时,BIOS_start调用将返回,并执行IDL_loop进入永久的idle循环,此时硬件和软件中断可以抢占idle循环得到执行。由于idle循环中管理和主机的通信,因此主机和目标机之间的数据交互可以进行了
通过了解DSP的启动顺序,我们刚才的疑惑就得以解释了,main函数只是被操作系统用来进行初始化的,初始化结束也就是执行完main函数,系统会调用BIOS_start来启动操作系统以及使能需要使用的模块。此时如果有tsk线程就会去执行tsk线程,空闲时就会进入IDL_loop进行无限循环了。