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 信号分配与接线
一个信号源怎么同时驱动两个相机?这里有三种常见做法:
- 直接并联:把两个相机的触发输入正极接在一起,负极共地。简单,但信号可能反射。
- 用74HC244缓冲:信号先经过缓冲器再分两路,抗干扰能力强。我推荐这个。
- 用光耦隔离:如果相机和控制器距离远(超过5米),用光耦隔离能防止地环路干扰。
接线时记住一条铁律:所有设备的GND必须连在一起。否则信号电平参考点不同,触发可能完全失效。
4.7 验证:用示波器看波形
代码写完了,线接好了,怎么知道对不对?拿示波器看波形。我一般检查三个点:
- 频率对不对:30Hz的周期应该是33.3ms
- 脉宽够不够:5ms的脉冲,示波器上量出来要准
- 上升沿陡不陡:上升时间最好小于1μs,太缓的边沿相机可能不认
如果没有示波器,也可以用逻辑分析仪。淘宝上几十块钱的就能用,采样率100MHz够用了。
4.8 避坑指南
做硬件触发,我踩过的坑能写满一页纸。挑几个最常见的:
- 信号反射:线太长(超过3米)又不加终端电阻,波形会振铃。解决办法:在相机端并联一个100Ω电阻到GND。
- 电源噪声:用同一个电源给控制器和相机供电,PWM信号上会叠加纹波。解决办法:控制器和相机分开供电,或者加磁珠隔离。
- 触发模式设置:有些相机默认是「软件触发」模式,你给了硬件信号它也不理。记得在相机配置里改成「硬件触发」或「外部触发」。
一句话总结:硬件触发的本质,就是用外部信号把两个相机的曝光时刻「钉死」在同一个时间点上。STM32或Arduino只是工具,真正重要的是信号的质量和接线的可靠性。
好了,这一章的内容就到这里。代码可以直接拿去用,参数根据你的相机手册微调一下就行。记住,做硬件触发,慢工出细活——示波器上看清楚了再上产线。