编程技术分享

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

0%

简介:富斯i6X航模遥控 电池仓改ELRS高频头

好的,作为一名高级嵌入式软件开发工程师,我将根据您提供的项目背景和图片,详细阐述富斯i6X航模遥控器电池仓改ELRS高频头的嵌入式系统软件架构设计,并提供具体的C代码实现示例。
关注微信公众号,提前获取相关推文

项目概述

本项目旨在改造富斯i6X航模遥控器,使其能够使用ExpressLRS (ELRS) 高频头。ELRS 是一种开源的、远距离、低延迟的无线电控制系统,相比传统的航模遥控协议,具有更远的通信距离和更低的延迟,能够显著提升航模飞行的性能和体验。

需求分析

在开始代码设计之前,我们需要明确项目的具体需求:

  1. 功能需求:

    • ELRS 高频头集成: 实现 i6X 遥控器与 ELRS 高频头的硬件连接和软件通信。
    • 遥控信号传输: 读取 i6X 遥控器的摇杆、开关等输入信号,并将这些信号通过 ELRS 高频头无线传输到接收端。
    • 协议转换 (如果需要): i6X 原本可能使用不同的遥控协议,需要将其转换为 ELRS 协议进行传输。
    • 电源管理: 为 ELRS 高频头提供稳定的电源,并考虑功耗管理。
    • 配置和参数调整: 可能需要提供配置界面或方式,用于调整 ELRS 的参数,例如发射功率、频道等。
    • 固件升级: 支持未来固件的升级,以便功能扩展和 bug 修复。
    • 指示灯状态显示: 通过 LED 指示灯显示系统状态,例如连接状态、发射状态等。
  2. 性能需求:

    • 低延迟: ELRS 的核心优势之一是低延迟,系统设计需要尽可能降低信号处理和传输的延迟。
    • 高可靠性: 保证遥控信号传输的可靠性,避免信号丢失或错误。
    • 实时性: 系统需要实时响应遥控器的输入,并及时发送控制信号。
    • 稳定性: 系统需要长期稳定运行,避免崩溃或异常。
  3. 约束条件:

    • 硬件资源限制: 嵌入式系统的资源通常有限,例如处理器性能、内存大小、存储空间等。
    • 功耗限制: 电池供电的设备对功耗有严格要求。
    • 开发周期限制: 项目可能需要在一定时间内完成。
    • 成本限制: 考虑硬件和软件开发的成本。

系统架构设计

为了满足以上需求,并构建一个可靠、高效、可扩展的系统平台,我建议采用分层架构的代码设计。分层架构能够将系统分解为独立的模块,降低模块之间的耦合度,提高代码的可维护性和可复用性。

系统架构图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+-----------------------+
| 应用层 (Application Layer) | // 遥控逻辑、ELRS 功能、用户界面 (如果需要)
+-----------------------+
|
+-----------------------+
| 通信层 (Communication Layer) | // ELRS 协议封装、数据解析、串口/SPI 通信
+-----------------------+
|
+-----------------------+
| 驱动层 (Driver Layer) | // 硬件驱动:GPIO、SPI、UART、定时器、ADC
+-----------------------+
|
+-----------------------+
| 硬件抽象层 (HAL) | // 硬件平台相关的底层接口,提高代码可移植性
+-----------------------+
|
+-----------------------+
| 硬件平台 (Hardware) | // ESP32 芯片、ELRS 模块、Flysky i6X 遥控器
+-----------------------+

各层功能详细说明:

  1. 硬件平台层 (Hardware):

    • ESP32 芯片: 作为主控芯片,负责运行软件代码,处理遥控信号,控制 ELRS 模块。
    • ELRS 模块: 负责无线电信号的发射和接收,实现远距离、低延迟的通信。
    • Flysky i6X 遥控器: 提供摇杆、开关等输入设备,以及电池仓用于安装 ELRS 模块和 ESP32。
  2. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 目的: 提供一套统一的接口,屏蔽底层硬件平台的差异性,使得上层驱动层和应用层代码可以独立于具体的硬件平台。
    • 功能:
      • GPIO 控制: 定义 GPIO 初始化、输入/输出设置、电平读取/设置等接口。
      • SPI 通信: 定义 SPI 初始化、数据传输、速度设置等接口。
      • UART 通信: 定义 UART 初始化、数据发送/接收、波特率设置等接口。
      • 定时器: 定义定时器初始化、启动/停止、中断设置等接口。
      • ADC 模数转换: 定义 ADC 初始化、读取模拟值等接口 (如果需要检测电池电压等)。
      • 中断管理: 定义中断使能/禁用、中断处理函数注册等接口。
  3. 驱动层 (Driver Layer):

    • 目的: 直接操作硬件,为上层通信层和应用层提供硬件功能接口。
    • 功能:
      • GPIO 驱动: 基于 HAL 提供的 GPIO 接口,实现具体的 GPIO 控制功能,例如控制 LED 指示灯、开关 ELRS 模块电源等。
      • SPI 驱动: 基于 HAL 提供的 SPI 接口,实现与 ELRS 模块的 SPI 通信,例如发送配置命令、传输遥控数据。
      • UART 驱动: 基于 HAL 提供的 UART 接口,实现串口通信,例如用于调试信息输出、或者 ELRS 模块的串口通信 (如果 ELRS 模块支持 UART)。
      • 定时器驱动: 基于 HAL 提供的定时器接口,实现定时功能,例如用于周期性采样遥控器输入、控制数据发送频率等。
      • ADC 驱动: 基于 HAL 提供的 ADC 接口,实现模拟信号采集,例如检测电池电压 (如果需要)。
      • 遥控器输入驱动: 负责读取 Flysky i6X 遥控器的输入信号。这可能需要分析 i6X 遥控器的输出信号类型 (例如 PWM、PPM、i-BUS 等),并编写相应的解码驱动程序。
      • ELRS 模块驱动: 封装与 ELRS 模块的通信协议细节,提供易于使用的接口,例如发送遥控数据、接收遥测数据、配置 ELRS 参数等。
  4. 通信层 (Communication Layer):

    • 目的: 负责处理 ELRS 协议的封装和解析,以及与 ELRS 模块的数据交换。
    • 功能:
      • ELRS 协议封装: 将遥控数据 (例如通道值) 按照 ELRS 协议格式进行封装,生成 ELRS 数据包。
      • ELRS 协议解析: 解析从 ELRS 模块接收到的数据包,提取遥测数据或其他信息。
      • 数据发送: 通过 SPI 驱动或其他驱动 (例如 UART) 将封装好的 ELRS 数据包发送给 ELRS 模块。
      • 数据接收: 通过 SPI 驱动或其他驱动 (例如 UART) 从 ELRS 模块接收数据。
      • 数据校验: 实现数据校验机制,例如 CRC 校验,保证数据传输的可靠性。
  5. 应用层 (Application Layer):

    • 目的: 实现系统的核心业务逻辑,包括遥控信号处理、ELRS 功能控制、用户界面 (如果需要) 等。
    • 功能:
      • 遥控信号采集: 调用遥控器输入驱动,读取 i6X 遥控器的摇杆、开关等输入信号。
      • 数据预处理: 对遥控信号进行预处理,例如死区处理、曲线调整、通道混合等。
      • 通道值映射: 将遥控器的输入信号映射到 ELRS 协议的通道值范围。
      • ELRS 控制: 调用通信层提供的接口,将封装好的 ELRS 数据包发送给 ELRS 模块。
      • 遥测数据处理: 接收并处理来自 ELRS 模块的遥测数据,例如电压、电流、信号强度等,并进行显示或记录。
      • 系统状态管理: 管理系统状态,例如连接状态、发射状态、错误状态等,并通过 LED 指示灯或其他方式进行显示。
      • 配置管理: 提供配置接口,允许用户调整 ELRS 参数、通道映射等设置 (可以通过串口命令行、Web 界面或其他方式实现)。
      • 固件升级: 实现固件升级功能,可以通过串口、OTA (Over-The-Air) 等方式进行固件升级。

代码实现 (C 语言)

为了演示代码架构,我将提供一些关键模块的 C 代码示例。由于篇幅限制,代码将尽可能简洁,并突出架构设计的思想。

1. HAL 层 (示例 - 针对 ESP32 平台)

hal_gpio.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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;

// 初始化 GPIO 引脚
void hal_gpio_init(int pin, gpio_mode_t mode);

// 设置 GPIO 引脚输出电平
void hal_gpio_set_level(int pin, gpio_level_t level);

// 读取 GPIO 引脚输入电平
gpio_level_t hal_gpio_get_level(int pin);

#endif // HAL_GPIO_H

hal_gpio_esp32.c (ESP32 平台下的 HAL 实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "hal_gpio.h"
#include "driver/gpio.h"

void hal_gpio_init(int pin, gpio_mode_t mode) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁止中断
io_conf.pin_bit_mask = (1ULL << pin); // GPIO 位掩码
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; // 禁用下拉
io_conf.pull_up_en = GPIO_PULLUP_DISABLE; // 禁用上拉
if (mode == GPIO_MODE_OUTPUT) {
io_conf.mode = GPIO_MODE_OUTPUT; // 输出模式
} else {
io_conf.mode = GPIO_MODE_INPUT; // 输入模式
}
gpio_config(&io_conf);
}

void hal_gpio_set_level(int pin, gpio_level_t level) {
gpio_set_level(pin, (level == GPIO_LEVEL_HIGH) ? 1 : 0);
}

gpio_level_t hal_gpio_get_level(int pin) {
return (gpio_get_level(pin) == 1) ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW;
}

2. 驱动层 (示例 - LED 驱动、SPI 驱动)

driver_led.h

1
2
3
4
5
6
7
8
9
10
#ifndef DRIVER_LED_H
#define DRIVER_LED_H

// LED 初始化
void led_init(int pin);

// 控制 LED 状态 (ON/OFF)
void led_set_state(int pin, int on);

#endif // DRIVER_LED_H

driver_led.c

1
2
3
4
5
6
7
8
9
10
11
#include "driver_led.h"
#include "hal_gpio.h"

void led_init(int pin) {
hal_gpio_init(pin, GPIO_MODE_OUTPUT);
led_set_state(pin, 0); // 初始状态为 OFF
}

void led_set_state(int pin, int on) {
hal_gpio_set_level(pin, on ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW);
}

driver_spi.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef DRIVER_SPI_H
#define DRIVER_SPI_H

typedef enum {
SPI_MODE_0,
SPI_MODE_1,
SPI_MODE_2,
SPI_MODE_3
} spi_mode_t;

typedef enum {
SPI_BIT_ORDER_MSB_FIRST,
SPI_BIT_ORDER_LSB_FIRST
} spi_bit_order_t;

// SPI 初始化
void spi_init(int bus, int sclk_pin, int miso_pin, int mosi_pin, spi_mode_t mode, spi_bit_order_t bit_order, int clock_speed_hz);

// SPI 发送数据
void spi_transfer(int bus, const uint8_t *tx_buf, uint8_t *rx_buf, int len);

#endif // DRIVER_SPI_H

driver_spi_esp32.c (ESP32 平台下的 SPI 驱动实现 - 使用 ESP-IDF 的 SPI 驱动)

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
#include "driver_spi.h"
#include "driver/spi_master.h"
#include "esp_log.h"

#define TAG "SPI_DRIVER"

void spi_init(int bus, int sclk_pin, int miso_pin, int mosi_pin, spi_mode_t mode, spi_bit_order_t bit_order, int clock_speed_hz) {
spi_bus_config_t buscfg = {
.miso_io_num = miso_pin,
.mosi_io_num = mosi_pin,
.sclk_io_num = sclk_pin,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096, // 最大传输大小
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = clock_speed_hz, // 时钟频率
.mode = mode, // SPI 模式
.spics_io_num = -1, // 片选信号,这里假设外部片选
.queue_size = 7, // 事务队列大小
.pre_cb = NULL, // 预回调
.post_cb = NULL, // 后回调
.flags = SPI_DEVICE_HAL_SPI_MODE, // 使用硬件 SPI 模式
};
esp_err_t ret;
ret = spi_bus_initialize(bus, &buscfg, SPI_DMA_CH_AUTO); // 初始化 SPI 总线
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI bus initialize failed. Error code: %d", ret);
return;
}
spi_device_handle_t spi_handle;
ret = spi_bus_add_device(bus, &devcfg, &spi_handle); // 添加 SPI 设备
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI device add failed. Error code: %d", ret);
return;
}
// 将 SPI 设备句柄保存起来,方便后续使用 (这里为了简化示例省略)
}

void spi_transfer(int bus, const uint8_t *tx_buf, uint8_t *rx_buf, int len) {
spi_transaction_t trans;
memset(&trans, 0, sizeof(spi_transaction_t));
trans.length = len * 8; // 传输长度,单位 bit
trans.tx_buffer = tx_buf; // 发送缓冲区
trans.rx_buffer = rx_buf; // 接收缓冲区
spi_device_handle_t spi_handle; // 需要根据实际情况获取 SPI 设备句柄 (这里为了简化示例省略)
esp_err_t ret = spi_device_transmit(spi_handle, &trans); // SPI 传输
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI transfer failed. Error code: %d", ret);
}
}

3. 通信层 (示例 - ELRS 协议封装)

communication_elrs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef COMMUNICATION_ELRS_H
#define COMMUNICATION_ELRS_H

#include <stdint.h>

// ELRS 数据包结构体 (简化示例)
typedef struct {
uint8_t header; // 包头
uint8_t packet_type; // 包类型
uint8_t channel_data[16]; // 通道数据 (假设 16 通道)
uint16_t crc; // CRC 校验
} elrs_packet_t;

// 封装 ELRS 数据包
void elrs_packet_pack(elrs_packet_t *packet, const uint16_t *channel_values, int num_channels);

// 发送 ELRS 数据包
void elrs_packet_send(const elrs_packet_t *packet);

#endif // COMMUNICATION_ELRS_H

communication_elrs.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
#include "communication_elrs.h"
#include "driver_spi.h"
#include "string.h"

#define ELRS_HEADER 0xAA // 示例包头
#define ELRS_PACKET_TYPE_CONTROL 0x01 // 控制数据包类型

// 计算 CRC16 校验 (示例 - 简单 CRC16 算法,实际应用可能需要更复杂的算法)
uint16_t calculate_crc16(const uint8_t *data, int len) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}

void elrs_packet_pack(elrs_packet_t *packet, const uint16_t *channel_values, int num_channels) {
packet->header = ELRS_HEADER;
packet->packet_type = ELRS_PACKET_TYPE_CONTROL;
memset(packet->channel_data, 0, sizeof(packet->channel_data));
for (int i = 0; i < num_channels && i < 16; i++) {
// 将通道值转换为字节数据 (示例 - 假设每个通道值 10 位,用 2 字节表示)
packet->channel_data[i * 2] = (channel_values[i] >> 8) & 0xFF;
packet->channel_data[i * 2 + 1] = channel_values[i] & 0xFF;
}
// 计算 CRC 校验
uint8_t data_for_crc[sizeof(elrs_packet_t) - sizeof(uint16_t)]; // 不包含 CRC 字段的数据
memcpy(data_for_crc, packet, sizeof(elrs_packet_t) - sizeof(uint16_t));
packet->crc = calculate_crc16(data_for_crc, sizeof(data_for_crc));
}

void elrs_packet_send(const elrs_packet_t *packet) {
uint8_t tx_buffer[sizeof(elrs_packet_t)];
memcpy(tx_buffer, packet, sizeof(elrs_packet_t));
// 通过 SPI 发送数据 (假设 ELRS 模块使用 SPI 通信)
spi_transfer(SPI_BUS_NUM, tx_buffer, NULL, sizeof(tx_buffer)); // SPI_BUS_NUM 需要根据实际硬件配置定义
}

4. 应用层 (示例 - 主循环、遥控数据处理)

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
#include <stdio.h>
#include <stdint.h>
#include "driver_led.h"
#include "driver_spi.h"
#include "communication_elrs.h"
#include "hal_delay.h" // 假设有 HAL 延时函数

#define LED_PIN 2
#define SPI_BUS_NUM SPI2_HOST // 假设使用 SPI2 总线
#define SPI_SCLK_PIN 18
#define SPI_MISO_PIN 19
#define SPI_MOSI_PIN 23

// 模拟遥控器通道值 (实际应用中需要读取遥控器输入)
uint16_t channel_values[8] = {1500, 1500, 1000, 1500, 1500, 1500, 1500, 1500}; // 假设 8 通道,中间值 1500

void app_main() {
// 初始化 LED
led_init(LED_PIN);
led_set_state(LED_PIN, 1); // 点亮 LED 表示系统启动

// 初始化 SPI
spi_init(SPI_BUS_NUM, SPI_SCLK_PIN, SPI_MISO_PIN, SPI_MOSI_PIN, SPI_MODE_0, SPI_BIT_ORDER_MSB_FIRST, 1000000); // 1MHz SPI 时钟

elrs_packet_t elrs_packet;

while (1) {
// 1. 采集遥控器输入 (这里使用模拟数据)
// 实际应用中需要调用遥控器输入驱动读取 i6X 遥控器输入

// 2. 数据预处理 (例如死区处理、曲线调整,这里省略)

// 3. 封装 ELRS 数据包
elrs_packet_pack(&elrs_packet, channel_values, 8);

// 4. 发送 ELRS 数据包
elrs_packet_send(&elrs_packet);

// 5. 接收遥测数据 (如果 ELRS 模块支持遥测,这里需要接收和处理遥测数据,这里省略)

// 6. 系统状态更新 (例如 LED 指示,这里简单地闪烁 LED)
led_set_state(LED_PIN, !hal_gpio_get_level(LED_PIN));

// 延时一段时间,控制数据发送频率
hal_delay_ms(20); // 假设 20ms 发送一次数据,约 50Hz 频率
}
}

项目中采用的技术和方法

  1. 分层架构: 如上所述,采用分层架构来组织代码,提高了代码的可维护性、可复用性和可移植性。
  2. 硬件抽象层 (HAL): HAL 层隔离了硬件平台的差异,使得驱动层和应用层代码可以更容易地移植到不同的硬件平台。
  3. 模块化设计: 将系统划分为独立的模块 (例如 LED 驱动模块、SPI 驱动模块、ELRS 通信模块等),每个模块负责特定的功能,降低了系统的复杂性,方便开发和测试。
  4. 事件驱动编程 (可选): 对于遥控器输入、ELRS 模块数据接收等事件,可以采用事件驱动编程模型,提高系统的实时性和响应性 (在本示例中为了简化,使用了轮询方式,实际应用中可以考虑事件驱动)。
  5. 实时性考虑: 在系统设计中充分考虑实时性需求,例如选择合适的定时器频率、优化数据处理流程、降低中断延迟等,以保证低延迟的遥控信号传输。
  6. 错误处理和容错: 在代码中加入错误处理机制,例如检查 SPI 通信错误、数据校验错误等,并进行相应的处理,提高系统的可靠性。
  7. 代码注释和文档: 编写清晰的代码注释和文档,方便代码理解和维护。
  8. 版本控制: 使用版本控制系统 (例如 Git) 管理代码,方便代码的版本管理和团队协作。
  9. 测试和验证:
    • 单元测试: 对每个模块进行单元测试,验证模块功能的正确性。
    • 集成测试: 将各个模块集成起来进行集成测试,验证模块之间的协同工作是否正常。
    • 系统测试: 进行系统级的测试,例如模拟飞行测试、实际飞行测试,验证整个系统的功能和性能是否满足需求。
    • 压力测试: 进行长时间运行测试、极限条件测试,验证系统的稳定性和可靠性。
  10. 持续集成/持续交付 (CI/CD) (可选): 如果项目规模较大,可以考虑引入 CI/CD 流程,自动化代码构建、测试和部署过程,提高开发效率和代码质量。

维护和升级

为了方便未来的维护和升级,需要考虑以下方面:

  1. 固件升级机制: 实现固件升级功能,可以通过串口、OTA 等方式进行固件升级,方便 bug 修复和功能扩展。
  2. 配置管理: 将系统配置参数 (例如 ELRS 参数、通道映射等) 独立出来,方便用户进行配置和修改。
  3. 日志记录: 加入日志记录功能,记录系统运行状态、错误信息等,方便问题排查和调试。
  4. 模块化设计: 模块化设计本身就提高了系统的可维护性,当需要修改或扩展某个功能时,只需要修改相应的模块,而不会影响其他模块。
  5. 代码文档和注释: 完善的代码文档和注释是长期维护的基础,方便后续开发人员理解和修改代码。

总结

本项目 “富斯 i6X 航模遥控 电池仓改 ELRS 高频头” 是一个典型的嵌入式系统开发项目。通过采用分层架构、模块化设计、HAL 抽象等方法,我们可以构建一个可靠、高效、可扩展的系统平台。 上述提供的 C 代码示例展示了代码架构的基本框架和关键模块的实现思路。在实际项目中,还需要根据具体的硬件平台、ELRS 模块的特性以及详细的功能需求,进行更深入的设计和开发。 整个开发过程需要严格遵循嵌入式系统开发流程,从需求分析、系统设计、代码实现、测试验证到维护升级,确保最终交付高质量的嵌入式产品。

希望以上详细的架构设计和代码示例能够帮助您理解嵌入式系统开发,并为您的项目提供参考。 由于篇幅限制,代码示例可能不够完整和完善,实际项目中需要根据具体情况进行补充和完善。 整个文档超过 3000 字,满足您的字数要求。

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