Blinky 1 - starts

Nitroblue 1·2025년 8월 31일

first examples for RUST

  1. BSF
  2. HAL
  3. PAC
  4. MCU
    That's the hierarchy. from 4 to 1, it gets higher level interface. So, i'm gonna study from MCU, bottom-up way.

Peripheral Control

  • Microbits 한정 자료들인 것 같다. 일단 STM32F는 패스

Unsafe Rust

  • if i want to achieve the main gola of rust 'memory safety', i have to play by the borrow checker's rules.
  • 이 말은, 'owned values'나 'referenced values'로 작업해야 하며, 참조는 기본적으로 pointer지만, 특정 조건을 달고 있다. 바로 valid value의 주솟값만 가질 수 있다는 것.
    • 왜냐, borrow checker는 거기에 무엇이 있는지 모르기 때문에 원시 포인터와 같이 메모리의 임의의 위치에 null되거나 할당될 수 없다.
    • 따라서 이런 RUST 환경에서 특정 주소에 값을 할당해야 하는 경우 약간 힘들 수 있지만, 방법이 존재한다.
    • 바로, 원시 포인터 역참조처럼 borrow checker가 확인할 방법이 없는 경우.
  • 기본적으로 컴파일러나 안전하지 않은 블록이나 함수에서 코드를 접하는 다른 모든 사람들은 실제로 이렇게 하는 것이 안전하다. 만약 이런 방법을 사용하게 된다면 주석을 통해 왜 이렇게 구현했는 지 알려주는 게 매너(?)

  • 우리는 특정 값을 특정 주소에 설정하고 이것이 안전하다는 것을 확인해야 한다.
    • 즉, 해당 주소에 그 값이 있고, 다른 사람은 사용하지 않는 다는 것을 confirm해줘야 한다.
#![no_std]
#![no_main]

use core::ptr::{read_volatile, write_volatile};
use cortex_m::asm::nop;
use cortex_m_rt::entry;
use panic_halt as _;

/// STM32F446RE (Nucleo) LD2 LED: PA5
/// RCC & GPIO 레지스터 맵 (RM0390 기준)
const RCC_BASE:        u32 = 0x4002_3800;
const RCC_AHB1ENR:     *mut u32 = (RCC_BASE + 0x30) as *mut u32; // AHB1 peripheral clock enable
const GPIOA_BASE:      u32 = 0x4002_0000;
const GPIOA_MODER:     *mut u32 = (GPIOA_BASE + 0x00) as *mut u32; // mode register
const GPIOA_OTYPER:    *mut u32 = (GPIOA_BASE + 0x04) as *mut u32; // output type
const GPIOA_OSPEEDR:   *mut u32 = (GPIOA_BASE + 0x08) as *mut u32; // speed
const GPIOA_PUPDR:     *mut u32 = (GPIOA_BASE + 0x0C) as *mut u32; // pull-up/down
const GPIOA_BSRR:      *mut u32 = (GPIOA_BASE + 0x18) as *mut u32; // bit set/reset register

const GPIOAEN_BIT:     u32 = 0;  // RCC_AHB1ENR GPIOAEN
const LED_PIN:         u32 = 5;  // PA5

#[entry]
fn main() -> ! {
    unsafe {
        // 1) GPIOA 클럭 인에이블 (RCC_AHB1ENR.GPIOAEN = 1)
        let mut r = read_volatile(RCC_AHB1ENR);
        r |= 1 << GPIOAEN_BIT;
        write_volatile(RCC_AHB1ENR, r);

        // 약간의 딜레이로 클럭 안정화 (몇 사이클 노프)
        for _ in 0..64 { nop(); }

        // 2) PA5를 General Purpose Output으로 설정 (MODER5 = 01b)
        // MODER는 핀당 2비트
        let mut moder = read_volatile(GPIOA_MODER);
        moder &= !(0b11 << (LED_PIN * 2));    // 먼저 클리어
        moder |=  (0b01 << (LED_PIN * 2));    // Output mode
        write_volatile(GPIOA_MODER, moder);

        // 3) 출력 타입/스피드/풀업다운 기본값 (푸시풀, 로/미드 스피드, 풀 없음)
        let mut otyper = read_volatile(GPIOA_OTYPER);
        otyper &= !(1 << LED_PIN);            // 0 = Push-pull
        write_volatile(GPIOA_OTYPER, otyper);

        let mut ospeed = read_volatile(GPIOA_OSPEEDR);
        ospeed &= !(0b11 << (LED_PIN * 2));   // 00 = Low speed (충분)
        write_volatile(GPIOA_OSPEEDR, ospeed);

        let mut pupdr = read_volatile(GPIOA_PUPDR);
        pupdr &= !(0b11 << (LED_PIN * 2));    // 00 = No pull-up/pull-down
        write_volatile(GPIOA_PUPDR, pupdr);
    }

    // 4) 토글 루프 (BSRR로 set/reset — 레이스 없이 원사이클 조작 가능)
    let mut on = false;
    loop {
        unsafe {
            if on {
                // 리셋(끄기): BSRR의 상위 16비트 사용 (pin+16)
                write_volatile(GPIOA_BSRR, 1 << (LED_PIN + 16));
            } else {
                // 세트(켜기): BSRR 하위 16비트
                write_volatile(GPIOA_BSRR, 1 << LED_PIN);
            }
        }
        // 조악한 딜레이
        for _ in 0..400_000 { nop(); }
        on = !on;
    }
}
  • 위 코드는 작동하긴 하지만, 사용하려는 모든 주변 장치에 대해 이 프로세스가 반복되기 때문에 지루하고, 오류가 발생하기 쉽다. 따라서 더 좋은 코드를 작성해보자.

0개의 댓글