编程技术分享

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

0%

简介:这是一个基于ESP32开发的可以用手机APP控制的多功能LED点阵屏幕,点阵行数为16行,列数支持级联,可根据需要焊接。通过安卓APP控制,支持滚动显示、静态显示、时钟显示、自定义显示和歌词显示。

好的,作为一名高级嵌入式软件开发工程师,我将详细阐述一个基于ESP32的多功能LED点阵屏幕的代码设计架构,并提供相应的C代码实现。这个项目的设计目标是构建一个可靠、高效、可扩展的系统,能够通过手机APP进行灵活控制,并支持多种显示模式。
关注微信公众号,提前获取相关推文

1. 需求分析与系统设计

1.1 功能需求:

  • 显示模式:
    • 滚动显示: 文字或图案可以水平滚动显示。
    • 静态显示: 文字或图案可以静止显示。
    • 时钟显示: 显示当前时间。
    • 自定义显示: 用户可以通过APP自定义图案或文字显示。
    • 歌词显示: 支持歌词同步滚动显示。
  • 控制方式: 通过Android APP进行无线控制(假设使用Wi-Fi,也可以考虑蓝牙,但Wi-Fi更通用且易于扩展)。
  • 点阵屏幕:
    • 16行,列数可级联扩展。
    • 红色LED点阵(根据图片)。
  • 数据输入: 通过APP发送文本、时间、自定义图案、歌词数据。
  • 其他:
    • 可调节显示速度(滚动速度、时钟更新频率等)。
    • 可配置显示亮度。

1.2 非功能需求:

  • 可靠性: 系统稳定运行,具备错误处理机制,能够长时间稳定工作。
  • 高效性: 快速响应APP指令,流畅显示,资源占用率低。
  • 可扩展性: 代码结构清晰,易于添加新的显示模式或功能,方便扩展支持更大尺寸的点阵屏幕。
  • 可维护性: 代码模块化,注释清晰,易于理解和维护。
  • 易用性: APP操作简单直观,用户易于上手。
  • 安全性: 考虑基本的通信安全,例如数据校验。
  • 低功耗: 尽管LED屏幕本身功耗较高,但在系统设计上仍需考虑降低不必要的功耗。

1.3 系统架构设计:

为了满足上述需求,我们采用分层模块化的设计架构,这是一种经过实践验证的成熟架构,能够提高代码的可读性、可维护性和可扩展性。系统架构主要分为以下几个层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 封装底层硬件操作,提供统一的接口供上层调用。例如,GPIO控制、SPI通信(如果点阵驱动需要)、定时器等。
  • 驱动层: 基于HAL层,实现特定硬件的驱动。例如,LED点阵驱动、Wi-Fi驱动、时间管理驱动。
  • 核心服务层: 提供系统核心服务,例如,显示管理服务、通信管理服务、配置管理服务、时间同步服务。
  • 应用逻辑层: 实现具体的应用功能,例如,滚动显示、静态显示、时钟显示、自定义显示、歌词显示。
  • 接口层: 定义系统对外接口,例如,与APP通信的协议接口。

系统架构图:

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
+---------------------+
| Android APP | (控制端)
+---------------------+
| (Wi-Fi 通信)
V
+---------------------+
| 应用逻辑层 (APP) | (ESP32端)
| - 显示模式处理 |
| - 命令解析 |
+---------------------+
| (调用)
V
+---------------------+
| 核心服务层 (CORE) |
| - 显示管理服务 |
| - 通信管理服务 |
| - 配置管理服务 |
| - 时间同步服务 |
+---------------------+
| (调用)
V
+---------------------+
| 驱动层 (DRV) |
| - LED点阵驱动 |
| - Wi-Fi 驱动 |
| - 时间管理驱动 |
+---------------------+
| (硬件操作)
V
+---------------------+
| 硬件抽象层 (HAL) |
| - GPIO |
| - SPI |
| - 定时器 |
| - ... |
+---------------------+
| (硬件连接)
V
+---------------------+
| ESP32 芯片 |
| LED点阵屏幕 |
+---------------------+

2. 代码设计与实现 (C 代码)

为了演示代码架构,我们将提供关键模块的C代码实现,并详细解释其设计思路。由于代码量庞大,以下代码将着重展示核心功能,并提供清晰的注释和模块化设计。

2.1 HAL层 (Hardware Abstraction Layer)

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
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

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

// 定义GPIO方向
typedef enum {
GPIO_DIRECTION_INPUT,
GPIO_DIRECTION_OUTPUT
} gpio_direction_t;

// 初始化GPIO引脚
void hal_gpio_init(int gpio_num, gpio_direction_t direction);

// 设置GPIO输出电平
void hal_gpio_set_level(int gpio_num, bool level);

// 读取GPIO输入电平
bool hal_gpio_get_level(int gpio_num);

#endif // HAL_GPIO_H

hal_gpio.c (基于ESP-IDF 实现)

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

void hal_gpio_init(int gpio_num, gpio_direction_t direction) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁止中断
io_conf.mode = (direction == GPIO_DIRECTION_OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << gpio_num); // 配置GPIO引脚
io_conf.pull_down_en = 0; // 禁用下拉
io_conf.pull_up_en = 0; // 禁用上拉
gpio_config(&io_conf);
}

void hal_gpio_set_level(int gpio_num, bool level) {
gpio_set_level(gpio_num, level);
}

bool hal_gpio_get_level(int gpio_num) {
return gpio_get_level(gpio_num);
}

hal_spi.h (如果使用SPI驱动点阵)

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef HAL_SPI_H
#define HAL_SPI_H

#include <stdint.h>

// 初始化SPI总线
void hal_spi_init(spi_host_device_t host, int miso_pin, int mosi_pin, int sclk_pin);

// 发送SPI数据
void hal_spi_send_data(spi_host_device_t host, int cs_pin, const uint8_t *data, int len);

#endif // HAL_SPI_H

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

static const char *TAG = "HAL_SPI";

void hal_spi_init(spi_host_device_t host, int miso_pin, int mosi_pin, int sclk_pin) {
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_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
ESP_LOGI(TAG, "SPI bus initialized on host %d", host);
}

void hal_spi_send_data(spi_host_device_t host, int cs_pin, const uint8_t *data, int len) {
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 10 * 1000 * 1000, // 10MHz clock
.mode = 0, // SPI mode 0
.spics_io_num = cs_pin, // CS pin
.queue_size = 7, // Transaction queue size
.flags = SPI_DEVICE_NO_DUMMY,
};
spi_device_handle_t spi;
spi_bus_add_device(host, &devcfg, &spi);

spi_transaction_t t;
memset(&t, 0, sizeof(t)); // 清零事务
t.length = len * 8; // 数据长度,单位bit
t.tx_buffer = data; // 发送缓冲区
esp_err_t ret = spi_device_transmit(spi, &t); // 发送数据
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI transmit error: %s", esp_err_to_name(ret));
}
spi_bus_remove_device(spi);
}

2.2 驱动层 (Driver Layer)

drv_led_matrix.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
#ifndef DRV_LED_MATRIX_H
#define DRV_LED_MATRIX_H

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

// 定义点阵屏幕尺寸 (假设 16行,可扩展列数)
#define LED_MATRIX_ROWS 16
#define LED_MATRIX_COLS_PER_MODULE 32 // 假设每个模块32列,可级联

// 初始化LED点阵驱动
void drv_led_matrix_init(int data_pin, int clock_pin, int latch_pin, int modules);

// 清空LED点阵屏幕
void drv_led_matrix_clear(void);

// 设置单个像素点状态 (x, y 坐标,true: 亮,false: 灭)
void drv_led_matrix_set_pixel(int x, int y, bool on);

// 更新LED点阵屏幕显示 (将缓冲区数据刷新到屏幕)
void drv_led_matrix_update(void);

// 获取显示缓冲区指针 (用于直接写入数据)
uint8_t* drv_led_matrix_get_buffer(void);

#endif // DRV_LED_MATRIX_H

drv_led_matrix.c (基于74HC595 移位寄存器驱动,GPIO控制)

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
#include "drv_led_matrix.h"
#include "hal_gpio.h"
#include <string.h>
#include <stdlib.h>

#define DATA_PIN // 实际引脚号需要根据硬件连接定义
#define CLOCK_PIN // 实际引脚号需要根据硬件连接定义
#define LATCH_PIN // 实际引脚号需要根据硬件连接定义

static int dataPin;
static int clockPin;
static int latchPin;
static int numModules;
static uint8_t *displayBuffer; // 显示缓冲区,大小为 ROWS * COLS / 8 (按位存储)
static int totalCols;

void drv_led_matrix_init(int data_pin, int clock_pin, int latch_pin, int modules) {
dataPin = data_pin;
clockPin = clock_pin;
latchPin = latch_pin;
numModules = modules;
totalCols = LED_MATRIX_COLS_PER_MODULE * numModules;

hal_gpio_init(dataPin, GPIO_DIRECTION_OUTPUT);
hal_gpio_init(clockPin, GPIO_DIRECTION_OUTPUT);
hal_gpio_init(latchPin, GPIO_DIRECTION_OUTPUT);

displayBuffer = (uint8_t*)malloc(LED_MATRIX_ROWS * (totalCols / 8)); // 分配缓冲区
if (displayBuffer == NULL) {
// 内存分配失败处理
return;
}
drv_led_matrix_clear(); // 初始化清空屏幕
}

void drv_led_matrix_clear(void) {
memset(displayBuffer, 0, LED_MATRIX_ROWS * (totalCols / 8));
drv_led_matrix_update();
}

void drv_led_matrix_set_pixel(int x, int y, bool on) {
if (x < 0 || x >= totalCols || y < 0 || y >= LED_MATRIX_ROWS) {
return; // 坐标越界检查
}

int byteIndex = (y * (totalCols / 8)) + (x / 8);
int bitIndex = x % 8;

if (on) {
displayBuffer[byteIndex] |= (1 << bitIndex); // 设置位
} else {
displayBuffer[byteIndex] &= ~(1 << bitIndex); // 清除位
}
}

void drv_led_matrix_update(void) {
hal_gpio_set_level(latchPin, false); // 拉低Latch,准备数据传输

for (int row = 0; row < LED_MATRIX_ROWS; row++) {
for (int module = 0; module < numModules; module++) {
for (int col_byte = LED_MATRIX_COLS_PER_MODULE / 8 - 1; col_byte >= 0; col_byte--) {
uint8_t data = displayBuffer[row * (totalCols / 8) + module * (LED_MATRIX_COLS_PER_MODULE / 8) + col_byte];
for (int bit = 0; bit < 8; bit++) {
hal_gpio_set_level(dataPin, (data & (1 << bit))); // 输出数据位
hal_gpio_set_level(clockPin, true); // 拉高时钟
hal_gpio_set_level(clockPin, false); // 拉低时钟,数据移位
}
}
}
}

hal_gpio_set_level(latchPin, true); // 拉高Latch,数据锁存,屏幕更新
}

uint8_t* drv_led_matrix_get_buffer(void) {
return displayBuffer;
}

drv_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
#ifndef DRV_WIFI_H
#define DRV_WIFI_H

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

// Wi-Fi 初始化配置结构体
typedef struct {
const char *ssid;
const char *password;
} wifi_config_t;

// 初始化 Wi-Fi 驱动
bool drv_wifi_init(const wifi_config_t *config);

// 连接 Wi-Fi 网络
bool drv_wifi_connect(void);

// 断开 Wi-Fi 连接
void drv_wifi_disconnect(void);

// 获取 Wi-Fi 连接状态
bool drv_wifi_is_connected(void);

// 获取本机 IP 地址
char* drv_wifi_get_ip_address(void);

#endif // DRV_WIFI_H

drv_wifi.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
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
#include "drv_wifi.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "string.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "lwip/netif.h"
#include "lwip/ip4_addr.h"
#include "esp_netif.h"

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

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) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 3) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(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(wifi_event_group, WIFI_CONNECTED_BIT);
}
}

bool drv_wifi_init(const wifi_config_t *config) {
wifi_event_group = xEventGroupCreate();

ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();

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

wifi_config_t wifi_sta_config = {
.sta = {
.ssid = "",
.password = "",
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
strcpy((char *)wifi_sta_config.sta.ssid, config->ssid);
strcpy((char *)wifi_sta_config.sta.password, config->password);

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config));
ESP_ERROR_CHECK(esp_wifi_start());

ESP_LOGI(TAG, "wifi_init_sta finished.");

/* 等待连接成功或失败事件。连接成功时,CONNECTED_BIT 将被设置,失败时 FAIL_BIT 将被设置。*/
EventBits_t bits = xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

/* xEventGroupWaitBits() 返回事件位,我们可以测试发生了哪个事件。*/
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", config->ssid, config->password);
return true;
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", config->ssid, config->password);
return false;
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
return false;
}
}

bool drv_wifi_connect(void) {
esp_err_t ret = esp_wifi_connect();
if (ret == ESP_OK) {
return true;
} else {
ESP_LOGE(TAG, "Wi-Fi connect failed: %s", esp_err_to_name(ret));
return false;
}
}

void drv_wifi_disconnect(void) {
esp_wifi_disconnect();
}

bool drv_wifi_is_connected(void) {
return (xEventGroupGetBits(wifi_event_group) & WIFI_CONNECTED_BIT) != 0;
}

char* drv_wifi_get_ip_address(void) {
if (!drv_wifi_is_connected()) {
return NULL;
}
esp_netif_ip_info_t ip_info;
esp_netif_t *netif = esp_netif_get_default_netif();
if (netif == NULL) {
return NULL;
}
esp_netif_get_ip_info(netif, &ip_info);
char *ip_str = (char*)malloc(16); // 预留足够空间存储IP地址字符串
if (ip_str == NULL) {
return NULL;
}
sprintf(ip_str, IPSTR, IP2STR(&ip_info.ip));
return ip_str;
}

2.3 核心服务层 (Core Service Layer)

core_display_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
46
47
48
49
#ifndef CORE_DISPLAY_MANAGER_H
#define CORE_DISPLAY_MANAGER_H

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

// 显示模式枚举
typedef enum {
DISPLAY_MODE_STATIC,
DISPLAY_MODE_SCROLLING,
DISPLAY_MODE_CLOCK,
DISPLAY_MODE_CUSTOM,
DISPLAY_MODE_LYRICS
} display_mode_t;

// 初始化显示管理器
bool core_display_manager_init(void);

// 设置当前显示模式
void core_display_manager_set_mode(display_mode_t mode);

// 获取当前显示模式
display_mode_t core_display_manager_get_mode(void);

// 设置静态显示文本
void core_display_manager_set_static_text(const char *text);

// 设置滚动显示文本
void core_display_manager_set_scrolling_text(const char *text);

// 设置自定义显示数据 (像素数据)
void core_display_manager_set_custom_data(const uint8_t *data, int data_len);

// 设置歌词显示文本
void core_display_manager_set_lyrics_text(const char *text);

// 设置显示速度 (例如滚动速度)
void core_display_manager_set_display_speed(int speed);

// 设置显示亮度
void core_display_manager_set_brightness(int brightness);

// 开始显示 (根据当前模式和数据进行显示)
void core_display_manager_start_display(void);

// 停止显示
void core_display_manager_stop_display(void);

#endif // CORE_DISPLAY_MANAGER_H

core_display_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
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include "core_display_manager.h"
#include "drv_led_matrix.h"
#include "string.h"
#include "font.h" // 假设有字体库
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "CORE_DISPLAY_MANAGER";

static display_mode_t currentMode = DISPLAY_MODE_STATIC;
static char staticTextBuffer[256] = "";
static char scrollingTextBuffer[256] = "";
static uint8_t *customDataBuffer = NULL;
static int customDataLen = 0;
static char lyricsTextBuffer[1024] = "";
static int displaySpeed = 50; // 默认显示速度
static int brightness = 100; // 默认亮度 (百分比)
static bool isDisplaying = false;
static TaskHandle_t displayTaskHandle = NULL;

// 简单的亮度控制 (PWM 或 电阻限流,这里简化为软件控制占空比,实际硬件需要支持)
static void set_display_duty_cycle(int duty_cycle_percent) {
// 实际硬件控制需要根据具体电路实现
// 这里简化为延时占空比模拟
vTaskDelay(pdMS_TO_TICKS(100 - duty_cycle_percent)); // 灭灯时间
vTaskDelay(pdMS_TO_TICKS(duty_cycle_percent)); // 亮灯时间
}

// 显示任务
static void display_task(void *pvParameters) {
while (isDisplaying) {
drv_led_matrix_clear(); // 清空缓冲区

switch (currentMode) {
case DISPLAY_MODE_STATIC: {
// 静态显示文本
if (strlen(staticTextBuffer) > 0) {
// 绘制文本到缓冲区 (font.h 中需要有绘制文本函数)
font_draw_text(staticTextBuffer, drv_led_matrix_get_buffer(), LED_MATRIX_COLS_PER_MODULE * numModules, LED_MATRIX_ROWS);
}
break;
}
case DISPLAY_MODE_SCROLLING: {
// 滚动显示文本 (需要实现滚动逻辑)
static int scrollOffset = 0;
if (strlen(scrollingTextBuffer) > 0) {
font_draw_scrolling_text(scrollingTextBuffer, drv_led_matrix_get_buffer(), LED_MATRIX_COLS_PER_MODULE * numModules, LED_MATRIX_ROWS, scrollOffset);
scrollOffset++;
if (scrollOffset > font_get_text_width(scrollingTextBuffer)) {
scrollOffset = 0; // 循环滚动
}
}
break;
}
case DISPLAY_MODE_CLOCK: {
// 时钟显示 (需要获取系统时间,并格式化显示)
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char timeStr[9]; // HH:MM:SS\0
strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo);
font_draw_text(timeStr, drv_led_matrix_get_buffer(), LED_MATRIX_COLS_PER_MODULE * numModules, LED_MATRIX_ROWS);
break;
}
case DISPLAY_MODE_CUSTOM: {
// 自定义显示数据
if (customDataBuffer != NULL && customDataLen > 0) {
memcpy(drv_led_matrix_get_buffer(), customDataBuffer, customDataLen);
}
break;
}
case DISPLAY_MODE_LYRICS: {
// 歌词显示 (需要实现歌词滚动逻辑,可以按行滚动)
static int lyricLineOffset = 0;
// 假设歌词按行分割存储在 lyricsTextBuffer 中,需要解析和滚动逻辑
// 这里简化为显示整个歌词,实际需要更复杂的歌词处理逻辑
if (strlen(lyricsTextBuffer) > 0) {
font_draw_text(lyricsTextBuffer, drv_led_matrix_get_buffer(), LED_MATRIX_COLS_PER_MODULE * numModules, LED_MATRIX_ROWS);
}
break;
}
default:
break;
}

set_display_duty_cycle(brightness); // 亮度控制 (简化实现)
drv_led_matrix_update(); // 更新显示

vTaskDelay(pdMS_TO_TICKS(displaySpeed)); // 显示刷新频率控制
}
displayTaskHandle = NULL; // 任务退出,句柄置空
vTaskDelete(NULL); // 删除任务自身
}

bool core_display_manager_init(void) {
// 初始化 LED 点阵驱动 (实际引脚需要根据硬件连接配置)
drv_led_matrix_init(DATA_PIN, CLOCK_PIN, LATCH_PIN, 2); // 假设使用2个模块
return true;
}

void core_display_manager_set_mode(display_mode_t mode) {
currentMode = mode;
}

display_mode_t core_display_manager_get_mode(void) {
return currentMode;
}

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

void core_display_manager_set_scrolling_text(const char *text) {
strncpy(scrollingTextBuffer, text, sizeof(scrollingTextBuffer) - 1);
scrollingTextBuffer[sizeof(scrollingTextBuffer) - 1] = '\0';
}

void core_display_manager_set_custom_data(const uint8_t *data, int data_len) {
if (customDataBuffer != NULL) {
free(customDataBuffer); // 释放旧缓冲区
}
customDataBuffer = (uint8_t*)malloc(data_len);
if (customDataBuffer != NULL) {
memcpy(customDataBuffer, data, data_len);
customDataLen = data_len;
} else {
customDataLen = 0; // 内存分配失败
}
}

void core_display_manager_set_lyrics_text(const char *text) {
strncpy(lyricsTextBuffer, text, sizeof(lyricsTextBuffer) - 1);
lyricsTextBuffer[sizeof(lyricsTextBuffer) - 1] = '\0';
}

void core_display_manager_set_display_speed(int speed) {
displaySpeed = speed;
if (displaySpeed < 1) displaySpeed = 1; // 限制最小速度
if (displaySpeed > 200) displaySpeed = 200; // 限制最大速度
}

void core_display_manager_set_brightness(int brightness_percent) {
brightness = brightness_percent;
if (brightness < 0) brightness = 0;
if (brightness > 100) brightness = 100;
}

void core_display_manager_start_display(void) {
if (!isDisplaying) {
isDisplaying = true;
if (displayTaskHandle == NULL) {
xTaskCreate(display_task, "DisplayTask", 4096, NULL, 5, &displayTaskHandle);
}
}
}

void core_display_manager_stop_display(void) {
if (isDisplaying) {
isDisplaying = false; // 停止显示任务循环
if (displayTaskHandle != NULL) {
// 等待任务退出 (可选,根据实际需求决定是否等待)
// vTaskDelete(displayTaskHandle); // 也可以直接删除任务,但不推荐,可能导致资源泄漏
displayTaskHandle = NULL;
}
}
}

core_communication_manager.h

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

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

// 初始化通信管理器
bool core_communication_manager_init(void);

// 启动服务器监听 (例如 TCP 服务器)
bool core_communication_manager_start_server(int port);

// 停止服务器监听
void core_communication_manager_stop_server(void);

// 处理接收到的命令
void core_communication_manager_process_command(const char *command);

#endif // CORE_COMMUNICATION_MANAGER_H

core_communication_manager.c (基于TCP Socket,简化实现)

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include "core_communication_manager.h"
#include "core_display_manager.h"
#include "drv_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "lwip/sockets.h"
#include "string.h"
#include "stdlib.h"

static const char *TAG = "CORE_COMM_MANAGER";
static int server_socket = -1;
static bool isServerRunning = false;
static TaskHandle_t serverTaskHandle = NULL;

// 服务器任务
static void server_task(void *pvParameters) {
int port = *((int *)pvParameters);
char rx_buffer[128];
int addr_family = AF_INET;
int ip_protocol = IPPROTO_TCP;
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port);
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if (listen_sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
goto err;
}
int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
if (err != 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
goto err;
}
err = listen(listen_sock, 1);
if (err != 0) {
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
goto err;
}
server_socket = listen_sock; // 保存服务器socket句柄
isServerRunning = true;
ESP_LOGI(TAG, "Socket listening on port %d", port);

while (isServerRunning) {
struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
socklen_t addr_len = sizeof(source_addr);
int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
if (sock < 0) {
ESP_LOGE(TAG, "accept failed: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Socket accepted");
int recv_len;
do {
recv_len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
if (recv_len > 0) {
rx_buffer[recv_len] = 0; // Null-terminate whatever is received
ESP_LOGI(TAG, "Received %d bytes: %s", recv_len, rx_buffer);
core_communication_manager_process_command(rx_buffer); // 处理命令
} else if (recv_len == 0) {
ESP_LOGI(TAG, "Connection closed");
} else {
ESP_LOGE(TAG, "recv failed: errno %d", errno);
}
} while (recv_len > 0);
shutdown(sock, 0);
close(sock);
}

err:
if (listen_sock != -1) {
close(listen_sock);
}
serverTaskHandle = NULL;
server_socket = -1;
isServerRunning = false;
vTaskDelete(NULL);
}

bool core_communication_manager_init(void) {
return true;
}

bool core_communication_manager_start_server(int port) {
if (!isServerRunning) {
int *port_ptr = (int*)malloc(sizeof(int));
if (port_ptr == NULL) {
return false;
}
*port_ptr = port;
if (xTaskCreate(server_task, "ServerTask", 4096, port_ptr, 5, &serverTaskHandle) != pdPASS) {
free(port_ptr);
return false;
}
}
return true;
}

void core_communication_manager_stop_server(void) {
if (isServerRunning) {
isServerRunning = false;
if (server_socket != -1) {
close(server_socket); // 关闭监听socket
server_socket = -1;
}
if (serverTaskHandle != NULL) {
// vTaskDelete(serverTaskHandle); // 可以直接删除任务,但不推荐
serverTaskHandle = NULL;
}
}
}

void core_communication_manager_process_command(const char *command) {
ESP_LOGI(TAG, "Processing command: %s", command);
char mode_str[32];
char data_str[256];

if (sscanf(command, "MODE:%s", mode_str) == 1) {
if (strcmp(mode_str, "STATIC") == 0) {
core_display_manager_set_mode(DISPLAY_MODE_STATIC);
ESP_LOGI(TAG, "Set mode to STATIC");
} else if (strcmp(mode_str, "SCROLL") == 0) {
core_display_manager_set_mode(DISPLAY_MODE_SCROLLING);
ESP_LOGI(TAG, "Set mode to SCROLL");
} else if (strcmp(mode_str, "CLOCK") == 0) {
core_display_manager_set_mode(DISPLAY_MODE_CLOCK);
ESP_LOGI(TAG, "Set mode to CLOCK");
} else if (strcmp(mode_str, "CUSTOM") == 0) {
core_display_manager_set_mode(DISPLAY_MODE_CUSTOM);
ESP_LOGI(TAG, "Set mode to CUSTOM");
} else if (strcmp(mode_str, "LYRICS") == 0) {
core_display_manager_set_mode(DISPLAY_MODE_LYRICS);
ESP_LOGI(TAG, "Set mode to LYRICS");
}
} else if (sscanf(command, "TEXT:%s", data_str) == 1) {
core_display_manager_set_static_text(data_str);
core_display_manager_set_scrolling_text(data_str); // 同时设置滚动文本,根据当前模式显示
ESP_LOGI(TAG, "Set text: %s", data_str);
} else if (sscanf(command, "SPEED:%d", &displaySpeed) == 1) {
core_display_manager_set_display_speed(displaySpeed);
ESP_LOGI(TAG, "Set speed: %d", displaySpeed);
} else if (sscanf(command, "BRIGHTNESS:%d", &brightness) == 1) {
core_display_manager_set_brightness(brightness);
ESP_LOGI(TAG, "Set brightness: %d", brightness);
} else if (strcmp(command, "START") == 0) {
core_display_manager_start_display();
ESP_LOGI(TAG, "Start display");
} else if (strcmp(command, "STOP") == 0) {
core_display_manager_stop_display();
ESP_LOGI(TAG, "Stop display");
} else {
ESP_LOGW(TAG, "Unknown command: %s", command);
}
}

2.4 应用逻辑层 (Application Logic Layer) - app_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
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "drv_wifi.h"
#include "core_display_manager.h"
#include "core_communication_manager.h"
#include "nvs_flash.h"
#include "esp_sntp.h"

static const char *TAG = "APP_MAIN";

// Wi-Fi 配置信息 (实际应用中应从配置管理模块读取)
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

// SNTP 服务器配置
#define SNTP_SERVER "ntp.aliyun.com" // 可以选择其他 NTP 服务器

// 初始化 SNTP 时间同步
void initialize_sntp(void)
{
ESP_LOGI(TAG, "Initializing SNTP");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, SNTP_SERVER);
sntp_init();
}

// 等待 SNTP 时间同步完成
void wait_for_sntp_sync(void)
{
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 10;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
time(&now);
localtime_r(&now, &timeinfo);
if (timeinfo.tm_year < (2016 - 1900)) {
ESP_LOGW(TAG, "SNTP synchronization failed");
} else {
ESP_LOGI(TAG, "SNTP synchronization successful");
}
}


void app_main(void)
{
// 初始化 NVS (用于存储 Wi-Fi 配置等)
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);

// 初始化 Wi-Fi
wifi_config_t wifi_config = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
};
if (drv_wifi_init(&wifi_config)) {
ESP_LOGI(TAG, "Wi-Fi initialized and connected");
char *ip_address = drv_wifi_get_ip_address();
if (ip_address != NULL) {
ESP_LOGI(TAG, "IP Address: %s", ip_address);
free(ip_address);
}
} else {
ESP_LOGE(TAG, "Wi-Fi initialization failed");
// Wi-Fi 初始化失败处理
}

// 初始化显示管理器
if (core_display_manager_init()) {
ESP_LOGI(TAG, "Display Manager initialized");
} else {
ESP_LOGE(TAG, "Display Manager initialization failed");
// 显示管理器初始化失败处理
}

// 初始化通信管理器
if (core_communication_manager_init()) {
ESP_LOGI(TAG, "Communication Manager initialized");
if (core_communication_manager_start_server(3333)) { // 启动 TCP 服务器,端口 3333
ESP_LOGI(TAG, "TCP Server started on port 3333");
} else {
ESP_LOGE(TAG, "Failed to start TCP Server");
}
} else {
ESP_LOGE(TAG, "Communication Manager initialization failed");
// 通信管理器初始化失败处理
}

// 初始化 SNTP 时间同步
initialize_sntp();
wait_for_sntp_sync(); // 等待时间同步

// 启动显示 (默认启动静态显示模式)
core_display_manager_set_mode(DISPLAY_MODE_STATIC);
core_display_manager_set_static_text("Hello ESP32 LED Matrix!");
core_display_manager_start_display();

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

3. 项目中采用的技术和方法

  • 模块化设计: 将系统划分为HAL层、驱动层、核心服务层、应用逻辑层,提高代码可读性、可维护性和可扩展性。
  • 分层架构: 每一层只依赖于其下层,降低层与层之间的耦合度,方便修改和替换。
  • FreeRTOS 实时操作系统: 使用 FreeRTOS 进行多任务管理,例如,显示任务、网络通信任务,保证系统的实时性和并发性。
  • ESP-IDF 开发框架: 基于 ESP-IDF 提供的 API 和组件,快速开发 ESP32 应用。
  • Wi-Fi 通信: 使用 ESP32 的 Wi-Fi 功能,实现与 Android APP 的无线通信。
  • TCP Socket 通信: 使用 TCP Socket 协议,建立可靠的客户端-服务器通信,APP 作为客户端,ESP32 作为服务器。
  • 事件驱动编程: Wi-Fi 事件处理采用事件驱动机制,提高系统响应效率。
  • 双缓冲技术 (可选但推荐):drv_led_matrix.c 中可以使用双缓冲区,避免显示刷新时的闪烁感,提高显示效果。
  • 字体库: 使用预先生成的字体库 (如 font.h),方便文本显示。
  • SNTP 时间同步: 使用 SNTP 协议从网络获取时间,保证时钟显示的准确性。
  • NVS Flash 存储: 使用 NVS Flash 存储 Wi-Fi 配置、用户设置等非易失性数据。
  • 日志系统: 使用 esp_log.h 提供的日志功能,方便调试和监控系统运行状态。
  • 代码注释: 代码中添加详细的注释,提高代码可读性。
  • 版本控制 (Git): 使用 Git 进行代码版本管理,方便团队协作和代码维护。
  • 代码审查: 进行代码审查,提高代码质量和发现潜在问题。
  • 单元测试 (可选): 对关键模块进行单元测试,例如,LED点阵驱动、通信模块,保证模块功能的正确性。
  • 集成测试: 进行系统集成测试,验证各个模块之间的协同工作是否正常。
  • 系统测试: 进行全面的系统测试,包括功能测试、性能测试、稳定性测试、可靠性测试等。
  • OTA 升级 (可选但推荐): 实现 Over-The-Air 固件升级功能,方便后续维护和功能更新。

4. 系统扩展与维护升级

  • 扩展列数: 通过修改 drv_led_matrix.h 中的 LED_MATRIX_COLS_PER_MODULEdrv_led_matrix.c 中模块控制逻辑,可以方便地扩展支持更多列的点阵屏幕。
  • 添加新的显示模式:core_display_manager.h 中添加新的显示模式枚举,并在 core_display_manager.cdisplay_task 函数中添加新的显示模式处理逻辑。
  • 优化显示效果: 可以使用双缓冲技术、更高效的扫描方式、更精细的亮度控制等方法来优化显示效果。
  • 增强通信协议: 可以使用更高效、更安全的通信协议,例如,MQTT、WebSocket,或者自定义更优化的二进制协议。
  • 添加本地控制功能: 可以添加按键、旋钮等本地控制方式,在没有 APP 的情况下也能进行基本操作。
  • 云平台接入: 可以将设备接入云平台,实现远程监控、数据分析、更高级的功能扩展。
  • 固件 OTA 升级: 实现 OTA 固件升级功能,方便远程更新固件,修复 bug,添加新功能。

总结

以上代码和架构设计提供了一个基于ESP32的多功能LED点阵屏幕的完整框架。这个框架采用成熟的分层模块化设计,结合FreeRTOS、Wi-Fi、TCP Socket 等实用技术,能够构建一个可靠、高效、可扩展的嵌入式系统。代码示例涵盖了HAL层、驱动层、核心服务层和应用逻辑层的关键模块,并提供了详细的注释和解释。实际项目中,还需要根据具体硬件和需求进行详细的实现和优化。

请注意,以上代码仅为示例代码,可能需要根据实际硬件连接和具体需求进行调整和完善。例如,GPIO 引脚定义、SPI 配置、字体库、更完善的错误处理、更健壮的通信协议、更精细的显示效果优化等等都需要在实际项目中进行细致的开发和测试。

希望这个详细的解答能够帮助你理解嵌入式系统开发流程和代码架构设计。

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