IoT-For-Beginners 实战:为 Wio Terminal 接入 ArduCAM OV2640 摄像头并完成图像采集
2026/9/15 13:50:19 网站建设 项目流程

IoT-For-Beginners 实战:为 Wio Terminal 接入 ArduCAM OV2640 摄像头并完成图像采集

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

本指南对应 IoT-For-Beginners 项目中「制造业」单元第 2 课(Check fruit quality from an IoT device)的 Wio Terminal 硬件篇,讲解如何为 Seeed Studio Wio Terminal 接驳 ArduCam Mini 2MP Plus(OV2640 传感器)摄像头,并通过 PlatformIO 编写固件完成 GPIO 接线、SPI/I2C 总线通信、按键触发拍照与串口调试输出。读完本文你将掌握:基于 OV2640 的 SPI 摄像头在 Arduino 框架下的接线规范、platformio.ini的编译标志配置、camera.h底层驱动封装思路,以及将 JPEG 图像写入 SD 卡进行结果核验的完整流程,为后续调用 Custom Vision 图像分类服务(见 wio-terminal-classify-image.md)打好数据采集基础。

本文内容以 translations/bg/4-manufacturing/lessons/2-check-fruit-from-device/wio-terminal-camera.md 及其英文原版 wio-terminal-camera.md 为骨架,源码级细节取自仓库中的 code-camera/wio-terminal 完整工程。

一、硬件选型:为什么选择 ArduCAM Mini 2MP Plus

本课使用的摄像头是ArduCam Mini 2MP Plus,核心是一颗OV26402 百万像素图像传感器。它的通信方式很有特点:

  • SPI 接口:负责传输图像数据。图像文件(即使是压缩后的 JPEG)体积远大于温度、湿度等传感器输出的单个数值,SPI 高带宽特性适合搬移大数据量;
  • I2C 接口:负责配置传感器内部寄存器,如分辨率、JPEG 尺寸、光照模式与特效。

与 Wio Terminal 上常见的 Grove 传感器不同,ArduCAM 摄像头没有 Grove 插座,需要直接通过跳线连到 Wio Terminal 的 GPIO 引脚,同时挂接在 SPI 与 I2C 两条总线上。

💁 需要留意 SPI 引脚命名的新旧约定:ArduCAM 板上的丝印标签与代码中使用的 Wio Terminal 引脚名仍沿用旧命名(如 MOSI/MISO),而本课文字说明采用新命名(Controller Output, Peripheral Input 等)。下文接线表中"描述"一栏即采用新命名。

二、硬件接线:ArduCAM 与 Wio Terminal 的引脚对照

开始接线前,建议先贴好 Wio Terminal 随附的 GPIO 引脚贴纸(围绕引脚排布贴上即可),便于快速定位引脚位置。

使用杜邦跳线,按下表完成 8 根线的连接:

ArduCAM 引脚Wio Terminal 引脚描述
CS24 (SPI_CS)SPI 片选(Chip Select)
MOSI19 (SPI_MOSI)SPI 控制器输出、外设输入
MISO21 (SPI_MISO)SPI 控制器输入、外设输出
SCK23 (SPI_SCLK)SPI 串行时钟
GND6 (GND)接地 – 0V
VCC4 (5V)5V 电源
SDA3 (I2C1_SDA)I2C 串行数据
SCL5 (I2C1_SCL)I2C 串行时钟

供电注意GNDVCC为 ArduCAM 提供5V供电(注意与 Grove 传感器的 3V 不同),该电源直接来自为设备供电的 USB-C 连接。因此 5V 供电能力受 USB-C 供电质量影响,请确保数据线/电源输出足够稳定。

接线完成后,即可将 Wio Terminal 通过 USB-C 连接到电脑,进入固件开发阶段。

三、工程初始化:PlatformIO 项目与依赖库

3.1 创建工程

在 VS Code 中通过 PlatformIO 插件新建 Wio Terminal 工程,命名为fruit-quality-detector。仓库中完整的参考工程位于 code-camera/wio-terminal/fruit-quality-detector,其 platformio.ini 给出了完整的平台配置:

[env:seeed_wio_terminal] platform = atmelsam board = seeed_wio_terminal framework = arduino lib_deps = seeed-studio/Seeed Arduino rpcWiFi @ 1.0.5 seeed-studio/Seeed Arduino FS @ 2.1.1 seeed-studio/Seeed Arduino SFUD @ 2.0.2 seeed-studio/Seeed Arduino rpcUnified @ 2.1.3 seeed-studio/Seeed_Arduino_mbedtls @ 3.0.1 seeed-studio/Seeed Arduino RTC @ 2.0.0 build_flags = -DARDUCAM_SHIELD_V2 -DOV2640_CAM

说明:

  • lib_deps中的rpcWiFiFSSFUDrpcUnifiedmbedtlsRTC等 Seeed 官方库用于 WiFi 连接、文件系统与 TLS 支持;
  • WiFi 凭据不写死在源码中,而是集中放在src/config.h(参考 config.h):
#pragma once #include <string> using namespace std; // WiFi credentials const char *SSID = "<SSID>"; const char *PASSWORD = "<PASSWORD>";

3.2 ArduCAM 库的特殊安装方式

ArduCAM 官方库无法像普通 Arduino 库那样从platformio.inilib_deps直接安装,必须从源码引入:

  1. 克隆官方仓库https://github.com/ArduCAM/Arduino.git,或访问其 GitHub 页面点击Code按钮下载 zip;
  2. 只需要其中的ArduCAM文件夹,整个文件夹复制到工程lib目录下。

⚠️ 必须是lib/ArduCam(整个文件夹,保持代码位于lib/ArduCam之下),不能只把ArduCam文件夹的内容散落到lib目录里。

3.3 编译标志:按需裁剪摄像头驱动

ArduCAM 库支持多款摄像头型号。为了在不使用某型号时将其驱动代码从最终固件中剔除、控制体积,库通过编译器标志build_flags)选择型号。针对 OV2640,需在platformio.ini末尾追加:

build_flags = -DARDUCAM_SHIELD_V2 -DOV2640_CAM

两个标志的作用:

  • ARDUCAM_SHIELD_V2:告知库,摄像头位于一块 Arduino 扩展板(shield)上;
  • OV2640_CAM:仅编译 OV2640 型号对应的驱动代码。

💡 若同时需要关闭编译器警告,可参考分类版工程 code-classify/wio-terminal 中额外追加的-w标志。

四、核心驱动:camera.h源码逐段剖析

src目录新建camera.h(完整实现见 code-camera 的 camera.h),它封装了与摄像头通信的全部底层细节。文件以#pragma once防止重复包含,并引入ArduCAM.hWire.h

#pragma once #include <ArduCAM.h> #include <Wire.h> class Camera { public: Camera(int format, int image_size) : _arducam(OV2640, PIN_SPI_SS) { _format = format; _image_size = image_size; } ... private: ArduCAM _arducam; int _format; int _image_size; };

构造函数将OV2640型号与PIN_SPI_SS片选引脚传入ArduCAM库对象,并保存图像格式与尺寸两个参数(由调用方在main.cpp中传入JPEGOV2640_640x480)。

4.1init():上电自检与传感器配置

init()依次完成以下关键步骤,每步失败都会返回false

bool init() { // Reset the CPLD _arducam.write_reg(0x07, 0x80); delay(100); _arducam.write_reg(0x07, 0x00); delay(100); // Check if the ArduCAM SPI bus is OK _arducam.write_reg(ARDUCHIP_TEST1, 0x55); if (_arducam.read_reg(ARDUCHIP_TEST1) != 0x55) { return false; } // Change MCU mode _arducam.set_mode(MCU2LCD_MODE); uint8_t vid, pid; // Check if the camera module type is OV2640 _arducam.wrSensorReg8_8(0xff, 0x01); _arducam.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid); _arducam.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid); if ((vid != 0x26) && ((pid != 0x41) || (pid != 0x42))) { return false; } _arducam.set_format(_format); _arducam.InitCAM(); _arducam.OV2640_set_JPEG_size(_image_size); _arducam.OV2640_set_Light_Mode(Auto); _arducam.OV2640_set_Special_effects(Normal); delay(1000); return true; }

逐步说明:

  1. 复位 CPLD:向寄存器0x07依次写入0x800x00(中间各延时 100ms),完成 ArduCAM 板上 CPLD 的复位时序;
  2. SPI 总线自检:向测试寄存器ARDUCHIP_TEST1写入0x55再读回,若读回值不一致说明 SPI 链路不通(常见于接线错误);
  3. 切换 MCU 模式set_mode(MCU2LCD_MODE)让 MCU 可以访问传感器配置;
  4. 校验传感器型号:通过 I2C(wrSensorReg8_8/rdSensorReg8_8)读取 OV2640 的芯片 ID 高字节与低字节,验证是否为 OV2640(vid == 0x26pid0x410x42);
  5. 应用画质参数set_format设置输出格式(JPEG/BMP/RAW),InitCAM()初始化,OV2640_set_JPEG_size设置 JPEG 分辨率,OV2640_set_Light_Mode(Auto)启用自动光照模式,OV2640_set_Special_effects(Normal)关闭特殊特效,最后延时 1s 等待传感器稳定。

4.2 异步采集模型:startCapture/captureReady/readImageToBuffer

ArduCAM 的采集模型是异步的:主控发出一条"开始采集"指令,摄像头在后台完成曝光、JPEG 编码并把数据存进自己的片上 FIFO 缓冲,主控随后轮询状态并搬走数据:

void startCapture() { _arducam.flush_fifo(); _arducam.clear_fifo_flag(); _arducam.start_capture(); } bool captureReady() { return _arducam.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK); }
  • flush_fifo()clear_fifo_flag()清空上次残留数据与完成标志,避免脏数据;
  • start_capture()下发采集指令;
  • captureReady()通过ARDUCHIP_TRIG寄存器的CAP_DONE_MASK位判断采集是否完成。

数据读取readImageToBuffer的要点:

bool readImageToBuffer(byte **buffer, uint32_t &buffer_length) { if (!captureReady()) return false; uint32_t length = _arducam.read_fifo_length(); buffer_length = length; if (length >= MAX_FIFO_SIZE) return false; if (length == 0) return false; byte *buf = new byte[length]; ... _arducam.CS_LOW(); _arducam.set_fifo_burst(); while (length--) { temp_last = temp; temp = SPI.transfer(0x00); if ((temp == 0xD9) && (temp_last == 0xFF)) // JPEG 结束标志 FF D9 { buf[buffer_pos] = temp; buffer_pos++; i++; _arducam.CS_HIGH(); } ... } ... *buffer = buf; }

实现逻辑:

  1. 先通过read_fifo_length()拿到 JPEG 数据的字节数,若为 0 或超过MAX_FIFO_SIZE上限则返回false
  2. new byte[length]在堆上分配接收缓冲区(调用方用完必须delete);
  3. CS_LOW()拉低片选、set_fifo_burst()进入突发读模式后,用SPI.transfer(0x00)逐个时钟读出字节,同时识别 JPEG 的FF D8(SOI,起始标记)与FF D9(EOI,结束标记)来界定有效数据边界,并按 256 字节一块的方式控制片选翻转;
  4. 读取完成后清空 FIFO 标志,并重新下发一次格式/尺寸设置以复位传感器;
  5. 通过出参buffer返回堆缓冲区指针。

这是一段与 ArduCAM 硬件强耦合的底层代码,本课不需要逐行深究——直接使用即可。你真正需要关心的接口只有三个:startCapture()captureReady()readImageToBuffer(&buffer, length)

五、在main.cpp中集成摄像头

5.1 实例化 Camera 对象

main.cpp的 include 语句下方创建全局摄像头实例(见 main.cpp):

#include "camera.h" Camera camera = Camera(JPEG, OV2640_640x480);

这里把图像保存为JPEG格式、分辨率640x480。虽然 OV2640 最高支持到 3280x2464,但本课后续的图像分类器输入尺寸很小(约 227x227),采集并上传更大的图片既浪费内存也拖慢网络传输,因此 640x480 是性价比最优选择。

5.2setupCamera():初始化总线

void setupCamera() { pinMode(PIN_SPI_SS, OUTPUT); digitalWrite(PIN_SPI_SS, HIGH); Wire.begin(); SPI.begin(); if (!camera.init()) { Serial.println("Error setting up the camera!"); } }

该函数按顺序完成:

  1. 把 SPI 片选引脚PIN_SPI_SS设为输出并拉高,使 Wio Terminal 处于SPI 控制器(Controller)地位;
  2. 启动 I2C 总线(Wire.begin())与 SPI 总线(SPI.begin());
  3. 调用camera.init(),若返回false则在串口打印Error setting up the camera!

setup()末尾调用setupCamera();即可。仓库参考工程的setup()完整流程为:Serial.begin(9600)→ 等待串口就绪 → 连接 WiFi(connectWiFi(),内部循环调用WiFi.begin(SSID, PASSWORD)直至WL_CONNECTED)→setupCamera()→ 配置按键 →(可选)setupSDCard()

5.3 编译、烧录与排查

编译并上传后打开串口监视器(波特率 9600)。若看到Error setting up the camera!,优先检查:

  • 8 根跳线是否一一对应接线表中的引脚(尤其 CS、MOSI、MISO、SCK 四条 SPI 线);
  • 跳线是否插牢(半松状态会导致信号时序异常);
  • 供电是否稳定(5V 由 USB-C 提供)。

六、按键触发拍照:C 按钮与loop()轮询

微控制器是不断循环执行代码的,无法像桌面程序那样"阻塞等待一个事件"。Wio Terminal 自带 3 个顶部按钮,本课用**靠近电源开关的那个按钮(C 键)**作为拍照触发源。

6.1 配置按键为上拉输入

setup()末尾加入:

pinMode(WIO_KEY_C, INPUT_PULLUP);

INPUT_PULLUP反转输入逻辑:默认情况下按钮未按下输出低电平、按下输出高电平;开启内部上拉后,未按下为高电平,按下为低电平。因此检测按下的条件就是读到LOW

6.2loop()轮询与防抖

void loop() { if (digitalRead(WIO_KEY_C) == LOW) { buttonPressed(); delay(2000); } delay(200); }
  • 每 200ms 轮询一次按键状态;
  • 检测到按下就调用buttonPressed(),随后延时 2 秒,给手指留出松开按钮的时间,避免一次长按被判定为多次触发。

6.3buttonPressed():拍照核心流程

void buttonPressed() { camera.startCapture(); while (!camera.captureReady()) delay(100); Serial.println("Image captured"); byte *buffer; uint32_t length; if (camera.readImageToBuffer(&buffer, length)) { Serial.print("Image read to buffer with length "); Serial.println(length); delete(buffer); } }
  1. startCapture()下发采集指令后,摄像头在后台完成曝光、JPEG 编码并暂存到自身 FIFO;
  2. 主控每 100ms 轮询captureReady(),直到CAP_DONE_MASK位置位;
  3. readImageToBuffer把 FIFO 中的 JPEG 字节搬进堆缓冲,并把字节数赋给length
  4. 将长度打印到串口,随后delete(buffer)释放堆内存。

⚠️ 内存管理纪律:每次readImageToBuffer成功都会new出一块缓冲区,使用后必须delete,否则连续拍照会不断泄漏堆内存,最终导致分配失败。

6.4 串口验证输出

编译上传后,每按一次 C 按钮,串口监视器应输出类似:

Connecting to WiFi.. Connected! Image captured Image read to buffer with length 9224 Image captured Image read to buffer with length 11272

不同画面的 JPEG 大小天然不同——JPEG 是内容相关的有损压缩,相同分辨率下画面细节越丰富体积越大,因此长度值不固定是正常现象。

七、可选扩展:把图片写入 SD 卡核验拍摄效果

串口只能看到字节数,无法直观确认画面内容。最省事的核验方式是把 JPEG 写到 Wio Terminal 的microSD 卡,再插回电脑查看。

限制条件:Wio Terminal 仅支持16GB 及以下的 microSD 卡;更大容量的卡无法工作。卡片需先格式化为FAT32 或 exFAT(macOS 用 Disk Utility、Windows 用资源管理器、Linux 用命令行工具)。

7.1 初始化 SD 卡

main.cpp顶部增加头文件:

#include "SD/Seeed_SD.h" #include <Seeed_FS.h>

setup()前定义初始化函数,并在setup()中调用:

void setupSDCard() { while (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI)) { Serial.println("SD Card Error"); } }

SD 卡同样通过 SPI 总线通信,SDCARD_SS_PINSDCARD_SPI是 Seeed 库提供的板级常量。

7.2 写文件:saveToSDCard

buttonPressed函数上方加入:

int fileNum = 1; void saveToSDCard(byte *buffer, uint32_t length) { char buff[16]; sprintf(buff, "%d.jpg", fileNum); fileNum++; File outFile = SD.open(buff, FILE_WRITE ); outFile.write(buffer, length); outFile.close(); Serial.print("Image written to file "); Serial.println(buff); }
  • fileNum是全局计数器,负责生成递增文件名1.jpg2.jpg……;
  • SD.open(buff, FILE_WRITE)以写模式打开文件,outFile.write(buffer, length)将整块 JPEG 二进制数据落盘,随后close()

7.3 在buttonPressed中调用(必须在 delete 之前)

Serial.print("Image read to buffer with length "); Serial.println(length); saveToSDCard(buffer, length); delete(buffer);

调用顺序很关键:saveToSDCard必须在delete(buffer)之前执行,否则写的是已释放的悬垂指针。

7.4 验证输出与取卡

按 C 键后串口输出示例:

Connecting to WiFi.. Connected! Image captured Image read to buffer with length 16392 Image written to file 1.jpg Image captured Image read to buffer with length 14344 Image written to file 2.jpg

随后将 Wio Terminal 断电,轻按 microSD 卡使其弹出(必要时用指甲或细工具辅助),插入电脑即可查看照片。

💁 白平衡校准提示:前几张照片可能出现偏色,这是摄像头自动白平衡(AWB)收敛期间的正常现象。若影响核验,可修改代码在setup()中静默拍摄几张并丢弃,让 AWB 提前稳定。

八、下一步衔接:把图片交给 Custom Vision 分类

本课采集到的 JPEG 图像正是下一环节——wio-terminal-classify-image.md——的输入。分类阶段会在buttonPressed中把bufferlength交给classifyImage(buffer, length),通过 HTTPS(WiFiClientSecure+ 硬编码的 DigiCert 根证书)POST 到 Custom Vision 预测端点,返回ripe/unripe标签及概率并打印到串口。届时可在platformio.inilib_deps中追加bblanchon/ArduinoJson @ 6.17.3用于解析 JSON 响应,完整参考工程见 code-classify/wio-terminal。

另外值得注意:用设备摄像头实拍的照片与训练集(通常来自手机)在清晰度、白平衡、光照上存在差异,可能拉低分类准确率。若出现偏差,可参考本课 README.md 的 "Improve the model" 章节,用设备实拍图片重新训练并发布新迭代。

九、小结

完成本课你已具备以下能力:

  1. 理解 OV2640 + ArduCAM 的 SPI(传数据)/ I2C(配寄存器)双总线架构,并能按接线表完成 Wio Terminal 的硬件连接;
  2. 掌握 ArduCAM 库的手动安装方式与ARDUCAM_SHIELD_V2OV2640_CAM编译标志的作用;
  3. 读懂camera.hinit/startCapture/captureReady/readImageToBuffer四个核心接口的异步采集模型;
  4. INPUT_PULLUP按键触发拍照,正确处理内存释放与防抖延时;
  5. 可选地,将 JPEG 写入 microSD 卡完成端到端的拍摄结果核验。

本课所有代码均可直接查看/复用仓库中的两个完整工程:code-camera/wio-terminal(拍照 + SD 卡保存)与 code-classify/wio-terminal(拍照 + 云端分类)。

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询