Arduino控制Dobot引脚为LOW时仍移动无法立即停止问题
问题根因
- 状态检测粒度过粗:当前代码仅在整段
moveX()、PutDown()函数执行完成后才读取一次按钮电平,而上述函数内部包含多步运动指令、最长1300ms的阻塞delay(),一旦进入函数,无论中途按钮是否切为LOW,都必须等函数内所有代码跑完才会判断状态,必然出现多跑数步动作的情况。 - Dobot指令队列缓存未清空:所有
SetPTPCmd()调用都是先把运动指令写入Dobot机载的指令队列,并非发完就立刻执行完成,就算靠break跳出循环,之前已经写入队列的未执行指令仍会被机械臂依次跑完,不会即时停止。 - 初始化逻辑错误:
InitRAM()、ProtocolInit()被放在loop()的死循环外层,每次loop重启都会重复初始化协议与运动参数,额外增加运行不稳定风险;外加2秒的定时检测间隔,进一步拉长了按钮响应延迟。 - 缺少按钮消抖处理:机械按钮电平跳变时的毫秒级抖动,也会导致状态识别出错。
修复方案
- 新增全局紧急停止标志,将按钮检测放到10ms级的定时器中断中执行,同时做20ms消抖处理,只要检测到按钮切为LOW,立刻置位停止标志。
- 检测到停止信号时,第一时间调用Dobot协议自带的停止、清队列接口,直接终止机械臂运动、清空所有缓存的未执行指令,不要仅靠break跳出循环。
- 把所有阻塞式
delay()替换为基于millis()的非阻塞延时,延时过程中持续运行协议处理、检测停止标志,避免程序卡死在延时阶段无法响应信号。 - 将
InitRAM()、ProtocolInit()移到setup()中仅执行一次,去掉loop()内多余的死循环嵌套,删除不必要的2秒检测间隔。 - 按钮引脚改为
INPUT_PULLUP上拉输入模式,降低外部电平干扰。
核心修改代码示例
#include "stdio.h" #include "Protocol.h" #include "command.h" #include "FlexiTimer2.h" #include "PROTOCOLID.h" #include "stdint.h" #include <Arduino.h> const int CL1 = 3; //Start Suction const int CL3 = 2; // Stop Suction const int StartButton = 10; const int StopButton = 4; #define SERIAL_TX_BUFFER_SIZE 64 #define SERIAL_RX_BUFFER_SIZE 256 EndEffectorParams gEndEffectorParams; JOGJointParams gJOGJointParams; JOGCoordinateParams gJOGCoordinateParams; JOGCommonParams gJOGCommonParams; JOGCmd gJOGCmd; PTPCoordinateParams gPTPCoordinateParams; PTPCommonParams gPTPCommonParams; PTPCmd gPTPCmd; PTPJumpParams gPTPJumpParams; HOMECmd ghomeCmd; WAITCmd gwaitCmd; uint64_t gQueuedCmdIndex; // 新增全局紧急停止标志 volatile bool isEmergencyStop = false; void Serialread() { while(Serial1.available()) { uint8_t data = Serial1.read(); if (RingBufferIsFull(&gSerialProtocolHandler.rxRawByteQueue) == false) { RingBufferEnqueue(&gSerialProtocolHandler.rxRawByteQueue, &data); } } } // 定时器中执行按钮检测,带消抖 void ButtonCheck() { static uint32_t lastDebounceTime = 0; static int lastButtonState = HIGH; int currentState = digitalRead(StartButton); if (currentState != lastButtonState) { lastDebounceTime = millis(); } if (millis() - lastDebounceTime > 20) { if (currentState == LOW) { isEmergencyStop = true; // 立刻停止运动、清空指令队列 SetQueuedCmdStopExec(true, &gQueuedCmdIndex); SetQueuedCmdClear(); ProtocolProcess(); StopSuck(); } else { isEmergencyStop = false; } } lastButtonState = currentState; } int Serial_putc( char c, struct __file * ) { Serial.write( c ); return c; } void printf_begin(void) { fdevopen( &Serial_putc, 0 ); } void InitRAM(void) { gPTPCoordinateParams.xyzVelocity = 200; gPTPCoordinateParams.rVelocity = 200; gPTPCoordinateParams.xyzAcceleration = 200; gPTPCoordinateParams.rAcceleration = 200; SetPTPCoordinateParams(&gPTPCoordinateParams, true, &gQueuedCmdIndex); ProtocolProcess(); gPTPCommonParams.velocityRatio = 200; gPTPCommonParams.accelerationRatio = 200; SetPTPCommonParams(&gPTPCommonParams, true, &gQueuedCmdIndex); ProtocolProcess(); gPTPCmd.ptpMode = MOVL_XYZ; gQueuedCmdIndex = 0; } void Suck() { digitalWrite(CL1,LOW); digitalWrite(CL3,HIGH); } void StopSuck() { digitalWrite(CL1,HIGH); digitalWrite(CL3,LOW); } // 非阻塞延时工具函数 bool NonBlockDelay(uint32_t delayTime) { static uint32_t startTick = 0; static bool isStart = false; if (!isStart) { startTick = millis(); isStart = true; } if (millis() - startTick >= delayTime) { isStart = false; return true; } return false; } void PutDown() { if (isEmergencyStop) return; gPTPCmd.z = 40; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.x = 0; gPTPCmd.y = 250; gPTPCmd.z = 10; gPTPCmd.r = 0; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.z = -10; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; while(!NonBlockDelay(1000)) { ProtocolProcess(); if (isEmergencyStop) return; } StopSuck(); gPTPCmd.z = 40; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); } void move1() { if (isEmergencyStop) return; gPTPCmd.x = 235; gPTPCmd.y = 0; gPTPCmd.z = 40; gPTPCmd.r = 0; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.z = -30; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; Suck(); while(!NonBlockDelay(1300)) { ProtocolProcess(); if (isEmergencyStop) return; } } void move2() { if (isEmergencyStop) return; gPTPCmd.x = 255; gPTPCmd.y = 0; gPTPCmd.z = 40; gPTPCmd.r = 0; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.z = -30; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; Suck(); while(!NonBlockDelay(1300)) { ProtocolProcess(); if (isEmergencyStop) return; } } void move3() { if (isEmergencyStop) return; gPTPCmd.x = 275; gPTPCmd.y = 0; gPTPCmd.z = 40; gPTPCmd.r = 0; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.z = -30; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; Suck(); while(!NonBlockDelay(1300)) { ProtocolProcess(); if (isEmergencyStop) return; } } void move4() { if (isEmergencyStop) return; gPTPCmd.x = 295; gPTPCmd.y = 0; gPTPCmd.z = 40; gPTPCmd.r = 0; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.z = -30; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; gPTPCmd.z = 40; SetPTPCmd(&gPTPCmd, true, &gQueuedCmdIndex); ProtocolProcess(); if (isEmergencyStop) return; Suck(); while(!NonBlockDelay(1300)) { ProtocolProcess(); if (isEmergencyStop) return; } } void setup() { Serial.begin(115200); Serial1.begin(115200); printf_begin(); pinMode(CL1,OUTPUT); pinMode(CL3,OUTPUT); pinMode(StartButton,INPUT_PULLUP); pinMode(StopButton,INPUT_PULLUP); // 定时器10ms触发,同时处理串口读取和按钮检测 FlexiTimer2::set(10, [](){ Serialread(); ButtonCheck(); }); FlexiTimer2::start(); // 初始化仅执行一次 ProtocolInit(); InitRAM(); printf("\r\n======Enter demo application======\r\n"); } void loop() { ProtocolProcess(); if (isEmergencyStop) return; static uint8_t step = 0; static uint32_t stepTick = millis(); // 每50ms推进一个动作步,动作内部自带停止检测 if (millis() - stepTick > 50) { stepTick = millis(); switch(step) { case 0: move1(); step++; break; case 1: PutDown(); step++; break; case 2: move2(); step++; break; case 3: PutDown(); step++; break; case 4: move3(); step++; break; case 5: PutDown(); step++; break; case 6: move4(); step++; break; case 7: PutDown(); step = 0; break; default: step = 0; } } }
注意事项
- 所有运动函数每发送完一条PTP指令都要加停止标志判断,不要把多步运动全部发完再检测状态。
- 发送停止、清队列指令后要多执行几次
ProtocolProcess(),确保指令完整发送到Dobot机载控制器,避免残留指令继续执行。 - 上拉输入模式下,按钮需接在对应IO引脚和GND之间,按下时为LOW电平、松开为HIGH电平,抗干扰能力远高于普通浮空输入模式。
内容的提问来源于stack exchange,提问作者Look Ace
相关产品推荐
相关产品推荐

