Closed JohannesProgrammiert closed 3 months ago
Hi @JohannesProgrammiert Could you provide a PR? If not I'll do it when I have time.
The reason is I am using this as external library on Zephyr
Very interesting, do you have an open-source example on the usage? So far I just combined rust lib's without HW access to Zephyr, but never drivers. How do you integrate? Do you wrap the driver around a generic SPI-device, or have you generated a proper device in the DT? Would be very interesting to me to see an example.
Unfortunately, it is not open source. Roughly, what I did is implementing a FFI wrapper/C API around the rather complex UWB-control-machinery which I implemented in Rust. Then, using CMake, I make a C library from the cbindgen header and static library artifact. Any OS-dependent behavior (like SPI bus access) is specified using C-style callbacks by the Zephyr application. So no, it is not a 'proper' integration but a simple C library with its core implemented in Rust.
I hope that helps.
Regarding the issue; even though I like this project I switched back to the manufacturer API. The official API is kind of awkward but it is feature-complete (in terms of high-level abstractions). Also there is this document which doesn't seem to be considered in this project. For example, the register TXFSEQ is at 0x10
not at 0x12
as written in the official documentation.
I did start to implement the high-level functions from the official API 1:1 for this library but I gave up at some point. This is the state I left it in case someone does something similar or wants to continue:
//! Some more DW3000 API abstractions the dw3000-ng crate doesn't provide.
//! Inspired by the DW3000 C API.
use crate::DecawaveSpi;
/// Read from OTP.
/// The dw3000_ng API doesn't provide this helper function so it is taken from dw3000_device.c:_dwt_otpread.
#[allow(unused)]
fn otp_read<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Uninitialized>,
address: u16,
) -> Result<u32, dw3000_ng::ll::Error<SPI>> {
// set manual access mode
dw.ll().otp_cfg().modify(|_, w| w.otp_man(1))?;
// set the address
dw.ll().otp_addr().modify(|_, w| w.otp_addr(address))?;
// assert the read strobe
dw.ll().otp_cfg().modify(|_, w| w.otp_read(1))?;
let v = dw.ll().otp_rdata().read()?.value();
Ok(v)
}
/// Write to AON.
fn aon_write<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Ready>,
address: u16,
data: u8,
) -> Result<(), dw3000_ng::ll::Error<SPI>> {
let write_hi = address > 0xFF;
dw.ll().aon_addr().write(|w| w.value(address))?;
dw.ll().aon_wdata().write(|w| w.value(data))?;
dw.ll()
.aon_ctrl()
.write(|w| w.dca_write_hi(write_hi as u8).dca_enab(1).dca_write(1))?;
dw.ll()
.aon_ctrl()
.write(|w| w.dca_write_hi(0).dca_enab(0).dca_write(0))?; // reset bits
Ok(())
}
/// Read from AON.
fn aon_read<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Ready>,
address: u16,
) -> Result<u8, dw3000_ng::ll::Error<SPI>> {
dw.ll().aon_addr().write(|w| w.value(address))?;
dw.ll().aon_ctrl().write(|w| w.dca_enab(1).dca_read(1))?;
dw.ll().aon_ctrl().write(|w| w.dca_enab(0).dca_read(0))?;
Ok(dw.ll().aon_rdata().read()?.value())
}
pub(crate) fn log_pmsc_state(state: u8) {
if state == 0x0 {
log::debug!("PMSC_STATE: WAKEUP");
} else if state == 0x1 {
log::debug!("PMSC_STATE: IDLE_RC 0x1");
} else if state == 0x2 {
log::debug!("PMSC_STATE: IDLE_RC 0x2");
} else if state == 0x3 {
log::debug!("PMSC_STATE: IDLE");
} else if state >= 0x8 && state <= 0xF {
log::debug!("PMSC_STATE: TX");
} else if state >= 0x12 && state <= 0x19 {
log::debug!("PMSC_STATE: RX");
} else {
log::debug!("PMSC_STATE: INVALID");
}
}
pub(crate) fn log_system_status(status: dw3000_ng::ll::sys_status::R) {
if status.irqs() != 0 {
log::debug!("irqs set");
}
if status.cplock() != 0 {
log::debug!("cplock set");
}
if status.spicrce() != 0 {
log::debug!("spicrce set");
}
if status.aat() != 0 {
log::debug!("aat set");
}
if status.txfrb() != 0 {
log::debug!("txfrb set");
}
if status.txprs() != 0 {
log::debug!("txprs set");
}
if status.txphs() != 0 {
log::debug!("txphs set");
}
if status.txfrs() != 0 {
log::debug!("txfrs set");
}
if status.rxprd() != 0 {
log::debug!("rxprd set");
}
if status.rxsfdd() != 0 {
log::debug!("rxsfdd set");
}
if status.ciadone() != 0 {
log::debug!("ciadone set");
}
if status.rxphd() != 0 {
log::debug!("rxphd set");
}
if status.rxphe() != 0 {
log::debug!("rxphe set");
}
if status.rxfr() != 0 {
log::debug!("rxfr set");
}
if status.rxfcg() != 0 {
log::debug!("rxfcg set");
}
if status.rxfce() != 0 {
log::debug!("rxfce set");
}
if status.rxfsl() != 0 {
log::debug!("rxfsl set");
}
if status.rxfto() != 0 {
log::debug!("rxfto set");
}
if status.ciaerr() != 0 {
log::debug!("ciaerr set");
}
if status.vwarn() != 0 {
log::debug!("vwarn set");
}
if status.rxovrr() != 0 {
log::debug!("rxovrr set");
}
if status.rxpto() != 0 {
log::debug!("rxpto set");
}
if status.spirdy() != 0 {
log::debug!("spirdy set");
}
if status.rcinit() != 0 {
log::debug!("rcinit set");
}
if status.pll_hilo() != 0 {
log::debug!("pll_hilo set");
}
if status.rxsto() != 0 {
log::debug!("rxsto set");
}
if status.hpdwarn() != 0 {
log::debug!("hpdwarn set");
}
if status.cperr() != 0 {
log::debug!("cperr set");
}
if status.arfe() != 0 {
log::debug!("arfe set");
}
if status.rxprej() != 0 {
log::debug!("rxprej set");
}
if status.vt_det() != 0 {
log::debug!("vt_det set");
}
if status.gpioirq() != 0 {
log::debug!("gpioirq set");
}
if status.aes_done() != 0 {
log::debug!("aes_done set");
}
if status.aes_err() != 0 {
log::debug!("aes_err set");
}
if status.cmd_err() != 0 {
log::debug!("cmd_err set");
}
if status.spi_ovf() != 0 {
log::debug!("spi_ovf set");
}
if status.spi_unf() != 0 {
log::debug!("spi_unf set");
}
if status.spierr() != 0 {
log::debug!("spierr set");
}
if status.cca_fail() != 0 {
log::debug!("cca_fail set");
}
}
pub(crate) fn configure_sleep<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Ready>,
) -> Result<(), dw3000_ng::Error<SPI>> {
// set LP OSC trim value to increase freq. to max
const AON_LPOSC_TRIM: u16 = 0x10B;
const AON_SLPCNT_CAL_CTRL: u16 = 0x104;
aon_write(dw, AON_LPOSC_TRIM, 0)?;
// reduce the internal WAKEUP delays to min to minimise wakeup time/reduce current consumption
/* NOTE: if using slow starting crystals > 1ms. Then this
* should not be used, as the device will issue SPI_RDY before crystal is stable.
* Should the host then issue start TX the PLL may not lock and the TX packet will not be sent.
*/
let temp = aon_read(dw, AON_SLPCNT_CAL_CTRL)? & 0x1F;
aon_write(dw, AON_SLPCNT_CAL_CTRL, temp)?;
dw.ll()
.aon_dig_cfg()
.write(|w| w.onw_run_sar(1).onw_pgfcal(1).onw_aon_dld(1))?;
dw.ll()
.aon_cfg()
.write(|w| w.pres_sleep(1).sleep_en(1).wake_csn(1))?;
Ok(())
}
pub(crate) fn basic_configure<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Uninitialized>,
config: &crate::settings::HardwareParams,
) -> Result<(), dw3000_ng::Error<SPI>> {
// uint8_t chan = config->chan;
// uint32_t temp;
let tx_rx_code = if config.preamble_symbol < 9 || config.preamble_symbol > 12 {
config.channel.preamble_symbol()
} else {
config.preamble_symbol
};
let scp = tx_rx_code > 24; // should never occur
if scp {
unimplemented!()
}
use crate::settings::*;
let mut preamble_length = config.tx_preamble_len as u32;
/////////////////////////////////////////////////////////////////////////
// SYS_CFG
// clear the PHR Mode, PHR Rate, STS Protocol, SDC, PDOA Mode,
// then set the relevant bits according to configuration of the PHR Mode, PHR Rate, STS Protocol, SDC, PDOA Mode,
// DW proprietary extended frames PHR mode
const PHRMODE_EXT: u8 = 0x1;
// standard PHR rate
const PHRRATE_STD: u8 = 0x0;
dw.ll().sys_cfg().modify(|_, w| {
w.phr_mode(PHRMODE_EXT)
.phr_6m8(PHRRATE_STD)
.cp_spc(config.sts_mode as u8)
.pdoa_mode(config.pdoa_mode as u8)
.cp_sdc(0)
})?;
if config.sts_mode != StsMode::Off {
// set STS minimum threshold
preamble_length += (config.sts_length as u32 + 1) * 8;
dw.ll()
.sts_conf_0()
.modify(|_, w| w.sts_rtm(config.sts_mnth()))?;
}
/* Disable ADC count and peak growth checks */
dw.ll()
.sts_conf_1()
.modify(|_, w| w.sts_pgr_en(0).sts_ss_en(0).res_b0(0))?;
// configure OPS tables for non-SCP mode
if preamble_length >= 256 {
const OPS_SEL_LONG: u8 = 0;
dw.ll()
.otp_cfg()
.modify(|_, w| w.ops_sel(OPS_SEL_LONG).ops_kick(1))?
} else {
const OPS_SEL_SHORT: u8 = 2;
dw.ll()
.otp_cfg()
.modify(|_, w| w.ops_sel(OPS_SEL_SHORT).ops_kick(1))?
}
dw.ll().dtune0().modify(|_, w| {
// configure PAC size
w.pac(config.tx_preamble_len.recommended_pac());
if config.pdoa_mode == PdoaMode::Id1 {
// Disable STS CMF
w.dt0b4(0b0)
} else {
// Enable STS CMF
w.dt0b4(0b1)
}
})?;
dw.ll()
.sts_cfg()
.modify(|_, w| w.cps_len(config.sts_length as u8))?;
if config.tx_preamble_len == PreambleLength::Len72 {
// value 8 sets fine preamble length to 72 symbols - this is needed to set 72 length.
dw.ll().tx_fctrl().modify(|_, w| w.fine_plen(8))?;
} else {
// clear the setting in the FINE_PLEN register.
dw.ll().tx_fctrl().modify(|_, w| w.fine_plen(0))?;
}
// Optimal PD threshold
const PD_THRESH_OPTIMAL: u32 = 0xAF5F35CC;
// configure optimal preamble detection threshold.
dw.ll().dtune3().modify(|_, w| w.value(PD_THRESH_OPTIMAL))?;
/////////////////////////////////////////////////////////////////////////
// CHAN_CTRL
// temp = dw.ll().chan_ctrl().read()?;
// temp = dwt_read32bitoffsetreg(dw, CHAN_CTRL_ID, 0);
// temp &= (~(CHAN_CTRL_RX_PCODE_BIT_MASK | CHAN_CTRL_TX_PCODE_BIT_MASK | CHAN_CTRL_SFD_TYPE_BIT_MASK | CHAN_CTRL_RF_CHAN_BIT_MASK));
dw.ll().chan_ctrl().modify(|_, w| {
w.rx_pcode(0).tx_pcode(0).sfd_type(0).rf_chan(0);
if config.channel == Channel::Id9 {
w.rf_chan(1);
}
const SFD_8: u8 = 1;
w.rx_pcode(tx_rx_code).tx_pcode(tx_rx_code).sfd_type(SFD_8)
})?;
/////////////////////////////////////////////////////////////////////////
// TX_FCTRL
// Set up TX Preamble Size, PRF and Data Rate
const DATARATE_BR_6M8: u8 = 1;
dw.ll()
.tx_fctrl()
.modify(|_, w| w.txbr(DATARATE_BR_6M8).txpsr(config.tx_preamble_len as u8))?;
// DTUNE (SFD timeout)
// ... TODO
Ok(())
}
/// Load calibration values from OTP to BIAS_CTRL register.
/// This should be done at startup.
/// Actually, the BIAS_KICK and LDO_KICK registers should do this, but they do only partially.
fn load_pmsc_bias_ctrl<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Uninitialized>,
) -> Result<(), dw3000_ng::Error<SPI>> {
const LDOTUNELO_ADDRESS: u16 = 0x04;
const LDOTUNEHI_ADDRESS: u16 = 0x05;
const BIAS_TUNE_ADDRESS: u16 = 0x0A;
let ldo_tune_lo = otp_read(dw, LDOTUNELO_ADDRESS)?;
let ldo_tune_hi = otp_read(dw, LDOTUNEHI_ADDRESS)?;
let bias_tune_otp = otp_read(dw, BIAS_TUNE_ADDRESS)?;
const BIAS_BIT_MASK: u32 = 0x001f;
let bias_tune_trimmed = ((bias_tune_otp >> 16) & BIAS_BIT_MASK) as u16;
if ldo_tune_lo != 0 && ldo_tune_hi != 0 && bias_tune_trimmed != 0 {
log::trace!("LDO tune successfully read");
dw.ll().otp_cfg().modify(|_, w| w.ldo_kick(1))?;
dw.ll().otp_cfg().modify(|_, w| w.bias_kick(1))?;
dw.ll()
.bias_ctrl()
.modify(|_, w| w.value(bias_tune_trimmed))?;
} else {
log::error!("LDO tune OTP read fail");
}
Ok(())
}
/// Do basic initialization stuff
///
/// Is pretty much the same as dwt_initialise of the C API.
pub(crate) fn init<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Uninitialized>,
xtal_trim_ch5: u8,
) -> Result<(), dw3000_ng::Error<SPI>> {
if load_pmsc_bias_ctrl(dw).is_err() {
log::error!("Error loading bias CTRL from PMSC");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
if dw
.ll()
.xtal()
.modify(|_, w| w.value(xtal_trim_ch5))
.is_err()
{
log::error!("Could not configure XTAL");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
// load PLL coarse code from OTP
const PLL_CC_ADDRESS: u16 = 0x35;
let Ok(pll_coarse_code) = otp_read(dw, PLL_CC_ADDRESS) else {
return Err(dw3000_ng::Error::InvalidConfiguration);
};
let ch5_cc: u16 = (pll_coarse_code >> 8) as u16;
let ch9_cc: u8 = pll_coarse_code as u8;
if pll_coarse_code != 0 {
if dw
.ll()
.pll_cc()
.modify(|_, w| w.ch5_code(ch5_cc).ch9_code(ch9_cc))
.is_err()
{
log::error!("Cannot write pll_cc code");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
} else {
log::warn!("No PLL coarse code found in OTP");
}
Ok(())
}
/// Probe DW3000 chip.
///
/// Will try to read the device ID and check for validity.
/// Then checks IDLE_RC state is entered.
pub(crate) fn probe<SPI: DecawaveSpi>(
dw: &mut dw3000_ng::DW3000<SPI, dw3000_ng::Uninitialized>,
) -> Result<(), dw3000_ng::Error<SPI>> {
let dev_id = dw.ll().dev_id().read()?;
const REVISION: u8 = 0x2;
const VERSION: u8 = 0x1; // with PDoA
const MODEL: u8 = 0x03; // decawave 3000
const RIDTAG: u16 = 0xDECA; // register identification tag
if dev_id.rev() != REVISION {
log::error!("Revision is not 0x2");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
if dev_id.ver() != VERSION {
log::error!("Version is not 1 (PDoA)");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
if dev_id.model() != MODEL {
log::error!("Model is not DW3000");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
if dev_id.ridtag() != RIDTAG {
log::error!("Register identification tag is wrong");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
// check dw3000 is in INIT_RC state.
let Ok(init_rc) = dw.init_rc_passed() else {
return Err(dw3000_ng::Error::InvalidConfiguration);
};
if !init_rc {
log::error!("Decawave is not in INIT_RC state");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
let mut idle_rc = false;
for _i in 0..20000 {
let Ok(idle_rc_tmp) = dw.idle_rc_passed() else {
continue;
};
if idle_rc_tmp {
idle_rc = true;
break;
}
}
if !idle_rc {
log::error!("Decawave is not in IDLE_RC state after 20000 CPU cycles, aborting init");
return Err(dw3000_ng::Error::InvalidConfiguration);
}
Ok(())
}
@JohannesProgrammiert Could you open an issue for the registers you found that mismatch the official driver? I am pretty sure that I incorporated https://gist.github.com/egnor/455d510e11c22deafdec14b09da5bf54#list-of-undocumented-registers before release, but I could have missed some of them. Many thanks!
Original issue is fixed, I'll close this for now :)
@JohannesProgrammiert @trembel I rewrote all initialization code to be on-par with the official implementation. Now it should have exactly the same behavior as the official driver in terms of RF calibration.
Hi, great work. However, I currently have to use a local, modified version of this driver in which I removed the forever loops in
DW3000::init()
. It would be convenient to be able to (optionally) specify a maximum number of loop iterations and return an error if that limit is exceeded.The reason is I am using this as external library on Zephyr + Cortex-M4 and Zephyr's log output is always delayed. So if the code runs into an infinite loop I won't be able to see the latest log messages and have to guess what happened.