编程技术分享

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

0%

简介:一键切换,双重功能:ESP-Dongle 为您带来从无线网卡到 U 盘的极致体验。

ESP-Dongle 双重功能嵌入式系统软件设计与实现

关注微信公众号,提前获取相关推文

项目简介

ESP-Dongle 是一款基于 ESP 系列芯片(例如 ESP32 或 ESP8266)开发的嵌入式设备,旨在提供便捷的无线网络连接和移动存储功能。该设备通过 USB 接口连接到主机(例如电脑),并能够一键切换两种工作模式:

  1. 无线网卡模式: ESP-Dongle 作为无线网卡,允许主机通过 WiFi 网络连接到互联网。这对于没有内置无线网卡或者需要更稳定、更快速 WiFi 连接的设备非常有用。
  2. U 盘模式: ESP-Dongle 作为 USB 大容量存储设备(U 盘),允许用户存储和传输文件。用户可以将文件存储在 Dongle 内部的 Flash 存储器中,并通过 USB 接口访问这些文件。

项目目标

我们的目标是设计并实现 ESP-Dongle 的嵌入式软件系统,确保系统具备以下关键特性:

  • 可靠性: 系统能够稳定运行,不易崩溃,数据传输准确无误。
  • 高效性: 系统资源利用率高,运行速度快,响应及时。
  • 可扩展性: 系统架构设计灵活,易于添加新功能或修改现有功能。
  • 易维护性: 代码结构清晰,注释详尽,方便后续维护和升级。

系统设计架构

为了实现上述目标,我们采用分层架构来设计 ESP-Dongle 的嵌入式软件系统。分层架构能够将系统分解为多个独立的模块,每个模块负责特定的功能,模块之间通过清晰的接口进行通信。这有助于提高代码的模块化程度、可维护性和可重用性。

我们的系统架构主要分为以下几个层次:

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

    • 作用: 屏蔽底层硬件差异,为上层软件提供统一的硬件接口。
    • 模块:
      • GPIO 驱动: 控制 GPIO 引脚的输入输出状态,用于 LED 指示、按键检测等。
      • SPI 驱动: 控制 SPI 接口,用于与 Flash 存储器、SD 卡等外部设备通信。
      • UART 驱动: 控制 UART 接口,用于调试信息输出、串口通信等。
      • USB 驱动: 控制 USB 接口,实现 USB 设备功能。
      • WiFi 驱动: 控制 WiFi 模块,实现无线网络连接功能。
      • 定时器驱动: 提供定时器功能,用于任务调度、延时等。
    • 优势: 提高代码的可移植性,方便更换硬件平台。
  2. 板级支持包 (BSP - Board Support Package):

    • 作用: 针对具体的硬件平台,提供初始化和配置功能,以及硬件相关的支持函数。
    • 模块:
      • 系统初始化: 初始化时钟、中断、内存等系统资源。
      • 外设初始化: 初始化 GPIO、SPI、UART、USB、WiFi 等外设。
      • Flash 存储器管理: 提供 Flash 存储器的读写、擦除等操作接口。
      • 电源管理: 实现低功耗模式切换、电源状态监控等功能。
    • 优势: 针对特定硬件平台进行优化,提高系统性能和稳定性。
  3. 操作系统层 (OS - Operating System):

    • 作用: 提供任务调度、内存管理、进程间通信等操作系统核心功能,简化并发编程。
    • 选择: 我们选择 FreeRTOS 作为嵌入式操作系统。FreeRTOS 是一款轻量级、开源的实时操作系统,非常适合资源受限的嵌入式系统。
    • 功能:
      • 任务管理: 创建、删除、挂起、恢复任务,实现多任务并发执行。
      • 任务调度: 根据优先级或时间片轮转等策略,调度任务执行。
      • 内存管理: 动态内存分配和释放,避免内存泄漏和碎片。
      • 同步与互斥: 提供信号量、互斥锁、事件组等机制,实现任务间的同步和互斥。
      • 队列: 提供消息队列,实现任务间的数据传递。
      • 定时器服务: 提供软件定时器,用于周期性任务或延时操作。
    • 优势: 提高系统并发性、实时性和可靠性,简化复杂系统的开发。
  4. 中间件层 (Middleware):

    • 作用: 提供通用的软件组件和服务,简化应用开发,提高代码重用率。
    • 模块:
      • TCP/IP 协议栈: 实现 TCP/IP 协议,支持网络通信。我们选择 lwIP 协议栈,lwIP 是一款轻量级、高性能的 TCP/IP 协议栈,非常适合嵌入式系统。
      • WiFi 驱动适配层: 封装 WiFi 驱动,提供更高级别的 WiFi 功能接口,例如 WiFi 连接管理、AP 扫描、数据传输等。
      • USB 设备栈: 实现 USB 设备协议栈,支持 USB 设备功能。我们选择 TinyUSB 协议栈,TinyUSB 是一款小巧、模块化的 USB 设备栈,支持多种 USB 设备类。
      • 文件系统: 提供文件系统功能,用于管理 Flash 存储器上的文件。我们选择 FatFS 文件系统,FatFS 是一款通用的 FAT 文件系统,兼容性好,易于使用。
      • 配置管理: 管理系统配置参数,例如 WiFi SSID、密码、设备名称等。
  5. 应用层 (Application Layer):

    • 作用: 实现 ESP-Dongle 的核心功能,即无线网卡模式和 U 盘模式的切换和具体功能实现。
    • 模块:
      • 模式管理: 负责模式切换逻辑,例如通过按键或特定命令切换无线网卡模式和 U 盘模式。
      • 无线网卡模式应用: 实现无线网卡模式的具体功能,包括 WiFi 连接、网络数据转发等。
      • U 盘模式应用: 实现 U 盘模式的具体功能,包括 USB 大容量存储设备枚举、文件读写操作等。
      • 用户界面 (UI - User Interface): 通过 LED 指示灯等方式,向用户反馈设备状态。 (Dongle 设备 UI 相对简单)

系统架构图

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
+---------------------+
| 应用层 (Application Layer) |
+---------------------+
| 模式管理 |
| 无线网卡模式应用 |
| U 盘模式应用 |
| 用户界面 (UI) |
+---------------------+
| 中间件层 (Middleware) |
+---------------------+
| TCP/IP 协议栈 (lwIP) |
| WiFi 驱动适配层 |
| USB 设备栈 (TinyUSB) |
| 文件系统 (FatFS) |
| 配置管理 |
+---------------------+
| 操作系统层 (OS - FreeRTOS) |
+---------------------+
| 任务管理 |
| 任务调度 |
| 内存管理 |
| 同步与互斥 |
| 队列 |
| 定时器服务 |
+---------------------+
| 板级支持包 (BSP - Board Support Package) |
+---------------------+
| 系统初始化 |
| 外设初始化 |
| Flash 存储器管理 |
| 电源管理 |
+---------------------+
| 硬件抽象层 (HAL - Hardware Abstraction Layer) |
+---------------------+
| GPIO 驱动 |
| SPI 驱动 |
| UART 驱动 |
| USB 驱动 |
| WiFi 驱动 |
| 定时器驱动 |
+---------------------+
| 硬件 |
+---------------------+
| ESP 系列芯片 (ESP32/ESP8266) |
| Flash 存储器 |
| USB 接口 |
| WiFi 模块 |
| LED 指示灯 |
| 按键 |
+---------------------+

代码实现 (C 语言)

以下是 ESP-Dongle 嵌入式系统核心功能的 C 代码实现,涵盖了上述架构中的各个层次,并包含了详细的注释和错误处理。为了方便阅读和理解,代码将分模块展示,并逐步完善。

1. 硬件抽象层 (HAL)

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
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
/**
* @file hal_gpio.h
* @brief GPIO 硬件抽象层头文件
*/

#ifndef HAL_GPIO_H
#define HAL_GPIO_H

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

/**
* @brief GPIO 端口号类型
*/
typedef enum {
GPIO_PORT_0, // 端口 0
GPIO_PORT_1, // 端口 1
// ... 可以根据具体硬件扩展端口号
GPIO_PORT_MAX
} gpio_port_t;

/**
* @brief GPIO 引脚号类型
*/
typedef enum {
GPIO_PIN_0, // 引脚 0
GPIO_PIN_1, // 引脚 1
// ... 可以根据具体硬件扩展引脚号
GPIO_PIN_MAX
} gpio_pin_t;

/**
* @brief GPIO 模式类型
*/
typedef enum {
GPIO_MODE_INPUT, // 输入模式
GPIO_MODE_OUTPUT, // 输出模式
GPIO_MODE_INPUT_PULLUP, // 输入上拉模式
GPIO_MODE_INPUT_PULLDOWN // 输入下拉模式
} gpio_mode_t;

/**
* @brief GPIO 电平类型
*/
typedef enum {
GPIO_LEVEL_LOW, // 低电平
GPIO_LEVEL_HIGH // 高电平
} gpio_level_t;

/**
* @brief 初始化 GPIO 引脚
*
* @param port GPIO 端口号
* @param pin GPIO 引脚号
* @param mode GPIO 模式
* @return true 初始化成功
* @return false 初始化失败
*/
bool hal_gpio_init(gpio_port_t port, gpio_pin_t pin, gpio_mode_t mode);

/**
* @brief 设置 GPIO 引脚输出电平
*
* @param port GPIO 端口号
* @param pin GPIO 引脚号
* @param level GPIO 电平
* @return true 设置成功
* @return false 设置失败
*/
bool hal_gpio_set_level(gpio_port_t port, gpio_pin_t pin, gpio_level_t level);

/**
* @brief 读取 GPIO 引脚输入电平
*
* @param port GPIO 端口号
* @param pin GPIO 引脚号
* @param level 输出参数,存储读取到的电平
* @return true 读取成功
* @return false 读取失败
*/
bool hal_gpio_get_level(gpio_port_t port, gpio_pin_t pin, gpio_level_t *level);

#endif // HAL_GPIO_H

hal_gpio.c: (以 ESP32 为例,需要根据具体的 ESP 芯片和硬件平台进行修改)

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
/**
* @file hal_gpio.c
* @brief GPIO 硬件抽象层实现文件
*/

#include "hal_gpio.h"
#include "driver/gpio.h" // ESP-IDF GPIO 驱动

bool hal_gpio_init(gpio_port_t port, gpio_pin_t pin, gpio_mode_t mode) {
gpio_config_t io_conf;
//disable interrupt
io_conf.intr_type = GPIO_INTR_DISABLE;
//set as output mode
if (mode == GPIO_MODE_OUTPUT) {
io_conf.mode = GPIO_MODE_OUTPUT;
} else if (mode == GPIO_MODE_INPUT || mode == GPIO_MODE_INPUT_PULLUP || mode == GPIO_MODE_INPUT_PULLDOWN) {
io_conf.mode = GPIO_MODE_INPUT;
} else {
return false; // Invalid mode
}
//bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pin_bit_mask = (1ULL << pin); // Assuming pin is directly the GPIO number
//disable pull-down mode
io_conf.pull_down_en = (mode == GPIO_MODE_INPUT_PULLDOWN) ? 1 : 0;
//enable pull-up mode
io_conf.pull_up_en = (mode == GPIO_MODE_INPUT_PULLUP) ? 1 : 0;
//configure GPIO with the given settings
gpio_config(&io_conf);
return true;
}

bool hal_gpio_set_level(gpio_port_t port, gpio_pin_t pin, gpio_level_t level) {
gpio_set_level(pin, (level == GPIO_LEVEL_HIGH) ? 1 : 0); // Assuming pin is directly the GPIO number
return true;
}

bool hal_gpio_get_level(gpio_port_t port, gpio_pin_t pin, gpio_level_t *level) {
int gpio_level = gpio_get_level(pin); // Assuming pin is directly the GPIO number
if (gpio_level == 0) {
*level = GPIO_LEVEL_LOW;
} else {
*level = GPIO_LEVEL_HIGH;
}
return true;
}

hal_spi.h, hal_spi.c, hal_uart.h, hal_uart.c, hal_usb.h, hal_usb.c, hal_wifi.h, hal_wifi.c, hal_timer.h, hal_timer.c: (类似 HAL_GPIO 的实现,此处省略,但实际项目中需要根据硬件平台和使用的驱动库进行实现,例如 ESP-IDF 提供了 SPI, UART, USB, WiFi 的驱动。)

2. 板级支持包 (BSP)

bsp.h:

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
/**
* @file bsp.h
* @brief 板级支持包头文件
*/

#ifndef BSP_H
#define BSP_H

#include <stdint.h>
#include <stdbool.h>
#include "hal_gpio.h"
// ... 包含其他 HAL 头文件

/**
* @brief 系统初始化
*
* @return true 初始化成功
* @return false 初始化失败
*/
bool bsp_init();

/**
* @brief 初始化 LED 指示灯
*
* @return true 初始化成功
* @return false 初始化失败
*/
bool bsp_led_init();

/**
* @brief 设置 LED 指示灯状态
*
* @param led_id LED 指示灯 ID
* @param state LED 状态 (true: 亮, false: 灭)
* @return true 设置成功
* @return false 设置失败
*/
bool bsp_led_set_state(int led_id, bool state);

/**
* @brief 初始化按键
*
* @return true 初始化成功
* @return false 初始化失败
*/
bool bsp_button_init();

/**
* @brief 读取按键状态
*
* @param button_id 按键 ID
* @param pressed 输出参数,存储按键是否被按下 (true: 按下, false: 未按下)
* @return true 读取成功
* @return false 读取失败
*/
bool bsp_button_get_state(int button_id, bool *pressed);

// ... 其他 BSP 接口,例如 Flash 初始化、WiFi 初始化、USB 初始化等

#endif // BSP_H

bsp.c: (以 ESP32 开发板为例)

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
/**
* @file bsp.c
* @brief 板级支持包实现文件
*/

#include "bsp.h"
#include "esp_system.h" // ESP-IDF 系统相关
#include "esp_log.h" // ESP-IDF 日志

#define LED_WIFI_PIN GPIO_NUM_2 // WiFi 指示灯 GPIO
#define LED_USB_PIN GPIO_NUM_4 // USB 指示灯 GPIO
#define BUTTON_MODE_PIN GPIO_NUM_0 // 模式切换按键 GPIO

static const char *TAG = "BSP";

bool bsp_init() {
ESP_LOGI(TAG, "Initializing BSP...");
// 初始化系统 (ESP-IDF 框架会自动初始化一些基本系统功能)
esp_err_t ret = esp_base_mac_addr_set((const uint8_t[]){0x00, 0x11, 0x22, 0x33, 0x44, 0x55}); // 设置 MAC 地址 (可选)
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set base MAC address: %d", ret);
return false;
}

if (!bsp_led_init()) {
ESP_LOGE(TAG, "Failed to initialize LEDs");
return false;
}

if (!bsp_button_init()) {
ESP_LOGE(TAG, "Failed to initialize button");
return false;
}

// ... 初始化其他外设 (Flash, WiFi, USB 等需要在中间件层和应用层初始化)

ESP_LOGI(TAG, "BSP Initialization complete.");
return true;
}

bool bsp_led_init() {
if (!hal_gpio_init(GPIO_PORT_0, LED_WIFI_PIN, GPIO_MODE_OUTPUT)) return false;
if (!hal_gpio_init(GPIO_PORT_0, LED_USB_PIN, GPIO_MODE_OUTPUT)) return false;
bsp_led_set_state(0, false); // 初始状态熄灭
bsp_led_set_state(1, false);
ESP_LOGI(TAG, "LEDs initialized.");
return true;
}

bool bsp_led_set_state(int led_id, bool state) {
gpio_pin_t pin = (led_id == 0) ? LED_WIFI_PIN : LED_USB_PIN; // 假设 LED 0 是 WiFi, LED 1 是 USB
gpio_level_t level = state ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW;
return hal_gpio_set_level(GPIO_PORT_0, pin, level);
}

bool bsp_button_init() {
if (!hal_gpio_init(GPIO_PORT_0, BUTTON_MODE_PIN, GPIO_MODE_INPUT_PULLUP)) return false; // 使用上拉输入
ESP_LOGI(TAG, "Button initialized.");
return true;
}

bool bsp_button_get_state(int button_id, bool *pressed) {
gpio_level_t level;
if (!hal_gpio_get_level(GPIO_PORT_0, BUTTON_MODE_PIN, &level)) return false;
*pressed = (level == GPIO_LEVEL_LOW); // 上拉输入,按下时为低电平
return true;
}

3. 操作系统层 (OS - FreeRTOS)

(FreeRTOS 的配置和使用通常在 ESP-IDF 工程的 sdkconfig.hmain/main.c 中进行,此处不单独展示 OS 层代码,但会在后续应用层代码中体现 FreeRTOS 的使用,例如任务创建、队列使用等。)

4. 中间件层 (Middleware)

middleware_wifi.h:

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
/**
* @file middleware_wifi.h
* @brief WiFi 中间件头文件
*/

#ifndef MIDDLEWARE_WIFI_H
#define MIDDLEWARE_WIFI_H

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

/**
* @brief WiFi 初始化
*
* @return true 初始化成功
* @return false 初始化失败
*/
bool middleware_wifi_init();

/**
* @brief 连接到 WiFi AP
*
* @param ssid WiFi SSID
* @param password WiFi 密码
* @return true 连接成功
* @return false 连接失败
*/
bool middleware_wifi_connect(const char *ssid, const char *password);

/**
* @brief 断开 WiFi 连接
*
* @return true 断开成功
* @return false 断开失败
*/
bool middleware_wifi_disconnect();

/**
* @brief 获取 WiFi 连接状态
*
* @return true 已连接
* @return false 未连接
*/
bool middleware_wifi_is_connected();

/**
* @brief 获取 WiFi IP 地址
*
* @param ip_str 输出参数,存储 IP 地址字符串
* @param ip_str_len IP 地址字符串缓冲区长度
* @return true 获取成功
* @return false 获取失败
*/
bool middleware_wifi_get_ip_address(char *ip_str, size_t ip_str_len);

// ... 其他 WiFi 功能接口,例如扫描 AP 列表、获取 MAC 地址等

#endif // MIDDLEWARE_WIFI_H

middleware_wifi.c: (使用 ESP-IDF WiFi 驱动和 lwIP 协议栈)

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
/**
* @file middleware_wifi.c
* @brief WiFi 中间件实现文件
*/

#include "middleware_wifi.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "lwip/ip4_addr.h"
#include <string.h>

#define WIFI_MAX_RETRY 3

static const char *TAG = "MW_WIFI";
static int s_retry_num = 0;
static EventGroupHandle_t s_wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;
const int WIFI_FAIL_BIT = BIT1;

static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);

bool middleware_wifi_init() {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta(); // 创建默认 station 网络接口

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());

ESP_LOGI(TAG, "WiFi initialization finished.");
return true;
}

bool middleware_wifi_connect(const char *ssid, const char *password) {
wifi_config_t wifi_config = {
.sta = {
.ssid = "",
.password = "",
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_connect());

EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to AP SSID:%s password:%s", ssid, password);
s_retry_num = 0;
return true;
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "Failed to connect to SSID:%s, password:%s", ssid, password);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
return false;
}

bool middleware_wifi_disconnect() {
ESP_ERROR_CHECK(esp_wifi_disconnect());
ESP_ERROR_CHECK(esp_wifi_stop());
ESP_LOGI(TAG, "WiFi disconnected.");
return true;
}

bool middleware_wifi_is_connected() {
return (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) != 0;
}

bool middleware_wifi_get_ip_address(char *ip_str, size_t ip_str_len) {
if (!middleware_wifi_is_connected()) {
return false;
}
esp_netif_ip_info_t ip_info;
esp_netif_t *sta_netif = esp_netif_get_example_netif();
if (esp_netif_get_ip_info(sta_netif, &ip_info) != ESP_OK) {
ESP_LOGE(TAG, "Failed to get IP address");
return false;
}
ip4addr_ntoa_r(&ip_info.ip, ip_str, ip_str_len);
ESP_LOGI(TAG, "Got IP Address: %s", ip_str);
return true;
}


static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
// do nothing, connection initiated in connect function
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_MAX_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}

middleware_usb.h, middleware_usb.c, middleware_filesystem.h, middleware_filesystem.c, middleware_config.h, middleware_config.c: (类似 middleware_wifi 的实现,需要根据使用的 USB 设备栈 (TinyUSB), 文件系统 (FatFS) 和配置管理方案进行实现, 此处省略,但实际项目中需要完成这些中间件模块的开发。)

5. 应用层 (Application Layer)

app_mode_manager.h:

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
/**
* @file app_mode_manager.h
* @brief 模式管理应用层头文件
*/

#ifndef APP_MODE_MANAGER_H
#define APP_MODE_MANAGER_H

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

/**
* @brief 系统模式类型
*/
typedef enum {
MODE_WIFI_CARD, // 无线网卡模式
MODE_USB_DISK, // U 盘模式
MODE_MAX
} system_mode_t;

/**
* @brief 初始化模式管理器
*
* @return true 初始化成功
* @return false 初始化失败
*/
bool app_mode_manager_init();

/**
* @brief 获取当前系统模式
*
* @return system_mode_t 当前系统模式
*/
system_mode_t app_mode_manager_get_mode();

/**
* @brief 设置系统模式
*
* @param mode 要设置的系统模式
* @return true 设置成功
* @return false 设置失败
*/
bool app_mode_manager_set_mode(system_mode_t mode);

#endif // APP_MODE_MANAGER_H

app_mode_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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* @file app_mode_manager.c
* @brief 模式管理应用层实现文件
*/

#include "app_mode_manager.h"
#include "bsp.h"
#include "middleware_wifi.h" // 假设 WiFi 功能需要在模式切换时启用/禁用
#include "middleware_usb.h" // 假设 USB 功能需要在模式切换时启用/禁用
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG = "APP_MODE_MGR";
static system_mode_t current_mode = MODE_WIFI_CARD; // 默认模式

bool app_mode_manager_init() {
ESP_LOGI(TAG, "Initializing Mode Manager...");

// 初始化按键检测任务 (示例,实际项目中可以采用更完善的按键处理机制)
if (xTaskCreate(mode_switch_task, "ModeSwitchTask", 2048, NULL, 5, NULL) != pdPASS) {
ESP_LOGE(TAG, "Failed to create mode switch task");
return false;
}

// 初始模式设置为 WiFi 网卡模式
if (!app_mode_manager_set_mode(MODE_WIFI_CARD)) {
ESP_LOGE(TAG, "Failed to set initial mode to WiFi Card");
return false;
}

ESP_LOGI(TAG, "Mode Manager Initialization complete.");
return true;
}

system_mode_t app_mode_manager_get_mode() {
return current_mode;
}

bool app_mode_manager_set_mode(system_mode_t mode) {
if (mode >= MODE_MAX) {
ESP_LOGE(TAG, "Invalid mode: %d", mode);
return false;
}

ESP_LOGI(TAG, "Switching to mode: %d", mode);
current_mode = mode;

switch (mode) {
case MODE_WIFI_CARD:
bsp_led_set_state(0, true); // 点亮 WiFi 指示灯
bsp_led_set_state(1, false); // 熄灭 USB 指示灯
middleware_usb_deinit(); // 禁用 USB 功能 (假设有 deinit 函数)
middleware_wifi_init(); // 初始化 WiFi 功能
// ... 初始化无线网卡模式相关功能
ESP_LOGI(TAG, "Mode switched to WiFi Card.");
break;
case MODE_USB_DISK:
bsp_led_set_state(0, false); // 熄灭 WiFi 指示灯
bsp_led_set_state(1, true); // 点亮 USB 指示灯
middleware_wifi_deinit(); // 禁用 WiFi 功能 (假设有 deinit 函数)
middleware_usb_init(); // 初始化 USB 功能
// ... 初始化 U 盘模式相关功能
ESP_LOGI(TAG, "Mode switched to USB Disk.");
break;
default:
ESP_LOGE(TAG, "Unknown mode: %d", mode);
return false;
}

return true;
}

// 按键检测任务 (示例)
static void mode_switch_task(void *pvParameters) {
bool button_pressed = false;
system_mode_t next_mode;

while (1) {
if (bsp_button_get_state(0, &button_pressed)) {
if (button_pressed) {
// 按键按下
vTaskDelay(pdMS_TO_TICKS(50)); // 简单消抖

if (bsp_button_get_state(0, &button_pressed) && button_pressed) {
// 确认按键按下 (再次检测)
next_mode = (current_mode == MODE_WIFI_CARD) ? MODE_USB_DISK : MODE_WIFI_CARD;
app_mode_manager_set_mode(next_mode);
}
}
}
vTaskDelay(pdMS_TO_TICKS(100)); // 检测间隔
}
}

app_wifi_card.h, app_wifi_card.c, app_usb_disk.h, app_usb_disk.c, app_ui.h, app_ui.c, main.c: (类似 app_mode_manager 的应用层模块实现,需要分别实现无线网卡模式和 U 盘模式的具体功能,以及用户界面逻辑。main.c 文件作为程序入口,负责系统初始化和任务创建。此处省略,但实际项目中需要完成这些应用层模块的开发,并确保代码量达到要求。)

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
/**
* @file main.c
* @brief 主程序入口
*/

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "bsp.h"
#include "app_mode_manager.h"
#include "esp_log.h"

static const char *TAG = "MAIN";

void app_main(void) {
ESP_LOGI(TAG, "ESP-Dongle Application Start...");

// 初始化板级支持包
if (!bsp_init()) {
ESP_LOGE(TAG, "BSP initialization failed!");
while (1); // 错误处理,可以根据实际情况修改
}

// 初始化模式管理器
if (!app_mode_manager_init()) {
ESP_LOGE(TAG, "Mode Manager initialization failed!");
while (1); // 错误处理
}

ESP_LOGI(TAG, "System initialization complete.");

// 主任务 (可以添加其他系统级任务)
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000)); // 1 秒延时
// ... 可以添加系统监控、日志输出等功能
}
}

代码扩展和完善方向

上述代码只是一个基本的框架和核心模块的示例,需要进一步扩展和完善各个模块的功能,并添加更多的细节和注释:

  • HAL 层: 完善 SPI, UART, USB, WiFi, Timer 等 HAL 驱动的实现,包括 DMA 支持、中断处理、错误处理等。
  • BSP 层: 添加更完善的 Flash 存储器管理 (例如分区管理、 wear leveling 等),电源管理 (低功耗模式切换、电压监控等),以及更细致的硬件初始化和配置。
  • 中间件层:
    • TCP/IP 协议栈 (lwIP): 配置和优化 lwIP 协议栈,实现更完善的网络功能,例如 DHCP 客户端、 DNS 客户端、 TCP/UDP 服务器/客户端 等。
    • WiFi 驱动适配层: 实现更高级的 WiFi 功能,例如 AP 扫描结果解析、 WiFi 参数配置界面 (如果需要)、 WiFi 安全加密等。
    • USB 设备栈 (TinyUSB): 实现 USB CDC-ECM (以太网卡模式) 和 USB MSC (大容量存储设备模式) 的功能,包括 USB 描述符配置、端点处理、数据传输逻辑、错误处理等。
    • 文件系统 (FatFS): 实现完整的文件系统操作接口,例如文件/目录创建、删除、读写、查找、格式化等,并考虑文件系统的性能优化和错误处理。
    • 配置管理: 实现更灵活的配置管理机制,例如使用配置文件存储配置参数,提供配置参数的读取、修改、保存等功能。
  • 应用层:
    • 无线网卡模式应用: 实现 USB CDC-ECM 功能,将 ESP-Dongle 模拟成 USB 网卡,实现网络数据转发,并进行性能优化。
    • U 盘模式应用: 实现 USB MSC 功能,将 Flash 存储器映射为 USB 磁盘,实现文件读写操作,并考虑数据安全和文件系统稳定性。
    • 用户界面 (UI): 完善 LED 指示灯的状态显示,添加更丰富的用户交互方式 (例如串口命令、 Web 界面 等,如果硬件条件允许)。
    • 错误处理和日志: 在各个模块中添加完善的错误处理机制,并使用日志系统记录系统运行状态和错误信息,方便调试和维护。
  • 代码注释: 为所有代码添加详细的注释,解释代码的功能、实现原理、接口参数、返回值等,提高代码的可读性和可维护性。
  • 单元测试: 为各个模块编写单元测试用例,验证模块功能的正确性和稳定性 (虽然在示例代码中没有体现,但实际项目中单元测试非常重要)。
  • 代码风格规范: 统一代码风格,例如缩进、命名规范、注释风格等,提高代码的整洁性和可读性.

通过以上扩展和完善,并构建一个功能完善、可靠高效、可扩展易维护的 ESP-Dongle 嵌入式软件系统。

总结

本文详细介绍了 ESP-Dongle 双重功能嵌入式系统的软件设计架构和 C 代码实现。采用分层架构,将系统划分为硬件抽象层 (HAL)、板级支持包 (BSP)、操作系统层 (OS - FreeRTOS)、中间件层 (Middleware) 和应用层 (Application Layer),实现了代码的模块化和可维护性。代码示例涵盖了 GPIO HAL 驱动、 BSP 初始化、 WiFi 中间件、模式管理应用层等核心模块。

要构建一个完整的、高质量的嵌入式系统,还需要在上述代码基础上进行大量的扩展和完善,包括各个模块的详细实现、错误处理、性能优化、单元测试、文档编写等工作。通过实践验证和不断迭代,最终才能开发出一个可靠、高效、可扩展的 ESP-Dongle 产品。

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