ฟังก์ชั่นสามารถเรียกใช้โดยอัตโนมัติเมื่ออินพุทเปลี่ยนแปลงได้หรือไม่?


21

ขณะนี้ร่างของฉันกำลังตรวจสอบพินอินพุตทุกครั้งที่วนรอบลูปหลัก หากตรวจพบการเปลี่ยนแปลงจะเรียกใช้ฟังก์ชันที่กำหนดเองเพื่อตอบสนองต่อการเปลี่ยนแปลงนั้น นี่คือรหัส (ตัดลงไปที่จำเป็น):

int pinValue = LOW;

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
}

void loop()
{
    // Read current input
    int newValue = digitalRead(2);

    // Has the input changed?
    if (newValue != pinValue) {
        pinValue = newValue;
        pinChanged();
    }
}

น่าเสียดายที่สิ่งนี้ไม่ได้ทำงานอย่างถูกต้องเสมอไปสำหรับการเปลี่ยนแปลงสั้น ๆ ในอินพุต (เช่นพัลส์สั้น ๆ ) โดยเฉพาะอย่างยิ่งหากloop()ทำงานช้าเล็กน้อย

มีวิธีที่จะทำให้ Arduino ตรวจจับการเปลี่ยนแปลงอินพุตและเรียกใช้ฟังก์ชั่นของฉันโดยอัตโนมัติหรือไม่?


1
สิ่งที่คุณกำลังมองหามันเป็นการขัดจังหวะภายนอก
Butzke

คำตอบ:


26

คุณสามารถทำได้โดยใช้อินเตอร์รัปต์ภายนอก Arduinos ส่วนใหญ่รองรับสิ่งนี้ในจำนวน จำกัด เท่านั้น attachInterrupt()สำหรับรายละเอียดโปรดดูเอกสารบน

สมมติว่าคุณใช้ Uno คุณสามารถทำสิ่งนี้ได้:

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
    attachInterrupt(0, pinChanged, CHANGE);
}

void loop()
{
}

สิ่งนี้จะเรียกใช้pinChanged()เมื่อใดก็ตามที่ตรวจพบการเปลี่ยนแปลงในการขัดจังหวะภายนอก 0 บน Uno ที่สอดคล้องกับ GPIO พิน 2 การกำหนดหมายเลขการขัดจังหวะจากภายนอกนั้นแตกต่างจากบอร์ดอื่น ๆ ดังนั้นจึงเป็นเรื่องสำคัญที่จะต้องตรวจสอบเอกสารที่เกี่ยวข้อง

มีข้อ จำกัด สำหรับวิธีการนี้แม้ว่า pinChanged()กำลังใช้ฟังก์ชันที่กำหนดเองเป็น Interrupt Service Routine (ISR) นั่นหมายความว่าส่วนที่เหลือของรหัส (ทุกอย่างในloop()) จะหยุดชั่วคราวในขณะที่การโทรกำลังดำเนินการ เพื่อป้องกันการรบกวนเวลาสำคัญคุณควรตั้งเป้าหมายที่จะทำให้ ISR เร็วที่สุดเท่าที่จะทำได้

สิ่งสำคัญคือต้องทราบว่าจะไม่มีการขัดจังหวะอื่น ๆ ระหว่าง ISR ของคุณ นั่นหมายความว่าทุกสิ่งที่ต้องอาศัยการขัดจังหวะ (เช่นแกนกลางdelay()และmillis()ฟังก์ชั่น) อาจทำงานไม่ถูกต้อง

สุดท้ายหาก ISR ของคุณต้องการเปลี่ยนตัวแปรทั่วโลกในแบบร่างพวกเขามักจะประกาศเป็นvolatileเช่น:

volatile int someNumber;

นั่นเป็นสิ่งสำคัญเพราะมันบอกคอมไพเลอร์ว่าค่าสามารถเปลี่ยนแปลงได้โดยไม่คาดคิดดังนั้นจึงควรระมัดระวังไม่ให้ใช้สำเนา / แคชที่ล้าสมัยของมัน


เกี่ยวกับ "พัลส์สั้น ๆ " ที่กล่าวถึงในคำถามมีเวลาขั้นต่ำที่พินจะต้องอยู่ในสถานะที่จะทำให้เกิดการขัดจังหวะหรือไม่? (เห็นได้ชัดว่ามันจะน้อยกว่าการลงคะแนนเลือกตั้งซึ่งขึ้นอยู่กับสิ่งอื่นที่เกิดขึ้นในวง)
sachleen

1
@sachleen ที่จะทำงานตราบใดที่มันไม่ได้เกิดขึ้นระหว่างการดำเนินการของฟังก์ชั่น ISR (ตามที่อธิบายไว้ในคำตอบ); นั่นเป็นเหตุผลที่pinChanged()ควรสั้นที่สุด ดังนั้นโดยทั่วไปแล้วเวลาต่ำสุดควรเป็นเวลาในการเรียกใช้pinChanged()ฟังก์ชันเอง
jfpoilpret

2
+1 สำหรับคำตอบที่มีรายละเอียดมากซึ่งรวมถึงสิ่งที่สำคัญทั้งหมดที่ต้องใส่ใจเมื่อใช้อินเทอร์รัปต์
jfpoilpret

3
นอกจากการประกาศ globals ที่ใช้ร่วมกันvolatileแล้วหากตัวแปรโกลบอลมีความกว้างมากกว่า 1 ไบต์ในขณะที่บางหมายเลขคุณต้องป้องกันการขัดจังหวะการเปลี่ยนพินที่เกิดขึ้นระหว่างการเข้าถึงไบต์โดยโปรแกรม คำสั่งเช่นsomeNumber +=5;เกี่ยวข้องกับการเพิ่มไบต์ต่ำและเพิ่มไบต์สูงพร้อมดำเนินการรวม สองตัวนี้ (มากกว่า, สำหรับตัวแปรที่กว้างขึ้น) จะต้องไม่ถูกหารด้วยการขัดจังหวะ การปิดอินเทอร์รัปต์และเรียกคืนก่อนและหลังการใช้งาน (ตามลำดับ) ก็เพียงพอแล้ว
JRobert

@sachleen - เกี่ยวกับขนาดพัลส์ขั้นต่ำ มันยากที่จะหาคำตอบที่ชัดเจนในแผ่นข้อมูล แต่ตัดสินโดยเวลาสำหรับการขัดจังหวะการเปลี่ยนพินพวกเขาจะ latched ภายในครึ่งรอบนาฬิกา เมื่ออินเทอร์รัปต์ "จดจำ" แล้วจะยังคงจดจำได้จนกระทั่ง ISR เริ่มต้นและจัดการกับมัน
Nick Gammon

5

สถานะการเปลี่ยนแปลงของพินใด ๆ ที่กำหนดค่าเป็นอินพุตดิจิตอลสามารถสร้างการขัดจังหวะ ซึ่งแตกต่างจากเวกเตอร์ที่ไม่ซ้ำกันสำหรับสาเหตุการขัดจังหวะโดย INT1 หรือ INT2 คุณลักษณะ PinChangeInt ใช้เวกเตอร์ทั่วไปและจากนั้น Interrupt Service Routine (aka ISR) สำหรับเวกเตอร์นี้จะต้องกำหนดหมุดที่เปลี่ยนแปลง

โชคดีที่PinChangeInt Libraryทำให้ง่าย

PCintPort::attachInterrupt(PIN, burpcount,RISING); // attach a PinChange Interrupt to our pin on the rising edge
// (RISING, FALLING and CHANGE all work with this library)
// and execute the function burpcount when that pin changes

0

ในกรณีที่คุณต้องการตรวจจับแรงดันไฟฟ้าที่ผ่านเกณฑ์แทนที่จะเป็นแค่สูงหรือต่ำคุณสามารถใช้เครื่องเปรียบเทียบแบบอนาล็อก ตัวอย่างร่าง:

volatile boolean triggered;

ISR (ANALOG_COMP_vect)
  {
  triggered = true;
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ("Started.");
  ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
  ACSR =  bit (ACI)     // (Clear) Analog Comparator Interrupt Flag
        | bit (ACIE)    // Analog Comparator Interrupt Enable
        | bit (ACIS1);  // ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on falling edge)
   }  // end of setup

void loop ()
  {
  if (triggered)
    {
    Serial.println ("Triggered!"); 
    triggered = false;
    }

  }  // end of loop

สิ่งนี้มีประโยชน์สำหรับสิ่งต่าง ๆ เช่นเครื่องตรวจจับแสงซึ่งคุณอาจต้องตรวจจับการเปลี่ยนแปลงจาก (1V) ถึง 2V ในอินพุต

วงจรตัวอย่าง:

ป้อนคำอธิบายรูปภาพที่นี่

นอกจากนี้คุณยังสามารถใช้ Input Capture Unit บนโปรเซสเซอร์ซึ่งจะจดจำเวลาที่แน่นอนของอินพุตบางตัวโดยบันทึกจำนวนตัวนับปัจจุบันของ Timer / Counter 1 ซึ่งจะช่วยให้คุณจัดเก็บช่วงเวลาที่แน่นอน (ดีเกือบแน่นอน) ที่เหตุการณ์ มีความสนใจเกิดขึ้นแทนที่จะแนะนำการหน่วงเวลา (อาจใช้เวลาสักสองสามไมโครวินาที) ก่อนที่จะใช้ ISR เพื่อจับภาพเวลาปัจจุบัน

สำหรับแอปพลิเคชั่นที่มีความสำคัญต่อจังหวะเวลาสิ่งนี้สามารถเพิ่มความแม่นยำได้บ้าง

ตัวอย่างการใช้งาน: เปลี่ยน Arduino ของคุณเป็นเครื่องทดสอบตัวเก็บประจุ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.