Bài tập ví dụ cho Vi điều khiển Atmega8 (P1)

Tập hợp một số bài tập ví dụ cho Vi điều khiển Atmega8. Trong phần này chúng ta sử dụng ngôn ngữ Hợp ngữ (Assembly) để thực hiện một số ví dụ minh họa. Các bài tập gồm có việc sử dụng cổng cửa có sẵn để bật tắt đèn LED; tạo hàm trễ; vòng lặp; tạo bảng và sử dụng bảng; ngắt phần cứng ngoài INT0; sử dụng bộ định thời Timer, biến đổi ADC, truyền thông chuẩn USART

Sơ đồ chân Atmega8

Ta sử dụng bộ dao động nội bên trong chip có tần số mặc định 1MHz. Nếu muốn thay đổi tốc độ xung nhịp, ta phải thay đổi các Fuse bits và gắn thạch anh ngoài nếu cần.

Chương trình được viết trên Atmel Studio.

Bài 1: Tạo trễ

/*
 * delay1.asm
 *
 *   Author: Tung
 */ 

.INCLUDE "m8def.inc"	; Khai báo thư viện cho biên dịch
.INCLUDE <m8def.inc>    ; hoặc dùng dấu < >
;----------------;
; Khai báo thanh ghi
;----------------;
.DEF A = R16	        ; Thanh ghi tổng dùng chung
.DEF I = R21	        ; Thanh ghi chỉ số
.DEF J = R22
.DEF K = R23
.DEF L = R24

.ORG $0000		; Địa chỉ gốc của chương trình

START:			; Khai báo nhãn trước dấu :
    LDI A,LOW(RAMEND)   ; Chuẩn bị Stack
    OUT SPL,A           
    LDI A,HIGH(RAMEND)  
    OUT SPH,A           
    LDI A,0b1111_1111   ; Muốn PortB là lối ra
    OUT DDRB,A          ; Nạp cho thanh ghi định hướng

;-------------------;
; Chương trình chính
;-------------------;
MAIN:
    SER A               ; Bật toàn nội dung thanh ghi lên 1
    OUT  PORTB,A	; Xuất ra PortB
    RCALL PAUSE		; Chờ 1 khoảng thời gian
    CLR A               ; Xóa nội dung thanh ghi
    OUT PORTB,A         ; Xuất ra
    RCALL PAUSE         ; chờ
    
LOOP:			; 
    RJMP MAIN	        ; Chương trình quay lại lặp vĩnh viễn
;----------------;
; Hàm tạo trễ 1s ;
;----------------;
PAUSE:
	LDI J, 8	; 1 Chu kỳ
Delay1:
	LDI K, 125	; 1 c
Delay2:
	LDI L, 250	; 1 c
Delay3:
	DEC L		; 1 c
	NOP		; 1 c
	BRNE Delay3	; 2 chu kỳ hoặc 1 nếu sang lệnh tiếp

	DEC K		; 1 c
	BRNE Delay2	; 2 c (1c)

	DEC J		; 1 c
	BRNE Delay1	; 2 c (1c)
RET			; Quay về chương trình chính

Bài 2: Hiển thị LED đơn giản với lệnh Shift

.INCLUDE "m8def.inc";

.DEF A = R16 
.DEF I = R21 
.DEF J = R22
.DEF K = R23
.DEF L = R24

.ORG $0000

START:
  LDI A,LOW(RAMEND) 
  OUT SPL,A 
  LDI A,HIGH(RAMEND) 
  OUT SPH,A 
  LDI A,0b1111_1111 
  OUT DDRB,A

; Chuong trình thực hiện dịch LED sang trái
; số lần quay phụ thuộc thanh ghi I
; khi kết thúc chương trình dừng lại
SHIFTLEFT: 
  CLR I
MAIN:
  LDI A, 0b0000_0001
  OUT PORTB,A
  RCALL PAUSE 

  LSL A              ; Dịch trái
  OUT PORTB,A
  RCALL PAUSE 

  LSL A
  OUT PORTB,A
  RCALL PAUSE

  LSL A
  OUT PORTB,A
  RCALL PAUSE

  LSL A
  OUT PORTB,A
  RCALL PAUSE

  LSL A
  OUT PORTB,A
  RCALL PAUSE

  LSL A
  OUT PORTB,A
  RCALL PAUSE

  LSL A
  OUT PORTB,A
  RCALL PAUSE

  DEC I
  BRNE MAIN
LOOP: 
  RJMP LOOP

PAUSE:
  LDI J, 8 
 Delay1:
  LDI K, 125 
 Delay2:
  LDI L, 250 
 Delay3:
  DEC L 
  NOP
  BRNE Delay3

  DEC K 
  BRNE Delay2

  DEC J 
  BRNE Delay1 
  RET

Bài 3: Hiển thị LED dùng vòng lặp

/*
* LED.asm
*
* Tung Le
*/

.INCLUDE "m8def.inc";

.DEF A = R16 
.DEF I = R21 
.DEF J = R22
.DEF K = R23
.DEF L = R24
.DEF N = R25

.ORG $0000

START:
   LDI A,LOW(RAMEND) 
   OUT SPL,A 
   LDI A,HIGH(RAMEND) 
   OUT SPH,A 
   LDI A,0b1111_1111 
   OUT DDRB,A

; Chương trình dịch LED sang trái
; Lặp lại mãi mãi

SHIFTLEFT: 
  LDI N, 7              ; Nạp 7 vào thanh ghi chỉ số
  LDI A, 0b0000_0001    ; Nạp vị trí LED 
  OUT PORTB,A           ; Xuất ra PortB
  RCALL PAUSE 
MAIN: 
  LSL A                 ; Dịch trái
  OUT PORTB,A
  RCALL PAUSE

  DEC N                 ; Giảm N đi 1 đơn vị
  BRNE MAIN             ; Nêu chưa bằng 0 về lại MAIN
LOOP:                   ; N = 0 thực hiện lênh tiếp
  RJMP SHIFTLEFT        ; Nhảy về SHIFTLEFT

PAUSE:
  LDI J, 8 
 Delay1:
  LDI K, 125 
 Delay2:
  LDI L, 250 
 Delay3:
  DEC L 
  NOP
  BRNE Delay3

  DEC K 
  BRNE Delay2

  DEC J 
  BRNE Delay1 
RET

Bài 4: Hiển thị LED theo bảng (cần cải tiến để sử dụng vòng lặp vĩnh viễn)

.INCLUDE "m8def.inc";

.DEF A = R16 
.DEF B = R17
.DEF C = R18
.DEF I = R21 
.DEF J = R22
.DEF K = R23
.DEF L = R24

.ORG $0000
RJMP INIT
; Bảng giá trị
LED: 
 .DB 0b1000_0001, 0b1100_0011, 0b1110_0111, 0b1111_1111
 .DB 0b0111_1110, 0b0011_1100, 0b0001_1000, 0b0000_0000
 .DB 0b0001_1000, 0b0011_1100, 0b0111_1110, 0b1111_1111

INIT:
  LDI A,LOW(RAMEND) 
  OUT SPL,A 
  LDI A,HIGH(RAMEND) 
  OUT SPH,A 
  LDI A,0b1111_1111 
  OUT DDRB,A 

MAIN: 
  LDI ZH, high(1<<LED)   ; Lưu địa chỉ bảng
  LDI ZL, low(1<<LED)
  LDI B, 12              ; Số lượng giá trị trong bảng

DISPLAY:
  LPM A, Z               ; Nạp nội dung địa chỉ
  OUT PORTB, A
  RCALL PAUSE
  ADIW ZL, 1             ; Tăng giá trị địa chỉ
  DEC B                  ; Giảm chỉ số bảng
  BRNE DISPLAY           ; Hiển thị cho đến khi hết bảng

LOOP: 
  RJMP LOOP              ; Dừng tại đây

PAUSE:
  LDI J, 3 
 Delay1:
  LDI K, 125 
 Delay2:
  LDI L, 250 
 Delay3:
  DEC L 
  NOP
  BRNE Delay3

  DEC K 
  BRNE Delay2

  DEC J 
  BRNE Delay1 
RET

Bài 5: Sử dụng ngắt ngoài INT0

/*
* Int0.asm
*
*/

.INCLUDE "m8def.inc";

.DEF A = R16

.ORG $0000
RJMP START

.ORG $0001            ; Ðịa chỉ của chương trình ngắt ngoài
RJMP NGAT0

START:
  LDI A,LOW(RAMEND) 
  OUT SPL,A 
  LDI A,HIGH(RAMEND) 
  OUT SPH,A 

KHOITAO:
  LDI A, 1 << INT0    ; Tương ứng với nạp 0b01000000
  OUT GICR, A         ; Cho phép ngắt ngoài 0 

  SEI                 ; Cho phép ngắt toàn cục

  LDI A, 0b1111_1111 
  OUT DDRB, A         ; Port B là lối ra
  LDI A, 0b0000_0000  ; Ext Int0 là lối vào vì chân ngắt
  OUT DDRD, A         ; các chân khác không dùng nên tạm để lối vào

HERE:                 ; Không làm gì trong chuơng trình chính
  RJMP HERE           ; Chờ ngắt
;-------------------------;
; Chương trình khi có ngắt
;-------------------------;
NGAT0:
  PUSH A
  IN A, SREG           ; Lưu thanh ghi trạng thái
  PUSH A
  IN A, PORTB          ; Đọc giá trị Port B
  COM A                ; Đảo 
  OUT PORTB, A         ; Xuất ra lại Port B
  POP A
  OUT SREG, A          ; Trả giá trị thanh ghi
  POP A
RETI                   ; Thoát chương trình ngắt

Bài 6: Sử dụng bộ định thời Timer

/*
* Timer0.asm
*
*/

.INCLUDE "m8def.inc";

.DEF A = R16

.ORG $0000
   RJMP START
.ORG $0009
   RJMP Ngat_TOV0

START:
  LDI A,LOW(RAMEND) 
  OUT SPL,A 
  LDI A,HIGH(RAMEND) 
  OUT SPH,A 

INIT:
  LDI R16,(1<<CS02)|(1<<CS00)   ; Bit chọn độ chia là 1024
  OUT TCCR0, R16                ; Tốc độ bộ định thời = xung chính (1MHz) / 1024
  LDI A, 1 << TOV0
  OUT TIFR, A                   ; Xóa ngắt hiện tại (nếu có)
  LDI A, 1 << TOIE0
  OUT TIMSK, A                  ; Cho phép ngắt Timer0
  LDI A, 0b1111_1111 
  OUT DDRB,A

  SEI
HERE:
  RJMP HERE
;-------------------------------------;
; Chương trình khi xảy ra ngắt Timer0 ;
;-------------------------------------;
Ngat_TOV0:
  PUSH A
  IN A, SREG
  PUSH A
  IN A, PORTB 
  COM A 
  OUT PORTB, A 
  POP A
  OUT SREG, A
  POP A
RETI

Bài 7: Sử dụng ADC

/*
* ADC0.asm
*
*/

.INCLUDE "m8def.inc";

.DEF A = R16 
.DEF B = R17

.ORG $0000
   RJMP START
.ORG $000E
   RJMP Ngat_ADC0

START:
  LDI A,LOW(RAMEND) 
  OUT SPL,A 
  LDI A,HIGH(RAMEND) 
  OUT SPH,A 

INIT:
  LDI A, (1<<ADLAR)       ; Canh phải 10bit, dồn 8bit cao lên thanh ghi trước
  OUT ADMUX, A            ; do chỉ cần đọc 8bit, bỏ 2bit thấp
  LDI A, (1<<ADEN) | (1<< ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0) 
  OUT ADCSRA, A            ; độ chia xung là 128, cho phép ngắt 

  SEI

  LDI A, 0b1111_1111 
  OUT DDRB, A
  LDI A, 0b0000_0000      ; ADC trên PortC nên đặt là lối vào
  OUT DDRC, A

  IN B, ADCSRA             ; Lưu thanh ghi trạng thái ADC
  ORI B, 1<<ADSC           ; Bật ADC chạy 1 lần
  OUT ADCSRA, B            ; Trả trạng thái

HERE:
  RJMP HERE                ; Chỉ chờ chương trình ngắt ADC
;---------------------------------;
; Nội dung chương trình ngắt ADC0 ;
;---------------------------------;
Ngat_ADC0:
  PUSH A
  IN A, SREG
  PUSH A
  IN A, ADCH               ; Đọc giá trị 8bit
  OUT PORTB, A             ; Xuất ra PortB
  NOP                      ; Chờ 1 c
  ORI B, 1<<ADSC           ; Bật ADC lần nữa
  OUT ADCSRA, B
  POP A
  OUT SREG, A
  POP A
RETI

Bài 8: Truyền thông chuẩn USART

.INCLUDE "m8def.inc";

.DEF A = R16 
.DEF B = R17

.ORG $0000
   RJMP START
.ORG $0001
   RJMP ISR_INT0
.ORG $000B              ; Địa chỉ ngắt USART
   RJMP ISR_USART

START:
  LDI A,LOW(RAMEND) 
  OUT SPL,A 
  LDI A,HIGH(RAMEND) 
  OUT SPH,A 

KHOITAO:
  LDI A, 1 << INT0 
  OUT GICR, A 

  LDI A, (1 << RXCIE) | (1 << RXEN) | (1 << TXEN) 
  OUT UCSRB, A             ; Cho phép truyền & nhận, bật ngắt khi nhận xong
  LDI A, (1 << UCSZ1) | (1 << UCSZ0)
  OUT UCSRC, A             ; 8bit dữ liệu, 1bit dừng, không chẵn lẻ
  LDI A, 6
  OUT UBRRL, A ; 9600 baud
  LDI A, 0
  OUT UBRRH, A

  SEI

  LDI A, 0b1111_1111 
  OUT DDRB, A
  LDI A, 0b0000_0010       ; Bật chân PortD theo chức năng
  OUT DDRD, A              ; PD0 ->RX, PD1 ->TXD, PD2 ->INT0

  LDI B, 0
  LDI A, 0b00001111
  OUT PORTB, A
HERE:
  RJMP HERE
;---------------------;
; 2 chương trình ngắt ;
;---------------------;
ISR_INT0:
  PUSH A
  IN A, SREG
  PUSH A
  INC B               ; Tăng B lên 1 
  OUT UDR, B          ; Truyền qua IC kia
  IN A, PORTB 
  COM A 
  OUT PORTB, A
  POP A
  OUT SREG, A
  POP A
RETI
;-------------------;
ISR_USART:
  PUSH A
  IN A, SREG
  PUSH A
  IN A, UDR              ; Đọc vào giá trị nhận được
  OUT PORTB, A           ; Xuất giá trị đó ra PortB
  POP A
  OUT SREG, A
  POP A
RETI

Trong phần 2 chúng ta sẽ làm quen với việc viết chương trình bằng ngôn ngữ cấp cao hơn là C.

Leave a Reply

Your email address will not be published. Required fields are marked *