Prototype RF driver for STM Sub-1 GHz RF expansion board based on the SPSGRF-868 module for STM32 Nucleo.
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