Files
LoraTestSystemRX_TX/components/lora_radio/lora_radio.c
2026-01-17 09:53:08 +02:00

477 lines
16 KiB
C

#include "lora_radio.h"
#include <string.h>
#include "esp_check.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lora_1121.h"
#include "lr1121_common/lr1121_common.h"
#include "lr11xx_driver/lr11xx_hal.h"
#include "lr11xx_driver/lr11xx_radio.h"
#include "lr11xx_driver/lr11xx_regmem.h"
#include "lr11xx_driver/lr11xx_system.h"
static const char *TAG = "lora_radio";
#define LR1121_SPI_FREQ_HZ (8 * 1000 * 1000)
#define LR1121_SPI_MODE (0)
#define LR1121_RX_CONTINUOUS (0xFFFFFF)
#define LR1121_SYNC_WORD (0x12) // Private network
#define LR1121_PA_RAMP (LR11XX_RADIO_RAMP_48_US)
static lr1121_t s_lr = {0};
static spi_device_handle_t s_spi = NULL;
static bool s_is_tx = false;
static lora_params_t s_params;
static lora_metrics_t s_metrics = { .rssi_dbm = -120, .snr_db = -20, .last_status = 0 };
static char s_last_payload[96] = "(no data)";
static size_t s_last_payload_len = 0;
static int64_t s_last_rx_restart_us = 0;
static bool s_tx_in_progress = false;
static int64_t s_last_tx_us = 0;
static inline esp_err_t lr_status_to_esp(lr11xx_status_t st)
{
return (st == LR11XX_STATUS_OK) ? ESP_OK : ESP_FAIL;
}
static uint32_t cmhz_to_hz(int cmhz)
{
if (cmhz <= 0) {
return 0;
}
return (uint32_t)cmhz * 10000U;
}
static lr11xx_radio_lora_sf_t map_sf(int sf)
{
switch (sf) {
case 5: return LR11XX_RADIO_LORA_SF5;
case 6: return LR11XX_RADIO_LORA_SF6;
case 7: return LR11XX_RADIO_LORA_SF7;
case 8: return LR11XX_RADIO_LORA_SF8;
case 9: return LR11XX_RADIO_LORA_SF9;
case 10: return LR11XX_RADIO_LORA_SF10;
case 11: return LR11XX_RADIO_LORA_SF11;
case 12: return LR11XX_RADIO_LORA_SF12;
default: return LR11XX_RADIO_LORA_SF7;
}
}
static lr11xx_radio_lora_bw_t map_bw(int bw_khz)
{
switch (bw_khz) {
case 125: return LR11XX_RADIO_LORA_BW_125;
case 250: return LR11XX_RADIO_LORA_BW_250;
case 500: return LR11XX_RADIO_LORA_BW_500;
default: return LR11XX_RADIO_LORA_BW_125;
}
}
static lr11xx_radio_lora_cr_t map_cr(int cr)
{
switch (cr) {
case 5: return LR11XX_RADIO_LORA_CR_4_5;
case 6: return LR11XX_RADIO_LORA_CR_4_6;
case 7: return LR11XX_RADIO_LORA_CR_4_7;
case 8: return LR11XX_RADIO_LORA_CR_4_8;
default: return LR11XX_RADIO_LORA_CR_4_5;
}
}
static void sanitize_params(lora_params_t *p)
{
if (!p) {
return;
}
if (p->freq_centi_mhz < 15000) p->freq_centi_mhz = 15000; // 150 MHz мінімум
if (p->freq_centi_mhz > 250000) p->freq_centi_mhz = 250000; // 2.5 GHz максимум
if (p->bw_khz != 125 && p->bw_khz != 250 && p->bw_khz != 500) {
p->bw_khz = 125;
}
if (p->sf < 5) p->sf = 5;
if (p->sf > 12) p->sf = 12;
if (p->cr < 5) p->cr = 5;
if (p->cr > 8) p->cr = 8;
int max_dbm = 22;
// HF port (L/S/2.4 GHz) має нижчу допустиму потужність
if (p->freq_centi_mhz >= 150000) {
max_dbm = 13;
}
if (p->tx_power_dbm < -17) p->tx_power_dbm = -17;
if (p->tx_power_dbm > max_dbm) p->tx_power_dbm = max_dbm;
if (p->preamble_syms == 0) p->preamble_syms = 8;
if (p->payload_len == 0 && p->header_implicit) {
p->payload_len = 1;
}
}
static void lr1121_calibrate_image_for_freq(uint32_t freq_hz)
{
// Використовуємо універсальне калібрування за частотою в MHz ±10 MHz
uint16_t mhz = (uint16_t)(freq_hz / 1000000U);
uint16_t f1 = (mhz > 10) ? (uint16_t)(mhz - 10) : mhz;
uint16_t f2 = (uint16_t)(mhz + 10);
lr11xx_system_calibrate_image_in_mhz(&s_lr, f1, f2);
}
static esp_err_t radio_setup_spi(void)
{
spi_bus_config_t buscfg = {
.mosi_io_num = CONFIG_LORA_PIN_MOSI,
.miso_io_num = CONFIG_LORA_PIN_MISO,
.sclk_io_num = CONFIG_LORA_PIN_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
esp_err_t err = spi_bus_initialize(CONFIG_LORA_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
return err;
}
if (s_spi) {
return ESP_OK;
}
spi_device_interface_config_t devcfg = {
.clock_speed_hz = LR1121_SPI_FREQ_HZ,
.mode = LR1121_SPI_MODE,
.spics_io_num = -1, // CS керуємо вручну в HAL
.queue_size = 2,
};
err = spi_bus_add_device(CONFIG_LORA_SPI_HOST, &devcfg, &s_spi);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
return err;
}
return ESP_OK;
}
static esp_err_t lr1121_hw_init(void)
{
lora_init_io_context(&s_lr,
CONFIG_LORA_PIN_CS,
CONFIG_LORA_PIN_RST,
CONFIG_LORA_PIN_BUSY,
CONFIG_LORA_PIN_DIO1);
lora_init_io(&s_lr);
lora_spi_init(&s_lr, s_spi);
return ESP_OK;
}
static esp_err_t lr1121_system_boot(void)
{
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_reset(&s_lr)), TAG, "reset");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_hal_wakeup(&s_lr)), TAG, "wakeup");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_standby(&s_lr, LR11XX_SYSTEM_STANDBY_CFG_XOSC)), TAG, "standby");
const lr11xx_system_reg_mode_t reg_mode = smtc_shield_lr11xx_common_get_reg_mode();
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_reg_mode(&s_lr, reg_mode)), TAG, "reg mode");
const lr11xx_system_rfswitch_cfg_t *rf_switch = smtc_shield_lr11xx_common_get_rf_switch_cfg();
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_dio_as_rf_switch(&s_lr, rf_switch)), TAG, "rf switch");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_tcxo_mode(&s_lr, LR11XX_SYSTEM_TCXO_CTRL_3_0V, 300)), TAG, "tcxo");
const smtc_shield_lr11xx_lfclk_cfg_t *lf = smtc_shield_lr11xx_common_get_lfclk_cfg();
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_cfg_lfclk(&s_lr, lf->lf_clk_cfg, lf->wait_32k_ready)), TAG, "lfclk");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_clear_errors(&s_lr)), TAG, "clear errors");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_calibrate(&s_lr, 0x3F)), TAG, "calibrate");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_clear_irq_status(&s_lr, LR11XX_SYSTEM_IRQ_ALL_MASK)), TAG, "clear irq");
lr11xx_system_version_t ver = {0};
if (lr11xx_system_get_version(&s_lr, &ver) == LR11XX_STATUS_OK) {
ESP_LOGI(TAG, "LR1121 ver hw=0x%02X type=0x%02X fw=0x%04X",
ver.hw, ver.type, ver.fw);
}
return ESP_OK;
}
static esp_err_t lr1121_apply_lora(const lora_params_t *p)
{
uint32_t freq_hz = cmhz_to_hz(p->freq_centi_mhz);
lr11xx_radio_lora_bw_t bw = map_bw(p->bw_khz);
lr11xx_radio_lora_sf_t sf = map_sf(p->sf);
lr11xx_radio_lora_cr_t cr = map_cr(p->cr);
lr11xx_radio_mod_params_lora_t mod = {
.sf = sf,
.bw = bw,
.cr = cr,
.ldro = smtc_shield_lr11xx_common_compute_lora_ldro(sf, bw),
};
lr11xx_radio_pkt_params_lora_t pkt = {
.preamble_len_in_symb = p->preamble_syms,
.header_type = p->header_implicit ? LR11XX_RADIO_LORA_PKT_IMPLICIT : LR11XX_RADIO_LORA_PKT_EXPLICIT,
.pld_len_in_bytes = p->payload_len,
.crc = p->crc_on ? LR11XX_RADIO_LORA_CRC_ON : LR11XX_RADIO_LORA_CRC_OFF,
.iq = p->iq_invert ? LR11XX_RADIO_LORA_IQ_INVERTED : LR11XX_RADIO_LORA_IQ_STANDARD,
};
const lr11xx_radio_rssi_calibration_table_t *rssi_cal = smtc_shield_lr11xx_get_rssi_calibration_table(freq_hz);
const smtc_shield_lr11xx_pa_pwr_cfg_t *pa_cfg = smtc_shield_lr1121mb1gis_get_pa_pwr_cfg(freq_hz, p->tx_power_dbm);
if (pa_cfg == NULL || rssi_cal == NULL) {
return ESP_ERR_INVALID_ARG;
}
lr1121_calibrate_image_for_freq(freq_hz);
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_pkt_type(&s_lr, LR11XX_RADIO_PKT_TYPE_LORA)), TAG, "pkt type");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_rf_freq(&s_lr, freq_hz)), TAG, "freq");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_rssi_calibration(&s_lr, rssi_cal)), TAG, "rssi cal");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_pa_cfg(&s_lr, &pa_cfg->pa_config)), TAG, "pa cfg");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_tx_params(&s_lr, pa_cfg->power, LR1121_PA_RAMP)), TAG, "tx params");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_rx_tx_fallback_mode(&s_lr, LR11XX_RADIO_FALLBACK_STDBY_RC)), TAG, "fallback");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_cfg_rx_boosted(&s_lr, true)), TAG, "rx boost");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_mod_params(&s_lr, &mod)), TAG, "mod params");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_pkt_params(&s_lr, &pkt)), TAG, "pkt params");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_sync_word(&s_lr, LR1121_SYNC_WORD)), TAG, "sync word");
lr11xx_system_irq_mask_t irq_mask = LR11XX_SYSTEM_IRQ_RX_DONE | LR11XX_SYSTEM_IRQ_TIMEOUT | LR11XX_SYSTEM_IRQ_HEADER_ERROR | LR11XX_SYSTEM_IRQ_CRC_ERROR;
if (s_is_tx) {
irq_mask |= LR11XX_SYSTEM_IRQ_TX_DONE;
}
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_dio_irq_params(&s_lr, irq_mask, 0)), TAG, "irq mask");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_clear_irq_status(&s_lr, LR11XX_SYSTEM_IRQ_ALL_MASK)), TAG, "clr irq");
return ESP_OK;
}
static esp_err_t lr1121_enter_rx_continuous(void)
{
s_last_rx_restart_us = esp_timer_get_time();
return lr_status_to_esp(lr11xx_radio_set_rx_with_timeout_in_rtc_step(&s_lr, LR1121_RX_CONTINUOUS));
}
bool lora_radio_init(bool is_tx_role)
{
s_is_tx = is_tx_role;
s_params.freq_centi_mhz = CONFIG_LORA_FREQ_MHZ * 100;
s_params.bw_khz = CONFIG_LORA_BW_KHZ;
s_params.sf = CONFIG_LORA_SF;
s_params.cr = CONFIG_LORA_CR;
s_params.tx_power_dbm = 14;
s_params.preamble_syms = 8;
s_params.payload_len = 0;
s_params.crc_on = true;
s_params.iq_invert = false;
s_params.header_implicit = false;
sanitize_params(&s_params);
ESP_LOGI(TAG, "Init LR1121 over SPI host %d (MOSI=%d, MISO=%d, SCK=%d, CS=%d, RST=%d, BUSY=%d, DIO1=%d)",
CONFIG_LORA_SPI_HOST,
CONFIG_LORA_PIN_MOSI,
CONFIG_LORA_PIN_MISO,
CONFIG_LORA_PIN_SCK,
CONFIG_LORA_PIN_CS,
CONFIG_LORA_PIN_RST,
CONFIG_LORA_PIN_BUSY,
CONFIG_LORA_PIN_DIO1);
if (CONFIG_LORA_PIN_BUSY < 0) {
ESP_LOGE(TAG, "BUSY pin обов'язковий для LR1121, вкажіть коректний GPIO");
return false;
}
if (radio_setup_spi() != ESP_OK) {
ESP_LOGE(TAG, "SPI init failed");
return false;
}
if (lr1121_hw_init() != ESP_OK) {
ESP_LOGE(TAG, "IO init failed");
return false;
}
if (lr1121_system_boot() != ESP_OK) {
ESP_LOGE(TAG, "System boot failed");
return false;
}
if (lr1121_apply_lora(&s_params) != ESP_OK) {
ESP_LOGE(TAG, "Initial params failed");
return false;
}
if (!s_is_tx) {
lr1121_enter_rx_continuous();
}
ESP_LOGI(TAG, "Radio ready: %.2f MHz BW %d kHz SF %d CR 4/%d TX %d dBm",
s_params.freq_centi_mhz / 100.0f,
s_params.bw_khz,
s_params.sf,
s_params.cr,
s_params.tx_power_dbm);
return true;
}
esp_err_t lora_radio_apply_params(const lora_params_t *params)
{
if (!params) {
return ESP_ERR_INVALID_ARG;
}
lora_params_t clean = *params;
sanitize_params(&clean);
esp_err_t err = lr1121_apply_lora(&clean);
if (err == ESP_OK) {
s_params = clean;
if (!s_is_tx) {
lr1121_enter_rx_continuous();
}
ESP_LOGI(TAG, "Apply LoRa params: %.2f MHz, BW %d kHz, SF %d, CR 4/%d, TX %d dBm",
s_params.freq_centi_mhz / 100.0f,
s_params.bw_khz,
s_params.sf,
s_params.cr,
s_params.tx_power_dbm);
}
return err;
}
esp_err_t lora_radio_set_tx_power_dbm(int dbm)
{
lora_params_t next = s_params;
next.tx_power_dbm = dbm;
return lora_radio_apply_params(&next);
}
esp_err_t lora_radio_set_frequency_centi_mhz(int freq_centi_mhz)
{
lora_params_t next = s_params;
next.freq_centi_mhz = freq_centi_mhz;
return lora_radio_apply_params(&next);
}
void lora_radio_get_params(lora_params_t *out)
{
if (!out) {
return;
}
*out = s_params;
}
void lora_radio_tick_tx(void)
{
if (!s_is_tx) {
return;
}
lr11xx_system_irq_mask_t irq_status = 0;
if (lr11xx_system_get_and_clear_irq_status(&s_lr, &irq_status) != LR11XX_STATUS_OK) {
return;
}
if (irq_status == 0) {
return;
}
s_metrics.last_status = (uint8_t)(irq_status & 0xFF);
if (irq_status & LR11XX_SYSTEM_IRQ_TX_DONE) {
s_tx_in_progress = false;
ESP_LOGI(TAG, "TX done");
}
if (irq_status & LR11XX_SYSTEM_IRQ_TIMEOUT) {
s_tx_in_progress = false;
ESP_LOGW(TAG, "TX timeout");
}
}
void lora_radio_tick_rx(void)
{
if (s_is_tx) {
return;
}
lr11xx_system_irq_mask_t irq_status = 0;
if (lr11xx_system_get_and_clear_irq_status(&s_lr, &irq_status) != LR11XX_STATUS_OK) {
return;
}
if (irq_status == 0) {
int64_t now = esp_timer_get_time();
if (now - s_last_rx_restart_us > 1000000) {
lr1121_enter_rx_continuous();
}
return;
}
s_metrics.last_status = (uint8_t)(irq_status & 0xFF);
if (irq_status & LR11XX_SYSTEM_IRQ_RX_DONE) {
lr11xx_radio_rx_buffer_status_t rx_status = {0};
if (lr11xx_radio_get_rx_buffer_status(&s_lr, &rx_status) == LR11XX_STATUS_OK && rx_status.pld_len_in_bytes > 0) {
uint8_t buf[sizeof(s_last_payload)];
uint8_t copy_len = rx_status.pld_len_in_bytes;
if (copy_len >= sizeof(buf)) {
copy_len = sizeof(buf) - 1;
}
if (lr11xx_regmem_read_buffer8(&s_lr, buf, rx_status.buffer_start_pointer, copy_len) == LR11XX_STATUS_OK) {
memcpy(s_last_payload, buf, copy_len);
s_last_payload[copy_len] = '\0';
s_last_payload_len = copy_len;
lr11xx_radio_pkt_status_lora_t pkt = {0};
if (lr11xx_radio_get_lora_pkt_status(&s_lr, &pkt) == LR11XX_STATUS_OK) {
s_metrics.rssi_dbm = pkt.rssi_pkt_in_dbm;
s_metrics.snr_db = pkt.snr_pkt_in_db;
}
ESP_LOGI(TAG, "RX %u bytes RSSI=%ddBm SNR=%ddB \"%s\"",
(unsigned)copy_len, s_metrics.rssi_dbm, s_metrics.snr_db, s_last_payload);
}
}
lr1121_enter_rx_continuous();
}
if (irq_status & (LR11XX_SYSTEM_IRQ_TIMEOUT | LR11XX_SYSTEM_IRQ_HEADER_ERROR | LR11XX_SYSTEM_IRQ_CRC_ERROR)) {
ESP_LOGW(TAG, "RX irq=0x%04X (timeout/header/crc)", irq_status);
lr1121_enter_rx_continuous();
}
}
void lora_radio_get_metrics(lora_metrics_t *out)
{
if (!out) {
return;
}
*out = s_metrics;
}
void lora_radio_get_last_payload(char *out, size_t out_len)
{
if (!out || out_len == 0) {
return;
}
if (s_last_payload_len == 0) {
strlcpy(out, "(no data)", out_len);
return;
}
strlcpy(out, s_last_payload, out_len);
}
esp_err_t lora_radio_send(const uint8_t *data, size_t len)
{
if (!s_is_tx) {
return ESP_ERR_INVALID_STATE;
}
if (!data || len == 0) {
return ESP_ERR_INVALID_ARG;
}
if (len > 255) {
len = 255; // обмеження довжини пакета
}
// Оновити pkt params під фактичну довжину
lr11xx_radio_pkt_params_lora_t pkt = {
.preamble_len_in_symb = s_params.preamble_syms,
.header_type = s_params.header_implicit ? LR11XX_RADIO_LORA_PKT_IMPLICIT : LR11XX_RADIO_LORA_PKT_EXPLICIT,
.pld_len_in_bytes = (uint8_t)len,
.crc = s_params.crc_on ? LR11XX_RADIO_LORA_CRC_ON : LR11XX_RADIO_LORA_CRC_OFF,
.iq = s_params.iq_invert ? LR11XX_RADIO_LORA_IQ_INVERTED : LR11XX_RADIO_LORA_IQ_STANDARD,
};
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_pkt_params(&s_lr, &pkt)), TAG, "pkt params tx");
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_regmem_write_buffer8(&s_lr, data, len)), TAG, "write buf");
// timeout 0 => без тайм-ауту, чекаємо TX_DONE
ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_tx(&s_lr, 0)), TAG, "set tx");
s_tx_in_progress = true;
s_last_tx_us = esp_timer_get_time();
s_params.payload_len = (uint8_t)len;
return ESP_OK;
}