AVR ภาษาแอสเซมบลี Blink ไฟกระพริบ LED
บทความนี้ จะกล่าวถึงการทำให้ LED ที่อยู่เชื่อมต่ออยู่ที่ ขา PB0 ของ ATtiny13 ให้กระพริบได้ ด้วยการหน่วงเวลา หรือเว้นระยะ ในการ เปิดไฟ LED ซึ่งพื้นฐานของการสลับพอร์ตเอาต์พุตได้อธิบายไว้แล้วในบทความก่อนหน้านี้ว่า: sbi PORTB, PORTB0 ไฟ LED ติด แต่บทความนี้จะเพิ่ม cbi PORTB, PORTB0 ไฟ LED ดับ
น่าเสียดายที่คอนโทรลเลอร์ที่มีความถี่สัญญาณนาฬิกา 1.2 Mcs / s ซึ่ง ATtiny13 ทำงานตามค่าเริ่มต้นต้องการการดำเนินการสองอย่างนี้เพียง 4 / 1.200.000 วินาที = 0.000,003,33 วินาที สิ่งนี้เร็วเกินกว่าที่สายตามนุษย์จะรับรู้ได้ วิธีแก้ปัญหาในการมีส่วนร่วมกับคอนโทรลเลอร์กับอย่างอื่นแสดงอยู่ที่นี่
นั่นคือวิธีที่ AVR ดำเนินการคำสั่ง:
- คำสั่งถัดไปจะอ่านจากที่เก็บข้อมูลแฟลช
- คำสั่งจะถูกถอดรหัสเป็นขั้นตอนที่จะดำเนินการ
- คำสั่งถูกดำเนินการ ในระหว่างดำเนินการคำสั่งถัดไปจะถูกอ่านและถอดรหัส (“Pre-Fetch”)
ที่จริงแล้วการดำเนินการคำสั่งต้องมี 2 รอบนาฬิกา แต่สิ่งนี้จะลดลงครึ่งหนึ่งโดยการดึงคำสั่งถัดไปไว้ล่วงหน้าในระหว่างการดำเนินการก่อนหน้าดังนั้นการดำเนินการที่มีประสิทธิภาพจึงต้องใช้เพียงรอบเดียว สิ่งนี้ใช้ได้ผลโดยทั่วไป แต่ไม่ใช่ในกรณีที่คำสั่งเปลี่ยนที่อยู่การดำเนินการ (ในกรณีที่ข้ามไปที่อื่นในโค้ด) ในกรณีนี้คำสั่งที่ดึงมาล่วงหน้าจะไร้ประโยชน์ ดังนั้นคำแนะนำการกระโดดทั้งหมดที่เปลี่ยนที่อยู่จึงต้องใช้สองรอบนาฬิกา
เนื่องจาก AVR ที่ดึงข้อมูลล่วงหน้าจะดำเนินการเร็วกว่าที่ไม่มีเป็นสองเท่า หากเปรียบเทียบคอนโทรลเลอร์ประเภทต่างๆและวงจรนาฬิกาของพวกเขาสิ่งนี้จะต้องนำมาพิจารณาด้วย
คำสั่งเกือบทั้งหมดของคอนโทรลเลอร์ AVR ดำเนินการในรอบนาฬิกาเดียว แต่บางคนต้องการสอง (หรือมากกว่านั้น) จากบทความที่ผ่านมานั้น เป็นผลมาจากการอ่านในพอร์ตทั้งหมดก่อน จากนั้นตั้งค่าหรือล้างบิตเดียวในไบต์นั้น แล้วเขียนผลลัพธ์กลับไปที่พอร์ต การดำเนินการด้วยการดึงข้อมูลล่วงหน้าต้องใช้ 2 รอบนาฬิกา
Execution times of instructions
เวลาในการประมวลผลของคำแนะนำทั้งหมดของ AVR แสดงอยู่ในฐานข้อมูลอุปกรณ์ในตาราง “Instruction Set Summary” ในคอลัมน์ “Clocks” แสดงจำนวนรอบนาฬิกา
ภาษาแอสเซมบลี Blink ไฟกระพริบ LED
รายการอุปกรณ์
- 1. ATtiny13 ATTINY13A-PU 8-bit AVR
- 2. Jumper (M2M) cable 20cm Male to Male
- 3. USBasp USBisp AVR Programmer
- 4. VAR 10Pin to 6Pin Adapter Converter
- 5. Breadboard 700 Points SYB-120
- 6. หลอดไฟ LED 5mm สีแดง
- 7. รีซิสเตอร์ 220 OHM 1/4W 5%
- 8. Jumper (M2M) cable 10cm Male to Male
ขั้นตอนการทํางาน
1 : โปรแกรมแรก เปิดไฟ LED
โปรแกรมแรกของ การใช้งานไมโครคอนโทรลเลอร์ ซึ่งเป็นหนึ่งในโปรแกรมที่ง่ายที่สุดเท่าที่จะเป็นไปได้ในการเขียนภาษาโปรแกรมต่างๆ เพราะฉะนั้นโดยธรรมเนียมปฏิบัติแล้ว มักจะใช้ในการตรวจสอบว่าเขียนภาษาโปรแกรมได้ถูกต้องหรือระบบมีการประมวลผลที่ถูกต้อง และมักถูกใช้เป็นตัวอย่างที่ง่ายที่สุดในการแสดงผลลัพธ์ของการเขียนโปรแกรม โดยทำตามตามขั้นตอนลิงค์ด้านล่าง
2 : ไฟกะพริบเร็วแบบธรรมดา
เขียนโค้ด และ อัพโหลด ตามโค้ดด้านล่างนี้
sbi DDRB,DDB0 ; PB0 output driver enable
sbi PORTB,PORTB0 ; LED on
cbi PORTB,PORTB0 ; LED off
ผลลัพธ์จะน่าผิดหวัง: LED เพราะเกือบมืด เราจะไม่เห็นแสงสว่าง LED มากนักเนื่องจากมีการเปิดอยู่ในช่วงเวลาสั้นเกินไป
LED เกือบมืดสาเหตุนี้เป็นเพราะเป็นคำสั่งที่ให้ LED ทำงานเพียง 2 รอบนาฬิกา แล้วคอนโทรลเลอร์อ่านที่เก็บข้อมูลแฟลชว่างพบ 0xFFFF ที่นั่น และไม่ทำอะไรเลย จนกว่าจะถึงจุดสิ้นสุดของแฟลชและเริ่มต้นใหม่ที่ที่อยู่ 0x0000
เราได้เรียนรู้สองสิ่งที่นี่: ประการแรกคอนโทรลเลอร์ไม่สามารถทำการเปิดและปิด LED ได้และอย่างที่สองเราต้องมีกลไกในการรีสตาร์ท sbi และ cib ลำดับการปิดและเปิด LED
กลไกในการเริ่มต้นด้วย sbi DDRB,DDB0 ; จนถึง rjmp Loop ; แล้วกลับไปทำงานที่ sbi PORTB,PORTB0 ; คือการทำงานแบบวนซ้ำ คำสั่งที่อยู่ด้านล่าง Label “Loop:” เนื่องจากพื้นที่แอดเดรสของแฟลชมีขนาดเล็กเราจึงใช้คำสั่งการกระโดดแบบสัมพัทธ์ rjmp ซึ่งสามารถข้ามไปข้างหน้าและถอยหลังได้มากกว่า 2,000 คำสั่ง โค้ดจะมีลักษณะดังนี้:
sbi DDRB,DDB0 ; PB0 as output, driver stage on, 2 clock cycles
Loop:
sbi PORTB,PORTB0 ; LED on, 2 clock cycles
cbi PORTB,PORTB0 ; LED off, 2 clock cycles
rjmp Loop ; Jump relative back to label Loop, 2 clock cycles
Label “Loop:” คือ การกระโดดที่อยู่ Label จะลงท้ายด้วย “:” เสมอ คำสั่ง rjmp จะคำนวณการกระจัดสัมพัทธ์ระหว่างที่อยู่ปัจจุบันและที่อยู่ของ Label และใส่สิ่งนี้ลงในการแสดงไบนารีของ rjmp โดยอัตโนมัติดังนั้นเราจึงไม่ต้องสนใจเรื่องนี้ . โค้ดด้านบนมีข้อเสียคือไฟ LED ติด 2 รอบและดับ 4 รอบ ในการแก้ไขสิ่งนี้เราใส่ nop สองตัวระหว่าง sbi และ cbi ซึ่งจะทำให้ LED ดับช้าลง โดยให้ LED ติด 4 รอบและดับ 4 รอบ
sbi DDRB,DDB0 ; PB0 as output, driver stage on, 2 clock cycles
Loop:
sbi PORTB,PORTB0 ; LED on, 2 clock cycles
nop ; do nothing, 1 clock cycle
nop ; do nothing, 1 clock cycle
cbi PORTB,PORTB0 ; LED off, 2 clock cycles
rjmp Loop ; Jump relative back to label Loop, 2 clock cycles
ในความเป็นจริงคำสั่ง nop เป็นเพียงการชะลอการดำเนินการสำหรับหนึ่งรอบนาฬิกาและไม่ทำอะไรเลย
แผนภาพการไหลที่นี่เราจะเห็นแผนภาพการไหลของซอร์สโค้ดที่เรียกว่า เริ่มต้นด้วยการรีเซ็ตคอนโทรลเลอร์และแสดงการทำงานของ I / O เป็นสี่เหลี่ยมคางหมู เพิ่มจำนวนรอบนาฬิกาและแสดงว่าการทำงานของ LED เป็นแบบสมมาตร
ผลลัพธ์ : น่าเสียดายที่ไฟ LED กะพริบติดและดับด้วยความถี่ 1,200,000 / 8 = 150,000 cs / s ซึ่งสายตามนุษย์ มองไม่ทัน ในส่วนถัดไปเราจะพยายามลดความเร็วที่สูงนี้ลงอีก
3 : กะพริบเร็วแบบหน่วงเวลา 8 บิต
คำสั่ง nop สองคำสั่งที่เราใช้เพื่อชะลอการเรียกใช้งานไม่สามารถหน่วงเวลาเกิน 500 รอบนาฬิกาได้เนื่องจากข้อ จำกัด ของแฟลช ดังนั้นวิธีแก้ปัญหาที่มีประสิทธิภาพมากขึ้นจึงจำเป็นต้องชะลอออกไป
ความล่าช้าคือการนับถอยหลังจนกว่าเขาจะถึงศูนย์ ลำดับต่อไปนี้เป็นลูปหน่วงเวลาทั่วไป:
.equ cCounter = 250 ; define the number of downcounts
ldi R16, cCounter ; load a register with that constant
Loop:
dec R16 ; decrease counter by one
brne Loop ; branch if zero flag was not set in last instruction
นี่คือ register (R16) ใช้เพื่อนับถอยหลัง AVR แต่ละตัวมี 32 รีจิสเตอร์ R0 ถึง R31 แต่ละอันมีความยาว 8 บิต ดังนั้นจึงสามารถเก็บค่าไบนารีระหว่าง 0 ถึงทศนิยม 255 และ ldi หมายถึง “load immediate” สามารถโหลดได้เฉพาะครึ่งบนของรีจิสเตอร์โดยใช้คำสั่ง ldi
cCounter คงที่กำหนดความถี่ในการวนซ้ำ DEC ลดเนื้อหาของการลงทะเบียนทีละรายการ แฟล็ก Z (ศูนย์) ในการลงทะเบียนสถานะของคอนโทรลเลอร์ถูกตั้งค่าหากรีจิสเตอร์ถึงศูนย์หลัง DEC หากไม่ถูกล้าง การลงทะเบียนสถานะกว้างแปดบิต ถ้าและที่มีการเปลี่ยนแปลงเล็กน้อยในการลงทะเบียนสถานะในระหว่างการดำเนินการเรียนการสอนมีการระบุไว้ใน ชุดคำสั่งอย่างย่อ
หากคำสั่ง DEC นำไปสู่ศูนย์ในตัวอย่างข้างต้นการดำเนินการของลูปจะสิ้นสุดลงหากไม่วนซ้ำ ที่มาถึงโดยคำสั่ง BRNE นั่นหมายถึง “Branch if not equal” ซึ่งเรียกว่าการกระโดดตามเงื่อนไข การกระโดดตามเงื่อนไขเหล่านั้นจะเคลื่อนที่ไปข้างหลัง 63 คำสั่งหรือ 64 คำสั่งไปข้างหน้า หากส่วนขยายนี้เกินจะต้องใช้โหมดกระโดดอื่น ในกรณีของเราด้านบนสาขาเป็นเพียงคำสั่งเดียวเท่านั้นดังนั้น BRNE จึงใช้ได้ ชุดคำสั่งอย่างย่อถัวเฉลี่ยรอบนาฬิกาต่อไปนี้สำหรับ loop:
.equ cCounter = 250 ; (no clock cycle, assembler internal operation)
ldi R16, cCounter ; 1 clock cycle
Loop: ; (no clock cycle, assembler internal operation)
dec R16 ; 1 clock cycle
brne Loop ; 2 cycles when branching, 1 cycle without branching
ชุดคำสั่งย่อบอกว่าต้อง BRNE หนึ่งหรือสองรอบนาฬิกา อย่างที่เราทราบกันดีว่ากลไกการดึงข้อมูลล่วงหน้าต้องใช้รอบนาฬิกาสองรอบหากมีการดำเนินการกระโดดกลับ หากไม่มีการกระโดดถอยหลังเกิดขึ้นจำนวนรอบนาฬิกาเท่ากับหนึ่ง การดำเนินการวนซ้ำทั้งหมดต้องการรอบนาฬิกาต่อไปนี้:
.equ cCounter = 250 ; (no code)
ldi R16, cCounter ; 1 clock once executed
Loop:
dec R16 ; 1 clock 250 times executed
brne Loop ; 2 clock 249 times executed, 1 clock once executed
การดำเนินการที่สมบูรณ์ใช้เวลา (1 + 250 + 2 * 249 + 1) = 750 รอบนาฬิกา ที่ความถี่สัญญาณนาฬิกาของระบบ 1.2 Mcs / s 750 รอบนาฬิกาจะเท่ากับ 625 µs ซึ่งยังเร็วเกินไปสำหรับสายตามนุษย์
กะพริบเร็วแบบหน่วงเวลา 16 บิต
ตอนนี้ลองเคาน์เตอร์ 16 บิต สิ่งนี้ต้องใช้การลงทะเบียน 16 บิตซึ่ง AVR มีสี่คู่: คู่ทะเบียน R25: R24, R27: R26, R29: R28 และ R31: R30 สิ่งเหล่านี้สามารถเข้าถึงได้เช่นเดียวกับการลงทะเบียนเดียว (เช่นด้วยคำแนะนำของ ldi) แต่คำแนะนำบางอย่างใช้ได้กับทั้งคู่
ลูปการนับ 16 บิตทำงานดังนี้:
.equ cCounter16 = 50000 ; 1 to 65535 (does not generate code)
ldi R25,HIGH(cCounter16) ; 1 clock cycle, executed once
ldi R24,LOW(cCounter16) ; 1 clock cycle, executed once
Loop16:
sbiw R24,1 ; count down 16 bit, 2 clock cycles, executed 50000 times
brne Loop16 ; 2 clock cycles when branching 49999 times, 1 clock cycle once
สูตรทางคณิตศาสตร์สองรายการ “HIGH” และ “LOW” แยกค่าคงที่ 16 บิตใน 8 บิตบนและล่างและวาง 16 บิตให้ชาญฉลาดกับคู่รีจิสเตอร์ R25: R24
SBIWนับคู่รีจิสเตอร์ R25: R24 ลงทีละคู่ในโหมด 16 บิต หากทั้งคู่ถึงศูนย์จะตั้งค่าสถานะ Z คำสั่ง “BRNE” จะแตกกิ่งก้านตามเงื่อนไขอีกครั้งตราบเท่าที่ Z ยังชัดเจน
ตอนนี้ลูปต้องการ (1 + 1 + 2 * 50000 + 2 * 49999 + 1) = 200.001 รอบนาฬิกาหรือ 0.167 วินาที นั่นค่อนข้างใกล้ถึงเสี้ยววินาทีแล้ว แต่ไม่ตรงกับมัน
4 : กะพริบวินาทีที่แน่นอน
การกะพริบครั้งที่สองต้องใช้การทำงานร่วมกันของลูป 8 บิตและ 16 บิต ตามโค้ดด้านล่าง
.def rCounterA = R16 ; Outer 8 bit counter
.def rCounterIL = R24 ; Inner 16 bit counter, LSB
.def rCounterIH = R25 ; Inner 16 bit counter, MSB
;
; Define constants
;
.equ cInner = 2458 ; Counter inner loop
.equ cOuter = 61 ; Counter outer loop
;
; Program start
;
sbi DDRB,DDB0 ; Port pin PB0 as output
;
; Program loop
;
Loop:
sbi PORTB,PORTB0 ; Port pin PB0 HIGH, Led on, 2 clock cycles
; Outer delay loop, Led on
ldi rCounterA,cOuter ; Outer 8 bit counter, 1 clock cycle
Loop1:
ldi rCounterIH,HIGH(cInner) ; Inner 16 bit counter, 1 clock cycle
ldi rCounterIL,LOW(cInner) ; 1 clock cycle
Loop1i:
sbiw rCounterIL,1 ; Inner 16 bit counter downwards, 2 clock cycles
brne Loop1i ; if not zero: jump to loop1i 2 cycles, if zero 1 cycle
dec rCounterA ; Outer 8 bit counter downwards, 1 clock cycle
brne Loop1 ; if not zero: jump to loop1 2 cycles, zero: 1 cycle
nop ; Delay, 1 clock cycle
nop ; Delay, 1 clock cycle
;
cbi PORTB,PORTB0 ; Port pin PB0 LOW, Led off, 2 clock cycles
; Outer delay loop, Led off
ldi rCounterA,cOuter ; Outer 8 bit counter, 1 clock cycle
Loop2:
ldi rCounterIH,HIGH(cInner) ; Inner 16 bit counter, 1 clock cycle
ldi rCounterIL,LOW(cInner) ; 1 clock cycle
Loop2i:
sbiw rCounterIL,1 ; Inner 16 bit counter downwards, 2 clock cycles
brne Loop2i ; if not zero: jump to Loop2i 2 cycles, if zero 1 cycle
dec rCounterA ; Outer 8 bit counter downwards, 1 clock cycle
brne Loop2 ; if not zero: jump to Loop2 2 cycles, if zero 1 cycle
; Cycle end
rjmp Loop ; start from the beginning, 2 clock cycles
ผลลัพธ์ : ไฟกระพริบ ตามคลิปด้านล่าง
ลูปการนับภายใน 16 บิตและ 8 บิตด้านนอกมีให้สองครั้งเหมือนกันเพื่อให้ระยะเวลาปิด 0.5 วินาทีของ Led และ 0.5 วินาทีต่อรอบ
นี่คือแผนภาพการไหลและการคำนวณวงจร:
ตัวอย่างดังกล่าวแสดงให้เห็นถึงข้อได้เปรียบที่ชัดเจนของแอสเซมเบลอร์เหนือภาษาโปรแกรมอื่น ๆ : การวางแผนที่แน่นอนและการควบคุมเวลาดำเนินการผ่านการนับลูปเป็นงานที่ง่ายและตรงไปตรงมา
credit : http://www.avr-asm-tutorial.net/avr_en/micro_beginner/3_Led_Blinking/3_Led_Blinking.html
<<< #2 โปรแกรมแรก เปิดไฟ LED บทความก่อนหน้า | บทความต่อไป #4 ไฟกระพริบ ด้วย Timer >>>