Chủ Nhật, 12 tháng 8, 2012

[dientu686.com]Dientu686.com nhận đặt mua linh kiện nước ngoài


Dientu686.com nhận đặt mua linh kiện nước ngoài

Linh kiện điện- điện tử bao gồm:
- Linh kiện trong nước không có sẵn hàng.
- Linh kiện hiếm gặp, ít sử dụng.
- Linh kiện cần số lượng lớn, giá tốt hơn giá thị trường.

Điều kiện đặt mua :
- Số lượng tốt thiểu đặt mua là 10 đơn vị, hoặc giá trị đơn hàng lớn hơn 300.000 vnd.

- Thông tin linh kiện bao gồm tên đầy đủ của linh kiện, kiểu chân, Datasheet kèm theo (nếu có). 

- Giá linh kiện phụ thuộc và số lượng đặt hàng.
- Báo giá linh kiện sau 1 ngày.
- Thanh toán 100% trước khi nhận hàng. 

Phương thức đặt mua:
- Gửi email gồm danh sách linh kiện, số lượng cần đặt mua, thông tin liên hệ theo mẫu sau đến e-mail dientu686@gmail.com (ưu tiên e-mail có đầy đủ thông tin liên hệ).
- Gọi điện, nhắn tin số điện thoại 0985 946 944
- Liên hệ trực tiếp tại Nguyên Xá  - Minh Khai - Từ Liêm - Hà Nội

Thời gian đặt mua :
- 2-14 ngày có hàng kể từ ngày đặt mua, Số ngày cụ thể tuỳ vào thời điểm và được thông báo khi đặt mua.

Xin chân thành cám ơn!
http://dientu686.com/

[dientu686.com]Dientu686.com nhận đặt mua linh kiện nước ngoài


Dientu686.com nhận đặt mua linh kiện nước ngoài

Linh kiện điện- điện tử bao gồm:
- Linh kiện trong nước không có sẵn hàng.
- Linh kiện hiếm gặp, ít sử dụng.
- Linh kiện cần số lượng lớn, giá tốt hơn giá thị trường.

Điều kiện đặt mua :
- Số lượng tốt thiểu đặt mua là 10 đơn vị, hoặc giá trị đơn hàng lớn hơn 300.000 vnd.

- Thông tin linh kiện bao gồm tên đầy đủ của linh kiện, kiểu chân, Datasheet kèm theo (nếu có). 

- Giá linh kiện phụ thuộc và số lượng đặt hàng.
- Báo giá linh kiện sau 1 ngày.
- Thanh toán 100% trước khi nhận hàng. 

Phương thức đặt mua:
- Gửi email gồm danh sách linh kiện, số lượng cần đặt mua, thông tin liên hệ theo mẫu sau đến e-mail dientu686@gmail.com (ưu tiên e-mail có đầy đủ thông tin liên hệ).
- Gọi điện, nhắn tin số điện thoại 0985 946 944
- Liên hệ trực tiếp tại Nguyên Xá  - Minh Khai - Từ Liêm - Hà Nội

Thời gian đặt mua :
- 2-14 ngày có hàng kể từ ngày đặt mua, Số ngày cụ thể tuỳ vào thời điểm và được thông báo khi đặt mua.

Xin chân thành cám ơn!
http://dientu686.com/

Thứ Sáu, 10 tháng 8, 2012

[dientu686.com]Bài 3 - Ngắt ngoài


I. Ngắt trên AVR.
     Interrupts, thường được gọi là ngắt, là một tín hiệu khẩn cấp gởi đến bộ xử lí, yêu cầu bộ xử lí tạm ngừng tức khắc các hoạt động hiện tại để “nhảy” đến một nơi khác thực hiện một nhiệm vụ khẩn cấp nào đó, nhiệm vụ này gọi là trình phục vụ ngắt – isr (interrupt service routine ). Sau khi kết thúc nhiệm vụ trong isr, bộ đếm chương trình sẽ được trả về giá trị trước đó để bộ xử lí quay về thực hiện tiếp các nhiệm vụ còn dang dở. Như vậy, ngắt có mức độ ưu tiên xử lí cao nhất, ngắt thường được dùng để xử lí các sự kiện bất ngờ nhưng không tốn quá nhiều thời gian. Các tín hiệu dẫn đến ngắt có thể xuất phát từ các thiết bị bên trong chip (ngắt báo bộ đếm timer/counter tràn, ngắt báo quá trình gởi dữ liệu bằng RS232 kết thúc…) hay do các tác nhân bên ngoài (ngắt báo có 1 button được nhấn, ngắt báo có 1 gói dữ liệu đã được nhận…).
     Ngắt là một trong 2 kỹ thuật “bắt” sự kiện cơ bản là hỏi vòng (Polling) và ngắt. Hãy tưởng tượng bạn cần thiết kế một mạch điều khiển hoàn chỉnh thực hiện rất nhiều nhiệm vụ bao gồm nhận thông tin từ người dùng qua các button hay keypad (hoặc keyboard), nhận tín hiệu từ cảm biến, xử lí thông tin, xuất tín hiệu điều khiển, hiển thị thông tin trạng thái lên các LCD…(bạn hoàn toàn có thể làm được với AVR), rõ ràng trong các nhiệm vụ này việc nhận thông tin người dùng (start, stop, setup, change,…) rất hiếm xảy ra (so với các nhiệm vụ khác) nhưng lại rất “khẩn cấp”, được ưu tiên hàng đầu. Nếu dùng Polling nghĩa là bạn cần viết 1 đoạn chương trình chuyên thăm dò trạng thái của các button (tôi tạm gọi đoạn chương trình đó là Input()) và bạn phải chèn đoạn chương trình Input() này vào rất nhiều vị trí trong chương trình chính để tránh trường hợp bỏ sót lệnh từ người dùng, điều này thật lãng phí thời gian thực thi. Giải pháp cho vấn đề này là sử dụng ngắt, bằng cách kết nối các button với đường ngắt của chip và sử dụng chương trình Input() làm trình phục vụ ngắt - isr của ngắt đó, bạn không cần phải chèn Input() trong lúc đang thực thi và vì thế không tốn thời gian cho nó, Input() chỉ được gọi khi người dùng nhấn các button. Đó là ý tưởng sử dụng ngắt.
     Hình 1 minh họa cách tổ chức ngắt thông thường trong các chip AVR. Số lượng ngắt trên mỗi dòng chip là khác nhau, ứng với mỗi ngắt sẽ có vector ngắt, vector ngắt là các thanh ghi có địa chỉ cố định được định nghĩa trước nằm trong phần đầu của bộ nhớ chương trình. Ví dụ vector ngắt ngoài 0 (external interrupt 0) của chip atmega8 có địa chỉ là 0x001 (theo datasheet từ Atmel). Trong lúc chương trình chính đang thực thi, nếu có một sự thay đổi dẫn đến ngắt xảy ra ở chân INT0 (chân 4), bộ đếm chương trình (Program Counter) nhảy đến địa chỉ 0x001, giả sử ngay tại địa chỉ 0x001 chúng ta có đặt 1 lệnh RJMP đến một trình phục vụ ngắt (IRS1 chẳng hạn), một lần nữa bộ đếm chương trình nhảy đến IRS1 để thực thi trình phục vụ ngắt, kết thúc ISR1, bộ đếm chương trình lại quay về vị trí trước đó trong chương trình chính, quá trình ngắt kết thúc. Không mang tính bắt buộc nhưng tôi khuyên bạn nên tổ chức chương trình ngắt theo cách này để tránh những lỗi liên quan đến địa chỉ chương trình.
ngắt
Hình 1. Ngắt.
     Bảng 1 tóm tắt các vector ngắt có trên chip atmega8, cho các chip khác bạn hãy tham khảo datasheet để biết thêm.
Bảng 1 các vector ngắt và Reset trên chip Atmega8.
ngắt
II. Ngắt ngoài (External Interrupt).
      Phần này tôi dành giới thiệu các bạn cách cài đặt và sử dụng ngắt ngoài vì đây là loại ngắt duy nhất độc lập với các thiết bị của chip, các ngắt khác thường gắn với hoạt động của 1 thiết bị nào đó như Timer/Counter, giao tiếp nối tiếp USART, chuyển đổi ADC…chúng ta sẽ khảo sát cụ thể khi tìm hiểu về hoạt động của các thiết bị này.
      Ngắt ngoài là cách rất hiệu quả để thực hiện giao tiếp giữa người dùng và chip. Trên chip atmega8 có 2 ngắt ngoài có tên là INT0 và INT1 tương ứng 2 chân số 4 (PD2) và số 5 (PD3). Như tôi đã đề cập trong bài AVR2, khi làm việc với các thiết bị ngoại vi của AVR, hầu như chúng ta chỉ thao tác trên các thanh ghi chức năng đặc biệt - SFR (Special Function Registers) trên vùng nhớ IO, mỗi thiết bị bao gồm một tập hợp các thanh ghi điều khiển, trạng thái, ngắt…khác nhau, điều này đồng nghĩa chúng ta phải nhớ tất cả các thanh ghi của AVR. Lúc này datasheet phát huy tác dụng, bạn phải nhanh chóng download file datasheet của chip mình đang sử dụng, có rất nhiều nơi để download như tại www.atmel.com hay trên các trang web chuyên cung cấp IC datasheet miễn phí (www.alldatasheet.com là 1 ví dụ). Quay về với ngắt ngoài, có 3 thanh ghi liên quan đến ngắt ngoài đó là MCUCR, GICR và GIFR. Cụ thể các thanh ghi được trình bày bên dưới.
      Thanh ghi điều khiển MCU – MCUCR (MCU Control Register) là thanh ghi xác lập chế độ ngắt cho ngắt ngoài, quan sát hình 2 trước khi tìm hiểu thanh ghi này.
connect
Hình 2. Kết nối ngắt ngoài cho atmega8.
      Giả sử chúng ta kết nối các ngắt ngoài trên AVR mega8 như phía trái hình 2, các button dùng tạo ra các ngắt. Có 4 khả năng (tạm gọi là các MODES) có thể xảy ra khi chúng ta nhấn và thả các button. Nếu không nhấn, trạng thái các chân INT là HIGH do điện trở kéo lên, khi vừa nhấn 1 button, sẽ có chuyển trạng thái từ HIGH sang LOW, chúng ta gọi là cạnh xuống - Falling Edge, khi button được nhấn và giữ, trạng thái các chân INT được xác định là LOW và cuối cùng khi thả các button, trạng thái chuyển từ LOW sang HIGH, gọi là cạnh lên – Rising Edge.  Trong những trường hợp cụ thể, 1 trong 4 MODES trên đều hữu ích, ví dụ trong các ứng dụng đếm xung (đếm encoder của servo motor chẳng hạn) thì 2 MODE “cạnh” phải được dùng. Thanh ghi MCUCR chứa các bits cho phép chúng ta chọn 1 trong 4 MODE trên cho các ngắt ngoài. Dưới đây là cấu trúc thanh ghi MCUCR được trích ra từ datasheet của chip atmega8.
MCUCR
      MCUCR là một thanh ghi 8 bit nhưng đối với hoạt động ngắt ngoài, chúng ta chỉ quan tâm đến 4 bit thấp của nó (4 bit cao dùng cho Power manager và Sleep Mode). Bốn bit thấp là các bit Interrupt Sense Control (ISC) trong đó 2 bit ISC11:ISC10 dùng cho INT1 và 2 bit ISC01:ISC00 dùng cho INT0. Hãy nhìn vào bảng tóm tắt bên dưới để biết chức năng của các bit trên, đây là bảng “chân trị” của 2 bit ISC11, ISC10. Bảng chân trị cho các bit ISC01, ISC00 hoàn toàn tương tự.
Bảng 2: INT1 Sense Control
Sense control
      Thật dễ dàng để hiểu chức năng của các bit Sense Control, ví dụ bạn muốn set cho INT1 là ngắt cạnh xuống (Falling Edge) trong khi INT0 là ngắt cạnh lên (Rising Edge), hãy đặt dòng lệnh MCUCR =0x0B (0x0B = 00001011 nhị phân) trong chương trình của bạn.
      Thanh ghi điều khiển ngắt chung – GICR (General Interrupt Control Register) (chú ý trên các chip AVR cũ, như các chip AT90Sxxxx, thanh ghi này có tên là thanh ghi mặt nạ ngắt thông thường GIMSK, bạn tham khảo thêm datasheet của các chip này nếu cần sử dụng đến). GICR cũng là 1 thanh ghi 8 bit nhưng chỉ có 2 bit cao (bit 6 và bit 7) là được sử dụng cho điều khiển ngắt, cấu trúc thanh ghi như bên dưới (trích datasheet).
MCUCR
      Bit 7 – INT1 gọi là bit cho phép ngắt 1(Interrupt Enable), set bit này bằng 1 nghĩa bạn cho phép ngắt INT1 hoạt động, tương tự, bit INT0 điều khiển ngắt INT0.
      Thanh ghi cờ ngắt chung – GIFR (General Interrupt Flag Register) có 2 bit INTF1 và INTF0 là các bit trạng thái (hay bit cờ - Flag) của 2 ngắt INT1 và INT0. Nếu có 1 sự kiện ngắt phù hợp xảy ra trên chân INT1, bit INTF1 được tự động set bằng 1 (tương tự cho trường hợp của INTF0), chúng ta có thể sử dụng các bit này để nhận ra các ngắt, tuy nhiên điều này là không cần thiết nếu chúng ta cho phép ngắt tự động, vì vậy thanh ghi này thường không được quan tâm khi lập trình ngắt ngoài. Cấu trúc thanh ghi GIFR được trình bày trong hình ngay bên dưới.
MCUCR
      Sau khi đã xác lập các bit sẵn sàng cho các ngắt ngoài, việc sau cùng chúng ta cần làm là set bit I, tức bit cho phép ngắt toàn cục, trong thanh ghi trạng thái chung của chip (thanh ghi SREG, xem lại bài AVR2). Một chú ý khác là vì các chân PD2, PD3 là các chân ngắt nên bạn phải set các chân này là Input (set thanh ghi DDRD). Quá trình thiết lập ngắt ngoài được trình bày trong hình 10.
order
Hình 3. Thiết lập ngắt ngoài.
      Ngắt ngoài với ASM: Dưới đây tôi trình bày cách viết chương trình  sử dụng ngắt ngoài bằng ngôn ngữ ASM, đối với các ngắt khác bạn chỉ cần thêm các DIRECTIVE để định vị các vector ngắt tương ứng và viết chương trình phục vụ ngắt tương ứng.
List 1. Ngắt với ASM.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
.CSEG
.INCLUDE "M8DEF.INC"
.ORG 0x000 ; Định vị vị trí đầu tiên      RJMP BATDAU
.ORG 0x001; Định vị vector ngắt ngoài 0 - INT0 (xem bảng vector)      RJMP INT0_ISR ; Nhảy đến INT0_ISR nếu có ngắt INT0 xảy ra
.ORG 0x002 ; Định vị vector ngắt ngoài 1 – INT1 (xem bảng vector)
      RJMP INT1_ISR ; Nhảy đến INT1_ISR nếu có ngắt INT1 xảy ra
;Tương tự, định vị các vector ngắt khác ở đây………………..
;………………………………………………………………..
.ORG 0x020 ; Định vị chương trình chínhBATDAU:
; khởi tạo Stack
      LDI R16, HIGH(RAMEND)
      LDI R17, LOW(RAMEND)
      OUT SPH, R16
      OUT SPL, R17
; set chân PD2 và PD3 như các chân input      LDI R16, 0Bxxxx00xx      ; x là trạng thái do bạn tự chọn, 0 hoặc 1      OUT DDRD, R16            ; PD2 và PD3 là input      LDI R16, 0Bxxxx11xx      ; x là trạng thái do bạn tự chọn, 0 hoặc 1      OUT PORTD, R16          ; mắc điện trở kéo lên cho PD2, PD3

; khởi động  ngắt
      LDI R16, $0B       ; $0B=00001011,  INT1: ngắt cạnh xuống, INT0: ngắt cạnh lên      OUT MCUCR, R16  ; xuất giá trị điều khiển  ra thanh ghi MCUCR      LDI R16, $C0      ;$C0=11000000: Enable INT1 và INT0
      OUT GICR, R16 ;xuất giá trị điều khiển  ra thanh ghi  GICR
      SEI ;set bit cho phép ngắt toàn cục
; Chương trình chínhMAIN:
;các công việc mà chương trình chính cần thực hiện………………
;…………………………………………………………………….

      RJMP MAIN
;và đây là định nghĩa trình phục vụ ngắt INT0_ISR…………………
INT0_ISR:
; các công việc cần thực hiện khi có ngắt ……………………
;……………………………………………………………….

      RETI ; phải dùng lệnh RETI để quay về chương trình chính
;và đây là định nghĩa trình phục vụ ngắt INT1_ISR…………………INT1_ISR:
; các công việc cần thực hiện khi có ngắt ……………………
;……………………………………………………………….
      RETI ; phải dùng lệnh RETI để quay về chương trình chính
      Bạn thấy các các ngắt được định vị nằm giữa vị trí 0x0000, khi mới khởi động, tại ví trí 0x000 là lệnh “RJMP BATDAU”, như thế các lệnh RJMP tại các vector ngắt và các ISR đều không được thực hiện, chúng chỉ được thực hiện một cách tự động khi có ngắt.
       Ngắt ngoài với C: Avr-libc hỗ trợ một thư viện hàm cho ngắt khá hoàn hảo, để sử dụng ngắt trong chương trình viết bằng C (avr-gcc) bạn chỉ cần include file “interrupt.h” nằm trong thư mục con “avr” là xong. file header interrupt.h chứa định nghĩa các hàm và phương thức phục vụ cho viết trình phục vụ ngắt, các vector ngắt không được định nghĩa trong file này mà trong file iom8.h (cho atmega8). Nếu bạn vô tình tìm thấy 1 chương trình ngắt nào đó không include file interrupt.h mà include file signal.h thì bạn đừng ngạc nhiên, đó là cách viết cũ trong avr-gcc, thật ra bạn hoàn toàn có thể sử dụng cách viết cũ vì các phiên bản mới của avr-libc (đi cùng với các bản WinAVR mới) vẫn hỗ trợ cách viết này nhưng không khuyên khích bạn dùng.
       Trong C, các trình phục vụ ngắt có dạng là ISR(vector_name). Trong các phiên bản cũ trình phục vụ ngắt có tên SIGNAL(vector_name), nhưng cũng như file header signal.h, cách viết này vẫn được hỗ trợ trong phiên bản mới nhưng không được khuyến khích.
List 2. Ngắt với C.
1
2
3
4
5
6
#include <avr/interrupt.h>

ISR (vector_name)
{
//user code here
}
      Trong đó vector_name là tên của các vector ngắt định nghĩa sẵn avr-libc, ISR là tên bắt buộc, bạn không được dùng các tên khác tùy ỳ (nhưng có thể dùng SIGNAL như đã trình bày ở trên). Đặc biệt, bạn có thể đặt ISR ở trước hoặc sau chương trình chính đều không ảnh hưởng vì thật ra, đã có khá nhiều “công đoạn” được thực hiện khi bạn gọi ISR (nhưng bạn không thấy và cũng không cần quan tâm). ISR luôn được trình biên dịch đặt ở ngoài vùng vector ngắt như cách chúng ta thực hiện trong ASM, như thế một chương trình sử dụng nhiều loại ngắt sẽ phải có số lượng trình ISR tương ứng nhưng với vector_name khác nhau, mỗi khi có ngắt xảy ra, tùy thuộc vào giá trị của vector_name mà 1 trong các trình ISR được thực thi. Đối với các vector_name, để biết được vector_name cho mỗi loại ngắt, bạn cần tham khảo tài liệu “avr-libc manual”. Bảng 10 tóm tắt các vector_name của một số ngắt thông dụng trên atmega8, bạn chú ý rằng các vector_name trong avr-libc được định nghĩa rất khác nhau cho từng loại chip, bạn nhất thiết  phải sử dụng tài liệu “avr-libc manual” để biết chính xác các vector_name cho loại chip mà bạn đang dùng.
Bảng 3: vector_name cho atmega8.
Vector nameOld vector nameDescription
ADC_vectSIG_ADCADC Conversion Complete
ANA_COMP_vectSIG_COMPARATORAnalog Comparator
EE_RDY_vectSIG_EEPROM_READYEEPROM Ready
INT0_vectSIG_INTERRUPT0External Interrupt 0
INT1_vectSIG_INTERRUPT1External Interrupt Request 1
SPI_STC_vectSIG_SPISerial Transfer Complete
SPM_RDY_vectSIG_SPM_READYStore Program Memory Ready
TIMER0_OVF_vectSIG_OVERFLOW0Timer/Counter0 Overflow
TIMER1_CAPT_vectSIG_INPUT_CAPTURE1Timer/Counter Capture Event
TIMER1_COMPA_vectSIG_OUTPUT_COMPARE1ATimer/Counter1 Compare Match A
TIMER1_COMPB_vectSIG_OUTPUT_COMPARE1BTimer/Counter1 Compare MatchB
TIMER1_OVF_vectSIG_OVERFLOW1Timer/Counter1 Overflow
TIMER2_COMP_vectSIG_OUTPUT_COMPARE2Timer/Counter2 Compare Match
TIMER2_OVF_vectSIG_OVERFLOW2Timer/Counter2 Overflow
TWI_vectSIG_2WIRE_SERIAL2-wire Serial Interface
USART3_UDRE_vectSIG_USART3_DATAUSART3 Data register Empty
III. Ví dụ ngắt ngoài với C.
      Để thực hiện ví dụ sử dụng ngắt ngoài bằng C, tôi sẽ viết lại chương trình ví dụ của bài "cấu trúc AVR" nhưng bằng ngôn ngữ C và sử dụng ngắt. Trong chương trình ví dụ của bài AVR2, chúng ta thực hiện việc đếm lên và đếm xuống dùng 2 button, chúng ta sẽ vẫn thực hiện trên ý tưởng này nhưng có chút thay đổi trong kết nối, trước hết bạn vẽ 1 mạch điện mô phỏng trong Proteus như hình 4.
order
Hình 4. Mạch điện mô phỏng ngắt.
      Kết nối button đếm lên với ngắt INT0, button đếm xuống với INT1, PORTB được chọn làm PORT xuất. Hãy chạyProgrammer Notepad,  tạo 1 Project mới tên AVR2-INT, type đoạn code bên dưới vào 1 file new và lưu với tên main.c, add file này vào Project của bạn, sau đó tạo một Makefile cho Project.
List 3. ví dụ ngắt ngoài bằng C.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/delay.h>

volatile int8_t  val=0;    //khai báo 1 biến val 8 bit, có dấu và giá trị khởi tạo bằng 0.
int main(void){

    DDRD=0x00;     //khai báo PORTD là Input để sử dụng 2 chân ngắt.
    PORTD=0xFF;  //sử dụng điện trở nội kéo lên.
    DDRB=0xFF;    //PORTB là Output để xuất LED 7 đoạn
  
    MCUCR|=(1<<ISC11)|(1<<ISC01); //cả 2 ngắt là ngắt cạnh xuống    
    GICR    |=(1<<INT1)|(1<<INT0);    //cho phép 2 ngắt hoạt động
    sei();                                            //set bit I cho phép ngắt toàn cục
  
    DDRC=0xFF;                                   //PORTC là Output 
    while (1){                                         //vòng lặp vô tận   
        PORTC++;                                //quét PORTC
        _delay_loop_2(60000);
    }
    return 0;
}

//Trình phục vụ ngắt của  INT0
ISR(INT0_vect){
    val++;                                       //nếu có ngắt INT0 xảy ra, tăng val thêm 1
    if (val>9) val=0;                        //giới hạn không vượt quá 9
    PORTB=val;
}

//Trình phục vụ ngắt của  INT1
ISR(INT1_vect){
    val--;                                        //nếu có ngắt INT1 xảy ra, giảm val đi 1
    if (val<0) val=9;                       //giới hạn không nhỏ hơn 0
    PORTB=val;
}
      Có lẽ đoạn code này khá dễ hiểu nếu các bạn theo dõi từ đầu bài học, tôi chỉ giải thích những nét cơ bản và “mới”. Ý tưởng là chúng ta sử dụng 1 biến tạm 8 bit, có dấu để lưu giá trị đếm, tên biến val,  mỗi khi có ngắt trên chân INT0, tăng val 1 đơn vị và ngược lại khi có ngắt trên INT1, giảm val đi 1, đó là nội dung của 2 trình phục vụ ngắt. Trong chương trình chính, trước hết chúng ta thực hiện việc xác lập hoạt động cho 2 ngắt, sau đó đưa chương trình vào 1 vòng lặp vô tận while(1), PORTC được dùng để kiểm tra rằng chương trình trong vòng lặp vô tận vẫn đang hoạt động. Có lẽ phần khó hiểu nhất trong đoạn code là cách mà tôi dùng để khai báo cho 2 thanh ghi điều khiển ngắt MCUCR và GICR.
      Nếu xem lại bảng tóm tắt các toán tử của C, toán tử “<<” được gọi là toán tử “dịch trái” dùng trên dạng nhị phân của các con số, nếu bạn thấy x=5<<3 nghĩa là dịch các bit nhị phân của 5 sang trái 3 vị trí và gán cho x, như mô tả như sau:
order
     Bạn thấy toàn bộ các bit của 5 đã dịch sang trái 3 vị trí và giá trị của số mới thu được là x=40, chú ý 40=5x8=5x2^3 . Hãy nhìn câu lệnh MCUCR|=(1<<ISC11)|(1<<ISC01), giờ thì bạn đã hiểu (1<<ISC11) nghĩa là dịch số 1 sang trái ISC11 vị trí, và (1<<ISC01) là dịch số 1 sang trái ISC01 vị trí, nhưng ISC11 và ISC01 ở đâu ra và giá trị của chúng là bao nhiêu? Bạn chú ý, khi bạn include file “io.h” thì file “iom8.h” được chèn vào, và trong file này chứa khai báo địa chỉ các thanh ghi của chip atmega8, các tên bit cũng được khai báo sẵn trong file này, nếu bạn mở file iom8.h (thường nằm trong thư mục ~\WinAVR\avr\include\avr) bằng 1 chương trình text editor như notepad, dùng chức năng find bạn sẽ thấy các dòng định nghĩa như sau:
/* MCUCR */
#define SE         7
#define SM2      6
#define SM1      5
#define SM0      4
#define ISC11   3
#define ISC10   2
#define ISC01   1
#define ISC00   0
        Đây là định nghĩa vị trí các bit trong thanh ghi MCUCR, vậy là đã rõ, ISC11=3, ISC01=1, do đó: (1<<ISC11) tương đương (1<<3) = 00001000 (Binary) và (1<<ISC01) = 00000010,  bạn hãy tưởng tượng rằng bạn đã mang số 1 đến các vị trí của ISC11 và ISC01 trong thanh ghi MCUCR. Bây giờ đến lượt toán tử OR bitwise “|”.
(1<<ISC11)                        = 00001000
(1<<ISC01)                        = 00000010
--------------------------------------------------
(1<<ISC11)|(1<<ISC01)     = 00001010
       Gán giá trị này cho MCUCR, đối chiếu với bảng các giá trị của các bit ISC (bảng 9) bạn sẽ thấy chúng ta đang set cho 2 ngắt là falling edge. Điều cuối cùng của câu lệnh set MCUCR là cách rút gọn câu lệnh MCUCR|=(1<<ISC11)|(1<<ISC01) thực chất là MCUCR= MCUCR|  ((1<<ISC11)|(1<<ISC01)), đây là cách set một số bit trong một thanh ghi mà không muốn làm ảnh hưởng đến các bit khác (nhưng bạn phải thật cẩn thận với cách làm này vì có thể sẽ phản tác dụng nếu bạn không nắm rõ), bạn có thể gán trực tiếp MCUCR=(1<<ISC11)|(1<<ISC01), hay nhanh hơn MCUCR=0x0A (0x0A=00001010). Vậy lí do nào khiến tôi biến 1 câu lệnh gán đơn giản thành một “bài toán” khó hiểu, câu trả lời chính là tính tổng quát. Trong các chip AVR khác nhau, vị trí các bit trong các thanh ghi là rất khác nhau, câu lệnh MCUCR=0x0A đúng cho atmega8 nhưng không áp dụng được cho các chip khác trong khi câu lệnh MCUCR=(1<<ISC11)|(1<<ISC01) thì hoạt động tốt, một lí do khác là cách viết gián tiếp này giúp người khác (hay chính bạn sau này) khi đọc code có thể dễ dàng hiểu được ý đồ người viết…
      Tôi nghĩ bạn đã quá hiểu dòng lệnh tiếp theo, GICR |=(1<<INT1)|(1<<INT0). Tôi dừng giải thích đoạn code ở đây và cũng dừng bài AVR3, bạn hãy thực tập bằng cách viết lại đoạn code trên bằng ASM. 
 Nguồn: hocavr.com

[dientu686.com]Bài 2 - Cấu Trúc AVR


Bài 2 - Cấu Trúc AVR

I. Giới thiệu. 
      Bài này tiếp tục bài đầu tiên trong loạt bài giới thiệu về AVR, nếu sau bài "Làm quen AVR" bạn đã phần nào biết cách lập trình cho AVR bằng AVRStudio thì trong bài này, chúng ta sẽ tìm hiểu kỹ hơn về cấu trúc của AVR. Sau bài này, bạn sẽ:
  • Hiểu được cấu trúc AVR, cấu trúc bộ nhớ và cách thức hoạt động của chip.
  • Hiểu về Stack và cách hoạt động.
  • Biết được một số instruction cơ bản truy xuất bộ nhớ.
  • Học các instruction rẽ nhánh và vòng lặp.
  • Chương trình con (Subroutine) và Macro.
  • Cải tiến ví dụ trong bài 1.
  • Viết 1 ví dụ minh họa cách sử dụng bộ nhớ và vòng lặp.

II. Tổ chức của AVR.  
     AVR có cấu trúc Harvard, trong đó đường truyền cho bộ nhớ dữ liệu (data memory bus) và đường truyền cho bộ nhớ chương trình (program memory bus) được tách riêng. Data memory bus chỉ có 8 bit và được kết nối với hầu hết các thiết bị ngoại vi, với register file. Trong khi đó program memory bus có độ rộng 16 bits và chỉ phục vụ cho instruction registers. Hình 1 mô tả cấu trúc bộ nhớ của AVR.
     Bộ nhớ chương trình (Program memory): Là bộ nhớ Flash lập trình được, trong các chip AVR cũ (như AT90S1200 hay AT90S2313…) bộ nhớ chương trình chỉ gồm 1 phần là Application Flash Section nhưng trong các chip AVR mới chúng ta có thêm phần Boot Flash setion. Boot section sẽ được khảo sát trong các phần sau, trong bài này khi nói về bộ nhớ chương trình, chúng ta tự hiểu là Application section. Thực chất, application section bao gồm 2 phần: phần chứa các instruction (mã lệnh cho hoạt động của chip) và phần chứa các vector ngắt (interrupt vectors). Các vector ngắt nằm ở phần đầu của application section (từ địa chỉ 0x0000) và dài đến bao nhiêu tùy thuộc vào loại chip. Phần chứa instruction nằm liền sau đó, chương trình viết cho chip phải được load vào phần này. Xem lại phần đầu của ví dụ trongbài 1:
.ORG 0x000
RJMP BATDAU
.ORG 0x020
       Trong ví dụ này, ngay sau khi set vị trí 0x000 bằng chỉ thị (DIRECTIVE) .ORG 0x000 chúng ta dùng instruction RJMP để nhảy đến vị trí 0x020, như thế phần bộ nhớ chương trình từ 0x00 đến 0x01F không được sử dụng (vì trong  ví dụ này chúng ta không sử dụng các vector ngắt). Chương trình chính được bắt đầu từ địa chỉ 0x020, con số 0x020 là do người lập trình chọn, thật ra các vector ngắt của chip ATMEGA8 chỉ kéo dài đến địa chỉ 0x012, vì vậy chương trình chính có thể được bắt đầu từ bất cứ vị trí nào sau đó. Để biết độ dài các vector ngắt của từng chip bạn hãy tham khảo datasheet của chip đó.
      Vì chức năng chính của bộ nhớ chương trình là chứa instruction, chúng ta không có nhiều cơ hội tác động lên bộ nhớ này khi lập trình cho chip, vì thế đối với người lập trình AVR, bộ nhớ này “không quá quan trọng”. Tất cả các thanh ghi quan trọng cần khảo sát nằm trong bộ nhớ dữ liệu của chip.
Hình 1. Tổ chức bộ nhớ của AVR.
      Bộ nhớ dữ liệu (data memory): Đây là phần chứa các thanh ghi quan trọng nhất của chip, việc lập trình cho chip phần lớn là truy cập bộ nhớ này. Bộ nhớ dữ liệu trên các chip AVR có độ lớn khác nhau tùy theo mỗi chip, tuy nhiên về cơ bản phần bộ nhớ này được chia thành 5 phần:
      Phần 1: là phần đầu tiên trong bộ nhớ dữ liệu, như mô tả trong hình 1, phần này bao gồm 32 thanh ghi có tên gọi là register file (RF), hay General Purpose Rgegister – GPR, hoặc đơn giản là các Thanh ghi. Tất cả các thanh ghi này đều là các thanh ghi 8 bits như trong hình 2.
thanh ghi 8 bit
Hình 2. Thanh ghi 8 bits.
    Tất cả các chip trong họ AVR đều bao gồm 32 thanh ghi Register File có địa chỉ tuyệt đối từ 0x0000 đến 0x001F. Mỗi thanh ghi có thể chứa giá trị dương từ 0 đến 255 hoặc các giá trị có dấu từ -128 đến 127 hoặc mã ASCII của một ký tự nào đó…Các thanh ghi này được đặt tên theo thứ tự là R0 đến R31. Chúng được chia thành 2 phần, phần 1 bao gồm các thanh ghi từ R0 đến R15 và phần 2 là các thanh ghi R16 đến R31. Các thanh ghi này có các đặc điểm sau:
  • Được truy cập trực tiếp trong các instruction.
  • Các toán tử, phép toán thực hiện trên các thanh ghi này chỉ cần 1 chu kỳ xung clock.
  • Register File được kết nối trực tiếp với bộ xử lí trung tâm – CPU của chip.
  • Chúng là nguồn chứa các số hạng trong các phép toán và cũng là đích chứa kết quả trả lại của phép toán.
Để minh họa, hãy xét ví dụ thực hiện phép cộng 2 thanh ghi bằng instruction ADD như sau:
ADD R1, R2
       Bạn thấy trong dòng lệnh trên, 2 thanh ghi R1 và R2 được sử dụng trực tiếp với tên của chúng, dòng lệnh trên khi được dịch sang opcode để download vào chip sẽ có dạng: 0000110000010010 trong đó 00001=1 tức thanh ghi R1 và 00010 = 2 chỉ thanh ghi R2. Sau phép cộng, kết quả sẽ được lưu vào thanh ghi R1.
      Tất cả các instruction sử dụng RF làm toán hạng đều có thể truy nhập tất cả các RF một cách trực tiếp trong 1 chu kỳ xung clock, ngoại trừ SBCI, SUBI, CPI, ANDI và LDI, các instruction này chỉ có thể truy nhập các thanh ghi từ R16 đến R31.
      Thanh ghi R0 là thanh ghi duy nhất được sử dụng trong instruction LPM (Load Program Memory). Các thanh ghi R26, R27, R28, R29, R30 và R31 ngoài chức năng thông thường còn được sử dụng như các con trỏ (Pointer register) trong một số instruction truy xuất gián tiếp. Chúng ta sẽ khảo sát vấn đề con trỏ sau này. Hình 3 mô tả các chức năng phụ của các thanh ghi.
RF
Hình 3. Register file.
      Tóm lại 32 RF của AVR được xem là 1 phần của CPU, vì thế chúng được CPU sử dụng trực tiếp và nhanh chóng, để gọi các thanh ghi này, chúng ta không cần gọi địa chỉ mà chỉ cần gọi trực tiếp tên của chúng. RF thường được sử dụng như các toán hạng (operand) của các phép toán trong lúc lập trình.
      Phần 2: là phần nằm ngay sau register file, phần này bao gồm 64 thanh ghi được gọi là 64 thanh ghi nhập/xuất (64 I/O register) hay còn gọi là vùng nhớ I/O (I/O Memory). Vùng nhớ I/O là cửa ngõ giao tiếp giữa CPU và thiết bị ngoại vi. Tất cả các thanh ghi điều khiển, trạng thái…của thiết bị ngoại vi đều nằm ở đây. Xem lại ví dụ trong bài 1, trong đó tôi có đề cập về việc điều khiển các PORT của AVR, mỗi PORT liên quan đến 3 thanh ghi DDRx, PORTx và PINx, tất cả 3 thanh ghi này đều nằm trong vùng nhớ I/O. Xa hơn, nếu muốn truy xuất các thiết bị ngoại vi khác như Timer, chuyển đổi Analog/Digital, giao tiếp USART…đều thực hiện thông qua việc điều khiển các thanh ghi trong vùng nhớ này.
     Vùng nhớ I/O có thể được truy cập như SRAM hay như các thanh ghi I/O. Nếu sử dụng instruction truy xuất SRAM để truy xuất vùng nhớ này thì địa chỉ của chúng được tính từ 0x0020 đến 0x005F. Nhưng nếu truy xuất như các thanh ghi I/O thì địa chỉ của chúng đựơc tính từ 0x0000 đến 0x003F.
     Xét ví dụ instruction OUT dùng xuất giá trị ra các thanh ghi I/O, lệnh này sử dụng địa chỉ kiểu thanh ghi, cấu trúc của lệnh như sau: OUT A, Rr, trong đó A là địa chỉ của thanh ghi trong vùng nhớ I/O, Rr là thanh ghi RF, lệnh OUT xuất giá trị từ thanh ghi Rr ra thanh ghi I/O có địa chỉ là A. Giả sử chúng ta muốn xuất giá trị chứa trong R6 ra thanh ghi điều khiển hướng của PORTD, tức thanh ghi DDRD, địa chỉ tính theo vùng I/O của thanh ghi DDRD là 0x0011, như thế câu lệnh của chúng ta sẽ có dạng: OUT 0x0011, R6. Tuy nhiên trong 1 trường hợp khác, nếu muốn truy xuất DDRD theo dạng SRAM, ví dụ lệnh STS hay LDS, thì phải dùng địa chỉ tuyệt đối của thanh ghi này, tức giá trị 0x0031, khi đó lệnh OUT ở trên được viết lại là STS 0x0031, R6.
     Để thống nhất cách sử dụng từ ngữ, từ bây giờ chúng ta dùng khái niệm “địa chỉ I/O” cho các thanh ghi trong vùng nhớ I/O để nói đến địa chỉ không tính phần Register File, khái niệm “địa chỉ bộ nhớ” của thanh ghi là chỉ địa chỉ tuyệt đối của chúng trong SRAM. Ví dụ thanh ghi DDRD có “địa chỉ I/O” là 0x0011 và “địa chỉ bộ nhớ” của nó là 0x0031, “địa chỉ bộ nhớ” = “địa chỉ I/O” + 0x0020.
     Vì các thanh ghi trong vùng I/O không được hiểu theo tên gọi như các Register file, khi lập trình cho các thanh ghi này, người lập trình cần nhớ địa chỉ của từng thanh ghi, đây là việc tương đối khó khăn. Tuy nhiên, trong hầu hết các phần mềm lập trình cho AVR, địa chỉ của tất cả các thanh ghi trong vùng I/O đều được định nghĩa trước trong 1 file Definition, bạn chỉ cần đính kèm file này vào chương trình của bạn là có thể truy xuất các thanh ghi với tên gọi của chúng. Giả sử trong ví dụ ở bài 1, để lập trình cho chip Atmega8 bằng AVRStudio, dòng thứ 2 chúng ta sử dụng INCLUDE "M8DEF.INC" để load file định nghĩa cho chip ATMega8, file M8DEF.INC. Vì vậy, trong sau này khi muốn sử dụng thanh ghi DDRD bạn chỉ cần gọi tên của chúng, như: OUT DDRD,R6.
     Phần 3: RAM tĩnh, nội (internal SRAM), là vùng không gian cho chứa các biến (tạm thời hoặc toàn cục) trong lúc thực thi chương trình, vùng này tương tự các thanh RAM trong máy tính nhưng có dung lượng khá nhỏ (khoảng vài KB, tùy thuộc vào loại chip).
     Phần 4: RAM ngoại (external SRAM), các chip AVR cho phép người sử dụng gắn thêm các bộ nhớ ngoài để chứa biến, vùng này thực chất chỉ tồn tại khi nào người sử dụng gắn thêm bộ nhớ ngoài vào chip.
     Phần 5: EEPROM (Electrically Ereasable Programmable ROM) là một phần quan trọng của các chip AVR mới, vì là ROM nên bộ nhớ này không bị xóa ngay cả khi không cung cấp nguồn nuôi cho chip, rất thích hợp cho các ứng dụng lưu trữ dữ liệu. Như trong hình 1, phần bộ nhớ EEPROM được tách riêng và có địa chỉ tính từ 0x0000.
     Câu hỏi bây giờ là AVR hoạt động như thế nào?
     Hình 4 biểu diễn cấu trong bên trong của 1 AVR. Bạn thấy rằng 32 thanh ghi trong Register File được kết nối trực tiếp với Arithmetic Logic Unit -ALU (ALU cũng được xem là CPU của AVR) bằng 2 line, vì thế ALU có thể truy xuất trực tiếp cùng lúc 2 thanh ghi RF chỉ trong 1 chu kỳ xung clock (vùng được khoanh tròn màu đỏ trong hình 4).
RF
Hình 4. Cấu trúc bên trong AVR.
      Các instruction được chứa trong bộ nhớ chương trình Flash memory dưới dạng các thanh ghi 16 bit. Bộ nhớ chương trình được truy cập trong mỗi chu kỳ xung clock và  1 instruction chứa trong program memory sẽ được load vào trong instruction register, instruction register tác động và lựa chọn register file cũng như RAM cho ALU thực thi. Trong lúc thực thi chương trình, địa chỉ của dòng lệnh đang thực thi được quyết định bởi một bộ đếm chương trình – PC (Program counter). Đó chính là cách thức hoạt động của AVR.
      AVR có ưu điểm là hầu hết các instruction đều được thực thi trong 1 chu kỳ xung clock, vì vậy có thể nguồn clock lớn nhất cho AVR có thể nhỏ hơn 1 số vi điều khiển khác như PIC nhưng thời gian thực thi vẫn nhanh hơn.
III. Stack.
      Stack được hiểu như là 1 “tháp” dữ liệu, dữ liệu được chứa vào stack ở đỉnh “tháp” và dữ liệu cũng được lấy ra từ đỉnh. Kiểu truy cập dữ liệu của stack gọi là LIFO (Last In First Out – vào sau ra trước). Hình 5 thể hiện cách truy cập dữ liệu của stack.
RF
Hình 5. Stack.
        Khái niệm và cách thức hoạt động của stack có thể được áp dụng cho AVR, bằng cách khai báo một vùng nhớ trong SRAM là stack ta có thể sử dụng vùng nhớ này như một stack thực thụ.
       Để khai báo một vùng SRAM làm stack chúng ta cần xác lập địa chỉ đầu của stack bằng cách xác lập con trỏ stack-SP (Stack Pointer). SP là 1 con trỏ 16 bit bao gồm 2 thanh ghi 8 bit SPL và SPH (chữ L là LOW chỉ thanh ghi mang giá trị byte thấp của SP, và H = HIGH), SPL và SPH nằm trong vùng nhớ I/O. Giá trị gán cho thanh ghi SP sẽ là địa chỉ khởi động của stack. Quay lại ví dụ ở bài 1, phần khởi tạo các điều kiện đầu.
; KHOI TAO CÁC DIEU KIEN DAU
LDI R16, HIGH(RAMEND)
LDI R17, LOW(RAMEND)
OUT SPH, R16
OUT SPL, R17
       Bốn dòng khai báo trên mục đích là gán giá trị của RAMEND cho con trỏ SP, RAMEND (tức End of Ram) là biến chứa địa chỉ lớn nhất của RAM nội trong AVR, biến này được định nghĩa trong file M8DEF.INC. Như thế sau 4 dòng trên, con trỏ SP chứa giá trị cuối cùng của SRAM hay nói cách khác vùng stack bắt đầu từ vị trí cuối cùng của bộ nhớ SRAM. Nhưng tại sao là vị trí cuối cùng mà không là 1 giá trị khác. Có thể giải thích như sau: stack trong AVR hoạt động từ trên xuống, sau khi dữ liệu được đẩy vào stack, SP sẽ giảm giá trị vì thế khởi động SP ở vị trí cuối cùng của SRAM sẽ tránh được việc mất dữ liệu do ghi đè. Bạn có thể khởi động stack với 1 địa chỉ khác, tuy nhiên vì lý do an toàn, nên khởi động stack ở RAMEND.
      Hai instruction dùng cho truy cập stack là PUSH và POP, trong đó PUSH dùng đẩy dữ liệu vào stack và POP dùng lấy dữ liệu ra khỏi stack. Dữ liệu được đẩy vào và lấy ra khỏi stack tại vị trí mà con trỏ SP trỏ đến. Ví dụ cho chip ATMega8, RAMEND=0x045F, sau khi khởi động, con trỏ SP trỏ đến vị trí 0x045F trong SRAM, nếu ta viết các câu lệnh sau:

LDI R16, 1
PUSH R16
LDI R16, 5PUSH R16LDI R16, 8PUSH R16
       Khi đó nội dung của stack sẽ như trong hình 6.
RF
Hình 6. Nội dung stack trong ví dụ.
       Sau mỗi lần PUSH dữ liệu, SP sẽ giảm 1 đơn vị và trỏ vào vị trí tiếp theo.
Bây giờ nếu ta dùng POP để lấy dữ liệu từ stack, POP R2, thì R2 sẽ mang giá trị của ngăn nhớ 0x045D, tức R2=8. Trước khi instruction POP được thực hiện, con trỏ SP được tăng lên 1 đơn vị, sau đó dữ liệu sẽ được lấy ra từ vị trí mà SP trỏ đến trong stack.
       Stack trong AVR không phải là “vô đáy”, nghĩa là chúng ta chỉ có thể PUSH dữ liệu vào stack ở 1 độ sâu nhất định nào đấy (phụ thuộc vào chip). Sử dụng stack không đúng cách đôi khi sẽ làm chương trình thực thi sai hoặc tốn thời gian thực thi vô ích. Vì thế không nên sử dụng stack chỉ để lưu các biến thông thường. Ứng dụng phổ biến nhất của stack là sử dụng trong các chương trình con (Subroutine), khi chúng ta cần “nhảy” từ một vị trí trong chương trình chính đến 1 chương trình con, sau khi thực hiện chương trình con lại muốn quay về vị trí ban đầu trong chương trình chính thì Stack là phương cách tối ưu dùng để chứa bộ đếm chương trình trong trường hợp này. Xem lại ví dụ trong bài 1, trong chương trình chính chúng ta dùng lệnh RCALL DELAY để nhảy đến đoạn chương trình con DELAY, RCALL là lệnh nhảy đến 1 vị trí trong bộ nhớ chương trình, trước khi nhảy, PC được cộng thêm 1 và PUSH một cách tự động vào stack. Cuối chương trình con DELAY, chúng ta dùng instruction RET, instruction này POP dữ liệu từ stack ra PC một cách tự động, bằng cách này chúng ta có thể quay lại vị trí trước đó. Chính vì các lệnh RCALL và RET sử dụng stack một cách tự động nên ta phải khởi động stack ngay từ đầu, nếu không chương trình sẽ thực thi sai chức năng.
      Tóm lại cần khởi động stack ở đầu chương trình và không nên sử dụng stack một cách tùy thích nếu chưa thật cần thiết.
IV. Thanh ghi trạng thái - SREG (STATUS REGISTRY).
       Nằm trong vùng nhớ I/O, thanh ghi SREG có địa chỉ I/O là 0x003F và địa chỉ bộ nhớ là 0x005F (thường đây là vị trí cuối cùng của vùng nhớ I/O) là một trong số các thanh ghi quan trọng nhất của AVR, vì thế mà tôi dành phần này để giới thiệu về thanh ghi này. Thanh ghi SREG chứa 8 bit cờ (flag) chỉ trạng thái của bộ xử lí, tất cả các bit này đều bị xóa sau khi reset, các bit này cũng có thể được đọc và ghi bởi chương trình. Chức năng của từng bit được mô tả như sau:
sreg
Hình 7. Thanh ghi trạng thái.
  • Bit 0 – C (Carry Flag: Cờ nhớ): là bit nhớ trong các phép đại số hoặc logic, ví dụ thanh ghi R1 chứa giá trị 200, R2 chứa 70, chúng ta thực hiện phép cộng có nhớ: ADC R1, R2, sau phép cộng, kết quả sẽ được lưu lại trong thanh ghi R1, trong khi kết quả thực là 270 mà thanh ghi R1 lại chỉ có khả năng chứa tối đa giá trị 255 (vì có 8 bit) nên trong trường hợp này, giá trị lưu lại trong R1 thực chất chỉ là 14, đồng thời cờ C được set lên 1 (vì 270=100001110, trong đó 8 bit sau 00001110 =14 sẽ được lưu lại trong R1).
  • Bit 1 – Z (Zero Flag: Cờ 0): cờ này được set nếu kết quả phép toán đại số hay phép Logic bằng 0.
  • Bit 2 – N (Negative Flag: Cờ âm): cờ này được set nếu kết quả phép toán đại số hay phép Logic là số âm.
  • Bit 3 – V (Two’s complement Overflow Flag: Cờ tràn của bù 2): hoạt động của cờ này có vẻ sẽ khó hiểu cho bạn vì nó liên quan đến kiến thức số nhị phân (phần bù), chúng ta sẽ đề cập đến khi nào thấy cần thiết.
  • Bit 4  – S (Sign Bit: Bit dấu): Bit S là kết quả phép XOR giữa 1 cờ N và V, S=N xor V.
  • Bit 5 – H (Half Carry Flag: Cờ nhờ nữa): cờ H là cờ nhớ trong 1 vài phép toán đại số và phép Logic, cờ này hiệu quả đối với các phép toán với số BCD.
  • Bit 6 – T (Bit Copy Storage): được sử dụng trong 2 Instruction BLD (Bit LoaD) và BST (Bit STorage). Tôi sẽ giải thích chức năng Bit T trong phần giới thiệu về BLD và BST.
  • Bit 7 – I (Global Interrupt Enable) : Cho phép ngắt toàn bộ): Bit này phải được set lên 1 nếu trong chương trình có sử dụng ngắt. Sau khi set bit này, bạn muốn kích hoạt loại ngắt nào cần set các bit ngắt riêng của ngắt đó. Hai instruction dùng riêng để Set và Clear bit I là SEI và CLI.
      Chú ý: tất cả các bit trong thanh ghi SREG đều có thể được xóa thông qua các instruction không toán hạng CLx và set bởi SEx, trong đó x là tên của Bit.Ví dụ CLT là xóa Bit T và SEI là set bit I.
Tôi chỉ giải thích ngắn gọn chức năng của các bit trong thanh ghi SREG, cụ thể chức năng và cách sử dụng của từng bit chúng ta sẽ tìm hiểu trong các trường hợp cụ thể sau này, người đọc có thể tự tìm hiểu thêm trong các tài liệu về INSTRUCTION cho AVR.
      Tôi cung cấp thêm 1 bảng tóm tắt sự ảnh hưởng của các phép toán đại số, logic lên các Bit trong thanh ghi SREG.
sreg
Hình 8. Ảnh hưởng của các phép toán lên SREG.
IV. Macro và chương trình con.
       Macro là khái niệm chỉ một đoạn code nhỏ để thực hiện một công việc nào đó, nếu có 1 đoạn code nào đó mà bạn rất hay sử dụng khi lập trình thì bạn nên dùng macro để tránh việc phải viết đi viết lại đoạn code đó. Lập trình ASM cho AVR cho phép bạn sử dụng Macro, để tạo 1 Macro bạn sử dụng DIRECTIVE.
.MACRO delay4
NOP
NOP
NOP
NOP
.ENDMACRO
       Đoạn Macro trên có tên delay4 thực hiện việc delay 4 chu kỳ máy bằng 4 lệnh NOP, nếu trong chương trình bạn cần dùng Macro này thì chỉ cần gọi  delay4 ở bất kỳ dòng nào.
[…] ; code của bạn
Delay4
[…] ; code của bạn
       Mỗi lần tên của Macro được gọi, trình biên dịch sẽ tìm đến Macro đó và copy toàn bộ nội dung Macro vào vị trí bạn gọi. Như vậy thực chất con trỏ chương trình không nhảy đến Macro, Macro không làm giảm dung lương chưong trình mà chỉ làm cho việc lập trình nhẹ nhàng hơn.  Đây chính là khác biệt lớn nhất của Macro và Subroutine (chương trình con).
       Chương trình con cũng là 1 đoạn code thực hiện 1 chức năng đặc biệt nào đó. Tuy nhiên khác với Macro, mỗi khi gọi chương trình con, con trỏ chương trình nhảy đến chương trình con đề thực thi chương trình con và sau đó quay về chương trình chính. Như thế chương trình con chỉ được biên dịch 1 lần và có thể sử dụng nhiều lần, nó làm giảm dung lượng chưong trình. Đây là  ưu điểm và cũng là điểm khác biệt lớn nhất giữa chương trình con và Macro. Tuy nhiên cần chú ý là việc nhảy đến chương trình con và nhảy về chương trình chính cần vài chu kỳ máy, có thể làm chậm chương trình, đây là nhược điểm của chương trình con so với macro.
      Chương trình con cho AVR luôn được bắt đầu bằng 1 Label, đó cũng là tên và địa chỉ của chương trình con. Chương trình con thường được kết thúc với câu lệnh RET (Return). Chúng ta đã biết về chương trình con qua ví dụ của bài 1, trong đó DELAY là 1 chương trình con.
       Để gọi chương trình con từ 1 vị trí nào đó trong chương trình, chúng ta có thể dùng lệnh CALL hoặc RCALL(Relative CALL) (xem lại ví dụ bài 1 về cách sử dụng RCALL). Mỗi khi các lệnh này được gọi, bộ đếm chương trình được tự động được PUSH vào stack và khi chương trình con kết thúc  bằng lệnh RET, bộ đếm chương trình được POP trở ra và quay về chương trình chính. Lệnh CALL có thể gọi 1 chương trình con ở bất kỳ vị trí nào trong khi RCALL chỉ gọi trong khoảng bộ nhớ 4KB, nhưng RCALL cần ít chu kỳ xung clock hơn khi thực thi.
       Hai instruction khác có thể được dùng để gọi chương trình con đó là JMP (Jump) và RJMP (Relative Jump). Khác với các lệnh call, các lệnh jump không cho phép quay lại vì không tự động PUSH bộ đếm chương trình vào Stack, để sử dụng các lệnh này gọi chương trình con bạn cần một số lệnh jump khác ở cuối chương trình con.
       Tóm lại bạn nên viết 1 chương trình con đúng chuẩn và dùng CALL hoặc RCALL để gọi chương các chương trình này, chỉ những trường hợp đặc biệt hoặc bạn hiểu rất rõ về chúng thì có thể dùng các lệnh jump.
V. Ví dụ minh họa.
       Nếu bạn đã đọc và hiểu đến thời điểm này thì bạn đã có thể hiểu hết hoạt động của chương trình ví dụ trong bài 1, thật sự ví dụ đó rất đơn giản và dễ hiểu. Tuy nhiên, bạn có thề tối ưu hóa ví dụ đó theo hướng làm giảm dung lượng chương trình và tất nhiên, chương trình sẽ khó hiểu hơn cho người khác. Các phần khởi động vị trí bộ nhớ, stack và chương trình con DELAY chúng ta không thay đổi, chỉ thay đổi phần chương trình chính, 1 trong những cách viết chương trình chính như cách sau:
; CHUONG TRINH CHINH , BAI 1, VI DU 1, VERSION 2///////////////////////////////
LDI R16, $1 ;LOAD GIA TRI KHOI DONG CHO  R16
MAIN:
OUT PORTB, R16 ; XUAT GIA TRI TRONG R16 RA PORTB
RCALL DELAY ; GOI CHUONG TRINH CON DELAY
ROL R16 ; XOAY THANH GHI R16 SANG TRAI 1 VI TRI
RJMP MAIN ; NEU R16 ≠0, NHAY VE MAIN, TIEP TUC QUET
;/////////////////////////////////////////////////////////////////////////////////////////
       Có thể không cần giải thích bạn cũng đã có thể hiểu đoạn code trên, đây chỉ là 1 trong những cách có thể, bạn hãy viết lại theo cách của riêng bạn với yêu cầu là chương trình phải thực hiện đúng chức năng và ngắn gọn.
      Bây giờ chúng ta sẽ thực hiện một ví dụ minh họa cho những gì chúng ta đã học trong bài 2 này. Nội dung của ví dụ thể hiện trong mạch điện hình 9. Hoạt động của mạch điện tử như sau: 1 chip ATMega8 được sử dụng như một counter, có thể dùng để đếm lên và đếm xuống, 2 button trong mạch điện tác động như 2 “kicker”, nhấn button 1 để đếm lên và button để đếm xuống, giá trị đếm nằm trong khoảng từ 0 đến 9. Giá trị đếm được hiển thị trên 1 LED 7 đoạn loại anod chung (dương chung), chip 7447 được dùng để giải mã từ giá trị BCD xuất ra bởi ATMega8 sang tín hiệu cho LED 7 đoạn anod chung, chúng ta cần sử dụng 7447 vì tín hiệu xuất ra từ chip ATMega8 là dạng nhị phân hoặc BCD , tín hiệu này không thể hiển thị trực tiếp trên các LED 7 đoạn, chip 7447 có nhiệm vụ chuyển 1 dữ liệu dạng digit BCD sang mã phù hợp cho LED 7 đoạn.
       Để thực hiện ví dụ, trước hết bạn hãy vẽ mạch điện như trong hình 9 bằng phần mềm Proteus (xem cách vẽ mạch điện bằng Proteus), mạch điện chỉ có 5 loại linh kiện là chip ATMega8 (từ khóa mega8), 1 LED 7 đoạn anod chung với tên đầy đủ trong Proteus là 7SEG-COM-AN-GRN (từ khóa 7SEG), 1 chip 7447 (từ khóa 7447), 1 điện trở 10 Ω và 2 button (từ khóa button).
sreg
Hình 9. Ví dụ cho bài 2.
      Sử dụng AVRStudio tạo 1 project mới với tên gọi avr2 (xem lại cách tạo Project mới trong AVRStudio). Viết lại phần code bên dưới vào vào file avr2.asm
List 1. Ví dụ cấu trúc AVR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
.INCLUDE "M8DEF.INC"
.CSEG.
.ORG 0x0000
      RJMP BATDAU
.ORG 0x0020
BATDAU:
;KHOI DONG STACK POINTER
      LDI R17, HIGH(RAMEND)      LDI R16, LOW(RAMEND)      OUT SPL, R16      OUT SPH,R17; KHOI DONG CAC PORT      CLR R16 ; XOA R16, R16=0      OUT DDRB, R16 ; DDRB=0, PORTB LA NGO NHAP       LDI R16, 0xFF ; SET TAT CA CAC BIT CUA R16 LEN 1      OUT PORTB,R16 ;DDRB=0, PORTB =0xFF, KEO LEN CAC CHAN PORTB      OUT DDRD, R16 ;DDRD=0xFF, PORTD LA NGO XUAT      CLR R25 ;XOA R25, R25 LA THANH GHI DUNG CHUA SO DEM      SER R20 ; R20 LA THANH GHI TAM CHUA GIA TRI TRUOC DO CUA PINB
MAIN:
      IN R21,PINB ;DOC GIA TRI TU PINB, TUC TU CAC BUTTON      RCALL SOSANH  ;GOI CHUONG TRINH CON SOSANH      OUT PORTD, R25 ;XUAT GIA TRI DEM RA PORTD      SBRS R21,0 ;NEU BIT 0 CUA R21 (TUC CHAN PB0) =1 THI BO QUA DONG ;TIEP THEO      RCALL TANG ;NHAY DEN CHUONG TRINH CON TANG GIA TRI DEM      SBRS R21,1 ;NEU BIT 1 CUA R21 (TUC CHAN PB1) =1 THI BO QUA DONG ;TIEP THEO      RCALL GIAM ;NHAY DEN CHUONG TRINH CON GIAM GIA TRI DEM      MOV R20,R21 ;LUU LAI TRANG THAI PINB      RJMP MAIN
;**********************CHUONG TRINH CON************************
; **************subroutine kiem tra gioi hang (tu 0 den 9) cua so dem

SOSANH:
      CPI R25, 10      BREQ RESET0 ;NEU GIA TRI DEM=10 THI TRA VE 0      CPI R25, 255      BREQ RESET9 ;NEU GIA TRI DEM =255 THI TRA VE 9      RJMP QUAYVE ;NHAY DEN NHAN QUAYVE
RESET0:
      LDI R25,$0 ;TRA GIA TRI DEM VE 0      RJMP QUAYVE
RESET9:
      LDI R25,$9 ;GAN 9 CHO GIA TRI DEM
QUAYVE:
      RET
; ************************************************************
; **************subroutine tang so dem 1 don vi neu dieu kien thoa
TANG:
      SBRS R20,0      RET      INC R25      RET
; **************subroutine giam so dem 1 don vi neu dieu kien thoa
GIAM:
      SBRS R20,1      RET
      DEC R25      RET
      Trong ví này này, chúng ta sử dụng 2 PORT của chip ATMega8, PORTD dùng xuất dữ liệu (số đếm) ra chip 7447 và sau đó hiển thị trên LED 7 đoạn. PORTB dùng như ngõ nhập, tín hiệu từ các button sẽ được chip ATMega8 nhận thông qua 2 chân PB0 và PB1 của PORTB.
      Hoạt động của cac PORT và việc xác lập 1 PORT như các ngõ xuất chúng ta đã khảo sát trong bài 1. Ở đây chúng ta khảo sát thêm về xác lập PORT như 1 ngõ nhập, trước hết bạn hãy quan sát mạch điện tương đương của 1 chân trong các PORT xuất nhập của AVR trong hình 10.
PIN
Hình 10. Cấu trúc chân trong PORT của AVR.
      Trong mạch điện hình 10, các diode và tụ điện chỉ có chức năng bảo vệ chân PORT, nhưng điện trở Rpu (R Pull up) đóng vai trò quan trọng như là điện trở kéo lên khi chân của PORT làm nhiệm vụ nhận tín hiệu (ngõ nhập). Tuy nhiên trong AVR, điện trở kéo lên này không phải luôn kích hoạt, chúng ta biết rằng mỗi PORT của AVR có 3 thanh ghi: DDRx, PORTx và PINx, nếu DDRx=0 thì PORT x là ngõ nhập, lúc này thanh ghi PINx là thanh ghi chứa dữ liệu nhận về, đặc biệt thanh ghi PORTx vẫn được sử dụng trong mode này, đó là thanh ghi xác lập điện trở kéo lên, như thế nếu DDRx=0 và PORTx=0xFF thì các chân PORTx là ngõ nhập và được kéo lên bởi 1 điện trở trong chip, nghĩa là các chân của PORTx luôn ở mức cao, muốn kích để thay đồi trạng thái chân này chúng ta cần nối chân đó trực tiếp với GND, đấy là lý do tại sao các button trong mạch điện của chúng ta có 1 đầu nối với chân của chip còn đầu kia được nối với GND. Đây cũng là ý nghĩa của khái niệm điện trở kéo lên (Pull up resistor) trong kỹ thuật điện tử. Đoạn code trong phần “KHOI DONG CAC PORT” của ví dụ này xác lập PORTD là ngõ xuất (DDRD=0xFF) , PORTB là ngõ nhập có sử dụng điện trở kéo lên (DDRB=0, PORTB=0xFF).
      Chúng ta sẽ giải thích hoạt động của đoạn chương trình chính và các đoạn chương trình con. Trước hết, trong chương trình này, chúng ta sử dụng 3 thanh ghi chính là R20, R21 và R25, trong đó R25 là thanh ghi chứa số đếm, giá trị của thanh ghi R25 sẽ được xuất ra PORTD của chip, thanh ghi R21 chứa trạng thái của thanh ghi PINB và cũng là trạng thái của các button, thanh ghi R20 kết hợp với thanh ghi R21 tạo thành 1 “bộ đếm cạnh xuống” của các button. Để hiểu thấu đáo hoạt động đếm (cũng là hoạt động chính của ví dụ này) chúng ta xét trạng thái chân PB0 như trong hình 11.
PIN
Hình 11. Thay đổi trạng thái ở các chân I/O.
      Trong trạng thái bình thường (button không được nhấn), chân PB0 ở mức cao (do điện trở kéo lên), bộ đếm không hoạt động, giá trị đếm không thay đổi, bây giờ nếu nhấn button, chân PB0 được nối trực tiếp với GND, chân này sẽ bị kéo xuống mức thấp, bằng cách kiểm tra trạng thái chân PB0, nếu PB0=0 ta tăng giá trị đếm 1 đơn vị. Ý tưởng như thế có vẻ hợp lý, tuy nhiên nếu áp dụng thì chương trình sẽ hoạt động không đúng chức năng, khi bạn nhấn 1 lần giá, trị đếm có thể tăng đến cả trăm hoặc không kiểm soát được, hiệu ứng này tương tự khi bạn nhấn và giữ 1 phím trên bàn phím máy tính, lý do là vì chúng ta sử dụng phương pháp kiểm tra mức để đếm, thời gian quét của chương trình  rất ngắn so với thời gian chúng ta giữ button. Để khắc phục, chúng ta dùng phương pháp kiểm tra cạnh xuống, chỉ khi nào phát hiện chân PB0 thay đổi từ 1 xuống 0 thì mới tăng giá trị đếm 1 đơn vị, kết quả là mỗi lần nhấn button thì giá trị đếm chỉ tăng 1 (ngay cả khi ta nhấn và giữ button), thanh ghi R20 được sử dụng để lưu trạng thái trước đó của PINB (cũng là trạng thái của các button).
      Trong chương trình, tôi sử dụng 2 istruction mới là SBRC và SBRS để kiểm tra trạng thái các chân của PORTB (button). SBRC – Skip if Bit in Register is Clear, lệnh này sẽ bỏ qua 1 dòng lệnh ngay sau đó (chỉ bỏ qua 1 dòng duy nhất) nếu 1 bit trong thanh ghi ở mức 0, SBRC – Skip if Bit in Register is Set- hoạt động tương tự SBRC nhưng skip sẽ xảy ra nếu bit trong thanh ghi ở mức 1. Dựa vào đây chúng ta giải thích 4 dòng sau:
SBRS R21,0 ;NEU BIT 0 CUA R21 (TUC CHAN PB0) =1 THI BO QUA DONG ;TIEP THEO
RCALL TANG ;NHAY DEN CHUONG TRINH CON TANG GIA TRI DEM
SBRS R21,1 ;NEU BIT 1 CUA R21 (TUC CHAN PB1) =1 THI BO QUA DONG ;TIEP THEO
RCALL GIAM ;NHAY DEN CHUONG TRINH CON GIAM GIA TRI DEM
       Dòng 1 dùng kiểm tra trạng thái bit 0 trong R21 (chú ý R21 chứa giá trị của PINB), nếu bit này bằng 1 (set), tức chân PB0=1 hay button không được nhấn, thì nhảy bỏ qua dòng lệnh tiếp theo để đến dòng 3. Ở dòng 3 chương trình kiểm tra trạng thái chân PB1 (button thứ 2). Quay lại dòng 1, nếu chương trình kiểm tra phát hiện chân PB0=0 (button thứ nhất được nhấn) thì dòng lệnh thứ 2 được thực thi, kết quả là chương trình nhảy đến chương trình con TANG.
TANG:
SBRS R20,0
RET
INC R25
RET
       Dòng đầu tiên của chương trình con TANG là kiểm tra trạng thái trước đó của chân PB0 (được lưu ở bit 0 trong thanh ghi R20), nếu trạng thái này bằng 0, nghĩa là không có sự chuyển từ 1 xuống 0 ở chân PB0, dòng 2 (lệnh RET) sẽ được thực thi để quay về chương trình chính. Nhưng nếu PB0 trước đó bằng 1, nghĩa là có sự thay đổi từ 1->0 ở chân này, giá trị đếm sẽ được tăng thêm 1 nhờ INC R25, sau đó quay về chương trình chính.
Tóm lại muốn tăng giá trị đếm thêm 1 đơn vị cần thỏa mãn 2 điều kiện: chân PB0 hiện tại =0 (button đang được nhấn) và trạng thái trước đó của PB0 phải là 1 (tránh trường hợp tăng liên tục). Phương pháp  này có thể áp dụng cho rất nhiều trường hợp đếm dạng đếm xung.
      Quá trình giảm giá trị đếm được hiểu tương tự, phần còn lại của ví dụ này bạn đọc hãy tự giải thích theo những gợi ý trên. 
 Nguồn : hocavr.com