tiny os

参考博客

自编STM32轻量级操作系统

tiny os

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#define TOS_MAX_TASK 32 // 最大任务数(优先级数)

// 任务控制块(TCB)
struct tos_tcb
{
unsigned int *stack_top_point; // 任务栈顶指针
unsigned int delay; // 任务延时时钟
};
typedef struct tos_tcb tos_tcb_t;

tos_tcb_t tos_tcb_list[TOS_MAX_TASK]; // 任务控制块列表

/**
* 刚开始的时候,每个任务都还没有自己的栈数据,所以我们就先手工写出一些数据。
* 其中LR寄存器时每个子函数返回时的寄存器,由于我们任务里是死循环,不会返回,所以这个地方随便写。
*/
static void __task_end(void)
{
while (1);
}

unsigned char tos_prio_current; // 记录当前运行的任务优先级
unsigned char tos_prio_high_ready; //
volatile unsigned int tos_ready_table; // 任务就绪表

// 设置优先级
inline void tos_set_prio_ready(unsigned char _prio)
{
tos_ready_table |= 0x01 << _prio;
}

// 从任务就绪表中删除
inline void tos_del_prio_ready(unsigned char _prio)
{
tos_ready_table &= ~(0x01 << _prio);
}

// 寻找最高优先级
inline void tos_get_high_ready(void)
{
unsigned char next_prio;
for (next_prio = 0; (next_prio < TOS_MAX_TASK) && (!(tos_ready_table&(0x01 << next_prio))); next_prio++)
{
tos_prio_high_ready = next_prio;
}
}

// 任务创建
void tos_task_create(void (*_task)(void), unsigned int *_stack, unsigned char _prio)
{
unsigned int *p_stack;

p_stack = _stack;
p_stack = (unsigned int *)((unsigned int)(p_stack)&0xFFFFFFF8U);
// 以下寄存器顺序和PendSV退出时寄存器恢复顺序一致
*(--p_stack) = (unsigned int)0x01000000UL; // xPSR状态寄存器,第24位THUMB模式必须置位1
*(--p_stack) = (unsigned int)_task; // entry point 任务函数入口
*(--p_stack) = (unsigned int)__task_end; // R14(LR)
*(--p_stack) = (unsigned int)0x12121212UL; // R12
*(--p_stack) = (unsigned int)0x03030303UL; // R3
*(--p_stack) = (unsigned int)0x02020202UL; // R2
*(--p_stack) = (unsigned int)0x01010101UL; // R1
*(--p_stack) = (unsigned int)0x00000000UL; // R0
// PendSV发生时未自动保存的内核寄存器:R4~R11
*(--p_stack) = (unsigned int)0x11111111UL; // R11
*(--p_stack) = (unsigned int)0x10101010UL; // R10
*(--p_stack) = (unsigned int)0x09090909UL; // R9
*(--p_stack) = (unsigned int)0x08080808UL; // R8
*(--p_stack) = (unsigned int)0x07070707UL; // R7
*(--p_stack) = (unsigned int)0x06060606UL; // R6
*(--p_stack) = (unsigned int)0x05050505UL; // R5
*(--p_stack) = (unsigned int)0x04040404UL; // R4
tos_tcb_list[_prio].stack_top_point = p_stack; // 将该任务控制块中应当指向栈顶的指针,指向了该任务的新栈顶
tos_tcb_list[_prio].delay = 0;
tos_set_prio_ready(_prio);
}

#define TOS_EXCEPT_STACK_SIZE 256
#define TOS_IDLE_STACK_SIZE 256
unsigned int tos_cpu_except_stack[TOS_EXCEPT_STACK_SIZE]; // 主任务堆栈
unsigned int *tos_cpu_except_stack_base; // 指向的是数组最后一个元素
unsigned int tos_idle_stack[TOS_IDLE_STACK_SIZE]; // 空闲任务堆栈
tos_tcb_t *p_tcb_current; // 指向当前任务的tcb
tos_tcb_t *p_tcb_high_ready; // 指向最高级任务的tcb

/**
*
*
*/
static void __idle_task(void)
{
unsigned int idle_count = 0;
while (1)
{
idle_count++;
}
}

/**
* 这里面,会初始化系统时钟,设置主堆栈,同时创建一个空闲任务,为防止某一时刻都没有任务为就绪态时,PC运行这个空闲任务
*
*/
void tos_start(void)
{
system_init();
tos_cpu_except_stack_base = tos_cpu_except_stack + TOS_EXCEPT_STACK_SIZE - 1; // cortex-m3栈向下增长
tos_task_create(__idle_task, &tos_idle_stack[TOS_IDLE_STACK_SIZE - 1], TOS_MAX_TASK - 1); // 空闲任务
tos_get_high_ready(); // 获取最高级的就绪任务
tos_prio_current = tos_prio_high_ready;
p_tcb_high_ready = &tos_tcb_list[tos_prio_high_ready];
tos_start_high_ready(); //
}

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
NVIC_INT_CTRL       EQU     0xE000ED04  ; 中断控制寄存器
NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14)
NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低)
NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值

tos_start_high_ready ; 设置PendSV的异常中断优先级
LDR R0,=NVIC_SYSPRI14
LDR R1,=NVIC_PENDSV_PRI
STRB R1,[R0]

MOVS R0,#0 ; 任务堆栈设置为0
MSR PSP,R0 ; PSP清零,作为首次上下文切换的标志

LDR R0,=tos_cpu_except_stack_base ; 初始化堆栈为tos_cpu_except_stack_base
LDR R1,[R0] ; R1的数据+R2的数据合成一个地址值,该地址存放的数据赋值给R0
MSR MSP,R1 ; 将MSP设置为我们为其分配的内存地址tos_cpu_except_stack_base

LDR R0,=NVIC_INT_CTRL
LDR R1,=NVIC_PENDSVSET
STR R1,[R0] ; 触发PendSV异常

CPSIE I ; 开启中断

PendSV_Handler
CPSID I ; 关中断
MRS R0,PSP ; 把PSP指针的值赋给R0
CBZ R0,tos_cpu_pendsv_handler_nosave ; 如果PSP为0跳到tos_cpu_pendsv_handler_nosave

SUBS R0,R0,#0X20
STM R0,{R4-R11} ; 手动入栈R4-R11

LDR R1,=p_tcb_current ; 这里我们入栈了很多寄存器,当前任务的指针
LDR R1,[R1] ; 这三句让p_tcb_current->stack_top_point指向新的栈顶
STR R0,[R1] ; 即p_tcb_current->stack_top_point = SP,记录下来,之后弹出用

tos_cpu_pendsv_handler_nosave
LDR R0,=p_tcb_current
LDR R1,=p_tcb_high_ready
LDR R2,[R1]
STR R2,[R0] ; 将R2的值写入到[R0]的地址中,实现p_tcb_current = p_tcb_high_ready

LDR R0,[R2] ; 将新的栈顶给R0,实现PSP = p_tcb_high_ready->stack_top_point

LDM R0,{R4-R11} ; 弹出R4-R11
ADDS R0,R0,#0X20

MSR PSP,R0 ; 更新PSP;MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也用MSP

ORR LR,LR,#0X04 ; 置LR的位2为1,那么异常返回后,用户线程使用PSP,40页

CPSID I ; 开中断
; 通常一个函数调用另一个函数时会使用BL指令,这个指令会把当前的PC放到LR寄存器
BX LR ; 把LR寄存器的内容复制到PC寄存器里,从而实现函数的返回
; 在中断出现时,LR寄存器会设为一个特殊的值(函数返回地址),而不是中断之前的PC寄存器内容,这样,当中断函数使用BX LR指令返回

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
void tos_schedule(void)
{
tos_get_high_ready();
if (tos_prio_high_ready != tos_prio_current)
{
p_tcb_high_ready = &tos_tcb_list[tos_prio_high_ready];
tos_prio_current = tos_prio_high_ready;
tos_context_switch();
}
}

void system_init(void)
{
unsigned int reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
reload = SystemCoreClock / 8000000;
reload *= 1000000 / System_Ticks;

Systick->CTRL |= SysTick_CTRL_TICKINT_Msk;
SysTick->LOAD = reload;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}

void SysTick_Handler(void)
{
unsigned char i = 0;

for (; i < TOS_MAX_TASK; i++)
{
if (tos_tcb_list[i].delay)
{
tos_tcb_list[i].delay -= 1000 / System_Ticks;
if (tos_tcb_list[i].delay == 0)
{
tos_set_prio_ready(i);
}
}
}

tos_schedule();
}

void tos_time_delay(unsigned int _ticks)
{
if (_ticks)
{
tos_del_prio_ready(tos_prio_current);
tos_tcb_list[tos_prio_current].delay = _ticks;
tos_schedule();
}
}

1
2
3
4
5
tos_context_switch
LDR R0,=NVIC_INT_CTRL
LDR R1,=NVIC_PENDSVSET
STR R1,[R0] ; 触发PendSV
BX LR

vscode stm32 开发调试

开发环境

  • 系统:window11
  • 硬件:FK407M2-ZGT6(stm32f407zgt6)
  • 调试器:jlink-ob(swd)
  • 软件:vscode

参考链接

注意

  • 调试配置(launch.json)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug",
"cwd": "${workspaceFolder}",
"executable": "./build/FK407M2-ZGT6.elf",
"request": "launch",
"type": "cortex-debug",
"runToEntryPoint": "main",
"servertype": "openocd",
"interface": "swd",
"device": "STM32F407ZGT6",
"configFiles": [
"jlink.cfg",
"stm32f4x.cfg"
]
}
]
}
  • 编译下载配置(tasks.json)
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
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "make",
"args": [
],
"group": "build"
},
{
"label": "download",
"type": "shell",
"command": "openocd",
"args": [
"-f",
"jlink.cfg",
"-f",
"stm32f4x.cfg",
"-c",
"program build/FK407M2-ZGT6.elf verify reset exit"
],
"group": "build"
}
]
}

  • vscode默认快捷键为[ctrl+shift+b],可选择build和download
  • jlink.cfg和stm32f4x.cfg是openocd里的配置
  • jlink.cfg默认路径 “你的安装目录\OpenOCD\share\openocd\scripts\interface\jlink.cfg”
  • stm32f4x.cfg默认路径 “你的安装目录\OpenOCD\share\openocd\scripts\target\stm32f4x.cfg”
  • 这两个文件我直接copy到工程目录下,所以launch.json中configFiles直接写名字(相对路径),也可以写绝对路径

工程文件

冷藏箱

1 引言


img


1.1 编写目的

因需存放生物制剂(便于外出携带),而电商搜索小型冷藏箱(半导体制冷)基本都是胰岛素的,而且尺寸相对较小,无法满足存放需求。
存放药品:阿达木单抗,药盒尺寸:18x9x3(cm),冷藏条件:2~8℃。
结合体积与便携性,设计携带量为两盒。


1.2背景

a. 待开发产品的名称:便携式药品冷藏箱
b. 项目的任务提出者:johnway
c. 项目的开发者:johnway
d. 项目的用户:johnway


1.3定义

系统结构:对系统整体布局的宏观的描述
算法:对于程序内部流程计算的逻辑表达方式。


1.4参考资料

列出有关的参考资料,如:
a. 详细设计说明书(G8567——88)

2 程序系统的结构



3 一般用户设计说明


3.1 程序描述

a. 人机交互:

  • 设置存储的温度范围,如:2~8℃
  • 查看当前温湿度
  • 查看当前功率
  • 查看电池电量

b. 传感器:

  • 温湿度传感器

c. 制冷:

  • 半导体制冷

d. 扩展

  • 数据存储与可视化

3.2 功能


3.3.1 精度

数据精度:只保留两位小数


3.3.2 灵活性

A.供电方式:

  • 电源适配器
  • 充电宝
  • 电池

B.可使用冰袋或者冰盒蓄冷


3.3.3 时间特性的要求。

实时性要求不高

3.4 输入项


3.5 输出项


3.6 算法


3.7 流程逻辑


3.8 接口


3.9 存储分配


3.10 注释设计

说明准备在本程序中安排的注释,如:
a. 在模块首部注释说明模块开始编写时间、编写人员及其基本功能
b. 在变量声明阶段,大概说明变量的类型和用途
c. 在判断、循环或者顺序枝分点上注释说明程序代码的功能


3.11 限制条件

必须保证程序正常


3.12 测试计划

测试用例:选取有代表性的数据,避免使用穷举法。
测试方法:使用白盒测试法,语句覆盖、判定覆盖、条件覆盖等操作。


3.13 尚未解决的问题

暂无