Programming Raspberry Pi Pico RP2040 with Arduino IDE

<rawat.s>
11 min readApr 13, 2021

บทความนี้กล่าวถึง การทดลองเขียนโค้ดภาษา C/C++ สำหรับบอร์ดไมโครคอนโทรลเลอร์ Raspberry Pi Pico RP2040 โดยใช้ซอฟต์แวร์ Arduino IDE (Windows 10) และ Arduino Core Mbed OS

Image Source: Arduino’s Twitter

บอร์ด Raspberry Pico RP2040

ในวันที่ 21 เดือนมกราคม พ.ศ. 2564 บริษัท Raspberry Pi Trading Ltd. (Cambridge, UK) ได้เปิดตัวบอร์ดไมโครคอนโทรลเลอร์ ซึ่งมีตัวประมวลผลที่ทางบริษัทได้ออกแบบและผลิตมาใหม่คือ RP2040 SoC ภายในมีซีพียู Dual-Core Arm Cortex-M0+

รูป: RPi Pico PinOut (Source: Raspberry Pi Trading Ltd.)

ในเบื้องต้น ผู้ใช้บอร์ดดังกล่าว สามารถเขียนโปรแกรมด้วยภาษา C/C++ โดยใช้ Pico SDK หรือใช้ภาษาไมโครไพธอน (MicroPythonRP2 Port) หรือ Adafruit’s CircuitPython for Pico เป็นตัวเลือก

นอกจากบอร์ด Pico ของ Raspberry Pi ก็มีบอร์ดไมโครคอนโทรลเลอร์ของบริษัทอื่นอีกที่ใช้ชิป RP2040 MCU / SoC เช่น Arduino, Sparkfun, Adafruit และ Pimoroni เป็นต้น

ทีมพัฒนาบอร์ด Arduino Nano RP2040 Connect ก็ได้ออกมาประกาศว่า จะเพิ่ม Arduino Core ให้รองรับการใช้งาน RP2040 แต่คงต้องใช้เวลาสักระยะหนึ่ง

Arduino Core for RP2040 — Community Support

ในช่วงต้นเดือนเมษายน พ.ศ. 2564 ได้มีผู้พัฒนา Arduino Core ให้ใช้งานสำหรับ Arduino IDE โดยสามารถติดตั้งเพิ่มผ่านทาง Arduino Boards Manager และได้นำมาแชร์ไว้ใน Github (https://github.com/earlephilhower/arduino-pico) ให้ทดลองใช้กับบอร์ด Pico

ถ้าลองสืบค้นดูใน Github ก็ยังมีตัวเลือกอื่นอีกสำหรับบอร์ด Pico ที่สามารถนำมาใช้ได้กับ Arduino IDE และ PlatformIO / VSCode เช่น

Official Arduino Core for RP2040

วันที่ 9 เดือนเมษายน พ.ศ. 2564 ใน Twitter ของผู้ใช้ชื่อ Arduino ก็ได้มีการเปิดตัว Arduino Core for Mbed (Release 2.0.0) และเปิดเผยซอร์ซโค้ดไว้ใน Github (https://github.com/arduino/ArduinoCore-mbed)

เวอร์ชันก่อนหน้านี้ของ Arduino Core (เช่น Release 1.3.2) ก็รองรับการใช้งานบอร์ด Arduino Portenta H7 (STM32H747) และ Arduino Nano 33 BLE (nRF52840) มาสักระยะหนึ่งแล้ว แต่ในเวอร์ชันใหม่นี้ ก็ได้เพิ่มบอร์ด Arduino Nano RP2040 Connect และ RPi Pico ไว้ในรายการบอร์ดเป้าหมายที่ใช้งานได้

รูป: Arduino Core Mbed — Git Repository

ข้อสังเกต:

  • Arduino Core for Mbed (v2.0.0) ใช้ ARM Mbed OS (v6.9.0) เป็นพื้นฐานในการพัฒนา และสามารถใช้งานร่วมกับระบบปฏิบัติการเวลาจริง (Real-Time Operating System: RTOS) ซึ่งเป็น Open Source RTOS ที่ทาง ARM MBed ได้พัฒนามาอย่างต่อเนื่อง

เริ่มต้นใช้งาน Arduino Core for Mbed / RP2040

ถัดไป เราจะมาลองเขียนโค้ดด้วย Arduino Sketch โดยใช้บอร์ด RPi Pico RP2040 ตามขั้นตอนดังนี้

  1. เปิดใช้งาน Arduino IDE v1.8.x หรือ v2.0 (beta) — ในบทความนี้ได้ลองใช้ซอฟต์แวร์สำหรับ Windows 10
  2. ไปยัง Boards Manager ของ Arduino IDE จากนั้นให้เลือก “Arduino Mbed OS RP2040 Boards” แล้วกดปุ่ม Install และรอจนกว่าจะเสร็จสิ้นขั้นตอนการติดตั้ง
รูป: การเลือกและติดตั้ง Arduino Mbed OS RP2040 Boards
รูป: Arduino IDE (running on Raspberry Pi 4)

เมื่อได้ติดตั้ง “Arduino Mbed OS for RP2040” ได้แล้ว สำหรับ Windows 10 ถ้าลองดูในไดเรกทอรีของผู้ใช้ดังนี้

C:\Users\%USERNAME%\AppData\Local\Arduino15\packages\

จะเห็นโปรแกรมต่าง ๆ ที่ได้มีการติดตั้งในระบบ แบ่งเป็นไดเรกทอรีย่อยดังนี้

  • arduino\hardware\mbed_rp2040\2.0.0\
  • arduino\tools\arm-none-eabi-gcc\
  • arduino\tools\rp2040tools\
  • arduino\tools\dfu-util\
  • arduino\tools\openocd\

ข้อสังเกต:

  • บอร์ด Pico RP2040 มีพอรต์ MicroUSB เพื่อเชื่อมต่อกับคอมพิวเตอร์ของผู้ใช้ เมื่อกดปุ่ม BOOTSEL (ปุ่มสีขาวบนบอร์ด) จะรีเซตการทำงานของชิป RP2040 และเข้าสู่โหมด UF2 USB Bootloader ผู้ใช้สามารถมองเห็นไดรฟ์ใหม่ชื่อ RPI-RP2 (เป็น USB Mass Storage) ถ้าเรามีไฟล์ .uf2 (ที่ได้จากการแปลงไฟล์เฟิร์มแวร์ประเภทอื่น เช่น .elf /.hex / .bin) ก็สามารถนำไปใส่ลงในไดรฟ์ดังกล่าว เพื่อโปรแกรมการทำงานของชิปใหม่ และถือว่าค่อนข้างสะดวก ไม่ต้องใช้อุปกรณ์เสริม
  • โดยทั่วไป บอร์ด Arduino แบบอื่น จะใช้ USB-to-Serial หรือ USB-Native ในการเชื่อมต่อกับคอมพิวเตอร์ของผู้ใช้ แต่จะต้องมีการติดตั้งเฟิร์มแวร์ที่เรียกว่า Arduino-Compatible Serial หรือ USB Bootloader ลงในหน่วยความจำ Flash ของไมโครคอนโทรลเลอร์ก่อน จึงจะพร้อมใช้งานร่วมกับ Arduino IDE และอัปโหลด Arduino Sketch ใหม่ไปยังบอร์ดได้
  • จุดเด่นของไมโครคอนโทรลเลอร์ RP2040 ในประเด็นนี้ก็คือ มี UF2 Bootloader อยู่ในหน่วยความจำ BOOT ROM ของชิป (ถูกใส่มาจากโรงงานผลิตไว้แล้ว) และไม่สูญหายหรือถูกเขียนทับโดยผู้ใช้ (ในเชิงเปรียบเทียบ บอร์ด Arduino Portenta H7 และ Arduino Nano 33 BLE จะต้องมีการติดตั้ง Arduino Bootloader ด้วย)
  • บอร์ด Pico ไม่มีปุ่มกด RESET (มีแต่ปุ่ม BOOTSEL) ดังนั้น เพื่อความสะดวกในการเข้าสู่โหมด UF2 USB Bootloader แนะนำให้ต่อวงจรปุ่มกด RESET (Active-Low) เพิ่มที่ขา RUN (ให้ดูตำแหน่งขาจากแผนผัง Pico Pinout ของบอร์ด Pico) — เมื่อกดปุ่มรีเซต จะได้สถานะลอจิกที่ขาดังกล่าวเป็น LOW แต่ถ้าปล่อยปุ่ม จะได้สถานะเป็น HIGH (3.3V มี pull-up ต่อไว้แล้ว)

เริ่มต้นจะต้องทำให้บอร์ด Pico ที่เชื่อมต่อกับคอมพิวเตอร์ของผู้ใช้ อยู่ในโหมด UF2 Bootloader (RP2 Boot) ก่อน โดยปุ่มกด BOOTSEL ค้างไว้ แล้วกดปุ่มรีเซตแล้วปล่อย และจะมองเห็นไดรฟ์ชื่อ RPI-RP2 ของบอร์ด Pico

ในบทความนี้ได้เลือกใช้โปรแกรม USBDriverTool เพื่อตรวจสอบรายการ USB Driver ของบอร์ด Pico ซึ่งตรงกับ “Nano RP2040 Connect Picoboot IF” (VID=2E8A, PID=0003) ตามรูปตัวอย่าง

หรือดูใน Device Manager

Arduino Sketch: On-Board LED Blink

​​
เริ่มต้นด้วยการสร้าง Arduino Sketch ใหม่ใน Arduino IDE และทดลองเขียนโค้ดตามตัวอย่างต่อไปนี้ เพื่อทำให้ LED บนบอร์ด Pico ซึ่งตรงกับขา GP25 ให้กระพริบได้ (สลับสถานะ 0 / 1 โดยเว้นช่วงเวลา เช่น 500 มิลลิวินาที)

ถ้าลองพิจารณาดูโค้ดตัวอย่างนี้ ก็จะเห็นว่า เหมือนตัวอย่างโค้ด Arduino Blink

#define LED_PIN  (25) // GP25 pin (onboard LED)void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){} // wait until the Serial port is open.
pinMode( LED_PIN, OUTPUT );
}
void loop() {
int next_state = !digitalRead( LED_PIN );
digitalWrite( LED_PIN, next_state ); // update LED output
SerialUSB.println( next_state ); // send to serial output
delay(500);
}
รูป: เลือกบอร์ด Raspberry Pi Pico

เลือกบอร์ด Raspberry Pi Pico ตามตัวอย่าง จากนั้นทำขั้นตอน Verify/Compile เมื่อคอมไพล์โค้ดได้สำเร็จแล้ว ถัดไป ให้กดปุ่มเพื่อทำให้บอร์ด Pico เข้าสู่ UF2 Bootloader โหมดก่อน

จากนั้นจึงทำขั้นตอน Upload ไปยังบอร์ด Pico ในขั้นตอนนี้จะมีการเรียกใช้คำสั่ง rp2040load.exe (เขียนโดยใช้ภาษา Golang) โดยอัตโนมัติ

เมื่อทำอัปโหลดได้สำเร็จ บอร์ด Pico จะถูกรีเซตและเริ่มต้นทำงานใหม่ และเราจะมองเห็น Serial COM Port ของบอร์ด Pico (ในรูปตัวอย่างคือ COM4)

ถ้าลองเปิด Arduino Serial Monitor จะได้รับข้อความส่งจากบอร์ด Pico แสดงสถานะของ LED และการอัปเดตสถานะ

รูป: ข้อความที่ได้รับผ่านทาง Arduino Serial Monitor

ข้อสังเกต:

  • การใช้คำสั่ง SerialUSB.begin( baudrate ); เป็นการเปิดใช้งาน Serial ผ่านทาง USB-CDC ของบอร์ด Pico
  • ถ้าต้องการใช้งาน Hardware Serial ที่ขา GPIO ของบอร์ด Pico เช่น ขา GP0/GP1 (UART0 TX/RX) ก็ให้ใช้คำสั่ง Serial1.begin( baudrate );
  • ถ้ามองเห็นพอร์ต Serial ของ Pico แล้ว ซึ่งเกิดจากการเปิดใช้งาน SerialUSB ใน Arduino Sketch การอัปโหลดในครั้งถัดไป ก็ไม่จำเป็นต้องทำให้บอร์ดเข้าสู่โหมด UF2 Bootloader

เมื่อได้ลองมาทำขั้นตอนนี้แล้ว เราจะเห็นได้ว่า เราสามารถใช้ Arduino IDE เขียนโค้ด Arduino Sketch สำหรับบอร์ด Pico ได้เหมือนบอร์ด Arduino ประเภทอื่น

การทดลองใช้คำสั่ง Picotool แบบ Command Line

ในขั้นตอนนี้ เรามาลองใช้คำสั่ง picotool.exe ที่อยู่ในไดเรกทอรี rp2040tools เพื่ออัปโหลดไฟล์ เช่น .elf หรือ .bin ไปยังบอร์ด Pico โดยจะต้องทำให้อยู่ในโหมด UF2 ก่อน

ถ้าเราได้ทำขั้นตอน Verify/Compile โค้ดของ Arduino Sketch แล้ว จะมีไฟล์ .elf และไฟล์ประเภท .bin / .hex / .uf2 ที่ถูกสร้างขึ้นโดยอัตโนมัติ อยู่ในไดเรกทอรีดังนี้

C:\Users\%USERNAME%\AppData\Local\Temp\arduino_build_XXXXX\

แต่ถ้าเป็นระบบปฏิบัติการอื่น เช่น Linux จะอยู่ในไดเรกทอรี

/tmp/arduino_build_XXXXX

โดยที่ XXXXX จะแตกต่างกันไปแล้วแต่การสุ่มสร้างไดเรกทอรีสำหรับ Arduino Sketch ที่ใช้งานในขณะนั้น

รูป: ตัวอย่างการทำคำสั่ง picotool แบบ Command Line
รูป: ตัวอย่างการทำคำสั่ง picotool เพื่ออัปโหลดไฟล์ .elf ไปยังบอร์ด Pico

อย่างไรก็ตาม การทำคำสั่งแบบ Command Line ในลักษณะนี้ อาจจะไม่ค่อยสะดวกเท่ากับการอัปโหลด Arduino Sketch โดยใช้ Arduino IDE และมีการเรียกใช้คำสั่ง rp2040load.exe แบบ Command Line โดยอัตโนมัติ

การลองใช้คำสั่งของ Mbed Driver สำหรับ Pico

ลองมาดูตัวอย่างโค้ดที่ใช้ DigitalOut ของ Mbed Drivers เพื่อใช้ในการกำหนดสถานะเอาต์พุตสำหรับ LED (onboard)

คำสั่ง delay() ของ Arduino API ที่ใช้สำหรับหน่วงเวลาหน่วยเป็น Milliseconds เป็นการเรียกใช้คำสั่ง ThisThread::sleep_for() ของ Mbed OS API

#include "mbed.h"
using namespace mbed; // for DigitalOut
using namespace rtos; // for ThisThread
using namespace std::chrono; // for milliseconds()
const PinName LED_PIN = p25;
DigitalOut led(LED_PIN);
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){} // wait until the Serial port is open.
}
void loop() {
led = !led; // toggle the LED state
SerialUSB.println( led.read() ); // show the LED state
ThisThread::sleep_for( milliseconds(500) ); // delay for 500 msec
}

รูปแบบการใช้งานและคำสั่งต่าง ๆ ของ Mbed OS API สามารถดูได้จาก https://os.mbed.com/docs/mbed-os/latest/apis/index.html

Arduino Sketch: Push Button


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

การทำงานของวงจรปุ่มกดภายนอกเป็นแบบ Active-Low และนำมาใช้เป็นสัญญาณอินพุต-ดิจิทัลที่ขา GP16 ของบอร์ด Pico และเมื่อมีการกดปุ่มในแต่ละครั้ง จะทำให้ LED (on-board) ที่ขา GP25 สลับสถานะลอจิกหนึ่งครั้ง

#define LED_PIN    (25) // GP25 pin (on-board LED)
#define BUTTON_PIN (16) // GP16 pin
#define STABLE_INTERVAL_MSEC (50)volatile boolean event_detected = false;
volatile uint32_t last_time = 0;
void isr_button() { // ISR function
uint32_t now_ms = millis();
if ( now_ms - last_time >= STABLE_INTERVAL_MSEC ) {
event_detected = (digitalRead(BUTTON_PIN)==0);
}
last_time = now_ms;
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){} // wait until the Serial port is open.
pinMode( LED_PIN, OUTPUT );
attachInterrupt( BUTTON_PIN, isr_button, CHANGE );
pinMode( BUTTON_PIN, INPUT_PULLUP );
}
String strbuf;
uint32_t event_cnt = 0;
void loop() {
if (event_detected) {
event_cnt += 1;
strbuf = "event count: ";
strbuf += event_cnt;
SerialUSB.println( strbuf.c_str() );
digitalWrite( LED_PIN, !digitalRead(LED_PIN) );
event_detected = false;
}
}
รูป: ตัวอย่างข้อความเอาต์พุต

แต่ถ้าจะลองเปลี่ยนมาเขียนตามรูปแบบของ Mbed OS API ก็ทำได้ตามตัวอย่างดังนี้ ยกตัวอย่างเช่น ใช้ DigitalOut สำหรับขาดิจิทัล-เอาต์พุต ใช้ InterruptIn สำหรับขาดิจิทัล-อินพุตและเปิดใช้งานอินเทอร์รัพท์จากภายนอกที่ขาดังกล่าว

#include "mbed.h"
using namespace mbed; // for DigitalOut, InterruptIn
using namespace rtos; // for Kernel
using namespace std::chrono; // for time_point_cast, milliseconds
const PinName LED_PIN = p25; // GP25 pin (on-board LED)
const PinName BUTTON_PIN = p16; // GP16 pin
DigitalOut led(LED_PIN);
InterruptIn button(BUTTON_PIN);
#define STABLE_INTERVAL_MSEC (50)volatile boolean event_detected = false;
volatile uint32_t last_time = 0;
void isr_button() { // ISR function
auto now = Kernel::Clock::now();
auto now_tp = time_point_cast<milliseconds>(now);
uint32_t now_ms = now_tp.time_since_epoch().count();
if ( now_ms - last_time >= STABLE_INTERVAL_MSEC ) {
event_detected = (button.read()==0);
}
last_time = now_ms;
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){} // wait until the Serial port is open.
button.mode( PullUp );
button.rise( isr_button );
button.fall( isr_button );
}
String strbuf;
uint32_t event_cnt = 0;
void loop() {
if (event_detected) {
event_cnt += 1;
strbuf = "event count: ";
strbuf += event_cnt;
SerialUSB.println( strbuf.c_str() );
led = !led;
event_detected = false;
}
}

Ultrasonic Distance Sensor: I/O Polling Method

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

ในการวัดระยะห่างโดยใช้โมดูล HC-SR04 จะต้องสร้างสัญญาณพัลส์ที่ขา Trigger กว้างอย่างน้อย 10 usec และวัดความกว้างของสัญญาณพัลส์ที่ขา Echo จากโมดูลเซ็นเซอร์ เพื่อนำมาคำนวณและแปลงให้เป็นระยะทาง (ใช้ความเร็วเสียงในอากาศประมาณ 343 m/s ในการคำนวณ)

สำหรับการวัดความกว้างของพัลส์ เราจะเริ่มจับเวลาตั้งแต่เกิดขอบขาขึ้น (Rising Edge) ไปจนถึงขอบขาลง (Falling Edge) ของสัญญาณพัลส์ที่ขา Echo

้อสังเกต: โดยทั่วไปแล้ว ถ้าเขียนโค้ดสำหรับ Arduino เราก็สามารถใช้คำสั่ง pulseIn() ในการวัดความกว้างของพัลส์ได้ แต่ Arduino Mbed OS v2.0.0 ยังไม่ได้สร้างคำสั่งนี้ไว้สำหรับ RP2040

กำหนดความกว้างสูงสุดของพัลส์ไว้ที่ 50000 ไมโครวินาที ถ้ามากกว่าค่านี้ ให้ถือว่า เกินระยะห่างที่ได้กำหนดไว้

#define TRIG_PIN      (15)
#define ECHO_PIN (14)
#define MAX_DURATION_US (50000)
#define TIME_TO_DISTANCE(t_us) ((343*100*t_us/2)/1000000)
uint32_t duration_us;
char sbuf[64];
uint32_t ping( ) {
// generate a short pulse on the TRIG pin
digitalWrite( TRIG_PIN, HIGH );
delayMicroseconds(20);
digitalWrite( TRIG_PIN, LOW );
// wait until the ECHO pin goes high
while( digitalRead(ECHO_PIN) == 0 ){} // busy-wait
uint32_t start_time_us = micros();
// wait until the ECHO pin goes low
while( digitalRead(ECHO_PIN) == 1 ){} // busy-wait
return (micros() - start_time_us);
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){}
pinMode( TRIG_PIN, OUTPUT );
digitalWrite( TRIG_PIN, LOW );
}
void loop() {
duration_us = ping();
if (duration_us <= MAX_DURATION_US) {
uint32_t distance_cm = TIME_TO_DISTANCE(duration_us);
sprintf( sbuf, "Duration: %lu us, distance: %lu cm",
duration_us, distance_cm );
} else {
sprintf( sbuf, "Duration: %lu us, distance: out of range",
duration_us );
}
SerialUSB.println( sbuf );
delay(250);
}

แต่ถ้าจะลองเขียนโค้ดใหม่ โดยใช้ Mbed Drivers เช่น DigitalOut และ DigitalIn สำหรับขา GPIO และใช้ Timer สำหรับการจับเวลา ก็มีตัวอย่างดังนี้

#include "mbed.h"
using namespace mbed;
#define MAX_DURATION_US (50000)
#define TIME_TO_DISTANCE(t_us) ((343*100*t_us/2)/1000000)
const PinName TRIG_PIN = p15, ECHO_PIN = p14;
DigitalOut trig(TRIG_PIN);
DigitalIn echo(ECHO_PIN);
Timer timer;
uint32_t duration_us;
char sbuf[64];
uint32_t ping( ) {
// generate a short pulse on the TRIG pin
trig = 1;
wait_us(20);
trig = 0;
// wait until the ECHO pin goes high
while( echo == 0 ){} // busy-wait
timer.reset(); // reset timer
// wait until the ECHO pin goes low
while( echo == 1 ){} // busy-wait
return timer.read_us(); // read the elapsed time
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB);
trig = 0;
timer.start();
}
void loop() {
duration_us = ping();
if (duration_us <= MAX_DURATION_US) {
uint32_t distance_cm = TIME_TO_DISTANCE(duration_us);
sprintf( sbuf, "Duration: %lu us, distance: %lu cm",
duration_us, distance_cm );
} else {
sprintf( sbuf, "Duration: %lu us, distance: out of range",
duration_us );
}
SerialUSB.println( sbuf );
delay(250);
}

Ultrasonic Distance Sensor: Interrupt Method

ลองเปลี่ยนมาใช้วิธีอินเทอร์รัพท์ (Interrupt-Driven Method) เพื่อตรวจสอบการเปลี่ยนแปลงของสถานะลอจิกที่ขาอินพุต ECHO

ในการใช้อินเทอร์รัพท์สำหรับขา GPIO จะต้องสร้างฟังก์ชันที่ทำหน้าที่เป็น ISR เพื่อตอบสนองต่อเหตุการณ์ที่เกิดขึ้น แบ่งเป็น 2 กรณีคือ ขอบขาขึ้น (Rising Edge) และขอบขาลง (Falling Edge) และมีการวัดความกว้างของสัญญาณพัลส์ที่ขา Echo เพื่อนำมาคำนวณและแปลงให้เป็นระยะทาง

#define LED_PIN   (25)
#define TRIG_PIN (15)
#define ECHO_PIN (14)
#define MAX_DURATION_US (50000)
#define TIME_TO_DISTANCE(t_us) ((343*100*t_us/2)/1000000)
volatile uint32_t duration_us, start_time_us;
void isr_change() {
uint32_t now = micros();
if ( digitalRead(ECHO_PIN)==1 ) { // rising edge
start_time_us = now;
} else { // falling edge
duration_us = now - start_time_us;
}
}
uint32_t ping() {
if ( digitalRead(ECHO_PIN)==0 ) {
// generate a short pulse on TRIG pin
digitalWrite( TRIG_PIN, HIGH );
delayMicroseconds(20);
digitalWrite( TRIG_PIN, LOW );
duration_us = 0;
}
delay(100);
return duration_us;
}
void setup() {
SerialUSB.begin(115200);
//while(!SerialUSB){}
pinMode( LED_PIN, OUTPUT );
pinMode( TRIG_PIN, OUTPUT );
digitalWrite( TRIG_PIN, LOW );
// enable external interrupt on ECHO pin
attachInterrupt( ECHO_PIN, isr_change, CHANGE );
pinMode( ECHO_PIN, INPUT );
}
char sbuf[64];void loop() {
digitalWrite( LED_PIN, HIGH );
uint32_t duration = ping();
if (duration <= MAX_DURATION_US) {
uint32_t distance_cm = TIME_TO_DISTANCE(duration);
sprintf( sbuf, "Duration: %lu us, distance: %lu cm",
duration, distance_cm );
} else {
sprintf( sbuf, "Duration: %lu us, distance: out of range",
duration );
}
SerialUSB.println( sbuf );
digitalWrite( LED_PIN, LOW );
delay(250);
}
รูป: ตัวอย่างข้อความเอาต์พุต

แต่ถ้าจะลองเขียนโค้ดโดยใช้ Mbed Drivers เช่น DigitalOut, InterruptIn และ Timer สำหรับการจับเวลา ก็มีตัวอย่างดังนี้

#include "mbed.h"
using namespace mbed;
#define MAX_DURATION_US (50000)
#define TIME_TO_DISTANCE(t_us) ((343*100*t_us/2)/1000000)
const PinName LED_PIN = p25, TRIG_PIN = p15, ECHO_PIN = p14;
DigitalOut led(LED_PIN);
DigitalOut trig(TRIG_PIN);
InterruptIn echo(ECHO_PIN, PullNone);
Timer timer;
volatile uint32_t duration_us = 0;void isr_rise() {
timer.reset(); // reset timer
}
void isr_fall() {
duration_us = timer.read_us(); // read the elapsed time
}
uint32_t ping() {
if (echo == 0) {
// generate a short pulse on TRIG pin
trig = 1;
wait_us(20);
trig = 0;
duration_us = 0;
echo.rise( isr_rise );
echo.fall( isr_fall );
}
delay(100);
echo.rise( NULL );
echo.fall( NULL );
return duration_us;
}
void setup() {
SerialUSB.begin(115200);
//while(!SerialUSB){}
trig = 0;
timer.start(); // start timer
}
char sbuf[64];void loop() {
led = 1;
uint32_t duration = ping();
if (duration <= MAX_DURATION_US) {
uint32_t distance_cm = TIME_TO_DISTANCE(duration);
sprintf( sbuf, "Duration: %lu us, distance: %lu cm",
duration, distance_cm );
} else {
sprintf( sbuf, "Duration: %lu us, distance: out of range",
duration );
}
SerialUSB.println( sbuf );
led = 0;
delay(250);
}

Ultrasonic Class

ลองมาสร้างคลาส (C++ Class) และลองใช้ Mbed OS Thread ในการทำงานสำหรับโมดูลเซ็นเซอร์ Ultrasonic HC-SR04 ตามตัวอย่างดังนี้

#include "mbed.h"using namespace mbed;
using namespace rtos;
using namespace std::chrono;
#define MAX_DURATION_US (50000)
#define TIME_TO_DISTANCE(t_us) ((343*100*t_us/2)/1000000)
class Ultrasonic {
public:
Ultrasonic(
PinName trig_pin, PinName echo_pin,
uint32_t interval_ms = 500,
osPriority threadPriority = osPriorityNormal
) :
_trig(trig_pin), _echo(echo_pin),
_threadPriority(threadPriority),
_thread( _threadPriority, OS_STACK_SIZE ),
_interval_ms(interval_ms), _running(false),
_duration_us(0),_distance_cm(0)
{
_timer.start();
_echo.rise( callback(this, &Ultrasonic::isr_rise) );
_echo.fall( callback(this, &Ultrasonic::isr_fall) );
}
void begin() {
if (!_running) {
_running = true;
_thread.start( callback(this, &Ultrasonic::threadFunc) );
}
}
uint32_t getDistance() {
return _distance_cm;
}
~Ultrasonic() {
if (_running) { _thread.terminate(); }
}
private:
DigitalOut _trig;
InterruptIn _echo;
Thread _thread;
Timer _timer;
uint32_t _duration_us;
uint32_t _distance_cm;
uint32_t _interval_ms;
osPriority _threadPriority;
boolean _running;
void isr_rise() {
_timer.reset();
}
void isr_fall() {
_duration_us = _timer.read_us();
if (_duration_us <= MAX_DURATION_US) {
_distance_cm = TIME_TO_DISTANCE(_duration_us);
} else {
_distance_cm = (uint32_t)(-1);
}
}
void threadFunc() {
while(1) {
if ( _echo == 0 ) {
_trig = 1;
wait_us(20);
_trig = 0;
_distance_cm = 0;
}
ThisThread::sleep_for( milliseconds(this->_interval_ms) );
}
}
};

และส่วนที่เรียกใช้งานคลาสดังกล่าว เพื่อใช้งานกับโมดูล HC-SR04 จำนวน 1 ชุด

const PinName LED_PIN = p25, TRIG_PIN = p15, ECHO_PIN = p14;
Ultrasonic *sensor;

void setup() {
SerialUSB.begin(115200);
while(!SerialUSB){}
pinMode(LED_PIN, OUTPUT );
sensor = new Ultrasonic(TRIG_PIN, ECHO_PIN, 200);
sensor->begin();
delay(100);
}
char sbuf[64];void loop() {
digitalWrite(LED_PIN, 1);
sprintf( sbuf, "Distance: %ld cm", (long)sensor->getDistance() );
SerialUSB.println( sbuf );
digitalWrite(LED_PIN, 0);
delay(250);
}
รูป: ตัวอย่างการทดลองโดยใช้อุปกรณ์จริง
รูป: โค้ดบางส่วนที่สาธิตการใช้งานโมดูลเซ็นเซอร์จำนวน 2 ชุด
รูป: ตัวอย่างข้อความเอาต์พุตเมื่อใช้โมดูลเซ็นเซอร์จำนวน 2 ชุด

Multi-Threading LED Blink

อีกสักหนึ่งตัวอย่างที่สาธิตการเขียนโค้ด Arduino Sketch โดยใช้ Mbed OS API และแบ่งการทำงานของโปรแกรมออกเป็นหน่วยย่อยที่เรียกว่า “เธรด” (Thread) เช่น จำนวน 3 เธรด และให้แต่ละเธรดทำหน้าที่เปลี่ยนสถานะเอาต์พุตสำหรับ LED ที่เกี่ยวข้อง (เลือกใช้ขา GP11 / GP12/ GP13 สำหรับเอาต์พุต ตามลำดับ)

นอกจากนั้นยังมีการใช้งาน Mutex เพื่อป้องกันการเข้าถึงทรัพยากรที่ใช้ร่วมกันระหว่างเธรด โดยให้มีเพียงเธรดในแต่ละช่วงเวลาเท่านั้นที่เข้าถึงได้ เช่น การส่งข้อความออกทาง SerialUSB เป็นต้น

#include "mbed.h"
#include "stdio.h"
using namespace mbed;
using namespace rtos;
using namespace std::chrono;
#define stack_size (OS_STACK_SIZE) // use the default stack size
#define NUM_LEDS (3)
typedef enum { LED_ON=0, LED_OFF=1 } led_state_t;const PinName LED_PINS[ NUM_LEDS ] = {p11,p12,p13};
DigitalOut *leds[ NUM_LEDS ];
Thread *threads[ NUM_LEDS ];
Mutex mutex;
char sbuf[32];
void thread_func ( void *arg ) { // thread-entry function
uint32_t id = (uint32_t) arg;
DigitalOut *led = leds[ id ];
while (1) {
mutex.lock(); // acquire the mutex
*led = LED_ON; // turn on the associated LED
sprintf( sbuf, "Thread id=%lu active", id );
SerialUSB.println( sbuf );
SerialUSB.flush();
ThisThread::sleep_for( 1000ms );
*led = LED_OFF; // turn off the associated LED
mutex.unlock(); // release the mutex
}
}
void setup() {
SerialUSB.begin(115200);
while(!SerialUSB);
sprintf( sbuf, "CPU clock: %lu MHz", SystemCoreClock/1000000 );
Serial.println( sbuf );
Serial.println( "Create and start threads..." );
Serial.flush();
for ( int i=0; i < NUM_LEDS; i++ ) {
// create a DigitalOut instance for an LED output pin
leds[i] = new DigitalOut( LED_PINS[i] );
leds[i]->write( LED_OFF );
// create a Thread instance and start the thread
threads[i] = new Thread( osPriorityNormal, stack_size );
threads[i]->start( callback(thread_func, (void *)i) );
}
}
void loop() {}
รูป: ตัวอย่างข้อความเอาต์พุตที่ได้รับผ่านทาง SerialUSB

ข้อสังเกต: จากข้อความบรรทัดแรกแสดงให้เห็นว่า Arduino Pico ทำงานด้วยความถี่ 120 MHz

รูป: ตัวอย่างการต่อวงจรทดลองโดยใช้โมดูล RGB LED (Active-Low)

ถ้าทดสอบการทำงานของโค้ดโดยใช้อุปกรณ์ฮาร์ดแวร์จริง จะเห็นว่า RGB LED จะมีแสงสีแดง น้ำเงิน และเขียว ในแต่ละช่วงเวลา (ประมาณ 1 วินาที)

กล่าวสรุป


ในบทความ เราได้เรียนรู้วิธีการติดตั้งและใช้งานซอฟต์แวร์สำหรับ Windows 10 เพื่อนำมาใช้ในการเขียนโค้ด Arduino Sketch สำหรับบอร์ด Raspberry Pi Pico (RP2040) พร้อมโค้ดสาธิตการทำงานในเบื้องต้นที่ใช้ Arduino API (บางคำสั่งเท่านั้น) และเปรียบเทียบกับการใช้งาน Mbed OS API … หวังว่าจะเป็นประโยชน์ต่อท่านที่สนใจครับ

--

--