#![doc = include_str!("../README.md")]
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
#![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-04")]
pub mod logger;
macro_rules! log_format {
($value:expr) => {
#[unsafe(link_section = concat!(".espressif.metadata"))]
#[used]
#[unsafe(export_name = concat!("espflash.LOG_FORMAT"))]
static LOG_FORMAT: [u8; $value.len()] = const {
let val_bytes = $value.as_bytes();
let mut val_bytes_array = [0; $value.len()];
let mut i = 0;
while i < val_bytes.len() {
val_bytes_array[i] = val_bytes[i];
i += 1;
}
val_bytes_array
};
};
}
#[cfg(feature = "defmt-espflash")]
log_format!("defmt-espflash");
#[cfg(not(feature = "defmt-espflash"))]
log_format!("serial");
#[cfg(not(feature = "no-op"))]
#[macro_export]
macro_rules! println {
() => {{
$crate::Printer::write_bytes(&[b'\n']);
}};
($($arg:tt)*) => {{
fn _do_print(args: ::core::fmt::Arguments<'_>) -> ::core::result::Result<(), ::core::fmt::Error> {
$crate::with(|_| {
use ::core::fmt::Write;
($crate::Printer).write_fmt(args)?;
$crate::Printer::write_bytes(&[b'\n']);
Ok(())
})
}
_do_print(::core::format_args!($($arg)*)).ok();
}};
}
#[cfg(not(feature = "no-op"))]
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{
fn _do_print(args: ::core::fmt::Arguments<'_>) -> ::core::result::Result<(), ::core::fmt::Error> {
$crate::with(|_| {
use ::core::fmt::Write;
($crate::Printer).write_fmt(args)
})
}
_do_print(::core::format_args!($($arg)*)).ok();
}};
}
#[cfg(feature = "no-op")]
#[macro_export]
macro_rules! println {
($($arg:tt)*) => {{}};
}
#[cfg(feature = "no-op")]
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {{}};
}
#[macro_export]
macro_rules! dbg {
() => {
$crate::println!("[{}:{}]", ::core::file!(), ::core::line!())
};
($val:expr $(,)?) => {
match $val {
tmp => {
$crate::println!("[{}:{}] {} = {:#?}",
::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp);
tmp
}
}
};
($($val:expr),+ $(,)?) => {
($($crate::dbg!($val)),+,)
};
}
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 {
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(feature = "no-op")]
type PrinterImpl = noop::Printer;
#[cfg(all(
feature = "auto",
any(
feature = "esp32c3",
feature = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2",
feature = "esp32s3"
)
))]
mod auto_printer {
use crate::{
LockToken,
serial_jtag_printer::Printer as PrinterSerialJtag,
uart_printer::Printer as PrinterUart,
};
pub struct Printer;
impl Printer {
fn use_jtag() -> bool {
#[cfg(feature = "esp32c3")]
const USB_DEVICE_INT_RAW: *const u32 = 0x60043008 as *const u32;
#[cfg(any(
feature = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2"
))]
const USB_DEVICE_INT_RAW: *const u32 = 0x6000f008 as *const u32;
#[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 = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2",
feature = "esp32s3"
))
))]
mod auto_printer {
pub type Printer = crate::uart_printer::Printer;
}
#[cfg(all(
any(feature = "jtag-serial", feature = "auto"),
any(
feature = "esp32c3",
feature = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2",
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 = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2"
))]
const SERIAL_JTAG_FIFO_REG: usize = 0x6000_F000;
#[cfg(any(
feature = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2"
))]
const SERIAL_JTAG_CONF_REG: usize = 0x6000_F004;
#[cfg(feature = "esp32s3")]
const SERIAL_JTAG_FIFO_REG: usize = 0x6003_8000;
#[cfg(feature = "esp32s3")]
const SERIAL_JTAG_CONF_REG: usize = 0x6003_8004;
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;
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() {
if TIMED_OUT.load(Ordering::Relaxed) {
return;
}
if !wait_for_flush() {
return;
}
} else {
TIMED_OUT.store(false, Ordering::Relaxed);
}
for &b in bytes {
if fifo_full() {
fifo_flush();
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<'_>) {
for chunk in bytes.chunks(64) {
for &b in chunk {
unsafe {
(0x3f400000 as *mut u32).write_volatile(b as u32);
};
}
while unsafe { (0x3f400004 as *const u32).read_volatile() } & (1 << 14) == 0 {}
unsafe {
(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() {
}
}
#[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 {
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 {
4
} else {
get_channel()
};
tx_flush(channel);
}
}
}
#[cfg(any(
feature = "esp32c5",
feature = "esp32c6",
feature = "esp32c61",
feature = "esp32h2"
))]
impl Functions for Device {
const TX_ONE_CHAR: usize = 0x4000_0058;
fn flush() {
unsafe {
const TX_FLUSH: usize = 0x4000_0074;
#[cfg(not(any(feature = "esp32c5", feature = "esp32c61")))]
const GET_CHANNEL: usize = 0x4000_003C;
#[cfg(any(feature = "esp32c5", feature = "esp32c61"))]
const GET_CHANNEL: usize = 0x4000_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(feature = "no-op")]
mod noop {
pub struct Printer;
impl Printer {
pub fn write_bytes_in_cs(_bytes: &[u8], _token: super::LockToken<'_>) {}
pub fn flush(_token: super::LockToken<'_>) {}
}
}
use core::marker::PhantomData;
#[derive(Clone, Copy)]
#[doc(hidden)]
pub struct LockToken<'a>(PhantomData<&'a ()>);
impl LockToken<'_> {
#[allow(unused)]
unsafe fn conjure() -> Self {
LockToken(PhantomData)
}
}
#[cfg(feature = "critical-section")]
static LOCK: esp_sync::RawMutex = esp_sync::RawMutex::new();
#[doc(hidden)]
#[inline]
pub fn with<R>(f: impl FnOnce(LockToken) -> R) -> R {
#[cfg(feature = "critical-section")]
return LOCK.lock(|| f(unsafe { LockToken::conjure() }));
#[cfg(not(feature = "critical-section"))]
f(unsafe { LockToken::conjure() })
}