Basic Microcontroller Programming
การทำงานกับข้อมูลระดับบิตในภาษาซี
ความแตกต่างของ Arduino platform กับ microcontroller อื่นๆ
การเขียนโปรแกรม C18 แบบเป็นโครงสร้าง
โดยทั่วไปแล้ว ถ้าโปรเจคไมโครคอนโทรลเลอร์ของเราไม่ได้มีขนาดใหญ่มากนัก หรือว่าเป็นงานเฉพาะที่ทำครั้งเดียว ไม่ได้ต้องทำกันบ่อย เรามักจะเขียนโปรแกรมของเราให้มีเพียงไฟล์ main.c เพียงไฟล์เดียวแล้วรวมโค๊ดทุกอย่างไว้ใน main.c แต่ในกรณีที่เราต้องการที่จะสร้างโปรเจคของเราให้มีการแยก header file (*.h) และ Source file (*.c) ออกจากกัน เพื่อประโยชน์ในการแก้ไข หรือเพื่อนำไปใช้กับโปรเจคอื่นๆ ได้ เราจะต้องสร้างและทำการเขียนโปรแกรมแยกออกเป็นโครงสร้าง (Structure Programming) วันนี้เราจะมาเรียนรู้การเขียนโปรแกรมแบบเป็นโครงสร้าง ผ่านตัวอย่างง่ายๆ ต่อไปนี้ ตัวอย่างนี้ จะเป็นโปรเจคง่ายๆ เป็นการแสดงไฟติดดับสลับกัน โดยผ่านการกดปุ่ม button 1 ตัว เพื่อเป็นตัวทำหน้าที่ให้หลอด LED ติดดับสลับกัน เครื่องมือ -MPLAB V8.83 หรือสูงกว่า -C18 Complier v3.35 หรือ สูงกว่า -PIC18F458 สร้างโปรเจคด้วย MPLAB โดยเปิดโปรแกรม MPLAB ขึ้นมา แล้วไปที่เมนู Project>ProjectWizard ขั้นที่1 - เลือก PIC18F458 ขั้นที่2 - เลือก C18 complier ขั้นที่3 - ตั้งชื่อโปรเจคของเรา Project.mcp ขั้นที่4 - เพิ่มไฟล์ C:\MCC18\lkr\18f458_g.lkr (บางเวอร์ชั่น 18f458.lkr) เข้าไปในโปรเจค ขั้นที่5 - เสร็จสมบูรณ์ กด Finish จะได้โปรเจคว่างเปล่า หน้าตาโปรแกรม (คล้ายๆดังรูป) อันดับแรกเลย การที่เราจะเขียนโปรแกรมให้ไมโครคอนโทรลเลอร์ของเราทำงานได้นั้น จะต้องมีการตั้งค่าเริ่มต้น หรือกำหนดค่าเริ่มต้น เช่น กำหนดหน้าที่การทำงานของแต่ละขาของไมโครคอนโทรลเลอร์ ว่าจะให้ทำงานในโหมด เอาท์พุต หรือว่า อินพุต และการกำหนดค่าเริ่มต้นของความถี่ในการทำงาน และการเปิด ปิด โหมดต่างๆ ที่เกี่ยวข้องกับงานของเรา ในที่นี้ เราจะมาสร้างไฟล์ที่ทำหน้าที่เป็นการกำหนดค่าเริ่มต้นให้โปรเจคของเรา เราจะให้ไฟล์ชื่อ system.h เก็บค่าเริ่มต้นเหล่านั้น ไปที่เมนู File>New สร้างไฟล์เปล่าๆ ขึ้นมา แล้ว เขียนโปรแกรม เพื่อกำหนดค่าเริ่มต้นให้กับไมโครคอนโทรลเลอร์ ในที่นี้ โค๊ดที่เราสร้าง จะเป็นโค๊ดที่อยู่ข้างนอก void main(void) ซึ่งในตัวอย่างนี้ ค่าที่สามารถสร้างได้ นอก void main(void) ก็คือ มาโคร #pragma config นั่นเอง เพราะฉะนั้นจะได้ โค๊ด ภายในไฟล์ system.h ดังนี้ #pragma config OSC = HS,OSCS = OFF #pragma config PWRT = OFF #pragma config BOR = OFF #pragma config WDT = OFF #pragma config LVP = OFF หมายเหตุ : ในไฟล์ต่างๆ พยายามเคาะ enter บรรทัดสุดท้าย ให้ว่างๆ ไว้ สักบรรทัดเป็นอย่างน้อย เพราะอาจจะทำให้เรา Complie ไม่ผ่าน แล้วหาไม่เจอ ตรงนี้ให้ระวังด้วยครับ ทำการบันทึกไฟล์ system.h ไว้ในโฟลเดอร์เดียวกันกับที่เราสร้างโปรเจค (ไว้ที่เดียวกันกับไฟล์ Project.mcp นั่นแหละ) ที่เมนู File>New สร้างไฟล์เปล่าๆ ขึ้นมา เราจะให้ไฟล์นี้เป็นไฟล์ ที่ฟังก์ชั่น main( ) เริ่มต้นทำงาน โดยเขียนโค๊ดเริ่มต้นดังนี้ #include <p18cxxx.h> #include "system.h" void main(void) { while(1) { } } จากนั้นบันทึกไว้เป็นไฟล์ main.c (เพื่อสื่อความหมายว่า main ฟังก์ชั่น อยู่ที่ไฟล์นี้) จุดสังเกต เราจะพบว่าในไฟล์ main.c นี้ จะมีการ นำไฟล์ system.h ( ดัวยคำสั่ง #include "system.h" )เข้ามาร่วมในไฟล์ main.c ด้วย นั่นหมายความว่า โค๊ดที่เราเขียนไว้ที่ไฟล์ system.h จะถูกนำมาแปะไว้ที่ไฟล์ main.c ก่อนขึ้นฟังก์ชั่น void main(void) และอยู่หลัง #include <p18cxxx.h> ที่ Source Files ให้ทำการ Add File main.c เข้าไป แล้วให้ทำการ build project ดูก่อน เพื่อให้มั่นใจว่า ไม่ได้เกิด Error ขึ้นในขั้นตอนนี้ ถ้าทุกอย่างถูกต้อง เพื่อนๆ จะต้องได้คำว่า BUILD SUCCEEDED ที่หน้าต่าง build แท๊ป output (ถ้าเกิด BUILD FAILED ให้ทำการแก้ไข ตาม บรรทัดที่แจ้งเตือน) ถ้าทุกอย่างเรียบร้อย เราจะมาเริ่มทำการเขียนโค๊ดไฟกระพริบ ก่อน โดยเราจะให้ ขา PB0 ทำหน้าที่เป็นเอาท์พุต และให้หลอด LED ที่ต่ออยู่ที่ขานี้ กระพริบติดดับสลับกัน เมื่อเราได้แนวคิดแล้ว ให้ทำการสร้างไฟล์ขึ้นมา เพื่อทำหน้าที่ควบคุมการติดดับของหลอด LED และเพื่อให้สื่อความหมายของไฟล์นี้ เราจะตั้งชื่อไฟล์ว่า LED ให้เพื่อนๆ สร้างไฟล์ LED.c ขึ้นมาโดยไปที่เมนู File>New แล้วเขียนโค๊ด #include <p18cxxx.h> void InitialiseLED(void) { TRISB = TRISB & 0xFE; // PB0 as output pin } void LED_Off(void) { PORTB &= (~0x01); // Turn off LED } void LED_On(void) { PORTB |= 0x01; // Turn on LED } จากนั้น เราจะทำการสร้างไฟล์ ที่เป็น header file ชื่อ LED.h ของไฟล์ LED.c เพื่อทำการประกาศฟังก์ชั่นโปรโตไทป์ ก่อนการเรียกใช้งานฟังก์ชั่น ที่เมนู File>New สร้างไฟล์ขึ้นมา แล้วทำการ ก๊อปปี้ชื่อฟังก์ชั่นไปไว้ในไฟล์ LED.h โดยมีคำว่า extern นำหน้า ซึ่งบอกให้คอมไพล์เลอร์ทราบว่า ชื่อฟังก์ชั่นนี้อยู่ที่ไฟล์อื่น ไม่ได้อยู่ที่ไฟล์ LED.h จริง ฉะนั้นในไฟล์ LED.h จะต้องเขียนโค๊ดเป็นดังนี้ extern void InitialiseLED(void); extern void LED_Off(void); extern void LED_On(void); ทำการบันทึกไฟล์ LED.h จากนั้นกลับไปที่ไฟล์ main.c ทำการเพิ่ม #include "LED.h " ไว้ที่หัวของไฟล์ เพื่อบอกให้คอมไพล์เลอร์ทราบว่าให้นำ LED.h เข้ามาร่วมด้วยที่ไฟล์ main.c และทำการเรียกฟังก์ชั่นที่เราสร้างไว้ที่ไฟล์ LED.c ซึ่งได้แก่ฟังก์ชั่น InitialiseLED(); LED_Off(); LED_On(); เข้ามาทำงานในไฟล์ main.c ดังนั้นที่ไฟล์ main.c เราจะได้ว่า #include <p18cxxx.h> #include "system.h" #include "LED.h" void main(void) { InitialiseLED(); LED_Off(); LED_On(); while(1) { } } ลองทำการกดปุ่ม Build All เพื่อนๆ จะพบว่า เกิด Error ขึ้น เพราะ Linker ไม่สามารถที่จะหาตัวฟังก์ชั่นที่เราเรียกมาใช้ได้ เราจะต้องทำการนำไฟล์ที่เป็นที่อยู่ของฟังก์ชั่นดังกล่าวเข้ามาในโปรเจคเราซะก่อน โดยการทำการคลิกขวาที่ Source Files แล้ว Add File แล้ว เลือกไฟล์ LED.c กด OK เราจะได้ Source File เพิ่ม ดังภาพ แล้วลอง Build All อีกครั้ง จะได้ BUILD SUCCEEDED แสดงว่าทุกอย่างถูกต้องแล้ว หากเราพิจารณา โดยที่ยังไม่ต้องเบิร์นโค๊ดเรา ลงไปที่ไมโครคอนโทรลเลอร์ เราก็อาจจะพิจารณาได้ว่า หลอด LED ที่ต่ออยู่ที่ขา PB0 ของไมโครคอนโทรลเลอร์ จะติดค้างอยู่ ที่ PB0 หากเราต้องการให้เกิดการติดดับสลับกัน เราก็แค่ให้ฟังก์ชั่น LED_On() และ LED_Off() วางสลับกัน ก็น่าจะทำให้หลอดไฟ LED ติดดับสลับกันได้ แต่ ด้วยความถี่ที่สูงมาก ทำให้สายตาของเราไม่สามารถแยกความแตกต่างได้ สิ่งที่เราต้องการก็คือ ควรจะมีการหน่วงเวลา เพื่อทำให้สายตาของเราแยกแยะความแตกต่างได้ ดังนั้น เราจึงควรที่จะสร้างฟังก์ชั่นหน่วงเวลาขึ้นมา หรือที่นิยมเรียกกันว่า delay ขึ้นมาใช้งาน ในการสร้างฟังก์ชั่น delay เราก็จะทำคล้ายๆ กับที่เราสร้างฟังก์ชั่น LED เหมือนกัน ให้เพื่อนๆ ลองสร้างไฟล์ delay.h และ delay.c ขึ้นมา (เรื่องการคำนวณ delay สามารถดูได้จากตอนที่ผ่านไปแล้ว ได้ครับ คลิก) โค๊ดของ delay.c #include <delays.h> void delay_ms(unsigned int ms) { while(ms--) { Delay1KTCYx(5); } } และ delay.h extern void delay_ms(unsigned int ms); สุดท้ายเราต้องการฟังก์ชั่นที่ควบคุมการกดปุ่ม ในที่นี้ผมจะสร้างไฟล์ button.c เพื่อทำหน้าที่กำหนดขาเริ่มต้นเพื่อทำหน้าที่รับค่าการกดปุ่ม และ ฟังก์ชั่นคืนค่าการกดปุ่ม ผมทำการสร้างไฟล์ button.c ดังนี้ #include <p18cxxx.h> void InitialiseButton(void) { TRISB = TRISB & 0xFF; // PB1 as input } char PushButton(void) { if(PORTB & 0x02){ return 1; }else{ return 0; } } และเช่นเดิม ผมต้องแยกไฟล์ button.h เพื่อเก็บฟังก์ชั่นโปรโตไทป์ ออกมา จะได้ไฟล์ button.h ที่มีโค๊ดดังนี้ extern void InitialiseButton(void); extern char PushButton(void); แล้วก็อย่าลืม เพิ่มไฟล์ *.h เข้าไปที่หัวไฟล์ของ main.c ในขณะที่ *.c เราจะ add file เข้าที่ source files ดังภาพ ทำการ Build All โปรเจคอีกครั้ง ต้อง BUILD SUCCEEDED ถ้าไม่ได้ ให้ทำการแก้ไข อย่าลืมเรื่องการเคาะ Enter ไว้ที่บรรทัดสุดท้าย ในแต่ละไฟล์ด้วย (สงสัยจะเป็น bug ของ IDE หรือเปล่าไม่แน่ใจ ) ทำการต่อวงจรในโปรแกรมจำลองการทำงาน Proteus โดยให้ Processor Clock Frequency มีค่าเท่ากับ 20MHz ทดลองกดปุ่ม ดูผลการทำงานของโปรแกรม |
การตั้งค่า Delay ใน MPLAB C18 C Complier Libraries
C18 - Storage Class
Storage class ของตัวแปร เป็นสิ่งที่ใช้ในการบอกให้ Compiler รู้ว่าจะต้องจัดเก็บตัวแปรที่ตามหลังมาอย่างไร ซึ่งมีผลถึง
ทำไมต้องมี Storage class ???
เนื่องจากภาษาซีเอื้ออำนวยให้สามารถเขียนโปรแกรมและคอมไพล์เป็นส่วนย่อย ๆ ก่อนที่จะนำมาลิงค์เป็นโปรแกรมใหญ่ ภาษาซียอมให้ตั้งชื่อตัวแปรซ้ำกันได้ในต่างฟังก์ชัน ดังนั้นส่วน storage class จะช่วยให้ compiler รู้ว่าควรจะจัดการกับตัวแปรต่าง ๆ อย่างไรจึงจะตรงกับความต้องการในการใช้งานของโปรแกรมเมอร์และหลีกเลี่ยงความผิดพลาดต่าง ๆ ที่อาจเกิดขึ้นได้
รูปแบบการใช้ ตัวแปร Storage Class
ตัวบ่งบอกStorage_Class ชนิดตัวแปร ตัวแปร
ตัวแปรแบบ Automatic
โดยปกติ ตัวแปรที่ประกาศภายใน function จะเป็นตัวแปร automatic ทุกตัว ไม่เว้นกระทั่งภายใน function main() เมื่อ function ทำงานสิ้นสุดลง ตัวแปรภายใน function ก็จะหมดหน้าที่ลง ถูกทำลายไปโดยอัตโนมัติ หรือที่เราเรียกว่า ตัวแปรแบบ local นั่นแหละ เราสามารถประกาศตัวแปร พวกนี้ โดยใส่คำว่า auto นำหน้าตัวแปร หรือไม่้ใส่ก็ได้ ซึ่งการประกาศตัวแปรพวกนี้ จะไม่ได้ถูกตั้งค่าเริ่มต้นให้ เราจะต้องตั้งค่าเริ่้มต้นเอาเอง และค่าเริ่มต้น ที่ไม่ได้กำหนด ก็ไม่ใช่ค่าศูนย์ เสีียด้วย เพราะฉะนั้น ตรงนี้ให้ระวังไว้ พิจรณา ตัวอย่าง
ตัวแปร external หรือตัวแปร extern
เป็นลักษณะการแก้ปัญหา การประกาศตัวแปร ที่เป็น global ที่มีชื่อซ้ำกัน แต่อยู่กันคนละไฟล์ ในโปรเจคเดียวกัน ถ้าเราไม่ได้ใช้ keyword extern ในการแก้ปัญหานี้ เวลาที่คอมไำพล์จะไม่มีปัญหา แต่เวลาที่ linker จะต้องเอาแต่ละไฟล์ที่โปรเจคอ้างถึง จะเกิดปัญหาแจ้ง error message ออกมา
ตัวแปร static ตัวแปรใดๆ ที่ถูกระบุว่าเป็น static ตัวแปรนี้ จะมีความพิเศษคือ จะสามารถคงค่าล่าสุดไว้ภายในไฟล์ที่ถูกประกาศไว้ การทำการกำหนดค่าเริ่มต้นจะมีผลแค่ครั้งแรกที่ตัวแปรนี้ถูกเรียกใช้เท่านั้น หากมีการเรียกใช้อีกครั้ง การกำหนดค่าเริ่มต้นจะไม่มีผลใดๆ เรามักใช้ตัวแปร static ในฟังก์ชั่น เพื่อที่จะคงค่าของตัวแปรไว้ เพราะว่า เมื่อฟังก์ชั่นใดๆ ถูกเรียกใช้ หลังจากฟังก์ชั่นทำงานเสร็จแล้ว จะทำให้ตัวแปรที่ถูกประกาศอยู่ภายในฟังก์ชั่นถูกทำลายทิ้ง ยกเว้นตัวแปร static ที่ยังคงค่าไว้ ตัวแปร register เราใช้การระบุ register หน้าตัวแปรทีเป็นตัวแปร local ที่เราต้องการให้เก็บค่าตัวแปรนั้นไว้ที่ register แทนที่เราจะเก็บไว้ที่ RAM นั่นหมายความว่า ขนาดของตัวแปรจะมีขนาดใหญ่เท่ากับขนาดของรีจิสเตอร์ (โดยทั่วไปก็ 1 word) และไม่สามารถใช้การอ้างอิงแบบแอดเดรสได้ (ไม่สามารถใช้ & หน้าตัวแปรได้) เพราะมันไม่ได้มีที่อยู่ในหน่วยความจำนั่นเอง เรามักใช้ register หน้าตัวแปรที่เราต้องการให้มีการเข้าถึงอย่างรวดเร็ว เช่น ตัวแปร counter ที่เก็บค่าการนับ แต่ทั้งนี้ ทั้งนั้นไม่ใช่ว่าเราจะสามารถประกาศตัวแปรที่เป็น register ได้ทุกตัวที่เราต้องการ เพราะขึ้นอยู่กับฮาร์ดแวร์ของระบบและกฏของ complier นั้นๆ ด้วย ดูรูปประกอบ เพื่อความเข้าใจครับ |
MPLAB C18 - ชนิดของตัวแปร
MPLAB C18 - ชนิดของตัวแปร เหมือนๆกับทุก complier ที่จะต้องมีตัวแปร เช่นเดียวกัน C18 complier มีตัวแปรทั้งที่เป็นชนิด char ,integer, float, double
นี่ก็เป็นเรื่องเบสิคทั่วๆำไป ที่ผู้ที่ต้องการเขียนโปรแกรมจะต้องรู้เกี่ยวกับชนิดของตัวแปรในภาษานั้น และขอบเขตของตัวแปรนั้นๆ ที่สามารถทำได้ เพราะถ้าหากเรากำหนดตัวแปร ตัวแปรหนึ่งแล้วไปเก็บค่าที่เกินขอบเขตของตัวแปรตัวนั้น จะเกิดอาการ overflow คือค่าที่เก็บจะย้อนกลับมาที่ค่าเริ่มต้นของมัน
ตัวอย่างเช่น เรากำหนดให้ตัวแปร A เป็นตัวแปร unsigned char แล้วให้ A เก็บค่าการนับอะไรก็ตาม เมื่อ A เก็บค่าการนับสะสมไปเรื่อยๆ เมื่อค่า A มีค่าไปจนถึงค่า 255 พอเราทำการบวกค่า A หรือเพิ่มค่า A ขึ้นไปอีกหนึ่งค่า ค่า A หลังจากการบวก เพิ่มค่า จะกลับกลายเป็น A = 0 เพราะว่า unsigned char สามารถเก็บค่าได้สูงสุด แค่ 255 เท่านั้น
เราสามารถสร้างตัวแปร แล้วกำหนดชนิดตัวแปร ให้สามารถเก็บค่า ค่าหนึ่งๆ โดยเราต้องพิจารณาขอบเขต Minimum และ Maximum ที่เป็นไปได้ ตามตาราง 2-1 ข้างล่างนี้
สำหรับจำนวนตัวเลขที่เป็นทศนิยมแล้ว ใน MPLAB C18 เราสามารถใช้ได้ทั้งตัวแปรที่เป็นชนิด float และ double ตามตาราง 2-2 ข้างล่างนี้
|
MPLAB C18 - รูปแบบการ Include file
ซึ่งทั้งสองแบบ ทำงานต่างกันดังนี้
แบบ System Header File จะเป็นการ Include File จาก Folder ที่เก็บไลบรารี หลักของ Complier ซึ่งเป็น Folder ที่ถูกระบุไว้ที่ระบบ รูปแบบการอ้างอิง #include <filename> เมื่อ Complier ทำการแปลข้อความบรรทัดนี้ Complier จะืำทำการดึงไฟล์ที่อยู่ใน Folder ของโปรแกรมภาษาที่เราใช้เขียน
แบบ User Header File จะเป็นการ Include File จาก Folder เดียวกับที่เราเก็บ source file ที่เรากำลัีงเขียนอยู่นี้ เข้ามาร่วมในการคอมไำพล์ด้วย รูปแบบการเรียกใช้ คือ #include "filename"
ทั้งสองรูปแบบต่างกัน หลังคำสั่ง #include
|
1-7 of 7