Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Prototype RF Driver for STM Sub-1 GHz RF Expansion Boards based on the SPSGRF-868 and SPSGRF-915 Modules for STM32 Nucleo
Currently supported boards:
Note, in order to use expansion board X-NUCLEO-IDS01A4 in mbed you need to perform the following HW modifications on the board:
- Unmount resistor
R4 - Mount resistor
R7
Furthermore, on some Nucleo development boards (e.g. the NUCLEO_F429ZI), in order to be able to use Ethernet together with these Sub-1 GHz RF expansion boards, you need to compile this driver with macro SPIRIT1_SPI_MOSI=PB_5 defined, while the development board typically requires some HW modification as e.g. described here!
This driver can be used together with the 6LoWPAN stack (a.k.a. Nanostack).
SimpleSpirit1.cpp
- Committer:
- Wolfgang Betz
- Date:
- 2016-10-21
- Revision:
- 7:e90fa8f6bc6c
- Parent:
- 6:f5d01793bf86
- Child:
- 8:10967c884e38
File content as of revision 7:e90fa8f6bc6c:
/*** Mbed Includes ***/
#include "SimpleSpirit1.h"
#include "radio_spi.h"
/*** Macros from Cube Implementation ***/
#define CLEAR_TXBUF() (spirit_tx_len = 0)
#define IS_RXBUF_EMPTY() (spirit_rx_len == 0)
#define CLEAR_RXBUF() do { \
spirit_rx_len = 0; \
_spirit_rx_pos = 0; \
} while(0)
#define NDEBUG
#ifndef NDEBUG
#include <stdio.h>
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...)
#endif
#if NULLRDC_CONF_802154_AUTOACK
#define ACK_LEN 3
static int wants_an_ack = 0; /* The packet sent expects an ack */
//#define ACKPRINTF printf
#define ACKPRINTF(...)
#endif /* NULLRDC_CONF_802154_AUTOACK */
#define SPIRIT_GPIO_IRQ (SPIRIT_GPIO_3)
#define SPIRIT1_STATUS() (arch_refresh_status() & SPIRIT1_STATE_STATEBITS)
#define SABORT_WAIT_US (400)
#define BUSYWAIT_UNTIL(cond, millisecs) \
do { \
uint32_t start = us_ticker_read(); \
while (!(cond) && ((us_ticker_read() - start) < ((uint32_t)millisecs)*1000U)); \
} while(0)
extern volatile SpiritStatus g_xStatus;
#define st_lib_g_x_status (g_xStatus)
#define st_lib_spirit_irqs SpiritIrqs
/*** Class Implementation ***/
/** Static Class Variables **/
SimpleSpirit1 *SimpleSpirit1::_singleton = NULL;
/** Constructor **/
SimpleSpirit1::SimpleSpirit1(PinName mosi, PinName miso, PinName sclk,
PinName irq, PinName cs, PinName sdn,
PinName led) :
_spi(mosi, miso, sclk),
_irq(irq),
_chip_select(cs),
_shut_down(sdn),
_led(led),
_current_irq_callback()
{
/* reset irq disable counter and irq callback & disable irq */
_nr_of_irq_disables = 0;
disable_spirit_irq();
/* unselect chip */
chip_unselect();
/* configure spi */
_spi.format(8, 0); /* 8-bit, mode = 0, [order = SPI_MSB] only available in mbed3 */
_spi.frequency(5000000); // 5MHz
/* install irq handler */
_irq.fall(Callback<void()>(this, &SimpleSpirit1::IrqHandler));
/* init cube vars */
spirit_on = OFF;
packet_is_prepared = 0;
receiving_packet = 0;
just_got_an_ack = 0;
last_rssi = 0 ; //MGR
last_lqi = 0 ; //MGR
}
/** Init Function **/
void SimpleSpirit1::init() {
/* set frequencies */
radio_set_xtal_freq(XTAL_FREQUENCY);
mgmt_set_freq_base((uint32_t)BASE_FREQUENCY);
/* restart board */
enter_shutdown();
exit_shutdown();
/* soft core reset */
cmd_strobe(SPIRIT1_STROBE_SRES);
/* Configures the SPIRIT1 radio part */
SRadioInit x_radio_init = {
XTAL_OFFSET_PPM,
(uint32_t)BASE_FREQUENCY,
(uint32_t)CHANNEL_SPACE,
CHANNEL_NUMBER,
MODULATION_SELECT,
DATARATE,
(uint32_t)FREQ_DEVIATION,
(uint32_t)BANDWIDTH
};
radio_init(&x_radio_init);
radio_set_pa_level_dbm(0,POWER_DBM);
radio_set_pa_level_max_index(0);
/* Configures the SPIRIT1 packet handler part*/
PktBasicInit x_basic_init = {
PREAMBLE_LENGTH,
SYNC_LENGTH,
SYNC_WORD,
LENGTH_TYPE,
LENGTH_WIDTH,
CRC_MODE,
CONTROL_LENGTH,
EN_ADDRESS,
EN_FEC,
EN_WHITENING
};
pkt_basic_init(&x_basic_init);
/* Enable the following interrupt sources, routed to GPIO */
irq_de_init(NULL);
irq_clear_status();
irq_set_status(TX_DATA_SENT, S_ENABLE);
irq_set_status(RX_DATA_READY,S_ENABLE);
irq_set_status(VALID_SYNC,S_ENABLE);
irq_set_status(RX_DATA_DISC, S_ENABLE);
irq_set_status(TX_FIFO_ERROR, S_ENABLE);
irq_set_status(RX_FIFO_ERROR, S_ENABLE);
irq_set_status(RX_FIFO_ALMOST_FULL, S_ENABLE);
/* Configure Spirit1 */
radio_persisten_rx(S_ENABLE);
qi_set_sqi_threshold(SQI_TH_0);
qi_sqi_check(S_ENABLE);
qi_set_rssi_threshold_dbm(CCA_THRESHOLD);
timer_set_rx_timeout_stop_condition(SQI_ABOVE_THRESHOLD);
timer_set_infinite_rx_timeout();
radio_afc_freeze_on_sync(S_ENABLE);
/* Puts the SPIRIT1 in STANDBY mode (125us -> rx/tx) */
cmd_strobe(SPIRIT1_STROBE_STANDBY);
spirit_on = OFF;
CLEAR_TXBUF();
CLEAR_RXBUF();
_spirit_tx_started = false;
_spirit_rx_err = false;
/* Configure the radio to route the IRQ signal to its GPIO 3 */
SGpioInit x_gpio_init = {
SPIRIT_GPIO_IRQ,
SPIRIT_GPIO_MODE_DIGITAL_OUTPUT_LP,
SPIRIT_GPIO_DIG_OUT_IRQ
};
spirit_gpio_init(&x_gpio_init);
}
/** Prepare the radio with a packet to be sent. **/
int SimpleSpirit1::prepare_contiki(const void *payload, unsigned short payload_len) {
PRINTF("Spirit1: prep %u\n", payload_len);
packet_is_prepared = 0;
/* Checks if the payload length is supported */
if(payload_len > MAX_PACKET_LEN) {
return RADIO_TX_ERR;
}
/* Should we delay for an ack? */
#if NULLRDC_CONF_802154_AUTOACK
frame802154_t info154;
wants_an_ack = 0;
if(payload_len > ACK_LEN
&& frame802154_parse((char*)payload, payload_len, &info154) != 0) {
if(info154.fcf.frame_type == FRAME802154_DATAFRAME
&& info154.fcf.ack_required != 0) {
wants_an_ack = 1;
}
}
#endif /* NULLRDC_CONF_802154_AUTOACK */
/* Sets the length of the packet to send */
disable_spirit_irq();
cmd_strobe(SPIRIT1_STROBE_FTX);
pkt_basic_set_payload_length(payload_len);
spi_write_linear_fifo(payload_len, (uint8_t *)payload);
enable_spirit_irq();
PRINTF("PREPARE OUT\n");
packet_is_prepared = 1;
return RADIO_TX_OK;
}
/** Send the packet that has previously been prepared. **/
int SimpleSpirit1::transmit_contiki(unsigned short payload_len) {
/* This function blocks until the packet has been transmitted */
//rtimer_clock_t rtimer_txdone, rtimer_rxack;
PRINTF("TRANSMIT IN\n");
if(!packet_is_prepared) {
return RADIO_TX_ERR;
}
/* Stores the length of the packet to send */
/* Others spirit_radio_prepare will be in hold */
spirit_tx_len = payload_len;
/* Puts the SPIRIT1 in TX state */
receiving_packet = 0;
set_ready_state();
cmd_strobe(SPIRIT1_STROBE_TX);
just_got_an_ack = 0;
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_TX, 1);
//BUSYWAIT_UNTIL(SPIRIT1_STATUS() != SPIRIT1_STATE_TX, 4); //For GFSK with high data rate
BUSYWAIT_UNTIL(SPIRIT1_STATUS() != SPIRIT1_STATE_TX, 50); //For FSK with low data rate
/* Reset radio - needed for immediate RX of ack */
CLEAR_TXBUF();
CLEAR_RXBUF();
disable_spirit_irq();
irq_clear_status();
receiving_packet = 0;
cmd_strobe(SPIRIT1_STROBE_SABORT);
wait_us(SABORT_WAIT_US);
cmd_strobe(SPIRIT1_STROBE_READY);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_READY, 1);
cmd_strobe(SPIRIT1_STROBE_FRX);
cmd_strobe(SPIRIT1_STROBE_RX);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, 1);
enable_spirit_irq();
#if XXX_ACK_WORKAROUND
just_got_an_ack = 1;
#endif /* XXX_ACK_WORKAROUND */
#if NULLRDC_CONF_802154_AUTOACK
if (wants_an_ack) {
rtimer_txdone = us_ticker_read();
BUSYWAIT_UNTIL(just_got_an_ack, 2);
rtimer_rxack = us_ticker_read();
if(just_got_an_ack) {
ACKPRINTF("debug_ack: ack received after %u us\n",
(uint32_t)(rtimer_rxack - rtimer_txdone));
} else {
ACKPRINTF("debug_ack: no ack received\n");
}
}
#endif /* NULLRDC_CONF_802154_AUTOACK */
PRINTF("TRANSMIT OUT\n");
CLEAR_TXBUF();
packet_is_prepared = 0;
wait_us(1);
return RADIO_TX_OK;
}
int SimpleSpirit1::send(const void *payload, unsigned int payload_len) {
/* Checks if the payload length is supported */
if(payload_len > MAX_PACKET_LEN) {
return RADIO_TX_ERR;
}
disable_spirit_irq();
/* Reset State to Ready */
set_ready_state();
cmd_strobe(SPIRIT1_STROBE_FTX); // flush TX FIFO buffer
MBED_ASSERT(linear_fifo_read_num_elements_tx_fifo() == 0);
pkt_basic_set_payload_length(payload_len); // set desired payload len
int i = 0;
int remaining = payload_len;
bool trigger_tx = true;
do {
uint8_t fifo_available = SPIRIT_MAX_FIFO_LEN - linear_fifo_read_num_elements_tx_fifo();
uint8_t to_send = (remaining > fifo_available) ? fifo_available : remaining;
const uint8_t *buffer = (const uint8_t*)payload;
/* Fill FIFO Buffer */
spi_write_linear_fifo(to_send, (uint8_t*)&buffer[i]);
/* Start Transmit FIFO Buffer */
if(trigger_tx) {
MBED_ASSERT(linear_fifo_read_num_elements_tx_fifo() == to_send);
cmd_strobe(SPIRIT1_STROBE_TX);
trigger_tx = false;
}
i += to_send;
remaining -= to_send;
} while(remaining != 0);
_spirit_tx_started = true;
enable_spirit_irq();
BUSYWAIT_UNTIL(SPIRIT1_STATUS() != SPIRIT1_STATE_TX, 50);
MBED_ASSERT(linear_fifo_read_num_elements_tx_fifo() == 0);
return RADIO_TX_OK;
}
/** Set Ready State **/
void SimpleSpirit1::set_ready_state(void) {
PRINTF("READY IN\n");
disable_spirit_irq();
if(SPIRIT1_STATUS() == SPIRIT1_STATE_STANDBY) {
cmd_strobe(SPIRIT1_STROBE_READY);
} else if(SPIRIT1_STATUS() == SPIRIT1_STATE_RX) {
receiving_packet = 0;
cmd_strobe(SPIRIT1_STROBE_SABORT);
}
irq_clear_status();
enable_spirit_irq();
PRINTF("READY OUT\n");
}
int SimpleSpirit1::off(void) {
PRINTF("Spirit1: ->off\n");
if(spirit_on == ON) {
/* Disables the mcu to get IRQ from the SPIRIT1 */
disable_spirit_irq();
/* first stop rx/tx */
receiving_packet = 0;
cmd_strobe(SPIRIT1_STROBE_SABORT);
/* Clear any pending irqs */
irq_clear_status();
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_READY, 5);
if(SPIRIT1_STATUS() != SPIRIT1_STATE_READY) {
PRINTF("Spirit1: failed off->ready\n");
return 1;
}
/* Puts the SPIRIT1 in STANDBY */
cmd_strobe(SPIRIT1_STROBE_STANDBY);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_STANDBY, 5);
if(SPIRIT1_STATUS() != SPIRIT1_STATE_STANDBY) {
PRINTF("Spirit1: failed off->standby\n");
return 1;
}
spirit_on = OFF;
_nr_of_irq_disables = 1;
_spirit_tx_started = false;
CLEAR_TXBUF();
CLEAR_RXBUF();
}
PRINTF("Spirit1: off.\n");
return 0;
}
int SimpleSpirit1::on(void) {
PRINTF("Spirit1: on\n");
cmd_strobe(SPIRIT1_STROBE_SABORT);
wait_us(SABORT_WAIT_US);
if(spirit_on == OFF) {
/* ensure we are in READY state as we go from there to Rx */
cmd_strobe(SPIRIT1_STROBE_READY);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_READY, 5);
if(SPIRIT1_STATUS() != SPIRIT1_STATE_READY) {
PRINTF("Spirit1: failed to turn on\n");
while(1);
//return 1;
}
/* now we go to Rx */
cmd_strobe(SPIRIT1_STROBE_FRX);
cmd_strobe(SPIRIT1_STROBE_RX);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, 5);
if(SPIRIT1_STATUS() != SPIRIT1_STATE_RX) {
PRINTF("Spirit1: failed to enter rx\n");
while(1);
//return 1;
}
CLEAR_RXBUF();
receiving_packet = 0;
_spirit_rx_err = false;
/* Enables the mcu to get IRQ from the SPIRIT1 */
spirit_on = ON;
MBED_ASSERT(_nr_of_irq_disables == 1);
enable_spirit_irq();
}
return 0;
}
uint16_t SimpleSpirit1::arch_refresh_status(void) {
uint16_t mcstate;
uint8_t header[2];
header[0]=READ_HEADER;
header[1]=MC_STATE1_BASE;
/* Puts the SPI chip select low to start the transaction */
chip_sync_select();
/* Write the aHeader bytes and read the SPIRIT1 status bytes */
mcstate = _spi.write(header[0]);
mcstate = mcstate<<8;
/* Write the aHeader bytes and read the SPIRIT1 status bytes */
mcstate |= _spi.write(header[1]);
/* Puts the SPI chip select high to end the transaction */
chip_sync_unselect();
return mcstate;
}
int SimpleSpirit1::read(void *buf, unsigned int bufsize)
{
PRINTF("READ IN\n");
disable_spirit_irq();
/* Checks if the RX buffer is empty */
if(IS_RXBUF_EMPTY()) {
CLEAR_RXBUF();
receiving_packet = 0;
cmd_strobe(SPIRIT1_STROBE_SABORT);
wait_us(SABORT_WAIT_US);
cmd_strobe(SPIRIT1_STROBE_READY);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_READY, 1);
cmd_strobe(SPIRIT1_STROBE_FRX);
cmd_strobe(SPIRIT1_STROBE_RX);
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, 1);
_spirit_rx_err = false;
PRINTF("READ OUT RX BUF EMPTY\n");
enable_spirit_irq();
return 0;
}
if(bufsize < spirit_rx_len) {
enable_spirit_irq();
/* If buf has the correct size */
PRINTF("TOO SMALL BUF\n");
return 0;
} else {
/* Copies the packet received */
memcpy(buf, spirit_rx_buf, spirit_rx_len);
#ifdef CONTIKI // betzw - TODO
packetbuf_set_attr(PACKETBUF_ATTR_RSSI, last_rssi); //MGR
packetbuf_set_attr(PACKETBUF_ATTR_LINK_QUALITY, last_lqi); //MGR
#endif
bufsize = spirit_rx_len;
CLEAR_RXBUF();
enable_spirit_irq();
PRINTF("READ OUT\n");
return bufsize;
}
}
int SimpleSpirit1::channel_clear(void)
{
float rssi_value;
/* Local variable used to memorize the SPIRIT1 state */
uint8_t spirit_state = ON;
PRINTF("CHANNEL CLEAR IN\n");
if(spirit_on == OFF) {
/* Wakes up the SPIRIT1 */
on();
spirit_state = OFF;
}
disable_spirit_irq();
/* Reset State to Ready */
set_ready_state();
{
uint32_t timeout = us_ticker_read() + 5000;
do {
mgmt_refresh_status();
} while((st_lib_g_x_status.MC_STATE != MC_STATE_READY) && (us_ticker_read() < timeout));
if(st_lib_g_x_status.MC_STATE != MC_STATE_READY) {
enable_spirit_irq();
return 1;
}
}
/* Stores the RSSI value */
rssi_value = qi_get_rssi_dbm();
enable_spirit_irq();
/* Puts the SPIRIT1 in its previous state */
if(spirit_state==OFF) {
off();
} else {
disable_spirit_irq();
cmd_strobe(SPIRIT1_STROBE_FRX);
cmd_strobe(SPIRIT1_STROBE_RX);
/* SpiritCmdStrobeRx();*/
BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, 5);
CLEAR_RXBUF();
_spirit_rx_err = false;
enable_spirit_irq();
}
PRINTF("CHANNEL CLEAR OUT\n");
/* Checks the RSSI value with the threshold */
if(rssi_value<CCA_THRESHOLD) {
return 0;
} else {
return 1;
}
}
int SimpleSpirit1::get_pending_packet(void)
{
PRINTF("PENDING PACKET\n");
return !IS_RXBUF_EMPTY();
}
/** Spirit Irq Callback **/
void SimpleSpirit1::IrqHandler() {
st_lib_spirit_irqs x_irq_status;
/* get interrupt source from radio */
irq_get_status(&x_irq_status);
irq_clear_status();
/* Reception errors */
if((x_irq_status.IRQ_RX_FIFO_ERROR) || (x_irq_status.IRQ_RX_DATA_DISC) || (x_irq_status.IRQ_RX_TIMEOUT)) {
receiving_packet = 0;
_spirit_rx_err = true;
CLEAR_RXBUF();
cmd_strobe(SPIRIT1_STROBE_FRX);
if(_spirit_tx_started) {
_spirit_tx_started = false;
CLEAR_TXBUF();
/* call user callback */
if(_current_irq_callback) {
_current_irq_callback(-1); // betzw - TODO: define enums for callback values
}
}
}
/* Transmission error */
if(x_irq_status.IRQ_TX_FIFO_ERROR) {
error("IRQ_TX_FIFO_ERROR should never happen!\n");
receiving_packet = 0;
cmd_strobe(SPIRIT1_STROBE_FTX);
if(_spirit_tx_started) {
_spirit_tx_started = false;
CLEAR_TXBUF();
/* call user callback */
if(_current_irq_callback) {
_current_irq_callback(-1); // betzw - TODO: define enums for callback values
}
}
}
/* The IRQ_VALID_SYNC is used to notify a new packet is coming */
if(x_irq_status.IRQ_VALID_SYNC) {
receiving_packet = 1;
_spirit_rx_err = false;
CLEAR_RXBUF();
if(_spirit_tx_started) { // betzw - TOCHECK: is this really correct to be done here?
_spirit_tx_started = false;
CLEAR_TXBUF();
/* call user callback */
if(_current_irq_callback) {
_current_irq_callback(-1); // betzw - TODO: define enums for callback values
}
}
}
/* RX FIFO almost full */
if(x_irq_status.IRQ_RX_FIFO_ALMOST_FULL) {
if(_spirit_rx_err) {
cmd_strobe(SPIRIT1_STROBE_FRX);
} else {
uint8_t fifo_available = linear_fifo_read_num_elements_rx_fifo();
unsigned int remaining = MAX_PACKET_LEN - _spirit_rx_pos;
if(fifo_available > remaining) {
receiving_packet = 0;
_spirit_rx_err = true;
CLEAR_RXBUF();
cmd_strobe(SPIRIT1_STROBE_FRX);
} else {
spi_read_linear_fifo(fifo_available, &spirit_rx_buf[_spirit_rx_pos]);
_spirit_rx_pos += fifo_available;
}
}
}
/* The IRQ_TX_DATA_SENT notifies the packet received. Puts the SPIRIT1 in RX */
if(x_irq_status.IRQ_TX_DATA_SENT) {
MBED_ASSERT(_spirit_tx_started);
cmd_strobe(SPIRIT1_STROBE_FRX);
cmd_strobe(SPIRIT1_STROBE_RX);
/* SpiritCmdStrobeRx();*/
CLEAR_TXBUF();
CLEAR_RXBUF();
_spirit_rx_err = false;
_spirit_tx_started = false;
/* call user callback */
if(_current_irq_callback) {
_current_irq_callback(0); // betzw - TODO: define enums for callback values
}
}
/* The IRQ_RX_DATA_READY notifies a new packet arrived */
if(x_irq_status.IRQ_RX_DATA_READY) {
if(_spirit_rx_err) {
receiving_packet = 0;
_spirit_rx_err = false;
CLEAR_RXBUF();
cmd_strobe(SPIRIT1_STROBE_FRX);
} else {
spirit_rx_len = pkt_basic_get_received_pkt_length();
unsigned int remaining = 0;
uint8_t fifo_available = 0;
uint8_t to_receive = 0;
MBED_ASSERT(spirit_rx_len <= MAX_PACKET_LEN);
for(; _spirit_rx_pos < spirit_rx_len;) {
remaining = spirit_rx_len - _spirit_rx_pos;
fifo_available = linear_fifo_read_num_elements_rx_fifo();
to_receive = (remaining < fifo_available) ? remaining : fifo_available;
if(to_receive > 0) {
spi_read_linear_fifo(to_receive, &spirit_rx_buf[_spirit_rx_pos]);
_spirit_rx_pos += to_receive;
}
}
cmd_strobe(SPIRIT1_STROBE_FRX);
last_rssi = qi_get_rssi(); //MGR
last_lqi = qi_get_lqi(); //MGR
receiving_packet = 0;
#if NULLRDC_CONF_802154_AUTOACK
if (spirit_rxbuf[0] == ACK_LEN) {
/* For debugging purposes we assume this is an ack for us */
just_got_an_ack = 1;
}
#endif /* NULLRDC_CONF_802154_AUTOACK */
/* call user callback */
if(_current_irq_callback) {
_current_irq_callback(1); // betzw - TODO: define enums for callback values
}
}
}
}
X-NUCLEO-IDS01A4 Sub-1GHz RF Expansion Board