RISC-V32 之系统构建


概述

1、rv32 目前暂不支持 cube 安装，用户需手动移植。移植过程中遇到问题
   可加入社区获取技术支持。
2、目前仅支持 PSP栈模式，即不存在全局唯一主栈或中断专用栈，而是当前
   任务栈即为当前主栈。这导致所有中断都是入任务栈，所以中断嵌套深度
   应尽量配置少一些，同时任务栈SIZE应尽量大一些，以防溢出。
3、CosyOS 采用了自研先进技术，可实现 CH32V 全程开启硬件压栈。
   该技术为纯软件实现，不依赖特定硬件（0x804->bit5），V103、203 等
   皆可完美支持。所以，如果您的处理器支持硬件压栈，建议开启。
   如果开启硬件压栈，用户应注意以下事项：
   （1）处理器需要仅工作在机器模式
        可在启动文件中，通过配置 mstatus 的 MPP 为 3 来实现。
   （2）中断函数的声明原则等同裸机
        即如果最大硬件压栈深度为3级，需要将低优先级的三级中断声明为
        硬件压栈，高优先级的中断声明为软件压栈。
   （3）x4(tp)、mscratch、0xE000E040（中断优先级阈值）等寄存器，
        专用于任务切换，不允许用户使用。


移植流程（以 MRS2 为例）

1、裸机工程
   准备项目的裸机工程，一个最基础的工程即可。

2、删除文件
   删除 CosyOS-源代码 中无关的文件，使其不被编译。

   建议必须删除的文件：
   （1）Port 中，仅保留 port_x32、syscfg.h
   （2）Port / port_x32 中，仅保留 RISC-V32、port_x32.c、port_x32.h

   建议可选删除的文件：
   （1）Demo-Service
   （2）Demo-Build 中，仅保留 RISC-V32、demo_task.c
   注：如果不删除不会影响正常编译

3、拷贝文件
   拷贝 CosyOS-源代码 至工程中。

4、包含路径
   打开工程，添加下方的包含路径。
   C包含路径：
      cosyos-v3.0.1/System
      cosyos-v3.0.1/Port
      cosyos-v3.0.1/Port/port_x32
      cosyos-v3.0.1/Port/port_x32/RISC-V32
   汇编包含路径：
      cosyos-v3.0.1/Port
      cosyos-v3.0.1/Port/port_x32/RISC-V32


构建流程

0、Include/Exclude
   Port / port_x32 / RISC-V32 / port_rv32_SWS.S，为软件压栈专用；
   Port / port_x32 / RISC-V32 / port_rv32_HWS.S，为硬件压栈专用；
   根据是否启用硬件压栈，Include相匹配的文件，Exclude不匹配的文件。

1、系统配置
   Port / syscfg.h -> 系统配置>>处理器架构，应定义为 4。
   Port / syscfg.h -> 系统配置>>标准头文件，需要正确定义，如定义为
   "ch32v30x.h"。

2、MCU配置
   Port / port_x32 / RISC-V32 / mcucfg_rv32.h 中，配置系统滴答和
   系统时钟。

3、main函数
   在main函数的末尾处，先配置 DEBUG（如果使用 CosyOS-任务管理器），
   最后再启动 CosyOS（必须）。

4、系统中断
   CosyOS 固定采用 SysTick_Handler 和 SW_Handler 做为系统中断。
   用户需要自行在 SysTick_Handler 中调用 tCosyTick_Handler。
   用户不要自行创建 SW_Handler。

5、DEBUG（可选，参照例程 demo_debug.c）

在未来，用户仍需重点关注如下内容：

1、系统配置
   Port / syscfg.h

2、MCU配置
   Port / port_x32 / RISC-V32 / mcucfg_rv32.h

3、启动文件配置
   在 handle_reset 中，配置特权级（mstatus）；
   配置硬件压栈使能、中断嵌套使能、中断嵌套深度、硬件压栈溢出后中断使能（0x804）。

4、ld文件配置
   配置硬件栈size。
   通常配置一个极小的数值即可，据不完全测试，该值配置为0即可正常运行：
   __stack_size = 0;


例程

/* 例程-main */
int main(void)
{
	/* USER INIT CODE BEGIN */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	SystemCoreClockUpdate();
	Delay_Init();
////USART_Printf_Init(115200);
	USART1_UART_Init (115200);
	printf("SystemClk:%d\r\n",SystemCoreClock);
	printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
	printf("This is printf example\r\n");
	/* USER INIT CODE END */

    /* USER DEBUG CONFIG */// for CosyOS-Taskmgr
    #if (SYSCFG_DEBUGGING == 1)
    if(1){
        void Debug_Config(void);
        Debug_Config();
    }// defined in demo_debug.c
    #endif
    
    /* USER 启动 CosyOS */// 在main函数的末尾处
    if(1){
        #if 1 // 建议前期调试时开启，如果CosyOS启动失败会返回错误码提示用户！
        unsigned char _ecode = uStartCosyOS();
        printf("CosyOS startup failed, error code: %d.\r\n", (int)_ecode);
        #else
        uStartCosyOS();
        #endif
    }

	while(1);
}

/* 例程-SysTick_Handler */// 根据是否启用硬件压栈，选择相匹配的声明方式：
//void SysTick_Handler(void) __attribute__((interrupt)); // 声明为软件压栈
void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); // 声明为硬件压栈
void SysTick_Handler(void)
{
    SysTick->SR = 0;
    tCosyTick_Handler();
}

/* 例程-启动文件 */// 使能硬件压栈并仅为机器模式
handle_reset:
/* Enable interrupt nesting and hardware stack */
    //      HWSTKOVEN, PMTCFG,    INESTEN,  HWSTKEN
	li t0, (1 << 4) | (3 << 2) | (1 << 1) | 1
	csrw 0x804, t0
/* Enable floating point and global interrupt, configure privileged mode */
    //      FS,         MPP,        MPIE,      MIE
   	li t0, (3 << 13) | (3 << 11) | (1 << 7) | (1 << 3)
   	csrw mstatus, t0

/*
0x804
HWSTKOVEN：硬件压栈溢出后中断使能
PMTCFG：   中断嵌套深度
INESTEN：  中断嵌套使能
HWSTKEN：  硬件压栈使能

mstatus
FS：  浮点单元状态
MPP： 进中断前特权模式
MPIE：进中断前中断使能状态
MIE： 机器模式中断使能
*/

--------------------------------------------------------------------------------

补充说明——硬件压栈技术可靠性验证

决定硬件压栈技术是否稳定可靠的关键因素，在于 port_rv32_HWS.S 中的最后三句代码。
简单来说，内核解锁后，最后两句代码（断点1、断点2）已不在服务层临界区的保护之中。
如果在内核解锁前，系统中断（SysTick、PendSV）已经被触发，要保证在内核解锁之后，
在最后两句代码（断点1、断点2）执行完之前，不会响应系统中断。否则说明硬件压栈技
术是不可靠的，不能使用。为此，特提供两种测试方法给自己和用户，以方便测试并验证
硬件压栈技术的可靠性。

方法一
在 port_rv32_HWS.S 中，通过自行 #define __TEST_PENDSV 来开启测试。
该测试已在内核解锁之前触发 PendSV，在内核解锁之后，如果由 断点1 或 断点2 再入
PendSV，当由 PendSV 直接返回后将跳转至 test_pendsv_fault_handler。
用户可在该处理函数中通过 打印 或 点灯 等方式，来报告异常发生。
/* 示例 */
void test_pendsv_fault_handler(void)
{
    printf("hardware stack test error\r\n");
    while(1);
}

方法二
在 pendsv_hook 中打印 mepc，持续一段时间后数据统计，如果 mepc 出现过 断点1
或 断点2 的地址，判定异常发生。
/* 示例 */
__WEAK void pendsv_hook(void)
{
    printf("mepc: %#x\r\n", __get_MEPC());
}

总结
1、方法二要依赖方法一，不要单独使用，因为只有在每次内核解锁之前都触发 PendSV，
   才会有良好的测试效果。
2、可两种方法同时使用，充分验证硬件压栈技术的可靠性。

