ARM Mbed OS for Raspberry Pi Pico RP2040: Code Examples (Part 5)#

เนื้อหาในส่วนนี้สาธิตการเขียนโค้ดโดยใช้ Mbed OS และ Arduino IDE สำหรับบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิป RP2040 เช่น Raspberry Pi Pico โดยนำเสนอเป็น ตอนที่ 5 ต่อจาก ตอนที่ 1 | ตอนที่ 2 | ตอนที่ 3 | ตอนที่ 4

Keywords: Mbed OS, Raspberry Pi Pico, RP2040, Mbed-enabled Platforms, RTOS Programming


การใช้งานโมดูล RGB LED แบบ Active-Low#

โมดูล RGB LED สามารถให้แสงที่ประกอบด้วยสีแดง สีเขียว และสีน้ำเงิน ตัวอย่างของโมดูลที่ได้นำมาทดลองใช้งาน เป็นแบบ Active-Low มี 4 ขา ได้แก่ VCC (+3.3V), R (Red), B (Blue), G (Green) ตามลำดับ ดังนั้นขาควบคุมสถานะมี 3 ขา ถ้ามีลอจิกเป็น 0 จะทำให้ LED ที่ตรงกับขาดังกล่าวอยู่ในสถานะ ON (สว่าง) แต่ถ้าเป็นลอจิก 1 จะเป็น OFF (ดับ)


รูป: โมดูล RGB LED (Active-Low)

 

ถ้าเขียนโค้ดโดยใช้คำสั่งของ Arduino API เพื่อทำให้ LED แต่ละสี สว่างและดับขึ้นทีละดวง ก็ใช้คำสั่ง pinMode(...) และ digitalWrite(...) และเลือกใช้ขา GP13, GP12, GP11 ตามลำดับ

#include "mbed.h"
using namespace mbed;

const int RGB_PINS[] = {13,12,11}; // (R,G,B) pins
const size_t NUM_PINS = sizeof(RGB_PINS)/sizeof(int);

void setup() {
  for ( int i=0; i < NUM_PINS; i++ ) {
     pinMode( RGB_PINS[i], OUTPUT );
     digitalWrite( RGB_PINS[i], HIGH ); // initial state: OFF
  }
  SerialUSB.begin( 115200 );
  while (!SerialUSB){} // wait for Serial to open
  delay( 1000 );
}

#define ROTATE_LFET_SHIFT(x,n) (((x<<1)|(x>>(n-1))) & ((1<<n)-1))

void loop() {
  static uint8_t state = 0b011;
  state = ROTATE_LFET_SHIFT(state,NUM_PINS);
  for ( int i=0; i < NUM_PINS; i++ ) {
     digitalWrite( RGB_PINS[i], (state >> i) & 1 );
  }
  delay( 1000 );
}

 

แต่ถ้าใช้คำสั่งของ Mbed Drivers ก็มีคลาส BusOut ที่เหมาะสำหรับการใช้งานในกรณีนี้ (มี 3 ขา) เป็นการใช้งานขา GPIO แบบรวมกันหรืออาร์เรย์ ไม่จำเป็นต้องมีหมายเลขขาต่อเนื่องกัน แต่ใช้งานในทิศทางเอาต์พุตเหมือนกัน และสามารถเขียนใหม่ได้ดังนี้

#include "mbed.h"
using namespace mbed;

BusOut rgb(p13,p12,p11); // (R,G,B) pins

void setup() {
  rgb = 0b111; // (R,G,B) initial state: OFF
  SerialUSB.begin( 115200 );
  while (!SerialUSB){}
  delay( 1000 );
}

#define ROTATE_LFET_SHIFT(x,n) (((x<<1)|(x>>(n-1))) & ((1<<n)-1))

void loop() {
  static uint8_t state = 0b011; 
  state = ROTATE_LFET_SHIFT(state,3); // update next state
  rgb   = state; // update output state
  delay( 1000 );
}


รูป: การต่อวงจรทดลองบนเบรดบอร์ดสำหรับโมดูล RGB LED

 


การใช้งานโมดูล WS2812B RGB LED (NeoPixel)#

ถัดไปเป็นการสาธิตการใช้ไลบรารีสำหรับโมดูล WS2812B หรืออีกชื่อหนึ่งคือ NeoPixel ของบริษัท Adafruit และทางบริษัทได้มีการพัฒนาไลบรารีให้สามารถนำไปใช้กับบอร์ดไมโครคอนโทรลเลอร์ เช่น AVR, nRF52, ESP8266/ESP32 เป็นต้น และได้มีการเพิ่มฟังก์ชันให้สามารถใช้งานได้กับบอร์ด Pico RP2040 โดยใช้ประโยชน์จากการทำงานของวงจรภายในที่เรียกว่า PIO


รูป: โมดูล WS2812 - NeoPixel (มุมมองด้านบน)


รูป: โมดูล WS2812 - NeoPixel (มุมมองด้านล่าง)

 

การติดตั้งและใช้งานไลบรารีใน Arduino IDE ก็มีขั้นตอนดังนี้ เปิดใช้งาน Arduino IDE และไปยังเมนู Tools > Manage Libraries ในหน้าต่าง Library Manager ให้ค้นหาไลบรารีที่มีชื่อว่า adafruit_neopixel คลิกเลือกที่รายการ "Adafruit NeoPixel" แล้วกดปุ่ม Install


รูป: การติดตั้งไลบรารี adafruit_neopixel สำหรับ Arduino IDE

ตัวอย่างโค้ดสาธิตการทำงานมีดังนี้ โดยเลือกใช้ขา GP15 บนบอร์ด Pico นำไปต่อกับขา DIN ของ โมดูล NeoPixel ที่มี RGB LEDs จำนวน 8 ดวง (พิกเซล) ซึ่งสามารถใช้แรงดันไฟเลี้ยง +3.3V ได้

โค้ดตัวอย่างนี้จะทำให้แต่ละดวงเปลี่ยนเป็นสีเขียว (Green) เพิ่มขึ้นทีละดวง จนครบ 8 ดวง แล้วดับลงตามลำดับในทิศทางตรงข้าม แล้วเกิดขึ้นซ้ำในลักษณะนี้ไปเรื่อย ๆ

// https://github.com/adafruit/Adafruit_NeoPixel
// support: AVR, NF52, ESP8266/ESP32, K210, RP2040
#include <Adafruit_NeoPixel.h>

#define PIN         (15)
#define NUM_PIXELS   (8)
Adafruit_NeoPixel pixels( NUM_PIXELS, PIN, NEO_GRB+NEO_KHZ800 );

void setup() {
  SerialUSB.begin(115200);
  while(!SerialUSB){}
  pixels.begin(); // initialize NeoPixel
}

const uint32_t GREEN_COLOR = pixels.Color(0, 150, 0);

void loop() {
  pixels.clear();
  for( int i=0; i < 2*NUM_PIXELS; i++ ) { // for each pixel...
    if (i < NUM_PIXELS) {
      pixels.setPixelColor( i, GREEN_COLOR );
    } else {
      pixels.setPixelColor( 2*NUM_PIXELS-i-1, 0 /*OFF*/ );
    }
    pixels.show();   // Send the updated pixel colors to the hardware.
    delay( 150 ); // Pause before next pass through loop
  }
}


รูป: การต่อวงจรทดลองบนเบรดบอร์ดเพื่อใช้งานโมดูล NeoPixel RGB LED Bar

 


การใช้งานโมดูล HC-SR04+ Ultrasonic Distance Sensor#

โมดูล HC-SR04+ เป็นตัวอย่างของเซ็นเซอร์ที่ใช้คลื่นเสียงที่มีความถี่สูง (เช่น 40kHz หรือมากกว่า) ในการตรวจจับวัตถุกีดขวาง เมื่อส่งสัญญาณเสียงจากตัวส่งแล้วไปกระทบกับวัตถุ จะได้เสียงสะท้อนกลับมายังตัวรับสัญญาณ ถ้าจับเวลาตั้งแต่ส่งคลื่นเสียงออกไปและได้รับคลื่นสะท้อนกลับมา ก็สามารถคำนวณระยะห่างจากวัตถุดังกล่าวได้


รูป: โมดูล HC-SR04+ Ultrasonic Distance Sensor

 

โมดูล HC-SR04+ สามารถใช้แรงดันไฟเลี้ยง +3.3V มีขาอินพุต TRIG ไว้สร้างสัญญาณพัลส์เพื่อเริ่มต้นการวัดระยะห่าง และมีขาเอาต์พุต ECHO เป็นสัญญาณพัลส์แบบ High

ความกว้างของพัลส์ (Pulse Width) ที่วัดได้ หรือค่า มีความสัมพันธ์แบบเชิงเส้นกับระยะห่างจากวัตถุ (Distance) ตามสูตรต่อไปนี้ (ถ้ากำหนดให้ความเร็วของคลื่นเสียงในอากาศเท่ากับ 343 m/s โดยประมาณ)

 

ตัวอย่างการเขียนโค้ดโดยใช้คำสั่งของ Arduino มีดังนี้

// speed of sound in the air at sea level
#define SOUND_SPEED  (340)  // ~340 m/s

#define TRIG_PIN  (p15)
#define ECHO_PIN  (p14)

void setup() {
  SerialUSB.begin( 115200 );
  while(!SerialUSB){}
  pinMode( ECHO_PIN, INPUT_PULLUP );
  pinMode( TRIG_PIN, OUTPUT );
  digitalWrite( TRIG_PIN, LOW );
}

bool measure( float *distance ) {
  uint32_t countdown;
  // generate a high pulse on TRIG pin
  digitalWrite( TRIG_PIN, HIGH );
  delayMicroseconds(20);
  digitalWrite( TRIG_PIN, LOW );

  countdown = 100;
  // wait until the ECHO pin goes high
  while ( digitalRead(ECHO_PIN) == LOW ){
    if ( countdown == 0 ) return false;
    countdown--;
    delayMicroseconds(1);
  }
  uint32_t t_start = micros();

  countdown = 20000;
  // wait until the ECHO pin goes low
  while (  digitalRead(ECHO_PIN) == HIGH ) {
    if ( countdown == 0 ) return false;
    countdown--;
    delayMicroseconds(1);
  }
  uint32_t t_end = micros();

  // calculate the pulse width
  uint32_t t_pulse = t_end - t_start;
  // calculate the distance (in cm.)
  *distance = SOUND_SPEED*t_pulse/2.0/10000;
  return true;
}

char sbuf[32];

void loop() {
  float distance;
  if ( measure( &distance ) ) {
     sprintf( sbuf, "Distance: %.1f cm", distance );
     SerialUSB.println( sbuf );
  } else {
     SerialUSB.println( "Timeout" );
  }
  delay( 500 );
}

 

คำสั่ง pulseIn() ของ Arduino Core for Mbed เป็นฟังก์ชันที่ถูกสร้างไว้ในไฟล์ cores/arduino/wiring_pulse.cpp.c


รูป: โค้ดในส่วนที่เกี่ยวข้องกับการทำงานของฟังก์ชัน pulseIn()

ถ้าจะเปลี่ยนมาใช้คำสั่ง pulseIn() ก็มีตัวอย่างดังนี้

// speed of sound in the air at sea level
#define SOUND_SPEED  (340)  // ~340 m/s

#define TRIG_PIN  (p15)
#define ECHO_PIN  (p14)

void setup() {
  SerialUSB.begin( 115200 );
  while(!SerialUSB){}
  pinMode( ECHO_PIN, INPUT_PULLUP );
  pinMode( TRIG_PIN, OUTPUT );
  digitalWrite( TRIG_PIN, LOW );
}

bool measure( float *distance ) {
  uint32_t t_pulse;

  // generate a high pulse on TRIG pin
  digitalWrite( TRIG_PIN, HIGH );
  delayMicroseconds(20);
  digitalWrite( TRIG_PIN, LOW );

  // measure the HIGH pulse width of the echo signal
  t_pulse = pulseIn( ECHO_PIN, HIGH );
  // calculate the distance (in cm.)
  *distance = SOUND_SPEED*t_pulse/2.0/10000;
  return (t_pulse > 0);
}

char sbuf[32];

void loop() {
  float distance;
  if ( measure( &distance ) ) {
     sprintf( sbuf, "Distance: %.1f cm", distance );
     SerialUSB.println( sbuf );
  } else {
     SerialUSB.println( "Timeout" );
  }
  delay( 500 );
}

 

ตัวอย่างการเขียนโค้ดโดยใช้ Mbed Drivers มีดังนี้

#include "mbed.h"
using namespace mbed;

// speed of sound in the air at sea level
#define SOUND_SPEED  (340)  // ~340 m/s

DigitalOut trig(p15);
DigitalIn  echo(p14);

void setup() {
  SerialUSB.begin( 115200 );
  while(!SerialUSB){}
  echo.mode( PullUp );
  trig = 0;
}

bool measure( float *distance ) {
  uint32_t countdown;
  // generate a high pulse on TRIG pin
  trig = 1;
  wait_us(20);
  trig = 0;

  countdown = 100;
  // wait until the ECHO pin goes high
  while ( echo == 0 ){
    if ( countdown == 0 ) return false;
    countdown--;
    wait_us(1);
  }
  uint32_t t_start = micros();

  countdown = 20000;
  // wait until the ECHO pin goes low
  while ( echo == 1 ) {
    if ( countdown == 0 ) return false;
    countdown--;
    wait_us(1);
  }
  uint32_t t_end = micros();

  // calculate the pulse width
  uint32_t t_pulse = t_end - t_start;
  // calculate the distance (in cm.)
  *distance = SOUND_SPEED*t_pulse/2.0/10000;
  return true;
}

char sbuf[32];

void loop() {
  float distance;
  if ( measure( &distance ) ) {
     sprintf( sbuf, "Distance: %.1f cm", distance );
     SerialUSB.println( sbuf );
  } else {
     SerialUSB.println( "Timeout" );
  }
  delay( 500 );
}

รูปแบบการเชื่อมต่อระหว่างโมดูล HC-SR04+ กับบอร์ด Pico มีดังนี้

Pico       HC-SR04+
GND   ---- GND
GP14  <--- Echo
GP15  ---> Trig
3.3V  ---- VCC

 


รูป: การต่อวงจรใช้งานโมดูล HC-SR04+ ร่วมกับบอร์ด Pico

 


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

 


การใช้งาน Hardware Serial#

วงจรภายในไมโครคอนโทรลเลอร์ที่เรียกว่า UART (Universally Asynchronous Receiver / Transmitter) ใช้วิธีรับส่งข้อมูลทีละบิตแบบสองทิศทางและไม่มีการสร้างสัญญาณ Clock เป็นสัญญาณควบคุม และเลือกรูปแบบการใช้งานขา I/O ที่เกี่ยวข้องให้เป็นขา TX/RX (Transmit Data / Receive Data)

การใช้งาน Serial ของบอร์ด Pico โดยใช้ Arduino API มีตัวเลือกดังนี้

  • Serial หรือ SerialUSB ใช้วิธีการสื่อสารผ่าน Native (built-in) USB โดยตรง
  • Serial1 เป็นการใช้งานวงจร UART0 ที่มีอยู่ภายในชิป RP2040 และใช้ขา GP0 / GP1 สำหรับขา TX / RX ของ UART0 ตามลำดับ

ตัวอย่างแรกสาธิตการเขียนโค้ดเพื่อรอรับข้อมูลจากคอมพิวเตอร์ของผู้ใช้ผ่านทาง SerialUSB เมื่อได้รับข้อมูลไบต์หนึ่งไบต์ ก็ส่งออกทาง Serial1 แต่ถ้ามีข้อมูลรับเข้ามาทาง Serial1 ให้รับข้อมูลแล้วส่งต่อออกทาง SerialUSB ในทางกลับกัน

ดังนั้น Pico ในตัวอย่างนี้ จึงทำงานเป็นตัวส่งต่อข้อมูลระหว่าง SerialUSB กับ Serial1 ในทั้งสองทิศทาง ในการทดลองกับฮาร์ดแวร์จริง ให้เชื่อมต่อขา GP0 ไปยัง GP1 (ขา TX เชื่อมต่อไปยัง RX) ด้วยสายไฟหนึ่งเส้น เพื่อใช้งานในลักษณะ Serial Loopback

void setup() {
  SerialUSB.begin( 115200 );
  while (!SerialUSB){} // wait for SerialUSB to open
  Serial1.begin( 115200 );
}

char data;

void loop() {
  // read from SerialUSB, write to Serial1
  while ( SerialUSB.available() > 0 ) {
     data = SerialUSB.read();
     Serial1.write( data );
  }
  // read from Serial1, write to SerialUSB
  while ( Serial1.available() > 0 ) {
     data = Serial1.read();
     SerialUSB.write( data );
  }
}

หรือถ้าจะเขียนโค้ดโดยการแบ่งการทำงานเป็น 2 ส่วนย่อยที่ทำงานได้พร้อม ๆ กัน ให้รับและส่งข้อมูลในแต่ละทิศทาง โดยใช้ Mbed Threads ก็มีตัวอย่างดังนี้

#include "mbed.h"
using namespace mbed;
using namespace rtos;

Thread t1, t2; // create two threads

void from_serialusb_to_serial1() { // for the first thread
  while (1) {
    // read from SerialUSB and write to Serial1
    while ( SerialUSB.available() > 0 ) {
       Serial1.write( SerialUSB.read() );
    }
    yield(); // pass control to other threads
  }
}

void from_serial1_to_serialusb() { // for the second thread
  while (1) {
    // read from Serial1 and write to SerialUSB
    while ( Serial1.available() > 0 ) {
      SerialUSB.write( Serial1.read() );
    }
    yield(); // pass control to other threads
  }
}

void setup() {
  SerialUSB.begin( 115200 );
  while (!SerialUSB){} // wait for SerialUSB to open
  Serial1.begin( 115200 );
  // start both threads to handle serial streams
  t1.start( &from_serialusb_to_serial1 );
  t2.start( &from_serial1_to_serialusb );
}

void loop() {}

วงจร UART (หรืออาจเรียกว่า Serial) ของไมโครคอนโทรลเลอร์ ไม่สามารถสื่อสารข้อมูลได้โดยตรงกับคอมพิวเตอร์ของผู้ใช้ ดังนั้นจึงต้องใช้โมดูล USB-to-Serial ในการเชื่อมต่อ ถ้าจะส่งข้อมูลไปยังคอมพิวเตอร์โดยไม่ใช้วงจร USB ของ Pico ตัวอย่างถัดไปเป็นการใช้โมดูล USB-to-Serial เพื่อส่งข้อมูลจาก ** Pico** ไปยังคอมพิวเตอร์ของผู้ใช้

void setup() {
  // use Serial1 (UART0)
  // Arduino default pins: Tx/Rx = GP0/GP1
  Serial1.begin( 115200 );
}

char sbuf[32];

void loop() {
  static uint32_t cnt = 0;
  sprintf( sbuf, "Count: %lu", cnt++ );
  Serial1.println( sbuf );
  delay( 500 );
}

หรืออาจเขียนโค้ดใหม่ที่ให้ผลเหมือนกัน โดยใช้คำสั่ง printf() ของ Mbed OS ซึ่งจะตรงกับ UART0 (ใช้ขา GP0 / GP1) ของบอร์ด Pico

void setup() {
  // empty
}

char sbuf[32];

void loop() {
  static uint32_t cnt = 0;
  sprintf( sbuf, "Count: %lu\n", cnt++ );
  std::printf( sbuf ); // send string to serial
  delay( 500 );
}

แต่ถ้าจะเปลี่ยนมาใช้ Mbed OS Drivers แทน Serial1 ก็มีคลาสให้ใช้งาน เช่น UnbufferedSerial ตามตัวอย่างโค้ดต่อไปนี้

#include "mbed.h"
using namespace mbed;

#define TX_PIN  (p0)
#define RX_PIN  (p1)

UnbufferedSerial serial( TX_PIN, RX_PIN, 115200 );

void setup() {
  // 8-bit data, no parity bit, 1 stop bit
  serial.format( 8, SerialBase::None, 1 );
}

char sbuf[32];

void loop() {
  static uint32_t cnt = 0;
  while (!serial.writable()) {}
  sprintf( sbuf, "Count: %lu\n", cnt++ );
  serial.write( sbuf, strlen(sbuf) ); // send string
  serial.sync(); // flush output
  delay( 500 );
}

การต่อวงจรใช้งานโมดูล USB-to-Serial (เช่น โมดูลที่ใช้ไอซี CP2102 หรือ CP2104) จะใช้เพียง 3 ขา คือ GND, TXD, RXD โดยใช้แรงดันไฟเลี้ยง +5V จากพอร์ต USB ของคอมพิวเตอร์ (แต่ขา TXD / RXD ทำงานที่ระดับแรงดันสำหรับลอจิกที่ +3.3V)

รูปแบบการเชื่อมต่อมีดังนี้

Pico        USB-to-Serial module
GND    ---- GND
GP0/TX ---> TXD  
GP1/RX <--- RXD 


รูป: การต่อวงจรทดลองใช้งานร่วมกับโมดูล CP2104 USB-to-Serial

 


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

Created: 2021-12-16 | Last Updated: 2022-03-03