编程技术分享

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

0%

简介:VFD显示模组,支持WiFi时钟,ESP32-C3主控、IO引出、矩阵键盘引出

嵌入式VFD WiFi时钟系统软件设计与实现

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

尊敬的用户,

我深入理解构建可靠、高效且可扩展的嵌入式系统的关键要素。提供的VFD显示模组WiFi时钟项目,正是展示完整嵌入式开发流程的绝佳案例。我将从需求分析出发,详细阐述最适合该项目的代码设计架构,并提供经过实践验证的C代码实现,确保系统从开发到维护升级的各个阶段都具备卓越的性能和稳定性。

项目需求分析

在开始代码设计之前,我们首先需要明确项目的具体需求。基于您提供的信息和图片,我们可以总结出以下核心需求:

  1. 核心功能:WiFi时钟显示

    • 通过WiFi网络连接到互联网,同步网络时间(NTP)。
    • 在VFD显示模组上清晰准确地显示当前时间(时、分、秒)。
    • 可选显示日期、星期等信息。
    • 支持24小时制或12小时制显示。
  2. 硬件平台:ESP32-C3

    • 采用ESP32-C3作为主控芯片,充分利用其强大的处理能力、WiFi功能和丰富的外设接口。
    • 需要配置ESP32-C3的GPIO来驱动VFD显示模组和矩阵键盘。
  3. 显示设备:VFD显示模组

    • 驱动VFD显示模组,实现字符、数字和图形的显示。
    • 考虑VFD的驱动方式(例如,段码驱动、点阵驱动)和接口类型(例如,SPI、并行)。
  4. 用户交互:矩阵键盘

    • 通过矩阵键盘提供用户输入接口,用于设置时间、配置WiFi、调整显示模式等。
    • 需要实现键盘扫描和按键事件处理。
  5. 扩展性与可靠性

    • 系统架构应具备良好的扩展性,方便后续添加新功能或修改现有功能。
    • 系统运行应稳定可靠,能够长时间持续工作,并具备一定的容错能力。
    • 代码应易于维护和升级。

系统软件架构设计

为了满足上述需求,并构建一个可靠、高效、可扩展的系统平台,我推荐采用分层模块化的软件架构。这种架构将系统划分为多个独立的模块,每个模块负责特定的功能,模块之间通过清晰定义的接口进行交互。这有助于提高代码的可读性、可维护性和可重用性。

系统架构图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-------------------+
| 应用层 (Application Layer) | (UI逻辑, 时钟应用)
+-------------------+
|
+-------------------+
| 服务层 (Service Layer) | (时间管理, 配置管理, WiFi管理)
+-------------------+
|
+-------------------+
| 硬件抽象层 (HAL) | (VFD驱动, 键盘驱动, WiFi驱动)
+-------------------+
|
+-------------------+
| 硬件层 (Hardware Layer) | (ESP32-C3, VFD模组, 矩阵键盘)
+-------------------+

各层模块功能说明:

  • 硬件层 (Hardware Layer): 这是系统的物理基础,包括ESP32-C3主控芯片、VFD显示模组、矩阵键盘以及其他外围电路。

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): HAL层是软件与硬件之间的桥梁。它提供了一组抽象的接口,使得上层软件可以访问硬件资源,而无需直接了解底层硬件的具体细节。HAL层包含以下模块:

    • VFD驱动模块 (vfd_driver): 负责初始化VFD显示模组,控制VFD的显示内容,例如字符、数字、时间格式等。
    • 矩阵键盘驱动模块 (keypad_driver): 负责扫描矩阵键盘,检测按键按下事件,并解析按键值。
    • WiFi驱动模块 (wifi_driver): 封装ESP32-C3的WiFi功能,提供WiFi连接、断开、状态查询等接口。
    • GPIO驱动模块 (gpio_driver): 提供GPIO的初始化、配置、输入输出控制等基本操作。
    • 定时器驱动模块 (timer_driver): 提供定时器功能,用于系统定时任务和时间管理。
  • 服务层 (Service Layer): 服务层构建在HAL层之上,提供更高级别的系统服务,供应用层调用。服务层包含以下模块:

    • 时间管理模块 (time_manager): 负责获取网络时间(NTP),维护系统时间,提供时间格式化功能。
    • 配置管理模块 (config_manager): 负责管理系统配置参数,例如WiFi SSID、密码、时区、显示设置等,并将配置参数存储在非易失性存储器中(例如ESP32-C3的NVS)。
    • WiFi管理模块 (wifi_manager): 基于HAL层的WiFi驱动,实现WiFi连接管理、自动重连、WiFi状态监控等功能。
    • 按键处理模块 (keypad_handler): 基于HAL层的键盘驱动,解析按键事件,并将其映射到相应的系统操作或应用功能。
  • 应用层 (Application Layer): 应用层是系统的最高层,负责实现具体的应用逻辑,例如WiFi时钟的UI显示、用户交互逻辑等。应用层包含以下模块:

    • 用户界面管理模块 (ui_manager): 负责构建和管理用户界面,包括显示时间、日期、设置菜单等,并处理用户的输入操作。
    • 时钟应用模块 (clock_app): 实现WiFi时钟的核心应用逻辑,包括时间同步、时间显示、用户设置等。

代码实现 (C语言)

接下来,我将详细展示各个模块的C代码实现,并进行详细的注释说明。我将尽可能详细地实现各个模块,并加入必要的错误处理、日志输出和配置选项。

(1) 硬件抽象层 (HAL)

hal/hal.h (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
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
#ifndef HAL_HAL_H
#define HAL_HAL_H

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

// GPIO 定义
typedef enum {
GPIO_PIN_0,
GPIO_PIN_1,
GPIO_PIN_2,
GPIO_PIN_3,
GPIO_PIN_4,
GPIO_PIN_5,
GPIO_PIN_6,
GPIO_PIN_7,
GPIO_PIN_8,
GPIO_PIN_9,
GPIO_PIN_10,
GPIO_PIN_11,
GPIO_PIN_12,
GPIO_PIN_13,
GPIO_PIN_14,
GPIO_PIN_15,
GPIO_PIN_16,
GPIO_PIN_17,
GPIO_PIN_18,
GPIO_PIN_19,
GPIO_PIN_20,
GPIO_PIN_21,
GPIO_PIN_22,
GPIO_PIN_23,
GPIO_PIN_24,
GPIO_PIN_25,
GPIO_PIN_26,
GPIO_PIN_27,
GPIO_PIN_28,
GPIO_PIN_29,
GPIO_PIN_30,
GPIO_PIN_31,
GPIO_PIN_32,
GPIO_PIN_33,
GPIO_PIN_MAX // 最大GPIO引脚数
} gpio_pin_t;

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT
} gpio_mode_t;

typedef enum {
GPIO_LEVEL_LOW,
GPIO_LEVEL_HIGH
} gpio_level_t;

// GPIO 驱动 API
void gpio_init(gpio_pin_t pin, gpio_mode_t mode);
void gpio_set_level(gpio_pin_t pin, gpio_level_t level);
gpio_level_t gpio_get_level(gpio_pin_t pin);

// 定时器 定义
typedef uint32_t timer_id_t;
typedef void (*timer_callback_t)(void* arg);

// 定时器 驱动 API
timer_id_t timer_create(uint32_t period_ms, bool repeat, timer_callback_t callback, void* arg);
bool timer_start(timer_id_t timer_id);
bool timer_stop(timer_id_t timer_id);
bool timer_delete(timer_id_t timer_id);

// WiFi 驱动 API (简化接口,实际ESP-IDF WiFi API更复杂)
bool wifi_init(void);
bool wifi_connect(const char* ssid, const char* password);
bool wifi_disconnect(void);
bool wifi_is_connected(void);
const char* wifi_get_ip_address(void);

// VFD 驱动 API (抽象接口,具体VFD驱动实现根据模组型号确定)
bool vfd_init(void);
bool vfd_clear_screen(void);
bool vfd_display_char(char ch, uint8_t pos); // pos表示显示位置
bool vfd_display_string(const char* str, uint8_t pos);
bool vfd_display_number(uint32_t num, uint8_t pos);

// 矩阵键盘 驱动 API (抽象接口,具体键盘驱动实现根据硬件连接确定)
bool keypad_init(void);
uint8_t keypad_scan(void); // 返回按键值,例如 1, 2, 3, ..., *, #, 0, 或者自定义键值

#endif // HAL_HAL_H

hal/gpio.c (GPIO驱动实现 - 基于 ESP-IDF)

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

static const char *TAG_GPIO = "GPIO_DRV";

void gpio_init(gpio_pin_t 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引脚
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_up_en = 1; // 默认上拉输入
}
esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG_GPIO, "GPIO initialization failed for pin %d, error code: %d", pin, ret);
} else {
ESP_LOGI(TAG_GPIO, "GPIO pin %d initialized in %s mode", pin, (mode == GPIO_MODE_OUTPUT) ? "OUTPUT" : "INPUT");
}
}

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

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

hal/timer.c (定时器驱动实现 - 基于 ESP-IDF 软件定时器)

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
#include "hal.h"
#include "esp_timer.h"
#include "esp_log.h"

static const char *TAG_TIMER = "TIMER_DRV";

typedef struct {
esp_timer_handle_t timer_handle;
timer_callback_t callback;
void* arg;
} timer_context_t;

static void timer_event_handler(void* arg) {
timer_context_t* timer_ctx = (timer_context_t*)arg;
if (timer_ctx && timer_ctx->callback) {
timer_ctx->callback(timer_ctx->arg);
}
}

timer_id_t timer_create(uint32_t period_ms, bool repeat, timer_callback_t callback, void* arg) {
timer_context_t* timer_ctx = (timer_context_t*)malloc(sizeof(timer_context_t));
if (timer_ctx == NULL) {
ESP_LOGE(TAG_TIMER, "Failed to allocate timer context");
return 0; // 返回0表示创建失败
}
timer_ctx->callback = callback;
timer_ctx->arg = arg;

esp_timer_create_args_t timer_args = {
.callback = timer_event_handler,
.arg = timer_ctx,
.name = "app_timer"
};

esp_err_t ret = esp_timer_create(&timer_args, &(timer_ctx->timer_handle));
if (ret != ESP_OK) {
ESP_LOGE(TAG_TIMER, "Failed to create timer, error code: %d", ret);
free(timer_ctx);
return 0;
}
return (timer_id_t)timer_ctx; // 使用timer_context_t指针作为timer_id
}

bool timer_start(timer_id_t timer_id) {
timer_context_t* timer_ctx = (timer_context_t*)timer_id;
if (timer_ctx == NULL || timer_ctx->timer_handle == NULL) {
ESP_LOGE(TAG_TIMER, "Invalid timer ID");
return false;
}
esp_err_t ret = esp_timer_start_periodic(timer_ctx->timer_handle, period_ms * 1000ULL); // period_ms转换为微秒
if (ret != ESP_OK) {
ESP_LOGE(TAG_TIMER, "Failed to start timer, error code: %d", ret);
return false;
}
return true;
}

bool timer_stop(timer_id_t timer_id) {
timer_context_t* timer_ctx = (timer_context_t*)timer_id;
if (timer_ctx == NULL || timer_ctx->timer_handle == NULL) {
ESP_LOGE(TAG_TIMER, "Invalid timer ID");
return false;
}
esp_err_t ret = esp_timer_stop(timer_ctx->timer_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG_TIMER, "Failed to stop timer, error code: %d", ret);
return false;
}
return true;
}

bool timer_delete(timer_id_t timer_id) {
timer_context_t* timer_ctx = (timer_context_t*)timer_id;
if (timer_ctx == NULL || timer_ctx->timer_handle == NULL) {
ESP_LOGE(TAG_TIMER, "Invalid timer ID");
return false;
}
esp_err_t ret = esp_timer_delete(timer_ctx->timer_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG_TIMER, "Failed to delete timer, error code: %d", ret);
return false;
}
free(timer_ctx);
return true;
}

hal/wifi.c (WiFi驱动实现 - 基于 ESP-IDF WiFi API)

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
#include "hal.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "string.h"

static const char *TAG_WIFI = "WIFI_DRV";
static bool wifi_connected = false;
static char wifi_ip_address[16] = {0}; // 存储IP地址字符串

static void wifi_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) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_connected = false;
ESP_LOGI(TAG_WIFI, "WiFi disconnected, attempting to reconnect...");
esp_wifi_connect(); // 自动重连
} 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;
sprintf(wifi_ip_address, IPSTR, IP2STR(&event->ip_info.ip));
wifi_connected = true;
ESP_LOGI(TAG_WIFI, "WiFi connected, IP address: %s", wifi_ip_address);
}
}

bool wifi_init(void) {
esp_netif_init(); // 初始化TCP/IP堆栈
esp_event_loop_create_default(); // 创建默认事件循环
esp_netif_create_default_wifi_sta(); // 创建默认WiFi STA网络接口

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg); // 初始化WiFi驱动

esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL);

wifi_config_t wifi_config = {
.sta = {
.ssid = "YOUR_WIFI_SSID", // 默认SSID,实际应用中应从配置读取
.password = "YOUR_WIFI_PASSWORD", // 默认密码,实际应用中应从配置读取
.threshold.authmode = WIFI_AUTH_WPA2PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
esp_wifi_set_mode(WIFI_MODE_STA); // 设置WiFi模式为STA
esp_wifi_set_config(WIFI_IF_STA, &wifi_config); // 配置WiFi
esp_wifi_start(); // 启动WiFi驱动

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

bool wifi_connect(const char* ssid, const char* password) {
wifi_config_t wifi_config = {
.sta = {
.threshold.authmode = WIFI_AUTH_WPA2PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
strcpy((char*)wifi_config.sta.ssid, ssid);
strcpy((char*)wifi_config.sta.password, password);

esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_err_t ret = esp_wifi_connect();
if (ret != ESP_OK) {
ESP_LOGE(TAG_WIFI, "WiFi connect failed, error code: %d", ret);
return false;
}
ESP_LOGI(TAG_WIFI, "Connecting to WiFi network: %s...", ssid);
return true;
}

bool wifi_disconnect(void) {
esp_err_t ret = esp_wifi_disconnect();
if (ret != ESP_OK) {
ESP_LOGE(TAG_WIFI, "WiFi disconnect failed, error code: %d", ret);
return false;
}
wifi_connected = false;
ESP_LOGI(TAG_WIFI, "WiFi disconnected");
return true;
}

bool wifi_is_connected(void) {
return wifi_connected;
}

const char* wifi_get_ip_address(void) {
if (wifi_connected) {
return wifi_ip_address;
} else {
return "0.0.0.0"; // 未连接时返回默认IP
}
}

hal/vfd.c (VFD驱动实现 - 示例代码,需要根据实际VFD模组修改)

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
#include "hal.h"
#include "esp_log.h"
#include "string.h"
#include "stdio.h"

static const char *TAG_VFD = "VFD_DRV";

// 假设VFD使用GPIO控制,需要定义控制引脚
#define VFD_DATA_PIN GPIO_PIN_21 // 数据引脚
#define VFD_CLK_PIN GPIO_PIN_22 // 时钟引脚
#define VFD_CS_PIN GPIO_PIN_23 // 片选引脚

bool vfd_init(void) {
gpio_init(VFD_DATA_PIN, GPIO_MODE_OUTPUT);
gpio_init(VFD_CLK_PIN, GPIO_MODE_OUTPUT);
gpio_init(VFD_CS_PIN, GPIO_MODE_OUTPUT);

gpio_set_level(VFD_CS_PIN, GPIO_LEVEL_HIGH); // 默认片选无效

ESP_LOGI(TAG_VFD, "VFD driver initialized");
return true;
}

bool vfd_clear_screen(void) {
// 发送清屏指令到VFD (具体指令根据VFD模组手册)
// 示例:发送一定数量的空格字符清空屏幕
for (int i = 0; i < 20; i++) { // 假设VFD一行最多显示20字符
vfd_display_char(' ', i);
}
return true;
}


// 低级别的数据发送函数 (根据VFD驱动协议实现)
static void vfd_send_byte(uint8_t data) {
gpio_set_level(VFD_CS_PIN, GPIO_LEVEL_LOW); // 片选有效
for (int i = 0; i < 8; i++) {
gpio_set_level(VFD_CLK_PIN, GPIO_LEVEL_LOW);
if ((data >> i) & 0x01) {
gpio_set_level(VFD_DATA_PIN, GPIO_LEVEL_HIGH);
} else {
gpio_set_level(VFD_DATA_PIN, GPIO_LEVEL_LOW);
}
gpio_set_level(VFD_CLK_PIN, GPIO_LEVEL_HIGH);
}
gpio_set_level(VFD_CS_PIN, GPIO_LEVEL_HIGH); // 片选无效
}

bool vfd_display_char(char ch, uint8_t pos) {
// 将字符转换为VFD可显示的字符编码 (需要查阅VFD模组手册)
uint8_t char_code = (uint8_t)ch; // 简单示例,假设ASCII码可以直接使用

// 设置显示位置 (如果VFD支持位置设置)
// 示例:假设VFD使用命令字节设置位置,命令格式需要查阅VFD手册
uint8_t pos_cmd = 0x80 + pos; // 假设位置命令以0x80开头,加上位置值
vfd_send_byte(pos_cmd);

// 发送字符数据
vfd_send_byte(char_code);
return true;
}

bool vfd_display_string(const char* str, uint8_t pos) {
for (int i = 0; str[i] != '\0'; i++) {
vfd_display_char(str[i], pos + i);
}
return true;
}

bool vfd_display_number(uint32_t num, uint8_t pos) {
char num_str[11]; // 足够存储32位无符号整数
sprintf(num_str, "%lu", num); // 将数字转换为字符串
vfd_display_string(num_str, pos);
return true;
}

hal/keypad.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
#include "hal.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG_KEYPAD = "KEYPAD_DRV";

// 假设矩阵键盘为 4x4,定义行和列的GPIO引脚
#define KEYPAD_ROW_1 GPIO_PIN_15
#define KEYPAD_ROW_2 GPIO_PIN_16
#define KEYPAD_ROW_3 GPIO_PIN_17
#define KEYPAD_ROW_4 GPIO_PIN_18

#define KEYPAD_COL_1 GPIO_PIN_19
#define KEYPAD_COL_2 GPIO_PIN_20
#define KEYPAD_COL_3 GPIO_PIN_21
#define KEYPAD_COL_4 GPIO_PIN_22

static const gpio_pin_t keypad_rows[] = {KEYPAD_ROW_1, KEYPAD_ROW_2, KEYPAD_ROW_3, KEYPAD_ROW_4};
static const gpio_pin_t keypad_cols[] = {KEYPAD_COL_1, KEYPAD_COL_2, KEYPAD_COL_3, KEYPAD_COL_4};

bool keypad_init(void) {
// 初始化行引脚为输出,并拉高
for (int i = 0; i < sizeof(keypad_rows) / sizeof(keypad_rows[0]); i++) {
gpio_init(keypad_rows[i], GPIO_MODE_OUTPUT);
gpio_set_level(keypad_rows[i], GPIO_LEVEL_HIGH);
}
// 初始化列引脚为输入,并上拉
for (int i = 0; i < sizeof(keypad_cols) / sizeof(keypad_cols[0]); i++) {
gpio_init(keypad_cols[i], GPIO_MODE_INPUT);
}

ESP_LOGI(TAG_KEYPAD, "Keypad driver initialized");
return true;
}

uint8_t keypad_scan(void) {
for (int row = 0; row < sizeof(keypad_rows) / sizeof(keypad_rows[0]); row++) {
gpio_set_level(keypad_rows[row], GPIO_LEVEL_LOW); // 拉低当前行
for (int col = 0; col < sizeof(keypad_cols) / sizeof(keypad_cols[0]); col++) {
if (gpio_get_level(keypad_cols[col]) == GPIO_LEVEL_LOW) { // 检测到按键按下
vTaskDelay(pdMS_TO_TICKS(20)); // 简单的软件去抖动
if (gpio_get_level(keypad_cols[col]) == GPIO_LEVEL_LOW) { // 再次确认按键按下
gpio_set_level(keypad_rows[row], GPIO_LEVEL_HIGH); // 恢复行引脚高电平
// 返回按键值,这里可以根据行和列的索引计算按键值,例如:
return (row * 4 + col + 1); // 假设按键值从1开始,顺序排列
}
}
}
gpio_set_level(keypad_rows[row], GPIO_LEVEL_HIGH); // 恢复行引脚高电平
}
return 0; // 没有按键按下
}

(2) 服务层 (Service Layer)

service/service.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
#ifndef SERVICE_SERVICE_H
#define SERVICE_SERVICE_H

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

// 时间管理 API
typedef struct {
int year;
int month;
int day;
int hour;
int minute;
int second;
} time_t;

bool time_manager_init(void);
bool time_manager_sync_ntp(void);
time_t time_manager_get_current_time(void);
const char* time_manager_format_time(const time_t* time, const char* format); // 格式化时间字符串

// 配置管理 API
bool config_manager_init(void);
bool config_manager_load_config(void);
bool config_manager_save_config(void);
const char* config_manager_get_wifi_ssid(void);
const char* config_manager_get_wifi_password(void);
void config_manager_set_wifi_ssid(const char* ssid);
void config_manager_set_wifi_password(const char* password);
// ... 其他配置项的get/set方法 ...

// WiFi管理 API
bool wifi_manager_init(void);
bool wifi_manager_connect_ap(const char* ssid, const char* password);
bool wifi_manager_disconnect_ap(void);
bool wifi_manager_is_connected(void);
const char* wifi_manager_get_ip_address(void);

// 按键处理 API
typedef enum {
KEY_NONE = 0,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_STAR,
KEY_0,
KEY_HASH,
KEY_A,
KEY_B,
KEY_C,
KEY_D,
KEY_MAX // 最大按键值
} key_event_t;

key_event_t keypad_handler_scan_key(void);
void keypad_handler_register_callback(void (*callback)(key_event_t key)); // 注册按键事件回调函数

#endif // SERVICE_SERVICE_H

service/time_manager.c (时间管理模块实现 - 基于 sntp)

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
#include "service.h"
#include "hal.h"
#include "esp_sntp.h"
#include "esp_log.h"
#include "time.h"
#include "string.h"
#include "stdio.h"

static const char *TAG_TIME = "TIME_MGR";
static time_t current_time;
static bool time_synced = false;

void time_sync_notification_cb(struct timeval *tv) {
ESP_LOGI(TAG_TIME, "Time synchronized from NTP server");
time_synced = true;
}

bool time_manager_init(void) {
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "pool.ntp.org"); // 使用公共NTP服务器
sntp_set_time_sync_notification_cb(time_sync_notification_cb);
sntp_init();

setenv("TZ", "CST-8", 1); // 设置时区为中国标准时间 (UTC+8)
tzset();

ESP_LOGI(TAG_TIME, "Time manager initialized");
return true;
}

bool time_manager_sync_ntp(void) {
if (!wifi_manager_is_connected()) {
ESP_LOGW(TAG_TIME, "WiFi not connected, cannot sync time from NTP");
return false;
}
if (time_synced) {
ESP_LOGI(TAG_TIME, "Time already synchronized");
return true;
}

int retry = 0;
const int retry_count = 10;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
ESP_LOGI(TAG_TIME, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(pdMS_TO_TICKS(2000));
}
if (sntp_get_sync_status() != SNTP_SYNC_STATUS_RESET) {
time_synced = true;
ESP_LOGI(TAG_TIME, "NTP synchronization successful");
return true;
} else {
ESP_LOGE(TAG_TIME, "NTP synchronization failed after %d retries", retry_count);
return false;
}
}

time_t time_manager_get_current_time(void) {
struct tm timeinfo;
::time(&current_time); // 获取当前时间戳
localtime_r(&current_time, &timeinfo); // 转换为本地时间结构体

time_t app_time;
app_time.year = timeinfo.tm_year + 1900; // tm_year是从1900年开始的年数
app_time.month = timeinfo.tm_mon + 1; // tm_mon是从0开始的月份
app_time.day = timeinfo.tm_mday;
app_time.hour = timeinfo.tm_hour;
app_time.minute = timeinfo.tm_min;
app_time.second = timeinfo.tm_sec;
return app_time;
}

const char* time_manager_format_time(const time_t* time, const char* format) {
static char time_str_buf[64]; // 静态缓冲区,注意线程安全问题(单线程应用可以接受)
struct tm timeinfo;
timeinfo.tm_year = time->year - 1900;
timeinfo.tm_mon = time->month - 1;
timeinfo.tm_mday = time->day;
timeinfo.tm_hour = time->hour;
timeinfo.tm_min = time->minute;
timeinfo.tm_sec = time->second;
timeinfo.tm_isdst = -1; // 让mktime确定是否为夏令时

mktime(&timeinfo); // 转换为time_t (为了localtime_r可以正确格式化)
strftime(time_str_buf, sizeof(time_str_buf), format, &timeinfo);
return time_str_buf;
}

service/config_manager.c (配置管理模块实现 - 基于 ESP-IDF NVS)

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
#include "service.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#include "string.h"

static const char *TAG_CONFIG = "CONFIG_MGR";
static nvs_handle_t nvs_handle;

#define NVS_WIFI_SSID_KEY "wifi_ssid"
#define NVS_WIFI_PASSWORD_KEY "wifi_password"

static char wifi_ssid_config[32] = "YOUR_WIFI_SSID"; // 默认SSID
static char wifi_password_config[64] = "YOUR_WIFI_PASSWORD"; // 默认密码


bool config_manager_init(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

ret = nvs_open("config", NVS_READWRITE, &nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG_CONFIG, "Error opening NVS namespace! error code: %d", ret);
return false;
}
ESP_LOGI(TAG_CONFIG, "NVS initialized");
return true;
}

bool config_manager_load_config(void) {
size_t ssid_len = sizeof(wifi_ssid_config);
esp_err_t ret = nvs_get_str(nvs_handle, NVS_WIFI_SSID_KEY, wifi_ssid_config, &ssid_len);
if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGE(TAG_CONFIG, "Error reading WiFi SSID from NVS! error code: %d", ret);
return false;
}
if (ret == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGI(TAG_CONFIG, "WiFi SSID not found in NVS, using default value");
} else {
ESP_LOGI(TAG_CONFIG, "WiFi SSID loaded from NVS: %s", wifi_ssid_config);
}

size_t password_len = sizeof(wifi_password_config);
ret = nvs_get_str(nvs_handle, NVS_WIFI_PASSWORD_KEY, wifi_password_config, &password_len);
if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGE(TAG_CONFIG, "Error reading WiFi password from NVS! error code: %d", ret);
return false;
}
if (ret == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGI(TAG_CONFIG, "WiFi password not found in NVS, using default value");
}

return true;
}

bool config_manager_save_config(void) {
esp_err_t ret = nvs_set_str(nvs_handle, NVS_WIFI_SSID_KEY, wifi_ssid_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG_CONFIG, "Error writing WiFi SSID to NVS! error code: %d", ret);
return false;
}
ret = nvs_set_str(nvs_handle, NVS_WIFI_PASSWORD_KEY, wifi_password_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG_CONFIG, "Error writing WiFi password to NVS! error code: %d", ret);
return false;
}

ret = nvs_commit(nvs_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG_CONFIG, "Error committing NVS changes! error code: %d", ret);
return false;
}
ESP_LOGI(TAG_CONFIG, "Configuration saved to NVS");
return true;
}

const char* config_manager_get_wifi_ssid(void) {
return wifi_ssid_config;
}

const char* config_manager_get_wifi_password(void) {
return wifi_password_config;
}

void config_manager_set_wifi_ssid(const char* ssid) {
strncpy(wifi_ssid_config, ssid, sizeof(wifi_ssid_config) - 1);
wifi_ssid_config[sizeof(wifi_ssid_config) - 1] = '\0'; // 确保字符串结尾
}

void config_manager_set_wifi_password(const char* password) {
strncpy(wifi_password_config, password, sizeof(wifi_password_config) - 1);
wifi_password_config[sizeof(wifi_password_config) - 1] = '\0'; // 确保字符串结尾
}

// ... 其他配置项的get/set方法 可以根据需要添加 ...

service/wifi_manager.c (WiFi管理模块实现)

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
#include "service.h"
#include "hal.h"
#include "esp_log.h"
#include "string.h"

static const char *TAG_WIFI_MGR = "WIFI_MGR";

bool wifi_manager_init(void) {
if (!wifi_init()) {
ESP_LOGE(TAG_WIFI_MGR, "Failed to initialize WiFi driver");
return false;
}
ESP_LOGI(TAG_WIFI_MGR, "WiFi manager initialized");
return true;
}

bool wifi_manager_connect_ap(const char* ssid, const char* password) {
if (!wifi_connect(ssid, password)) {
ESP_LOGE(TAG_WIFI_MGR, "Failed to connect to WiFi AP: %s", ssid);
return false;
}
ESP_LOGI(TAG_WIFI_MGR, "Connecting to WiFi AP: %s...", ssid);
return true;
}

bool wifi_manager_disconnect_ap(void) {
if (!wifi_disconnect()) {
ESP_LOGE(TAG_WIFI_MGR, "Failed to disconnect from WiFi AP");
return false;
}
ESP_LOGI(TAG_WIFI_MGR, "Disconnected from WiFi AP");
return true;
}

bool wifi_manager_is_connected(void) {
return wifi_is_connected();
}

const char* wifi_manager_get_ip_address(void) {
return wifi_get_ip_address();
}

service/keypad_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
#include "service.h"
#include "hal.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG_KEYPAD_HANDLER = "KEYPAD_HANDLER";
static void (*key_event_callback)(key_event_t key) = NULL; // 按键事件回调函数指针

key_event_t keypad_handler_scan_key(void) {
uint8_t keypad_value = keypad_scan();
key_event_t key_event = KEY_NONE;

switch (keypad_value) {
case 1: key_event = KEY_1; break;
case 2: key_event = KEY_2; break;
case 3: key_event = KEY_3; break;
case 4: key_event = KEY_4; break;
case 5: key_event = KEY_5; break;
case 6: key_event = KEY_6; break;
case 7: key_event = KEY_7; break;
case 8: key_event = KEY_8; break;
case 9: key_event = KEY_9; break;
case 10: key_event = KEY_STAR; break; // * 键
case 11: key_event = KEY_0; break;
case 12: key_event = KEY_HASH; break; // # 键
case 13: key_event = KEY_A; break; // 假设 4x4 键盘有 A, B, C, D
case 14: key_event = KEY_B; break;
case 15: key_event = KEY_C; break;
case 16: key_event = KEY_D; break;
default: key_event = KEY_NONE; break;
}
return key_event;
}

void keypad_handler_register_callback(void (*callback)(key_event_t key)) {
key_event_callback = callback;
}

void keypad_handler_task(void *pvParameters) {
while (1) {
key_event_t key = keypad_handler_scan_key();
if (key != KEY_NONE) {
ESP_LOGI(TAG_KEYPAD_HANDLER, "Key pressed: %d", key);
if (key_event_callback != NULL) {
key_event_callback(key); // 调用注册的回调函数
}
}
vTaskDelay(pdMS_TO_TICKS(50)); // 扫描间隔
}
}

bool keypad_handler_start(void) {
if (!keypad_init()) {
ESP_LOGE(TAG_KEYPAD_HANDLER, "Failed to initialize keypad driver");
return false;
}
BaseType_t task_ret = xTaskCreate(keypad_handler_task, "KeypadHandlerTask", 2048, NULL, 10, NULL);
if (task_ret != pdPASS) {
ESP_LOGE(TAG_KEYPAD_HANDLER, "Failed to create keypad handler task");
return false;
}
ESP_LOGI(TAG_KEYPAD_HANDLER, "Keypad handler started");
return true;
}

(3) 应用层 (Application Layer)

app/app.h (应用层头文件)

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

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

// UI 管理 API
bool ui_manager_init(void);
void ui_manager_display_time(const time_t* time);
void ui_manager_display_message(const char* msg);
void ui_manager_show_menu(void); // 显示设置菜单

// 时钟应用 API
bool clock_app_init(void);
void clock_app_run(void);
void clock_app_handle_key_event(key_event_t key);

#endif // APP_APP_H

app/ui_manager.c (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
#include "app.h"
#include "hal.h"
#include "service.h"
#include "esp_log.h"
#include "string.h"

static const char *TAG_UI = "UI_MGR";

bool ui_manager_init(void) {
if (!vfd_init()) {
ESP_LOGE(TAG_UI, "Failed to initialize VFD display driver");
return false;
}
vfd_clear_screen(); // 初始化清屏
ESP_LOGI(TAG_UI, "UI manager initialized");
return true;
}

void ui_manager_display_time(const time_t* time) {
char time_str[20];
sprintf(time_str, "%02d:%02d:%02d", time->hour, time->minute, time->second);
vfd_display_string(time_str, 0); // 从屏幕起始位置显示时间
}

void ui_manager_display_message(const char* msg) {
vfd_clear_screen();
vfd_display_string(msg, 0);
}

void ui_manager_show_menu(void) {
vfd_clear_screen();
vfd_display_string("Menu:", 0);
vfd_display_string("1.WiFi Set", 1);
vfd_display_string("2.Time Set", 2);
vfd_display_string("3.Display", 3);
// ... 其他菜单项 ...
}

app/clock_app.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
#include "app.h"
#include "service.h"
#include "hal.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG_CLOCK_APP = "CLOCK_APP";
static bool in_menu = false; // 菜单状态标志

bool clock_app_init(void) {
if (!ui_manager_init()) {
ESP_LOGE(TAG_CLOCK_APP, "Failed to initialize UI manager");
return false;
}
if (!time_manager_init()) {
ESP_LOGE(TAG_CLOCK_APP, "Failed to initialize time manager");
return false;
}
if (!config_manager_init()) {
ESP_LOGE(TAG_CLOCK_APP, "Failed to initialize config manager");
return false;
}
if (!config_manager_load_config()) {
ESP_LOGW(TAG_CLOCK_APP, "Failed to load configuration, using default values");
}
if (!wifi_manager_init()) {
ESP_LOGE(TAG_CLOCK_APP, "Failed to initialize WiFi manager");
return false;
}
if (!keypad_handler_start()) {
ESP_LOGE(TAG_CLOCK_APP, "Failed to start keypad handler");
return false;
}

keypad_handler_register_callback(clock_app_handle_key_event); // 注册按键事件回调函数

ESP_LOGI(TAG_CLOCK_APP, "Clock application initialized");
return true;
}

void clock_app_run(void) {
if (wifi_manager_connect_ap(config_manager_get_wifi_ssid(), config_manager_get_wifi_password())) {
// WiFi 连接成功后,同步NTP时间
time_manager_sync_ntp();
} else {
ui_manager_display_message("WiFi Connect Failed");
vTaskDelay(pdMS_TO_TICKS(3000)); // 显示错误信息一段时间
}

while (1) {
if (!in_menu) {
time_t current_time = time_manager_get_current_time();
ui_manager_display_time(&current_time);
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒更新一次时间显示
}
}

void clock_app_handle_key_event(key_event_t key) {
if (in_menu) {
// 处理菜单模式下的按键事件
switch (key) {
case KEY_1:
ui_manager_display_message("WiFi Setting...");
// ... 进入WiFi设置子菜单 ...
break;
case KEY_2:
ui_manager_display_message("Time Setting...");
// ... 进入时间设置子菜单 ...
break;
case KEY_3:
ui_manager_display_message("Display Set...");
// ... 进入显示设置子菜单 ...
break;
case KEY_HASH: // # 键退出菜单
in_menu = false;
ui_manager_display_message("Exit Menu");
vTaskDelay(pdMS_TO_TICKS(1000));
vfd_clear_screen(); // 清空菜单显示
break;
default:
// ... 处理其他菜单按键 ...
break;
}
} else {
// 处理正常显示模式下的按键事件
switch (key) {
case KEY_STAR: // * 键进入菜单
in_menu = true;
ui_manager_show_menu();
break;
default:
// ... 处理其他按键,例如切换显示模式等 ...
break;
}
}
}

(4) 主程序 main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include "app.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG_MAIN = "MAIN";

void app_main(void) {
ESP_LOGI(TAG_MAIN, "Starting WiFi VFD Clock Application...");

if (!clock_app_init()) {
ESP_LOGE(TAG_MAIN, "Clock application initialization failed!");
while(1); // 初始化失败,进入死循环
}

clock_app_run(); // 启动时钟应用主循环

ESP_LOGI(TAG_MAIN, "Application finished (should not reach here)");
}

测试验证与维护升级

测试验证:

  • 单元测试: 针对每个模块进行单元测试,例如测试VFD驱动的显示功能、键盘驱动的扫描功能、时间管理模块的时间同步功能等。可以使用模拟器或者开发板进行单元测试。
  • 集成测试: 将各个模块集成起来进行测试,验证模块之间的协同工作是否正常。例如测试WiFi连接、NTP时间同步、VFD时间显示的完整流程。
  • 系统测试: 进行全面的系统测试,模拟实际使用场景,测试系统的稳定性、可靠性、性能和用户体验。包括长时间运行测试、异常情况测试、用户交互测试等。

维护升级:

  • 模块化设计: 分层模块化的架构使得系统的维护和升级更加容易。修改或添加功能时,只需要修改相应的模块,而不会影响其他模块。
  • 代码注释: 清晰的代码注释可以提高代码的可读性,方便后续的维护人员理解代码逻辑。
  • 版本控制: 使用版本控制系统(例如Git)管理代码,方便代码的版本管理、bug修复和功能升级。
  • OTA升级: 对于嵌入式系统,可以考虑实现OTA (Over-The-Air) 升级功能,方便远程更新固件,修复bug或添加新功能。

总结

以上代码提供了一个基于ESP32-C3、VFD显示模组和矩阵键盘的WiFi时钟系统的完整软件设计框架和C代码实现。 该设计采用了分层模块化的架构,将系统划分为硬件抽象层、服务层和应用层,每个层级包含多个独立的模块,实现了功能的解耦和代码的复用。 代码中包含了GPIO驱动、定时器驱动、WiFi驱动、VFD驱动、键盘驱动等HAL层模块,时间管理、配置管理、WiFi管理、按键处理等服务层模块,以及UI管理和时钟应用等应用层模块。

这套代码框架具备良好的可扩展性和可维护性,可以作为您嵌入式VFD WiFi时钟项目的坚实基础。 在实际应用中,您需要根据具体的硬件连接和VFD模组型号,调整HAL层驱动代码,并根据实际需求完善应用层的功能逻辑,例如添加更多的显示模式、设置选项、用户交互功能等。

希望这份详细的设计方案和代码实现能够帮助您成功开发出可靠、高效、可扩展的嵌入式VFD WiFi时钟系统!

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