这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 【AtomS3R-M12】分享板载OV3660摄像头调试经验

共1条 1/1 1 跳转至页

【AtomS3R-M12】分享板载OV3660摄像头调试经验

高工
2026-11-03 17:57:27     打赏

    AtomS3R-M12 开发板集成的 M12 摄像头模组采用 OmniVision OV3660 CMOS 图像传感器,最大分辨率 3 MP(2048 × 1536),配合 120° 广角 M12 镜头,可在 30 帧/秒的速率下输出多种格式的图像数据。

1.1 传感器概述

    OV3660 是一款低功耗 CMOS 图像传感器,有效像素 3 MP,最大输出分辨率 2048 × 1536。传感器通过 8 位并行 DVP(Digital Video Port)接口输出图像数据,并通过 SCCB(兼容 I2C)总线完成寄存器配置与参数调节,可调节的项目包括输出分辨率、曝光、增益、白平衡、亮度、对比度与饱和度等。

1.2 光学参数

    模组采用 M12 接口镜头,光圈值 F2.4,焦距 1.8 ± 5% mm,视场角(FOV)达 120°,属于大广角设计,在相同安装距离下能够覆盖更宽的画面范围。M12 镜头座支持更换不同焦距与视场角的镜头,便于根据应用需求灵活调整取景范围。

1.3 图像输出格式

    传感器支持 RAW RGB、RGB565、RGB555、RGB444、CCIR656、YCbCr422 以及压缩格式等多种输出方式。在 Arduino 与 ESP-IDF 开发中,通常选择 JPEG 压缩输出,以降低总线带宽与内存占用,并可由 ESP32-S3 的硬件加速完成编码。

1.4 接口与引脚连接

    摄像头通过 8 位并行数据总线与 ESP32-S3 主控连接。SCCB 配置总线使用 G12 作为数据线 SDA、G9 作为时钟线 SCL;像素时钟 PCLK 使用 G40,行同步信号 HREF 使用 G14,场同步信号 VSYNC 使用 G10,主时钟 XCLK 使用 G21,电源使能信号 POWER_N 使用 G18。

    8 位图像数据线的连接关系为:Y2 对应 G3,Y3 对应 G42,Y4 对应 G46,Y5 对应 G48,Y6 对应 G4,Y7 对应 G17,Y8 对应 G11,Y9 对应 G13。开发时这些引脚需在程序中的 camera_config_t 结构体里逐一定义,否则将出现初始化失败或图像错位。

1.5 与主控的协同工作

    OV3660 输出的大尺寸图像帧需要缓存在 ESP32-S3 的 8 MB PSRAM 中。配合双核 240 MHz 处理器与硬件 JPEG 编码器,开发板可以实现较高分辨率下的连续采集。这也是使用摄像头功能时必须正确配置 PSRAM 工作模式(OPI PSRAM)的根本原因;若 PSRAM 模式配置错误,摄像头初始化通常会直接失败。

第二章  产品特点

    AtomS3R-M12 上的 OV3660 摄像头模组在同类小型开发板中具有较为均衡的性能与体积表现,主要特点如下。

2.1 3 MP 高分辨率成像

    有效像素 3 MP,最高可输出 2048 × 1536 的图像,相比常见的 30 万像素摄像头,画面细节明显更丰富,可用于文字识别、目标轮廓提取等对清晰度要求较高的场景。

2.2 120° 大广角取景

    120° 视场角带来更宽的覆盖范围,在近距离安装时即可拍摄较大区域,适合室内监控、桌面全幅采集等应用;同时广角镜头也存在一定的桶形畸变,实际使用中需结合畸变测评结果判断可用性。

2.3 30 帧/秒流畅输出

    模组支持最高 30 帧/秒的输出帧率,能够满足实时预览、动态目标跟踪等应用的基本流畅度要求。实际帧率会随分辨率提升而下降,高分辨率下受总线带宽与内存拷贝速度限制更为明显。

2.4 多种输出格式与硬件加速

    支持 RAW RGB、RGB565/555/444、CCIR656、YCbCr422 与压缩格式输出。配合 ESP32-S3 的硬件 JPEG 编码能力,可在保证画质的同时显著降低数据传输量,是嵌入式视觉项目的常用配置方式。

2.5 UVC 免驱即插即用

    出厂固件已内置 UVC(USB Video Class)功能,通过 USB Type-C 连接电脑后即可作为标准摄像头使用,无需安装额外驱动,可用于快速验证成像效果,也可作为日常视频会议摄像头。

2.6 M12 可更换镜头

    采用 M12 标准镜头接口,可根据需求更换不同焦距、不同视场角的镜头,例如更换长焦镜头以获得更远距离的细节,或更换更广视角镜头以获得更大的覆盖范围。

2.7 与九轴传感器融合

    开发板同时板载 BMI270 六轴姿态传感器与 BMM150 三轴地磁传感器。摄像头可与九轴传感器数据同步采集,使图像信息与姿态、方向信息相互补充,适合运动感知、稳定补偿类应用。

第三章  应用场景

    基于 OV3660 摄像头的成像能力与开发板整体配置,该摄像头模组可应用于以下场景。

3.1 家庭与商业视觉监控

    通过 Wi-Fi 图传将摄像头画面推送到浏览器或移动端,可实现远程实时查看,适用于家庭安防、店铺监控、仓库与设备状态观察等场景。

3.2 机器视觉与图像识别

    3 MP 分辨率与广角取景为 OCR 文本识别、目标检测、人脸检测等算法提供了较好的图像输入条件。图像可在端侧做初步处理,也可上传至云端完成识别任务。

3.3 教育与创客实验

    结合 Arduino IDE 与图形化编程平台,可用于摄像头初始化、图像采集、分辨率切换、帧率统计等教学实验,是学习嵌入式视觉与物联网开发的合适载体。

3.4 智能家居集成

    可将设备接入 Home Assistant 等智能家居平台,作为摄像头节点参与自动化联动,例如触发拍摄、画面抓拍存档与状态推送。

3.5 图像采集与数据记录

    可用于定时抓拍、周期性图像存档、实验数据采集等用途,配合存储与网络能力可构建轻量级图像采集终端。

3.6 机器人视觉与运动感知

    将摄像头与九轴传感器数据结合,可为小型移动平台提供前方画面感知与自身姿态判断,用于避障、循迹、稳定补偿等功能的原型验证。

第四章  Arduino 开发例程

    本章给出在 Arduino IDE 下驱动 AtomS3R-M12 摄像头的完整例程,包括开发环境配置、引脚定义、初始化采集以及分辨率与图像参数调节。

4.1 开发环境准备

首先在 Arduino IDE 中安装 ESP32 开发板支持包,并按 AtomS3R-M12 的硬件特性完成开发板选项配置。

安装 Arduino IDE 2.x,打开

开发板选择 ESP32S3 Dev Module(如已安装 M5Stack 板级支持包,也可选择对应的 AtomS3 系列开发板)。

•Flash Size 选择 8MB,PSRAM 选择 OPI PSRAM,Partition Scheme 选择默认 8MB,USB CDC On Boot 设为 Enabled。

如需烧录固件,先长按侧面复位键约 2 秒,待内部绿色 LED 亮起后松开,设备进入下载模式,再点击上传。

若使用 PlatformIO,需在配置中设置 board_build.arduino.memory_type = qio_opi,并添加 -DBOARD_HAS_PSRAM 编译选项。

4.2 摄像头引脚定义

    OV3660 采用 8 位并行接口,各信号线对应的 GPIO 如下:电源使能 PWDN 为 G18,主时钟 XCLK 为 G21,SCCB 数据 SDA 为 G12,SCCB 时钟 SCL 为 G9,像素时钟 PCLK 为 G40,行同步 HREF 为 G14,场同步 VSYNC 为 G10。

    图像数据线 D0 至 D7 依次对应 G3、G42、G46、G48、G4、G17、G11、G13。程序中需按此对应关系为 pin_d0 至 pin_d7 赋值,顺序错误会导致图像出现条纹或颜色异常。

4.3 软件代码如下所示:

/*
 * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
 *
 * SPDX-License-Identifier: MIT
 */

/**
 * @Hardwares: AtomS3R-CAM / AtomS3R-M12
 * @Platform Version: Arduino M5Stack Board Manager v2.1.4
 */

#include "camera_pins.h"
#include <WiFi.h>
#include "esp_camera.h"

//#define USE_ATOMS3R_CAM
 #define USE_ATOMS3R_M12

#define STA_MODE
 //#define AP_MODE

const char* ssid     = "航天科宇";
const char* password = "88888888";

WiFiServer server(80);
camera_fb_t* fb    = NULL;
uint8_t* out_jpg   = NULL;
size_t out_jpg_len = 0;

static void jpegStream(WiFiClient* client);

static camera_config_t camera_config = {
    .pin_pwdn     = PWDN_GPIO_NUM,
    .pin_reset    = RESET_GPIO_NUM,
    .pin_xclk     = XCLK_GPIO_NUM,
    .pin_sscb_sda = SIOD_GPIO_NUM,
    .pin_sscb_scl = SIOC_GPIO_NUM,
    .pin_d7       = Y9_GPIO_NUM,
    .pin_d6       = Y8_GPIO_NUM,
    .pin_d5       = Y7_GPIO_NUM,
    .pin_d4       = Y6_GPIO_NUM,
    .pin_d3       = Y5_GPIO_NUM,
    .pin_d2       = Y4_GPIO_NUM,
    .pin_d1       = Y3_GPIO_NUM,
    .pin_d0       = Y2_GPIO_NUM,

    .pin_vsync = VSYNC_GPIO_NUM,
    .pin_href  = HREF_GPIO_NUM,
    .pin_pclk  = PCLK_GPIO_NUM,

    .xclk_freq_hz = 20000000,
    .ledc_timer   = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,

#ifdef USE_ATOMS3R_CAM
    .pixel_format = PIXFORMAT_RGB565,
    .frame_size   = FRAMESIZE_QVGA,
#endif

#ifdef USE_ATOMS3R_M12
    .pixel_format = PIXFORMAT_JPEG,
    .frame_size   = FRAMESIZE_UXGA,
#endif

    .jpeg_quality  = 12,
    .fb_count      = 2,
    .fb_location   = CAMERA_FB_IN_PSRAM,
    .grab_mode     = CAMERA_GRAB_LATEST,
    .sccb_i2c_port = 0,
};

void setup()
{
    Serial.begin(115200);
    pinMode(POWER_GPIO_NUM, OUTPUT);
    digitalWrite(POWER_GPIO_NUM, LOW);
    delay(500);
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.println("Camera Init Fail");
        delay(1000);
        esp_restart();
    } else {
        Serial.println("Camera Init Success");
    }
    delay(100);

#ifdef STA_MODE

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    WiFi.setSleep(false);
    Serial.println("");

    Serial.print("Connecting to ");
    Serial.println(ssid);

    // Wait for connection
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
#endif

#ifdef AP_MODE
    if (!WiFi.softAP(ssid, password)) {
        log_e("Soft AP creation failed.");
        while (1);
    }

    Serial.println("AP SSID:");
    Serial.println(ssid);
    Serial.println("AP PASSWORD:");
    Serial.println(password);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(IP);
#endif

    server.begin();
}

void loop()
{
    WiFiClient client = server.available();  // listen for incoming clients
    if (client) {                            // if you get a client,
        while (client.connected()) {         // loop while the client's connected
            if (client.available()) {        // if there's bytes to read from the
                jpegStream(&client);
            }
        }
        // close the connection:
        client.stop();
        Serial.println("Client Disconnected.");
    }
}

// used to image stream
#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY     = "--" PART_BOUNDARY "rn";
static const char* _STREAM_PART         = "Content-Type: image/jpegrnContent-Length: %urnrn";

static void jpegStream(WiFiClient* client)
{
    Serial.println("Image stream start");
    client->println("HTTP/1.1 200 OK");
    client->printf("Content-Type: %srn", _STREAM_CONTENT_TYPE);
    client->println("Content-Disposition: inline; filename=capture.jpg");
    client->println("Access-Control-Allow-Origin: *");
    client->println();
    static int64_t last_frame = 0;
    if (!last_frame) {
        last_frame = esp_timer_get_time();
    }

    for (;;) {
        fb = esp_camera_fb_get();
        if (fb) {
#ifdef USE_ATOMS3R_CAM
            frame2jpg(fb, 255, &out_jpg, &out_jpg_len);
#endif

#ifdef USE_ATOMS3R_M12
            out_jpg     = fb->buf;
            out_jpg_len = fb->len;
#endif

            Serial.printf("pic size: %dn", out_jpg_len);
            client->print(_STREAM_BOUNDARY);
            client->printf(_STREAM_PART, out_jpg_len);
            int32_t to_sends    = out_jpg_len;
            int32_t now_sends   = 0;
            uint8_t* out_buf    = out_jpg;
            uint32_t packet_len = 8 * 1024;
            while (to_sends > 0) {
                now_sends = to_sends > packet_len ? packet_len : to_sends;
                if (client->write(out_buf, now_sends) == 0) {
                    goto client_exit;
                }
                out_buf += now_sends;
                to_sends -= now_sends;
            }

            int64_t fr_end     = esp_timer_get_time();
            int64_t frame_time = fr_end - last_frame;
            last_frame         = fr_end;
            frame_time /= 1000;
            Serial.printf("MJPG: %luKB %lums (%.1ffps)rn", (long unsigned int)(out_jpg_len / 1024),
                          (long unsigned int)frame_time, 1000.0 / (long unsigned int)frame_time);

            if (fb) {
                esp_camera_fb_return(fb);
                fb = NULL;
            }
#ifdef USE_ATOMS3R_CAM
            if (out_jpg) {
                free(out_jpg);
                out_jpg     = NULL;
                out_jpg_len = 0;
            }
#endif
        } else {
            Serial.println("Camera capture failed");
        }
    }

client_exit:
    if (fb) {
        esp_camera_fb_return(fb);
        fb = NULL;
    }
#ifdef USE_ATOMS3R_CAM
    if (out_jpg) {
        free(out_jpg);
        out_jpg     = NULL;
        out_jpg_len = 0;
    }
#endif
    client->stop();
    Serial.printf("Image stream endrn");
}

五:实物演示

5.1 软件代码仿真效果图

02-1 仿真图片.png

5.2 网页结果图如下所示

02-2 电脑浏览页面.png




关键词: AtomS3R-M12     摄像头调试    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]