Arduino C/C++ versus MicroPython
ตัวอย่างสำหรับการเปรียบเทียบรูปแบบการเขียนโค้ดด้วยภาษา Arduino C/C++ และไมโครไพธอนสำหรับบอร์ดไมโครคอนโทรลเลอร์ ESP32
ตัวอย่างที่ 1: การคำนวณเลขยกกำลังฐานสอง
ถ้าเราต้องการเขียนโค้ดเพื่อคำนวณค่าของเลขจำนวนเต็ม เช่น "สองยกกำลังสี่สิบลบด้วยหนึ่ง" ให้เป็นค่าของตัวแปร x และแสดงค่าของตัวแปรให้เป็นเลขฐานสิบ (decimal) และฐานสิบหก (hexadecimal) เราจะเขียนโค้ดอย่างไร ?
ในกรณีนี้ การเก็บข้อมูลสำหรับตัวแปร x จะต้องมีขนาดหน่วยความจำอย่างน้อย 40 บิต หรือ 5 ไบต์
ข้อความเอาต์พุตจากการทำงานของโค้ดสำหรับค่าของตัวแปร x จะต้องได้ตามรูปแบบต่อไปนี้
ถ้าเขียนโค้ดด้วยไมโครไพธอน ก็สามารถเขียนโดยใช้คำสั่งต่อไปนี้
การเขียนนิพจน์ 2**40
หมายถึง 2 ยกกำลัง 40 โดยที่ **
เป็นโอเปอร์เรเตอร์ (Operator) สำหรับเลขยกกำลังในภาษาไพธอน
การแสดงข้อความเอาต์พุตสำหรับค่าของตัวแปร ทำได้ โดยใช้คำสั่ง print()
ร่วมกับคำสั่ง format()
เมื่อใช้กับข้อมูลแบบstring
ถัดไปลองมาดูโค้ดสำหรับ Arduino-ESP32 เปรียบเทียบกัน
ในกรณีนี้ เราจะใช้โอเปอร์เรเตอร์เลื่อนบิต (Shift Operator) ไปทางซ้าย (<<
) แทนการคำนวณเลขยกกำลังสำหรับฐานสอง และเขียนนิพจน์ในภาษา C/C++ ได้เป็น (1LLU << 40)
และจะต้องใช้ชนิดของข้อมูลแบบ unsigned long long
หรือ uint64_t
ซึ่งมีขนาด 8 ไบต์ (64 บิต) ในกรณีตัวอย่างนี้ เราไม่สามารถใช้ชนิดข้อมูลเลขจำนวนเต็มขนาด 32 บิต เนื่องจากต้องใช้อย่างน้อย 40 บิต
การแสดงข้อความเอาต์พุตสำหรับค่าของตัวแปร ทำได้โดยใช้คำสั่ง Serial.printf()
โครงสร้างสำหรับการเขียนโค้ด Arduino Sketch จะต้องมีฟังก์ชัน void setup() {...} สำหรับโค้ดที่ต้องทำเมื่อเริ่มต้น และ void loop() {...} สำหรับโค้ดที่ต้องทำซ้ำไปเรื่อย ๆ หลังจากนั้น
หรืออีกกรณีหนึ่ง เราอาจใช้ฟังก์ชัน pow()
สำหรับการคำนวณเลขยกกำลังดังนี้
แต่มีข้อสังเกตและข้อควรระวังในการใช้งานคือ ฟังก์ชัน pow()
ของ Arduino API นั้น จะได้ผลลัพธ์จากการคำนวณเป็น double
ที่มีขนาด 8 ไบต์ (สำหรับ ESP32 หรือตัวประมวลผลขนาด 32 บิต) ตามรูปแบบมาตรฐาน IEEE 754 Double-Precision ประกอบด้วย
1 บิต: Sign Bit (S)
11 บิต: Exponent (e)
52 บิต: Fraction (F)
ในกรณีตัวอย่างนี้ ถ้าจะคำนวณค่า "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) ของข้อมูลชุดนี้ แล้วแสดงเป็นข้อความเอาต์พุตสำหรับผลลัพธ์ที่ได้ จะเขียนโค้ดอย่างไร ?
กำหนดให้ตัวอย่างข้อความเอาต์พุตสำหรับข้อมูลตัวเลขดังกล่าว เป็นดังนี้
ในกรณีของไมโครไพธอน เราก็สามารถเขียนโค้ดได้ โดยสร้างรายการหรือลิสต์ (List) ของเลขจำนวนเต็ม จากนั้นก็ใช้คำสั่ง min()
, max()
, len()
, sum()
หาค่าต่ำสุด ค่าสูงสุด จำนวนข้อมูลสมาชิกทั้งหมดในรายการ และผลรวมของข้อมูลเหล่านั้น
ถ้าจะเขียนโค้ด Arduino Sketch ให้ได้ผลลัพธ์เหมือนกัน อาจเขียนโค้ดตามรูปแบบต่อไปนี้ โดยใช้ชนิดข้อมูลเป็น int32_t
(ข้อมูลแบบ signed integer
ขนาด 32 บิต หรือ 4 ไบต์)
ตัวอย่างที่ 3: การคำนวณค่า CRC8 สำหรับลำดับข้อมูลไบต์
การคำนวณค่า Cyclic Redundancy Check (CRC) เป็นวิธีการในการตรวจสอบความผิดพลาดของข้อมูล (Error Detection) เช่น ถ้ามีข้อมูลไบต์ตามลำดับอยู่จำนวนหนึ่ง เราสามารถนำไปคำนวณค่า CRC (มีขนาด n บิต เช่น n=8, 16, 32 สำหรับ CRC-8, CRC-16, CRC-32 เป็นต้น) โดยใช้อัลกอริธึม หรือวิธีการคำนวณแบบใดแบบหนึ่ง (มีหลายรูปแบบให้เลือกใช้) ค่าที่ได้นี้เรียกว่า CRC checksum
ถ้ามีการเปลี่ยนแปลงของข้อมูลเกิดขึ้น เช่น ระหว่างการสื่อสารรับส่งข้อมูล เราก็สามารถคำนวณค่า 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)
รูปแบบการคำนวณค่า CRC มีหลายวิธี เช่น ขึ้นอยู่กับหหุนามตัวหารที่ได้เลือกมาใช้งาน และทิศทางของบิต (Bit Ordering) เช่น MSB First / normal order หรือ LSB First / reverse order
ในตัวอย่างนี้ สมมุติว่า เราต้องการคำนวณค่า CRC-8 สำหรับลำดับข้อมูลไบต์ดังนี้ 0x34, 0xff, 0x40, 0x52, 0x67, 0x01 และเลือกใช้วิธีการตามรูปแบบที่เรียกว่า CRC-8 Dallas/Maxim ซึ่งจะได้ผลลัพธ์เท่ากับ 0x46 และตัวอย่างข้อความเอาต์พุตมีดังนี้
การคำนวณ CRC-8 ตามรูปแบบที่กำหนดโดยบริษัท Dallas/Maxim (อ้างอิงจากเอกสาร Maxim Application Note 27: "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
โค้ดไมโครไพธอนมีดังนี้
จากตัวอย่างโค้ดในภาษาไมโครไพธอน ถ้าจะเขียนด้วย Arduino Sketch ก็มีตัวอย่างโค้ดเอาไว้เปรียบเทียบกันดังนี้
ตัวอย่างที่ 4: การสร้างเลขสุ่มขนาด 32 บิต แสดงเป็นเลขฐานสอง
ตัวอย่างถัดไปเป็นการสาธิตการสร้างเลขสุ่ม (Pseudo-Random Number Generation) ให้เป็นเลขจำนวนเต็มขนาด 32 บิต และแสดงเป็นข้อความเอาต์พุตให้เป็นเลขฐานสอง จะมีวิธีการเขียนโค้ดอย่างไร ?
ในตัวอย่างนี้ การตั้งค่าเริ่มต้นหรือ Seed ให้กับตัวสร้างเลขสุ่ม เราจะใช้วิธีการอ่านค่าจากขาแอนะล็อกอินพุตที่ไม่ได้ต่อใช้งาน (Floating Input) กับวงจรหรือสัญญาณใด ๆ ของบอร์ด ESP32 และค่าที่อ่านได้เมื่อโปรแกรมทำงานในแต่ละครั้ง ควรจะได้ไม่ซ้ำเดิม
ตัวอย่างการเขียนโค้ดไมโครไพธอนมีดังนี้
ในโค้ดตัวอย่าง ได้เลือกใช้ขา GPIO-32 / ADC1_CH4 และใช้คำสั่งของคลาส ADC
อ่านค่าอินพุตแล้วนำมาใช้เป็นค่า Seed สำหรับการทำงานของคลาส random
คำสั่ง random.getrandbits()
จะสร้างเลขสุ่มตามจำนวนบิตที่ต้องการ (ในกรณีนี้คือ 32 บิต) และได้เป็ข้อมูลเลขจำนวนเต็ม จากนั้นจึงใช้คำสั่ง bin()
แปลงเป็นข้อความเลขฐานสอง (32-bit Binary String)
แต่ถ้าเขียนโค้ด Arduino Sketch ก็มีตัวอย่างดังนี้
คำสั่ง analogRead()
ใช้สำหรับการอ่านค่าจากขาแอนะล็อกอินพุต คำสั่ง randomSeed()
กำหนดค่าเริ่มต้นสำหรับตัวสร้างเลขสุ่ม และการใช้คำสั่ง random(0,2)
จะสุ่มตัวเลขได้เป็น 0 หรือ 1
ESP32 มีวงจรภายในที่เรียกว่า Hardware Random Number Generator (RNG) และสามารถนำมาใช้สร้างเลขสุ่มขนาด 32 บิต แต่จะต้องเปิดใช้งาน Wi-Fi หรือ Bluetooth ร่วมด้วย และฟังก์ชันสำหรับสร้างเลขสุ่มโดยใช้ RNG คือ esp_random()
ซึ่งมีการประกาศไว้ในไฟล์ esp_system.h
)
เราสามารถเขียนโค้ดอีกกรณีหนึ่งได้ดังนี้
ตัวอย่างโค้ดที่ 5: การทำให้ LED กระพริบ
ในตัวอย่างนีัสาธิตการทำให้ LED กระพริบ โดยเลือกใช้ขา GPIO ของ ESP32 เช่น GPIO-22 หรือ GPIO-5 เป็นเอาต์พุต (เลือกขาให้ตรงกับ Onboard LED) และมีการกำหนดสถานะเอาต์พุต High / Low สลับกันไปเรื่อย ๆ โดยเว้นระยะเวลาประมาณ 500 มิลลิวินาที
ถ้าเขียนโค้ดด้วยไมโครไพธอน ก็ทำได้ดังนี้
หรือถ้าจะเขียนแบบไม่ใช้คำสั่ง time.sleep_ms()
ก็ทำได้โดยใช้วิธีตรวจสอบระยะเวลาในการอัปเดตสถานะของ LED ในครั้งถัดไป
แต่ถ้าจะเขียนโค้ด Arduino Sketch ก็มีตัวอย่างดังนี้
หรือใช้วิธีตรวจสอบระยะเวลาในการอัปเดตสถานะของ LED ในครั้งถัดไป
โค้ดตัวอย่างที่ 6: การแปลงค่า BCD ให้เป็นข้อมูลบิตสำหรับ 7-Segment Display
ตัวอย่างนี้สาธิตการเขียนโค้ดเพื่อแปลงค่าแบบ BCD (Binary-Coded Decimal) ขนาด 4 บิต ให้กลายเป็นข้อมูลขนาด 7 บิต (หรือขนาด 1 ไบต์) เพื่อแสดงตัวเลข 0..9 สำหรับ 7-Segment Display (สำหรับเซ็กเมนต์ 7 ส่วน ได้แก่ a, b, c,..., g ) จำนวนหนึ่งหลัก (ทำงานหรือต่อวงจรแบบ Common-Cathode) โดยใช้บิตที่ 0 (LSB) สำหรับเซ็กเมนต์ a, บิตที่ 1 สำหรับเซ็กเมนต์ b ไปตามลำดับ
ค่าตัวเลข 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
ที่ได้จากการทำงานของฟังก์ชันนี้
ตัวอย่างข้อความเอาต์พุต
หรือจะใช้วิธีอ่านค่าจากตารางค่าคงที่ (Lookup Table) จากอาร์เรย์ ขนาด 16 ไบต์
ในการเขียนโค้ดสำหรับ Arduino Sketch เพื่อตรวจสอบเงื่อนไข เราสามารถใช้ประโยค switch-case ได้แทนการใช้ประโยค if-else ในกรณีตัวอย่างนี้
หรือเขียนโค้ดโดยใช้วิธีอ่านค่าจากตารางค่าคงที่ มึจำนวนข้อมูล 16 ไบต์
โค้ดตัวอย่างที่ 7: การตรวจสอบอินเทอร์รัพท์ภายนอกจากปุ่มกด
ตัวอย่างถัดไป สาธิตการเขียนโค้ดเพื่อตรวจสอบดูว่า มีการกดปุ่มหรือไม่ โดยเปิดใช้งานอินเทอร์รัพท์สำหรับขา GPIO ที่เกี่ยวข้อง เช่น เลือกใช้ขา GPIO-23
การเขียนโค้ดไมโครไพธอนในตัวอย่างนี้ จะต้องมีการใช้คลาส machine.Pin
โดยสร้างอ็อปเจกต์จากคลาสนี้ เพื่อใข้งานในโหมดอินพุต และเปิดใช้งานวงจรภายในสำหรับตัวต้านทานแบบ Pull-Up
ถัดไปมีการเปิดใช้งาน IRQ (Interrupt Request) สำหรับขา GPIO ดังกล่าว และจะต้องระบุว่า ให้ตรวจสอบการเกิดเหตุการณ์แบบใด ในกรณีนี้คือ ขอบขาลง หรือ Falling Edge และสร้างฟังก์ชัน btn_callback()
เพื่อทำหน้าที่เป็น Callback Function หรือ Interrupt Handler ทุกครั้งที่เกิดเหตุการณ์ ฟังก์ชันนี้จะทำให้ค่าของตัวแปร num_presses
เพิ่มขึ้นทีละหนึ่ง
ในส่วนของ Main Loop ที่ใช้ประโยคแบบ while True
จะต้องมีการตรวจสอบค่าของตัวแปร num_presses
ถ้ามีค่ามากกว่า 0 แสดงว่า มีการกดปุ่มเกิดขึ้น
ถ้าเกิดเหตุการณ์สองครั้งตามลำดับ มีระยะห่างอย่างน้อย 100 มิลลิวินาที และค่าของอินพุตจะต้องคงที่ (stable) เป็น 0 ในช่วงเวลาสั้น ๆ หลังเกิดเหตุการณ์จากการกดปุ่ม จะถือว่าเป็นการกดปุ่มที่ถูกต้อง เพื่อลดปัญหาของ Button Bouncing และถ้ากดปุ่มไปทั้งหมด 10 ครั้งแล้ว ให้ปิดการทำงานของ IRQ สำหรับขาอินพุตดังกล่าว
ลองมาดูตัวอย่างการเขียนโค้ด Arduino C/C++ ในเชิงเปรียบเทียบกัน
ในการเปิดหรือปิดการใช้งานอินเทอร์รัพท์ที่ขา GPIO ของ ESP32 เราใช้คำสั่ง attachInterrupt()
และ detachInterrupt()
ตามลำดับ (ผู้อ่านสามารถศึกษาการทำงานของคำสั่งนี้ได้จากโค้ด esp32-hal-gpio.h
และ esp32-hal-gpio.c
หรือโค้ดตัวอย่าง GPIOInterrupt.ino
)
โค้ดตัวอย่างที่ 8: การเรียงลำดับข้อมูลในอาร์เรย์
ตัวอย่างนี้สาธิตการสุ่มตัวเลขซึ่งมีทั้งหมด N (เช่น N=32) และเป็นเลขจำนวนเต็มในช่วงที่กำหนด [0, N-1] นำไปใส่ลงในอาร์เรย์ แล้วแสดงค่าตัวเลข ก่อนและหลังการเรียงข้อมูลจากน้อยไปมาก
โค้ดไมโครไพธอนมีดังนี้
ถ้าจะเขียนโค้ดสำหรับ Arduino ESP32 ก็มีแนวทางดังนี้
สำหรับการเรียงข้อมูลในอาร์เรย์ สามารถใช้คำสั่ง qsort()
ได้ นอกจากตัวแปรแบบพอยน์เตอร์ที่อ้างอิงอาร์เรย์ของข้อมูล จำนวนข้อมูล และขนาดของข้อมูลแต่ละตัว ยังจะต้องระบุฟังก์ชันในการเปรียบเทียบข้อมูลด้วย ในตัวอย่างนี้ เราสร้างและใช้ฟังก์ชันแบบที่เรียกว่า Lambda (Anonymous) Function
โค้ดตัวอย่างที่ 9: การใช้งาน Timer
ESP32 มีวงจร Timer อยู่ภายใน จำนวน 4 ชุด (หมายเลข 0...3) ตัวอย่างต่อไปนี้สาธิตการเปิดใช้งานวงจร Timer และสร้างอินเทอร์รัพท์จากการนับครบหนึ่งช่วงเวลา (Time Interval) เช่น กำหนดให้คาบเวลาเท่ากับ 500 มิลลิวินาที และสลับสถานะลอจิกของ LED เมื่อเกิดอินเทอร์รัพท์จาก Timer และฟังก์ชัน Callback ทำงานในแต่ละครั้ง
การเขียนโค้ดไมโครไพธอน มีดังนี้
สำหรับการเขียนโค้ด Arduino ESP32 เพื่อใช้งาน Timer ก็มีคำสั่งที่เกี่ยวข้องดังนี้ (ศึกษาเพิ่มเติ่มได้จากไฟล์ esp32-hal-timer.c
)
timerBegin()
สร้างไทม์เมอร์ (hw_timer_t *
) อ้างอิงโดยใช้ตัวแปรแบบพอยน์เตอร์ กำหนดค่าตัวหารความถี่ (Prescaler) และเลือกโหมดการนับtimerAlarmWrite()
กำหนดค่าสูงสุดของการนับ (หรือคาบของการนับ) และจะให้เกิดซ้ำหรือทำเพียงครั้งเดียวtimerAlarmEnable()
เปิดการใช้งานอินเทอร์รัพท์ของไทม์เมอร์timerAttachInterrupt()
ระบุฟังก์ชันสำหรับทำหน้าที่ Callback เมื่อเกิดอินเทอร์รัพท์แบบ edge
ในตัวอย่างนี้ ตั้งค่าอัตราการนับให้เท่ากับ 1MHz (ตั้งค่า Prescaler = 80 เพื่อหารความถี่ 80MHz ของบัส APB) และมีคาบหรือช่วงเวลาในการนับเท่ากับ 500,000 หรือ 500 msec
กล่าวสรุป
การศึกษาและเปรียบเทียบโค้ดในภาษาไมโครไพธอนและภาษา C/C++ (สำหรับ Arduino) โดยใช้บอร์ดไมโครคอนโทรลเลอร์ ESP32 เป็นกรณีศึกษา จะช่วยให้เห็นความเหมือนและความแตกต่างในการเขียนโค้ดในแต่ละภาษาได้ดีขึ้น รวมถึงการใช้คำสั่งต่าง ๆ ที่เกี่ยวข้อง
Last updated
Was this helpful?