> For the complete documentation index, see [llms.txt](https://think-embedded.gitbook.io/micropython/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://think-embedded.gitbook.io/micropython/arduino-versus-micropython.md).

# 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** เป็นกรณีศึกษา จะช่วยให้เห็นความเหมือนและความแตกต่างในการเขียนโค้ดในแต่ละภาษาได้ดีขึ้น รวมถึงการใช้คำสั่งต่าง ๆ ที่เกี่ยวข้อง


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://think-embedded.gitbook.io/micropython/arduino-versus-micropython.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
