ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LoRa OTA 网关升级系统开发笔记(一):LoRa通信基础

LoRa OTA 网关升级系统开发笔记(一):LoRa通信基础 系列文章目录第一篇LoRa通信基础第二篇协议层设计与CRC校验第三篇W25Q128固件管理与串口接收第四篇可靠传输机制ACK/超时/重传/窗口一、项目背景在物联网应用中设备固件升级OTA是必不可少的功能。传统的有线升级方式效率低、成本高而 LoRa 无线技术具有远距离、低功耗的优势非常适合做远程设备升级。本项目实现了一套完整的 LoRa OTA 网关升级系统网关采用 STM32F429 作为主控节点采用 STM32F103通过 LoRa 模块LLCC68实现无线通信。系统架构图┌────────────────┐│ PC/云平台│ (固件下发源)└────────────────┘│ 串口 / MQTT▼┌─────────────────┐│ F429 LoRa 网关│ (STM32F429 LLCC68)│ W25Q128 (固件存储)└─────────────────┘│ LoRa 无线▼┌──────────────────┐│ F103 LoRa 节点│ (STM32F103 LLCC68)│ Bootloader 升级└──────────────────┘硬件平台组件型号作用网关主控STM32F429协议解析、流程控制节点主控STM32F103接收固件、执行升级LoRa模块LLCC68无线通信外部FlashW25Q128 (16MB)存储固件和元数据EEPROMAT24C02存储升级标志和密钥二、LoRa 硬件驱动​​​​2.1 引脚说明引脚功能说明SPI数据通信与LLCC68进行数据交换RST复位引脚低电平有效Busy忙状态输入引脚高电平表示忙TxEN发送使能低电平有效控制收发切换三、移植LoRa 软件驱动3.1 芯片官方源码获取官方仓库地址GitHub - Lora-net/llcc68_driver: Driver for LLCC68 radio · GitHub移植到你自己的文件夹中如上图所示前两个文件为官方源码我们不需要动中间两个为官方的函数接口我们需要根据自己的需要进行配置后面两个为我们自用的函数接口3.2 修改配置官方的函数接口1. 宏定义拉低拉高LLCC68的片选引脚2.LLCC68的SPI初始化与注销使用CubeMx配置的话不需要添加如自身搭的库则调用芯片对应的SPI初始化函数即可3.LLCC68的读写4.LLCC68复位引脚初始化与注销 与LLCC68的SPI初始化处理方式一致5.写LLCC68的复位引脚6.LLCC68忙状态引脚的初始化与注销 与上述的初始化处理方式一致7.读取LLCC68的忙状态数值 低电平有效8.LLCC68的延时函数HAL库自己就有延时函数直接调用即可9.LLCC68日志打印 写入红框中代码即可其余的代码不需要配置与修改3.3 编写自己的接口函数在此阶段中我们直接参照官方的示例代码即可对其中的些许函数进行修改示例代码文件名如下1. 声明全局变量2. LLCC68初始化 只需要把参数全部删掉换成void即可其他一律不用修改全部复制过来3. 发送模式与接收模式(单次接收模式、连续接收模式)3.1 收发切换逻辑因为 LLCC68 只有一根天线不能同时收发所以需要软件控制收发模式切换默认状态接收模式TxEN 关闭发送数据时切换为发送模式TxEN 打开发送完成后立即切回接收模式所以我们只需要在两个发送模式下把TxEN(发送使能引脚)关闭在发送模式下打开即可办到4. LLCC68发送函数 我们只需要在发送的时候切换为发送模式发送完成后切换回接收模式.即可5. LLCC68的读取 官方示例的源码接收是在中断里接收的我们为了方便调用与理解采用了中断 轮询的方式首先我们现在源码里的结构体添加了rece_buf_len;其次我们在接收中断里把接收到的长度记录在结构体中方便我们调用最后的接收函数是这样的 这个函数需要我们自己写官方的示例没有这个代码自己编写的接口函数整体如下#include Int_llcc68.h // 将SPI的处理函数都以函数指针的形式注册到结构体 llcc68_handle_t gs_handle; /** * brief lora example init * param[in] *callback points to a callback address * return status code * - 0 success * - 1 init failed * note none */ uint8_t llcc68_lora_init(void) { // 声明变量 uint8_t res; uint32_t reg; uint8_t modulation; uint8_t config; // 注册SPI的函数到处理器中 DRIVER_LLCC68_LINK_INIT(gs_handle, llcc68_handle_t); DRIVER_LLCC68_LINK_SPI_INIT(gs_handle, llcc68_interface_spi_init); DRIVER_LLCC68_LINK_SPI_DEINIT(gs_handle, llcc68_interface_spi_deinit); DRIVER_LLCC68_LINK_SPI_WRITE_READ(gs_handle, llcc68_interface_spi_write_read); DRIVER_LLCC68_LINK_RESET_GPIO_INIT(gs_handle, llcc68_interface_reset_gpio_init); DRIVER_LLCC68_LINK_RESET_GPIO_DEINIT(gs_handle, llcc68_interface_reset_gpio_deinit); DRIVER_LLCC68_LINK_RESET_GPIO_WRITE(gs_handle, llcc68_interface_reset_gpio_write); DRIVER_LLCC68_LINK_BUSY_GPIO_INIT(gs_handle, llcc68_interface_busy_gpio_init); DRIVER_LLCC68_LINK_BUSY_GPIO_DEINIT(gs_handle, llcc68_interface_busy_gpio_deinit); DRIVER_LLCC68_LINK_BUSY_GPIO_READ(gs_handle, llcc68_interface_busy_gpio_read); DRIVER_LLCC68_LINK_DELAY_MS(gs_handle, llcc68_interface_delay_ms); DRIVER_LLCC68_LINK_DEBUG_PRINT(gs_handle, llcc68_interface_debug_print); DRIVER_LLCC68_LINK_RECEIVE_CALLBACK(gs_handle, llcc68_interface_receive_callback); /* init the llcc68 */ res llcc68_init(gs_handle); if (res ! 0) { llcc68_interface_debug_print(llcc68: init failed.\n); return 1; } /* enter standby */ res llcc68_set_standby(gs_handle, LLCC68_CLOCK_SOURCE_XTAL_32MHZ); if (res ! 0) { llcc68_interface_debug_print(llcc68: set standby failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set stop timer on preamble */ res llcc68_set_stop_timer_on_preamble(gs_handle, LLCC68_LORA_DEFAULT_STOP_TIMER_ON_PREAMBLE); if (res ! 0) { llcc68_interface_debug_print(llcc68: stop timer on preamble failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set regulator mode */ res llcc68_set_regulator_mode(gs_handle, LLCC68_LORA_DEFAULT_REGULATOR_MODE); if (res ! 0) { llcc68_interface_debug_print(llcc68: set regulator mode failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set pa config */ res llcc68_set_pa_config(gs_handle, LLCC68_LORA_DEFAULT_PA_CONFIG_DUTY_CYCLE, LLCC68_LORA_DEFAULT_PA_CONFIG_HP_MAX); if (res ! 0) { llcc68_interface_debug_print(llcc68: set pa config failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* enter to stdby xosc mode */ res llcc68_set_rx_tx_fallback_mode(gs_handle, LLCC68_RX_TX_FALLBACK_MODE_STDBY_XOSC); if (res ! 0) { llcc68_interface_debug_print(llcc68: set rx tx fallback mode failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set lora mode */ res llcc68_set_packet_type(gs_handle, LLCC68_PACKET_TYPE_LORA); if (res ! 0) { llcc68_interface_debug_print(llcc68: set packet type failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set tx params */ res llcc68_set_tx_params(gs_handle, LLCC68_LORA_DEFAULT_TX_DBM, LLCC68_LORA_DEFAULT_RAMP_TIME); if (res ! 0) { llcc68_interface_debug_print(llcc68: set tx params failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set lora modulation params */ res llcc68_set_lora_modulation_params(gs_handle, LLCC68_LORA_DEFAULT_SF, LLCC68_LORA_DEFAULT_BANDWIDTH, LLCC68_LORA_DEFAULT_CR, LLCC68_LORA_DEFAULT_LOW_DATA_RATE_OPTIMIZE); if (res ! 0) { llcc68_interface_debug_print(llcc68: set lora modulation params failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* convert the frequency */ res llcc68_frequency_convert_to_register(gs_handle, LLCC68_LORA_DEFAULT_RF_FREQUENCY, (uint32_t *)reg); if (res ! 0) { llcc68_interface_debug_print(llcc68: convert to register failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set the frequency */ res llcc68_set_rf_frequency(gs_handle, reg); if (res ! 0) { llcc68_interface_debug_print(llcc68: set rf frequency failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set base address */ res llcc68_set_buffer_base_address(gs_handle, 0x00, 0x00); if (res ! 0) { llcc68_interface_debug_print(llcc68: set buffer base address failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set lora symb num */ res llcc68_set_lora_symb_num_timeout(gs_handle, LLCC68_LORA_DEFAULT_SYMB_NUM_TIMEOUT); if (res ! 0) { llcc68_interface_debug_print(llcc68: set lora symb num timeout failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* reset stats */ res llcc68_reset_stats(gs_handle, 0x0000, 0x0000, 0x0000); if (res ! 0) { llcc68_interface_debug_print(llcc68: reset stats failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* clear device errors */ res llcc68_clear_device_errors(gs_handle); if (res ! 0) { llcc68_interface_debug_print(llcc68: clear device errors failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set the lora sync word */ res llcc68_set_lora_sync_word(gs_handle, LLCC68_LORA_DEFAULT_SYNC_WORD); if (res ! 0) { llcc68_interface_debug_print(llcc68: set lora sync word failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* get tx modulation */ res llcc68_get_tx_modulation(gs_handle, (uint8_t *)modulation); if (res ! 0) { llcc68_interface_debug_print(llcc68: get tx modulation failed.\n); (void)llcc68_deinit(gs_handle); return 1; } modulation | 0x04; /* set the tx modulation */ res llcc68_set_tx_modulation(gs_handle, modulation); if (res ! 0) { llcc68_interface_debug_print(llcc68: set tx modulation failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set the rx gain */ res llcc68_set_rx_gain(gs_handle, LLCC68_LORA_DEFAULT_RX_GAIN); if (res ! 0) { llcc68_interface_debug_print(llcc68: set rx gain failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* set the ocp */ res llcc68_set_ocp(gs_handle, LLCC68_LORA_DEFAULT_OCP); if (res ! 0) { llcc68_interface_debug_print(llcc68: set ocp failed.\n); (void)llcc68_deinit(gs_handle); return 1; } /* get the tx clamp config */ res llcc68_get_tx_clamp_config(gs_handle, (uint8_t *)config); if (res ! 0) { llcc68_interface_debug_print(llcc68: get tx clamp config failed.\n); (void)llcc68_deinit(gs_handle); return 1; } config | 0x1E; /* set the tx clamp config */ res llcc68_set_tx_clamp_config(gs_handle, config); if (res ! 0) { llcc68_interface_debug_print(llcc68: set tx clamp config failed.\n); (void)llcc68_deinit(gs_handle); return 1; } // 默认为接收模式 需要发数据的时候 切换到发送模式 发送完成之后再切回接收模式 llcc68_lora_set_continuous_receive_mode(); return 0; } /** * brief lora example enter to the continuous receive mode * return status code * - 0 success * - 1 enter failed * note none */ uint8_t llcc68_lora_set_continuous_receive_mode(void) { // txEN关闭 HAL_GPIO_WritePin(LORA_TXEN_GPIO_Port, LORA_TXEN_Pin, GPIO_PIN_RESET); uint8_t setup; /* set dio irq */ if (llcc68_set_dio_irq_params(gs_handle, LLCC68_IRQ_RX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CRC_ERR | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED, LLCC68_IRQ_RX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CRC_ERR | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED, 0x0000, 0x0000) ! 0) { return 1; } /* clear irq status */ if (llcc68_clear_irq_status(gs_handle, 0x03FFU) ! 0) { return 1; } /* set lora packet params */ if (llcc68_set_lora_packet_params(gs_handle, LLCC68_LORA_DEFAULT_PREAMBLE_LENGTH, LLCC68_LORA_DEFAULT_HEADER, LLCC68_LORA_DEFAULT_BUFFER_SIZE, LLCC68_LORA_DEFAULT_CRC_TYPE, LLCC68_LORA_DEFAULT_INVERT_IQ) ! 0) { return 1; } /* get iq polarity */ if (llcc68_get_iq_polarity(gs_handle, (uint8_t *)setup) ! 0) { return 1; } #if LLCC68_LORA_DEFAULT_INVERT_IQ LLCC68_BOOL_FALSE setup | 1 2; #else setup ~(1 2); #endif /* set the iq polarity */ if (llcc68_set_iq_polarity(gs_handle, setup) ! 0) { return 1; } /* start receive */ if (llcc68_continuous_receive(gs_handle) ! 0) { return 1; } return 0; } /** * brief lora example enter to the shot receive mode * return status code * - 0 success * - 1 enter failed * note none */ uint8_t llcc68_lora_set_shot_receive_mode(double us) { // txEN关闭 HAL_GPIO_WritePin(LORA_TXEN_GPIO_Port, LORA_TXEN_Pin, GPIO_PIN_RESET); uint8_t setup; /* set dio irq */ if (llcc68_set_dio_irq_params(gs_handle, LLCC68_IRQ_RX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CRC_ERR | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED, LLCC68_IRQ_RX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CRC_ERR | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED, 0x0000, 0x0000) ! 0) { return 1; } /* clear irq status */ if (llcc68_clear_irq_status(gs_handle, 0x03FFU) ! 0) { return 1; } /* set lora packet params */ if (llcc68_set_lora_packet_params(gs_handle, LLCC68_LORA_DEFAULT_PREAMBLE_LENGTH, LLCC68_LORA_DEFAULT_HEADER, LLCC68_LORA_DEFAULT_BUFFER_SIZE, LLCC68_LORA_DEFAULT_CRC_TYPE, LLCC68_LORA_DEFAULT_INVERT_IQ) ! 0) { return 1; } /* get iq polarity */ if (llcc68_get_iq_polarity(gs_handle, (uint8_t *)setup) ! 0) { return 1; } #if LLCC68_LORA_DEFAULT_INVERT_IQ LLCC68_BOOL_FALSE setup | 1 2; #else setup ~(1 2); #endif /* set the iq polarity */ if (llcc68_set_iq_polarity(gs_handle, setup) ! 0) { return 1; } /* start receive */ if (llcc68_single_receive(gs_handle, us) ! 0) { return 1; } return 0; } /** * brief lora example enter to the send mode * return status code * - 0 success * - 1 enter failed * note none */ uint8_t llcc68_lora_set_send_mode(void) { // 打开TXEN HAL_GPIO_WritePin(LORA_TXEN_GPIO_Port, LORA_TXEN_Pin, GPIO_PIN_SET); /* set dio irq */ if (llcc68_set_dio_irq_params(gs_handle, LLCC68_IRQ_TX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED, LLCC68_IRQ_TX_DONE | LLCC68_IRQ_TIMEOUT | LLCC68_IRQ_CAD_DONE | LLCC68_IRQ_CAD_DETECTED, 0x0000, 0x0000) ! 0) { return 1; } /* clear irq status */ if (llcc68_clear_irq_status(gs_handle, 0x03FFU) ! 0) { return 1; } return 0; } uint8_t llcc68_lora_send(uint8_t *buf, uint16_t len) { llcc68_lora_set_send_mode(); /* send the data */ if (llcc68_lora_transmit(gs_handle, LLCC68_CLOCK_SOURCE_XTAL_32MHZ, LLCC68_LORA_DEFAULT_PREAMBLE_LENGTH, LLCC68_LORA_DEFAULT_HEADER, LLCC68_LORA_DEFAULT_CRC_TYPE, LLCC68_LORA_DEFAULT_INVERT_IQ, (uint8_t *)buf, len, 0) ! 0) { return 1; } // 发送完成之后 改回接收模式 llcc68_lora_set_continuous_receive_mode(); return 0; } /** * brief lora example receive data * * param buf * param len * return uint8_t */ uint8_t llcc68_lora_receive(void) { if (gs_handle.receive_buf_len 0) { return 0; } return 1; }四、测试编写main.c函数4.1 导入头文件与进行初始化4.2 测试发送功能在mian函数的while循环里持续发送hello结果触发了发送中断llcc68日志持续打印irq tx done4.3 测试网关4.3.1 测试网关功能需要两个开发板一个作为网关一个作为节点如果你的两个开发板使用的LoRa通信的芯片是一样的只需要配置修改好引脚即可其余和上一份代码一样即可运行由于我两个开发板使用的LoRa芯片是一致的且芯片使用的引脚与芯片都是一致的此过程就略过了。4.3.2 测试网关接收功能第一块开发板作为网关代码在main函数的whlie循环一直调用LLCC68接收代码如下所示先把结构体在mian函数中extern因为接收到的数据存储在结构体里然后在while循环里轮询是否接收到数据如果接收到则通过串口打印第二块开发板作为节点则在main函数的whlie循环一直调用LLCC68发送如测试网关发送功能那样结果测试网关开发板连接串口助手接收到节点发送的数据并打印无误五、总结本篇完成了 LoRa 通信的基础配置功能状态SPI 通信✅ 已实现收发切换✅ 已实现发送函数✅ 已实现轮询接收✅ 已实现连续接收模式✅ 已实现下一篇预告在 LoRa 通信基础上添加协议层包括帧结构定义、设备ID、CRC校验等。
返回列表