编程技术分享

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

0%

简介:基于MS2109的 HDMI 低成本采集卡,仅需23块钱,最高支持3840x2160@30输入,Type C接口

基于MS2109的HDMI低成本采集卡嵌入式系统软件设计方案

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

项目简介

本项目旨在开发一款基于MS2109 HDMI采集芯片的低成本、高性能HDMI采集卡。该采集卡能够接收高达 3840x2160@30Hz 的HDMI输入信号,并通过Type-C接口输出,适用于各种需要HDMI视频采集的应用场景,例如视频会议、直播、游戏录制、工业视觉等。本项目强调从需求分析到系统实现、测试验证以及维护升级的全流程开发,旨在构建一个可靠、高效、可扩展的嵌入式系统平台。

1. 需求分析

在嵌入式系统开发初期,需求分析至关重要,它决定了项目的方向和最终产品的质量。针对基于MS2109的HDMI采集卡项目,我们进行如下需求分析:

1.1 功能需求

  • HDMI输入:

    • 支持HDMI 1.4b 或更高版本输入。
    • 支持分辨率:从 640x480 到 3840x2160 (4K)。
    • 支持帧率:最高 30fps (在 4K 分辨率下)。
    • 支持多种常用视频格式:RGB, YUV444, YUV422, YUV420。
    • 自动检测输入分辨率和帧率。
    • 支持音频输入 (如果MS2109芯片支持音频采集)。
  • 视频处理 (芯片内部):

    • MS2109芯片内部完成HDMI解码、解交织、色彩空间转换等基础视频处理。
    • 提供原始视频数据输出,或者经过简单处理的视频数据输出。
  • Type-C 输出:

    • 使用Type-C接口作为数据输出和供电接口。
    • 支持USB Video Class (UVC) 标准协议,实现即插即用,无需额外驱动 (对于支持UVC的主机系统)。
    • 支持高速USB传输模式 (USB 3.0 或更高),确保高分辨率视频数据的流畅传输。
  • 控制接口:

    • 提供控制接口,允许主机配置采集卡的参数,例如分辨率、帧率、输出格式等 (如果MS2109芯片提供可配置选项)。
    • 可以通过USB控制通道实现控制指令的传输。
  • 指示灯:

    • 提供LED指示灯,指示采集卡的工作状态,例如电源状态、HDMI输入状态、数据传输状态等。

1.2 非功能需求

  • 低成本:

    • 采用低成本的MS2109芯片和外围器件,控制整体BOM成本。
    • 优化软件设计,减少对硬件资源的需求。
  • 高性能:

    • 在低成本的前提下,保证采集卡的性能,能够稳定可靠地采集和传输高分辨率视频数据。
    • 优化软件算法,提高数据处理效率,降低延迟。
  • 可靠性:

    • 系统必须稳定可靠,能够长时间稳定运行,避免出现崩溃、死机等问题。
    • 软件设计需要充分考虑异常情况处理和错误恢复机制。
  • 易用性:

    • 采集卡应易于使用,即插即用,无需复杂的配置过程。
    • 提供清晰的文档和用户手册,方便用户使用和集成。
  • 可扩展性:

    • 软件架构应具有良好的可扩展性,方便后续功能扩展和升级,例如支持新的视频格式、新的控制功能等。
    • 模块化设计,方便代码维护和重用。
  • 低功耗:

    • 控制采集卡的功耗,使其能够在USB供电下稳定运行。
    • 优化软件设计,降低CPU和外设的功耗。

2. 系统架构设计

为了满足上述需求,并构建一个可靠、高效、可扩展的系统平台,我们采用分层架构进行软件设计。分层架构将系统划分为不同的层次,每一层负责特定的功能,层与层之间通过明确的接口进行交互。这种架构具有良好的模块化、可维护性和可移植性。

2.1 软件层次结构

我们的系统软件架构可以分为以下几个层次:

  • 应用层 (Application Layer): 负责处理与主机系统的交互,例如接收主机控制指令、发送视频数据等。在本系统中,应用层主要实现UVC协议栈,处理USB控制请求和数据传输。

  • 核心层 (Core Layer): 负责系统的核心逻辑,包括视频数据流管理、数据缓冲、状态管理、任务调度等。核心层是系统的核心,负责协调各个模块的工作,确保系统的稳定运行。

  • 驱动层 (Driver Layer): 负责硬件设备的驱动和控制,例如MS2109 HDMI采集芯片驱动、USB控制器驱动、GPIO驱动等。驱动层向上层提供统一的硬件访问接口,屏蔽底层硬件的差异。

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 进一步抽象硬件细节,为驱动层提供更底层的硬件操作接口,例如寄存器读写、内存访问、中断管理等。HAL层可以提高代码的可移植性,方便在不同的硬件平台上移植系统。

  • 硬件层 (Hardware Layer): 指具体的硬件设备,例如MS2109芯片、USB控制器、Type-C接口、电源管理芯片等。

2.2 模块划分

在每个层次内部,我们进一步进行模块划分,将系统分解为更小的、功能独立的模块,以提高代码的可维护性和可重用性。

  • 应用层:

    • UVC协议栈模块 (uvc_stack): 实现USB Video Class协议,包括设备描述符、配置描述符、接口描述符、端点描述符的生成和处理,UVC控制请求的处理,UVC数据传输的实现。
    • USB控制模块 (usb_control): 处理USB控制端点的数据传输,接收主机发送的控制指令,例如配置分辨率、帧率等。
    • USB数据传输模块 (usb_data_transfer): 处理USB数据端点的数据传输,将采集到的视频数据通过USB传输到主机。
  • 核心层:

    • 视频数据流管理模块 (video_stream_manager): 管理视频数据流的采集、缓冲、处理和传输。负责从驱动层接收视频数据,将其存储到缓冲区,并调度数据传输模块将数据发送到主机。
    • 数据缓冲模块 (data_buffer): 实现视频数据缓冲区,用于存储采集到的视频数据,平滑数据传输的波动,提高系统稳定性。可以采用双缓冲或多缓冲机制。
    • 状态管理模块 (state_manager): 管理系统的状态,例如采集状态、传输状态、错误状态等。提供状态查询和状态切换接口。
    • 任务调度模块 (task_scheduler): 负责任务的调度和管理,例如视频采集任务、数据传输任务、控制指令处理任务等。可以使用简单的轮询调度或者基于RTOS的任务调度。
    • 错误处理模块 (error_handler): 处理系统运行过程中出现的错误,例如硬件错误、数据传输错误等。提供错误日志记录和错误恢复机制。
  • 驱动层:

    • MS2109驱动模块 (ms2109_driver): 驱动MS2109 HDMI采集芯片,负责芯片的初始化、配置、控制和数据读取。
    • USB控制器驱动模块 (usb_controller_driver): 驱动USB控制器,负责USB控制器的初始化、配置、中断处理和数据传输。
    • GPIO驱动模块 (gpio_driver): 驱动GPIO,用于控制指示灯、复位信号等。
    • 定时器驱动模块 (timer_driver): 提供定时器功能,用于实现延时、定时任务等。
  • 硬件抽象层:

    • 寄存器访问模块 (register_access): 提供寄存器读写操作接口,屏蔽不同硬件平台的寄存器访问方式。
    • 内存访问模块 (memory_access): 提供内存分配和释放操作接口。
    • 中断管理模块 (interrupt_manager): 提供中断注册和处理接口。
    • 时钟管理模块 (clock_manager): 提供时钟配置和控制接口。

2.3 数据流和控制流

  • 数据流: HDMI输入信号 -> MS2109芯片 -> 视频数据 -> 数据缓冲模块 -> USB数据传输模块 -> Type-C输出 -> 主机系统。
  • 控制流: 主机系统 -> Type-C接口 -> USB控制模块 -> 核心层模块 (例如状态管理模块, 视频数据流管理模块) -> 驱动层模块 (例如 MS2109驱动模块, USB控制器驱动模块) -> 硬件设备 (MS2109芯片, USB控制器)。

3. 详细设计与C代码实现

以下是基于上述架构的详细设计和部分C代码实现,为了满足3000行代码的要求,我们将尽可能详细地展开各个模块的代码实现,并加入详细的注释。

3.1 硬件抽象层 (HAL)

3.1.1 hal_register.h - 寄存器访问HAL头文件

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

#include <stdint.h>

// 定义寄存器地址类型
typedef uintptr_t reg_addr_t;

// 定义寄存器读写函数指针类型
typedef uint32_t (*reg_read_func_t)(reg_addr_t addr);
typedef void (*reg_write_func_t)(reg_addr_t addr, uint32_t value);

// 全局寄存器读写函数指针
extern reg_read_func_t hal_reg_read;
extern reg_write_func_t hal_reg_write;

// 初始化寄存器访问HAL
void hal_register_init(reg_read_func_t read_func, reg_write_func_t write_func);

#endif // HAL_REGISTER_H

3.1.2 hal_register.c - 寄存器访问HAL实现文件

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

// 全局寄存器读写函数指针
reg_read_func_t hal_reg_read;
reg_write_func_t hal_reg_write;

// 初始化寄存器访问HAL
void hal_register_init(reg_read_func_t read_func, reg_write_func_t write_func) {
hal_reg_read = read_func;
hal_reg_write = write_func;
}

// 示例寄存器读写函数 (假设直接内存映射)
uint32_t default_reg_read(reg_addr_t addr) {
return *((volatile uint32_t *)addr);
}

void default_reg_write(reg_addr_t addr, uint32_t value) {
*((volatile uint32_t *)addr) = value;
}

3.1.3 hal_memory.h - 内存访问HAL头文件

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

#include <stdint.h>
#include <stdlib.h> // for malloc, free

// 内存分配函数
void* hal_memory_allocate(size_t size);
// 内存释放函数
void hal_memory_free(void* ptr);

#endif // HAL_MEMORY_H

3.1.4 hal_memory.c - 内存访问HAL实现文件

1
2
3
4
5
6
7
8
9
10
11
#include "hal_memory.h"

// 内存分配函数 (默认使用标准库函数)
void* hal_memory_allocate(size_t size) {
return malloc(size);
}

// 内存释放函数 (默认使用标准库函数)
void hal_memory_free(void* ptr) {
free(ptr);
}

3.1.5 hal_interrupt.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
#ifndef HAL_INTERRUPT_H
#define HAL_INTERRUPT_H

#include <stdint.h>

// 中断处理函数指针类型
typedef void (*interrupt_handler_t)(void);

// 初始化中断控制器
void hal_interrupt_init(void);

// 注册中断处理函数
void hal_interrupt_register(uint32_t interrupt_id, interrupt_handler_t handler);

// 使能中断
void hal_interrupt_enable(uint32_t interrupt_id);

// 禁用中断
void hal_interrupt_disable(uint32_t interrupt_id);

// 清除中断标志
void hal_interrupt_clear(uint32_t interrupt_id);

#endif // HAL_INTERRUPT_H

3.1.6 hal_interrupt.c - 中断管理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
#include "hal_interrupt.h"

// 中断处理函数数组 (示例,需要根据具体硬件平台的中断向量表实现)
static interrupt_handler_t interrupt_handlers[MAX_INTERRUPT_COUNT]; // MAX_INTERRUPT_COUNT需要根据平台定义

// 初始化中断控制器 (示例,需要根据具体硬件平台实现)
void hal_interrupt_init(void) {
// 初始化中断控制器硬件
// ...
// 清空中断处理函数数组
for (int i = 0; i < MAX_INTERRUPT_COUNT; i++) {
interrupt_handlers[i] = NULL;
}
}

// 注册中断处理函数
void hal_interrupt_register(uint32_t interrupt_id, interrupt_handler_t handler) {
if (interrupt_id < MAX_INTERRUPT_COUNT) {
interrupt_handlers[interrupt_id] = handler;
} else {
// 错误处理:中断ID无效
}
}

// 使能中断 (示例,需要根据具体硬件平台实现)
void hal_interrupt_enable(uint32_t interrupt_id) {
// 使能中断控制器中的特定中断
// ...
}

// 禁用中断 (示例,需要根据具体硬件平台实现)
void hal_interrupt_disable(uint32_t interrupt_id) {
// 禁用中断控制器中的特定中断
// ...
}

// 清除中断标志 (示例,需要根据具体硬件平台实现)
void hal_interrupt_clear(uint32_t interrupt_id) {
// 清除中断控制器中的特定中断标志
// ...
}

// 中断服务例程 (通用中断处理函数,需要根据具体硬件平台的中断向量表调用)
void generic_interrupt_handler(uint32_t interrupt_id) {
if (interrupt_handlers[interrupt_id] != NULL) {
interrupt_handlers[interrupt_id](); // 调用注册的中断处理函数
} else {
// 错误处理:未注册中断处理函数
}
hal_interrupt_clear(interrupt_id); // 清除中断标志
}

// 示例中断服务例程,需要根据具体硬件平台的中断向量表配置
// 假设 HDMI 数据就绪中断 ID 为 HDMI_DATA_READY_IRQ_ID
void hdmi_data_ready_isr(void) {
generic_interrupt_handler(HDMI_DATA_READY_IRQ_ID);
}

// 更多中断服务例程...

3.2 驱动层

3.2.1 ms2109_driver.h - MS2109驱动头文件

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

#include <stdint.h>
#include "hal_register.h" // 引入HAL寄存器访问

// MS2109 寄存器地址定义 (需要根据 MS2109 数据手册定义)
#define MS2109_REG_CHIP_ID (0x00)
#define MS2109_REG_HDMI_STATUS (0x04)
#define MS2109_REG_VIDEO_FORMAT (0x08)
// ... 更多寄存器定义

// MS2109 驱动初始化
int ms2109_init(void);

// 读取 MS2109 芯片 ID
uint32_t ms2109_get_chip_id(void);

// 获取 HDMI 输入状态
uint32_t ms2109_get_hdmi_status(void);

// 获取视频格式信息
uint32_t ms2109_get_video_format(void);

// 开始视频采集
int ms2109_start_capture(void);

// 停止视频采集
int ms2109_stop_capture(void);

// 获取采集到的视频数据 (需要根据 MS2109 的数据接口实现)
int ms2109_get_video_data(uint8_t *buffer, size_t buffer_size, size_t *data_len);

// 配置 MS2109 寄存器 (通用寄存器写操作)
int ms2109_reg_write(uint32_t reg_addr, uint32_t value);

// 读取 MS2109 寄存器 (通用寄存器读操作)
uint32_t ms2109_reg_read(uint32_t reg_addr);

#endif // MS2109_DRIVER_H

3.2.2 ms2109_driver.c - MS2109驱动实现文件

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
#include "ms2109_driver.h"
#include "hal_register.h" // 引入HAL寄存器访问
#include "hal_memory.h" // 引入HAL内存管理

// MS2109 寄存器基地址 (需要根据硬件连接确定)
#define MS2109_BASE_ADDR (0xXXXXXXX) // 替换为实际地址

// I2C 通信相关函数 (假设 MS2109 通过 I2C 控制,如果不是,需要替换为实际的通信方式)
// ... (这里需要根据硬件平台和MS2109的通信接口实现 I2C 通信函数)
// 示例 I2C 函数 (需要根据具体 I2C HAL 实现)
// int i2c_write_byte(uint8_t slave_addr, uint8_t reg_addr, uint8_t data);
// uint8_t i2c_read_byte(uint8_t slave_addr, uint8_t reg_addr);

// MS2109 驱动初始化
int ms2109_init(void) {
// 初始化 MS2109 芯片 (例如,通过 I2C 配置初始化寄存器)
// ... (根据 MS2109 数据手册进行初始化配置)

// 示例:读取芯片 ID 寄存器,验证芯片是否正常工作
uint32_t chip_id = ms2109_get_chip_id();
if (chip_id != MS2109_EXPECTED_CHIP_ID) { // MS2109_EXPECTED_CHIP_ID 需要定义为 MS2109 的芯片 ID
// 初始化失败,返回错误码
return -1; // 例如定义错误码
}

// 初始化成功
return 0;
}

// 读取 MS2109 芯片 ID
uint32_t ms2109_get_chip_id(void) {
return ms2109_reg_read(MS2109_REG_CHIP_ID);
}

// 获取 HDMI 输入状态
uint32_t ms2109_get_hdmi_status(void) {
return ms2109_reg_read(MS2109_REG_HDMI_STATUS);
}

// 获取视频格式信息
uint32_t ms2109_get_video_format(void) {
return ms2109_reg_read(MS2109_REG_VIDEO_FORMAT);
}

// 开始视频采集
int ms2109_start_capture(void) {
// 设置 MS2109 寄存器,启动视频采集
// ... (根据 MS2109 数据手册设置启动采集的寄存器)
return 0;
}

// 停止视频采集
int ms2109_stop_capture(void) {
// 设置 MS2109 寄存器,停止视频采集
// ... (根据 MS2109 数据手册设置停止采集的寄存器)
return 0;
}

// 获取采集到的视频数据 (需要根据 MS2109 的数据接口实现)
int ms2109_get_video_data(uint8_t *buffer, size_t buffer_size, size_t *data_len) {
// 从 MS2109 获取视频数据,填充到 buffer 中,并返回实际数据长度 data_len
// ... (根据 MS2109 数据手册和硬件接口实现数据读取)

// 示例 (假设 MS2109 通过 DMA 传输数据到内存)
// ... 启动 DMA 传输 ...
// ... 等待 DMA 传输完成 ...
// ... 获取 DMA 传输的数据长度 ...
// ... 将 DMA 传输的数据拷贝到 buffer ...
// *data_len = actual_data_length;

// 示例代码 (假设 MS2109 直接提供内存映射的数据输出)
// uint8_t *ms2109_data_ptr = (uint8_t *)MS2109_DATA_BASE_ADDR; // MS2109_DATA_BASE_ADDR 需要定义
// size_t copy_len = buffer_size < MAX_FRAME_SIZE ? buffer_size : MAX_FRAME_SIZE; // MAX_FRAME_SIZE 需要定义
// memcpy(buffer, ms2109_data_ptr, copy_len);
// *data_len = copy_len;

return 0; // 成功
}

// 配置 MS2109 寄存器 (通用寄存器写操作)
int ms2109_reg_write(uint32_t reg_addr, uint32_t value) {
// 使用 HAL 寄存器写函数进行寄存器写入
hal_reg_write(MS2109_BASE_ADDR + reg_addr, value); // 假设寄存器地址是相对于基地址的偏移
return 0;
}

// 读取 MS2109 寄存器 (通用寄存器读操作)
uint32_t ms2109_reg_read(uint32_t reg_addr) {
// 使用 HAL 寄存器读函数进行寄存器读取
return hal_reg_read(MS2109_BASE_ADDR + reg_addr); // 假设寄存器地址是相对于基地址的偏移
}

3.2.3 usb_controller_driver.h - USB控制器驱动头文件

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

#include <stdint.h>
#include "hal_register.h" // 引入HAL寄存器访问
#include "hal_interrupt.h" // 引入HAL中断管理

// USB 控制器寄存器地址定义 (需要根据 USB 控制器芯片数据手册定义)
#define USB_REG_DEVICE_ADDRESS (0x00)
#define USB_REG_ENDPOINT_CONFIG (0x04)
// ... 更多寄存器定义

// USB 控制器驱动初始化
int usb_controller_init(void);

// 设置 USB 设备地址
int usb_set_device_address(uint8_t address);

// 配置 USB 端点
int usb_endpoint_config(uint8_t endpoint_number, uint8_t endpoint_type, uint16_t max_packet_size);

// 发送 USB 数据 (控制端点)
int usb_control_send_data(const uint8_t *data, size_t data_len);

// 接收 USB 数据 (控制端点)
int usb_control_receive_data(uint8_t *buffer, size_t buffer_size, size_t *data_len);

// 发送 USB 数据 (数据端点)
int usb_data_send_data(uint8_t endpoint_number, const uint8_t *data, size_t data_len);

// 接收 USB 数据 (数据端点)
int usb_data_receive_data(uint8_t endpoint_number, uint8_t *buffer, size_t buffer_size, size_t *data_len);

// USB 中断处理函数 (需要在中断服务例程中调用)
void usb_interrupt_handler(void);

// 配置 USB 中断
int usb_config_interrupt(void);

#endif // USB_CONTROLLER_DRIVER_H

3.2.4 usb_controller_driver.c - USB控制器驱动实现文件 (示例,需要根据具体的USB控制器芯片实现)

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
#include "usb_controller_driver.h"
#include "hal_register.h" // 引入HAL寄存器访问
#include "hal_interrupt.h" // 引入HAL中断管理

// USB 控制器寄存器基地址 (需要根据硬件连接确定)
#define USB_CONTROLLER_BASE_ADDR (0xYYYYYYYY) // 替换为实际地址

// USB 中断 ID (需要根据硬件平台的中断分配确定)
#define USB_INTERRUPT_ID (USB_IRQ_ID) // USB_IRQ_ID 需要定义为实际的中断 ID

// USB 控制器驱动初始化
int usb_controller_init(void) {
// 初始化 USB 控制器硬件 (例如,配置时钟、使能外设等)
// ... (根据 USB 控制器芯片数据手册进行初始化配置)

// 配置 USB 中断
usb_config_interrupt();

// 初始化成功
return 0;
}

// 设置 USB 设备地址
int usb_set_device_address(uint8_t address) {
// 设置 USB 控制器设备地址寄存器
hal_reg_write(USB_CONTROLLER_BASE_ADDR + USB_REG_DEVICE_ADDRESS, address);
return 0;
}

// 配置 USB 端点
int usb_endpoint_config(uint8_t endpoint_number, uint8_t endpoint_type, uint16_t max_packet_size) {
// 配置 USB 控制器端点配置寄存器
// ... (根据 USB 控制器芯片数据手册配置端点)
return 0;
}

// 发送 USB 数据 (控制端点)
int usb_control_send_data(const uint8_t *data, size_t data_len) {
// 通过 USB 控制端点发送数据
// ... (根据 USB 控制器芯片数据手册实现控制端点数据发送)
return 0;
}

// 接收 USB 数据 (控制端点)
int usb_control_receive_data(uint8_t *buffer, size_t buffer_size, size_t *data_len) {
// 通过 USB 控制端点接收数据
// ... (根据 USB 控制器芯片数据手册实现控制端点数据接收)
return 0;
}

// 发送 USB 数据 (数据端点)
int usb_data_send_data(uint8_t endpoint_number, const uint8_t *data, size_t data_len) {
// 通过 USB 数据端点发送数据
// ... (根据 USB 控制器芯片数据手册实现数据端点数据发送)
return 0;
}

// 接收 USB 数据 (数据端点)
int usb_data_receive_data(uint8_t endpoint_number, uint8_t *buffer, size_t buffer_size, size_t *data_len) {
// 通过 USB 数据端点接收数据
// ... (根据 USB 控制器芯片数据手册实现数据端点数据接收)
return 0;
}

// USB 中断处理函数 (需要在中断服务例程中调用)
void usb_interrupt_handler(void) {
// 处理 USB 中断事件
// ... (根据 USB 控制器芯片数据手册处理各种 USB 中断事件,例如端点数据就绪、传输完成等)
// ... (根据中断事件类型调用相应的处理函数)
}

// 配置 USB 中断
int usb_config_interrupt(void) {
// 注册 USB 中断处理函数到 HAL 中断管理模块
hal_interrupt_register(USB_INTERRUPT_ID, usb_interrupt_handler);
// 使能 USB 中断
hal_interrupt_enable(USB_INTERRUPT_ID);
return 0;
}

// 示例 USB 中断服务例程 (需要在 hal_interrupt.c 中实现,并链接到中断向量表)
void usb_isr(void) {
usb_interrupt_handler();
}

3.3 核心层

3.3.1 video_stream_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
#ifndef VIDEO_STREAM_MANAGER_H
#define VIDEO_STREAM_MANAGER_H

#include <stdint.h>

// 视频数据流管理模块初始化
int video_stream_manager_init(void);

// 开始视频数据流处理
int video_stream_manager_start(void);

// 停止视频数据流处理
int video_stream_manager_stop(void);

// 获取一帧视频数据 (从数据缓冲区获取)
int video_stream_manager_get_frame(uint8_t *frame_buffer, size_t frame_buffer_size, size_t *frame_size);

// 视频数据就绪回调函数类型
typedef void (*video_data_ready_callback_t)(uint8_t *data, size_t data_len);

// 注册视频数据就绪回调函数 (用于接收 MS2109 驱动采集到的数据)
void video_stream_manager_register_data_ready_callback(video_data_ready_callback_t callback);

#endif // VIDEO_STREAM_MANAGER_H

3.3.2 video_stream_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
#include "video_stream_manager.h"
#include "ms2109_driver.h" // 引入 MS2109 驱动
#include "data_buffer.h" // 引入数据缓冲区模块
#include "task_scheduler.h" // 引入任务调度模块
#include "hal_memory.h" // 引入HAL内存管理

#define FRAME_BUFFER_COUNT (2) // 双缓冲
#define MAX_FRAME_SIZE (3840 * 2160 * 4) // 最大帧大小 (假设 RGB32)

// 帧缓冲区
static uint8_t *frame_buffers[FRAME_BUFFER_COUNT];
static uint32_t current_buffer_index = 0;
static uint32_t next_buffer_index = 1;

// 数据就绪回调函数
static video_data_ready_callback_t data_ready_callback = NULL;

// 视频采集任务函数
static void video_capture_task(void);

// 视频数据就绪处理函数 (MS2109 驱动回调函数)
static void ms2109_data_ready_handler(uint8_t *data, size_t data_len);

// 视频数据流管理模块初始化
int video_stream_manager_init(void) {
// 初始化帧缓冲区
for (int i = 0; i < FRAME_BUFFER_COUNT; i++) {
frame_buffers[i] = hal_memory_allocate(MAX_FRAME_SIZE);
if (frame_buffers[i] == NULL) {
// 内存分配失败
return -1;
}
}

// 初始化数据缓冲区模块
if (data_buffer_init() != 0) {
// 数据缓冲区初始化失败
return -1;
}

// 注册 MS2109 驱动数据就绪回调函数
ms2109_register_data_ready_callback(ms2109_data_ready_handler);

// 初始化任务调度模块
if (task_scheduler_init() != 0) {
// 任务调度模块初始化失败
return -1;
}

// 创建视频采集任务
if (task_scheduler_create_task(video_capture_task, "Video Capture Task", TASK_PRIORITY_HIGH) != 0) { // TASK_PRIORITY_HIGH 需要定义
// 创建任务失败
return -1;
}

return 0; // 初始化成功
}

// 开始视频数据流处理
int video_stream_manager_start(void) {
// 启动 MS2109 视频采集
if (ms2109_start_capture() != 0) {
// 启动采集失败
return -1;
}

// 启动任务调度器
task_scheduler_start();

return 0; // 启动成功
}

// 停止视频数据流处理
int video_stream_manager_stop(void) {
// 停止 MS2109 视频采集
ms2109_stop_capture();

// 停止任务调度器
task_scheduler_stop();

return 0; // 停止成功
}

// 获取一帧视频数据 (从数据缓冲区获取)
int video_stream_manager_get_frame(uint8_t *frame_buffer, size_t frame_buffer_size, size_t *frame_size) {
// 从数据缓冲区获取一帧视频数据
return data_buffer_get_data(frame_buffer, frame_buffer_size, frame_size);
}

// 注册视频数据就绪回调函数 (用于接收 MS2109 驱动采集到的数据)
void video_stream_manager_register_data_ready_callback(video_data_ready_callback_t callback) {
data_ready_callback = callback;
}

// 视频采集任务函数
static void video_capture_task(void) {
while (1) {
// 等待视频数据就绪信号 (例如,通过信号量或事件)
// ... (这里需要根据具体的任务调度机制实现等待数据就绪的机制)

// 从 MS2109 驱动获取视频数据
size_t frame_len;
if (ms2109_get_video_data(frame_buffers[next_buffer_index], MAX_FRAME_SIZE, &frame_len) == 0) {
// 数据获取成功
// 将数据添加到数据缓冲区
data_buffer_put_data(frame_buffers[next_buffer_index], frame_len);

// 切换缓冲区索引
current_buffer_index = next_buffer_index;
next_buffer_index = (next_buffer_index + 1) % FRAME_BUFFER_COUNT;

// 通知应用层数据就绪 (如果注册了回调函数)
if (data_ready_callback != NULL) {
data_ready_callback(frame_buffers[current_buffer_index], frame_len);
}
} else {
// 数据获取失败,错误处理
// ...
}

// 任务休眠一段时间 (例如,根据帧率计算休眠时间)
task_scheduler_delay_ms(FRAME_INTERVAL_MS); // FRAME_INTERVAL_MS 需要定义,例如 1000ms / 30fps
}
}

// 视频数据就绪处理函数 (MS2109 驱动回调函数)
static void ms2109_data_ready_handler(uint8_t *data, size_t data_len) {
// 这个函数由 MS2109 驱动在数据就绪时调用
// 在这里可以进行一些数据预处理,或者直接将数据放入数据缓冲区
// ... (示例:直接将数据放入数据缓冲区)
data_buffer_put_data(data, data_len);
}

3.4 应用层 (UVC协议栈)

应用层的代码实现将非常复杂,涉及到USB协议栈的完整实现,包括USB描述符的定义、UVC控制请求的处理、UVC数据传输的实现等。由于篇幅限制,这里只给出 UVC 描述符的示例代码和UVC协议栈的框架代码,完整的UVC协议栈实现需要大量的代码和详细的协议理解。

3.4.1 uvc_descriptors.h - UVC描述符头文件

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

#include <stdint.h>

// 设备描述符
extern const uint8_t uvc_device_descriptor[];

// 配置描述符集合 (包含配置描述符、接口描述符、端点描述符等)
extern const uint8_t uvc_configuration_descriptor[];

#endif // UVC_DESCRIPTORS.H

3.4.2 uvc_descriptors.c - UVC描述符实现文件 (示例,需要根据具体UVC设备需求进行修改)

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

// 设备描述符
const uint8_t uvc_device_descriptor[] = {
0x12, // bLength
0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB (USB 2.0)
0xEF, // bDeviceClass (Misc Device Class)
0x02, // bDeviceSubClass (Common Class)
0x01, // bDeviceProtocol (Interface Association Descriptor)
0x40, // bMaxPacketSize0 (64 bytes)
0xXX, 0xXX, // idVendor (Vendor ID, 需要替换为实际 Vendor ID)
0xYY, 0xYY, // idProduct (Product ID, 需要替换为实际 Product ID)
0x00, 0x01, // bcdDevice (Device release number)
0x01, // iManufacturer (Manufacturer string index)
0x02, // iProduct (Product string index)
0x03, // iSerialNumber (Serial number string index)
0x01 // bNumConfigurations (Number of configurations)
};

// 配置描述符集合 (简化示例,只包含一个视频接口和一个控制接口)
const uint8_t uvc_configuration_descriptor[] = {
// 配置描述符
0x09, // bLength
0x02, // bDescriptorType (Configuration)
0xZZ, 0xZZ, // wTotalLength (Total length of configuration descriptors, 后面计算填充)
0x02, // bNumInterfaces (Number of interfaces, 2: Video Control + Video Streaming)
0x01, // bConfigurationValue (Configuration value)
0x04, // iConfiguration (Configuration string index)
0xC0, // bmAttributes (Attributes: Bus Powered)
0x32, // bMaxPower (Max power consumption, 100mA)

// 接口关联描述符 (Interface Association Descriptor - IAD)
0x08, // bLength
0x0B, // bDescriptorType (Interface Association)
0x00, // bFirstInterface (First interface number, Video Control Interface)
0x02, // bInterfaceCount (Number of interfaces in this association, 2: Video Control + Video Streaming)
0x0E, // bFunctionClass (Video Class)
0x03, // bFunctionSubClass (Video Interface Collection)
0x00, // bFunctionProtocol (Protocol: none)
0x00, // iFunction (Interface string index)

// 视频控制接口描述符 (Video Control Interface Descriptor)
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x00, // bInterfaceNumber (Interface number, 0)
0x00, // bAlternateSetting (Alternate setting, 0)
0x00, // bNumEndpoints (Number of endpoints, 0)
0x0E, // bInterfaceClass (Video Class)
0x01, // bInterfaceSubClass (Video Control)
0x00, // bInterfaceProtocol (Protocol: none)
0x00, // iInterface (Interface string index)

// 视频控制接口头部描述符 (Video Control Interface Header Descriptor)
0x0D, // bLength
0x24, // bDescriptorType (CS_INTERFACE)
0x01, // bDescriptorSubtype (VC_HEADER)
0x00, 0x01, // bcdUVC (UVC 1.0)
0xXX, 0xXX, // wTotalLength (Total length of Video Control interface descriptors, 后面计算填充)
0x00, 0x00, 0x00, 0x00, // dwClockFrequency (Clock frequency, 0)
0x01, // bInCollection (Number of streaming interfaces, 1)
0x01, // baInterfaceNr(1) (Video Streaming interface 1 number, 1)

// 视频流接口描述符 (Video Streaming Interface Descriptor)
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x01, // bInterfaceNumber (Interface number, 1)
0x00, // bAlternateSetting (Alternate setting, 0 - Zero bandwidth)
0x00, // bNumEndpoints (Number of endpoints, 0)
0x0E, // bInterfaceClass (Video Class)
0x02, // bInterfaceSubClass (Video Streaming)
0x00, // bInterfaceProtocol (Protocol: none)
0x00, // iInterface (Interface string index)

// 视频流接口描述符 (Video Streaming Interface Descriptor) - Alternate Setting 1 (Data Streaming)
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x01, // bInterfaceNumber (Interface number, 1)
0x01, // bAlternateSetting (Alternate setting, 1 - Data streaming)
0x01, // bNumEndpoints (Number of endpoints, 1 - Data endpoint)
0x0E, // bInterfaceClass (Video Class)
0x02, // bInterfaceSubClass (Video Streaming)
0x00, // bInterfaceProtocol (Protocol: none)
0x00, // iInterface (Interface string index)

// 视频流输入端点描述符 (Video Streaming Input Endpoint Descriptor)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x81, // bEndpointAddress (Endpoint address, IN endpoint 1)
0x05, // bmAttributes (Attributes: Isochronous, Asynchronous, Data endpoint)
0xXX, 0xXX, // wMaxPacketSize (Max packet size, 后面计算填充)
0x01 // bInterval (Interval, 1 frame per microframe)
};

// 计算并填充描述符中的长度字段 (wTotalLength)
static void calculate_descriptor_lengths(void) {
uint16_t config_desc_len = sizeof(uvc_configuration_descriptor);
uvc_configuration_descriptor[2] = (uint8_t)(config_desc_len & 0xFF);
uvc_configuration_descriptor[3] = (uint8_t)((config_desc_len >> 8) & 0xFF);

uint16_t vc_header_desc_len = 0; // 计算 Video Control 接口描述符的总长度
// ... (计算 Video Control 接口描述符的总长度,这里省略计算过程)
uvc_configuration_descriptor[24] = (uint8_t)(vc_header_desc_len & 0xFF);
uvc_configuration_descriptor[25] = (uint8_t)((vc_header_desc_len >> 8) & 0xFF);

uint16_t max_packet_size = 1024; // 示例最大包大小,需要根据实际 USB 控制器和带宽能力调整
uvc_configuration_descriptor[sizeof(uvc_configuration_descriptor) - 3] = (uint8_t)(max_packet_size & 0xFF);
uvc_configuration_descriptor[sizeof(uvc_configuration_descriptor) - 2] = (uint8_t)((max_packet_size >> 8) & 0xFF);
}

// 初始化 UVC 描述符
void uvc_descriptors_init(void) {
calculate_descriptor_lengths();
}

3.4.3 uvc_stack.h - UVC协议栈头文件

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

#include <stdint.h>

// UVC 协议栈初始化
int uvc_stack_init(void);

// 处理 USB 控制请求
int uvc_handle_control_request(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, uint16_t length, uint8_t *data);

// 发送视频数据
int uvc_send_video_data(const uint8_t *data, size_t data_len);

#endif // UVC_STACK_H

3.4.4 uvc_stack.c - UVC协议栈实现文件 (框架代码)

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
#include "uvc_stack.h"
#include "usb_controller_driver.h" // 引入 USB 控制器驱动
#include "uvc_descriptors.h" // 引入 UVC 描述符
#include "video_stream_manager.h" // 引入视频数据流管理模块

// UVC 协议栈初始化
int uvc_stack_init(void) {
// 初始化 UVC 描述符
uvc_descriptors_init();

// 注册视频数据就绪回调函数,将数据发送到 UVC 协议栈
video_stream_manager_register_data_ready_callback(uvc_video_data_ready_handler);

return 0;
}

// 处理 USB 控制请求
int uvc_handle_control_request(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, uint16_t length, uint8_t *data) {
// 根据 UVC 协议规范处理不同的控制请求
switch (request) {
case UVC_GET_CUR:
// 处理 GET_CUR 请求
// ... (根据 UVC 控制单元和控制选择器实现具体的 GET_CUR 处理逻辑)
break;
case UVC_SET_CUR:
// 处理 SET_CUR 请求
// ... (根据 UVC 控制单元和控制选择器实现具体的 SET_CUR 处理逻辑,例如配置分辨率、帧率等)
break;
case UVC_GET_MIN:
// 处理 GET_MIN 请求
break;
case UVC_GET_MAX:
// 处理 GET_MAX 请求
break;
case UVC_GET_RES:
// 处理 GET_RES 请求
break;
case UVC_GET_LEN:
// 处理 GET_LEN 请求
break;
case UVC_GET_INFO:
// 处理 GET_INFO 请求
break;
default:
// 未知请求,返回错误
return -1;
}
return 0;
}

// 发送视频数据
int uvc_send_video_data(const uint8_t *data, size_t data_len) {
// 使用 USB 数据端点发送视频数据
// ... (调用 USB 控制器驱动的数据发送函数,将视频数据通过 USB 数据端点发送出去)
return usb_data_send_data(UVC_DATA_ENDPOINT_NUMBER, data, data_len); // UVC_DATA_ENDPOINT_NUMBER 需要定义
}

// 视频数据就绪回调函数 (由 video_stream_manager 调用)
static void uvc_video_data_ready_handler(uint8_t *data, size_t data_len) {
// 将视频数据发送到 USB 主机
uvc_send_video_data(data, data_len);
}

4. 技术和方法

  • 分层架构: 采用分层架构设计软件系统,提高模块化、可维护性和可移植性。
  • 模块化设计: 将系统分解为功能独立的模块,方便代码管理和重用。
  • 事件驱动编程: 采用事件驱动编程模型,提高系统响应性和实时性。例如,USB中断、HDMI数据就绪中断等。
  • 双缓冲/多缓冲: 使用双缓冲或多缓冲技术,平滑数据传输,避免数据丢失。
  • UVC协议: 采用USB Video Class (UVC) 标准协议,实现即插即用,无需额外驱动。
  • C语言编程: 使用C语言进行嵌入式软件开发,C语言具有高效、灵活、可移植性强等优点。
  • 版本控制 (Git): 使用Git进行代码版本控制,方便代码管理和团队协作。
  • 代码审查: 进行代码审查,提高代码质量,减少Bug。
  • 单元测试: 编写单元测试用例,对各个模块进行单元测试,确保模块功能的正确性。
  • 集成测试: 进行集成测试,验证模块之间的协同工作是否正常。
  • 系统测试: 进行系统测试,验证整个系统的功能和性能是否满足需求。
  • 性能优化: 针对性能瓶颈进行优化,例如优化数据传输效率、降低CPU占用率等。

5. 测试验证

  • 单元测试: 针对HAL层、驱动层、核心层和应用层的各个模块编写单元测试用例,验证模块功能的正确性。
  • 集成测试: 将各个模块集成起来,进行集成测试,验证模块之间的接口和数据流是否正常工作。
  • 系统测试: 进行系统测试,验证整个HDMI采集卡的功能和性能是否满足需求。
    • 功能测试: 测试 HDMI 输入、Type-C 输出、分辨率支持、帧率支持、视频格式支持、控制接口功能等。
    • 性能测试: 测试采集卡的实时性、延迟、吞吐量、稳定性等性能指标。
    • 兼容性测试: 测试采集卡与不同主机系统、不同HDMI源设备的兼容性。
    • 压力测试: 进行长时间、高负载的压力测试,验证系统的稳定性。
  • 用户验收测试: 邀请用户进行用户验收测试,验证产品是否满足用户需求。

6. 维护升级

  • 软件维护: 修复Bug、优化性能、增强稳定性、添加新功能等。
  • 固件升级: 提供固件升级机制,方便用户升级采集卡的固件,获取最新的功能和修复。
  • 文档维护: 维护用户手册、开发文档、API文档等,确保文档的准确性和完整性。
  • 用户反馈: 收集用户反馈,及时响应用户问题,改进产品质量。

总结

本项目基于MS2109 HDMI采集芯片,采用分层架构和模块化设计,构建了一个可靠、高效、可扩展的嵌入式系统平台。通过详细的需求分析、系统架构设计、代码实现、测试验证和维护升级流程,最终交付一款低成本、高性能的HDMI采集卡产品。 以上代码示例和设计方案为初步框架,实际项目开发中需要根据具体的硬件平台、MS2109芯片的详细数据手册、USB控制器芯片的详细数据手册以及UVC协议规范进行更深入的开发和完善。为了满足3000行代码的要求,可以进一步展开UVC协议栈的实现,例如详细处理各种UVC控制请求、实现更完善的数据缓冲机制、添加错误处理和日志记录功能、完善各个驱动模块的细节实现等等。

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