การพัฒนาเฟิร์มแวร์สำหรับไมโครคอนโทรลเลอร์
- วิกินี้เป็นส่วนหนึ่งของรายวิชา 01204223
วิกินี้อธิบายถึงขั้นตอนและตัวอย่างการพัฒนาเฟิร์มแวร์ลงบนบอร์ดไมโครคอนโทรลเลอร์ที่เราได้ประกอบขึ้นมา โดยเนื้อหาครอบคลุมเฉพาะสภาพแวดล้อมการพัฒนาโปรแกรมบนลินุกซ์และ Mac OS X เท่านั้น
เนื้อหา
ติดตั้งซอฟท์แวร์ที่เกี่ยวข้อง
สำหรับระบบปฏิบัติการ Linux (Debian/Ubuntu/Mint)
- ครอสคอมไพเลอร์ (cross compiler) สำหรับไมโครคอนโทรลเลอร์ตระกูล AVR รวมถึงไลบรารีที่เกี่ยวข้อง
sudo apt-get install gcc-avr avr-libc
- AVR toolchain
sudo apt-get install binutils-avr
- AVRDUDE (AVR Downloader/UploaDEr) ใช้สำหรับโหลดรหัสภาษาเครื่องลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ผ่านพอร์ต USB
sudo apt-get install avrdude
สำหรับระบบปฏิบัติการ Mac OS X
- ดาวน์โหลดและติดตั้งชุดโปรแกรม CrossPack-AVR เวอร์ชันล่าสุดจาก Objective Development
การพัฒนาเฟิร์มแวร์ด้วยภาษาแอสเซมบลี้
ภาษาแอสเซมบลี้ (assembly language) จัดเป็นภาษาระดับต่ำที่ใช้การแทนรหัสภาษาเครื่อง (machine code) ด้วยสัญลักษณ์ ดังนั้นคำสั่งในภาษาแอสเซมบลี้หนึ่งคำสั่งจึงเทียบเท่ากับคำสั่งในภาษาเครื่องหนึ่งคำสั่งเช่นกัน แม้ภาษาแอสเซมบลี้จะมีความยุ่งยากมากกว่าภาษาชั้นสูงเช่นภาษาซี การเขียนโปรแกรมในภาษาแอสเซมบลี้ก็ยังเป็นที่นิยมในหลาย ๆ สถานการณ์เนื่องจากผู้พัฒนาโปรแกรมสามารถทราบถึงการทำงานของหน่วยประมวลผลได้ในรายละเอียดทุกคำสั่ง โดยเฉพาะอย่างยิ่งในงานที่ต้องมีการกำหนดเวลาในการประมวลผลที่แม่นยำ
ตัวอย่างโปรแกรมภาษาแอสเซมบลี้
ใช้โปรแกรมเท็กซ์เอดิเตอร์สร้างโปรแกรมภาษาแอสเซมบลี้ขึ้นมาหนึ่งโปรแกรมตามตัวอย่างด้านล่าง จากนั้นบันทึกโปรแกรมลงในไฟล์ชื่อ first.S
(สังเกตว่าใช้นามสกุล .S ตัวใหญ่)
.global main main: ldi r16,0b00001000 ; ทำให้ขา PD3 เป็นเอาท์พุท out 0x0A,r16 ldi r16,0b00000000 ; ทำให้ LED สีเขียวติด out 0x0B,r16 loop: rjmp loop ; วนซ้ำอยู่ที่เดิมเพื่อไม่ให้โปรแกรมจบการทำงาน
แต่ละบรรทัดมีความหมายดังนี้
.global main
เป็นการประกาศให้ป้ายระบุตำแหน่งmain
เป็นที่รู้จักของไลบรารีภายนอกmain:
ระบุว่า ณ ตำแหน่งนี้สามารถถูกอ้างถึงได้โดยใช้ชื่อmain
ldi r16,0b00001000
โหลดค่า 00001000 ฐานสอง (ซึ่งเท่ากับ 8 ในฐานสิบ) ลงไปในรีจีสเตอร์ r16out 0x0A,r16
นำค่าใน r16 ไปใส่ไว้ใน I/O รีจีสเตอร์หมายเลข 0xA (ซึ่งเท่ากับ 10 ในฐานสิบ) ซึ่งหมายถึงรีจีสเตอร์ DDRD ดังนั้นจึงมีผลทำให้ขา PD3 ทำหน้าที่เป็นเอาท์พุท ส่วนขา PD0, PD1, PD2, PD4, PD5, PD6 และ PD7 ทำหน้าที่เป็นอินพุทldi r16,0b00000000
โหลดค่า 00000000 ฐานสอง (ซึ่งเท่ากับ 0) ลงไปในรีจีสเตอร์ r16out 0x0B,r16
นำค่าใน r16 ไปใส่ไว้ใน I/O รีจีสเตอร์หมายเลข 0xB (ซึ่งเท่ากับ 11 ในฐานสิบ) ซึ่งหมายถึงรีจีสเตอร์ PORTD มีผลทำให้ขา PD3 มีค่าลอจิกเป็น 0 จึงทำให้ LED สีเขียวติดสว่างloop:
ระบุว่า ณ ตำแหน่งนี้สามารถถูกอ้างถึงได้โดยใช้ชื่อloop
rjmp loop
กระโดดกลับไปทำงานที่ตำแหน่งหน่วยความจำที่ระบุด้วยป้ายloop
ซึ่งก็คือตำแหน่งเดิม
การแอสเซมเบิลเพื่อสร้างรหัสภาษาเครื่อง
โปรแกรมที่เขียนด้วยภาษาแอสเซมบลี้ต้องผ่านกระบวนการแปลภาษาเพื่อให้อยู่ในรูปรหัสภาษาเครื่องโดยอาศัยโปรแกรมแปลภาษาที่เรียกว่าแอสเซมเบลอร์ (assembler) ในที่นี้เราจะใช้โปรแกรม avr-gcc
ซึ่งตัวมันเองจะไปเรียกใช้โปรแกรม avr-as
เพื่อแปลโปรแกรมภาษาแอสเซมบลี้เป็นรหัสภาษาเครื่องสำหรับสถาปัตยกรรม AVR อีกทีหนึ่ง
avr-gcc -mmcu=atmega168 -o first.elf first.S
อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้
-mmcu=atmega168
เป็นตัวบอกคอมไพเลอร์ว่าไมโครคอนโทรลเลอร์ที่ใช้เป็นเบอร์ ATMega168-o first.elf
ระบุว่าให้เอาท์พุทถูกเก็บลงในไฟล์first.elf
หากไม่ระบุโปรแกรมจะสร้างไฟล์ชื่อa.out
แทน
ผลลัพธ์ที่ได้จะอยู่ในรูปของไฟล์ฟอร์แมต ELF (Excutable and Linkable Format) ซึ่งประกอบไปด้วยเฮดเดอร์และข้อมูลเสริมอื่น ๆ อีกมากมาย อย่างไรก็ตามเราต้องการเพียงแค่ส่วนที่เป็นรหัสภาษาเครื่องของโปรแกรม ซึ่งสกัดออกมาได้โดยใช้คำสั่ง avr-objcopy
ดังนี้
avr-objcopy -j .text -j .data -O ihex first.elf first.hex
อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้
-j .text -j .data
สกัดข้อมูลจากเซคชัน .text และ .data ซึ่งเป็นเซคชันที่เก็บโค้ดโปรแกรมและข้อมูลเริ่มต้นในไฟล์ ELF-O ihex
ส่งเอาท์พุทในรูปแบบ Intel HEX
ผลลัพธ์ที่ได้จะถูกเก็บไว้ในไฟล์ first.hex
ซึ่งเป็นไฟล์ ASCII โดยภายในไฟล์จะมีข้อมูลคล้ายคลึงกับที่แสดงในตัวอย่าง
$ cat first.hex :100000000C9434000C943E000C943E000C943E0082 :100010000C943E000C943E000C943E000C943E0068 :100020000C943E000C943E000C943E000C943E0058 :100030000C943E000C943E000C943E000C943E0048 :100040000C943E000C943E000C943E000C943E0038 :100050000C943E000C943E000C943E000C943E0028 :100060000C943E000C943E0011241FBECFEFD4E050 :10007000DEBFCDBF0E9440000C9445000C940000F0 :0E00800007E007B904E008B9FFCFF894FFCFFE :00000001FF
สังเกตว่าไฟล์รหัสภาษาเครื่องดูจะยาวกว่าต้นฉบับโปรแกรมภาษาแอสเซมบลี้มาก ทดลองแปลงโปรแกรมภาษาเครื่องกลับมาเป็นภาษาแอสเซมบลี้โดยใช้คำสั่ง avr-objdump
ดังนี้
$ avr-objdump -d first.elf
กระบวนการข้างต้นเรียกว่าเป็นการทำดิสแอสเซมเบิล (disassemble) ซึ่งอ็อปชัน -d
ย่อมาจาก disassemble บนหน้าจอจะแสดงผลลัพธ์ดังนี้
first.elf: file format elf32-avr Disassembly of section .text: 00000000 <__vectors>: 0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end> 4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt> 8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt> : : ละไว้ : 64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt> 00000068 <__ctors_end>: 68: 11 24 eor r1, r1 6a: 1f be out 0x3f, r1 ; 63 6c: cf ef ldi r28, 0xFF ; 255 6e: d4 e0 ldi r29, 0x04 ; 4 70: de bf out 0x3e, r29 ; 62 72: cd bf out 0x3d, r28 ; 61 74: 0e 94 40 00 call 0x80 ; 0x80 <main> 78: 0c 94 45 00 jmp 0x8a ; 0x8a <_exit> 0000007c <__bad_interrupt>: 7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors> 00000080 <main>: 80: 07 e0 ldi r16, 0x07 ; 7 82: 07 b9 out 0x07, r16 ; 7 84: 04 e0 ldi r16, 0x04 ; 4 86: 08 b9 out 0x08, r16 ; 8 00000088 <loop>: 88: ff cf rjmp .-2 ; 0x88 <loop> 0000008a <_exit>: 8a: f8 94 cli 0000008c <__stop_program>: 8c: ff cf rjmp .-2 ; 0x8c <__stop_program>
ตัวเลขด้านซ้ายมือสุดในแต่ละบรรทัดที่มีคำสั่งภาษาแอสเซมบลี้อยู่ (เช่น 7c:) แสดงตำแหน่งที่อยู่ (address) ของแฟลชโปรแกรมในรูปฐานสิบหก ตัวเลขชุดถัดมาคือรหัสคำสั่งภาษาเครื่อง และส่วนที่เหลือเป็นคำสั่งภาษาแอสเซมบลี้ที่สอดคล้องกัน ดังนั้นบรรทัดที่ระบุว่า
80: 07 e0 ldi r16, 0x07 ; 7
จึงมีความหมายว่าให้บรรจุรหัสคำสั่งภาษาเครื่อง 07 และ e0 (สองไบต์) ลงไปในชิปที่ตำแหน่งหน่วยความจำ 80 และ 81 (ฐานสิบหก)
แต่สังเกตว่าคำสั่งภาษาแอสเซมบลี้ที่เราเขียนไว้ในต้นฉบับปรากฏอยู่ที่ตำแหน่งหน่วยความจำที่ 80 ถึง 89 เท่านั้น แต่รหัสภาษาเครื่องที่ดิสแอมเซมเบิลออกมานั้นกลับประกอบไปด้วยอะไรอีกหลายอย่างที่เราไม่ได้เขียน ทั้งนี้เนื่องจากในสถาปัตยกรรม AVR จำเป็นต้องมีการตั้งค่าเริ่มต้นให้กับชิปหลายอย่างก่อนที่จะเริ่มทำงานได้อย่างถูกต้อง ดังนั้น avr-gcc
จึงปะส่วนการตั้งค่าเริ่มต้นนี้ให้เราในส่วนหัวของโปรแกรม นอกจากนั้นยังมีการปะโค้ดให้วนซ้ำไว้ในส่วนท้ายโปรแกรมเพื้อป้องกันกรณีที่เราเผลอปล่อยโปรแกรมให้จบการทำงานออกมา
การโหลดโปรแกรมลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์
โดยทั่วไปการนำโปรแกรมลงสู่แฟลชของไมโครคอนโทรลเลอร์นั้นมักอาศัยเครื่องโปรแกรมชิป (chip programmer) อย่างไรก็ตามชิป ATmega168 ที่แจกไปให้นั้นได้ถูกป้อนโปรแกรมพิเศษที่เรียกว่าบูทโหลดเดอร์ (boot loader) เอาไว้เพื่อจำลองบอร์ดไมโครคอนโทรลเลอร์เป็นอุปกรณ์โปรแกรมชิปชนิด USBasp ซึ่งจะรอรับรหัสภาษาเครื่องที่ส่งมาทางพอร์ท USB ของเครื่องคอมพิวเตอร์ และเขียนข้อมูลเหล่านั้นลงสู่หน่วยความจำแฟลช
บูทโหลดเดอร์ถูกติดตั้งไว้ในตำแหน่งแฟลชที่เป็นบูทเซคเตอร์ของชิป (เริ่มต้นที่แอดเดรส 0x3800 ของหน่วยความจำแฟลช) ซึ่งเป็นจุดแรกที่ไมโครคอนโทรลเลอร์เริ่มต้นทำงาน กระบวนการทำงานของบูทโหลดเดอร์ที่เตรียมไว้ให้เป็นดังรูปด้านล่าง
จะเห็นว่าเงื่อนไขของการที่จะให้บูทโหลดเดอร์เข้าสู่โหมด USB เพื่อรอรับข้อมูลนั้นคือไมโครคอนโทรลเลอร์ต้องถูกรีเซ็ตด้วยปุ่มรีเซ็ต และขา Bootloader (PD7) ต้องถูกเชื่อมลงกราวนด์ ซึ่งทำได้โดยการเสียบจั๊มเปอร์เพื่อชอร์ตวงจรในตำแหน่งที่ระบุว่า Boot-loader บนบอร์ด จุดสังเกตที่แสดงให้เห็นว่าไมโครคอนโทรลเลอร์กำลังรอรับข้อมูลจากพอร์ท USB คือ LED สีเขียวบนบอร์ดจะกระพริบสั้น ๆ และถี่ ๆ
ในระหว่างที่ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB หากเรียกคำสั่ง lsusb
บนลินุกซ์ บนเครื่องคอมพิวเตอร์จะต้องปรากฏรายการอุปกรณ์ที่มี VID:PID เป็น 16c0:05dc ดังตัวอย่าง
$ lsusb Bus 004 Device 001: ID 0000:0000 Bus 003 Device 007: ID 16c0:05dc VOTI shared ID for use with libusb <-- ต้องปรากฏบรรทัดนี้ (หมายเลข Bus และ Device อาจแตกต่างออกไป) Bus 003 Device 001: ID 0000:0000 Bus 002 Device 001: ID 0000:0000 Bus 001 Device 001: ID 0000:0000
สำหรับเครื่องที่ใช้ Mac OS X ให้ใช้คำสั่ง system_profiler
ควบคู่กับ grep
เพื่อหาอุปกรณ์ที่ชื่อ USBasp ดังแสดง
$ system_profiler SPUSBDataType | grep -A 10 USBasp USBasp: Product ID: 0x05dc Vendor ID: 0x16c0 Version: 1.02 Speed: Up to 1.5 Mb/sec Manufacturer: www.fischl.de Location ID: 0xfd130000 Current Available (mA): 500 Current Required (mA): Unknown (Device has not been configured)
อันแสดงว่าไมโครคอนโทรลเลอร์อยู่ในสภาพพร้อมที่จะรับโปรแกรมแล้ว
การส่งโปรแกรมไปยังไมโครคอนโทรลเลอร์ผ่านพอร์ท USB นั้นให้ใช้คำสั่ง avrdude
ดังแสดง
avrdude -p atmega168 -c usbasp -U flash:w:first.hex
อ็อพชันต่าง ๆ ที่ใช้ในคำสั่งข้างต้นมีหน้าที่ดังนี้
-p atmega168
ระบุว่าไมโครคอนโทรลเลอร์ปลายทางคือเบอร์ ATmega168-c usbasp
ระบุว่าเครื่องโปรแกรมชิปที่ใช้คือชนิด USBAsp-U flash:w:first.hex
ระบุว่าให้ดำเนินการเขียนข้อมูลลงสู่หน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ โดยนำเข้าข้อมูลจากไฟล์first.hex
หมายเหตุ: หากพบปัญหาเกี่ยวกับสิทธิการเข้าถึงอุปกรณ์ USB ให้ดำเนินตามขั้นตอนที่อธิบายไว้ในเอกสาร การแก้ไขสิทธิการเข้าถึงพอร์ท USB ของบอร์ด MCU
การพัฒนาเฟิร์มแวร์ด้วยภาษาซี
ภาษาซีจัดเป็นภาษาระดับสูง (high-level programming language) ที่ถือว่ามีการทำงานใกล้เคียงกับภาษาเครื่องมากโดยที่ผู้พัฒนาโปรแกรมไม่จำเป็นต้องเขียนคำสั่งให้ละเอียดยิบเหมือนกับภาษาแอสเซมบลี้ จึงเป็นภาษาที่ได้รับความนิยมมากในงานที่ต้องเขียนโปรแกรมใกล้ชิดกับฮาร์ดแวร์ดังเช่นงานด้านระบบฝังตัว (embedded system)
Short, sweet, to the point, FREE-exactly as informtaoin should be!
การคอมไพล์โปรแกรมให้เป็นภาษาเครื่อง
กระบวนการแปลโปรแกรมภาษาชั้นสูงให้เป็นภาษาเครื่องนั้นเรียกว่าเป็นการคอมไพล์ (compile) ซึ่งอาศัยโปรแกรมที่เรียกว่าคอมไพเลอร์ (compiler) เป็นตัวดำเนินการ เราอาศัยโปรแกรม avr-gcc
เป็นตัวคอมไพเลอร์โดยเรียกใช้งานดังนี้ (สังเกตว่ามีการใช้อ็อปชัน -O
เพิ่มเติมขึ้นมา)
avr-gcc -mmcu=atmega168 -O -o first.elf first.c
อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้
-mmcu=atmega168
เป็นตัวบอกคอมไพเลอร์ว่าไมโครคอนโทรลเลอร์ที่ใช้เป็นเบอร์ ATMega168-O
ระบุว่าให้คอมไพเลอร์ทำ code optimization ซึ่งจำเป็นต้องใช้เพื่อให้ฟังก์ชัน_delay_ms()
ทำงานได้ถูกต้อง-o first.elf
ระบุว่าให้เอาท์พุทถูกเก็บลงในไฟล์first.elf
หากไม่ระบุโปรแกรมจะสร้างไฟล์ชื่อa.out
แทน
หมายเหตุ: avr-gcc
ทำหน้าที่เป็นได้ทั้งแอสเซมเบลอร์และคอมไพเลอร์ ขึ้นอยู่กับนามสกุลของไฟล์อินพุทว่าเป็น .S หรือ .c
การสร้างกระบวนการอัตโนมัติด้วย Makefile
จากที่ผ่านมาจะเห็นว่าการพัฒนาเฟิร์มแวร์สำหรับไมโครคอนโทรลเลอร์นั้นประกอบด้วยการแก้ไขโปรแกรมด้วยเท็กซ์เอดิเตอร์ และเซฟลงในไฟล์ .c จากนั้นจึงดำเนินตามขั้นตอนดังนี้
- ครอสคอมไพล์โปรแกรมด้วยคำสั่ง avr-gcc
- สกัดรหัสภาษาเครื่องจากไฟล์ ELF ด้วยคำสั่ง avr-objcopy
- ส่งรหัสภาษาเครื่องไปยังไมโครคอนโทรลเลอร์ด้วยคำสั่ง avrdude
ในแต่ละขั้นตอนนั้นมีการเรียกคำสั่งที่ค่อนข้างยาว เราจึงควรสร้าง Makefile ขึ้นมาเพื่อให้คำสั่งเหล่านี้ถูกเรียกใช้งานโดยอัตโนมัติ
all: first.hex flash: first.hex avrdude -p atmega168 -c usbasp -U flash:w:first.hex first.hex: first.elf avr-objcopy -j .text -j .data -O ihex first.elf first.hex first.elf: first.c avr-gcc -mmcu=atmega168 -O -o first.elf first.c
(ระวังว่าบรรทัดที่เยื้องเข้าไปนั้นต้องเป็นอักขระแท็บ ไม่ใช่ช่องว่าง)
สมมติว่าภายในไดเรคตอรีที่เรียกใช้คำสั่ง make
มีเพียงไฟล์ first.c
และ Makefile
การเรียกคำสั่ง
make
จะถือเป็นการสร้างเป้าหมายที่ระบุไว้ตัวแรกสุด ในที่นี้คือ all ซึ่งจะมีผลให้มีการดำเนินการดังนี้
- เป้าหมาย all ระบุไว้ว่าให้สร้างเป้าหมาย
first.hex
ขึ้นมา make
หาไฟล์first.hex
ไม่พบ แต่ทราบว่าสามารถสร้างขึ้นจากfirst.elf
make
หาไฟล์first.elf
ไม่พบ แต่ทราบว่าสามารถสร้างขึ้นจากfirst.c
make
พบไฟล์first.c
ในไดเรคตอรี จึงเรียกคำสั่งavr-gcc
เพื่อสร้างไฟล์first.elf
- เมื่อได้
first.elf
มาแล้วจึงเรียกavr-objcopy
เพื่อสร้างไฟล์first.hex
เป็นอันเสร็จกระบวนการ
หากต้องการให้มีการเขียนแฟลชไมโครคอนโทรลเลอร์ ให้เรียกคำสั่ง make โดยระบุเป้าหมาย flash ดังนี้
make flash
ซึ่งจะมีการดำเนินการสร้างเป้าหมาย first.hex
โดยอัตโนมัติหากหาไฟล์นี้ไม่พบหรือไฟล์ที่มีอยู่นั้นเก่ากว่าไฟล์ first.c
จากนั้นจึงตามด้วยการเรียกใช้คำสั่ง avrdude
เพื่อแฟลชเฟิร์มแวร์ใหม่ให้กับไมโครคอนโทรลเลอร์
หากต้องการนำ Makefile ไปปรับใช้ในโครงงานอื่นได้ง่าย เราสามารถปรับ Makefile ให้ยืดยุ่นขึ้นโดยระบุขั้นตอนด้วยรูปแบบ (pattern) ดังตัวอย่าง
TARGET=first.hex MCU=atmega168 F_CPU=16000000L CFLAGS=-DF_CPU=$(F_CPU) -mmcu=$(MCU) -O all: $(TARGET) flash: $(TARGET) avrdude -p atmega168 -c usbasp -U flash:w:$(TARGET) %.hex: %.elf avr-objcopy -j .text -j .data -O ihex $< $@ %.elf: %.c avr-gcc $(CFLAGS) -o $@ $? %.o: %.c avr-gcc -c $(CFLAGS) $@ $<
การนำ Makefile นี้ไปใช้กับโครงงานอื่นทำได้โดยการเปลี่ยนบรรทัดแรกจาก first.hex
เป็ืนชื่อไฟล์สำหรับโครงงานนั้น ๆ เท่านั้น สังเกตว่าเรายังได้ระบุค่าของ F_CPU
ไว้ระหว่างการคอมไพล์ ดังนั้นบรรทัด
#define F_CPU 16000000L
ในไฟล์ .c จึงไม่จำเป็นอีกต่อไป แต่เพื่อความแน่ใจว่าค่าของ F_CPU จะต้องถูกนิยามไว้ที่ใดที่หนึ่ง และต้องไม่ถูกนิยามซ้ำ เราควรใส่เงื่อนไขการนิยามไว้ตอนต้นของโปรแกรมดังนี้
#ifndef F_CPU #define F_CPU 16000000L #endif
บทความที่เกี่ยวข้อง
- การบัดกรีแผงวงจรไมโครคอนโทรลเลอร์
- แผงวงจรพ่วง (Peripheral Board)
- การวัดสัญญาณแอนะล็อกด้วยไมโครคอนโทรลเลอร์