From 2bc97768b6a516128c5f11087741430d8f3ae1fe Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Thu, 21 Oct 2021 14:53:34 -0700 Subject: [PATCH] Add a minimal HAL crate for the ESP32 with a serial example --- esp32-hal/.cargo/config.toml | 12 +++ esp32-hal/Cargo.toml | 22 ++++ esp32-hal/README.md | 1 + esp32-hal/build.rs | 21 ++++ esp32-hal/examples/serial.rs | 25 +++++ esp32-hal/memory.x | 193 +++++++++++++++++++++++++++++++++ esp32-hal/rom.x | 4 + esp32-hal/rust-toolchain.toml | 4 + esp32-hal/src/lib.rs | 57 ++++++++++ esp32-hal/src/prelude.rs | 9 ++ esp32-hal/src/serial.rs | 180 +++++++++++++++++++++++++++++++ esp32-hal/src/timer.rs | 196 ++++++++++++++++++++++++++++++++++ 12 files changed, 724 insertions(+) create mode 100644 esp32-hal/.cargo/config.toml create mode 100644 esp32-hal/Cargo.toml create mode 100644 esp32-hal/README.md create mode 100644 esp32-hal/build.rs create mode 100644 esp32-hal/examples/serial.rs create mode 100644 esp32-hal/memory.x create mode 100644 esp32-hal/rom.x create mode 100644 esp32-hal/rust-toolchain.toml create mode 100644 esp32-hal/src/lib.rs create mode 100644 esp32-hal/src/prelude.rs create mode 100644 esp32-hal/src/serial.rs create mode 100644 esp32-hal/src/timer.rs diff --git a/esp32-hal/.cargo/config.toml b/esp32-hal/.cargo/config.toml new file mode 100644 index 000000000..e64a8d308 --- /dev/null +++ b/esp32-hal/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.xtensa-esp32-none-elf] +runner = "xtensa-esp32-elf-gdb -q -x xtensa.gdb" + +[build] +rustflags = [ + "-C", "link-arg=-nostartfiles", + "-C", "link-arg=-Wl,-Tlink.x", +] +target = "xtensa-esp32-none-elf" + +[unstable] +build-std = ["core"] diff --git a/esp32-hal/Cargo.toml b/esp32-hal/Cargo.toml new file mode 100644 index 000000000..c6cfad159 --- /dev/null +++ b/esp32-hal/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "esp32-hal" +version = "0.1.0" +edition = "2018" + +[dependencies] +bare-metal = "1.0" +embedded-hal = { version = "0.2", features = ["unproven"] } +nb = "1.0" +void = { version = "1.0", default-features = false } +xtensa-lx = { version = "0.4", features = ["lx6"] } +xtensa-lx-rt = { version = "0.7", optional = true, features = ["lx6"] } + +[dependencies.esp32] +path = "../../esp-pacs/esp32" + +[dev-dependencies] +panic-halt = "0.2" + +[features] +default = ["rt"] +rt = ["esp32/rt", "xtensa-lx-rt"] diff --git a/esp32-hal/README.md b/esp32-hal/README.md new file mode 100644 index 000000000..5ebd100c6 --- /dev/null +++ b/esp32-hal/README.md @@ -0,0 +1 @@ +# esp32-hal diff --git a/esp32-hal/build.rs b/esp32-hal/build.rs new file mode 100644 index 000000000..bd5affeec --- /dev/null +++ b/esp32-hal/build.rs @@ -0,0 +1,21 @@ +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!("memory.x")) + .unwrap(); + + File::create(out.join("alias.x")) + .unwrap() + .write_all(include_bytes!("rom.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/esp32-hal/examples/serial.rs b/esp32-hal/examples/serial.rs new file mode 100644 index 000000000..103178120 --- /dev/null +++ b/esp32-hal/examples/serial.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32_hal::{pac, prelude::*, Serial, Timer}; +use nb::block; +use panic_halt as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = pac::Peripherals::take().unwrap(); + + let mut timer0 = Timer::new(peripherals.TIMG0); + let mut serial0 = Serial::new(peripherals.UART0).unwrap(); + + timer0.disable(); + timer0.start(10_000_000u64); + + loop { + writeln!(serial0, "Hello world!").unwrap(); + block!(timer0.wait()).unwrap(); + } +} diff --git a/esp32-hal/memory.x b/esp32-hal/memory.x new file mode 100644 index 000000000..0c4200e60 --- /dev/null +++ b/esp32-hal/memory.x @@ -0,0 +1,193 @@ +/* This memory map assumes the flash cache is on; + the blocks used are excluded from the various memory ranges + + see: https://github.com/espressif/esp-idf/blob/master/components/soc/src/esp32/soc_memory_layout.c + for details + */ + +/* override entry point */ +ENTRY(ESP32Reset) + +/* reserved at the start of DRAM for e.g. teh BT stack */ +RESERVE_DRAM = 0; + +/* reserved at the start of the RTC memories for use by the ULP processor */ +RESERVE_RTC_FAST = 0; +RESERVE_RTC_SLOW = 0; + +/* define stack size for both cores */ +STACK_SIZE = 8k; + +/* Specify main memory areas */ +MEMORY +{ + reserved_cache_seg : ORIGIN = 0x40070000, len = 64k /* SRAM0; reserved for usage as flash cache*/ + vectors_seg ( RX ) : ORIGIN = 0x40080000, len = 1k /* SRAM0 */ + iram_seg ( RX ) : ORIGIN = 0x40080400, len = 128k-0x400 /* SRAM0 */ + + reserved_for_rom_seg : ORIGIN = 0x3FFAE000, len = 8k /* SRAM2; reserved for usage by the ROM */ + dram_seg ( RW ) : ORIGIN = 0x3FFB0000 + RESERVE_DRAM, len = 176k - RESERVE_DRAM /* SRAM2+1; first 64kB used by BT if enable */ + reserved_for_boot_seg : ORIGIN = 0x3FFDC200, len = 144k /* SRAM1; reserved for static ROM usage; can be used for heap */ + + /* external flash + The 0x20 offset is a convenience for the app binary image generation. + Flash cache has 64KB pages. The .bin file which is flashed to the chip + has a 0x18 byte file header, and each segment has a 0x08 byte segment + header. Setting this offset makes it simple to meet the flash cache MMU's + constraint that (paddr % 64KB == vaddr % 64KB).) + */ + irom_seg ( RX ) : ORIGIN = 0x400D0020, len = 3M - 0x20 + drom_seg ( R ) : ORIGIN = 0x3F400020, len = 4M - 0x20 + + + /* RTC fast memory (executable). Persists over deep sleep. Only for core 0 (PRO_CPU) */ + rtc_fast_iram_seg(RWX) : ORIGIN = 0x400C0000, len = 8k + + /* RTC fast memory (same block as above), viewed from data bus. Only for core 0 (PRO_CPU) */ + rtc_fast_dram_seg(RW) : ORIGIN = 0x3FF80000 + RESERVE_RTC_FAST, len = 8k - RESERVE_RTC_FAST + + /* RTC slow memory (data accessible). Persists over deep sleep. */ + rtc_slow_seg(RW) : ORIGIN = 0x50000000 + RESERVE_RTC_SLOW, len = 8k - RESERVE_RTC_SLOW + + /* external memory, including data and text, + 4MB is the maximum, if external psram is bigger, paging is required */ + psram_seg(RWX) : ORIGIN = 0x3F800000, len = 4M +} + +/* map generic regions to output sections */ +INCLUDE "alias.x" + +/* esp32 specific regions */ +SECTIONS { + .rtc_fast.text : { + . = ALIGN(4); + *(.rtc_fast.literal .rtc_fast.text .rtc_fast.literal.* .rtc_fast.text.*) + } > rtc_fast_iram_seg AT > RODATA + + /* + This section is required to skip rtc.text area because rtc_iram_seg and + rtc_data_seg are reflect the same address space on different buses. + */ + .rtc_fast.dummy (NOLOAD) : + { + _rtc_dummy_start = ABSOLUTE(.); /* needed to make section proper size */ + . = SIZEOF(.rtc_fast.text); + _rtc_dummy_end = ABSOLUTE(.); /* needed to make section proper size */ + } > rtc_fast_dram_seg + + + .rtc_fast.data : + { + _rtc_fast_data_start = ABSOLUTE(.); + . = ALIGN(4); + *(.rtc_fast.data .rtc_fast.data.*) + _rtc_fast_data_end = ABSOLUTE(.); + } > rtc_fast_dram_seg AT > RODATA + + .rtc_fast.bss (NOLOAD) : + { + _rtc_fast_bss_start = ABSOLUTE(.); + . = ALIGN(4); + *(.rtc_fast.bss .rtc_fast.bss.*) + _rtc_fast_bss_end = ABSOLUTE(.); + } > rtc_fast_dram_seg + + .rtc_fast.noinit (NOLOAD) : + { + . = ALIGN(4); + *(.rtc_fast.noinit .rtc_fast.noinit.*) + } > rtc_fast_dram_seg + + + .rtc_slow.text : { + . = ALIGN(4); + *(.rtc_slow.literal .rtc_slow.text .rtc_slow.literal.* .rtc_slow.text.*) + } > rtc_slow_seg AT > RODATA + + .rtc_slow.data : + { + _rtc_slow_data_start = ABSOLUTE(.); + . = ALIGN(4); + *(.rtc_slow.data .rtc_slow.data.*) + _rtc_slow_data_end = ABSOLUTE(.); + } > rtc_slow_seg AT > RODATA + + .rtc_slow.bss (NOLOAD) : + { + _rtc_slow_bss_start = ABSOLUTE(.); + . = ALIGN(4); + *(.rtc_slow.bss .rtc_slow.bss.*) + _rtc_slow_bss_end = ABSOLUTE(.); + } > rtc_slow_seg + + .rtc_slow.noinit (NOLOAD) : + { + . = ALIGN(4); + *(.rtc_slow.noinit .rtc_slow.noinit.*) + } > rtc_slow_seg + + .external.data : + { + _external_data_start = ABSOLUTE(.); + . = ALIGN(4); + *(.external.data .external.data.*) + _external_data_end = ABSOLUTE(.); + } > psram_seg AT > RODATA + + .external.bss (NOLOAD) : + { + _external_bss_start = ABSOLUTE(.); + . = ALIGN(4); + *(.external.bss .external.bss.*) + _external_bss_end = ABSOLUTE(.); + } > psram_seg + + .external.noinit (NOLOAD) : + { + . = ALIGN(4); + *(.external.noinit .external.noinit.*) + } > psram_seg + + /* must be last segment using psram_seg */ + .external_heap_start (NOLOAD) : + { + . = ALIGN (4); + _external_heap_start = ABSOLUTE(.); + } > psram_seg + + + /* wifi data */ + + .rwtext.wifi : + { + . = ALIGN(4); + *( .wifi0iram .wifi0iram.*) + *( .wifirxiram .wifirxiram.*) + *( .iram1 .iram1.*) + } > RWTEXT AT > RODATA + + .data.wifi : + { + . = ALIGN(4); + *( .dram1 .dram1.*) + } > RWDATA AT > RODATA +} + +_external_ram_start = ABSOLUTE(ORIGIN(psram_seg)); +_external_ram_end = ABSOLUTE(ORIGIN(psram_seg)+LENGTH(psram_seg)); + +_heap_end = ABSOLUTE(ORIGIN(dram_seg))+LENGTH(dram_seg)+LENGTH(reserved_for_boot_seg) - 2*STACK_SIZE; +_text_heap_end = ABSOLUTE(ORIGIN(iram_seg)+LENGTH(iram_seg)); +_external_heap_end = ABSOLUTE(ORIGIN(psram_seg)+LENGTH(psram_seg)); + +_stack_start_cpu1 = _heap_end; +_stack_end_cpu1 = _stack_start_cpu1 + STACK_SIZE; +_stack_start_cpu0 = _stack_end_cpu1; +_stack_end_cpu0 = _stack_start_cpu0 + STACK_SIZE; + +EXTERN(DefaultHandler); + +EXTERN(WIFI_EVENT); /* Force inclusion of WiFi libraries */ + +INCLUDE "device.x" + diff --git a/esp32-hal/rom.x b/esp32-hal/rom.x new file mode 100644 index 000000000..af743b0cb --- /dev/null +++ b/esp32-hal/rom.x @@ -0,0 +1,4 @@ +REGION_ALIAS("ROTEXT", irom_seg); +REGION_ALIAS("RWTEXT", iram_seg); +REGION_ALIAS("RODATA", drom_seg); +REGION_ALIAS("RWDATA", dram_seg); diff --git a/esp32-hal/rust-toolchain.toml b/esp32-hal/rust-toolchain.toml new file mode 100644 index 000000000..90aabae96 --- /dev/null +++ b/esp32-hal/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "esp" +components = ["rustfmt", "rustc-dev"] +targets = ["xtensa-esp32-none-elf"] diff --git a/esp32-hal/src/lib.rs b/esp32-hal/src/lib.rs new file mode 100644 index 000000000..c0cab0c0a --- /dev/null +++ b/esp32-hal/src/lib.rs @@ -0,0 +1,57 @@ +#![no_std] + +pub use embedded_hal as ehal; +pub use esp32 as pac; + +pub mod prelude; +pub mod serial; +pub mod timer; + +pub use serial::Serial; +pub use timer::Timer; + +#[no_mangle] +extern "C" fn DefaultHandler(_level: u32, _interrupt: pac::Interrupt) {} + +/// Function initializes ESP32 specific memories (RTC slow and fast) and +/// then calls original Reset function +/// +/// ENTRY point is defined in memory.x +/// *Note: the pre_init function is called in the original reset handler +/// after the initializations done in this function* +#[cfg(feature = "rt")] +#[doc(hidden)] +#[no_mangle] +pub unsafe extern "C" fn ESP32Reset() -> ! { + // These symbols come from `memory.x` + extern "C" { + static mut _rtc_fast_bss_start: u32; + static mut _rtc_fast_bss_end: u32; + + static mut _rtc_slow_bss_start: u32; + static mut _rtc_slow_bss_end: u32; + + static mut _stack_end_cpu0: u32; + } + + // copying data from flash to various data segments is done by the bootloader + // initialization to zero needs to be done by the application + + // Initialize RTC RAM + xtensa_lx_rt::zero_bss(&mut _rtc_fast_bss_start, &mut _rtc_fast_bss_end); + xtensa_lx_rt::zero_bss(&mut _rtc_slow_bss_start, &mut _rtc_slow_bss_end); + + // set stack pointer to end of memory: no need to retain stack up to this point + xtensa_lx::set_stack_pointer(&mut _stack_end_cpu0); + + // continue with default reset handler + xtensa_lx_rt::Reset(); +} + +/// The esp32 has a first stage bootloader that handles loading program data into the right place +/// therefore we skip loading it again. +#[no_mangle] +#[rustfmt::skip] +pub extern "Rust" fn __init_data() -> bool { + false +} diff --git a/esp32-hal/src/prelude.rs b/esp32-hal/src/prelude.rs new file mode 100644 index 000000000..8a9e87a91 --- /dev/null +++ b/esp32-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/esp32-hal/src/serial.rs b/esp32-hal/src/serial.rs new file mode 100644 index 000000000..8a2897089 --- /dev/null +++ b/esp32-hal/src/serial.rs @@ -0,0 +1,180 @@ +//! 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, UART2}; + +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().into() + } + + /// 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().into() + } + + /// Check if the UART TX statemachine is is idle + fn is_tx_idle(&mut self) -> bool { + self.as_uart0().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().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 + } +} + +/// Specific instance implementation for the UART2 peripheral +impl Instance for UART2 { + #[inline(always)] + fn as_uart0(&self) -> &RegisterBlock { + self + } +} diff --git a/esp32-hal/src/timer.rs b/esp32-hal/src/timer.rs new file mode 100644 index 000000000..180e42a16 --- /dev/null +++ b/esp32-hal/src/timer.rs @@ -0,0 +1,196 @@ +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() + .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