打印

GD 32 Trochili RTOS 移植

[复制链接]
3040|38
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
rochili RTOS是一个全新的适用于嵌入式领域的实时内核,它完全由C语言开发,支持多任务、多优先级、抢占式调度。
trochili RTOS的含义,取蜂鸟之意,意味着体积小巧、动作灵敏。
沙发
chenqiang10|  楼主 | 2018-10-26 22:06 | 只看该作者
处理器启动
在移植内核时,我们首先比较关心处理器的启动流程。在 GD 提供的库文件启动文件(3.5版)中有一段启动代码

使用特权

评论回复
板凳
chenqiang10|  楼主 | 2018-10-26 22:08 | 只看该作者
代码清单 : GD32 启动代码 startup_gd32f20x.s  

;Reset_Handler 子程序开始
Reset_Handler PROC

;输出子程序 Reset_Handler 到外部文件
EXPORT Reset_Handler [WEAK]

;从外部文件中引入__main 函数
IMPORT __main

;从外部文件引入 SystemInit 函数
IMPORT SystemInit

;把 SystemInit 函数调用地址加载到通用寄存器 r0
LDR R0, =SystemInit

1;跳转到 r0 中保存的地址执行程序(调用 SystemInit 函数)
BLX R0

;把 main 函数调用地址加载到通用寄存器 r0
LDR R0, =__main

;跳转到 r0 中保存的地址执行程序(调用 main 函数)
BX R0

;Reset_Handler 子程序结束
ENDP


使用特权

评论回复
地板
chenqiang10|  楼主 | 2018-10-26 22:09 | 只看该作者
当芯片复位或上电的时候,会首先执行这段程序。 先后调用函数 SystemInit()和函数
__main,最后转到用户文件中的“main”函数入口,开始运行用户程序。

使用特权

评论回复
5
chenqiang10|  楼主 | 2018-10-26 22:11 | 只看该作者
SystemInit()函数定义在 system_gd32f1x0.c 文件之中,它的作用是设置系统时钟
SYSCLK。它首先将与配置时钟相关的寄存器都复位,然后调用函数 Set_SysClock ()来设置
时钟。

使用特权

评论回复
6
chenqiang10|  楼主 | 2018-10-26 22:12 | 只看该作者
Set_SysClock ()代码 :
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#elif defined SYSCLK_FREQ_96MHz
SetSysClockTo96();
#elif defined SYSCLK_FREQ_108MHz
SetSysClockTo108();
#elif defined SYSCLK_FREQ_120MHz
SetSysClockTo120();
#elif defined SYSCLK_FREQ_48MHz_HSI
SetSysClockTo48HSI();
#elif defined SYSCLK_FREQ_72MHz_HSI
SetSysClockTo72HSI();#elif defined SYSCLK_FREQ_108MHz_HSI
SetSysClockTo108HSI();
#elif defined SYSCLK_FREQ_120MHz_HSI
SetSysClockTo120HSI();
#endif
}




使用特权

评论回复
7
chenqiang10|  楼主 | 2018-10-26 22:15 | 只看该作者
从 Set_SysClock ()代码可以知道,它是通过宏来选择编译不同时钟配置的。 而且 GD32F207
能够达到 120M 最大主频。

使用特权

评论回复
8
chenqiang10|  楼主 | 2018-10-26 22:17 | 只看该作者
SYSCLK_FREQ 宏定义 :
/* Uncomment the corresponding line to configure system clock that you need
*/
/* The clock is from HSE oscillator clock */
//#define SYSCLK_FREQ_HSE HSE_VALUE
//#define SYSCLK_FREQ_24MHz 24000000
//#define SYSCLK_FREQ_36MHz 36000000
//#define SYSCLK_FREQ_48MHz 48000000
//#define SYSCLK_FREQ_56MHz 56000000
//#define SYSCLK_FREQ_72MHz 72000000
//#define SYSCLK_FREQ_96MHz 96000000
//#define SYSCLK_FREQ_108MHz 108000000
#define SYSCLK_FREQ_120MHz 120000000




使用特权

评论回复
9
chenqiang10|  楼主 | 2018-10-26 22:17 | 只看该作者
这里定义了宏 SYSCLK_FREQ_72MHz, Set_SysClockTo120()函数就是最底层的库函数,它会具体配置和检查各种底层寄存器。

使用特权

评论回复
10
chenqiang10|  楼主 | 2018-10-26 22:23 | 只看该作者
内核文件和剪裁
看一下 Trochili RTOS 文件目录,下面列举了内核的全部代码文件:
目录 文件 功能
\src\cpu\ tcl.GD32f20x.a.asm GD32 处理器功能代码,汇编实现
tcl.GD32f20x.c GD32 处理器功能代码,C 实现
\src\ tcl.kernel.c 内核启动和中断代码
tcl.thread.c 线程管理和调度实现代码
tcl.timer.c 定时器功能实现代码
tcl.irq.c 中断管理代码
tcl.mem.pool.c 对象池管理代码
tcl.debug.c 调试代码
\src\ipc\ ipc.c 各种 ipc 机制的支持函数
semaphore.c 信号量实现代码
mutex.c 互斥量实现代码
mailbox.c 邮箱实现代码
message.c 消息队列实现代码
\src\lib\ tcl.lib.c 队列等数据结构支持函数
\src\ trochili.c 内核 API 实现代码
\inc\ tcl.config.h 内核配置文件,主要是各种宏定义
tcl.types.h 内核数据类型定义
***.h 内核头文件集合,各个模块的头文件





  



使用特权

评论回复
11
chenqiang10|  楼主 | 2018-10-26 22:25 | 只看该作者
Trochili RTOS 的文件目录结构是很清晰的,并且各个文件很独立。在上面介绍的内核文
件列表中,文件 tcl.config.h 是内核的配置文件。 在这个文件中,有很多内核功能的宏开关,
通过对这些宏的配置,可以对内核功能做出最合适的剪裁。

使用特权

评论回复
12
chenqiang10|  楼主 | 2018-10-26 22:26 | 只看该作者
这些宏包括:内核配置代码  

#ifndef _TCL_CFG_H
#define _TCL_CFG_H
/* 内核时钟节拍配置,硬件定时器每秒中断次数 */
#define TCL_TIME_TICK_RATE (100U)
/* 线程优先级 {0,1,2,30,31} 共5个优先级保留给内核用,其它优先级分配给用户线程使
用 */
/* 内核支持的最大优先级数 */
#define TCL_PRIORITY_NUM (32U)
/* 内核最低线程优先级 */
#define TCL_LOWEST_PRIORITY (TCL_PRIORITY_NUM - 1U)
/* 用户线程优先级范围配置 */
#define TCL_USER_PRIORITY_LOW (29U)
#define TCL_USER_PRIORITY_HIGH (3U)
/* 内核断言功能配置 */
#define TCL_ASSERT_ENABLE (1)/* 栈溢出检查功能配置 */
#define TCL_THREAD_STACK_CHECK_ENABLE (1)
#define TCL_THREAD_STACK_BARRIER_VALUE (0x5A5A5A5A)
#define TCL_THREAD_STACK_ALARM_RATIO (90U) /* n%, 栈使用量
阀值百分比 */
/* 各种 IPC 功能配置 */
#define TCL_IPC_ENABLE (1)
#define TCL_IPC_MAILBOX_ENABLE (1)
#define TCL_IPC_MQUE_ENABLE (1)
#define TCL_IPC_MUTEX_ENABLE (1)
#define TCL_IPC_SEMN_ENABLE (1)
#define TCL_IPC_FLAGS_ENABLE (1)
#define TCL_IPC_TIMER_ENABLE (1)
/* 定时器功能配置 */
#define TCL_TIMER_ENABLE (1)
#define TCL_TIMER_DAEMON_ENABLE (1)
#define TCL_TIMER_WHEEL_SIZE (32U)
/* 中断管理配置 */
#define TCL_IRQ_ENABLE (1) /* 使能中断管理功能
*/
#define TCL_IRQ_VECTOR_NUM (8U) /* 配置中断向量表表
项数目 */
#define TCL_IRQ_DAEMON_ENABLE (1) /* 使能异步中断处理
线程 */
/* 动态内存管理配置 */
#define TCL_MEMORY_ENABLE (1)
#define TCL_MEMORY_POOL_ENABLE (1)
#define TCL_MEMORY_POOL_PAGES (256U)
#define TCL_MEMORY_BUDDY_ENABLE (0)
#define TCL_MEMORY_BUDDY_PAGES (32*16)
/* 用户异步中断服务线程优先级、时间片 */
#define TCL_IRQ_AISR_PRIORITY (2U)
#define TCL_IRQ_AISR_SLICE (10U)
/* 内核中断守护线程优先级和时间片 */
#define TCL_IRQ_DAEMON_PRIORITY (1U)
#define TCL_IRQ_DAEMON_SLICE (10U)
#define TCL_IRQ_DAEMON_STACK_BYTES (512U)/* 内核定时器守护线程优先级、时间片和栈大小 */
#define TCL_TIMER_DAEMON_PRIORITY (2U)
#define TCL_TIMER_DAEMON_SLICE (10U)
#define TCL_TIMER_DAEMON_STACK_BYTES (512U)
/* 内核 IDLE 守护线程优先级、时间片和栈大小 */
#define TCL_IDLE_DAEMON_PRIORITY (TCL_LOWEST_PRIORITY)
#define TCL_IDLE_DAEMON_SLICE (0xFFFFFFFF)
#define TCL_IDLE_DAEMON_STACK_BYTES (512U)
/* 内核网络服务线程优先级、时间片和栈大小 */
#define TCL_NET_SERVICE_PRIORITY (4)
#define TCL_NET_SERVICE_SLICE (~0x0)
#define TCL_NET_SERVICE_STACK_BYTES (256*4)
/* 处理器参数配置 */
#define TCL_CPU_LEAST_STACK (256U)
#define TCL_CPU_IRQ_NUM (68)
#define TCL_CPU_CLOCK_FREQ (72U*1024U*1024U)
#endif /* _TCL_CFG_H */


使用特权

评论回复
13
chenqiang10|  楼主 | 2018-10-26 22:36 | 只看该作者
内核移植实现
文件 tcl.gd32f207.a.asm 和 tcl.gd32f207.c 就是我们在 GD32 处理器上移植内核的具体实
现,前者是用汇编实现的部分功能,后者则是 C 语言来实现的。我们来具体分析这两个文件
中的各个函数。  


使用特权

评论回复
14
chenqiang10|  楼主 | 2018-10-26 22:40 | 只看该作者
线程栈初始化函数
首先是线程栈的初始化函数 uCpuBuildThreadStack()。 该函数的作用是伪造一个中断现
场,把线程的上下文初始值保存到线程栈中,然后通过 PendSV 中断来将这个线程的上下文
恢复到处理器,这样就实现了线程的第一次调度运行 。
void CpuBuildThreadStack(TAddr32* pTop, void* pStack, TWord bytes,
void* pEntry, TArgument argument)
{
TReg32* pTemp;
pTemp = (TReg32*)((TWord)pStack + bytes);
/* 伪造处理器中断栈现场,在线程第一次被加载运行时使用。
注意 LR 的值是个非法值,这决定了线程没法通过 LR 退出 */
*(--pTemp) = (TReg32)0x01000000; /* PSR */
*(--pTemp) = (TReg32)pEntry; /* 线程函数 */
*(--pTemp) = (TReg32)0xFFFFFFFE; /* R14 (LR) */
*(--pTemp) = (TReg32)0x12121212; /* R12 */
*(--pTemp) = (TReg32)0x03030303; /* R3 */
*(--pTemp) = (TReg32)0x02020202; /* R2 */
*(--pTemp) = (TReg32)0x01010101; /* R1 */
*(--pTemp) = (TReg32)argument; /* R0, 线程参数 */
/* 初始化在处理器硬件中断时不会自动保存的线程上下文,
这几个寄存器数值没有什么意义,就算内核的指纹吧 */
*(--pTemp) = (TReg32)0x00000054; /* R11 ,T */
*(--pTemp) = (TReg32)0x00000052; /* R10 ,R */
*(--pTemp) = (TReg32)0x0000004F; /* R9 ,O */
*(--pTemp) = (TReg32)0x00000043; /* R8 ,C */
*(--pTemp) = (TReg32)0x00000048; /* R7 ,H */
*(--pTemp) = (TReg32)0x00000049; /* R6 ,I */
*(--pTemp) = (TReg32)0x0000004C; /* R5 ,L */
*(--pTemp) = (TReg32)0x00000049; /* R4 ,I */
*pTop = (TReg32)pTemp;
}




使用特权

评论回复
15
chenqiang10|  楼主 | 2018-10-26 22:42 | 只看该作者
PendSV 中断管理函数
前面的章节中,没有明确说明内核在 Cortex M3 处理器上具体如何完成线程调度的。其实在
内核中,所有线程的调度并不是立刻完成的,而是通过函数 CpuConfirmThreadSwitch ()来
触发 PendSV 来实现的。在有些情况下,还没被响应的 PendSV 中断有可能被系统通过函数
uCpuCancelThreadSwitch()取消。 PendSV 中断处理函数是线程调度的核心代码,这段汇编代
码虽然很短,但涉及的技术细节很多。

使用特权

评论回复
16
chenqiang10|  楼主 | 2018-10-26 23:28 | 只看该作者
PendSV 中断处理函数实现和注解如下:
;Cortex-M3 进入异常服务例程时,自动压栈了 R0-R3,R12,LR(R14,连接寄存器),PSR(程
序状态寄存器)和 PC(R15).
;PSP 不自动压栈,不需要保存到栈中,而是保存到线程结构中;CpuSwitchThread
PendSV_Handler
CPSID I
; 取得线程内容
LDR R0, =uKernelVariable
ADD R1, R0, #4;Nominee
ADD R0, R0, #8;Current
; 如果 uThreadCurrent 和 uThreadNominee 相等则不需要保存寄存器到栈中
LDR R2, [R0]
LDR R3, [R1]
CMP R2, R3
BEQ LOAD_NOMINEE_FILE
; 如果 uThreadCurrent 线程没有被初始化则不需要保存寄存器到栈中
LDR R3, [R2,#0]
AND R3, R3, #0x1
CBZ R3, SWAP_THREAD
STORE_CURRENT_FILE
MRS R3, PSP
SUBS R3, R3, #0x20
STM R3, {R4-R11} ;保存 r4-r11 到 uThreadCurrent 栈中
STR R3, [R2,#4] ;保存 psp 到 uThreadCurrent 线程结构
SWAP_THREAD ; 使得 uThreadCurrent = uThreadNominee;
LDR R3, [R1]
STR R3, [R0]
LOAD_NOMINEE_FILE
LDR R3, [R3,#4] ; 根据 uThreadCurrent 中取得 SP 数值到 R0
LDM R3, {R4-R11} ; 从新线程栈中弹出 r4-11
ADDS R3, R3, #0x20 ; psp 指向中断自动压栈后的栈顶
MSR PSP, R3
; 上电后,处理器处于线程+特权模式+msp。
; 对于第一次 activate 任务,当引发 pendsv 中断后,处理器进入 handler 模式。使
用 msp,
; 返回时,在这里准备使用 psp,从 psp 中弹出 r0...这些寄存器,所以需要修改 LR,
强制使用 psp。
ORR LR, LR, #0x04
CPSIE I;在这里有可能发生中断,而此时新的当前线程的上下文并没有完全恢复。和线程被中断
的情景相似:
;硬件自动保存部分寄存器到线程栈中,其它寄存器还游离在处理器上下文中。
;假如在此时产生的中断 ISR 中调用那些
; (1)会将当前线程从就绪队列中移出的 API,
; (2)或者唤醒了更高优先级的线程,
; (3)调整当前线程或者其它就绪线程的优先级
; (4)系统定时器中断,发生时间片轮转
;那么有可能导致一次新的 PensSv 请求被挂起。
;当下面的语句启动异常返回流程时,会发生前后两个 PendSV 咬尾中断。
;按照 uCM3PendSVHandler 的流程,当前线程的上下文中那些游离的寄存器会再次被
保存到线程栈中,即不继续
;弹栈,也就是说第一次线程上下文切换被强制取消了,转而执行第二次的线程上下文切
换。
; 启动异常返回流程,弹出 r0、 r1、 r2、 r3 寄存器,切换到任务。
BX LR
; 返回后,处理器使用线程+特权模式+psp。线程就在这种环境下运行。




使用特权

评论回复
17
chenqiang10|  楼主 | 2018-10-26 23:30 | 只看该作者
晚到中断的情景
PendSV 中断的优先级被设置成最低,所以从 PendSV 中断被触发到响应再到中断处理的
过程中,有可能发生且他更高级的中断。那些更高级的中断处理函数具体做什么操作都
有可能,所以之前的 PendSV 请求可能又不需要了。

使用特权

评论回复
18
chenqiang10|  楼主 | 2018-10-26 23:30 | 只看该作者
中断嵌套情景
在第一个中断执行的时候, 处理器首先是把部分寄存器保存到用户栈中的,即 PSP, 假
如这个中断被第二个更高优先级中断抢占的话,那么一些寄存器就要自动保存在中断栈
中,即 MSP 中。如果再发生第三个中断, 那么第三个中断也使用 MSP。中断进入时,内
核会把中断计数加 1,在中断退出时,内核会把中断计数减 1,然后当中断计数为 0 的
时候,内核检查当前线程是不是最优线程,如果不是则内核发出线程调度请求,即触发
PendSV 中断。一旦 PendSV 中断执行,它会关闭中断,此时线程调度才真正执行。

使用特权

评论回复
19
chenqiang10|  楼主 | 2018-10-26 23:31 | 只看该作者
中断咬尾的情景
如果第三个中断的优先级又比第一个中断还低,因为不能发生嵌套,所以在第一个中断
退出时,内核发现中断计数为 0, 因此可能会发出 PendSV 中断请求; 随后第三个中断
马上会咬尾操作, 然后在第三个中断退出时,内核又发现中断计数为 0,所以可能会再
次发出 PendSV 中断请求,也有可能会取消线程调度。

使用特权

评论回复
20
chenqiang10|  楼主 | 2018-10-27 12:01 | 只看该作者
临界区管理函数
函数 uCpuEnterCritical()和 uCpuLeaveCritical()通过 CPSID、CPSIE 指令来控制处理器中断。内核多任务启动函数
函数 uCpuStartSysTick()由内核 IDEL 线程调用,前面章节曾介绍过, IDLE 线程启动后,首先调用本函数,然后进入无限循环。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

39

主题

940

帖子

1

粉丝