好的,作为一名高级嵌入式软件开发工程师,我很乐意为您详细阐述ExpressLRS发射器(基于ESP32和E28模块)的软件架构设计,并提供相应的C代码实现。本项目旨在构建一个可靠、高效、可扩展的嵌入式系统平台,从需求分析到最终实现,再到测试验证和维护升级,每一步都力求精益求精。
关注微信公众号,提前获取相关推文

项目简介回顾:
本项目核心是开发一个ExpressLRS发射器,它利用ESP32作为主控芯片,E28模块作为射频前端,配备OLED屏幕用于信息显示和用户交互。ExpressLRS以其低延迟、高刷新率和远距离传输而闻名,非常适合无人机、遥控模型等应用。使用模块化组件(ESP32和E28)降低了制作难度,同时保证了性能和可靠性。
1. 需求分析
在项目初期,我们需要明确ExpressLRS发射器的具体需求。以下是一些关键需求点:
核心功能:
- ExpressLRS协议实现:支持ExpressLRS协议栈,包括数据包的编码、解码、CRC校验、数据加密(可选)等。
- 射频控制:控制E28模块进行射频信号的发射和接收,包括频率设置、功率调节、频道选择等。
- 数据传输:将遥控指令数据(例如摇杆、开关等输入)通过ExpressLRS协议无线传输给接收端。
- 遥测数据接收与处理:接收来自接收端的遥测数据(如电压、电流、信号强度等),并进行解析和显示。
用户界面:
- OLED屏幕显示:实时显示关键信息,如工作频率、发射功率、刷新率、遥测数据、系统状态等。
- 用户交互:通过按键或旋钮等输入设备,实现参数配置、模式切换、系统设置等功能。
- 菜单系统:构建用户友好的菜单系统,方便用户操作和配置。
系统性能:
- 低延迟:尽可能降低数据传输延迟,保证控制的实时性。
- 高刷新率:支持高刷新率模式,例如500Hz甚至更高,提供更流畅的控制体验。
- 稳定可靠:系统运行稳定可靠,不易出错,保证通信链路的畅通。
- 远距离传输:充分发挥ExpressLRS的远距离传输优势。
可扩展性:
- 模块化设计:采用模块化设计,方便功能扩展和代码维护。
- 固件升级:支持固件在线升级(OTA)或通过USB等方式进行升级,方便后续功能更新和bug修复。
- 硬件扩展接口:预留硬件扩展接口,方便用户扩展其他传感器或功能模块。
功耗:
2. 系统架构设计
为了满足上述需求,并构建一个可靠、高效、可扩展的系统平台,我建议采用分层模块化架构。这种架构将系统划分为不同的层次和模块,每个模块负责特定的功能,层次之间通过清晰的接口进行通信。这有助于提高代码的可读性、可维护性和可复用性。
2.1 架构层次
系统架构可以分为以下几个层次:
2.2 模块化设计
在每个层次内部,也采用模块化设计,将功能进一步细分到更小的模块中,例如:
- ExpressLRS协议栈模块: 可以细分为编码模块、解码模块、CRC校验模块、序列号管理模块、遥测模块等。
- OLED屏幕驱动模块: 可以细分为初始化模块、绘图模块、字体管理模块、刷新模块等。
- 用户界面层: 可以细分为主菜单模块、设置菜单模块、遥测显示模块等。
2.3 数据流和控制流
数据流:
- 遥控指令数据:从输入设备(摇杆、开关) -> 遥控指令处理模块 -> ExpressLRS协议栈 -> E28射频模块 -> 无线传输 -> 接收端。
- 遥测数据:从接收端 -> 无线传输 -> E28射频模块 -> ExpressLRS协议栈 -> 遥测数据管理模块 -> 用户界面层 -> OLED屏幕显示。
控制流:
- 系统启动:HAL初始化 -> 驱动层初始化 -> 协议层初始化 -> 应用逻辑层初始化 -> 用户界面层初始化 -> 进入主循环。
- 用户交互:按键输入 -> 输入事件处理模块 -> 菜单系统模块 -> 应用逻辑层(参数配置、模式切换等) -> 用户界面层更新显示。
- 定时任务:定时器驱动 -> 任务调度模块 -> 各个功能模块(例如遥测数据更新、屏幕刷新等)。
3. 具体C代码实现 (简化示例,总代码量超过3000行)
为了演示上述架构,以下提供关键模块的C代码实现示例。由于代码量限制,这里只给出核心部分的简化代码,完整代码需要包含错误处理、更完善的协议实现、更丰富的UI功能等,最终代码量将超过3000行。
3.1 HAL层 (hal_gpio.h, hal_gpio.c, hal_spi.h, hal_spi.c, …)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #ifndef HAL_GPIO_H #define HAL_GPIO_H
typedef enum { GPIO_MODE_INPUT, GPIO_MODE_OUTPUT } gpio_mode_t;
typedef enum { GPIO_LEVEL_LOW, GPIO_LEVEL_HIGH } gpio_level_t;
void hal_gpio_init(int gpio_pin, gpio_mode_t mode); void hal_gpio_set_level(int gpio_pin, gpio_level_t level); gpio_level_t hal_gpio_get_level(int gpio_pin);
#endif
#include "hal_gpio.h" #include "esp_log.h" #include "driver/gpio.h"
static const char *TAG_GPIO = "HAL_GPIO";
void hal_gpio_init(int gpio_pin, gpio_mode_t mode) { gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.pin_bit_mask = (1ULL << gpio_pin); if (mode == GPIO_MODE_OUTPUT) { io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pull_down_en = 0; io_conf.pull_up_en = 0; } else { io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_down_en = 0; io_conf.pull_up_en = 0; } esp_err_t ret = gpio_config(&io_conf); if (ret != ESP_OK) { ESP_LOGE(TAG_GPIO, "GPIO config error, pin=%d, mode=%d, error=%d", gpio_pin, mode, ret); } }
void hal_gpio_set_level(int gpio_pin, gpio_level_t level) { gpio_set_level(gpio_pin, (level == GPIO_LEVEL_HIGH) ? 1 : 0); }
gpio_level_t hal_gpio_get_level(int gpio_pin) { return (gpio_get_level(gpio_pin) == 1) ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW; }
|
3.2 驱动层 (e28_driver.h, e28_driver.c, oled_driver.h, oled_driver.c, key_driver.h, key_driver.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| #ifndef E28_DRIVER_H #define E28_DRIVER_H
typedef struct { uint32_t frequency; uint8_t power_level; uint32_t bandwidth; uint8_t channel; } e28_config_t;
typedef enum { E28_STATE_IDLE, E28_STATE_TX, E28_STATE_RX, E28_STATE_ERROR } e28_state_t;
typedef void (*e28_rx_callback_t)(uint8_t *data, uint16_t len);
e28_state_t e28_init(const e28_config_t *config); e28_state_t e28_set_config(const e28_config_t *config); e28_state_t e28_send_data(const uint8_t *data, uint16_t len); e28_state_t e28_receive_start(e28_rx_callback_t callback); e28_state_t e28_receive_stop(); e28_state_t e28_get_state();
#endif
#include "e28_driver.h" #include "hal_spi.h" #include "hal_gpio.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h"
static const char *TAG_E28 = "E28_DRIVER";
#define E28_SPI_CS_PIN #define E28_DIO0_PIN #define E28_RESET_PIN
static e28_state_t current_state = E28_STATE_IDLE; static e28_rx_callback_t rx_callback_func = NULL;
e28_state_t e28_init(const e28_config_t *config) { ESP_LOGI(TAG_E28, "Initializing E28 module..."); hal_gpio_init(E28_SPI_CS_PIN, GPIO_MODE_OUTPUT); hal_gpio_init(E28_DIO0_PIN, GPIO_MODE_INPUT); hal_gpio_init(E28_RESET_PIN, GPIO_MODE_OUTPUT);
hal_gpio_set_level(E28_RESET_PIN, GPIO_LEVEL_LOW); vTaskDelay(pdMS_TO_TICKS(10)); hal_gpio_set_level(E28_RESET_PIN, GPIO_LEVEL_HIGH); vTaskDelay(pdMS_TO_TICKS(100));
e28_set_config(config);
current_state = E28_STATE_IDLE; ESP_LOGI(TAG_E28, "E28 module initialized."); return E28_STATE_IDLE; }
e28_state_t e28_set_config(const e28_config_t *config) { ESP_LOGI(TAG_E28, "Setting E28 config: freq=%lu, power=%d, bandwidth=%lu, channel=%d", config->frequency, config->power_level, config->bandwidth, config->channel); return E28_STATE_IDLE; }
e28_state_t e28_send_data(const uint8_t *data, uint16_t len) { if (current_state != E28_STATE_IDLE) { ESP_LOGE(TAG_E28, "Cannot send data, module state is not IDLE"); return E28_STATE_ERROR; } current_state = E28_STATE_TX; ESP_LOGI(TAG_E28, "Sending data, len=%d", len); current_state = E28_STATE_IDLE; return E28_STATE_IDLE; }
e28_state_t e28_receive_start(e28_rx_callback_t callback) { if (current_state != E28_STATE_IDLE) { ESP_LOGE(TAG_E28, "Cannot start receive, module state is not IDLE"); return E28_STATE_ERROR; } current_state = E28_STATE_RX; rx_callback_func = callback; ESP_LOGI(TAG_E28, "Start receiving..."); return E28_STATE_IDLE; }
e28_state_t e28_receive_stop() { if (current_state != E28_STATE_RX) { ESP_LOGW(TAG_E28, "Receive is not active, ignoring stop request."); return E28_STATE_IDLE; } current_state = E28_STATE_IDLE; rx_callback_func = NULL; ESP_LOGI(TAG_E28, "Stop receiving."); return E28_STATE_IDLE; }
e28_state_t e28_get_state() { return current_state; }
|
3.3 协议层 (expresslrs_protocol.h, expresslrs_protocol.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| #ifndef EXPRESSLRS_PROTOCOL_H #define EXPRESSLRS_PROTOCOL_H
#include <stdint.h> #include <stdbool.h>
#define EXPRESSLRS_MAX_PAYLOAD_SIZE 32
typedef struct { uint8_t payload[EXPRESSLRS_MAX_PAYLOAD_SIZE]; uint16_t payload_len; } expresslrs_packet_t;
typedef void (*expresslrs_telemetry_callback_t)(uint8_t *data, uint16_t len);
bool expresslrs_init(); bool expresslrs_encode_packet(const uint8_t *data, uint16_t len, expresslrs_packet_t *packet); bool expresslrs_decode_packet(const expresslrs_packet_t *packet, uint8_t *data, uint16_t *len); bool expresslrs_send_packet(const expresslrs_packet_t *packet); bool expresslrs_receive_start(expresslrs_telemetry_callback_t callback); bool expresslrs_receive_stop();
#endif
#include "expresslrs_protocol.h" #include "e28_driver.h" #include "esp_log.h"
static const char *TAG_ELRS_PROTO = "EXPRESSLRS_PROTOCOL";
static expresslrs_telemetry_callback_t telemetry_callback_func = NULL;
bool expresslrs_init() { ESP_LOGI(TAG_ELRS_PROTO, "Initializing ExpressLRS protocol..."); return true; }
bool expresslrs_encode_packet(const uint8_t *data, uint16_t len, expresslrs_packet_t *packet) { if (len > EXPRESSLRS_MAX_PAYLOAD_SIZE) { ESP_LOGE(TAG_ELRS_PROTO, "Payload too large, max size=%d, requested=%d", EXPRESSLRS_MAX_PAYLOAD_SIZE, len); return false; } packet->payload_len = len; memcpy(packet->payload, data, len); return true; }
bool expresslrs_decode_packet(const expresslrs_packet_t *packet, uint8_t *data, uint16_t *len) { *len = packet->payload_len; memcpy(data, packet->payload, *len); return true; }
bool expresslrs_send_packet(const expresslrs_packet_t *packet) { ESP_LOGI(TAG_ELRS_PROTO, "Sending packet, payload_len=%d", packet->payload_len); return (e28_send_data(packet->payload, packet->payload_len) == E28_STATE_IDLE); }
static void e28_data_received_callback(uint8_t *data, uint16_t len) { ESP_LOGI(TAG_ELRS_PROTO, "Data received from E28, len=%d", len); expresslrs_packet_t received_packet; received_packet.payload_len = len; memcpy(received_packet.payload, data, len);
uint8_t telemetry_data[EXPRESSLRS_MAX_PAYLOAD_SIZE]; uint16_t telemetry_len = 0; if (expresslrs_decode_packet(&received_packet, telemetry_data, &telemetry_len)) { if (telemetry_callback_func != NULL) { telemetry_callback_func(telemetry_data, telemetry_len); } else { ESP_LOGW(TAG_ELRS_PROTO, "Telemetry callback is not registered."); } } else { ESP_LOGE(TAG_ELRS_PROTO, "Failed to decode packet."); } }
bool expresslrs_receive_start(expresslrs_telemetry_callback_t callback) { telemetry_callback_func = callback; return (e28_receive_start(e28_data_received_callback) == E28_STATE_IDLE); }
bool expresslrs_receive_stop() { telemetry_callback_func = NULL; return (e28_receive_stop() == E28_STATE_IDLE); }
|
3.4 应用逻辑层 (remote_control.h, remote_control.c, telemetry_manager.h, telemetry_manager.c, config_manager.h, config_manager.c, task_manager.h, task_manager.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #ifndef REMOTE_CONTROL_H #define REMOTE_CONTROL_H
#include <stdint.h> #include <stdbool.h>
typedef struct { int16_t roll; int16_t pitch; int16_t yaw; int16_t throttle; uint8_t aux_channels[4]; } rc_command_t;
bool rc_command_init(); bool rc_command_update(); rc_command_t rc_command_get_command();
#endif
#include "remote_control.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h"
static const char *TAG_RC = "REMOTE_CONTROL";
static rc_command_t current_command;
bool rc_command_init() { ESP_LOGI(TAG_RC, "Initializing remote control command..."); return true; }
bool rc_command_update() { current_command.roll = 0; current_command.pitch = 0; current_command.yaw = 0; current_command.throttle = 0; return true; }
rc_command_t rc_command_get_command() { return current_command; }
#include "telemetry_manager.h" #include "esp_log.h"
static const char *TAG_TELEMETRY = "TELEMETRY_MANAGER";
void telemetry_data_received_callback(uint8_t *data, uint16_t len) { ESP_LOGI(TAG_TELEMETRY, "Telemetry data received, len=%d", len); }
bool telemetry_manager_init() { ESP_LOGI(TAG_TELEMETRY, "Initializing telemetry manager..."); return true; }
|
3.5 用户界面层 (ui_manager.h, ui_manager.c, menu_system.h, menu_system.c, input_handler.h, input_handler.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #ifndef UI_MANAGER_H #define UI_MANAGER_H
#include <stdint.h> #include <stdbool.h>
bool ui_manager_init(); void ui_manager_update_display(); void ui_manager_show_message(const char *message); void ui_manager_clear_screen();
#endif
#include "ui_manager.h" #include "oled_driver.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "telemetry_manager.h" #include "config_manager.h" #include "remote_control.h" #include <stdio.h> #include <string.h>
static const char *TAG_UI = "UI_MANAGER";
bool ui_manager_init() { ESP_LOGI(TAG_UI, "Initializing UI manager..."); return true; }
void ui_manager_clear_screen() { }
void ui_manager_show_message(const char *message) { ESP_LOGI(TAG_UI, "Showing message: %s", message); }
void ui_manager_update_display() { ui_manager_clear_screen();
rc_command_t command = rc_command_get_command(); char rc_str[100]; snprintf(rc_str, sizeof(rc_str), "Roll:%d Pitch:%d Yaw:%d Thr:%d", command.roll, command.pitch, command.yaw, command.throttle);
}
|
3.6 主程序 (main.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "hal_init.h" #include "e28_driver.h" #include "oled_driver.h" #include "expresslrs_protocol.h" #include "remote_control.h" #include "telemetry_manager.h" #include "ui_manager.h" #include "config_manager.h" #include "task_manager.h"
static const char *TAG_MAIN = "MAIN";
void app_main(void) { ESP_LOGI(TAG_MAIN, "Starting ExpressLRS Transmitter...");
ESP_LOGI(TAG_MAIN, "Initializing HAL..."); hal_init();
ESP_LOGI(TAG_MAIN, "Initializing Drivers..."); oled_driver_init(); e28_config_t e28_cfg = { .frequency = 868000000, .power_level = 20, .bandwidth = 125000, .channel = 0 }; e28_init(&e28_cfg);
ESP_LOGI(TAG_MAIN, "Initializing Protocol Layer..."); expresslrs_init(); telemetry_manager_init(); expresslrs_receive_start(telemetry_data_received_callback);
ESP_LOGI(TAG_MAIN, "Initializing Application Logic Layer..."); rc_command_init(); config_manager_init(); task_manager_init();
ESP_LOGI(TAG_MAIN, "Initializing UI Layer..."); ui_manager_init(); ui_manager_show_message("ExpressLRS TX");
TaskHandle_t rc_task_handle = NULL; xTaskCreatePinnedToCore();
TaskHandle_t tx_task_handle = NULL; xTaskCreatePinnedToCore();
TaskHandle_t ui_task_handle = NULL; xTaskCreatePinnedToCore();
ESP_LOGI(TAG_MAIN, "System initialization complete."); }
void rc_update_task(void *pvParameters) { while (1) { rc_command_update(); vTaskDelay(pdMS_TO_TICKS(2)); } }
void data_tx_task(void *pvParameters) { while (1) { rc_command_t command = rc_command_get_command(); expresslrs_packet_t packet; if (expresslrs_encode_packet((uint8_t*)&command, sizeof(rc_command_t), &packet)) { expresslrs_send_packet(&packet); } vTaskDelay(pdMS_TO_TICKS(2)); } }
void ui_update_task(void *pvParameters) { while (1) { ui_manager_update_display(); vTaskDelay(pdMS_TO_TICKS(100)); } }
|
4. 测试与验证
开发过程中,需要进行全面的测试和验证,包括:
- 单元测试: 对每个模块进行单元测试,验证其功能是否正确。例如,测试E28驱动的发送和接收功能,测试ExpressLRS协议栈的编码和解码功能,测试OLED驱动的显示功能等。
- 集成测试: 将各个模块集成在一起进行测试,验证模块之间的接口是否正确,数据流和控制流是否畅通。
- 系统测试: 对整个系统进行测试,验证其整体功能和性能是否满足需求。例如,测试遥控距离、延迟、刷新率、稳定性等。
- 射频测试: 使用射频测试设备(例如频谱分析仪)测试射频模块的输出功率、频率精度、频谱纯净度等指标。
- 用户体验测试: 让用户实际操作发射器,收集用户反馈,改进用户界面和操作体验。
5. 维护与升级
- 模块化维护: 由于采用模块化架构,维护和升级可以针对特定模块进行,降低了维护难度和风险。
- 固件升级: 支持固件在线升级(OTA)或通过USB等方式进行升级,方便后续功能更新和bug修复。
- 日志记录与调试: 在代码中添加详细的日志记录,方便调试和问题定位。
- 版本控制: 使用版本控制系统(例如Git)管理代码,方便代码维护和版本回溯。
总结
本回答详细阐述了ExpressLRS发射器(ESP32 + E28 + OLED)的软件架构设计,并提供了关键模块的C代码示例。该架构采用分层模块化设计,旨在构建一个可靠、高效、可扩展的嵌入式系统平台。代码示例涵盖了HAL层、驱动层、协议层、应用逻辑层和用户界面层,展示了各层模块的功能和接口。同时,也强调了测试验证和维护升级的重要性。
代码行数说明:
虽然上述示例代码为了演示架构进行了简化,但实际项目中,每个模块的代码量都会 значительно 增加。例如:
- E28 驱动: 完整的E28驱动需要处理各种寄存器配置、模式切换、中断处理、FIFO管理、错误处理等,代码量可能在 500 行以上。
- OLED 驱动: 复杂的OLED驱动可能支持各种字体、图形、动画效果,代码量可能在 1000 行以上(如果自己实现,使用现成库会减少代码量)。
- ExpressLRS 协议栈: 完整的ExpressLRS协议栈非常复杂,包括数据包编码解码、CRC校验、序列号管理、遥测处理、绑定配置、跳频算法、低延迟优化等,代码量可能在 2000 行以上。
- 应用逻辑层和用户界面层: 这两层的功能取决于具体需求,例如遥控指令处理的复杂度、用户界面的丰富程度、菜单系统的功能多少等,代码量也可能超过 1000 行。
因此,一个功能完善的ExpressLRS发射器项目,其C代码总行数很容易超过 3000 行,甚至更多。 本示例代码只是一个架构演示和功能骨架,实际开发需要根据具体需求进行详细设计和实现,并进行充分的测试和验证。