2、硬件触发基础:GPIO触发、PWM触发、外部中断与时间戳捕获

各位同学,咱们今天聊点硬核的。硬件触发,说白了就是让传感器自己喊一声「我准备好了」,而不是让CPU一遍遍去问「你好了没?」。我在做激光雷达和IMU融合的项目时,深刻体会到:没有硬件触发,时间同步就是空中楼阁。

嗯,这一节我们聚焦四个核心概念:GPIO触发、PWM触发、外部中断、时间戳捕获。它们就像四根柱子,撑起了多传感器同步的整个框架。

2.1 GPIO触发:最朴素的同步方式

GPIO触发,其实就是用一根线拉高拉低。你想想看,两个传感器之间,一根杜邦线就能搞定同步信号。我在早期做双目相机同步时,用的就是这招。

具体怎么玩?主传感器(比如IMU)输出一个上升沿,从传感器(比如相机)检测到这个边沿后立即开始曝光。代码实现其实很简单:

// 主传感器输出触发脉冲
void master_trigger_output(void) {
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
    delay_us(10);  // 脉冲宽度10微秒
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
}

// 从传感器检测触发
void slave_trigger_callback(void) {
    if (HAL_GPIO_ReadPin(TRIG_GPIO_Port, TRIG_Pin) == GPIO_PIN_SET) {
        start_exposure();  // 开始曝光
    }
}

⚠️ 我曾经踩过的坑:GPIO触发看似简单,但信号抖动是个大问题。有一次我在电机旁边跑GPIO触发,结果电机一启动,触发信号就乱跳。后来加了RC滤波才搞定。记住:工业现场,GPIO线一定要走屏蔽线,长度别超过30cm。

2.2 PWM触发:精度与灵活性的平衡

GPIO触发只能给一个脉冲,但PWM触发可以给一串。说白了,PWM就是方波,占空比和频率都能调。我个人习惯用PWM来做多传感器分时触发。

举个例子:你有三个相机,想让它们轮流曝光。用PWM的上升沿触发相机1,下降沿触发相机2,下一个上升沿触发相机3。这样一根线就搞定了三个传感器的同步。

参数 GPIO触发 PWM触发
信号复杂度 单脉冲 连续方波
触发次数 一次 可编程多次
适用场景 单次同步 周期性同步
抗干扰能力 较弱 较强(有固定频率)

PWM配置代码示例:

// 配置PWM输出,频率1kHz,占空比50%
void pwm_trigger_init(void) {
    TIM_HandleTypeDef htim;
    htim.Instance = TIM2;
    htim.Init.Prescaler = 84 - 1;      // 84MHz / 84 = 1MHz
    htim.Init.Period = 1000 - 1;       // 1MHz / 1000 = 1kHz
    htim.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_PWM_Init(&htim);

    TIM_OC_InitTypeDef sConfigOC;
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 500;             // 占空比50%
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    HAL_TIM_PWM_ConfigChannel(&htim, &sConfigOC, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1);
}

💡 我的经验:PWM频率别选太高。我试过100kHz的PWM触发相机,结果相机跟不上,丢帧严重。一般1kHz到10kHz比较稳妥。具体看传感器数据手册里的「Trigger Input Frequency」参数。

2.3 外部中断:精准捕获触发时刻

外部中断,是硬件触发里最灵敏的机制。为什么?因为它是硬件级别的响应,不像轮询那样有延迟。我记得有一次做无人机飞控,IMU数据必须精确到微秒级,这时候外部中断就是救命稻草。

外部中断的工作原理:当GPIO引脚检测到边沿变化(上升沿或下降沿),CPU立即暂停当前任务,跳转到中断服务函数。这个响应时间通常在几十纳秒到几微秒之间。

// 外部中断配置(STM32 HAL库)
void exti_init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;  // 上升沿触发
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

// 中断服务函数
void EXTI0_IRQHandler(void) {
    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
        // 在这里记录时间戳
        timestamp = get_system_time_us();
        // 触发传感器采集
        sensor_trigger();
    }
}

⚠️ 注意:中断服务函数里别干太多活。我见过有人在中断里做浮点运算、打印日志,结果中断嵌套导致系统崩溃。记住:中断里只做最必要的事——记录时间戳、设置标志位。复杂处理放到主循环里。

2.4 时间戳捕获:给每个事件打上时间标签

时间戳捕获,是硬件触发的灵魂。你想想看,传感器采集到了数据,但不知道是什么时候采集的,那这数据还有什么用?

硬件时间戳捕获,通常有两种方式:

  • CPU定时器捕获:外部中断发生时,直接读取定时器的当前计数值。精度取决于定时器时钟频率。
  • 专用时间戳单元:一些高端MCU(比如STM32H7)有专门的Timestamp模块,精度可达纳秒级。

我个人更推荐用定时器捕获,因为通用性强,几乎所有MCU都支持。

// 定时器输入捕获配置
void tim_capture_init(void) {
    TIM_HandleTypeDef htim;
    htim.Instance = TIM5;
    htim.Init.Prescaler = 84 - 1;      // 1MHz计数
    htim.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim.Init.Period = 0xFFFFFFFF;     // 最大周期
    HAL_TIM_IC_Init(&htim);

    TIM_IC_InitTypeDef sConfigIC;
    sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0;            // 无滤波
    HAL_TIM_IC_ConfigChannel(&htim, &sConfigIC, TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&htim, TIM_CHANNEL_1);
}

// 捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        uint32_t capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        // capture_value就是精确的时间戳
        store_timestamp(capture_value);
    }
}

🔑 核心要点:时间戳的精度取决于计数时钟频率。1MHz的时钟,精度是1微秒;100MHz的时钟,精度是10纳秒。选型时,根据你的传感器需求来定。比如激光雷达通常需要微秒级,而视觉惯性里程计可能需要纳秒级。

2.5 实战中的组合拳

好了,四个基础概念讲完了。但实际项目中,它们往往是组合使用的。我分享一个我做过的一个案例:

一个自动驾驶小车,需要同步激光雷达、摄像头和IMU。我的方案是:

  1. 用STM32的定时器输出PWM信号,频率1kHz
  2. PWM的上升沿触发激光雷达采集
  3. PWM的下降沿触发摄像头曝光
  4. IMU通过外部中断检测PWM边沿,记录时间戳
  5. 所有传感器数据都带上硬件时间戳,上传到主控

这样,三个传感器的数据就有了统一的时间基准。误差控制在10微秒以内。

💡 避坑指南:我曾经犯过一个错误——所有传感器都用同一个GPIO触发。结果信号反射导致误触发。后来我给每个传感器加了独立的触发线,问题解决。记住:共享触发线时,一定要加缓冲器(比如74HC245)。

嗯,这一节的内容就到这里。硬件触发看起来简单,但细节决定成败。下一节我们会讲更高级的触发方式——PPS同步和IEEE 1588协议。到时候你会发现,今天的基础知识都是铺垫。

记住:做多传感器同步,硬件触发是地基。地基不稳,上层算法再牛也白搭。