5.22 Camera设备
5.22 Camera设备
📚 本节导读
学习时长: 约 40 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、摄像头基础知识、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS Camera 设备驱动的架构设计(SAL 传感器抽象层 + 驱动层)
- 掌握
camera_config_t配置结构体的各项参数 - 掌握帧缓冲
camera_fb_t的获取与归还机制 - 理解
sensor_t传感器抽象层的多态设计 - 掌握摄像头初始化、拍照、参数保存的完整流程
一、概述
Camera 摄像头设备是 OneOS 提供的图像采集驱动框架,基于 ESP32 摄像头驱动,支持 JPEG、RGB565、YUV422、Grayscale 等多种像素格式。驱动源码位于 drivers/camera/ 目录下,核心头文件为 camera.h,传感器抽象层位于 driver/include/sensor.h。
Camera 设备框架的特点:
- SAL 传感器抽象层:
sensor_t结构体封装了 30 个函数指针,实现多态传感器驱动 - 多格式支持:支持 JPEG、RGB565、YUV422、Grayscale 四种像素格式
- 帧缓冲管理:通过
camera_fb_t实现帧缓冲的获取与归还,支持多缓冲队列 - 双缓冲模式:
fb_count配置帧缓冲数量,支持双缓冲提高帧率 - NVS 持久化:支持将摄像头配置保存到 NVS(Non-Volatile Storage),实现参数持久化
1.1 使用场景
一句话核心:Camera 框架是"图像采集抽象层"——esp_camera_init 初始化传感器 + DMA 采集,esp_camera_fb_get/fb_return 提供帧缓冲队列,sensor_t 提供统一图像参数控制。应用只关心"取到帧、处理、归还",不关心底层是 OV2640 还是 GC2145。
与"直接操作摄像头寄存器"对比:
| 维度 | 用 Camera 框架 | 直接操作摄像头 |
|---|---|---|
| 传感器识别 | 自动探测型号,填充多态 sensor_t | 手动判断芯片 |
| 帧采集 | 帧缓冲队列 + DMA,fb_get/fb_return | 手动管 DMA/缓冲 |
| 参数控制 | sensor->set_* 统一接口 | 逐个寄存器写 |
| 换传感器 | 只换驱动,应用不变 | 应用代码要改 |
典型应用场景:
| 场景 | 谁在用 | 实际工作情况 |
|---|---|---|
| 智能摄像头 | 采集 + AI 推理 | JPEG 格式采集,fb_get 拿帧送 AI,fb_return 归还 |
| 人脸识别终端 | 采集 + 人脸库 | VGA/SVGA 帧,YUV/RGB 格式处理 |
| 条码扫描器 | 采集 + 解码 | 低分辨率连续采集,双缓冲提高帧率 |
| 视觉传感器节点 | 周期拍照 | 定时 fb_get 拍照上传 |
选型边界表:
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 无摄像头 | 不用 Camera | —— |
| 极高分辨率/帧率 | 专用 ISP 方案 | ESP32 Camera 帧率/分辨率有限 |
| 需要大缓冲 | PSRAM + fb_location | 高分辨率帧需外部 PSRAM |
结合源码的使用注意点:
- 取帧后必须归还:
esp_camera_fb_return忘了调则缓冲耗尽,fb_get超时返回NULL; - 取帧后才有分辨率:
fb->width/height/format是fb_get时填的,取帧前无意义; - 帧缓冲队列是采集-处理模型:
fb_count>1用双缓冲提高帧率,但处理速度要跟上采集速度; - 初始化只能一次:
esp_camera_init只能调一次,且先camera_probe。
二、核心数据结构
2.1 摄像头配置结构体 camera_config_t
来源:drivers/camera/driver/include/camera.h
typedef struct {
int pin_pwdn; /*!< GPIO pin for camera power down line */
int pin_reset; /*!< GPIO pin for camera reset line */
int pin_xclk; /*!< GPIO pin for camera XCLK line */
int pin_sscb_sda; /*!< GPIO pin for camera SDA line */
int pin_sscb_scl; /*!< GPIO pin for camera SCL line */
char *sscb_name;
int pin_d7; /*!< GPIO pin for camera D7 line */
int pin_d6; /*!< GPIO pin for camera D6 line */
int pin_d5; /*!< GPIO pin for camera D5 line */
int pin_d4; /*!< GPIO pin for camera D4 line */
int pin_d3; /*!< GPIO pin for camera D3 line */
int pin_d2; /*!< GPIO pin for camera D2 line */
int pin_d1; /*!< GPIO pin for camera D1 line */
int pin_d0; /*!< GPIO pin for camera D0 line */
int pin_vsync; /*!< GPIO pin for camera VSYNC line */
int pin_href; /*!< GPIO pin for camera HREF line */
int pin_pclk; /*!< GPIO pin for camera PCLK line */
int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode */
//ledc_timer_t ledc_timer; /*!< LEDC timer to be used for generating XCLK */
//ledc_channel_t ledc_channel; /*!< LEDC channel to be used for generating XCLK */
pixformat_t pixel_format; /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG */
framesize_t frame_size; /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */
int jpeg_quality; /*!< Quality of JPEG output. 0-63 lower means higher quality */
size_t fb_count; /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) */
camera_fb_location_t fb_location; /*!< The location where the frame buffer will be allocated */
camera_grab_mode_t grab_mode; /*!< When buffers should be filled */
} camera_config_t;字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 说明 |
|---|---|
pin_pwdn / pin_reset | 摄像头电源与复位控制引脚 |
pin_xclk | 主时钟输出,典型值 20MHz |
pin_sscb_sda/scl | SCCB 配置接口(兼容 I2C 协议) |
sscb_name | 命名的 I2C 总线名称(可空) |
pin_d7~d0 | 8 位并行数据总线 |
pin_vsync/href/pclk | 同步信号(垂直/水平/像素时钟) |
xclk_freq_hz | 主时钟频率,典型值 20000000(20MHz) |
pixel_format | 像素格式(PIXFORMAT_YUV422/GRAYSCALE/RGB565/JPEG 等) |
frame_size | 帧尺寸(FRAMESIZE_QVGA/VGA/SVGA/XGA 等) |
jpeg_quality | JPEG 质量 0-63,越小质量越高 |
fb_count | 帧缓冲数量,>1 启用双缓冲(提高采集速度) |
fb_location | 缓冲位置(CAMERA_FB_IN_PSRAM/DRAM) |
grab_mode | 抓取模式(WHEN_EMPTY/LATEST) |
说明:源码中
ledc_timer/ledc_channel已被注释掉(不参与结构体),XCLK 时钟现在由xclk_freq_hz+sensor->set_xclk配置。阅读源码时注意这两个字段不存在。
2.2 抓取模式枚举
来源:drivers/camera/driver/include/camera.h
typedef enum {
CAMERA_GRAB_WHEN_EMPTY, /*!< Fills buffers when they are empty. Less resources but first 'fb_count' frames might be old */
CAMERA_GRAB_LATEST /*!< Except when 1 frame buffer is used, queue will always contain the last 'fb_count' frames */
} camera_grab_mode_t;| 枚举值 | 含义 |
|---|---|
CAMERA_GRAB_WHEN_EMPTY | 缓冲区为空时填充:省资源,但前 fb_count 帧可能偏旧 |
CAMERA_GRAB_LATEST | 始终保留最新的 fb_count 帧:除单缓冲外,队列里总是最新几帧 |
2.3 帧缓冲位置枚举
来源:drivers/camera/driver/include/camera.h
typedef enum {
CAMERA_FB_IN_PSRAM, /*!< Frame buffer is placed in external PSRAM */
CAMERA_FB_IN_DRAM /*!< Frame buffer is placed in internal DRAM */
} camera_fb_location_t;| 枚举值 | 含义 |
|---|---|
CAMERA_FB_IN_PSRAM | 帧缓冲位于外部 PSRAM(容量大,适合高分辨率) |
CAMERA_FB_IN_DRAM | 帧缓冲位于内部 DRAM(速度快,但容量有限) |
2.4 帧缓冲结构体 camera_fb_t
来源:drivers/camera/driver/include/camera.h
typedef struct {
uint8_t * buf; /*!< Pointer to the pixel data */
size_t len; /*!< Length of the buffer in bytes */
size_t width; /*!< Width of the buffer in pixels */
size_t height; /*!< Height of the buffer in pixels */
pixformat_t format; /*!< Format of the pixel data */
struct timeval timestamp; /*!< Timestamp since boot of the first DMA buffer of the frame */
} camera_fb_t;字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 说明 |
|---|---|
buf | 指向像素数据的指针 |
len | 缓冲区字节长度 |
width/height | 图像分辨率(像素) |
format | 像素格式(pixformat_t) |
timestamp | 帧时间戳(自启动以来的时间) |
关键理解 —— 帧属性是"取帧时填写的":
esp_camera_fb_get(见 3.3)在cam_take拿到缓冲后,才把resolution[framesize].width/.height和sensor.pixformat填入fb->width/height/format。所以取帧后才有正确的分辨率信息,取帧前fb的这些字段无意义。
2.5 传感器抽象层 sensor_t
来源:drivers/camera/driver/include/sensor.h
sensor_t 是传感器多态抽象层(SAL,来自 OpenMV)。它通过一组函数指针让上层用统一接口操作不同型号的传感器(OV2640、OV3660、OV5640、GC2145 等),每种传感器驱动在初始化时把自己的实现填进 sensor_t——这是典型的 C 语言多态(vtable 风格)。
完整源码(drivers/camera/driver/include/sensor.h,与项目逐字一致):
typedef struct _sensor sensor_t;
typedef struct _sensor {
sensor_id_t id; // Sensor ID.
uint8_t slv_addr; // Sensor I2C slave address.
pixformat_t pixformat;
camera_status_t status;
int xclk_freq_hz;
// Sensor function pointers
int (*init_status) (sensor_t *sensor);
int (*reset) (sensor_t *sensor);
int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat);
int (*set_framesize) (sensor_t *sensor, framesize_t framesize);
int (*set_contrast) (sensor_t *sensor, int level);
int (*set_brightness) (sensor_t *sensor, int level);
int (*set_saturation) (sensor_t *sensor, int level);
int (*set_sharpness) (sensor_t *sensor, int level);
int (*set_denoise) (sensor_t *sensor, int level);
int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling);
int (*set_quality) (sensor_t *sensor, int quality);
int (*set_colorbar) (sensor_t *sensor, int enable);
int (*set_whitebal) (sensor_t *sensor, int enable);
int (*set_gain_ctrl) (sensor_t *sensor, int enable);
int (*set_exposure_ctrl) (sensor_t *sensor, int enable);
int (*set_hmirror) (sensor_t *sensor, int enable);
int (*set_vflip) (sensor_t *sensor, int enable);
int (*set_aec2) (sensor_t *sensor, int enable);
int (*set_awb_gain) (sensor_t *sensor, int enable);
int (*set_agc_gain) (sensor_t *sensor, int gain);
int (*set_aec_value) (sensor_t *sensor, int gain);
int (*set_special_effect) (sensor_t *sensor, int effect);
int (*set_wb_mode) (sensor_t *sensor, int mode);
int (*set_ae_level) (sensor_t *sensor, int level);
int (*set_dcw) (sensor_t *sensor, int enable);
int (*set_bpc) (sensor_t *sensor, int enable);
int (*set_wpc) (sensor_t *sensor, int enable);
int (*set_raw_gma) (sensor_t *sensor, int enable);
int (*set_lenc) (sensor_t *sensor, int enable);
int (*get_reg) (sensor_t *sensor, int reg, int mask);
int (*set_reg) (sensor_t *sensor, int reg, int mask, int value);
int (*set_res_raw) (sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning);
int (*set_pll) (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk);
int (*set_xclk) (sensor_t *sensor, int timer, int xclk);
} sensor_t;字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
id | 传感器 ID(MIDH/MIDL/PID/VER,用于识别型号) |
slv_addr | 传感器 I2C 从机地址(SCCB 地址) |
pixformat | 当前像素格式 |
status | 当前传感器状态(帧尺寸/质量/亮度/对比度/镜像等,见 camera_status_t) |
xclk_freq_hz | XCLK 主时钟频率 |
| 30 个函数指针 | 从 init_status 到 set_xclk,实现具体传感器操作 |
说明:
sensor_t共封装 30 个函数指针(不是 28 个)。早期文档说的"28 个"不准确——以sensor.h源码为准,从init_status/reset到get_reg/set_reg/set_res_raw/set_pll/set_xclk共 30 个回调。
关键函数指针表:
| 函数指针 | 说明 |
|---|---|
init_status / reset | 初始化传感器状态 / 复位传感器 |
set_pixformat | 设置像素格式(JPEG/RGB565/YUV422/Grayscale) |
set_framesize | 设置帧尺寸(QVGA/VGA/SVGA/XGA 等) |
set_contrast / set_brightness / set_saturation | 对比度 / 亮度 / 饱和度 |
set_sharpness / set_denoise | 锐度 / 降噪 |
set_quality | 设置 JPEG 质量 |
set_whitebal / set_gain_ctrl / set_exposure_ctrl | 白平衡 / 增益 / 曝光控制开关 |
set_hmirror / set_vflip | 水平镜像 / 垂直翻转 |
set_special_effect | 特殊效果(负片/灰度/棕褐色等) |
set_wb_mode | 白平衡模式(自动/晴天/阴天/荧光灯等) |
get_reg / set_reg | 直接读写传感器寄存器 |
set_res_raw / set_pll / set_xclk | 原始分辨率 / PLL 时钟 / XCLK 配置 |
关键理解 —— 多态如何实现:
esp_camera_sensor_get()返回的sensor_t*,其函数指针由探测到的传感器型号对应的驱动填充(camera_sensor[]表 + 各传感器sensor_xxx_init)。应用拿到sensor后调sensor->set_vflip(sensor, 1),实际执行的是该型号传感器的实现。换传感器只需换驱动,应用代码不变——这就是 SAL 抽象的意义。
三、关键 API
3.1 摄像头初始化
函数签名:
os_err_t esp_camera_init(const camera_config_t* config);| 参数 | 说明 |
|---|---|
config | 摄像头配置参数(camera_config_t,引脚/格式/尺寸/缓冲数等) |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 初始化成功 |
ESP_ERR_CAMERA_NOT_DETECTED | 未检测到摄像头 |
ESP_ERR_CAMERA_FAILED_TO_SET_* | 设置帧尺寸/输出格式失败 |
ESP_ERR_CAMERA_NOT_SUPPORTED | 摄像头不支持该格式/尺寸 |
此函数通过 I2C(SCCB)接口检测并配置摄像头,分配帧缓冲和 DMA 缓冲区,初始化并行 I2S 输入,设置 DMA 描述符。
注意:
esp_camera_init只能调用一次,且调用前需先camera_probe(探测传感器型号)。
3.2 摄像头反初始化
函数签名:
os_err_t esp_camera_deinit(void);| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 反初始化成功 |
ESP_ERR_INVALID_STATE | 驱动尚未初始化 |
3.3 获取帧缓冲
函数签名:
camera_fb_t* esp_camera_fb_get(void);| 返回值 | 说明 |
|---|---|
NULL | 驱动未初始化 / 超时(FB_GET_TIMEOUT=4000 tick 内无帧) |
| 其他 | 帧缓冲指针(camera_fb_t*) |
完整源码(drivers/camera/driver/camera.c,与项目逐字一致):
camera_fb_t *esp_camera_fb_get(void)
{
if (s_state == NULL)
{
return NULL;
}
camera_fb_t *fb = cam_take(FB_GET_TIMEOUT);
// set the frame properties
if (fb != NULL)
{
fb->width = resolution[s_state->sensor.status.framesize].width;
fb->height = resolution[s_state->sensor.status.framesize].height;
fb->format = s_state->sensor.pixformat;
}
return fb;
}分步详解:
第 1 步:检查驱动是否初始化(条件 s_state == NULL)
if (s_state == NULL)
{
return NULL;
}详细解释:s_state 是全局相机状态指针,esp_camera_init 时赋值。未初始化则直接返回 NULL。
第 2 步:从帧缓冲队列取一帧(条件 取帧结果)
camera_fb_t *fb = cam_take(FB_GET_TIMEOUT);详细解释:cam_take(FB_GET_TIMEOUT) 从帧缓冲队列取一个已填好的缓冲,最多等 FB_GET_TIMEOUT(4000 tick)。取到返回缓冲指针,超时返回 NULL(此时无新帧)。
第 3 步:填写帧属性(条件 fb 非空)
if (fb != NULL)
{
fb->width = resolution[s_state->sensor.status.framesize].width;
fb->height = resolution[s_state->sensor.status.framesize].height;
fb->format = s_state->sensor.pixformat;
}
return fb;
}详细解释:从 resolution[] 表按当前 framesize 查分辨率,填入 fb->width/height,并填入当前 pixformat。注意:resolution[] 是全局分辨率表(sensor.c 声明),framesize 作为下标。
3.4 归还帧缓冲
函数签名:
void esp_camera_fb_return(camera_fb_t * fb);完整源码(drivers/camera/driver/camera.c,与项目逐字一致):
void esp_camera_fb_return(camera_fb_t *fb)
{
if (s_state == NULL)
{
return;
}
cam_give(fb);
}分步详解:检查 s_state 非空后,cam_give(fb) 把帧缓冲归还队列(可再次被 DMA 填充、被下次 cam_take 取走)。必须取帧后及时归还,否则缓冲耗尽、cam_take 一直超时返回 NULL。
关键理解 —— 帧缓冲队列(
cam_take/cam_give):esp_camera_init分配fb_count个缓冲,DMA 每采集一帧填满一个缓冲,cam_give入队;应用cam_take取出处理,处理完cam_give归还。这是"采集(生产者)— 处理(消费者)"队列,fb_count越大缓冲越多,双缓冲(fb_count=2)可避免"等上一帧处理完才能采下一帧"的停顿。
3.5 获取传感器控制句柄
函数签名:
sensor_t * esp_camera_sensor_get(void);| 返回值 | 说明 |
|---|---|
NULL | 驱动未初始化 |
| 其他 | 传感器控制结构体指针(sensor_t*,见 2.5) |
应用拿到
sensor后通过函数指针配置图像参数(镜像/翻转/亮度/JPEG 质量等),见 4.1 示例。
3.6 保存/加载配置到 NVS
函数签名:
os_err_t esp_camera_save_to_nvs(const char *key);
os_err_t esp_camera_load_from_nvs(const char *key);| 参数 | 说明 |
|---|---|
key | NVS 中的唯一键名(如 "camera_config") |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 保存/加载成功 |
| 其他 | NVS 操作失败(未初始化/键不存在/空间不足) |
save_to_nvs把当前传感器状态(camera_status_t)写入 NVS,load_from_nvs读回并应用到传感器。必须在esp_camera_init之后调用(需要s_state),且应检查返回值——NVS 未初始化或键不存在会失败。
四、使用示例
4.1 摄像头初始化与拍照
#include <camera.h>
#include <device.h>
static camera_config_t camera_config = {
.pin_pwdn = 32,
.pin_reset = -1,
.pin_xclk = 0,
.pin_sscb_sda = 26,
.pin_sscb_scl = 27,
.pin_d7 = 35, .pin_d6 = 34, .pin_d5 = 39, .pin_d4 = 36,
.pin_d3 = 21, .pin_d2 = 19, .pin_d1 = 18, .pin_d0 = 5,
.pin_vsync = 25,
.pin_href = 23,
.pin_pclk = 22,
.xclk_freq_hz = 20000000,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_SVGA,
.jpeg_quality = 10,
.fb_count = 2,
.fb_location = CAMERA_FB_IN_PSRAM, /* 帧缓冲放 PSRAM */
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
void camera_capture_task(void *arg)
{
/* 1. 初始化摄像头 */
os_err_t ret = esp_camera_init(&camera_config);
if (ret != OS_SUCCESS)
{
os_kprintf("camera init failed, ret=%d!\r\n", ret);
return;
}
/* 2. 获取传感器句柄,配置图像参数 */
sensor_t *sensor = esp_camera_sensor_get();
if (sensor == NULL)
{
os_kprintf("camera sensor get failed!\r\n");
return;
}
if (sensor->set_vflip(sensor, 1) != 0) /* 垂直翻转 */
os_kprintf("set_vflip failed\r\n");
if (sensor->set_hmirror(sensor, 1) != 0) /* 水平镜像 */
os_kprintf("set_hmirror failed\r\n");
if (sensor->set_brightness(sensor, 2) != 0) /* 亮度 +2 */
os_kprintf("set_brightness failed\r\n");
while (1)
{
/* 3. 获取一帧图像 */
camera_fb_t *fb = esp_camera_fb_get();
if (fb == NULL)
{
os_kprintf("camera capture failed!\r\n");
os_task_msleep(100);
continue;
}
os_kprintf("Frame: %dx%d, format=%d, size=%d\r\n",
fb->width, fb->height, fb->format, fb->len);
/* 4. 处理图像数据(保存、发送等) */
process_image(fb->buf, fb->len);
/* 5. 归还帧缓冲 */
esp_camera_fb_return(fb);
os_task_msleep(1000); /* 每秒拍一张 */
}
}4.2 保存摄像头配置到 NVS
void camera_save_settings(void)
{
os_err_t ret = esp_camera_save_to_nvs("camera_config");
if (ret == OS_SUCCESS)
{
os_kprintf("camera settings saved to NVS\r\n");
}
}
void camera_load_settings(void)
{
/* 先加载 NVS 中的配置,再初始化摄像头 */
esp_camera_load_from_nvs("camera_config");
esp_camera_init(&camera_config);
}五、Camera 的适配(接入新传感器型号)
5.1 先认识适配层:Camera 驱动是怎么"长"到传感器上的
drivers/camera/driver/ 下是通用采集框架(camera.c 的 esp_camera_* + cam_take/cam_give 帧队列 + I2S/DMA 采集),不绑定具体传感器芯片。真正操作某型号传感器(OV2640/OV5640/GC2145 等)的是各传感器驱动文件(ov2640.c/ov5640.c 等)。换传感器型号,就是实现一套 sensor_t 函数指针并注册。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 应用层(esp_camera_fb_get 拿帧处理) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ 采集框架层(driver/camera.c,通用不改) │
│ esp_camera_init / fb_get / fb_return + 帧缓冲队列 │
│ I2S 并行输入 + DMA 采集 │
└───────────────┬──────────────────────────┬────────────────┘
│ ①实现 sensor_t 函数指针 │ ②camera_sensor[] 表
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 传感器驱动(ov2640.c / ov5640.c 等)★需实现★ │
│ set_pixformat/set_framesize/set_brightness/... + init │
│ camera_probe 探测 PID → 选驱动 → 填 sensor_t │
└───────────────┬──────────────────────────┬────────────────┘
│ SCCB/I2C 寄存器 │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(摄像头传感器芯片 + I2S/DMA + GPIO) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:接入新传感器型号只需做两件事:① 实现 sensor_t 全部函数指针(set_pixformat/set_framesize/set_brightness 等,通过 SCCB 写传感器寄存器);② 把该型号加入 camera_sensor[] 表(含 PID、SCCB 地址、型号名),camera_probe 探测到对应 PID 时自动选中并填充 sensor_t。框架层不用改。
5.2 以接入新传感器为例,看一整套适配
① 实现 sensor_t 函数指针(示意某传感器的关键几个):
static int xxx_set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
/* 通过 SCCB 写传感器寄存器切换像素格式 */
write_reg(XXX_JPEG_CTRL_REG, (pixformat == PIXFORMAT_JPEG) ? JPEG_ON : JPEG_OFF);
sensor->pixformat = pixformat;
return 0;
}
static int xxx_set_framesize(sensor_t *sensor, framesize_t framesize)
{
/* 查分辨率表,写窗口/缩放寄存器(示意) */
write_reg(XXX_WIN_W_REG, resolution[framesize].width);
write_reg(XXX_WIN_H_REG, resolution[framesize].height);
sensor->status.framesize = framesize;
return 0;
}
static int xxx_init_status(sensor_t *sensor)
{
/* 复位并写入默认寄存器配置(示意) */
write_reg(XXX_RESET_REG, 0x00);
write_reg(XXX_DEFAULT_REG, 0x01);
return 0;
}② 把该型号加入 camera_sensor[] 表(driver/sensor.c):
const camera_sensor_info_t camera_sensor[] = {
/* 其他已支持型号的条目 */
{CAMERA_XXX, "XXX", XXX_SCCB_ADDR, XXX_PID, FRAMESIZE_UXGA, true},
{CAMERA_OV5640, "OV5640", OV5640_SCCB_ADDR, OV5640_PID, FRAMESIZE_UXGA, true},
};要点:
camera_probe读传感器 PID,与表里逐条比对;命中后调用该驱动的xxx_init_status,把所有函数指针填进sensor_t。camera_sensor_info_t还声明了该型号支持的最大分辨率max_size和是否支持 JPEGsupport_jpeg,用于能力校验。
5.3 接口契约:接入新传感器需要实现什么
| 层 | 接口/数据 | 职责 | 说明 |
|---|---|---|---|
| 函数指针 | sensor_t.*(30 个) | 具体寄存器操作 | set_pixformat/set_framesize 必实现 |
| 初始化 | xxx_init_status | 复位 + 写默认寄存器 | camera_probe 选中后调用 |
| 信息表 | camera_sensor[] | 声明 PID/地址/最大尺寸/JPEG 支持 | sensor.c 里追加条目 |
| 探测 | camera_probe | 读 PID → 选驱动 → 填 sensor_t | 框架已有 |
| 板级 | camera_config_t | 引脚/格式/缓冲配置 | 应用侧设置 |
5.4 适配新传感器型号的标准流程
第 1 步:查芯片手册——确认传感器 PID(camera_pid_t)、SCCB 从机地址、寄存器表。
第 2 步:新建传感器驱动——实现 sensor_t 的函数指针(init_status/set_pixformat/set_framesize/set_brightness 等,通过 SCCB 写寄存器)。
第 3 步:追加 camera_sensor[] 表项——填 PID、SCCB 地址、型号名、最大分辨率、是否支持 JPEG。
第 4 步:板级配置——按 camera_config_t 配好引脚(SCCB/D7~D0/VSYNC/HREF/PCLK/PWDN)、像素格式、帧尺寸。
第 5 步:测试——esp_camera_init 后 fb_get 拿帧,用 sensor->set_* 调参验证寄存器写入是否正确。
5.5 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
PID 是否在 camera_pid_t 枚举里? | 新 PID 需先加枚举 |
camera_sensor[] 表项 PID 与芯片一致? | PID 不匹配则 camera_probe 探测不到 |
set_framesize 写的分辨率表对吗? | 写错则输出图像尺寸错误 |
| SCCB 地址对?(7bit 还是 8bit) | 地址错则 I2C 通信失败、探测不到 |
| 是否支持 JPEG? | 不支持 JPEG 的芯片 support_jpeg=false,配 PIXFORMAT_JPEG 会失败 |
📝 本节小结
核心要点回顾
本节围绕 Camera 设备展开,核心要点如下:
- SAL 传感器抽象层:
sensor_t封装 30 个函数指针(init_status~set_xclk),实现多态传感器驱动;camera_probe按 PID 选驱动填sensor_t。 - 配置结构体:
camera_config_t涵盖硬件引脚、像素格式、帧尺寸、JPEG 质量、fb_count、fb_location、grab_mode等全部配置参数。 - 帧缓冲机制:
esp_camera_fb_get()(cam_take取帧 + 填分辨率)获取帧,esp_camera_fb_return()(cam_give归还)归还,支持多缓冲(fb_count>1)。 - 帧属性是取帧时填的:
fb->width/height/format在fb_get时从resolution[]表和sensor.pixformat填入。 - NVS 持久化:
esp_camera_save_to_nvs()/esp_camera_load_from_nvs()实现配置持久化,须在esp_camera_init后调用。 - 硬件适配:接入新传感器 = 实现
sensor_t30 个函数指针 + 追加camera_sensor[]表项,框架层不用改。
Camera 设备驱动框架适用于需要图像采集的嵌入式 IoT 设备,如智能摄像头、人脸识别终端、条码扫描器等。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 取帧后忘记归还 | fb_get 后处理完必须 fb_return,否则缓冲耗尽、fb_get 超时返回 NULL |
| 帧尺寸/格式与显示不匹配 | 配置的像素格式、帧尺寸需与处理/显示链路一致,否则花屏或无法处理 |
| 忽略 NVS 存储失败 | 配置持久化到 NVS 可能失败,保存前检查返回值,失败应重试或回退默认 |
| 在多线程同时取帧 | 帧缓冲通常不允许多线程并发取用,需加锁串行访问 |
| 初始化只能一次 | esp_camera_init 只能调一次,且先 camera_probe;重复调用失败 |
误以为 fb 取帧前有分辨率 | fb->width/height/format 是 fb_get 时填的,取帧前无意义 |
适配漏实现关键 sensor_t 回调 | set_pixformat/set_framesize 不实现则图像格式/尺寸无法切换 |
下一步
掌握了 Camera 摄像头设备后,接下来学习 Bus 总线子系统:
- 5.23 Bus总线子系统:了解 OneOS 的声明式驱动-设备匹配机制与 probe 初始化流程。