在RISC-V微控制器上使用FreeRTOS

原文 FreeRTOS for RISC-V RV32 and RV64

前言

Upgrading to V10.3.0 - FreeRTOS 所言,configCLINT_BASE_ADDRESS 配置设置项已经被弃用,并被本页描述的 configMTIME_BASE_ADDRESSconfigMTIMECMP_BASE_ADDRESS 所替代。仍然使用configCLINT_BASE_ADDRESS 将生成编译器警告,除此之外和之前一样依旧可以编译和运行。

介绍

RISC-V 指令集架构 (ISA) 易于扩展,并且没有指定在微控制器或者片上系统(SOC)要实现的内容。因此,FreeRTOS RISC-V 接口也是可扩展的,它提供了一个基本接口来处理所有RISC-V实现的通用寄存器,以及一组必须实现的宏来处理硬件实现的特定的功能及扩展,例如额外的寄存器。

快速开始

此页面的主体提供了有关为 RISC-V 内核构建 FreeRTOS 的详细信息,但最简单的入门方法是使用以下预配置示例项目之一(在撰写本文时列表正确)

示例 RISC-V 项目位于 FreeRTOS/Demo` 的子目录中,在 FreeRTOS zip 主文件下载中以“RISC-V”开头。 这些项目可以直接使用,也可以简单地用作源文件、配置选项和编译器设置的工作示例和参考,详情如下。

总之,要为 RISC-V 内核构建 FreeRTOS,您需要:

  1. 在项目中包含核心 FreeRTOS 源文件和 FreeRTOS RISC-V 端口层源文件
  2. 确保汇编器的包含路径包括描述任何芯片特定实现细节的头文件的路径
  3. 在 FreeRTOSConfig.h 中定义一个常量或一个链接器变量以指定用作中断堆栈的内存.
  4. FreeRTOSConfig.h 中定义 configMTIME_BASE_ADDRESSconfigMTIMECMP_BASE_ADDRESS
  5. 对于汇编器,#define portasmHANDLE_INTERRUPT 指向由您的芯片或工具供应商提供的用于处理外部中断的函数的函数名
  6. 安装 FreeRTOS 陷阱处理程序

其他可能有用的链接包括:


详细资料

在本页面:

FreeRTOS RISC-V 接口的特性

FreeRTOS RISC-V 接口:

  • 同时支持 IAR 和 GCC 编译器
  • 在32位和64位RISC-V核上仅支持机器模式和整数执行单元,但正在积极开发中,未来的 FreeRTOS 版本将根据用户的需要添加特性和功能。
  • 实现一个单独的中断堆栈,这样做可以大大减少小型微控制器上的 RAM 使用量,因为不需要每个任务都有足够大的堆栈来容纳中断和非中断堆栈帧。
  • 提供可轻松扩展以适应 RISC-V 实现特定架构扩展的基本端口。

源文件

FreeRTOS 内核源代码组织页面包含有关将 FreeRTOS 内核添加到项目的信息。 除了该页面上的信息外,FreeRTOS RISC-V 端口还需要一个额外的头文件。 附加的头文件描述了芯片特定的细节,并且是必需的,因为 RISC-V 芯片通常包含芯片特定的架构扩展。

附加的头文件称为 freertos_risc_v_chip_specific_extensions.h。 每个受支持的架构扩展都有一个此头文件的实现,所有实现都位于 /FreeRTOS/Source/Portable/[compiler]/RISC-V/chip_specific_extensions 目录的子目录中。

要为您的芯片包含正确的 freertos_risc_v_chip_specific_extensions.h 头文件,只需将该头文件的路径添加到汇编器的包含路径(注意这是汇编器的包含路径,而不是编译器的包含路径)。 例如:

  • 如果您的芯片实现了基本 RV32I 或 RV64I 架构,包括处理器核局部中断控制器 (CLINT),但没有其他寄存器扩展,则将 /FreeRTOS/Source/Portable/[compiler]/RISC-V/chip_specific_extensions/RV32I_CLINT_no_extensions 添加到汇编器的头文件路径。
  • 如果您的芯片使用在 RV32M1RM Vega 板上实现的 PULP RI5KY 内核,其中包括六个附加寄存器并且不包括处理器核局部中断控制器 (CLINT),则添加 /FreeRTOS/Source/Portable/[compiler]/RISC-V/ chip_specific_extensions/Pulpino_Vega_RV32M1RM 到汇编器的包含路径。

另请参阅下面的编译器和汇编器命令行选项部分以获取有关设置汇编器命令行选项的信息,以及将 FreeRTOS 移植到新的 RISC-V 实现部分以获取有关创建您自己的 freertos_risc_v_chip_specific_extensions.h 头文件的信息。

FreeRTOSConfig.h 设置

configMTIME_BASE_ADDRESSconfigMTIMECMP_BASE_ADDRESS 必须在 FreeRTOSConfig.h 中定义。 如果目标 RISC-V 芯片包含机器定时器 (MTIME),则将 configMTIME_BASE_ADDRESS 设置为 MTIME 基地址,并将 configMTIMECMP_BASE_ADDRESS 设置为 MTIME 的比较寄存器 (MTIMECMP) 的地址。 否则将两个定义都设置为 0。

例如,如果 MTIME 基地址是 0x2000BFF8,而 MTIMECMP 地址是 0x20004000,则将以下行添加到 FreeRTOSConfig.h:

1
2
#define configMTIME_BASE_ADDRESS		( 0x2000BFF8UL )
#define configMTIMECMP_BASE_ADDRESS ( 0x20004000UL )

如果没有 MTIME 时钟,则将以下行添加到 FreeRTOSConfig.h:

1
2
#define configMTIME_BASE_ADDRESS		( 0 )
#define configMTIMECMP_BASE_ADDRESS ( 0 )

中断(系统)堆栈设置

在从中断服务例程 (ISR) 调用任何 C 函数之前,FreeRTOS RISC-V 端口切换到专用中断(或系统)堆栈。

用作中断堆栈的内存可以在链接描述文件中定义,也可以在 FreeRTOS 端口层中声明为静态分配的数组。 链接描述文件方法在内存受限的 MCU 上是首选的,因为它允许在调度程序启动之前由 main() 使用的堆栈(在调度程序启动后不再用于此目的)重新用作中断堆栈。

  • 使用静态分配的数组作为中断堆栈:

    将 FreeRTOSConfig.h 中的 configISR_STACK_SIZE_WORDS 定义为要分配的中断堆栈的大小。 请注意,大小是用字定义的,而不是字节。

    例如,要使用 500 字(在 RV32 上为 2000 字节,其中每个字为 4 字节)静态分配的中断堆栈,请将以下内容添加到 FreeRTOSConfig.h:

1
#define configISR_STACK_SIZE_WORDS ( 500 )
  • 在链接描述文件中定义中断堆栈 - 请注意在编写此方法时仅在 GCC 接口中支持
  1. 声明一个名为 __freertos_irq_stack_top 的链接器变量,它保存中断堆栈的最高地址
  2. 确保未定义 configISR_STACK_SIZE_WORDS

使用此方法需要编辑链接描述文件。 如果您不熟悉链接器脚本,那么至少在使用 GCC 时了解“.”是很重要的。 是所谓的位置计数器,它保存链接描述文件中该点的内存地址的值。 不过,无需了解链接器脚本的详细信息,只需复制下面的示例即可。

在调度程序启动之前 main() 使用的堆栈在调度程序启动后不再需要,因此理想情况下通过将 __freertos_irq_stack_top 设置为等于分配给 main() 使用的堆栈的最高地址的值来重用该堆栈 . 例如,如果您的链接描述文件包含如下内容(实际使用的链接描述文件会有所不同):

1
2
3
4
5
6
.stack : ALIGN(0x10)
{
__stack_bottom = .;
. += STACK_SIZE;
__stack_top = .;
} > ram

然后 __stack_top (仅示例名称)是一个链接器变量,其值等于 main() 使用的堆栈的最高地址(回忆 ‘.’ 保存链接描述文件中任何给定位置的内存地址的值)。 在这种情况下,要赋予 __freertos_irq_stack_top__stack_top相同的值,只需在 __stack_top 之后立即定义 ``__freertos_irq_stack_top`。 请参见下面的示例:

1
2
3
4
5
6
7
.stack : ALIGN(0x10)
{
__stack_bottom = .;
. += STACK_SIZE;
__stack_top = .;
__freertos_irq_stack_top= .; /* ADDED THIS LINE. */
} > ram

注意:在撰写本文时,与任务堆栈不同,内核不检查中断堆栈中的溢出。

必需的编译器命令行选项

不同的 RISC-V 实现为外部中断提供了不同的处理程序,因此有必要告诉 FreeRTOS 内核调用哪个外部中断处理程序。 设置外部中断处理程序的名称:

  1. 找到您的 RISC-V 运行时软件分发提供的外部中断处理程序的名称 - 这通常是芯片供应商提供的软件。 中断处理程序必须有一个参数,即进入中断时 RISC-V 原因寄存器的值。 例如,中断处理程序的原型应该是(仅示例名称,为您的软件使用正确的名称):
1
void external_interrupt_handler( uint32_t cause );
  1. 定义一个名为 portasmHANDLE_INTERRUPT 的汇编器宏(注意这是一个汇编器宏,而不是编译器宏)与中断处理程序的名称相同。如果使用 GCC,可以通过将以下内容添加到汇编程序的命令行来实现,假设中断处理程序称为 external_interrupt_handler:
1
-DportasmHANDLE_INTERRUPT=external_interrupt_handler

如果使用 IAR,这可以通过打开项目选项对话框并在 Assembler 类别中添加以下行作为已定义符号来实现,假设调用了中断处理程序vApplicationHandleTrap.

1
portasmHANDLE_INTERRUPT=vApplicationHandleTrap

img

还需要将使用的 RISC-V 芯片的正确 freertos_risc_v_chip_specific_extensions.h 头文件的路径添加到汇编器的包含路径(注意这是汇编器的包含路径,而不是编译器的包含路径)。 请参阅上面的源文件部分。

安装 FreeRTOS 中断处理程序

FreeRTOS 陷阱处理程序称为 freertos_risc_v_trap_handler() 并且是所有中断和异常的中央入口点。 当陷阱源是外部中断时,FreeRTOS 陷阱处理程序调用外部中断处理程序。

要安装陷阱处理程序:

  1. 如果使用的 RISC-V 内核包含内核本地中断器 (CLINT),则可以在 freertos_risc_v_chip_specific_extensions.h 中将 portasmHAS_SIFIVE_CLINT 定义为 1,这会导致自动安装 freertos_risc_v_trap_handler(),因此不需要其他特定操作。
  2. 在所有其他情况下,必须手动安装 freertos_risc_v_trap_handler()。 这可以通过编辑芯片供应商提供的启动代码来完成。

注意:如果 RISC-V 芯片使用向量中断控制器,则安装 freertos_risc_v_trap_handler() 作为每个向量的处理程序。

移植到新的 32 位 RISC-V 或 64 位 RISC-V 实现

在阅读本节之前,请阅读上面的 FreeRTOS RISC-V 源文件部分。

freertos_risc_v_chip_specific_extensions.h 文件包含以下必须定义的宏:

  • portasmHAS_MTIME

如果芯片有机器定时器(MTIME),则将 portasmHAS_MTIME 设置为 1,否则将 portasmHAS_MTIME 设置为 0。

  • portasmADDITIONAL_CONTEXT_SIZE

RISC-V 指令集架构 (ISA) 是可扩展的,因此 RISC-V 芯片可能包含超出基本架构规范所需的额外寄存器。

#define portasmADDITIONAL_CONTEXT_SIZE 为目标芯片上存在的附加寄存器的数量——可能为零。 例如,Vega 板上的 RI5CY 内核包括六个额外的寄存器,因此提供用于该芯片的 freertos_risc_v_chip_specific_extensions.h 包括以下行:

1
#define portasmADDITIONAL_CONTEXT_SIZE 6
  • portasmSAVE_ADDITIONAL_REGISTERS

portasmSAVE_ADDITIONAL_REGISTERS 是一个汇编宏(不是#define),必须实现它以保存任何芯片特定的附加寄存器。

如果没有特定于芯片的扩展寄存器(portasmADDITIONAL_CONTEXT_SIZE 设置为零),则 portasmSAVE_ADDITIONAL_REGISTERS 必须是一个空的汇编宏,如下所示:

1
2
3
.macro portasmSAVE_ADDITIONAL_REGISTERS
/* No additional registers to save, so this macro does nothing. */
.endm

如果有芯片特定的扩展寄存器(portasmADDITIONAL_CONTEXT_SIZE 大于零),那么 portasmSAVE_ADDITIONAL_REGISTERS 必须:

  1. 递减堆栈指针以为附加寄存器创建足够的堆栈空间
  2. Save the additional registers into the created stack space

例如,如果芯片有三个额外的寄存器,那么 portasmSAVE_ADDITIONAL_REGISTERS 必须按如下方式实现(其中寄存器的名称将取决于芯片,而不是在这里显示):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.macro portasmSAVE_ADDITIONAL_REGISTERS
/* Use the portasmADDITIONAL_CONTEXT_SIZE and portWORD_SIZE
macros to calculate how much additional stack space is needed,
and subtract that from the stack pointer. This line can just
be copied from here provided portasmADDITIONAL_CONTEXT_SIZE
is set correctly. Note the minus sign ('-'). portWORD_SIZE
is already defined elsewhere. */
addi sp, sp, -(portasmADDITIONAL_CONTEXT_SIZE * portWORD_SIZE)

/* Next save the additional registers, which here are assumed
to be called xx0 to xx2, but will be called something different
on your chip, to the stack. Assumes portasmADDITIONAL_CONTEXT_SIZE
is 3. */
sw xx0, 1 * portWORD_SIZE( sp )
sw xx1, 2 * portWORD_SIZE( sp )
sw xx2, 3 * portWORD_SIZE( sp )
.endm
  • portasmRESTORE_ADDITIONAL_REGISTERS

portasmRESTORE_ADDITIONAL_REGISTERS 与 portasmSAVE_ADDITIONAL_REGISTERS 相反。

如果没有特定于芯片的扩展寄存器(portasmADDITIONAL_CONTEXT_SIZE 设置为零),则 portasmRESTORE_ADDITIONAL_REGISTERS 必须是一个空的汇编宏,如下所示:

1
2
3
.macro portasmRESTORE_ADDITIONAL_REGISTERS
/* No additional registers to restore, so this macro does nothing. */
.endm

如果有芯片特定的扩展寄存器(portasmADDITIONAL_CONTEXT_SIZE 大于零),那么 portasmRESTORE_ADDITIONAL_REGISTERS 必须:

  1. 从 portasmSAVE_ADDITIONAL_REGISTERS 使用的堆栈位置读取附加寄存器,
  2. 通过将堆栈指针增加正确的数量来删除用于保存附加寄存器的堆栈空间。

例如,如果芯片有三个额外的寄存器,那么 portasmRESTORE_ADDITIONAL_REGISTERS 必须按如下方式实现(其中寄存器的名称将取决于芯片,此处不显示):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.macro portasmRESTORE_ADDITIONAL_REGISTERS
/* Restore the additional registers, which here are assumed
to be called xx0 to xx2, but will be called something different
on your chip from the stack. Assumes
portasmADDITIONAL_CONTEXT_SIZE is 3. */
lw xx0, 1 * portWORD_SIZE( sp )
lw xx1, 2 * portWORD_SIZE( sp )
lw xx2, 3 * portWORD_SIZE( sp )

/* Use the portasmADDITIONAL_CONTEXT_SIZE and portWORD_SIZE
macros to calculate how much space to remove from the stack.
This line can just be copied from here provided
portasmADDITIONAL_CONTEXT_SIZE is set correctly. portWORD_SIZE
is already defined elsewhere. */
addi sp, sp, (portasmADDITIONAL_CONTEXT_SIZE * portWORD_SIZE)
.endm

freertos risc_v_chip_specific_extensions.h 文件还可以选择包括:

  • portasmHAS_SIFIVE_CLINT

如果目标 RISC-V 芯片包含核心本地中断器 (CLINT),如果您希望自动安装 FreeRTOS RISC-V 陷阱处理程序,则 #define portasmHAS_SIFIVE_CLINT 为 1。