DSPBIOS总结

更新时间:2023-10-16 16:35:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

DSP/BISO线程调度

DSP/BIOS使用户的应用程序可以由一个线程集合构筑起来,每一个线程执行一个模块化的功能。通过允许高优先级的线程抢占低优先级的线程,以及允许阻塞、同步、通信等各种线程间的交互方式,使得多线程的应用程序可以在一个处理器上运行。DSP/BIOS支持多种不同优先级的线程,每种线程类型都有不同的执行和抢占特性。这些线程按照优先级从高到低的顺序排列如下:

? 硬件中断(HWI),包括CLK函数。 ? 软件中断(SWI),包括PRD函数。 ? 任务(TSK)。 ? 后台线程(IDL)。

在DSP/BIOS中,硬件中断有最高的优先级,然后是软件中断,软件中断可以被高优先权软件中断或硬件中断抢先。软件中断是不能被阻塞的。任务的优先权低于软件中断,共有15个任务优先权级别(加上TSK_idle应该16个)。任务在等待某个资源有效时可以被阻塞。后台线程idle_loop是优先级最低的线程。线程优先级如下图表示:

下面具体介绍各种线程。

HWI module

硬件中断是用来处理应用程序响应外部异步事件时必须执行的关键操作。

在典型的DSP应用中,硬件中断是由片上的外设或者DSP外部的设备触发的。中断发生后处理器会通过中断向量跳转到相应的ISR地址。一个中断的跳转由一个DSP/BIOS HWI对象负责,跳转的地址可以是一个用户函数或者是通用的系统HWI调度程序(dispatcher)的入口地址。

硬件中断ISR可以使用汇编语言、C语言或者混合编写。HWI函数通常使用汇编来提高效率。如果要完全使用C语言编写,应该使用系统的HWI调度程序,它会在调用用户C函数的前后分别惊醒现场环境保护与恢复。

所有的硬件中断都会一直运行到结束,如果一个硬件中断在其ISR得到运行机会前被多次触发,该ISR也只运行一次。因此,用户必须尽量和减少HWI执行的代码量。如果GIE位被使能,一个硬件中断可能被任何其他的被使能的中断抢占。

硬件中断没有优先级的区分;线程调度结果首先取决于该类就绪线程是被使能还是被禁止的。

下图表示了硬件中断的抢占线程以及整个调度过程:

当第一个硬件中断(HWI2)产生时,相应的ISR触发了一个比当前运行的

软件中断线程(SWIB)优先级更高的软件中断(SWIA)。并且当第一个ISR运行时,第二个硬件中断(HWI1)产生。因为第一个ISR没有屏蔽掉第二个硬件中断,因此产生第二个ISR抢占了第一个ISR。由于优先级较低的SWI可以被硬件中断异步抢占,因此第一个ISR触发的软件中断在两个硬件中断都执行完毕后才被调度执行。

一、 实验目的

熟悉HWI模块在BIOS下的配置,并初步学会自己独立编写程序实现对TIMER的配置和使用。

二、 实验任务

1、设置定时器来产生周期性的中断。

在CCS3.1版本的bios下右击CSL - Chip Support Library->TIMER->Timer Configuration Manager选择插入timercfg,根据实验要求配置相应的参数。(对于ccs3.3版本的bios下没有csl的配置选项,我们需要自己在程序中编写配置指令) 2、设置HWI

在bios下的Scheduling->HWI->HWI_INT15目录,根据实验要求修改HWI_INT15的Properties。 3、建立LOG

在bios下的Instrumentation->LOG目录,新建一个名为trace的LOG。 4、编写main.c

根据以下的程序框架,编写实验程序。 #include #include #include #include

#include #include #include \

static Uint32 TimerEventId; main() {

/* Get the timer event ID */

TimerEventId = TIMER_getEventId(hTimer1); /* Enable the event */ IRQ_enable(TimerEventId); /* Start the timer */ TIMER_start(hTimer1); }

三、 实验过程及内容

在本实验中使用的是CCS3.3,因此,timer的配置需要自己来编写实现,对于HWI的配置如图1和图2所示,

图1、HWI->GENERAL的配置

图2、HWI->GENERAL的配置

实验的源程序如下:

#include #include #include #include

#include #include #include \

static TIMER_Handle hTimer; static Uint32 TimerEventId; main() {

//---------Configure the timer devices---------// TIMER_Config MyTimConfig={

0x000002C0, //set the value of Timer control register (CTL)

0x00001000, //Set period

0x00000000 //Start count value at zero

};

//Initialise CSL CSL_init();

//Open TIMER1 device, and reset it to power-on default state hTimer=TIMER_open(TIMER_DEV1,0);

TIMER_config(hTimer,&MyTimConfig);

//Enable the timer events(events are disabled while resetting) IRQ_enable(TimerEventId);

//Start the timers TIMER_start(hTimer);

//Obtain the event ID for the timer device TimerEventId = TIMER_getEventId(hTimer);

}

//ISR to service TIMERINT1. void timerHWI(){

LOG_printf(&trace,\}

SWI module

软件中断是通过程序中的调用SWI函数而触发的,它提供了一个介于HWI和TSK之间的额外的优先级,用于处理那些时间限制比TSK严格但比HWI宽松的工作。SWI线程适用于处理发生频率较低的或者实时限制没有硬件中断严格的应用程序作业。SWI线程都会一直运行直到完成。SWI使得HWI可以将一些不太关键的处理委托给一个优先级比它低的SWI线程,从而减少CPU在中断服务程序中花费的时间,使得其他的HWI可以得到运行。

当软件中断被触发时,它会在所有等待中的硬件中断都执行完才开始执行。

SWI_andn函数的作用是该函数提供的参数的“非”与邮箱值做“与”运算,若邮箱为0,则启动该软件中断;如果一个SWI必须在多个事件都发生之后才触发,就使用SWI_andn来调用;当其他的事件都结束后,都应该调用函数SWI_andn来以互补的位掩模参数来清除SWI邮箱里的值,使其为0触发SWI。

Part5: 1、实验步骤

1)基本与PART3和PART4相同,只是利用函数SWI_or在SWI0或者SWI1运行

时触发SWI2:

2)设置SWI2的邮箱值为0,优先级为2;

3)改变main5.c使得:unSWI0函数减少SWI1的邮箱值并设置SWI2的一位;funSWI1设置SWI2的另一位;

2、实验代码

基于PART4的程序修改程序如下: void funPRD0()

{

SWI_post(&SWI0); }

void funSWI0() {

LOG_printf(&trace,\ SWI_dec(&SWI1); SWI_or(&SWI2,1);

LOG_printf(&trace,\}

void funSWI1() {

LOG_printf(&trace,\ SWI_or(&SWI2,2);

LOG_printf(&trace,\}

void funSWI2() {

LOG_printf(&trace,\ LOG_printf(&trace,\}

3、时序图

4、小结

执行PRD_swi后,触发SWI0,在SWI0的执行过程中由于函数SWI_or的作用触发SWI2(优先级为2,最高),SWI0被pending,直到SWI2结束后继续;SWI0结束后触发SWI1(SWI1和SWI2同为1的优先级),在SWI1的执行过程中由于SWI_or的作用再次触发SWI2,待SWI2结束后运行SWI1,直到其结束。

SWI_or是启动该软件中断,并且邮箱值与该函数提供的参数做“或”运算;通过使用函数SWI_or可以根据触发事件的不同来调用不同的函数;SWI_or使用位掩模值来对触发事件的类型进行编码,可以将该位掩模值作为标志来识别事件并选择不同的函数来执行。

IDLE module

空闲循环是DSP/BIOS的后台程序,只有没有硬件中断、软件中断和任务

运行的时候才会循环运行。其他的任何线程都可以在任何时候抢占空闲循环。

IDL管理器允许用户配置自定义的IDL函数,在进入空闲循环时这些IDL函数会被执行。IDL_loop会依照IDL对象创建的顺序调用与每个对象相关联的函数,每次运行一个且当最后一个对象函数运行完后会重新调用第一个IDL函数不断循环。空闲循环是DSP/BIOS中优先级最低的线程。目标DSP和主机DSP/BIOS分析工具间的通信通常是在空闲循环中进行的。这样保证了DSP/BIOS分析工具不会影响应用程序的处理。

默认情况下,空闲循环执行下列IDL对象的函数:

LNK_dataPump:用来管理主机和DSP处理器之间的实时分析数据(LOG和STS数据)和HST数据的传输。这通过使用RTDX实现。

RTA_dispatcher:用于在目标DSP端接收主机实时分析工具的命令,收集目标DSP的监测信息并实时上传给主机PC。它处在两个主机专用通道的末端,通过LNK_dataPump传输命令和数据。

IDL_cpuload:使用一个STS对象IDL_busyObj来计算目标DSP的负荷。它的内容会通过RTA_dispatcher上传给DSP/BIOS分析工具。

任务过程目的及结果:

本实验主要是设置将IDL,SWI,TSK放在一个工程中,看它们的程序调度

以及优先级和创建顺序对以上对象函数执行效果的影响。 实验步骤:

? 创建工程以及DSP/BIOS文件以及将.cmd文件添加入工程; ? 将输入输出改为模拟Simulator;

? 在Instrumentation->LOG添加一个LOG叫做trace; ? 添加两个SWI,如下设置:

? SWI0, priority: 1, mailbox: 0, function: _swi_yjq. ? SWI1, priority: 2, mailbox: 0, function: _swi_yjq1. ? 添加两个TSK,如下设置:

? TSK0, priority: 1,function: _task. ? TSK1, priority: 1,function: _task.

? 编译连接并通过DSP/BIOS->Message Log观察。 程序代码:

#include #include\#include #include

void task(Arg id_arg); void swi_yjq(void); void swi_yjq1(void); void idl_trace(void);

void main() {

LOG_printf(&trace,\}

void idl_trace(void) {

LOG_printf(&trace,\SWI_post(&SWI0); }

void swi_yjq(void) {

LOG_printf(&trace,\ SWI_post(&SWI1);

}

void swi_yjq1(void) {

LOG_printf(&trace,\}

void task(Arg id_arg) {

int i;

int id=ArgToInt(id_arg); for(i=0;i<3;i++) {

LOG_printf(&trace, \ TSK_yield(); }

LOG_printf(&trace, \}

时序图:

结果:在IDL中触发了SWI0,SWI0执行后触发SWI1,因为SWI0和SWI1同一优

先级,所以按创建顺序执行。之后因为有TSK等待,所以执行TSK,待TSK执行完后,又回到了IDL,再次触发SWI0,SWI0再次触发SWI1,不断循环,不过不再出现TSK,这是因为软中断由IDL触发,而IDL是不断循环的,所以一直触发SWI0和SWI1。

MBX module

MBX模块提供了一整套的职能函数来管理邮箱。MBX邮箱可以用来传递讯息,从一个任务到另一个任务在同一个处理器上。一个固定长度的共享邮箱促使了任务间的同步,来确保到来的信息流不会超过系统处理这些信息的能力。 MBX里的邮箱管理不同于SWI中的邮箱结构体。

MBX_create和MBX_delete分别是用来创建和删除邮箱,如下: MBX_Handle MBX_create(msgsize, mbxlength, attrs) Uns msgsize; Uns mbxlength; MBX_Attrs *attrs; Void MBX_delete(mbx) MBX_Handle mbx;

MBX_pend用来从邮箱读取数据。如果邮箱信息不可用(也就是邮箱为空),MBX_pend被阻止。在这种情况下,超时参数允许的任务要等待直到1个超时时间,或者无限期地等待,或不需要等待。 Bool MBX_pend(mbx, msg, timeout) MBX_Handle mbx; Void *msg;

Uns timeout; /* return after this many */ /* system clock ticks */

相反的,MBX_post是用来向邮箱发送数据。如果没有空位(也就是说邮箱是满的),MBX_post被阻止。在这种情况下,超时参数允许的任务要等待直到1个超时时间,或者无限期地等待,或不需要等待。 Bool MBX_post(mbx, msg, timeout) MBX_Handle mbx; Void *msg;

Uns timeout; /* return after this many */ /* system clock ticks */

任务过程目的及结果:

本次实验主要是创建一个读任务(_reader)和三个写任(_writer)来分别向邮箱读取数据或者写入数据。观察实验过程的LOG输出,看邮箱是如何协调任务之间的同步来避免信息流超过系统的处理能力,以及邮箱参数对实验结果的影响和作用。邮箱、读任务和写任务分别是在DSP/BIOS中静态配置的。

实验步骤:

1.创建一个工程和BIOS文件以及main函数,添加进工程; 2.在BIOS文件中创建一下配置:

TSK0,优先级2,函数_writer,参数0; TSK1,优先级2,函数_writer,参数1; TSK2,优先级2,函数_writer,参数2; TSK3,优先级1,函数_reader,参数0;

以及在LOG中配置trace;将RXTD设置为simular以及进行内存配置;

图6 PART6实验的Execution Graph 和Message LOG的分析

从Message LOG的分析窗口,我们可以知道:因为task0的优先级比task1高,因此在task0执行完后(由于SEM_post的执行)semaphore count的值有原来的3累加到4,所以在task1中loop循环了4次,task1才被挂起。

7、在PART7验中,我们将task1和task0的优先级相反(即task1的优先级高于task0), 通过Execution Graph 和Message LOG的分析,我们可以清楚的看到task0和task1在优先级不同的情况下,SEM_post和SEM_pend的执行效果(SEM的初始值为3),如图7所示。

图7 PART7实验的Execution Graph 和Message LOG的分析

PART7的实验结果,从Message LOG上来看执行的结果是一样,但是在Execution Graph上我们可以很清楚的看出task1和task0在优先级不同是执行过程的不同。在PART7中,优先级高的task1先被执行,由于semaphore count的值减至0而被pending,task转至低优先级的task0执行。

在task0执行完后,由于SEM_post的作用,SEM的semaphore count被累加到1,所以在task0结束后,task1又被执行了一次,而SEM_pend的作用下,semaphore count被减到0后,task1将再次被pending。

通过这个实验我们可以知道:通过SEM的semaphore count的值可以主动的控制高优先级的task的执行。

为了进一步证实这一结论,我们把低优先级的task0中的SEM_post(&SEM0)语句注释掉。执行的效果如图8所示。

图8 Execution Graph 和Message LOG的分析

从Execution Graph 和Message LOG的分析窗口中,我们可以明确的看到,由于task1挂起后semaphore count的值一直为0,所以在task0执行完后,尽管task1的优先级高,但却未被执行。

CLK module

DSP/BIOS提供两种计时方法——高、低分辨率时间和系统时钟。低分辨率的计数器记录了定时器中断发生的次数,也就是说低分辨率的时隙管理时钟与定时器中断时钟一致。高分辨率时间是定时器的计数寄存器被减的次数,等于低分辨率计时时间乘以定时器周期寄存器的值,然后再加上定时器计数寄存器当前的值。在默认配置中,系统时钟和低分辨率时间是相同的。在这次实验中将会介绍如何使用系统时钟和如何配置时钟模型。

时钟模块:

CLK模块为用户周期性调用函数提供了方法,同时对一些评估工具提供时间参考。实际上CLK模块完全依赖于DSP的定时器中断。CLK管理器还允许随意建立各种时钟函数,当定时器中断发生时,CLK管理器就执行这些时钟函数。若DSP/BIOS使用了DSP的某个定时器,用户就不能自由地控制这个定时器,而只能将自己的时钟函数放在CLK模块中,有DSP/BIOS内核调用。在C6000系列的开发板中,CLK模型中默认的定时器设置是Timer 0.图一就显示了高、低分辨率时钟是怎样工作的。Timer0设置了一个周期值,当计数器的值等于该周期值的

时候会发生一次中断,低分辨率的计数器会加1。32位的高分辨率计时时间等于低分辨率计时时间(即中断次数)乘以定时器周期寄存器的值,然后在加上定时器计数寄存器当前的值。Microseconds/Int选项中设置两次定时器中断之间的微秒数。默认设置的值为1000,表示定时器中断间隔为1000微妙。即1ms发生一次定时器中断。

在C6000平台上,当CLK管理器被使能时,定时器的计数寄存器值每4个CPU时钟递增一次。在软件仿真的情况下,CPU时钟速率默认设置为133MHZ。所以定时器每4/133000000s计数一次。

在访问时钟模块的时候有几个比较重要的函数:

1.CLK_countspms():函数返回每毫秒的定时器高分辨率时钟的计数值; 2.CLK_gethtime():函数返回高分辨率时间计数; 3.CLK_getltime():函数返回低分辨率时间计数;

4.CLK_getprd:函数返回定时器的周期寄存器(PRD)的值。

前3个函数返回值类型是LgUns,最后一个函数返回值类型是Uns。它们都没有参数。

系统时钟:

很多DSP/BIOS API函数都有一个超时(timeout)参数。DSP/BIOS使用系统时钟来判断是否超时。系统时钟可由低分辨率时钟驱动,也可以由外部时钟源驱动。

系统时钟由PRD模块配置,并由PRD_tick函数执行。当产生定时器中断的时候,DSP/BIOS内核进入中断服务子程序CLK_F_isr函数,这是DSP/BIOS内部的代码,该函数完成一下两个功能:

(1) 对低分辨率计数器加1.其中,低分辨率寄存器是DSP/BIOS内核中

的一个32位的寄存器(地址为0x7C和0x7D单元)。

(2) 执行所有CLK模块中预定义的函数,包括用户定义的时钟中断函数。

一、实验目的:

为了对时钟模型和系统时钟有更深的了解。

二、实验步骤:

1.创建一个名字为clklab的工程文件;

2.在软件仿真下创建一个DSP/BIOS配置文件(.cdb); 3.保存该cdb文件,并加到工程里面。

4.在进行软件仿真的时候应该先在RTDX模块下选择simulator模式,否则在运行的时候会报错。

5.在配置里面添加一个新的任务。新的任务被命名为TSK0. 6.在TSK0的属性中设置其函数名为_testClock。

7.创建一个main.c文件,里面包括一个main空函数和一个testClock函数,并把这个C文件添加到上述工程中。 #include

#include main() { }

testClock() {

/* your code here */ }

8.在testClock中添加相应的代码,并完成以下操作; · 用C语言的printf函数显示出标准I/O每毫秒的计数。 · 写一个100次的循环,显示高分辨率计数器的数字。 · 写一个1000次的循环,显示高分辨率计数器的数字。 ?要确保优化选项是关着的. 如果优化选项是打开的,由于在循环中结果

没有被使用到,所以优化将不执行。我们选择project下的Build options选项关掉优化选项。

· 然后编译和装载程序。 · 运行改工程,并记录结果。

· 核对每秒产生的值,是否是自己所希望的。

三、实验结果:

由于Microseconds/Int的默认设置是1000,即每1ms发生一次定时器中断,而低分辨率的计数器记录了定时器中断发生的次数。所以I/O每毫秒的计数即是低分辨率计数器的值。

部分函数实现代码: Void testClock()

{LOG_printf(&trace,\| %d\\n\

}

实现效果:

本文来源:https://www.bwwdw.com/article/xtff.html

Top