# gd32e2-hal [![CI/CD](https://github.com/Szizoid/gd32e2-hal/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/Szizoid/gd32e2-hal/actions/workflows/ci-cd.yml) [English](#english) · [Русский](#русский) --- ## English Hardware abstraction layer (HAL) for the GD32E230 microcontrollers (Cortex-M23), written on top of the PAC crate: [github](https://github.com/gd32-rust/gd32-rs), [crates.io](https://crates.io/crates/gd32e2). > **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`](https://szizoid.github.io/gd32e2-hal/gd32e230f4xx/gd32e2_hal/index.html) / [`f6xx`](https://szizoid.github.io/gd32e2-hal/gd32e230f6xx/gd32e2_hal/index.html) / [`f8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230f8xx/gd32e2_hal/index.html) | 20 | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230e8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230e8xx/gd32e2_hal/index.html) | 24 | 64K | 8K | | [`gd32e230g4xx`](https://szizoid.github.io/gd32e2-hal/gd32e230g4xx/gd32e2_hal/index.html) / [`g6xx`](https://szizoid.github.io/gd32e2-hal/gd32e230g6xx/gd32e2_hal/index.html) / [`g8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230g8xx/gd32e2_hal/index.html) | 28 | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230k4tx`](https://szizoid.github.io/gd32e2-hal/gd32e230k4tx/gd32e2_hal/index.html) / [`k6tx`](https://szizoid.github.io/gd32e2-hal/gd32e230k6tx/gd32e2_hal/index.html) / [`k8tx`](https://szizoid.github.io/gd32e2-hal/gd32e230k8tx/gd32e2_hal/index.html) | 32, LQFP | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230k4ux`](https://szizoid.github.io/gd32e2-hal/gd32e230k4ux/gd32e2_hal/index.html) / [`k6ux`](https://szizoid.github.io/gd32e2-hal/gd32e230k6ux/gd32e2_hal/index.html) / [`k8ux`](https://szizoid.github.io/gd32e2-hal/gd32e230k8ux/gd32e2_hal/index.html) | 32, QFN | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230c4xx`](https://szizoid.github.io/gd32e2-hal/gd32e230c4xx/gd32e2_hal/index.html) / [`c6xx`](https://szizoid.github.io/gd32e2-hal/gd32e230c6xx/gd32e2_hal/index.html) / [`c8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230c8xx/gd32e2_hal/index.html) | 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](https://docs.rs/gd32e2-hal/latest/gd32e2_hal/) for `gd32e230c8xx`, [GitHub Pages](https://szizoid.github.io/gd32e2-hal/) for every part. A shorter view of most of the public API is in `examples/`. ### Example ```rust 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 ```shell 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: - Apache License 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) - MIT ([LICENSE-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](https://github.com/gd32-rust/gd32-rs), [crates.io](https://crates.io/crates/gd32e2). > **В работе.** Пишется частями, 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`](https://szizoid.github.io/gd32e2-hal/gd32e230f4xx/gd32e2_hal/index.html) / [`f6xx`](https://szizoid.github.io/gd32e2-hal/gd32e230f6xx/gd32e2_hal/index.html) / [`f8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230f8xx/gd32e2_hal/index.html) | 20 | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230e8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230e8xx/gd32e2_hal/index.html) | 24 | 64K | 8K | | [`gd32e230g4xx`](https://szizoid.github.io/gd32e2-hal/gd32e230g4xx/gd32e2_hal/index.html) / [`g6xx`](https://szizoid.github.io/gd32e2-hal/gd32e230g6xx/gd32e2_hal/index.html) / [`g8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230g8xx/gd32e2_hal/index.html) | 28 | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230k4tx`](https://szizoid.github.io/gd32e2-hal/gd32e230k4tx/gd32e2_hal/index.html) / [`k6tx`](https://szizoid.github.io/gd32e2-hal/gd32e230k6tx/gd32e2_hal/index.html) / [`k8tx`](https://szizoid.github.io/gd32e2-hal/gd32e230k8tx/gd32e2_hal/index.html) | 32, LQFP | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230k4ux`](https://szizoid.github.io/gd32e2-hal/gd32e230k4ux/gd32e2_hal/index.html) / [`k6ux`](https://szizoid.github.io/gd32e2-hal/gd32e230k6ux/gd32e2_hal/index.html) / [`k8ux`](https://szizoid.github.io/gd32e2-hal/gd32e230k8ux/gd32e2_hal/index.html) | 32, QFN | 16K / 32K / 64K | 4K / 6K / 8K | | [`gd32e230c4xx`](https://szizoid.github.io/gd32e2-hal/gd32e230c4xx/gd32e2_hal/index.html) / [`c6xx`](https://szizoid.github.io/gd32e2-hal/gd32e230c6xx/gd32e2_hal/index.html) / [`c8xx`](https://szizoid.github.io/gd32e2-hal/gd32e230c8xx/gd32e2_hal/index.html) | 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](https://docs.rs/gd32e2-hal/latest/gd32e2_hal/) для `gd32e230c8xx`, [GitHub Pages](https://szizoid.github.io/gd32e2-hal/) для каждого партномера. Более краткое отражение большей части публичного API библиотеки в `examples/`. ### Пример ```rust 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 ```shell 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 License 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) - MIT ([LICENSE-MIT](LICENSE-MIT)) Любой вклад, отправленный для включения в эту работу, лицензируется так же, без дополнительных условий, если не оговорено иное (Apache-2.0, раздел 5).