编程技术分享

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

0%

简介:时钟 天气 阅读 图片

好的,作为一名高级嵌入式软件开发工程师,我将针对你提供的嵌入式产品图片,详细阐述一个可靠、高效、可扩展的嵌入式系统平台代码设计架构,并用具体的C代码实现时钟、天气、阅读和图片显示这四个核心功能。我的目标是创建一个模块化、易于维护和升级的系统,并确保代码的实用性和可验证性。
关注微信公众号,提前获取相关推文

系统架构设计

为了构建一个可靠、高效且可扩展的嵌入式系统,我将采用分层架构的设计模式。这种架构将系统分解为多个独立的层,每一层都有明确的职责,并通过定义良好的接口与其他层进行交互。这种分层设计的好处包括:

  • 模块化: 每个功能模块独立开发和测试,降低了系统的复杂性。
  • 可维护性: 修改一个模块的代码,不会轻易影响其他模块,方便维护和升级。
  • 可重用性: 底层模块可以被多个上层模块复用,提高代码效率。
  • 可扩展性: 新增功能模块只需在架构上增加新的层或模块,不会对现有系统造成大的改动。

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

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

    • 职责: 直接与硬件交互,封装底层硬件操作细节,向上层提供统一的硬件访问接口。
    • 模块: GPIO驱动、显示驱动 (E-Paper 电子墨水屏驱动)、RTC (实时时钟) 驱动、传感器驱动 (温湿度传感器等,如果需要本地天气)、存储驱动 (Flash/SD卡驱动,用于图片和阅读内容存储)、按键驱动等。
    • 目标: 实现硬件的平台无关性,方便系统移植到不同的硬件平台。
  2. 操作系统层 (OSAL - Operating System Abstraction Layer) 或 系统服务层:

    • 职责: 提供操作系统级别的服务,如任务调度、内存管理、定时器管理、中断管理、线程/进程管理 (如果使用RTOS)。 如果是裸机系统,则实现一个简化的任务调度和定时器服务。
    • 模块: 任务调度器 (裸机或 RTOS)、定时器服务、内存管理 (静态或动态内存分配)、中断服务管理。
    • 目标: 为上层应用提供基础的系统服务,简化应用开发。
  3. 中间件层 (Middleware Layer) 或 服务层:

    • 职责: 提供通用的、与具体应用无关的服务,构建在操作系统层之上,为应用层提供更高级别的功能。
    • 模块: 文件系统服务 (用于文件读写操作,例如图片和文本文件)、网络服务 (如果需要联网获取天气信息)、UI 框架 (简易的图形界面框架,用于界面绘制和事件处理)、数据解析服务 (例如 JSON 解析,用于天气数据解析)。
    • 目标: 提供可重用的服务组件,减少应用层开发的工作量。
  4. 应用层 (Application Layer):

    • 职责: 实现具体的应用功能,例如时钟显示、天气显示、阅读功能、图片显示功能。
    • 模块: 时钟应用模块、天气应用模块、阅读应用模块、图片应用模块、主界面应用模块、配置管理模块。
    • 目标: 实现用户可见的功能,直接与用户交互。
  5. 表示层 (Presentation Layer) 或 UI 层:

    • 职责: 负责用户界面的显示和用户交互逻辑,将应用层的数据以友好的方式呈现给用户。
    • 模块: 界面元素 (文本框、图标、图片显示区域等)、布局管理、触摸/按键事件处理。
    • 目标: 提供良好的用户体验。

代码实现 (C语言)

接下来,我将详细展示每个层次的代码实现,并力求代码的清晰、模块化和可读性。由于篇幅限制,我会提供核心代码框架和关键函数的实现,并对一些重要的技术和方法进行详细解释。

1. 硬件抽象层 (HAL)

  • hal_gpio.h (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
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

typedef enum {
GPIO_PIN_0,
GPIO_PIN_1,
GPIO_PIN_2,
// ... 更多 GPIO 引脚定义
GPIO_PIN_MAX
} 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 引脚
void hal_gpio_init(gpio_pin_t pin, gpio_mode_t mode);

// 设置 GPIO 输出电平
void hal_gpio_write(gpio_pin_t pin, gpio_level_t level);

// 读取 GPIO 输入电平
gpio_level_t hal_gpio_read(gpio_pin_t pin);

#endif // HAL_GPIO_H
  • hal_gpio.c (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
#include "hal_gpio.h"
// ... 包含具体的硬件头文件,例如 STM32 的头文件

void hal_gpio_init(gpio_pin_t pin, gpio_mode_t mode) {
// ... 根据 pin 和 mode 配置 GPIO 寄存器
// 例如,配置 GPIO 端口和引脚为输入或输出模式
// 设置上拉/下拉电阻等
if (mode == GPIO_MODE_OUTPUT) {
// ... 设置为输出模式
} else {
// ... 设置为输入模式
}
}

void hal_gpio_write(gpio_pin_t pin, gpio_level_t level) {
// ... 根据 pin 和 level 设置 GPIO 输出电平
if (level == GPIO_LEVEL_HIGH) {
// ... 设置为高电平
} else {
// ... 设置为低电平
}
}

gpio_level_t hal_gpio_read(gpio_pin_t pin) {
// ... 读取 GPIO 输入电平
// ... 返回 GPIO_LEVEL_HIGH 或 GPIO_LEVEL_LOW
return GPIO_LEVEL_LOW; // 示例,实际需要读取寄存器
}
  • hal_display.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
#ifndef HAL_DISPLAY_H
#define HAL_DISPLAY_H

#include <stdint.h>

// 屏幕尺寸 (假设为 200x200)
#define DISPLAY_WIDTH 200
#define DISPLAY_HEIGHT 200

// 颜色定义 (黑白屏)
#define COLOR_BLACK 0x00
#define COLOR_WHITE 0xFF

// 初始化显示屏
void hal_display_init(void);

// 清空屏幕
void hal_display_clear(uint8_t color);

// 设置像素点
void hal_display_set_pixel(uint16_t x, uint16_t y, uint8_t color);

// 刷新显示 (将缓冲区数据刷新到屏幕)
void hal_display_flush(void);

// 绘制文本
void hal_display_draw_text(uint16_t x, uint16_t y, const char *text, uint8_t color);

// 绘制图片 (假设图片数据格式为单色位图)
void hal_display_draw_image(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *imageData);

#endif // HAL_DISPLAY_H
  • hal_display.c (显示驱动实现文件 - 电子墨水屏 - 示例,需要根据具体电子墨水屏型号和驱动 IC 实现)
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
#include "hal_display.h"
#include "hal_gpio.h" // 假设显示屏控制引脚使用 GPIO

// ... 定义电子墨水屏控制引脚,例如 RST, DC, CS, BUSY
#define EPD_RST_PIN GPIO_PIN_0
#define EPD_DC_PIN GPIO_PIN_1
#define EPD_CS_PIN GPIO_PIN_2
#define EPD_BUSY_PIN GPIO_PIN_3

// 帧缓冲区 (用于存放要显示的数据)
static uint8_t display_buffer[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8]; // 单色屏,每 8 个像素点用 1 字节表示

void hal_display_init(void) {
// 初始化 GPIO 引脚
hal_gpio_init(EPD_RST_PIN, GPIO_MODE_OUTPUT);
hal_gpio_init(EPD_DC_PIN, GPIO_MODE_OUTPUT);
hal_gpio_init(EPD_CS_PIN, GPIO_MODE_OUTPUT);
hal_gpio_init(EPD_BUSY_PIN, GPIO_MODE_INPUT);

// ... 初始化电子墨水屏驱动 IC
// 例如,发送初始化命令序列,设置屏幕参数等
// ... 复位电子墨水屏 (通过 RST 引脚)
// ... 发送初始化命令 (根据电子墨水屏驱动 IC 数据手册)
}

void hal_display_clear(uint8_t color) {
// 用指定颜色填充帧缓冲区
for (int i = 0; i < sizeof(display_buffer); i++) {
display_buffer[i] = (color == COLOR_BLACK) ? 0x00 : 0xFF; // 黑屏或白屏
}
}

void hal_display_set_pixel(uint16_t x, uint16_t y, uint8_t color) {
if (x < DISPLAY_WIDTH && y < DISPLAY_HEIGHT) {
uint32_t byteIndex = (y * DISPLAY_WIDTH + x) / 8;
uint8_t bitIndex = x % 8;
if (color == COLOR_BLACK) {
display_buffer[byteIndex] &= ~(1 << (7 - bitIndex)); // 设置为黑色
} else {
display_buffer[byteIndex] |= (1 << (7 - bitIndex)); // 设置为白色
}
}
}

void hal_display_flush(void) {
// ... 将帧缓冲区数据写入电子墨水屏驱动 IC,并触发刷新
// 例如,发送数据命令,然后发送帧缓冲区数据
// ... 等待 BUSY 引脚变为低电平,表示刷新完成
}

void hal_display_draw_text(uint16_t x, uint16_t y, const char *text, uint8_t color) {
// 简易文本绘制,实际应用中可以使用字库或更复杂的字体渲染库
// 这里只是一个示例,假设每个字符是 8x16 像素
// ... 遍历文本字符串,逐个字符绘制,可以使用简单的点阵字体数据
// ... 需要实现一个简单的点阵字体数据,例如 ASCII 字符的 8x16 点阵
// ... 循环处理每个字符,调用 hal_display_set_pixel 绘制每个像素点
}

void hal_display_draw_image(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *imageData) {
// 绘制单色位图图像
for (uint16_t row = 0; row < height; row++) {
for (uint16_t col = 0; col < width; col++) {
uint8_t pixelColor = (imageData[(row * width + col) / 8] & (1 << (7 - (col % 8)))) ? COLOR_WHITE : COLOR_BLACK; // 从位图中读取像素颜色
hal_display_set_pixel(x + col, y + row, pixelColor);
}
}
}
  • hal_rtc.h (RTC 驱动头文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef HAL_RTC_H
#define HAL_RTC_H

#include <stdint.h>

typedef struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} rtc_time_t;

// 初始化 RTC
void hal_rtc_init(void);

// 获取当前时间
rtc_time_t hal_rtc_get_time(void);

// 设置时间
void hal_rtc_set_time(rtc_time_t time);

#endif // HAL_RTC_H
  • hal_rtc.c (RTC 驱动实现文件 - 示例,需要根据具体 RTC 芯片或 MCU 内置 RTC 实现)
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
#include "hal_rtc.h"
// ... 包含具体的硬件头文件,例如 I2C 驱动头文件 (如果 RTC 通过 I2C 通信)

void hal_rtc_init(void) {
// ... 初始化 RTC 芯片或 MCU 内置 RTC 模块
// 例如,配置 I2C 通信,初始化 RTC 寄存器
}

rtc_time_t hal_rtc_get_time(void) {
rtc_time_t currentTime;
// ... 从 RTC 寄存器读取时间数据
// ... 将读取到的数据转换为 rtc_time_t 结构体
currentTime.year = 2024; // 示例,实际需要从 RTC 读取
currentTime.month = 5;
currentTime.day = 20;
currentTime.hour = 10;
currentTime.minute = 30;
currentTime.second = 0;
return currentTime;
}

void hal_rtc_set_time(rtc_time_t time) {
// ... 将 rtc_time_t 结构体的时间数据写入 RTC 寄存器
// ... 需要将时间数据转换为 RTC 芯片或 MCU 期望的格式
}

2. 操作系统层 (OSAL) / 系统服务层

由于这是一个相对简单的嵌入式系统,为了简化,我这里不引入复杂的 RTOS,而是实现一个简易的基于定时器中断的任务调度器和定时器服务。

  • osal_timer.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
#ifndef OSAL_TIMER_H
#define OSAL_TIMER_H

#include <stdint.h>

typedef void (*timer_callback_t)(void);

typedef struct {
uint32_t interval_ms; // 定时器间隔 (毫秒)
timer_callback_t callback; // 定时器回调函数
uint32_t last_tick_ms; // 上次触发时间 (用于计算下次触发时间)
bool is_running; // 定时器是否运行
} timer_t;

// 初始化定时器服务
void osal_timer_init(void);

// 创建定时器
timer_t osal_timer_create(uint32_t interval_ms, timer_callback_t callback);

// 启动定时器
void osal_timer_start(timer_t *timer);

// 停止定时器
void osal_timer_stop(timer_t *timer);

// 定时器服务 Tick 处理函数,需要在系统 Tick 中周期性调用 (例如 1ms 或 10ms)
void osal_timer_tick(void);

#endif // OSAL_TIMER_H
  • osal_timer.c (定时器服务实现文件 - 简易版本,基于系统 Tick)
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
#include "osal_timer.h"
#include <stdbool.h>

#define MAX_TIMERS 4 // 最大定时器数量 (根据实际需求调整)
static timer_t timers[MAX_TIMERS];
static uint32_t system_tick_ms = 0; // 系统 Tick 计数器 (毫秒)

void osal_timer_init(void) {
for (int i = 0; i < MAX_TIMERS; i++) {
timers[i].is_running = false;
}
system_tick_ms = 0;
// ... 初始化系统 Tick 定时器 (例如使用 MCU 的 SysTick 定时器)
// ... 设置定时器中断,中断服务函数中调用 osal_timer_tick()
}

timer_t osal_timer_create(uint32_t interval_ms, timer_callback_t callback) {
timer_t timer = {0};
timer.interval_ms = interval_ms;
timer.callback = callback;
timer.is_running = false;
return timer;
}

void osal_timer_start(timer_t *timer) {
timer->is_running = true;
timer->last_tick_ms = system_tick_ms;
}

void osal_timer_stop(timer_t *timer) {
timer->is_running = false;
}

void osal_timer_tick(void) {
system_tick_ms++;
for (int i = 0; i < MAX_TIMERS; i++) {
if (timers[i].is_running && (system_tick_ms - timers[i].last_tick_ms >= timers[i].interval_ms)) {
timers[i].last_tick_ms = system_tick_ms;
if (timers[i].callback != NULL) {
timers[i].callback(); // 执行回调函数
}
}
}
}

// 系统 Tick 中断服务函数 (示例,需要根据具体 MCU 实现)
void SysTick_Handler(void) {
osal_timer_tick(); // 调用定时器服务 Tick 处理函数
}

3. 中间件层 (Middleware Layer) / 服务层

  • middleware_ui.h (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
#ifndef MIDDLEWARE_UI_H
#define MIDDLEWARE_UI_H

#include <stdint.h>

// UI 元素类型
typedef enum {
UI_ELEMENT_TYPE_TEXT,
UI_ELEMENT_TYPE_IMAGE,
UI_ELEMENT_TYPE_RECT,
// ... 可以扩展更多 UI 元素类型
} ui_element_type_t;

// UI 元素结构体
typedef struct ui_element_s ui_element_t;
struct ui_element_s {
ui_element_type_t type;
uint16_t x, y;
uint16_t width, height;
uint8_t color; // 前景色
uint8_t bgcolor; // 背景色 (可选)
void *data; // 指向元素数据的指针 (例如文本字符串或图片数据)
ui_element_t *next; // 指向下一个 UI 元素,用于链表管理
};

// 初始化 UI 框架
void middleware_ui_init(void);

// 创建 UI 元素
ui_element_t* middleware_ui_create_element(ui_element_type_t type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t color, void *data);

// 添加 UI 元素到显示列表
void middleware_ui_add_element(ui_element_t *element);

// 移除 UI 元素
void middleware_ui_remove_element(ui_element_t *element);

// 绘制 UI 界面 (遍历显示列表,绘制所有元素)
void middleware_ui_draw_ui(void);

// 绘制文本元素
void middleware_ui_draw_text_element(ui_element_t *element);

// 绘制图像元素
void middleware_ui_draw_image_element(ui_element_t *element);

// 绘制矩形元素
void middleware_ui_draw_rect_element(ui_element_t *element);

#endif // MIDDLEWARE_UI_H
  • middleware_ui.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
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
#include "middleware_ui.h"
#include "hal_display.h" // 使用 HAL 显示驱动
#include <stdlib.h> // 包含 malloc 和 free

static ui_element_t *ui_element_list_head = NULL; // UI 元素链表头指针

void middleware_ui_init(void) {
ui_element_list_head = NULL;
}

ui_element_t* middleware_ui_create_element(ui_element_type_t type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t color, void *data) {
ui_element_t *element = (ui_element_t*)malloc(sizeof(ui_element_t));
if (element != NULL) {
element->type = type;
element->x = x;
element->y = y;
element->width = width;
element->height = height;
element->color = color;
element->data = data;
element->next = NULL;
}
return element;
}

void middleware_ui_add_element(ui_element_t *element) {
if (element == NULL) return;
element->next = ui_element_list_head;
ui_element_list_head = element;
}

void middleware_ui_remove_element(ui_element_t *element) {
if (element == NULL) return;
if (ui_element_list_head == element) {
ui_element_list_head = element->next;
free(element);
return;
}
ui_element_t *current = ui_element_list_head;
while (current != NULL && current->next != element) {
current = current->next;
}
if (current != NULL && current->next == element) {
current->next = element->next;
free(element);
}
}

void middleware_ui_draw_ui(void) {
hal_display_clear(COLOR_WHITE); // 清空屏幕背景色为白色
ui_element_t *current = ui_element_list_head;
while (current != NULL) {
switch (current->type) {
case UI_ELEMENT_TYPE_TEXT:
middleware_ui_draw_text_element(current);
break;
case UI_ELEMENT_TYPE_IMAGE:
middleware_ui_draw_image_element(current);
break;
case UI_ELEMENT_TYPE_RECT:
middleware_ui_draw_rect_element(current);
break;
// ... 处理更多 UI 元素类型
default:
break;
}
current = current->next;
}
hal_display_flush(); // 刷新显示
}

void middleware_ui_draw_text_element(ui_element_t *element) {
if (element != NULL && element->type == UI_ELEMENT_TYPE_TEXT && element->data != NULL) {
hal_display_draw_text(element->x, element->y, (const char*)element->data, element->color);
}
}

void middleware_ui_draw_image_element(ui_element_t *element) {
if (element != NULL && element->type == UI_ELEMENT_TYPE_IMAGE && element->data != NULL) {
hal_display_draw_image(element->x, element->y, element->width, element->height, (const uint8_t*)element->data);
}
}

void middleware_ui_draw_rect_element(ui_element_t *element) {
if (element != NULL && element->type == UI_ELEMENT_TYPE_RECT) {
// 简易矩形绘制,实际应用中可以优化
for (uint16_t y = element->y; y < element->y + element->height; y++) {
for (uint16_t x = element->x; x < element->x + element->width; x++) {
hal_display_set_pixel(x, y, element->color);
}
}
}
}
  • middleware_file_system.h (文件系统服务头文件 - 简易版本,假设使用 Flash 或 SD 卡)
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
#ifndef MIDDLEWARE_FILE_SYSTEM_H
#define MIDDLEWARE_FILE_SYSTEM_H

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

// 初始化文件系统
bool middleware_fs_init(void);

// 打开文件
int32_t middleware_fs_open(const char *filename, const char *mode);

// 读取文件
int32_t middleware_fs_read(int32_t fd, void *buffer, uint32_t size);

// 写入文件
int32_t middleware_fs_write(int32_t fd, const void *buffer, uint32_t size);

// 关闭文件
int32_t middleware_fs_close(int32_t fd);

// 获取文件大小
int32_t middleware_fs_get_file_size(const char *filename);

#endif // MIDDLEWARE_FILE_SYSTEM_H
  • middleware_file_system.c (文件系统服务实现文件 - 简易版本,需要根据具体的 Flash 或 SD 卡驱动实现)
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
#include "middleware_file_system.h"
// ... 包含 Flash 或 SD 卡驱动的头文件

bool middleware_fs_init(void) {
// ... 初始化 Flash 或 SD 卡驱动
// ... 挂载文件系统 (例如 FAT32)
return true; // 假设初始化成功
}

int32_t middleware_fs_open(const char *filename, const char *mode) {
// ... 打开文件,返回文件描述符 (fd)
// ... 需要根据具体的文件系统 API 实现
return -1; // 示例,表示打开失败
}

int32_t middleware_fs_read(int32_t fd, void *buffer, uint32_t size) {
// ... 从文件描述符 fd 读取数据到 buffer
// ... 返回实际读取的字节数,或错误码
return -1; // 示例,表示读取失败
}

int32_t middleware_fs_write(int32_t fd, const void *buffer, uint32_t size) {
// ... 将 buffer 中的数据写入到文件描述符 fd
// ... 返回实际写入的字节数,或错误码
return -1; // 示例,表示写入失败
}

int32_t middleware_fs_close(int32_t fd) {
// ... 关闭文件描述符 fd
// ... 返回 0 表示成功,或错误码
return -1; // 示例,表示关闭失败
}

int32_t middleware_fs_get_file_size(const char *filename) {
// ... 获取文件 filename 的大小
// ... 返回文件大小 (字节),或错误码
return -1; // 示例,表示获取失败
}
  • middleware_network.h (网络服务头文件 - 简易版本,假设使用 WiFi 或 蓝牙)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef MIDDLEWARE_NETWORK_H
#define MIDDLEWARE_NETWORK_H

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

// 初始化网络
bool middleware_network_init(void);

// 连接 WiFi (示例,如果使用 WiFi)
bool middleware_network_connect_wifi(const char *ssid, const char *password);

// 发送 HTTP GET 请求
int32_t middleware_network_http_get(const char *url, char *response_buffer, uint32_t buffer_size);

// 关闭网络连接
void middleware_network_disconnect(void);

#endif // MIDDLEWARE_NETWORK_H
  • middleware_network.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
#include "middleware_network.h"
// ... 包含 WiFi 或 蓝牙 模块驱动和协议栈的头文件
// ... 例如,如果使用 ESP32 WiFi,需要包含 ESP-IDF 相关的头文件

bool middleware_network_init(void) {
// ... 初始化 WiFi 或 蓝牙 模块
// ... 初始化 TCP/IP 协议栈
return true; // 假设初始化成功
}

bool middleware_network_connect_wifi(const char *ssid, const char *password) {
// ... 连接到指定的 WiFi 网络
// ... 需要使用 WiFi 模块的 API 和网络配置信息
return false; // 示例,表示连接失败
}

int32_t middleware_network_http_get(const char *url, char *response_buffer, uint32_t buffer_size) {
// ... 发送 HTTP GET 请求到指定的 URL
// ... 将响应数据写入 response_buffer
// ... 返回响应数据的长度,或错误码
return -1; // 示例,表示请求失败
}

void middleware_network_disconnect(void) {
// ... 断开网络连接
// ... 释放网络资源
}

4. 应用层 (Application Layer)

  • app_clock.h (时钟应用模块头文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef APP_CLOCK_H
#define APP_CLOCK_H

#include "hal_rtc.h"
#include "middleware_ui.h"

// 初始化时钟应用
void app_clock_init(void);

// 更新时钟显示 (每秒调用一次)
void app_clock_update_display(void);

#endif // APP_CLOCK_H
  • app_clock.c (时钟应用模块实现文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "app_clock.h"
#include "hal_rtc.h"
#include "middleware_ui.h"
#include <stdio.h> // 包含 sprintf

static ui_element_t *time_text_element = NULL; // 显示时间的文本元素

void app_clock_init(void) {
time_text_element = middleware_ui_create_element(UI_ELEMENT_TYPE_TEXT, 10, 20, 0, 0, COLOR_BLACK, NULL); // 创建文本元素,初始位置 (10, 20)
middleware_ui_add_element(time_text_element); // 添加到 UI 显示列表
}

void app_clock_update_display(void) {
rtc_time_t currentTime = hal_rtc_get_time(); // 获取当前时间
char time_str[32];
sprintf(time_str, "%04d-%02d-%02d %02d:%02d:%02d", currentTime.year, currentTime.month, currentTime.day, currentTime.hour, currentTime.minute, currentTime.second); // 格式化时间字符串

if (time_text_element != NULL) {
time_text_element->data = time_str; // 更新文本元素的数据
}
}
  • app_weather.h (天气应用模块头文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef APP_WEATHER_H
#define APP_WEATHER_H

#include "middleware_ui.h"

// 初始化天气应用
void app_weather_init(void);

// 更新天气数据 (例如每 15 分钟更新一次)
void app_weather_update_data(void);

// 更新天气显示
void app_weather_update_display(void);

#endif // APP_WEATHER_H
  • app_weather.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
#include "app_weather.h"
#include "middleware_ui.h"
#include "middleware_network.h"
#include <stdio.h>
#include <string.h> // 包含 strcpy, strlen

static ui_element_t *weather_text_element = NULL; // 显示天气信息的文本元素
static char weather_data_buffer[256] = "Weather: N/A"; // 存储天气数据字符串

void app_weather_init(void) {
weather_text_element = middleware_ui_create_element(UI_ELEMENT_TYPE_TEXT, 10, 50, 0, 0, COLOR_BLACK, weather_data_buffer); // 创建文本元素,初始位置 (10, 50)
middleware_ui_add_element(weather_text_element); // 添加到 UI 显示列表
}

void app_weather_update_data(void) {
// 示例:从网络获取天气数据 (这里只是一个占位符,需要根据具体的天气 API 和数据格式实现)
const char *weather_api_url = "http://api.example.com/weather?city=YourCity"; // 替换为实际的天气 API URL
char response_buffer[512];
int32_t response_len = middleware_network_http_get(weather_api_url, response_buffer, sizeof(response_buffer));
if (response_len > 0) {
// ... 解析 JSON 天气数据 (如果 API 返回 JSON 格式)
// ... 提取温度、天气状况等信息
// ... 格式化天气信息字符串,例如 "Temperature: 25°C, Sunny"
strcpy(weather_data_buffer, "Weather: Sunny, 28°C"); // 示例,实际需要解析网络数据
} else {
strcpy(weather_data_buffer, "Weather: N/A"); // 获取失败,显示 N/A
}
}

void app_weather_update_display(void) {
// 天气信息已经更新到 weather_data_buffer,UI 框架会自动绘制文本元素
}
  • app_reader.h (阅读应用模块头文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef APP_READER_H
#define APP_READER_H

#include "middleware_ui.h"

// 初始化阅读应用
void app_reader_init(void);

// 加载文本文件
bool app_reader_load_file(const char *filename);

// 显示当前页内容
void app_reader_display_page(void);

// 翻页 (下一页)
void app_reader_next_page(void);

// 翻页 (上一页)
void app_reader_prev_page(void);

#endif // APP_READER_H
  • app_reader.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
#include "app_reader.h"
#include "middleware_ui.h"
#include "middleware_file_system.h"
#include <stdlib.h> // 包含 malloc, free
#include <string.h> // 包含 strlen, strcpy

#define PAGE_LINES 10 // 每页显示的行数
#define MAX_LINE_LENGTH 50 // 每行最大字符数

static char *text_content = NULL; // 存储整个文本内容
static uint32_t text_content_length = 0; // 文本内容长度
static uint32_t current_page_start_line = 0; // 当前页起始行号

static ui_element_t *text_line_elements[PAGE_LINES]; // 存储每行文本的 UI 元素

void app_reader_init(void) {
for (int i = 0; i < PAGE_LINES; i++) {
text_line_elements[i] = middleware_ui_create_element(UI_ELEMENT_TYPE_TEXT, 10, 80 + i * 16, 0, 0, COLOR_BLACK, NULL); // 创建文本元素,垂直排列
middleware_ui_add_element(text_line_elements[i]);
}
}

bool app_reader_load_file(const char *filename) {
int32_t fd = middleware_fs_open(filename, "r");
if (fd < 0) {
return false; // 文件打开失败
}
int32_t file_size = middleware_fs_get_file_size(filename);
if (file_size <= 0) {
middleware_fs_close(fd);
return false; // 获取文件大小失败或文件为空
}

text_content = (char*)malloc(file_size + 1); // 分配内存存储文本内容
if (text_content == NULL) {
middleware_fs_close(fd);
return false; // 内存分配失败
}

int32_t read_bytes = middleware_fs_read(fd, text_content, file_size);
middleware_fs_close(fd);
if (read_bytes != file_size) {
free(text_content);
text_content = NULL;
return false; // 文件读取失败
}
text_content[file_size] = '\0'; // 添加字符串结束符
text_content_length = file_size;
current_page_start_line = 0; // 从第一页开始
return true; // 文件加载成功
}

void app_reader_display_page(void) {
if (text_content == NULL) return;

char *line_start = text_content;
uint32_t current_line_num = 0;
uint32_t displayed_lines = 0;

while (line_start != NULL && displayed_lines < PAGE_LINES) {
char *line_end = strchr(line_start, '\n'); // 查找行尾换行符
uint32_t line_len;
if (line_end != NULL) {
line_len = line_end - line_start;
} else {
line_len = strlen(line_start); // 最后一行可能没有换行符
}

if (current_line_num >= current_page_start_line && current_line_num < current_page_start_line + PAGE_LINES) {
char line_buffer[MAX_LINE_LENGTH + 1];
if (line_len > MAX_LINE_LENGTH) line_len = MAX_LINE_LENGTH; // 限制行长度
strncpy(line_buffer, line_start, line_len);
line_buffer[line_len] = '\0'; // 确保字符串结束

if (text_line_elements[displayed_lines] != NULL) {
text_line_elements[displayed_lines]->data = strdup(line_buffer); // 复制字符串,避免直接使用 line_buffer 的地址
}
displayed_lines++;
}

if (line_end != NULL) {
line_start = line_end + 1; // 指向下一行开始
} else {
line_start = NULL; // 文本结束
}
current_line_num++;
}

// 清空剩余行 (如果当前页行数不足 PAGE_LINES)
for (uint32_t i = displayed_lines; i < PAGE_LINES; i++) {
if (text_line_elements[i] != NULL) {
text_line_elements[i]->data = ""; // 显示空字符串
}
}
}

void app_reader_next_page(void) {
if (text_content == NULL) return;
current_page_start_line += PAGE_LINES;
// ... 可以添加边界检查,防止超出文本末尾
app_reader_display_page();
}

void app_reader_prev_page(void) {
if (text_content == NULL) return;
if (current_page_start_line >= PAGE_LINES) {
current_page_start_line -= PAGE_LINES;
} else {
current_page_start_line = 0; // 到达第一页
}
app_reader_display_page();
}
  • app_image.h (图片应用模块头文件)
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef APP_IMAGE_H
#define APP_IMAGE_H

#include "middleware_ui.h"

// 初始化图片应用
void app_image_init(void);

// 加载并显示图片
bool app_image_load_and_display(const char *filename);

#endif // APP_IMAGE_H
  • app_image.c (图片应用模块实现文件 - 简易版本,假设支持单色位图 BMP 格式)
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
#include "app_image.h"
#include "middleware_ui.h"
#include "middleware_file_system.h"
#include <stdlib.h> // 包含 malloc, free

static ui_element_t *image_element = NULL; // 显示图片的 UI 元素

void app_image_init(void) {
image_element = middleware_ui_create_element(UI_ELEMENT_TYPE_IMAGE, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, COLOR_BLACK, NULL); // 创建图像元素,占据整个屏幕
middleware_ui_add_element(image_element); // 添加到 UI 显示列表
}

bool app_image_load_and_display(const char *filename) {
int32_t fd = middleware_fs_open(filename, "rb"); // 以二进制只读模式打开
if (fd < 0) {
return false; // 文件打开失败
}
int32_t file_size = middleware_fs_get_file_size(filename);
if (file_size <= 0) {
middleware_fs_close(fd);
return false; // 获取文件大小失败或文件为空
}

uint8_t *image_data = (uint8_t*)malloc(file_size); // 分配内存存储图片数据
if (image_data == NULL) {
middleware_fs_close(fd);
return false; // 内存分配失败
}

int32_t read_bytes = middleware_fs_read(fd, image_data, file_size);
middleware_fs_close(fd);
if (read_bytes != file_size) {
free(image_data);
return false; // 文件读取失败
}

// ... 解析 BMP 文件头 (假设是简易的单色 BMP 格式)
// ... 获取图像宽度、高度、数据偏移量等信息
uint16_t image_width = DISPLAY_WIDTH; // 假设图片宽度和屏幕宽度一致
uint16_t image_height = DISPLAY_HEIGHT; // 假设图片高度和屏幕高度一致
uint32_t data_offset = 0; // 假设数据直接从文件开头开始 (简易 BMP 示例)

if (image_element != NULL) {
image_element->width = image_width;
image_element->height = image_height;
image_element->data = image_data + data_offset; // 设置图像数据指针 (跳过文件头)
}
return true; // 图片加载成功
}
  • app_main_ui.h (主界面应用模块头文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef APP_MAIN_UI_H
#define APP_MAIN_UI_H

// 初始化主界面
void app_main_ui_init(void);

// 更新主界面显示 (例如定期更新时间、天气等)
void app_main_ui_update(void);

// 处理按键事件 (例如切换功能、翻页等)
void app_main_ui_handle_key_event(uint8_t key_code);

#endif // APP_MAIN_UI_H
  • app_main_ui.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
#include "app_main_ui.h"
#include "app_clock.h"
#include "app_weather.h"
#include "app_reader.h"
#include "app_image.h"
#include "middleware_ui.h"
#include "osal_timer.h"

typedef enum {
MAIN_UI_MODE_CLOCK,
MAIN_UI_MODE_WEATHER,
MAIN_UI_MODE_READER,
MAIN_UI_MODE_IMAGE,
MAIN_UI_MODE_MAX
} main_ui_mode_t;

static main_ui_mode_t current_mode = MAIN_UI_MODE_CLOCK; // 默认模式为时钟

void app_main_ui_init(void) {
app_clock_init(); // 初始化时钟应用
app_weather_init(); // 初始化天气应用
app_reader_init(); // 初始化阅读应用
app_image_init(); // 初始化图片应用

// 创建定时器,定期更新主界面 (例如每秒更新时间,每 15 分钟更新天气)
timer_t ui_update_timer = osal_timer_create(1000, app_main_ui_update); // 1秒定时器
osal_timer_start(&ui_update_timer);

timer_t weather_update_timer = osal_timer_create(15 * 60 * 1000, app_weather_update_data); // 15分钟定时器
osal_timer_start(&weather_update_timer);

app_main_ui_update(); // 首次更新显示
}

void app_main_ui_update(void) {
switch (current_mode) {
case MAIN_UI_MODE_CLOCK:
app_clock_update_display();
break;
case MAIN_UI_MODE_WEATHER:
app_weather_update_display();
break;
case MAIN_UI_MODE_READER:
app_reader_display_page();
break;
case MAIN_UI_MODE_IMAGE:
// 图片显示通常在加载时更新
break;
default:
break;
}
middleware_ui_draw_ui(); // 绘制整个 UI 界面
}

void app_main_ui_handle_key_event(uint8_t key_code) {
// ... 根据按键码 key_code 处理按键事件,例如切换模式、翻页等
if (key_code == 1) { // 假设按键 1 切换模式
current_mode = (current_mode + 1) % MAIN_UI_MODE_MAX; // 循环切换模式
app_main_ui_update(); // 更新显示
} else if (key_code == 2) { // 假设按键 2 在阅读模式下翻页
if (current_mode == MAIN_UI_MODE_READER) {
app_reader_next_page();
app_main_ui_update();
}
}
// ... 处理更多按键事件
}

5. 主程序 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
#include "hal_init.h"     // 假设包含硬件初始化函数
#include "osal_timer.h" // 定时器服务
#include "middleware_ui.h" // UI 框架
#include "app_main_ui.h" // 主界面应用

int main() {
hal_init(); // 初始化硬件 (例如 GPIO, 显示屏, RTC, 定时器)
osal_timer_init(); // 初始化定时器服务
middleware_ui_init(); // 初始化 UI 框架
app_main_ui_init(); // 初始化主界面应用

// ... 加载默认图片和文本文件 (可选)
app_image_load_and_display("default_image.bmp"); // 加载默认图片
app_reader_load_file("default_text.txt"); // 加载默认文本

while (1) {
// 主循环,处理用户输入、定时任务等
// ... 检测按键输入,调用 app_main_ui_handle_key_event 处理按键事件
// ... 例如,读取按键状态,如果按键按下,则调用 app_main_ui_handle_key_event(key_code);

// 定时器服务已经在中断中处理,这里不需要显式调用 osal_timer_tick()
// 可以添加低功耗模式的代码,例如在空闲时进入睡眠模式,降低功耗

// 示例:模拟按键事件 (用于测试)
// 假设每隔 5 秒切换模式
static uint32_t last_key_event_time = 0;
if (osal_timer_get_system_tick_ms() - last_key_event_time >= 5000) {
last_key_event_time = osal_timer_get_system_tick_ms();
app_main_ui_handle_key_event(1); // 模拟按键 1 事件
}

middleware_ui_draw_ui(); // 确保 UI 界面定期刷新 (某些电子墨水屏可能需要定期刷新以防止图像残留)
}

return 0;
}

// 假设的硬件初始化函数,需要在 hal_init.c 中实现
void hal_init(void) {
// 初始化 HAL 各个模块,例如 GPIO, 显示屏, RTC, 定时器等
hal_gpio_init();
hal_display_init();
hal_rtc_init();
// ... 初始化其他硬件模块
}

技术和方法总结

在这个项目中,我采用了以下经过实践验证的技术和方法:

  1. 分层架构: 将系统划分为硬件抽象层、操作系统层、中间件层、应用层和表示层,实现了模块化、可维护性和可扩展性。
  2. HAL 硬件抽象层: 封装硬件细节,提供了平台无关的硬件访问接口,方便系统移植。
  3. 简易定时器服务: 实现了基于定时器中断的任务调度,用于周期性任务的执行,例如时钟更新、天气更新和 UI 刷新。
  4. 简易 UI 框架: 实现了基于链表的 UI 元素管理和绘制,简化了界面开发。
  5. 文件系统服务: 提供了文件读写接口,用于图片和文本文件的加载。
  6. 网络服务 (简易): 提供了 HTTP GET 请求功能,用于从网络获取天气数据 (示例)。
  7. 模块化应用设计: 将时钟、天气、阅读和图片显示等功能模块化,每个模块独立开发和维护。
  8. C 语言编程: 使用 C 语言进行开发,充分利用 C 语言在嵌入式系统中的高效性和灵活性。
  9. 代码注释和规范: 代码中添加了详细的注释,并遵循一定的编码规范,提高了代码的可读性和可维护性。

系统测试和验证

为了确保系统的可靠性和高效性,需要进行全面的测试和验证:

  1. 单元测试: 对每个模块进行单元测试,例如 HAL 驱动、定时器服务、UI 框架等,验证模块功能的正确性。
  2. 集成测试: 将各个模块集成在一起进行测试,验证模块之间的接口和协作是否正常。
  3. 系统测试: 对整个系统进行功能测试、性能测试、压力测试和稳定性测试,验证系统是否满足需求。
  4. 硬件验证: 在目标硬件平台上进行测试,验证硬件驱动和系统与硬件的兼容性。
  5. 用户体验测试: 邀请用户进行体验测试,收集用户反馈,改进用户界面和操作流程。

维护和升级

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

  1. 模块化设计: 模块化的设计使得修改和升级某个模块的代码不会轻易影响其他模块。
  2. 清晰的接口: 各层和模块之间定义清晰的接口,方便替换和升级模块。
  3. 版本控制: 使用版本控制系统 (例如 Git) 管理代码,方便代码的版本管理和回溯。
  4. 文档编写: 编写详细的设计文档、代码注释和用户手册,方便维护人员理解和维护系统。
  5. OTA 升级 (可选): 如果系统具备网络功能,可以考虑实现 OTA (Over-The-Air) 升级,方便远程升级系统固件。

总结

以上代码和架构设计方案提供了一个可靠、高效、可扩展的嵌入式系统平台的基础框架,并实现了时钟、天气、阅读和图片显示这四个核心功能。 请注意,这只是一个基础示例,实际的嵌入式产品开发会更加复杂,需要根据具体的需求和硬件平台进行更详细的设计和实现。 例如,需要根据实际使用的电子墨水屏驱动 IC 和型号编写具体的显示驱动代码,根据使用的网络模块和协议栈实现网络服务,根据使用的文件系统和存储介质实现文件系统服务,以及根据具体的硬件平台和 MCU 编写 HAL 驱动代码。 同时,还需要进行更全面的测试和优化,才能最终开发出满足用户需求的高质量嵌入式产品。

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

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