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 inexamples/, 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,CMPOSELandlockof the comparator; USARTEvent::Error; internal EXTI lines (LVD, RTC, CMP, USART0 wakeup); option byte fields other than the data bytes;SYSCFGother thanEXTISS— thePA11/PA12remap needs a package below 32 pins.
- 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.
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 isgd32e230k8ux; the examples take the feature from the crate depending on itself in[dev-dependencies]. docs.rs is built forgd32e230c8xx, 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.rsturns the choice into thememory.xthe 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.
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/.
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
}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.rswrites thememory.xthe linker needs from the selected library feature, nothing has to be copied. (Amemory.xin 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 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 afterwardsPeripherals not covered at all
-
PMU— sleep / deep-sleep / standby, wakeup pin, LDO. - The rest of
SYSCFG— the second DMA channel map, and theCFG2links 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/listenshape has settled, the work is mechanical. - DMA: circular mode,
M2M,embedded-dmafor buffers. - ADC: scan over several channels, which needs DMA.
- Timers: complementary outputs, break and dead time;
TRGOonTIMER5so 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_HOLDandWWDGT_HOLDin a DBG module, so that the watchdogs do not count under a halted debugger. - RCU:
HXTALandLXTAL(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.
Dual licensed, at your option:
- Apache License 2.0 (LICENSE-APACHE)
- MIT (LICENSE-MIT)
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 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, дальше остаётся подключённымПериферия, не покрытая вовсе
-
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 License 2.0 (LICENSE-APACHE)
- MIT (LICENSE-MIT)
Любой вклад, отправленный для включения в эту работу, лицензируется так же, без дополнительных условий, если не оговорено иное (Apache-2.0, раздел 5).