# Arduino C/C++ versus MicroPython

## ตัวอย่างที่ 1: การคำนวณเลขยกกำลังฐานสอง

ถ้าเราต้องการเขียนโค้ดเพื่อคำนวณค่าของเลขจำนวนเต็ม เช่น "สองยกกำลังสี่สิบลบด้วยหนึ่ง" ให้เป็นค่าของตัวแปร **x** และแสดงค่าของตัวแปรให้เป็นเลขฐานสิบ (**decimal**) และฐานสิบหก (**hexadecimal**) เราจะเขียนโค้ดอย่างไร ?

$$
x = 2^{40} -1
$$

ในกรณีนี้ การเก็บข้อมูลสำหรับตัวแปร **x** จะต้องมีขนาดหน่วยความจำอย่างน้อย 40 บิต หรือ 5 ไบต์&#x20;

ข้อความเอาต์พุตจากการทำงานของโค้ดสำหรับค่าของตัวแปร **x** จะต้องได้ตามรูปแบบต่อไปนี้

```python
x = 1099511627775 (dec)
x = 0xffffffffff (hex)
```

ถ้าเขียนโค้ดด้วยไมโครไพธอน ก็สามารถเขียนโดยใช้คำสั่งต่อไปนี้

```python
x = 2**40 - 1
print( 'x = {} (dec)'.format(x) )
print( 'x = 0x{:x} (hex)'.format(x) )
```

การเขียนนิพจน์ `2**40` หมายถึง 2 ยกกำลัง 40 โดยที่  `**` เป็นโอเปอร์เรเตอร์ (**Operator**) สำหรับเลขยกกำลังในภาษาไพธอน&#x20;

การแสดงข้อความเอาต์พุตสำหรับค่าของตัวแปร ทำได้ โดยใช้คำสั่ง `print()` ร่วมกับคำสั่ง `format()` เมื่อใช้กับข้อมูลแบบ`string`&#x20;

ถัดไปลองมาดูโค้ดสำหรับ [**Arduino-ESP32**](https://github.com/espressif/arduino-esp32/) เปรียบเทียบกัน

```cpp
void setup() {
    Serial.begin( 115200 );
    uint64_t x = (1LLU << 40) - 1;
    
    Serial.printf( "x = %llu (dec)\n", x );
    Serial.printf( "x = 0x%llx (hex)\n", x );      
}

void loop() { }
```

ในกรณีนี้ เราจะใช้โอเปอร์เรเตอร์เลื่อนบิต (**Shift Operato**r) ไปทางซ้าย (`<<`) แทนการคำนวณเลขยกกำลังสำหรับฐานสอง และเขียนนิพจน์ในภาษา **C/C++** ได้เป็น `(1LLU << 40)` และจะต้องใช้ชนิดของข้อมูลแบบ `unsigned long long` หรือ `uint64_t` ซึ่งมีขนาด 8 ไบต์ (64 บิต) ในกรณีตัวอย่างนี้ เราไม่สามารถใช้ชนิดข้อมูลเลขจำนวนเต็มขนาด 32 บิต เนื่องจากต้องใช้อย่างน้อย 40 บิต

การแสดงข้อความเอาต์พุตสำหรับค่าของตัวแปร ทำได้โดยใช้คำสั่ง `Serial.printf()`

{% hint style="info" %}
โครงสร้างสำหรับการเขียนโค้ด **Arduino Sketch** จะต้องมีฟังก์ชัน **void setup() {...}** สำหรับโค้ดที่ต้องทำเมื่อเริ่มต้น และ **void loop() {...}** สำหรับโค้ดที่ต้องทำซ้ำไปเรื่อย ๆ หลังจากนั้น&#x20;
{% endhint %}

หรืออีกกรณีหนึ่ง เราอาจใช้ฟังก์ชัน [`pow()`](https://www.arduino.cc/reference/en/language/functions/math/pow/) สำหรับการคำนวณเลขยกกำลังดังนี้

```cpp
void setup() {
   Serial.begin( 115200 );
   uint64_t x = pow(2,40) - 1;
   
   Serial.printf( "x = %llu (dec)\n", x );
   Serial.printf( "x = 0x%llx (hex)\n", x );
}

void loop() { }
```

แต่มีข้อสังเกตและข้อควรระวังในการใช้งานคือ ฟังก์ชัน [`pow()`](https://www.arduino.cc/reference/en/language/functions/math/pow/) ของ **Arduino API** นั้น จะได้ผลลัพธ์จากการคำนวณเป็น [`double`](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) ที่มีขนาด 8 ไบต์ (สำหรับ **ESP32** หรือตัวประมวลผลขนาด **32** บิต) ตามรูปแบบมาตรฐาน **IEEE 754 Double-Precision** ประกอบด้วย&#x20;

* 1 บิต: **Sign Bit** (S)
* 11 บิต: **Exponent** (e)
* 52 บิต: **Fraction** (F)

$$
(-1)^{S}(1.\underbrace{b\_{51}b\_{50},...,b\_{1}b\_{0}}*{F})*{,2} \times
2^{,e-1023}
$$

ในกรณีตัวอย่างนี้ ถ้าจะคำนวณค่า "2 ยกกำลัง 40 แล้วลบด้วย 1" ก็สามารถเก็บค่าจำนวนเต็มในส่วนของ **Fraction** ขนาด 52 บิต ร่วมกับส่วนที่เป็น **Exponent** ขนาด 11 บิต ของ `double` ได้ และค่าที่ได้จะต้องถูกแปลงชนิดข้อมูล (**Type Casting**) จาก `double` ให้เป็น `uint64_t` เพื่อให้ได้เลขจำนวนเต็มบวก

## ตัวอย่างที่ 2: การหาค่าเชิงสถิติจากรายการข้อมูลตัวเลข

ในตัวอย่างถัดไป มีโจทย์ดังนี้ ถ้ามีตัวเลขจำนวนเต็ม เช่น **5, 13, 33, 46, 50, 54, 67, 87, 95** และต้องการหาค่าผลรวม (**Summation**) ค่าต่ำสุด (**Minimum**) ค่าสูงสุด (**Maximum**) และค่าเฉลี่ย (**Average**) ของข้อมูลชุดนี้ แล้วแสดงเป็นข้อความเอาต์พุตสำหรับผลลัพธ์ที่ได้ จะเขียนโค้ดอย่างไร ?

กำหนดให้ตัวอย่างข้อความเอาต์พุตสำหรับข้อมูลตัวเลขดังกล่าว เป็นดังนี้

```
Sum: 450
 Min: 5
 Max: 95
Avg.: 50.000
```

ในกรณีของไมโครไพธอน เราก็สามารถเขียนโค้ดได้ โดยสร้างรายการหรือลิสต์ (**List**) ของเลขจำนวนเต็ม จากนั้นก็ใช้คำสั่ง `min()`, `max()`, `len()`, `sum()` หาค่าต่ำสุด ค่าสูงสุด จำนวนข้อมูลสมาชิกทั้งหมดในรายการ และผลรวมของข้อมูลเหล่านั้น

```python
data = [5,13,33,46,50,54,67,87,95]
n    = len(data)
_min = min(data)
_max = max(data)
_sum = sum(data)

print( ' Sum: {}'.format( _sum ) )
print( ' Min: {}'.format( _min ) )
print( ' Max: {}'.format( _max ) )
print( 'Avg.: {:.3f}'.format( _sum/n ) )
```

ถ้าจะเขียนโค้ด **Arduino Sketch** ให้ได้ผลลัพธ์เหมือนกัน อาจเขียนโค้ดตามรูปแบบต่อไปนี้ โดยใช้ชนิดข้อมูลเป็น `int32_t` (ข้อมูลแบบ `signed integer` ขนาด 32 บิต หรือ 4 ไบต์)

```cpp
void setup() {
   Serial.begin( 115200 );
   int32_t data[] = { 5,13,33,46,50,54,67,87,95 };
   int32_t _min=data[0], _max=data[0];
   int32_t _sum = 0;
   int n = sizeof(data)/sizeof(data[0]);
   
   for (int i=0; i < n; i++) {
     _min = min( data[i],_min );
     _max = max( data[i],_max );
     _sum += data[i];
   }
   Serial.printf( " Sum: %d\n", _sum );
   Serial.printf( " Min: %d\n", _min );
   Serial.printf( " Max: %d\n", _max );
   Serial.printf( "Avg.: %.3lf\n", ((double)_sum)/n );
}

void loop() { } // no action
```

## ตัวอย่างที่ 3: การคำนวณค่า CRC8 สำหรับลำดับข้อมูลไบต์

การคำนวณค่า [**Cyclic Redundancy Check** (**CRC**)](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) เป็นวิธีการในการตรวจสอบความผิดพลาดของข้อมูล (**Error Detection**) เช่น ถ้ามีข้อมูลไบต์ตามลำดับอยู่จำนวนหนึ่ง เราสามารถนำไปคำนวณค่า **CRC** (มีขนาด **n** บิต เช่น **n=8, 16, 32** สำหรับ **CRC-8**, **CRC-16**, **CRC-32** เป็นต้น) โดยใช้อัลกอริธึม หรือวิธีการคำนวณแบบใดแบบหนึ่ง (มีหลายรูปแบบให้เลือกใช้) ค่าที่ได้นี้เรียกว่า **CRC checksum**&#x20;

ถ้ามีการเปลี่ยนแปลงของข้อมูลเกิดขึ้น เช่น ระหว่างการสื่อสารรับส่งข้อมูล เราก็สามารถคำนวณค่า **CRC checksum** ของข้อมูลที่ได้รับ แล้วเปรียบเทียบกับค่า **CRC checksum** ที่ได้มีการคำนวณไว้ก่อนแล้วและนำมาเปรียบเทียบกัน

โดยทั่วไป ข้อมูลจะถูกมองว่าเป็นลำดับบิตหรือเลขฐานสอง (**Bitstream**) ถ้ามีหลายไบต์ก็นำมาเรียงต่อกัน บิตในแต่ลำตำแหน่งจะถูกใช้เป็นค่าสัมประสิทธิ์แบบไบนารี (**Binary Coefficient**) ของพหุนาม (**Polynomial**) ที่มีดีกรีเท่ากับจำนวนบิตทั้งหมด

จากนั้นจึงนำไปหารด้วยพหุนามฐานสองที่มีดีกรีเท่ากับ **n** (เรียกว่า **Polynomial Divisor** หรือ **Generator Polynomial**) เพื่อคำนวณหาเศษที่เหลือจากการหาร (**Remainder**) การหารในเลขฐานสองทำได้ง่าย โดยใช้โอเปอร์เรเตอร์พื้นฐาน 2 ชนิด คือ **Exclusive-OR** (**XOR**) และการเลื่อนบิต (**Bit Shifting**)

ตัวอย่างของหหุนามตัวหารสำหรับ **CRC-8 (n=8)** เช่น

* x^8+x^2+x^1+1 : เขียนสัมประสิทธิ์เป็นเลขฐานสองได้เป็น "100000111" (0x107)
* x^8+x^5+x^4+1 : เขียนสัมประสิทธิ์เป็นเลขฐานสองได้เป็น "100110001" (0x131)&#x20;

รูปแบบการคำนวณค่า **CRC** มีหลายวิธี เช่น ขึ้นอยู่กับหหุนามตัวหารที่ได้เลือกมาใช้งาน และทิศทางของบิต (**Bit Ordering**) เช่น **MSB First / normal order** หรือ **LSB First / reverse order**

ในตัวอย่างนี้ สมมุติว่า เราต้องการคำนวณค่า **CRC-8** สำหรับลำดับข้อมูลไบต์ดังนี้ **0x34, 0xff, 0x40, 0x52, 0x67, 0x01** และเลือกใช้วิธีการตามรูปแบบที่เรียกว่า **CRC-8 Dallas/Maxim** ซึ่งจะได้ผลลัพธ์เท่ากับ **0x46** และตัวอย่างข้อความเอาต์พุตมีดังนี้

```python
data = [0x34,0xff,0x40,0x52,0x67,0x01]
crc8 = 0x46
```

การคำนวณ **CRC-8** ตามรูปแบบที่กำหนดโดยบริษัท **Dallas/Maxim** (อ้างอิงจากเอกสาร [**Maxim Application Note 27**](https://pdfserv.maximintegrated.com/en/an/AN27.pdf)**: "Understanding and Using Cyclic Redundancy Checks with Maxim 1-Wire and iButton Products", 2001**) มีการใช้พหุนามตัวหาร **x^8 + x^5 + x^4 + 1** (0x131) แต่ใช้ทิศทางหรือลำดับของบิตแบบ **LSB First / Reverse Order**

โค้ดไมโครไพธอนมีดังนี้

```python
def CRC8(data,num_bytes):
    crc = 0x00 # initialize the remainder
    for i in range(num_bytes):
        x = data[i]
        for j in range(8):
            b = (crc ^ x) & 0x01
            crc >>= 1
            if b != 0:
                crc ^= 0x8C
            x >>= 1
    return crc

data = [ 0x34,0xff,0x40,0x52,0x67,0x01 ]
s = ','.join( ['0x{:02x}'.format(x) for x in data] )
print( 'data = [{}]'.format( s ) )
crc8 = CRC8( data, len(data) )
print( 'crc8 = 0x{:02x}'.format(crc8) )
```

จากตัวอย่างโค้ดในภาษาไมโครไพธอน ถ้าจะเขียนด้วย **Arduino Sketch** ก็มีตัวอย่างโค้ดเอาไว้เปรียบเทียบกันดังนี้

```cpp
byte CRC8( const byte *data, byte num_bytes ) {
  byte crc = 0x00; // initialize the remainder
  for ( int i=0; i < num_bytes; i++ ) {
    byte x = data[i];
    for ( byte j=0; j < 8; j++ ) {
      byte b = (crc ^ x) & 0x01;
      crc >>= 1;
      if (b) {
        crc ^= 0x8C;
      }
      x >>= 1;
    }
  }
  return crc;
}

void setup() {
   Serial.begin( 115200 );
   byte data[] = { 0x34,0xff,0x40,0x52,0x67,0x01 };
   byte n = sizeof(data)/sizeof(byte);
   byte crc8 = CRC8( data, n );
   
   Serial.print( "data = [" );
   for ( int i=0; i < n; i++ ) {
      if (i != 0) {
         Serial.print( "," );
      }
      Serial.printf( "0x%02x", data[i] );
   }
   Serial.println( "]" );
   Serial.printf( "crc8 = 0x%02X\n", crc8 );
}

void loop() { }
```

## ตัวอย่างที่ 4: การสร้างเลขสุ่มขนาด 32 บิต แสดงเป็นเลขฐานสอง

ตัวอย่างถัดไปเป็นการสาธิตการสร้างเลขสุ่ม (**Pseudo-Random Number Generation**) ให้เป็นเลขจำนวนเต็มขนาด 32 บิต และแสดงเป็นข้อความเอาต์พุตให้เป็นเลขฐานสอง จะมีวิธีการเขียนโค้ดอย่างไร ?&#x20;

ในตัวอย่างนี้ การตั้งค่าเริ่มต้นหรือ **Seed** ให้กับตัวสร้างเลขสุ่ม เราจะใช้วิธีการอ่านค่าจากขาแอนะล็อกอินพุตที่ไม่ได้ต่อใช้งาน (**Floating Input**) กับวงจรหรือสัญญาณใด ๆ ของบอร์ด **ESP32** และค่าที่อ่านได้เมื่อโปรแกรมทำงานในแต่ละครั้ง ควรจะได้ไม่ซ้ำเดิม

ตัวอย่างการเขียนโค้ดไมโครไพธอนมีดังนี้

```python
import urandom as random
from machine import Pin, ADC

adc = ADC(Pin(32))  # use GPIO-32 pin
seed = adc.read()   # read analog value
print( 'Seed: {}'.format(seed) )
random.seed( seed ) # set the seed value
bin_str = bin( random.getrandbits(32) )[2:]
print( bin_str )    # show the binary string
```

ในโค้ดตัวอย่าง ได้เลือกใช้ขา **GPIO-32** **/ ADC1\_CH4** และใช้คำสั่งของคลาส `ADC` อ่านค่าอินพุตแล้วนำมาใช้เป็นค่า **Seed** สำหรับการทำงานของคลาส `random`&#x20;

คำสั่ง `random.getrandbits()` จะสร้างเลขสุ่มตามจำนวนบิตที่ต้องการ (ในกรณีนี้คือ 32 บิต) และได้เป็ข้อมูลเลขจำนวนเต็ม จากนั้นจึงใช้คำสั่ง `bin()` แปลงเป็นข้อความเลขฐานสอง (**32-bit Binary String**)

แต่ถ้าเขียนโค้ด **Arduino Sketch** ก็มีตัวอย่างดังนี้

```cpp
void setup() {
   Serial.begin(115200);
   int seed = analogRead(32); // read analog GPIO-32 pin
   Serial.printf( "Seed = %d\n", seed );
   
   // initialize the pseudo-random number generator
   randomSeed( seed ); // set the seed value
   
   String bin_str = "";
   for ( int i=0; i < 32; i++ ) { // 32 bits
       int value = random(0,2); // either 0 or 1
       bin_str += value;
   }
   Serial.printf( "%s\n", bin_str.c_str() );
}

void loop() { }
```

คำสั่ง `analogRead()` ใช้สำหรับการอ่านค่าจากขาแอนะล็อกอินพุต คำสั่ง `randomSeed()` กำหนดค่าเริ่มต้นสำหรับตัวสร้างเลขสุ่ม และการใช้คำสั่ง `random(0,2)` จะสุ่มตัวเลขได้เป็น 0 หรือ 1

**ESP32** มีวงจรภายในที่เรียกว่า **Hardware Random Number Generator (RNG)** และสามารถนำมาใช้สร้างเลขสุ่มขนาด 32 บิต แต่จะต้องเปิดใช้งาน **Wi-Fi** หรือ **Bluetooth** ร่วมด้วย และฟังก์ชันสำหรับสร้างเลขสุ่มโดยใช้ **RNG** คือ `esp_random()` ซึ่งมีการประกาศไว้ในไฟล์ [`esp_system.h`](https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/esp32/esp_system.h)) &#x20;

เราสามารถเขียนโค้ดอีกกรณีหนึ่งได้ดังนี้

```cpp
#include <WiFi.h> // for WiFi.begin()

void setup() {
   Serial.begin(115200);
   WiFi.begin(); // enable WiFi
   
   String bin_str = "";
   for ( int i=0; i < 32; i++ ) { // 32 bits
       int value = esp_random() % 2;
       bin_str += value;
   }
   Serial.printf( "%s\n", bin_str.c_str() );
}

void loop() { }
```

## ตัวอย่างโค้ดที่ 5: การทำให้ LED กระพริบ

ในตัวอย่างนีัสาธิตการทำให้ **LED** กระพริบ โดยเลือกใช้ขา **GPIO** ของ **ESP32** เช่น **GPIO-22** หรือ **GPIO-5** เป็นเอาต์พุต (เลือกขาให้ตรงกับ **Onboard LED**) และมีการกำหนดสถานะเอาต์พุต **High / Low** สลับกันไปเรื่อย ๆ โดยเว้นระยะเวลาประมาณ 500 มิลลิวินาที

ถ้าเขียนโค้ดด้วยไมโครไพธอน ก็ทำได้ดังนี้

```python
from micropython import const
import time
from machine import Pin

LED_PIN = const(5) # use GPIO-5 for LED output
led = Pin( LED_PIN, Pin.OUT )
print( "LED Pin:", LED_PIN )

while True:
    state = not led.value() # read value and invert it
    print( 'LED state:', int(state) )
    led.value( state )  # update LED output
    time.sleep_ms( 500 )
```

หรือถ้าจะเขียนแบบไม่ใช้คำสั่ง `time.sleep_ms()`  ก็ทำได้โดยใช้วิธีตรวจสอบระยะเวลาในการอัปเดตสถานะของ **LED** ในครั้งถัดไป

```python
from micropython import const
import time
from machine import Pin

LED_PIN = const(5) # use GPIO-5 for LED output
led = Pin( LED_PIN, Pin.OUT )
print( "LED Pin:", LED_PIN )

last_update = time.ticks_ms()
while True:
    now = time.ticks_ms()
    if time.ticks_diff( now, last_update ) >= 500:
        last_update = now # save last update time
        state = not led.value() # read value and invert it
        print( 'LED state:', int(state) )
        led.value( state ) # update LED output
```

แต่ถ้าจะเขียนโค้ด **Arduino Sketch** ก็มีตัวอย่างดังนี้

```cpp
const int LED_PIN = 22;

void setup() {
   Serial.begin( 115200 );
   pinMode( LED_PIN, OUTPUT );
   Serial.printf( "LED Pin: %d\n", LED_PIN ); 
}

void loop() {
   int state = !digitalRead( LED_PIN );
   digitalWrite( LED_PIN, state );
   Serial.printf( "LED: %d\n", state );
   delay( 500 );
}
```

หรือใช้วิธีตรวจสอบระยะเวลาในการอัปเดตสถานะของ **LED** ในครั้งถัดไป

```cpp
const int LED_PIN = 22;

uint32_t last_update, now;

void setup() {
   Serial.begin( 115200 );
   pinMode( LED_PIN, OUTPUT );
   Serial.printf( "LED Pin: %d\n", LED_PIN ); 
   last_update = millis();
}

void loop() {
   now = millis();
   if ( now - last_update >= 500 ) {
      last_update = now; 
      int state = ! digitalRead( LED_PIN );
      digitalWrite( LED_PIN, state );
      Serial.printf( "LED: %d\n", state );
   }
}
```

## โค้ดตัวอย่างที่ 6: การแปลงค่า BCD ให้เป็นข้อมูลบิตสำหรับ 7-Segment Display

ตัวอย่างนี้สาธิตการเขียนโค้ดเพื่อแปลงค่าแบบ **BCD** (**Binary-Coded Decimal**) ขนาด 4 บิต ให้กลายเป็นข้อมูลขนาด 7 บิต (หรือขนาด 1 ไบต์) เพื่อแสดงตัวเลข **0..9** สำหรับ [**7-Segment Display**](https://en.wikipedia.org/wiki/Seven-segment_display) (สำหรับเซ็กเมนต์ 7 ส่วน ได้แก่ a, b, c,..., g ) จำนวนหนึ่งหลัก (ทำงานหรือต่อวงจรแบบ **Common-Cathode**) โดยใช้บิตที่ 0 (LSB) สำหรับเซ็กเมนต์ a, บิตที่ 1 สำหรับเซ็กเมนต์ b ไปตามลำดับ&#x20;

| ค่าตัวเลข BCD (4 บิต) | ฐานสิบหก (gfedcba) | ฐานสอง (gfedcba) |
| --------------------- | ------------------ | ---------------- |
| 0                     | 0x3f               | 0b00111111       |
| 1                     | 0x06               | 0b00000110       |
| 2                     | 0x5b               | 0b01011011       |
| 3                     | 0x4f               | 0b01001111       |
| 4                     | 0x66               | 0b01100110       |
| 5                     | 0x6d               | 0b01101101       |
| 6                     | 0x7d               | 0b01111101       |
| 7                     | 0x07               | 0b00000111       |
| 8                     | 0x7f               | 0b01111111       |
| 9                     | 0x6f               | 0b01101111       |
| 10 .. 15              | 0x00               | 0b00000000       |

ลองมาดูโค้ดสำหรับไมโครไพธอน เราสร้างฟังก์ชัน `bcd2seg()` และใช้ประโยคคำสั่งแบบ **if-elif** ตรวจสอบค่า `bcd` ที่รับมาเป็นอาร์กิวเมนต์ ไปทีละกรณี ซึ่งมีทั้งหมด 16 กรณี เพื่อระบุค่า `value` ที่ได้จากการทำงานของฟังก์ชันนี้

```python
def bcd2seg( bcd, invert=False ): # BCD to 7-segment decode
    if bcd == 0:
        value = 0x3f
    elif bcd == 1:
        value = 0x06
    elif bcd == 2:
        value = 0x5b
    elif bcd == 3:
        value = 0x4f
    elif bcd == 4:
        value = 0x66
    elif bcd == 5:
        value = 0x6d
    elif bcd == 6:
        value = 0x7d
    elif bcd == 7:
        value = 0x07
    elif bcd == 8:
        value = 0x7f
    elif bcd == 9:
        value = 0x6f
    else:
        value = 0x00
    if invert:
        value ^= 0xff
    return value
    
for i in range(16):
   value = bcd2seg(i)
   bin_str = '0b{:08b}'.format(value)
   hex_str = '0x{:02x}'.format(value)
   print( "{:2d} => {}, {}".format(i, hex_str, bin_str) )
```

ตัวอย่างข้อความเอาต์พุต

```
 0 => 0x3f, 0b00111111
 1 => 0x06, 0b00000110
 2 => 0x5b, 0b01011011
 3 => 0x4f, 0b01001111
 4 => 0x66, 0b01100110
 5 => 0x6d, 0b01101101
 6 => 0x7d, 0b01111101
 7 => 0x07, 0b00000111
 8 => 0x7f, 0b01111111
 9 => 0x6f, 0b01101111
10 => 0x00, 0b00000000
11 => 0x00, 0b00000000
12 => 0x00, 0b00000000
13 => 0x00, 0b00000000
14 => 0x00, 0b00000000
15 => 0x00, 0b00000000
```

หรือจะใช้วิธีอ่านค่าจากตารางค่าคงที่ (**Lookup Table**) จากอาร์เรย์ ขนาด 16 ไบต์

```python
LOOKUP_TABLE = [
    0x3f, 0x06, 0x5b, 0x4f, 
    0x66, 0x6d, 0x7d, 0x07, 
    0x7f, 0x6f, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, ]
    
def bcd2seg( bcd, invert=False ):
    return LOOKUP_TABLE[bcd & 0x0f]
    
for i in range(16):
   value = bcd2seg(i)
   bin_str = '0b{:08b}'.format(value)
   hex_str = '0x{:02x}'.format(value)
   print( "{:2d} => {}, {}".format(i, hex_str, bin_str) )
```

ในการเขียนโค้ดสำหรับ **Arduino Sketch** เพื่อตรวจสอบเงื่อนไข เราสามารถใช้ประโยค **switch-case** ได้แทนการใช้ประโยค **if-else** ในกรณีตัวอย่างนี้

```cpp
byte bcd2seg( byte bcd, bool invert=false ) {
    byte value;
    switch (bcd) {
      case 0: value = 0x3f; break; // 0b00111111 
      case 1: value = 0x06; break; // 0b00000110 
      case 2: value = 0x5b; break; // 0b01011011
      case 3: value = 0x4f; break; // 0b01001111 
      case 4: value = 0x66; break; // 0b01100110
      case 5: value = 0x6d; break; // 0b01101101 
      case 6: value = 0x7d; break; // 0b01111101 
      case 7: value = 0x07; break; // 0b00000111
      case 8: value = 0x7f; break; // 0b01111111
      case 9: value = 0x6f; break; // 0b01101111
      default:
        value = 0x00; 
    }
    if (invert) { 
      value ^= 0xff; // bit-inverting
    }
    return value;
}

void byte2bin_str( byte x, String& s ) {
    s = "0b";
    for ( int i=7; i >= 0; i-- ) {
       s += (x & 0x80) ? 1 : 0;
       x <<= 1;
    }
}

void setup() {
   Serial.begin(115200);
   String s;
   for ( int i=0; i < 16; i++ ) {
      byte value = bcd2seg(i);      
      byte2bin_str( value, s );
      Serial.printf( "%2d => 0x%02x, %s\n",
      i, value, s.c_str() );
   }
}

void loop() { }
```

หรือเขียนโค้ดโดยใช้วิธีอ่านค่าจากตารางค่าคงที่ มึจำนวนข้อมูล 16 ไบต์

```cpp
const byte LOOKUP_TABLE[16] = {
    0x3f, 0x06, 0x5b, 0x4f, 
    0x66, 0x6d, 0x7d, 0x07, 
    0x7f, 0x6f, 0x00, 0x00, 
    0x00, 0x00, 0x00, 0x00, 
};

byte bcd2seg( byte bcd, bool invert=false ) {
    byte value = LOOKUP_TABLE[ bcd & 0x0f ];
    if (invert) { 
      value ^= 0xff; // bit-inverting
    }
    return value;
}

void byte2bin_str( byte x, String& s ) {
    s = "0b";
    for (int i=7; i >= 0; i--) {
       s += (x & 0x80) ? 1 : 0;
       x <<= 1;
    }
}

void setup() {
   Serial.begin(115200);
   String s;
   for ( int i=0; i < 16; i++ ) {
      byte value = bcd2seg(i);      
      byte2bin_str( value, s );
      Serial.printf( "%2d => 0x%02x, %s\n",
        i, value, s.c_str() );
   }
}

void loop() { }
```

## โค้ดตัวอย่างที่ 7: การตรวจสอบอินเทอร์รัพท์ภายนอกจากปุ่มกด

ตัวอย่างถัดไป สาธิตการเขียนโค้ดเพื่อตรวจสอบดูว่า มีการกดปุ่มหรือไม่ โดยเปิดใช้งานอินเทอร์รัพท์สำหรับขา **GPIO** ที่เกี่ยวข้อง เช่น เลือกใช้ขา **GPIO-23**

การเขียนโค้ดไมโครไพธอนในตัวอย่างนี้ จะต้องมีการใช้คลาส `machine.Pin` โดยสร้างอ็อปเจกต์จากคลาสนี้ เพื่อใข้งานในโหมดอินพุต และเปิดใช้งานวงจรภายในสำหรับตัวต้านทานแบบ **Pull-Up**&#x20;

ถัดไปมีการเปิดใช้งาน **IRQ (Interrupt Request)** สำหรับขา **GPIO** ดังกล่าว และจะต้องระบุว่า ให้ตรวจสอบการเกิดเหตุการณ์แบบใด ในกรณีนี้คือ ขอบขาลง หรือ **Falling Edge** และสร้างฟังก์ชัน `btn_callback()` เพื่อทำหน้าที่เป็น **Callback Function** หรือ I**nterrupt Handler** ทุกครั้งที่เกิดเหตุการณ์ ฟังก์ชันนี้จะทำให้ค่าของตัวแปร `num_presses` เพิ่มขึ้นทีละหนึ่ง&#x20;

ในส่วนของ **Main Loop** ที่ใช้ประโยคแบบ `while True` จะต้องมีการตรวจสอบค่าของตัวแปร `num_presses` ถ้ามีค่ามากกว่า 0 แสดงว่า มีการกดปุ่มเกิดขึ้น

ถ้าเกิดเหตุการณ์สองครั้งตามลำดับ มีระยะห่างอย่างน้อย 100 มิลลิวินาที และค่าของอินพุตจะต้องคงที่ (**stable**) เป็น 0 ในช่วงเวลาสั้น ๆ หลังเกิดเหตุการณ์จากการกดปุ่ม จะถือว่าเป็นการกดปุ่มที่ถูกต้อง เพื่อลดปัญหาของ **Button Bouncing** และถ้ากดปุ่มไปทั้งหมด 10 ครั้งแล้ว ให้ปิดการทำงานของ **IRQ** สำหรับขาอินพุตดังกล่าว

```python
from micropython import const
from machine import Pin
import machine, time

BTN_PIN = const(23) # use GPIO-23 for input

# global variables
num_presses = 0 
last_event = time.ticks_ms()
cnt = 0

def btn_callback(pin): # IRQ handler
    global num_presses
    num_presses += 1
    
btn = Pin( BTN_PIN, Pin.IN, Pin.PULL_UP )
btn.irq( trigger=Pin.IRQ_FALLING, handler=btn_callback )

while True:
    if num_presses > 0:
        is_input_low = True
        for i in range(5):
            if btn.value() != 0: # input not stable
                is_input_low = False
                break
            time.sleep_ms(2)
            
        now   = time.ticks_ms()
        delta = time.ticks_diff( now, last_event )
        
        if delta >= 100 and is_input_low:
           cnt += 1
           print( 'Button pressed: #{}'.format(cnt) )
           if cnt == 10:
             btn.irq( handler=None ) # disable IRQ
             print('Disabled IRQ for push button')
             
        last_event = now
        num_presses = 0
```

ลองมาดูตัวอย่างการเขียนโค้ด **Arduino C/C++** ในเชิงเปรียบเทียบกัน

```cpp
const int BTN_PIN = 23; // use GPIO-23 for input

// global varaibles
uint32_t last_event;
int cnt = 0;

volatile int num_presses = 0;

void IRAM_ATTR btn_callback() { // Interrupt handler
   num_presses += 1;
}

void setup() {
   Serial.begin( 115200 );
   pinMode( BTN_PIN, INPUT_PULLUP );
   attachInterrupt( BTN_PIN, btn_callback, FALLING );
   last_event = millis();
}

void loop() {
   if ( num_presses > 0 ) {
      bool is_input_low = true;
      for ( int i=0; i < 5; i++ ) {
         if ( digitalRead(BTN_PIN)  ) {
             is_input_low = false; // input not stable
             break;
         }
         delay(2); 
      }
      
      uint32_t now   = millis();
      uint32_t delta = now - last_event;
      
      if ( delta >= 100 && is_input_low ) {
        cnt += 1;
        Serial.printf( "Button pressed: #%d\n", cnt );
        if ( cnt == 10 ) {
          detachInterrupt( BTN_PIN ); // disable IRQ
          Serial.println( "Disabled IRQ for push button" );
        }
      }
      last_event = now; 
      num_presses = 0;
   }
}
```

ในการเปิดหรือปิดการใช้งานอินเทอร์รัพท์ที่ขา **GPIO** ของ **ESP32** เราใช้คำสั่ง `attachInterrupt()` และ `detachInterrupt()` ตามลำดับ (ผู้อ่านสามารถศึกษาการทำงานของคำสั่งนี้ได้จากโค้ด [`esp32-hal-gpio.h`](https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-gpio.h) และ [`esp32-hal-gpio.c`](https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-gpio.c) หรือโค้ดตัวอย่าง [`GPIOInterrupt.ino`](https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/GPIO/GPIOInterrupt/GPIOInterrupt.ino))

## โค้ดตัวอย่างที่ 8: การเรียงลำดับข้อมูลในอาร์เรย์

ตัวอย่างนี้สาธิตการสุ่มตัวเลขซึ่งมีทั้งหมด **N** (เช่น **N=32**) และเป็นเลขจำนวนเต็มในช่วงที่กำหนด **\[0, N-1]** นำไปใส่ลงในอาร์เรย์ แล้วแสดงค่าตัวเลข ก่อนและหลังการเรียงข้อมูลจากน้อยไปมาก

โค้ดไมโครไพธอนมีดังนี้

```python
import urandom as random
from machine import Pin, ADC

def printData(data):
   print( ','.join( [str(x) for x in data]) )
    
adc = ADC(Pin(32))  # use GPIO-32 pin
seed = adc.read()   # read analog value
print( 'Seed: {}'.format(seed) )
random.seed( seed ) # set the seed value

N = 32
# create an array of N randomized integers
data = [ random.randint(0,N) for i in range(N) ]
# show data before sorting 
printData( data )
# sort data
data = sorted(data)
# show data after sorting 
printData( data )
```

ถ้าจะเขียนโค้ดสำหรับ **Arduino ESP32** ก็มีแนวทางดังนี้

```cpp
void setup() {
   Serial.begin(115200);
   int seed = analogRead(32); // read analog GPIO-32 pin
   Serial.printf( "Seed = %d\n", seed );
   // initialize the pseudo-random number generator
   randomSeed( seed ); // set the seed value
   
   int N = 32; // the number of integers
   int *data = new int[N]; // allocate memory
   
   if ( data != NULL ) {
      // generate randomized integers
      for ( int i=0; i < N; i++ ) { 
         data[i] = random(0,N);
      }
      // show data before sorting
      printData( data, N ); 
      // sort data 
      qsort( data, N, sizeof(int),
        [] (const void *arg1, const void *arg2) {
         int a = *(int *) arg1; 
         int b = *(int *) arg2;
         return (a - b); // is a less than b?
        } // end of lambda function 
      );
      // show data after sorting
      printData( data, N ); 
      // delete data (free allocated memory)
      delete [] data; 
   } else {
      Serial.println("Memory allocation failed!");
   } 
}

void loop() { }

void printData( const int*data, size_t data_len ) {
  String s;
  s.reserve( 2*data_len );
  for ( int i=0; i < data_len; i++ ) { 
      if (i != 0) {
        s += ',';
      }
      s += data[i];
   }
   Serial.println( s.c_str() );
}
```

สำหรับการเรียงข้อมูลในอาร์เรย์ สามารถใช้คำสั่ง `qsort()` ได้ นอกจากตัวแปรแบบพอยน์เตอร์ที่อ้างอิงอาร์เรย์ของข้อมูล จำนวนข้อมูล และขนาดของข้อมูลแต่ละตัว ยังจะต้องระบุฟังก์ชันในการเปรียบเทียบข้อมูลด้วย ในตัวอย่างนี้ เราสร้างและใช้ฟังก์ชันแบบที่เรียกว่า **Lambda (Anonymous) Function**&#x20;

## **โค้ดตัวอย่างที่ 9: การใช้งาน Timer**&#x20;

**ESP32** มีวงจร **Timer** อยู่ภายใน จำนวน 4 ชุด (หมายเลข 0...3) ตัวอย่างต่อไปนี้สาธิตการเปิดใช้งานวงจร **Timer** และสร้างอินเทอร์รัพท์จากการนับครบหนึ่งช่วงเวลา (**Time Interval**) เช่น กำหนดให้คาบเวลาเท่ากับ 500 มิลลิวินาที และสลับสถานะลอจิกของ **LED** เมื่อเกิดอินเทอร์รัพท์จาก **Timer** และฟังก์ชัน **Callback** ทำงานในแต่ละครั้ง

การเขียนโค้ดไมโครไพธอน มีดังนี้

```python
from micropython import const
from machine import Pin, Timer

LED_PIN = const(5) # use GPIO-5 for LED output
led = Pin( LED_PIN, Pin.OUT )
state = False

def timer_callback(timer): # callback for IRQ timer
    global state
    state = not state  # toggle state
    led.value( state ) # update LED output

timer = Timer( 0 ) # create a Timer object (id = 0)
# use timer in periodic mode (period = 500 msec)
timer.init( period=500, 
            mode=Timer.PERIODIC,
            callback=timer_callback )
try:
    while True:
        pass
except Exception:
    pass
finally:
    timer.deinit() # stop timer
```

สำหรับการเขียนโค้ด **Arduino ESP32** เพื่อใช้งาน **Timer** ก็มีคำสั่งที่เกี่ยวข้องดังนี้ (ศึกษาเพิ่มเติ่มได้จากไฟล์ [`esp32-hal-timer.c`](https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-timer.c))&#x20;

* `timerBegin()` สร้างไทม์เมอร์ (`hw_timer_t *`) อ้างอิงโดยใช้ตัวแปรแบบพอยน์เตอร์ กำหนดค่าตัวหารความถี่ (**Prescaler**) และเลือกโหมดการนับ
* `timerAlarmWrite()` กำหนดค่าสูงสุดของการนับ (หรือคาบของการนับ) และจะให้เกิดซ้ำหรือทำเพียงครั้งเดียว
* `timerAlarmEnable()` เปิดการใช้งานอินเทอร์รัพท์ของไทม์เมอร์
* `timerAttachInterrupt()`ระบุฟังก์ชันสำหรับทำหน้าที่ **Callback** เมื่อเกิดอินเทอร์รัพท์แบบ **edge**

ในตัวอย่างนี้ ตั้งค่าอัตราการนับให้เท่ากับ **1MHz** (ตั้งค่า **Prescaler = 80** เพื่อหารความถี่ **80MHz** ของบัส **APB**) และมีคาบหรือช่วงเวลาในการนับเท่ากับ **500,000** หรือ **500 msec**

```cpp
const int LED_PIN = 22; // use GPIO-22 pin for LED
const int TIMER_ID = 0; // select id from {0,1,2,3}
hw_timer_t *timer = NULL;

volatile bool state = 0;

void IRAM_ATTR timer_callback() {
  state = ! state; // toggle state 
  digitalWrite( LED_PIN, state ); // update LED
}

void setup() {
  Serial.begin(115200);
  pinMode( LED_PIN, OUTPUT );
  // create a timer, count-up mode (true)
  // set 16-bit prescaler to 80 => 80MHz/80 = 1MHz
  timer = timerBegin( TIMER_ID, 80, true );
  
  // set alarm interval: 500,000 usec 
  // auto-reload (periodic): true
  timerAlarmWrite( timer, 500000, true );
  
  // enable timer alarm interrupt
  timerAlarmEnable( timer );
  
  // set function callback for the timer interrupt
  // edge type interrupt: true
  timerAttachInterrupt( timer, &timer_callback, true );
}

void loop() {}
```

## กล่าวสรุป

การศึกษาและเปรียบเทียบโค้ดในภาษาไมโครไพธอนและภาษา **C/C++** (สำหรับ **Arduino**) โดยใช้บอร์ดไมโครคอนโทรลเลอร์ **ESP32** เป็นกรณีศึกษา จะช่วยให้เห็นความเหมือนและความแตกต่างในการเขียนโค้ดในแต่ละภาษาได้ดีขึ้น รวมถึงการใช้คำสั่งต่าง ๆ ที่เกี่ยวข้อง
