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.