การเขียนโปรแกรม Arduino สำหรับบอร์ดไมโครคอนโทรลเลอร์ Seeeduino XIAO (SAMD21)#


Seeed Studio XIAO#

บทความนี้กล่าวถึง การใช้งานและการเขียนโปรแกรม Arduino สำหรับบอร์ดไมโครคอนโทรลเลอร์ในตระกูล XIAO ที่ใช้ชิป SAMD21

บอร์ดในตระกูล XIAO ของบริษัท Seeed Studio จากประเทศจีน มีขนาดเล็ก และมีการเลือกใช้ชิปไมโครคอนโทรลเลอร์ 32 บิต หลายแบบ เช่น

  • SAMD21 (Arm Cortex-M0+)
  • RP2040 (Arm Cortex-M0+, Dual-Core)
  • nRF52840 (Arm Cortex-M4F)
  • ESP32-C3 (RISC-V)
  • ESP32-S3 (Xtensa LX7)

บอร์ดในตระกูล XIAO มีลักษณะเหมือนกันคือ

  • บอร์ดมีขนาดเล็ก (Small Form Factor) มีขนาดโดยประมาณ 22 x 18 mm.
  • สามารถนำไปบัดกรีขา Pin Header และเสียบขาลงบนเบรดบอร์ดได้ง่าย
  • มีคอนเนกเตอร์สำหรับ USB แบบ Type-C
  • ไม่มีชิป USB-to-Serial Bridge เนื่องจากชิปไมโครคอนโทรลเลอร์ตามที่ได้กล่าวไป รองรับการใช้งาน Native USB 2.0
  • บอร์ดบางรุ่น มีวงจรชาร์จแบตเตอรีลิเธียม (Single-cell Lithium Battery Charger) บางรุ่นก็มี RGB LED อยู่บนบอร์ดด้วย

รูป: แผนผังแสดงตำแหน่งของขาต่าง ๆ บนบอร์ด XIAO SAMD21 (Source: SeeedStudio)

บอร์ดรุ่นแรกในตระกูล XIAO คือ Seeeduino XIAO SAMD21 มีคุณสมบัติดังนี้

  • ทางบริษัทได้เปิดตัวบอร์ดในช่วงปลายปี ค.ศ. 2019
    • แต่มีข้อสังเกตว่า ชิป SAMD21 มีการใช้งานมาตั้งแต่ปี 2012 และหลายปีที่ผ่านมา ก็มีบอร์ดไมโครคอนโทรลเลอร์ที่ใช้ชิปดังกล่าวออกมาสู่ตลาด เช่น บอร์ด Arduino MKR Family, Adafruit Feather M0, Adafruit Circuit Playground เป็นต้น และชิปรุ่นนี้ได้รับความนิยมน้อยลงในปัจจุบันเมื่อเปรียบเทียบกับชิปไมโครคอนโทรลเลอร์รุ่นใหม่ ๆ
  • มีราคาประมาณ $US 5 (ประมาณ THB 250.00 จากการสำรวจสินค้าบนเว็บไซต์ในประเทศไทย)
  • ใช้ชิปไมโครคอนโทรลเลอร์ Atmel/Microchip AT-SAMD21G18-MU
    • ภายในมีตัวประมวลผล 32-bit ARM Cortex M0+ (48MHz)
    • มีหน่วยความจำภายใน 256KB Flash และ 32KB SRAM
    • เน้นการทำงานแบบใช้พลังงานต่ำ (Low-Power Application)
  • มีขา GPIO ใช้เป็นขาดิจิทัลได้ ทำงานที่ระดับแรงดันสำหรับลอจิกไม่เกิน VCC=3.3V
    • Digital I/O: D0 ~ D10 pins
  • ภายในชิปมีวงจร 12-bit, 350ksps ADC (Analog-to-Digital Converter) และ 10-bit, 350 ksps DAC (Digital-to-Analog Converter)
    • ADC Input: A0 ~ A10 pins รับอินพุตในช่วง 0V ~ 3.3V และให้ค่าตัวเลขในช่วง 0 ~ 4095
    • DAC Output จำนวน 1 ขา (A0/D0 pin) ใช้ค่าตัวเลขในช่วง 0 ~ 1023 และมีแรงดันไฟฟ้าเอาต์พุตในช่วง 0V ~ 3.3V
  • มีวงจรแปลงและควบคุมระดับแรงดันไฟฟ้าจากขา 5V (VIN) ให้เป็น 3.3V
    • ใช้ไอซี XC6206P33MR LDO Voltage Regulator
  • มีขาเชื่อมต่อกับวงจรภายนอกสำหรับ I2C, SPI และ SPI อย่างละหนึ่งชุด ได้แก่
    • I2C: D5=SCL, D4=SDA pins
    • UART: D6=TX, D7=RX pins
    • SPI: D7=SS, D8=SCK, D9=MISO, D10=MOSI pins
  • มี LED บนบอร์ดดังนี้
    • Power LED (Green)
    • User LED (Orange): D13 pin
    • Tx & Rx LEDs (Blue): D11 & D12 pins ถ้ามีการรับหรือส่งข้อมูลผ่าน Serial จะเห็นสังเกตเห็นการกระพริบของ LED สีน้ำเงิน
  • มีขาอยู่ด้านล่างของบอร์ดเป็นแบบ Soldering Pads สำหรับ SWD Interface
    • มี 4 จุด ได้แก่ SWCLK, SWDIO, RESET, GND ตามลำดับ
  • มีการติดตั้งเฟิร์มแวร์ Arduino-Compatible USB-Serial / UF2 Bootloader มาให้แล้ว
    • ใช้วิธีลากไฟล์เฟิร์มแวร์ .uf2 ไปยังไดร์ฟ (ชื่อ Arduino) เพื่ออัปโหลดเฟิร์มแวร์
    • ถ้าเป็นไฟล์ .hex / .bin จะต้องมีการแปลงให้เป็นไฟล์ .uf2 ก่อนนำไปใช้
  • ข้อจำกัดในการใช้งานบอร์ด XIAO มีดังนี้
    • เหมาะสำหรับผู้ใช้ต้องการใช้บอร์ดขนาดเล็ก และมีจำนวนขา I/O ไม่มาก
    • ไม่มีวงจร WiFi / BLE สำหรับการสื่อสารข้อมูลแบบไร้สาย
    • ไม่มีปุ่มกด RESET อยู่บนบอร์ด หากต้องการรีเซต หรือ เข้าสู่โหมด Bootloader จะต้องใช้ปลายสายไฟสองด้านสัมผัสที่ Soldering Pads บนบอร์ด สำหรับขา RESET และ GND การสัมผัสสองครั้งในระยะเวลาสั้น ๆ เป็นเหมือนการกดปุ่มรีเซตแบบ Double Click ซึ่งจะทำให้ชิปไมโครคอนโทรลเลอร์เข้าสู่ Bootloader Mode
  • เมื่ออยู่ในโหมด Bootloader จะมองเห็นพอร์ต Serial ได้เช่นกัน และถ้ามีการใช้ SerialUSB ในการเขียนโค้ด Arduino เพื่อเปิดใช้งาน USB CDC ก็จะมองเห็นพอร์ต Serial ได้ แต่มีหมายเลขพอร์ตต่างกันได้

รูป: ตำแหน่งของ Soldering Pads ที่อยู่ด้านล่าง (Bottom Side) ของบอร์ด XIAO เวอร์ชันเก่า และ เวอร์ชันใหม่ (Source: SeeedStudio)

รูป: การรีเซตบอร์ด หรือการทำให้เข้าสู่โหมด Bootloader โดยการเชื่อมต่อระหว่างขา Reset Pad และ GND Pad (Source: SeeedStudio)

 

รูป: การมองเห็นไดร์ฟในระบบปฏิบัติการ Windows 10 เมื่อเชื่อมต่อกับบอร์ด XIAO ที่อยู่ในโหมด UF2 Bootloader

ตัวเลือกสำหรับการเขียนโปรแกรมได้แก่

 


การเขียนโปรแกรมด้วย Arduino IDE#

ถัดไปเป็นขั้นตอนการติดตั้ง Arduino Core for SAMD21 / D51 สำหรับบอร์ด SeeedStudio XIAO เพื่อนำมาใช้กับซอฟต์แวร์ Arduino IDE v2.0.0

เปิดใช้งาน Arduino IDE v2.0 แล้วไปที่เมนู File > Preferences เพิ่มรายการ URL ลงในช่องข้อความ "Additional Boards Manager URLs" ดังต่อไปนี้ แล้วกดปุ่ม OK

https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json

จากนั้นคลิกเลือกไอคอนทางซ้ายมือสำหรับ BOARD MANAGER แล้วค้นหาด้วยคำว่า "Seeed SAMD" แล้วคลิกเลือกติดตั้งเวอร์ชันล่าสุด (ทดลองใช้ v1.8.3) โดยกดปุ่ม INSTALL

รูป: การติดตั้ง Arduino Core สำหรับ SeeedStudio XIAO SAMD21

 

ถัดไปให้เชื่อมต่อระหว่างคอมพิวเตอร์ผู้ใช้กับบอร์ด XIAO ด้วยสาย USB หากเป็นการใช้งานครั้งแรก แนะนำให้เริ่มต้นด้วยการทำงานบอร์ด XIAO เข้าสู่โหมด Bootloader ตามวิธีการที่ได้กล่าวถึงไปแล้ว จากนั้นจะมองเห็นหมายเลขพอร์ตอนุกรม และเลือกบอร์ด Seeeduino XIAO

รูป: การเลือกหมายเลขพอร์ตที่เชื่อมต่อกับ XIAO SAMD21 ใน Arduino IDE v2.0

 

ในส่วนถัดไปของบทความนี้ มีตัวอย่างโค้ด Arduino Sketches สาธิตและทดลองใช้งานบอร์ด XIAO มีดังนี้

  • Digital Output / PWM Output: สร้างสัญญาณเอาต์พุตแบบดิจิทัล และ PWM สำหรับวงจร LEDs
  • Push Button & Digital Input Sampling: อ่านอินพุตดิจิทัล และตรวจสอบสถานะการกดปุ่ม
  • Analog Input Reading (ADC): อ่านอินพุตแอนะล็อกโดยใช้วงจร ADC ภายในชิป SAMD21
  • Analog Output (DAC) and Analog Input (ADC): สร้างสัญญาณแอนะล็อกเอาต์พุตโดยใช้วงจร DAC ภายในชิป และอ่านสัญญาณดังกล่าวเป็นอินพุตด้วย ADC
  • I2C Master & BH1750 Light Sensor Reading: อ่านข้อมูลจากโมดูลเซนเซอร์วัดความเข้มแสง BH1750 และเชื่อมต่อด้วยบัส I2C
  • External Interrupt & Rotary Encoder Switch: อ่านค่าอินพุตดิจิทัลและตรวจสอบการหมุนปุ่มของโมดูล Rotary Encoder เพื่อเพิ่มหรือลดค่าตัวนับ และสาธิตการใช้งานอินเทอร์รัพท์ภายนอก
  • HC-SR04P Ultrasonic Distance Sensor: อ่านค่าระยะห่างจากวัตถุกีดขวางโดยใช้โมดูลเซนเซอร์อัลตราโซนิก HC-SR04P

 


โค้ดตัวอย่าง: Digital Output / PWM Output#

โค้ดตัวอย่างแรกเป็นการทดสอบการทำงานของบอร์ด Seeeduino XIAO โดยทำให้ LED 2 ดวง (ขา D11 และ D12) ที่อยู่บนบอร์ด เกิดการกระพริบหลายครั้งในช่วงเวลาหนึ่ง โดยใช้คำสั่ง digitalWrite() กำหนดสถานะเอาต์พุต และมีการสร้างสัญญาณ PWM โดยใช้คำสั่ง analogWrite(...) ของ Arduino API เพื่อใช้ในการปรับความสว่างของ LED (ขา D12) และมีการเปลี่ยนค่าไปทุก ๆ 4 มิลลิวินาที

ในโค้ดตัวอย่างมีการใช้คำสั่ง analogWriteResolution(...) เพื่อกำหนดจำนวนบิตที่ต้องการใช้และอยู่ในช่วง 8 .. 12 บิต จำนวนบิตนี้จะเป็นตัวกำหนดช่วงของค่าตัวเลขที่จะนำไปใช้กับคำสั่ง analogWrite(...) ถ้าเป็น 12 บิต ก็ใช้ตัวเลขในช่วง 0 .. 4095 สำหรับการตั้งค่า PWM Duty Cycle แต่ถ้าเป็น 8 บิต ก็จะมีค่าอยู่ในช่วง 0 .. 255 เป็นต้น

ในตัวอย่างนี้ได้กำหนดให้ค่า PWM Duty Cycle เพิ่มขึ้นไปจนถึงค่าสูงสุด 4095 แล้วลดลงไปจนถึง 0 ดังนั้นจะเห็นว่า LED มีความสว่างเพิ่มขึ้นจนสูงสุดแล้วจึงลดลง แล้วเกิดขึ้นซ้ำไปเรื่อย ๆ

Arduino Sketch: Dual-LED Blink & PWM-based LED Fading

// Target board: Seeeduino XIAO SAMD21
#define PWM_LED_PIN  (13)  // D13 pin (onboard Orange USER LED, active-low)
#define TX_LED_PIN   (12)  // D12 pin (onboard Blue TX LED, activew-low)
#define RX_LED_PIN   (11)  // D11 pin (onboard Blue RX LED, active-low)

#define LED_ON          (0)
#define LED_OFF         (1)
#define TIMEOUT_MS      (4000)
#define PWM_RESOLUTION  (12)

const int COUNTER_MAX = (1<<PWM_RESOLUTION);
int counter = COUNTER_MAX-1;
uint32_t ts; // timestamp

void toggle_leds( int n=10 ) {
   int state = 1;
   for ( int i=0; i < n; i++ ) {
     state = !state;
     digitalWrite( TX_LED_PIN, !state );
     digitalWrite( RX_LED_PIN, state  );
     delay( 100 );
   }
   digitalWrite( TX_LED_PIN, LED_OFF ); 
   digitalWrite( RX_LED_PIN, LED_OFF );  
}

String getSketchName( const char *fullpath ) {
  String str = fullpath;
  int pos = str.lastIndexOf("\\");
  if (pos >= 0) {
    return str.substring(pos+1, str.length());
  }
  return str;
}

// Note: Serial is the same as SerialUSB.
void setup() {
   // Start the SerialUSB interface
   // Note: Serial is the same as SerialUSB.
   Serial.begin( 1000000);  // can be any value (ignored)
   // Wait for Arduino Serial Monitor to open.
   while ( !Serial );
   Serial.println( F("SAMD21 - Seeeduino XIAO board.") );
   Serial.print( F("Arduino Sketch: ") );
   Serial.println( getSketchName( __FILE__ ) );
   Serial.println( F("Build time: " __DATE__ " " __TIME__ ) );
   Serial.flush();
   // Show the CPU clock frequency (in MHz)
   Serial.printf( "CPU clock: %lu MHz\r\n", 
                  (uint32_t)(SystemCoreClock/1e6) );
   Serial.flush();

   // Set pin directions for LED outputs
   pinMode( TX_LED_PIN, OUTPUT );
   pinMode( RX_LED_PIN, OUTPUT );
   pinMode( PWM_LED_PIN, OUTPUT );
   // Toggle onboard TX/RX LEDs.
   toggle_leds( 20 );

   // Set PWM resolution bits
   analogWriteResolution( PWM_RESOLUTION ); 
   analogWrite( PWM_LED_PIN, counter );
   ts = millis();
}

#define PWM_UPDATE_INTERVAL_MS  (4)

void loop() {
  uint32_t t_now = millis();
  if ( (t_now - ts) >= PWM_UPDATE_INTERVAL_MS ) { 
     ts = t_now;      // Update the last PWM update timestamp
     counter += 8; // Increment counter
     if ( counter >= (2*COUNTER_MAX) ) {
       counter = 0;
     }
     int brightness = (counter >= COUNTER_MAX) ?  
                 (2*COUNTER_MAX - 1 - counter) : counter; 
     // Update the PWM duty cycle
     analogWrite( PWM_LED_PIN, brightness );  
  }
  yield();
}

 


โค้ดตัวอย่าง: Push Button & Digital Input Sampling#

ตัวอย่างโค้ดถัดไปสาธิตการตรวจสอบการเปลี่ยนแปลงสถานะของอินพุต-ดิจิทัลที่ได้จากการต่อวงจรปุ่มกดภายนอก (Push Button) และทำงานแบบ Active-Low (เปิดใช้งานตัวต้านทานภายในแบบ Internal PullUp) โดยเลือกใช้ขา D2 ของบอร์ด XIAO

การอ่านค่าบิตจากขาอินพุต จะใช้คำสั่ง digitalRead(...) ทุก ๆ 5 มิลลิวินาที แล้วนำไปเก็บไว้ในตัวแปร btn_bits ขนาด 8 บิต โดยการเลื่อนบิตข้อมูลเดิมไปทางซ้ายหนึ่งตำแหน่ง และเขียนค่าบิตใหม่ลงในตำแหน่งบิต LSB จากนั้นจะมีการตรวจสอบค่าของตัวแปร ดูว่ามีการเปลี่ยนแปลงจากค่าเริ่มต้นคือ 0xFF ไปเป็น 0xF0 แล้วหรือไม่ ถ้ามีค่าดังกล่าว ก็หมายความว่า จากการอ่านพุตหลายครั้งตามลำดับ ได้ค่าลอจิกเป็น 0 (Low) ซึ่งเกิดจากการกดปุ่ม

#define BTN_PIN  (2)   // use D2 pin
#define LED_PIN  (12)  // use D12 pin (onboard)

void setup() {
  Serial.begin(1000000);
  while (!Serial);
  pinMode( LED_PIN, OUTPUT );
  pinMode( BTN_PIN, INPUT_PULLUP );
}

#define SAMPLING_MSEC  (5)

void loop() {
  static uint32_t ts_saved = 0, ts_now;
  static uint32_t ts_last_event = 0;
  static uint32_t btn_count = 0;
  static uint8_t  btn_bits = 0xff;

  ts_now = millis();
  if ( (ts_now - ts_saved) >= SAMPLING_MSEC ) { // periodic input sampling
    // Save the timestamp for the next periodic sampling.
    ts_saved = ts_now; 
    // Update input bit patterns.
    btn_bits = (btn_bits<<1) | digitalRead(BTN_PIN);
    if ( btn_bits == 0xF0) { // A falling edge was detected.
      // Toggle the LED status.
      digitalWrite( LED_PIN, !digitalRead( LED_PIN) );
      Serial.printf( "Button press: #%lu (%lu msec)\r\n", 
                      ++btn_count, (ts_now-ts_last_event) );
      // Update the timestamp of the last LED toggle action.
      ts_last_event = ts_now;
    }
  }
}

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

 


โค้ดตัวอย่าง: Analog Input Reading (ADC)#

ตัวอย่างถัดไปเป็นการเขียนโค้ด เพื่ออ่านค่าสัญญาณแอนะล็อกด้วยคำสั่ง analogRead(...) ของ Arduino API จำนวน 3 ช่อง ตามลำดับ โดยเลือกใช้ขา A1, A2, A3 และเลือกใช้ขนาดข้อมูล 12 บิต (0..4095) สำหรับการแปลงค่าโดยวงจร ADC ภายในชิป SAMD21

Arduino Sketch: 3-Channel Analog Input Readings

const int SENSOR_PINS[] = {A1,A2, A3}; // Analog input pins
const size_t NUM_PINS = sizeof(SENSOR_PINS)/sizeof(int);
void setup() {
   Serial.begin(1000000);
   while (!Serial); 
   delay(100);
   analogReadResolution(12);
}

void loop() {
  static uint16_t values[ NUM_PINS ];
  // Read analog input channels
  for ( int i=0; i < NUM_PINS; i++ ) {
    values[i] = analogRead( SENSOR_PINS[i] );
  }
  // Send analog input values to SerialUSB
  for ( int i=0; i < NUM_PINS; i++ ) {
    Serial.printf( "AIN%d: %u%s", i, values[i], 
                   (i < (NUM_PINS-1) ? "," : "\r\n") );
  }
  Serial.flush();
  delay(100);
}

ในการป้อนสัญญาณแอนะล็อกเป็นอินพุต อาจใช้วิธีต่อวงจรแบ่งแรงดัน (Voltage Divider) โดยใช้ตัวต้านทาน 4 ตัว นำมาต่ออนุกรมกัน แล้วเลือกวัดแรงดันไฟฟ้าที่อยู่ระหว่าง 0V กับ 3.3V ซึ่งมี 3 ตำแหน่งที่แตกต่างกัน และควรจะได้ค่าตัวเลขคงที่ แต่ในทางปฏิบัติจะมีความคลาดเคลื่อนเกิดขึ้นในการวัดค่าด้วย ADC

รูป: วงจรแบ่งแรงดันที่ใช้ตัวต้านทาน 100Ω จำนวน 3 ตัว และ 4.7kΩ อีก 1 ตัว นำมาต่ออนุกรมกัน

รูป: การแสดงกราฟจากข้อมูลที่อ่านจากขาแอนะล็อก 3 ช่องอินพุต (จากวงจรแบ่งแรงดัน) โดยใช้ Arduino Serial Plotter

หากมีโมดูลเซนเซอร์ที่ให้เอาต์พุตแบบแอนะล็อก และมีระดับแรงดันไฟฟ้าอยู่ในช่วง 0V..3.3V ก็สามารถนำมาใช้ได้เช่นกัน เช่น TEMT6000 Analog Light Sensor ซึ่งให้แรงดันเอาต์พุตขึ้นอยู่กับสภาพแสงแวดล้อม

รูป: โมดูลเซนเซอร์แสง TEMT6000 สำหรับทดลองอ่านค่าแอนะล็อก 3 ช่องสัญญาณ

รูป: การแสดงกราฟจากข้อมูลที่อ่านจากขาแอนะล็อก 3 ช่องอินพุต (จากโมดูลเซนเซอร์แสง TEMT6000)

 


โค้ดตัวอย่าง: Analog Output (DAC) and Analog Input (ADC)#

ตัวอย่างถัดไปเป็นตัวอย่างการเขียนโค้ดเพื่อสร้างสัญญาณแอนะล็อก โดยใช้วงจร DAC ที่อยู่ภายในชิป SAMD21 ดังนั้นจึงต้องเลือกใช้ขา A0 และไม่ใช่การสร้างสัญญาณ PWM เหมือนขาอื่น A1 ~ A10

เมื่อนำสัญญาณนี้มาใช้เป็นอินพุตสำหรับวงจร ADC โดยการเชื่อมต่อด้วยลวดสายไฟ (Jumper Wire) หนึ่งเส้น และทำได้ง่าย โดยใช้คำสั่ง analogWrite(...) และ analogRead(...) ของ Arduino API ตามลำดับ

ในตัวอย่างนี้ได้กำหนดจำนวนบิตที่จะใช้กับ DAC และ ADC โดยใช้คำสั่ง analogWriteResolution(...) และ analogReadResolution(...) ตามลำดับ

เมื่อกำหนดค่าเอาต์พุตให้ DAC แล้ว ก็เว้นระยะเวลาก่อนอ่านค่าอินพุตแล้วแปลงให้เป็นค่าตัวเลขสำหรับแรงดันไฟฟ้า ในหน่วยเป็นมิลลิโวลต์ เมื่อใช้แรงดันอ้างอิง 3.3V หรือ 3300mV

Arduino Sketch: DAC - ADC Loopback

#define ANALOG_OUT_PIN  (A0)  // use DAC output (PA02 pin)
#define ANALOG_IN_PIN   (A1)
#define ADC_RESOLUTION  (12)
#define DAC_RESOLUTION  (10)
#define ADC_MAX         (1UL << ADC_RESOLUTION)
#define DAC_MAX         (1UL << DAC_RESOLUTION)
#define VREF_MV         (3300UL)

void setup() {
   // Start the SerialUSB interface
   Serial.begin(1000000); 
   // Wait for Arduino Serial Monitor to open.
   while ( !Serial );
   Serial.println( F("SAMD21 - Seeeduino XIAO board.") );
   Serial.flush();
   // Set ADC and DAC resolution
   analogWriteResolution( DAC_RESOLUTION );
   analogReadResolution( ADC_RESOLUTION );
   delay(10);
}

void loop() {
  static uint32_t dac_value = 0;
  static uint32_t adc_value = 0;
  // Write DAC output.
  analogWrite( ANALOG_OUT_PIN, dac_value );
  // Read ADC input.
  adc_value = analogRead( ANALOG_IN_PIN );
  // Display ADC and DAC values (converted to millivolts)
  Serial.printf( "DAC:%u,ADC:%u\r\n", 
       (dac_value*VREF_MV)/DAC_MAX, (adc_value*VREF_MV)/ADC_MAX );
  Serial.flush();
  dac_value = (dac_value+1) % DAC_MAX;
  if (dac_value%100 == 0) {
    delay(2000);
  }
}

ข้อมูลที่ถูกส่งออกมาทาง Serial ในแต่ละบรรทัดจะมีสองค่าคือ DAC และ ADC โดยแปลงให้เป็นค่าแรงดันไฟฟ้าเมื่อใช้แรงดันอ้างอิง VREF เท่ากับ 3300mV

รูป: ตัวอย่างการแสดงกราฟจากข้อมูลที่ได้รับใน Arduino Serial Plotter

จากรูปกราฟ แกนตั้งมีหน่วยเป็น mV มีค่าไม่เกิน 3300 และแกนนอนเป็นตัวเลขลำดับข้อมูลที่ถูกส่งออกมาแต่ละบรรทัด ค่าตัวเลขสำหรับ DAC เป็นระดับแรงดันเอาต์พุตที่วงจร DAC จะต้องสร้างเอาต์พุตออกมา และค่าตัวเลข ADC เป็นระดับแรงดันอินพุตที่วัดได้ด้วยวงจร ADC

หากจะลองสร้างชุดข้อมูลตามรูปคลื่นไซน์ (Sine Wave) เช่น มีจำนวนข้อมูล (N_SAMPLES) เท่ากับ 50 ก็มีตัวอย่างดังนี้

Arduino Sketch: DAC - ADC Loopback (Sine Wave)

#define ANALOG_OUT_PIN  (A0)  // DAC output pin
#define ANALOG_IN_PIN   (A1)  // ADC input pin
#define ADC_RESOLUTION  (12)
#define DAC_RESOLUTION  (10)
#define ADC_MAX         (1UL << ADC_RESOLUTION)
#define DAC_MAX         (1UL << DAC_RESOLUTION)
#define VREF_MV         (3300UL)

#define N_SAMPLES       (50)
uint16_t SINE_TABLE[N_SAMPLES] = {0};

void setup() {
  // Start the SerialUSB interface
  Serial.begin(1000000); 
  // Wait for Arduino Serial Monitor to open.
  while ( !Serial );
  Serial.println( F("SAMD21 - Seeeduino XIAO board.") );
  Serial.flush();
  // Set ADC and DAC resolution
  analogWriteResolution( DAC_RESOLUTION );
  analogReadResolution( ADC_RESOLUTION );
  delay(10);

  // Computes a sine table for one period
  uint32_t value;
  for ( int i=0; i < N_SAMPLES; i++ ) {
    value = DAC_MAX*(1+sin(2*M_PI*i/N_SAMPLES))/2;
    if (value >= DAC_MAX) {
      value = DAC_MAX-1;
    }
    SINE_TABLE[i] = value;  
  }
}

void loop() {
  static uint32_t adc_value = 0;
  static uint32_t dac_value = 0;
  for ( int i=0; i < N_SAMPLES; i++ ) {
    dac_value = SINE_TABLE[ i ];
    // Write DAC output.
    analogWrite( ANALOG_OUT_PIN, dac_value );
    // Read ADC input.
    adc_value = analogRead( ANALOG_IN_PIN );
    // Display ADC and DAC values (converted to millivolts).
    Serial.printf( "DAC:%u,ADC:%u\r\n", 
                   (dac_value*VREF_MV)/DAC_MAX,
                   (adc_value*VREF_MV)/ADC_MAX );
  }
  Serial.flush();
  delay(2000);
}

รูป: ตัวอย่างการแสดงกราฟจากข้อมูลที่ได้รับและแสดงให้เห็นรูปคลื่นไซน์

ข้อสังเกต: Arduino IDE 1.8.x จะแสดงผลใน Serial Plotter ได้คราวละ 500 จุด (Data Points) แต่สำหรับ IDE 2.0.0 จะได้แค่ 50 จุด

 


โค้ดตัวอย่าง: I2C Master & BH1750 Light Sensor Reading#

ตัวอย่างถัดไปเป็นการทดลองใช้งานบัส I2C โดยใช้ขา D5=SCL / D4=SDA pins และเขียนโปรแกรมโดยใช้คำสั่งของไลบรารี Wire.h ของ Arduino API เพื่อใช้ในการเชื่อมต่อและอ่านค่าจากโมดูลเซนเซอร์ BH1750 Light Sensor

การตั้งค่าและอ่านข้อมูลจาก BH1750 จะใช้ไลบรารีจาก https://github.com/claws/BH1750 ดังนั้นจะต้องมีการติดตั้งไลบรารีนี้ใน Arduino IDE ก่อน

รูป: การค้นหาและติดตั้งไลบรารีสำหรับนำมาใช้งานกับโมดูล BH1750

Arduino Sketch: BH1750 Reading

// Date: 2022-10-23
// Target board: Seeeduino XIAO SAMD21
#include <Wire.h>
#include <BH1750.h> // https://github.com/claws/BH1750 (version 1.3.0)

// Set the I2C address for the BH1750 device
#define BH1750_ADDR  (0x23) // 0x23 or 0x5c

// Create a BH1750 instance.
BH1750 bh1750;

String getSketchName( const char *fullpath ) {
  String str = fullpath;
  int pos = str.lastIndexOf("\\");
  if (pos >= 0) {
    return str.substring(pos+1, str.length());
  }
  return str;
}

void setup() {
  // Start the SerialUSB interface
  Serial.begin(115200); 
  // Wait for Arduino Serial Monitor to open.
  while (!Serial); 
  Serial.println( F("SAMD21 - Seeeduino XIAO board.") );
  Serial.print( F("Arduino Sketch: ") );
  Serial.println( getSketchName( __FILE__ ) );
  Serial.println( F("Build time: " __DATE__ " " __TIME__ "\n") );
  Serial.flush();

  // Start the I2C device (in master mode) 
  Wire.begin();
  // Set I2C clock speed to 100kHz
  Wire.setClock( 100000 );
  // Scan I2C slave devices.
  i2c_scan();

  // Set operating mode for BH1750: 
  // - 1 Lux resolution
  // - 120 ms measurement time
  // - continuous measurement mode
  bh1750.begin( BH1750::CONTINUOUS_HIGH_RES_MODE, BH1750_ADDR, NULL );
}

#define LINE_SEP  "--------------------"

void i2c_scan() {
  char sbuf[32];
  int n_devices = 0;
  Serial.println( F("Scanning I2C bus...") );
  Serial.print( "   " );
  for ( uint8_t col=0; col < 16; col++ ) {
    sprintf( sbuf, "%3x", col );
    Serial.print( sbuf );
  }
  Serial.println( "" );
  uint8_t addr=0;
  for( uint8_t row=0; row < 8; row++ ) {
    sprintf( sbuf, "%02x:", row << 4 );
    Serial.print( sbuf );
    for ( uint8_t col=0; col < 16; col++ ) {
      if ( row==0 && addr<=1 ) {
        Serial.print("   ");
      } else {
        Wire.beginTransmission( addr );
        if ( Wire.endTransmission() > 0 ) {
          Serial.print( " --" );
        } else {
          sprintf( sbuf, " %2x", addr );
          Serial.print( sbuf );
          n_devices++;
        }
      }
      addr++;
    }
    Serial.println( "" );
  }
  Serial.println( F(LINE_SEP LINE_SEP LINE_SEP LINE_SEP) );
  Serial.flush();
}

void read_bh1750() {
  while ( !bh1750.measurementReady() ) {
    delay(10);
  }
  // Read a value from the BH1750 sensor.
  float lux = bh1750.readLightLevel();
  Serial.print( "Light (Lux): " );
  Serial.println( lux, 1 ); // only 1 digit after the decimal point}

void loop() {
  read_bh1750(); // read sensor value
  delay(100);
}

รูป: ข้อความเอาต์พุตที่ได้รับใน Arduino Serial Monitor แสดงหมายเลขแอดเดรส 0x23 ของอุปกรณ์ BH1750 ที่เชื่อมต่อกับบัส I2C

รูป: ตัวอย่างการแสดงกราฟจากข้อมูลที่ได้รับใน Arduino Serial Plotter (แกนตั้งมีหน่วยเป็น Lux สำหรับความเข้มแสงที่วัดได้จากเซนเซอร์ BH1750)

 


โค้ดตัวอย่าง: External Interrupt & Rotary Encoder Switch#

โมดูล Rotary Encoder เป็นอุปกรณ์ที่สร้างสัญญาณดิจิทัลแบบพัลส์ และมี 2 ช่อง (A และ B) โดยปรกติสัญญาณทั้งสองจะมีสถานะลอจิกเป็น High แต่เมื่อใช้มือหมุนในทิศทางทวนหรือตามเข็มนาฬิกา จะเกิดการเปลี่ยนแปลงลอจิกที่ช่องสัญญาณ A และ B ในลักษณะพัลส์ แต่การเปลี่ยนแปลงของแต่ละช่องจะเกิดขึ้นไม่พร้อมกัน จำนวนพัลส์ที่เกิดขึ้นจะขึ้นอยู่กับตำแหน่งเชิงมุมที่เปลี่ยนไป อุปกรณ์ประเภทนี้สามารถนำมาใช้สร้างสัญญาณอินพุต-ดิจิทัลให้ไมโครคอนโทรลเลอร์

โค้ดตัวอย่างต่อไปนี้ สาธิตการตรวจสอบการเกิดสัญญาณพัลส์ที่ช่องสัญญาณ A และ B และระบุทิศทางการหมุน ซึ่งจะทำให้ค่าของตัวนับ encoder_count เพิ่มขึ้นหรือลดลงตามทิศทางการหมุน

หลักการทำงานคือ การตรวจสอบการเปลี่ยนแปลงโดยใช้อินเทอร์รัพท์ภายนอก ทั้งขอบขาขึ้นและขอบขาลง และใช้คำสั่ง attachInterrupt(...) ของ Arduino API สำหรับอินเทอร์รัพท์จากภายนอก (External Interrupt) ที่ขาสัญญาณ A

เมื่อเกิดอินเทอร์รัพท์ที่ช่องสัญญาณ A ในแต่ละครั้ง ฟังก์ชันที่ทำหน้าที่เป็น ISR (Interrupt Service Routine) จะทำงานและตรวจสอบดูว่า มีสัญญาณพัลส์แบบ Low ที่ช่อง A เกิดขึ้น และมีความกว้างไม่ต่ำกว่าค่าที่กำหนดไว้หรือไม่ (เช่น 20 มิลลิวินาที)

ถ้าเงื่อนไขดังกล่าวเป็นจริง ให้ถือว่ามีการเปลี่ยนตำแหน่งไปหนึ่งตำแหน่ง สถานะของสัญญาณช่อง B ในขณะนั้น จะถูกใช้ในการระบุทิศทางการเปลี่ยนแปลงที่เกิดขึ้น (เพิ่มขึ้นหรือลดลงทีละ 1)

โมดูล Rotary Encoder ที่ได้นำมาทดลองใช้งาน สามารถใช้แรงดันไฟเลี้ยง VCC=+3.3V ได้ มีขาสัญญาณ S1, S2 และ KEY (ปุ่มกด) และนำไปต่อเข้ากับขา D10, D9 และ D8 ของบอร์ด XIAO ตามลำดับ ถ้ามีการกดปุ่มม KEY จะทำให้เกิดการรีเซตค่าของตัวนับ encoder_count ให้มีค่าเป็น 0

Arduino Sketch: Rotary Encoder Inputs

#define PIN_A    (10) // S1
#define PIN_B    (9)  // S2
#define PIN_KEY  (8)  // KEY
#define MIN_PULSE_MS (20)

volatile int32_t encoder_count = 0;
volatile uint32_t last_time = 0;

void isr_pin_key() {
  encoder_count = 0; // reset counter
}

void isr_pin_a() { // ISR function
  // Read current time in msec.
  uint32_t now = millis();
  // Read value from input A.
  int a = digitalRead( PIN_A );
  // Read value from input B.
  int b = digitalRead( PIN_B );
  if ( (now - last_time) >= MIN_PULSE_MS ) {
    if (a==0) { // A is low
      // Update encoder counter.
      encoder_count += b ? -1 : 1;
    }
  }
  last_time = now;
}

void setup() {
  SerialUSB.begin(1000000);
  while(!Serial);
  // Use PIN_A and PIN_B as digital input with internal pullup enabled.
  pinMode( PIN_A, INPUT_PULLUP );
  pinMode( PIN_B, INPUT_PULLUP );
  pinMode( PIN_KEY, INPUT_PULLUP );
  // Enable external interrupt on PIN_A.
  attachInterrupt( PIN_A, isr_pin_a, CHANGE );
  // Enable external interrupt on PIN_KEY.
  attachInterrupt( PIN_KEY, isr_pin_key, RISING );
}

#define UPDATE_INTERVAL_MS (5)

void loop() {
  static int32_t  saved_count = 0;
  static uint32_t ts_saved = 0;
  if ( millis() - ts_saved >= UPDATE_INTERVAL_MS ) { // check every 5 msec
    ts_saved = millis();
    if ( saved_count != encoder_count ) { // Encoder counter was changed.
      saved_count = encoder_count;
      Serial.printf( "Count: %d\r\n", saved_count );
    }
  }
}

รูป: ตัวอย่างข้อความเอาต์พุตแสดงการเปลี่ยนค่าของตัวนับเมื่อทดลองหมุนปุ่มของโมดูล Rotary Encoder

รูป: การต่อวงจรทดลองใช้งานโมดูล Rotary Encoder บนเบรดบอร์ด

 


โค้ดตัวอย่าง: HC-SR04P Ultrasonic Distance Sensor#

ตัวอย่างถัดไปเป็นการอ่านค่าจากโมดูลเซนเซอร์ HC-SR04 เพื่อใช้ในการวัดระยะห่างจากวัตถุกีดขวางด้วยคลื่นเสียงอัลตราโซนิก การวัดค่าจะเริ่มต้นด้วยการสร้างสัญญาณพัลส์ที่ขา TRIG ของโมดูล ที่มีความกว้างอย่างน้อย 10 usec (10 ไมโครวินาที) และจะเกิดสัญญาณเอาต์พุตที่ขา ECHO ของโมดูล โดยเปลี่ยนจาก LOW เป็น HIGH แล้วเปลี่ยนกลับเป็น LOW ความกว้างช่วงที่เป็น HIGH ของสัญญาณพัลส์ตอบกลับมานั้น จะใช้ในการคำนวณระยะเวลาเดินทางของคลื่นเสียงในอากาศในทิศทางไปและสะท้อนกลับมา

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

ในโค้ดตัวอย่างนี้ ได้เลือกใช้ขา D9 / D10 ของบอร์ด XIAO สำหรับนำไปต่อกับขาสัญญาณ ECHO / TRIG ของโมดูล HC-SR04P (เป็นรุ่นที่สามารถใช้แรงดันไฟเลี้ยง VCC=+3.3 ได้)

Arduino Sketch: HC-SR04P Sensor Reading

#define ECHO_PIN      (9)   // D9 pin
#define TRIG_PIN      (10)  // D10 pin

#define SOUND_SPEED   (342)
#define US_TO_CM(t)   ((SOUND_SPEED*t)/20000.0f)

void setup() {
  pinMode( ECHO_PIN, INPUT );
  pinMode( TRIG_PIN, OUTPUT );
  Serial.begin( 1000000 );
  while (!Serial);
  Serial.println( F("XIAO SAMD21 + HC-SR04 Ultrasonic Sensor Demo") );
  Serial.flush();
  digitalWrite( TRIG_PIN, LOW );
  delay(100);
}

volatile uint32_t timestamps[2] = {0,0};

void echo_isr() { // ISR code executed from SRAM
  uint32_t t_now = micros(); // Get the current time (usec)
  if ( digitalRead( ECHO_PIN ) ) { // rising-edge event
     timestamps[0] = t_now; // Save the timestamp. 
  } else { // falling-edge event
     timestamps[1] = t_now; // Save the timestamp.
  }
}

uint32_t read_sensor() {
  // Clear timestamps for rising-edge and falling-edge events.
  timestamps[0] = timestamps[1] = 0;
  // Enable GPIO interrrupt on the Echo pin. 
  attachInterrupt( ECHO_PIN, echo_isr, CHANGE ); 
  // Create a short pulse on the Trigger pin.
  digitalWrite( TRIG_PIN, HIGH );
  delayMicroseconds( 10 );
  digitalWrite( TRIG_PIN, LOW );
  // Wait for 50 msec after the measurement was started.
  delay(50);
  // Disable the interrupt on the Echo pin.
  detachInterrupt( ECHO_PIN ); 
  // Check the timestamps for valid pulse width
  if ( timestamps[0] < timestamps[1] ) {
     return (timestamps[1] - timestamps[0]);
  }
  return 0; // no pulse
}

#define T_USEC_MAX   (30000)

void loop() {
  uint32_t t_usec = read_sensor();
  if ( t_usec > 0 ) {
    if (t_usec > T_USEC_MAX ) { // value limit 
      t_usec = T_USEC_MAX;
    }
    Serial.printf( "Distance [cm]: %.1f\r\n", US_TO_CM(t_usec) );
  }
  delay(100);
}

รูป: ตัวอย่างข้อความเอาต์พุตแสดงการวัดค่าระยะห่างจากวัตถุกีดขวางโดยใช้โมดูล HC-SR04P

รูป: การต่อวงจรทดลองใช้งานโมดูล HC-SR04P บนเบรดบอร์ด

 


กล่าวสรุป#

บทความนี้ได้นำเสนอการใช้งานบอร์ด Seeeduino XIAO - SAMD21 ในเบื้องต้น และมีโค้ดตัวอย่างสำหรับการเขียนโปรแกรมด้วย Arduino IDE เพื่อทดลองใช้กับบอร์ดไมโครคอนโทรลเลอร์ดังกล่าว

 


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

Created: 2022-10-24 | Last Updated: 2022-10-25