diff --git a/Cargo.toml b/Cargo.toml index 5ef6153f9fe..bce49417ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ exclude = [ "esp-hal", "esp-hal-procmacros", "esp-hal-smartled", + "esp-ieee802154", "esp-lp-hal", "esp-metadata", "esp-riscv-rt", diff --git a/esp-ieee802154/Cargo.toml b/esp-ieee802154/Cargo.toml new file mode 100644 index 00000000000..593839a8a1d --- /dev/null +++ b/esp-ieee802154/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "esp-ieee802154" +version = "0.1.0" +edition = "2021" +rust-version = "1.76.0" +description = "Low-level IEEE 802.15.4 driver for the ESP32-C6 and ESP32-H2" +repository = "https://github.com/esp-rs/esp-hal" +license = "MIT OR Apache-2.0" + +[package.metadata.docs.rs] +default-target = "riscv32imac-unknown-none-elf" +features = ["esp32c6"] + +[lib] +bench = false +test = false + +[dependencies] +byte = "0.2.7" +critical-section = "1.1.2" +esp-hal = { version = "0.17.0", path = "../esp-hal" } +esp-wifi-sys = { git = "https://github.com/esp-rs/esp-wifi", rev = "2ceb4b3" } +heapless = "0.8.0" +ieee802154 = "0.6.1" +log = "0.4.21" +vcell = "0.1.3" + +[features] +default = [] +esp32c6 = ["esp-hal/esp32c6", "esp-wifi-sys/esp32c6"] +esp32h2 = ["esp-hal/esp32h2", "esp-wifi-sys/esp32h2"] diff --git a/esp-ieee802154/README.md b/esp-ieee802154/README.md new file mode 100644 index 00000000000..34d448cf89f --- /dev/null +++ b/esp-ieee802154/README.md @@ -0,0 +1,35 @@ +# esp-ieee802154 + +[![Crates.io](https://img.shields.io/crates/v/esp-ieee802154?color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/esp-ieee802154) +[![docs.rs](https://img.shields.io/docsrs/esp-ieee802154?color=C96329&logo=rust&style=flat-square)](https://docs.rs/esp-ieee802154) +![MSRV](https://img.shields.io/badge/MSRV-1.76-blue?labelColor=1C2C2E&style=flat-square) +![Crates.io](https://img.shields.io/crates/l/esp-ieee802154?style=flat-square) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org) + +Low-level [IEEE 802.15.4] driver for the ESP32-C6 and ESP32-H2. + +Implements the PHY/MAC layers of the IEEE802.15.4 protocol stack, and supports sending and receiving of raw frames. + +[IEEE 802.15.4]: https://en.wikipedia.org/wiki/IEEE_802.15.4 + +## [Documentation](https://docs.rs/crate/esp-ieee802154) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.76 and up. It _might_ +compile with older versions but that may change in any new patch release. + +## License + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without +any additional terms or conditions. diff --git a/esp-ieee802154/build.rs b/esp-ieee802154/build.rs new file mode 100644 index 00000000000..c0971b4765d --- /dev/null +++ b/esp-ieee802154/build.rs @@ -0,0 +1,6 @@ +use std::{env, path::PathBuf}; + +fn main() { + let out = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); +} diff --git a/esp-ieee802154/src/compat/mod.rs b/esp-ieee802154/src/compat/mod.rs new file mode 100644 index 00000000000..7965041e5b0 --- /dev/null +++ b/esp-ieee802154/src/compat/mod.rs @@ -0,0 +1,130 @@ +use core::{ffi::VaListImpl, fmt::Write}; + +use log::info; + +use self::str_buf::StrBuf; + +mod str_buf; + +#[no_mangle] +pub unsafe extern "C" fn phy_printf(format: *const u8, args: ...) { + syslog(format, args); +} + +#[no_mangle] +pub unsafe extern "C" fn rtc_printf(format: *const u8, args: ...) { + syslog(format, args); +} + +#[no_mangle] +pub unsafe extern "C" fn coexist_printf(format: *const u8, args: ...) { + syslog(format, args); +} + +pub unsafe extern "C" fn syslog(format: *const u8, args: VaListImpl) { + let mut buf = [0u8; 512]; + vsnprintf(&mut buf as *mut u8, 511, format, args); + let res_str = StrBuf::from(&buf as *const u8); + info!("{}", res_str.as_str_ref()); +} + +pub(crate) unsafe fn vsnprintf( + dst: *mut u8, + _n: u32, + format: *const u8, + mut args: VaListImpl, +) -> i32 { + let fmt_str_ptr = format; + + let mut res_str = StrBuf::new(); + + let strbuf = StrBuf::from(fmt_str_ptr); + let s = strbuf.as_str_ref(); + + let mut format_char = ' '; + let mut is_long = false; + let mut found = false; + for c in s.chars() { + if !found { + if c == '%' { + found = true; + } + + if !found { + res_str.append_char(c); + } + } else if c.is_numeric() || c == '-' || c == 'l' { + if c == 'l' { + is_long = true; + } + // ignore + } else { + // a format char + format_char = c; + } + + if found && format_char != ' ' { + // have to format an arg + match format_char { + 'd' => { + if is_long { + let v = args.arg::(); + write!(res_str, "{}", v).ok(); + } else { + let v = args.arg::(); + write!(res_str, "{}", v).ok(); + } + } + + 'u' => { + let v = args.arg::(); + write!(res_str, "{}", v).ok(); + } + + 'p' => { + let v = args.arg::(); + write!(res_str, "0x{:x}", v).ok(); + } + + 'X' => { + let v = args.arg::(); + write!(res_str, "{:02x}", (v & 0xff000000) >> 24).ok(); + } + + 'x' => { + let v = args.arg::(); + write!(res_str, "{:02x}", v).ok(); + } + + 's' => { + let v = args.arg::() as *const u8; + let vbuf = StrBuf::from(v); + write!(res_str, "{}", vbuf.as_str_ref()).ok(); + } + + 'c' => { + let v = args.arg::(); + if v != 0 { + write!(res_str, "{}", v as char).ok(); + } + } + + _ => { + write!(res_str, "", format_char).ok(); + } + } + + format_char = ' '; + found = false; + is_long = false; + } + } + let mut idx = 0; + res_str.as_str_ref().chars().for_each(|c| { + *(dst.offset(idx)) = c as u8; + idx += 1; + }); + *(dst.offset(idx)) = 0; + + idx as i32 +} diff --git a/esp-ieee802154/src/compat/str_buf.rs b/esp-ieee802154/src/compat/str_buf.rs new file mode 100644 index 00000000000..42693c77a0e --- /dev/null +++ b/esp-ieee802154/src/compat/str_buf.rs @@ -0,0 +1,58 @@ +use core::fmt::Write; + +pub struct StrBuf { + buffer: [u8; 512], + len: usize, +} + +impl StrBuf { + pub fn new() -> StrBuf { + StrBuf { + buffer: [0u8; 512], + len: 0, + } + } + + pub unsafe fn from(c_str: *const u8) -> StrBuf { + let mut res = StrBuf { + buffer: [0u8; 512], + len: 0, + }; + + let mut idx: usize = 0; + while *(c_str.add(idx)) != 0 { + res.buffer[idx] = *(c_str.add(idx)); + idx += 1; + } + + res.len = idx; + res + } + + pub fn append(&mut self, s: &str) { + let mut idx: usize = self.len; + s.chars().for_each(|c| { + self.buffer[idx] = c as u8; + idx += 1; + }); + self.len = idx; + } + + pub fn append_char(&mut self, c: char) { + let mut idx: usize = self.len; + self.buffer[idx] = c as u8; + idx += 1; + self.len = idx; + } + + pub unsafe fn as_str_ref(&self) -> &str { + core::str::from_utf8_unchecked(&self.buffer[..self.len]) + } +} + +impl Write for StrBuf { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + self.append(s); + Ok(()) + } +} diff --git a/esp-ieee802154/src/frame.rs b/esp-ieee802154/src/frame.rs new file mode 100644 index 00000000000..611303f7f9e --- /dev/null +++ b/esp-ieee802154/src/frame.rs @@ -0,0 +1,45 @@ +use heapless::Vec; +use ieee802154::mac::{FrameContent, Header}; + +pub(crate) const FRAME_SIZE: usize = 129; +pub(crate) const FRAME_VERSION_1: u8 = 0x10; // IEEE 802.15.4 - 2006 & 2011 +pub(crate) const FRAME_VERSION_2: u8 = 0x20; // IEEE 802.15.4 - 2015 + +const FRAME_AR_OFFSET: usize = 1; +const FRAME_AR_BIT: u8 = 0x20; +const FRAME_VERSION_OFFSET: usize = 2; +const FRAME_VERSION_MASK: u8 = 0x30; + +/// IEEE 802.15.4 MAC frame +#[derive(Debug, Clone)] +pub struct Frame { + /// Header + pub header: Header, + /// Content + pub content: FrameContent, + /// Payload + pub payload: Vec, + /// This is a 2-byte CRC checksum + pub footer: [u8; 2], +} + +/// IEEE 802.15.4 MAC frame which has been received +#[derive(Debug, Clone)] +pub struct ReceivedFrame { + /// Frame + pub frame: Frame, + /// Receiver channel + pub channel: u8, + /// Received Signal Strength Indicator (RSSI) + pub rssi: i8, + /// Link Quality Indication (LQI) + pub lqi: u8, +} + +pub(crate) fn frame_is_ack_required(frame: &[u8]) -> bool { + (frame[FRAME_AR_OFFSET] & FRAME_AR_BIT) != 0 +} + +pub(crate) fn frame_get_version(frame: &[u8]) -> u8 { + frame[FRAME_VERSION_OFFSET] & FRAME_VERSION_MASK +} diff --git a/esp-ieee802154/src/hal.rs b/esp-ieee802154/src/hal.rs new file mode 100644 index 00000000000..afe86da8809 --- /dev/null +++ b/esp-ieee802154/src/hal.rs @@ -0,0 +1,437 @@ +use core::ops::{BitAnd, BitOr}; + +use esp_hal::peripherals::IEEE802154; + +use crate::pib::CcaMode; + +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub(crate) enum Event { + TxDone = 1 << 0, + RxDone = 1 << 1, + AckTxDone = 1 << 2, + AckRxDone = 1 << 3, + RxAbort = 1 << 4, + TxAbort = 1 << 5, + EdDone = 1 << 6, + Timer0Overflow = 1 << 8, + Timer1Overflow = 1 << 9, + ClockCountMatch = 1 << 10, + TxSfdDone = 1 << 11, + RxSfdDone = 1 << 12, +} + +impl Event { + pub(crate) fn mask() -> u16 { + 0x0000_1FFF + } +} + +impl BitAnd for u16 { + type Output = u16; + + fn bitand(self, rhs: Event) -> Self::Output { + self & rhs as u16 + } +} + +impl BitOr for Event { + type Output = u16; + + fn bitor(self, rhs: Self) -> Self::Output { + self as u16 | rhs as u16 + } +} + +impl BitOr for u16 { + type Output = u16; + + fn bitor(self, rhs: Event) -> Self::Output { + self | rhs as u16 + } +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub(crate) enum TxAbortReason { + RxAckStop = 1, + RxAckSfdTimeout = 2, + RxAckCrcError = 3, + RxAckInvalidLen = 4, + RxAckFilterFail = 5, + RxAckNoRss = 6, + RxAckCoexBreak = 7, + RxAckTypeNotAck = 8, + RxAckRestart = 9, + RxAckTimeout = 16, + TxStop = 17, + TxCoexBreak = 18, + TxSecurityError = 19, + CcaFailed = 24, + CcaBusy = 25, +} + +impl TxAbortReason { + pub fn bit(&self) -> u32 { + 1 << (*self as u32 - 1) + } +} + +impl BitOr for TxAbortReason { + type Output = u32; + + fn bitor(self, rhs: Self) -> Self::Output { + self.bit() | rhs.bit() + } +} + +impl BitOr for u32 { + type Output = u32; + + fn bitor(self, rhs: TxAbortReason) -> Self::Output { + self | rhs.bit() + } +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub(crate) enum RxAbortReason { + RxStop = 1, + SfdTimeout = 2, + CrcError = 3, + InvalidLen = 4, + FilterFail = 5, + NoRss = 6, + CoexBreak = 7, + UnexpectedAck = 8, + RxRestart = 9, + TxAckTimeout = 16, + TxAckStop = 17, + TxAckCoexBreak = 18, + EnhackSecurityError = 19, + EdAbort = 24, + EdStop = 25, + EdCoexReject = 26, +} + +impl RxAbortReason { + pub fn bit(&self) -> u32 { + 1 << (*self as u32 - 1) + } +} + +impl BitOr for RxAbortReason { + type Output = u32; + + fn bitor(self, rhs: Self) -> Self::Output { + self.bit() | rhs.bit() + } +} + +impl BitOr for u32 { + type Output = u32; + + fn bitor(self, rhs: RxAbortReason) -> Self::Output { + self | rhs.bit() + } +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub(crate) enum EdSampleMode { + Max = 0, + Avg = 1, +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub(crate) enum Command { + TxStart = 0x41, + RxStart = 0x42, + CcaTxStart = 0x43, + EdStart = 0x44, + Stop = 0x45, + DtmTxStart = 0x46, + DtmRxStart = 0x47, + DtmStop = 0x48, + Timer0Start = 0x4C, + Timer0Stop = 0x4D, + Timer1Start = 0x4E, + Timer1Stop = 0x4F, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum MultipanIndex { + Multipan0 = 0, + Multipan1 = 1, + Multipan2 = 2, + Multipan3 = 3, +} + +impl From for MultipanIndex { + fn from(value: usize) -> Self { + match value { + 0 => MultipanIndex::Multipan0, + 1 => MultipanIndex::Multipan1, + 2 => MultipanIndex::Multipan2, + 3 => MultipanIndex::Multipan3, + _ => panic!(), + } + } +} + +#[inline(always)] +pub(crate) fn mac_date() -> u32 { + unsafe { &*IEEE802154::PTR }.mac_date().read().bits() +} + +#[inline(always)] +pub(crate) fn set_rx_on_delay(delay: u16) { + unsafe { &*IEEE802154::PTR } + .rxon_delay() + .modify(|_, w| unsafe { w.rxon_delay().bits(delay) }); +} + +#[inline(always)] +pub(crate) fn enable_events(events: u16) { + unsafe { &*IEEE802154::PTR } + .event_en() + .modify(|r, w| unsafe { w.event_en().bits(r.event_en().bits() | events) }); +} + +#[inline(always)] +pub(crate) fn disable_events(events: u16) { + unsafe { &*IEEE802154::PTR } + .event_en() + .modify(|r, w| unsafe { w.event_en().bits(r.event_en().bits() & !events) }); +} + +#[inline(always)] +pub(crate) fn enable_tx_abort_events(events: u32) { + unsafe { &*IEEE802154::PTR } + .tx_abort_interrupt_control() + .modify(|r, w| unsafe { + w.tx_abort_interrupt_control() + .bits(r.tx_abort_interrupt_control().bits() | events) + }); +} + +#[inline(always)] +pub(crate) fn enable_rx_abort_events(events: u32) { + unsafe { &*IEEE802154::PTR } + .rx_abort_intr_ctrl() + .modify(|r, w| unsafe { + w.rx_abort_intr_ctrl() + .bits(r.rx_abort_intr_ctrl().bits() | events) + }); +} + +#[inline(always)] +pub(crate) fn set_ed_sample_mode(ed_sample_mode: EdSampleMode) { + unsafe { &*IEEE802154::PTR } + .ed_scan_cfg() + .modify(|_, w| unsafe { w.ed_sample_mode().bits(ed_sample_mode as u8) }); +} + +#[inline(always)] +pub(crate) fn set_tx_addr(addr: *const u8) { + unsafe { &*IEEE802154::PTR } + .txdma_addr() + .modify(|_, w| unsafe { w.txdma_addr().bits(addr as u32) }); +} + +#[inline(always)] +pub(crate) fn set_cmd(cmd: Command) { + unsafe { &*IEEE802154::PTR } + .command() + .modify(|_, w| unsafe { w.opcode().bits(cmd as u8) }) +} + +#[inline(always)] +pub(crate) fn set_freq(freq: u8) { + unsafe { &*IEEE802154::PTR } + .channel() + .modify(|_, w| unsafe { w.hop().bits(freq) }); +} + +#[inline(always)] +pub(crate) fn get_freq() -> u8 { + unsafe { &*IEEE802154::PTR }.channel().read().hop().bits() +} + +#[inline(always)] +pub(crate) fn set_power(power: u8) { + unsafe { &*IEEE802154::PTR } + .tx_power() + .modify(|_, w| unsafe { w.tx_power().bits(power) }); +} + +#[inline(always)] +pub(crate) fn set_multipan_enable_mask(mask: u8) { + // apparently the REGS are garbage and the struct is right? + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|r, w| unsafe { w.bits(r.bits() & !(0b1111 << 29) | (mask as u32) << 29) }) +} + +#[inline(always)] +pub(crate) fn set_multipan_panid(index: MultipanIndex, panid: u16) { + unsafe { + let pan_id = (&*IEEE802154::PTR) + .inf0_pan_id() + .as_ptr() + .offset(4 * index as isize); + pan_id.write_volatile(panid as u32); + } +} + +#[inline(always)] +pub(crate) fn set_multipan_short_addr(index: MultipanIndex, value: u16) { + unsafe { + let short_addr = (&*IEEE802154::PTR) + .inf0_short_addr() + .as_ptr() + .offset(4 * index as isize); + short_addr.write_volatile(value as u32); + } +} + +#[inline(always)] +pub(crate) fn set_multipan_ext_addr(index: MultipanIndex, ext_addr: *const u8) { + unsafe { + let mut ext_addr_ptr = (&*IEEE802154::PTR) + .inf0_extend_addr0() + .as_ptr() + .offset(4 * index as isize); + + ext_addr_ptr.write_volatile( + (ext_addr.offset(0).read_volatile() as u32) + | ((ext_addr.offset(1).read_volatile() as u32) << 8) + | ((ext_addr.offset(2).read_volatile() as u32) << 16) + | ((ext_addr.offset(3).read_volatile() as u32) << 24), + ); + + ext_addr_ptr = ext_addr_ptr.offset(1); + + ext_addr_ptr.write_volatile( + (ext_addr.offset(4).read_volatile() as u32) + | ((ext_addr.offset(5).read_volatile() as u32) << 8) + | ((ext_addr.offset(6).read_volatile() as u32) << 16) + | ((ext_addr.offset(7).read_volatile() as u32) << 24), + ); + } +} + +#[inline(always)] +pub(crate) fn set_cca_mode(cca_mode: CcaMode) { + unsafe { &*IEEE802154::PTR } + .ed_scan_cfg() + .modify(|_, w| unsafe { w.cca_mode().bits(cca_mode as u8) }); +} + +#[inline(always)] +pub(crate) fn set_cca_threshold(cca_threshold: i8) { + unsafe { &*IEEE802154::PTR } + .ed_scan_cfg() + .modify(|_, w| unsafe { w.cca_ed_threshold().bits(cca_threshold as u8) }) +} + +#[inline(always)] +pub(crate) fn set_tx_auto_ack(enable: bool) { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|_, w| w.hw_auto_ack_tx_en().bit(enable)); +} + +#[inline(always)] +pub(crate) fn get_tx_auto_ack() -> bool { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .read() + .hw_auto_ack_tx_en() + .bit_is_set() +} + +#[inline(always)] +pub(crate) fn set_rx_auto_ack(enable: bool) { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|_, w| w.hw_auto_ack_rx_en().bit(enable)); +} + +#[inline(always)] +pub(crate) fn set_tx_enhance_ack(enable: bool) { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|_, w| w.hw_enhance_ack_tx_en().bit(enable)); +} + +#[inline(always)] +pub(crate) fn get_tx_enhance_ack() -> bool { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .read() + .hw_enhance_ack_tx_en() + .bit_is_set() +} + +#[inline(always)] +pub(crate) fn set_coordinator(enable: bool) { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|_, w| w.pan_coordinator().bit(enable)); +} + +#[inline(always)] +pub(crate) fn set_promiscuous(enable: bool) { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|_, w| w.promiscuous_mode().bit(enable)); +} + +#[inline(always)] +pub(crate) fn set_pending_mode(enable: bool) { + unsafe { &*IEEE802154::PTR } + .ctrl_cfg() + .modify(|_, w| w.autopend_enhance().bit(enable)); +} + +#[inline(always)] +pub(crate) fn get_events() -> u16 { + unsafe { &*IEEE802154::PTR }.event_status().read().bits() as u16 +} + +#[inline(always)] +pub(crate) fn clear_events(events: u16) { + unsafe { &*IEEE802154::PTR } + .event_status() + .modify(|r, w| unsafe { w.event_status().bits(r.event_status().bits() & events) }); +} + +#[inline(always)] +pub(crate) fn set_transmit_security(enable: bool) { + unsafe { &*IEEE802154::PTR } + .sec_ctrl() + .modify(|_, w| w.sec_en().bit(enable)); +} + +#[inline(always)] +pub(crate) fn set_rx_addr(addr: *mut u8) { + unsafe { &*IEEE802154::PTR } + .rxdma_addr() + .modify(|_, w| unsafe { w.rxdma_addr().bits(addr as u32) }); +} + +#[inline(always)] +pub(crate) fn abort_tx() { + unsafe { &*IEEE802154::PTR } + .tx_status() + .modify(|_, w| unsafe { w.tx_abort_status().bits(0) }); +} + +#[inline(always)] +pub(crate) fn abort_rx() { + unsafe { &*IEEE802154::PTR } + .rx_status() + .modify(|_, w| unsafe { w.rx_abort_status().bits(0) }); +} diff --git a/esp-ieee802154/src/lib.rs b/esp-ieee802154/src/lib.rs new file mode 100644 index 00000000000..284127b6c8f --- /dev/null +++ b/esp-ieee802154/src/lib.rs @@ -0,0 +1,349 @@ +//! Low-level [IEEE 802.15.4] driver for the ESP32-C6 and ESP32-H2 +//! +//! Implements the PHY/MAC layers of the IEEE 802.15.4 protocol stack, and +//! supports sending and receiving of raw frames. +//! +//! [IEEE 802.15.4]: https://en.wikipedia.org/wiki/IEEE_802.15.4 + +#![no_std] +#![feature(c_variadic)] + +use core::{cell::RefCell, marker::PhantomData}; + +use byte::{BytesExt, TryRead}; +use critical_section::Mutex; +use esp_hal::peripherals::{IEEE802154, RADIO_CLK}; +use heapless::Vec; +use ieee802154::mac::{self, FooterMode, FrameSerDesContext}; + +use self::{ + frame::FRAME_SIZE, + pib::{CONFIG_IEEE802154_CCA_THRESHOLD, IEEE802154_FRAME_EXT_ADDR_SIZE}, + raw::*, +}; +pub use self::{ + frame::{Frame, ReceivedFrame}, + pib::{CcaMode, PendingMode}, + raw::RawReceived, +}; + +mod compat; +mod frame; +mod hal; +mod pib; +mod raw; + +#[no_mangle] +extern "C" fn rtc_clk_xtal_freq_get() -> i32 { + 0 +} + +/// IEEE 802.15.4 errors +#[derive(Debug, Clone, Copy)] +pub enum Error { + /// The requested data is bigger than available range, and/or the offset is + /// invalid + Incomplete, + /// The requested data content is invalid + BadInput, +} + +impl From for Error { + fn from(err: byte::Error) -> Self { + match err { + byte::Error::Incomplete | byte::Error::BadOffset(_) => Error::Incomplete, + byte::Error::BadInput { .. } => Error::BadInput, + } + } +} + +/// IEEE 802.15.4 driver configuration +#[derive(Debug, Clone, Copy)] +pub struct Config { + pub auto_ack_tx: bool, + pub auto_ack_rx: bool, + pub enhance_ack_tx: bool, + pub promiscuous: bool, + pub coordinator: bool, + pub rx_when_idle: bool, + pub txpower: i8, + pub channel: u8, + pub cca_threshold: i8, + pub cca_mode: CcaMode, + pub pan_id: Option, + pub short_addr: Option, + pub ext_addr: Option, +} + +impl Default for Config { + fn default() -> Self { + Self { + auto_ack_tx: Default::default(), + auto_ack_rx: Default::default(), + enhance_ack_tx: Default::default(), + promiscuous: Default::default(), + coordinator: Default::default(), + rx_when_idle: Default::default(), + txpower: 10, + channel: 15, + cca_threshold: CONFIG_IEEE802154_CCA_THRESHOLD, + cca_mode: CcaMode::Ed, + pan_id: None, + short_addr: None, + ext_addr: None, + } + } +} + +/// IEEE 802.15.4 driver +#[derive(Debug)] +pub struct Ieee802154<'a> { + _align: u32, + transmit_buffer: [u8; FRAME_SIZE], + _phantom1: PhantomData<&'a ()>, + //_phantom2:PhantomData< &'b ()>, +} + +impl<'a> Ieee802154<'a> { + /// Construct a new driver, enabling the IEEE 802.15.4 radio in the process + pub fn new(_radio: IEEE802154, radio_clocks: &mut RADIO_CLK) -> Self { + esp_ieee802154_enable(radio_clocks); + + Self { + _align: 0, + transmit_buffer: [0u8; FRAME_SIZE], + _phantom1: PhantomData::default(), + //_phantom2: PhantomData::default(), + } + } + + /// Set the configuration for the driver + pub fn set_config(&mut self, cfg: Config) { + set_auto_ack_tx(cfg.auto_ack_tx); + set_auto_ack_rx(cfg.auto_ack_rx); + set_enhance_ack_tx(cfg.enhance_ack_tx); + set_promiscuous(cfg.promiscuous); + set_coordinator(cfg.coordinator); + set_rx_when_idle(cfg.rx_when_idle); + set_tx_power(cfg.txpower); + set_channel(cfg.channel); + set_cca_theshold(cfg.cca_threshold); + set_cca_mode(cfg.cca_mode); + + if let Some(pan_id) = cfg.pan_id { + set_panid(0, pan_id); + } + + if let Some(short_addr) = cfg.short_addr { + set_short_address(0, short_addr); + } + + if let Some(ext_addr) = cfg.ext_addr { + let mut address = [0u8; IEEE802154_FRAME_EXT_ADDR_SIZE]; + address.copy_from_slice(&ext_addr.to_be_bytes()); // LE or BE? + + set_extended_address(0, address); + } + } + + /// Start receiving frames + pub fn start_receive(&mut self) { + ieee802154_receive(); + } + + /// Return the raw data of a received frame + pub fn get_raw_received(&mut self) -> Option { + ieee802154_poll() + } + + /// Get a received frame, if available + pub fn get_received(&mut self) -> Option> { + if let Some(raw) = ieee802154_poll() { + let maybe_decoded = + mac::Frame::try_read(&raw.data[1..][..raw.data[0] as usize], FooterMode::Explicit); + + let result = match maybe_decoded { + Ok((decoded, _)) => { + let rssi = raw.data[raw.data[0] as usize - 1] as i8; // crc is not written to rx buffer + + Ok(ReceivedFrame { + frame: Frame { + header: decoded.header, + content: decoded.content, + payload: Vec::from_slice(decoded.payload).unwrap(), + footer: decoded.footer, + }, + channel: raw.channel, + rssi, + lqi: rssi_to_lqi(rssi), + }) + } + Err(err) => Err(err.into()), + }; + + Some(result) + } else { + None + } + } + + /// Transmit a frame + pub fn transmit(&mut self, frame: &Frame) -> Result<(), Error> { + let frm = mac::Frame { + header: frame.header, + content: frame.content, + payload: &frame.payload, + footer: frame.footer, + }; + + let mut offset = 1usize; + self.transmit_buffer + .write_with( + &mut offset, + frm, + &mut FrameSerDesContext::no_security(FooterMode::Explicit), + ) + .unwrap(); + self.transmit_buffer[0] = (offset - 1) as u8; + + ieee802154_transmit(self.transmit_buffer.as_ptr() as *const u8, false); // what about CCA? + + Ok(()) + } + + /// Transmit a raw frame + pub fn transmit_raw(&mut self, frame: &[u8]) -> Result<(), Error> { + self.transmit_buffer[1..][..frame.len()].copy_from_slice(frame); + self.transmit_buffer[0] = frame.len() as u8; + + ieee802154_transmit(self.transmit_buffer.as_ptr() as *const u8, false); // what about CCA? + + Ok(()) + } + + pub fn set_tx_done_callback(&mut self, callback: &'a mut (dyn FnMut() + Send)) { + critical_section::with(|cs| { + let mut tx_done_callback = TX_DONE_CALLBACK.borrow_ref_mut(cs); + tx_done_callback.replace(unsafe { core::mem::transmute(callback) }); + }); + } + + pub fn clear_tx_done_callback(&mut self) { + critical_section::with(|cs| { + let mut tx_done_callback = TX_DONE_CALLBACK.borrow_ref_mut(cs); + tx_done_callback.take(); + }); + } + + pub fn set_rx_available_callback(&mut self, callback: &'a mut (dyn FnMut() + Send)) { + critical_section::with(|cs| { + let mut rx_available_callback = RX_AVAILABLE_CALLBACK.borrow_ref_mut(cs); + rx_available_callback.replace(unsafe { core::mem::transmute(callback) }); + }); + } + + pub fn clear_rx_available_callback(&mut self) { + critical_section::with(|cs| { + let mut rx_available_callback = RX_AVAILABLE_CALLBACK.borrow_ref_mut(cs); + rx_available_callback.take(); + }); + } + + pub fn set_tx_done_callback_fn(&mut self, callback: fn()) { + critical_section::with(|cs| { + let mut tx_done_callback_fn = TX_DONE_CALLBACK_FN.borrow_ref_mut(cs); + tx_done_callback_fn.replace(callback); + }); + } + + pub fn clear_tx_done_callback_fn(&mut self) { + critical_section::with(|cs| { + let mut tx_done_callback_fn = TX_DONE_CALLBACK_FN.borrow_ref_mut(cs); + tx_done_callback_fn.take(); + }); + } + + pub fn set_rx_available_callback_fn(&mut self, callback: fn()) { + critical_section::with(|cs| { + let mut rx_available_callback_fn = RX_AVAILABLE_CALLBACK_FN.borrow_ref_mut(cs); + rx_available_callback_fn.replace(unsafe { core::mem::transmute(callback) }); + }); + } + + pub fn clear_rx_available_callback_fn(&mut self) { + critical_section::with(|cs| { + let mut rx_available_callback_fn = RX_AVAILABLE_CALLBACK_FN.borrow_ref_mut(cs); + rx_available_callback_fn.take(); + }); + } +} + +impl<'a> Drop for Ieee802154<'a> { + fn drop(&mut self) { + self.clear_tx_done_callback(); + self.clear_tx_done_callback_fn(); + self.clear_rx_available_callback(); + self.clear_rx_available_callback_fn(); + } +} + +pub fn rssi_to_lqi(rssi: i8) -> u8 { + if rssi < -80 { + 0 + } else if rssi > -30 { + 0xff + } else { + let lqi_convert = ((rssi as u32).wrapping_add(80)) * 255; + (lqi_convert / 50) as u8 + } +} + +static TX_DONE_CALLBACK: Mutex>> = + Mutex::new(RefCell::new(None)); + +static RX_AVAILABLE_CALLBACK: Mutex>> = + Mutex::new(RefCell::new(None)); + +static TX_DONE_CALLBACK_FN: Mutex>> = Mutex::new(RefCell::new(None)); + +static RX_AVAILABLE_CALLBACK_FN: Mutex>> = Mutex::new(RefCell::new(None)); + +fn tx_done() { + log::trace!("tx_done callback"); + + critical_section::with(|cs| { + let mut tx_done_callback = TX_DONE_CALLBACK.borrow_ref_mut(cs); + let tx_done_callback = tx_done_callback.as_mut(); + + if let Some(tx_done_callback) = tx_done_callback { + tx_done_callback(); + } + + let mut tx_done_callback_fn = TX_DONE_CALLBACK_FN.borrow_ref_mut(cs); + let tx_done_callback_fn = tx_done_callback_fn.as_mut(); + + if let Some(tx_done_callback_fn) = tx_done_callback_fn { + tx_done_callback_fn(); + } + }); +} + +fn rx_available() { + log::trace!("rx available callback"); + + critical_section::with(|cs| { + let mut rx_available_callback = RX_AVAILABLE_CALLBACK.borrow_ref_mut(cs); + let rx_available_callback = rx_available_callback.as_mut(); + + if let Some(rx_available_callback) = rx_available_callback { + rx_available_callback(); + } + + let mut rx_available_callback_fn = RX_AVAILABLE_CALLBACK_FN.borrow_ref_mut(cs); + let rx_available_callback_fn = rx_available_callback_fn.as_mut(); + + if let Some(rx_available_callback_fn) = rx_available_callback_fn { + rx_available_callback_fn(); + } + }); +} diff --git a/esp-ieee802154/src/pib.rs b/esp-ieee802154/src/pib.rs new file mode 100644 index 00000000000..77aadd767fc --- /dev/null +++ b/esp-ieee802154/src/pib.rs @@ -0,0 +1,241 @@ +use core::cell::RefCell; + +use critical_section::Mutex; + +use crate::hal::{ + set_cca_mode, + set_cca_threshold, + set_coordinator, + set_freq, + set_multipan_enable_mask, + set_multipan_ext_addr, + set_multipan_panid, + set_multipan_short_addr, + set_pending_mode, + set_power, + set_promiscuous, + set_rx_auto_ack, + set_tx_auto_ack, + set_tx_enhance_ack, +}; + +pub(crate) const CONFIG_IEEE802154_CCA_THRESHOLD: i8 = 1; +pub(crate) const IEEE802154_FRAME_EXT_ADDR_SIZE: usize = 8; + +const IEEE802154_MULTIPAN_0: u8 = 0; +const IEEE802154_MULTIPAN_MAX: usize = 4; + +static PIB: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Frame pending mode +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub enum PendingMode { + /// Frame pending bit always set to 1 in the ack to Data Request + #[default] + Disable = 0, + /// Frame pending bit set to 1 if src address matches, in the ack to Data + /// Request + Enable = 1, + /// Frame pending bit set to 1 if src address matches, in all ack frames + Enhanced = 2, + /// Frame pending bit set to 0 only if src address is short address and + /// matches in table, in the ack to Data Request + Zigbee = 3, +} + +/// CCA mode +#[derive(Debug, Default, Clone, Copy)] +pub enum CcaMode { + /// Carrier only + #[default] + Carrier = 0x00, + /// Energy Detect only + Ed = 0x01, + /// Carrier or Energy Detect + CarrierOrEd = 0x02, + /// Carrier and Energy Detect + CarrierAndEd = 0x03, +} + +#[derive(Debug, Default, Clone, Copy)] +struct Pib { + auto_ack_tx: bool, + auto_ack_rx: bool, + enhance_ack_tx: bool, + promiscuous: bool, + coordinator: bool, + rx_when_idle: bool, + txpower: i8, + channel: u8, + pending_mode: PendingMode, + multipan_mask: u8, + panid: [u16; IEEE802154_MULTIPAN_MAX], + short_addr: [u16; IEEE802154_MULTIPAN_MAX], + ext_addr: [[u8; IEEE802154_FRAME_EXT_ADDR_SIZE]; IEEE802154_MULTIPAN_MAX], + cca_threshold: i8, + cca_mode: CcaMode, +} + +pub(crate) fn ieee802154_pib_init() { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).replace(Pib { + auto_ack_tx: true, + auto_ack_rx: true, + enhance_ack_tx: true, + coordinator: false, + promiscuous: true, + rx_when_idle: false, + txpower: 10, + channel: 11, + pending_mode: PendingMode::Disable, + multipan_mask: 1 << IEEE802154_MULTIPAN_0, + panid: [0u16; 4], + short_addr: [0u16; IEEE802154_MULTIPAN_MAX], + ext_addr: [[0xffu8; IEEE802154_FRAME_EXT_ADDR_SIZE]; IEEE802154_MULTIPAN_MAX], + cca_threshold: CONFIG_IEEE802154_CCA_THRESHOLD, + cca_mode: CcaMode::Ed, + }); + }); +} + +pub(crate) fn ieee802154_pib_set_panid(index: u8, panid: u16) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().panid[index as usize] = panid; + }); +} + +pub(crate) fn ieee802154_pib_set_promiscuous(enable: bool) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().promiscuous = enable; + }); +} + +pub(crate) fn ieee802154_pib_set_auto_ack_tx(enable: bool) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().auto_ack_tx = enable; + }); +} + +pub(crate) fn ieee802154_pib_set_auto_ack_rx(enable: bool) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().auto_ack_rx = enable; + }); +} + +pub(crate) fn ieee802154_pib_set_enhance_ack_tx(enable: bool) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().enhance_ack_tx = enable; + }); +} + +pub(crate) fn ieee802154_pib_set_coordinator(enable: bool) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().coordinator = enable; + }); +} + +pub(crate) fn ieee802154_pib_set_rx_when_idle(enable: bool) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().rx_when_idle = enable; + }); +} + +pub(crate) fn ieee802154_pib_get_rx_when_idle() -> bool { + critical_section::with(|cs| PIB.borrow_ref_mut(cs).as_mut().unwrap().rx_when_idle) +} + +pub(crate) fn ieee802154_pib_set_tx_power(power: i8) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().txpower = power; + }); +} + +pub(crate) fn ieee802154_pib_set_channel(channel: u8) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().channel = channel; + }); +} + +pub(crate) fn ieee802154_pib_set_pending_mode(mode: PendingMode) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().pending_mode = mode; + }); +} + +pub(crate) fn ieee802154_pib_set_short_address(index: u8, address: u16) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().short_addr[index as usize] = address; + }); +} + +pub(crate) fn ieee802154_pib_set_extended_address( + index: u8, + address: [u8; IEEE802154_FRAME_EXT_ADDR_SIZE], +) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().ext_addr[index as usize] = address; + }); +} + +pub(crate) fn ieee802154_pib_set_cca_theshold(cca_threshold: i8) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().cca_threshold = cca_threshold; + }); +} + +pub(crate) fn ieee802154_pib_set_cca_mode(mode: CcaMode) { + critical_section::with(|cs| { + PIB.borrow_ref_mut(cs).as_mut().unwrap().cca_mode = mode; + }); +} + +pub(crate) fn ieee802154_pib_update() { + critical_section::with(|cs| { + let mut pib = PIB.borrow_ref_mut(cs); + let pib = pib.as_mut().unwrap(); + + set_freq(channel_to_freq(pib.channel)); + set_power(ieee802154_txpower_convert(pib.txpower)); + + set_multipan_enable_mask(pib.multipan_mask); + ieee802154_set_multipan_hal(pib); + + set_cca_mode(pib.cca_mode); + set_cca_threshold(pib.cca_threshold); + + set_tx_auto_ack(pib.auto_ack_tx); + set_rx_auto_ack(pib.auto_ack_rx); + set_tx_enhance_ack(pib.enhance_ack_tx); + + set_coordinator(pib.coordinator); + set_promiscuous(pib.promiscuous); + set_pending_mode(pib.pending_mode == PendingMode::Enhanced); + }); +} + +fn channel_to_freq(channel: u8) -> u8 { + (channel - 11) * 5 + 3 +} + +fn ieee802154_set_multipan_hal(pib: &Pib) { + for index in 0..IEEE802154_MULTIPAN_MAX { + if (pib.multipan_mask & (1 << index)) != 0 { + set_multipan_panid(index.into(), pib.panid[index]); + set_multipan_short_addr(index.into(), pib.short_addr[index]); + set_multipan_ext_addr(index.into(), pib.ext_addr[index].as_ptr() as *const u8); + } + } +} + +fn ieee802154_txpower_convert(txpower: i8) -> u8 { + const IEEE802154_TXPOWER_VALUE_MAX: i8 = 13; + const IEEE802154_TXPOWER_VALUE_MIN: i8 = -32; + + if txpower > IEEE802154_TXPOWER_VALUE_MAX { + 15 + } else if txpower < IEEE802154_TXPOWER_VALUE_MIN { + 0 + } else { + ((txpower - IEEE802154_TXPOWER_VALUE_MIN) / 3) as u8 + } +} diff --git a/esp-ieee802154/src/raw.rs b/esp-ieee802154/src/raw.rs new file mode 100644 index 00000000000..834a793d741 --- /dev/null +++ b/esp-ieee802154/src/raw.rs @@ -0,0 +1,439 @@ +use core::cell::RefCell; + +use critical_section::Mutex; +use esp_hal::{ + interrupt::Priority, + peripherals::RADIO_CLK, + prelude::handler, + system::{RadioClockController, RadioPeripherals}, +}; +use esp_wifi_sys::include::{ + esp_phy_calibration_data_t, + esp_phy_calibration_mode_t_PHY_RF_CAL_FULL, + ieee802154_coex_event_t, + ieee802154_coex_event_t_IEEE802154_IDLE, + ieee802154_coex_event_t_IEEE802154_LOW, + ieee802154_coex_event_t_IEEE802154_MIDDLE, + register_chipv7_phy, +}; +use heapless::spsc::Queue; + +use crate::{ + frame::{frame_get_version, frame_is_ack_required, FRAME_VERSION_1, FRAME_VERSION_2}, + hal::*, + pib::*, +}; + +pub(crate) const FRAME_SIZE: usize = 129; + +const PHY_ENABLE_VERSION_PRINT: u32 = 1; + +static mut RX_BUFFER: [u8; FRAME_SIZE] = [0u8; FRAME_SIZE]; +static RX_QUEUE: Mutex>> = Mutex::new(RefCell::new(Queue::new())); +static STATE: Mutex> = Mutex::new(RefCell::new(Ieee802154State::Idle)); + +extern "C" { + fn bt_bb_v2_init_cmplx(print_version: u32); // from libbtbb.a + + fn bt_bb_set_zb_tx_on_delay(time: u16); // from libbtbb.a + + fn esp_coex_ieee802154_ack_pti_set(event: ieee802154_coex_event_t); // from ??? + + fn esp_coex_ieee802154_txrx_pti_set(event: ieee802154_coex_event_t); // from ??? + + fn phy_version_print(); // from libphy.a +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum Ieee802154State { + Idle, + Receive, + Transmit, + TxAck, +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq)] +enum Ieee802154TxRxScene { + Idle, + Tx, + Rx, + TxAt, + RxAt, +} + +/// A raw payload received on some channel +#[derive(Debug)] +pub struct RawReceived { + /// Payload + pub data: [u8; FRAME_SIZE], + /// Receiver channel + pub channel: u8, +} + +pub(crate) fn esp_ieee802154_enable(radio_clock_control: &mut RADIO_CLK) { + radio_clock_control.init_clocks(); + radio_clock_control.enable(RadioPeripherals::Phy); + radio_clock_control.enable(RadioPeripherals::Ieee802154); + + esp_phy_enable(); + esp_btbb_enable(); + ieee802154_mac_init(); + + unsafe { phy_version_print() }; // libphy.a + log::info!("date={:x}", mac_date()); +} + +fn esp_phy_enable() { + unsafe { + let mut calibration_data = esp_phy_calibration_data_t { + version: [0u8; 4], + mac: [0u8; 6], + opaque: [0u8; 1894], + }; + + register_chipv7_phy( + core::ptr::null(), + &mut calibration_data as *mut esp_phy_calibration_data_t, + esp_phy_calibration_mode_t_PHY_RF_CAL_FULL, + ); + } +} + +fn esp_btbb_enable() { + unsafe { bt_bb_v2_init_cmplx(PHY_ENABLE_VERSION_PRINT) }; +} + +fn ieee802154_mac_init() { + #[cfg(feature = "esp32c6")] + unsafe { + extern "C" { + static mut coex_pti_tab_ptr: u32; + static coex_pti_tab: u8; + } + + // Manually set `coex_pti_tab_ptr` pointing to `coex_pti_tab` + core::ptr::addr_of_mut!(coex_pti_tab_ptr).write_volatile(&coex_pti_tab as *const _ as u32); + } + + ieee802154_pib_init(); + + enable_events(Event::mask()); + disable_events(Event::Timer0Overflow | Event::Timer1Overflow); + + enable_tx_abort_events( + TxAbortReason::RxAckTimeout + | TxAbortReason::TxCoexBreak + | TxAbortReason::TxSecurityError + | TxAbortReason::CcaFailed + | TxAbortReason::CcaBusy + | TxAbortReason::TxStop, + ); + enable_rx_abort_events( + RxAbortReason::TxAckTimeout | RxAbortReason::TxAckCoexBreak | RxAbortReason::RxStop, + ); + + set_ed_sample_mode(EdSampleMode::Avg); + + unsafe { esp_coex_ieee802154_ack_pti_set(ieee802154_coex_event_t_IEEE802154_MIDDLE) }; + ieee802154_set_txrx_pti(Ieee802154TxRxScene::Idle); + + unsafe { + bt_bb_set_zb_tx_on_delay(50); // set tx on delay for libbtbb.a + } + set_rx_on_delay(50); + + // memset(s_rx_frame, 0, sizeof(s_rx_frame)); + // s_ieee802154_state = IEEE802154_STATE_IDLE; + + unsafe { + esp_hal::interrupt::bind_interrupt( + esp_hal::peripherals::Interrupt::ZB_MAC, + ZB_MAC.handler(), + ); + } + esp_hal::interrupt::enable(esp_hal::peripherals::Interrupt::ZB_MAC, ZB_MAC.priority()).unwrap(); +} + +fn ieee802154_set_txrx_pti(txrx_scene: Ieee802154TxRxScene) { + match txrx_scene { + Ieee802154TxRxScene::Idle => { + unsafe { esp_coex_ieee802154_txrx_pti_set(ieee802154_coex_event_t_IEEE802154_IDLE) }; + } + Ieee802154TxRxScene::Tx | Ieee802154TxRxScene::Rx => { + unsafe { esp_coex_ieee802154_txrx_pti_set(ieee802154_coex_event_t_IEEE802154_LOW) }; + } + Ieee802154TxRxScene::TxAt | Ieee802154TxRxScene::RxAt => { + unsafe { esp_coex_ieee802154_txrx_pti_set(ieee802154_coex_event_t_IEEE802154_MIDDLE) }; + } + } +} + +pub fn tx_init(frame: *const u8) { + let tx_frame = frame; + stop_current_operation(); + ieee802154_pib_update(); + ieee802154_sec_update(); + + set_tx_addr(tx_frame); + + if true + // ieee802154_frame_is_ack_required(frame) + { + // set rx pointer for ack frame + set_next_rx_buffer(); + } +} + +pub fn ieee802154_transmit(frame: *const u8, cca: bool) -> i32 { + critical_section::with(|cs| { + tx_init(frame); + + ieee802154_set_txrx_pti(Ieee802154TxRxScene::Tx); + + if cca { + // disable_events(IEEE802154_EVENT_ED_DONE); + // set_cmd(IEEE802154_CMD_CCA_TX_START); + // ieee802154_state = IEEE802154_STATE_TX_CCA; + } else { + set_cmd(Command::TxStart); + // if (ieee802154_frame_get_type(frame) == IEEE802154_FRAME_TYPE_ACK + // && ieee802154_frame_get_version(frame) == IEEE802154_FRAME_VERSION_2) + // { + // ieee802154_state = IEEE802154_STATE_TX_ENH_ACK; + // } else { + *STATE.borrow_ref_mut(cs) = Ieee802154State::Transmit; + // } + } + }); + + 0 // ESP_OK +} + +pub fn ieee802154_receive() -> i32 { + critical_section::with(|cs| { + if *STATE.borrow_ref(cs) == Ieee802154State::Receive { + return; + } + + rx_init(); + enable_rx(); + + *STATE.borrow_ref_mut(cs) = Ieee802154State::Receive; + }); + + 0 // ESP-OK +} + +pub fn ieee802154_poll() -> Option { + critical_section::with(|cs| { + let mut queue = RX_QUEUE.borrow_ref_mut(cs); + queue.dequeue() + }) +} + +fn rx_init() { + stop_current_operation(); + ieee802154_pib_update(); +} + +fn enable_rx() { + set_next_rx_buffer(); + ieee802154_set_txrx_pti(Ieee802154TxRxScene::Rx); + + set_cmd(Command::RxStart); + + // ieee802154_state = IEEE802154_STATE_RX; +} + +fn stop_current_operation() { + let events = get_events(); + set_cmd(Command::Stop); + clear_events(events); +} + +fn set_next_rx_buffer() { + unsafe { + set_rx_addr(RX_BUFFER.as_mut_ptr() as *mut u8); + } +} + +pub fn set_promiscuous(enable: bool) { + ieee802154_pib_set_promiscuous(enable); +} + +pub fn set_auto_ack_tx(enable: bool) { + ieee802154_pib_set_auto_ack_tx(enable); +} + +pub fn set_auto_ack_rx(enable: bool) { + ieee802154_pib_set_auto_ack_rx(enable); +} + +pub fn set_enhance_ack_tx(enable: bool) { + ieee802154_pib_set_enhance_ack_tx(enable); +} + +pub fn set_coordinator(enable: bool) { + ieee802154_pib_set_coordinator(enable); +} + +pub fn set_rx_when_idle(enable: bool) { + ieee802154_pib_set_rx_when_idle(enable); +} + +pub fn set_tx_power(power: i8) { + ieee802154_pib_set_tx_power(power); +} + +pub fn set_channel(channel: u8) { + ieee802154_pib_set_channel(channel); +} + +#[allow(unused)] +pub fn set_pending_mode(mode: PendingMode) { + ieee802154_pib_set_pending_mode(mode); +} + +#[allow(unused)] +pub fn set_multipan_enable(mask: u8) { + set_multipan_enable_mask(mask); +} + +pub fn set_short_address(index: u8, address: u16) { + ieee802154_pib_set_short_address(index, address); +} + +pub fn set_extended_address(index: u8, address: [u8; IEEE802154_FRAME_EXT_ADDR_SIZE]) { + ieee802154_pib_set_extended_address(index, address); +} + +pub fn set_cca_theshold(cca_threshold: i8) { + ieee802154_pib_set_cca_theshold(cca_threshold); +} + +pub fn set_cca_mode(mode: CcaMode) { + ieee802154_pib_set_cca_mode(mode); +} + +pub fn set_panid(index: u8, id: u16) { + ieee802154_pib_set_panid(index, id); +} + +#[inline(always)] +fn ieee802154_sec_update() { + let is_security = false; + set_transmit_security(is_security); + // ieee802154_sec_clr_transmit_security(); +} + +fn next_operation() { + let previous_operation = critical_section::with(|cs| { + let state = STATE.borrow_ref(cs).clone(); + + if ieee802154_pib_get_rx_when_idle() { + enable_rx(); + *STATE.borrow_ref_mut(cs) = Ieee802154State::Receive; + } else { + *STATE.borrow_ref_mut(cs) = Ieee802154State::Idle; + } + + state + }); + + match previous_operation { + Ieee802154State::Receive => crate::rx_available(), + Ieee802154State::Transmit => crate::tx_done(), + Ieee802154State::TxAck => crate::tx_done(), + _ => (), + } +} + +#[handler(priority = "Priority::Priority1")] +fn ZB_MAC() { + log::trace!("ZB_MAC interrupt"); + + let events = get_events(); + clear_events(events); + + log::trace!("events = {:032b}", events); + + if events & Event::RxSfdDone != 0 { + // IEEE802154_STATE_TX && IEEE802154_STATE_TX_CCA && IEEE802154_STATE_TX_ENH_ACK + // for isr processing delay + log::trace!("rx sfd done"); + } + + if events & Event::TxSfdDone != 0 { + // IEEE802154_STATE_RX for isr processing delay, only 821 + // IEEE802154_STATE_TX_ACK for workaround jira ZB-81. + log::trace!("tx sfd done"); + } + + if events & Event::TxDone != 0 { + log::trace!("tx done"); + next_operation(); + } + + if events & Event::RxDone != 0 { + log::trace!("rx done"); + unsafe { + log::trace!("Received raw {:x?}", RX_BUFFER); + critical_section::with(|cs| { + let mut queue = RX_QUEUE.borrow_ref_mut(cs); + if !queue.is_full() { + let item = RawReceived { + data: RX_BUFFER, + channel: freq_to_channel(get_freq()), + }; + queue.enqueue(item).ok(); + } else { + log::warn!("Receive queue full"); + } + + let frm = &RX_BUFFER[1..][..RX_BUFFER[0] as usize]; + if will_auto_send_ack(frm) { + *STATE.borrow_ref_mut(cs) = Ieee802154State::TxAck; + } else if should_send_enhanced_ack(frm) { + // TODO + } else { + // esp_ieee802154_coex_pti_set(IEEE802154_IDLE_RX); + next_operation(); + } + }); + } + } + + if events & Event::AckRxDone != 0 { + log::info!("EventAckRxDone"); + } + + if events & Event::AckTxDone != 0 { + log::trace!("EventAckTxDone"); + next_operation(); + } + + if events & Event::TxAbort != 0 { + log::trace!("TxAbort"); + abort_tx(); + } + + if events & Event::RxAbort != 0 { + log::trace!("RxAbort"); + abort_rx(); + } +} + +fn freq_to_channel(freq: u8) -> u8 { + (freq - 3) / 5 + 11 +} + +fn will_auto_send_ack(frame: &[u8]) -> bool { + frame_is_ack_required(frame) && frame_get_version(frame) <= FRAME_VERSION_1 && get_tx_auto_ack() +} + +fn should_send_enhanced_ack(frame: &[u8]) -> bool { + frame_is_ack_required(frame) + && frame_get_version(frame) <= FRAME_VERSION_2 + && get_tx_enhance_ack() +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 14b02c74fdf..9f9a9d10195 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -11,48 +11,49 @@ cfg-if = "1.0.0" critical-section = "1.1.2" crypto-bigint = { version = "0.5.5", default-features = false } elliptic-curve = { version = "0.13.8", default-features = false, features = ["sec1"] } -# some examples use a huge amount of stack embassy-executor = { version = "0.5.0", features = ["task-arena-size-40960"] } -embassy-sync = "0.5.0" embassy-futures = "0.1.1" +embassy-sync = "0.5.0" embassy-time = "0.3.0" embassy-time-driver = { version = "0.1.0", optional = true } embassy-usb = { version = "0.1.0", default-features = false, optional = true } +embedded-can = "0.4.1" embedded-graphics = "0.8.1" embedded-hal = "1.0.0" embedded-hal-02 = { version = "0.2.7", package = "embedded-hal", features = ["unproven"] } embedded-hal-async = "1.0.0" embedded-hal-bus = "0.1.0" embedded-io-async = "0.6.1" -embedded-can = "0.4.1" esp-alloc = { version = "0.3.0", path = "../esp-alloc" } esp-backtrace = { version = "0.11.1", features = ["exception-handler", "panic-handler", "println"] } esp-hal = { version = "0.17.0", path = "../esp-hal", features = ["log"] } -esp-hal-smartled = { version = "0.10.0", path = "../esp-hal-smartled", optional = true } +esp-hal-smartled = { version = "0.10.0", path = "../esp-hal-smartled", optional = true } +esp-ieee802154 = { version = "0.1.0", path = "../esp-ieee802154", optional = true } esp-println = { version = "0.9.1", features = ["log"] } fugit = "0.3.7" heapless = "0.8.0" hex-literal = "0.4.1" hmac = { version = "0.12.1", default-features = false } +ieee802154 = "0.6.1" lis3dh-async = "0.9.2" +log = "0.4.21" nb = "1.1.0" p192 = { version = "0.13.0", default-features = false, features = ["arithmetic"] } p256 = { version = "0.13.2", default-features = false, features = ["arithmetic"] } -portable-atomic = { version = "1.6.0", default-features = false } +portable-atomic = { version = "1.6.0", default-features = false } sha2 = { version = "0.10.8", default-features = false } smart-leds = "0.4.0" ssd1306 = "0.8.4" static_cell = { version = "2.0.0", features = ["nightly"] } usb-device = "0.3.2" usbd-serial = "0.2.1" -log = "0.4" [features] esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-hal-smartled/esp32"] esp32c2 = ["esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-println/esp32c2"] esp32c3 = ["esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-println/esp32c3", "esp-hal-smartled/esp32c3"] -esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-hal-smartled/esp32c6"] -esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-println/esp32h2", "esp-hal-smartled/esp32h2"] +esp32c6 = ["esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-hal-smartled/esp32c6", "esp-ieee802154/esp32c6"] +esp32h2 = ["esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-println/esp32h2", "esp-hal-smartled/esp32h2", "esp-ieee802154/esp32h2"] esp32s2 = ["esp-hal/esp32s2", "esp-backtrace/esp32s2", "esp-println/esp32s2", "esp-hal-smartled/esp32s2"] esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-println/esp32s3", "esp-hal-smartled/esp32s3"] diff --git a/examples/build.rs b/examples/build.rs new file mode 100644 index 00000000000..ce3122db3eb --- /dev/null +++ b/examples/build.rs @@ -0,0 +1,7 @@ +fn main() { + if cfg!(feature = "esp32c6") || cfg!(feature = "esp32h2") { + println!("cargo::rustc-link-arg=-Trom_coexist.x"); + println!("cargo::rustc-link-arg=-Trom_functions.x"); + println!("cargo::rustc-link-arg=-Trom_phy.x"); + } +} diff --git a/examples/src/bin/ieee802154_receive_all_frames.rs b/examples/src/bin/ieee802154_receive_all_frames.rs new file mode 100644 index 00000000000..5adb3c0ad0c --- /dev/null +++ b/examples/src/bin/ieee802154_receive_all_frames.rs @@ -0,0 +1,33 @@ +//% CHIPS: esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{peripherals::Peripherals, prelude::*}; +use esp_ieee802154::*; +use esp_println::println; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take(); + let mut ieee802154 = Ieee802154::new(peripherals.IEEE802154, &mut peripherals.RADIO_CLK); + + ieee802154.set_config(Config { + channel: 15, + promiscuous: true, + rx_when_idle: true, + auto_ack_rx: false, + auto_ack_tx: false, + ..Config::default() + }); + + println!("Start receiving:"); + ieee802154.start_receive(); + + loop { + if let Some(frame) = ieee802154.get_received() { + println!("Received {:?}\n", &frame); + } + } +} diff --git a/examples/src/bin/ieee802154_receive_frame.rs b/examples/src/bin/ieee802154_receive_frame.rs new file mode 100644 index 00000000000..07e5de3f071 --- /dev/null +++ b/examples/src/bin/ieee802154_receive_frame.rs @@ -0,0 +1,35 @@ +//% CHIPS: esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{peripherals::Peripherals, prelude::*}; +use esp_ieee802154::*; +use esp_println::println; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take(); + let mut ieee802154 = Ieee802154::new(peripherals.IEEE802154, &mut peripherals.RADIO_CLK); + + ieee802154.set_config(Config { + channel: 15, + promiscuous: false, + rx_when_idle: true, + auto_ack_rx: true, + auto_ack_tx: true, + pan_id: Some(0x4242), + short_addr: Some(0x2323), + ..Config::default() + }); + + println!("Start receiving:"); + ieee802154.start_receive(); + + loop { + if let Some(frame) = ieee802154.get_received() { + println!("Received {:?}\n", &frame); + } + } +} diff --git a/examples/src/bin/ieee802154_send_broadcast_frame.rs b/examples/src/bin/ieee802154_send_broadcast_frame.rs new file mode 100644 index 00000000000..070cf8e7983 --- /dev/null +++ b/examples/src/bin/ieee802154_send_broadcast_frame.rs @@ -0,0 +1,71 @@ +//% CHIPS: esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + clock::ClockControl, + delay::Delay, + peripherals::Peripherals, + prelude::*, + system::SystemControl, +}; +use esp_ieee802154::*; +use esp_println::println; +use ieee802154::mac::{ + Address, + FrameContent, + FrameType, + FrameVersion, + Header, + PanId, + ShortAddress, +}; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::max(system.clock_control).freeze(); + + let delay = Delay::new(&clocks); + + let mut ieee802154 = Ieee802154::new(peripherals.IEEE802154, &mut peripherals.RADIO_CLK); + + ieee802154.set_config(Config { + channel: 15, + promiscuous: false, + pan_id: Some(0x4242), + short_addr: Some(0x2323), + ..Config::default() + }); + + let mut seq_number = 0u8; + loop { + ieee802154 + .transmit(&Frame { + header: Header { + frame_type: FrameType::Data, + frame_pending: false, + ack_request: false, + pan_id_compress: false, + seq_no_suppress: false, + ie_present: false, + version: FrameVersion::Ieee802154_2003, + seq: seq_number, + destination: Some(Address::Short(PanId(0xffff), ShortAddress(0xffff))), + source: None, + auxiliary_security_header: None, + }, + content: FrameContent::Data, + payload: heapless::Vec::from_slice(b"Hello World").unwrap(), + footer: [0u8; 2], + }) + .ok(); + + println!("Send frame with sequence number {seq_number}"); + delay.delay_millis(1000u32); + seq_number = seq_number.wrapping_add(1); + } +} diff --git a/examples/src/bin/ieee802154_send_frame.rs b/examples/src/bin/ieee802154_send_frame.rs new file mode 100644 index 00000000000..7992a71694e --- /dev/null +++ b/examples/src/bin/ieee802154_send_frame.rs @@ -0,0 +1,71 @@ +//% CHIPS: esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + clock::ClockControl, + delay::Delay, + peripherals::Peripherals, + prelude::*, + system::SystemControl, +}; +use esp_ieee802154::*; +use esp_println::println; +use ieee802154::mac::{ + Address, + FrameContent, + FrameType, + FrameVersion, + Header, + PanId, + ShortAddress, +}; + +#[entry] +fn main() -> ! { + let mut peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::max(system.clock_control).freeze(); + + let delay = Delay::new(&clocks); + + let mut ieee802154 = Ieee802154::new(peripherals.IEEE802154, &mut peripherals.RADIO_CLK); + + ieee802154.set_config(Config { + channel: 15, + promiscuous: false, + pan_id: Some(0x4242), + short_addr: Some(0x2222), + ..Config::default() + }); + + let mut seq_number = 0u8; + loop { + ieee802154 + .transmit(&Frame { + header: Header { + frame_type: FrameType::Data, + frame_pending: false, + ack_request: true, + pan_id_compress: false, + seq_no_suppress: false, + ie_present: false, + version: FrameVersion::Ieee802154_2003, + seq: seq_number, + destination: Some(Address::Short(PanId(0x4242), ShortAddress(0x2323))), + source: None, + auxiliary_security_header: None, + }, + content: FrameContent::Data, + payload: heapless::Vec::from_slice(b"Hello World").unwrap(), + footer: [0u8; 2], + }) + .ok(); + + println!("Send frame with sequence number {seq_number}"); + delay.delay_millis(1000u32); + seq_number = seq_number.wrapping_add(1); + } +}