ผลต่างระหว่างรุ่นของ "การพัฒนาเฟิร์มแวร์สำหรับไมโครคอนโทรลเลอร์"
Chaiporn (คุย | มีส่วนร่วม) |
Chaiporn (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 52 รุ่นระหว่างกลางโดยผู้ใช้ 4 คน) | |||
แถว 1: | แถว 1: | ||
− | วิกินี้อธิบายถึงขั้นตอนและตัวอย่างการพัฒนาเฟิร์มแวร์ลงบนบอร์ดไมโครคอนโทรลเลอร์ที่เราได้ประกอบขึ้นมา | + | : ''วิกินี้เป็นส่วนหนึ่งของรายวิชา [[01204223]]'' |
+ | |||
+ | วิกินี้อธิบายถึงขั้นตอนและตัวอย่างการพัฒนาเฟิร์มแวร์ลงบนบอร์ดไมโครคอนโทรลเลอร์ที่เราได้ประกอบขึ้นมา โดยเนื้อหาครอบคลุมเฉพาะสภาพแวดล้อมการพัฒนาโปรแกรมบนลินุกซ์และ Mac OS X เท่านั้น | ||
== ติดตั้งซอฟท์แวร์ที่เกี่ยวข้อง == | == ติดตั้งซอฟท์แวร์ที่เกี่ยวข้อง == | ||
+ | === สำหรับระบบปฏิบัติการ Linux (Debian/Ubuntu/Mint รวมถึง Raspberry Pi) === | ||
* ครอสคอมไพเลอร์ (cross compiler) สำหรับไมโครคอนโทรลเลอร์ตระกูล AVR รวมถึงไลบรารีที่เกี่ยวข้อง | * ครอสคอมไพเลอร์ (cross compiler) สำหรับไมโครคอนโทรลเลอร์ตระกูล AVR รวมถึงไลบรารีที่เกี่ยวข้อง | ||
− | sudo apt | + | sudo apt install gcc-avr avr-libc |
* AVR toolchain | * AVR toolchain | ||
− | sudo apt | + | sudo apt install binutils-avr |
* [http://www.bsdhome.com/avrdude/ AVRDUDE] (AVR Downloader/UploaDEr) ใช้สำหรับโหลดรหัสภาษาเครื่องลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ผ่านพอร์ต USB | * [http://www.bsdhome.com/avrdude/ AVRDUDE] (AVR Downloader/UploaDEr) ใช้สำหรับโหลดรหัสภาษาเครื่องลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ผ่านพอร์ต USB | ||
− | sudo apt | + | sudo apt install avrdude |
+ | |||
+ | === สำหรับระบบปฏิบัติการ Mac OS X === | ||
+ | * ดาวน์โหลดและติดตั้งชุดโปรแกรม CrossPack-AVR เวอร์ชันล่าสุดจาก [http://www.obdev.at/products/crosspack/download.html Objective Development] | ||
+ | |||
+ | == การพัฒนาเฟิร์มแวร์ด้วยภาษาซี == | ||
+ | ภาษาซีจัดเป็นภาษาระดับสูง (high-level programming language) ที่ถือว่ามีการทำงานใกล้เคียงกับภาษาเครื่องมากโดยที่ผู้พัฒนาโปรแกรมไม่จำเป็นต้องเขียนคำสั่งให้ละเอียดยิบเหมือนกับภาษาแอสเซมบลี้ จึงเป็นภาษาที่ได้รับความนิยมมากในงานที่ต้องเขียนโปรแกรมใกล้ชิดกับฮาร์ดแวร์ดังเช่นงานด้าน[http://en.wikipedia.org/wiki/Embedded_system ระบบฝังตัว (embedded system)] | ||
− | == | + | === โปรแกรมตัวอย่างภาษาซี === |
− | + | ทดลองพิมพ์โปรแกรมตัวอย่างต่อไปนี้ และบันทึกไว้ในชื่อ <code>first.c</code> | |
− | + | #define F_CPU 16000000 // บอกไลบรารีว่า MCU ทำงานที่ 16MHz | |
− | |||
− | . | + | #include <avr/io.h> // โหลดนิยามสำหรับรีจีสเตอร์ที่ควบคุมอินพุท/เอาท์พุท (เช่น PORTD, DDRD) |
− | + | #include <util/delay.h> // โหลดนิยามสำหรับฟังก์ชัน _delay_ms() | |
− | + | ||
− | + | int main() | |
− | + | { | |
− | + | PORTD = 0b00000000; // กำหนดลอจิกขา PD7..0 เป็น 0 | |
− | + | DDRD = 0b00001000; // กำหนดให้ขา PD3 ทำหน้าที่เอาท์พุท | |
− | + | ||
+ | while (1) | ||
+ | { | ||
+ | PORTD = 0b00001000; // ส่งลอจิก 1 ไปที่ขา PD3 | ||
+ | _delay_ms(1000); // หน่วงเวลารอ 1000 มิลลิวินาที | ||
+ | PORTD = 0b00000000; // ส่งลอจิก 0 ไปที่ขา PD3 | ||
+ | _delay_ms(1000); // หน่วงเวลารอ 1000 มิลลิวินาที | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | รายละเอียดเพิ่มเติมเกี่ยวกับโปรแกรม | ||
+ | * โปรแกรมข้างต้นเรียกใช้นิยามชื่อรีจีสเตอร์ (<code>PORTD</code>, <code>DDRD</code> ฯลฯ) จากไฟล์เฮดเดอร์ <code>avr/io.h</code> และฟังก์ชันหน่วงเวลา <code>_delay_ms()</code> จากไฟล์เฮดเดอร์ <code>util/delay.h</code> | ||
+ | * ตัวเลขที่นำหน้าด้วย <code>0b</code> ในโค้ดภาษาซีเป็นการบอกให้คอมไพเลอร์ตีความค่าตัวเลขที่ตามมาให้เป็นตัวเลขฐานสอง ดังนั้น <code>0b00001000</code> จึงมีค่าเท่ากับ <code>8</code> (ฐานสิบ) นอกจากเลขฐานสองแล้วภาษาซียังรองรับการระบุค่าจำนวนเต็มคงที่ในรูปฐานแปด (ขึ้นต้นด้วย <code>0</code>) และฐานสิบหก (ขึ้นต้นด้วย <code>0x</code>) | ||
− | === | + | === การคอมไพล์โปรแกรมให้เป็นภาษาเครื่อง === |
− | + | กระบวนการแปลโปรแกรมภาษาชั้นสูงให้เป็นภาษาเครื่องนั้นเรียกว่าเป็นการ''คอมไพล์ (compile)'' ซึ่งอาศัยโปรแกรมที่เรียกว่า''คอมไพเลอร์ (compiler)'' เป็นตัวดำเนินการ เราอาศัยโปรแกรม <code>avr-gcc</code> เป็นตัวคอมไพเลอร์โดยเรียกใช้งานดังนี้ (สังเกตว่ามีการใช้อ็อปชัน <code>-O</code> เพิ่มเติมขึ้นมา) | |
− | avr-gcc -mmcu= | + | avr-gcc -mmcu=atmega328p -O -o first.elf first.c |
อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้ | อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้ | ||
− | * <code>-mmcu= | + | * <code>-mmcu=atmega328p</code> เป็นตัวบอกคอมไพเลอร์ว่าไมโครคอนโทรลเลอร์ที่ใช้เป็นเบอร์ ATMega328P |
+ | * <code>-O</code> ระบุว่าให้คอมไพเลอร์ทำ code optimization ซึ่งจำเป็นต้องใช้เพื่อให้ฟังก์ชัน <code>_delay_ms()</code> ทำงานได้ถูกต้อง | ||
* <code>-o first.elf</code> ระบุว่าให้เอาท์พุทถูกเก็บลงในไฟล์ <code>first.elf</code> หากไม่ระบุโปรแกรมจะสร้างไฟล์ชื่อ <code>a.out</code> แทน | * <code>-o first.elf</code> ระบุว่าให้เอาท์พุทถูกเก็บลงในไฟล์ <code>first.elf</code> หากไม่ระบุโปรแกรมจะสร้างไฟล์ชื่อ <code>a.out</code> แทน | ||
+ | ''หมายเหตุ: <code>avr-gcc</code> ทำหน้าที่เป็นได้ทั้งแอสเซมเบลอร์และคอมไพเลอร์ ขึ้นอยู่กับนามสกุลของไฟล์อินพุทว่าเป็น .S หรือ .c'' | ||
− | + | === การสกัดโค้ดภาษาเครื่องและดาวน์โหลดโปรแกรมลงสู่ไมโครคอนโทรลเลอร์ === | |
+ | ผลลัพธ์ที่ได้จากการคอมไพล์จะอยู่ในรูปของไฟล์ฟอร์แมต [http://en.wikipedia.org/wiki/Executable_and_Linkable_Format ELF] (Excutable and Linkable Format) ซึ่งประกอบไปด้วยเฮดเดอร์และข้อมูลเสริมอื่น ๆ อีกมากมาย อย่างไรก็ตามเราต้องการเพียงแค่ส่วนที่เป็นรหัสภาษาเครื่องของโปรแกรม ซึ่งสกัดออกมาได้โดยใช้คำสั่ง <code>avr-objcopy</code> ดังนี้ | ||
avr-objcopy -j .text -j .data -O ihex first.elf first.hex | avr-objcopy -j .text -j .data -O ihex first.elf first.hex | ||
อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้ | อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้ | ||
− | * <code>-j .text -j .data</code> สกัดข้อมูลจากเซคชัน .text และ .data | + | * <code>-j .text -j .data</code> สกัดข้อมูลจากเซคชัน .text (ส่วนของโค้ดโปรแกรม) และ .data (ส่วนของข้อมูลที่กำหนดค่าเริ่มต้นให้ตัวแปร) ออกมาจากไฟล์ ELF |
− | * <code>-O ihex</code> | + | * <code>-O ihex</code> บันทึกเอาท์พุทในรูปแบบ [http://en.wikipedia.org/wiki/Intel_HEX Intel HEX] |
− | ผลลัพธ์ที่ได้จะถูกเก็บไว้ในไฟล์ <code>first.hex</code> ซึ่งเป็นไฟล์ ASCII โดยภายในไฟล์จะมีข้อมูลคล้ายคลึงกับที่แสดงในตัวอย่าง | + | ผลลัพธ์ที่ได้จะถูกเก็บไว้ในไฟล์ <code>first.hex</code> ซึ่งเป็นไฟล์ ASCII ที่บันทึกไบท์โค้ดภาษาเครื่องเอาไว้ โดยภายในไฟล์จะมีข้อมูลคล้ายคลึงกับที่แสดงในตัวอย่าง |
$ cat first.hex | $ cat first.hex | ||
:100000000C9434000C943E000C943E000C943E0082 | :100000000C9434000C943E000C943E000C943E0082 | ||
แถว 47: | แถว 70: | ||
:100060000C943E000C943E0011241FBECFEFD4E050 | :100060000C943E000C943E0011241FBECFEFD4E050 | ||
:10007000DEBFCDBF0E9440000C9445000C940000F0 | :10007000DEBFCDBF0E9440000C9445000C940000F0 | ||
− | : | + | :0E00800008E00AB900E00BB9FFCFF894FFCFFB |
:00000001FF | :00000001FF | ||
− | + | == การโหลดโปรแกรมลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ == | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | ||
โดยทั่วไปการนำโปรแกรมลงสู่แฟลชของไมโครคอนโทรลเลอร์นั้นมักอาศัย[http://en.wikipedia.org/wiki/Programmer_(hardware) เครื่องโปรแกรมชิป] (chip programmer) อย่างไรก็ตามชิป | โดยทั่วไปการนำโปรแกรมลงสู่แฟลชของไมโครคอนโทรลเลอร์นั้นมักอาศัย[http://en.wikipedia.org/wiki/Programmer_(hardware) เครื่องโปรแกรมชิป] (chip programmer) อย่างไรก็ตามชิป | ||
− | + | ATmega328P ที่แจกไปให้นั้นได้ถูกป้อนโปรแกรมพิเศษที่เรียกว่า[http://en.wikipedia.org/wiki/Bootloader#Boot_loader บูทโหลดเดอร์] (boot loader) | |
เอาไว้เพื่อจำลองบอร์ดไมโครคอนโทรลเลอร์เป็นอุปกรณ์โปรแกรมชิปชนิด [http://www.fischl.de/usbasp/ USBasp] ซึ่งจะรอรับรหัสภาษาเครื่องที่ส่งมาทางพอร์ท USB ของเครื่องคอมพิวเตอร์ และเขียนข้อมูลเหล่านั้นลงสู่หน่วยความจำแฟลช | เอาไว้เพื่อจำลองบอร์ดไมโครคอนโทรลเลอร์เป็นอุปกรณ์โปรแกรมชิปชนิด [http://www.fischl.de/usbasp/ USBasp] ซึ่งจะรอรับรหัสภาษาเครื่องที่ส่งมาทางพอร์ท USB ของเครื่องคอมพิวเตอร์ และเขียนข้อมูลเหล่านั้นลงสู่หน่วยความจำแฟลช | ||
บูทโหลดเดอร์ถูกติดตั้งไว้ในตำแหน่งแฟลชที่เป็นบูทเซคเตอร์ของชิป (เริ่มต้นที่แอดเดรส 0x3800 ของหน่วยความจำแฟลช) ซึ่งเป็นจุดแรกที่ไมโครคอนโทรลเลอร์เริ่มต้นทำงาน กระบวนการทำงานของบูทโหลดเดอร์ที่เตรียมไว้ให้เป็นดังรูปด้านล่าง | บูทโหลดเดอร์ถูกติดตั้งไว้ในตำแหน่งแฟลชที่เป็นบูทเซคเตอร์ของชิป (เริ่มต้นที่แอดเดรส 0x3800 ของหน่วยความจำแฟลช) ซึ่งเป็นจุดแรกที่ไมโครคอนโทรลเลอร์เริ่มต้นทำงาน กระบวนการทำงานของบูทโหลดเดอร์ที่เตรียมไว้ให้เป็นดังรูปด้านล่าง | ||
− | [[Image:flow.png|center | + | [[Image:flow.png|center]] |
− | จะเห็นว่าเงื่อนไขของการที่จะให้บูทโหลดเดอร์เข้าสู่โหมด USB เพื่อรอรับข้อมูลนั้นคือไมโครคอนโทรลเลอร์ต้องถูกรีเซ็ตด้วยปุ่มรีเซ็ต และขา PD7 ต้องถูกเชื่อมลงกราวนด์ | + | จะเห็นว่าเงื่อนไขของการที่จะให้บูทโหลดเดอร์เข้าสู่โหมด USB เพื่อรอรับข้อมูลนั้นคือไมโครคอนโทรลเลอร์ต้องถูกรีเซ็ตด้วยปุ่มรีเซ็ต และขา Bootloader (PD7) ต้องถูกเชื่อมลงกราวนด์ |
− | + | ซึ่งทำได้โดยการเสียบจั๊มเปอร์เพื่อชอร์ตวงจรในตำแหน่งที่ระบุว่า Boot-loader บนบอร์ด จุดสังเกตที่แสดงให้เห็นว่าไมโครคอนโทรลเลอร์กำลังรอรับข้อมูลจากพอร์ท USB คือ LED สีเขียวบนบอร์ดจะกระพริบสั้น ๆ และถี่ ๆ | |
− | ในระหว่างที่ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB หากเรียกคำสั่ง <code>lsusb</code> | + | ในระหว่างที่ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB หากเรียกคำสั่ง <code>lsusb</code> บนลินุกซ์ บนเครื่องคอมพิวเตอร์จะต้องปรากฏรายการอุปกรณ์ที่มี VID:PID เป็น 16c0:05dc ดังตัวอย่าง |
$ lsusb | $ lsusb | ||
− | + | : | |
− | Bus | + | Bus xxx Device xxx: ID 16c0:05dc Van Ooijen Technische Informatica shared ID for use with libusb |
− | + | : | |
− | + | ||
− | + | สำหรับเครื่องที่ใช้ Mac OS X ให้ใช้คำสั่ง <code>system_profiler</code> ควบคู่กับ <code>grep</code> เพื่อหาบรรทัดที่มีข้อความ USBasp และพิมพ์ผลลัพธ์ที่ตามมาอีก 10 บรรทัด (ด้วยตัวเลือก -A 10) ดังแสดง | |
− | + | $ 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 นั้นให้ใช้คำสั่ง <code>avrdude</code> ดังแสดง | การส่งโปรแกรมไปยังไมโครคอนโทรลเลอร์ผ่านพอร์ท USB นั้นให้ใช้คำสั่ง <code>avrdude</code> ดังแสดง | ||
− | avrdude -p | + | avrdude -p atmega328p -c usbasp -U flash:w:first.hex |
อ็อพชันต่าง ๆ ที่ใช้ในคำสั่งข้างต้นมีหน้าที่ดังนี้ | อ็อพชันต่าง ๆ ที่ใช้ในคำสั่งข้างต้นมีหน้าที่ดังนี้ | ||
− | * <code>-p | + | * <code>-p atmega328p</code> ระบุว่าไมโครคอนโทรลเลอร์ปลายทางคือเบอร์ ATmega328P |
* <code>-c usbasp</code> ระบุว่าเครื่องโปรแกรมชิปที่ใช้คือชนิด USBAsp | * <code>-c usbasp</code> ระบุว่าเครื่องโปรแกรมชิปที่ใช้คือชนิด USBAsp | ||
* <code>-U flash:w:first.hex</code> ระบุว่าให้ดำเนินการเขียนข้อมูลลงสู่หน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ โดยนำเข้าข้อมูลจากไฟล์ <code>first.hex</code> | * <code>-U flash:w:first.hex</code> ระบุว่าให้ดำเนินการเขียนข้อมูลลงสู่หน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ โดยนำเข้าข้อมูลจากไฟล์ <code>first.hex</code> | ||
แถว 130: | แถว 113: | ||
<span style="color:red;">'''หมายเหตุ:''' หากพบปัญหาเกี่ยวกับสิทธิการเข้าถึงอุปกรณ์ USB ให้ดำเนินตามขั้นตอนที่อธิบายไว้ในเอกสาร [[การแก้ไขสิทธิการเข้าถึงพอร์ท USB ของบอร์ด MCU]]</span> | <span style="color:red;">'''หมายเหตุ:''' หากพบปัญหาเกี่ยวกับสิทธิการเข้าถึงอุปกรณ์ USB ให้ดำเนินตามขั้นตอนที่อธิบายไว้ในเอกสาร [[การแก้ไขสิทธิการเข้าถึงพอร์ท USB ของบอร์ด MCU]]</span> | ||
− | == | + | == การสร้างกระบวนการอัตโนมัติด้วย <code>Makefile</code> == |
− | + | จากที่ผ่านมาจะเห็นว่าการพัฒนาเฟิร์มแวร์สำหรับไมโครคอนโทรลเลอร์นั้นประกอบด้วยการแก้ไขโปรแกรมด้วยเท็กซ์เอดิเตอร์ และเซฟลงในไฟล์ .c จากนั้นจึงดำเนินตามขั้นตอนดังนี้ | |
− | + | # ครอสคอมไพล์โปรแกรมด้วยคำสั่ง <code>avr-gcc</code> | |
− | + | # สกัดรหัสภาษาเครื่องจากไฟล์ ELF ด้วยคำสั่ง <code>avr-objcopy</code> | |
− | + | # ส่งรหัสภาษาเครื่องไปยังไมโครคอนโทรลเลอร์ด้วยคำสั่ง <code>avrdude</code> | |
− | + | ในแต่ละขั้นตอนนั้นมีการเรียกคำสั่งที่ค่อนข้างยาว อย่างไรก็ตาม ยูนิกซ์มีคำสั่ง <code>make</code> ที่ช่วยเรียกคำสั่งเหล่านี้ให้เราอัตโนมัติ ช่วยให้เราไม่ต้องพิมพ์คำสั่งยาว ๆ ทุกครั้งหลังจากแก้ไขโปรแกรม | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ในการใช้คำสั่ง <code>make</code> ให้เตรียมไฟล์ชื่อ <code>Makefile</code> ขึ้นมาในไดเรคตอรีเดียวกับ <code>first.c</code> และป้อนคำสั่งดังนี้ | |
− | + | <span style="color:red;">(ระวังว่าบรรทัดที่เยื้องเข้าไปนั้นต้องเป็นอักขระ<u>แท็บ</u> ไม่ใช่ช่องว่าง)</span> | |
− | |||
− | |||
− | |||
− | |||
all: first.hex | all: first.hex | ||
flash: first.hex | flash: first.hex | ||
− | + | avrdude -p atmega328p -c usbasp -U flash:w:first.hex | |
first.hex: first.elf | first.hex: first.elf | ||
− | + | avr-objcopy -j .text -j .data -O ihex first.elf first.hex | |
first.elf: first.c | first.elf: first.c | ||
− | + | avr-gcc -mmcu=atmega328p -O -o first.elf first.c | |
− | |||
สมมติว่าภายในไดเรคตอรีที่เรียกใช้คำสั่ง <code>make</code> มีเพียงไฟล์ <code>first.c</code> และ <code>Makefile</code> การเรียกคำสั่ง | สมมติว่าภายในไดเรคตอรีที่เรียกใช้คำสั่ง <code>make</code> มีเพียงไฟล์ <code>first.c</code> และ <code>Makefile</code> การเรียกคำสั่ง | ||
make | make | ||
− | จะถือเป็นการสร้างเป้าหมายที่ระบุไว้ตัวแรกสุด ในที่นี้คือ all ซึ่งจะมีผลให้มีการดำเนินการดังนี้ | + | จะถือเป็นการสร้างเป้าหมายที่ระบุไว้ตัวแรกสุด ในที่นี้คือ <code>all</code> ซึ่งจะมีผลให้มีการดำเนินการดังนี้ |
− | * เป้าหมาย all ระบุไว้ว่าให้สร้างเป้าหมาย <code>first.hex</code> ขึ้นมา | + | * เป้าหมาย <code>all</code> ระบุไว้ว่าให้สร้างเป้าหมาย <code>first.hex</code> ขึ้นมา |
* <code>make</code> หาไฟล์ <code>first.hex</code> ไม่พบ แต่ทราบว่าสามารถสร้างขึ้นจาก <code>first.elf</code> | * <code>make</code> หาไฟล์ <code>first.hex</code> ไม่พบ แต่ทราบว่าสามารถสร้างขึ้นจาก <code>first.elf</code> | ||
* <code>make</code> หาไฟล์ <code>first.elf</code> ไม่พบ แต่ทราบว่าสามารถสร้างขึ้นจาก <code>first.c</code> | * <code>make</code> หาไฟล์ <code>first.elf</code> ไม่พบ แต่ทราบว่าสามารถสร้างขึ้นจาก <code>first.c</code> | ||
แถว 197: | แถว 148: | ||
ซึ่งจะมีการดำเนินการสร้างเป้าหมาย <code>first.hex</code> โดยอัตโนมัติหากหาไฟล์นี้ไม่พบหรือไฟล์ที่มีอยู่นั้นเก่ากว่าไฟล์ <code>first.c</code> จากนั้นจึงตามด้วยการเรียกใช้คำสั่ง <code>avrdude</code> เพื่อแฟลชเฟิร์มแวร์ใหม่ให้กับไมโครคอนโทรลเลอร์ | ซึ่งจะมีการดำเนินการสร้างเป้าหมาย <code>first.hex</code> โดยอัตโนมัติหากหาไฟล์นี้ไม่พบหรือไฟล์ที่มีอยู่นั้นเก่ากว่าไฟล์ <code>first.c</code> จากนั้นจึงตามด้วยการเรียกใช้คำสั่ง <code>avrdude</code> เพื่อแฟลชเฟิร์มแวร์ใหม่ให้กับไมโครคอนโทรลเลอร์ | ||
− | หากต้องการนำ Makefile ไปปรับใช้ในโครงงานอื่นได้ง่าย เราสามารถปรับ Makefile | + | หากต้องการนำ <code>Makefile</code> ไปปรับใช้ในโครงงานอื่นได้ง่าย เราสามารถปรับ <code>Makefile</code> ให้ยืดหยุ่นขึ้นโดยนิยามตัวแปรดังตัวอย่าง |
− | TARGET=first | + | TARGET=first |
− | MCU= | + | MCU=atmega328p |
− | |||
− | |||
− | |||
− | |||
− | + | all: $(TARGET).hex | |
− | |||
− | + | flash: $(TARGET).hex | |
− | + | avrdude -p $(MCU) -c usbasp -U flash:w:$(TARGET).hex | |
− | + | $(TARGET).hex: $(TARGET).elf | |
− | + | avr-objcopy -j .text -j .data -O ihex $(TARGET).elf $(TARGET).hex | |
− | + | $(TARGET).elf: $(TARGET).c | |
− | + | avr-gcc -mmcu=$(MCU) -O -o $(TARGET).elf $(TARGET).c | |
− | การนำ | + | การนำ <code>Makefile</code> นี้ไปใช้กับโครงงานอื่นทำได้โดยการเปลี่ยนบรรทัดแรกจาก <code>first</code> เป็นชื่อไฟล์สำหรับโครงงานนั้น ๆ เท่านั้น |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
== บทความที่เกี่ยวข้อง == | == บทความที่เกี่ยวข้อง == | ||
แถว 232: | แถว 172: | ||
== ข้อมูลเพิ่มเติม == | == ข้อมูลเพิ่มเติม == | ||
* [http://www.nongnu.org/avr-libc/user-manual/modules.html เอกสารอธิบายการใช้งานไลบรารี AVR Libc] | * [http://www.nongnu.org/avr-libc/user-manual/modules.html เอกสารอธิบายการใช้งานไลบรารี AVR Libc] | ||
− | * [http://kunetlab2.cpe.ku.ac.th/download/mcu/doc8161-pa-series.pdf Datasheet สำหรับ ATmega168] | + | * [http://kunetlab2.cpe.ku.ac.th/download/mcu/doc8161-pa-series.pdf Datasheet สำหรับ ATmega168/ATmega328P] |
* [http://kunetlab2.cpe.ku.ac.th/download/mcu/doc0856-avr-instr.pdf ชุดคำสั่งของ AVR (ภาษาเครื่อง)] | * [http://kunetlab2.cpe.ku.ac.th/download/mcu/doc0856-avr-instr.pdf ชุดคำสั่งของ AVR (ภาษาเครื่อง)] |
รุ่นแก้ไขปัจจุบันเมื่อ 23:44, 22 มกราคม 2563
- วิกินี้เป็นส่วนหนึ่งของรายวิชา 01204223
วิกินี้อธิบายถึงขั้นตอนและตัวอย่างการพัฒนาเฟิร์มแวร์ลงบนบอร์ดไมโครคอนโทรลเลอร์ที่เราได้ประกอบขึ้นมา โดยเนื้อหาครอบคลุมเฉพาะสภาพแวดล้อมการพัฒนาโปรแกรมบนลินุกซ์และ Mac OS X เท่านั้น
เนื้อหา
ติดตั้งซอฟท์แวร์ที่เกี่ยวข้อง
สำหรับระบบปฏิบัติการ Linux (Debian/Ubuntu/Mint รวมถึง Raspberry Pi)
- ครอสคอมไพเลอร์ (cross compiler) สำหรับไมโครคอนโทรลเลอร์ตระกูล AVR รวมถึงไลบรารีที่เกี่ยวข้อง
sudo apt install gcc-avr avr-libc
- AVR toolchain
sudo apt install binutils-avr
- AVRDUDE (AVR Downloader/UploaDEr) ใช้สำหรับโหลดรหัสภาษาเครื่องลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ผ่านพอร์ต USB
sudo apt install avrdude
สำหรับระบบปฏิบัติการ Mac OS X
- ดาวน์โหลดและติดตั้งชุดโปรแกรม CrossPack-AVR เวอร์ชันล่าสุดจาก Objective Development
การพัฒนาเฟิร์มแวร์ด้วยภาษาซี
ภาษาซีจัดเป็นภาษาระดับสูง (high-level programming language) ที่ถือว่ามีการทำงานใกล้เคียงกับภาษาเครื่องมากโดยที่ผู้พัฒนาโปรแกรมไม่จำเป็นต้องเขียนคำสั่งให้ละเอียดยิบเหมือนกับภาษาแอสเซมบลี้ จึงเป็นภาษาที่ได้รับความนิยมมากในงานที่ต้องเขียนโปรแกรมใกล้ชิดกับฮาร์ดแวร์ดังเช่นงานด้านระบบฝังตัว (embedded system)
โปรแกรมตัวอย่างภาษาซี
ทดลองพิมพ์โปรแกรมตัวอย่างต่อไปนี้ และบันทึกไว้ในชื่อ first.c
#define F_CPU 16000000 // บอกไลบรารีว่า MCU ทำงานที่ 16MHz
#include <avr/io.h> // โหลดนิยามสำหรับรีจีสเตอร์ที่ควบคุมอินพุท/เอาท์พุท (เช่น PORTD, DDRD) #include <util/delay.h> // โหลดนิยามสำหรับฟังก์ชัน _delay_ms() int main() { PORTD = 0b00000000; // กำหนดลอจิกขา PD7..0 เป็น 0 DDRD = 0b00001000; // กำหนดให้ขา PD3 ทำหน้าที่เอาท์พุท while (1) { PORTD = 0b00001000; // ส่งลอจิก 1 ไปที่ขา PD3 _delay_ms(1000); // หน่วงเวลารอ 1000 มิลลิวินาที PORTD = 0b00000000; // ส่งลอจิก 0 ไปที่ขา PD3 _delay_ms(1000); // หน่วงเวลารอ 1000 มิลลิวินาที } return 0; }
รายละเอียดเพิ่มเติมเกี่ยวกับโปรแกรม
- โปรแกรมข้างต้นเรียกใช้นิยามชื่อรีจีสเตอร์ (
PORTD
,DDRD
ฯลฯ) จากไฟล์เฮดเดอร์avr/io.h
และฟังก์ชันหน่วงเวลา_delay_ms()
จากไฟล์เฮดเดอร์util/delay.h
- ตัวเลขที่นำหน้าด้วย
0b
ในโค้ดภาษาซีเป็นการบอกให้คอมไพเลอร์ตีความค่าตัวเลขที่ตามมาให้เป็นตัวเลขฐานสอง ดังนั้น0b00001000
จึงมีค่าเท่ากับ8
(ฐานสิบ) นอกจากเลขฐานสองแล้วภาษาซียังรองรับการระบุค่าจำนวนเต็มคงที่ในรูปฐานแปด (ขึ้นต้นด้วย0
) และฐานสิบหก (ขึ้นต้นด้วย0x
)
การคอมไพล์โปรแกรมให้เป็นภาษาเครื่อง
กระบวนการแปลโปรแกรมภาษาชั้นสูงให้เป็นภาษาเครื่องนั้นเรียกว่าเป็นการคอมไพล์ (compile) ซึ่งอาศัยโปรแกรมที่เรียกว่าคอมไพเลอร์ (compiler) เป็นตัวดำเนินการ เราอาศัยโปรแกรม avr-gcc
เป็นตัวคอมไพเลอร์โดยเรียกใช้งานดังนี้ (สังเกตว่ามีการใช้อ็อปชัน -O
เพิ่มเติมขึ้นมา)
avr-gcc -mmcu=atmega328p -O -o first.elf first.c
อ็อพชันต่าง ๆ ที่ระบุในคำสั่งข้างต้นมีหน้าที่ดังนี้
-mmcu=atmega328p
เป็นตัวบอกคอมไพเลอร์ว่าไมโครคอนโทรลเลอร์ที่ใช้เป็นเบอร์ ATMega328P-O
ระบุว่าให้คอมไพเลอร์ทำ code optimization ซึ่งจำเป็นต้องใช้เพื่อให้ฟังก์ชัน_delay_ms()
ทำงานได้ถูกต้อง-o first.elf
ระบุว่าให้เอาท์พุทถูกเก็บลงในไฟล์first.elf
หากไม่ระบุโปรแกรมจะสร้างไฟล์ชื่อa.out
แทน
หมายเหตุ: avr-gcc
ทำหน้าที่เป็นได้ทั้งแอสเซมเบลอร์และคอมไพเลอร์ ขึ้นอยู่กับนามสกุลของไฟล์อินพุทว่าเป็น .S หรือ .c
การสกัดโค้ดภาษาเครื่องและดาวน์โหลดโปรแกรมลงสู่ไมโครคอนโทรลเลอร์
ผลลัพธ์ที่ได้จากการคอมไพล์จะอยู่ในรูปของไฟล์ฟอร์แมต 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 :0E00800008E00AB900E00BB9FFCFF894FFCFFB :00000001FF
การโหลดโปรแกรมลงบนหน่วยความจำแฟลชของไมโครคอนโทรลเลอร์
โดยทั่วไปการนำโปรแกรมลงสู่แฟลชของไมโครคอนโทรลเลอร์นั้นมักอาศัยเครื่องโปรแกรมชิป (chip programmer) อย่างไรก็ตามชิป ATmega328P ที่แจกไปให้นั้นได้ถูกป้อนโปรแกรมพิเศษที่เรียกว่าบูทโหลดเดอร์ (boot loader) เอาไว้เพื่อจำลองบอร์ดไมโครคอนโทรลเลอร์เป็นอุปกรณ์โปรแกรมชิปชนิด USBasp ซึ่งจะรอรับรหัสภาษาเครื่องที่ส่งมาทางพอร์ท USB ของเครื่องคอมพิวเตอร์ และเขียนข้อมูลเหล่านั้นลงสู่หน่วยความจำแฟลช
บูทโหลดเดอร์ถูกติดตั้งไว้ในตำแหน่งแฟลชที่เป็นบูทเซคเตอร์ของชิป (เริ่มต้นที่แอดเดรส 0x3800 ของหน่วยความจำแฟลช) ซึ่งเป็นจุดแรกที่ไมโครคอนโทรลเลอร์เริ่มต้นทำงาน กระบวนการทำงานของบูทโหลดเดอร์ที่เตรียมไว้ให้เป็นดังรูปด้านล่าง
จะเห็นว่าเงื่อนไขของการที่จะให้บูทโหลดเดอร์เข้าสู่โหมด USB เพื่อรอรับข้อมูลนั้นคือไมโครคอนโทรลเลอร์ต้องถูกรีเซ็ตด้วยปุ่มรีเซ็ต และขา Bootloader (PD7) ต้องถูกเชื่อมลงกราวนด์ ซึ่งทำได้โดยการเสียบจั๊มเปอร์เพื่อชอร์ตวงจรในตำแหน่งที่ระบุว่า Boot-loader บนบอร์ด จุดสังเกตที่แสดงให้เห็นว่าไมโครคอนโทรลเลอร์กำลังรอรับข้อมูลจากพอร์ท USB คือ LED สีเขียวบนบอร์ดจะกระพริบสั้น ๆ และถี่ ๆ
ในระหว่างที่ไมโครคอนโทรลเลอร์จำลองตัวเองเป็นอุปกรณ์ USB หากเรียกคำสั่ง lsusb
บนลินุกซ์ บนเครื่องคอมพิวเตอร์จะต้องปรากฏรายการอุปกรณ์ที่มี VID:PID เป็น 16c0:05dc ดังตัวอย่าง
$ lsusb : Bus xxx Device xxx: ID 16c0:05dc Van Ooijen Technische Informatica shared ID for use with libusb :
สำหรับเครื่องที่ใช้ Mac OS X ให้ใช้คำสั่ง system_profiler
ควบคู่กับ grep
เพื่อหาบรรทัดที่มีข้อความ USBasp และพิมพ์ผลลัพธ์ที่ตามมาอีก 10 บรรทัด (ด้วยตัวเลือก -A 10) ดังแสดง
$ 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 atmega328p -c usbasp -U flash:w:first.hex
อ็อพชันต่าง ๆ ที่ใช้ในคำสั่งข้างต้นมีหน้าที่ดังนี้
-p atmega328p
ระบุว่าไมโครคอนโทรลเลอร์ปลายทางคือเบอร์ ATmega328P-c usbasp
ระบุว่าเครื่องโปรแกรมชิปที่ใช้คือชนิด USBAsp-U flash:w:first.hex
ระบุว่าให้ดำเนินการเขียนข้อมูลลงสู่หน่วยความจำแฟลชของไมโครคอนโทรลเลอร์ โดยนำเข้าข้อมูลจากไฟล์first.hex
หมายเหตุ: หากพบปัญหาเกี่ยวกับสิทธิการเข้าถึงอุปกรณ์ USB ให้ดำเนินตามขั้นตอนที่อธิบายไว้ในเอกสาร การแก้ไขสิทธิการเข้าถึงพอร์ท USB ของบอร์ด MCU
การสร้างกระบวนการอัตโนมัติด้วย Makefile
จากที่ผ่านมาจะเห็นว่าการพัฒนาเฟิร์มแวร์สำหรับไมโครคอนโทรลเลอร์นั้นประกอบด้วยการแก้ไขโปรแกรมด้วยเท็กซ์เอดิเตอร์ และเซฟลงในไฟล์ .c จากนั้นจึงดำเนินตามขั้นตอนดังนี้
- ครอสคอมไพล์โปรแกรมด้วยคำสั่ง
avr-gcc
- สกัดรหัสภาษาเครื่องจากไฟล์ ELF ด้วยคำสั่ง
avr-objcopy
- ส่งรหัสภาษาเครื่องไปยังไมโครคอนโทรลเลอร์ด้วยคำสั่ง
avrdude
ในแต่ละขั้นตอนนั้นมีการเรียกคำสั่งที่ค่อนข้างยาว อย่างไรก็ตาม ยูนิกซ์มีคำสั่ง make
ที่ช่วยเรียกคำสั่งเหล่านี้ให้เราอัตโนมัติ ช่วยให้เราไม่ต้องพิมพ์คำสั่งยาว ๆ ทุกครั้งหลังจากแก้ไขโปรแกรม
ในการใช้คำสั่ง make
ให้เตรียมไฟล์ชื่อ Makefile
ขึ้นมาในไดเรคตอรีเดียวกับ first.c
และป้อนคำสั่งดังนี้
(ระวังว่าบรรทัดที่เยื้องเข้าไปนั้นต้องเป็นอักขระแท็บ ไม่ใช่ช่องว่าง)
all: first.hex flash: first.hex avrdude -p atmega328p -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=atmega328p -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
ให้ยืดหยุ่นขึ้นโดยนิยามตัวแปรดังตัวอย่าง
TARGET=first MCU=atmega328p all: $(TARGET).hex flash: $(TARGET).hex avrdude -p $(MCU) -c usbasp -U flash:w:$(TARGET).hex $(TARGET).hex: $(TARGET).elf avr-objcopy -j .text -j .data -O ihex $(TARGET).elf $(TARGET).hex $(TARGET).elf: $(TARGET).c avr-gcc -mmcu=$(MCU) -O -o $(TARGET).elf $(TARGET).c
การนำ Makefile
นี้ไปใช้กับโครงงานอื่นทำได้โดยการเปลี่ยนบรรทัดแรกจาก first
เป็นชื่อไฟล์สำหรับโครงงานนั้น ๆ เท่านั้น
บทความที่เกี่ยวข้อง
- การบัดกรีแผงวงจรไมโครคอนโทรลเลอร์
- แผงวงจรพ่วง (Peripheral Board)
- การวัดสัญญาณแอนะล็อกด้วยไมโครคอนโทรลเลอร์