#include "sx127x_driver.h" #include <esp_log.h> #include <freertos/task.h> #include <string.h> static const char *TAG = "sx127x"; #define MAX_PKT_LENGTH 255 #define RESET_DELAY (pdMS_TO_TICKS(15)) // 8mhz #define SPI_CLOCK_HZ (8 * 1000 * 1000) #define TASK_STACK_SIZE (2 * 1024) #define TASK_PRIORITY 3 #define REG_FIFO 0x00 #define REG_OP_MODE 0x01 #define REG_FRF_MSB 0x06 #define REG_FRF_MID 0x07 #define REG_FRF_LSB 0x08 #define REG_PA_CONFIG 0x09 #define REG_OCP 0x0b #define REG_LNA 0x0c #define REG_FIFO_ADDR_PTR 0x0d #define REG_FIFO_TX_BASE_ADDR 0x0e #define REG_FIFO_RX_BASE_ADDR 0x0f #define REG_FIFO_RX_CURRENT_ADDR 0x10 #define REG_IRQ_FLAGS 0x12 #define REG_RX_NB_BYTES 0x13 #define REG_PKT_SNR_VALUE 0x19 #define REG_PKT_RSSI_VALUE 0x1a #define REG_MODEM_CONFIG_1 0x1d #define REG_MODEM_CONFIG_2 0x1e #define REG_PREAMBLE_MSB 0x20 #define REG_PREAMBLE_LSB 0x21 #define REG_PAYLOAD_LENGTH 0x22 #define REG_MODEM_CONFIG_3 0x26 #define REG_FREQ_ERROR_MSB 0x28 #define REG_FREQ_ERROR_MID 0x29 #define REG_FREQ_ERROR_LSB 0x2a #define REG_RSSI_WIDEBAND 0x2c #define REG_DETECTION_OPTIMIZE 0x31 #define REG_INVERTIQ 0x33 #define REG_DETECTION_THRESHOLD 0x37 #define REG_SYNC_WORD 0x39 #define REG_INVERTIQ2 0x3b #define REG_DIO_MAPPING_1 0x40 #define REG_VERSION 0x42 #define REG_PA_DAC 0x4d // modes #define MODE_LONG_RANGE_MODE 0x80 #define MODE_SLEEP 0x00 #define MODE_STDBY 0x01 #define MODE_TX 0x03 #define MODE_RX_CONTINUOUS 0x05 #define MODE_RX_SINGLE 0x06 #define CONFIG2_CRC 0x04 #define CONFIG3_AUTO_AGC 0x04 // PA config #define PA_BOOST 0x80 // IRQ masks #define IRQ_TX_DONE_MASK 0x08 #define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20 #define IRQ_RX_DONE_MASK 0x40 #ifdef NODEBUG #define SX127X_CHECK(check, str, ret_val, ...) \ if (!(check)) { \ ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ return (ret_val); \ } #define SX127X_ERROR_CHECK(ret) \ { \ esp_err_t _error_code = (ret); \ if (_error_code != ESP_OK) { \ return _error_code; \ } \ } #define SX127X_ERROR_CHECK2(ret, fun) SX127X_CHECK(ret) #else #define SX127X_CHECK(check, str, ret_val, ...) \ if (!(check)) { \ ESP_LOGE(TAG, "%s(%d): " #check ": " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ return (ret_val); \ } #define SX127X_ERROR_CHECK(ret) \ { \ esp_err_t _error_code = (ret); \ if (_error_code != ESP_OK) { \ const char *_error_name = esp_err_to_name(_error_code); \ ESP_LOGE(TAG, "%s(%d): %s (%d)", __FUNCTION__, __LINE__, _error_name, _error_code); \ return _error_code; \ } \ } #define SX127X_ERROR_CHECK2(ret, fun) \ { \ esp_err_t _error_code = (ret); \ if (_error_code != ESP_OK) { \ const char *_error_name = esp_err_to_name(_error_code); \ ESP_LOGE(TAG, "%s(%d): " #fun ": %s (%d)", __FUNCTION__, __LINE__, _error_name, _error_code); \ return _error_code; \ } \ } #endif struct sx127x { sx127x_config_t config; spi_device_handle_t device_handle; TaskHandle_t task_handle; bool task_running; }; static esp_err_t sx127x_read_register(sx127x_t *handle, uint8_t reg, uint8_t *value); static esp_err_t sx127x_write_register(sx127x_t *handle, uint8_t reg, uint8_t value); static esp_err_t sx127x_single_transfer(sx127x_t *handle, uint8_t addr, uint8_t to_slave, uint8_t *from_slave); static uint8_t sx127x_bw_to_reg(uint64_t bandwidth); static uint64_t sx127x_reg_to_bw(uint8_t bandwidth_reg); esp_err_t sx127x_init(sx127x_config_t *config, sx127x_t **handle_ptr) { esp_err_t ret; sx127x_t *handle = malloc(sizeof(sx127x_t)); SX127X_CHECK(handle != NULL, "malloc error", ESP_ERR_NO_MEM); handle->task_handle = NULL; memcpy(&handle->config, config, sizeof(sx127x_config_t)); ret = gpio_set_direction(config->rst_io_num, GPIO_MODE_OUTPUT); SX127X_ERROR_CHECK2(ret, gpio_set_direction) // perform reset gpio_set_level(config->rst_io_num, 0); vTaskDelay(RESET_DELAY); gpio_set_level(config->rst_io_num, 1); vTaskDelay(RESET_DELAY); spi_bus_config_t bus_config = {.mosi_io_num = config->mosi_io_num, .miso_io_num = config->miso_io_num, .sclk_io_num = config->sck_io_num, .quadhd_io_num = -1, .quadwp_io_num = -1, .max_transfer_sz = SX127X_MAX_TRANSFER}; ret = spi_bus_initialize(config->spi_host, &bus_config, 1); SX127X_ERROR_CHECK2(ret, spi_bus_initialize) spi_device_interface_config_t device_config = { .command_bits = 0, .address_bits = 8, .dummy_bits = 0, .mode = 0, .duty_cycle_pos = 0, .cs_ena_pretrans = 0, .cs_ena_posttrans = 0, .clock_speed_hz = SPI_CLOCK_HZ, // 8mhz .input_delay_ns = 0, .spics_io_num = config->cs_io_num, .flags = 0, .queue_size = 8, .pre_cb = NULL, .post_cb = NULL, }; ret = spi_bus_add_device(config->spi_host, &device_config, &handle->device_handle); SX127X_ERROR_CHECK2(ret, spi_bus_add_device) // read version and check that it is compatible uint8_t version; ret = sx127x_read_register(handle, REG_VERSION, &version); SX127X_ERROR_CHECK2(ret, sx127x_read_register); SX127X_CHECK(version == 0x12, "unsupported version %#x", ESP_ERR_INVALID_VERSION, version); ret = sx127x_sleep(handle); SX127X_ERROR_CHECK(ret); ret = sx127x_set_frequency(handle, config->frequency); SX127X_ERROR_CHECK(ret); ret = sx127x_write_register(handle, REG_FIFO_TX_BASE_ADDR, 0); SX127X_ERROR_CHECK(ret); sx127x_write_register(handle, REG_FIFO_RX_BASE_ADDR, 0); SX127X_ERROR_CHECK(ret); uint8_t reg_lna; ret = sx127x_read_register(handle, REG_LNA, ®_lna); SX127X_ERROR_CHECK(ret); reg_lna |= 0x03; // set LNA boost ret = sx127x_write_register(handle, REG_LNA, reg_lna); SX127X_ERROR_CHECK(ret); // set auto AGC ret = sx127x_write_register(handle, REG_MODEM_CONFIG_3, CONFIG3_AUTO_AGC); SX127X_ERROR_CHECK(ret); ret = sx127x_set_tx_power(handle, config->tx_power, true); SX127X_ERROR_CHECK(ret); ret = sx127x_set_spreading_factor(handle, config->spreading_factor); SX127X_ERROR_CHECK(ret); ret = sx127x_set_sync_word(handle, config->sync_word); SX127X_ERROR_CHECK(ret); ret = sx127x_set_crc(handle, config->crc); SX127X_ERROR_CHECK(ret); ret = sx127x_standby(handle); ESP_ERROR_CHECK(ret); *handle_ptr = handle; return ESP_OK; } esp_err_t sx127x_free(sx127x_t *handle) { esp_err_t ret; if (handle->task_handle) { ret = sx127x_stop(handle); SX127X_ERROR_CHECK(ret); } ret = spi_bus_remove_device(handle->device_handle); SX127X_ERROR_CHECK2(ret, spi_bus_remove_device) ret = spi_bus_free(handle->config.spi_host); SX127X_ERROR_CHECK2(ret, spi_bus_free) free(handle); return ESP_OK; } static esp_err_t sx127x_read_register(sx127x_t *handle, uint8_t reg, uint8_t *value) { return sx127x_single_transfer(handle, reg & 0x7f, 0x00, value); } static esp_err_t sx127x_write_register(sx127x_t *handle, uint8_t reg, uint8_t value) { return sx127x_single_transfer(handle, reg | 0x80, value, NULL); } static esp_err_t sx127x_single_transfer(sx127x_t *handle, uint8_t addr, uint8_t to_slave, uint8_t *from_slave) { spi_transaction_t trans; memset(&trans, 0, sizeof(trans)); trans.flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA; trans.addr = addr; trans.length = 8; trans.rxlength = 8; trans.tx_data[0] = to_slave; esp_err_t ret = spi_device_transmit(handle->device_handle, &trans); SX127X_ERROR_CHECK2(ret, spi_device_transmit); if (from_slave) { *from_slave = trans.rx_data[0]; } ESP_LOGV(TAG, "sx127x_single_transfer(%#x, %#x, %#x)", addr, trans.tx_data[0], trans.rx_data[0]); return ESP_OK; } esp_err_t sx127x_sleep(sx127x_t *handle) { return sx127x_write_register(handle, REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_SLEEP); } esp_err_t sx127x_standby(sx127x_t *handle) { return sx127x_write_register(handle, REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_STDBY); } esp_err_t sx127x_set_frequency(sx127x_t *handle, uint64_t frequency) { uint64_t frf = ((uint64_t)frequency << 19) / 32000000; esp_err_t ret; ret = sx127x_write_register(handle, REG_FRF_MSB, (uint8_t)(frf)); SX127X_ERROR_CHECK(ret); frf >>= 8; ret = sx127x_write_register(handle, REG_FRF_MID, (uint8_t)(frf)); SX127X_ERROR_CHECK(ret); frf >>= 8; ret = sx127x_write_register(handle, REG_FRF_LSB, (uint8_t)(frf)); SX127X_ERROR_CHECK(ret); handle->config.frequency = frequency; return ESP_OK; } esp_err_t sx127x_set_tx_power(sx127x_t *handle, uint8_t tx_power, sx127x_pa_boost_t pa_boost) { esp_err_t ret; if (pa_boost == SX127X_PA_BOOST_ENABLED) { // PA BOOST SX127X_CHECK(tx_power >= 2 && tx_power <= 20, "invalid tx_power: %d", ESP_ERR_INVALID_ARG, tx_power); ret = sx127x_write_register(handle, REG_PA_CONFIG, PA_BOOST | (tx_power - 2)); } else { // RFO SX127X_CHECK(tx_power <= 14, "invalid tx_power: %d", ESP_ERR_INVALID_ARG, tx_power); ret = sx127x_write_register(handle, REG_PA_CONFIG, 0x70 | tx_power); } SX127X_ERROR_CHECK(ret); handle->config.tx_power = tx_power; return ESP_OK; } esp_err_t sx127x_set_spreading_factor(sx127x_t *handle, uint8_t spreading_factor) { SX127X_CHECK(spreading_factor >= 6 && spreading_factor <= 12, "invalid spreading_factor", ESP_ERR_INVALID_ARG); // section 4.1.1.2 in SX1276 datasheet uint8_t detection_optimize, detection_threshold; if (spreading_factor == 6) { detection_optimize = 0xc5; detection_threshold = 0x0c; } else { detection_optimize = 0xc3; detection_threshold = 0x0a; } SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_DETECTION_OPTIMIZE, detection_optimize)); SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_DETECTION_THRESHOLD, detection_threshold)); uint8_t modem_config_3; SX127X_ERROR_CHECK(sx127x_read_register(handle, REG_MODEM_CONFIG_3, &modem_config_3)); modem_config_3 = (modem_config_3 & 0x03) | ((spreading_factor << 4) & 0xf0); SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_MODEM_CONFIG_3, modem_config_3)); handle->config.spreading_factor = spreading_factor; return ESP_OK; } esp_err_t sx127x_set_signal_bandwidth(sx127x_t *handle, uint64_t signal_bandwidth) { uint8_t bw_reg = sx127x_bw_to_reg(signal_bandwidth); uint8_t modem_config_1; SX127X_ERROR_CHECK(sx127x_read_register(handle, REG_MODEM_CONFIG_1, &modem_config_1)); modem_config_1 = (modem_config_1 & 0x0f) | (bw_reg << 4); SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_MODEM_CONFIG_1, modem_config_1)); handle->config.signal_bandwidth = signal_bandwidth; // set low data rate optimization flag uint64_t bw = sx127x_reg_to_bw(bw_reg); uint8_t sf = handle->config.spreading_factor; // section 4.1.1.5 uint64_t symbol_duration_ms = 1000 / (bw / (1L << sf)); // section 4.1.1.6 bool must_have_ldo = (symbol_duration_ms > 16); bool ldo = must_have_ldo || (handle->config.ldo == SX127X_LDO_ENABLED); uint8_t modem_config_3; SX127X_ERROR_CHECK(sx127x_read_register(handle, REG_MODEM_CONFIG_3, &modem_config_3)); if (ldo) { modem_config_3 |= (1 << 3); } else { modem_config_3 &= ~(1 << 3); } SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_MODEM_CONFIG_3, modem_config_3)); return ESP_OK; } static uint8_t sx127x_bw_to_reg(uint64_t bandwidth) { if (bandwidth <= 7.8E3) { return 0; } else if (bandwidth <= 10.4E3) { return 1; } else if (bandwidth <= 15.6E3) { return 2; } else if (bandwidth <= 20.8E3) { return 3; } else if (bandwidth <= 31.25E3) { return 4; } else if (bandwidth <= 41.7E3) { return 5; } else if (bandwidth <= 62.5E3) { return 6; } else if (bandwidth <= 125E3) { return 7; } else if (bandwidth <= 250E3) { return 8; } else /* if (bandwidth <= 500E3) */ { return 9; } } static uint64_t sx127x_reg_to_bw(uint8_t bandwidth_reg) { switch (bandwidth_reg) { case 0: return 7.8E3; case 1: return 10.4E3; case 2: return 15.6E3; case 3: return 20.8E3; case 4: return 31.25E3; case 5: return 41.7E3; case 6: return 62.5E3; case 7: return 125E3; case 8: return 250E3; default: case 9: return 500E3; } } esp_err_t sx127x_set_sync_word(sx127x_t *handle, uint8_t sync_word) { SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_SYNC_WORD, sync_word)); handle->config.sync_word = sync_word; return ESP_OK; } esp_err_t sx127x_set_crc(sx127x_t *handle, sx127x_crc_t crc) { uint8_t modem_config_2; SX127X_ERROR_CHECK(sx127x_read_register(handle, REG_MODEM_CONFIG_2, &modem_config_2)); if (crc == SX127X_CRC_ENABLED) { modem_config_2 |= CONFIG2_CRC; } else { modem_config_2 &= ~CONFIG2_CRC; } SX127X_ERROR_CHECK(sx127x_write_register(handle, REG_MODEM_CONFIG_2, modem_config_2)); handle->config.crc = crc; return ESP_OK; } void sx127x_isr(void *arg) { sx127x_t *handle = (sx127x_t *)arg; ESP_LOGI(TAG, "sx127x_isr"); } void sx127x_task(void *arg) { sx127x_t *handle = (sx127x_t *)arg; while (handle->task_running) { vTaskDelay(pdMS_TO_TICKS(10)); } } esp_err_t sx127x_start(sx127x_t *handle) { esp_err_t ret; SX127X_CHECK(handle->task_handle == NULL, "task already running", ESP_ERR_INVALID_STATE); ret = sx127x_write_register(handle, REG_DIO_MAPPING_1, 0x00); SX127X_ERROR_CHECK(ret); gpio_config_t irq_io_config; irq_io_config.intr_type = GPIO_INTR_POSEDGE; irq_io_config.mode = GPIO_MODE_INPUT; irq_io_config.pin_bit_mask = (1ULL << handle->config.irq_io_num); irq_io_config.pull_down_en = 0; irq_io_config.pull_up_en = 0; ret = gpio_config(&irq_io_config); SX127X_ERROR_CHECK2(ret, gpio_config) ret = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1); SX127X_ERROR_CHECK2(ret, gpio_install_isr_service); ret = gpio_isr_handler_add(handle->config.irq_io_num, sx127x_isr, (void *)handle); SX127X_ERROR_CHECK2(ret, gpio_isr_handler_add); handle->task_running = true; BaseType_t pdRet = xTaskCreate(sx127x_task, "sx127x_task", TASK_STACK_SIZE, (void *)handle, TASK_PRIORITY, &handle->task_handle); SX127X_CHECK(pdRet == pdPASS, "failed to create task", ESP_FAIL); return ESP_OK; } esp_err_t sx127x_stop(sx127x_t *handle) { esp_err_t ret; SX127X_CHECK(handle->task_handle != NULL, "task has not been started", ESP_ERR_INVALID_STATE); handle->task_running = false; xTaskNotify(handle->task_handle, 0, eNoAction); ret = gpio_isr_handler_remove(handle->config.irq_io_num); SX127X_ERROR_CHECK2(ret, gpio_isr_handler_remove); gpio_uninstall_isr_service(); return ESP_OK; }