2. 硬件触发信号基础:GPIO、PWM、外部中断,信号电平与边沿触发

各位同学,咱们今天聊点实在的。做多摄像头同步,说白了就是让几个摄像头“齐步走”。那怎么让它们听指挥?靠的就是硬件触发信号。这一节,我把最基础、也最容易被忽视的几个概念掰开揉碎了讲。

我个人习惯,在设计触发方案前,先问自己三个问题:信号从哪来?信号长什么样?信号怎么被识别?搞明白这三点,后面写代码、调时序才不会抓瞎。

2.1 GPIO:最朴素的触发方式

GPIO,通用输入输出口。你把它拉高,摄像头就干活;拉低,就歇着。简单粗暴,但坑也不少。

输出模式下的GPIO

我刚开始做项目时,直接用MCU的GPIO接摄像头触发脚。代码里写个HAL_GPIO_WritePin(),以为万事大吉。结果一上示波器,波形上升沿软得像面条——驱动能力不够。

注意: 普通GPIO的驱动电流通常只有几毫安到十几毫安。如果摄像头触发脚内部有下拉电阻或电容较大,波形会变缓,导致触发时序不准。建议加一级74HC系列缓冲器,或者用推挽输出模式。

输入模式下的GPIO

有时候我们需要用GPIO来读取外部信号,比如判断某个传感器是否就绪。这里有个细节:输入模式下的GPIO,内部上拉/下拉电阻一定要配置对。我曾经因为忘记配置上拉,导致引脚浮空,读到的电平随机跳变,排查了整整一个下午。

// 以STM32为例,配置GPIO为推挽输出,速度设为High
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = TRIGGER_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;   // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;           // 不上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(TRIGGER_PORT, &GPIO_InitStruct);

2.2 PWM:精确控制触发频率

如果你需要摄像头以固定帧率连续采集,比如30fps,那用GPIO手动翻转就太累了。这时候PWM(脉冲宽度调制)就派上用场了。

PWM的核心参数就两个:频率占空比。频率决定触发间隔,占空比决定脉冲宽度。

帧率 (fps) PWM频率 (Hz) 周期 (ms) 推荐占空比
30 30 33.33 10% ~ 50%
60 60 16.67 10% ~ 50%
120 120 8.33 10% ~ 30%
经验之谈: 占空比别设得太小,比如低于5%。因为信号传输过程中会有寄生电容,太窄的脉冲会被“吃掉”。我一般设到20%左右,既保证可靠触发,又留足余量。

用PWM做触发,还有个好处:多路信号天然同步。只要用同一个定时器的多个通道输出PWM,它们的相位关系是确定的。我在一个四目同步项目里就是这么干的,一个定时器搞定四路触发,省了外部同步芯片。

// 配置定时器PWM输出,频率30Hz,占空比20%
// 以STM32 TIM2为例
TIM_HandleTypeDef htim2;
TIM_OC_InitTypeDef sConfigOC = {0};

htim2.Instance = TIM2;
htim2.Init.Prescaler = 8399;      // 72MHz / 8400 = 8.57kHz
htim2.Init.Period = 285;          // 8.57kHz / 286 ≈ 30Hz
HAL_TIM_PWM_Init(&htim2);

sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 57;             // 285 * 20% ≈ 57
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

2.3 外部中断:响应外部触发信号

有时候摄像头是被动触发的——外部设备发来一个脉冲,摄像头收到后开始采集。这时候就需要外部中断来“听”这个信号。

外部中断的配置,关键点在于触发边沿的选择

  • 上升沿触发:信号从低变高时触发。适合高电平有效的信号。
  • 下降沿触发:信号从高变低时触发。适合低电平有效的信号。
  • 双边沿触发:上升和下降都触发。适合需要捕捉每个变化的场景。

嗯,这里要注意:外部中断服务函数里,千万别干耗时的事。比如在中断里调用printf或者操作I2C,那基本等于自杀。正确的做法是:中断里只置一个标志位,主循环里轮询处理。

避坑指南: 我曾经在一个项目中,把摄像头帧处理逻辑直接写在了外部中断里。结果触发频率一高,程序直接卡死。后来改成“中断置标志 + 主循环处理”,问题解决。记住:中断服务函数,越快越好,越短越好。

// 外部中断回调函数(精简版)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == TRIGGER_PIN)
    {
        trigger_flag = 1;  // 只置标志,不做其他
    }
}

// 主循环中处理
while(1)
{
    if(trigger_flag)
    {
        trigger_flag = 0;
        // 在这里执行摄像头采集逻辑
        camera_capture();
    }
}

2.4 信号电平与边沿触发:细节决定成败

很多新手搞不清“电平触发”和“边沿触发”的区别。我打个比方:

  • 电平触发:就像门一直开着,只要门开着,你就一直往里进。信号为高,就一直触发。
  • 边沿触发:就像门开的那一瞬间你挤进去,门开着的时候你反而不进。只认跳变的那一下。

在摄像头触发场景里,绝大多数情况下用边沿触发。为什么?因为电平触发容易出问题——如果信号因为干扰一直保持高电平,摄像头就会不停地触发,帧率失控。

但边沿触发也有弱点:对信号质量要求高。如果信号上有毛刺(glitch),一个脉冲可能被识别成多个边沿。解决办法有两个:

  1. 硬件滤波:在信号线上加RC低通滤波,或者用施密特触发器整形。
  2. 软件去抖:在中断里加一个短暂延时,确认信号稳定后再响应。
重要提醒: 信号线走线要远离高频噪声源,比如DC-DC转换器、电机驱动线。我见过一个案例,触发线跟电源线并排走了10cm,结果每次电机启动,摄像头就误触发一次。后来把触发线改成屏蔽线,问题解决。

最后,总结一下我的个人经验:GPIO适合低频、手动控制;PWM适合固定频率、多路同步;外部中断适合响应外部事件。选哪种,取决于你的具体场景。但不管选哪种,信号完整性永远是第一位的——波形不好,后面全是白搭。

下一节,咱们聊聊如何把这些信号组合起来,设计一个真正可靠的多摄像头同步方案。到时候我会拿一个实际项目案例来拆解,敬请期待。