#![no_std]
mod configure;
mod eratta;
pub mod params;
pub mod shared;
pub mod spi_interface;
mod status;
use defmt::{Format, println};
use hal::dma::DmaChannel;
use crate::{
params::{ModulationParams8x, ModulationParamsLora6x, PacketParams, PacketParamsLora},
shared::{OpCode, RadioError, RadioPins, Register, Register::Reg8x, Register6x, Register8x},
spi_interface::{Interface, RADIO_BUF_SIZE, Spi_},
};
const FIRMWARE_VERSION_8X_A: u16 = 0xA9B5;
const FIRMWARE_VERSION_8X_B: u16 = 0xA9B7;
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Format)]
#[allow(dead_code)]
pub enum PacketType {
Gfsk = 0,
Lora = 1,
Ranging = 2,
LrFhssFlrc = 3,
Ble = 4,
}
#[repr(u16)]
#[derive(Clone, Copy, Format)]
#[allow(dead_code)]
pub enum LoraNetwork {
Public = 0x3444, Private = 0x1424, }
#[repr(u8)]
#[derive(Clone, Copy, Format)]
#[allow(dead_code)]
pub enum RampTime6x {
R10 = 0,
R20 = 1,
R40 = 2,
R80 = 3,
R200 = 4,
R800 = 5,
R1700 = 6,
R3400 = 7,
}
#[repr(u8)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum RampTime8x {
R02 = 0x0,
R04 = 0x20,
R06 = 0x40,
R08 = 0x60,
R10 = 0x80,
R12 = 0xa0,
R16 = 0xc0,
R20 = 0xe0,
}
#[repr(u16)]
#[derive(Clone, Copy, Format)]
#[allow(dead_code)]
pub enum Irq {
TxDone = 0,
RxDone = 1,
PremableDetected = 2,
SyncWordValid = 3,
HeaderValid = 4,
HeaderErr = 5,
CrcErr = 6,
CadDone = 7,
CadDetected = 8,
Timeout = 9,
LrFhssHop = 14,
SyncWordError,
}
impl Irq {
pub fn val_8x(&self) -> u16 {
match self {
Self::SyncWordValid => 2,
Self::SyncWordError => 3,
Self::CadDone => 12,
Self::CadDetected => 13,
Self::Timeout => 14,
Self::PremableDetected => 15, _ => *self as u16, }
}
}
#[repr(u8)] #[derive(Clone, Copy)]
pub enum OutputPower6x {
Db14 = 0x0e,
Db17 = 0x11,
Db20 = 0x14,
Db22 = 0x16,
}
impl Default for OutputPower6x {
fn default() -> Self {
Self::Db22 }
}
impl OutputPower6x {
pub fn dutycycle_hpmax(&self) -> (u8, u8) {
match self {
Self::Db14 => (0x02, 0x02),
Self::Db17 => (0x02, 0x03),
Self::Db20 => (0x03, 0x05),
Self::Db22 => (0x04, 0x07),
}
}
}
#[repr(u8)]
#[derive(Clone, Copy)]
pub enum FallbackMode {
Fs = 0x40,
StdbyXosc = 0x30,
StdbyRc = 0x20,
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum RxMode {
Continuous,
Single,
SingleWithTimeout,
Listen,
}
#[repr(u8)]
#[derive(Clone, Copy, Format)]
pub enum SleepConfig {
ColdStart = 0,
WarmStart = 1,
}
#[derive(Clone, Copy, Format)]
#[allow(dead_code)]
pub enum OperatingMode {
Sleep(SleepConfig),
StbyRc,
StbyOsc,
Fs,
Tx(f32),
Rx(f32),
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Format, Debug)]
pub enum OperatingModeRead {
StbyRc = 2,
StbyOsc = 3,
Fs = 4,
Tx = 6,
Rx = 5,
}
#[derive(Format)]
pub struct RxBufferStatus {
pub status: u8,
pub payload_len: u8,
pub rx_start_buf_pointer: u8,
}
#[derive(Format, Default)]
pub struct RxPacketStatusLora {
pub status: u8,
pub rssi: u8,
pub snr: u8,
pub signal_rssi: u8,
}
pub struct RxStatistics6x {
pub status: u8,
pub num_received: u16,
pub num_crc_error: u16,
pub num_length_error: u16,
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Format, Debug)]
pub enum CommandStatus {
CommandProcessSuccess8x,
DataAvailable = 2,
CommandTimeout = 3,
CommandProcessingError = 4,
FailureToExecuteCommand = 5,
CommandTxDone = 6,
}
#[derive(Clone)]
pub struct RadioConfig6x {
pub packet_type: PacketType,
pub rf_freq: u32,
pub use_dio2_as_rfswitch: bool,
pub dc_dc_enabled: bool,
pub modulation_params: ModulationParamsLora6x,
pub packet_params: PacketParamsLora,
pub tx_timeout: f32,
pub rx_timeout: f32,
pub fallback_mode: FallbackMode,
pub ramp_time: RampTime6x,
pub lora_network: LoraNetwork,
pub output_power: OutputPower6x,
}
impl Default for RadioConfig6x {
fn default() -> Self {
Self {
packet_type: PacketType::Lora,
rf_freq: 915_000_000,
use_dio2_as_rfswitch: true,
dc_dc_enabled: true,
modulation_params: Default::default(),
packet_params: Default::default(),
tx_timeout: 0., rx_timeout: 0.,
fallback_mode: FallbackMode::StdbyRc,
ramp_time: RampTime6x::R200, lora_network: LoraNetwork::Private,
output_power: OutputPower6x::Db22,
}
}
}
#[derive(Clone)]
pub struct RadioConfig8x {
pub packet_type: PacketType,
pub rf_freq: u32,
pub dc_dc_enabled: bool,
pub modulation_params: ModulationParams8x,
pub packet_params: PacketParams,
pub tx_timeout: f32,
pub rx_timeout: f32,
pub ramp_time: RampTime8x,
pub output_power: i8, }
impl Default for RadioConfig8x {
fn default() -> Self {
Self {
packet_type: PacketType::Lora,
rf_freq: 2_400_000_000,
dc_dc_enabled: true,
modulation_params: Default::default(),
packet_params: Default::default(),
tx_timeout: 0., rx_timeout: 0.,
ramp_time: RampTime8x::R10, output_power: 13,
}
}
}
#[derive(Clone)]
pub enum RadioConfig {
R6x(RadioConfig6x),
R8x(RadioConfig8x),
}
pub struct Radio {
pub interface: Interface,
pub config: RadioConfig,
}
impl Radio {
pub fn new(
config: RadioConfig,
spi: Spi_,
pins: RadioPins,
tx_ch: DmaChannel,
rx_ch: DmaChannel,
) -> Result<Self, RadioError> {
let tx_addr = 0;
let rx_addr = 0;
let r8x = matches!(config, RadioConfig::R8x(_));
let mut result = Self {
config,
interface: Interface {
spi,
pins,
tx_ch,
rx_ch,
read_buf: [0; RADIO_BUF_SIZE],
write_buf: [0; RADIO_BUF_SIZE],
rx_payload_len: 0,
rx_payload_start: 0,
r8x,
},
};
if r8x {
let firmware_version = result
.interface
.read_reg_word_16(Reg8x(Register8x::FirmwareVersions))?;
if ![FIRMWARE_VERSION_8X_A, FIRMWARE_VERSION_8X_B].contains(&firmware_version) {
return Err(RadioError::FirmwareVersion);
}
} else {
result.interface.reset();
}
result.interface.wait_on_busy()?;
result.set_op_mode(OperatingMode::StbyRc)?;
let packet_type = match result.config {
RadioConfig::R6x(ref config) => config.packet_type,
RadioConfig::R8x(ref config) => config.packet_type,
};
result
.interface
.write_op_word(OpCode::SetPacketType, packet_type as u8)?;
result.set_rf_freq()?;
result.set_mod_params()?;
result.set_packet_params()?;
if let RadioConfig::R6x(_) = result.config {
result.set_rxgain_retention()?;
result.tx_clamp_workaround()?;
}
result.set_tx_params()?;
result
.interface
.write(&[OpCode::SetBufferBaseAddress as u8, tx_addr, rx_addr])?;
match result.config {
RadioConfig::R6x(ref config) => {
let (dc_dc, fallback, dio, network) = (
config.dc_dc_enabled,
config.fallback_mode,
config.use_dio2_as_rfswitch,
config.lora_network,
);
result
.interface
.write_op_word(OpCode::SetRegulatorMode, dc_dc as u8)?;
result.set_pa_config()?;
result
.interface
.write_op_word(OpCode::SetRxTxFallbackMode, fallback as u8)?;
result
.interface
.write_op_word(OpCode::SetDIO2AsRfSwitchCtrl, dio as u8)?;
result.set_sync_word(network)?;
}
RadioConfig::R8x(ref config) => {
}
}
Ok(result)
}
pub fn send_payload(&mut self, payload: &[u8], rf_freq: u32) -> Result<(), RadioError> {
let payload_len = payload.len();
let op_code = match self.config {
RadioConfig::R6x(_) => OpCode::WriteBuffer as u8,
RadioConfig::R8x(_) => OpCode::WriteBuffer.val_8x(),
};
let offset = 0;
if payload_len + 2 > RADIO_BUF_SIZE {
return Err(RadioError::PayloadSize(payload_len));
}
self.interface.write_buf[0] = op_code;
self.interface.write_buf[1] = offset;
for (i, word) in payload.iter().enumerate() {
self.interface.write_buf[i + 2] = *word;
}
let mut write_buf = [0; RADIO_BUF_SIZE];
write_buf[..payload_len + 2].copy_from_slice(&self.interface.write_buf[..payload_len + 2]);
if payload_len > RADIO_BUF_SIZE {
return Err(RadioError::PayloadSize(payload_len));
}
match &mut self.config {
RadioConfig::R6x(config) => {
config.rf_freq = rf_freq;
config.packet_params.payload_len = payload_len as u8;
}
RadioConfig::R8x(config) => {
config.rf_freq = rf_freq;
match &mut config.packet_params {
PacketParams::Lora(p) => p.payload_len = payload_len as u8,
PacketParams::Flrc(p) => p.payload_len = payload_len as u8,
}
}
}
self.set_op_mode(OperatingMode::StbyRc)?;
if !self.interface.r8x {
self.mod_quality_workaround();
}
self.set_rf_freq()?;
let tx_addr = 0;
let rx_addr = 0;
self.interface
.write(&[OpCode::SetBufferBaseAddress as u8, tx_addr, rx_addr])?;
self.set_packet_params()?;
{
self.interface.write(&write_buf[..2 + payload_len])?;
}
self.start_transmission()?;
Ok(())
}
pub fn start_transmission(&mut self) -> Result<(), RadioError> {
self.set_irq(&[Irq::TxDone, Irq::Timeout], &[])?;
let timeout = match &self.config {
RadioConfig::R6x(c) => c.tx_timeout,
RadioConfig::R8x(c) => c.tx_timeout,
};
self.set_op_mode(OperatingMode::Tx(timeout))?;
Ok(())
}
pub fn receive(&mut self, max_payload_len: u8, rf_freq: u32, timeout_override: Option<f32>) -> Result<(), RadioError> {
let timeout = match timeout_override {
Some(t) => t,
None => {
match &self.config {
RadioConfig::R6x(config) => config.rx_timeout,
RadioConfig::R8x(config) => config.rx_timeout,
}
}
};
match &mut self.config {
RadioConfig::R6x(config) => {
config.rf_freq = rf_freq;
config.packet_params.payload_len = max_payload_len;
}
RadioConfig::R8x(config) => {
config.rf_freq = rf_freq;
match &mut config.packet_params {
PacketParams::Lora(p) => p.payload_len = max_payload_len,
PacketParams::Flrc(p) => p.payload_len = max_payload_len,
}
}
}
self.set_op_mode(OperatingMode::StbyRc)?;
self.set_rf_freq()?;
let tx_addr = 0;
let rx_addr = 0;
self.interface
.write(&[OpCode::SetBufferBaseAddress as u8, tx_addr, rx_addr])?;
self.set_irq(&[], &[Irq::RxDone, Irq::Timeout])?;
self.set_op_mode(OperatingMode::Rx(timeout))?;
Ok(())
}
pub fn cleanup_tx(&mut self) -> Result<(), RadioError> {
self.clear_irq(&[Irq::TxDone, Irq::Timeout])?;
let status = self.get_status()?;
if status.0 != OperatingModeRead::StbyRc || status.1 != CommandStatus::CommandTxDone {
println!(
"\nProblem with Tx status post-write. Operating mode: {} Command status: {}",
status.0, status.1
);
return Err(RadioError::Status((status.0, status.1)));
}
if let RadioConfig::R6x(_) = self.config {
let device_errors = self.get_device_errors()?;
if device_errors != 0 {
println!("\nDevice error: {}", device_errors);
return Err(RadioError::Device);
}
}
Ok(())
}
pub fn cleanup_rx(&mut self) -> Result<(RxBufferStatus, CommandStatus), RadioError> {
let (op_mode, cmd_status) = self.get_status()?;
if op_mode != OperatingModeRead::StbyRc
|| (cmd_status != CommandStatus::DataAvailable
&& cmd_status != CommandStatus::CommandTimeout)
{
println!(
"\nProblem with Rx status post-read. Operating mode: {} Command status: {}",
op_mode, cmd_status
);
self.clear_irq(&[Irq::RxDone, Irq::Timeout])?;
return Err(RadioError::Status((op_mode, cmd_status)));
}
if cmd_status == CommandStatus::DataAvailable {
let op_code = match self.config {
RadioConfig::R6x(_) => OpCode::GetIrqStatus as u8,
RadioConfig::R8x(_) => OpCode::GetIrqStatus.val_8x(),
};
let mut irq_status_buf = [op_code, 0, 0, 0];
self.interface.read(&mut irq_status_buf)?;
let irq_status = u16::from_be_bytes([irq_status_buf[2], irq_status_buf[3]]);
if irq_status & (0b11 << 5) != 0 {
println!("Irq CRC error post-read: {}", irq_status); self.clear_irq(&[Irq::RxDone, Irq::Timeout])?; return Err(RadioError::Crc);
}
}
self.clear_irq(&[Irq::RxDone, Irq::Timeout])?;
let buf_status = self.get_rx_buffer_status()?;
self.interface.rx_payload_len = buf_status.payload_len;
self.interface.rx_payload_start = buf_status.rx_start_buf_pointer;
if let RadioConfig::R6x(_) = self.config {
self.implicit_header_to_workaround()?;
let device_errors = self.get_device_errors()?;
if device_errors != 0 {
println!("\nDevice error: {}", device_errors);
return Err(RadioError::Device);
}
}
if cmd_status == CommandStatus::DataAvailable {
let mut buf = [0; RADIO_BUF_SIZE];
let op_code = match self.config {
RadioConfig::R6x(_) => OpCode::ReadBuffer as u8,
RadioConfig::R8x(_) => OpCode::ReadBuffer.val_8x(),
};
buf[0] = op_code;
buf[1] = buf_status.rx_start_buf_pointer;
buf[2] = 0;
let payload_len = buf_status.payload_len as usize;
let buf_len = payload_len + 3;
if self.interface.read(&mut buf[..buf_len]).is_err() {
println!("Error reading the buffer");
}
self.interface.read_buf[..payload_len].copy_from_slice(&buf[3..buf_len])
}
Ok((buf_status, cmd_status))
}
fn set_irq(&mut self, dio1: &[Irq], dio3: &[Irq]) -> Result<(), RadioError> {
let mut irq_word: u16 = 0;
let mut dio1_word: u16 = 0;
let mut dio3_word: u16 = 0;
match self.config {
RadioConfig::R6x(_) => {
for irq in dio1 {
irq_word |= 1 << (*irq as u16);
dio1_word |= 1 << (*irq as u16);
}
for irq in dio3 {
irq_word |= 1 << (*irq as u16);
dio3_word |= 1 << (*irq as u16);
}
}
RadioConfig::R8x(_) => {
for irq in dio1 {
irq_word |= 1 << (irq.val_8x());
dio1_word |= 1 << (irq.val_8x());
}
for irq in dio3 {
irq_word |= 1 << (irq.val_8x());
dio3_word |= 1 << (irq.val_8x());
}
}
}
let irq_bytes = irq_word.to_be_bytes();
let dio1_bytes = dio1_word.to_be_bytes();
let dio3_bytes = dio3_word.to_be_bytes();
let op_code = match self.config {
RadioConfig::R6x(_) => OpCode::SetDioIrqParams as u8,
RadioConfig::R8x(_) => OpCode::SetDioIrqParams.val_8x(),
};
self.interface.write(&[
op_code,
irq_bytes[0],
irq_bytes[1],
dio1_bytes[0],
dio1_bytes[1],
0, 0,
dio3_bytes[0],
dio3_bytes[1],
])
}
fn set_sync_word(&mut self, network: LoraNetwork) -> Result<(), RadioError> {
let sync_word_bytes = (network as u16).to_be_bytes();
match self.config {
RadioConfig::R6x(_) => {
self.interface.write_reg_word(
Register::Reg6x(Register6x::LoraSyncWordMsb),
sync_word_bytes[0],
)?;
self.interface.write_reg_word(
Register::Reg6x(Register6x::LoraSyncWordLsb),
sync_word_bytes[1],
)?;
}
RadioConfig::R8x(_) => {
}
};
Ok(())
}
pub fn clear_irq(&mut self, irqs: &[Irq]) -> Result<(), RadioError> {
let mut irq_word: u16 = 0;
for irq in irqs {
let irq_val = match self.config {
RadioConfig::R6x(_) => *irq as u16,
RadioConfig::R8x(_) => irq.val_8x(),
};
irq_word |= 1 << irq_val;
}
let bytes = irq_word.to_be_bytes();
let op_code = match self.config {
RadioConfig::R6x(_) => OpCode::ClearIrqStatus as u8,
RadioConfig::R8x(_) => OpCode::ClearIrqStatus.val_8x(),
};
self.interface.write(&[op_code, bytes[0], bytes[1]])
}
pub fn get_irq_status(&mut self) -> Result<u8, RadioError> {
self.interface.read_op_word(OpCode::GetIrqStatus)
}
pub fn set_high_rx_gain(&mut self) -> Result<(), RadioError> {
if !self.interface.r8x {
panic!("High gain is unavailable on SX126x.")
}
let word = 0x25 | (3 << 6);
self.interface
.write_reg_word(Reg8x(Register8x::RxGain), word)
}
}