diff --git a/esp32c3-hal/.cargo/config.toml b/esp32c3-hal/.cargo/config.toml new file mode 100644 index 000000000..c2b4f5e58 --- /dev/null +++ b/esp32c3-hal/.cargo/config.toml @@ -0,0 +1,7 @@ +[target.riscv32imc-unknown-none-elf] +rustflags = [ + "-C", "link-arg=-Tesp32c3-link.x", +] + +[build] +target = "riscv32imc-unknown-none-elf" diff --git a/esp32c3-hal/Cargo.toml b/esp32c3-hal/Cargo.toml new file mode 100644 index 000000000..d60ec8edd --- /dev/null +++ b/esp32c3-hal/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "esp32c3-hal" +version = "0.1.0" +edition = "2018" + +[dependencies] +bare-metal = "1.0" +embedded-hal = { version = "0.2", features = ["unproven"] } +nb = "1.0" +riscv = "0.7" +void = { version = "1.0", default-features = false } + +[dependencies.esp32c3] +path = "../../esp-pacs/esp32c3" + +[dependencies.riscv-rt] +git = "https://github.com/MabezDev/riscv-rt" +rev = "6b55e4aa3895924e31bcd151f2f0ab840836fa07" +optional = true + +[dev-dependencies] +panic-halt = "0.2" + +[features] +default = ["rt"] +rt = ["esp32c3/rt", "riscv-rt"] diff --git a/esp32c3-hal/README.md b/esp32c3-hal/README.md new file mode 100644 index 000000000..402b1f0b9 --- /dev/null +++ b/esp32c3-hal/README.md @@ -0,0 +1 @@ +# esp32c3-hal diff --git a/esp32c3-hal/build.rs b/esp32c3-hal/build.rs new file mode 100644 index 000000000..c4d8bc95c --- /dev/null +++ b/esp32c3-hal/build.rs @@ -0,0 +1,16 @@ +use std::{env, fs::File, io::Write, path::PathBuf}; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("esp32c3-memory.x")) + .unwrap(); + + println!("cargo:rustc-link-search={}", out.display()); + + // Only re-run the build script when memory.x is changed, + // instead of when any part of the source code changes. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/esp32c3-hal/esp32c3-link.x b/esp32c3-hal/esp32c3-link.x new file mode 100644 index 000000000..0e8e08974 --- /dev/null +++ b/esp32c3-hal/esp32c3-link.x @@ -0,0 +1,2 @@ +INCLUDE esp32c3-memory.x +INCLUDE link.x diff --git a/esp32c3-hal/esp32c3-memory.x b/esp32c3-hal/esp32c3-memory.x new file mode 100644 index 000000000..5e29c5bb3 --- /dev/null +++ b/esp32c3-hal/esp32c3-memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* 400K of on soc RAM, 16K reserved for cache */ + ICACHE : ORIGIN = 0x4037C000, LENGTH = 0x4000 + IRAM : ORIGIN = 0x4037C000 + 0x4000, LENGTH = 400K - 0x4000 + /* 384K of on soc ROM */ + IROM : ORIGIN = 0x42000020, LENGTH = 0x60000 +} + +REGION_ALIAS("REGION_TEXT", IRAM); +REGION_ALIAS("REGION_RODATA", IRAM); +REGION_ALIAS("REGION_DATA", IRAM); +REGION_ALIAS("REGION_BSS", IRAM); +REGION_ALIAS("REGION_HEAP", IRAM); +REGION_ALIAS("REGION_STACK", IRAM); diff --git a/esp32c3-hal/examples/serial.rs b/esp32c3-hal/examples/serial.rs new file mode 100644 index 000000000..963f0eb95 --- /dev/null +++ b/esp32c3-hal/examples/serial.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32c3_hal::{pac, prelude::*, RtcCntl, Serial, Timer}; +use nb::block; +use panic_halt as _; +use riscv_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = pac::Peripherals::take().unwrap(); + + let rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + let mut timer0 = Timer::new(peripherals.TIMG0); + let mut serial0 = Serial::new(peripherals.UART0).unwrap(); + + rtc_cntl.set_super_wdt_enable(false); + rtc_cntl.set_wdt_enable(false); + timer0.disable(); + + timer0.start(10_000_000u64); + + loop { + writeln!(serial0, "Hello world!").unwrap(); + block!(timer0.wait()).unwrap(); + } +} diff --git a/esp32c3-hal/src/lib.rs b/esp32c3-hal/src/lib.rs new file mode 100644 index 000000000..016da9236 --- /dev/null +++ b/esp32c3-hal/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] + +pub use embedded_hal as ehal; +pub use esp32c3 as pac; + +pub mod prelude; +pub mod rtc_cntl; +pub mod serial; +pub mod timer; + +pub use rtc_cntl::RtcCntl; +pub use serial::Serial; +pub use timer::Timer; diff --git a/esp32c3-hal/src/prelude.rs b/esp32c3-hal/src/prelude.rs new file mode 100644 index 000000000..8a9e87a91 --- /dev/null +++ b/esp32c3-hal/src/prelude.rs @@ -0,0 +1,9 @@ +pub use embedded_hal::{ + digital::v2::{ + InputPin as _, + OutputPin as _, + StatefulOutputPin as _, + ToggleableOutputPin as _, + }, + prelude::*, +}; diff --git a/esp32c3-hal/src/rtc_cntl.rs b/esp32c3-hal/src/rtc_cntl.rs new file mode 100644 index 000000000..c80809440 --- /dev/null +++ b/esp32c3-hal/src/rtc_cntl.rs @@ -0,0 +1,47 @@ +use crate::pac::RTC_CNTL; + +pub struct RtcCntl { + rtc_cntl: RTC_CNTL, +} + +impl RtcCntl { + pub fn new(rtc_cntl: RTC_CNTL) -> Self { + Self { rtc_cntl } + } + + pub fn set_super_wdt_enable(&self, enable: bool) { + self.set_swd_write_protection(false); + + self.rtc_cntl + .rtc_swd_conf + .write(|w| w.swd_auto_feed_en().bit(!enable)); + + self.set_swd_write_protection(true); + } + + fn set_swd_write_protection(&self, enable: bool) { + let wkey = if enable { 0u32 } else { 0x8F1D_312A }; + + self.rtc_cntl + .rtc_swd_wprotect + .write(|w| unsafe { w.swd_wkey().bits(wkey) }); + } + + pub fn set_wdt_enable(&self, enable: bool) { + self.set_wdt_write_protection(false); + + self.rtc_cntl + .rtc_wdtconfig0 + .write(|w| w.wdt_en().bit(enable)); + + self.set_wdt_write_protection(true); + } + + fn set_wdt_write_protection(&self, enable: bool) { + let wkey = if enable { 0u32 } else { 0x50D8_3AA1 }; + + self.rtc_cntl + .rtc_wdtwprotect + .write(|w| unsafe { w.wdt_wkey().bits(wkey) }); + } +} diff --git a/esp32c3-hal/src/serial.rs b/esp32c3-hal/src/serial.rs new file mode 100644 index 000000000..473e16d18 --- /dev/null +++ b/esp32c3-hal/src/serial.rs @@ -0,0 +1,171 @@ +//! UART Functionality (UART) +//! +//! Reference: ESP32-C3 TRM v0.3 Section 20 (as TRM) +//! +//! The ESP32-C3 has two identical UART groups UART0 and UART1. +//! +//! TODO: +//! - Interrupt support + +use embedded_hal::serial::{Read, Write}; + +use crate::pac::{uart0::RegisterBlock, UART0, UART1}; + +const UART_FIFO_SIZE: u16 = 128; + +/// UART-specific errors +#[derive(Debug)] +pub enum Error {} + +/// UART peripheral (UART) +pub struct Serial { + uart: T, +} + +impl Serial { + pub fn new(uart: T) -> Result { + let mut serial = Serial { uart }; + serial.uart.disable_rx_interrupts(); + serial.uart.disable_tx_interrupts(); + Ok(serial) + } +} + +pub trait Instance { + /// Return registerblock of uart instance as if it were UART0 + fn as_uart0(&self) -> &RegisterBlock; + + /// Clear and disable all tx-related interrupts + fn disable_tx_interrupts(&mut self) { + // Disable UART TX interrupts + self.as_uart0().int_clr.write(|w| { + w.txfifo_empty_int_clr() + .set_bit() + .tx_brk_done_int_clr() + .set_bit() + .tx_brk_idle_done_int_clr() + .set_bit() + .tx_done_int_clr() + .set_bit() + }); + + self.as_uart0().int_ena.write(|w| { + w.txfifo_empty_int_ena() + .clear_bit() + .tx_brk_done_int_ena() + .clear_bit() + .tx_brk_idle_done_int_ena() + .clear_bit() + .tx_done_int_ena() + .clear_bit() + }); + } + + /// Clear and disable all rx-related interrupts + fn disable_rx_interrupts(&mut self) { + // Disable UART RX interrupts + self.as_uart0().int_clr.write(|w| { + w.rxfifo_full_int_clr() + .set_bit() + .rxfifo_ovf_int_clr() + .set_bit() + .rxfifo_tout_int_clr() + .set_bit() + }); + + self.as_uart0().int_ena.write(|w| { + w.rxfifo_full_int_ena() + .clear_bit() + .rxfifo_ovf_int_ena() + .clear_bit() + .rxfifo_tout_int_ena() + .clear_bit() + }); + } + + /// Get the number of occupied entries in the tx fifo buffer + fn get_tx_fifo_count(&mut self) -> u16 { + self.as_uart0().status.read().txfifo_cnt().bits() + } + + /// Get the number of occupied entries in the rx fifo buffer + fn get_rx_fifo_count(&mut self) -> u16 { + self.as_uart0().status.read().rxfifo_cnt().bits() + } + + /// Check if the UART TX statemachine is is idle + fn is_tx_idle(&mut self) -> bool { + self.as_uart0().fsm_status.read().st_utx_out().bits() == 0x0u8 + } + + /// Check if the UART RX statemachine is is idle + fn is_rx_idle(&mut self) -> bool { + self.as_uart0().fsm_status.read().st_urx_out().bits() == 0x0u8 + } +} + +/// Write half of a serial interface +impl Write for Serial { + type Error = Error; + + /// Writes a single word to the serial interface + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.uart.get_tx_fifo_count() >= UART_FIFO_SIZE { + Err(nb::Error::WouldBlock) + } else { + self.uart + .as_uart0() + .fifo + .write(|w| unsafe { w.rxfifo_rd_byte().bits(word) }); + Ok(()) + } + } + + /// Ensures that none of the previously written words are still buffered + fn flush(&mut self) -> nb::Result<(), Self::Error> { + if self.uart.is_tx_idle() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +/// Write half of a serial interface +impl Read for Serial { + type Error = Error; + + /// Reads a single word from the serial interface + fn read(&mut self) -> nb::Result { + if self.uart.get_rx_fifo_count() > 0 { + Ok(self.uart.as_uart0().fifo.read().rxfifo_rd_byte().bits()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +impl core::fmt::Write for Serial { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + s.as_bytes() + .iter() + .try_for_each(|c| nb::block!(self.write(*c))) + .map_err(|_| core::fmt::Error) + } +} + +/// Specific instance implementation for the UART0 peripheral +impl Instance for UART0 { + #[inline(always)] + fn as_uart0(&self) -> &RegisterBlock { + self + } +} + +/// Specific instance implementation for the UART1 peripheral +impl Instance for UART1 { + #[inline(always)] + fn as_uart0(&self) -> &RegisterBlock { + self + } +} diff --git a/esp32c3-hal/src/timer.rs b/esp32c3-hal/src/timer.rs new file mode 100644 index 000000000..9b5fb7a38 --- /dev/null +++ b/esp32c3-hal/src/timer.rs @@ -0,0 +1,200 @@ +use embedded_hal::{ + timer::{Cancel, CountDown, Periodic}, + watchdog::WatchdogDisable, +}; +use void::Void; + +use crate::pac::{timg0::RegisterBlock, TIMG0, TIMG1}; + +pub struct Timer { + timg: T, +} + +pub enum Error { + /// Report that the timer is active and certain management + /// operations cannot be performed safely + TimerActive, + /// Report that the timer is inactive and thus not + /// ever reaching any potentially configured alarm value + TimerInactive, + /// Report that the alarm functionality is disabled + AlarmInactive, +} + +impl Timer +where + T: Instance, +{ + pub fn new(timg: T) -> Self { + Self { timg } + } +} + +pub trait Instance { + fn as_timg0(&self) -> &RegisterBlock; + + fn reset_counter(&mut self) { + self.as_timg0() + .t0loadlo + .write(|w| unsafe { w.t0_load_lo().bits(0) }); + + self.as_timg0() + .t0loadhi + .write(|w| unsafe { w.t0_load_hi().bits(0) }); + + self.as_timg0() + .t0load + .write(|w| unsafe { w.t0_load().bits(1) }); + } + + fn set_counter_active(&mut self, state: bool) { + self.as_timg0().t0config.modify(|_, w| w.t0_en().bit(state)); + } + + fn is_counter_active(&mut self) -> bool { + self.as_timg0().t0config.read().t0_en().bit_is_set() + } + + fn set_counter_decrementing(&mut self, decrementing: bool) { + self.as_timg0() + .t0config + .modify(|_, w| w.t0_increase().bit(!decrementing)); + } + + fn set_auto_reload(&mut self, auto_reload: bool) { + self.as_timg0() + .t0config + .modify(|_, w| w.t0_autoreload().bit(auto_reload)); + } + + fn set_alarm_active(&mut self, state: bool) { + self.as_timg0() + .t0config + .modify(|_, w| w.t0_alarm_en().bit(state)); + } + + fn is_alarm_active(&mut self) -> bool { + self.as_timg0().t0config.read().t0_alarm_en().bit_is_set() + } + + fn load_alarm_value(&mut self, value: u64) { + let value = value & 0x3F_FFFF_FFFF_FFFF; + let high = (value >> 32) as u32; + let low = (value & 0xFFFF_FFFF) as u32; + + self.as_timg0() + .t0alarmlo + .write(|w| unsafe { w.t0_alarm_lo().bits(low) }); + self.as_timg0() + .t0alarmhi + .write(|w| unsafe { w.t0_alarm_hi().bits(high) }); + } + + fn set_wdt_enabled(&mut self, enabled: bool) { + self.as_timg0() + .wdtwprotect + .write(|w| unsafe { w.wdt_wkey().bits(0u32) }); + + self.as_timg0() + .wdtconfig0 + .write(|w| w.wdt_en().bit(enabled)); + + self.as_timg0() + .wdtwprotect + .write(|w| unsafe { w.wdt_wkey().bits(0x50D8_3AA1u32) }); + } +} + +impl CountDown for Timer +where + T: Instance, +{ + type Time = u64; + + fn start