การใช้งานโมดูลรีโมตตัวส่งและตัวรับแสงอินฟราเรดเป็นอุปกรณ์ควบคุมแบบไร้สาย#


การสื่อสารข้อมูลด้วยแสงอินฟราเรด#

บทความนี้นำเสนอการใช้งานอุปกรณ์ตัวรับและตัวส่งสัญญาณแสงอินฟราเรดและเขียนโปรแกรมด้วย Arduino สำหรับบอร์ดไมโครคอนโทรลเลอร์ เช่น Arduino Nano และ ESP32

การสื่อสารข้อมูลด้วยแสงอินฟราเรด (Infrared Data Communication: IrDA) ประกอบด้วยสองส่วนที่สำคัญคือ ตัวส่ง (IR Transmitter) และตัวรับแสงอินฟราเรด (IR Receiver) โดยทั่วไปแล้ว ตัวส่งแสงอินฟราเรดจะส่งข้อมูลออกไปทีละบิตโดยใช้คลื่นแสง แต่จะต้องมีการมอดูเลตสัญญาณ (เรียกว่า Pulse-Code Modulation หรือ PCM) ด้วยสัญญาณพัลส์หรือสัญญาณพาหะที่มีความถี่ เช่น 36kHz, 38kHz หรือ 40kHz (Carrier Frequency หรือ PCM Frequency) แล้วแปลงสัญญาณไฟฟ้าให้เป็นแสงอินฟราเรดโดยใช้ IR LED

รูปแบบการส่งข้อมูลด้วยแสงอินฟราเรดจะถูกกำหนดโดยโพรโตคอลของผู้ผลิตอุปกรณ์ เช่น NEC Protocol, Sony SIRC หรือ Philips RC5/RC6 Protocol เป็นต้น การส่งสัญญาณพัลส์จะมีช่วงเวลาจำกัด เช่น 600 usec สำหรับ NEC Protocol แล้วเว้นช่วงเวลาไว้ไม่มีการส่งสัญญาณ และขึ้นอยู่กับค่าของบิตที่ต้องการส่งในขณะนั้น เช่น 600 usec (บิตมีค่า 0) หรือ 900 usec (บิตมีค่า 1) ก่อนส่งบิตถัดไปตามลำดับ

สัญญาณแสงอินฟราเรดที่ไปถึงตัวรับ จะถูกแปลงให้เป็นสัญญาณไฟฟ้าด้วยวงจรที่มีโฟโต้ไดโอด (PIN-type Photodiode) วงจรขยายสัญญาณ (Amplifier) และถูกกรองตามความถี่ของสัญญาณพาหะด้วยวงจรตัวกรอง (Bandpass Filter) ก่อนจะถูกแปลงให้เป็นสัญญาณดิจิทัลตามลำดับ

ตัวอย่างของไอซีตัวรับแสงอินฟราเรดที่ใช้กับความถี่ประมาณ 38kHz รับแสงอินฟราเรดได้ดีในช่วงความยาวคลื่นประมาณ 940 - 950 นาโนเมตร และสามารถใช้แรงดันไฟเลี้ยงได้ในช่วง 2.7V ~ 5.5V ได้แก่

  • TSOP34438
  • TSOP33538
  • TSOP38238
  • VS1838B
  • TL1838

เอาต์พุตของตัวรับประเภทนี้ ถ้าไม่ได้รับแสงอินฟราเรดจากตัวส่ง จะได้ค่าลอจิกเป็น High (Logic 1) แต่ถ้าได้รับแสงอินฟราเรดความถี่ 38kHz จะได้เอาต์พุตเป็น Low (Logic 0) ดังนั้นเอาต์พุตจึงทำงานแบบ Active-Low

รูป: แผนผัง Block Diagram แสดงองค์ประกอบภายในที่สำคัญของ TSOP34438 (Source: Vishay)

รูป: ตัวอย่างการสร้างสัญญาณทดสอบและสัญญาณเอาต์พุตของวงจรที่ใช้ตัวรับ TSOP34438 (Source: Vishay)

ตัวส่งทำหน้าที่สร้างสัญญาณไฟฟ้าแบบ Pulse Burst เป็นรูปคลื่นสี่เหลี่ยมด้วยความถี่ 38kHz (มีค่า Duty Cycle 33% สำหรับ NEC Protocol) แล้วส่งเป็นสัญญาณอินฟราเรด

การส่งข้อมูลจะเริ่มด้วยสัญลักษณ์ที่เรียกว่า Mark และ Space ตามลำดับ สำหรับช่วงแรกหรือ เรียกว่า "ส่วนหัว" (Head) ซึ่งจะมีช่วงความกว้างตามที่โพรโตคอลสื่อสารกำหนดไว้ จากนั้นจะตามด้วย Mark และ Space สำหรับข้อมูลบิต และใช้ความกว้างเป็นตัวกำหนดว่า บิตใดมีค่าเป็น 0 หรือ 1

  • ช่วงแรกเรียกว่า Mark มีลักษณะเป็น Pulse Burst ระยะเวลาจำกัด และเมื่อตัวรับได้รับสัญญาณดังกล่าวในช่วงเวลานั้น จะได้เอาต์พุตเป็นลอจิก 0 (Low)
  • ช่วงถัดไปให้หยุดส่งคลื่นแสงอินฟราเรด ช่วงนี้เรียกว่า Space ไม่มีสัญญาณอินฟราเรด เอาต์พุตของตัวรับจะได้เป็นลอจิก 1 (High)

ช่วง Mark สำหรับบิตข้อมูล 0 และ 1 จะเท่ากัน แต่ช่วง Space ของบิต 1 จะกว้างกว่าของบิต 0

รูป: การส่งข้อมูลบิตด้วยแสงอินฟราเรดตามโพรโทคอลของ NEC (Source: NXP)

รูป: สัญญาณตัวส่งและสัญญาณที่ตัวรับ (Source: Adafruit)

 

รูปต่อไปนี้แสดงให้เห็นตัวอย่างรูปแบบของการส่งข้อมูลทั้งหมด 32 บิต โดยใช้โพรโตคอลของ NEC และจะเห็นได้ว่า ในรูปคลื่นสัญญาณดิจิทัลที่ตัวรับ (เป็นแบบกลับค่าลอจิกให้แล้ว) ช่วงแรกเป็น Mark และ Space สำหรับ Head มีความกว้าง 9 msec และ 4.5 msec (โดยประมาณ) ตามลำดับ ถัดไปจึงเป็นข้อมูลบิต

  • ช่วง Mark สำหรับข้อมูลบิต มีความกว้าง 562.5 μsec
  • ช่วง Space สำหรับบิต 0 มีความกว้าง 562.5 μsec และสำหรับบิต 1 มีความกว้างประมาณ 1687.5 μsec

ถ้าหากกดปุ่มของรีโมตอินฟราเรดค้างไว้ จะมีการส่งโค้ดซ้ำ (Repeat Code) และมีการเว้นระยะเวลา

รูป: ตัวอย่างสัญญาณดิจิทัลที่ตัวรับสำหรับข้อมูล 32 บิต และการส่งโค้ดซ้ำ

 


ตัวอย่างโมดูลแสงอินฟราเรดสำหรับการทดลอง#

โมดูล KY-022 มีตัวรับ VS1838B มีคอนเนกเตอร์แบบ 3 ขา เป็น Pin Header คือ GND, VCC(+) และ S (Signal) ตามลำดับ และสามารถใช้แรงดันไฟเลี้ยง VCC เท่ากับ +3.3V หรือ +5V ได้ บนแผงวงจรของโมดูล มีวงจร LED (SMD) และตัวต้านทาน 1kΩ ต่ออยู่ระหว่างขา VCC กับขา S (Signal)

รูป: ตัวอย่างอุปกรณ์ตัวรับประเภทไอซี VS1838B (38kHz, 940 nm) (ซ้าย) และโมดูล Keyes KY-022 Infrared Receiver (ขวา) ที่มี VS1838B เป็นตัวรับ และมีตัวต้านทาน Pullup เชื่อมต่อระหว่างขาสัญญาณและขา VCC ไว้ให้แล้ว

ลองมาดูตัวอย่างการสร้างสัญญาณดิจิทัลในรูปแบบที่เรียกว่า Pulse Burst เป็นสัญญาณพัลส์ความถี่ 38kHz ที่มีจำนวนไซเคิลจำนวนหนึ่ง (เช่น 10 ไซเคิล) และสร้างซ้ำใหม่ ๆ ทุก 20 msec สัญญาณนี้จะถูกนำไปใช้ในการขับวงจร IR LED เพื่อสร้างแสงอินฟราเรดที่ตัวส่ง และมีโมดูล KY-022 (VS1832B IR Reciever) เป็นตัวรับแสง

รูป: เครื่องสร้างสัญญาณ (Function Generator) และออสซิลโลสโคป (Oscilloscope) ที่ใช้ในการทดลองวัดสัญญาณ

รูป: โมดูลตัวส่งแสงอินฟราเรดที่ใช้สัญญาณแบบ Pulse Burst จาก Function Generator และโมดูลตัวรับแสง

สัญญาณที่วัดได้ด้วยเครื่องออสซิลโลสโคป มีลักษณะดังนี้ สัญญาณช่อง 1 เป็นสัญญาณ Pulse Burst จาก Function Generator และสัญญาณช่อง 2 เป็นสัญญาณเอาต์พุต S จากโมดูล KY-022

รูป: การวัดสัญญาณด้วยออสซิลโลสโคปสำหรับ Pulse Burst (10 Cycles)

รูป: การวัดสัญญาณด้วยออสซิลโลสโคปสำหรับ Pulse Burst (20 Cycles)

สัญญาณเอาต์พุต S จากโมดูล KY-022 มีระดับลอจิกเป็น High ในสถานะปรกติ และจะเปลี่ยนจาก High เป็น Low (ทำงานแบบ Active-Low) เมื่อได้รับแสงอินฟราเรด 38kHz แต่จะไม่เกิดขึ้นทันที มีการหน่วงเวลาประมาณ 200 usec (ไมโครวินาที)

 

หากลองต่อวงจรโดยใช้โฟโต้ทรานซิสเตอร์แสงอินฟราเรด (IR Phototransistor) เช่น การต่อวงจรแบบ Common-Emitter ร่วมกับตัวต้านทาน (4.7kΩ) เปรียบเทียบกับการใช้ไอซีตัวรับแสงอินฟราเรด เช่น VS1838B แล้ววัดดูสัญญาณเอาต์พุต จะเห็นได้ว่า สัญญาณเอาต์พุตที่ได้จาก VS1838B เป็นสัญญาณดิจิทัลที่เหมาะสำหรับนำไปใช้กับไมโครคอนโทรลเลอร์มากกว่า มีความไวต่อการรับแสงอินฟราเรด (มอดูเลตด้วยความถี่ 38kHz) ได้ดีกว่า ระยะห่างระหว่างตัวรับและตัวส่งก็มีผลต่อสัญญาณเอาต์พุตของวงจรที่ใช้โฟโต้ทรานซิสเตอร์แสงอินฟราเรดด้วยเช่นกัน

รูป: โฟโต้ทรานซิสเตอร์แสงอินฟราเรด และตัวอย่างการต่อวงจร

รูป: การทดลองต่อวงจรรับแสงอินฟราเรดจากโมดูลตัวส่ง โดยใช้โฟโต้ทรานซิสเตอร์

รูป: เปรียบเทียบสัญญาณ (ช่อง 1) จากวงจรโฟโต้ทรานซิสเตอร์แสงอินฟราเรด และสัญญาณ (ช่อง 2) จากตัวรับอินฟราเรด VS1838B เมื่อได้รับแสงอินฟราเรดจากอุปกรณ์รีโมตคอนโทรล

 

ตัวอย่างอุปกรณ์ที่มีการทำงานโดยใช้แสงอินฟราเรด และมีราคาไม่แพง มีดังนี้

รูป: ตัวอย่างอุปกรณ์ตัวรับและตัวส่ง

รูป: ตัวอย่างอุปกรณ์รีโมตอินฟราเรดที่มีปุ่มกด (NEC Protocol, CR2025/160mAH coin-cell battery)

ถ้าต้องการต่อวงจรตัวส่งแสงอินฟราเรด ก็อาจจะใช้ IR LED ร่วมกับตัวต้านทานจำกัดกระแส เช่น TSAL6200 / TSAL6400 High Power Infrared Emitting Diode (940 nm, Vf=1.35V, If=100mA max.) แต่จะต้องมีการมอดูเลตสัญญาณด้วยความถี่ 38kHz ด้วย

อย่างไรก็ตาม ถ้านำไปใช้กับขา GPIO ของไมโครคอนโทรลเลอร์ โดยทั่วไปแล้ว ขา GPIO จะมีข้อจำกัดในการจ่ายหรือรับกระแส เช่น ในระดับ 10mA เป็นต้น (ขึ้นอยู่กับชิปไมโครคอนโทรลเลอร์ที่นำมาใช้งาน)

ถ้าให้ VCC=5V และมีตัวต้านทานนำมาต่ออนุกรม 220 Ω ก็สามารถคำนวณกระแสที่ใช้โดยประมาณ ได้ตามสูตรคำนวณดังนี้

แต่หากต้องการความสะดวกในการทดลอง ก็สามารถเลือกใช้โมดูลตัวส่งสัญญาณอินฟราเรด หรือ อุปกรณ์รีโมตอินฟราเรดที่มีปุ่มกดหลายปุ่มได้เช่นกัน โมดูลตัวส่งบางรุ่น มีวงจรทรานซิสเตอร์ NPN (เช่น 2N2222) ไว้สำหรับควบคุมการจ่ายกระแสให้ IR LED ซึ่งทำให้สามารถจ่ายกระแสได้สูงขึ้น แต่ส่วนใหญ่ก็จะใช้แค่ตัวต้านทานจำกัดกระแส เช่น 220 โอห์ม สำหรับนำไปใช้กับขา GPIO ของไมโครคอนโทรลเลอร์ และมีระยะในการรับส่งสัญญาณอินฟราเรด ก็จะได้ในระยะใกล้ ๆ เท่านั้น

ไฟล์เอกสารของผู้ผลิต / Datasheet Files (PDF)


ตัวอย่างโค้ด Arduino#

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

เมื่อเกิดเหตุการณ์อินเทอร์รัพท์ในแต่ละครั้ง จะมีการอ่านเวลาของระบบและคำนวณระยะเวลา หรือ ผลต่างระหว่างสองเหตุการณ์ที่เกิดขึ้นตามลำดับ บันทึกเป็นค่าตัวเลข (หน่วยเป็น ไมโครวินาที) ไว้ในอาร์เรย์ pulse_widths ค่าตัวเลขนี้จะนำไปใช้ในการตรวจสอบและแปลงเป็นข้อมูลบิต ตามโพรโทคอลของ NEC และจะได้รหัสตัวเลข 32 บิต ฟังก์ชัน ir_decode() จะใช้สำหรับการตรวจสอบดูว่า ได้รับสัญญาณจากการกดปุ่มของอุปกรณ์รีโมตอินฟราเรดหรือไม่ และฟังก์ชันนี้จะต้องถูกเรียกซ้ำไปเรื่อย ๆ

#if defined(__AVR__)
#define IR_PIN    (2) // use Arduino D2 pin
#else if defined(ESP32)
#define IR_PIN    (18) // use GPIO-18 pin
#endif

#define BUF_SIZE          (128)
#define HEADER_MARK_MIN   (8000)
#define TIMEOUT_MS        (70)
#define HEADER_MARK_MAX   (HEADER_MARK_MIN+2000)  
#define HEADER_SPACE_MIN  (4000)
#define HEADER_SPACE_MAX  (HEADER_SPACE_MIN+1000)  
#define REPEAT_SPACE_MIN  (2000)
#define REPEAT_SPACE_MAX  (REPEAT_SPACE_MIN+1000)
#define BIT_MARK_MIN      (500)
#define BIT_MARK_MAX      (BIT_MARK_MIN+200)
#define INVALID_CODE      (0x00000000)
#define REPEATED_CODE     (0xffffffff)

#define DEBUG

#define BUF_SIZE  (128)
volatile uint32_t pulse_widths_count;
volatile uint16_t pulse_widths[ BUF_SIZE ];
volatile uint32_t ts_saved;

#if defined(ESP32)
IRAM_ATTR void ir_recv_isr();
#endif

void setup() {
  Serial.begin( 115200 );
  Serial.println( F("Arduino Infrared Sender - Receiver") );
  pinMode( IR_PIN, INPUT );
}

void loop() {
  uint32_t ir_code = ir_decode();
  if ( ir_code != INVALID_CODE ) {
    if ( ir_code == REPEATED_CODE ) {
      Serial.println( "Repeated Code" );
    } else {
      Serial.print( "Code: " );
      Serial.println( ir_code, HEX );
    }
  }
  delay(5);
}

void ir_recv_isr() { // ISR
  uint32_t ts_now = micros();
  if ( pulse_widths_count > 0 ) {
    pulse_widths[ pulse_widths_count-1 ] = (ts_now - ts_saved);
  }
  ts_saved = ts_now;
  if ( pulse_widths_count < BUF_SIZE ) {
    pulse_widths_count++;
  }
}

typedef enum { S0=0, S1, S2, S3 } state_t;

uint32_t ir_decode() {
  static state_t state = S0;
  static uint32_t ts_start = 0;
  uint32_t ret_code = INVALID_CODE; 

  switch (state) {
    case S0:
      pulse_widths_count = 0;
      // Enable the external interrupt on the IR pin.
      attachInterrupt( digitalPinToInterrupt(IR_PIN), 
                        ir_recv_isr, CHANGE );
      state = S1;
      break;

    case S1:
      if ( pulse_widths_count > 2 ) {
        ts_start = millis();
        state = S2;
      }
      break;

    case S2:
      if ( millis() - ts_start >= TIMEOUT_MS ) {
        // Disable the external interrupt on the IR pin.
        detachInterrupt( digitalPinToInterrupt(IR_PIN) );
        uint32_t n = pulse_widths_count-2;
        volatile uint16_t *p = pulse_widths;
        if ( HEADER_MARK_MIN < p[0] && p[0] < HEADER_MARK_MAX ) {
          if ( HEADER_SPACE_MIN < p[1] && p[1] < HEADER_SPACE_MAX ) {
            // message header found
            uint32_t code = 0;
            for ( uint32_t i=2; i < n; i+=2 ) {
#ifdef DEBUG                   
              Serial.print( p[i] );
              Serial.print( ' ' );
              Serial.println( p[i+1] );
#endif
              if ( BIT_MARK_MIN < p[i] && p[i] < BIT_MARK_MAX ) {
                code = (code << 1) | (p[i+1] > BIT_MARK_MAX);
              } else { // bit timing error
                code = INVALID_CODE;
                break;
              }
            } // end-for
            if ( code != INVALID_CODE ) {
              ret_code = code; 
            }
          }
          else if ( REPEAT_SPACE_MIN < p[1] && p[1] < REPEAT_SPACE_MAX ) {
            // repeated code found
            ret_code = REPEATED_CODE;
          }
        }
        state = S0;
       }
       break;

    default:
       state = S0;
  }
  return ret_code;
}

รูป: การทดลองตัวรับและตัวส่งสัญญาณแสงอินฟราเรดร่วมกับบอร์ด ESP32 และ Arduino Nano

รูป: ตัวอย่างข้อความเอาต์พุตที่ได้รับใน Arduino Serial Monitor

 

นอกจากการทดลองโดยใช้อุปกรณ์ฮาร์ดแวร์จริงแล้ว การทดสอบและสาธิตการทำงานของโค้ด ยังสามารถทำได้โดยใช้วิธีการจำลองการทำงาน เช่น การใช้ Wokwi Simulator บนหน้าเว็บเบราว์เซอร์

รูป: ตัวอย่างการจำลองการทำงานด้วย WokWi Simulator โดยเลือกใข้บอร์ด ESP32 DevKit ร่วมกับโมดูลตัวส่ง IR Remote และโมดูลตัวรับ IR Receiver

รูป: ตัวอย่างการจำลองการทำงานด้วย WokWi Simulator และบันทึกสัญญาณดิจิทัลลงในไฟล์ .vcd ด้วย Logic Analyzer เสมือนจริง

รูป: การแสดงสัญญาณดิจิทัลจากไฟล์ .vcd ด้วยโปรแกรม GTKWave

 

ถัดไปเป็นตัวอย่างการเขียนโค้ดและใช้ไลบรารีที่มีชื่อว่า "IRRemote" โดยจะต้องมีการติดตั้งไลบรารี ก่อนใช้งาน โดยใช้ Arduino IDE - Library Manager

รูป: การติดตั้งไลบรารี "IRremote"

#include <IRremote.hpp> 
// see: https://github.com/Arduino-IRremote/Arduino-IRremote

#if defined(__AVR__)
#define IR_PIN    (2) // use Arduino D2 pin
#else if defined(ESP32)
#define IR_PIN    (18) // use GPIO-18 pin
#endif

void setup(){
  Serial.begin( 115200 );
  // Set IR receiver pin
  pinMode( IR_PIN, INPUT );
  // Start IR receiver
  IrReceiver.begin( IR_PIN, false );
}

void loop(){
  static char sbuf[32];
  if ( IrReceiver.decode() ){
    // IrReceiver.printIRResultMinimal( &Serial );
    uint32_t code = IrReceiver.decodedIRData.decodedRawData;
    Serial.print( code, HEX );
    Serial.print( " " );
    Serial.println( reverse_bits(code), HEX );
    IrReceiver.resume(); // Receive next code
  }
  delay(5);
}

uint32_t reverse_bits( uint32_t value ) {
  uint32_t result = 0;
  for ( int i=0; i < 32; i++ ) {
    result = (result << 1) | ((value >> i) & 1);
  }
  return result;
}

รูป: การจำลองการทำงานด้วย WokWi Simulator (ใช้บอร์ด ESP32)

รูป: การจำลองการทำงานด้วย WokWi Simulator (ใช้บอร์ด Arduino Nano)

 


การใช้วงจร RMT ของ ESP32 สำหรับการรับสัญญาณจากรีโมตอินฟราเรด#

ชิป ESP32 มีวงจรภายในที่เรียกว่า RMT (Remote Control Transceiver) เหมาะสำหรับการส่งหรือรับชุดข้อมูลบิตด้วยแสงอินฟราเรด ตัวอย่างนี้สาธิตการใช้ฟังก์ชันที่ได้มีการประกาศไว้ในไฟล์ esp32-hal-rmt.h เช่น rmtInit(), rmtSetTick(), rmtRead() เป็นต้น เพื่อเปิดใช้งาน RMT Channel และอินเทอร์รัพท์ที่เกี่ยวข้องสำหรับการรับข้อมูลจากโมดูลอินฟราเรด

วงจร RMT จะทำหน้าที่ตรวจสอบดูว่า มีสัญญาณพัลส์เข้ามาหรือไม่ และให้วัดความกว้างของพัลส์ช่วงที่เป็น Low และ High ตามลำดับ แล้วบันทึกลงในโครงสร้างข้อมูล (rmt_data_t)

#if !defined(ESP32)
#error "This Arduino sketch is targeted at ESP32."
#endif

#include "esp32-hal-rmt.h"
// https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-rmt.h

// Set the GPIO pin for IR receiver 
#define IR_PIN            (GPIO_NUM_18)

#define BUF_SIZE          (128)
#define HEADER_MARK_MIN   (8000)
#define TIMEOUT_MS        (70)
#define HEADER_MARK_MAX   (HEADER_MARK_MIN+2000)  
#define HEADER_SPACE_MIN  (4000)
#define HEADER_SPACE_MAX  (HEADER_SPACE_MIN+1000)  
#define REPEAT_SPACE_MIN  (2000)
#define REPEAT_SPACE_MAX  (REPEAT_SPACE_MIN+1000)
#define BIT_MARK_MIN      (500)
#define BIT_MARK_MAX      (BIT_MARK_MIN+200)
#define INVALID_CODE      (0x00000000)
#define REPEATED_CODE     (0xffffffff)

rmt_obj_t* rmt_recv = NULL;
uint32_t rmt_tick;

#define BUF_SIZE  (128)
volatile uint32_t pulse_widths_count;
volatile uint16_t pulse_widths[ BUF_SIZE ];

IRAM_ATTR
void rmt_recv_data_callback( uint32_t *rmt_data, size_t len, void *arg ) {
  rmt_data_t* data = (rmt_data_t*)rmt_data;
  pulse_widths_count = 2*len;
  if ( 0 < len && pulse_widths_count < BUF_SIZE ) {
    for ( int i=0; i < len; i++ ) {
      pulse_widths[2*i]   = data[i].duration0; // low pulse width
      pulse_widths[2*i+1] = data[i].duration1; // high pulse width
     }
  }
  else { pulse_widths_count = 0; }
}

void setup() {
  Serial.begin( 115200 );
  delay( 1000 );
  Serial.println( "\n\nESP32 - RMT IR Receiver Demo..." );
  Serial.flush();

  // Initialize the RMT channel to capture up to 192 items.
  rmt_recv = rmtInit( IR_PIN, RMT_RX_MODE, RMT_MEM_192 );
  if ( rmt_recv != NULL ) {
    Serial.println( "Initialized RMT Receiver..." );
  }
  // Set RMT tick period (in usec): 1 usec.
  rmt_tick = rmtSetTick(rmt_recv, 1000) / 1000;
  Serial.printf( "RMT tick = %lu usec\n", rmt_tick );
  // Set RMT receive filter.
  rmtSetFilter( rmt_recv, true, 255 );
  // Set RMT receiver threshold.
  rmtSetRxThreshold( rmt_recv, 10000 / rmt_tick );
  // Set RMT receiver callback.
  rmtRead( rmt_recv, rmt_recv_data_callback, NULL );
}

void loop() {
  uint32_t ir_code = ir_decode();
  if ( ir_code != INVALID_CODE ) {
    if ( ir_code == REPEATED_CODE ) {
      Serial.println( "Repeated Code" );
    } else {
      Serial.print( "Code: " );
      Serial.println( ir_code, HEX );
    }
  }
  delay(5);
}

uint32_t ir_decode() {
  uint32_t ret_code = INVALID_CODE; 
  int32_t n = pulse_widths_count-2; // 'n' must be an even number.
  volatile uint16_t *p = pulse_widths;

  if ( n <= 0 ) return ret_code;
  if ( HEADER_MARK_MIN < p[0] && p[0] < HEADER_MARK_MAX ) {
    if ( HEADER_SPACE_MIN < p[1] && p[1] < HEADER_SPACE_MAX ) { 
      // message header found
      uint32_t code = 0;
      for ( uint32_t i=2; i < n; i+=2 ) {
        if ( BIT_MARK_MIN < p[i] && p[i] < BIT_MARK_MAX ) {
          code = (code << 1) | (p[i+1] > BIT_MARK_MAX);
        } else { // bit timing error
          code = INVALID_CODE;
          break;
        }
      } // end-for
      if ( code != INVALID_CODE ) {
        ret_code = code; 
      }
    }
    else if ( REPEAT_SPACE_MIN < p[1] && p[1] < REPEAT_SPACE_MAX ) {
      // repeated code found
      ret_code = REPEATED_CODE;
    }
  }
  pulse_widths_count = 0;
  return ret_code;
}

 


แนวทางและกิจกรรมเรียนรู้เพิ่มเติม#

ตัวอย่างโจทย์และกิจกรรมฝึกปฏิบัติ โดยใช้อุปกรณ์อิเล็กทรอนิกส์สำหรับการสื่อสารข้อมูลด้วยแสงอินฟราเรด ได้แก่

  • การวัดสัญญาณเอาต์พุตจากตัวรับ ด้วยเครื่องมือวัด เช่น ออสซิลโลสโคป (Oscilloscope) หรือเครื่องวิเคราะห์สัญญาณดิจิทัล (Logic Analyzer) และการบันทึกสัญญาณเพื่อวิเคราะห์ข้อมูลบิตที่ได้รับ แล้วลองวัดความกว้างพัลส์ของข้อมูลแต่บิตที่ได้รับ และแปลงเป็นค่าบิตของข้อมูล การตั้งค่าสำหรับ Trigger Type (เช่น Edge Type หรือ Pulse Type เป็นต้น) และ Trigger Mode สำหรับการทำงานของเครื่องออสซิลโลสโคปแบบดิจิทัล เพื่อคอยตรวจจับการเริ่มต้นของการรับสัญญาณ-บิตข้อมูล เป็นต้น
  • การสร้างสัญญาณดิจิทัลเป็นอินพุตสำหรับตัวส่งสัญญาณแสงอินฟราเรด เช่น การสร้างสัญญาณด้วยเครื่องกำเนิดสัญญาณ (Function Generator) แบบดิจิทัล เช่น การสร้างสัญญาณแบบพัลส์เป็นชุด การทดลองเปลี่ยนความถี่ (เช่น ในช่วง 38kHz +/- 10kHz) ค่า Duty Cycle และจำนวนพัลส์ในการส่งข้อมูลในหนึ่งชุด และดูการเปลี่ยนแปลงที่สัญญาณเอาต์พุตที่โมดูลตัวรับแสงอินฟราเรด
  • การทดลองเพื่อศึกษาดูว่า ระยะทางและทิศทางจากตัวส่งไปยังตัวรับแสงอินฟราเรด มีผลอย่างไรบ้าง ?
  • การสร้างอุปกรณ์ตัวส่งสัญญาณอินฟราเรดเป็นรีโมตคอนโทรล โดยใช้ไมโครคอนโทรลเลอร์ แผงปุ่มกด (Keypad) วงจร Infrared LED และใช้แบตเตอรี่เป็นแหล่งพลังงานไฟฟ้า
  • การเปรียบเทียบคุณสมบัติของเซนเซอร์แสงประเภทอื่น เพื่อดูว่า จะให้ผลต่างจากการใช้ไอซีหรือโมดูลตัวรับแสงอินฟราเรดโดยเฉพาะอย่างไร เช่น การเลือกใช้ LDR (Light-Dependent Resistor) และเซนเซอร์แสงอย่างเช่น TEMT6000 (Ambient Light Sensor) เป็นตัวรับสัญญาณแสงอินฟราเรด
  • การเขียนโปรแกรมไมโครคอนโทรลเลอร์ เช่น Arduino สำหรับการรับสัญญาณและแปลงเป็นข้อมูลบิตหรือรหัสตัวเลขที่ส่งมาจากตัวส่ง หากไม่มีอุปกรณ์จริง ก็สามารถจำลองการทำงานของระบบได้เช่นกัน
  • การรับสัญญาณจากรีโมตคอนโทรลด้วยโมดูลรับแสงและประมวลผลด้วยบอร์ด ESP32 แล้วแปลงเป็นโค้ดตัวเลข และส่งข้อมูลเข้าไปยัง MQTT Broker ด้วย Wi-Fi สำหรับนำไปใช้กับระบบ Smart Home ที่ใช้ซอฟต์แวร์ Open Source เป็นต้น

 


กล่าวสรุป#

บทความนี้ได้นำเสนอการใช้งานโมดูลตัวรับและตัวส่งสัญญาณอินฟราเรด การวัดสัญญาณไฟฟ้าที่ได้จากการทดลองใช้งานอุปกรณ์ และตัวอย่างการเขียนโค้ด Arduino Sketch สำหรับการทดลองใช้งานกับบอร์ดไมโครคอนโทรลเลอร์ ESP32 และ Arduino Nano หรือจำลองการทำงานด้วย WokWi Simulator บนหน้าเว็บเบราว์เซอร์

 


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2022-10-20 | Last Updated: 2022-11-07