野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 1057|回复: 2

【野火】瑞萨RA MCU创意氛围赛 基于优先级的RTOS内核

[复制链接]
发表于 2023-6-30 10:40:32 | 显示全部楼层 |阅读模式
本帖最后由 桃子念peach 于 2023-8-24 11:48 编辑

一、简介
1.1 项目简介
SimpleRTOS(catOS) 是我大学实习期间为了学习 RTOS 编写的一个简单的内核,主要调度方式基于优先级抢占,该项目重构了两次,故内容和功能有所不同,最新版本仅保留了固定优先级调度方式。
本次项目内容为将该内核对野火的瑞萨启明6M5开发板进行适配,并编写简单的demo验证正确性。
第一次发帖,如果有什么错误请大家批评指正。
1.2 开发板简介
官网特性介绍如下
支持 TrustZone® 的 200MHz Arm® Cortex®-M33
安全芯片功能
1MB - 2MB 闪存、448KB 支持奇偶校验的 SRAM 和 64KB ECC SRAM
具有后台运行能力的双区闪存,以及存储块交换功能
8KB 数据闪存,提供与 EEPROM 类似的数据存储功能
100 引脚封装至 176 引脚封装
带有专用 DMA 的以太网控制器
电容触摸按键感应单元
高速和全速 USB 2.0
CAN FD(也支持CAN 2.0B)
QuadSPI 和 OctaSPI
SCI多功能串口(UART、简单 SPI、简单 I2C)
SPI/ I2C 多主接口
SDHI 和 MMC

1.3 内核设计概述
1.3.1 总体架构设计

- 抢占式优先级调度
- 可移植层
- 重写更简易更轻量级的部分库函数
- 除内核外还提供部分组件
  - 简易的`shell`程序
  - 设备驱动框架
  - 内存地址读写功能

catOS_DESIGN_simple_struct.jpg catOS_DESIGN_sys_start-sys_start.jpg

1.3.2 优先级调度策略
内核中固定优先级调度使用了位图+链表队列的形式,利用位图寻找最高优先级任务并以有序链表的形式进行调度,并且在信号量等临界资源访问上设计了优先级天花板防止优先级反转
bitmap.jpg catOS_DESIGN_sched-prio_sched.jpg prio_inversion-solve.drawio.png

1.3.3 内核实现了一个以组件形式存在的 shell 命令行界面,该 shell 作为一个任务运行在 os 之上
catOS_DESIGN_shell-get_input_char.jpg

二、适配内核
2.1 可移植接口概览
需要实现的可移植接口包括以下部分
  1. /**
  2. * @brief 硬件初始化
  3. */
  4. void cat_hw_init(void);

  5. /**
  6. * @brief 开始调度
  7. *
  8. */
  9. void catos_start_sched(void);

  10. /**
  11. * @brief 上下文切换
  12. *
  13. */
  14. //void cat_hw_context_switch(void);
  15. /**
  16. * @brief 上下文切换
  17. *
  18. * @param  from_task_sp_addr 上一个任务tcb中堆栈指针变量的 *地址*
  19. * @param  to_task_sp_addr   下一个任务tcb中堆栈指针变量的 *地址*
  20. */
  21. void cat_hw_context_switch(cat_uint32_t from_task_sp_addr, cat_uint32_t to_task_sp_addr);

  22. /**
  23. * @brief 切换到第一个任务的上下文
  24. *
  25. * @param  first_task_sp_addr  要切换的任务tcb中堆栈指针变量的 *地址*
  26. */
  27. void cat_hw_context_switch_to_first(cat_uint32_t first_task_sp_addr);

  28. /**
  29. * @brief 关中断进临界区
  30. *
  31. * @return cat_uint32_t
  32. */
  33. cat_uint32_t cat_hw_irq_disable(void);

  34. /**
  35. * @brief 开中断出临界区
  36. *
  37. * @param status
  38. */
  39. void cat_hw_irq_enable(cat_uint32_t status);

  40. /**
  41. * @brief 栈初始化
  42. *
  43. * @param task_entry    任务入口函数地址
  44. * @param parameter     参数
  45. * @param stack_addr    栈起始地址
  46. * @param exit          任务退出函数地址
  47. * @return cat_uint8_t*     初始化后的栈顶地址
  48. */
  49. cat_uint8_t *cat_hw_stack_init(void *task_entry, void *parameter, cat_uint8_t *stack_addr, void *exit);
复制代码

2.2 硬件初始化
在硬件初始化中主要是设置系统时钟中断频率,初始化时设置时钟中断为关闭状态
  1. /**
  2. * @brief 硬件初始化
  3. */
  4. void cat_hw_init(void)
  5. {
  6. /* 设置系统时钟中断频率为100Hz(每秒100次) */
  7. cat_set_systick_period(CATOS_SYSTICK_MS);
  8. }

  9. /**
  10. * @brief 初始化时钟中断
  11. *
  12. * @param ms 每个tick的时间(ms)
  13. */
  14. static void cat_set_systick_period(cat_uint32_t ms)
  15. {
  16. cat_uint32_t err = 0;
  17. cat_uint32_t IT_Period = 0;

  18. IT_Period = ms * SystemCoreClock / 1000;

  19. //err = SysTick_Config(IT_Period);

  20. /* 如果设定的周期太离谱就停在这 */
  21. if ((IT_Period - 1UL) > SysTick_LOAD_RELOAD_Msk)
  22. {
  23. err = 1;
  24. }
  25. assert(0 == err);

  26. SysTick->LOAD = (uint32_t)(IT_Period - 1UL); /* 设置重装载寄存器 */
  27. NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* 设置时钟中断优先级 */
  28. SysTick->VAL = 0UL; /* 设置计数器装载值 */
  29. SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | /* 设定为内核时钟FCLK */
  30. SysTick_CTRL_TICKINT_Msk | /* 设定为systick计数器倒数到0时触发中断 */
  31. ~SysTick_CTRL_ENABLE_Msk; /* 关闭定时器中断,若创建任务则在catos_start_sched()中开启该中断 */

  32. }
复制代码


2.3 开始调度与切换到第一个任务的上下文
开始调度需要从就绪表中获取最高优先级任务并设置为当前任务,并且在恢复第一个任务上下文之前需要打开时钟中断并初始化 pendsv 中断以保证调度的正常工作。
注:这里暂时没有对不使用 fpu 的情况适配,参考 freertos 的可移植接口实现。
  1. /**
  2. * @brief 开始调度
  3. *
  4. */
  5. void catos_start_sched(void)
  6. {
  7. cat_uint32_t tmp_reg = 0;
  8. struct _cat_task_t *first_task = NULL;

  9. /* 获取最高优先级任务 */
  10. first_task = cat_sp_task_highest_ready();

  11. /* 因为是第一个任务,不用像调度时判断是否和上一个任务一样,直接赋值给当前任务就行 */
  12. cat_sp_cur_task = first_task;

  13. /* 允许调度(打开调度锁,并且不在该处进行调度) */
  14. cat_sp_task_sched_enable_without_sched();

  15. /* 开启时钟中断 */
  16. SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;

  17. /* 设置pendsv中断优先级 */
  18. tmp_reg = MEM32(NVIC_SHPR3);
  19. tmp_reg |= NVIC_PENDSV_PRI;
  20. MEM32(NVIC_SHPR3) = tmp_reg;

  21. /* 切换到第一个任务 */
  22. cat_hw_context_switch_to_first((cat_uint32_t)&(first_task->sp));
  23. }

  24. /**
  25. * @brief 切换到第一个任务的上下文
  26. *
  27. */
  28. void cat_hw_context_switch_to_first(void)
  29. {
  30. __enable_irq();
  31. __ISB();

  32. /* 各个寄存器地址 */
  33. __asm volatile (
  34. ".equ SCB_ICSR, 0xE000ED04 \n" // 中断控制寄存器
  35. ".equ SCB_VTOR, 0xE000ED08 \n" // 中断向量表偏移寄存器
  36. ".equ ICSR_PENDSVSET, 0x10000000 \n" // pendsv触发值

  37. ".equ SHPR3_PRI_14, 0xE000ED22 \n" // 系统异常handler优先级寄存器 3 (PendSV).
  38. ".equ PRI_LVL_PENDSV, 0xFF \n"// pendsv优先级 (最低).
  39. ".equ SHPR3_PRI_15, 0xE000ED23 \n"// 系统异常handler优先级寄存器 3 (Systick).

  40. "ldr r1, =cat_context_to_task_sp_ptr \n"
  41. "str r0, [r1] \n"

  42. #if __FPU_USED
  43. //#error "__FPU_USED"
  44. /* 清除control寄存器的FPCA */
  45. "mrs r2, control \n" /* read */
  46. "bic r2, r2, #0x04 \n" /* modify */
  47. "msr control, r2 \n" /* write-back */
  48. #else
  49. #error "must use fpu"
  50. #endif

  51. /* 将变量 cat_context_from_task_sp_ptr 设置为0*/
  52. "ldr r1, =cat_context_from_task_sp_ptr \n"
  53. "mov r0, #0 \n"
  54. "str r0, [r1] \n"

  55. "mov r4, #0x1234 \n"

  56. /* 触发pendsv中断,允许中断后会立即进入pendsv切换 */
  57. "ldr r0, =SCB_ICSR \n"
  58. "ldr r1, =ICSR_PENDSVSET \n"
  59. "str r1, [r0] \n" /* *(SCB_ICSR) = "ICSR_PENDSVSET */

  60. /* 不会到达这里 */
  61. "dsb \n"
  62. "isb \n"
  63. "svc 0 \n"
  64. );
  65. }
复制代码


2.4 上下文切换

上下文切换使用 pendsv 中断进行,主要工作为保存当前任务堆栈和寄存器以及恢复下一个任务的堆栈和寄存器。
  1. /**
  2. * void cat_hw_context_switch(void)
  3. * 触发pendsv中断进行任务切换(pendsv的优先级在开始第一个任务时已经设置)
  4. */
  5.     .global cat_hw_context_switch
  6.     .type cat_hw_context_switch, %function
  7. cat_hw_context_switch:
  8.     /* 将两个任务的堆栈指针变量的 *地址* 加载到临时变量中 */
  9.     /* cat_context_from_task_sp_ptr = &(cat_sp_cur_task->sp) */
  10.     ldr r2, =cat_context_from_task_sp_ptr
  11.     str r0, [r2]

  12.     /* cat_context_to_task_sp_ptr = &(cat_sp_next_task->sp) */
  13.     ldr r2, =cat_context_to_task_sp_ptr
  14.     str r1, [r2]

  15.     /* 触发pendsv中断进行切换 */
  16.     ldr r0, =SCB_ICSR      
  17.     ldr r1, =ICSR_PENDSVSET
  18.     str r1, [r0]                /* *(SCB_ICSR) = ICSR_PENDSVSET */
  19.     bx  lr


  20. void PendSV_Handler(void)
  21. {
  22.     __asm volatile (
  23.         /* 关闭全局中断并保存当前中断屏蔽寄存器中的值方便恢复 */
  24.     "mrs r2, primask                            \n"
  25.     "cpsid i                                    \n"

  26.     /* 保存中断屏蔽寄存器状态 */
  27.     "push {r2}                                  \n"

  28.     /* 获取from任务的堆栈指针变量中的值 */
  29.     /* 在进入pendsv之前 cat_context_from_task_sp_ptr = &(from_task->sp) */
  30.     /** 故有:
  31.      * r0 =  &(from_task->sp)
  32.      * r1 = *(&(from_task->sp)) 等价于 r1 = from_task->sp
  33.      */
  34.     "ldr r0, =cat_context_from_task_sp_ptr      \n"
  35.     "ldr r1, [r0]                               \n"
  36.     /* 如果为零则说明是第一个任务 */
  37.     "cbz r1, switch_to_thread                   \n"

  38. /* 暂时可能用不到trustzone, 因此直接跳转 */
  39.     "b contex_ns_store                          \n"
  40. /* 暂时可能用不到 END */

  41. "contex_ns_store:                               \n"

  42.     /* 用户级堆栈是psp,特权级堆栈是msp */
  43.     /* 任务用的是psp,将当前寄存器保存到堆栈中 */
  44.     "mrs r1, psp                                \n"

  45. #if __FPU_USED
  46.     "tst lr, #0x10                              \n"
  47.     "it eq                                      \n"
  48.     "vstmdbeq r1!, {s16-s31}                    \n"

  49. #if BSP_TZ_NONSECURE_BUILD
  50. #else
  51.         /* Stack R4-R11 on the process stack. Also stack LR since the FPU is supported. */
  52.         "STMDB     R1!, {R4-R11, LR}             \n"
  53. #endif
  54. #else
  55. #error "must use fpu"
  56. #endif

  57. #if BSP_TZ_NONSECURE_BUILD
  58. #error "should not use BSP_TZ_NONSECURE_BUILD"
  59. #elif RM_CATOS_PORT_PSPLIM_PRESENT
  60.         "mrs     r3, psplim                      \n" /* R3 = PSPLIM. */
  61.         "stmdb   r1!, {r3}                       \n"
  62. #endif

  63.     /* 记录最后的指针到任务栈curstk->stack */
  64.     /* 更新tcb的堆栈指针变量值 */
  65.     /**
  66.         from_task->sp = r1
  67.      */
  68.     "ldr r0, [r0]                               \n"
  69.     "str r1, [r0]                               \n"

  70. #if !__FPU_USED
  71. #error "must use fpu"
  72. #endif

  73.         /* 上下文保存结束 */
  74.                
  75. "switch_to_thread:                              \n"
  76.     "ldr r1, =cat_context_to_task_sp_ptr        \n"
  77.     "ldr r1, [r1]                               \n"
  78.     "ldr r1, [r1]                               \n"

  79. #if BSP_TZ_NONSECURE_BUILD
  80. #error "not support BSP_TZ_NONSECURE_BUILD"
  81. #elif RM_CATOS_PORT_PSPLIM_PRESENT
  82.         "LDMIA   R1!, {R2}                       \n" /* R1 = PSPLIM */
  83.         "MSR     PSPLIM, R2                      \n" /* Restore the PSPLIM register value for the task. */
  84. #endif

  85. #if BSP_TZ_NONSECURE_BUILD
  86. #error "not support BSP_TZ_NONSECURE_BUILD"
  87. #endif

  88.         "b contex_ns_load                           \n"
  89. "contex_ns_load:                                    \n"

  90. #if __FPU_USED
  91. #if BSP_TZ_NONSECURE_BUILD
  92.   #error "not support BSP_TZ_NONSECURE_BUILD"
  93. #else
  94.         /* Restore R4-R11 and LR from the process stack. */
  95.         "LDMIA   R1!, {R4-R11, LR}               \n"
  96. #endif

  97.         /* Check to see if the thread being restored is using the FPU. If so, restore S16-S31. */
  98.         "TST       LR, #0x10                     \n"
  99.         "IT        EQ                            \n"
  100.         "VLDMIAEQ  R1!, {S16-S31}                \n"
  101. #else
  102. #error "must use fpu"
  103. #endif

  104. "pendsv_exit:                                   \n"
  105.     "msr psp, r1                                \n"

  106. #if !__FPU_USED
  107. #error "must use fpu"
  108. #endif

  109.     "pop {r2}                                   \n"

  110.     /* 恢复屏蔽寄存器值 */
  111.     "msr primask, r2                            \n"

  112. #if __FPU_USED
  113.     "bx lr                                      \n"
  114. #else
  115. #error "must use fpu"
  116. #endif
  117.     );
  118. }
复制代码


2.5 临界区的开关中断
开关中断主要是对primask的操作,和 cortex-m3 差不多,比较简单。
  1. /**
  2. * cat_uint32_t cat_hw_irq_disable(void)
  3. * 关中断方式进入临界区
  4. * primask-->r0
  5. */
  6.     .global cat_hw_irq_disable
  7.     .type cat_hw_irq_disable, %function
  8. cat_hw_irq_disable:
  9.     mrs r0, primask             /* ret = primask */
  10.     cpsid I                     /* disable irq */
  11.     bx lr                       /* return ret */

  12. /**
  13. * void cat_hw_irq_enable(cat_uint32_t status)
  14. * 开中断方式出临界区
  15. * r0-->status
  16. */
  17.     .global cat_hw_irq_enable
  18.     .type cat_hw_irq_enable, %function
  19. cat_hw_irq_enable:
  20.     msr primask, r0             /* primask = status */
  21.     bx lr
复制代码

2.6 任务栈初始化
在任务创建时需要根据任务的相关信息对任务栈帧中各项进行初始化,包括设置 psr 寄存器、任务入口地址、退出函数地址和任务参数。
需要特别注意的是 cortex-m33 还需要正确设置 psplim 等寄存器。
  1. /**
  2. * @brief 栈初始化
  3. *
  4. * @param task_entry    任务入口函数地址
  5. * @param parameter     参数
  6. * @param stack_addr    栈起始地址
  7. * @param exit          任务退出函数地址
  8. * @return cat_uint8_t*     初始化后的栈顶地址
  9. */
  10. cat_uint8_t *cat_hw_stack_init(void *task_entry, void *arg, cat_uint8_t *stack_addr, void *exit_func)
  11. {
  12.   struct _stack_frame *stack_frame;
  13.   cat_uint32_t         *stack;
  14.   cat_uint32_t         i;

  15.   /* 先加上4字节再8字节向下取整对齐(相当于四舍五入) */
  16.   stack = stack_addr + sizeof(cat_uint32_t);
  17.   stack = (cat_uint8_t *)CAT_ALIGN_DOWN((cat_uint32_t)stack, 8);

  18.     /* task context saved & restore by hardware: */
  19.     *(--stack) = (cat_uint32_t)0x01000000L; /* xPSR: EPSR.T = 1, thumb mode   */
  20.     *(--stack) = (cat_uint32_t)task_entry;       /* Entry Point */
  21.     *(--stack) = (cat_uint32_t)exit_func; /* R14 (LR)              */
  22.     *(--stack) = (cat_uint32_t)0x12121212L; /* R12                            */
  23.     *(--stack) = (cat_uint32_t)0x03030303L; /* R3                             */
  24.     *(--stack) = (cat_uint32_t)0x02020202L; /* R2                             */
  25.     *(--stack) = (cat_uint32_t)0x01010101L; /* R1                             */
  26.     *(--stack) = (cat_uint32_t)arg;         /* R0 : argument                  */

  27. #if __FPU_USED && !BSP_TZ_NONSECURE_BUILD
  28.     *(--stack) = (cat_uint32_t)portINITIAL_EXC_RETURN; /* exe_return值 */
  29. #endif

  30.     *(--stack) = (cat_uint32_t)0x11111111L; /* R11 */
  31.     *(--stack) = (cat_uint32_t)0x10101010L; /* R10 */
  32.     *(--stack) = (cat_uint32_t)0x09090909L; /* R9  */
  33.     *(--stack) = (cat_uint32_t)0x08080808L; /* R8  */
  34.     *(--stack) = (cat_uint32_t)0x07070707L; /* R7  */
  35.     *(--stack) = (cat_uint32_t)0x06060606L; /* R6  */
  36.     *(--stack) = (cat_uint32_t)0x05050505L; /* R5  */
  37.     *(--stack) = (cat_uint32_t)0x04040404L; /* R4  */

  38. #if RM_CATOS_PORT_PSPLIM_PRESENT
  39.     *(--stack) = (cat_uint32_t)0x00; /* psplim */
  40. #endif

  41.     stack_frame = (struct _stack_frame *)stack;
  42. #endif /* #if 0 */

  43.   /* 返回当前栈指针 */
  44.   return stack;
  45. }
复制代码

三、板载led和串口的驱动
3.1 板载led(GPIO)
ra6m5 的引脚结构体定义
  1. typedef struct _ra6m5_fire_pin_t
  2. {
  3.     cat_uint32_t             pin_num;
  4.     ioport_instance_ctrl_t  *p_ctrl_ptr;
  5.     const bsp_io_port_pin_t  gpio_pin;
  6. }ra6m5_fire_pin_t;
复制代码
暂时只用得上输出引脚,实现 pin_write 接口,利用 fsp 库实现引脚输出

  1. cat_uint8_t cat_pin_write(cat_uint32_t pin_num, cat_uint8_t val)
  2. {
  3.     cat_uint8_t ret = CAT_ERROR;
  4.     ra6m5_fire_pin_t *p = &(pin_map[0]);
  5.    
  6.     /* 遍历pin_map数组 */
  7.     for(; p->pin_num!=0xffff; p++)
  8.     {
  9.         if(p->pin_num == pin_num)
  10.         {
  11.             if(0 == val)
  12.             {
  13.                 R_IOPORT_PinWrite(p->p_ctrl_ptr, p->gpio_pin, BSP_IO_LEVEL_LOW);
  14.                 ret = CAT_EOK;
  15.             }
  16.             else if(1 == val)
  17.             {
  18.                 R_IOPORT_PinWrite(p->p_ctrl_ptr, p->gpio_pin, BSP_IO_LEVEL_HIGH);
  19.                 ret = CAT_EOK;
  20.             }
  21.             else
  22.             {
  23.                 /* 非法值,之后打印错误信息 */
  24.                 ret = CAT_ERROR;
  25.                 while(1);
  26.             }
  27.             
  28.             /* 写入结束结束循环 */
  29.             break;
  30.         } /* if */
  31.     } /* for */

  32.     return ret;
  33. }
复制代码

3.2 串口
野火教程以及我找到的官方例程中 ra6m5 的串口收发均使用中断实现,但在本内核中的标准输入输出函数大部分是以字符为单位,故发送接口用寄存器方式实现,串口接收仍然使用 fsp 库 + 中断实现

3.2.1 串口发送
  1. static cat_int8_t ra6m5_uart_send_char(cat_device_t*dev, cat_uint32_t timeout, cat_uint8_t data)
  2. {
  3.     cat_int8_t ret = CAT_ERROR;
  4.     struct _cat_ra6m5_fire_uart_private_data_t *private_data = NULL;

  5.     /* 获取设备实例数据 */
  6.     private_data = (struct _cat_ra6m5_fire_uart_private_data_t *)(dev->pri_data);

  7.     sci_uart_instance_ctrl_t *p_ctrl = private_data->inst_ctrl_ptr;

  8.     /* 将要发送的数据放进数据寄存器 */
  9.     p_ctrl->p_reg->TDR = data;

  10.     /* 等待发送完成或超时 */
  11.     while(
  12.         ((p_ctrl->p_reg->SSR_b.TEND) == 0) &&
  13.         (0 != timeout)
  14.     )
  15.     {
  16.         timeout--;
  17.     }

  18.     /* 未超时才成功 */
  19.     if(0 != timeout)
  20.     {
  21.         ret = CAT_EOK;
  22.     }

  23.     return ret;
  24. }

  25. static cat_uint32_t ra6m5_uart_send(cat_device_t*dev, uint32_t timeout, uint8_t const * const buffer, uint32_t const size)
  26. {
  27.     (void)timeout;
  28.     cat_uint32_t cnt = 0;
  29.     cat_int8_t err = CAT_EOK;
  30.    
  31.     while(
  32.         (CAT_EOK == err) &&
  33.         (cnt < size)
  34.     )
  35.     {
  36.         err = ra6m5_uart_send_char(dev, 0xffff, buffer[cnt]);
  37.         cnt++;
  38.     }

  39.     /* 因为前面在一次 while 循环中无论发送是否成功 cnt 都会无条件加一,所以如果失败了就有一个多加上的计数 */
  40.     if(CAT_ERROR == err)
  41.     {
  42.         cnt--;
  43.     }

  44.     return cnt;
  45. }
复制代码
3.2.2 串口接收
  1. static cat_uint32_t ra6m5_uart_recv(cat_device_t*dev, uint32_t timeout, uint8_t *buffer, uint32_t const size)
  2. {
  3. cat_uint32_t recv_buffer_idx = 0; /**< 串口接收缓冲区访问索引 */
  4. cat_uint32_t err = CAT_ERROR;
  5. struct _cat_ra6m5_fire_uart_private_data_t *private_data = NULL;

  6. /* 获取设备实例数据 */
  7. private_data = (struct _cat_ra6m5_fire_uart_private_data_t *)(dev->pri_data);
  8. assert(NULL != private_data);

  9. /* 读取 */
  10. while(recv_buffer_idx != size)
  11. {
  12. while(
  13. (false == uart4_receive_char) &&
  14. (0 != timeout)
  15. )
  16. {
  17. timeout--;
  18. };

  19. if(0 == timeout)
  20. {
  21. break;
  22. }

  23. while(
  24. (cat_ringbuffer_is_empty(private_data->p_ringbuffer) == 0)
  25. )
  26. {
  27. /* 获取接收到的字符 */
  28. err = cat_ringbuffer_get(private_data->p_ringbuffer, &(buffer[recv_buffer_idx++]));

  29. if(CAT_ERROR == err)
  30. {
  31. /* 获取失败,因为while条件判断过非空,所以出大问题 */
  32. while(1);
  33. }

  34. if(recv_buffer_idx == size)
  35. {
  36. break;
  37. }
  38. }

  39. if(cat_ringbuffer_is_empty(private_data->p_ringbuffer))
  40. {
  41. /* 取完才改遍flag */
  42. uart4_receive_char = false;
  43. }
  44. }

  45. return recv_buffer_idx;
  46. }
复制代码


3.2.3 串口中断服务函数
  1. /* uart4中断回调函数 */
  2. void debug_uart4_callback(uart_callback_args_t *pargs)
  3. {
  4.     switch(pargs->event)
  5.     {
  6.         case UART_EVENT_RX_CHAR:
  7.         {
  8.             cat_ringbuffer_put(&uart4_rb, pargs->data);
  9.             uart4_receive_char = true;
  10.         }
  11.         default:
  12.         {
  13.             break;
  14.         }
  15.     }
  16. }
复制代码

3.3 基于 GPIO 引脚驱动实现 iic 总线驱动
利用 gpio 的基本输入输出实现软件 iic 功能
3.3.1 设计 iic 数据结构
因为是软件 iic,所以每个总线需要设置 scl 和 sda 引脚,并且总线时序中可能需要不同的 delay 操作。此外,is_init 用来防止重复初始化同一个总线。
  1. typedef struct _cat_iic_bus_t
  2. {
  3. cat_uint32_t scl;
  4.     cat_uint32_t sda;
  5.     cat_bool_t   is_init;
  6.     cat_uint32_t delay_us;
  7.     cat_uint32_t timeout_us;
  8.     const cat_uint8_t *name;
  9. }cat_iic_bus_t;
复制代码
3.3.2 设计引脚电平操作
因为有 GPIO 驱动作为基础,设置引脚输入输出和高低电平直接调用 pin 驱动即可
  1. #define SET_SCL(bus, val)    cat_pin_write((bus)->scl, val)
  2. #define SET_SDA(bus, val)    cat_pin_write((bus)->sda, val)
  3. #define GET_SCL(bus)         cat_pin_read((bus)->scl)
  4. #define GET_SDA(bus)         cat_pin_read((bus)->sda)

  5. #define SDA_OUT(bus)         cat_pin_set_mode((bus)->sda, CAT_PIN_MODE_OUTPUT);
  6. #define SDA_IN(bus)          cat_pin_set_mode((bus)->sda, CAT_PIN_MODE_INPUT);

  7. #define SCL_L(bus)  SET_SCL((bus), CAT_PIN_LOW)
  8. #define SDA_L(bus)  SET_SDA((bus), CAT_PIN_LOW)
  9. #define SDA_H(bus)  SET_SDA((bus), CAT_PIN_HIGH)
复制代码






四、调试验证
4.1 创建 demo 项目
在 projects 目录利用 rasc 工具生成 keil 裸机工程,只需配置 led 引脚以及 uart 串口


4.2 编写程序创建任务
在 hal_entry 中创建用户任务
  1. #include "hal_data.h"
  2. #include "catos.h"

  3. FSP_CPP_HEADER
  4. void R_BSP_WarmStart(bsp_warm_start_event_t event);
  5. FSP_CPP_FOOTER

  6. /*******************************************************************************************************************//**
  7. * main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used.  This function
  8. * is called by main() when no RTOS is used.
  9. **********************************************************************************************************************/
  10. #define TASK1_STACK_SIZE    (1024)
  11. #define TASK2_STACK_SIZE    (1024)

  12. struct _cat_task_t task1;
  13. struct _cat_task_t task2;

  14. cat_stack_type_t task1_env[TASK1_STACK_SIZE];
  15. cat_stack_type_t task2_env[TASK2_STACK_SIZE];

  16. uint32_t sched_task1_times = 0;
  17. uint32_t sched_task2_times = 0;

  18. #define BOARD_LED_PIN 0

  19. void board_led_init(void)
  20. {
  21.     cat_pin_init(BOARD_LED_PIN, CAT_PIN_MODE_OUTPUT);
  22. }

  23. void board_led_on(void)
  24. {
  25.     cat_pin_write(BOARD_LED_PIN, CAT_PIN_LOW);
  26. }

  27. void board_led_off(void)
  28. {
  29.     cat_pin_write(BOARD_LED_PIN, CAT_PIN_HIGH);
  30. }

  31. void task1_entry(void *arg)
  32. {

  33.     for(;;)
  34.     {
  35.         sched_task1_times++;
  36.         board_led_on();
  37.         cat_sp_task_delay(100);
  38.         board_led_off();
  39.         cat_sp_task_delay(100);
  40.     }
  41. }

  42. void task2_entry(void *arg)
  43. {
  44.     for(;;)
  45.     {
  46.         cat_sp_task_delay(100);
  47.         //CAT_DEBUG_PRINTF("[task2] %d\r\n", catos_systicks);
  48.     }
  49. }

  50. void hal_entry(void)
  51. {
  52.     /* TODO: add your own code here */

  53.     /* 初始化os */
  54.     catos_init();

  55.     /* 利用pin驱动初始化板载led */
  56.     board_led_init();

  57.     /* 测试创建任务运行 */
  58.     cat_sp_task_create(
  59.       (const uint8_t *)"task1_task",
  60.       &task1,
  61.       task1_entry,
  62.       NULL,
  63.       0,
  64.       task1_env,
  65.       TASK1_STACK_SIZE
  66.     );

  67.     cat_sp_task_create(
  68.       (const uint8_t *)"task2_task",
  69.       &task2,
  70.       task2_entry,
  71.       NULL,
  72.       0,
  73.       task2_env,
  74.       sizeof(task2_env)
  75.     );


  76.     /* 开始调度 */
  77.     catos_start_sched();

  78.     /* 不会到达这里 */

  79.     while(1);

  80. #if BSP_TZ_SECURE_BUILD
  81.     /* Enter non-secure code */
  82.     R_BSP_NonSecureEnter();
  83. #endif
  84. }

复制代码
4.3 烧写与验证
使用 xshell 连接串口并烧录程序,可以观察到l led 闪烁,并且串口有 shell 的信息,命令可以正常使用。


五、总结
在整个项目过程中,因为涉及底层操作,并且教程还比较少,走了不少弯路,但总体来说还是很不错的,特别是 fsp 配置方面。
建议瑞萨可以在 eestudio 适配更多调试器方便开发者使用;并且在使用野火 dap 时发现其在 ubuntu 下要被识别比较麻烦,如果能改进,使用场景会更多。

六、附件与项目地址
B站视频介绍:【野火】瑞萨RA MCU创意氛围赛 基于优先级的实时RTOS内核_哔哩哔哩_bilibili
Github链接  :Simple-XX/SimpleRTOS at ver2 (github.com)








回复

使用道具 举报

发表于 2023-7-1 14:44:48 | 显示全部楼层
顶顶顶,太厉害啦
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-7-2 17:03:09 | 显示全部楼层
ID-fHtdZc 发表于 2023-7-1 14:44
顶顶顶,太厉害啦

谢谢老哥,互相学习嘿嘿
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-5-31 17:27 , Processed in 0.058303 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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