AVR ภาษาแอสเซมบลี Blink ไฟกระพริบ LED

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 วินาที สิ่งนี้เร็วเกินกว่าที่สายตามนุษย์จะรับรู้ได้ วิธีแก้ปัญหาในการมีส่วนร่วมกับคอนโทรลเลอร์กับอย่างอื่นแสดงอยู่ที่นี่

Exceution of instructions by the controller


นั่นคือวิธีที่ AVR ดำเนินการคำสั่ง:

  1. คำสั่งถัดไปจะอ่านจากที่เก็บข้อมูลแฟลช
  2. คำสั่งจะถูกถอดรหัสเป็นขั้นตอนที่จะดำเนินการ
  3. คำสั่งถูกดำเนินการ ในระหว่างดำเนินการคำสั่งถัดไปจะถูกอ่านและถอดรหัส (“Pre-Fetch”)

ที่จริงแล้วการดำเนินการคำสั่งต้องมี 2 รอบนาฬิกา แต่สิ่งนี้จะลดลงครึ่งหนึ่งโดยการดึงคำสั่งถัดไปไว้ล่วงหน้าในระหว่างการดำเนินการก่อนหน้าดังนั้นการดำเนินการที่มีประสิทธิภาพจึงต้องใช้เพียงรอบเดียว สิ่งนี้ใช้ได้ผลโดยทั่วไป แต่ไม่ใช่ในกรณีที่คำสั่งเปลี่ยนที่อยู่การดำเนินการ (ในกรณีที่ข้ามไปที่อื่นในโค้ด) ในกรณีนี้คำสั่งที่ดึงมาล่วงหน้าจะไร้ประโยชน์ ดังนั้นคำแนะนำการกระโดดทั้งหมดที่เปลี่ยนที่อยู่จึงต้องใช้สองรอบนาฬิกา

เนื่องจาก AVR ที่ดึงข้อมูลล่วงหน้าจะดำเนินการเร็วกว่าที่ไม่มีเป็นสองเท่า หากเปรียบเทียบคอนโทรลเลอร์ประเภทต่างๆและวงจรนาฬิกาของพวกเขาสิ่งนี้จะต้องนำมาพิจารณาด้วย


คำสั่งเกือบทั้งหมดของคอนโทรลเลอร์ AVR ดำเนินการในรอบนาฬิกาเดียว แต่บางคนต้องการสอง (หรือมากกว่านั้น) จากบทความที่ผ่านมานั้น เป็นผลมาจากการอ่านในพอร์ตทั้งหมดก่อน จากนั้นตั้งค่าหรือล้างบิตเดียวในไบต์นั้น แล้วเขียนผลลัพธ์กลับไปที่พอร์ต การดำเนินการด้วยการดึงข้อมูลล่วงหน้าต้องใช้ 2 รอบนาฬิกา

Execution times of instructions


เวลาในการประมวลผลของคำแนะนำทั้งหมดของ AVR แสดงอยู่ในฐานข้อมูลอุปกรณ์ในตาราง “Instruction Set Summary” ในคอลัมน์ “Clocks” แสดงจำนวนรอบนาฬิกา


ภาษาแอสเซมบลี Blink ไฟกระพริบ LED

 รายการอุปกรณ์


ขั้นตอนการทํางาน


1 : โปรแกรมแรก เปิดไฟ LED


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



2 : ไฟกะพริบเร็วแบบธรรมดา


เขียนโค้ด และ อัพโหลด ตามโค้ดด้านล่างนี้

sbi DDRB,DDB0 ; PB0 output driver enable
sbi PORTB,PORTB0 ; LED on
cbi PORTB,PORTB0 ; LED off


ผลลัพธ์จะน่าผิดหวัง: LED เพราะเกือบมืด เราจะไม่เห็นแสงสว่าง LED มากนักเนื่องจากมีการเปิดอยู่ในช่วงเวลาสั้นเกินไป

AVR ภาษาแอสเซมบลี Blink ไฟกระพริบ 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 ซึ่งสายตามนุษย์ มองไม่ทัน ในส่วนถัดไปเราจะพยายามลดความเร็วที่สูงนี้ลงอีก

AVR ภาษาแอสเซมบลี Blink ไฟกระพริบ LED


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 >>>