Skip to content

About

Embedded Rust HAL for GD32E23x microcontrollers

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

gd32e2-hal

CI/CD

English · Русский


English

Hardware abstraction layer (HAL) for the GD32E230 microcontrollers (Cortex-M23), written on top of the PAC crate: github, crates.io.

Work in progress. Written piece by piece, the API is unstable. Implemented modules: adc, cmp, crc, dma, exti, fmc, gpio, i2c, rcu, spi, syscfg, time, timer, usart, watchdog. Usage examples live in examples/, all of them run on the board. Not verified: the I²C fast and fast plus modes — written, unreachable on the test bench without external pull-ups; CMPHST, CMPOSEL and lock of the comparator; USART Event::Error; internal EXTI lines (LVD, RTC, CMP, USART0 wakeup); option byte fields other than the data bytes; SYSCFG other than EXTISS — the PA11/PA12 remap needs a package below 32 pins.

Principles

  • Errors at compile time. The identity of an entity (a pin, a peripheral, a peripheral channel and so on) determines the list of available methods — an input pin has no .set_high() to call;
  • Thanks to Rust ownership and borrowing, the same peripheral cannot be initialized twice or taken before its clock is enabled, and hardware cannot be modified from two places at once;
  • Zero-cost. The same register writes as less abstract PAC code; most of the abstractions are ZSTs.

Chip variants

A feature name is the part number with x in the fields the code does not see: the letter is the number of bonded pads, the digit is the flash code, the trailing x is the temperature grade.

feature pads flash SRAM
gd32e230f4xx / f6xx / f8xx 20 16K / 32K / 64K 4K / 6K / 8K
gd32e230e8xx 24 64K 8K
gd32e230g4xx / g6xx / g8xx 28 16K / 32K / 64K 4K / 6K / 8K
gd32e230k4tx / k6tx / k8tx 32, LQFP 16K / 32K / 64K 4K / 6K / 8K
gd32e230k4ux / k6ux / k8ux 32, QFN 16K / 32K / 64K 4K / 6K / 8K
gd32e230c4xx / c6xx / c8xx 48 16K / 32K / 64K 4K / 6K / 8K

32 pads is the only place where the package matters: on QFN32 ground moves to the belly pad, and the two freed pins go to PB2 and PB8; LQFP32 has neither.

Development targets GD32E230K8U6, that is gd32e230k8ux; the examples take the feature from the crate depending on itself in [dev-dependencies]. docs.rs is built for gd32e230c8xx, the largest part, and shows pins and peripherals the smaller parts do not have; each feature in the table links to the documentation of its own part. build.rs turns the choice into the memory.x the linker needs and into cfg flags that gate the source. The AF map depends on the flash code — the same pin on the same AF number may lead to a different peripheral.

The orthogonal defmt feature derives defmt::Format on public enums and error types. Off by default; it also enables embedded-hal/defmt-03.

Documentation

Method details are in the documentation: docs.rs for gd32e230c8xx, GitHub Pages for every part. A shorter view of most of the public API is in examples/.

Example

use gd32e2_hal::prelude::*;
use gd32e2_hal::pac;
use gd32e2_hal::rcu::{ClockConfig, PllFreq, SysClk};
use gd32e2_hal::usart::{Usart, UsartConfig};

let dp = pac::Peripherals::take().unwrap();
let mut fmc = dp.fmc.constrain();
let config = ClockConfig::default()
    .sysclk(SysClk::Pll(PllFreq::Mhz48));    // PLL from IRC8M -> 48 MHz
let mut rcu = dp.rcu.constrain().freeze(&mut fmc, config);
let parts = dp.gpioa.split(&mut rcu);        // enables the GPIOA clock

let mut led = parts.pa5.into_output();
led.set_high();

let tx = parts.pa9.into_alternate::<1>();    // USART0_TX; ::<3>() does not compile
let rx = parts.pa10.into_alternate::<1>();
let mut usart0 = Usart::new(&mut rcu, dp.usart0, tx, rx, UsartConfig::default());
if let Ok(byte) = usart0.read_byte() {
    usart0.write_byte(byte);                 // verified on hardware: the echo comes back
}

Building

Flashing and debugging over SWD (ST-Link V2 + probe-rs, PA13 / PA14). Firmware output goes over RTT through the same probe (defmt + defmt-rtt).

  • Target thumbv8m.base-none-eabi;
  • build.rs writes the memory.x the linker needs from the selected library feature, nothing has to be copied. (A memory.x in the project root still overrides the generated one — the linker looks there before the search paths — and that is the supported way out for a board that is not in the table);
  • scripts/check.ps1 / scripts/check.sh — every CI check, stopping at the first failure.

cargo aliases

cargo lib             # the library only, an alias for build --features gd32e230c8xx
cargo libclippy       # clippy over the library, with the same feature
cargo pubdry          # publish --dry-run with the same feature
cargo be usart-echo   # check that one example builds, no probe needed
cargo bre usart-echo  # the same in release
cargo examples        # every example in release, skipping the ones for another part
cargo examplesclippy  # clippy over the same examples
cargo re usart-echo   # build + flash over SWD, stays attached afterwards

Roadmap

Peripherals not covered at all

  • PMU — sleep / deep-sleep / standby, wakeup pin, LDO.
  • The rest of SYSCFG — the second DMA channel map, and the CFG2 links into the timer break input.
  • RTC — calendar. With no crystal it runs off IRC40K, with the accuracy of IRC40K.

Finishing what is there

  • DMA interrupts — the Event / listen shape has settled, the work is mechanical.
  • DMA: circular mode, M2M, embedded-dma for buffers.
  • ADC: scan over several channels, which needs DMA.
  • Timers: complementary outputs, break and dead time; TRGO on TIMER5 so it can drive DMA.
  • I²C: 10-bit addressing, SMBus, slave, DMA.
  • SPI: half-duplex and single-wire modes (BDEN/BDOEN/RO); hardware NSS, CRC, TI mode and slave — low priority.
  • USART: hardware flow control (CTS/RTS); a non-blocking API for 9-bit words.
  • FWDGT: window mode (WND); FWDGT_HOLD and WWDGT_HOLD in a DBG module, so that the watchdogs do not count under a halted debugger.
  • RCU: HXTAL and LXTAL (no crystal is fitted on the board).
  • FMC: embedded-storage (ReadNorFlash / NorFlash) on top of the existing program and erase.

Infrastructure

  • gd32e231x support: part number features, pin map, gates. The PAC module exists, the chip is not at hand.
  • Async: embedded-hal-async / embedded-io-async, starting with a time driver on one of the timers — that is what Embassy expects from a HAL.
  • Host tests — there are none at all; the crate does not build for the host, so mocks are needed.
  • Moving the HAL into its own crate/repository.

License

Dual licensed, at your option:

Any contribution submitted for inclusion in this work shall be licensed the same way, without additional terms, unless stated otherwise (Apache-2.0, section 5).


Русский

Hardware abstraction layer (HAL) для микроконтроллеров GD32E230 (Cortex-M23), написанный поверх PAC-крейта: github, crates.io.

В работе. Пишется частями, API нестабилен. Реализованные модули: adc, cmp, crc, dma, exti, fmc, gpio, i2c, rcu, spi, syscfg, time, timer, usart, watchdog. Имеются примеры использования в examples/, все прогнаны на плате. Из непроверенного: режимы I²C fast и fast plus — написаны, на стенде без внешних подтяжек недостижимы; CMPHST, CMPOSEL и lock у компаратора; Event::Error у USART; внутренние линии EXTI (LVD, RTC, CMP, USART0 wakeup); поля option bytes кроме байтов данных; SYSCFG кроме EXTISS — ремапу PA11/PA12 нужен корпус меньше 32 выводов.

Принципы

  • Ошибки на этапе компиляции. Идентичность сущности (пин, элемент периферии, канал периферии и пр.) определяет список доступных методов (например, input-пин не позволит вызвать у него метод .set_high());
  • Благодаря концепциям владения и заимствования языка Rust одну и ту же периферию не удастся инициализировать дважды или взять до включения тактования, а также не получится менять железо из двух разных участков кода одновременно;
  • Zero-cost. Те же записи в регистры, что и у менее абстрактного PAC-кода; большая часть абстракций — ZST.

Варианты чипа

Имя фичи — партномер с x в тех полях, которых код не видит: буква — число разваренных выводов, цифра — код флеша, хвостовой x — температурный класс.

фича выводы флеш SRAM
gd32e230f4xx / f6xx / f8xx 20 16K / 32K / 64K 4K / 6K / 8K
gd32e230e8xx 24 64K 8K
gd32e230g4xx / g6xx / g8xx 28 16K / 32K / 64K 4K / 6K / 8K
gd32e230k4tx / k6tx / k8tx 32, LQFP 16K / 32K / 64K 4K / 6K / 8K
gd32e230k4ux / k6ux / k8ux 32, QFN 16K / 32K / 64K 4K / 6K / 8K
gd32e230c4xx / c6xx / c8xx 48 16K / 32K / 64K 4K / 6K / 8K

32 вывода — единственное место, где корпус имеет значение: у QFN32 земля уходит на брюшко, и два освободившихся вывода достаются PB2 и PB8, у LQFP32 нет.

Разработка идёт под GD32E230K8U6, то есть gd32e230k8ux; примеры берут фичу из записи крейта на себя в [dev-dependencies]. docs.rs собрана для gd32e230c8xx, самой жирной детали, и показывает ноги и периферию, которых у мелкой нет; каждая фича в таблице ведёт на документацию своего партномера. build.rs превращает выбор в нужный линкеру memory.x и в cfg-флаги, по которым гейтится исходник. Карта AF зависит от кода флеша — одна и та же нога на одном номере AF может вести к разной периферии.

Ортогональная фича defmt вешает defmt::Format на публичные enum и типы ошибок. По умолчанию выключена; заодно включает embedded-hal/defmt-03.

Документация

Подробности по методам — в документации: docs.rs для gd32e230c8xx, GitHub Pages для каждого партномера. Более краткое отражение большей части публичного API библиотеки в examples/.

Пример

use gd32e2_hal::prelude::*;
use gd32e2_hal::pac;
use gd32e2_hal::rcu::{ClockConfig, PllFreq, SysClk};
use gd32e2_hal::usart::{Usart, UsartConfig};

let dp = pac::Peripherals::take().unwrap();
let mut fmc = dp.fmc.constrain();
let config = ClockConfig::default()
    .sysclk(SysClk::Pll(PllFreq::Mhz48));    // PLL от IRC8M -> 48 МГц
let mut rcu = dp.rcu.constrain().freeze(&mut fmc, config);
let parts = dp.gpioa.split(&mut rcu);        // включает такт GPIOA

let mut led = parts.pa5.into_output();
led.set_high();

let tx = parts.pa9.into_alternate::<1>();    // USART0_TX; ::<3>() не соберётся
let rx = parts.pa10.into_alternate::<1>();
let mut usart0 = Usart::new(&mut rcu, dp.usart0, tx, rx, UsartConfig::default());
if let Ok(byte) = usart0.read_byte() {
    usart0.write_byte(byte);                 // проверено на железе: эхо возвращается
}

Сборка

Прошивка и отладка по SWD (ST-Link V2 + probe-rs, PA13 / PA14). Вывод из прошивки идёт по RTT через тот же зонд (defmt + defmt-rtt).

  • Таргет thumbv8m.base-none-eabi;
  • build.rs пишет нужный линкеру memory.x из выбранной фичи библиотеки, копировать ничего не надо. (memory.x в корне проекта всё равно перебивает сгенерированный — линкер смотрит туда раньше путей поиска, — и это штатный выход для платы, которой нет в таблице);
  • scripts/check.ps1 / scripts/check.sh — все проверки CI, до первого падения.

Алиасы cargo

cargo lib             # только библиотека, алиас для build --features gd32e230c8xx
cargo libclippy       # clippy по библиотеке, с той же фичей
cargo pubdry          # publish --dry-run с той же фичей
cargo be usart-echo   # проверить сборку одного примера, зонд не нужен
cargo bre usart-echo  # то же в release
cargo examples        # все примеры в release, кроме примеров под другой партномер
cargo examplesclippy  # clippy по тем же примерам
cargo re usart-echo   # сборка + прошивка по SWD, дальше остаётся подключённым

Roadmap

Периферия, не покрытая вовсе

  • PMU — sleep / deep-sleep / standby, wakeup-нога, LDO.
  • Остаток SYSCFG — вторая карта каналов DMA и подключения CFG2 к break-входу таймеров.
  • RTC — календарь. Без кварца пойдёт от IRC40K, с его же точностью.

Доделать имеющееся

  • Прерывания DMA — форма Event / listen устоялась, работа механическая.
  • DMA: циклический режим, M2M, embedded-dma для буферов.
  • ADC: scan по нескольким каналам, для чего нужен DMA.
  • Таймеры: комплементарные выходы, break и dead time; TRGO у TIMER5, чтобы он мог запускать DMA.
  • I²C: 10-битная адресация, SMBus, slave, DMA.
  • SPI: half-duplex и однопроводные режимы (BDEN/BDOEN/RO); аппаратный NSS, CRC, TI mode и slave — низкий приоритет.
  • USART: аппаратное управление потоком (CTS/RTS); неблокирующий API для 9-битных слов.
  • FWDGT: оконный режим (WND); FWDGT_HOLD и WWDGT_HOLD в модуле DBG, чтобы сторожа не считали под остановленным отладчиком.
  • RCU: HXTAL и LXTAL (ни один кварц на плате не запаян).
  • FMC: embedded-storage (ReadNorFlash / NorFlash) поверх готовых записи и стирания.

Инфраструктура

  • Поддержка gd32e231x: фичи партномеров, карта пинов, гейты. PAC-модуль есть, чипа на руках нет.
  • Async: embedded-hal-async / embedded-io-async, начиная с драйвера времени поверх одного из таймеров — именно его Embassy ждёт от HAL.
  • Тесты на хосте — их нет вовсе; крейт не собирается под хост, поэтому нужны моки.
  • Вынос HAL в отдельный крейт/репозиторий.

Лицензия

Двойное лицензирование, на выбор:

Любой вклад, отправленный для включения в эту работу, лицензируется так же, без дополнительных условий, если не оговорено иное (Apache-2.0, раздел 5).

About

Embedded Rust HAL for GD32E23x microcontrollers

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages