前言
在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
中使用定義,向量表由以下部分組成:
如果為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
}
}