3. GPIO控制LED与按键中断——为机器人关节使能与限位开关做准备
做机器人运动学调试,第一步不是算正逆解,而是让关节动起来。怎么动?靠使能信号。怎么停?靠限位开关。这两个信号,在STM32上就是GPIO的输入输出。
我见过不少新手,一上来就搞CAN总线、搞EtherCAT,结果关节没使能,电机干烧。说白了,GPIO是嵌入式世界的“手”和“眼”。今天咱们就把这只手练利索。
3.1 从LED开始:关节使能信号的“Hello World”
关节使能信号,本质上就是一个高电平或低电平。你给驱动器一个ENABLE引脚拉高,电机就通电;拉低,就断电。跟LED亮灭一个道理。
先看最简代码,用STM32CubeMX生成工程后,在main.c里写:
// 使能关节(假设PA0接使能引脚,高电平有效)
void Joint_Enable(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
}
// 关闭关节
void Joint_Disable(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
}
// 翻转使能状态(调试时常用)
void Joint_Toggle(void)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}
嗯,就这么简单。但我在项目里吃过亏——使能瞬间的电流冲击。驱动器使能瞬间,母线电容会猛充电,如果电源功率不够,STM32会掉电复位。后来我加了个延时:
void Joint_Enable_Safe(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
HAL_Delay(50); // 等50ms,让驱动器缓启动
}
我的习惯:使能引脚不要直接连STM32,中间串一个100Ω电阻。万一驱动器那边短路,电阻能保命。我烧过两个IO口才长记性。
3.2 按键中断:限位开关的“急停”逻辑
限位开关,就是机器人的“墙壁传感器”。关节转到极限位置,限位开关被触发,必须立刻停。用轮询?不行。万一主循环在算矩阵,关节就撞上去了。
必须用中断。看配置:
| 参数 | 配置值 | 说明 |
|---|---|---|
| GPIO Mode | External Interrupt Mode with Rising/Falling edge trigger | 双边沿触发,适应常开/常闭两种限位开关 |
| GPIO Pull-up/Pull-down | Pull-up | 内部上拉,防止悬空误触发 |
| NVIC Priority | Preemption Priority = 0, Sub Priority = 0 | 最高优先级,必须立刻响应 |
| GPIO Speed | High | 限位信号变化快,用高速模式 |
中断服务函数怎么写?
// 假设限位开关接PB0,低电平触发(常闭型限位开关被压开时变高)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0)
{
// 读取当前电平
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
// 限位触发!立刻关闭关节使能
Joint_Disable();
// 设置故障标志
g_LimitSwitchTriggered = 1;
}
else
{
// 限位释放,可以重新使能
g_LimitSwitchReleased = 1;
}
}
}
我曾经踩过的坑:中断里调用HAL_Delay()会导致死锁。因为HAL_Delay依赖SysTick中断,而SysTick中断优先级比EXTI低,如果EXTI不退出,SysTick永远进不来。中断里只做最轻量的操作——置标志位、清中断、关使能。其他事交给主循环。
3.3 硬件去抖:别让开关“抖”掉你的关节
机械开关都有抖动。按下一次,电平会跳变几十毫秒。如果你不做处理,一次限位触发可能产生几十次中断。
软件去抖最简单:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0)
{
// 先关中断,防止重复触发
HAL_NVIC_DisableIRQ(EXTI0_IRQn);
// 启动定时器,20ms后重新采样
HAL_TIM_Base_Start_IT(&htim6);
g_DebounceTimerPin = GPIO_PIN_0;
}
}
// 定时器回调中重新采样
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
HAL_TIM_Base_Stop_IT(&htim6);
// 重新读取电平
uint8_t level = HAL_GPIO_ReadPin(GPIOB, g_DebounceTimerPin);
// 根据电平做相应处理
ProcessLimitSwitch(level);
// 重新使能中断
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
}
硬件去抖更可靠——在GPIO引脚对地并一个0.1μF电容,再串一个10kΩ电阻。RC时间常数≈1ms,能滤掉大部分抖动。
实战建议:量产产品用硬件去抖+软件去抖双重保险。我见过电容虚焊导致整批机器限位失灵的事故,软件去抖是最后的防线。
3.4 本章知识体系
下面这张图把GPIO在机器人关节控制中的角色串起来了:
3.5 实战:搭建一个关节使能+限位保护的最小系统
把上面所有知识点串起来,写一个完整示例:
// 头文件
#include "main.h"
#include "gpio.h"
#include "tim.h"
// 全局变量
volatile uint8_t g_JointEnabled = 0;
volatile uint8_t g_LimitSwitchHit = 0;
// 初始化
void JointControl_Init(void)
{
// 默认关闭关节
Joint_Disable();
g_JointEnabled = 0;
g_LimitSwitchHit = 0;
// 使能限位开关中断
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}
// 主循环中的状态机
void JointControl_Task(void)
{
if(g_LimitSwitchHit)
{
// 限位触发,保持关节关闭
Joint_Disable();
// 点亮报警LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
}
else
{
// 正常运行时,根据指令使能
if(g_JointEnabled)
{
Joint_Enable_Safe();
// 熄灭报警LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
}
}
}
// 中断回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == LIMIT_SWITCH_PIN)
{
// 硬件去抖已经做了,这里直接读
if(HAL_GPIO_ReadPin(LIMIT_SWITCH_PORT, LIMIT_SWITCH_PIN) == GPIO_PIN_RESET)
{
g_LimitSwitchHit = 1;
Joint_Disable(); // 立刻断电
}
else
{
g_LimitSwitchHit = 0;
}
}
}
调试技巧:用逻辑分析仪抓GPIO波形。使能信号上升沿到电机实际转动,中间有驱动器响应延迟。我测过某款国产驱动器,延迟高达120ms。这个参数在运动学控制里必须补偿。
3.6 常见故障与定位
| 故障现象 | 可能原因 | 排查方法 |
|---|---|---|
| 关节无法使能 | GPIO配置为开漏输出 | 检查GPIO模式,改为推挽输出 |
| 限位触发后关节不停 | 中断优先级不够高 | 检查NVIC配置,设为最高抢占优先级 |
| 限位误触发 | 未做去抖处理 | 加硬件RC或软件延时去抖 |
| 使能瞬间MCU复位 | 电源功率不足 | 使能后加延时,或增加电源电容 |
| 中断不响应 | GPIO时钟未使能 | 检查RCC配置,确保GPIOB时钟已开启 |
GPIO看似简单,但它是机器人控制的基石。使能信号给不对,后面所有运动学计算都是白搭。限位开关处理不好,轻则撞限位,重则损坏机械结构。
我个人习惯,每做一个新项目,先把GPIO这一层测透——用示波器看波形,用逻辑分析仪看时序,确保每个信号都干净可靠。这一步省不了,也急不得。