├── .cargo └── config.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── README.md ├── build.rs ├── examples ├── blinky.rs ├── display.rs ├── ferris.raw ├── ferris.rs ├── hello_world.rs ├── interrupt.rs ├── led_shell.rs ├── scan.rs └── sdcard_test.rs ├── memory-c8.x ├── memory-cb.x ├── openocd.cfg ├── openocd.gdb ├── sipeed-jtag.cfg └── src ├── lcd.rs ├── led.rs ├── lib.rs ├── sdcard.rs └── stdout.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.riscv32imac-unknown-none-elf] 2 | runner = 'riscv64-unknown-elf-gdb -x openocd.gdb' 3 | rustflags = [ 4 | "-C", "link-arg=-Tmemory-c8.x", 5 | "-C", "link-arg=-Tlink.x", 6 | ] 7 | 8 | [build] 9 | target = "riscv32imac-unknown-none-elf" 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ staging, trying, master ] 4 | pull_request: 5 | 6 | name: CI 7 | 8 | jobs: 9 | ci-linux: 10 | runs-on: ubuntu-20.04 11 | continue-on-error: ${{ matrix.experimental || false }} 12 | strategy: 13 | matrix: 14 | rust: [stable] 15 | 16 | include: 17 | # Test nightly but don't fail 18 | - rust: nightly 19 | experimental: true 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | profile: minimal 25 | toolchain: ${{ matrix.rust }} 26 | override: true 27 | - name: Install Rust target 28 | run: rustup target install --toolchain=${{ matrix.rust }} riscv32imac-unknown-none-elf 29 | 30 | - name: Check code 31 | run: cargo check --all-features 32 | - name: Check examples 33 | run: cargo build --target riscv32imac-unknown-none-elf --examples --all-features --release 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The Rust Code of Conduct 2 | 3 | ## Conduct 4 | 5 | **Contact**: [RISC-V team](https://github.com/rust-embedded/wg#the-riscv-team) 6 | 7 | * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. 8 | * On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. 9 | * Please be kind and courteous. There's no need to be mean or rude. 10 | * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. 11 | * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. 12 | * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. 13 | * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [RISC-V team][team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. 14 | * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. 15 | 16 | ## Moderation 17 | 18 | These are the policies for upholding our community's standards of conduct. 19 | 20 | 1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) 21 | 2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. 22 | 3. Moderators will first respond to such remarks with a warning. 23 | 4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. 24 | 5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. 25 | 6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. 26 | 7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. 27 | 8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. 28 | 29 | In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. 30 | 31 | And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. 32 | 33 | The enforcement policies listed above apply to all official embedded WG venues; including official IRC channels (#rust-embedded); GitHub repositories under rust-embedded; and all forums under rust-embedded.org (forum.rust-embedded.org). 34 | 35 | *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* 36 | 37 | [team]: https://github.com/rust-embedded/wg#the-riscv-team 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "longan-nano" 3 | version = "0.3.0" 4 | authors = ["Vadim Kaushan "] 5 | repository = "https://github.com/riscv-rust/longan-nano" 6 | categories = ["embedded", "hardware-support", "no-std"] 7 | description = "Board support package for the Longan Nano board" 8 | keywords = ["riscv", "gd32", "bsp"] 9 | license = "ISC" 10 | edition = "2018" 11 | 12 | [package.metadata.docs.rs] 13 | features = ['lcd'] 14 | rustdoc-args = ["--cfg", "docsrs"] 15 | default-target = "x86_64-unknown-linux-gnu" 16 | 17 | [dependencies] 18 | gd32vf103xx-hal = "0.5.0" 19 | embedded-hal = "0.2.6" 20 | nb = "1.0.0" 21 | riscv = "0.10.1" 22 | st7735-lcd = { version = "0.8.1", optional = true } 23 | embedded-sdmmc = { version = "0.3.0", optional = true } 24 | 25 | [dev-dependencies] 26 | riscv-rt = "0.11.0" 27 | panic-halt = "0.2.0" 28 | embedded-graphics = "0.7.1" 29 | ushell = "0.3.5" 30 | 31 | [features] 32 | lcd = ["st7735-lcd"] 33 | sdcard = ["embedded-sdmmc"] 34 | 35 | [[example]] 36 | name = "display" 37 | required-features = ["lcd"] 38 | 39 | [[example]] 40 | name = "ferris" 41 | required-features = ["lcd"] 42 | 43 | [[example]] 44 | name = "sdcard_test" 45 | required-features = ["sdcard"] 46 | 47 | [profile.release] 48 | opt-level = "z" # Optimize for size. 49 | codegen-units = 1 50 | lto = true 51 | 52 | [profile.dev] 53 | opt-level = "z" # Optimize for size. 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/d/longan-nano.svg)](https://crates.io/crates/longan-nano) 2 | [![crates.io](https://img.shields.io/crates/v/longan-nano.svg)](https://crates.io/crates/longan-nano) 3 | ![Build Status](https://github.com/riscv-rust/longan-nano/workflows/CI/badge.svg) 4 | 5 | # `longan-nano` 6 | 7 | > Board support crate for the Longan Nano board 8 | 9 | ## [Documentation](https://docs.rs/crate/longan-nano) 10 | 11 | ## Getting started 12 | 13 | ### Installing dependencies 14 | 15 | - Rust 1.36 or a newer toolchain. e.g. `rustup default stable` 16 | 17 | - `rust-std` components (pre-compiled `core` crate) for the RISC-V target. Run: 18 | 19 | ``` console 20 | rustup target add riscv32imac-unknown-none-elf 21 | ``` 22 | 23 | - RISC-V toolchain ([e.g. from SiFive](https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.1.0-2019.01.0-x86_64-linux-ubuntu14.tar.gz)) 24 | 25 | One of: 26 | 27 | - [dfu-util](http://dfu-util.sourceforge.net/) 28 | - [openocd for GD32VF103](https://github.com/riscv-mcu/riscv-openocd) 29 | - [RV-LINK](https://gitee.com/zoomdy/RV-LINK) 30 | - [stm32flash](https://sourceforge.net/projects/stm32flash/) 31 | 32 | When using `dfu-util`, the RISC-V toolchain is not necessary. Only `rust-objcopy` from [cargo-binutils](https://github.com/rust-embedded/cargo-binutils) is needed. 33 | 34 | Option B - Install prebuilt toolchain (easier): 35 | 36 | For Ubuntu/Debian: 37 | ``` 38 | sudo apt-get install gcc-riscv64-unknown-elf 39 | ``` 40 | 41 | ### Building 42 | 43 | If you have a GD32VF103C**B** chip on your board, edit `.cargo/config.toml` and replace 44 | `memory-c8.x` with `memory-cb.x`. 45 | 46 | To build all the provided examples run 47 | ``` 48 | cargo build --examples --release --all-features 49 | ``` 50 | 51 | ### Using dfu-util for Flashing 52 | 53 | The GD32VF103 contains a [DFU](https://www.usb.org/sites/default/files/DFU_1.1.pdf) 54 | compatible bootloader which allows to program the firmware of your longan-nano without 55 | additional hardware like a JTAG adapter; instead just using an ordenary USB-C cable. 56 | You can use [dfu-util](http://dfu-util.sourceforge.net/) or the vendor supplied tool to 57 | flash the firmware. 58 | 59 | Unfortunately, some versions of this chip shipped with a buggy bootloader and it won't report 60 | the correct parameters to flash it sucessfully. As of May 2020, the most recent version of 61 | [dfu-util](http://dfu-util.sourceforge.net/) from the git repository contains a workaround. 62 | Make sure you use an up-to-date version. 63 | See [this issue](https://github.com/riscv-rust/longan-nano/issues/5) for details. 64 | 65 | Steps to flash an example via DFU: 66 | 67 | 1) Extract the binary 68 | 69 | ```sh 70 | rust-objcopy -O binary target/riscv32imac-unknown-none-elf/release/examples/blinky firmware.bin 71 | ``` 72 | 73 | If using prebuilt toolchain 74 | ```sh 75 | riscv64-unknown-elf-objcopy -O binary target/riscv32imac-unknown-none-elf/release/blinky firmware.bin 76 | ``` 77 | 78 | 79 | 2) Flash using `dfu-util`: 80 | 81 | Keep the BOOT0 button pressed while power-up or while pressing and releaseing the reset button. Then 82 | run 83 | 84 | ```sh 85 | dfu-util -a 0 -s 0x08000000:leave -D firmware.bin 86 | ``` 87 | 88 | Ensure that dfu-util uses a page size of 1024; either because your GD32VF103 has 89 | a bootloader without the the aforementioned bug, or because the output reads 90 | 91 | ``` 92 | [...] 93 | Device returned transfer size 2048 94 | DfuSe interface name: "Internal Flash " 95 | Found GD32VF103, which reports a bad page size and count for its internal memory. 96 | Fixed layout based on part number: page size 1024, count 128. 97 | Downloading to address = 0x08000000, size = 23784 98 | [...] 99 | ``` 100 | 101 | ### Using OpenOCD for Flashing and Debugging 102 | 103 | Start openocd assuming you have Sipeed JTAG adapter: 104 | ```sh 105 | /path/to/openocd -f sipeed-jtag.cfg -f openocd.cfg 106 | ``` 107 | 108 | Run one of the examples: 109 | ```sh 110 | cargo run --example blinky 111 | ``` 112 | or 113 | ```sh 114 | cargo run --release --example ferris --features lcd 115 | ``` 116 | 117 | ### Using RV-LINK for Flashing and Debugging 118 | 119 | [RV-LINK](https://gitee.com/zoomdy/RV-LINK) is a Chinese firmware, similar to 120 | [Black Magic Probe (BMP)](https://github.com/blacksphere/blackmagic/wiki). It 121 | supports the Longan Nano, allowing to use one Longan Nano board as a debug 122 | probe for another one. It can be built & flashed via 123 | [PlatformIO](https://platformio.org/). Check out the latest version to ensure 124 | that the code compiles: 125 | 126 | ``` 127 | > git clone https://gitee.com/zoomdy/RV-LINK 128 | > cd RV-LINK 129 | > git tag 130 | v0.0.1 131 | v0.1 132 | v0.2 # <- seems to be the latest tag, so let's check this out 133 | > git checkout tags/v0.2 134 | ``` 135 | 136 | PlatformIO allows building & flashing of firmware with a single command. To do 137 | so, the board needs to be in bootloader mode (DFU mode). The board boots to 138 | bootloader mode if the bootloader button is pressed while powering it up (e.g. 139 | by plugging it in). However, it is also possible to enter bootloader mode 140 | without un- and replugging the board: press the boot button, press the reset 141 | button, release the reset button and finally release the boot button while the 142 | board is plugged in. 143 | 144 | ``` 145 | > pio run -t upload # put the board in bootloader mode before 146 | ``` 147 | 148 | Once RV-LINK is flashed to your probe, connect the eight debug pins on the 149 | probe with the debug pins on the debug target. Ensure that you connect the pins 150 | according to this table: 151 | 152 | | Probe Pin | Target Pin | 153 | | --- | --- | 154 | | JTDO | JTDO | 155 | | JTDI | JTDI | 156 | | JTCK | JTCK | 157 | | JTMS | JTMS | 158 | | 3V3 | 3V3 | 159 | | GND | GND | 160 | 161 | After you connected the debug probe to your host, a new serial port shows up. 162 | You can connect GDB to this serial port as an `extended-remote`. For 163 | predictable behavior when multiple serial devices are present (and hence 164 | `/dev/ttyACM0` is not necessarily the RV-LINK device), 165 | [udev](https://www.freedesktop.org/wiki/Software/systemd/) offers id symlinks. 166 | However you may also use `/dev/ttyACM0` or even `COMx` if you run Windows. 167 | 168 | ``` 169 | > gdb 170 | (gdb) target extended-remote /dev/serial/by-id/usb-RV-LINK_Longan_Nano_GD32XXX-3.0.0-7z8x9yer-if00 171 | ``` 172 | 173 | To flash the firmware, execute `load` in GDB: 174 | 175 | ``` 176 | > gdb target/remote/debug/demo 177 | (gdb) target extended-remote /dev/ttyACM0 178 | (gdb) monitor reset halt 179 | (gdb) load 180 | (gdb) monitor reset 181 | ``` 182 | 183 | To improve your workflow, you can put the aforementioned GDB commands in 184 | a `debug.gdb` file and add these lines to `.cargo/config.toml`: 185 | 186 | ``` 187 | [target.riscv32imac-unknown-none-elf] 188 | runner = 'gdb -command=debug.gdb' 189 | ``` 190 | 191 | This way `cargo run --target riscv32imac-unknown-none-elf` will automatically 192 | launch GDB, flash your firmware on the target and provide you with a full debug 193 | environment. 194 | 195 | You can infer the current status of the board by observing the blinking pattern 196 | of the green LED: 197 | 198 | | Blink Behavior | Probe Status | 199 | | --- | --- | 200 | | Short pulse, on for 100ms, off for 900ms | GDB is not connected | 201 | | Slow flashing, on for 500ms, off for 500ms | GDB is connected and the debugged MCU is in halt state | 202 | | Fast blinking, on for 100ms, off for 100ms | GDB is connected, and the debugged MCU is running | 203 | | Long pulse, on for 900ms, off for 100ms | RV-LINK has failed. Retry after resetting RV-LINK | 204 | 205 | 206 | ## Using serial for Flashing 207 | 208 | Using a TTL-to-USB adapter, one can flash the firmware to the device over a serial connection: 209 | 210 | ``` 211 | $ stm32flash -g 0x08000000 -b 115200 -w firmware.bin /dev/ttyUSB0 212 | ``` 213 | 214 | which sets the base address (`-g 0x08000000`) for the firmware to the start of main flash. 215 | 216 | The baud rate (`-b 115200`), firmware image (`-w firmware.bin`), and serial port (`/dev/ttyUSB0`) are set next. 217 | 218 | The serial port may vary machine to machine, so select the port that makes sense. 219 | 220 | Make sure the device is writeable by the current user, or run `stm32flash` as root. 221 | 222 | ## License 223 | 224 | Copyright 2019-2020 [RISC-V team][team] 225 | 226 | Permission to use, copy, modify, and/or distribute this software for any purpose 227 | with or without fee is hereby granted, provided that the above copyright notice 228 | and this permission notice appear in all copies. 229 | 230 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 231 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 232 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 233 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 234 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 235 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 236 | THIS SOFTWARE. 237 | 238 | ## Code of Conduct 239 | 240 | Contribution to this crate is organized under the terms of the [Rust Code of 241 | Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises 242 | to intervene to uphold that code of conduct. 243 | 244 | [CoC]: CODE_OF_CONDUCT.md 245 | [team]: https://github.com/rust-embedded/wg#the-risc-v-team 246 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs}; 2 | use std::path::PathBuf; 3 | 4 | fn main() { 5 | // Put the memory definitions somewhere the linker can find it 6 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 7 | println!("cargo:rustc-link-search={}", out_dir.display()); 8 | 9 | fs::copy("memory-c8.x", out_dir.join("memory-c8.x")).unwrap(); 10 | println!("cargo:rerun-if-changed=memory-c8.x"); 11 | 12 | fs::copy("memory-cb.x", out_dir.join("memory-cb.x")).unwrap(); 13 | println!("cargo:rerun-if-changed=memory-cb.x"); 14 | } 15 | -------------------------------------------------------------------------------- /examples/blinky.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use panic_halt as _; 5 | 6 | use riscv_rt::entry; 7 | use longan_nano::hal::{pac, prelude::*}; 8 | use longan_nano::led::{Led, rgb}; 9 | use longan_nano::hal::delay::McycleDelay; 10 | 11 | #[entry] 12 | fn main() -> ! { 13 | let dp = pac::Peripherals::take().unwrap(); 14 | let mut rcu = dp.RCU.configure().freeze(); 15 | 16 | let gpioa = dp.GPIOA.split(&mut rcu); 17 | let gpioc = dp.GPIOC.split(&mut rcu); 18 | 19 | let (mut red, mut green, mut blue) = rgb(gpioc.pc13, gpioa.pa1, gpioa.pa2); 20 | let leds: [&mut dyn Led; 3] = [&mut red, &mut green, &mut blue]; 21 | 22 | let mut delay = McycleDelay::new(&rcu.clocks); 23 | 24 | let mut i = 0; 25 | loop { 26 | let inext = (i + 1) % leds.len(); 27 | leds[i].off(); 28 | leds[inext].on(); 29 | delay.delay_ms(500); 30 | 31 | i = inext; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/display.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use panic_halt as _; 5 | 6 | use embedded_graphics::mono_font::{ 7 | ascii::FONT_5X8, 8 | MonoTextStyleBuilder, 9 | }; 10 | use embedded_graphics::pixelcolor::Rgb565; 11 | use embedded_graphics::prelude::*; 12 | use embedded_graphics::primitives::{Rectangle, PrimitiveStyle}; 13 | use embedded_graphics::text::Text; 14 | use longan_nano::hal::{pac, prelude::*}; 15 | use longan_nano::{lcd, lcd_pins}; 16 | use riscv_rt::entry; 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | let dp = pac::Peripherals::take().unwrap(); 21 | 22 | // Configure clocks 23 | let mut rcu = dp 24 | .RCU 25 | .configure() 26 | .ext_hf_clock(8.mhz()) 27 | .sysclk(108.mhz()) 28 | .freeze(); 29 | let mut afio = dp.AFIO.constrain(&mut rcu); 30 | 31 | let gpioa = dp.GPIOA.split(&mut rcu); 32 | let gpiob = dp.GPIOB.split(&mut rcu); 33 | 34 | let lcd_pins = lcd_pins!(gpioa, gpiob); 35 | let mut lcd = lcd::configure(dp.SPI0, lcd_pins, &mut afio, &mut rcu); 36 | let (width, height) = (lcd.size().width as i32, lcd.size().height as i32); 37 | 38 | // Clear screen 39 | Rectangle::new(Point::new(0, 0), Size::new(width as u32, height as u32)) 40 | .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) 41 | .draw(&mut lcd) 42 | .unwrap(); 43 | 44 | let style = MonoTextStyleBuilder::new() 45 | .font(&FONT_5X8) 46 | .text_color(Rgb565::BLACK) 47 | .background_color(Rgb565::GREEN) 48 | .build(); 49 | 50 | // Create a text at position (20, 30) and draw it using style defined above 51 | Text::new(" Hello Rust! ", Point::new(40, 35), style) 52 | .draw(&mut lcd) 53 | .unwrap(); 54 | 55 | loop {} 56 | } 57 | -------------------------------------------------------------------------------- /examples/ferris.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/riscv-rust/longan-nano/571a7980fab6f878832ab667559bb2446d6d81d0/examples/ferris.raw -------------------------------------------------------------------------------- /examples/ferris.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use panic_halt as _; 5 | 6 | use embedded_graphics::image::{Image, ImageRaw}; 7 | use embedded_graphics::pixelcolor::raw::LittleEndian; 8 | use embedded_graphics::pixelcolor::Rgb565; 9 | use embedded_graphics::prelude::*; 10 | use embedded_graphics::primitives::{Rectangle, PrimitiveStyle}; 11 | use longan_nano::hal::{pac, prelude::*}; 12 | use longan_nano::{lcd, lcd_pins}; 13 | use riscv_rt::entry; 14 | 15 | const FERRIS: &[u8] = include_bytes!("ferris.raw"); 16 | 17 | #[entry] 18 | fn main() -> ! { 19 | let dp = pac::Peripherals::take().unwrap(); 20 | 21 | // Configure clocks 22 | let mut rcu = dp 23 | .RCU 24 | .configure() 25 | .ext_hf_clock(8.mhz()) 26 | .sysclk(108.mhz()) 27 | .freeze(); 28 | let mut afio = dp.AFIO.constrain(&mut rcu); 29 | 30 | let gpioa = dp.GPIOA.split(&mut rcu); 31 | let gpiob = dp.GPIOB.split(&mut rcu); 32 | 33 | let lcd_pins = lcd_pins!(gpioa, gpiob); 34 | let mut lcd = lcd::configure(dp.SPI0, lcd_pins, &mut afio, &mut rcu); 35 | let (width, height) = (lcd.size().width as i32, lcd.size().height as i32); 36 | 37 | // Clear screen 38 | Rectangle::new(Point::new(0, 0), Size::new(width as u32, height as u32)) 39 | .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) 40 | .draw(&mut lcd) 41 | .unwrap(); 42 | 43 | // Load Image Data 44 | let raw_image: ImageRaw = ImageRaw::new(&FERRIS, 86); 45 | Image::new(&raw_image, Point::new(width / 2 - 43, height / 2 - 32)) 46 | .draw(&mut lcd) 47 | .unwrap(); 48 | 49 | loop {} 50 | } 51 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use panic_halt as _; 5 | 6 | use riscv_rt::entry; 7 | use longan_nano::hal::{pac, prelude::*}; 8 | use longan_nano::sprintln; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | let dp = pac::Peripherals::take().unwrap(); 13 | 14 | // Configure clocks 15 | let mut rcu = dp.RCU.configure() 16 | .ext_hf_clock(8.mhz()) 17 | .sysclk(108.mhz()) 18 | .freeze(); 19 | 20 | let mut afio = dp.AFIO.constrain(&mut rcu); 21 | 22 | let gpioa = dp.GPIOA.split(&mut rcu); 23 | longan_nano::stdout::configure(dp.USART0, gpioa.pa9, gpioa.pa10, 115_200.bps(), &mut afio, &mut rcu); 24 | 25 | sprintln!("Hello, world"); 26 | 27 | loop { } 28 | } 29 | -------------------------------------------------------------------------------- /examples/interrupt.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use gd32vf103xx_hal::pac::Interrupt; 5 | use panic_halt as _; 6 | use longan_nano::hal::{pac, prelude::*, pac::*, eclic::*}; 7 | use gd32vf103xx_hal::timer; 8 | use gd32vf103xx_hal::timer::Timer; 9 | use longan_nano::led::{rgb, Led, RED}; 10 | use riscv_rt::entry; 11 | 12 | static mut R_LED: Option = None; 13 | static mut G_TIMER1: Option> = None; 14 | 15 | #[entry] 16 | fn main() -> ! { 17 | let dp = pac::Peripherals::take().unwrap(); 18 | let mut rcu = dp 19 | .RCU 20 | .configure() 21 | .ext_hf_clock(8.mhz()) 22 | .sysclk(108.mhz()) 23 | .freeze(); 24 | 25 | let gpioa = dp.GPIOA.split(&mut rcu); 26 | let gpioc = dp.GPIOC.split(&mut rcu); 27 | 28 | let (mut red, mut green, mut blue) = rgb(gpioc.pc13, gpioa.pa1, gpioa.pa2); 29 | red.off(); 30 | green.off(); 31 | blue.off(); 32 | unsafe { R_LED = Some(red); }; 33 | 34 | ECLIC::reset(); 35 | ECLIC::set_threshold_level(Level::L0); 36 | ECLIC::set_level_priority_bits(LevelPriorityBits::L3P1); 37 | 38 | // timer 39 | let mut timer = Timer::timer1(dp.TIMER1, 1.hz(), &mut rcu); 40 | timer.listen(timer::Event::Update); 41 | unsafe {G_TIMER1 = Some(timer)}; 42 | 43 | ECLIC::setup( 44 | Interrupt::TIMER1, 45 | TriggerType::Level, 46 | Level::L1, 47 | Priority::P1, 48 | ); 49 | unsafe { 50 | ECLIC::unmask(Interrupt::TIMER1); 51 | riscv::interrupt::enable(); 52 | }; 53 | 54 | loop { } 55 | } 56 | 57 | #[allow(non_snake_case)] 58 | #[no_mangle] 59 | fn TIMER1() { 60 | unsafe { 61 | if let Some(timer1) = G_TIMER1.as_mut() { 62 | timer1.clear_update_interrupt_flag(); 63 | } 64 | if let Some(led) = R_LED.as_mut() { 65 | if led.is_on() { 66 | led.off(); 67 | } else { 68 | led.on(); 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /examples/led_shell.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use panic_halt as _; 5 | 6 | use core::fmt::Write; 7 | use gd32vf103xx_hal::{ 8 | pac::USART0, 9 | serial::{self, Config, Parity, Rx, StopBits, Tx}, 10 | }; 11 | use longan_nano::{ 12 | hal::{pac, prelude::*}, 13 | led::{rgb, Led, BLUE, GREEN, RED}, 14 | }; 15 | use riscv_rt::entry; 16 | use ushell::{autocomplete::*, history::*, *}; 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | let dp = pac::Peripherals::take().unwrap(); 21 | 22 | // Configure clocks 23 | let mut rcu = dp 24 | .RCU 25 | .configure() 26 | .ext_hf_clock(8.mhz()) 27 | .sysclk(108.mhz()) 28 | .freeze(); 29 | 30 | let mut afio = dp.AFIO.constrain(&mut rcu); 31 | 32 | let gpioa = dp.GPIOA.split(&mut rcu); 33 | let gpioc = dp.GPIOC.split(&mut rcu); 34 | 35 | let (mut red, mut green, mut blue) = rgb(gpioc.pc13, gpioa.pa1, gpioa.pa2); 36 | red.off(); 37 | green.off(); 38 | blue.off(); 39 | 40 | let tx = gpioa.pa9.into_alternate_push_pull(); 41 | let rx = gpioa.pa10.into_floating_input(); 42 | 43 | let config = Config { 44 | baudrate: 115_200.bps(), 45 | parity: Parity::ParityNone, 46 | stopbits: StopBits::STOP1, 47 | }; 48 | let uart = serial::Serial::new(dp.USART0, (tx, rx), config, &mut afio, &mut rcu); 49 | let (tx, rx) = uart.split(); 50 | let serial = ushell::Serial::from_parts(tx, rx); 51 | 52 | let autocomplete = StaticAutocomplete(["clear", "help", "status", "off ", "on "]); 53 | let mut shell = UShell::new(serial, autocomplete, LRUHistory::default()); 54 | let mut env = Env { red, green, blue }; 55 | 56 | loop { 57 | shell.spin(&mut env).ok(); 58 | } 59 | } 60 | 61 | const CMD_LEN: usize = 16; 62 | const HISTORY_SIZE: usize = 4; 63 | const COMMANDS: usize = 5; 64 | 65 | type Serial = ushell::Serial, Rx>; 66 | type Autocomplete = StaticAutocomplete<{ COMMANDS }>; 67 | type History = LRUHistory<{ CMD_LEN }, { HISTORY_SIZE }>; 68 | type Shell = UShell; 69 | 70 | struct Env { 71 | red: RED, 72 | green: GREEN, 73 | blue: BLUE, 74 | } 75 | 76 | type EnvResult = SpinResult; 77 | 78 | impl Env { 79 | fn on_cmd(&mut self, shell: &mut Shell, args: &str) -> EnvResult { 80 | match args { 81 | "r" | "red" => self.red.on(), 82 | "g" | "green" => self.green.on(), 83 | "b" | "blue" => self.blue.on(), 84 | "a" | "all" => { 85 | self.red.on(); 86 | self.green.on(); 87 | self.blue.on(); 88 | } 89 | _ => { 90 | write!(shell, "{0:}unsupported color channel", CR).ok(); 91 | } 92 | } 93 | shell.write_str(CR)?; 94 | Ok(()) 95 | } 96 | 97 | fn off_cmd(&mut self, shell: &mut Shell, args: &str) -> EnvResult { 98 | match args { 99 | "r" | "red" => self.red.off(), 100 | "g" | "green" => self.green.off(), 101 | "b" | "blue" => self.blue.off(), 102 | "a" | "all" => { 103 | self.red.off(); 104 | self.green.off(); 105 | self.blue.off(); 106 | } 107 | _ => { 108 | write!(shell, "{0:}unsupported color channel", CR).ok(); 109 | } 110 | } 111 | shell.write_str(CR)?; 112 | Ok(()) 113 | } 114 | 115 | fn status_cmd(&mut self, shell: &mut Shell, _args: &str) -> EnvResult { 116 | let red = if self.red.is_on() { "On" } else { "Off" }; 117 | let green = if self.green.is_on() { "On" } else { "Off" }; 118 | let blue = if self.blue.is_on() { "On" } else { "Off" }; 119 | write!( 120 | shell, 121 | "{0:}Red: {1:}{0:}Green: {2:}{0:}Blue: {3:}{0:}", 122 | CR, red, green, blue, 123 | )?; 124 | 125 | Ok(()) 126 | } 127 | } 128 | 129 | impl Environment for Env { 130 | fn command(&mut self, shell: &mut Shell, cmd: &str, args: &str) -> EnvResult { 131 | match cmd { 132 | "clear" => shell.clear()?, 133 | "help" => shell.write_str(HELP)?, 134 | "status" => self.status_cmd(shell, args)?, 135 | "on" => self.on_cmd(shell, args)?, 136 | "off" => self.off_cmd(shell, args)?, 137 | "" => shell.write_str(CR)?, 138 | _ => write!(shell, "{0:}unsupported command: \"{1:}\"{0:}", CR, cmd)?, 139 | } 140 | shell.write_str(SHELL_PROMPT)?; 141 | Ok(()) 142 | } 143 | 144 | fn control(&mut self, _shell: &mut Shell, _code: u8) -> EnvResult { 145 | Ok(()) 146 | } 147 | } 148 | 149 | const SHELL_PROMPT: &str = "#> "; 150 | const CR: &str = "\r\n"; 151 | const HELP: &str = "\r\n\ 152 | \x1b[31mL\x1b[32mE\x1b[34mD\x1b[33m Shell\x1b[0m\r\n\r\n\ 153 | USAGE:\r\n\ 154 | \x20 command [arg]\r\n\r\n\ 155 | COMMANDS:\r\n\ 156 | \x20 on Switch led channel on [r,g,b,a]\r\n\ 157 | \x20 off Switch led channel off [r,g,b,a]\r\n\ 158 | \x20 status Get leds status\r\n\ 159 | \x20 clear Clear screen\r\n\ 160 | \x20 help Print this message\r\n 161 | "; 162 | -------------------------------------------------------------------------------- /examples/scan.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate panic_halt; 5 | 6 | use riscv_rt::entry; 7 | use longan_nano::hal::{pac, prelude::*}; 8 | use longan_nano::{sprint, sprintln}; 9 | 10 | #[inline(never)] 11 | pub unsafe extern "C" fn __read32(_default: usize, addr: usize) -> u32 { 12 | let ptr = addr as *const u32; 13 | ptr.read_volatile() 14 | } 15 | 16 | #[export_name = "trap_handler"] 17 | fn trap_handler() { 18 | use riscv::register::{mcause, mepc, mtval, mcause::{Trap, Exception}}; 19 | let ld_insn_addr = __read32 as *const () as usize; 20 | 21 | let mcause = mcause::read(); 22 | let mepc = mepc::read(); 23 | 24 | if mepc == ld_insn_addr && mcause.cause() == Trap::Exception(Exception::LoadFault) { 25 | mepc::write(mepc + 2); 26 | return; 27 | } 28 | 29 | sprintln!("trap!"); 30 | sprintln!("mcause={:08x} mepc={:08x} mtval={:08x} addr={:08x}", mcause.bits(), mepc, mtval::read(), ld_insn_addr); 31 | 32 | loop {} 33 | } 34 | 35 | fn read32_safe(addr: usize) -> Option { 36 | unsafe { 37 | let v = __read32(0xdeadbeef, addr); 38 | if v != 0xdeadbeef { 39 | return Some(v); 40 | } 41 | let v = __read32(0x12345678, addr); 42 | if v != 0x12345678 { 43 | Some(v) 44 | } else { 45 | None 46 | } 47 | } 48 | } 49 | 50 | fn is_readable(addr: usize) -> bool { 51 | read32_safe(addr).is_some() 52 | } 53 | 54 | #[derive(Copy, Clone)] 55 | enum ScanState { 56 | Invalid, 57 | Valid { 58 | interval_start: usize, 59 | }, 60 | } 61 | 62 | #[entry] 63 | fn main() -> ! { 64 | let p = pac::Peripherals::take().unwrap(); 65 | 66 | // Configure clocks 67 | let mut rcu = p.RCU.configure().freeze(); 68 | let mut afio = p.AFIO.constrain(&mut rcu); 69 | 70 | // Configure UART for stdout 71 | let gpioa = p.GPIOA.split(&mut rcu); 72 | longan_nano::stdout::configure(p.USART0, gpioa.pa9, gpioa.pa10, 115_200.bps(), &mut afio, &mut rcu); 73 | 74 | sprintln!("scan started"); 75 | 76 | 77 | let mut addr: usize = 0; 78 | let mut state = ScanState::Invalid; 79 | loop { 80 | if (addr & 0xfffff) == 0 { 81 | sprint!("\r==> {:08x}", addr); 82 | } 83 | 84 | let readable = is_readable(addr); 85 | state = match (state, readable) { 86 | (ScanState::Invalid, true) => ScanState::Valid { 87 | interval_start: addr 88 | }, 89 | (ScanState::Valid { interval_start }, false) => { 90 | sprintln!("\r{:08x}..{:08x}", interval_start, addr - 1); 91 | ScanState::Invalid 92 | }, 93 | (ScanState::Valid {..}, true) | (ScanState::Invalid, false) => state, 94 | }; 95 | 96 | if let Some(v) = addr.checked_add(0x100) { 97 | addr = v; 98 | } else { 99 | sprint!("\r==> {:08x}", addr); 100 | break; 101 | } 102 | } 103 | if let ScanState::Valid { interval_start } = state { 104 | sprintln!("\n{:08x}..{:08x}..", interval_start, addr - 1); 105 | } 106 | 107 | sprintln!("\nscan finished"); 108 | 109 | loop {} 110 | } 111 | -------------------------------------------------------------------------------- /examples/sdcard_test.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use embedded_sdmmc::{Directory, Volume, VolumeIdx}; 5 | 6 | use longan_nano::sdcard::SdCard; 7 | use panic_halt as _; 8 | 9 | use riscv_rt::entry; 10 | use longan_nano::hal::{pac, prelude::*}; 11 | use longan_nano::{sdcard, sdcard_pins, sprint, sprintln}; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | let dp = pac::Peripherals::take().unwrap(); 16 | 17 | // Configure clocks 18 | let mut rcu = dp.RCU.configure() 19 | .ext_hf_clock(8.mhz()) 20 | .sysclk(108.mhz()) 21 | .freeze(); 22 | 23 | let mut afio = dp.AFIO.constrain(&mut rcu); 24 | 25 | let gpioa = dp.GPIOA.split(&mut rcu); 26 | longan_nano::stdout::configure(dp.USART0, gpioa.pa9, gpioa.pa10, 115_200.bps(), &mut afio, &mut rcu); 27 | 28 | let gpiob = dp.GPIOB.split(&mut rcu); 29 | let sdcard_pins = sdcard_pins!(gpiob); 30 | let mut sdcard = sdcard::configure(dp.SPI1, sdcard_pins, sdcard::SdCardFreq::Safe, &mut rcu); 31 | 32 | sprint!("Initializing SD card ... "); 33 | if let Err(_) = sdcard.device().init() { 34 | sprintln!("Failed to initialize sdcard."); 35 | } else { 36 | sprintln!("OK"); 37 | 38 | let size = sdcard.device().card_size_bytes().unwrap(); 39 | sprintln!("SD Card Capacity: {} MB", size / 1000 / 1000); 40 | 41 | // open the first partition 42 | sprintln!("Partition 0:"); 43 | let mut volume = sdcard.get_volume(VolumeIdx(0)).unwrap(); 44 | 45 | // list files in root dir 46 | let root_dir = sdcard.open_root_dir(&volume).unwrap(); 47 | sdcard.iterate_dir(&volume, &root_dir, | entry | { 48 | sprintln!("{: >5}B {}", entry.size, entry.name); 49 | }).unwrap(); 50 | 51 | // if a file with the name SDTST.TXT is present, do a read/write test 52 | if let Ok(_) = sdcard.find_directory_entry(&volume, &root_dir, "SDTST.TXT") { 53 | read_write_test(&mut sdcard, &mut volume, &root_dir); 54 | } 55 | } 56 | sprintln!("Done"); 57 | 58 | loop { } 59 | } 60 | 61 | fn read_write_test(sdcard: &mut SdCard, volume: &mut Volume, dir: &Directory) { 62 | sprint!("Write test: "); 63 | let mut file = sdcard.open_file_in_dir(volume, dir, "SDTST.CSV", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate).unwrap(); 64 | let data = "1,2,3,4,20"; 65 | if let Ok(size_written) = sdcard.write(volume, &mut file, data.as_bytes()) { 66 | sprintln!("Success (Wrote {} bytes)", size_written); 67 | } else { 68 | sprintln!("Failed"); 69 | } 70 | sdcard.close_file(volume, file).unwrap(); 71 | 72 | sprint!("Read test: "); 73 | let mut file = sdcard.open_file_in_dir(volume, dir, "SDTST.CSV", embedded_sdmmc::Mode::ReadOnly).unwrap(); 74 | let mut buffer: [u8; 32] = [0; 32]; 75 | if let Ok(size_read) = sdcard.read(volume, &mut file, &mut buffer) { 76 | if size_read == data.len() && buffer[0..size_read].eq(data.as_bytes()) { 77 | sprintln!("Success (Read same {} bytes)", size_read); 78 | } else { 79 | sprintln!("Content differs."); 80 | } 81 | } else { 82 | sprintln!("Failed"); 83 | } 84 | sdcard.close_file(volume, file).unwrap(); 85 | } 86 | -------------------------------------------------------------------------------- /memory-c8.x: -------------------------------------------------------------------------------- 1 | /* GD32VF103C8 */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 64k 5 | RAM : ORIGIN = 0x20000000, LENGTH = 20k 6 | } 7 | 8 | REGION_ALIAS("REGION_TEXT", FLASH); 9 | REGION_ALIAS("REGION_RODATA", FLASH); 10 | REGION_ALIAS("REGION_DATA", RAM); 11 | REGION_ALIAS("REGION_BSS", RAM); 12 | REGION_ALIAS("REGION_HEAP", RAM); 13 | REGION_ALIAS("REGION_STACK", RAM); 14 | -------------------------------------------------------------------------------- /memory-cb.x: -------------------------------------------------------------------------------- 1 | /* GD32VF103CB */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 128k 5 | RAM : ORIGIN = 0x20000000, LENGTH = 32k 6 | } 7 | 8 | REGION_ALIAS("REGION_TEXT", FLASH); 9 | REGION_ALIAS("REGION_RODATA", FLASH); 10 | REGION_ALIAS("REGION_DATA", RAM); 11 | REGION_ALIAS("REGION_BSS", RAM); 12 | REGION_ALIAS("REGION_HEAP", RAM); 13 | REGION_ALIAS("REGION_STACK", RAM); 14 | -------------------------------------------------------------------------------- /openocd.cfg: -------------------------------------------------------------------------------- 1 | # autoexit true 2 | 3 | set _CHIPNAME riscv 4 | jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1e200a6d 5 | 6 | set _TARGETNAME $_CHIPNAME.cpu 7 | target create $_TARGETNAME riscv -chain-position $_TARGETNAME 8 | $_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size 20480 -work-area-backup 0 9 | 10 | 11 | # Work-area is a space in RAM used for flash programming 12 | if { [info exists WORKAREASIZE] } { 13 | set _WORKAREASIZE $WORKAREASIZE 14 | } else { 15 | set _WORKAREASIZE 0x5000 16 | } 17 | 18 | # Allow overriding the Flash bank size 19 | if { [info exists FLASH_SIZE] } { 20 | set _FLASH_SIZE $FLASH_SIZE 21 | } else { 22 | # autodetect size 23 | set _FLASH_SIZE 0 24 | } 25 | 26 | # flash size will be probed 27 | set _FLASHNAME $_CHIPNAME.flash 28 | 29 | flash bank $_FLASHNAME gd32vf103 0x08000000 0 0 0 $_TARGETNAME 30 | riscv set_reset_timeout_sec 1 31 | init 32 | 33 | halt 34 | -------------------------------------------------------------------------------- /openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # print demangled symbols 4 | set print asm-demangle on 5 | 6 | set confirm off 7 | 8 | # set backtrace limit to not have infinite backtrace loops 9 | set backtrace limit 32 10 | 11 | load 12 | continue 13 | -------------------------------------------------------------------------------- /sipeed-jtag.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # Sipeed USB-JTAG Debugger 3 | # 4 | # https://www.seeedstudio.com/Sipeed-USB-JTAG-TTL-RISC-V-Debugger-p-2910.html 5 | # 6 | 7 | adapter driver ftdi 8 | ftdi_vid_pid 0x0403 0x6010 9 | ftdi_channel 0 10 | 11 | ftdi_layout_init 0x00e8 0x00eb 12 | 13 | transport select jtag 14 | adapter speed 8000 15 | -------------------------------------------------------------------------------- /src/lcd.rs: -------------------------------------------------------------------------------- 1 | //! On-board LCD 2 | 3 | use embedded_hal::digital::v2::OutputPin; 4 | use gd32vf103xx_hal::afio::Afio; 5 | use gd32vf103xx_hal::delay::McycleDelay; 6 | use gd32vf103xx_hal::gpio::gpioa::{PA5, PA6, PA7}; 7 | use gd32vf103xx_hal::gpio::gpiob::{PB0, PB1, PB2}; 8 | use gd32vf103xx_hal::gpio::{Alternate, Floating, Input, Output, PushPull}; 9 | use gd32vf103xx_hal::pac::SPI0; 10 | use gd32vf103xx_hal::rcu::Rcu; 11 | use gd32vf103xx_hal::spi::{Spi, MODE_0}; 12 | use gd32vf103xx_hal::time::U32Ext; 13 | use st7735_lcd::{Orientation, ST7735}; 14 | 15 | /// Sets up all the needed GPIO pins for the LCD 16 | /// 17 | /// ``` 18 | /// let gpioa = dp.GPIOA.split(&mut rcu); 19 | /// let gpiob = dp.GPIOB.split(&mut rcu); 20 | /// let lcd_pins = lcd_pins!(gpioa, gpiob); 21 | /// ``` 22 | #[macro_export] 23 | macro_rules! lcd_pins { 24 | ($gpioa:ident, $gpiob:ident) => {{ 25 | $crate::lcd::LcdPins { 26 | miso: $gpioa.pa6.into_floating_input(), 27 | mosi: $gpioa.pa7.into_alternate_push_pull(), 28 | sck: $gpioa.pa5.into_alternate_push_pull(), 29 | cs: $gpiob.pb2.into_push_pull_output(), 30 | dc: $gpiob.pb0.into_push_pull_output(), 31 | rst: $gpiob.pb1.into_push_pull_output(), 32 | } 33 | }}; 34 | } 35 | 36 | type MisoPin = PA6>; 37 | type MosiPin = PA7>; 38 | type SckPin = PA5>; 39 | type CsPin = PB2>; 40 | type DcPin = PB0>; 41 | type RstPin = PB1>; 42 | type SpiType = Spi; 43 | pub type Lcd = ST7735; 44 | 45 | /// Pins consumed by LCD driver 46 | pub struct LcdPins { 47 | pub miso: MisoPin, 48 | pub mosi: MosiPin, 49 | pub sck: SckPin, 50 | pub cs: CsPin, 51 | pub dc: DcPin, 52 | pub rst: RstPin, 53 | } 54 | 55 | /// Constructs LCD driver from the required components 56 | pub fn configure(spi: SPI0, pins: LcdPins, afio: &mut Afio, rcu: &mut Rcu) -> Lcd { 57 | let (width, height) = (160, 80); 58 | let spi0 = Spi::spi0( 59 | spi, 60 | (pins.sck, pins.miso, pins.mosi), 61 | afio, 62 | MODE_0, 63 | 16.mhz(), 64 | rcu, 65 | ); 66 | 67 | let mut cs = pins.cs; 68 | cs.set_low().unwrap(); 69 | 70 | let mut lcd = ST7735::new( 71 | spi0, 72 | pins.dc, 73 | pins.rst, 74 | false, 75 | true, 76 | width, 77 | height); 78 | let mut delay = McycleDelay::new(&rcu.clocks); 79 | lcd.init(&mut delay).unwrap(); 80 | lcd.set_orientation(&Orientation::Landscape).unwrap(); 81 | lcd.set_offset(1, 26); 82 | 83 | lcd 84 | } 85 | -------------------------------------------------------------------------------- /src/led.rs: -------------------------------------------------------------------------------- 1 | //! On-board RGB leds 2 | //! 3 | //! - Red = PC13 4 | //! - Green = PA1 5 | //! - Blue = PA2 6 | use embedded_hal::digital::v2::{OutputPin, StatefulOutputPin}; 7 | use gd32vf103xx_hal::gpio::gpioc::PC13; 8 | use gd32vf103xx_hal::gpio::gpioa::{PA1, PA2}; 9 | use gd32vf103xx_hal::gpio::{Output, PushPull, Active}; 10 | 11 | /// Red LED 12 | pub struct RED { 13 | port: PC13> 14 | } 15 | 16 | impl RED { 17 | pub fn new(port: PC13) -> Self { 18 | Self { 19 | port: port.into_push_pull_output() 20 | } 21 | } 22 | } 23 | 24 | /// Green LED 25 | pub struct GREEN { 26 | port: PA1> 27 | } 28 | 29 | impl GREEN { 30 | pub fn new(port: PA1) -> Self { 31 | Self { 32 | port: port.into_push_pull_output() 33 | } 34 | } 35 | } 36 | 37 | /// Blue LED 38 | pub struct BLUE { 39 | port: PA2> 40 | } 41 | 42 | impl BLUE { 43 | pub fn new(port: PA2) -> Self { 44 | Self { 45 | port: port.into_push_pull_output() 46 | } 47 | } 48 | } 49 | 50 | /// Returns RED, GREEN and BLUE LEDs. 51 | pub fn rgb( 52 | red: PC13, green: PA1, blue: PA2 53 | ) -> (RED, GREEN, BLUE) 54 | where X: Active, Y: Active, Z: Active 55 | { 56 | let red: RED = RED::new(red); 57 | let green: GREEN = GREEN::new(green); 58 | let blue: BLUE = BLUE::new(blue); 59 | (red, green, blue) 60 | } 61 | 62 | /// Generic LED 63 | pub trait Led { 64 | /// Turns the LED off 65 | fn off(&mut self); 66 | 67 | /// Turns the LED on 68 | fn on(&mut self); 69 | 70 | /// Checks the LED status 71 | fn is_on(&mut self) -> bool; 72 | } 73 | 74 | impl Led for RED { 75 | fn off(&mut self) { 76 | self.port.set_high().unwrap(); 77 | } 78 | 79 | fn on(&mut self) { 80 | self.port.set_low().unwrap(); 81 | } 82 | 83 | fn is_on(&mut self) -> bool { 84 | self.port.is_set_low().unwrap() 85 | } 86 | } 87 | 88 | impl Led for GREEN { 89 | fn off(&mut self) { 90 | self.port.set_high().unwrap(); 91 | } 92 | 93 | fn on(&mut self) { 94 | self.port.set_low().unwrap(); 95 | } 96 | 97 | fn is_on(&mut self) -> bool { 98 | self.port.is_set_low().unwrap() 99 | } 100 | } 101 | 102 | impl Led for BLUE { 103 | fn off(&mut self) { 104 | self.port.set_high().unwrap(); 105 | } 106 | 107 | fn on(&mut self) { 108 | self.port.set_low().unwrap(); 109 | } 110 | 111 | fn is_on(&mut self) -> bool { 112 | self.port.is_set_low().unwrap() 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Board support crate for the Longan Nano board 2 | 3 | #![no_std] 4 | #![cfg_attr(docsrs, feature(doc_cfg))] 5 | 6 | pub use gd32vf103xx_hal as hal; 7 | 8 | #[cfg(feature = "lcd")] 9 | #[cfg_attr(docsrs, doc(cfg(feature = "lcd")))] 10 | pub mod lcd; 11 | pub mod led; 12 | pub mod stdout; 13 | #[cfg(feature = "sdcard")] 14 | #[cfg_attr(docsrs, doc(cfg(feature = "sdcard")))] 15 | pub mod sdcard; 16 | -------------------------------------------------------------------------------- /src/sdcard.rs: -------------------------------------------------------------------------------- 1 | //! On-board SD Card Slot 2 | 3 | use embedded_hal::digital::v2::OutputPin; 4 | use gd32vf103xx_hal::gpio::gpiob::{PB12, PB13, PB14, PB15}; 5 | use gd32vf103xx_hal::gpio::{Alternate, Floating, Input, Output, PushPull}; 6 | use gd32vf103xx_hal::pac::{SPI1}; 7 | use gd32vf103xx_hal::rcu::Rcu; 8 | use gd32vf103xx_hal::spi::{Spi, MODE_0}; 9 | use gd32vf103xx_hal::time::{Hertz, U32Ext}; 10 | use embedded_sdmmc::{Controller, SdMmcSpi, TimeSource, Timestamp}; 11 | 12 | /// Sets up all the needed GPIO pins for the sdcard 13 | /// 14 | /// ``` 15 | /// let gpiob = dp.GPIOB.split(&mut rcu); 16 | /// let sdcard_pins = sdcard_pins!(gpiob); 17 | /// ``` 18 | #[macro_export] 19 | macro_rules! sdcard_pins { 20 | ($gpiob:ident) => {{ 21 | $crate::sdcard::SdCardPins { 22 | miso: $gpiob.pb14.into_floating_input(), 23 | mosi: $gpiob.pb15.into_alternate_push_pull(), 24 | sck: $gpiob.pb13.into_alternate_push_pull(), 25 | cs: $gpiob.pb12.into_push_pull_output(), 26 | } 27 | }}; 28 | } 29 | 30 | type SckPin = PB13>; 31 | type MisoPin = PB14>; 32 | type MosiPin = PB15>; 33 | type CsPin = PB12>; 34 | type SPI1Pins = (SckPin, MisoPin, MosiPin); 35 | 36 | type Spi1 = Spi; 37 | 38 | /// A type based on embedded_sdmmc::SdMmcSpi that is used by SdCard. 39 | pub type SdCardSpi = SdMmcSpi; 40 | 41 | /// A type based on embedded_sdmmc::Controller. 42 | pub type SdCard = Controller; 43 | 44 | pub struct SdCardPins { 45 | pub miso: MisoPin, 46 | pub mosi: MosiPin, 47 | pub sck: SckPin, 48 | pub cs: CsPin, 49 | } 50 | 51 | pub enum SdCardFreq { 52 | /// Should work for all cards 53 | Safe, 54 | /// May not work for some cards 55 | Fast, 56 | /// Specify SPI frequency 57 | Custom(Hertz) 58 | } 59 | 60 | impl From for Hertz { 61 | fn from(value: SdCardFreq) -> Hertz { 62 | match value { 63 | SdCardFreq::Safe => 200.khz().into(), // using 300 kHz here because the sdcard init needs 100 to 400 kHz (see SdMmcSpi.init) 64 | SdCardFreq::Fast => 27.mhz().into(), // this is the max SPI frequency according to datasheet 65 | SdCardFreq::Custom(val) => val, 66 | } 67 | } 68 | } 69 | 70 | /// Constructs SD Card driver from the required components. 71 | pub fn configure(spi: SPI1, pins: SdCardPins, freq: SdCardFreq, rcu: &mut Rcu) -> SdCard { 72 | let spi1 = Spi::spi1( 73 | spi, 74 | (pins.sck, pins.miso, pins.mosi), 75 | MODE_0, 76 | freq, 77 | rcu, 78 | ); 79 | 80 | let mut cs = pins.cs; 81 | cs.set_high().unwrap(); 82 | 83 | let sdmmcspi = SdMmcSpi::new(spi1, cs); 84 | let ctime_source = FakeTimeSource {}; 85 | 86 | Controller::new(sdmmcspi, ctime_source) 87 | } 88 | 89 | /// A fake time source that always returns a date of zero. 90 | pub struct FakeTimeSource {} 91 | 92 | impl TimeSource for FakeTimeSource { 93 | fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { 94 | Timestamp { 95 | year_since_1970: 0, 96 | zero_indexed_month: 0, 97 | zero_indexed_day: 0, 98 | hours: 0, 99 | minutes: 0, 100 | seconds: 0, 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/stdout.rs: -------------------------------------------------------------------------------- 1 | //! Stdout based on the UART hooked up to the debug connector 2 | 3 | use core::fmt::{self, Write}; 4 | use nb::block; 5 | use riscv::interrupt; 6 | use gd32vf103xx_hal::{ 7 | serial::{Serial, Tx}, 8 | gpio::{Active, gpioa::{PA10, PA9}}, 9 | time::Bps, 10 | rcu::Rcu, 11 | afio::Afio, 12 | pac::USART0, 13 | prelude::* 14 | }; 15 | use gd32vf103xx_hal::serial::{Config, Parity, StopBits}; 16 | 17 | 18 | static mut STDOUT: Option = None; 19 | 20 | 21 | struct SerialWrapper(Tx); 22 | 23 | impl fmt::Write for SerialWrapper { 24 | fn write_str(&mut self, s: &str) -> fmt::Result { 25 | for byte in s.as_bytes() { 26 | if *byte == '\n' as u8 { 27 | let res = block!(self.0.write('\r' as u8)); 28 | 29 | if res.is_err() { 30 | return Err(::core::fmt::Error); 31 | } 32 | } 33 | 34 | let res = block!(self.0.write(*byte)); 35 | 36 | if res.is_err() { 37 | return Err(::core::fmt::Error); 38 | } 39 | } 40 | Ok(()) 41 | } 42 | } 43 | 44 | /// Configures stdout 45 | pub fn configure( 46 | uart: USART0, tx: PA9, rx: PA10, 47 | baud_rate: Bps, afio: &mut Afio, rcu: &mut Rcu 48 | ) where X: Active, Y: Active 49 | { 50 | let tx = tx.into_alternate_push_pull(); 51 | let rx = rx.into_floating_input(); 52 | let config = Config { 53 | baudrate: baud_rate, 54 | parity: Parity::ParityNone, 55 | stopbits: StopBits::STOP1 56 | }; 57 | let serial = Serial::new(uart, (tx, rx), config, afio, rcu); 58 | let (tx, _) = serial.split(); 59 | 60 | interrupt::free(|| { 61 | unsafe { 62 | STDOUT.replace(SerialWrapper(tx)); 63 | } 64 | }) 65 | } 66 | 67 | /// Writes string to stdout 68 | pub fn write_str(s: &str) { 69 | interrupt::free(|| unsafe { 70 | if let Some(stdout) = STDOUT.as_mut() { 71 | let _ = stdout.write_str(s); 72 | } 73 | }) 74 | } 75 | 76 | /// Writes formatted string to stdout 77 | pub fn write_fmt(args: fmt::Arguments) { 78 | interrupt::free(|| unsafe { 79 | if let Some(stdout) = STDOUT.as_mut() { 80 | let _ = stdout.write_fmt(args); 81 | } 82 | }) 83 | } 84 | 85 | /// Macro for printing to the serial standard output 86 | #[macro_export] 87 | macro_rules! sprint { 88 | ($s:expr) => { 89 | $crate::stdout::write_str($s) 90 | }; 91 | ($($tt:tt)*) => { 92 | $crate::stdout::write_fmt(format_args!($($tt)*)) 93 | }; 94 | } 95 | 96 | /// Macro for printing to the serial standard output, with a newline. 97 | #[macro_export] 98 | macro_rules! sprintln { 99 | () => { 100 | $crate::stdout::write_str("\n") 101 | }; 102 | ($s:expr) => { 103 | $crate::stdout::write_str(concat!($s, "\n")) 104 | }; 105 | ($s:expr, $($tt:tt)*) => { 106 | $crate::stdout::write_fmt(format_args!(concat!($s, "\n"), $($tt)*)) 107 | }; 108 | } 109 | --------------------------------------------------------------------------------