Giao diện
Microcontroller Programming Hardware
MMIO, Registers, và Portable Drivers
1. Memory-Mapped I/O (MMIO)
Concept
MEMORY-MAPPED I/O
┌─────────────────────────────────────────────────────────────────┐
│ │
│ CPU Address Space │
│ ───────────────── │
│ │
│ 0x0000_0000 ┌───────────────────────┐ │
│ │ Flash │ ← Code, constants │
│ 0x0800_0000 ├───────────────────────┤ │
│ │ RAM │ ← Variables, stack │
│ 0x2000_0000 ├───────────────────────┤ │
│ │ │ │
│ │ Peripheral Regs │ ← GPIO, UART, SPI... │
│ │ │ │
│ 0x4000_0000 │ ┌─────────────────┐ │ │
│ │ │ GPIO Port A │ │ 0x4002_0000 │
│ │ │ GPIO Port B │ │ 0x4002_0400 │
│ │ │ UART1 │ │ 0x4001_1000 │
│ │ │ SPI1 │ │ 0x4001_3000 │
│ │ └─────────────────┘ │ │
│ 0x5000_0000 └───────────────────────┘ │
│ │
│ Reading/Writing to peripheral address = Hardware operation │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Raw Pointer Access
rust
use core::ptr::{read_volatile, write_volatile};
// STM32 GPIO Port A base address
const GPIOA_BASE: usize = 0x4002_0000;
// Register offsets
const GPIO_MODER: usize = 0x00; // Mode register
const GPIO_ODR: usize = 0x14; // Output data register
const GPIO_IDR: usize = 0x10; // Input data register
/// Set pin as output
fn set_pin_output(pin: u8) {
let moder = (GPIOA_BASE + GPIO_MODER) as *mut u32;
unsafe {
let mut value = read_volatile(moder);
// Clear bits (2 bits per pin)
value &= !(0b11 << (pin * 2));
// Set as output (01)
value |= 0b01 << (pin * 2);
write_volatile(moder, value);
}
}
/// Set pin high
fn set_pin_high(pin: u8) {
let odr = (GPIOA_BASE + GPIO_ODR) as *mut u32;
unsafe {
let mut value = read_volatile(odr);
value |= 1 << pin;
write_volatile(odr, value);
}
}
/// Read pin state
fn read_pin(pin: u8) -> bool {
let idr = (GPIOA_BASE + GPIO_IDR) as *const u32;
unsafe {
let value = read_volatile(idr);
(value & (1 << pin)) != 0
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Type-Safe Register Access
rust
use core::ptr::{read_volatile, write_volatile};
/// GPIO Mode values
#[repr(u8)]
pub enum GpioMode {
Input = 0b00,
Output = 0b01,
Alternate = 0b10,
Analog = 0b11,
}
/// Type-safe GPIO register block
#[repr(C)]
pub struct GpioRegisters {
pub moder: u32, // 0x00
pub otyper: u32, // 0x04
pub ospeedr: u32, // 0x08
pub pupdr: u32, // 0x0C
pub idr: u32, // 0x10
pub odr: u32, // 0x14
pub bsrr: u32, // 0x18
pub lckr: u32, // 0x1C
pub afrl: u32, // 0x20
pub afrh: u32, // 0x24
}
impl GpioRegisters {
/// Get GPIO port by base address
pub fn get(base: usize) -> &'static mut Self {
unsafe { &mut *(base as *mut Self) }
}
/// Configure pin mode
pub fn set_mode(&mut self, pin: u8, mode: GpioMode) {
unsafe {
let mut value = read_volatile(&self.moder);
value &= !(0b11 << (pin * 2));
value |= (mode as u32) << (pin * 2);
write_volatile(&mut self.moder, value);
}
}
/// Set pin high (atomic, no read-modify-write)
pub fn set_high(&mut self, pin: u8) {
unsafe {
write_volatile(&mut self.bsrr, 1 << pin);
}
}
/// Set pin low (atomic)
pub fn set_low(&mut self, pin: u8) {
unsafe {
write_volatile(&mut self.bsrr, 1 << (pin + 16));
}
}
}
// Usage
fn blink_led() {
let gpioa = GpioRegisters::get(0x4002_0000);
gpioa.set_mode(5, GpioMode::Output); // PA5 = LED
loop {
gpioa.set_high(5);
delay(1_000_000);
gpioa.set_low(5);
delay(1_000_000);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
2. Peripheral Access Crates (PAC)
Auto-generated từ SVD
rust
// Sử dụng stm32f4 PAC (generated từ SVD file)
use stm32f4::stm32f401;
fn configure_gpio() {
// Get peripheral singleton
let dp = stm32f401::Peripherals::take().unwrap();
// Enable GPIOA clock
dp.RCC.ahb1enr.modify(|_, w| w.gpioaen().enabled());
// Configure PA5 as output
dp.GPIOA.moder.modify(|_, w| w.moder5().output());
// Set high
dp.GPIOA.odr.modify(|_, w| w.odr5().high());
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PAC với Type-State
rust
// PAC registers có type-safe API
dp.GPIOA.moder.modify(|_, w| {
w.moder0().input() // Pin 0: input
.moder1().output() // Pin 1: output
.moder2().alternate() // Pin 2: alternate function
.moder3().analog() // Pin 3: analog
});
// Bit manipulation helpers
dp.GPIOA.bsrr.write(|w| {
w.bs5().set_bit() // Set pin 5 high
.br6().set_bit() // Set pin 6 low
});1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
3. embedded-hal Traits
Tại sao cần HAL Traits?
PORTABLE DRIVER ARCHITECTURE
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Application Layer │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ LED Driver, Sensor Driver │ │
│ │ (Uses embedded-hal traits only) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ HAL Traits (embedded-hal) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ OutputPin InputPin SpiDevice I2c Delay │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ HAL Implementations │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ stm32- │ │ esp- │ │ rp2040 │ │
│ │ hal │ │ hal │ │ -hal │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Driver code = portable across all platforms! │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Core HAL Traits
rust
// embedded-hal 1.0 traits
/// Digital output pin
pub trait OutputPin {
type Error;
fn set_low(&mut self) -> Result<(), Self::Error>;
fn set_high(&mut self) -> Result<(), Self::Error>;
}
/// Digital input pin
pub trait InputPin {
type Error;
fn is_high(&mut self) -> Result<bool, Self::Error>;
fn is_low(&mut self) -> Result<bool, Self::Error>;
}
/// Blocking delay
pub trait DelayNs {
fn delay_ns(&mut self, ns: u32);
fn delay_us(&mut self, us: u32) {
self.delay_ns(us * 1000);
}
fn delay_ms(&mut self, ms: u32) {
self.delay_ns(ms * 1_000_000);
}
}
/// SPI device
pub trait SpiDevice {
type Error;
fn transaction(&mut self, operations: &mut [Operation])
-> Result<(), Self::Error>;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Writing Portable Drivers
rust
use embedded_hal::digital::OutputPin;
use embedded_hal::delay::DelayNs;
/// LED driver - works on ANY platform implementing embedded-hal
pub struct Led<P: OutputPin> {
pin: P,
}
impl<P: OutputPin> Led<P> {
pub fn new(pin: P) -> Self {
Self { pin }
}
pub fn on(&mut self) -> Result<(), P::Error> {
self.pin.set_high()
}
pub fn off(&mut self) -> Result<(), P::Error> {
self.pin.set_low()
}
pub fn blink<D: DelayNs>(&mut self, delay: &mut D, ms: u32)
-> Result<(), P::Error>
{
self.on()?;
delay.delay_ms(ms);
self.off()?;
delay.delay_ms(ms);
Ok(())
}
}
// Usage trên STM32
use stm32f4xx_hal::{gpio::PA5, prelude::*};
fn main() {
let pin: PA5<Output<PushPull>> = /* configure */;
let mut led = Led::new(pin);
led.on().unwrap();
}
// Usage trên ESP32 - CÙNG driver code!
use esp_hal::gpio::GpioPin;
fn main() {
let pin: GpioPin<Output, 5> = /* configure */;
let mut led = Led::new(pin);
led.on().unwrap();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
4. Interrupt Handling
Interrupt Vector Table
rust
use cortex_m_rt::entry;
use stm32f4xx_hal::{pac, prelude::*, interrupt};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
// Configure external interrupt on PA0
dp.SYSCFG.exticr1.modify(|_, w| w.exti0().pa0());
dp.EXTI.imr.modify(|_, w| w.mr0().unmasked());
dp.EXTI.rtsr.modify(|_, w| w.tr0().enabled()); // Rising edge
// Enable interrupt in NVIC
unsafe {
cortex_m::peripheral::NVIC::unmask(pac::Interrupt::EXTI0);
}
loop {
cortex_m::asm::wfi(); // Wait for interrupt
}
}
/// Interrupt handler
#[interrupt]
fn EXTI0() {
// Clear interrupt flag
let dp = unsafe { pac::Peripherals::steal() };
dp.EXTI.pr.write(|w| w.pr0().clear());
// Handle button press
toggle_led();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Critical Sections
rust
use cortex_m::interrupt;
static mut COUNTER: u32 = 0;
fn increment_counter() {
// Disable interrupts during access
interrupt::free(|_cs| {
unsafe {
COUNTER += 1;
}
});
}
// Với RTIC framework
#[rtic::app(device = stm32f4::stm32f401)]
mod app {
#[shared]
struct Shared {
counter: u32,
}
#[task(shared = [counter])]
fn task1(mut cx: task1::Context) {
cx.shared.counter.lock(|counter| {
*counter += 1;
});
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
5. Complete Example: Blink LED
rust
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
// Take peripherals
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// Configure clocks
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
// Configure GPIO
let gpioa = dp.GPIOA.split();
let mut led = gpioa.pa5.into_push_pull_output();
// Create delay
let mut delay = cp.SYST.delay(&clocks);
// Blink forever
loop {
led.set_high();
delay.delay_ms(500u32);
led.set_low();
delay.delay_ms(500u32);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
🎯 Best Practices
| Practice | Reason |
|---|---|
| Use PAC/HAL | Type-safe, less bugs than raw pointers |
| Use embedded-hal | Portable drivers |
| Minimize unsafe | Contain all unsafe in HAL layer |
| Critical sections | Protect shared state |
| Check errata | Hardware has bugs too! |