diff --git a/.github/workflows/e310x-hal.yaml b/.github/workflows/e310x-hal.yaml index ea4af6b..e3c572c 100644 --- a/.github/workflows/e310x-hal.yaml +++ b/.github/workflows/e310x-hal.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.76.0 - toolchain: [ stable, nightly, 1.76.0 ] + # All generated code should be running on stable now, MRSV is 1.79.0 + toolchain: [ stable, nightly, 1.79.0 ] include: # Nightly is only for reference and allowed to fail - toolchain: nightly @@ -47,4 +47,4 @@ jobs: run: cargo test --package e310x-hal - name: Build (all features) run: cargo test --package e310x-hal --all-features - \ No newline at end of file + diff --git a/.github/workflows/hifive1.yaml b/.github/workflows/hifive1.yaml index e644f21..c6932b7 100644 --- a/.github/workflows/hifive1.yaml +++ b/.github/workflows/hifive1.yaml @@ -11,8 +11,8 @@ jobs: build-riscv: strategy: matrix: - # All generated code should be running on stable now, MRSV is 1.76.0 - toolchain: [nightly, stable, 1.76.0] + # All generated code should be running on stable now, MRSV is 1.79.0 + toolchain: [nightly, stable, 1.79.0] board: [hifive1, hifive1-revb, redv, lofive, lofive-r1] include: # Nightly is only for reference and allowed to fail @@ -49,4 +49,4 @@ jobs: run: cargo test --package hifive1 --features board-${{ matrix.board }} - name: Build (vectored) run: cargo test --package hifive1 --features board-${{ matrix.board }},v-trap - \ No newline at end of file + diff --git a/Cargo.toml b/Cargo.toml index 0d60e54..da5a596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "e310x-hal", "hifive1", "hifive1-examples", + "hifive1-async-examples", ] default-members = [ "e310x", diff --git a/e310x-hal/CHANGELOG.md b/e310x-hal/CHANGELOG.md index 96e1ab2..88f8327 100644 --- a/e310x-hal/CHANGELOG.md +++ b/e310x-hal/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Update `e310x` dependency and adapt code - Add interrupt managing methods to `e310x-hal::gpio` module +- Add embedded-hal-async digital module support to `e310x-hal::gpio` module ## [v0.12.0] - 2024-12-10 diff --git a/e310x-hal/Cargo.toml b/e310x-hal/Cargo.toml index fe63e95..8410d1c 100644 --- a/e310x-hal/Cargo.toml +++ b/e310x-hal/Cargo.toml @@ -8,7 +8,7 @@ description = "HAL for the E310x family of microcontrollers." keywords = ["riscv", "e310", "hal"] license = "ISC" edition = "2021" -rust-version = "1.76" +rust-version = "1.79" [dependencies] embedded-hal = "1.0.0" @@ -19,9 +19,15 @@ nb = "1.0.0" portable-atomic = { version = "1.9", default-features = false } riscv = { workspace = true, features = ["critical-section-single-hart"] } +# Async HAL dependencies +riscv-rt = { workspace = true, optional = true } +embedded-hal-async = { version = "1.0.0", optional = true } +critical-section = { workspace = true, optional = true } + [features] g002 = ["e310x/g002"] v-trap = ["e310x/v-trap"] +async = ["riscv-rt", "embedded-hal-async", "critical-section"] [package.metadata.docs.rs] features = ["g002"] diff --git a/e310x-hal/README.md b/e310x-hal/README.md index a3f65bb..8eb554a 100644 --- a/e310x-hal/README.md +++ b/e310x-hal/README.md @@ -12,7 +12,7 @@ This project is developed and maintained by the [RISC-V team][team]. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.72.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.79.0 and up. It *might* compile with older versions but that may change in any new patch release. ## License diff --git a/e310x-hal/src/asynch.rs b/e310x-hal/src/asynch.rs new file mode 100644 index 0000000..354a25b --- /dev/null +++ b/e310x-hal/src/asynch.rs @@ -0,0 +1,9 @@ +//! Asynchronous HAL for the E310x family of microcontrollers +//! +//! This is an implementation of the [`embedded-hal-async`] traits for the E310x +//! family of microcontrollers. + +#![deny(missing_docs)] + +pub mod digital; +pub mod prelude; diff --git a/e310x-hal/src/asynch/digital.rs b/e310x-hal/src/asynch/digital.rs new file mode 100644 index 0000000..b2fd405 --- /dev/null +++ b/e310x-hal/src/asynch/digital.rs @@ -0,0 +1,277 @@ +//! # Digital I/O +//! # Note +//! +//! Implementation of the Async Embedded HAL I/O functionality. +//! + +macro_rules! gpio_async { + ($GPIOX:ident, [ + $($PXi:ident: ($pxi:ident, $i:expr, $handle:ident),)+ + ]) => { + use core::cell::RefCell; + use core::task::{Poll, Waker}; + use core::future::poll_fn; + use critical_section::Mutex; + use crate::gpio::*; + use crate::gpio::gpio0::*; + use e310x::$GPIOX; + use e310x::interrupt::ExternalInterrupt; + use embedded_hal::digital::{Error, ErrorKind, ErrorType, InputPin}; + use embedded_hal_async::digital::Wait; + + /// Error type for wait trait. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum DigitalError { + /// Error indicating that a wait operation was already in progress. + AlreadyWaiting, + /// Other errors. + Other, + } + + const N_PINS: usize = 32; + static PIN_WAKERS: Mutex; N_PINS]>> = + Mutex::new(RefCell::new([const{None}; N_PINS])); + + impl Error for DigitalError { + fn kind(&self) -> ErrorKind { + ErrorKind::Other + } + } + + /// Interrupt handler for GPIO pins. + #[inline] + fn on_irq(pin_n: usize) { + let gpio_block = unsafe { $GPIOX::steal() }; + let pin_mask = 1 << pin_n; + + // Disable the interrupt for the pin + unsafe{ + gpio_block.high_ie().modify(|r, w| w.bits(r.bits() &! pin_mask)); + gpio_block.low_ie().modify(|r, w| w.bits(r.bits() &! pin_mask)); + gpio_block.rise_ie().modify(|r, w| w.bits(r.bits() &! pin_mask)); + gpio_block.fall_ie().modify(|r, w| w.bits(r.bits() &! pin_mask)); + } + + // Wake the pin if possible + critical_section::with(|cs| { + let mut pin_wakers = PIN_WAKERS.borrow_ref_mut(cs); + if let Some(pinwaker) = pin_wakers[pin_n].take() { + pinwaker.wake(); + } + }); + + // Clear pending pin interrupts + unsafe{ + gpio_block.high_ip().write(|w| w.bits(pin_mask)); + gpio_block.low_ip().write(|w| w.bits(pin_mask)); + gpio_block.rise_ip().write(|w| w.bits(pin_mask)); + gpio_block.fall_ip().write(|w| w.bits(pin_mask)); + } + } + + /// GPIO + $( + impl ErrorType for $PXi> { + type Error = DigitalError; + } + /// Wait trait implementation + impl Wait for $PXi> { + #[inline] + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + // If the pin is already high, no need to wait. + if self.is_high().unwrap() { + return Ok(()); + } + + // Prevent concurrent waiters. + if critical_section::with(|cs| { + PIN_WAKERS.borrow_ref(cs)[$i].is_some() + }){ + return Err(DigitalError::AlreadyWaiting); + } + + // Clear previous high interrupts for the pin. + self.clear_interrupt(EventType::High); + + // Enable the high interrupt for the pin. + self.enable_interrupt(EventType::High); + + // Await until an interrupt indicates that the pin has transitioned high. + poll_fn(|cx| { + if !self.is_interrupt_enabled(EventType::High) { + Poll::Ready(Ok(())) + } else { + critical_section::with(|cs| { + let mut pinwaker = PIN_WAKERS.borrow_ref_mut(cs); + pinwaker[$i] = Some(cx.waker().clone()); + }); + Poll::Pending + } + }).await + } + + #[inline] + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + // If the pin is already low, no need to wait. + if self.is_low().unwrap() { + return Ok(()); + } + + // Prevent concurrent waiters. + if critical_section::with(|cs| { + PIN_WAKERS.borrow_ref(cs)[$i].is_some() + }){ + return Err(DigitalError::AlreadyWaiting); + } + + // Clear previous low interrupts for the pin. + self.clear_interrupt(EventType::Low); + + // Enable the low interrupt for the pin. + self.enable_interrupt(EventType::Low); + + // Await until an interrupt indicates that the pin has transitioned high. + poll_fn(|cx| { + if !self.is_interrupt_enabled(EventType::Low) { + Poll::Ready(Ok(())) + } else { + critical_section::with(|cs| { + let mut pinwaker = PIN_WAKERS.borrow_ref_mut(cs); + pinwaker[$i] = Some(cx.waker().clone()); + }); + Poll::Pending + } + }).await + } + + #[inline] + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + // Prevent concurrent waiters. + if critical_section::with(|cs| { + PIN_WAKERS.borrow_ref(cs)[$i].is_some() + }){ + return Err(DigitalError::AlreadyWaiting); + } + + // Clear previous rising edge interrupts for the pin. + self.clear_interrupt(EventType::Rise); + + // Enable the rising edge interrupt for the pin. + self.enable_interrupt(EventType::Rise); + + // Await until an interrupt indicates that the pin has transitioned high. + poll_fn(|cx| { + if !self.is_interrupt_enabled(EventType::Rise) { + Poll::Ready(Ok(())) + } else { + critical_section::with(|cs| { + let mut pinwaker = PIN_WAKERS.borrow_ref_mut(cs); + pinwaker[$i] = Some(cx.waker().clone()); + }); + Poll::Pending + } + }).await + } + + #[inline] + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + // Prevent concurrent waiters. + if critical_section::with(|cs| { + PIN_WAKERS.borrow_ref(cs)[$i].is_some() + }){ + return Err(DigitalError::AlreadyWaiting); + } + + // Clear previous falling edge interrupts for the pin. + self.clear_interrupt(EventType::Fall); + + // Enable the falling edge interrupt for the pin. + self.enable_interrupt(EventType::Fall); + + // Await until an interrupt indicates that the pin has transitioned high. + poll_fn(|cx| { + if !self.is_interrupt_enabled(EventType::Fall) { + Poll::Ready(Ok(())) + } else { + critical_section::with(|cs| { + let mut pinwaker = PIN_WAKERS.borrow_ref_mut(cs); + pinwaker[$i] = Some(cx.waker().clone()); + }); + Poll::Pending + } + }).await + } + + #[inline] + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + // Prevent concurrent waiters. + if critical_section::with(|cs| { + PIN_WAKERS.borrow_ref(cs)[$i].is_some() + }){ + return Err(DigitalError::AlreadyWaiting); + } + + // Clear previous rising and falling edge interrupts for the pin. + self.clear_interrupt(EventType::BothEdges); + + // Enable the rising and falling edge interrupts for the pin. + self.enable_interrupt(EventType::BothEdges); + + // Await until an interrupt indicates that the pin has transitioned high. + poll_fn(|cx| { + if !self.is_interrupt_enabled(EventType::BothEdges) { + Poll::Ready(Ok(())) + } else { + critical_section::with(|cs| { + let mut pinwaker = PIN_WAKERS.borrow_ref_mut(cs); + pinwaker[$i] = Some(cx.waker().clone()); + }); + Poll::Pending + } + }).await + } + } + + /// Pin Interrupt Handler + #[riscv_rt::external_interrupt(ExternalInterrupt::$handle)] + fn $pxi() { + on_irq($i); + } + )+ + } +} + +gpio_async!(Gpio0, [ + Pin0: (pin0, 0, GPIO0), + Pin1: (pin1, 1, GPIO1), + Pin2: (pin2, 2, GPIO2), + Pin3: (pin3, 3, GPIO3), + Pin4: (pin4, 4, GPIO4), + Pin5: (pin5, 5, GPIO5), + Pin6: (pin6, 6, GPIO6), + Pin7: (pin7, 7, GPIO7), + Pin8: (pin8, 8, GPIO8), + Pin9: (pin9, 9, GPIO9), + Pin10: (pin10, 10, GPIO10), + Pin11: (pin11, 11, GPIO11), + Pin12: (pin12, 12, GPIO12), + Pin13: (pin13, 13, GPIO13), + Pin14: (pin14, 14, GPIO14), + Pin15: (pin15, 15, GPIO15), + Pin16: (pin16, 16, GPIO16), + Pin17: (pin17, 17, GPIO17), + Pin18: (pin18, 18, GPIO18), + Pin19: (pin19, 19, GPIO19), + Pin20: (pin20, 20, GPIO20), + Pin21: (pin21, 21, GPIO21), + Pin22: (pin22, 22, GPIO22), + Pin23: (pin23, 23, GPIO23), + Pin24: (pin24, 24, GPIO24), + Pin25: (pin25, 25, GPIO25), + Pin26: (pin26, 26, GPIO26), + Pin27: (pin27, 27, GPIO27), + Pin28: (pin28, 28, GPIO28), + Pin29: (pin29, 29, GPIO29), + Pin30: (pin30, 30, GPIO30), + Pin31: (pin31, 31, GPIO31), +]); diff --git a/e310x-hal/src/asynch/prelude.rs b/e310x-hal/src/asynch/prelude.rs new file mode 100644 index 0000000..88a5519 --- /dev/null +++ b/e310x-hal/src/asynch/prelude.rs @@ -0,0 +1,2 @@ +//! Prelude +pub use embedded_hal_async::digital::Wait as _eha_Wait; diff --git a/e310x-hal/src/gpio.rs b/e310x-hal/src/gpio.rs index 9c6ca7e..083e86e 100644 --- a/e310x-hal/src/gpio.rs +++ b/e310x-hal/src/gpio.rs @@ -558,6 +558,7 @@ macro_rules! gpio { } } + #[cfg(not(feature = "async"))] impl ErrorType for $PXi> { type Error = Infallible; } diff --git a/e310x-hal/src/lib.rs b/e310x-hal/src/lib.rs index b040046..ca3faa9 100644 --- a/e310x-hal/src/lib.rs +++ b/e310x-hal/src/lib.rs @@ -62,4 +62,7 @@ pub mod wdog; #[cfg(feature = "g002")] pub mod i2c; +#[cfg(feature = "async")] +pub mod asynch; + pub use device::DeviceResources; diff --git a/hifive1-async-examples/.cargo/config.toml b/hifive1-async-examples/.cargo/config.toml new file mode 100644 index 0000000..685d595 --- /dev/null +++ b/hifive1-async-examples/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.'cfg(all(target_arch = "riscv32", target_os = "none"))'] +runner = "qemu-system-riscv32 -machine sifive_e,revb=true -nographic -semihosting-config enable=on,target=native -kernel" # Uncomment for QEMU +# runner = "riscv64-unknown-elf-gdb -q -x gdb_init" # Uncomment for hardware (no semihosting) +# runner = "riscv64-unknown-elf-gdb -q -x gdb_init_sh" # Uncomment for hardware (semihosting) +rustflags = [ + "-C", "link-arg=-Thifive1-link.x", + "--cfg", "portable_atomic_target_feature=\"zaamo\"", +] + +[build] +target = "riscv32imc-unknown-none-elf" diff --git a/hifive1-async-examples/Cargo.toml b/hifive1-async-examples/Cargo.toml new file mode 100644 index 0000000..14e8e05 --- /dev/null +++ b/hifive1-async-examples/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hifive1-async-examples" +version = "0.1.0" +edition = "2021" +rust-version = "1.83" + +[dependencies] +embassy-executor = { version = "0.9.1", features = ["arch-riscv32", "executor-thread"] } #embassy executor for async tasks +hifive1 = { path = "../hifive1", version = "0.13.0", features = ["board-hifive1-revb", "async"] } # Change to your boardW +riscv = { workspace = true } +riscv-rt = { workspace = true, features = [] } +panic-halt = "1.0.0" + +[features] +v-trap = ["hifive1/v-trap"] diff --git a/hifive1-async-examples/examples/button_led.rs b/hifive1-async-examples/examples/button_led.rs new file mode 100644 index 0000000..69c11af --- /dev/null +++ b/hifive1-async-examples/examples/button_led.rs @@ -0,0 +1,77 @@ +//! Basic example where the LED changes its state according to a button connected to pin 9. +//! The LED must be connected to pin 10 of the board +//! This example uses synchronous UART and only tests asynchronous GPIO. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use hifive1::{ + clock, + hal::{asynch::prelude::*, gpio::EventType, prelude::*, DeviceResources}, + sprintln, +}; +extern crate panic_halt; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let dr = DeviceResources::take().unwrap(); + let cp = dr.core_peripherals; + let p = dr.peripherals; + let mut pins = dr.pins; + + // Configure clocks + let clocks = clock::configure(p.PRCI, p.AONCLK, 320.mhz().into()); + + // Disable and clear pending GPIO interrupts from previous states + pins.disable_interrupts(EventType::All); + pins.clear_interrupts(EventType::All); + + // Configure UART for stdout + hifive1::stdout::configure(p.UART0, pins.pin17, pins.pin16, 115_200.bps(), clocks); + + sprintln!("Configuring GPIOs..."); + + // Button pin (GPIO9) as pull-up input + let mut button = pins.pin9.into_pull_up_input(); + // Configure blue LED pin (GPIO10) as output + let mut led = pins.pin10.into_output(); + + sprintln!("Configuring external interrupts..."); + + // Make sure interrupts are disabled + riscv::interrupt::disable(); + + // Reset PLIC interrupts and set priority threshold + let plic = cp.plic; + let priorities = plic.priorities(); + let ctx = plic.ctx0(); + priorities.reset::(); + unsafe { + ctx.enables().disable_all::(); + ctx.threshold().set_threshold(Priority::P0); + } + + // Enable GPIO9 interrupt for both edges + button.enable_interrupt(EventType::BothEdges); + unsafe { + button.set_exti_priority(&plic, Priority::P1); + button.enable_exti(&plic); + } + + sprintln!("Enabling external interrupts..."); + + // Enable global interrupts + unsafe { + riscv::interrupt::enable(); + plic.enable(); + } + + // Execute loop + loop { + led.toggle().unwrap(); + let led_state = led.is_set_high().unwrap(); + sprintln!("LED toggled. New state: {}", led_state); + button.wait_for_any_edge().await.unwrap(); + } +} diff --git a/hifive1-async-examples/gdb_init b/hifive1-async-examples/gdb_init new file mode 100644 index 0000000..8719bc8 --- /dev/null +++ b/hifive1-async-examples/gdb_init @@ -0,0 +1,12 @@ +# GDB init file for HiFive1 boards + +# set history save on # uncomment to save history +set confirm off +set remotetimeout 240 +set print asm-demangle on + +target extended-remote :3333 +monitor reset halt +load +continue # uncomment to start running after loading +# quit # uncomment to exit after loading diff --git a/hifive1-async-examples/gdb_init_sh b/hifive1-async-examples/gdb_init_sh new file mode 100644 index 0000000..925f2f9 --- /dev/null +++ b/hifive1-async-examples/gdb_init_sh @@ -0,0 +1,13 @@ +# GDB init file for HiFive1 boards (including semihosting) + +# set history save on # uncomment to save history +set confirm off +set remotetimeout 240 +set print asm-demangle on + +target extended-remote :3333 +monitor reset halt +monitor arm semihosting enable +load +continue # uncomment to start running after loading +# quit # uncomment to exit after loading diff --git a/hifive1-async-examples/openocd.sh b/hifive1-async-examples/openocd.sh new file mode 100644 index 0000000..2587073 --- /dev/null +++ b/hifive1-async-examples/openocd.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# This script runs OpenOCD with the specified configuration file +# for the HiFive1 board. The configuration file is selected based +# on the revb argument, which is a boolean flag that indicates +# whether the HiFive1 Rev B board is being used. If the revb +# argument is not provided, the default configuration file for +# the HiFive1 Rev A board is used. + +# Default path to OpenOCD +OPENOCD_PATH="openocd" +REVB=false + +# Parse command-line arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + -p|--path) OPENOCD_PATH="$2"; shift ;; + revb) REVB=true ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +# Determine the configuration file based on the revb argument +if [ "$REVB" = true ]; then + CONFIG_FILE="sifive-hifive1-revb.cfg" +else + CONFIG_FILE="sifive-hifive1.cfg" +fi + +# Run OpenOCD with the specified configuration file +echo "Running $OPENOCD_PATH -f board/$CONFIG_FILE" +$OPENOCD_PATH -f board/$CONFIG_FILE diff --git a/hifive1-async-examples/src/main.rs b/hifive1-async-examples/src/main.rs new file mode 100644 index 0000000..a39f54a --- /dev/null +++ b/hifive1-async-examples/src/main.rs @@ -0,0 +1,42 @@ +//! Prints "hello world!" to the host console using the on-board UART. +//! +//! # Note +//! +//! We have noticed that using the UART while debugging with GDB can cause +//! the GDB session to hang. Thus, you will probably want to run this example +//! without GDB. Otherwise, you might not be able to see the output. + +#![no_std] +#![no_main] + +use hifive1::{ + hal::{prelude::*, DeviceResources}, + pin, sprintln, +}; + +extern crate panic_halt; + +#[riscv_rt::entry] +fn main() -> ! { + let dr = DeviceResources::take().unwrap(); + let p = dr.peripherals; + let pins = dr.pins; + + // Configure clocks + let clocks = hifive1::clock::configure(p.PRCI, p.AONCLK, 320.mhz().into()); + + // Configure UART for stdout + hifive1::stdout::configure( + p.UART0, + pin!(pins, uart0_tx), + pin!(pins, uart0_rx), + 115_200.bps(), + clocks, + ); + + sprintln!("Hello, world!"); + + loop { + riscv::asm::wfi(); + } +} diff --git a/hifive1-examples/Cargo.toml b/hifive1-examples/Cargo.toml index aba550f..cb4f2f0 100644 --- a/hifive1-examples/Cargo.toml +++ b/hifive1-examples/Cargo.toml @@ -8,7 +8,7 @@ description = "Running examples for HiFive1 and LoFive boards" keywords = ["riscv", "register", "peripheral"] license = "ISC" edition = "2021" -rust-version = "1.72" +rust-version = "1.79" [dependencies] critical-section = { workspace = true } diff --git a/hifive1/CHANGELOG.md b/hifive1/CHANGELOG.md index 2701b92..0983e67 100644 --- a/hifive1/CHANGELOG.md +++ b/hifive1/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - Update `e310x-hal` dependency and adapt code +- Add async feature flag to enable embedded-hal-async digital module support ## [v0.13.0] - 2024-12-10 diff --git a/hifive1/Cargo.toml b/hifive1/Cargo.toml index 5dc30dc..0a2d9a4 100644 --- a/hifive1/Cargo.toml +++ b/hifive1/Cargo.toml @@ -8,7 +8,7 @@ description = "Board support crate for HiFive1 and LoFive boards" keywords = ["riscv", "register", "peripheral"] license = "ISC" edition = "2021" -rust-version = "1.76" +rust-version = "1.79" [dependencies] critical-section = { workspace = true } @@ -23,6 +23,7 @@ board-redv = ["e310x-hal/g002"] board-lofive = [] board-lofive-r1 = ["e310x-hal/g002"] v-trap = ["e310x-hal/v-trap"] +async = ["e310x-hal/async"] [package.metadata.docs.rs] features = ['board-hifive1-revb'] diff --git a/hifive1/README.md b/hifive1/README.md index a6454d3..ea2c24c 100644 --- a/hifive1/README.md +++ b/hifive1/README.md @@ -18,7 +18,7 @@ ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.72.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.79.0 and up. It *might* compile with older versions but that may change in any new patch release. ## License