GD 32 Trochili RTOS 移植

[复制链接]
679|38
 楼主 | 2018-10-26 22:05 | 显示全部楼层 |阅读模式
rochili RTOS是一个全新的适用于嵌入式领域的实时内核,它完全由C语言开发,支持多任务、多优先级、抢占式调度。
trochili RTOS的含义,取蜂鸟之意,意味着体积小巧、动作灵敏。
 楼主 | 2018-10-26 22:06 | 显示全部楼层
处理器启动
在移植内核时,我们首先比较关心处理器的启动流程。在 GD 提供的库文件启动文件(3.5版)中有一段启动代码
 楼主 | 2018-10-26 22:08 | 显示全部楼层
代码清单 : GD32 启动代码 startup_gd32f20x.s  

  1. ;Reset_Handler 子程序开始
  2. Reset_Handler PROC

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

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

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

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

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

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

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

  17. ;Reset_Handler 子程序结束
  18. ENDP
复制代码


 楼主 | 2018-10-26 22:09 | 显示全部楼层
当芯片复位或上电的时候,会首先执行这段程序。 先后调用函数 SystemInit()和函数
__main,最后转到用户文件中的“main”函数入口,开始运行用户程序。
 楼主 | 2018-10-26 22:11 | 显示全部楼层
SystemInit()函数定义在 system_gd32f1x0.c 文件之中,它的作用是设置系统时钟
SYSCLK。它首先将与配置时钟相关的寄存器都复位,然后调用函数 Set_SysClock ()来设置
时钟。
 楼主 | 2018-10-26 22:12 | 显示全部楼层
Set_SysClock ()代码 :
  1. static void SetSysClock(void)
  2. {
  3. #ifdef SYSCLK_FREQ_HSE
  4. SetSysClockToHSE();
  5. #elif defined SYSCLK_FREQ_24MHz
  6. SetSysClockTo24();
  7. #elif defined SYSCLK_FREQ_36MHz
  8. SetSysClockTo36();
  9. #elif defined SYSCLK_FREQ_48MHz
  10. SetSysClockTo48();
  11. #elif defined SYSCLK_FREQ_56MHz
  12. SetSysClockTo56();
  13. #elif defined SYSCLK_FREQ_72MHz
  14. SetSysClockTo72();
  15. #elif defined SYSCLK_FREQ_96MHz
  16. SetSysClockTo96();
  17. #elif defined SYSCLK_FREQ_108MHz
  18. SetSysClockTo108();
  19. #elif defined SYSCLK_FREQ_120MHz
  20. SetSysClockTo120();
  21. #elif defined SYSCLK_FREQ_48MHz_HSI
  22. SetSysClockTo48HSI();
  23. #elif defined SYSCLK_FREQ_72MHz_HSI
  24. SetSysClockTo72HSI();#elif defined SYSCLK_FREQ_108MHz_HSI
  25. SetSysClockTo108HSI();
  26. #elif defined SYSCLK_FREQ_120MHz_HSI
  27. SetSysClockTo120HSI();
  28. #endif
  29. }
复制代码




 楼主 | 2018-10-26 22:15 | 显示全部楼层
从 Set_SysClock ()代码可以知道,它是通过宏来选择编译不同时钟配置的。 而且 GD32F207
能够达到 120M 最大主频。
 楼主 | 2018-10-26 22:17 | 显示全部楼层
SYSCLK_FREQ 宏定义 :
  1. /* Uncomment the corresponding line to configure system clock that you need
  2. */
  3. /* The clock is from HSE oscillator clock */
  4. //#define SYSCLK_FREQ_HSE HSE_VALUE
  5. //#define SYSCLK_FREQ_24MHz 24000000
  6. //#define SYSCLK_FREQ_36MHz 36000000
  7. //#define SYSCLK_FREQ_48MHz 48000000
  8. //#define SYSCLK_FREQ_56MHz 56000000
  9. //#define SYSCLK_FREQ_72MHz 72000000
  10. //#define SYSCLK_FREQ_96MHz 96000000
  11. //#define SYSCLK_FREQ_108MHz 108000000
  12. #define SYSCLK_FREQ_120MHz 120000000
复制代码




 楼主 | 2018-10-26 22:17 | 显示全部楼层
这里定义了宏 SYSCLK_FREQ_72MHz, Set_SysClockTo120()函数就是最底层的库函数,它会具体配置和检查各种底层寄存器。
 楼主 | 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 内核头文件集合,各个模块的头文件





  



 楼主 | 2018-10-26 22:25 | 显示全部楼层
Trochili RTOS 的文件目录结构是很清晰的,并且各个文件很独立。在上面介绍的内核文
件列表中,文件 tcl.config.h 是内核的配置文件。 在这个文件中,有很多内核功能的宏开关,
通过对这些宏的配置,可以对内核功能做出最合适的剪裁。
 楼主 | 2018-10-26 22:26 | 显示全部楼层
这些宏包括:内核配置代码  

  1. #ifndef _TCL_CFG_H
  2. #define _TCL_CFG_H
  3. /* 内核时钟节拍配置,硬件定时器每秒中断次数 */
  4. #define TCL_TIME_TICK_RATE (100U)
  5. /* 线程优先级 {0,1,2,30,31} 共5个优先级保留给内核用,其它优先级分配给用户线程使
  6. 用 */
  7. /* 内核支持的最大优先级数 */
  8. #define TCL_PRIORITY_NUM (32U)
  9. /* 内核最低线程优先级 */
  10. #define TCL_LOWEST_PRIORITY (TCL_PRIORITY_NUM - 1U)
  11. /* 用户线程优先级范围配置 */
  12. #define TCL_USER_PRIORITY_LOW (29U)
  13. #define TCL_USER_PRIORITY_HIGH (3U)
  14. /* 内核断言功能配置 */
  15. #define TCL_ASSERT_ENABLE (1)/* 栈溢出检查功能配置 */
  16. #define TCL_THREAD_STACK_CHECK_ENABLE (1)
  17. #define TCL_THREAD_STACK_BARRIER_VALUE (0x5A5A5A5A)
  18. #define TCL_THREAD_STACK_ALARM_RATIO (90U) /* n%, 栈使用量
  19. 阀值百分比 */
  20. /* 各种 IPC 功能配置 */
  21. #define TCL_IPC_ENABLE (1)
  22. #define TCL_IPC_MAILBOX_ENABLE (1)
  23. #define TCL_IPC_MQUE_ENABLE (1)
  24. #define TCL_IPC_MUTEX_ENABLE (1)
  25. #define TCL_IPC_SEMN_ENABLE (1)
  26. #define TCL_IPC_FLAGS_ENABLE (1)
  27. #define TCL_IPC_TIMER_ENABLE (1)
  28. /* 定时器功能配置 */
  29. #define TCL_TIMER_ENABLE (1)
  30. #define TCL_TIMER_DAEMON_ENABLE (1)
  31. #define TCL_TIMER_WHEEL_SIZE (32U)
  32. /* 中断管理配置 */
  33. #define TCL_IRQ_ENABLE (1) /* 使能中断管理功能
  34. */
  35. #define TCL_IRQ_VECTOR_NUM (8U) /* 配置中断向量表表
  36. 项数目 */
  37. #define TCL_IRQ_DAEMON_ENABLE (1) /* 使能异步中断处理
  38. 线程 */
  39. /* 动态内存管理配置 */
  40. #define TCL_MEMORY_ENABLE (1)
  41. #define TCL_MEMORY_POOL_ENABLE (1)
  42. #define TCL_MEMORY_POOL_PAGES (256U)
  43. #define TCL_MEMORY_BUDDY_ENABLE (0)
  44. #define TCL_MEMORY_BUDDY_PAGES (32*16)
  45. /* 用户异步中断服务线程优先级、时间片 */
  46. #define TCL_IRQ_AISR_PRIORITY (2U)
  47. #define TCL_IRQ_AISR_SLICE (10U)
  48. /* 内核中断守护线程优先级和时间片 */
  49. #define TCL_IRQ_DAEMON_PRIORITY (1U)
  50. #define TCL_IRQ_DAEMON_SLICE (10U)
  51. #define TCL_IRQ_DAEMON_STACK_BYTES (512U)/* 内核定时器守护线程优先级、时间片和栈大小 */
  52. #define TCL_TIMER_DAEMON_PRIORITY (2U)
  53. #define TCL_TIMER_DAEMON_SLICE (10U)
  54. #define TCL_TIMER_DAEMON_STACK_BYTES (512U)
  55. /* 内核 IDLE 守护线程优先级、时间片和栈大小 */
  56. #define TCL_IDLE_DAEMON_PRIORITY (TCL_LOWEST_PRIORITY)
  57. #define TCL_IDLE_DAEMON_SLICE (0xFFFFFFFF)
  58. #define TCL_IDLE_DAEMON_STACK_BYTES (512U)
  59. /* 内核网络服务线程优先级、时间片和栈大小 */
  60. #define TCL_NET_SERVICE_PRIORITY (4)
  61. #define TCL_NET_SERVICE_SLICE (~0x0)
  62. #define TCL_NET_SERVICE_STACK_BYTES (256*4)
  63. /* 处理器参数配置 */
  64. #define TCL_CPU_LEAST_STACK (256U)
  65. #define TCL_CPU_IRQ_NUM (68)
  66. #define TCL_CPU_CLOCK_FREQ (72U*1024U*1024U)
  67. #endif /* _TCL_CFG_H */
复制代码


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


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

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




 楼主 | 2018-10-26 22:42 | 显示全部楼层
PendSV 中断管理函数
前面的章节中,没有明确说明内核在 Cortex M3 处理器上具体如何完成线程调度的。其实在
内核中,所有线程的调度并不是立刻完成的,而是通过函数 CpuConfirmThreadSwitch ()来
触发 PendSV 来实现的。在有些情况下,还没被响应的 PendSV 中断有可能被系统通过函数
uCpuCancelThreadSwitch()取消。 PendSV 中断处理函数是线程调度的核心代码,这段汇编代
码虽然很短,但涉及的技术细节很多。
 楼主 | 2018-10-26 23:28 | 显示全部楼层
PendSV 中断处理函数实现和注解如下:
  1. ;Cortex-M3 进入异常服务例程时,自动压栈了 R0-R3,R12,LR(R14,连接寄存器),PSR(程
  2. 序状态寄存器)和 PC(R15).
  3. ;PSP 不自动压栈,不需要保存到栈中,而是保存到线程结构中;CpuSwitchThread
  4. PendSV_Handler
  5. CPSID I
  6. ; 取得线程内容
  7. LDR R0, =uKernelVariable
  8. ADD R1, R0, #4;Nominee
  9. ADD R0, R0, #8;Current
  10. ; 如果 uThreadCurrent 和 uThreadNominee 相等则不需要保存寄存器到栈中
  11. LDR R2, [R0]
  12. LDR R3, [R1]
  13. CMP R2, R3
  14. BEQ LOAD_NOMINEE_FILE
  15. ; 如果 uThreadCurrent 线程没有被初始化则不需要保存寄存器到栈中
  16. LDR R3, [R2,#0]
  17. AND R3, R3, #0x1
  18. CBZ R3, SWAP_THREAD
  19. STORE_CURRENT_FILE
  20. MRS R3, PSP
  21. SUBS R3, R3, #0x20
  22. STM R3, {R4-R11} ;保存 r4-r11 到 uThreadCurrent 栈中
  23. STR R3, [R2,#4] ;保存 psp 到 uThreadCurrent 线程结构
  24. SWAP_THREAD ; 使得 uThreadCurrent = uThreadNominee;
  25. LDR R3, [R1]
  26. STR R3, [R0]
  27. LOAD_NOMINEE_FILE
  28. LDR R3, [R3,#4] ; 根据 uThreadCurrent 中取得 SP 数值到 R0
  29. LDM R3, {R4-R11} ; 从新线程栈中弹出 r4-11
  30. ADDS R3, R3, #0x20 ; psp 指向中断自动压栈后的栈顶
  31. MSR PSP, R3
  32. ; 上电后,处理器处于线程+特权模式+msp。
  33. ; 对于第一次 activate 任务,当引发 pendsv 中断后,处理器进入 handler 模式。使
  34. 用 msp,
  35. ; 返回时,在这里准备使用 psp,从 psp 中弹出 r0...这些寄存器,所以需要修改 LR,
  36. 强制使用 psp。
  37. ORR LR, LR, #0x04
  38. CPSIE I;在这里有可能发生中断,而此时新的当前线程的上下文并没有完全恢复。和线程被中断
  39. 的情景相似:
  40. ;硬件自动保存部分寄存器到线程栈中,其它寄存器还游离在处理器上下文中。
  41. ;假如在此时产生的中断 ISR 中调用那些
  42. ; (1)会将当前线程从就绪队列中移出的 API,
  43. ; (2)或者唤醒了更高优先级的线程,
  44. ; (3)调整当前线程或者其它就绪线程的优先级
  45. ; (4)系统定时器中断,发生时间片轮转
  46. ;那么有可能导致一次新的 PensSv 请求被挂起。
  47. ;当下面的语句启动异常返回流程时,会发生前后两个 PendSV 咬尾中断。
  48. ;按照 uCM3PendSVHandler 的流程,当前线程的上下文中那些游离的寄存器会再次被
  49. 保存到线程栈中,即不继续
  50. ;弹栈,也就是说第一次线程上下文切换被强制取消了,转而执行第二次的线程上下文切
  51. 换。
  52. ; 启动异常返回流程,弹出 r0、 r1、 r2、 r3 寄存器,切换到任务。
  53. BX LR
  54. ; 返回后,处理器使用线程+特权模式+psp。线程就在这种环境下运行。
复制代码




 楼主 | 2018-10-26 23:30 | 显示全部楼层
晚到中断的情景
PendSV 中断的优先级被设置成最低,所以从 PendSV 中断被触发到响应再到中断处理的
过程中,有可能发生且他更高级的中断。那些更高级的中断处理函数具体做什么操作都
有可能,所以之前的 PendSV 请求可能又不需要了。
 楼主 | 2018-10-26 23:30 | 显示全部楼层
中断嵌套情景
在第一个中断执行的时候, 处理器首先是把部分寄存器保存到用户栈中的,即 PSP, 假
如这个中断被第二个更高优先级中断抢占的话,那么一些寄存器就要自动保存在中断栈
中,即 MSP 中。如果再发生第三个中断, 那么第三个中断也使用 MSP。中断进入时,内
核会把中断计数加 1,在中断退出时,内核会把中断计数减 1,然后当中断计数为 0 的
时候,内核检查当前线程是不是最优线程,如果不是则内核发出线程调度请求,即触发
PendSV 中断。一旦 PendSV 中断执行,它会关闭中断,此时线程调度才真正执行。
 楼主 | 2018-10-26 23:31 | 显示全部楼层
中断咬尾的情景
如果第三个中断的优先级又比第一个中断还低,因为不能发生嵌套,所以在第一个中断
退出时,内核发现中断计数为 0, 因此可能会发出 PendSV 中断请求; 随后第三个中断
马上会咬尾操作, 然后在第三个中断退出时,内核又发现中断计数为 0,所以可能会再
次发出 PendSV 中断请求,也有可能会取消线程调度。
 楼主 | 2018-10-27 12:01 | 显示全部楼层
临界区管理函数
函数 uCpuEnterCritical()和 uCpuLeaveCritical()通过 CPSID、CPSIE 指令来控制处理器中断。内核多任务启动函数
函数 uCpuStartSysTick()由内核 IDEL 线程调用,前面章节曾介绍过, IDLE 线程启动后,首先调用本函数,然后进入无限循环。
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式
我要创建版块 申请成为版主

论坛热帖

快速回复 返回顶部 返回列表