skaiuijing

前言

在上一篇文章中,笔者介绍了实现时钟触发型RTOS的实现,介绍了Sparrow的时钟及延时阻塞的实现,现在让我们编写具体的代码。

配置SysTick时钟

先把宏添加:

1
2
#define configSysTickClockHz			( ( unsigned long ) 72000000 )
#define configTickRateHz ( ( uint32_t ) 1000 )

修改SchedulerStart函数,配置SysTick中断,装载对应的值,确保1ms发生一次中断。

参考cm3手册装载对应的值:

image-20241030105810641

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
__attribute__( ( always_inline ) ) inline void SchedulerStart( void )
{
/* Start the timer that generates the tick ISR. Interrupts are disabled
* here already. */
( *( ( volatile uint32_t * ) 0xe000ed20 ) ) |= ( ( ( uint32_t ) 255UL ) << 16UL );
/*加入以下代码*/
( *( ( volatile uint32_t * ) 0xe000ed20 ) ) |= ( ( ( uint32_t ) 255UL ) << 24UL );

/* Stop and clear the SysTick. */
SysTick->CTRL = 0UL;
SysTick->VAL = 0UL;
/* Configure SysTick to interrupt at the requested rate. */
SysTick->LOAD = ( configSysTickClockHz / configTickRateHz ) - 1UL;
SysTick->CTRL = ( ( 1UL << 2UL ) | ( 1UL << 1UL ) | ( 1UL << 0UL ) );
/*加入以上代码*/

/* Start the first task. */
__asm volatile (
" ldr r0, =0xE000ED08 \n"/* Use the NVIC offset register to locate the stack. */
" ldr r0, [r0] \n"
" ldr r0, [r0] \n"
" msr msp, r0 \n"/* Set the msp back to the start of the stack. */
" cpsie i \n"/* Globally enable interrupts. */
" cpsie f \n"
" dsb \n"
" isb \n"
" svc 0 \n"/* System call to start first task. */
" nop \n"
" .ltorg \n"
);
}

添加延时表

当然,不止延时表,程序中也有SuspendBitTable这些,这可以被使用者用来记录挂起的任务。

这样,Sparrow的线程就有四种状态了:运行态、就绪态、阻塞态、挂起态。

具体的状态转换函数就由感兴趣的读者自己去完成了。

简单回顾一下延时的算法:

使用两个数组,一个记录溢出的时钟值,一个记录未溢出的延时值。然后使用两个指针,分别指向这两个表,当溢出发生时,两个指针交换地址,这样就完成了溢出表和未溢出表的交换。

介绍一下各个函数:

TicksTableInit:初始化两个延时表数组

TicksTableSwitch:交换两个指针

TaskDelay:先判断计数是否发生溢出,然后把值存储到不同的表中,延时表对应的位置1,表示这个优先级对应的任务在延时。就绪表对应的位置0,表示这个优先级对应的位没有任务需要执行,最后触发上下文切换。

CheckTicks:每发生一次SysTick中断,先判断是否发生了溢出代表当前未溢出的延时数组的任务都已经延时完成,就进行切换。然后遍历未溢出的延时数组的值,判断是否有任务延时结束,如果是,那么就移除延时表,加入就绪表,最后触发上下文切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

uint32_t NextTicks = ~(uint32_t)0;
uint32_t TicksBase = 0;


uint32_t DelayBitTable = 0;
uint32_t TicksTable[configMaxPriori];
uint32_t TicksTableAssist[configMaxPriori];
uint32_t* WakeTicksTable;
uint32_t* OverWakeTicksTable;
#define TicksTableInit( ) { \
WakeTicksTable = TicksTable; \
OverWakeTicksTable = TicksTableAssist; \
}
#define TicksTableSwitch( ){ \
uint32_t *temp; \
temp = WakeTicksTable; \
WakeTicksTable = OverWakeTicksTable; \
OverWakeTicksTable = temp; \
}

uint32_t SuspendBitTable = 0;

//the table is defined for signal mechanism
uint32_t BlockedBitTable = 0;


/*The RTOS delay will switch the task.It is used to liberate low-priority task*/
void TaskDelay( uint16_t ticks )
{
uint32_t WakeTime = TicksBase + ticks;
TCB_t *self = pxCurrentTCB;
if( WakeTime < TicksBase)
{
OverWakeTicksTable[self->uxPriority] = WakeTime;
}
else
{
WakeTicksTable[self->uxPriority] = WakeTime;
}
/* This is a useless operation(for DelayBitTable), it can be discarded.
* But it is retained for the sake of normativity.For example, view the status of all current tasks.*/
DelayBitTable |= (1 << (self->uxPriority) );
ReadyBitTable &= ~(1 << (self->uxPriority) );
switchTask();
}

void CheckTicks( void )
{
TicksBase += 1;
if( TicksBase == 0){
TicksTableSwitch( );
}
for(int i=0 ; i < configMaxPriori;i++)
{
if( WakeTicksTable[i] > 0)
{
if ( TicksBase >= WakeTicksTable[i] )
{
WakeTicksTable[i] = 0;
DelayBitTable &= ~(1 << i );//it is retained for the sake of normativity.
ReadyBitTable |= (1 << i);
}
}
}
switchTask();
}

调度器初始化如下:

1
2
3
4
5
6
7
8
9
10
void SchedulerInit( void )
{
TicksTableInit();
xTaskCreate( leisureTask,
128,
NULL,
0,
&leisureTcb
);
}

添加systick中断,为了防止被打断,加入临界区:

1
2
3
4
5
6
7

void SysTick_Handler(void)
{
uint32_t xre = xEnterCritical();
CheckTicks();
xExitCritical(xre);
}

实验

验证思路:使用Sparrow的阻塞延时代替原先的延时,观察开发板的运行情况是否正常。

修改任务:

对于优先级和延时的设置是有要求的,因为灯亮和灯灭是互斥的。优先级设置不当,可能导致前脚执行灯亮,后脚就执行灯灭了,这样下去,虽然线程是并行执行,但是灯始终是灭的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

//Task Area!The user must create task handle manually because of debugging and specification
TaskHandle_t tcbTask1 = NULL;
TaskHandle_t tcbTask2 = NULL;


void led_bright( )
{
while (1) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
TaskDelay(1000);

}
}

void led_extinguish( )
{
while (1) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
TaskDelay(500);
}
}

void APP( )
{

xTaskCreate( led_bright,
128,
NULL,
1,
&tcbTask1
);

xTaskCreate( led_extinguish,
128,
NULL,
2,
&tcbTask2
);
}





int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();

SchedulerInit();
APP();

SchedulerStart();

while (1)
{

}
}

实验现象

编译程序下载到开发板中,实验现象为led灯闪烁。

本次实验文件夹:skaiui2/SKRTOS_sparrow at experiment

有需要的读者可以自取。

总结

编写了时钟触发和阻塞延时的具体代码,彻底完成了Sparrow的代码。经历二十天的编写,Sparrow的教程也将迎来尾声。

下一篇博客,也就是第二十篇,笔者不得不为Sparrow RTOS的教程画上一个圆满的句号了,是时候让我们的这趟旅程落下帷幕了。

跟着教程一步步来到这里的小伙伴,祝贺你们,完成了一个小小的Sparrow RTOS。虽然很小,但确实是一个真真正正的实时操作系统,如果你们有心去修改它、添加自己的东西,那么它也可以在你们手上大放光彩,发展成为一个优秀的RTOS。