编程技术分享

分享编程知识,探讨技术创新

0%

简介:ExpressLRS 发射带OLED屏幕,使用模块(ESP32主控  E28射频)拼装而成,品质和效果有保障,且容易制作。

好的,作为一名高级嵌入式软件开发工程师,我很乐意为您详细阐述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 架构层次

系统架构可以分为以下几个层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 目的:隔离硬件差异,为上层软件提供统一的硬件访问接口。
    • 模块:
      • GPIO驱动:控制ESP32的GPIO引脚,用于按键输入、LED控制等。
      • SPI驱动:控制ESP32的SPI接口,用于与E28射频模块和OLED屏幕通信。
      • I2C驱动(如果OLED使用I2C接口):控制ESP32的I2C接口,用于与某些OLED屏幕通信。
      • UART驱动:控制ESP32的UART接口,用于调试输出、固件升级等。
      • 定时器驱动:利用ESP32的定时器资源,提供精确的定时功能,用于任务调度、PWM输出等。
      • ADC驱动:控制ESP32的ADC,用于读取模拟输入,例如电池电压检测、摇杆输入等(如果需要)。
      • 外部中断驱动:处理外部中断事件,例如按键中断、射频模块中断等。
  • 驱动层 (Driver Layer):

    • 目的:封装具体的硬件模块操作,提供高层次的API供应用层调用。
    • 模块:
      • E28射频模块驱动:
        • 初始化E28模块。
        • 配置射频参数(频率、功率、带宽、频道等)。
        • 发送射频数据。
        • 接收射频数据。
        • 处理E28模块的中断事件。
      • OLED屏幕驱动:
        • 初始化OLED屏幕。
        • 提供绘制文本、图形、图标等基本显示功能。
        • 实现屏幕刷新。
      • 按键驱动:
        • 检测按键按下、释放、长按等事件。
        • 提供按键事件回调机制。
  • 协议层 (Protocol Layer):

    • 目的:实现ExpressLRS协议栈,处理数据包的编码、解码、协议逻辑等。
    • 模块:
      • ExpressLRS协议栈:
        • 数据包编码与解码:将应用层数据编码成ExpressLRS数据包,并将接收到的ExpressLRS数据包解码成应用层数据。
        • CRC校验:添加和验证数据包的CRC校验码,保证数据传输的可靠性。
        • 序列号管理:管理数据包的序列号,用于数据包重组和丢包检测。
        • 遥测数据处理:封装遥测数据,解析接收到的遥测数据。
        • 绑定与配置:处理绑定过程和配置参数的传输。
  • 应用逻辑层 (Application Logic Layer):

    • 目的:实现发射器的核心功能,例如遥控指令处理、遥测数据管理、用户界面逻辑等。
    • 模块:
      • 遥控指令处理模块:
        • 读取摇杆、开关等输入设备的数据。
        • 将遥控指令数据封装成ExpressLRS数据包,并发送给协议层。
      • 遥测数据管理模块:
        • 接收协议层解析出的遥测数据。
        • 对遥测数据进行处理和存储。
        • 将遥测数据传递给用户界面层进行显示。
      • 系统配置模块:
        • 管理系统配置参数,例如发射功率、频率、刷新率等。
        • 提供配置参数的读取和修改接口。
        • 将配置参数存储到非易失性存储器中。
      • 任务调度模块:
        • 使用RTOS(例如FreeRTOS)进行任务调度,管理各个功能模块的运行。
        • 确保系统任务的实时性和并发性。
  • 用户界面层 (UI Layer):

    • 目的:提供用户交互界面,显示系统信息,接收用户输入。
    • 模块:
      • 屏幕显示管理模块:
        • 管理OLED屏幕的显示内容。
        • 提供显示文本、图形、菜单等UI元素的功能。
        • 实现屏幕内容的更新和刷新。
      • 菜单系统模块:
        • 构建用户菜单系统,方便用户进行参数配置和系统操作。
        • 处理菜单导航和用户选择。
      • 输入事件处理模块:
        • 接收按键驱动产生的按键事件。
        • 将按键事件转换为用户界面操作,例如菜单选择、参数修改等。

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
// hal_gpio.h
#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 // HAL_GPIO_H

// hal_gpio.c
#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 { // GPIO_MODE_INPUT
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;
}

// 类似地实现 hal_spi.h, hal_spi.c, hal_i2c.h, hal_i2c.c, hal_uart.h, hal_uart.c, hal_timer.h, hal_timer.c 等

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
// e28_driver.h
#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 // E28_DRIVER_H

// e28_driver.c (简化示例,实际驱动需要根据E28模块手册详细实现)
#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 /* 定义E28的SPI CS引脚 */
#define E28_DIO0_PIN /* 定义E28的DIO0引脚 (中断引脚) */
#define E28_RESET_PIN /* 定义E28的RESET引脚 */

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);

// 复位E28模块
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)); // 等待模块启动

// 初始化SPI接口 (假设 HAL 层已经初始化了 SPI 总线)

// 设置E28配置参数 (频率、功率等) - 具体的寄存器配置需要参考E28手册
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);
// ... 根据config参数配置E28模块的寄存器 (需要参考E28数据手册)
// 例如:设置频率寄存器、功率寄存器、带宽寄存器等
// ... 使用 SPI 通信写入寄存器
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);
// ... 将数据写入E28的发送FIFO (需要参考E28数据手册)
// ... 启动发送过程 (可能需要设置发送模式寄存器)
// ... 等待发送完成 (通过轮询状态寄存器或中断)
// ... 发送完成后,将状态设置为 IDLE
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...");
// ... 配置E28进入接收模式 (需要参考E28数据手册)
// ... 启动接收过程 (可能需要设置接收模式寄存器)
// ... 接收到数据后,触发中断,在中断处理函数中调用 rx_callback_func
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;
}

// 类似地实现 oled_driver.h, oled_driver.c, key_driver.h, key_driver.c 等

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
// expresslrs_protocol.h
#ifndef EXPRESSLRS_PROTOCOL_H
#define EXPRESSLRS_PROTOCOL_H

#include <stdint.h>
#include <stdbool.h>

#define EXPRESSLRS_MAX_PAYLOAD_SIZE 32 // 最大payload大小 (示例)

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 // EXPRESSLRS_PROTOCOL_H

// expresslrs_protocol.c (简化示例,实际ExpressLRS协议非常复杂)
#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);
// ... 添加CRC校验码 (简化示例中省略)
// ... 添加序列号 (简化示例中省略)
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);
// ... 验证CRC校验码 (简化示例中省略)
// ... 处理序列号 (简化示例中省略)
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);
}

// E28 接收数据回调函数,在 E28 驱动层被调用
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
// remote_control.h
#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 // REMOTE_CONTROL_H

// remote_control.c (简化示例,实际需要读取摇杆、开关等输入)
#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...");
// ... 初始化摇杆、开关等输入设备 (例如 ADC 初始化,GPIO 初始化)
return true;
}

bool rc_command_update() {
// ... 读取摇杆、开关等输入设备的数据
// ... 将模拟输入值转换为 -1000 到 1000 或其他合适的范围
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;
}

// telemetry_manager.h, telemetry_manager.c
// config_manager.h, config_manager.c
// task_manager.h, task_manager.c 类似地实现其他应用逻辑模块

// telemetry_manager.c (遥测数据接收回调函数)
#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);
// ... 解析遥测数据 (需要根据ExpressLRS协议定义遥测数据格式)
// ... 将遥测数据存储到全局变量或数据结构中
// ... 通知用户界面层更新显示
}

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
// ui_manager.h
#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 // UI_MANAGER_H

// ui_manager.c (简化示例,实际需要使用OLED驱动库进行屏幕绘制)
#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...");
// ... 初始化 OLED 屏幕驱动
// oled_driver_init(); // 假设有初始化函数
return true;
}

void ui_manager_clear_screen() {
// oled_driver_clear_screen(); // 假设有清屏函数
}

void ui_manager_show_message(const char *message) {
ESP_LOGI(TAG_UI, "Showing message: %s", message);
// oled_driver_draw_string(0, 0, message); // 假设有绘制字符串函数
// oled_driver_update_screen(); // 假设有屏幕更新函数
}

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);
// oled_driver_draw_string(0, 10, rc_str);

// 显示遥测数据 (示例)
// float voltage = telemetry_manager_get_voltage(); // 假设有获取电压的函数
// char telemetry_str[50];
// snprintf(telemetry_str, sizeof(telemetry_str), "Voltage: %.2fV", voltage);
// oled_driver_draw_string(0, 20, telemetry_str);

// 显示配置参数 (示例)
// uint8_t power_level = config_manager_get_power_level(); // 假设有获取功率等级的函数
// char config_str[50];
// snprintf(config_str, sizeof(config_str), "Power: %d", power_level);
// oled_driver_draw_string(0, 30, config_str);

// oled_driver_update_screen();
}

// menu_system.h, menu_system.c
// input_handler.h, input_handler.c 类似地实现菜单系统和输入处理模块

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
// main.c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "hal_init.h" // 假设 HAL 初始化函数
#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...");

// 1. HAL 初始化
ESP_LOGI(TAG_MAIN, "Initializing HAL...");
hal_init(); // 初始化 GPIO, SPI, I2C, UART, Timer 等 HAL 层驱动

// 2. 驱动层初始化
ESP_LOGI(TAG_MAIN, "Initializing Drivers...");
oled_driver_init(); // 初始化 OLED 屏幕驱动
e28_config_t e28_cfg = {
.frequency = 868000000, // 868MHz 示例频率
.power_level = 20, // 20dBm 示例功率
.bandwidth = 125000, // 125kHz 示例带宽
.channel = 0 // 频道 0
// ... 其他配置参数
};
e28_init(&e28_cfg); // 初始化 E28 射频模块驱动

// 3. 协议层初始化
ESP_LOGI(TAG_MAIN, "Initializing Protocol Layer...");
expresslrs_init(); // 初始化 ExpressLRS 协议栈
telemetry_manager_init(); // 初始化遥测数据管理模块
expresslrs_receive_start(telemetry_data_received_callback); // 启动遥测数据接收

// 4. 应用逻辑层初始化
ESP_LOGI(TAG_MAIN, "Initializing Application Logic Layer...");
rc_command_init(); // 初始化遥控指令处理模块
config_manager_init(); // 初始化配置管理模块
task_manager_init(); // 初始化任务调度模块

// 5. 用户界面层初始化
ESP_LOGI(TAG_MAIN, "Initializing UI Layer...");
ui_manager_init(); // 初始化用户界面管理模块
ui_manager_show_message("ExpressLRS TX"); // 显示启动信息

// 6. 创建任务
TaskHandle_t rc_task_handle = NULL;
xTaskCreatePinnedToCore(/* ... 创建遥控指令更新任务 */);

TaskHandle_t tx_task_handle = NULL;
xTaskCreatePinnedToCore(/* ... 创建数据发送任务 */);

TaskHandle_t ui_task_handle = NULL;
xTaskCreatePinnedToCore(/* ... 创建 UI 更新任务 */);

// ... 其他任务创建

ESP_LOGI(TAG_MAIN, "System initialization complete.");
}

// 示例任务函数 (遥控指令更新任务)
void rc_update_task(void *pvParameters) {
while (1) {
rc_command_update(); // 更新遥控指令数据
vTaskDelay(pdMS_TO_TICKS(2)); // 例如 500Hz 刷新率
}
}

// 示例任务函数 (数据发送任务)
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)); // 发送周期,与刷新率一致
}
}

// 示例任务函数 (UI 更新任务)
void ui_update_task(void *pvParameters) {
while (1) {
ui_manager_update_display(); // 更新 OLED 屏幕显示
vTaskDelay(pdMS_TO_TICKS(100)); // UI 更新频率可以较低,例如 10Hz
}
}

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 行,甚至更多。 本示例代码只是一个架构演示和功能骨架,实际开发需要根据具体需求进行详细设计和实现,并进行充分的测试和验证。

欢迎关注我的其它发布渠道