สารบัญ:

AVR Assembler บทช่วยสอน 2: 4 ขั้นตอน
AVR Assembler บทช่วยสอน 2: 4 ขั้นตอน

วีดีโอ: AVR Assembler บทช่วยสอน 2: 4 ขั้นตอน

วีดีโอ: AVR Assembler บทช่วยสอน 2: 4 ขั้นตอน
วีดีโอ: 2. Arduino for Production! How to Setup Atmel Studio 6 for the AVR Microcontroller 2024, กรกฎาคม
Anonim
AVR Assembler บทช่วยสอน 2
AVR Assembler บทช่วยสอน 2

บทช่วยสอนนี้เป็นความต่อเนื่องของ "AVR Assembler Tutorial 1"

หากคุณยังไม่ผ่านบทช่วยสอน 1 คุณควรหยุดตอนนี้และทำอย่างใดอย่างหนึ่งก่อน

ในบทช่วยสอนนี้ เราจะทำการศึกษาการเขียนโปรแกรมภาษาแอสเซมบลีของ atmega328p ที่ใช้ใน Arduino ต่อไป

คุณจะต้องการ:

  1. เขียงหั่นขนม Arduino หรือเพียงแค่ Arduino ปกติเช่นเดียวกับในบทช่วยสอน 1
  2. LED
  3. ตัวต้านทาน 220 โอห์ม
  4. ปุ่มกด
  5. สายต่อสำหรับทำวงจรบนเขียงหั่นขนมของคุณ
  6. คู่มือการใช้งานชุดคำสั่ง: www.atmel.com/images/atmel-0856-avr-instruction-s…
  7. เอกสารข้อมูล: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…

สามารถดูบทเรียนทั้งหมดของฉันได้ที่นี่:

ขั้นตอนที่ 1: สร้างวงจร

การสร้างวงจร
การสร้างวงจร

ก่อนอื่นคุณต้องสร้างวงจรที่เราจะศึกษาในบทช่วยสอนนี้

นี่คือวิธีการเชื่อมต่อ:

PB0 (พินดิจิตอล 8) - LED - R (220 โอห์ม) - 5V

PD0 (พินดิจิตอล 0) - ปุ่มกด - GND

คุณสามารถตรวจสอบว่า LED ของคุณอยู่ในแนวที่ถูกต้องโดยเชื่อมต่อกับ GND แทน PB0 หากไม่มีอะไรเกิดขึ้น ให้กลับทิศทางเดิมและไฟจะสว่างขึ้น จากนั้นเชื่อมต่อใหม่กับ PB0 และดำเนินการต่อ รูปภาพแสดงให้เห็นว่าบอร์ด Arduino ของฉันเชื่อมต่ออย่างไร

ขั้นตอนที่ 2: การเขียนรหัสแอสเซมบลี

การเขียนรหัสแอสเซมบลี
การเขียนรหัสแอสเซมบลี

เขียนโค้ดต่อไปนี้ในไฟล์ข้อความชื่อ pushbutton.asm และคอมไพล์ด้วย avra เช่นเดียวกับที่คุณทำในบทช่วยสอน 1

ขอให้สังเกตว่าในรหัสนี้ เรามีความคิดเห็นมากมาย ทุกครั้งที่แอสเซมเบลอร์เห็นอัฒภาค จะข้ามบรรทัดที่เหลือและไปยังบรรทัดถัดไป เป็นแนวทางปฏิบัติในการเขียนโปรแกรมที่ดี (โดยเฉพาะในภาษาแอสเซมบลี!) ที่จะแสดงความคิดเห็นโค้ดของคุณอย่างหนัก เพื่อที่ว่าเมื่อคุณกลับมาดูโค้ดอีกครั้งในอนาคต คุณจะรู้ว่าคุณกำลังทำอะไรอยู่ ฉันจะแสดงความคิดเห็นค่อนข้างมากในบทช่วยสอนสองสามข้อแรก เพื่อให้เรารู้ว่าเกิดอะไรขึ้นและทำไม ต่อมาเมื่อเราเก่งขึ้นเล็กน้อยในการเข้ารหัสแอสเซมบลีแล้วฉันจะแสดงความคิดเห็นในรายละเอียดน้อยลงเล็กน้อย

;************************************

; เขียนโดย: 1o_o7; วันที่: 23 ต.ค. 2557;************************************

.nolist

.include "m328Pdef.inc".list.def temp = r16; กำหนดการลงทะเบียนการทำงาน r16 เป็น temp rjmp Init; ดำเนินการบรรทัดแรก

ในนั้น:

อุณหภูมิเซอร์; ตั้งค่าบิตทั้งหมดใน temp เป็น 1 ออก DDRB อุณหภูมิ; การตั้งค่าบิตเป็น 1 บน Data Direction I/O; ลงทะเบียนสำหรับ PortB ซึ่งเป็น DDRB ตั้งค่าว่า; พินเป็นเอาต์พุต 0 จะตั้งค่าพินนั้นเป็นอินพุต; ดังนั้นที่นี่ พิน PortB ทั้งหมดเป็นเอาต์พุต (ตั้งค่าเป็น 1) ldi temp, 0b11111110; โหลดหมายเลข `ทันที' ไปที่ temp register; ถ้ามันเป็นเพียง ld อาร์กิวเมนต์ที่สอง; จะต้องเป็นตำแหน่งหน่วยความจำแทน DDRD, temp; mv temp เป็น DDRD ผลลัพธ์คือ PD0 เป็นอินพุต; และส่วนที่เหลือเป็นเอาต์พุต clr temp; บิตทั้งหมดใน temp ถูกตั้งค่าเป็น 0 ของ PortB, temp; ตั้งค่าบิตทั้งหมด (เช่นพิน) ใน PortB เป็น 0V ldi temp, 0b00000001; โหลดหมายเลขทันทีเพื่อ temp out PortD, temp; ย้ายอุณหภูมิไปที่ PortD PD0 มีตัวต้านทานแบบดึงขึ้น; (เช่นตั้งค่าเป็น 5V) เนื่องจากมี 1 ในบิตนั้น ส่วนที่เหลือเป็น 0V ตั้งแต่ 0

หลัก:

ในอุณหภูมิ PinD; PinD ถือสถานะของ PortD คัดลอกไปที่ temp; หากปุ่มเชื่อมต่อกับ PD0 นี่จะเป็น; 0 เมื่อกดปุ่ม 1 มิฉะนั้นตั้งแต่; PD0 มีตัวต้านทานแบบดึงขึ้นโดยปกติอยู่ที่ 5V ออก PortB, อุณหภูมิ; ส่ง 0 และ 1 ที่อ่านด้านบนไปยัง PortB; นี่หมายความว่าเราต้องการให้ LED เชื่อมต่อกับ PB0,; เมื่อ PD0 เป็น LOW จะตั้งค่า PB0 เป็น LOW และหมุน; บน LED (เนื่องจากอีกด้านหนึ่งของ LED เป็น; เชื่อมต่อกับ 5V และจะตั้งค่า PB0 เป็น 0V ดังนั้น กระแสจะไหล) rjmp Main; วนกลับไปที่จุดเริ่มต้นของ Main

ขอให้สังเกตว่าครั้งนี้เราไม่เพียงแต่มีความคิดเห็นอีกมากมายในโค้ดของเรา แต่เรายังมีส่วนหัวที่ให้ข้อมูลว่าใครเป็นคนเขียนและเขียนเมื่อใด โค้ดที่เหลือจะถูกแยกออกเป็นส่วนๆ

หลังจากคุณคอมไพล์โค้ดข้างต้นแล้ว คุณควรโหลดโค้ดดังกล่าวลงในไมโครคอนโทรลเลอร์และดูว่าใช้งานได้ ไฟ LED ควรสว่างขึ้นในขณะที่คุณกดปุ่ม และดับลงอีกครั้งเมื่อคุณปล่อยมือ ฉันได้แสดงสิ่งที่ดูเหมือนในภาพ

ขั้นตอนที่ 3: การวิเคราะห์โค้ดทีละบรรทัด

ฉันจะข้ามบรรทัดที่เป็นเพียงความคิดเห็นเนื่องจากจุดประสงค์ของพวกเขาชัดเจนในตัวเอง

.nolist

.include "m328Pdef.inc".list

สามบรรทัดนี้รวมถึงไฟล์ที่มีข้อกำหนด Register และ Bit สำหรับ ATmega328P ที่เรากำลังตั้งโปรแกรม คำสั่ง.nolist จะบอกแอสเซมเบลอร์ไม่ให้รวมไฟล์นี้ในไฟล์ pushbutton.lst ที่สร้างขึ้นเมื่อคุณประกอบ มันปิดตัวเลือกรายการ หลังจากรวมไฟล์แล้ว เราจะเปิดตัวเลือกรายการอีกครั้งด้วยคำสั่ง.list เหตุผลที่เราทำเช่นนี้ก็เพราะไฟล์ m328Pdef.inc ค่อนข้างยาว และเราไม่จำเป็นต้องเห็นมันในไฟล์รายการ แอสเซมเบลอร์ avra ของเราไม่ได้สร้างไฟล์รายการโดยอัตโนมัติ และหากเราต้องการไฟล์ เราจะประกอบโดยใช้คำสั่งต่อไปนี้:

avra -l pushbutton.lst pushbutton.asm

หากคุณทำเช่นนี้ มันจะสร้างไฟล์ชื่อ pushbutton.lst และหากคุณตรวจสอบไฟล์นี้ คุณจะพบว่ามันแสดงรหัสโปรแกรมของคุณพร้อมกับข้อมูลเพิ่มเติม หากคุณดูข้อมูลเพิ่มเติม คุณจะเห็นว่าบรรทัดที่ขึ้นต้นด้วย C: ตามด้วยที่อยู่สัมพัทธ์ในฐานสิบหกของตำแหน่งที่โค้ดวางอยู่ในหน่วยความจำ โดยพื้นฐานแล้วมันเริ่มต้นที่ 000000 ด้วยคำสั่งแรกและเพิ่มขึ้นจากที่นั่นด้วยคำสั่งที่ตามมาแต่ละคำสั่ง คอลัมน์ที่สองหลังจากตำแหน่งสัมพัทธ์ในหน่วยความจำคือรหัสฐานสิบหกสำหรับคำสั่งตามด้วยรหัสฐานสิบหกสำหรับอาร์กิวเมนต์ของคำสั่ง เราจะพูดถึงไฟล์รายการเพิ่มเติมในบทช่วยสอนในอนาคต

.def ชั่วคราว = r16; กำหนดให้รีจิสเตอร์ทำงาน r16 เป็น temp

ในบรรทัดนี้เราใช้คำสั่งแอสเซมเบลอร์ ".def" เพื่อกำหนดตัวแปร "temp" ให้เท่ากับ r16 "working register" เราจะใช้ register r16 เป็นที่เก็บหมายเลขที่เราต้องการคัดลอกไปยังพอร์ตและรีจิสเตอร์ต่างๆ (ซึ่งไม่สามารถเขียนได้โดยตรง)

แบบฝึกหัดที่ 1: ลองคัดลอกเลขฐานสองโดยตรงไปยังพอร์ตหรือการลงทะเบียนพิเศษ เช่น DDRB และดูว่าเกิดอะไรขึ้นเมื่อคุณพยายามรวบรวมรหัส

รีจิสเตอร์ประกอบด้วยข้อมูลหนึ่งไบต์ (8 บิต) โดยทั่วไปแล้วจะเป็นชุดของ SR-Latches แต่ละอันเป็น "บิต" และมี 1 หรือ 0 เราอาจพูดถึงเรื่องนี้ (และแม้แต่สร้างใหม่!) ในภายหลังในชุดนี้ คุณอาจสงสัยว่า "เครื่องบันทึกการทำงาน" คืออะไร และเหตุใดเราจึงเลือก r16 เราจะพูดถึงสิ่งนั้นในบทช่วยสอนในอนาคตเมื่อเราดำดิ่งลงไปในหล่มของ internals ของชิป สำหรับตอนนี้ ฉันต้องการให้คุณเข้าใจวิธีการทำสิ่งต่างๆ เช่น เขียนโค้ดและโปรแกรมฮาร์ดแวร์จริง จากนั้นคุณจะมีกรอบอ้างอิงจากประสบการณ์นั้นซึ่งจะทำให้หน่วยความจำและคุณสมบัติการลงทะเบียนของไมโครคอนโทรลเลอร์เข้าใจง่ายขึ้น ฉันตระหนักดีว่าหนังสือเรียนและการอภิปรายเบื้องต้นส่วนใหญ่ใช้วิธีตรงกันข้าม แต่ฉันพบว่าการเล่นวิดีโอเกมมาระยะหนึ่งก่อนเพื่อให้ได้มุมมองที่เป็นสากลก่อนที่จะอ่านคู่มือการใช้งานนั้นง่ายกว่าการอ่านคู่มือก่อนมาก

rjmp เริ่มต้น; ดำเนินการบรรทัดแรก

บรรทัดนี้เป็น "การข้ามแบบสัมพัทธ์" ไปที่ป้ายกำกับ "Init" และไม่จำเป็นจริงๆ เนื่องจากคำสั่งถัดไปมีอยู่แล้วใน Init แต่เรารวมไว้เพื่อใช้ในอนาคต

ในนั้น:

อุณหภูมิเซอร์; ตั้งค่าบิตทั้งหมดใน temp เป็น 1

หลังจากป้ายกำกับ Init เราจะดำเนินการคำสั่ง "set register" สิ่งนี้ตั้งค่า 8 บิตทั้งหมดในรีจิสเตอร์ "temp" (ซึ่งคุณจำได้คือ r16) เป็น 1 ตอนนี้อุณหภูมิมี 0b11111111

ออก DDRB อุณหภูมิ; ตั้งค่าบิตเป็น 1 บนรีจิสเตอร์ Data Direction I/O

; สำหรับ PortB ซึ่งเป็น DDRB ตั้งค่าพินนั้นเป็นเอาต์พุต 0 จะตั้งค่าพินนั้นเป็นอินพุต; ดังนั้นที่นี่ พิน PortB ทั้งหมดเป็นเอาต์พุต (ตั้งค่าเป็น 1)

รีจิสเตอร์ DDRB (Data Direction Register for PortB) บอกว่าพินใดบน PortB (เช่น PB0 ถึง PB7) ถูกกำหนดให้เป็นอินพุตและตัวใดถูกกำหนดให้เป็นเอาต์พุต เนื่องจากเรามีพิน PB0 ที่เชื่อมต่อกับ LED ของเรา และส่วนที่เหลือไม่ได้เชื่อมต่อกับสิ่งใด เราจะตั้งค่าบิตทั้งหมดเป็น 1 ซึ่งหมายความว่าเป็นเอาต์พุตทั้งหมด

อุณหภูมิ ldi, 0b11111110; โหลดหมายเลข `ทันที' ไปที่ temp register

; ถ้ามันเป็นเพียง ld อาร์กิวเมนต์ที่สองก็จะ; ต้องเป็นสถานที่แห่งความทรงจำ

บรรทัดนี้โหลดเลขฐานสอง 0b11111110 ลงในเครื่องบันทึกเวลา

ออก DDRD อุณหภูมิ; mv temp เป็น DDRD ผลลัพธ์คือ PD0 เป็นอินพุตและ

; ที่เหลือคือเอาท์พุต

ตอนนี้เราตั้งค่า Data Direction Register สำหรับ PortD จาก temp เนื่องจาก temp ยังมี 0b11111110 เราจะเห็นว่า PD0 จะถูกกำหนดให้เป็นอินพุตพิน (เนื่องจากมี 0 อยู่ที่จุดขวาสุด) และส่วนที่เหลือถูกกำหนดเป็นเอาต์พุตเนื่องจากมี 1 อยู่ในจุดเหล่านั้น

clr อุณหภูมิ; บิตทั้งหมดใน temp ถูกตั้งค่าเป็น 0's

ออก PortB, อุณหภูมิ; ตั้งค่าบิตทั้งหมด (เช่น พิน) ใน PortB เป็น 0V

ก่อนอื่นเรา "ล้าง" อุณหภูมิการลงทะเบียนซึ่งหมายถึงการตั้งค่าบิตทั้งหมดเป็นศูนย์ จากนั้นเราคัดลอกไปที่ PortB register ซึ่งตั้งค่า 0V บนพินเหล่านั้นทั้งหมด ศูนย์บนบิต PortB หมายความว่าโปรเซสเซอร์จะเก็บพินนั้นไว้ที่ 0V ทีละบิตจะทำให้พินนั้นถูกตั้งค่าเป็น 5V

แบบฝึกหัดที่ 2: ใช้มัลติมิเตอร์เพื่อตรวจสอบว่าพินทั้งหมดบน PortB เป็นศูนย์จริงหรือไม่ มีอะไรแปลก ๆ เกิดขึ้นกับ PB1 หรือไม่? มีความคิดว่าเหตุใดจึงอาจเป็น? (คล้ายกับแบบฝึกหัดที่ 4 ด้านล่าง จากนั้นทำตามรหัส…)แบบฝึกหัดที่ 3: ลบสองบรรทัดด้านบนออกจากโค้ดของคุณ โปรแกรมยังคงทำงานอย่างถูกต้องหรือไม่? ทำไม?

อุณหภูมิ ldi, 0b00000001; โหลดหมายเลขทันทีไปที่ temp

ออก PortD, อุณหภูมิ; ย้ายอุณหภูมิไปที่ PortD PD0 อยู่ที่ 5V (มีตัวต้านทานแบบดึงขึ้น); เนื่องจากมี 1 ในบิตนั้น ส่วนที่เหลือจะเป็น 0V แบบฝึกหัดที่ 4: ลบสองบรรทัดข้างต้นออกจากโค้ดของคุณ โปรแกรมยังคงทำงานอย่างถูกต้องหรือไม่? ทำไม? (ซึ่งแตกต่างจากแบบฝึกหัดที่ 3 ด้านบน ดูแผนภาพ pin out การตั้งค่า DDRD เริ่มต้นสำหรับ PD0 คืออะไร (ดูหน้า 90 ของแผ่นข้อมูล

ก่อนอื่นเรา "โหลดทันที" หมายเลข 0b00000001 ไปที่ temp ส่วน "ทันที" อยู่ที่นั่นเนื่องจากเรากำลังโหลดตัวเลขโดยตรงไปที่ temp แทนที่จะเป็นตัวชี้ไปยังตำแหน่งหน่วยความจำที่มีหมายเลขที่จะโหลด ในกรณีนั้นเราจะใช้ "ld" แทน "ldi" จากนั้นเราจะส่งหมายเลขนี้ไปที่ PortD ซึ่งตั้งค่า PD0 เป็น 5V และที่เหลือเป็น 0V

ตอนนี้เราได้ตั้งค่าพินเป็นอินพุตหรือเอาต์พุตแล้ว และเราได้ตั้งค่าสถานะเริ่มต้นเป็น 0V หรือ 5V (ต่ำหรือสูง) ดังนั้นเราจึงเข้าสู่โปรแกรม "ลูป" ของเรา

หลัก: ในอุณหภูมิ, PinD; PinD ถือสถานะของ PortD คัดลอกไปที่temp

; หากปุ่มเชื่อมต่อกับ PD0 นี่จะเป็น; 0 เมื่อกดปุ่ม 1 มิฉะนั้นตั้งแต่; PD0 มีตัวต้านทานแบบดึงขึ้นตามปกติที่5V

การลงทะเบียน PinD มีสถานะปัจจุบันของพิน PortD ตัวอย่างเช่น หากคุณต่อสาย 5V เข้ากับ PD3 ในรอบสัญญาณนาฬิกาถัดไป (ซึ่งเกิดขึ้น 16 ล้านครั้งต่อวินาทีเนื่องจากเรามีไมโครคอนโทรลเลอร์เชื่อมต่อกับสัญญาณนาฬิกา 16MHz) บิต PinD3 (จากสถานะปัจจุบันของ PD3) จะกลายเป็น 1 แทนที่จะเป็น 0 ดังนั้นในบรรทัดนี้ เราจะคัดลอกสถานะปัจจุบันของพินไปที่ temp

ออก PortB, อุณหภูมิ; ส่ง 0 และ 1 ที่อ่านด้านบนไปยัง PortB

; นี่หมายความว่าเราต้องการให้ LED เชื่อมต่อกับ PB0 ดังนั้น; เมื่อ PD0 เป็น LOW มันจะตั้งค่า PB0 เป็น LOW และหมุน; บน LED (ด้านอื่น ๆ ของ LED เชื่อมต่อ; ถึง 5V และสิ่งนี้จะตั้งค่า PB0 เป็น 0V ดังนั้นกระแสไฟ)

ตอนนี้เราส่งสถานะของพินใน PinD ไปยังเอาต์พุต PortB อย่างมีประสิทธิภาพ นี่หมายความว่า PD0 จะส่ง 1 ไปยัง PortD0 เว้นแต่จะกดปุ่ม ในกรณีนั้นเนื่องจากปุ่มเชื่อมต่อกับกราวด์นั้นพินนั้นจะอยู่ที่ 0V และมันจะส่ง 0 ไปยัง PortB0 ทีนี้ ถ้าคุณดูแผนภาพวงจร 0V บน PB0 หมายความว่า LED จะติดสว่างเนื่องจากอีกด้านหนึ่งอยู่ที่ 5V หากเราไม่กดปุ่มเพื่อให้ 1 ถูกส่งไปยัง PB0 นั่นหมายความว่าเรามี 5V บน PB0 และ 5V ที่อีกด้านหนึ่งของ LED ดังนั้นจึงไม่มีความต่างศักย์และไม่มีกระแสไหล ดังนั้น LED จะไม่เรืองแสง (ในกรณีนี้คือ LED ซึ่งเป็นไดโอด ดังนั้นกระแสไฟจะไหลเพียงทิศทางเดียวโดยไม่คำนึงถึงอะไรก็ตาม)

rjmp หลัก; วนกลับไปที่ Start

การกระโดดแบบสัมพัทธ์นี้จะนำเรากลับไปที่ Main: label และเราตรวจสอบ PinD ซ้ำแล้วซ้ำเล่า ตรวจสอบทุก ๆ 16 ล้านของวินาทีว่ามีการกดปุ่มและตั้งค่า PB0 ตามนั้นหรือไม่

แบบฝึกหัดที่ 5: แก้ไขโค้ดของคุณเพื่อให้ LED ของคุณเชื่อมต่อกับ PB3 แทนที่จะเป็น PB0 และดูว่าใช้งานได้ แบบฝึกหัดที่ 6: เสียบ LED ของคุณเข้ากับ GND แทน 5V และแก้ไขโค้ดของคุณตามนั้น

ขั้นตอนที่ 4: บทสรุป

ในบทช่วยสอนนี้ เราได้ตรวจสอบภาษาแอสเซมบลีเพิ่มเติมสำหรับ ATmega328p และเรียนรู้วิธีควบคุม LED ด้วยปุ่มกด โดยเฉพาะอย่างยิ่ง เราได้เรียนรู้คำสั่งต่อไปนี้:

ser register ตั้งค่าบิตทั้งหมดของ register เป็น 1's

clr register ตั้งค่าบิตทั้งหมดของรีจิสเตอร์เป็น 0's

ในรีจิสเตอร์ รีจิสเตอร์ i/o จะคัดลอกหมายเลขจากรีจิสเตอร์ i/o ไปยังรีจิสเตอร์ที่ใช้งานได้

ในบทช่วยสอนถัดไป เราจะตรวจสอบโครงสร้างของ ATmega328p และรีจิสเตอร์ การดำเนินการ และทรัพยากรต่างๆ ที่อยู่ในนั้น

ก่อนที่ฉันจะดำเนินการต่อกับบทช่วยสอนเหล่านี้ ฉันจะรอดูระดับความสนใจก่อน หากมีคนจำนวนมากที่สนุกกับการเรียนรู้วิธีการเขียนโค้ดโปรแกรมสำหรับไมโครโปรเซสเซอร์นี้ในภาษาแอสเซมบลี ฉันก็จะทำต่อไปและสร้างวงจรที่ซับซ้อนมากขึ้นและใช้โค้ดที่มีประสิทธิภาพมากขึ้น

แนะนำ: