编程技术分享

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

0%

简介:一款带有读卡器功能的hdmi便携显示器,3.8寸480X800分辨率MVA屏,板载左右扬声器,支持TF卡双卡双读、屏幕亮度与音量调节。

好的,作为一名高级嵌入式软件开发工程师,我将针对这款带读卡器功能的HDMI便携显示器项目,详细阐述最适合的代码设计架构,并提供相应的C代码实现。
关注微信公众号,提前获取相关推文

项目概述与需求分析

项目名称: HDMI便携显示器(带双TF卡读卡器)

项目目标: 开发一款可靠、高效、可扩展的嵌入式系统平台,用于驱动3.8寸HDMI便携显示器,并集成双TF卡读卡器功能,支持屏幕亮度与音量调节。

硬件规格:

  • 显示屏: 3.8寸 MVA屏,480x800分辨率
  • 音频: 板载左右扬声器
  • 存储: 双TF卡槽,支持双卡双读
  • 接口: HDMI输入,TF卡接口,控制按键(亮度/音量)

软件功能需求:

  1. 显示驱动:

    • 初始化显示屏
    • 接收并显示HDMI输入信号
    • 支持图像帧缓冲管理
    • 实现屏幕亮度调节
  2. 音频驱动:

    • 初始化音频输出模块
    • 播放HDMI输入的音频信号
    • 实现音量调节
    • 支持左右声道输出
  3. TF卡读卡器驱动:

    • 初始化SD卡控制器
    • 支持双TF卡同时工作
    • 提供文件系统接口(例如FAT32)
    • 支持文件读写操作
  4. 用户界面与控制:

    • 亮度调节功能(通过按键或触摸屏)
    • 音量调节功能(通过按键或触摸屏)
    • 简单的系统菜单(可选,例如显示当前亮度/音量级别)
  5. 系统管理:

    • 电源管理(低功耗模式,背光控制)
    • 错误处理与日志记录
    • 系统初始化与启动流程
    • 固件升级机制(预留接口)

软件设计架构

为了构建可靠、高效、可扩展的系统,我将采用分层架构模块化设计相结合的方式。这种架构将系统划分为不同的层次和模块,每个模块负责特定的功能,层次之间通过清晰的接口进行通信。

1. 架构层次

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 直接与硬件交互,封装底层硬件操作,为上层提供统一的硬件访问接口。HAL层负责屏蔽不同硬件平台的差异,提高代码的可移植性。

  • 设备驱动层 (Device Drivers): 构建在HAL层之上,负责驱动具体的硬件设备,如显示屏驱动、音频驱动、SD卡驱动、GPIO驱动等。驱动层提供高层次的API,供系统服务层调用。

  • 系统服务层 (System Services): 提供操作系统的核心服务,例如文件系统管理、内存管理、任务调度、电源管理、输入事件处理等。这层可以包含一个轻量级的实时操作系统 (RTOS) 或裸机系统。

  • 应用层 (Application Layer): 构建在系统服务层之上,实现产品的具体功能,例如用户界面、亮度/音量控制、文件浏览(如果需要)等。应用层调用系统服务层提供的API来实现业务逻辑。

2. 模块划分

根据功能需求,可以将系统划分为以下模块:

  • 显示模块 (Display Module):

    • 显示驱动 (Display Driver)
    • 帧缓冲管理 (Framebuffer Manager)
    • 亮度控制 (Brightness Control)
    • HDMI输入处理 (HDMI Input Handler)
  • 音频模块 (Audio Module):

    • 音频驱动 (Audio Driver)
    • 音量控制 (Volume Control)
    • 音频解码/处理 (Audio Processing - 如果需要)
  • 存储模块 (Storage Module):

    • SD卡驱动 (SD Card Driver)
    • 文件系统 (File System - 例如FATFS)
    • 存储管理 (Storage Manager - 可选)
  • 输入模块 (Input Module):

    • 按键驱动 (Button Driver)
    • 触摸屏驱动 (Touchscreen Driver - 如果有)
    • 输入事件处理 (Input Event Handler)
  • 系统核心模块 (System Core Module):

    • 系统初始化 (System Initialization)
    • 电源管理 (Power Management)
    • 错误处理 (Error Handler)
    • 任务调度 (Task Scheduler - 如果使用RTOS)
    • 配置管理 (Configuration Manager)
  • 用户界面模块 (UI Module):

    • UI框架 (UI Framework - 简单GUI)
    • 菜单管理 (Menu Manager - 如果需要)
    • 亮度/音量显示 (Brightness/Volume Display)

3. 架构图示

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
+---------------------+
| 应用层 (UI) |
+---------------------+
| 系统服务层 (System Services) |
+---------------------+
| 设备驱动层 (Drivers) |
+---------------------+
| 硬件抽象层 (HAL) |
+---------------------+
| 硬件 (Hardware) |
+---------------------+

模块划分:

+-------------------------------------------------------------------+
| 应用层 (UI) |
| - 用户界面模块 (UI Module) |
+-------------------------------------------------------------------+
| 系统服务层 (System Services) |
| - 系统核心模块 (System Core Module) |
| - 文件系统 (File System) |
| - 输入事件处理 (Input Event Handler) |
+-------------------------------------------------------------------+
| 设备驱动层 (Drivers) |
| - 显示模块 (Display Module): 显示驱动, 帧缓冲, 亮度控制, HDMI输入处理 |
| - 音频模块 (Audio Module): 音频驱动, 音量控制, 音频处理 |
| - 存储模块 (Storage Module): SD卡驱动, 存储管理 |
| - 输入模块 (Input Module): 按键驱动, 触摸屏驱动 |
+-------------------------------------------------------------------+
| 硬件抽象层 (HAL) |
| - GPIO HAL |
| - SPI HAL |
| - I2C HAL |
| - SDIO HAL |
| - PWM HAL |
| - 时钟/定时器 HAL |
+-------------------------------------------------------------------+
| 硬件 (Hardware) |
| - 3.8寸 MVA屏 |
| - 音频Codec |
| - SD卡控制器 |
| - HDMI接收芯片 |
| - MCU/处理器 |
| - 按键/触摸屏 |
+-------------------------------------------------------------------+

C 代码实现 (示例代码,不保证完整运行,仅为架构演示)

为了满足3000行代码的要求,我会尽量详细地展开各个模块的代码,并加入必要的注释和说明。请注意,以下代码是示例性质的,可能需要根据具体的硬件平台和芯片型号进行调整。

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
#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_set_level(gpio_pin_t pin, gpio_level_t level);

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

#endif // HAL_GPIO_H

hal_gpio.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
#include "hal_gpio.h"
// ... 包含具体的硬件头文件,例如寄存器定义

void hal_gpio_init(gpio_pin_t pin, gpio_mode_t mode) {
// ... 根据pin和mode配置GPIO寄存器
// 例如: 设置GPIO方向寄存器、上拉/下拉电阻等
switch (pin) {
case GPIO_PIN_0:
// ... 配置 GPIO_PIN_0
break;
case GPIO_PIN_1:
// ... 配置 GPIO_PIN_1
break;
// ... 其他引脚配置
default:
break;
}

if (mode == GPIO_MODE_OUTPUT) {
// 设置为输出模式
} else { // GPIO_MODE_INPUT
// 设置为输入模式
}
}

void hal_gpio_set_level(gpio_pin_t pin, gpio_level_t level) {
// ... 根据pin和level设置GPIO输出数据寄存器
if (level == GPIO_LEVEL_HIGH) {
// 设置GPIO输出高电平
} else { // GPIO_LEVEL_LOW
// 设置GPIO输出低电平
}
}

gpio_level_t hal_gpio_get_level(gpio_pin_t pin) {
// ... 读取GPIO输入数据寄存器,并返回电平状态
// ... 返回 GPIO_LEVEL_HIGH 或 GPIO_LEVEL_LOW
return GPIO_LEVEL_LOW; // 示例返回值
}

类似地,可以创建 hal_spi.h, hal_spi.c, hal_i2c.h, hal_i2c.c, hal_sdio.h, hal_sdio.c, hal_pwm.h, hal_pwm.c 等HAL层文件,分别封装SPI、I2C、SDIO、PWM等硬件接口的操作。

2. 设备驱动层 (Device Drivers)

display_driver.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
#ifndef DISPLAY_DRIVER_H
#define DISPLAY_DRIVER_H

#include "hal_gpio.h" // 假设显示屏控制线使用GPIO

// 显示屏参数配置结构体
typedef struct {
int width; // 屏幕宽度
int height; // 屏幕高度
// ... 其他显示屏参数,例如像素格式、接口类型等
} display_config_t;

// 初始化显示屏驱动
int display_driver_init(const display_config_t *config);

// 发送命令到显示屏
void display_driver_send_command(uint8_t command);

// 发送数据到显示屏
void display_driver_send_data(uint8_t data);

// 设置显示区域
void display_driver_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);

// 写入像素数据到显示缓冲区
void display_driver_draw_pixel(uint16_t x, uint16_t y, uint16_t color);

// 清空显示缓冲区
void display_driver_clear_screen(uint16_t color);

// 更新显示屏内容 (将显示缓冲区数据刷新到屏幕)
void display_driver_update_screen(void);

// 设置背光亮度 (使用PWM控制)
void display_driver_set_backlight(uint8_t brightness); // 0-100 表示亮度百分比

#endif // DISPLAY_DRIVER_H

display_driver.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 "display_driver.h"
#include "hal_spi.h" // 假设显示屏使用SPI接口
#include "hal_pwm.h" // 假设背光控制使用PWM

// 帧缓冲区 (假设使用RGB565格式)
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 800
uint16_t framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT];

// 显示屏控制引脚 (根据实际硬件连接定义)
#define LCD_RST_PIN GPIO_PIN_X // 复位引脚
#define LCD_CS_PIN GPIO_PIN_Y // 片选引脚
#define LCD_DC_PIN GPIO_PIN_Z // 数据/命令选择引脚
#define LCD_BL_PWM_CHANNEL PWM_CHANNEL_A // 背光PWM通道

// 初始化显示屏驱动
int display_driver_init(const display_config_t *config) {
// 1. 初始化GPIO引脚 (RST, CS, DC)
hal_gpio_init(LCD_RST_PIN, GPIO_MODE_OUTPUT);
hal_gpio_init(LCD_CS_PIN, GPIO_MODE_OUTPUT);
hal_gpio_init(LCD_DC_PIN, GPIO_MODE_OUTPUT);

// 2. 初始化SPI接口
// ... 根据显示屏SPI接口参数配置 SPI HAL

// 3. 初始化背光PWM
// ... 根据背光PWM参数配置 PWM HAL,例如频率、占空比范围等

// 4. 显示屏复位
hal_gpio_set_level(LCD_RST_PIN, GPIO_LEVEL_LOW);
// ... 延时一段时间
hal_gpio_set_level(LCD_RST_PIN, GPIO_LEVEL_HIGH);
// ... 延时一段时间

// 5. 发送初始化命令序列 (根据显示屏IC手册)
display_driver_send_command(0x01); // 例如:软件复位
// ... 其他初始化命令,例如设置像素格式、扫描方向、gamma校正等

// 6. 设置初始背光亮度 (例如50%)
display_driver_set_backlight(50);

// 7. 清空屏幕
display_driver_clear_screen(0x0000); // 黑色

return 0; // 初始化成功
}

// 发送命令到显示屏
void display_driver_send_command(uint8_t command) {
hal_gpio_set_level(LCD_CS_PIN, GPIO_LEVEL_LOW); // 片选使能
hal_gpio_set_level(LCD_DC_PIN, GPIO_LEVEL_LOW); // 命令模式
// ... 通过SPI发送 command
hal_gpio_set_level(LCD_CS_PIN, GPIO_LEVEL_HIGH); // 片选失能
}

// 发送数据到显示屏
void display_driver_send_data(uint8_t data) {
hal_gpio_set_level(LCD_CS_PIN, GPIO_LEVEL_LOW); // 片选使能
hal_gpio_set_level(LCD_DC_PIN, GPIO_LEVEL_HIGH); // 数据模式
// ... 通过SPI发送 data
hal_gpio_set_level(LCD_CS_PIN, GPIO_LEVEL_HIGH); // 片选失能
}

// 设置显示区域
void display_driver_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
// ... 根据显示屏IC手册,发送设置窗口地址的命令
// ... 例如设置列地址范围 (CASET)、行地址范围 (RASET)
}

// 写入像素数据到显示缓冲区
void display_driver_draw_pixel(uint16_t x, uint16_t y, uint16_t color) {
if (x < SCREEN_WIDTH && y < SCREEN_HEIGHT) {
framebuffer[y * SCREEN_WIDTH + x] = color;
}
}

// 清空显示缓冲区
void display_driver_clear_screen(uint16_t color) {
for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
framebuffer[i] = color;
}
}

// 更新显示屏内容 (将显示缓冲区数据刷新到屏幕)
void display_driver_update_screen(void) {
display_driver_set_window(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);
hal_gpio_set_level(LCD_CS_PIN, GPIO_LEVEL_LOW); // 片选使能
hal_gpio_set_level(LCD_DC_PIN, GPIO_LEVEL_HIGH); // 数据模式

// 循环发送帧缓冲区数据 (假设使用SPI)
for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
uint16_t pixel = framebuffer[i];
// 发送高字节
display_driver_send_data((pixel >> 8) & 0xFF);
// 发送低字节
display_driver_send_data(pixel & 0xFF);
}

hal_gpio_set_level(LCD_CS_PIN, GPIO_LEVEL_HIGH); // 片选失能
}

// 设置背光亮度 (使用PWM控制)
void display_driver_set_backlight(uint8_t brightness) {
if (brightness > 100) brightness = 100;
// ... 根据 brightness 计算 PWM 占空比
uint32_t duty_cycle = (brightness * PWM_MAX_DUTY) / 100; // PWM_MAX_DUTY 是PWM最大占空比值
hal_pwm_set_duty_cycle(LCD_BL_PWM_CHANNEL, duty_cycle);
}

audio_driver.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef AUDIO_DRIVER_H
#define AUDIO_DRIVER_H

// 初始化音频驱动
int audio_driver_init(void);

// 设置音量 (0-100 表示音量百分比)
void audio_driver_set_volume(uint8_t volume);

// 播放音频数据 (假设音频数据已经解码为 PCM 格式)
void audio_driver_play_data(const uint8_t *data, uint32_t data_size);

// 停止播放音频
void audio_driver_stop_play(void);

#endif // AUDIO_DRIVER_H

audio_driver.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
#include "audio_driver.h"
#include "hal_i2c.h" // 假设音频Codec使用I2C控制
#include "hal_spi.h" // 假设音频数据通过SPI输出 (例如I2S)

// 音频Codec I2C 地址 (根据Codec芯片手册定义)
#define AUDIO_CODEC_I2C_ADDR 0xXX

// 初始化音频驱动
int audio_driver_init(void) {
// 1. 初始化I2C接口
// ... 配置 I2C HAL

// 2. 初始化音频Codec芯片 (通过I2C配置寄存器)
// ... 根据音频Codec芯片手册,发送初始化配置命令,例如:
// ... 设置采样率、声道数、输出模式、音量等

// 3. 初始化音频数据输出接口 (例如I2S SPI)
// ... 配置 SPI HAL 用于 I2S 音频数据传输

// 4. 设置初始音量 (例如50%)
audio_driver_set_volume(50);

return 0; // 初始化成功
}

// 设置音量 (0-100 表示音量百分比)
void audio_driver_set_volume(uint8_t volume) {
if (volume > 100) volume = 100;
// ... 根据 volume 计算 音频Codec 的音量控制寄存器值
uint8_t codec_volume_reg_value = ...; // 计算音量寄存器值
// ... 通过I2C写入音量寄存器
hal_i2c_write_reg(AUDIO_CODEC_I2C_ADDR, AUDIO_CODEC_VOLUME_REG, codec_volume_reg_value);
}

// 播放音频数据 (假设音频数据已经解码为 PCM 格式)
void audio_driver_play_data(const uint8_t *data, uint32_t data_size) {
// ... 通过 SPI (I2S) 发送音频数据
// ... 可能需要使用 DMA 或中断方式进行数据传输,以提高效率
// ... 示例:循环发送数据
for (uint32_t i = 0; i < data_size; i++) {
// ... 通过 SPI 发送 data[i]
}
}

// 停止播放音频
void audio_driver_stop_play(void) {
// ... 停止音频数据传输
// ... 如果需要,可以静音音频输出
}

sdcard_driver.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef SDCARD_DRIVER_H
#define SDCARD_DRIVER_H

// 初始化SD卡驱动
int sdcard_driver_init(void);

// 检测SD卡是否插入
int sdcard_driver_is_card_inserted(int card_slot); // card_slot: 0 或 1

// 读取SD卡扇区
int sdcard_driver_read_sector(int card_slot, uint32_t sector_addr, uint8_t *buffer, uint32_t sector_count);

// 写入SD卡扇区
int sdcard_driver_write_sector(int card_slot, uint32_t sector_addr, const uint8_t *buffer, uint32_t sector_count);

#endif // SDCARD_DRIVER_H

sdcard_driver.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
#include "sdcard_driver.h"
#include "hal_sdio.h" // 假设SD卡使用SDIO接口
#include "hal_gpio.h" // 假设使用GPIO检测SD卡插入

// SD卡检测引脚 (根据实际硬件连接定义)
#define SD_CARD0_DETECT_PIN GPIO_PIN_XX
#define SD_CARD1_DETECT_PIN GPIO_PIN_YY

// 初始化SD卡驱动
int sdcard_driver_init(void) {
// 1. 初始化SDIO接口
// ... 配置 SDIO HAL

// 2. 初始化SD卡检测GPIO
hal_gpio_init(SD_CARD0_DETECT_PIN, GPIO_MODE_INPUT);
hal_gpio_init(SD_CARD1_DETECT_PIN, GPIO_MODE_INPUT);

return 0; // 初始化成功
}

// 检测SD卡是否插入
int sdcard_driver_is_card_inserted(int card_slot) {
gpio_level_t level;
if (card_slot == 0) {
level = hal_gpio_get_level(SD_CARD0_DETECT_PIN);
} else { // card_slot == 1
level = hal_gpio_get_level(SD_CARD1_DETECT_PIN);
}
// ... 根据检测引脚电平判断SD卡是否插入 (高电平或低电平有效,根据硬件设计)
if (level == GPIO_LEVEL_HIGH) { // 假设高电平表示插入
return 1; // 卡已插入
} else {
return 0; // 卡未插入
}
}

// 读取SD卡扇区
int sdcard_driver_read_sector(int card_slot, uint32_t sector_addr, uint8_t *buffer, uint32_t sector_count) {
// ... 使用 SDIO HAL 读取指定扇区的数据到 buffer
// ... 需要处理SD卡初始化、命令发送、数据传输、错误处理等细节
// ... 示例:
// ... sdio_send_command(SD_CMD_READ_BLOCK, sector_addr);
// ... sdio_receive_data(buffer, sector_count * 512); // 假设扇区大小 512 字节
return 0; // 假设读取成功
}

// 写入SD卡扇区
int sdcard_driver_write_sector(int card_slot, uint32_t sector_addr, const uint8_t *buffer, uint32_t sector_count) {
// ... 使用 SDIO HAL 写入 buffer 中的数据到指定扇区
// ... 需要处理SD卡初始化、命令发送、数据传输、错误处理等细节
// ... 示例:
// ... sdio_send_command(SD_CMD_WRITE_BLOCK, sector_addr);
// ... sdio_send_data(buffer, sector_count * 512);
return 0; // 假设写入成功
}

button_driver.h

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

typedef enum {
BUTTON_BRIGHTNESS_UP,
BUTTON_BRIGHTNESS_DOWN,
BUTTON_VOLUME_UP,
BUTTON_VOLUME_DOWN,
BUTTON_MENU,
BUTTON_MAX
} button_t;

// 初始化按键驱动
int button_driver_init(void);

// 获取按键状态 (轮询方式)
int button_driver_get_button_state(button_t button); // 返回 1 表示按下,0 表示释放

// 注册按键事件回调函数 (中断方式,可选)
typedef void (*button_event_callback_t)(button_t button);
void button_driver_register_callback(button_t button, button_event_callback_t callback);

#endif // BUTTON_DRIVER_H

button_driver.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
#include "button_driver.h"
#include "hal_gpio.h"

// 按键引脚定义 (根据实际硬件连接定义)
#define BUTTON_BRIGHTNESS_UP_PIN GPIO_PIN_AA
#define BUTTON_BRIGHTNESS_DOWN_PIN GPIO_PIN_BB
#define BUTTON_VOLUME_UP_PIN GPIO_PIN_CC
#define BUTTON_VOLUME_DOWN_PIN GPIO_PIN_DD
#define BUTTON_MENU_PIN GPIO_PIN_EE

// 按键事件回调函数数组 (中断方式使用)
static button_event_callback_t button_callbacks[BUTTON_MAX] = {NULL};

// 初始化按键驱动
int button_driver_init(void) {
// 初始化按键GPIO为输入模式,并启用上拉/下拉电阻 (根据硬件设计)
hal_gpio_init(BUTTON_BRIGHTNESS_UP_PIN, GPIO_MODE_INPUT);
hal_gpio_init(BUTTON_BRIGHTNESS_DOWN_PIN, GPIO_MODE_INPUT);
hal_gpio_init(BUTTON_VOLUME_UP_PIN, GPIO_MODE_INPUT);
hal_gpio_init(BUTTON_VOLUME_DOWN_PIN, GPIO_MODE_INPUT);
hal_gpio_init(BUTTON_MENU_PIN, GPIO_MODE_INPUT);

return 0; // 初始化成功
}

// 获取按键状态 (轮询方式)
int button_driver_get_button_state(button_t button) {
gpio_level_t level;
switch (button) {
case BUTTON_BRIGHTNESS_UP:
level = hal_gpio_get_level(BUTTON_BRIGHTNESS_UP_PIN);
break;
case BUTTON_BRIGHTNESS_DOWN:
level = hal_gpio_get_level(BUTTON_BRIGHTNESS_DOWN_PIN);
break;
case BUTTON_VOLUME_UP:
level = hal_gpio_get_level(BUTTON_VOLUME_UP_PIN);
break;
case BUTTON_VOLUME_DOWN:
level = hal_gpio_get_level(BUTTON_VOLUME_DOWN_PIN);
break;
case BUTTON_MENU:
level = hal_gpio_get_level(BUTTON_MENU_PIN);
break;
default:
return 0; // 无效按键
}

// ... 根据按键引脚电平判断按键是否按下 (高电平或低电平有效,根据硬件设计)
if (level == GPIO_LEVEL_LOW) { // 假设低电平表示按下
return 1; // 按下
} else {
return 0; // 释放
}
}

// 注册按键事件回调函数 (中断方式,可选)
void button_driver_register_callback(button_t button, button_event_callback_t callback) {
if (button < BUTTON_MAX) {
button_callbacks[button] = callback;
// ... 配置 GPIO 中断,当按键引脚电平变化时触发中断
// ... 在中断处理函数中调用 button_callbacks[button](button);
}
}

// ... (可选) 实现 GPIO 中断处理函数,例如:
// void GPIO_IRQHandler(void) {
// // ... 检查是哪个GPIO引脚触发了中断
// if (GPIO_PIN_IS_INTERRUPT_SOURCE(BUTTON_BRIGHTNESS_UP_PIN)) {
// if (button_callbacks[BUTTON_BRIGHTNESS_UP] != NULL) {
// button_callbacks[BUTTON_BRIGHTNESS_UP](BUTTON_BRIGHTNESS_UP);
// }
// }
// // ... 处理其他按键中断
// // ... 清除中断标志
// }

3. 系统服务层 (System Services)

system_core.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef SYSTEM_CORE_H
#define SYSTEM_CORE_H

// 系统初始化
int system_init(void);

// 系统电源管理 (进入低功耗模式)
void system_power_down(void);

// 系统错误处理函数
void system_error_handler(const char *message);

// 系统延时函数 (毫秒级)
void system_delay_ms(uint32_t ms);

#endif // SYSTEM_CORE_H

system_core.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 "system_core.h"
#include "display_driver.h"
#include "audio_driver.h"
#include "sdcard_driver.h"
#include "button_driver.h"
// ... 包含其他驱动头文件

// 系统初始化
int system_init(void) {
// 1. 初始化硬件抽象层 (HAL) - 通常在更底层的启动代码中完成,这里可以省略
// ...

// 2. 初始化设备驱动层
if (display_driver_init(NULL) != 0) {
system_error_handler("Display driver initialization failed!");
return -1;
}
if (audio_driver_init() != 0) {
system_error_handler("Audio driver initialization failed!");
return -1;
}
if (sdcard_driver_init() != 0) {
system_error_handler("SD card driver initialization failed!");
return -1;
}
if (button_driver_init() != 0) {
system_error_handler("Button driver initialization failed!");
return -1;
}

// 3. 初始化文件系统 (例如 FATFS)
// ... 挂载 SD 卡文件系统

// 4. 初始化其他系统服务 (例如内存管理、任务调度 - 如果使用 RTOS)
// ...

return 0; // 系统初始化成功
}

// 系统电源管理 (进入低功耗模式)
void system_power_down(void) {
// ... 关闭显示背光
display_driver_set_backlight(0);
// ... 停止音频播放
audio_driver_stop_play();
// ... 进入低功耗模式,例如 MCU 进入睡眠模式
// ... 根据具体 MCU 平台实现低功耗操作
}

// 系统错误处理函数
void system_error_handler(const char *message) {
// ... 错误日志记录 (例如通过串口输出错误信息)
printf("System Error: %s\n", message);
// ... 可以根据错误级别决定是否重启系统或进入安全模式
while (1) { // 错误发生后进入死循环,等待外部干预
// ... 闪烁LED指示错误状态 (可选)
}
}

// 系统延时函数 (毫秒级)
void system_delay_ms(uint32_t ms) {
// ... 使用硬件定时器或软件循环实现延时
// ... 建议使用硬件定时器,精度更高,CPU占用率更低
// ... 示例 (简单软件循环延时,精度不高):
volatile uint32_t count;
for (count = 0; count < ms * 1000; count++); // 粗略延时
}

4. 应用层 (Application Layer)

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
#include "system_core.h"
#include "display_driver.h"
#include "audio_driver.h"
#include "button_driver.h"
#include "sdcard_driver.h"
// ... 包含其他模块头文件
#include <stdio.h> // 标准输入输出库 (用于printf)

// 亮度级别 (0-100)
static uint8_t brightness_level = 50;
// 音量级别 (0-100)
static uint8_t volume_level = 50;

// 按键事件处理回调函数
static void button_brightness_up_callback(button_t button);
static void button_brightness_down_callback(button_t button);
static void button_volume_up_callback(button_t button);
static void button_volume_down_callback(button_t button);
static void button_menu_callback(button_t button);

int main() {
// 1. 系统初始化
if (system_init() != 0) {
printf("System initialization failed!\n");
return -1;
}
printf("System initialized successfully!\n");

// 2. 注册按键事件回调函数 (如果使用中断方式)
// button_driver_register_callback(BUTTON_BRIGHTNESS_UP, button_brightness_up_callback);
// button_driver_register_callback(BUTTON_BRIGHTNESS_DOWN, button_brightness_down_callback);
// button_driver_register_callback(BUTTON_VOLUME_UP, button_volume_up_callback);
// button_driver_register_callback(BUTTON_VOLUME_DOWN, button_volume_down_callback);
// button_driver_register_callback(BUTTON_MENU, button_menu_callback);

// 3. 主循环
while (1) {
// a. 轮询按键状态 (如果未使用中断方式)
if (button_driver_get_button_state(BUTTON_BRIGHTNESS_UP)) {
button_brightness_up_callback(BUTTON_BRIGHTNESS_UP);
}
if (button_driver_get_button_state(BUTTON_BRIGHTNESS_DOWN)) {
button_brightness_down_callback(BUTTON_BRIGHTNESS_DOWN);
}
if (button_driver_get_button_state(BUTTON_VOLUME_UP)) {
button_volume_up_callback(BUTTON_VOLUME_UP);
}
if (button_driver_get_button_state(BUTTON_VOLUME_DOWN)) {
button_volume_down_callback(BUTTON_VOLUME_DOWN);
}
if (button_driver_get_button_state(BUTTON_MENU)) {
button_menu_callback(BUTTON_MENU);
}

// b. 处理HDMI输入信号 (假设有HDMI输入处理模块)
// ... 从 HDMI 接收芯片读取视频帧数据,解码后写入显示缓冲区
// ... 从 HDMI 接收芯片读取音频数据,解码后通过音频驱动播放

// c. 刷新显示屏
display_driver_update_screen();

// d. 系统延时 (控制循环频率,降低CPU占用率)
system_delay_ms(10); // 10ms 延时
}

return 0;
}

// 按键事件处理回调函数
static void button_brightness_up_callback(button_t button) {
if (brightness_level < 100) {
brightness_level += 5; // 每次增加 5% 亮度
display_driver_set_backlight(brightness_level);
printf("Brightness: %d%%\n", brightness_level); // 打印亮度信息 (可选)
}
}

static void button_brightness_down_callback(button_t button) {
if (brightness_level > 0) {
brightness_level -= 5; // 每次减少 5% 亮度
display_driver_set_backlight(brightness_level);
printf("Brightness: %d%%\n", brightness_level); // 打印亮度信息 (可选)
}
}

static void button_volume_up_callback(button_t button) {
if (volume_level < 100) {
volume_level += 5; // 每次增加 5% 音量
audio_driver_set_volume(volume_level);
printf("Volume: %d%%\n", volume_level); // 打印音量信息 (可选)
}
}

static void button_volume_down_callback(button_t button) {
if (volume_level > 0) {
volume_level -= 5; // 每次减少 5% 音量
audio_driver_set_volume(volume_level);
printf("Volume: %d%%\n", volume_level); // 打印音量信息 (可选)
}
}

static void button_menu_callback(button_t button) {
// ... 处理菜单按键事件,例如显示系统菜单、切换功能等
printf("Menu Button Pressed\n"); // 打印菜单按键信息 (可选)
}

5. 文件系统 (FATFS) 集成 (示例)

为了支持TF卡的文件读写,我们需要集成一个文件系统。这里以常用的 FatFS 为例。

  • 下载 FatFS 源码: 从 FatFS 官网下载源码,并将其添加到项目中。
  • 配置 FatFS: 根据硬件平台和SD卡驱动,配置 ffconf.h 文件,例如:
    • _DRIVES: 定义驱动器数量 (例如 2 个 SD 卡槽)
    • _USE_LFN: 启用长文件名支持 (可选)
    • _CODE_PAGE: 设置代码页 (例如 CP936 for GBK 中文)
    • FF_FS_TINY: 选择 Tiny FatFS 配置以减小代码体积 (如果资源有限)
  • 实现 Disk I/O Layer: FatFS 需要一个底层的 Disk I/O layer 来访问硬件存储设备。我们需要实现以下函数,并在 diskio.c 中定义:
    • disk_initialize: 初始化磁盘驱动器
    • disk_status: 获取磁盘状态
    • disk_read: 从磁盘读取扇区
    • disk_write: 写入扇区到磁盘
    • disk_ioctl: 控制磁盘设备 (例如获取扇区数量、扇区大小等)

示例 diskio.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
#include "diskio.h"
#include "sdcard_driver.h"

/* 磁盘状态 */
static BYTE card_status[2] = {STA_NOINIT, STA_NOINIT}; // 2 个 SD 卡槽

/* 驱动器初始化 */
DSTATUS disk_initialize (BYTE pdrv)
{
int card_slot = pdrv; // 驱动器号对应 SD 卡槽号 (0 或 1)

if (sdcard_driver_init() != 0) {
card_status[card_slot] |= STA_NOINIT;
return STA_NOINIT;
}

card_status[card_slot] &= ~STA_NOINIT; // 清除 NOINIT 状态
return card_status[card_slot];
}

/* 获取磁盘状态 */
DSTATUS disk_status (BYTE pdrv)
{
int card_slot = pdrv;
return card_status[card_slot];
}

/* 扇区读取 */
DRESULT disk_read (
BYTE pdrv, /* 驱动器号 (0-...) */
BYTE *buff, /* 数据缓冲区 */
LBA_t sector, /* 起始扇区号 */
UINT count /* 扇区数量 */
)
{
int card_slot = pdrv;
if (sdcard_driver_read_sector(card_slot, sector, buff, count) == 0) {
return RES_OK;
} else {
return RES_ERROR;
}
}

/* 扇区写入 (可选,如果需要写操作) */
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* 驱动器号 (0-...) */
const BYTE *buff, /* 数据缓冲区 */
LBA_t sector, /* 起始扇区号 */
UINT count /* 扇区数量 */
)
{
int card_slot = pdrv;
if (sdcard_driver_write_sector(card_slot, sector, buff, count) == 0) {
return RES_OK;
} else {
return RES_ERROR;
}
}
#endif

/* IO 控制 (可选,根据需要实现) */
DRESULT disk_ioctl (
BYTE pdrv, /* 驱动器号 (0-...) */
BYTE cmd, /* 控制命令 */
void *buff /* 参数缓冲区 */
)
{
// ... 根据 cmd 命令执行相应的控制操作,例如获取扇区数量、扇区大小等
switch (cmd) {
case CTRL_SYNC: /* 强制扇区写入同步 */
break;
case GET_SECTOR_COUNT: /* 获取扇区数量 */
// ... 获取 SD 卡扇区数量,并写入到 buff 指向的 LBA_t 变量
break;
case GET_SECTOR_SIZE: /* 获取扇区大小 */
*(DWORD*)buff = 512; // 扇区大小 512 字节
break;
case GET_BLOCK_SIZE: /* 获取块大小 */
*(DWORD*)buff = 1; // 块大小 1 扇区
break;
default:
return RES_PARERR;
}
return RES_OK;
}

/* 获取当前时间 (可选,用于文件时间戳) */
DWORD get_fattime (void)
{
// ... 获取当前时间 (年、月、日、时、分、秒),并编码为 DWORD 返回
// ... 可以使用 RTC 实时时钟或系统时间
return 0; // 简化示例,返回 0
}
  • 在应用层使用 FatFS API:main.c 或其他应用模块中,可以使用 FatFS 提供的 API 进行文件操作,例如:
    • f_mount: 挂载文件系统
    • f_open: 打开文件
    • f_read: 读取文件
    • f_write: 写入文件
    • f_close: 关闭文件
    • f_mkdir: 创建目录
    • f_readdir: 读取目录项
    • f_stat: 获取文件/目录信息
    • f_unlink: 删除文件/目录
    • f_unmount: 卸载文件系统

示例应用层代码 (使用 FatFS)

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
#include "ff.h" // FatFS 头文件

int main() {
// ... 系统初始化

FATFS FatFs[2]; // 文件系统对象 (用于 2 个 SD 卡槽)
FRESULT res; // FatFS 函数返回值
FIL file; // 文件对象
char buffer[100];

// 挂载 SD 卡文件系统 (驱动器 0)
res = f_mount(&FatFs[0], "0:", 1); // 立即挂载
if (res != FR_OK) {
printf("Mount SD card 0 failed! (%d)\n", res);
// ... 错误处理
} else {
printf("SD card 0 mounted successfully!\n");

// 打开文件 (驱动器 0, 根目录下 test.txt 文件)
res = f_open(&file, "0:/test.txt", FA_READ);
if (res != FR_OK) {
printf("Open file failed! (%d)\n", res);
// ... 错误处理
} else {
printf("File opened successfully!\n");

// 读取文件内容
UINT bytes_read;
res = f_read(&file, buffer, sizeof(buffer) - 1, &bytes_read);
if (res != FR_OK) {
printf("Read file failed! (%d)\n", res);
// ... 错误处理
} else {
buffer[bytes_read] = '\0'; // 添加字符串结束符
printf("File content: %s\n", buffer);
}

// 关闭文件
f_close(&file);
}

// 卸载文件系统
f_unmount("0:");
}

// ... 主循环
return 0;
}

总结与展望

以上代码提供了一个基于分层架构和模块化设计的嵌入式系统框架,用于驱动HDMI便携显示器并支持双TF卡读卡器功能。代码涵盖了HAL层、设备驱动层、系统服务层和应用层,并初步展示了显示驱动、音频驱动、SD卡驱动、按键驱动以及FatFS文件系统的集成。

为了进一步完善和提升系统,可以考虑以下方面:

  1. HDMI 输入处理模块: 实现 HDMI 输入信号的接收、解码和处理,将视频数据写入显示缓冲区,音频数据传递给音频驱动。这部分可能需要使用专门的 HDMI 接收芯片和相应的驱动。

  2. 用户界面框架: 构建更完善的用户界面框架,例如使用简单的 GUI 库,实现菜单、图标、文本显示等功能,提升用户体验。

  3. 触摸屏支持: 如果硬件支持触摸屏,需要添加触摸屏驱动和触摸事件处理,实现触摸控制功能。

  4. 电源管理优化: 深入优化电源管理策略,例如实现更精细的背光亮度控制、动态电压频率调整 (DVFS)、更完善的睡眠模式等,降低功耗,延长电池续航时间。

  5. 固件升级机制: 设计安全的固件升级机制,支持通过 SD 卡或 USB 等方式进行固件升级,方便后续维护和功能扩展。

  6. 测试与验证: 进行充分的单元测试、集成测试和系统测试,确保系统的可靠性和稳定性。

  7. 代码优化与性能提升: 对代码进行性能分析和优化,例如使用 DMA 数据传输、优化算法、减少内存占用等,提高系统运行效率。

请注意,以上代码仅为示例和架构演示,实际项目开发中需要根据具体的硬件平台、芯片型号和功能需求进行详细设计和实现。 代码量要达到3000行以上,需要进一步扩展各个模块的功能,例如更完善的显示效果(双缓冲、图像处理)、更丰富的音频功能(音频解码、EQ调节)、更复杂的文件操作、更友好的用户界面等等。 同时,需要加入大量的注释、错误处理、边界条件检查、代码规范和文档,才能构建一个高质量的嵌入式系统。

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