Krittapon Phacharoen

Krittapon's github web

View My GitHub Profile

Arduino Control LED Simulator with Wokwi


ตัวอย่างการเขียนโค้ด Arduino บนบอร์ด Arduino nano และ ESP32 เพื่อควบคุมหลอด LED ให้แสดงผลในรูปแบบต่างๆ

    รูปแบบการแสดงผลของ LED ในบทความนี้จะยกตัวอย่าง 4 รูปแบบ โดยจะมีการเขียนโปรแกรมใน 2 รูปแบบคือ การเขียนโปรแกรมแบบปกติ และการเขียนแบบ OOP โดยใช้ Class ของภาษา C++ จะมีการยกตัวอย่างให้เห็นทั้งสองรูปแบบในบางข้อ

รูปแบบที่ 1


    เป็นรูปแบบที่ต้องการให้หลอด LED เริ่มจากดับทั้งหมด และค่อยๆติดที่ละดวง และค่อยๆเลื่อนไปทีละ 1

    ข้อนี้จะมีตัวอย่างโปรแกรมใน 2 รูปแบบ การเขียนโปรแกรมแบบปกติ และการเขียนแบบ OOP โดยการเขียนโค้ดแบบแรก มีตัวอย่างตามโค้ดด้านล่าง


#if defined(ESP32)//ตั้งค่า Pin ให้สามารถใช้กับบอร์ด Arduino Nano และ ESP32 ได้โดยไม่ต้องเปลี่ยน Setting Pin
const int LED_PINS[] = {23,22,32,33,25,26,27,14,12,13};
#else
const int LED_PINS[] = {2,3,4,5,6,7,8,9,10,11};
#endif

const int NUM_LEDS = sizeof(LED_PINS)/sizeof(int);
uint16_t MASK = (1U << NUM_LEDS)-1;

String str;

void setup() {
  Serial.begin(115200);
  for ( int i=0; i < NUM_LEDS; i++ ) {
    // set the direction of the i-th LED pin
    pinMode( LED_PINS[i], OUTPUT ); 
    // turn on the first LED (i=0) and the rest off. 
    digitalWrite( LED_PINS[i], (i==0) ? HIGH : LOW );
  }
}

void loop() {
  int value[NUM_LEDS]={0};
  for ( int i=0; i < NUM_LEDS; i++ ) {//โค้ดใช้การไล่ลำดับใน Array ในการเลื่อนการติดของ LED
      value[i]=1;
      digitalWrite( LED_PINS[i], value[i] );  
      delay(500);
      value[i]=0;
      digitalWrite( LED_PINS[i], value[i] );  
  }
  /*โค้ดที่ใช้หลักการ Bit Shift 
  // str = "LEDs: value=0b";
  // str += String(value, BIN);
  // Serial.println( str.c_str() );
  // value = ((value << 1) | (value >> (NUM_LEDS-1))) & MASK;
  */
}

และการเขียนโค้ดด้วย Class ตัวอย่างตามโค้ดด้านล่าง โดยแบ่งเป็นทั้งหมด 3 ไฟล์ โดยจะมีโค้ด 2 ไฟล์เป็นไฟล์ของ Class และอีก 1 ไฟล์เป็นไฟล์การทำงานของโปรแกรม

#This file name is Pin.h
#ifndef PIN_H
#define PIN_H

#include <Arduino.h>

// This is a C++ class for an Arduino digital pin
class Pin {
   public:
     enum class Direction { OUT=0, IN=1, IN_PULLUP=2 };
     // a class constructor with parameters
     Pin( int8_t pin, Direction dir=Direction::IN, bool init_value=false );
     // read the input pin
     bool read( bool force_update=true ); 
     // write the output pin
     void write( bool value, bool force_update=true );
     // toggle the output pin
     void toggle( bool force_update=true ) ;
     // assign a new value to the output pin
     void operator=(bool value);
     // set the direction of the pin
     void setDirection( Direction dir, bool init_value=false );
     // inline methods
     inline operator bool()           { return _value; }
     inline int8_t getPin()           { return _pin;   }
     inline bool   getValue()         { return _value; }
     inline Direction getDirection()  { return _dir;   }
     // the class destructor
     ~Pin() {} 

   protected:
     // initialize the GPIO pin
     void init( int8_t pin, Direction dir, bool init_value );
     // update the logic state of the GPIO pin 
     void update();
 
   private:
     int8_t     _pin;   // the GPIO pin number
     bool       _value; // the value of pin
     Direction  _dir;   // the direction of pin
};

#endif
///////////////////////////////////////////////////////////////////////////////////
#This file name is Pin.cpp
#include "Pin.h"

// Class implementation for Pin.h

Pin::Pin( int8_t pin, Direction dir, bool init_value ) {
  init( pin, dir, init_value );
}

bool Pin::read( bool force_update ) {
  if (force_update) { update(); }
  return _value;
}

void Pin::write( bool value, bool force_update ) {
  _value = value;
  if (force_update) { update(); }
}

void Pin::toggle( bool force_update ) {
  if ( _dir == Direction::OUT ) {
     _value = !_value;
  }
  if (force_update) {
     update();
  }
}
     
void Pin::operator=(bool value) {
  write( value );
}   

void Pin::setDirection( Direction dir, bool init_value ) {
  init( _pin, dir, init_value );
}

void Pin::init( int8_t pin, Direction dir, bool init_value ) {
  _pin   = pin;
  _dir   = dir;
  _value = init_value;
  if ( _dir == Direction::OUT ) { 
    pinMode( _pin, OUTPUT );
  }
  else if ( _dir == Direction::IN ) {
    pinMode( _pin, INPUT );
  }
  else if ( _dir == Direction::IN_PULLUP ) {
    pinMode( _pin, INPUT_PULLUP );
  }
  update();
}

void Pin::update() {
  if ( _dir == Direction::OUT ) { 
    digitalWrite( _pin, _value ? true : false ) ;
    //Serial.printf( "Debug> write output: %d on pin %d\n", _value, _pin );
  }
  else {
    _value = digitalRead( _pin ) ? true : false;
    //Serial.printf( "Debug> read input: %d on pin %d\n", _value, _pin );
  }
}

///////////////////////////////////////////////////////////////////////////////////

#Programming Code
#include "Pin.h"

#define DEFAULT_PIN   (22) // (onboard LED)
#define OFF           (0)
#define ON            (1)
#define REPEAT_TIMES  (20)
#define DELAY_MS      (500)

// define a function type
typedef void (*test_func_ptr)(int);

// function prototypes
void test1(int);
void test2(int);

// an array of function pointers which point to test functions
test_func_ptr test_func_list[] = { &test1};

int LED_PINS[] = {23,22,32,33,25,26,27,14,12,13};

void setup() {
   Serial.begin( 115200 );
   Serial.flush();
}

void loop() {
   int num_funcs = sizeof( test_func_list ) / sizeof(test_func_ptr);
   int num_pins = sizeof( LED_PINS ) / sizeof(int);
   for ( int i=0; i < num_pins; i++ ) {
      test_func_list[i % num_funcs]( LED_PINS[ i ] );
   }
}

void test1( int led_pin=DEFAULT_PIN ) {
   Pin pin( led_pin );
   pin.setDirection( Pin::Direction::OUT );
   pin = OFF;
   Serial.printf( "use pin: %d\n", led_pin );
   pin = ON;
   delay(  DELAY_MS );
   pin = OFF;
}

ตัวอย่างการทำงานจากการ Simulator จาก Wokwi

รูปแบบที่ 2


    เป็นรูปแบบที่ต้องการให้หลอด LED เริ่มจากดับทั้งหมด และค่อยๆติดที่ละดวง และเพิ่มจำนวนขึ้นเรื่อยๆ และเมื่อ LED ติดทั้งหมดจะค่อยๆดับทีละดวง แบบดวงไหนติดทีหลังจะดับก่อน

    ข้อนี้จะมีตัวอย่างโปรแกรมใน 2 รูปแบบ การเขียนโปรแกรมแบบปกติ และการเขียนแบบ OOP โดยการเขียนโค้ดแบบแรก มีตัวอย่างตามโค้ดด้านล่าง


#if defined(ESP32)
const int LED_PINS[] = {23,22,32,33,25,26,27,14,12,13};
#else
const int LED_PINS[] = {2,3,4,5,6,7,8,9,10,11};
#endif

const int NUM_LEDS = sizeof(LED_PINS)/sizeof(int);
uint16_t MASK = (1U << NUM_LEDS)-1;

String str;

void setup() {
  Serial.begin(115200);
  for ( int i=0; i < NUM_LEDS; i++ ) {
    // set the direction of the i-th LED pin
    pinMode( LED_PINS[i], OUTPUT ); 
    // turn on the first LED (i=0) and the rest off. 
    digitalWrite( LED_PINS[i], LOW );
  }
}
bool state = 1;
void loop() {
  //-------------------------------1-----------------------------
  //ใช้หลักการของ Bit Shift ในการควบคุมรูปแบบของ LED
  static uint16_t value=1;
  if (value == 0){//ใช้เพื่อเลือกให้ LED วิ่งขึ้นหรือวิ่งลง
    Serial.println("Change state!");
    state = !state;
  }
  if (state){
    for ( int i=0; i < NUM_LEDS; i++ ) {
      digitalWrite( LED_PINS[i], (value >> i) & 1 );//สั่งให้ LED ติดปกติ    
    }
    str = "LEDs: value=0b";
    str += String(value, BIN);
    Serial.println( str.c_str() );
  }
  else{
    for ( int i=0; i < NUM_LEDS; i++ ) {
      digitalWrite( LED_PINS[NUM_LEDS-i-1], !(value >> i) & 1 ); //สั่งให้ LED ค่อยๆดับ   
    }
    str = "LEDs: value=0b";
    str += String(value, BIN);
    Serial.println( str.c_str() );
 }
 value = (((value << 1) | (value >> (NUM_LEDS-2)))+1U) & MASK;//ใช้กำหนดว่า Bit ที่ใช้ควบคุมรูปแบบของ LED จะมีค่าสูงสุดเมื่อไหร่ แล้วจึงจะ Reset ค่าให้เป็น 0
 delay(200);
  //----------------------------------------------------------

  //--------------------------2-------------------------------
  //โค้ดนี้เป็นการใช้ Array ในการควบคุมรูปแบบของ LED 
  // for ( int i=0; i < NUM_LEDS; i++ ) {
  //   digitalWrite( LED_PINS[i], HIGH);  
  //   delay(500);   
  // }
  // for ( int i=0; i < NUM_LEDS; i++ ) {
  //   digitalWrite( LED_PINS[NUM_LEDS-1-i], LOW);  
  //   delay(500);   
  // } 
  //---------------------------------------------------------
}

และการเขียนโค้ดด้วย Class ตัวอย่างตามโค้ดด้านล่าง โดยไฟล์ของ Class จะใช้ไฟล์เดียวกันกับการคุม LED รูปแบบที่ 1


#include "Pin.h"

#define DEFAULT_PIN   (22) // (onboard LED)
#define OFF           (0)
#define ON            (1)
#define REPEAT_TIMES  (20)
#define DELAY_MS      (300)

// define a function type
typedef void (*test_func_ptr)(int);

// function prototypes
void test1(int);
void test2(int);

// an array of function pointers which point to test functions
test_func_ptr test_func_list[] = { &test1, &test2};

int LED_PINS[] = {23,22,32,33,25,26,27,14,12,13};

void setup() {
   Serial.begin( 115200 );
   Serial.flush();
}

int num_funcs = sizeof( test_func_list ) / sizeof(test_func_ptr);
int num_pins = sizeof( LED_PINS ) / sizeof(int);

void loop() {

   for ( int i=0; i < num_funcs; i++ ) {
      for (int j=0; j<num_pins; j++){
      test_func_list[i](j);
      }
   }
}

void test1( int led_pin ) {
   Pin pin( LED_PINS[led_pin] );
   pin.setDirection( Pin::Direction::OUT );
   Serial.printf( "use pin: %d\n", LED_PINS[led_pin] );
   pin = ON;
   delay(  DELAY_MS );
}

void test2( int led_pin) {
   Pin pin( LED_PINS[num_pins-led_pin-1] );
   pin.setDirection( Pin::Direction::OUT );
   Serial.printf( "use pin: %d\n", LED_PINS[num_pins-led_pin-1] );
   pin = OFF;
   delay(  DELAY_MS );
}

/////////////////////////////////////////////////////////////////////////////

ตัวอย่างการทำงานจากการ Simulator จาก Wokwi

โดยข้อนี้ได้ลองนำโค้ดไป Upload ให้กับ Board Arduino Nano ที่ต่อเข้ากับ LED Array ได้ผลตามวิดีโอด้านล่าง

รูปแบบที่ 3


    รูปแบบนี้เป็นรูปแบบที่ปรับความสว่างของ LED โดยแต่ละหลอดจะค่อยๆติด จนติดทั้งหมด จากนั้น LED ก็จะค่อยๆดับจนดับทั้งหมด

    ข้อนี้จะมีตัวอย่างการเขียนโปรแกรมแบบปกติ มีตัวอย่างตามโค้ดด้านล่าง


const int LED_PINS[] = {5,18,19,21};
const int NUM_LEDS   = sizeof(LED_PINS)/sizeof(int);

#define OFF (LOW) // active-low LED

void setup() {
  Serial.begin(115200);
  for ( int i=0; i < NUM_LEDS; i++ ) {
    // set the direction of the i-th LED pin
    pinMode( LED_PINS[i], OUTPUT ); 
    digitalWrite( LED_PINS[i], OFF );
  }
}

const int PWM_RESOLUTION = 8;
const int PWM_FREQ = 1000;
const int DUTY_MAX = (1 << PWM_RESOLUTION);

void loop() {
  for ( int i=0; i < NUM_LEDS; i++ ) {//loop นี้ใช้เพื่อ
      ledcSetup( i /*channel*/, PWM_FREQ, PWM_RESOLUTION );
      ledcAttachPin( LED_PINS[i] /*pin*/, i /*channel*/ );
      for ( int x=0; x < DUTY_MAX; x++ ) {
        Serial.println(x);
         ledcWrite( i, x);
         delay(5);
      }
  }
  for ( int i=0; i < NUM_LEDS; i++ ) {
      ledcSetup( i /*channel*/, PWM_FREQ, PWM_RESOLUTION );
      ledcAttachPin( LED_PINS[i] /*pin*/, i /*channel*/ );
      for ( int x=DUTY_MAX; x >= 0; x-- ) {
        Serial.println(x);
         ledcWrite( i, x);
         delay(5);
      }
  }
  for ( int i=0; i < NUM_LEDS; i++ ) {
    ledcDetachPin( LED_PINS[i] ); 
    digitalWrite( LED_PINS[i], OFF );
  }
  delay(200);
}

ตัวอย่างการทำงานจากการ Simulator จาก Wokwi

รูปแบบที่ 4


    รูปแบบนี้เป็นรูปแบบที่ให้ LED ติดพร้อมกันที่ละหลายหลอด โดยแต่ละหลอดมีความสว่างไม่เท่ากัน แล้วค่อยๆเลื่อน LED ที่ติดพร้อมกันไปเรื่อยๆ ทำให้มีลักษณะคล้ายกับไฟวิ่ง

    ข้อนี้จะมีตัวอย่างการเขียนโปรแกรมแบบปกติ มีตัวอย่างตามโค้ดด้านล่าง


const int LED_PINS[] = {5,18,19,21,12,14,27,26};
const float diff_duty[] = {1,0.4,0.1,0.01};
const int NUM_LEDS   = sizeof(LED_PINS)/sizeof(int);
const int NUM_DUTY = sizeof(diff_duty)/sizeof(int);


#define OFF (LOW) // active-low LED

void setup() {
  Serial.begin(115200);
  for ( int i=0; i < NUM_LEDS; i++ ) {
    // set the direction of the i-th LED pin
    pinMode( LED_PINS[i], OUTPUT ); 
    digitalWrite( LED_PINS[i], OFF );
  }
}

const int PWM_RESOLUTION = 8;
const int PWM_FREQ = 1000;
const int DUTY_MAX = (1 << PWM_RESOLUTION);

void loop() {
  for ( int i=0; i < NUM_LEDS+NUM_DUTY; i++ ) {
    int max_led_on;
    if (i<NUM_DUTY){//ใช้เพื่อเลือกว่า LED จะดิดสูงสุดกี่ดวง
      max_led_on = i+1;
    }
    else if (i>=NUM_LEDS){
      max_led_on = NUM_LEDS+NUM_DUTY-i-1;
    }
    else{
      max_led_on = NUM_DUTY;
    }
    Serial.println("i "+String(i)+"| number of led on "+String(max_led_on));
    if (i < NUM_LEDS){//กรณีที่หัวแถวยังอยู่ใน LED
      for (int x = 0 ; x < max_led_on; x++){
        ledcSetup( i-x /*channel*/, PWM_FREQ, PWM_RESOLUTION );
        ledcAttachPin( LED_PINS[i-x] /*pin*/, i-x /*channel*/ );
        Serial.println("<pin"+String(i-x)+" | analog "+String(diff_duty[x]*DUTY_MAX));
        ledcWrite( i-x, diff_duty[x]*DUTY_MAX );
      }
    }
    else{
      for (int x = 0 ; x < max_led_on; x++){//กรณีที่หัวแถวไม่อยู่ใน LED
        ledcSetup( NUM_LEDS-1-x /*channel*/, PWM_FREQ, PWM_RESOLUTION );
        ledcAttachPin( LED_PINS[NUM_LEDS-1-x] /*pin*/, NUM_LEDS-1-x /*channel*/ );
        Serial.println(">pin"+String(NUM_LEDS-1-x)+" | analog "+String(diff_duty[x+(NUM_DUTY-max_led_on)]*DUTY_MAX));
        ledcWrite( NUM_LEDS-1-x, diff_duty[x+(NUM_DUTY-max_led_on)]*DUTY_MAX );
      }
    }
    delay(100);
    for ( int i=0; i < NUM_LEDS; i++ ) {
      ledcDetachPin( LED_PINS[i] );
      digitalWrite( LED_PINS[i], OFF );
    }
  }
}


ตัวอย่างการทำงานจากการ Simulator จาก Wokwi

บทสรุป


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

» Homepage