我有一个限位开关连接到arduino Mega 2650进行运动控制。限位开关的两个常开触点连接到Arduino引脚和地,这样当限位开关接合时,Arduino引脚会短路接地。
正如预期的那样,我在这个设置上遇到了问题。我在ISR中使用计数器确认了它。最后,我编写了以下代码,似乎可以可靠地识别我的限位开关是否在任何给定时间点处于接合或脱离状态。
const int lsOuterLeftIn = 18; // lsOuterLeftIn is my Limit Switch
const int LED = 9;
volatile bool lsEngaged = false; // flag for limit switch engaged
void setup() {
pinMode(lsOuterLeftIn, INPUT_PULLUP);
pinMode(LED, OUTPUT);
attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR1, FALLING);
attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR2, RISING);
}
void loop() {
if (lsEngaged) digitalWrite(LED, HIGH);
else digitalWrite(LED, LOW);
}
void ISR1(){
delay(100);
lsEngaged = (digitalRead(lsOuterLeftIn));
}
void ISR2(){
delay(100);
lsEngaged = (digitalRead(lsOuterLeftIn));
}
但是,这是我的问题。我遇到了这个Arduino documentation page,它说
“由于delay()需要中断才能工作,如果被调用则无法工作 在ISR内部。 “
但是,我确实在ISR中使用了delay()
,它似乎有效,发生了什么?我目前的情况是否有效,但可能很容易破解,因为delay()
函数可能会因文档错误而导致我出现故障?
答案 0 :(得分:3)
TomKeddie的回答看起来很正确:你没有遇到任何问题。无论如何,在我看来,你的代码在概念上至少有两个原因是错误的。现在我将解释你原因。
有两种输入:您必须立即回答的问题以及您必须回答的问题,但不是直接的威胁。例如,通常安全限位挡块落在第一组中,因为一旦击中它就需要停止执行器。另一方面,UI按钮属于第二组,因为您不需要立即回答它。
注意:在一个完善的程序中,您通常可以在十分之一毫秒内回答第二类输入,因此用户永远不会看到延迟。
现在,如果您的输入属于第二组输入,则不应使用ISR来读取它,因为您可能会阻止更重要的事情。而是在主循环中读取它,正确地去除它。例如,您可以使用Bounce
库,或者自己实现它:
#define CHECK_EVERY_MS 20
#define MIN_STABLE_VALS 5
unsigned long previousMillis;
char stableVals;
...
void loop() {
if ((millis() - previousMillis) > CHECK_EVERY_MS)
{
previousMillis += CHECK_EVERY_MS;
if (digitalRead(lsOuterLeftIn) != lsEngaged)
{
stableVals++;
if (stableVals >= MIN_STABLE_VALS)
{
lsEngaged = !lsEngaged;
stableVals = 0;
}
}
else
stableVals = 0;
}
...
}
如果值发生变化,将每20ms检查一次。但是,该值仅在其稳定超过5个周期(即100 ms)时才会更新。
这样您就不会使用该任务阻止主程序。
另一方面,如果您的输入对您的设备构成严重威胁(例如终端),您需要尽快回答。如果是这种情况,您在等待100ms才能回答,这与快速输入的需要相反。
当然,你可以去除这样的输入,因为去抖会出现延迟。但是,您可以将一个州优先于另一个州。在连接到地面的限位挡块的情况下,严重的威胁是输入状态被接地时。所以我建议你设置变量:
执行此操作的代码类似于:
#define CHECK_EVERY_MS 20
#define MIN_STABLE_VALS 5
unsigned long previousMillis;
char stableVals;
attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR1, FALLING);
...
void loop() {
if ((millis() - previousMillis) > CHECK_EVERY_MS)
{
previousMillis += CHECK_EVERY_MS;
if ((digitalRead(lsOuterLeftIn) == HIGH) && (lsEngaged == LOW))
{
stableVals++;
if (stableVals >= MIN_STABLE_VALS)
{
lsEngaged = HIGH;
stableVals = 0;
}
}
else
stableVals = 0;
}
...
}
void ISR1()
{
lsEngaged = LOW;
}
正如你所看到的那样,唯一的中断是下降的,而且最重要的是它很短。
如果您需要执行其他指令,例如暂停电机,您可以使用ISR1功能(如果它们非常短)。
请记住:ISR必须尽可能短,因为当微控制器位于其中之一时,它会对其他所有内容视而不见
答案 1 :(得分:2)
在AVR上,delay()实现如下。没有涉及中断(micros()返回timer0计数值,yield()指的是不会在简单草图中使用的调度程序。)
我认为评论是为了便于携带,您使用的环境适用于越来越多的平台。你正在做什么在AVR上很好,而不是在另一个平台上。
我建议使用简单的for循环等待旋转。除非耗电是一个问题,否则cpu没有做任何其他事情,但这超出了这里的范围。
void delay(unsigned long ms)
{
uint16_t start = (uint16_t)micros();
while (ms > 0) {
yield();
if (((uint16_t)micros() - start) >= 1000) {
ms--;
start += 1000;
}
}
}
答案 2 :(得分:0)
从您的去抖动代码中,您可以节省100毫秒的交换时间。
因此,如果你真的不需要在事件的微秒内作出反应,那么考虑每隔10毫秒(例如从计时器ISR)轮询输入。
(使用外部中断的原因只有两个:1。您需要对信号做出快速反应(μs!),或2.您需要从深度省电模式中唤醒,其中定时器不是对于其他一切你可以进行基于计时器的轮询。)
伪代码:
#define STABLE_SIGNAL_DURATION 5
uint8_t button_time_on = 0;
volatile bool button_is_pressed = false;
...
// Every 10ms do (can be done in a timer ISR):
if ( read_button_input() == ON ) {
if ( button_time_on >= STABLE_SIGNAL_DURATION ) {
button_is_pressed = true;
} else {
button_time_on++;
}
} else {
button_time_on = 0; // button not pressed (any more).
button_is_pressed = false;
}
...
和main()
:
bool button_press_handled = false;
while(1) {
// do your other main loop stuff...
button_press_handled = button_press_handled && button_is_pressed;
if ( !button_press_handled && button_is_pressed ) {
// Handle press of the button
// ...
// Note that we handled the event for now:
button_press_handled = true;
}
}
答案 3 :(得分:0)
比使用时间戳更容易
volatile bool buttonDirty = false;
void setup() {
attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPress, FALLING);
}
void loop() {
while(1){
readButtons();
}
}
void buttonPress(){
if(buttonDirty) return;
buttonDirty = true;
}
void readButtons(){
if(!buttonDirty) return;
delay(100);
...........
}