重点看<ARM Cortex-M0权威指南(中文) 高清扫描版>第3章 体系结构
embOS用户手册的第2章:Basic concepts
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0348bc/index.html
RealView® 编译工具 编译器参考指南
4.0 版
参考代码: 新唐 官网
[NUC240][ADC]KEIL_RTX[NUC240][ADC]KEIL RTX\NUC230_240BSP_CMSIS_V3.00.001-KEIL RTX\SampleCode\StdDriver
设置 Keil 工程
别的不需要做。
参考资料:
RL-ARM 实时库用户指南.chm
编译RTX CM1 库 — Link
–cpu Cortex-M0 *.o
C:\Keil_v5\ARM\RV31\LIB\RTX_CM1.LIB --ro-base 0x00000000 --entry 0x00000000 --rw-base 0x20000000 --entry Reset_Handler --first __Vectors --strict
–map --first=‘startup_NUC230_240.o(RESET)’ --datacompressor=off --info=inline --entry Reset_Handler --summary_stderr --info summarysizes --map --xref --callgraph --symbols
–info sizes --info totals --info unused --info veneers
–list “.\lst\GPIO_OutputInput.map”
-o .\obj\GPIO_OutputInput.axf
线程模式(Thread mode) 和 处理模式(Handler mode)
1.MSP和PSP 的含义是Main_Stack_Pointer 和Process_Stack_Pointer,在逻辑地址上他们都是R13
2.权威手册上说的很清楚PSP主要是在Handler的模式下使用,MSP主要在线程模式下使用(当然你在线程模式下也可以调用PSP,需要你做特殊的处理).
3.这意味着同一个逻辑地址,实际上有两个物理寄存器,一个为MSP,一个为PSP,在不同的工作模式调用不同的物理寄存器。举一个简单的例子,很多MCU的的UART只有一个BUFF,TXBUFF和RXBUFF都是一个地址,当你写BUFF时写入的是TXBUFF, 读操作时调用的是RXBUFF。基本原理就是这样。
4.至于为什么这么设计,我想是为了在进行模式转换的时候,减少堆栈的保存工作。同时也可以为不同权限的工作模式设置不同的堆栈。
MSR
page272
__asm void os_set_env (void) {
/* Switch to Unprivileged/Privileged Thread mode, use PSP. */
MOV R0,SP ; PSP = MSP
MSR PSP,R0 ; 将R0写到 PSP中去,也就是将SP写到PSP中去 ;
LDR R0,=__cpp(&os_flags) ;
LDRB R0,[R0] ; 读取一个字节 小端模式 低字节在前
LSLS R0,#31 ; 逻辑左移
BNE PrivilegedE ; Z!=0 的时候跳转
MOVS R0,#0x03 ; Unprivileged Thread mode, use PSP ---- b11
MSR CONTROL,R0 ; 将 R0 写到 CONTROL 寄存器中去,之前是使用MSP,这句话之后开始使用PSP作为堆栈
;现在堆栈中已经有一个值了,该值是 MSP指针指向的位置。
BX LR ; LR是链接寄存器
PrivilegedE
MOVS R0,#0x02 ; Privileged Thread mode, use PSP ---- b10
MSR CONTROL,R0 ;将R0 写到 CONTROL寄存器中去
BX LR ; LR是链接寄存器
ALIGN
}
CONTROL 寄存器的设置:
LR
bx lr
的作用等同于
mov pc,lr
即跳转到lr中存放的地址处。
那么lr存放的是什么地址呢?
lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。
当通过BL或BLX指令调用子程序时,硬件自动将子程序返回地址保存在R14寄存器中。在子程序返回时,把LR的值复制到程序计数器PC即可实现子程序返回。
如,可以使用MOV PC, LR或者BX LR来完成子程序返回。另外,也可以在在子程序入口处使用下面的指令将LR保存到栈中
#define os_sys_init(tsk) os_set_env();
_os_sys_init((U32)rt_sys_init,tsk,0,NULL)
extern void _os_sys_init(U32 p, void (*task)(void), U32 prio_stksz,
void *stk) __SVC_0;
#define __SVC_0 __svc_indirect(0)
import:翻译为进口或引入,表明要调用的函数为外部文件定义
export:翻译为出口或输出,表明该符号可以被外部模块使用,类似于C中的extern功能。
s_flags 是干嘛的
// <q>Run in privileged mode
// =========================
// <i> Run all Tasks in privileged mode.
// <i> Default: Unprivileged
#ifndef OS_RUNPRIV
#define OS_RUNPRIV 0
#endif
#if (__CM__ || __CR__)
U8 const os_flags = OS_RUNPRIV;
#endif
IMPORT SVC_Count
IMPORT SVC_Table
SVC_Table.s
#define os_sys_init(tsk) os_set_env();
_os_sys_init((U32)rt_sys_init,tsk,0,NULL)
extern void _os_sys_init(U32 p, void (*task)(void), U32 prio_stksz,
void *stk) __SVC_0;
#define __SVC_0 __svc_indirect(0)
MRS R0,PSP ; Read PSP
LDR R1,[R0,#24] ; Read Saved PC from Stack ;堆栈向下生长
SUBS R1,R1,#2 ; Point to SVC Instruction
LDRB R1,[R1] ; Load SVC Number
CMP R1,#0
BNE SVC_User ; User SVC Number > 0
MOV LR,R4
LDMIA R0,{R0-R3,R4} ; Read R0-R3,R12 from stack
MOV R12,R4
MOV R4,LR
BLX R12 ; Call SVC Function ;跳转到 rt_sys_init 再跳转回来
这里的R12就是函数地址。rt_sys_init
终于找到为什么了,RealView编译工具3.1版里面有
B 是最简单的分支。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的地址,从那里继续执行。
BNE指令,是个条件跳转,即:是“不相等(或不为0)跳转指令”。如果不为0就跳转到后面指定的地址,继续执行
楼上说的差不多,BNE指令会去查看状态寄存器,当Z!=0的时候就跳转到指定位置.
BEQ功能与BNE刚好相反,Z==0的时候才跳转到指定位置.
__SVC_0 跳转的时候 都默认做了哪些操作 ?
保存了哪些寄存器 ?
LDMIA
RealView® 编译工具 编译器参考指南4.0 版
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0348bc/index.html
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203ic/Bgbcjggh.html
_os_sys_init((U32)rt_sys_init,tsk,0,NULL)
调用:
void rt_sys_init (FUNCP first_task, U32 prio_stksz, void *stk)
rt_sys_init(tsk,0,NULL)
#define os_sys_init(tsk) os_set_env();
_os_sys_init((U32)rt_sys_init,tsk,0,NULL)
__task void init (void) {
t_phaseA = os_tsk_create (phaseA, 1); /* start task phaseA /
t_phaseB = os_tsk_create (phaseB, 1); / start task phaseB /
t_phaseC = os_tsk_create (phaseC, 1); / start task phaseC /
t_phaseD = os_tsk_create (phaseD, 1); / start task phaseD /
t_clock = os_tsk_create (clock, 1); / start task clock /
os_evt_set (0x0001, t_phaseA); / send signal event to task phaseA */
os_tsk_delete_self ();
}
os_sys_init(init);
void rt_sys_init (FUNCP first_task, U32 prio_stksz, void *stk) 解析
__TARGET_ARCH_6S_M
/* Start up first user task before entering the endless loop */
rt_tsk_create (first_task, prio_stksz, stk, NULL);
OS_TID rt_tsk_create (FUNCP task, U32 prio_stksz, void *stk, void *argv) {
/* Start a new task declared with "task". */
P_TCB task_context;
U32 i;
/* Priority 0 is reserved for idle task! */
if ((prio_stksz & 0xFF) == 0) {
prio_stksz += 1;
}
task_context = rt_alloc_box (mp_tcb);
if (task_context == NULL) {
return (0);
}
/* If "size != 0" use a private user provided stack. */
task_context->stack = stk;
task_context->priv_stack = prio_stksz >> 8;
/* Pass parameter 'argv' to 'rt_init_context' */
task_context->msg = argv;
/* For 'size == 0' system allocates the user stack from the memory pool. */
rt_init_context (task_context, prio_stksz & 0xFF, task);
/* Find a free entry in 'os_active_TCB' table. */
i = rt_get_TID ();
os_active_TCB[i-1] = task_context;
task_context->task_id = i;
DBG_TASK_NOTIFY(task_context, __TRUE);
rt_dispatch (task_context);
os_tsk.run->ret_val = i;
return ((OS_TID)i);
}
mp_tcb rtx
TCB - 线程控制块(Thread Control Block,TCB)
rt_init_box
Keil的RTX内核关于内存管理的就这些了,很少很独立吧。比较简单,这块可以单独摘出来为自己学习和使用。
包括Linux源码中的双向循环链表,也是很经典很不错的,实际上都可以单独摘出来,,说不定哪天的项目中就可以用上了。
/* Memory pool for TCB allocation */
_declare_box (mp_tcb, OS_TCB_SIZE, OS_TASKCNT);
U16 const mp_tcb_size = sizeof(mp_tcb);
#define _declare_box(pool,size,cnt) U32 pool[(((size)+3)/4)*(cnt) + 3]
#define rt_init_box _init_box
rt_init_box (&mp_tcb, mp_tcb_size, sizeof(struct OS_TCB));
把一个长度为box_size的连续的地址box_mem,按照 blk_size 分成若干个小格子。
用来存放数据。
这些小格子 之间 互相 标记一下位置,方便联系。就是链表。
int _init_box (void *box_mem, U32 box_size, U32 blk_size)
typedef struct OS_BM {
void free; / Pointer to first free memory block */
void end; / Pointer to memory block end /
U32 blk_size; / Memory block size */
} *P_BM;
rt_tsk_create
OS_TID rt_tsk_create (FUNCP task, U32 prio_stksz, void *stk, void *argv)
rt_init_context (task_context, prio_stksz & 0xFF, task);
task_context — 指针 存放地址
task — 函数指针
rt_init_context (&os_idle_TCB, 0, os_idle_demon);