esp-hal/esp-println/src/lib.rs
Scott Mabin 85d30e9816
Updates for Xtensa enabled 1.83 compiler (#2615)
* test ci

* fix ci with new compiler

* god bless clippy fix

* refmuts

* clippy

* tests work

* use new compiler

* use new compiler

* bump MSRV of esp-hal and crates that depend on esp-hal

* fix eyesore

* clippy again

* remove hardcoded compiler version

* bump rust-version as well

* note MSRV bump in changelog
2024-12-17 15:02:03 +00:00

502 lines
14 KiB
Rust

#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![allow(rustdoc::bare_urls)]
#![no_std]
#[cfg(feature = "defmt-espflash")]
pub mod defmt;
#[cfg(feature = "log")]
pub mod logger;
/// Prints to the selected output, with a newline.
#[cfg(not(feature = "no-op"))]
#[macro_export]
macro_rules! println {
($($arg:tt)*) => {{
{
use core::fmt::Write;
writeln!($crate::Printer, $($arg)*).ok();
}
}};
}
/// Prints to the selected output.
#[cfg(not(feature = "no-op"))]
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{
{
use core::fmt::Write;
write!($crate::Printer, $($arg)*).ok();
}
}};
}
/// Prints to the configured output, with a newline.
#[cfg(feature = "no-op")]
#[macro_export]
macro_rules! println {
($($arg:tt)*) => {{}};
}
/// Prints to the configured output.
#[cfg(feature = "no-op")]
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{}};
}
/// Prints and returns the value of a given expression for quick and dirty
/// debugging.
// implementation adapted from `std::dbg`
#[macro_export]
macro_rules! dbg {
// NOTE: We cannot use `concat!` to make a static string as a format argument
// of `eprintln!` because `file!` could contain a `{` or
// `$val` expression could be a block (`{ .. }`), in which case the `println!`
// will be malformed.
() => {
$crate::println!("[{}:{}]", ::core::file!(), ::core::line!())
};
($val:expr $(,)?) => {
// Use of `match` here is intentional because it affects the lifetimes
// of temporaries - https://stackoverflow.com/a/48732525/1063961
match $val {
tmp => {
$crate::println!("[{}:{}] {} = {:#?}",
::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp);
tmp
}
}
};
($($val:expr),+ $(,)?) => {
($($crate::dbg!($val)),+,)
};
}
/// The printer that is used by the `print!` and `println!` macros.
pub struct Printer;
impl core::fmt::Write for Printer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
Printer::write_bytes(s.as_bytes());
Ok(())
}
}
impl Printer {
/// Writes a byte slice to the configured output.
pub fn write_bytes(bytes: &[u8]) {
with(|token| {
PrinterImpl::write_bytes_in_cs(bytes, token);
PrinterImpl::flush(token);
})
}
}
#[cfg(feature = "jtag-serial")]
type PrinterImpl = serial_jtag_printer::Printer;
#[cfg(feature = "uart")]
type PrinterImpl = uart_printer::Printer;
#[cfg(feature = "auto")]
type PrinterImpl = auto_printer::Printer;
#[cfg(all(
feature = "auto",
any(
feature = "esp32c3",
feature = "esp32c6",
feature = "esp32h2",
feature = "esp32p4", // as mentioned in 'build.rs'
feature = "esp32s3"
)
))]
mod auto_printer {
use crate::{
serial_jtag_printer::Printer as PrinterSerialJtag,
uart_printer::Printer as PrinterUart,
LockToken,
};
pub struct Printer;
impl Printer {
fn use_jtag() -> bool {
// Decide if serial-jtag is used by checking SOF interrupt flag.
// SOF packet is sent by the HOST every 1ms on a full speed bus.
// Between two consecutive ticks, there will be at least 1ms (selectable tick
// rate range is 1 - 1000Hz).
// We don't reset the flag - if it was ever connected we assume serial-jtag is
// used
#[cfg(feature = "esp32c3")]
const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32;
#[cfg(feature = "esp32c6")]
const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
#[cfg(feature = "esp32h2")]
const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
#[cfg(feature = "esp32p4")]
const USB_DEVICE_INT_RAW: *const u32 = unimplemented!();
#[cfg(feature = "esp32s3")]
const USB_DEVICE_INT_RAW: *const u32 = 0x60038000 as *const u32;
const SOF_INT_MASK: u32 = 0b10;
unsafe { (USB_DEVICE_INT_RAW.read_volatile() & SOF_INT_MASK) != 0 }
}
pub fn write_bytes_in_cs(bytes: &[u8], token: LockToken<'_>) {
if Self::use_jtag() {
PrinterSerialJtag::write_bytes_in_cs(bytes, token);
} else {
PrinterUart::write_bytes_in_cs(bytes, token);
}
}
pub fn flush(token: LockToken<'_>) {
if Self::use_jtag() {
PrinterSerialJtag::flush(token);
} else {
PrinterUart::flush(token);
}
}
}
}
#[cfg(all(
feature = "auto",
not(any(
feature = "esp32c3",
feature = "esp32c6",
feature = "esp32h2",
feature = "esp32p4",
feature = "esp32s3"
))
))]
mod auto_printer {
// models that only have UART
pub type Printer = crate::uart_printer::Printer;
}
#[cfg(all(
any(feature = "jtag-serial", feature = "auto"),
any(
feature = "esp32c3",
feature = "esp32c6",
feature = "esp32h2",
feature = "esp32p4",
feature = "esp32s3"
)
))]
mod serial_jtag_printer {
use portable_atomic::{AtomicBool, Ordering};
use super::LockToken;
pub struct Printer;
#[cfg(feature = "esp32c3")]
const SERIAL_JTAG_FIFO_REG: usize = 0x6004_3000;
#[cfg(feature = "esp32c3")]
const SERIAL_JTAG_CONF_REG: usize = 0x6004_3004;
#[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
const SERIAL_JTAG_FIFO_REG: usize = 0x6000_F000;
#[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
const SERIAL_JTAG_CONF_REG: usize = 0x6000_F004;
#[cfg(feature = "esp32p4")]
const SERIAL_JTAG_FIFO_REG: usize = 0x500D_2000;
#[cfg(feature = "esp32p4")]
const SERIAL_JTAG_CONF_REG: usize = 0x500D_2004;
#[cfg(feature = "esp32s3")]
const SERIAL_JTAG_FIFO_REG: usize = 0x6003_8000;
#[cfg(feature = "esp32s3")]
const SERIAL_JTAG_CONF_REG: usize = 0x6003_8004;
/// A previous wait has timed out. We use this flag to avoid blocking
/// forever if there is no host attached.
static TIMED_OUT: AtomicBool = AtomicBool::new(false);
fn fifo_flush() {
let conf = SERIAL_JTAG_CONF_REG as *mut u32;
unsafe { conf.write_volatile(0b001) };
}
fn fifo_full() -> bool {
let conf = SERIAL_JTAG_CONF_REG as *mut u32;
unsafe { conf.read_volatile() & 0b010 == 0b000 }
}
fn fifo_write(byte: u8) {
let fifo = SERIAL_JTAG_FIFO_REG as *mut u32;
unsafe { fifo.write_volatile(byte as u32) }
}
fn wait_for_flush() -> bool {
const TIMEOUT_ITERATIONS: usize = 50_000;
// Wait for some time for the FIFO to clear.
let mut timeout = TIMEOUT_ITERATIONS;
while fifo_full() {
if timeout == 0 {
TIMED_OUT.store(true, Ordering::Relaxed);
return false;
}
timeout -= 1;
}
true
}
impl Printer {
pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
if fifo_full() {
// The FIFO is full. Let's see if we can progress.
if TIMED_OUT.load(Ordering::Relaxed) {
// Still wasn't able to drain the FIFO. Let's assume we won't be able to, and
// don't queue up more data.
// This is important so we don't block forever if there is no host attached.
return;
}
// Give the fifo some time to drain.
if !wait_for_flush() {
return;
}
} else {
// Reset the flag - we managed to clear our FIFO.
TIMED_OUT.store(false, Ordering::Relaxed);
}
for &b in bytes {
if fifo_full() {
fifo_flush();
// Wait for the FIFO to clear, we have more data to shift out.
if !wait_for_flush() {
return;
}
}
fifo_write(b);
}
}
pub fn flush(_token: LockToken<'_>) {
fifo_flush();
}
}
}
#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32"))]
mod uart_printer {
use super::LockToken;
const UART_TX_ONE_CHAR: usize = 0x4000_9200;
pub struct Printer;
impl Printer {
pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
for &b in bytes {
unsafe {
let uart_tx_one_char: unsafe extern "C" fn(u8) -> i32 =
core::mem::transmute(UART_TX_ONE_CHAR);
uart_tx_one_char(b)
};
}
}
pub fn flush(_token: LockToken<'_>) {}
}
}
#[cfg(all(any(feature = "uart", feature = "auto"), feature = "esp32s2"))]
mod uart_printer {
use super::LockToken;
pub struct Printer;
impl Printer {
pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
// On ESP32-S2 the UART_TX_ONE_CHAR ROM-function seems to have some issues.
for chunk in bytes.chunks(64) {
for &b in chunk {
unsafe {
// write FIFO
(0x3f400000 as *mut u32).write_volatile(b as u32);
};
}
// wait for TX_DONE
while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {}
unsafe {
// reset TX_DONE
(0x3f400010 as *mut u32).write_volatile(1 << 14);
}
}
}
pub fn flush(_token: LockToken<'_>) {}
}
}
#[cfg(all(
any(feature = "uart", feature = "auto"),
not(any(feature = "esp32", feature = "esp32s2"))
))]
mod uart_printer {
use super::LockToken;
trait Functions {
const TX_ONE_CHAR: usize;
const CHUNK_SIZE: usize = 32;
fn tx_byte(b: u8) {
unsafe {
let tx_one_char: unsafe extern "C" fn(u8) -> i32 =
core::mem::transmute(Self::TX_ONE_CHAR);
tx_one_char(b);
}
}
fn flush();
}
struct Device;
#[cfg(feature = "esp32c2")]
impl Functions for Device {
const TX_ONE_CHAR: usize = 0x4000_005C;
fn flush() {
// tx_one_char waits for empty
}
}
#[cfg(feature = "esp32c3")]
impl Functions for Device {
const TX_ONE_CHAR: usize = 0x4000_0068;
fn flush() {
unsafe {
const TX_FLUSH: usize = 0x4000_0080;
const GET_CHANNEL: usize = 0x4000_058C;
let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
const G_USB_PRINT_ADDR: usize = 0x3FCD_FFD0;
let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
let channel = if *g_usb_print {
// Flush USB-JTAG
3
} else {
get_channel()
};
tx_flush(channel);
}
}
}
#[cfg(feature = "esp32s3")]
impl Functions for Device {
const TX_ONE_CHAR: usize = 0x4000_0648;
fn flush() {
unsafe {
const TX_FLUSH: usize = 0x4000_0690;
const GET_CHANNEL: usize = 0x4000_1A58;
let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
const G_USB_PRINT_ADDR: usize = 0x3FCE_FFB8;
let g_usb_print = G_USB_PRINT_ADDR as *mut bool;
let channel = if *g_usb_print {
// Flush USB-JTAG
4
} else {
get_channel()
};
tx_flush(channel);
}
}
}
#[cfg(any(feature = "esp32c6", feature = "esp32h2"))]
impl Functions for Device {
const TX_ONE_CHAR: usize = 0x4000_0058;
fn flush() {
unsafe {
const TX_FLUSH: usize = 0x4000_0074;
const GET_CHANNEL: usize = 0x4000_003C;
let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
tx_flush(get_channel());
}
}
}
#[cfg(feature = "esp32p4")]
impl Functions for Device {
const TX_ONE_CHAR: usize = 0x4FC0_0054;
fn flush() {
unsafe {
const TX_FLUSH: usize = 0x4FC0_0074;
const GET_CHANNEL: usize = 0x4FC0_0038;
let tx_flush: unsafe extern "C" fn(u8) = core::mem::transmute(TX_FLUSH);
let get_channel: unsafe extern "C" fn() -> u8 = core::mem::transmute(GET_CHANNEL);
tx_flush(get_channel());
}
}
}
pub struct Printer;
impl Printer {
pub fn write_bytes_in_cs(bytes: &[u8], _token: LockToken<'_>) {
for chunk in bytes.chunks(Device::CHUNK_SIZE) {
for &b in chunk {
Device::tx_byte(b);
}
Device::flush();
}
}
pub fn flush(_token: LockToken<'_>) {}
}
}
#[cfg(not(feature = "critical-section"))]
type LockInner<'a> = PhantomData<&'a ()>;
#[cfg(feature = "critical-section")]
type LockInner<'a> = critical_section::CriticalSection<'a>;
#[derive(Clone, Copy)]
struct LockToken<'a>(LockInner<'a>);
impl LockToken<'_> {
#[allow(unused)]
unsafe fn conjure() -> Self {
#[cfg(feature = "critical-section")]
let inner = critical_section::CriticalSection::new();
#[cfg(not(feature = "critical-section"))]
let inner = PhantomData;
LockToken(inner)
}
}
/// Runs the callback in a critical section, if enabled.
#[inline]
fn with<R>(f: impl FnOnce(LockToken) -> R) -> R {
#[cfg(feature = "critical-section")]
return critical_section::with(|cs| f(LockToken(cs)));
#[cfg(not(feature = "critical-section"))]
f(unsafe { LockToken::conjure() })
}