4、硬件触发方案设计:使用STM32/Arduino产生PWM信号,实现双相机硬同步

说到双目相机的同步,很多新手第一反应是「用软件同时发指令不就行了?」。嗯,我当年也这么想过。结果呢?两个相机各自有几十毫秒的启动延迟,拍运动物体时画面错位得一塌糊涂。说白了,软件同步靠不住。

真正靠谱的方案,是硬件触发。用外部信号同时「电」一下两个相机,让它们在同一时刻开始曝光。今天我就带你走一遍,怎么用STM32或者Arduino产生PWM信号,把两个相机牢牢绑在一起。

4.1 为什么非要用硬件触发?

先说说我踩过的坑。有一次做机器人视觉抓取,传送带速度1.5m/s,两个相机拍同一颗螺丝。软件触发下,左相机拍到螺丝在位置A,右相机拍到它在位置B——差了足足3厘米。抓取失败率直接飙到40%。

硬件触发解决的就是这个「时间差」问题。它的核心逻辑很简单:

  • 一个信号源:STM32或Arduino产生一个脉冲
  • 同时送达:信号通过线缆分发给两个相机的触发输入口
  • 同时响应:两个相机在脉冲上升沿开始曝光

你想想看,如果信号传播延迟是纳秒级的,两个相机的曝光起始时间差就能控制在微秒以内。对于绝大多数工业场景,这已经绰绰有余了。

4.2 方案选型:STM32 vs Arduino

我个人习惯根据项目复杂度来选。简单列个对比表,你一看就明白:

对比项 STM32 Arduino
PWM精度 16位定时器,频率可精确到Hz级 8位定时器,精度一般
多路输出 最多可同时输出8路独立PWM 通常只有2~4路
开发难度 需要配置寄存器或HAL库 几行代码搞定
成本 15~30元 10~20元
适合场景 工业级、多相机、高精度 原型验证、教学演示

我的建议是:做产品用STM32,做实验用Arduino。别问我为什么——Arduino的PWM频率抖动在示波器上看着真的有点心塞。

4.3 核心原理:PWM触发信号长什么样?

大多数工业相机(如Basler、海康、大恒)的触发输入,接受的是这样的信号:

  • 电平标准:3.3V或5V,TTL电平
  • 脉冲宽度:至少1ms(有些相机要求更宽)
  • 上升沿触发:信号从低到高跳变时,相机开始曝光
  • 频率:根据你的采集帧率来定,比如30fps就是30Hz

关键参数速查

曝光时间 = 脉冲宽度(通常由相机内部设置)
触发间隔 = 1 / 帧率
占空比 = 脉冲宽度 / 触发间隔 × 100%

举个例子:你要30fps采集,曝光时间5ms。那么PWM频率就是30Hz,脉冲宽度5ms,占空比15%。

4.4 实战:用Arduino产生触发信号

先来个最简单的。Arduino Uno的Pin 9支持硬件PWM,几行代码就能跑起来:

// 双相机硬件触发 - Arduino版本
// 引脚定义
const int triggerPin = 9;      // PWM输出引脚
const int ledPin = 13;         // 板载LED指示

// 参数配置
const float frameRate = 30.0;          // 帧率 30fps
const float exposureTime = 0.005;      // 曝光时间 5ms

void setup() {
  pinMode(triggerPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  
  // 计算PWM参数
  int pwmFrequency = (int)frameRate;           // 30Hz
  int pwmResolution = 255;                     // 8位分辨率
  int dutyCycle = (int)(exposureTime * frameRate * 255);
  
  // 配置PWM
  TCCR1A = 0b10000010;  // 快速PWM模式,非反相
  TCCR1B = 0b00011001;  // 无预分频,WGM模式
  ICR1 = 16000000 / pwmFrequency - 1;  // 设置频率
  OCR1A = dutyCycle;                    // 设置占空比
  
  digitalWrite(ledPin, HIGH);  // 指示运行
}

void loop() {
  // 空循环,PWM由硬件自动产生
}

小技巧:Arduino的PWM频率默认是490Hz或980Hz,不适合直接做触发。上面代码通过直接操作定时器寄存器,把频率降到了30Hz。我曾经在这上面卡了半天,后来发现用analogWrite()根本调不出低频。

4.5 进阶:用STM32实现高精度触发

如果你需要更稳定的信号,或者要同时触发4个以上的相机,STM32是更好的选择。我用的是STM32F103C8T6(蓝色 pill板),性价比很高。

// STM32硬件触发 - HAL库版本
// 使用TIM2的CH1输出PWM

#include "stm32f1xx_hal.h"

// PWM句柄
TIM_HandleTypeDef htim2;

// 参数配置
#define FRAME_RATE      30      // 30fps
#define EXPOSURE_US     5000    // 5ms = 5000us

void PWM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    
    // 使能时钟
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // PA0作为TIM2_CH1输出
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置TIM2
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1;          // 72MHz / 72 = 1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 1000000 / FRAME_RATE - 1;  // 1MHz / 30 = 33333
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim2);
    
    // 配置PWM通道
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = EXPOSURE_US;           // 5ms对应的计数值
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
    
    // 启动PWM
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}

int main(void) {
    HAL_Init();
    PWM_Init();
    
    while(1) {
        // 主循环,PWM由硬件自动产生
    }
}

注意:STM32的GPIO输出电平是3.3V。如果你的相机需要5V触发信号,记得加一个电平转换电路(比如用MOS管或74LVC4245)。我曾经直接连5V相机,结果烧了一个STM32的引脚——别学我。

4.6 信号分配与接线

一个信号源怎么同时驱动两个相机?这里有三种常见做法:

  1. 直接并联:把两个相机的触发输入正极接在一起,负极共地。简单,但信号可能反射。
  2. 用74HC244缓冲:信号先经过缓冲器再分两路,抗干扰能力强。我推荐这个。
  3. 用光耦隔离:如果相机和控制器距离远(超过5米),用光耦隔离能防止地环路干扰。

接线时记住一条铁律:所有设备的GND必须连在一起。否则信号电平参考点不同,触发可能完全失效。

4.7 验证:用示波器看波形

代码写完了,线接好了,怎么知道对不对?拿示波器看波形。我一般检查三个点:

  • 频率对不对:30Hz的周期应该是33.3ms
  • 脉宽够不够:5ms的脉冲,示波器上量出来要准
  • 上升沿陡不陡:上升时间最好小于1μs,太缓的边沿相机可能不认

如果没有示波器,也可以用逻辑分析仪。淘宝上几十块钱的就能用,采样率100MHz够用了。

4.8 避坑指南

做硬件触发,我踩过的坑能写满一页纸。挑几个最常见的:

  • 信号反射:线太长(超过3米)又不加终端电阻,波形会振铃。解决办法:在相机端并联一个100Ω电阻到GND。
  • 电源噪声:用同一个电源给控制器和相机供电,PWM信号上会叠加纹波。解决办法:控制器和相机分开供电,或者加磁珠隔离。
  • 触发模式设置:有些相机默认是「软件触发」模式,你给了硬件信号它也不理。记得在相机配置里改成「硬件触发」或「外部触发」。

一句话总结:硬件触发的本质,就是用外部信号把两个相机的曝光时刻「钉死」在同一个时间点上。STM32或Arduino只是工具,真正重要的是信号的质量和接线的可靠性。

好了,这一章的内容就到这里。代码可以直接拿去用,参数根据你的相机手册微调一下就行。记住,做硬件触发,慢工出细活——示波器上看清楚了再上产线。


专注资料整理