/** library to communication with Semtech SX172x LoRa radio module using SPI * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2020 * @note peripherals used: SPI @ref radio_sx172x_spi, GPIO @ref radio_sx172x_gpio * @note the interrupts and corresponding DIO should be handled directly by the user */ /* standard libraries */ #include // standard integer types #include // general utilities #include // boolean utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // real-time control clock library #include // general purpose input output library #include // SPI library /* own libraries */ #include "global.h" // common methods #include "radio_sx172x.h" // own definitions /** @defgroup radio_sx172x_spi SPI peripheral used to communicate with the SX172x * @{ */ #define RADIO_SX172X_SPI 2 /**< SPI peripheral */ /** @} */ /** @defgroup radio_sx172x_gpio GPIO used to control the SX172x * @{ */ #define RADIO_SX172X_GPIO_NRESET PB7 /**< reset input (active low) */ /** @} */ bool radio_sx172x_setup(void) { // setup SPI rcc_periph_clock_enable(RCC_SPI_SCK_PORT(RADIO_SX172X_SPI)); // enable clock for GPIO peripheral for clock signal gpio_set_mode(SPI_SCK_PORT(RADIO_SX172X_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(RADIO_SX172X_SPI)); // set SCK as output (clock speed will be negotiated later) rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(RADIO_SX172X_SPI)); // enable clock for GPIO peripheral for MOSI signal gpio_set_mode(SPI_MOSI_PORT(RADIO_SX172X_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(RADIO_SX172X_SPI)); // set MOSI as output rcc_periph_clock_enable(RCC_SPI_MISO_PORT(RADIO_SX172X_SPI)); // enable clock for GPIO peripheral for MISO signal gpio_set_mode(SPI_MISO_PORT(RADIO_SX172X_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(RADIO_SX172X_SPI)); // set MISO as input rcc_periph_clock_enable(RCC_SPI_NSS_PORT(RADIO_SX172X_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal gpio_set_mode(SPI_NSS_PORT(RADIO_SX172X_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(RADIO_SX172X_SPI)); // set NSS (CS) as output rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function rcc_periph_clock_enable(RCC_SPI(RADIO_SX172X_SPI)); // enable clock for SPI peripheral spi_reset(SPI(RADIO_SX172X_SPI)); // clear SPI values to default spi_init_master(SPI(RADIO_SX172X_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_8, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_2, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 8 (72E6/8 < 10 MHz, max SX172X SCK is 10 MHz, maximum SPI PCLK clock is 72 MHz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (polarity depends on clock phase), use 8-bit frames , use MSb first spi_set_full_duplex_mode(SPI(RADIO_SX172X_SPI)); // ensure we are in full duplex mode spi_enable_software_slave_management(SPI(RADIO_SX172X_SPI)); // control NSS (CS) manually spi_set_nss_high(SPI(RADIO_SX172X_SPI)); // set NSS high (internally) so we can output spi_disable_ss_output(SPI(RADIO_SX172X_SPI)); // disable NSS output since we control CS manually gpio_set(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS high to unselect device spi_enable(SPI(RADIO_SX172X_SPI)); // enable SPI // use NRESET pin to read ensure it started rcc_periph_clock_enable(GPIO_RCC(RADIO_SX172X_GPIO_NRESET)); // enable clock for GPIO port gpio_set_mode(GPIO_PORT(RADIO_SX172X_GPIO_NRESET), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(RADIO_SX172X_GPIO_NRESET)); // set GPIO as input sleep_ms(10); // wait for POR sequence to complete return (0 != gpio_get(GPIO_PORT(RADIO_SX172X_GPIO_NRESET), GPIO_PIN(RADIO_SX172X_GPIO_NRESET))); // ensure the module has started } void radio_sx172x_release(void) { spi_disable(SPI(RADIO_SX172X_SPI)); rcc_periph_clock_disable(RCC_SPI(RADIO_SX172X_SPI)); gpio_set_mode(SPI_NSS_PORT(RADIO_SX172X_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_NSS_PIN(RADIO_SX172X_SPI)); gpio_set_mode(SPI_MISO_PORT(RADIO_SX172X_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_MISO_PIN(RADIO_SX172X_SPI)); gpio_set_mode(SPI_MOSI_PORT(RADIO_SX172X_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_MOSI_PIN(RADIO_SX172X_SPI)); gpio_set_mode(SPI_SCK_PORT(RADIO_SX172X_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_SCK_PIN(RADIO_SX172X_SPI)); } bool radio_sx172x_reset(void) { gpio_clear(GPIO_PORT(RADIO_SX172X_GPIO_NRESET), GPIO_PIN(RADIO_SX172X_GPIO_NRESET)); // set low to reset device gpio_set_mode(GPIO_PORT(RADIO_SX172X_GPIO_NRESET), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RADIO_SX172X_GPIO_NRESET)); // set GPIO as output sleep_us(200); // set low for at least 100 us (see datasheet section 7.2.2) gpio_set_mode(GPIO_PORT(RADIO_SX172X_GPIO_NRESET), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(RADIO_SX172X_GPIO_NRESET)); // set GPIO as input sleep_ms(6); // wait for at least 5 ms for reset to complete (see datasheet section 7.2.2) return (0 != gpio_get(GPIO_PORT(RADIO_SX172X_GPIO_NRESET), GPIO_PIN(RADIO_SX172X_GPIO_NRESET))); // ensure the module has started } uint8_t radio_sx172x_read_register(enum radio_sx172x_register_t name) { gpio_clear(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS low to select device (void)SPI_DR(SPI(RADIO_SX172X_SPI)); // clear RXNE flag (by reading previously received data) spi_send(SPI(RADIO_SX172X_SPI), name & 0x7f); // send register address (with 7th bit at 0 to read) and any data spi_read(SPI(RADIO_SX172X_SPI)); // wait for data to be transferred, but discard the received data (clears RXNE go get the next data correctly) spi_send(SPI(RADIO_SX172X_SPI), 0); // write any data so we can read back data const uint8_t value = spi_read(SPI(RADIO_SX172X_SPI)); // read data while ((SPI_SR(SPI(RADIO_SX172X_SPI)) & SPI_SR_BSY)); // wait for SPI to not be busy (communication completed) gpio_set(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS high to unselect device return value; } void radio_sx172x_write_register(enum radio_sx172x_register_t name, uint8_t value) { gpio_clear(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS low to select device spi_send(SPI(RADIO_SX172X_SPI), name | 0x80); // send register address (with 7th bit at 1 to write) and value spi_send(SPI(RADIO_SX172X_SPI), value); // write value while ((SPI_SR(SPI(RADIO_SX172X_SPI)) & SPI_SR_BSY)); // wait for SPI to not be busy (communication completed) gpio_set(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS high to unselect device } void radio_sx172x_read_fifo(uint8_t addr, uint8_t* data, uint8_t length) { if (NULL == data || 0 == length || addr + length > 256) { // sanity check return; } const uint8_t mode = radio_sx172x_read_register(RADIO_SX172X_REG_OP_MODE); // read in which mode we are if (0 == (mode & 0x7)) { // we are in sleep mode radio_sx172x_write_register(RADIO_SX172X_REG_OP_MODE, (mode & 0xf8) | 1); // go to standby mode so we can access the FIFO registers } radio_sx172x_write_register(RADIO_SX172X_REG_LORA_FIFO_ADDR_PTR, addr); // set address to be read gpio_clear(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS low to select device (void)SPI_DR(SPI(RADIO_SX172X_SPI)); // clear RXNE flag (by reading previously received data) spi_send(SPI(RADIO_SX172X_SPI), RADIO_SX172X_REG_FIFO & 0x7f); // select FIFO register (with 7th bit at 0 to read) spi_read(SPI(RADIO_SX172X_SPI)); // wait for data to be transferred, but discard the received data (clears RXNE go get the next data correctly) for (uint8_t i = 0; i < length; i++) { spi_send(SPI(RADIO_SX172X_SPI), 0); // write any data so we can read back data data[i] = spi_read(SPI(RADIO_SX172X_SPI)); // read data } while ((SPI_SR(SPI(RADIO_SX172X_SPI)) & SPI_SR_BSY)); // wait for SPI to not be busy (communication completed) gpio_set(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS high to unselect device } void radio_sx172x_write_fifo(uint8_t addr, const uint8_t* data, uint8_t length) { if (NULL == data || 0 == length || addr + length > 256) { // sanity check return; } const uint8_t mode = radio_sx172x_read_register(RADIO_SX172X_REG_OP_MODE); // read in which mode we are if (0 == (mode & 0x7)) { // we are in sleep mode radio_sx172x_write_register(RADIO_SX172X_REG_OP_MODE, (mode & 0xf8) | 1); // go to standby mode so we can access the FIFO registers } radio_sx172x_write_register(RADIO_SX172X_REG_LORA_FIFO_ADDR_PTR, addr); // set address to be read gpio_clear(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS low to select device spi_send(SPI(RADIO_SX172X_SPI), RADIO_SX172X_REG_FIFO | 0x80); // select FIFO register (with 7th bit at 1 to write) for (uint8_t i = 0; i < length; i++) { spi_send(SPI(RADIO_SX172X_SPI), data[i]); // write data } while ((SPI_SR(SPI(RADIO_SX172X_SPI)) & SPI_SR_BSY)); // wait for SPI to not be busy (communication completed) gpio_set(SPI_NSS_PORT(RADIO_SX172X_SPI), SPI_NSS_PIN(RADIO_SX172X_SPI)); // set CS high to unselect device }