組み込み・マイコン開発

NagiScript での組み込みシステム開発を学びます。

ロードマップ

組み込み・マイコン開発は、現行コンパイラー v0.2.0 ではまだ対応していない分野です(標準ライブラリが未実装のため)。下記の内容は将来仕様であり、現行の nagiscript コマンドでは動きません。現行で動作するのは組み込み print / println / panic と、C 標準関数の extern "C" fn 呼び出しのみです。


概要

NagiScript は以下の特性により、組み込み開発に適しています(将来計画):


対応プラットフォーム(準備中)

プラットフォーム 状態 用途
ARM Cortex-M 🔶 準備中 STM32, nRF52, etc.
RISC-V 🔶 準備中 ESP32-C3, etc.
x86 ベアメタル 🔶 準備中 UEFI, etc.

ハードウェア定義と現行構文の基礎

下記コードはすべて将来仕様で、文法のみ現行構文(extern "C" fn、戻り値は -> と明示 return、string 型)に合わせてあります。

C言語との連携(基本形)

filename.ngs
// ハードウェア定義(C言語で記述)→ 現行では extern "C" fn で宣言
extern "C" fn gpio_init(pin: i32)
extern "C" fn gpio_set(pin: i32, state: bool)
extern "C" fn delay(ms: i32)

// NagiScript での利用
fn blink_led(pin: i32, count: i32) {
    gpio_init(pin)

    var i = 0
    while i < count {
        gpio_set(pin, true)
        delay(500)

        gpio_set(pin, false)
        delay(500)

        i = i + 1
    }
}

fn main() {
    blink_led(13, 10)  // ピン 13 の LED を 10 回点滅
}

GPIO 操作

ピン定義

filename.ngs
extern "C" fn gpio_init(pin: i32)
extern "C" fn gpio_set(pin: i32, state: bool)
extern "C" fn gpio_read(pin: i32) -> bool

struct GpioPin {
    pin: i32
    mode: GpioMode
}

enum GpioMode { Input Output Alternate }

impl GpioPin {
    fn write(self: GpioPin, state: bool) {
        gpio_set(self.pin, state)
    }
}

fn pin_read(pin: i32) -> bool {
    return gpio_read(pin)
}

センサー読み取り

温度センサーの例

filename.ngs
extern "C" fn adc_read(channel: i32) -> i32
extern "C" fn delay(ms: i32)

fn read_temperature(sensor_pin: i32) -> f64 {
    val raw = adc_read(sensor_pin)
    // 10 ビット ADC の場合
    val voltage = (raw as f64) * 3.3 / 1023.0
    // TMP36 センサーの場合
    return (voltage - 0.5) * 100.0
}

fn main() {
    while true {
        val temp = read_temperature(0)
        println(temp)
        delay(1000)
    }
}

UART 通信

filename.ngs
extern "C" fn uart_init(baud_rate: i32)
extern "C" fn uart_send(data: *u8, len: i32)
extern "C" fn uart_receive(buffer: *u8, len: i32) -> i32

// ロードマップ: 文字列は *u8 + 長さで扱う(現行では string の生ポインタ取得は未検証)
fn send_bytes(data: *u8, len: i32) {
    uart_send(data, len)
}

fn receive_bytes(buffer: *u8, buffer_size: i32) -> i32 {
    return uart_receive(buffer, buffer_size)
}

I2C デバイス

filename.ngs
extern "C" fn i2c_init(sda: i32, scl: i32, freq: i32)
extern "C" fn i2c_write(addr: u8, data: *u8, len: i32)
extern "C" fn i2c_read(addr: u8, buffer: *u8, len: i32)

struct I2cDevice {
    addr: u8
}

impl I2cDevice {
    fn write(self: I2cDevice, data: u8) {
        var buf = data
        i2c_write(self.addr, &buf as *u8, 1)
    }
}

SPI 通信

filename.ngs
extern "C" fn spi_init(mosi: i32, miso: i32, sck: i32, cs: i32)
extern "C" fn spi_transfer(data: u8) -> u8

fn spi_write(buffer: *u8, len: i32) {
    var i = 0
    while i < len {
        unsafe {
            spi_transfer(*buffer)
        }
        i = i + 1
    }
}

割り込み

filename.ngs
extern "C" fn interrupt_enable(irq: i32, handler_id: i32)
extern "C" fn interrupt_disable(irq: i32)
extern "C" fn delay(ms: i32)

fn button_handler() {
    println("Button pressed!")
}

fn setup_interrupts() {
    interrupt_enable(10, 0)  // IRQ 10, ハンドラ ID 0 → if 分岐でハンドラ選択(ロードマップ)
}

fn main() {
    setup_interrupts()

    while true {
        // メインループ
        delay(100)
    }
}

パワーマネジメント

filename.ngs
extern "C" fn sleep_enter()
extern "C" fn sleep_exit()
extern "C" fn deep_sleep(ms: i32)

fn low_power_mode() {
    sleep_enter()
    // 低消費電力モード
    sleep_exit()
}

fn power_save(seconds: i32) {
    deep_sleep(seconds * 1000)
}

実践的な例: IoT センサーノード

ロードマップ

この例はハードウェア制御(GPIO / ADC / UART)を前提としており、現行では利用できません。文法のみ現行構文に合わせてあります。loop は現行では使えないため while true に、文字列連結 + は使わない形に直しています。

filename.ngs
extern "C" fn gpio_init(pin: i32)
extern "C" fn gpio_set(pin: i32, state: bool)
extern "C" fn adc_read(channel: i32) -> i32
extern "C" fn uart_init(baud_rate: i32)
extern "C" fn uart_send(data: *u8, len: i32)
extern "C" fn delay(ms: i32)
extern "C" fn deep_sleep(ms: i32)

struct SensorNode {
    led_pin: i32
    sensor_pin: i32
    uart_baud: i32
}

impl SensorNode {
    fn init(self: SensorNode) {
        gpio_init(self.led_pin)
        uart_init(self.uart_baud)
    }

    fn read_sensor(self: SensorNode) -> f64 {
        val raw = adc_read(self.sensor_pin)
        return (raw as f64) * 3.3 / 1023.0
    }

    fn send_data(self: SensorNode, data: *u8, len: i32) {
        gpio_set(self.led_pin, true)
        uart_send(data, len)
        gpio_set(self.led_pin, false)
    }
}

fn main() {
    val node = SensorNode {
        led_pin: 13,
        sensor_pin: 0,
        uart_baud: 115200
    }

    node.init()

    while true {
        val temp = node.read_sensor()
        println(temp)

        // 1 秒間スリープ
        deep_sleep(1000)
    }
}

ビルドとフラッシュ

ロードマップ

クロスコンパイル(組み込み向けターゲット)は現行では未対応です。下記は将来仕様です。

bash
# アーム Cortex-M の場合(将来仕様)
nagiscript build main.ngs --target arm-cortex-m4 -o firmware.bin

# フラッシュ
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
    -c "program firmware.bin verify reset exit 0x08000000"

注意事項

  1. スタックサイズ: 組み込み環境ではスタックが限られる
  2. ヒープ確保: Rc を使用する場合はヒープサイズに注意
  3. 割り込み安全性: 割り込みハンドラ内でのメモリ確保は避ける
  4. ボルテージ: ハードウェアの仕様を確認する