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).

source/SimpleSpirit1.cpp

Committer:
Wolfgang Betz
Date:
2016-12-16
Revision:
43:a512f909514a
Parent:
42:92a60a905ee7
Child:
44:aedd63cb0ce3

File content as of revision 43:a512f909514a:

/*** Mbed Includes ***/
#include "SimpleSpirit1.h"
#include "radio_spi.h"

#define SPIRIT_GPIO_IRQ			(SPIRIT_GPIO_3)

static uint16_t last_state;
#define SPIRIT1_STATUS()		((last_state = (uint16_t)refresh_state()) & SPIRIT1_STATE_STATEBITS)

#define XO_ON                   (0x1)

#define BUSYWAIT_UNTIL(cond, millisecs)                                        					\
		do {                                                                 					 		\
			uint32_t start = us_ticker_read();                         							\
			while (!(cond) && ((us_ticker_read() - start) < ((uint32_t)millisecs)*1000U));	\
		} while(0)

#define st_lib_spirit_irqs		SpiritIrqs

#define STATE_TIMEOUT           (100)

// betzw: switching force & back from standby is on some devices quite unstable
#define USE_STANDBY_STATE

/*** 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(),
			_rx_receiving_timeout()
{
}

/** Init Function **/
void SimpleSpirit1::init() {
	/* 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(100000); // 100kHz // betzw: heuristic value // betzw - NOTE: high frequencies lead to instability of Spirit1

	/* install irq handler */
	_irq.mode(PullUp);
	_irq.fall(Callback<void()>(this, &SimpleSpirit1::IrqHandler));

	/* init cube vars */
	spirit_on = OFF;
	last_rssi = 0 ; //MGR
	last_sqi = 0 ;  //MGR

	/* 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(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);
	irq_set_status(VALID_SYNC, S_ENABLE);

	/* Configure Spirit1 */
	radio_persistent_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);
	calibration_rco(S_ENABLE);

	spirit_on = OFF;
	CLEAR_TXBUF();
	CLEAR_RXBUF();
	_spirit_tx_started = false;
	_is_receiving = 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);

// #ifdef RX_FIFO_THR_AO_CSMA_WA
	// betzw - TODO: enabling CSMA/CA seems to be incompatible with TX FIFO usage (to be investigated)
	/* Setup CSMA/CA */
	CsmaInit x_csma_init = {
			S_ENABLE,         // enable persistent mode
			TBIT_TIME_64,     // Tcca time
			TCCA_TIME_3,      // Lcca length
			3,                // max nr of backoffs (<8)
			1,                // BU counter seed
			8                 // BU prescaler
	};
	csma_ca_init(&x_csma_init);
// #endif

#ifdef RX_FIFO_THR_AO_CSMA_WA
	linear_fifo_set_almost_full_thr_rx(SPIRIT_MAX_FIFO_LEN-(MAX_PACKET_LEN+1));
#else
	linear_fifo_set_almost_full_thr_rx(20); // betzw: heuristic value
#endif

#ifdef USE_STANDBY_STATE
	/* Puts the SPIRIT1 in STANDBY mode (125us -> rx/tx) */
	cmd_strobe(SPIRIT1_STROBE_STANDBY);
#endif // USE_STANDBY_STATE
}

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();

	BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, STATE_TIMEOUT);
#ifndef NDEBUG
	if((last_state & SPIRIT1_STATE_STATEBITS) != SPIRIT1_STATE_RX) {
		debug("\n\rAssert failed in: %s (%d): state=%x\n\r", __func__, __LINE__, last_state>>1);
	}
#endif

	/* Reset State to Ready */
	set_ready_state();

	cmd_strobe(SPIRIT1_STROBE_FTX); // flush TX FIFO buffer

#ifndef NDEBUG
	debug_if(!(linear_fifo_read_num_elements_tx_fifo() == 0), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif

	pkt_basic_set_payload_length(payload_len); // set desired payload len

// #ifdef RX_FIFO_THR_AO_CSMA_WA
	// betzw - TODO: enabling CSMA/CA seems to be incompatible with TX FIFO usage (to be investigated)
	csma_ca_state(S_ENABLE); // enable CSMA/CA
// #endif

	int i = 0;
	int remaining = payload_len;
	const uint8_t *buffer = (const uint8_t*)payload;
	bool tx_triggered = false;
	int8_t fifo_available = (3*SPIRIT_MAX_FIFO_LEN/4) - linear_fifo_read_num_elements_tx_fifo(); // betzw: heuristic value
	do {
		int8_t to_send = (remaining > fifo_available) ? fifo_available : remaining;

		/* Fill FIFO Buffer */
		if(to_send > 0) {
			spi_write_linear_fifo(to_send, (uint8_t*)&buffer[i]);
		}

		if(!tx_triggered) {
			cmd_strobe(SPIRIT1_STROBE_TX);
			tx_triggered = true;
		}

		i += to_send;
		remaining -= to_send;
		fifo_available = SPIRIT_MAX_FIFO_LEN - linear_fifo_read_num_elements_tx_fifo();
	} while(remaining != 0);

	_spirit_tx_started = true;

	enable_spirit_irq();

	BUSYWAIT_UNTIL(!_spirit_tx_started, STATE_TIMEOUT);
#ifdef HEAVY_DEBUG
	debug("\n\r%s (%d): state=%x, _spirit_tx_started=%d\n\r", __func__, __LINE__, SPIRIT1_STATUS()>>1, _spirit_tx_started);
#endif

	_spirit_tx_started = false; // in case of state timeout

// #ifdef RX_FIFO_THR_AO_CSMA_WA
	// betzw - TODO: enabling CSMA/CA seems to be incompatible with TX FIFO usage (to be investigated)
	csma_ca_state(S_DISABLE); // disable CSMA/CA
// #endif

	cmd_strobe(SPIRIT1_STROBE_RX); // Return to RX state

	return RADIO_TX_OK;
}

/** Set Ready State **/
void SimpleSpirit1::set_ready_state(void) {
	uint16_t state;

	disable_spirit_irq();

	_spirit_tx_started = false;
	_is_receiving = false;
	stop_rx_timeout();

	cmd_strobe(SPIRIT1_STROBE_FRX);
	CLEAR_RXBUF();
	CLEAR_TXBUF();

	state = SPIRIT1_STATUS();
	if(state == SPIRIT1_STATE_STANDBY) {
		cmd_strobe(SPIRIT1_STROBE_READY);
	} else if(state == SPIRIT1_STATE_RX) {
		cmd_strobe(SPIRIT1_STROBE_SABORT);
	} else if(state != SPIRIT1_STATE_READY) {
#ifndef NDEBUG
		debug("\n\rAssert failed in: %s (%d): state=%x\n\r", __func__, __LINE__, state>>1);
#endif
	}

	BUSYWAIT_UNTIL((SPIRIT1_STATUS() == SPIRIT1_STATE_READY) && ((last_state & XO_ON) == XO_ON), STATE_TIMEOUT);
	if(last_state != (SPIRIT1_STATE_READY | XO_ON)) {
		error("\n\rSpirit1: failed to become ready (%x) => pls. reset!\n\r", last_state);
		enable_spirit_irq();
		return;
	}

	irq_clear_status();

	enable_spirit_irq();
}

int SimpleSpirit1::off(void) {
	if(spirit_on == ON) {
		/* Disables the mcu to get IRQ from the SPIRIT1 */
		disable_spirit_irq();

		/* first stop rx/tx */
		set_ready_state();

#ifdef USE_STANDBY_STATE
		/* Puts the SPIRIT1 in STANDBY */
		cmd_strobe(SPIRIT1_STROBE_STANDBY);
		BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_STANDBY, STATE_TIMEOUT);
		if((last_state & SPIRIT1_STATE_STATEBITS) != SPIRIT1_STATE_STANDBY) {
			error("\n\rSpirit1: failed to enter standby (%x)\n\r", last_state>>1);
			return 1;
		}
#endif // USE_STANDBY_STATE

		spirit_on = OFF;
		_nr_of_irq_disables = 1;
	}
	return 0;
}

int SimpleSpirit1::on(void) {
	if(spirit_on == OFF) {
		set_ready_state();

		/* now we go to Rx */
		cmd_strobe(SPIRIT1_STROBE_RX);

		BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, STATE_TIMEOUT);
		if((last_state & SPIRIT1_STATE_STATEBITS) != SPIRIT1_STATE_RX) {
			error("\n\rSpirit1: failed to enter rx (%x) => retry\n\r", last_state>>1);
		}

		/* Enables the mcu to get IRQ from the SPIRIT1 */
		spirit_on = ON;
#ifndef NDEBUG
		debug_if(!(_nr_of_irq_disables == 1), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif
		enable_spirit_irq();
	}

#ifndef NDEBUG
	if(SPIRIT1_STATUS() != SPIRIT1_STATE_RX) {
		debug("\n\rAssert failed in: %s (%d): state=%x\n\r", __func__, __LINE__, last_state>>1);
	}
#endif

	return 0;
}

uint8_t SimpleSpirit1::refresh_state(void) {
	uint8_t mcstate;

	SpiritSpiReadRegisters(MC_STATE0_BASE, 1, &mcstate);

	return mcstate;
}

int SimpleSpirit1::read(void *buf, unsigned int bufsize)
{
	disable_spirit_irq();

	/* Checks if the RX buffer is empty */
	if(IS_RXBUF_EMPTY()) {
#ifndef NDEBUG
		debug("\n\rBuffer is empty\n\r");
#endif
		set_ready_state();

		cmd_strobe(SPIRIT1_STROBE_RX);
		BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, STATE_TIMEOUT);
		enable_spirit_irq();
		return 0;
	}

	if(bufsize < spirit_rx_len) {
		enable_spirit_irq();

		/* If buf has the correct size */
#ifndef NDEBUG
		debug("\n\rTOO SMALL BUF\n\r");
#endif
		return 0;
	} else {
		/* Copies the packet received */
		memcpy(buf, spirit_rx_buf, spirit_rx_len);

		bufsize = spirit_rx_len;
		CLEAR_RXBUF();

		enable_spirit_irq();

		return bufsize;
	}

}

int SimpleSpirit1::channel_clear(void)
{
	float rssi_value;
	/* Local variable used to memorize the SPIRIT1 state */
	uint8_t spirit_state = ON;

	if(spirit_on == OFF) {
		/* Wakes up the SPIRIT1 */
		on();
		spirit_state = OFF;
	}

#ifndef NDEBUG
	if(SPIRIT1_STATUS() != SPIRIT1_STATE_RX) {
		debug("\n\rAssert failed in: %s (%d): state=%x\n\r", __func__, __LINE__, last_state>>1);
	}
#endif

	disable_spirit_irq();

	/* Reset State to Ready */
	set_ready_state();

	/* 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();
#ifndef NDEBUG
#ifdef USE_STANDBY_STATE
		if(SPIRIT1_STATUS() != SPIRIT1_STATE_STANDBY) {
#else
		if(SPIRIT1_STATUS() != SPIRIT1_STATE_READY) {
#endif
			debug("\n\rAssert failed in: %s (%d): state=%x\n\r", __func__, __LINE__, last_state>>1);
		}
#endif
	} else {
		disable_spirit_irq();

        set_ready_state();

		cmd_strobe(SPIRIT1_STROBE_RX);
		BUSYWAIT_UNTIL(SPIRIT1_STATUS() == SPIRIT1_STATE_RX, STATE_TIMEOUT);
		if((last_state & SPIRIT1_STATE_STATEBITS) != SPIRIT1_STATE_RX) {
			error("\n\rSpirit1: (#2) failed to enter rx (%x) => retry\n\r", last_state>>1);
		}

		enable_spirit_irq();

#ifndef NDEBUG
		if(SPIRIT1_STATUS() != SPIRIT1_STATE_RX) {
			debug("\n\rAssert failed in: %s (%d): state=%x\n\r", __func__, __LINE__, last_state>>1);
		}
#endif
	}

	/* Checks the RSSI value with the threshold */
	if(rssi_value<CCA_THRESHOLD) {
		return 0;
	} else {
		return 1;
	}
}

int SimpleSpirit1::get_pending_packet(void)
{
	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);

	/* Reception errors */
	if((x_irq_status.IRQ_RX_FIFO_ERROR) || (x_irq_status.IRQ_RX_DATA_DISC)) {
#ifdef DEBUG_IRQ
		uint32_t *tmp = (uint32_t*)&x_irq_status;
		debug("\n\r%s (%d): irq=%x\n\r", __func__, __LINE__, *tmp);
		debug_if(!((*tmp) & (IRQ_RX_FIFO_ERROR_MASK | IRQ_RX_DATA_DISC_MASK)), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif
		rx_timeout_handler();
		if(_spirit_tx_started) {
			_spirit_tx_started = false;
			/* call user callback */
			if(_current_irq_callback) {
				_current_irq_callback(TX_ERR);
			}
		}

		/* Disable handling of other RX flags */
		x_irq_status.IRQ_RX_DATA_READY = x_irq_status.IRQ_RX_FIFO_ALMOST_FULL = S_RESET;
	}

	/* Transmission error */
	if(x_irq_status.IRQ_TX_FIFO_ERROR) {
#ifdef DEBUG_IRQ
		uint32_t *tmp = (uint32_t*)&x_irq_status;
		debug("\n\r%s (%d): irq=%x\n\r", __func__, __LINE__, *tmp);
		debug_if(!((*tmp) & IRQ_TX_FIFO_ERROR_MASK), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif
		if(_spirit_tx_started) {
			_spirit_tx_started = false;
			/* call user callback */
			if(_current_irq_callback) {
				_current_irq_callback(TX_ERR);
			}
		}

		/* Disable handling of other TX flags */
		x_irq_status.IRQ_TX_DATA_SENT = S_RESET;
	}

	/* The IRQ_TX_DATA_SENT notifies the packet received. Puts the SPIRIT1 in RX */
	if(x_irq_status.IRQ_TX_DATA_SENT) {
#ifdef DEBUG_IRQ
		uint32_t *tmp = (uint32_t*)&x_irq_status;
		debug_if(!_spirit_tx_started, "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
		debug_if(!((*tmp) & IRQ_TX_DATA_SENT_MASK), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif

		_spirit_tx_started = false;

		/* call user callback */
		if(_current_irq_callback) {
			_current_irq_callback(TX_DONE);
		}
	}

	/* The IRQ_RX_DATA_READY notifies a new packet arrived */
	if(x_irq_status.IRQ_RX_DATA_READY) {
#ifdef DEBUG_IRQ
		uint32_t *tmp = (uint32_t*)&x_irq_status;
		debug_if(!((*tmp) & IRQ_RX_DATA_READY_MASK), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif

		if(!_is_receiving) { // spurious irq?!?
#ifdef DEBUG_IRQ
			debug("\n\r%s (%d): irq=%x\n\r", __func__, __LINE__, *tmp);
#endif
		} else {
			_is_receiving = false; // Finished receiving
			stop_rx_timeout();

			spirit_rx_len = pkt_basic_get_received_pkt_length();

#ifdef DEBUG_IRQ
			debug_if(!(spirit_rx_len <= MAX_PACKET_LEN), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif

			for(; _spirit_rx_pos < spirit_rx_len;) {
				uint8_t to_receive = spirit_rx_len - _spirit_rx_pos;
				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_sqi  = qi_get_sqi();  //MGR

			/* call user callback */
			if(_current_irq_callback) {
				_current_irq_callback(RX_DONE);
			}

			/* Disable handling of other RX flags */
			x_irq_status.IRQ_RX_FIFO_ALMOST_FULL = S_RESET;
		}
	}

	/* RX FIFO almost full */
	if(x_irq_status.IRQ_RX_FIFO_ALMOST_FULL) {
#ifdef DEBUG_IRQ
		uint32_t *tmp = (uint32_t*)&x_irq_status;
		debug_if(!((*tmp) & IRQ_RX_FIFO_ALMOST_FULL_MASK), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif
		if(!_is_receiving) { // spurious irq?!?
#ifdef DEBUG_IRQ
			debug("\n\r%s (%d): irq=%x\n\r", __func__, __LINE__, *tmp);
#endif
		} else {
			uint8_t fifo_available = linear_fifo_read_num_elements_rx_fifo();
			spi_read_linear_fifo(fifo_available, &spirit_rx_buf[_spirit_rx_pos]);
			_spirit_rx_pos += fifo_available;

#if 1
			spirit_rx_len = pkt_basic_get_received_pkt_length();

#ifdef DEBUG_IRQ
			debug_if(!(spirit_rx_len <= MAX_PACKET_LEN), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif

			if((spirit_rx_len > 0) && (_spirit_rx_pos >= spirit_rx_len)) { // already received everything
				_is_receiving = false; // Finished receiving
				stop_rx_timeout();

				cmd_strobe(SPIRIT1_STROBE_FRX);

				last_rssi = qi_get_rssi(); //MGR
				last_sqi  = qi_get_sqi();  //MGR

				/* call user callback */
				if(_current_irq_callback) {
					_current_irq_callback(RX_DONE);
				}
			}
#endif // 0

		}
	}

	/* The IRQ_VALID_SYNC is used to notify a new packet is coming */
	if(x_irq_status.IRQ_VALID_SYNC) {
#ifdef DEBUG_IRQ
		uint32_t *tmp = (uint32_t*)&x_irq_status;
		debug_if(!((*tmp) & IRQ_VALID_SYNC_MASK), "\n\rAssert failed in: %s (%d)\n\r", __func__, __LINE__);
#endif
		/* betzw - NOTE: there is a race condition between Spirit1 receiving packets and
		 *               the MCU trying to send a packet, which gets resolved in favor of
		 *               sending.
		 */
		if(_spirit_tx_started) {
#ifdef DEBUG_IRQ
			uint32_t *tmp = (uint32_t*)&x_irq_status;
			debug("\n\r%s (%d): irq=%x\n\r", __func__, __LINE__, *tmp);
#endif
		} else {
			_is_receiving = true;
			start_rx_timeout();
		}
	}
}