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

mbed_driver_api.cpp

Committer:
Wolfgang Betz
Date:
2016-10-26
Revision:
11:b769d6caad82
Parent:
10:dedd44d58c0f
Child:
12:b8056eda4028

File content as of revision 11:b769d6caad82:

#include "SimpleSpirit1.h"
#include "nanostack/platform/arm_hal_phy.h"

/*Atmel RF Part Type*/
// betzw - TODO
typedef enum
{
    ATMEL_UNKNOW_DEV = 0,
    ATMEL_AT86RF212,
    ATMEL_AT86RF231,
    ATMEL_AT86RF233
}rf_trx_part_e;

static uint8_t mac_address[8] = {
		MBED_CONF_SPIRIT1_MAC_ADDRESS_0,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_1,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_2,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_3,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_4,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_5,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_6,
		MBED_CONF_SPIRIT1_MAC_ADDRESS_7
};
static phy_device_driver_s device_driver;
static int8_t rf_radio_driver_id = -1;
static uint8_t rf_rnd_rssi = 0;

const phy_rf_channel_configuration_s phy_subghz = {868000000, 1000000, 250000, 11, M_GFSK};

static phy_device_channel_page_s phy_channel_pages[] = {
    {CHANNEL_PAGE_2, &phy_subghz},
    {CHANNEL_PAGE_0, NULL}
};

static uint8_t tx_sequence = 0xff;
static uint8_t mac_tx_handle = 0;

static SimpleSpirit1 *rf_device = NULL;
static uint8_t rf_rx_buf[MAX_PACKET_LEN];

static int8_t rf_start_cca(uint8_t *data_ptr, uint16_t data_length, uint8_t tx_handle, data_protocol_e data_protocol)
{
    /*Check if transmitter is busy*/
    if((rf_device->get_receiving_packet()) || (rf_device->channel_clear() == 0)) {
        /*Return busy*/
        return -1;
    } else {
        /*Store the sequence number for ACK handling*/
        tx_sequence = *(data_ptr + 2);

        /*Store TX handle*/
        mac_tx_handle = tx_handle;

        /*Send the packet*/
        rf_device->send(data_ptr, data_length);
    }

    /*Return success*/
    return 0;
}

static int8_t rf_interface_state_control(phy_interface_state_e new_state, uint8_t rf_channel)
{
    int8_t ret_val = 0;
    switch (new_state)
    {
        /*Reset PHY driver and set to idle*/
        case PHY_INTERFACE_RESET:
        	rf_device->reset_board();
            break;
        /*Disable PHY Interface driver*/
        case PHY_INTERFACE_DOWN:
            ret_val = rf_device->off();
            if(ret_val != 0) ret_val = -1;
            break;
        /*Enable PHY Interface driver*/
        case PHY_INTERFACE_UP:
        	ret_val = rf_device->on();
        	if(ret_val != 0) {
        		ret_val = -1;
        		break;
        	}
            rf_device->set_channel(rf_channel);
            break;
        /*Enable wireless interface ED scan mode*/
        case PHY_INTERFACE_RX_ENERGY_STATE:
            break;
        /*Enable Sniffer state*/
        case PHY_INTERFACE_SNIFFER_STATE:
            // TODO - if we really need this - WAS: rf_setup_sniffer(rf_channel);
        	ret_val = -1;
            break;
    }
    return ret_val;
}

static int8_t rf_extension(phy_extension_type_e extension_type, uint8_t *data_ptr)
{
    switch (extension_type)
    {
        /*Control MAC pending bit for Indirect data transmission*/
        case PHY_EXTENSION_CTRL_PENDING_BIT:
            break;

        /*Return frame pending status*/
        case PHY_EXTENSION_READ_LAST_ACK_PENDING_STATUS:
            // TODO: *data_ptr = rf_if_last_acked_pending();
            *data_ptr = 0;
            break;

        /*Set channel, used for setting channel for energy scan*/
        case PHY_EXTENSION_SET_CHANNEL:
            break;

        /*Read energy on the channel*/
        case PHY_EXTENSION_READ_CHANNEL_ENERGY:
            // TODO: *data_ptr = rf_get_channel_energy();
           	*data_ptr = rf_device->get_last_rssi_dbm();
            break;

        /*Read status of the link*/
        case PHY_EXTENSION_READ_LINK_STATUS:
            // TODO: *data_ptr = rf_get_link_status();
        	*data_ptr = rf_device->get_last_lqi();
            break;

        default:
        	break;
    }
    return 0;
}

static int8_t rf_address_write(phy_address_type_e address_type, uint8_t *address_ptr)
{

#if 0 // TODO - if we really need this - WAS
    switch (address_type)
    {
        /*Set 48-bit address*/
        case PHY_MAC_48BIT:
            /* Not used in this example */
            break;
        /*Set 64-bit address*/
        case PHY_MAC_64BIT:
            rf_set_mac_address(address_ptr);
            break;
        /*Set 16-bit address*/
        case PHY_MAC_16BIT:
            rf_set_short_adr(address_ptr);
            break;
        /*Set PAN Id*/
        case PHY_MAC_PANID:
            rf_set_pan_id(address_ptr);
            break;
    }
#endif // 0

    return 0;
}

/* Note: we are in IRQ context */
static void rf_handle_ack(uint8_t seq_number, uint8_t data_pending)
{
    phy_link_tx_status_e phy_status;

    // TODO - if we really need this - WAS: rf_if_lock();

    /*Received ACK sequence must be equal with transmitted packet sequence*/
    if(tx_sequence == seq_number)
    {
        /*When data pending bit in ACK frame is set, inform NET library*/
        if(data_pending)
            phy_status = PHY_LINK_TX_DONE_PENDING;
        else
            phy_status = PHY_LINK_TX_DONE;

        /*Call PHY TX Done API*/
        if(device_driver.phy_tx_done_cb){
            device_driver.phy_tx_done_cb(rf_radio_driver_id, mac_tx_handle, phy_status, 1, 1);
        }
    }

    // TODO - if we really need this - WAS: rf_if_unlock();
}

/* Note: we are in IRQ context */
static inline void rf_handle_rx_end(void)
{
    uint8_t rf_lqi;
    int8_t rf_rssi;
    uint16_t rf_buffer_len;

    /* Get received data */
    rf_buffer_len = rf_device->read(rf_rx_buf, MAX_PACKET_LEN);
    if(!rf_buffer_len)
        return;

    /* If waiting for ACK, check here if the packet is an ACK to a message previously sent */
    if((rf_buffer_len == 3) && ((rf_rx_buf[0] & 0x07) == 0x02)) {
    	uint8_t pending = 0;

    	/*Check if data is pending*/
    	if ((rf_rx_buf[0] & 0x10)) {
    		pending=1;
        }

       	/*Send sequence number in ACK handler*/
       	rf_handle_ack(rf_rx_buf[2], pending);
       	return;
    }

    /* Get link information */
    rf_rssi = (int8_t)rf_device->get_last_rssi_dbm();
    rf_lqi = (uint8_t)rf_device->get_last_lqi();

    /* Note: Checksum of the packet must be checked and removed before entering here */

    /* Send received data and link information to the network stack */
    if( device_driver.phy_rx_cb ){
        device_driver.phy_rx_cb(rf_rx_buf, rf_buffer_len, rf_lqi, rf_rssi, rf_radio_driver_id);
    }
}

/* Note: we are in IRQ context */
static inline void rf_handle_tx_end(void)
{
    phy_link_tx_status_e phy_status = PHY_LINK_TX_SUCCESS;

    /*Call PHY TX Done API*/
    if(device_driver.phy_tx_done_cb){
        device_driver.phy_tx_done_cb(rf_radio_driver_id, mac_tx_handle, phy_status, 1, 1);
    }
}

/* Note: we are in IRQ context */
static inline void rf_handle_tx_err(void) {
    phy_link_tx_status_e phy_status = PHY_LINK_TX_FAIL;

    /*Call PHY TX Done API*/
    if(device_driver.phy_tx_done_cb){
        device_driver.phy_tx_done_cb(rf_radio_driver_id, mac_tx_handle, phy_status, 1, 1);
    }
}

/* Note: we are in IRQ context */
static void rf_callback_func(int event) {
	switch(event) {
	case SimpleSpirit1::RX_DONE:
		rf_handle_rx_end();
		break;
	case SimpleSpirit1::TX_DONE:
		rf_handle_tx_end();
		break;
	case SimpleSpirit1::TX_ERR:
		rf_handle_tx_err();
		break;
	}
}

static void rf_init(void) {
	rf_device = &SimpleSpirit1::CreateInstance(D11, D12, D13, D9, D10, D2);
	rf_device->attach_irq_callback(rf_callback_func);
}

extern "C" int8_t rf_device_register(void)
{
    /* Do some initialization */
    rf_init();

    /* Set pointer to MAC address */
    device_driver.PHY_MAC = mac_address;

    /* Set driver Name */
    device_driver.driver_description = (char*)"Spirit1 Sub-GHz RF";

    /*Type of RF PHY is SubGHz*/
    device_driver.link_type = PHY_LINK_15_4_SUBGHZ_TYPE;

    /*Maximum size of payload is 255*/
    device_driver.phy_MTU = MAX_PACKET_LEN;

    /*No header in PHY*/
    device_driver.phy_header_length = 0;

    /*No tail in PHY*/
    device_driver.phy_tail_length = 0;

    /*Set up driver functions*/
    device_driver.address_write = &rf_address_write;
    device_driver.extension = &rf_extension;
    device_driver.state_control = &rf_interface_state_control;
    device_driver.tx = &rf_start_cca;

    /*Set supported channel pages*/
    device_driver.phy_channel_pages = phy_channel_pages;

    //Nullify rx/tx callbacks
    device_driver.phy_rx_cb = NULL;
    device_driver.phy_tx_done_cb = NULL;
    device_driver.arm_net_virtual_rx_cb = NULL;
    device_driver.arm_net_virtual_tx_cb = NULL;

    /*Register device driver*/
    rf_radio_driver_id = arm_net_phy_register(&device_driver);

    return rf_radio_driver_id;
}

/*
 * \brief Function reads the MAC address array.
 *
 * \param ptr Pointer to read array
 *
 * \return none
 */
extern "C" void rf_read_mac_address(uint8_t *ptr)
{
    memcpy(ptr, mac_address, 8);
}

/*
 * \brief Function returns the generated 8-bit random value for seeding Pseudo-random generator. This value was generated by reading noise from RF channel in RF initialisation.
 *
 * \param none
 *
 * \return random RSSI value
 */
extern "C" int8_t rf_read_random(void)
{
    return rf_rnd_rssi;
}

/*
 * \brief Read connected radio part.
 *
 * This function only return valid information when rf_init() is called
 *
 * \return
 */
extern "C" rf_trx_part_e rf_radio_type_read(void)
{
  return ATMEL_UNKNOW_DEV;
}