การเขียนโค้ด Arduino สำหรับ LED Blink ด้วยวิธีที่แตกต่างกัน#

บทความนี้นำเสนอตัวอย่างการเขียนโค้ด Arduino Sketch เพื่อทำให้ LED บนบอร์ดไมโครคอนโทรลเลอร์ Arduino Uno / Nano กระพริบด้วยอัตราคงที่ โดยใช้วิธีที่แตกต่างกันมากกว่าหนึ่งวิธี โค้ดตัวอย่างในบทความนี้สามารถนำไปทดลองได้กับ WokWi Simulator เพื่อจำลองการทำงานเสมือนจริง และ Arduino IDE เพื่อนำไปอัปโหลดไปยังบอร์ดไมโครคอนโทรลเลอร์

Keywords: Arduino Nano / Uno, Arduino Sketch, LED Blink, WokWi Simulator


ตัวอย่างที่ 1#

โค้ดตัวอย่างแรกเริ่มต้นด้วยการสร้างฟังก์ชัน setup() ที่มีการกำหนดให้ขา LED_PIN ซึ่งตรงกับ LED_BUILTIN เป็นเอาต์พุตแบบดิจิทัล และเขียนค่าเอาต์พุตให้เป็น LOW โดยใช้คำสั่ง digitalWrite( LED_PIN, LOW ); ถัดไปเป็นฟังก์ชัน loop() ซึ่งจะถูกเรียกให้ทำงานซ้ำโดยอัตโนมัติ ภายในมีคำสั่งที่เรียกฟังก์ชัน led_blink1( LED_PIN );

ในส่วนของการสร้างฟังก์ชัน void led_blink1( int pin ) { ... } มีการทำคำสั่ง digitalWrite(...); เพื่อกำหนดสถานะลอจิก HIGH หรือ LOW ของขาที่ระบุโดยตัวแปรหรืออาร์กิวเมนต์ชื่อ pin และคำสั่ง delay( DELAY_MS ); เพื่อการหน่วงเวลาไว้เท่ากับค่าตัวเลขของ DELAY_MS ก่อนทำคำสั่งต่อไปตามลำดับ

#define LED_PIN   (LED_BUILTIN) // the default LED pin
#define DELAY_MS  (500)

void setup() {
  // Set the LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink1( LED_PIN );
}

void led_blink1( int pin ) {
  // Output HIGH to the LED pin
  digitalWrite( pin, HIGH );
  // Delay for a few hundred milliseconds
  delay( DELAY_MS );
  // Output LOW to the LED pin
  digitalWrite( pin, LOW );
  // Delay for a few hundred milliseconds
  delay( DELAY_MS );
}

รูป: ตัวอย่างการจำลองการทำงานของโค้ดโดยใช้ WokWi Simulator

 


ตัวอย่างที่ 2#

ในโค้ดตัวอย่างนี้ มีการสร้างฟังก์ชันและเรียกใช้ led_blink2(...) มาแทนที่และเปรียบเทียบกับฟังก์ชัน led_blink1(...) ในตัวอย่างแรก แต่มีรูปแบบการเขียนโค้ดที่แตกต่างกัน ซึ่งมีการประกาศและใช้งานตัวแปรภายในฟังก์ชันและเป็นแบบ static ที่มีชื่อว่า state เพื่อใช้กำหนดสถานะลอจิกของขาเอาต์พุต LED_PIN

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

คำสั่ง digitalWrite( pin, state ^= 1 ); จะทำให้ตัวแปร state เปลี่ยนค่าลอจิก (สลับค่าลอจิก ระหว่าง 0 กับ 1) แล้วนำค่าใหม่ของตัวแปรนี้ ไปใช้ในการเขียนค่าเอาต์พุตของขา pin ดังนั้นจะทำให้ LED เปลี่ยนสถานะลอจิก

#define LED_PIN   (LED_BUILTIN) // the default LED pin
#define DELAY_MS  (500)

void setup() {
  // Set the LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink2( LED_PIN );
}

void led_blink2( int pin ) {
  // Use a static local variable to keep the LED state 
  static uint8_t state = 0;
  // Toggle the LED pin
  digitalWrite( pin, state ^= 1 );
  // Delay for a few hundred milliseconds
  delay( DELAY_MS );
}

 


ตัวอย่างที่ 3#

ในโค้ดตัวอย่างนี้ มีการสร้างและเรียกใช้ฟังก์ชัน led_blink3(...) ที่มีความแตกต่างจากฟังก์ชันในตัวอย่างที่แล้ว

คำสั่ง digitalWrite( pin, !digitalRead(pin) ); เป็นการอ่านค่าของ pin ในขณะนั้น แล้วนำค่ามาสลับลอจิก (Bit Inverse) แล้วนำไปใช้ในการเขียนค่าเอาต์พุตของขา pin ดังนั้นจะทำให้ LED เปลี่ยนสถานะลอจิก

#define LED_PIN   (LED_BUILTIN) // the default LED pin
#define DELAY_MS  (500)

void setup() {
  // Set the LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink3( LED_PIN );
}

void led_blink3( int pin ) {
  // Toggle the LED pin
  digitalWrite( pin, !digitalRead(pin) );
  // Delay for a few hundred milliseconds
  delay( DELAY_MS );
}

 


ตัวอย่างที่ 4#

ในโค้ดตัวอย่างนี้ มีการสร้างและเรียกใช้ฟังก์ชัน led_blink4(...) ที่มีความแตกต่างจากฟังก์ชันก่อนหน้านี้ โดยใช้วิธีการอ่านเวลาของระบบในขณะนั้น ด้วยคำสั่ง millis() ซึ่งจะได้ค่าเป็นเลขจำนวนเต็มที่มีชนิดข้อมูลเป็น uint32_t และมีหน่วยเป็นมิลลิวินาที เก็บไว้ในตัวแปร now แล้วนำมาเปรียบเทียบกับค่าที่ได้เก็บบันทึกไว้ในอีกตัวแปรหนึ่งคือ last_update_time ถ้าคำนวณผลต่างได้มากกว่าหรือเท่ากับ DELAY_MS ก็ให้สลับสถานะลอจิกของเอาต์พุต pin โดยเรียกฟังก์ชัน led_toggle(...) แล้วอัปเดตค่าของตัวแปร last_update_time ให้เท่ากับค่าของตัวแปร now

#define LED_PIN   (LED_BUILTIN) // the default LED pin
#define DELAY_MS  (500)

void setup() {
  // Set the LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink4( LED_PIN );
}

void led_toggle( int pin=LED_PIN ) {
  // Toggle the specified pin
  digitalWrite( pin, !digitalRead(pin) );
}

void led_blink4( int pin ) {
  static uint32_t last_update_time = 0;
  uint32_t now = millis();
  if ( now - last_update_time >= DELAY_MS ) {
    // Save the last update time
    last_update_time = now;
    // Toggle the LED pin
    led_toggle( pin );
  }
}

 


ตัวอย่างที่ 5#

โค้ดตัวอย่างนี้สาธิตการเปิดใช้งานวงจร Timer1 ของชิปไมโครคอนโทรลเลอร์ ATmega328P มีการสร้างและเรียกใช้ฟังก์ชัน void init_timer1() เพื่อตั้งค่าการทำงานของวงจร Timer1 (มีขนาดของตัวนับเท่ากับ 16 บิต) เช่น การตั้งค่าตัวหารความถี่ เพื่อทำให้วงจรนับทำงานด้วยมีความถี่ลดลงจาก 16MHz ให้เท่ากับ 2Hz หรือ มีคาบเท่ากับ 500 มิลลิวินาที

นอกจากนั้นแล้ว ยังมีการเปิดใช้งานอินเทอร์รัพท์ที่เกี่ยวข้องกับ Timer1 เมื่อนับครบหนึ่งรอบ โดยนับจาก 0 ถึง (31250-1) แล้วเริ่มต้นนับใหม่ ฟังก์ชันที่เกี่ยวข้องกับอินเทอร์รัพท์ (หรือ ISR) ของตัวนับนี้คือ ISR(TIMER1_COMPA_vect) ซึ่งจะทำหน้าที่เพิ่มค่าของตัวแปร counter ขึ้นครั้งละหนึ่ง

ในฟังก์ชัน led_blink5(...) จะมีการเรียกฟังก์ชัน init_timer1() เพียงครั้งเดียว และในการทำงานของฟังก์ชันเมื่อถูกเรียก จะมีการตรวจสอบดูว่า ค่าของตัวแปร counter มีการเปลี่ยนแปลงหรือไม่ โดยเปรียบเทียบกับค่าของตัวแปร last_counter ที่บันทึกค่าเพื่อการเปรียบเทียบ ถ้ามีการเปลี่ยนแปลง (ค่าของตัวแปร last_counter ไม่เท่ากับ counter) ก็ให้สลับสถานะลอจิกของเอาต์พุต pin แล้วอัปเดตค่าของตัวแปร last_counter ให้เท่ากับค่า counter

#define LED_PIN   (LED_BUILTIN) // the default LED pin

void setup() {
  // Set LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink5( LED_PIN );
}

void led_toggle( int pin=LED_PIN ) {
  // Toggle the specified pin
  digitalWrite( pin, !digitalRead(pin) );
}

// This global variable is used by ISR.
volatile uint32_t counter = 0;

void led_blink5( int pin ) {
  static uint8_t timer1_initialized = 0;
  static uint32_t last_counter = 0;
  if ( timer1_initialized == 0 ) { // For the first function call
    // Mark timer1 as initialized
    timer1_initialized = 1;
    // Initialize Timer1
    init_timer1();
  }
  else if ( last_counter != counter ) {
    // Save the last counter value
    last_counter = counter;
    // Toggle the LED pin
    led_toggle( pin );
  }
}

void init_timer1() {
  // Set Timer1 to CTC (Clear Timer on Compare) mode
  TCCR1A = 0;
  TCCR1B = (1 << WGM12);
  // Set the timer compare value to generate an interrupt every 500 msec
  OCR1A  = (31250-1); // (16MHz /256 /2) - 1
  // Enable Timer1 compare interrupt
  TIMSK1 = (1 << OCIE1A);  
  // Set Timer1 prescaler to 256 and start the timer
  TCCR1B |= (1 << CS12);
  // Enable interrupts
  sei();
}

ISR(TIMER1_COMPA_vect) {
  counter++;
}

 


ตัวอย่างที่ 6#

โค้ดตัวอย่างนี้สาธิตการใช้ไลบรารีสำหรับ Arduino ที่มีชื่อว่า TimerOne โดยจะต้องตั้งค่าสำหรับช่วงเวลาในการทำงาน และฟังก์ชัน Callback ที่จะถูกเรียกให้ทำงานเมื่อเกิดเหตุการณ์ดังกล่าว

  • Timer1.initialize( 500000 ); เป็นการตั้งค่าเวลา (ช่วงเวลาหรือคาบเวลา) ให้ทำงานทุก ๆ 500000 ไมโครวินาที หรือ 500 มิลลิวินาที
  • Timer1.attachInterrupt( led_toggle ); เป็นการระบุว่า ให้ใช้ฟังก์ชัน led_toggle เป็นฟังก์ชันสำหรับ Callback ที่จะทำงานตามคาบเวลาที่กำหนดไว้
#include <TimerOne.h> // if the TimerOne library is used.

#define LED_PIN   (LED_BUILTIN) // the default LED pin

void setup() {
  // Set LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink6( LED_PIN );
}

void led_toggle() {
  // Toggle the specified pin
  digitalWrite( LED_PIN, !digitalRead(LED_PIN) );
}

void led_blink6( int pin ) {
  static uint8_t timer1_initialized = 0;
  if ( timer1_initialized == 0 ) { // For the first function call
    // Mark timer1 as initialized
    timer1_initialized = 1;
    // Set period in microseconds
    Timer1.initialize( 500000 ); 
    // Attach a callback function
    Timer1.attachInterrupt( led_toggle );
  }
}

 


ตัวอย่างที่ 7#

โค้ดตัวอย่างนี้สาธิตการเปิดใช้งานวงจร WDT (Watchdog Timer) ของ ATmega328P ซึ่งเป็นวงจรตัวนับ และมีการสร้างและเรียกใช้ฟังก์ชัน init_wdt(...) เพื่อตั้งค่าให้นับแล้วทำให้เกิดอินเทอร์รัพท์ ทุก 500 มิลลิวินาที (โดยประมาณ) และมีฟังก์ชัน ISR(WDT_vect) ที่จะทำงานโดยอัตโนมัติ เมื่อเกิดอินเทอร์รัพท์ในแต่ละครั้ง ซึ่งจะเพิ่มค่าของตัวแปร counter ครั้งละหนึ่ง

ในฟังก์ชัน led_blink7(...) จะมีการเปรียบเทียบระหว่างค่าของตัวแปร last_counter และ counter ถ้ามีค่าไม่เท่ากัน ให้สลับค่าลอจิกของขาเอาต์พุต pin แล้วอัปเดตค่าของตัวแปร last_counter ให้เท่ากับค่า counter

#include <avr/wdt.h>  // if WDT is used.

#define LED_PIN   (LED_BUILTIN) // the default LED pin

// This global variable is used by ISR.
volatile uint32_t counter = 0;

void setup() {
  // Set LED pin direction to output
  pinMode( LED_PIN, OUTPUT );
  // Output LOW to the LED pin
  digitalWrite( LED_PIN, LOW );
}

void loop() {
  led_blink7( LED_PIN );
}

void led_toggle( int pin=LED_PIN ) {
  // Toggle the specified pin
  digitalWrite( pin, !digitalRead(pin) );
}

void led_blink7( int pin ) {
  static uint8_t wdt_initialized = 0;
  static uint32_t last_counter = 0;
  if ( wdt_initialized == 0 ) {
    // Mark WDT as initialized
    wdt_initialized = 1;
    // Initialize WDT
    init_wdt();
  }
  else if ( last_counter != counter ) {
    // Save the last counter value
    last_counter = counter;
    // Toggle the LED pin
    led_toggle( pin );
  }
}

void init_wdt() {
  // Make sure the WDT is disabled before changing the settings
  wdt_reset(); 
  // Set up WDT with 500ms timeout (approximately).
  wdt_enable(WDTO_500MS);
  // Enable WDT interrupt.
  WDTCSR |= (1<<WDIE);
  // Reset the WDT
  wdt_reset(); 
  // Enable interrupts
  sei(); 
}

ISR(WDT_vect) {
  counter++;
}

รูป: การวัดสัญญาณเอาต์พุตด้วยออสซิลโลสโคป (RIGOL DS1054Z) ที่ได้จากการทำงานของโค้ดตัวอย่างที่ 7

 


ตัวอย่างที่ 8#

โค้ดตัวอย่างนี้สาธิตการใช้งานวงจร Timer1 ในโหมดการสร้างสัญญาณ Fast PWM (Pulse Width Modulation) โดยตั้งค่าตัวหารความถี่ (Prescaler) ให้เท่ากับ 256 และกำหนดค่าในรีจิสเตอร์ ICR1 มีค่าสูงสุดเป็น (62500-1) หรือคาบของการนับขึ้น ตัวเลข 62500 คำนวณได้จากความถี่ของซีพียู (16MHz) หารด้วย 256

ค่าในรีจิสเตอร์ OCR1A จะต้องถูกกำหนดให้เป็นครึ่งหนึ่งของค่าในรีจิสเตอร์ ICR1 เพื่อทำให้ได้สัญญาณ PWM ที่มีค่า Duty Cycle เท่ากับ 50%

ข้อสังเกต: สัญญาณเอาต์พุต PWM ที่ได้จากการทำงานของ Timer1 ในตัวอย่างนี้ ตรงกับขา Arduino D9 Pin

#define LED_PIN   (9)

void setup() {
  // Set the output pin for PWM
  pinMode(LED_PIN, OUTPUT);
  // Initialize Timer1 for PWM output on D9 pin
  init_timer1_pwm();
}

void loop() {
  // empty
}

void init_timer1_pwm() {
  // Set prescaler to 256.
  // Use fast-PWM, non-inverting mode 14: WGM1[3:0]="1110".
  // Set OC1A pin at BOTTOM (0x0000). 
  // Clear OC1A pin on Compare Match.
  TCCR1A = (1 << COM1A1) | (1 << WGM11);
  TCCR1B = (1 << WGM13)  | (1 << WGM12) | (1 << CS12);
  // Set top value: 16MHz/(256)/1Hz = 62500.
  ICR1 = 62500-1; 
  // Use 50% duty cycle
  OCR1A = ICR1 / 2;
}

รูป: การวัดสัญญาณเอาต์พุตด้วยออสซิลโลสโคป (RIGOL DS1054Z) ที่ได้จากการทำงานของโค้ดตัวอย่างที่ 8

 


ตัวอย่างที่ 9#

ในตัวอย่างนี้ มีการสาธิตการใช้ไลบรารี "Arduino FreeRTOS" (Arduino_FreeRTOS.h) สำหรับ Arduino Uno / Nano / MEGA และสร้างทาสก์ (FreeRTOS Task) โดยใช้คำสั่ง xTaskCreate(...) และมีการสร้างฟังก์ชัน led_toggle_task(...) สำหรับทาสก์ดังกล่าว เพื่อให้ทำหน้าที่สลับสถานะลอจิกของขาเอาต์พุตด้วยอัตราคงที่ (ประมาณ 512 มิลลิวินาที)

นอกจากนั้นแล้วยังมีการสาธิตการสร้างคลาสที่มีชื่อว่า LED ในภาษา C++ เพื่อใช้ในการกำหนดหรือเปลี่ยนสถานะลอจิกของขาเอาต์พุตสำหรับ LED


#include <Arduino_FreeRTOS.h> // if the Arduino FreeRTOS library is used.

#define LED_PIN   (LED_BUILTIN)
#define DELAY_MS  (500)

// Define a LED class with inline member functions
class LED {
  public:
    LED(int pin, int state=0) : _pin(pin), _state(state) { // Constructor
      pinMode( _pin, OUTPUT );
      digitalWrite( _pin, _state ); 
    }
    int toggle() { // Toggle the state of the LED pin
      digitalWrite(_pin, _state^=1 ); 
      return _state;
    }
    LED& operator=(bool value) { // Implemen an assignment operator
      _state = value;
      digitalWrite(_pin, _state);
      return *this;
    }
    operator bool() const { // Return the current state of the LED pin
      return _state;
    }
  private: 
    int _pin;   // Used to keed Arduino pin number for the LED pin
    int _state; // Used to keep the current LED state
};

// Create an LED object
LED led(LED_PIN);

void led_toggle_task(void *pvParameters) {
  LED _led = *((LED *)pvParameters);
  while (1) {
    // Toggle the LED state
    //_led.toggle(); // Method 1
    _led = !_led;   // Method 2
    // Delay for a few OS ticks
    vTaskDelay( DELAY_MS / portTICK_PERIOD_MS );
  }
}

void setup( ) {
  // Create a FreeRTOS task which is used to toggle the LED pin
  xTaskCreate(led_toggle_task,  /* task function */
              "task",           /* task name */
              96,               /* task stack size */
              (void*)&led,      /* task function's parameter */
              1,                /* task priority */
              NULL              /* task handle */ 
  ); 
}

void loop() {  
  // empty or yield() 
  yield();
}

 


กล่าวสรุป#

บทความนี้นำเสนอตัวอย่างการเขียนโค้ด Arduino Sketch ในรูปแบบที่แตกต่างกันเพื่อทำให้ขาเอาต์พุต สำหรับ LED เปลี่ยนสถานะลอจิก ทำให้ LED กระพริบได้ และจะเห็นได้ว่า บางวิธีก็จะเจาะจงใช้วงจรภายในของชิป ไมโครคอนโทรลเลอร์ ATmega328P เช่น วงจรตัวนับ Timer1 และ WDT เป็นต้น

 


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

Created: 2023-03-30 | Last Updated: 2023-04-01