嵌入式 Rust 中断向量表详解

前言

在ARM Cortex-M 微控制器编写 Rust 代码时,通常使用 cortex-m-rt 和 cortex-m crate。cortex-m-rt提供了 Cortex-M 内核的基本寄存器存取接口,启动代码,内核标准向量表、代码段、数据段的定义,cortex-m提供对所有基于 Cortex-M 的微控制器标准的内核中的外设(例如 NVIC和 SysTick)以及一些外设操作的标准接口代码。cortex-m-rt 还确保复位、中断和异常向量表的定位,确保应用程序定义的中断入口函数填充到指定的位置。 按照 cortex-m-rt 中使用定义,向量表由以下部分组成:

链接段 符号名 功能
_stack_start 堆栈指针的初始值
vector_table.reset_vector __RESET_VECTOR 芯片复位地址
vector_table.exceptions __EXCEPTIONS Cortex-M 常见异常的向量表
vector_table.interrupts __INTERRUPTS 各厂商自定义的中断的向量表

如果为cortex-m-rt crate启用device功能,则它不会填充设备特定中断的向量表。相反,这是留给外设访问箱(PAC),用于特定的设备。.text 和 _stack_start 部分也可以通过配置移动或修改,但异常向量表则必须与芯片硬件定义的规则一致。 异常向量表由 ARM 制定,因此在任何基于 Cortex-M 的微控制器上都应该是相同的。当然也有一些芯片厂商没有完全按照 ARM 的规则设计芯片,例如,某些微控制器将启动配置、标记或校验和存储在异常向量表的“保留”字段中,或者需要在向量表或应用程序之间添加额外的标头,在这种情况下,我们需要修改表以便我们的代码能够自动运行,而不至于跑飞。

建立一个基本的项目

让我们设置一个典型的、最小的 Cortex-M0 项目作为示例。我们将省略 PAC 部分的代码,因为本示例不需要它。

$ cargo new cmexample --bin
    Created binary (application) `cmexample` package
$ cd cmexample/
$ cargo add cortex-m-rt
    Adding cortex-m-rt v0.7.2 to dependencies.
           Features:
            - device
            - set-sp
            - set-vtor
$ cargo add -F critical-section-single-core cortex-m
    Adding cortex-m v0.7.7 to dependencies.
           Features:
           + critical-section-single-core
           - cm7
           - cm7-r0p1
           - critical-section
           - inline-asm
           - linker-plugin-lto
           - serde
           - std
$ cargo add panic-halt
    Adding panic-halt v0.2.0 to dependencies.

新建一个简单的 main 文件

#![no_std]
#![no_main]

use panic_halt as _;
use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {}
}

添加链接文件memory.x

MEMORY
{
    FLASH : ORIGIN = 0x00000000, LENGTH = 64K
    RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

配置 Cargo 使用thumbv6目标并添加以下内容到.cargo/config.toml来获取cortex-m-rt提供的链接器脚本:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = [
    "-C", "link-arg=-Tlink.x",
]

[build]
target = "thumbv6m-none-eabi"

查看可执行文件的中断向量表

如果一切顺利,构建后,将有一个针对 ARM 目标的 ELF 可执行文件,其中包含向量表和代码段、数据段等部分:

$ file target/thumbv6m-none-eabi/release/cmexample
target/thumbv6m-none-eabi/release/cmexample: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
$ arm-none-eabi-size -Ax target/thumbv6m-none-eabi/release/cmexample
target/thumbv6m-none-eabi/release/cmexample  :
section              size         addr
.vector_table        0xc0          0x0
.text                0x64         0xc0
.rodata               0x0        0x124
.data                 0x0   0x20000000
.gnu.sgstubs          0x0        0x140
.bss                  0x0   0x20000000
.uninit               0x0   0x20000000
.ARM.attributes      0x32          0x0
.debug_frame       0x6528          0x0
.debug_abbrev      0x1139          0x0
.debug_info       0x26271          0x0
.debug_aranges     0x1e00          0x0
.debug_ranges     0x153f0          0x0
.debug_str        0x3e653          0x0
.debug_pubnames   0x16152          0x0
.debug_pubtypes     0x252          0x0
.debug_line       0x25015          0x0
.debug_loc           0x74          0x0
.comment             0x6d          0x0
Total             0xbe705

使用 objdump 我们可以检查向量表的内容:

$ arm-none-eabi-objdump -s --section .vector_table target/thumbv6m-none-eabi/release/cmexample
target/thumbv6m-none-eabi/release/cmexample:     file format elf32-littlearm

Contents of section .vector_table:
 0000 00500020 c1000000 0b010000 0f010000  .P. ............
 0010 00000000 00000000 00000000 00000000  ................
 0020 00000000 00000000 00000000 0b010000  ................
 0030 00000000 00000000 0b010000 0b010000  ................
 0040 0b010000 0b010000 0b010000 0b010000  ................
 0050 0b010000 0b010000 0b010000 0b010000  ................
 0060 0b010000 0b010000 0b010000 0b010000  ................
 0070 0b010000 0b010000 0b010000 0b010000  ................
 0080 0b010000 0b010000 0b010000 0b010000  ................
 0090 0b010000 0b010000 0b010000 0b010000  ................
 00a0 0b010000 0b010000 0b010000 0b010000  ................
 00b0 0b010000 0b010000 0b010000 0b010000  ................

所有向量都指向默认处理程序。让我们为 SysTick 中断定义一个新的处理程序,例如:

#[exception]
fn SysTick() {
 todo!();
}

现在我们可以看到 0x3c 处的 SysTick 向量已更新:

$ arm-none-eabi-objdump -s --section .vector_table target/thumbv6m-none-eabi/release/cmexample
target/thumbv6m-none-eabi/release/cmexample:     file format elf32-littlearm

Contents of section .vector_table:
 0000 00500020 c1000000 23010000 91010000  .P. ....#.......
 0010 00000000 00000000 00000000 00000000  ................
 0020 00000000 00000000 00000000 23010000  ............#...
 0030 00000000 00000000 23010000 09010000  ........#.......
 0040 23010000 23010000 23010000 23010000  #...#...#...#...
 0050 23010000 23010000 23010000 23010000  #...#...#...#...
 0060 23010000 23010000 23010000 23010000  #...#...#...#...
 0070 23010000 23010000 23010000 23010000  #...#...#...#...
 0080 23010000 23010000 23010000 23010000  #...#...#...#...
 0090 23010000 23010000 23010000 23010000  #...#...#...#...
 00a0 23010000 23010000 23010000 23010000  #...#...#...#...
 00b0 23010000 23010000 23010000 23010000  #...#...#...#...

定义厂商芯片的向量表

注意:通常不需要我们这样做, 当在应用程序中定义异常处理程序时,cortex-m-rt 和 PAC 将自动填充该表。仅对于具有 cortex-m-rt 无法满足的特殊要求的控制器时才需要执行此操作。以某厂商的CortexM33芯片为例。

#[cfg(feature = "rt")]
extern "C" {
    fn UART0();
    fn UART1();
    fn TIMR0();
    fn TIMR1();
    fn DMA();
    fn SPI0();
    fn PWM_CH0();
    fn WDT();
    fn UART2();
    fn PWM_CH1();
    fn SARADC0();
    fn BTIMER0();
    fn HALL0();
    fn PWM_CH2();
    fn PWM_HALT();
    fn I2C0();
    fn CAN0();
    fn SPI1();
    fn RTC_BASE();
    fn PWM_CH3();
    fn TIMER2();
    fn UART3();
    fn TIMER3();
    fn SARADC1();
    fn BOD();
    fn CORDIC();
    fn BTIMER1();
    fn PWM_CH4();
    fn HALL1();
}
#[doc(hidden)]
pub union Vector {
    _handler: unsafe extern "C" fn(),
    _reserved: u32,
}
#[cfg(feature = "rt")]
#[doc(hidden)]
#[link_section = ".vector_table.interrupts"]
#[no_mangle]
pub static __INTERRUPTS: [Vector; 29] = [
    Vector { _handler: UART0 },
    Vector { _handler: UART1 },
    Vector { _handler: TIMR0 },
    Vector { _handler: TIMR1 },
    Vector { _handler: DMA },
    Vector { _handler: SPI0 },
    Vector { _handler: PWM_CH0 },
    Vector { _handler: WDT },
    Vector { _handler: UART2 },
    Vector { _handler: PWM_CH1 },
    Vector { _handler: SARADC0 },
    Vector { _handler: BTIMER0 },
    Vector { _handler: HALL0 },
    Vector { _handler: PWM_CH2 },
    Vector { _handler: PWM_HALT },
    Vector { _handler: I2C0 },
    Vector { _handler: CAN0 },
    Vector { _handler: SPI1 },
    Vector { _handler: RTC_BASE },
    Vector { _handler: PWM_CH3 },
    Vector { _handler: TIMER2 },
    Vector { _handler: UART3 },
    Vector { _handler: TIMER3 },
    Vector { _handler: SARADC1 },
    Vector { _handler: BOD },
    Vector { _handler: CORDIC },
    Vector { _handler: BTIMER1 },
    Vector { _handler: PWM_CH4 },
    Vector { _handler: HALL1 },
];
#[doc = r"Enumeration of all the interrupts."]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum Interrupt {
    #[doc = "0 - UART0 global interrupt"]
    UART0 = 0,
    #[doc = "1 - UART1 global interrupt"]
    UART1 = 1,
    #[doc = "2 - TIMR0 global interrupt"]
    TIMR0 = 2,
    #[doc = "3 - TIMR1 global interrupt"]
    TIMR1 = 3,
    #[doc = "4 - DMA global interrupt"]
    DMA = 4,
    #[doc = "5 - SPI0 global interrupt"]
    SPI0 = 5,
    #[doc = "6 - PWM_CH0 global interrupt"]
    PWM_CH0 = 6,
    #[doc = "7 - WDT global interrupt"]
    WDT = 7,
    #[doc = "8 - UART2 global interrupt"]
    UART2 = 8,
    #[doc = "9 - PWM_CH1 global interrupt"]
    PWM_CH1 = 9,
    #[doc = "10 - SARADC0 global interrupt"]
    SARADC0 = 10,
    #[doc = "11 - BTIMER0 global interrupt"]
    BTIMER0 = 11,
    #[doc = "12 - HALL0 global interrupt"]
    HALL0 = 12,
    #[doc = "13 - PWM_CH2 global interrupt"]
    PWM_CH2 = 13,
    #[doc = "14 - PWM_HALT global interrupt"]
    PWM_HALT = 14,
    #[doc = "15 - I2C0 global interrupt"]
    I2C0 = 15,
    #[doc = "16 - CAN0 global interrupt"]
    CAN0 = 16,
    #[doc = "17 - SPI1 global interrupt"]
    SPI1 = 17,
    #[doc = "18 - RTC_BASE global interrupt"]
    RTC_BASE = 18,
    #[doc = "19 - PWM_CH3 global interrupt"]
    PWM_CH3 = 19,
    #[doc = "20 - TIMER2 global interrupt"]
    TIMER2 = 20,
    #[doc = "21 - UART3 global interrupt"]
    UART3 = 21,
    #[doc = "22 - TIMER3 global interrupt"]
    TIMER3 = 22,
    #[doc = "23 - SARADC1 global interrupt"]
    SARADC1 = 23,
    #[doc = "24 - BOD global interrupt"]
    BOD = 24,
    #[doc = "25 - CORDIC global interrupt"]
    CORDIC = 25,
    #[doc = "26 - BTIMER1 global interrupt"]
    BTIMER1 = 26,
    #[doc = "27 - PWM_CH4 global interrupt"]
    PWM_CH4 = 27,
    #[doc = "28 - HALL1 global interrupt"]
    HALL1 = 28,
}
unsafe impl cortex_m::interrupt::InterruptNumber for Interrupt {
    #[inline(always)]
    fn number(self) -> u16 {
        self as u16
    }
}

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/88944.html

Like (0)
guozi的头像guozi
Previous 2024年6月4日 上午11:10
Next 2024年6月4日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注