diff --git a/CHANGELOG.md b/CHANGELOG.md index 367a6ad75..7827f930b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `embassy-time-driver` to `esp-hal-common` due to updating `embassy-time` to `v0.3.0` (#1075) - ESP32-S3: Added support for 80Mhz PSRAM (#1069) - ESP32-C3/S3: Add workaround for USB pin exchange on usb-serial-jtag (#1104). - +- ESP32C6: Added LP_UART initialization (#1113) ### Changed - Set up interrupts for the DMA and async enabled peripherals only when `async` feature is provided (#1042) diff --git a/esp-hal-common/src/uart.rs b/esp-hal-common/src/uart.rs index c84c08508..2fa605a6c 100644 --- a/esp-hal-common/src/uart.rs +++ b/esp-hal-common/src/uart.rs @@ -1790,3 +1790,197 @@ mod asynch { } } } + +#[cfg(lp_uart)] +pub mod lp_uart { + use crate::{ + gpio::{lp_gpio::LowPowerPin, Floating, Input, Output, PushPull}, + peripherals::{LP_CLKRST, LP_UART}, + uart::{config, config::Config}, + }; + /// UART driver + pub struct LpUart { + uart: LP_UART, + } + + impl LpUart { + /// Initialize the UART driver using the default configuration + // TODO: CTS and RTS pins + pub fn new( + uart: LP_UART, + _tx: LowPowerPin, 5>, + _rx: LowPowerPin, 4>, + ) -> Self { + let lp_io = unsafe { &*crate::peripherals::LP_IO::PTR }; + let lp_aon = unsafe { &*crate::peripherals::LP_AON::PTR }; + + lp_aon + .gpio_mux() + .modify(|r, w| w.sel().variant(r.sel().bits() | 1 << 4)); + lp_aon + .gpio_mux() + .modify(|r, w| w.sel().variant(r.sel().bits() | 1 << 5)); + + lp_io.gpio4().modify(|_, w| w.lp_gpio4_mcu_sel().variant(1)); + lp_io.gpio5().modify(|_, w| w.lp_gpio5_mcu_sel().variant(1)); + + Self::new_with_config(uart, Config::default()) + } + + /// Initialize the UART driver using the provided configuration + pub fn new_with_config(uart: LP_UART, config: Config) -> Self { + let mut me = Self { uart }; + + // Set UART mode - do nothing for LP + + // Disable UART parity + // 8-bit world + // 1-bit stop bit + me.uart.conf0().modify(|_, w| unsafe { + w.parity() + .clear_bit() + .parity_en() + .clear_bit() + .bit_num() + .bits(0x3) + .stop_bit_num() + .bits(0x1) + }); + // Set tx idle + me.uart + .idle_conf() + .modify(|_, w| unsafe { w.tx_idle_num().bits(0) }); + // Disable hw-flow control + me.uart + .hwfc_conf() + .modify(|_, w| w.rx_flow_en().clear_bit()); + + // Get source clock frequency + // default == SOC_MOD_CLK_RTC_FAST == 2 + + // LP_CLKRST.lpperi.lp_uart_clk_sel = 0; + unsafe { &*LP_CLKRST::PTR } + .lpperi() + .modify(|_, w| w.lp_uart_clk_sel().clear_bit()); + + // Override protocol parameters from the configuration + // uart_hal_set_baudrate(&hal, cfg->uart_proto_cfg.baud_rate, sclk_freq); + me.change_baud(config.baudrate); + // uart_hal_set_parity(&hal, cfg->uart_proto_cfg.parity); + me.change_parity(config.parity); + // uart_hal_set_data_bit_num(&hal, cfg->uart_proto_cfg.data_bits); + me.change_data_bits(config.data_bits); + // uart_hal_set_stop_bits(&hal, cfg->uart_proto_cfg.stop_bits); + me.change_stop_bits(config.stop_bits); + // uart_hal_set_tx_idle_num(&hal, LP_UART_TX_IDLE_NUM_DEFAULT); + me.change_tx_idle(0); // LP_UART_TX_IDLE_NUM_DEFAULT == 0 + + // Reset Tx/Rx FIFOs + me.rxfifo_reset(); + me.txfifo_reset(); + + me + } + + fn rxfifo_reset(&mut self) { + self.uart.conf0().modify(|_, w| w.rxfifo_rst().set_bit()); + self.update(); + + self.uart.conf0().modify(|_, w| w.rxfifo_rst().clear_bit()); + self.update(); + } + + fn txfifo_reset(&mut self) { + self.uart.conf0().modify(|_, w| w.txfifo_rst().set_bit()); + self.update(); + + self.uart.conf0().modify(|_, w| w.txfifo_rst().clear_bit()); + self.update(); + } + + fn update(&mut self) { + self.uart + .reg_update() + .modify(|_, w| w.reg_update().set_bit()); + while self.uart.reg_update().read().reg_update().bit_is_set() { + // wait + } + } + + fn change_baud(&mut self, baudrate: u32) { + // we force the clock source to be XTAL and don't use the decimal part of + // the divider + // TODO: Currently it's not possible to use XtalD2Clk + let clk = 16_000_000; + let max_div = 0b1111_1111_1111 - 1; + let clk_div = ((clk) + (max_div * baudrate) - 1) / (max_div * baudrate); + + self.uart.clk_conf().modify(|_, w| unsafe { + w.sclk_div_a() + .bits(0) + .sclk_div_b() + .bits(0) + .sclk_div_num() + .bits(clk_div as u8 - 1) + .sclk_sel() + .bits(0x3) // TODO: this probably shouldn't be hard-coded + .sclk_en() + .set_bit() + }); + + let clk = clk / clk_div; + let divider = clk / baudrate; + let divider = divider as u16; + + self.uart + .clkdiv() + .write(|w| unsafe { w.clkdiv().bits(divider).frag().bits(0) }); + + self.update(); + } + + fn change_parity(&mut self, parity: config::Parity) -> &mut Self { + if parity != config::Parity::ParityNone { + self.uart + .conf0() + .modify(|_, w| w.parity().bit((parity as u8 & 0x1) != 0)); + } + + self.uart.conf0().modify(|_, w| match parity { + config::Parity::ParityNone => w.parity_en().clear_bit(), + config::Parity::ParityEven => w.parity_en().set_bit().parity().clear_bit(), + config::Parity::ParityOdd => w.parity_en().set_bit().parity().set_bit(), + }); + + self + } + + fn change_data_bits(&mut self, data_bits: config::DataBits) -> &mut Self { + self.uart + .conf0() + .modify(|_, w| unsafe { w.bit_num().bits(data_bits as u8) }); + + self.update(); + + self + } + + fn change_stop_bits(&mut self, stop_bits: config::StopBits) -> &mut Self { + self.uart + .conf0() + .modify(|_, w| unsafe { w.stop_bit_num().bits(stop_bits as u8) }); + + self.update(); + self + } + + fn change_tx_idle(&mut self, idle_num: u16) -> &mut Self { + self.uart + .idle_conf() + .modify(|_, w| unsafe { w.tx_idle_num().bits(idle_num) }); + + self.update(); + self + } + } +} diff --git a/esp-hal-procmacros/src/lib.rs b/esp-hal-procmacros/src/lib.rs index 18a4f153f..8c01b4caa 100644 --- a/esp-hal-procmacros/src/lib.rs +++ b/esp-hal-procmacros/src/lib.rs @@ -31,7 +31,7 @@ //! - `rtc_slow` - Use RTC slow RAM (not all targets support slow RTC RAM) //! - `uninitialized` - Skip initialization of the memory //! - `zeroed` - Initialize the memory to zero -//! +//! //! ## Examples //! //! #### `interrupt` macro @@ -615,6 +615,7 @@ pub fn load_lp_code(input: TokenStream) -> TokenStream { use #hal_crate::lp_core::LpCoreWakeupSource; use #hal_crate::gpio::lp_gpio::LowPowerPin; use #hal_crate::gpio::*; + use #hal_crate::uart::lp_uart::LpUart; }; #[cfg(any(feature = "esp32s2", feature = "esp32s3"))] let imports = quote! { @@ -705,9 +706,12 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { let f = parse_macro_input!(input as ItemFn); let mut argument_types = Vec::new(); + let mut create_peripheral = Vec::new(); let mut used_pins: Vec = Vec::new(); - for arg in &f.sig.inputs { + + for (num, arg) in f.sig.inputs.iter().enumerate() { + let param_name = format_ident!("param{}", num); match arg { FnArg::Receiver(_) => { return parse::Error::new(arg.span(), "invalid argument") @@ -715,19 +719,30 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { .into(); } FnArg::Typed(t) => { - if get_simplename(&t.ty) != "GpioPin" { - return parse::Error::new(arg.span(), "invalid argument to main") - .to_compile_error() - .into(); + match get_simplename(&t.ty).as_str() { + "GpioPin" => { + let pin = extract_pin(&t.ty); + if used_pins.contains(&pin) { + return parse::Error::new(arg.span(), "duplicate pin") + .to_compile_error() + .into(); + } + used_pins.push(pin); + create_peripheral.push(quote!( + let mut #param_name = unsafe { the_hal::gpio::conjour().unwrap() }; + )); + } + "LpUart" => { + create_peripheral.push(quote!( + let mut #param_name = unsafe { the_hal::uart::conjour().unwrap() }; + )); + } + _ => { + return parse::Error::new(arg.span(), "invalid argument to main") + .to_compile_error() + .into(); + } } - let pin = extract_pin(&t.ty); - if used_pins.contains(&pin) { - return parse::Error::new(arg.span(), "duplicate pin") - .to_compile_error() - .into(); - } - used_pins.push(pin); - argument_types.push(t); } } @@ -752,7 +767,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { use #hal_crate as the_hal; #( - let mut #param_names = unsafe { the_hal::gpio::conjour().unwrap() }; + #create_peripheral; )* main(#(#param_names),*); diff --git a/esp32c6-hal/examples/lp_core_uart.rs b/esp32c6-hal/examples/lp_core_uart.rs new file mode 100644 index 000000000..7be142817 --- /dev/null +++ b/esp32c6-hal/examples/lp_core_uart.rs @@ -0,0 +1,79 @@ +//! This shows a very basic example of running code on the LP core. +//! +//! Code on LP core uses LP_UART initialized on HP core. For more information +//! check `lp_core_uart` example in the `esp32c6-lp-hal. +//! Make sure to first compile the `esp32c6-lp-hal/examples/uart.rs` example + +#![no_std] +#![no_main] + +use esp32c6_hal::{ + clock::ClockControl, + gpio::lp_gpio::IntoLowPowerPin, + lp_core, + peripherals::Peripherals, + prelude::*, + uart::{ + config::{Config, DataBits, Parity, StopBits}, + lp_uart::LpUart, + TxRxPins, + }, + Uart, + IO, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Set up (HP) UART1: + + let config = Config { + baudrate: 115_200, + data_bits: DataBits::DataBits8, + parity: Parity::ParityNone, + stop_bits: StopBits::STOP1, + }; + + let pins = TxRxPins::new_tx_rx( + io.pins.gpio6.into_push_pull_output(), + io.pins.gpio7.into_floating_input(), + ); + + let mut uart1 = Uart::new_with_config(peripherals.UART1, config, Some(pins), &clocks); + + // Set up (LP) UART: + + let lp_tx = io.pins.gpio5.into_low_power().into_push_pull_output(); + let lp_rx = io.pins.gpio4.into_low_power().into_floating_input(); + + let lp_uart = LpUart::new(peripherals.LP_UART, lp_tx, lp_rx); + + let mut lp_core = esp32c6_hal::lp_core::LpCore::new(peripherals.LP_CORE); + lp_core.stop(); + println!("lp core stopped"); + + // load code to LP core + let lp_core_code = load_lp_code!( + "../esp32c6-lp-hal/target/riscv32imac-unknown-none-elf/release/examples/uart" + ); + + // start LP core + lp_core_code.run(&mut lp_core, lp_core::LpCoreWakeupSource::HpCpu, lp_uart); + println!("lpcore run"); + + loop { + let read = nb::block!(uart1.read()); + + match read { + Ok(read) => println!("Read 0x{:02x}", read), + Err(err) => println!("Error {:?}", err), + } + } +} diff --git a/esp32c6-lp-hal/CHANGELOG.md b/esp32c6-lp-hal/CHANGELOG.md index a983d12f5..e87104680 100644 --- a/esp32c6-lp-hal/CHANGELOG.md +++ b/esp32c6-lp-hal/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add the `esp32c6-lp-hal` package (#714) - Add GPIO (output) and delay functionality to `esp32c6-lp-hal` (#715) - Add GPIO input support and implement additional `embedded-hal` output traits for the C6's LP core [#720] +- Add LP_UART basic driver (#1113) ### Changed diff --git a/esp32c6-lp-hal/Cargo.toml b/esp32c6-lp-hal/Cargo.toml index 0dc5aa67c..1038b826e 100644 --- a/esp32c6-lp-hal/Cargo.toml +++ b/esp32c6-lp-hal/Cargo.toml @@ -24,12 +24,13 @@ categories = [ critical-section = { version = "1.1.2", features = ["restore-state-u8"] } embedded-hal = { version = "0.2.7", features = ["unproven"] } esp32c6-lp = { version = "0.1.0", features = ["critical-section"] } +nb = "1.1.0" +paste = "1.0.14" procmacros = { package = "esp-hal-procmacros", path = "../esp-hal-procmacros", features = ["esp32c6-lp"] } riscv = "0.10.1" -paste = "1.0.14" [dev-dependencies] -panic-halt = "0.2.0" +panic-halt = "0.2.0" [features] default = [] diff --git a/esp32c6-lp-hal/examples/uart.rs b/esp32c6-lp-hal/examples/uart.rs new file mode 100644 index 000000000..acd18ae3e --- /dev/null +++ b/esp32c6-lp-hal/examples/uart.rs @@ -0,0 +1,23 @@ +//! Uses `LP_UART` and logs "Hello World from LP Core". +//! Uses GPIO4 for RX and GPIO5 for TX. GPIOs can't be changed. +//! It is neccessary to use Serial-Uart bridge connected to TX and RX to see +//! logs from LP_UART. Make sure the LP RAM is cleared before loading the code. + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32c6_lp_hal::{delay::Delay, prelude::*, uart::LpUart}; +use panic_halt as _; + +#[entry] +fn main(mut uart: LpUart) -> ! { + let _peripherals = esp32c6_lp::Peripherals::take().unwrap(); + + let mut delay = Delay::new(); + loop { + writeln!(uart, "Hello World from LP Core").unwrap(); + delay.delay_ms(1500); + } +} diff --git a/esp32c6-lp-hal/src/lib.rs b/esp32c6-lp-hal/src/lib.rs index 650ac2307..4b56ff75e 100644 --- a/esp32c6-lp-hal/src/lib.rs +++ b/esp32c6-lp-hal/src/lib.rs @@ -5,6 +5,8 @@ use core::arch::global_asm; pub mod delay; pub mod gpio; +pub mod prelude; +pub mod uart; pub mod riscv { //! Low level access to RISC-V processors. @@ -13,7 +15,6 @@ pub mod riscv { pub use riscv::*; } -pub mod prelude; // LP_FAST_CLK is not very accurate, for now use a rough estimate const LP_FAST_CLK_HZ: u32 = 16_000_000; diff --git a/esp32c6-lp-hal/src/uart.rs b/esp32c6-lp-hal/src/uart.rs new file mode 100644 index 000000000..10b99f873 --- /dev/null +++ b/esp32c6-lp-hal/src/uart.rs @@ -0,0 +1,175 @@ +//! Low-power UART driver + +use esp32c6_lp::LP_UART; + +const UART_FIFO_SIZE: u16 = 128; + +#[doc(hidden)] +pub unsafe fn conjour() -> Option { + Some(LpUart { + uart: LP_UART::steal(), + }) +} + +#[derive(Debug)] +pub enum Error {} + +/// UART configuration +pub mod config { + /// Number of data bits + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + pub enum DataBits { + DataBits5 = 0, + DataBits6 = 1, + DataBits7 = 2, + DataBits8 = 3, + } + + /// Parity check + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + pub enum Parity { + ParityNone = 0, + ParityEven = 1, + ParityOdd = 2, + } + + /// Number of stop bits + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + pub enum StopBits { + /// 1 stop bit + STOP1 = 1, + /// 1.5 stop bits + STOP1P5 = 2, + /// 2 stop bits + STOP2 = 3, + } + + /// UART configuration + #[derive(Debug, Copy, Clone)] + pub struct Config { + pub baudrate: u32, + pub data_bits: DataBits, + pub parity: Parity, + pub stop_bits: StopBits, + } + + impl Config { + pub fn baudrate(mut self, baudrate: u32) -> Self { + self.baudrate = baudrate; + self + } + + pub fn parity_none(mut self) -> Self { + self.parity = Parity::ParityNone; + self + } + + pub fn parity_even(mut self) -> Self { + self.parity = Parity::ParityEven; + self + } + + pub fn parity_odd(mut self) -> Self { + self.parity = Parity::ParityOdd; + self + } + + pub fn data_bits(mut self, data_bits: DataBits) -> Self { + self.data_bits = data_bits; + self + } + + pub fn stop_bits(mut self, stop_bits: StopBits) -> Self { + self.stop_bits = stop_bits; + self + } + } + + impl Default for Config { + fn default() -> Config { + Config { + baudrate: 115200, + data_bits: DataBits::DataBits8, + parity: Parity::ParityNone, + stop_bits: StopBits::STOP1, + } + } + } +} + +/// LP-UART driver +pub struct LpUart { + uart: LP_UART, +} + +impl LpUart { + fn read_byte(&mut self) -> nb::Result { + if self.get_rx_fifo_count() > 0 { + let byte = self.uart.fifo().read().rxfifo_rd_byte().bits(); + Ok(byte) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn write_byte(&mut self, byte: u8) -> nb::Result<(), Error> { + if self.get_tx_fifo_count() < UART_FIFO_SIZE { + self.uart + .fifo() + .write(|w| unsafe { w.rxfifo_rd_byte().bits(byte) }); + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn write_bytes(&mut self, data: &[u8]) -> nb::Result<(), Error> { + data.iter().try_for_each(|c| self.write_byte(*c)) + } + + fn flush_tx(&mut self) -> nb::Result<(), Error> { + if self.is_tx_idle() { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + + fn get_rx_fifo_count(&mut self) -> u16 { + self.uart.status().read().rxfifo_cnt().bits().into() + } + + fn get_tx_fifo_count(&mut self) -> u16 { + self.uart.status().read().txfifo_cnt().bits().into() + } + + fn is_tx_idle(&self) -> bool { + self.uart.fsm_status().read().st_utx_out().bits() == 0 + } +} + +impl core::fmt::Write for LpUart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write_bytes(s.as_bytes()).map_err(|_| core::fmt::Error) + } +} + +impl embedded_hal::serial::Read for LpUart { + type Error = Error; + + fn read(&mut self) -> nb::Result { + self.read_byte() + } +} + +impl embedded_hal::serial::Write for LpUart { + type Error = Error; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.write_byte(word) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.flush_tx() + } +}