tÀi liỆu tỪ trang hocavr.com

149
[CAO SONG TOÀN] [email protected] Cơ điện tử 1-K4 0976958769-0948849945 Page 1 Bài 1: làm quen với AVR I. Giới thiệu. AVR là một họ vi điều khiển do hãng Atmel sản xuất (Atmel cũng là nhà sản xuất dòng vi điều khiển 89C51 mà có thể bạn đã từng nghe đến). AVR là chip vi điều khiển 8 bits với cấu trúc tập lệnh đơn giản hóa-RISC(Reduced Instruction Set Computer), một kiểu cấu trúc đang thể hiện ưu thế trong các bộ xử lí. Tại sao AVR: so với các chip vi điều khiển 8 bits khác, AVR có nhiều đặc tính hơn hẳn, hơn cả trong tính ứng dụng (dễ sử dụng) và đặc biệt là về chức năng: Gần như chúng ta không cần mắc thêm bất kỳ linh kiện phụ nào khi sử dụng AVR, thậm chí không cần nguồn tạo xung clock cho chip (thường là các khối thạch anh). Thiết bị lập trình (mạch nạp) cho AVR rất đơn giản, có loại mạch nạp chỉ cần vài điện trở là có thể làm được. một số AVR còn hỗ trợ lập trình on – chip bằng bootloader không cần mạch nạp… Bên cạnh lập trình bằng ASM, cấu trúc AVR được thiết kế tương thích C. Nguồn tài nguyên về source code, tài liệu, application note…rất lớn trên internet. Hầu hết các chip AVR có những tính năng (features) sau: Có thể sử dụng xung clock lên đến 16MHz, hoặc sử dụng xung clock nội lên đến 8 MHz (sai số 3%) Bộ nhớ chương trình Flash có thể lập trình lại rất nhiều lần và dung lượng lớn, có SRAM (Ram tĩnh) lớn, và đặc biệt có bộ nhớ lưu trữ lập trình được EEPROM. Bộ nhớ chương trình Flash có thể lập trình lại rất nhiều lần và dung lượng lớn, có SRAM (Ram tĩnh) lớn, và đặc biệt có bộ nhớ lưu trữ lập trình được EEPROM.

Upload: toan-cao-song

Post on 06-Aug-2015

43 views

Category:

Documents


0 download

DESCRIPTION

TẤT CẢ CÁC MỤC

TRANSCRIPT

Page 1: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 1

Bài 1: làm quen với AVR I. Giới thiệu. AVR là một họ vi điều khiển do hãng Atmel sản xuất (Atmel cũng là nhà sản xuất dòng vi điều khiển 89C51 mà có thể bạn đã từng nghe đến). AVR là chip vi điều khiển 8 bits với cấu trúc tập lệnh đơn giản hóa-RISC(Reduced Instruction Set Computer), một kiểu cấu trúc đang thể hiện ưu thế trong các bộ xử lí. Tại sao AVR: so với các chip vi điều khiển 8 bits khác, AVR có nhiều đặc tính hơn hẳn, hơn cả trong tính ứng dụng (dễ sử dụng) và đặc biệt là về chức năng:

Gần như chúng ta không cần mắc thêm bất kỳ linh kiện phụ nào khi sử dụng AVR,

thậm chí không cần nguồn tạo xung clock cho chip (thường là các khối thạch anh).

Thiết bị lập trình (mạch nạp) cho AVR rất đơn giản, có loại mạch nạp chỉ cần vài

điện trở là có thể làm được. một số AVR còn hỗ trợ lập trình on – chip bằng

bootloader không cần mạch nạp…

Bên cạnh lập trình bằng ASM, cấu trúc AVR được thiết kế tương thích C.

Nguồn tài nguyên về source code, tài liệu, application note…rất lớn trên internet.

Hầu hết các chip AVR có những tính năng (features) sau:

Có thể sử dụng xung clock lên đến 16MHz, hoặc sử dụng xung clock nội lên đến 8 MHz (sai số

3%)

Bộ nhớ chương trình Flash có thể lập trình lại rất nhiều lần và dung lượng lớn, có SRAM (Ram

tĩnh) lớn, và đặc biệt có bộ nhớ lưu trữ lập trình được EEPROM.

Bộ nhớ chương trình Flash có thể lập trình lại rất nhiều lần và dung lượng lớn, có SRAM (Ram

tĩnh) lớn, và đặc biệt có bộ nhớ lưu trữ lập trình được EEPROM.

Page 2: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 2

Nhiều ngõ vào ra (I/O PORT) 2 hướng (bi-directional).

8 bits, 16 bits timer/counter tích hợp PWM.

Các bộ chuyển đối Analog – Digital phân giải 10 bits, nhiều kênh.

Chức năng Analog comparator.

Giao diện nối tiếp USART (tương thích chuẩn nối tiếp RS-232).

Giao diện nối tiếp Two –Wire –Serial (tương thích chuẩn I2C) Master và Slaver.

Giao diện nối tiếp Serial Peripheral Interface (SPI)

...

Một số chip AVR thông dụng:

AT90S1200

AT90S2313

AT90S2323 and AT90S2343

AT90S2333 and AT90S4433

AT90S4414 and AT90S8515

AT90S4434 and AT90S8535

AT90C8534

ATtiny10, ATtiny11 and ATtiny12

ATtiny15

ATtiny22

ATtiny26

ATtiny28

ATmega8/8515/8535

Page 3: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 3

ATmega16

ATmega161

ATmega162

ATmega163

ATmega169

ATmega32

ATmega323

ATmega103

ATmega64/128/2560/2561

AT86RF401.

....

Trong bài viết này tôi sử dụng chip ATmega8 để làm ví dụ, tôi chọn ATmega8 vì đây là loại chip thuộc dòng AVR mới nhất, nó có đầy đủ các tính năng của AVR nhưng lại nhỏ gọn (gói PDIP có 28 chân) và low cost nên các bạn có thể mua để tự mình tạo ứng dụng.

Tại sao Assembly (ASM): bạn có thể không cần biết về cấu trúc của AVR vẫn có thể lập trình cho AVR bằng các phần mềm hỗ trợ ngôn ngữ cấp cao như BascomAVR (Basic) hay CodevisionAVR (C), tuy nhiên đó không phải là mục đích của bài viết này. Để hiểu thấu đáo về AVR bạn phải lập trình bằng chính ngôn ngữ của nó, ASM. Như vậy lập trình bằng ASM giúp bạn hiểu tường tận về AVR, và tất nhiên để lập trình được bằng ASM bạn phải hiểu về cấu trúc AVR….Một lý do khác bạn mà tôi khuyên bạn nên lập trình bằng ASM là các trình dịch (compiler) ASM cho AVR là hoàn toàn miễn phí, và nguồn source code cho AVR viết bằng ASM là rất lớn. Tuy nhiên một khi bạn đã thành thạo AVR và ASM bạn có thể sử dụng các ngôn ngữ cấp cao như C để viết ứng dụng vì ưu điểm của ngôn ngữ cấp cao là giúp bạn dễ dàng thực hiện các phép toán đại số 16 hay 32 bit (vốn là vấn đề khó khăn khi lập trình bằng ASM).

II. Công cụ.

Page 4: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 4

Trình biên dịch: có rất nhiều trình biên dịch bạn có thể sử dụng đế biên dịch code của bạn thành file intel hex để nạp vào chip, một số trình dịch quen thuộc có thể kể đến như sau:

AvrStudio: là trình biên dịch ASM chính thức cung cấp bởi Atmel, đây là trình

biên dịch hoàn toàn miễn phí và tất nhiên là tốt nhất cho lập trình AVR bằng ASM.

Phiên bản hiện tại là 4.12 SP4, bạn có thể download phần mềm AvrStudio tại trang

web chính thức của Atmel.http://www.atmel.com/tools/AVRSTUDIO4.aspx

Wavrasm: cũng được cung cấp bởi Atmel, nó chính là tiền thân của AvrStudio.

Hiện tại wavrasm không còn được sử dụng nhiều vì so với AvrStudio trình biên

dịch này có nhiều hạng chế.

WinAVR hay avr-gcc: là bộ trình dịch được phát triển bởi gnu, ngôn ngữ sử dụng

là C và có thể được dùng tích hợp với AvrStudio (dùng Avrstudio làm trình biên

tập – editor). Đặc biệt bộ biên dịch này cũng miễn phí và đa số nguồn source code

C được viết bằng bộ này, vì vậy nó rất lí tưởng cho bạn khi viết các ứng dụng

chuyên nghiệp. Việc lập trình bằng avrgcc tôi sẽ đề cập trong những phần sau.

CodeVisionAvr: một chương trình bằng ngôn ngữ C rất hay cho AVR, hỗ trợ nhiều

thư viện lập trình. Tuy nhiên là chương trình thương mại. Bạn có thể download

bản demo (đầy đủ chức năng nhưng nhưng giới hạn dung lượng bộ nhớ chương

trình 2KB) tại Website http://www.hpinfotech.ro

ICCAVR: lập trình C cho avr, download bản demo tại! http://www.imagecraft.com

BascomAVR: lập trình cho AVR bằng basic, đây là trình biên dịch khá hay và dễ

sử dụng, hỗ trợ rất nhiều thư viện. Tuy nhiên rất khó debug lỗi và không thích hợp

cho việc tìm hiểu AVR. Vì vậy tôi không bạn khuyến khích bạn sử dụng trình dịch

Page 5: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 5

này. Bạn có thể download bản demo (4K limit) tại:

http://www.mcselec.com/index.php?option=comdocman&task=catview&gid=99&

ltemid=54.

Và còn rất nhiều trình biên dịch khác cho AVR mà tôi không kể ra đây, nhìn chung

tất cả các trình biên dịch này hỗ trợ C hoặc Basic hoặc thậm chí Pascal. Việc chọn

1 trình biên dịch tùy thuộc vào mục đích, vào mức độ ứng dụng, vào kinh nghiệm

sử dụng và nhiều lý do khác nữa. Ví dụ tôi thường dùng Avrstudio và avrgcc khi

học sử dụng AVR và khi viết thư viện. Nhưng khi cần viết chương trình ứng dụng

tôi thường chọn avrgcc và CodeVisionAVR.

Trong bài viết này tôi hướng dẫn bạn sử dụng AvrStudio để viết chương trình cho AVR bằng ASM.

Chương trình nạp (Chip Programmer): đa số các trình biên dịch (AvrStudio, CodeVisionAVR, Bascom…) đều tích hợp sẵn 1 chương trình nạp chip hỗ trợ nhiều loại mạch nạp nên bạn không quá lo lắng. Trong trường hợp khác, bạn có thể sử dụng các chương trình nạp như Icprog hay Ponyprog…là các chương trình nạp miễn phí cho AVR. Việc chọn và sử dụng chương trình nạp sẽ được giới thiệu trong các bài sau.

Mạch nạp: Bạn hãy đọc bài biết giới thiệu Mạch nạp tại http://www.hocavr.com/index.php/hardware/machnap

Chương trình mô phỏng: avr simulator là trình mô phỏng và debbug được tích hợp sẵn trong Avrstudio, avr simulator cho phép bạn quan sát trạng thái các thanh ghi bên trong AVR nên rất phù hợp để bạn debug chương trình. Proteus là chương trình thứ hai tôi muốn nói đến, Proteus không mô phỏng hoạt động bên trong chip mà mô phỏng kết quả chương trình, nó là trình mô phỏng mạch điện tử giả nên bạn có thể sử dụng để kiểm tra chương trình 1 cách trực quan hơn. Proteus là 1 công cụ hữu ích khi khi các bạn chưa có điều kiện làm các mạch điện tử.

III. Ví dụ đầu tiên của bạn.

Trong bài viết này tôi sử dụng 2 phần mềm là AvrStudio và Proteus. Sau khi download AvrStudio, bạn hãy cài đăt phần mềm trên máy của bạn, quá trình cài đặt

Page 6: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 6

rất đơn giản, bạn hãy theo các mặc định và nhấn “next” để cài đặt. Trong bài đầu tiên này chúng ta sẽ viết thử 1 chương trình đơn giản cho AVR sau đó chạy mô phỏng bằng Proteus. Có thể có một số câu lệnh các bạn sẽ không hiểu, nhưng đừng lo lắng quá, trong bài thứ 2 chúng ta sẽ học về cấu trúc AVR tại http://www.hocavr.com/index.php/lectures/cautrucavr các bạn sẽ được giải thich rõ hơn.

Để thực hiện ví dụ này, bạn hãy tạo một Project bằng AVRStudio, phần hướng dẫn chi tiết cho việc tạo Project trong AVRStudio bạn hãy tham khảo ở bài hướng dẫn AVRStudio http://www.hocavr.com/index.php/software/avr-studio.Đoạn code ví dụ trong bài đầu tiên này được trình bày trong List1.

List 1. Đoạn code đầu tiên của bạn.

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

.CSEG

.INCLUDE "M8DEF.INC"

.ORG 0x000 RJMP BATDAU .ORG 0x020 BATDAU: ; KHOI TAO CAC DIEU KIEN DAU LDI R16, HIGH(RAMEND) LDI R17, LOW(RAMEND) OUT SPH, R16 OUT SPL, R17 LDI R16, 0xFF; OUT DDRB, R16 ; CHUONG TRINH CHINH MAIN: LDI R16, 0B00000001 OUT PORTB, R16 RCALL DELAY LDI R16, 0B00000010 OUT PORTB, R16 RCALL DELAY LDI R16, 0B00000100 OUT PORTB, R16

Page 7: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 7

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 58 59 60 61

RCALL DELAY LDI R16, 0B00001000 OUT PORTB, R16 RCALL DELAY LDI R16, 0B00010000 OUT PORTB, R16 RCALL DELAY LDI R16, 0B00100000 OUT PORTB, R16 RCALL DELAY LDI R16, 0B01000000 OUT PORTB, R16 RCALL DELAY LDI R16, 0B10000000 OUT PORTB, R16 RCALL DELAY RJMP MAIN ; CHUONG TRING CON DELAY 65535 chu ky (khoang 65535us neu xung ;clock cho chip la 1M) DELAY: LDI R20, 0xFF DELAY0: LDI R21, 0xFF DELAY1: DEC R21 BRNE DELAY1 DEC R20 BRNE DELAY0 RET

Trước khi tìm hiểu ý nghĩa đoạn code, hãy nhìn 1 lượt qua đoạn code. Trước hết việc viết HOA hay viết thường là không quan trọng, bạn có thể viết đoạn code với bất cứ hình thức nào miễn đúng cú pháp, từ khóa là được. Trong đoạn code:

Page 8: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 8

Bạn thấy 1 số từ có màu BLUE (ví dụ LDI, OUT, RJMP, RCALL, RET…)đó là

các INSTRUCTiON, tức là các câu lệnh của ngôn ngữ ASM, bạn có thể đọc tài

liệu “AVR INSTRUCTION” để tìm hiểu tất cả các INSTRUCTION. Các

INSTRUCTION sau đó sẽ được trình dịch dịch thành các mã tương ứng.

Một số từ bắt đầu bằng bằng dấu chấm “.” là các DIRECTIVE (ví dụ .INCLUDE

hay .ORG )đó cũng là những từ khóa mặc định của ASM AVR, các DIRECTIVE

không phải là mã lệnh mà chỉ là các chỉ dẫn về địa chỉ bộ nhớ, khởi động bộ nhớ,

định nghĩa macro…và không được trình dịch dịch thành mã. Chi tiết về

DIRECTIVE có thể tìm thấy trong các tài liệu về ASM AVR, dưới đây tôi tóm tắt

các DIRECTIVE và chức năng của chúng như sau:

Thông thường 1 INSTRUCTION được theo sau bởi 2 toán hạng – operand (tuy

nhiên có nhiều trường hợp chỉ có 1 toán hạng hoặc không có toán hạng), khi đó

Page 9: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 9

toán hạng thứ nhất sẽ là các THANH GHI. của AVR (như đã đề cập, chúng ta sẽ

khảo sát thanh ghi AVR trong các bài sau), ví dụ : “LDI R16, 0xFF;” trong đó toán

hạng “R16” là tên 1 thanh ghi trong AVR, và “0xFF” là 1 hằng số dạng

hexadecimal có giá trị tương ứng là 255 dạng thập phân hay 11111111 nhị phân.

Các từ theo sau bởi dấu “:” là các nhãn – label (ví dụ MAIN, DELAY…), đó là từ

do chúng ta tự đặt, nó thực chất là 1 vị trí trong bộ nhớ chương trình, có thể sử

dụng nhãn như 1 chương trình con.

Phần đi sau dấu “;” gọi là giải thích – comment, phần này không được biên dịch,

bạn có thể ghi comment ở bất cứ đâu trong chương trình với yêu cầu phải sử dụng

dấu “;” trước nó.

Giải thích đoạn code:có thể chia đoạn code trên thành 4 phần: phần đầu chứa các DIRECTIVE và lệnh RJMP dùng để xác định các địa chỉ bộ nhớ chương trình, phần 2 là khởi tạo một số điều kiện đầu cho Stack Pointer và PORT, phần 3 là chương trình chính, và phần 4 là chương trình con ( chú ý đây chỉ là cách bố trí của riêng tôi, một khi đã quen thuộc, bạn có thể bố trí chương trình theo cách riêng của bạn).

Phần 1 và phần 2:

.CSEG

Chỉ thị .CSEG: Code Segment báo cho trình biên dịch rằng phần code theo sau là

phần chương trình thực thi, phần này sẽ được download vào bộ nhớ chương trình

của chip.

.INCLUDE "M8DEF.INC"

Page 10: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 10

Chỉ thị .INCLUDE báo cho trình biên dịch bắt đầu đọc 1 file đính kèm, trong

trường hợp trên là file “M8DEF.INC”, đây là file chứa các khai báo cho chip

Atmega8 như thanh ghi, ngắt…cho việc truy xuất trong chương trình của bạn, đây

là dòng bắt buộc, nếu bạn lập trình cho chip khác bạn hãy đổi tên file đính kèm, ví

dụ “m32def.inc” cho chip ATmega32… bạn có thể tìm thấy các file này trong thư

mục “C:\Program Files\Atmel\AVR Tools\AvrAssembler2\Appnotes”.

.ORG 0x000

Chỉ thị .ORG: Set Program Origin, set vị trí trong bộ nhớ sẽ được tác động đến,

trong trường hợp trên, .ORG 0x000 xác định phần code theo ngay sau sẽ nằm ở địa

chỉ 000, vị trí đầu tiên, trong bộ nhớ chương trình. Và dòng lênh trong vị trí đầu

tiên đó là:

RJMP BATDAU

RJMP: Relative Jump là lệnh nhảy không điều kiện đến 1 vị trí trong bộ nhớ,

trong trường hợp trên là nhảy đến nhãn BATDAU, và nhãn BATDAU nằm ở vị trí

0x020 (số hexadecimal, 0x020 =32 decimal) vì nó được khai báo ngay sau

DIRECTIVE .ORG 0x020.

.ORG 0x020

BATDAU

Page 11: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 11

Như thế phần bộ nhớ chương trình nằm giữa 0 và 0x020 không được sử dụng trong

đoạn code của chúng ta, phần này được sử dụng cho mục đích khác, đó là các

vectơ ngắt ( không được đề cập ở đây). Tiếp theo:

; 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 code trên khởi tạo cho Stack Pointer, chúng ta sẽ tìm hiểu phần này

trong các bài về Stack và chương trình con.

Lời khuyên: các bạn nên khởi động 1 chương trình theo cách trên và chúng ta

sẽ hiểu chúng rõ hơn sau này !

LDI R16, 0xFF

OUT DDRB, R16

Bạn chú ý 2 dòng trên và những gì tôi giải thích sau đây, 2 dòng này có tác dụng

khởi động PORTB của chip ATmega8 tác dụng như các ngõ xuất tín hiệu

(OUTPUT). Trước hết hãy quan sát chip ATmega8 trong hình sau

Page 12: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 12

Hình 1: chip ATmega8.

Bạn có thể thấy chip này gồm 28 chân, trông đó có các chân được ghi là PB0(chân

14), PB1(chân 15),…,PB7(chân 10), đó là các chân của PORTB. PORT là khái

niệm chỉ các ngõ xuất nhập. Trong AVR, PORT có thể giao tiếp theo 2 hướng (bi –

directional), có thể dùng để xuất hoặc nhận thông tin, mỗi PORT có 8 chân. Chip

Atmega8 có 3 PORT có tên tương ứng là PORTB, PORTC và PORTD (một số

chip AVR khác có 4 hoặc 6 PORT). PORT được coi là “cửa ngõ” then chốt của vi

điều khiển.

Trong AVR, mỗi PORT liên quan đến 3 thanh ghi (8 bits) có tên tương ứng là

DDRx, PINx, và PORTx với “x” là tên của PORT, mỗi bit trong thanh ghi tương

ứng với mỗi chân của PORT. Trong trường hợp của Atmega8 “x” là B, C hoặc D.

Ví dụ chúng ta quan tâm đến PORTB thì 3 thanh ghi tương ứng có tên là DDRB,

PINB và PORTB, trong đó 2 thanh ghi PORTB và PINB được nối trực tiếp với các

chân của PORTB, DDRB là thanh ghi điều khiển hướng ( Input hoặc Output). Viết

Page 13: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 13

giá trị 1 vào một bit trong thanh ghi DDRB thì chân tương ứng của PORTB sẽ là

chân xuất (Output), ngược lại giá trị 0 xác lập chân tương ứng là ngõ nhập. Sau

khi viết giá trị điều khiển vào DDRB, việc truy xuất PORTB được thực hiện thông

qua 2 thanh ghi PINB và PORTB.

Quay lại với 2 dòng code của chúng ta, dòng đầu: “LDI R16, 0xFF”, với LDI –

LoaD Immediately, dòng lệnh có ý nghĩa là load giá trị 0xFF vào thanh ghi R16,

R16 là tên 1 thanh ghi trong bộ nhớ của AVR, 0xFF là 1 hằng số có dạng thập lục

phân, ký hiệu “0x” nói lên điều đó, bạn cũng có thể dùng ký hiệu khác là “$” để

chỉ 1 số thập lục phân, ví dụ &FF, và 0xFF=255(thập phân)=0B11111111 (nhị

phân). Như thế sau dòng đầu thanh ghi R16 có giá trị là 11111111 (nhị phân).

Dòng thứ 2: “OUT DDRB, R16” nghĩa là xuất giá trị từ thanh ghi R16 ra thanh ghi

DDRB, tóm lại sau 2 dòng trên giá trị DDRB như sau:

1 1 1 1 1 1 1 1

Có thể bạn sẽ hỏi tải sao chúng không sử dụng 1 dòng duy nhất là “LDI DDRB,

0xFF” hay “OUT DDRB, 0xFF”, chúng ta không thể vì lệnh LDI chỉ cho phép

thực hiện trên các thanh ghi R16,…R31 và lệnh OUT không thực hiện được với

các hằng số.

Và vì DDRB=11111111 nên trong trường hợp này tất cả các chân của PORTB đã

sẵn sàng cho việc xuất dữ liệu. Lúc này thanh ghi PINB không có tác dụng, thanh

ghi PORTB sẽ là thanh ghi xuất, ghi giá trị vào thanh ghi này sẽ tác động đến các

chân của PORTB.1

Page 14: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 14

Phần 3: Chương trình chính

MAIN:

LDI R16, 0B00000001

OUT PORTB, R16

RCALL DELAY

Bạn chỉ cần chú ý 4 dòng trên trong toàn bộ phần chương trình chính, trước hết

“MAIN:” chỉ là 1 nhãn do chúng ta tự đặt tên, giống như 1 “cột mốc” trong chương

trình thôi. Dòng “LDI R16, 0B00000001” thì bạn đã hiểu, chỉ có 1 khác biệt nhỏ là

tôi sử dụng hằng số dạng nhị phân cho bạn dễ hiểu hơn. Và dòng “OUT PORTB,

R16” để xuất giá trị 0B00000001 có sẵn trong R16 ra thanh ghi PORTB, lúc này

chân PB0 của chip sẽ lên 1 (5V) và các chân còn lại sẽ ở mức 0 (0V). Dòng thứ 3:

“RCALL DELAY” là lệnh gọi chương trình con DELAY, tạm hoãn trước khi thực

hiện các dòng lệnh tiếp theo:

LDI R16, 0B00000010

OUT PORTB, R16

RCALL DELAY

Ba dòng lệnh này cũng giống ba dòng trên, nhưng giá trị xuất ra lúc này là

0B00000010, chân PB1 sẽ lên 5V và các chân khác xuống mức 0V. Và cứ như thế

đến đoạn cuối:

Page 15: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 15

LDI R16, 0B10000000

OUT PORTB, R16

RCALL DELAY

RJMP MAIN

Sau khi kết thức 3 dòng trên chân PB7 sẽ lên 5V, kết thúc 1 vòng xoay. Cuối cùng

là quay vế đầu chương trình chính bằng dòng “RJMP MAIN”

Bây giờ chắc bạn đã đoán được chương trình của chúng ta thực hiện việc gì, đó là

quét xoay vòng các chân của PORTB, nếu chúng ta kết nối các chân của PORTB

với các LED, chúng ta sẽ có 1 hiệu ứng quét LED xoay vòng, chúng ta thực hiện

điều này bằng phần mềm Proteus.

Phần 4: chương trinh con DELAY: đoạn chương trình này không làm gì cả ngoài

việc trì hoãn 1 khoảng thời gian, tuy nhiên bạn chưa thể hiểu nó ngay được.

Đây chỉ là 1 ví dụ đơn giản, tôi cố gắng thực hiện nó theo cách dễ hiểu nhất cho

bạn, vì thế đoạn code có vẻ hơi dài dòng, bạn hãy thực hiện lại đoạn chương trình

chính bằng đoạn code của bạn.

Phần cuối cùng là biên dịch đoạn code thành file intel hex để đổ vào chip,

nhấn phím F7 để biên dịch.

Sau khi biên dịch bạn sẽ có 1 file tên “avr1.hex” trong thưc mục project, chúng ta

sẽ dùng file này đổ vào chip sau này.

IV. Mô phỏng bằng Proteus.

Page 16: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 16

Chúng ta hãy thử nghiệm đoạn chương trình của chúng ta bằng Proteus. Nếu bạn thực hiện đúng kết quả sẽ như minh họa trong hình 2 Hướng dẫn cụ thể cách vẽ mạch điện và mô phỏng bằng phần mềm Proteus bạn hãy xem bài "Mô phỏng Proteus".

Hình 2. Mô phỏng.

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 http://www.hocavr.com/index.php/lectures/lamquenavr" 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.

Page 17: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 17

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ụ trong bài 1tại http://www.hocavr.com/index.php/lectures/lamquenavr: .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 đó.

Page 18: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 18

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.

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:

Page 19: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 19

Đượ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.

Page 20: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 20

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

Page 21: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 21

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?

Page 22: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 22

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

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.

Page 23: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 23

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.

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ể

Page 24: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 24

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 R1, 1 PUSH R1 LDI R1, 5 PUSH R1 LDI R1, 8 PUSH R1 Khi đó nội dung của stack sẽ như trong hình 6.

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,

Page 25: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 25

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:

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.

Page 26: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 26

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.

Page 27: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 27

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

Page 28: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 28

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

Page 29: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 29

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 tại http://www.hocavr.com/index.php/software/protues), 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).

Page 30: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 30

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 http://www.hocavr.com/index.php/software/avr-studio). 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

.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

Page 31: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 31

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

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.

Page 32: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 32

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.

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

Page 33: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 33

đế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.

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.

Page 34: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 34

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.

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 bao gồm: 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

Page 35: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 35

đ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ụ, 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 hang đầ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 là Input() ) và bạn phải chèn đoạn chương trình 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 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 của 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à 0x009 (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ỉ 0x009, giả sử ngay tại địa chỉ 0x009 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ác này để tránh những lỗi liên quan đến địa chỉ chương trình.

Page 36: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 36

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.

Page 37: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 37

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ị ngoại vi 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

Page 38: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 38

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 tương ứng 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 free (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, giả sử chúng ta kết nối các chân ngắt như hình 2.

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 9, các button dùng tạo ra các ngắt, có 4 khả năng (tạm gọi là các MODE) 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

Page 39: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 39

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 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 thanh ghi này (4 bit cao dùng cho Power manager và Sleep Mode), 4 bit thấp là các bit Interrupt Sense Control (ISC), 2 bit ISC11, ISC10 dùng cho INT1 và 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

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) (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).

Page 40: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 40

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.

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.

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.

Page 41: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 41

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

.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ính BATDAU: ; 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ính MAIN: ;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:

Page 42: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 42

40 41 42 43 44 45 46 47 48

; 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ó tên 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, trình này vẫn được hỗ trợ trong phiên bản mới nhưng không được khuyến khích sử dụng.

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

Page 43: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 43

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 name Old vector name Description

ADC_vect SIG_ADC ADC Conversion Complete

ANA_COMP_vect SIG_COMPARATOR Analog Comparator

EE_RDY_vect SIG_EEPROM_READY EEPROM Ready

INT0_vect SIG_INTERRUPT0 External Interrupt 0

INT1_vect SIG_INTERRUPT1 External Interrupt Request 1

SPI_STC_vect SIG_SPI Serial Transfer Complete

SPM_RDY_vect SIG_SPM_READY Store Program Memory Ready

TIMER0_OVF_vect SIG_OVERFLOW0 Timer/Counter0 Overflow

TIMER1_CAPT_vect SIG_INPUT_CAPTURE1 Timer/Counter Capture Event

TIMER1_COMPA_vect SIG_OUTPUT_COMPARE1A Timer/Counter1 Compare Match A

TIMER1_COMPB_vect SIG_OUTPUT_COMPARE1B Timer/Counter1 Compare MatchB

TIMER1_OVF_vect SIG_OVERFLOW1 Timer/Counter1 Overflow

TIMER2_COMP_vect SIG_OUTPUT_COMPARE2 Timer/Counter2 Compare Match

TIMER2_OVF_vect SIG_OVERFLOW2 Timer/Counter2 Overflow

TWI_vect SIG_2WIRE_SERIAL 2-wire Serial Interface

USART3_UDRE_vect SIG_USART3_DATA USART3 Data register Empty

III. Ví dụ ngắt ngoài với C.

Page 44: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 44

Để 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 http://www.hocavr.com/index.php/lectures/cautrucavr " 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.

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ạy Programmer Notepad http://www.hocavr.com/index.php/software/winavr, 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

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

Page 45: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 45

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

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.

Page 46: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 46

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:

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

Page 47: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 47

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

Bài 4 – Timer - Counter

I. Giới thiệu.

Trong bài 3 tôi đã giới thiệu khái quát phương pháp lập trình bằng ngôn ngữ C cho AVR với WinAVR và cách sử dụng ngắt trong AVR. Bài 4 này chúng ta sẽ khảo sát các chế độ hoạt động của phương pháp điều khiển các bộ định thời, đếm (Timer/Counter) trong AVR. Công cụ phục vụ cho bài này vẫn là bộ công cụ WinAVR và phần mềm mô phỏng Proteus. Tôi vẫn dùng chip Atmega8 để làm ví dụ. Một điều không may mắn là không phải tất cả các bộ Timer/Counter trên tất cả các dòng chip AVR là như nhau, vì thế những gì tôi trình bày trong bài này có thể sẽ không đúng với các dòng AVR khác như AT90S…Tuy nhiên tôi cũng sẽ cố gắng chỉ ra một số điểm khác biệt cơ bản để các bạn có thể tự mình điều khiển các chip khác. Nội dung bài học này bao gồm:

Nắm bắt cơ bản các bộ Timer/Counter có trên AVR.

Page 48: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 48

Sử dụng các Timer/Counter như các bộ định thời.

Sử dụng các Timer/Counter như các bộ đếm.

Sử dụng các Timer/Counter như các bộ tạo xung điều rộng PWM.

Viết một ví dụ điều khiển động cơ RC servo bằng PWM.

II. Tổng quan các bộ Timer/Counter trên chip Atmega8.

Timer/Counter là các module độc lập với CPU. Chức năng chính của các bộ Timer/Counter, như tên gọi của chúng, là định thì (tạo ra một khoảng thời gian, đếm thời gian…) và đếm sự kiện. Trên các chip AVR, các bộ Timer/Counter còn có thêm chức năng tạo ra các xung điều rộng PWM (Pulse Width Modulation), ở một số dòng AVR, một số Timer/Counter còn được dùng như các bộ canh chỉnh thời gian (calibration) trong các ứng dụng thời gian thực. Các bộ Timer/Counter được chia theo độ rộng thanh ghi chứa giá trị định thời hay giá trị đếm của chúng, cụ thể trên chip Atmega8 có 2 bộ Timer 8 bit (Timer/Counter0 và Timer/Counter2) và 1 bộ 16 bit (Timer/Counter1). Chế độ hoạt động và phương pháp điều khiển của từng Timer/Counter cũng không hoàn toàn giống nhau, ví dụ ở chip Atmega8: Timer/Counter0: là một bộ định thời, đếm đơn giản với 8 bit. Gọi là đơn giản vì bộ này chỉ có 1 chế độ hoạt động (mode) so với 5 chế độ của bộ Timer/Counter1. Chế độ hoat động của Timer/Counter0 thực chất có thể coi như 2 chế độ nhỏ (và cũng là 2 chức năng cơ bản) đó là tạo ra một khoảng thời gian và đếm sự kiện. Chú ý là trên các chip AVR dòng mega sau này như Atmega16,32,64…chức năng của Timer/Counter0 được nâng lên như các bộ Timer/Counter1… Timer/Counter1: là bộ định thời, đếm đa năng 16 bit. Bộ Timer/Counter này có 5 chế độ hoạt động chính. Ngoài các chức năng thông thường, Timer/Counter1 còn được dùng để tạo ra xung điều rộng PWM dùng cho các mục đích điều khiển. Có thể tạo 2 tín hiệu PWM độc lập trên các chân OC1A (chân 15) và OC1B (chân 16) bằng Timer/Counter1. Các bộ Timer/Counter kiểu này được tích hợp thêm khá nhiều trong các chip AVR sau này, ví dụ Atmega128 có 2 bộ, Atmega2561 có 4 bộ… Timer/Counter2: tuy là một module 8 bit như Timer/Counter0 nhưng Timer/Counter2 có đến 4 chế độ hoạt động như Timer/Counter1, ngoài ra nó nó còn được sử dụng như một module canh chỉnh thời gian cho các ứng dụng thời gian thực (chế độ asynchronous).

Page 49: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 49

Trong phạm vi bài 4 này, tôi chủ yếu hướng dẫn cách sử dụng 4 chế độ hoạt động của các Timer/Counter. Chế độ asynchronous của Timer/Counter2 sẽ được bỏ qua vì có thể chế độ này không được sử dụng phổ biến. Trước khi khảo sát hoạt động của các Timer/Counter, chúng ta thống nhất cách gọi tắt tên gọi của các Timer/Counter là T/C, ví dụ T/C0 để chỉ Timer/Counter0…

II. Sử dụng Timer/Counter.

Có một số định nghĩa quan trọng mà chúng ta cần nắm bắt trước khi sử dụng các T/C trong AVR:

BOTTOM: là giá trị thấp nhất mà một T/C có thể đạt được, giá trị này luôn là 0.

MAX: là giá trị lớn nhất mà một T/C có thể đạt được, giá trị này được quy định bởi

bởi giá trị lớn nhất mà thanh ghi đếm của T/C có thể chứa được. Ví dụ với một bộ

T/C 8 bit thì giá trị MAX luôn là 0xFF (tức 255 trong hệ thập phân), với bộ T/C 16

bit thì MAX bằng 0xFFFF (65535). Như thế MAX là giá trị không đổi trong mỗi

T/C.

TOP: là giá trị mà khi T/C đạt đến nó sẽ thay đổi trạng thái, giá trị này không nhất

thiết là số lơn nhất 8 bit hay 16 bit như MAX, giá trị của MAX có thể thanh đổi

bằng cách điều khiển các bit điều khiển tương ứng hoặc có thể nhập trừ tiếp thông

qua một số thanh ghi. Chúng ta sẽ hiểu rõ về giá trị TOP trong lúc khảo sát T/C1.

1. Timer/Counter0: Thanh ghi: có 4 thanh ghi được thiết kế riêng cho hoạt động và điều khiển T/C0, đó là:

TCNT0 (Timer/Counter Register): là 1 thanh ghi 8 bit chứa giá trị vận hành của

T/C0. Thanh ghi này cho phép bạn đọc và ghi giá trị một cách trực tiếp.

Page 50: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 50

TCCR0 (Timer/Counter Control Register): là thanh ghi điều khiển hoạt động của

T/C0. Tuy là thanh ghi 8 bit nhưng thực chất chỉ có 3 bit có tác dụng đó là CS00,

CS01 và CS02.

Các bit CS00, CS01 và CS02 gọi là các chip chọn nguồn xung nhịp cho T/C0 (Clock Select). Chức năng các bit này được mô tả trong bảng 1.

Bảng 1: chức năng các bit CS0X

TIMSK (Timer/Counter Interrupt Mask Register): là thanh ghi mặt nạ cho ngắt của

tất cả các T/C trong Atmega8, trong đó chỉ có bit TOIE0 tức bit số 0 (bit đầu tiên)

trong thanh ghi này là liên quan đến T/C0, bit này có tên là bit cho phép ngắt khi

có tràn ở T/C0. Tràn (Overflow) là hiện tượng xảy ra khi bộ giá trị trong thanh ghi

TCNT0 đã đạt đến MAX (255) và lại đếm thêm 1 lần nữa.

Page 51: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 51

Khi bit TOIE0=1, và bit I trong thanh ghi trạng thái được set (xem lại bài 3 về điều khiển ngắt), nếu một “tràn” xảy ra sẽ dẫn đến ngắt tràn.

TIFR (Timer/Counter Interrupt Flag Register): là thanh ghi cờ nhớ cho tất cả các

bộ T/C. Trong thanh ghi này bit số 0, TOV0 là cờ chỉ thị ngắt tràn của T/C0. Khi

có ngắt tràn xảy ra, bit này tự động được set lên 1. Thông thường trong điều khiển

các T/C vai trò của thanh ghi TIFR không quá quan trọng.

Hoạt động: T/C0 hoạt động rất đơn giản, hoạt động của T/C được “kích” bởi một tín hiệu (signal), cứ mỗi lần xuất hiện tín hiệu “kích” giá trị của thanh ghi TCNT0 lại tăng thêm 1 đơn vị, thanh ghi này tăng cho đến khi nó đạt mức MAX là 255, tín hiệu kích tiếp theo sẽ làm thanh ghi TCNT0 trở về 0 (tràn), lúc này bit cờ tràn TOV0 sẽ tự động được set bằng 1. Với cách thức hoạt động như thế có vẻ T/C0 “vô dụng” vì cứ tăng từ 0 đến 255 rồi lại quay về không và quá trình lặp lại. Tuy nhiên, yếu tố tạo sự khác biệt chính là “tính hiệu kích” và ngắt tràn, kết hợp 2 yếu tố này chúng ta có thể tạo ra 1 bộ định thời gian hoặc 1 bộ đếm sự kiện. Trước hết bạn hãy nhìn lại bảng 1 về các bit chọn xung nhịp cho T/C0. Xung nhịp cho T/C0 chính là tín hiệu kích cho T/C0, xung nhịp này có thể tạo bằng nguồn tạo dao động của chip (thạch anh, dao động nội trong chip…). Bằng cách đặt giá trị cho các bit CS00, CS01 và CS02 của thanh ghi điều khiển TCCR0 chúng ta sẽ quyết định “bao lâu” thì sẽ kích T/C0 một lần. Ví dụ mạch ứng dụng của bạn có nguồn dao động clkI/O = 1MHz tức chu kỳ 1 nhịp là 1us ( 1 micro giây), bạn đặt TCCR0=5 (SC02=1, CS01=0, CS00=1), căn cứ theo bảng 1 tín hiệu kích cho T/C0 sẽ bằng clkI/O/1024 nghĩa là sau 1024us thì T/C0 mới được “kích” 1 lần, nói cách khác giá trị của TCNT0 tăng thêm 1 sau 1024us (chú ý là tần số được chia cho 1024 thì chu kỳ sẽ tăng 1024 lần). Quan sát 2 dòng cuối cùng trong bảng 1 bạn sẽ thấy rằng tín hiệu kích cho T/C0 có thể lấy từ bên ngoài (External clock source), đây chính là ý tưởng cho hoạt động của chức năng đếm sự kiện trên T/C0. Bằng cách thay đổi trạng thái chân T0 (chân 6 trên chip Atmega8) chúng ta sẽ làm tăng giá trị thanh ghi TCNT0 hay nói cách khác T/C0 có thể dùng để đếm sự kiện xảy ra trên chân T0. Dưới đây chúng ta sẽ xem xét cụ thể cách điều khiển T/C0 theo 1 chế độ định thời gian và đếm.

1.1 Bộ định thời gian.

Chúng ta có thể tạo ra 1 bộ định thì để cài đặt một khoảng thời gian nào đó. Ví dụ bạn muốn rằng cứ sau chính xác 1ms thì chân PB0 thay đổi trạng thái 1 lần (nhấp nháy), bạn lại không muốn dùng các lệnh delay như trước nay vẫn dùng vì

Page 52: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 52

nhược điểm của delay là “CPU không làm gì cả” trong lúc delay, vì thế trong nhiều trường hợp các lệnh delay rất hạn chế được sử dụng. Bây giờ chúng ta dùng T/C0 để làm việc này, ý tưởng là chúng ta cho bộ đếm T/C0 hoạt động, khi nó đếm đủ 1ms thì nó sẽ tự kích hoạt ngắt tràn, trong trình phục vụ ngắt tràn chúng tat hay đổi trạng thái chân PB0. Tôi minh họa ý tưởng như trong hình 1.

Hình 1. So sánh 2 cách làm việc.

(CPU nop: CPU không làm gì cả) Một vấn đề nảy sinh lúc này, như tôi trình bày trong phần trước, T/C0 chỉ đếm từ 0 đến 255 rồi lại quay về 0 (xảy ra 1 ngắt tràn), như thế dường như chúng ta không thể cài đặt giá trị mong muốn bất kỳ cho T/C0? Câu trả lời là chúng ta có thể bằng cách gán trước một giá trị cho thanh ghi TCNT0, khi ấy T/C0 sẽ đếm từ giá trị mà chúng ta gán trước và kết thúc ở 255. Tuy nhiên do khi tràn xảy ra, TCNT0 lại được tự động trả về 0, do đó việc gán giá trị khởi tạo cho TCNT0 phải được thực hiện liên tục sau mỗi lần xảy ra tràn, vị trí tốt nhất là đặt trong trình phục vụ ngắt tràn. Việc còn lại và cũng là việc quan trọng nhất là việc tính toán giá trị chia (prescaler) cho xung nhịp của T/C0 và việc xác định giá trị khởi đầu cần gán cho thanh ghi TCNT0 để có được 1 khoảng thời gian định thì chính xác như mong muốn. Trước hết chúng ta sẽ chọn prescaler sao cho hợp lí nhất (chọn giá trị chia bằng cách set 3 bit CS02,CS01,CS00). Giả sử nguồn xung clock “nuôi” chip của chúng ta là clkI/O=1MHz tức là 1 nhịp mất 1us, nếu chúng ta để prescaler=1, tức là tần số của T/C0 (tạm gọi là fT/C0) cũng bằng clkI/O=1MHz, cứ 1us T/C0 được kích và TCNT0 sẽ tăng 1 đơn vị. Khi đó giá trị lớn nhất mà T/C0 có thể đạt được là 256 x 1us=256us, giá trị này nhỏ hơn 1ms mà ta mong muốn. Nếu chọn prescaler=8 (xem bảng 1) nghĩa là cứ sau 8 nhịp (8us) thì TCNT0 mới tăng 1 đơn vị, khả năng lớn nhất mà T/C0 đếm được là 256 x 8us=2048us, lớn hơn 1ms, vậy ta hoàn toàn có thể sử dụng prescaler=8 để tạo ra một khoảng định thì 1ms. Bước tiếp theo là xác định giá trị khởi đầu của TCNT0 để T/C0 đếm đúng 1ms (1000us).

Page 53: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 53

Ứng với prescaler=8 chúng ta đã biết là cứ 8us thì TCNT0 tăng 1 đơn vị, dễ dàng tính được bộ đếm cần đếm 1000/8=125 lần để hết 1ms, do đó giá trị ban đầu của TCNT0 phải là 256-125=131. Bạn có thể quan sát hình 2 để hiểu thấu đáo hơn.

Hình 2. Quá trình thực hiện.

Hãy tạo 1 Project bằng Programmer Notepad với tên gọi TIMER0 và viết đoạn code cho Project này như trong list 1. List 1. Định thì 1ms với T/C0.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> int main(void){ DDRB=0xFF; //PORTB la output PORT PORTB=0x00; TCCR0=(1<<CS01);// CS02=0, CS01=1, CS00=0: chon Prescaler = 8 TCNT0=131; //gan gia tri khoi tao cho T/C0 TIMSK=(1<<TOIE0);//cho phep ngat khi co tran o T/C0 sei(); //set bit I cho phep ngat toan cuc while (1){ //vòng lặp vô tận //do nothing } return 0; } //trinh phuc vu ngat tran T/C0 ISR (TIMER0_OVF_vect ){ PORTB ^=1; //doi trang thai Bit PB0 TCNT0=131; //gan gia tri khoi tao cho T/C0 }

Page 54: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 54

Đoạn code rất đơn giản, bạn chỉ cần chú ý đến 3 dòng khai báo cho T/C0 (dòng 9, 10, 11). Với dòng 9: TCCR0=(1<<CS01) là 1 cách set bit CS01 trong thanh ghi điều khiển TCCR0 lên 1, 2 bit CS02 và CS00 được để giá trị 0 (bạn xem lại bài 3 về cách set các bit đặc biệt trong các thanh ghi), tóm lại dòng này tương đương TCCR0=2, giá trị Prescaler được chọn bằng 8 (tham khảo bảng 1). Dòng 10 chúng ta gán giá trị khởi tạo cho thanh ghi TCNT0. Và dòng 11 set bit TIOE0 lên 1 để cho phép ngắt xảy ra khi có tràn ở T/C0. Trong trình phục vụ ngắt tràn T/C0, chúng ta sẽ thực hiện đổi trạng thái chân PB0 bằng toán từ XOR (^), chú ý đến ý nghĩa của toán tử XOR: nếu XOR một bit với số 1 thì bit này sẽ chuyển trạng thái (từ 0 sang 1 và ngược lại). Cuối cùng và quan trọng là chúng ta cần gán lại giá trị khởi tạo cho T/C0. Bạn có thể vẽ môt mạch điện mô phỏng đơn giản dùng 1 Oscilloscope như trong hình 3 để kiểm tra hoạt động của đoạn code.

Hình 3. Mô phỏng định thì của T/C0.

1.2 Bộ đếm sự kiện. Như tôi trình bày trong phần hoạt động của T/C0, chúng ta có thể dùng T/C0 như một bộ đếm (counter) để đếm các sự kiện (sự thay đổi trạng thái) xảy ra trên chân T0. Bằng cách đặt giá trị cho thanh ghi TCCR0 = 6 (CS02=1, CS01=1, CS00=0) cho phép đếm “cạnh xuống” trên chân T0, nếu TCCR0 = 7 (CS02=1, CS01=1, CS00=1) thì “cạnh lên” trên chân T0 sẽ được đếm. Có sử dụng ngắt hay không phụ thuộc vào mục đích sử dụng. Khảo sát 1 ví dụ đơn giản gần giống với ví dụ đếm trong bài AVR2 nhưng sử dụng T/C0 và chỉ đếm 1 chiều tăng. Kết nối mạch điện như trong hình 4, mỗi lần Button 1 được nhấn, giá trị đếm tăng thêm 1. Button 2 dùng reset giá trị đếm về 0. Đoạn code cho ví dụ thứ 2 này được trình bày trong List 2.

Page 55: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 55

Hình 4. Đếm 1 chiều bằng T/C0.

List 2. Đếm sự kiện với T/C0

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

#include <avr/io.h> #include <avr/interrupt.h> int main(void){ DDRB=0xFF; //PORTB la output PORT PORTB=0x00; DDRD=0x00; //khai bao PORTD la input de ket noi Button kich vao chan T0 PORTD=0xFF; //su dung dien tro keo len cho PORTD TCCR0=(1<<CS02)|(1<<CS01);// CS02=1, CS01=1, CS00=0: xung nhip tu chan T0, down TCNT0=0; while (1){ //vòng lặp vô tận if (TCNT0==10) TCNT0=0; PORTB=TCNT0; //xuat gia tri dem ra led 7 doan if (bit_is_clear(PIND,7)) TCNT0=0; //Reset bo dem neu chan PD7=0 } return 0; }

Nội dung trong chương trình chính là khai báo các hướng giao tiếp cho các PORT, PORTB là ouput để xuất kết quả đếm ra led 7 đoạn, PORTD được khái báo input vì các button được nối với PORT này. T/C0 được khai báo sử dụng nguồn kích ngoài từ T0, dạng cạnh xuống thông qua dòng

Page 56: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 56

TCCR0=(1<<CS02)|(1<<CS01), bạn cũng có thể khai báo tương đương là TCCR0=6 (tham khảo bảng 1). Giá trị của bộ đếm sẽ được xuất ra PORTB để kiểm tra. Điểm chú ý trong đoạn chương trình này là macro “bit_is_clear”, đây là một macro được định nghĩa trong file “sfr_defs.h” dùng để kiểm tra 1 bit trong một thanh ghi đặc biệt có được xóa (bằng 0) hay không, trong trường hợp của đoạn code trên: “if(bit_is_clear(PIND,7)) TCNT0=0;” nghĩa là kiểm tra xem nếu chân PD7 được kéo xuống 0 (button 2 được nhấn) thì sẽ reset bộ đếm về 0. Như vậy việc sử dụng T/C0 là tương đối đơn giản, bạn chỉ cần khai báo các giá trị thích hợp cho thanh ghi điều khiển TCCR0 bằng cách tham khảo bảng 1, sau đó khởi tạo giá trị cho TCNT0 (nếu cần thiết), khai báo có sử dụng ngắt hay không bằng cách set hay không set bit TOIE0 trong thanh ghi TIMSK là hoàn tất. 2. Timer/Counter1: Timer/Counter1 là bộ T/C 16 bits, đa chức năng. Đây là bộ T/C rất lý tưởng cho lập trình đo lường và điều khiển vì có độ phân giải cao (16 bits) và có khả năng tạo xung điều rộng PWM (Pulse Width Modulation – thường dùng để điều khiển động cơ). Thanh ghi: có khá nhiều thanh ghi liên quan đến T/C1. Vì là T/C 16 bits trong khi độ rộng bộ nhớ dữ liệu của AVR là 8 bit (xem lại bài 2) nên đôi khi cần dùng những cặp thanh ghi 8 bits tạo thành 1 thanh ghi 16 bit, 2 thanh ghi 8 bits sẽ có tên kết thúc bằng các ký tự L và H trong đó L là thanh ghi chứa 8 bits thấp (LOW) và H là thanh ghi chứa 8 bits cao (High) của giá trị 16 bits mà chúng tạo thành.

TCNT1H và TCNT1L (Timer/Counter Register): là 2 thanh ghi 8 bit tạo thành

thanh ghi 16 bits (TCNT1) chứa giá trị vận hành của T/C1. Cả 2 thanh ghi này cho

phép bạn đọc và ghi giá trị một cách trực tiếp. 2 thanh ghi được kết hợp như sau:

TCCR1A và TCCR1B (Timer/Counter Control Register): là 2 thanh ghi điều khiển

hoạt động của T/C1. Tất cả các mode hoạt động của T/C1 đều được xác định thông

qua các bit trong 2 thanh ghi này. Tuy nhiên, đây không phải là 2 byte cao và thấp

của một thanh ghi mà là 2 thanh ghi hoàn toàn độc lập. Các bit trong 2 thanh ghi

Page 57: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 57

này bao gồm các bit chọn mode hay chọn dạng sóng (Waveform Generating Mode

– WGM), các bit quy định dạng ngõ ra (Compare Output Match – COM), các bit

chọn giá trị chia prescaler cho xung nhịp (Clock Select – CS)…Cấu trúc của 2

thanh ghi được trình bày như bên dưới.

Nhìn chung để “thuộc” hết cách phối hợp các bit trong 2 thanh ghi TCCR1A và TCCR1B là tương đối phức tạp vì T/C1 có rất nhiều mode hoạt động, chúng ta sẽ khảo sát chúng trong phần các chế độ hoạt động của T/C1 bên dưới. Ở đây, trong thanh ghi TCCR1B có 3 bit khá quen thuộc là CS10, CS11 và CS12. Đây là các bit chọn xung nhịp cho T/C1 như truong T/C0. Bảng 2 sẽ tóm tắt các chế độ xung nhịp trong T/C1. Bảng 2: chức năng các bit CS12, CS11 và CS10.

OCR1A và OCR1B (Ouput Compare Register A và B): có một số khái niệm mới

mà chúng ta cần biết khi làm việc với T/C1, một trong số đó là Ouput Compare

(sorry, I don’t wanna translate it to Vietnamese). Trong lúc T/C hoạt động, giá trị

Page 58: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 58

thanh ghi TCNT1 tăng, giá trị này được liên tục so sánh với các thanh ghi OCR1A

và OCR1B (so sánh độc lập với từng thanh ghi), việc so sánh này trên AVR gọi là

gọi là Ouput Compare. Khi giá trị so sánh bằng nhau thì 1 “Match” xảy ra, khi đó

một ngắt hoặc 1 sự thay đổi trên chân OC1A (hoặc/và chân OC1B) xảy ra (đây là

cách tạo PWM bởi T/C1). Tại sao lại có A và B? Đó là vì người thiết kế AVR

muốn mở rộng khả năng ứng dụng T/C1 cho bạn. A và B đại diện cho 2 kênh

(channel) và B. Cũng vì điều này mà chúng ta có thể tạo 2 kênh PWM bằng T/C1.

Tóm lại, cơ bản 2 thanh ghi này chứa các giá trị để so sánh, chức năng và các chế

độ hoạt động cụ thể của chúng sẽ được khảo sát trong các phần sau.

ICR1 (InputCapture Register 1): khái niệm mới thứ 2 của T/C1 là Input Capture.

Khi có 1 sự kiện trên chân ICP1 (chân 14 trên Atmega8), thanh ghi ICR1sẽ

“capture” giá trị của thanh ghi đếm TCNT1. Một ngắt có thể xảy ra trong trường

hợp này, vì thế Input Capture có thể được dùng để cập nhật giá trị “TOP” của

T/C1.

Page 59: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 59

TIMSK (Timer/Counter Interrupt Mask Register): các bộ T/C trên AVR dùng

chung thanh ghi mặt nạ ngắt, vì thế TIMSK cũng được dùng để quy định ngắt cho

T/C1. Có điều lúc này chúng ta chỉ quan tâm đến các bit từ 2 đến 5 của TIMSK.

Có tất cả 4 loại ngắt trên T/C1 (nhớ lại T/C0 chỉ có 1 loại ngắt tràn)

Bit 2 trong TIMSK là TOIE1, bit quy định ngắt tràn cho thanh T/C1 (tương tự trường hợp của T/C0). Bit 3, OCIE1B là bit cho phép ngắt khi có 1 “Match” xảy ra trong việc so sánh TCNT1 với OCR1B. Bit 4, OCIE1A là bit cho phép ngắt khi có 1 “Match” xảy ra trong việc so sánh TCNT1 với OCR1A. Bit 5, TICIE1 là bit cho phép ngắt trong trường hợp Input Capture được dùng.

Cùng với việc set các bit trên, bit I trong thanh ghi trạng thái phải được set nếu muốn sử dụng ngắt (xem lại bài 3 tại http://www.hocavr.com/index.php/lectures/ngatngoai về điều khiển ngắt).

TIFR (Timer/Counter Interrupt Flag Register): là thanh ghi cờ nhớ cho tất cả các

bộ T/C. Các bit từ 2 đến 5 trong thanh ghi này là các cờ trạng thái của T/C1.

Các mode hoạt động: có tất cả 5 chế độ hoạt động chính trên T/C1. Các chế độ hoạt động cơ bản được quy định bởi 4 bit Waveform Generation Mode (WGM13, WGM12, WGM11 WGM10) và một số bit phụ khác. 4 bit Waveform Generation Mode lại được bố trí nằm trong 2 thanh ghi TCCR1A và TCCR1B (WGM13 là bit 4, WGM12 là bit 3 trong TCCR1B trong khi WGM11 là bit 1 và WGM10 là bit 0 trong thanh ghi TCCR1A) vì thế cần phối hợp 2 thanh ghi TCCR1 trong lúc điều khiển T/C1. Các chế độ hoạt động của T/C1 được tóm tắt trong bảng sau 3: Bảng 3: các bit WGM và các chế độ hoạt động của T/C1.

Page 60: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 60

2.1 Normal mode (Chế độ thường). Đây là chế độ hoạt động đơn giản nhất của T/C1. Trong chế độ này, thanh ghi đếm TCNT1 được tăng giá trị từ 0 (BOTTOM) đến 65535 hay 0xFFFF (TOP) và quay về 0. Chế độ này hoàn toàn giống cách mà Timer0 hoạt động chỉ có khác là giá trị đếm cao nhất là 65535 thay vì 255 như trong timer0. Nhìn vào bảng 3, để set T/C1 ở Normal mode chúng ta cần set 4 bit WGM về 0, vì 0 là giá trị mặc định của các thanh ghi nên thực tế chúng ta không cần tác động đến các bit WGM. Duy nhất một việc quan trọng cần làm là set các bit Clock Select (CS12, SC11, CS10) trong thanh ghi TCCR1B (xem thêm bảng 2). Bạn có thể tham khảo ví dụ của Timer0. Đoạn code trong list 3 là 1 ví dụ tạo 1 khoảng thời gian 10ms bằng T/C1, normal mode: List 3. Định thì 10ms với T/C1.

1 2 3 4 5 6 7 8 9 10

#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> int main(void){ DDRB=0xFF; //PORTB la output PORT PORTB=0x00; TCCR1B=(1<CS10);// CS12=0, CS11=0, CS10=1: chon Prescaler =1 // thanh ghi TCCR1B duoc dung thay vi TCCR0 cua Timer0

Page 61: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 61

11 12 13 14 15 16 17 18 19 20 21 22 23 24

TCNT1=55535; //gan gia tri khoi tao cho T/C1 TIMSK=(1<<TOIE1);//cho phep ngat khi co tran o T/C1 sei(); //set bit I cho phep ngat toan cuc while (1){ //vòng lặp vô tận //do nothing } return 0; } //trinh phuc vu ngat tran T/C1 ISR (TIMER1_OVF_vect ){ PORTB ^=1; //doi trang thai Bit PB0 TCNT1=55535; //gan gia tri khoi tao cho T/C1 }

2.2 Clear Timer on Compare Match (xóa timer nếu xảy ra bằng trong so sánh)-CTC. Một cách gọi tắt của chế độ hoạt động này là CTC, một chế độ hoạt động mới trên T/C1. Nhìn vào bảng 3 bạn sẽ thấy có 2 mode CTC (mode 4 và mode 12). Tôi lấy ví dụ mode 4 để giải thích hoạt động của CTC. Khi bạn set các bit Waveform Generation Mode tuong ứng: WGM13=0, WGM12=1, WGM11=0, WGM10=0 thì mode 4 được chọn. Trong mode này, thanh ghi OCR1A chứa giá trị TOP (giá trị so sánh do người dùng đặt), thanh ghi đếm TCNT1 tăng từ 0, khi TCNT1 bằng giá trị chứa trong OCR1A thì một “Compare Match” xảy ra. Khi đó, một ngắt có thể xảy ra nếu chúng ta cho phép ngắt Compare Match (set bit OCF1A trong thanh ghi TIMSK lên 1). Mode này cũng tương đối đơn giản, một ứng dụng cơ bản của mode này là đơn giản hóa việc đếm các sự kiện bên ngoài. Ví dụ bạn kết nối 1 sensor đếm số người đi vào 1 căn phòng với chấn T1 (chân counter source của T/C1), bạn muốn rằng cứ sau khi đếm 5 người thì sẽ thông báo 1 lần. List 4 là đoạn code mô tả ví dụ này: List 4. Phối hợp CTC với đếm sự kiện.

1 2 3 4 5 6 7

#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> volatile usigned char val=0; //khai bao 1 bien tam val va khoi tao =0 int main(void){ DDRB=0xFF; //PORTB la output PORT PORTB=0x00;

Page 62: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 62

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

TCCR1B=GM12)|(1<<CS12)|(1<<CS11);//xung nhip tu chan T1, canh xuong OCR1A=4; //gan gia tri can so sanh TIMSK=(1<OCIE1A);//cho phep ngat khi gia tri dem bang 4 sei(); //set bit I cho phep ngat toan cuc while (1){ //vòng lặp vô tận //do nothing } return 0; } //trinh phuc vu ngat compare match ISR (TIMER1_COMPA_vect){ val++; if (val==10) val=0; //gioi han bien val tu 0 den 9 PORTB =val; //xuat gia tri ra PORTB }

Tôi chỉ giải thích những điểm mới trong List 4. Thứ nhất là “attribute” volatile dùng trước khai báo biến val, biến val được khai báo là unsigned char (8 bit, không dấu) dùng chứa giá trị tạm thời để xuất ra PORTB khi có ngắt xảy ra. Điều đặc biệt là từ khóa volatile đặt trước nó, volatile là một thuộc tính (attribute) của bộ biên dịch gcc-avr, nó nói với trình dịch rằng biến val sẽ được dùng trong chương trình chính và cả trong các trình phục vụ ngắt. Nếu bạn muốn cập nhập giá trị 1 biến toàn cục trong các trình phục vụ ngắt mà biến đó không được chỉ định thuộc tính volatile trước thì quá trình cập nhật thất bại. Một cách dễ hiểu hơn, bạn xem trình ISR trong ví dụ trên, cứ mỗi lần có ngắt Compare Match xảy ra, biến val được tăng thêm 1 (dòng 21) sau đó kiểm tra điều kiện bằng 10 hay không và cuối cùng là gán cho PORTB. Nếu trong khai báo của val (dòng 4) chúng ta không chỉ định volatile thì giá trị xuất ra PORTB sẽ luôn là 1 khi có ngắt. Chú ý là điều này chỉ đúng it nhất là với phiên bản WinAVR tháng 12 năm 2007, các phiên bản sau có thể không cần dùng volatile (tôi sẽ cập nhật sau). Dòng 8 set các bit điều khiển: TCCR1B=(1<<WGM12)|(1<<CS12)|(1<<CS11); bạn thấy tôi chỉ set bit WGM12 trong 4 bit WGM vì tôi muốn chọn mode CTC 4 (xem bảng 3). Hai bit CS12 và CS11 được set bằng 1 trong khi CS10 được giữ ở 0 để chọn xung clock là từ bên ngoài, chân T1 (xem bảng 2). Trong dòng 10, OCR1A=4; là giá trị cần so sánh, chúng ta biết rằng TCNT1 tăng lên từ 0, vì thế để đếm 5 sự kiện thì cần đặt giá trị

Page 63: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 63

so sánh là 4 (0, 1, 2, 3, 4). Dòng 11 set bit cho phép ngắt khi có Compare match xảy ra (dùng cho channel A). Mode 12 của CTC (WGM13=1, WGM12=1, WGM11=0, WGM10=0) cũng tương tự mode 4 nhưng cái khác là giá trị cần so sánh được chứa trong thanh ghi ICR1 (không phải OCR1A hay OCR1B). Khi đó nếu muốn dùng ngắt thì bạn phải dùng ngắt Input capture. Cụ thể dòng 8 trong list 4 đổi thành: TCCR1B=(1<<WGM13)|( (1<<WGM12)|(1<<CS12)|(1<<CS11); dòng 10: ICR1=4 và dòng 20: ISR (TIMER1_CAPT_vect ){ Một khả năng khác của CTC là xuất tín hiệu xung vuông trên chân OC1A (chân 15 trên Atmega8) bằng cách set các bit Compare Output Mode trong thanh ghi TCCR1A. Tuy nhiên việc tạo các tín hiệu output trong mode CTC không thật sự thú vị. Vì vậy chúng ta sẽ khảo sát cách tạo tín hiệu output trong 1 chế độ chuyên nghiệp và thú vị hơn, chế độ PWM. Trước khi bắt đầu làm việc với các chế độ PWM tôi nghĩ cần thiết giới thiệu thế nào là PWM và nhắc lại các khái niệm giá trị đếm của Timer1 (hay bất kỳ timer nào khác) trên AVR. Trước hết, PWM hay Pulse Width Modulation được hiểu theo nghĩa tiếng Việt là “xung điều rộng” là khái niệm chỉ tín hiệu xung mà thường thì chu kỳ (Time period) của nó được cố định, duty cycle (thời thời gian tín hiệu ở mức HIGH) của nó có thể được thay đổi. Bạn xem 1 ví dụ về PWM trong hình 5.

Hình 5. Ví dụ về tín hiệu PWM.

Tạo ra PWM tức là tạo ra những tín hiệu xung mà ta có thể điều khiển duty cycle (và cả tần số ~ Time period nếu cần thiết). Timer 1 trêsn Atmega8 là 1 module lý tưởng để tạo ra các tín hiệu dạng này. Nhưng PWM dùng để làm gì và cách mà nó được sử dụng như thế nào? Tôi lấy một ví dụ như trong hình 6: một động cơ DC và một switch button.

Page 64: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 64

Hình 6. Motor và switch.

Nếu nhấn button thì động cơ hoạt động, thả button thì động cơ dừng. Tuy nhiên do tốc độ nhấn và thả của con người có hạn, bạn sẽ thấy động cơ hoạt động hơi “sượng” (ripple). Điều gì xảy ra nếu bạn nhấn và thả button với vận tốc 5000 lần/giây. Câu trả lời là tay bạn sẽ bị gãy và button sẽ bị hỏng (^^). 5000 lần/s là điều không tưởng, tuy nhiên nếu bạn làm được như thế thì tổng thời gian cho 1 lần nhấn+thả là 1:5000=0.0002s = 200us. Có sự khác biệt nào không giữa trường hợp thời gian nhấn = 150us, thời gian thả 50us và trường hợp thời gian nhấn là 50us còn thời gian thả là 150us. Bạn sẽ dễ dàng tìm câu trả lời, trong trường hợp 1 động cơ sẽ quay với vận tốc nhanh hơn trường hợp 2. Đó là ý tưởng cơ bản để sử dụng PWM điều khiển vận tốc động cơ (và điều khiển nhiều thứ khác nữa). để biến cái không tưởng trên (5000 lần/s) thành hiện thực, chúng ta sẽ thay thế cái button cơ khí kia bằng 1 công tắc điện tử (electronics switch). Thường thì các chip MOSFET được dùng làm các khóa điện tử. MOSFET thường có 3 chân G (gate), D (drain) và S (source). Ví dụ 1 MOSFET kênh N ở trạng thái thông thường 2 chân D và S ko có dòng điện chạy qua, nếu điện áp chân G lớn hơn chân S khoảng 3V trở lên thì dòng điện có thể chạy từ D sang S. hãy xem cách mô tả tương đương 1 MOSFET với 1 button trong hình 7.

Page 65: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 65

Hình 7. MOSFET và button.

Việc “kích” các MOSFET có thể thực hiện bằng các tín hiệu PWM. Vì thế ý tưởng điều khiền động cơ trong hình 6 có thể được thực hiện lại thông qua PWM như trong hình 8.

Hình 8. Mô hình điều khiển tốc độ động cơ bằng PWM đơn giản. Như vậy là xong phần giới thiệu về PWM, bây giờ chúng ta sang các khái niệm số đếm trong Timer. Hình 9 minh họa cách bố trí các số đếm trong Timer1 trên hệ trục đếm.

Page 66: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 66

Hình 9: các mốc giá trị của T/C1.

BOTTOM luôn được cố định là 0 (giá trị nhỏ nhất), MAX luôn là 0xFFFF (65535). TOP là giá trị đỉnh do người dùng định nghĩa, giá trị của TOP có thể được cố định là 0xFF (255), 0x1FF (511), 0x3FF 91023) hoặc định nghĩa bởi các thanh ghi ICR1 hoặc OCR1A. thực chất đối với ứng dụng PWM thì TOP chính là Time period của PWM. Do mục đích sử dụng mà có thể chọn TOP là các giá trị cố định hay các thanh ghi, riêng với tôi, cho mục đích tạo tín hiệu PWM tôi chọn TOP định nghĩa bởi thanh ghi ICR1. Ouput Compare là giá trị so sánh của bộ Timer. Trong chế độ PWM thì Output Compare quy định Duty cycle. Với T/C1, Output Comapre là giá trị trong các thanh ghi OCR1A và OCR1B. Do có 2 thanh ghi độc lập A và B, tương ứng chúng ta có thể tạo ra 2 tín hiệu PWM trên 2 chân OC1A và OC1B bằng T/C1. Đã đến lúc chúng ta tìm hiểu cách tạo PWM trên AVR. 2.3 Fast PWM (PWM tần số cao). Trong chế độ Fast PWM, 1 chu kỳ được tính trong 1 lần đếm từ BOTTOM lên TOP (single-slope), vì thế mà chế độ này gọi là Fast PWM (PWM nhanh). Có tất cả 5 mode trong Fast PWM tương ứng với 5 cách chọn giá trị TOP khác nhau (tham khảo bảng 3). Việc xác lập chế độ hoạt động cho Fast PWM thực hiện thông qua 4 bit WGM và các bit chọn dạng xung ngõ ra, Compare Output Mode trong thanh ghi TCCR1A, nhìn lại 2 thanh ghi TCCR1A và TCCR1B.

Chú ý các bit COM1A1, COM1A0 và COM1B1, COM1B0 là các bit chọn dạng tín hiệu ra của PWM (Compare Output Mode bits). COM1A1, COM1A0 dùng cho kênh A và COM1B1, COM1B0 dùng cho kênh B. Hãy đối chiếu bảng 4. Bảng 4: mô tả các bit COM trong chế độ fast PWM.

Page 67: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 67

Tôi sẽ giải thích hoạt động của Fast PWM kênh A thông qua 1 trường hợp cụ thể, mode 14 (WGM13=1, WGM12=1, WGM11=1, WGM10=0). Trong mode 14, giá trị TOP (cũng là chu kỳ của PWM) được chứa trong thanh ghi ICR1, khi hoạt động thanh ghi TCNT1 tăng giá trị từ 0, giả sử các bit phụ COM1A=1, COM1A0=0, lúc này trạng thái của chân OC1A (chân 15) là HIGH (5V), khi TCNT1 tăng đến bằng giá trị của thanh ghi OCR1A thì chân OC1A được xóa về mức LOW (0V), thanh ghi đếm TCNT1 vẫn tiếp tục tăng đến khi nào nó bằng giá trị TOP chứa trong thanh ghi ICR1 thì TCNT1 tự động reset về 0 và chân OC1A trở về trạng thái HIGH, cái này gọi là “Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at TOP” mà bạn thấy trong hàng 4 bảng 4. Hình 10 mô tả cách tạo xung PWM trên chân OC1A ở mode 14.

Hình 10: Fast PMW mode 14.

Rõ ràng chúng ta có thể điều khiển cả time period và duty cycle của PWM bằng 2 thanh ghi ICR1 và OCR1A. Thông thường giá trị của ICR1 được tính toán và gán cố định, giá trị của OCR1A được thay đổi để thực hiện mục đích điều khiển (như thay đổi vận tốc động cơ). Chú ý là nếu chúng ta set các bit phụ ngược lại: COM1A=0, COM1A0=1, thì tín hiệu PWM trên chân OC1A sẽ có phần “LOW” từ 0 đến OCR1A và “HIGH” từ OCR1A đến ICR1, đây gọi là “set OC1A/OC1B on Compare Match, clear OC1A/OC1B at TOP” (ngược với tín hiệu trên hình 10). Hoạt động của fast PWM kênh B hoàn toàn tương tự, trong đó thanh ghi ICR1 cũng chứa TOP của PWM kênh B và thanh ghi ICR1B chứa duty cycle. Như vậy 2 kênh A và B có cùng tần số hay Time period và duty cycle được điều khiển độc lập. Chân xuất tín hiệu PWM của kênh B là chân OC1B (chân 16 trên Atmega8). Các mode 5, 6 và 7 của Fast PWM hoạt động hoàn toàn tương tự mode 14. Điểm khác nhau cơ bản là giá trị TOP(Time period). Trong các mode này giá trị TOP không do thanh thi ICR1 định nghĩa mà là các hằng số không đổi. Với mode 5, tức

Page 68: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 68

mode 8 bits, (WGM13=0, WGM12=1, WGM11=0, WGM10=1) giá trị TOP là 1 hằng số, TOP = 255 (số 8 bits lớn nhất). Với mode 6, tức mode 9 bits, (WGM13=0, WGM12=1, WGM11=1, WGM10=0) giá trị TOP là 1 hằng số, TOP = 511 (số 9 bits lớn nhất). Và với mode 7, tức mode 10 bits, (WGM13=0, WGM12=1, WGM11=1, WGM10=1) TOP =1023 (số 10 bits lớn nhất). Mode 15 cũng là Fast PWM trong đó TOP do OCR1A quy định, vì thế mà tín hiệu ra ở kênh A hầu như không phải là 1 xung, nó chỉ thay đổi trạng thái trong 1 clock. Theo tôi, để sử dụng Fast PWM bạn nên dùng mode 14 đã được giải thích trên. Các mode 5, 6, 7 cũng có thể dùng nhưng không nên dùng mode 15. Chúng ta tiến hành viết 1 ví dụ minh họa dùng 2 kênh ở chế độ fast PWM điều khiển 2 động cơ RC servo (gọi tắt là Servo). Mạch điện minh họa như trong hình 11.

Hình 11: Điều khiển 2 RC servo bằng PWM.

Hai button được nối với 2 ngõ ngắt ngoài INT0 và INT1 để điều khiển góc xoay của 2 Servo. Tên của Servo trong phần mềm Proteus là “MOTOR-PWMSERVO”. Trước khi viết code điều khiển các Servo, bạn cần biết cách điều khiển chúng, tôi giới thiệu ngắn gọn như sau: RC servo là một tổ hợp gồm 1 động cơ DC công suất nhỏ, hộp giảm tốc và bộ điều khiển góc quay. Có 2 loại chính là Servo thường và digital Servo, trong ví dụ này tôi giới thiệu Servo thường (phổ biến). Servo thường có 3 dây, dây màu đen là dây GND, dây đỏ là dây nguồn (thường là 5V) và 1 dây trắng hoặc vàng và dây điều khiển (có một số loại Servo có màu dây khác, bạn cần tham khảo datasheet của chúng). Vì các Servo đã có sẵn mạch điều khiển góc quay bên trong nên chúng ta không cần bất cứ giải thuật gì mà chỉ cần cấp tín hiệu PWM cho dây điều khiển là Servo có thể xoay đến 1 vị trí nào đó (chú ý là Servo thường chỉ xoay nữa vòng, điều khiển servo là điều khiển góc xoay chứ không phải điều khiển cận tốc xoay). Hình 12 là hình ảnh servo và cách điều khiển servo.

Page 69: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 69

Hình 12. Servo và cách điều khiển. Bạn xem hình 12b, để điều khiển servo bạn cần cấp cho dây điều khiển một tín hiệu PWM có Time Period khoảng 20ms, duty cycle của PWM sẽ quyết định góc xoay của servo. Với Duty cycle là 1ms, servo xoay về vị trí 0o, khi duty cycle =2ms, góc xoay sẽ là 180o, từ đó bạn có thể tính được duty cycle cần thiết khi bạn muốn servo xoay đến 1 vị trí bất kỳ giữa 0o và 180o. Sau khi hiểu cách điều khiển servo, chúng ta có thể dễ dàng viết code điều khiển chúng, chỉ cần tạo các xung PWM bằng T/C1. Đoạn code cho ví dụ này được trình bày trong list 5. List 5. Điều khiển Servo bằng PWM.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

#include <avr/io.h> #include <avr/interrupt.h> int main(void){ DDRB=0xFF; //PORTB la output PORT PORTB=0x00; MCUCR|=(1<<ISC11)|(1<<ISC01); //ngat canh xuong GICR |=((1<<INT1)|(1<<INT0); //cho phép 2 ngat hoat dong TCCR1A=(1<<COM1A1)|(1<<COM1B1)|(1<<WGM11); TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS10); OCR1A=1000; //Duty cycle servo1=1000us=1ms (0 degree) OCR1B=1500; //Duty cycle servo2=1500us=1.5ms (90 degree) ICR1=20000; //Time period = 20000us=20ms

Page 70: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 70

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

sei(); //set bit I cho phep ngat toan cuc while (1){ //vòng lặp vô tận //do nothing } return 0; } //trinh phuc vu ngat ngoai ISR (INT0_vect ){ if (OCR1A==1000) OCR1A=1500; //thay doi goc xoay servo1 den 90 do else OCR1A = 1000; // thay doi goc xoay servo1 den 0 do } ISR (INT1_vect ){ if (OCR1B==1000) OCR1B=1500; //thay doi goc xoay servo1 den 90 do else OCR1B = 1000; // thay doi goc xoay servo1 den 0 do }

Với ví dụ này tôi chỉ cần giải thích các dòng từ 11 đến 15 liên quan đến việc xác lập chế độ hoạt động Fast PWM mode 14 inverse, phần còn lại bạn đọc tự đối chiếu với các bài trước. Dòng 11 và 12 thực hiện set các bit điều khiển Timer1, trước hết là các bit COM. Bạn thấy tôi chỉ set 2 bit COM1A1 và COM1B1: (1<<COM1A1)|(1<<COM1B1). Hai bit COM1A0 và COM1B0 không set tức mặc định bằng 0. Đối chiếu với bảng 4 bạn thấy chúng ta sẽ dùng “Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at TOP” cho tất cả 2 kênh A và B. Chúng ta set 3 bit WGM13, WGM12 (thanh ghi TCCR1B, dòng 12) và WGM11 (thanh ghi TCCR1A, dòng 11) như thế thu được tổ hợp (WGM13=1, WGM12=1, WGM11=1, WGM10=0) tức là mode 14 được chọn (bảng 3). Còn lại chúng ta set bit CS10 để khai báo rằng nguồn xung clock cho Timer1 bằng clock cho vi điều khiển (prescaler=1) tức là 1us trong tường hợp f=1Mhz. (nếu bạn dùng các trình biên dịch khác không hỗ trợ định nghĩa tên các bit thì 2 dòng 11 và 12 tương đương: TCCR1A=0xA2; TCCR1B=0x19). Dòng 15 chúng ta khai nhập giá trị cho ICR1 cũng là Time period cho PWM, ICR1=20000 chúng ta thu được Time period =20000 us = 20ms thỏa yêu cầu của servo. Hai dòng 13 và 14 khai báo giá trị ban đầu của các duty cycle của 2 kênh PWM, các giá trị này định vị trí góc xoay của các servo. Trong 2 trình phục vụ ngắt, các giá trị này được thay đổi khi các button được nhấn.

Page 71: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 71

2.3 Fast PWM (PWM tần số cao). Phase correct PWM cung cấp một chế độ tạo xung PWM có độ phân giải cao (high resolution) nên được gọi là Phase correct PWM. Tương tự Fast PWM, cũng có 5 mode hoạt động thuộc Phase correct PWM đó là các mode 1, 2, 3, 10 và 11 (xem bảng 3). Năm mode này tương ứng các mode 5, 6, 7, 14 và 15 của fast PWM. Về cách điều khiển, Phase correct hầu như giống fast PWM, nghĩa là nếu bạn đã biết cách sử dụng các mode của fast PWM thì bạn sẽ hoàn toàn điều khiển được Phase correct PWM. Khác nhau cơ bản của 2 chế độ này là trong cách hoạt động, nếu Fast PWM có chu kỳ hoạt động trong 1 single-slope (một sườn) thì Phase correct PWM lại dual-slope (hai sườn). Lấy ví dụ mode 10 của Phase correct PWM tương ứng với mode 14 của Fast PWM, trong mode này thanh ghi ICR1 chứa TOP và OCR1A (hoặc OCR1B đối với kênh B) chứa giá trị so sánh. Khi hoạt động, thanh ghi TCNT1 tăng từ 0, khi TCNT1 bằng với OCR1A thì chân OC1A được xóa xuống mức LOW (tôi đang nói trường hợp COM1A1=1, COM1A0=0), TCNT1 tiếp tục tăng đến TOP, khi TCNT1=TOP thì TCNT1 KHÔNG được tự động reset về 0 như trường hợp Fast PWM mà TCNT1 bắt đầu đếm ngược, tức giảm từng giá trị từ TOP về 0. Trong lúc TCNT1 giảm, đến 1 lúc nó sẽ bằng giá trị của OCR1A lần thứ 2, và lần này, chân OC1A được set lên mức HIGH, TCNT1 tiếp tục giảm đến 0 thì 1 chu kỳ hoàn tất. Rõ ràng 1 chu kỳ là quá trình đếm trong 2 “sườn” nên ta gọi Phase correct PWM là dual-slope. Cũng vì tính chất dual-slope mà tín hiệu PWM trong chế độ này có tính đối xứng, thích hợp cho các ứng dụng điều khiển động cơ. Hình 13 mô tả cách mà Phase correct PWM hoạt động tron mode 10 với ngõ ra đảo (COM1A1=1, COM1A0=0).

Hình 13. Phase correct PWM mode 10.

Việc viết code cho chế độ Phase correct PWM gần như tương tự fast PWM, bạn chỉ cần thay đổi tổ hợp các bit WGM dựa theo bảng 3 và sau đó nhâp các giá trị phù hợp cho ICR1 và ORC1A, OCR1B là được. 2.3 Fast PWM (PWM tần số cao). Chế độ này có 2 mode là 8 và 9. Về hầu hết các phương diện, 2 mode này giống với 2 mode 10 và 11 của Phase correct PWM. Cái khác nhau duy nhất là thời

Page 72: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 72

điểm mà thanh ghi OCR1A và OCR1B được cập nhật dữ liệu nếu có sự thay đổi. Việc này, nhìn chung không ảnh hưởng đến hầu hết người dùng PWM để điều khiển. Bạn sẽ rất khó để thấy sự khác biệt nếu bạn không phải đang viết 1 ứng dụng mà sai số trong 1 micro giây là điều tệ hại. Vì thế tôi không đề cập chi tiết chế độ này, bạn đọc có thể tham khảo datasheet của chip để hiểu rõ hơn nếu cần thiết. Ngoài ra trên chip atmega8 còn có bộ timer2 8 bits có PWM và asynchronous operation. Về mặt chức năng timer2 giống như phiên bản 8 bit của timer1 (độ phân giải thấp hơn nhưng có cùng chế độ và phương thức hoạt động). Điểm khác biệt và cũng là điểm đặc biệt của Timer2 là khả năng hoạt động không đồng bộ với chip, nó giống như việc bạn tách timer2 ra thành 1 chip timer riêng, vì thế cần cung cấp 1 nguồn xung clock khác cho timer này (1 thạch anh khác). Chế độ này có thể được dùng để calip (calibrate), canh chỉnh sai số và bù cho nguồn xung clock chính trên chip.

Bài 5 – Giao Tiếp UART

I. Giới thiệu.

Bài này giúp các bạn biết cách sử dụng cách truyền thông nối tiếp UART trên AVR. Công cụ chính cũng là 2 bộ phần mềm quen thuộc WinAVR và Proteus nhưng trong bài này (và các bài sau nữa) chúng ta sẽ sử dụng chip Atmega32 làm chip minh họa. Về cơ bản việc thay đổi chip minh họa không ảnh hưởng lớn đến tính mạch lạc của loạt bài vì sự khác biệt của hai chip Atmega8 và Atmega32 là không đáng kể. Tuy nhiên, nếu có sự khác biệt lớn ở phần nào đó tôi sẽ kể ra cho bạn tiện so sánh.

Sau bài này, tôi hy vọng bạn có thể hiểu và thực hiện được:

Nguyên lý truyền thông nối tiếp đồng bộ và không đồng bộ.

Module truyền thông nối tiếp USART trên AVR.

Truyền thông đa xử lí bằng UART.

II. Truyền thông nối tiếp không đồng bộ.

Page 73: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 73

Thuật ngữ USART trong tiếng anh là viết tắt của cụm từ: Universal Synchronous & Asynchronous serial Reveiver and Transmitter, nghĩa là bộ truyền nhận nối tiếp đồng bộ và không đồng bộ. Cần chú ý rằng khái niệm USART (hay UART nếu chỉ nói đến bộ truyền nhận không đồng bộ) thường để chỉ thiết bị phần cứng (device, hardware), không phải chỉ một chuẩn giao tiếp. USART hay UART cần phải kết hợp với một thiết bị chuyển đổi mức điện áp để tạo ra một chuẩn giao tiếp nào đó. Ví dụ, chuẩn RS232 (hay COM) trên các máy tính cá nhân là sự kết hợp của chip UART và chip chuyển đổi mức điện áp. Tín hiệu từ chip UART thường theo mức TTL: mức logic high là 5, mức low là 0V. Trong khi đó, tín hiệu theo chuẩn RS232 trên máy tính cá nhân thường là -12V cho mức logic high và +12 cho mức low (tham khảo hình 1). Chú ý là các giải thích trong tài liệu này theo mức logic TTL của USART, không theo RS232.

Hình 1. Tín hiệu tương đương của UART và RS232.

Truyền thông nối tiếp: giả sử bạn đang xây dựng một ứng dụng phức tạp cần sử dụng nhiều vi điều khiển (hoặc vi điều khiển và máy tính) kết nối với nhau. Trong quá trình làm việc các vi điều khiển cần trao đổi dữ liệu cho nhau, ví dụ tình huống Master truyền lệnh cho Slaver hoặc Slaver gởi tín hiệu thu thập được về Master xử lí…Giả sử dữ liệu cần trao đổi là các mã có chiều dài 8 bits, bạn có thể sẽ nghĩ đến cách kết nối đơn giản nhất là kết nối 1 PORT (8 bit) của mỗi vi điều khiển với nhau, mỗi line trên PORT sẽ chịu trách nhiệm truyền/nhận 1 bit dữ liệu. Đây gọi là cách giao tiếp song song, cách này là cách đơn giản nhất vì dữ liệu được xuất và nhận trực tiếp không thông qua bất kỳ một giải thuật biến đổi nào và vì thế tốc độ truyền cũng rất nhanh. Tuy nhiên, như bạn thấy, nhược điểm của cách

Page 74: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 74

truyền này là số đường truyền quá nhiều, bạn hãy tưởng tượng nếu dữ liệu của bạn có giá trị càng lớn thì số đường truyền cũng sẽ nhiều thêm. Hệ thống truyền thông song song thường rất cồng kềnh và vì thế kém hiệu quả. Truyền thông nối tiếp sẽ giải quyết vần đề này, trong tuyền thông nối tiếp dữ liệu được truyền từng bit trên 1 (hoặc một ít) đường truyền. Vì lý do này, cho dù dữ liệu của bạn có lớn đến đâu bạn cũng chỉ dùng rất ít đường truyền. Hình 2 mô tả sự so sánh giữa 2 cách truyền song song và nối tiếp trong việc truyền con số 187 thập phân (tức 10111011 nhị phân).

Hình 2. Truyền 8 bit theo phương pháp song song và nối tiếp.

Một hạn chế rất dễ nhận thấy khi truyền nối tiếp so với song song là tốc độ truyền và độ chính xác của dữ liệu khi truyền và nhận. Vì dữ liệu cần được “chia nhỏ” thành từng bit khi truyền/nhận, tốc độ truyền sẽ bị giảm. Mặt khác, để đảm bảo tính chính xác của dữ liệu, bộ truyền và bộ nhận cần có những “thỏa hiệp” hay những tiêu chuẩn nhất định. Phần tiếp theo trong chương này giới thiệu các tiêu chuẩn trong truyền thông nối tiếp không đồng bộ. Khái niệm “đồng bộ” để chỉ sự “báo trước” trong quá trình truyền. Lấy ví dụ thiết bị 1 (tb1) kết với với thiết bị 2 (tb2) bởi 2 đường, một đường dữ liệu và 1 đường xung nhịp. Cứ mỗi lần tb1 muốn send 1 bit dữ liệu, tb1 điều khiển đường xung nhịp chuyển từ mức thấp lên mức cao báo cho tb2 sẵn sàng nhận một bit. Bằng cách “báo trước” này tất cả các bit dữ liệu có thể truyền/nhận dễ dàng với ít “rủi ro” trong quá trình truyền. Tuy nhiên, cách truyền này đòi hỏi ít nhất 2 đường truyền cho 1 quá trình (send or receive), tốc độ truyền cũng bị hạn chế vì đòi hỏi bên nhận phải liên tục “quan sát” đường xung nhịp trước khi đọc bất kỳ bit dữ liệu nào. Giao tiếp giữa máy tính và các bàn phím (trừ bàn phím kết nối theo chuẩn USB) là một ví dụ của cách truyền thông nối tiếp đồng bộ. Khác với cách truyền đồng bộ, truyền thông “không đồng bộ” chỉ cần một đường truyền cho một quá trình. “Khung dữ liệu” đã được chuẩn hóa bởi các thiết bị nên không cần đường xung nhịp báo trước dữ liệu đến. Ví dụ 2 thiết bị đang giao tiếp với nhau theo phương pháp này, chúng đã được thỏa thuận với nhau rằng cứ 1ms thì sẽ có 1 bit dữ liệu truyền đến, như thế thiết bị nhận chỉ cần kiểm tra và

Page 75: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 75

đọc đường truyền mỗi mili-giây để đọc các bit dữ liệu và sau đó kết hợp chúng lại thành dữ liệu có ý nghĩa. Truyền thông nối tiếp không đồng bộ vì thế nhanh và hiệu quả hơn truyền thông đồng bộ. Tuy nhiên, để quá trình truyền thành công thì việc tuân thủ các tiêu chuẩn truyền là hết sức quan trọng. Chúng ta sẽ bắt đầu tìm hiểu các khái niệm quan trọng trong phương pháp truyền thông này. Baud rate (tốc độ Baud): như trong ví dụ trên về việc truyền 1 bit trong 1ms, bạn thấy rằng để việc truyền và nhận không đồng bộ xảy ra thành công thì các thiết bị tham gia phải “thống nhất” nhau về khoảng thời dành cho 1 bit truyền, hay nói cách khác tốc độ truyền phải được cài đặt như nhau trước, tốc độ này gọi là tốc độ Baud. Theo định nghĩa, tốc độ baud là số bit truyền trong 1 giây. Ví dụ nếu tốc độ baud được đặt là 19200 thì thời gian dành cho 1 bit truyền là 1/19200 ~ 52.083us. Frame (khung truyền): do truyền thông nối tiếp mà nhất là nối tiếp không đồng bộ rất dễ mất hoặc sai lệch dữ liệu, quá trình truyền thông theo kiểu này phải tuân theo một số quy cách nhất định. Bên cạnh tốc độ baud, khung truyền là một yếu tốc quan trọng tạo nên sự thành công khi truyền và nhận. Khung truyền bao gồm các quy định về số bit trong mỗi lần truyền, các bit “báo” như bit Start và bit Stop, các bit kiểm tra như Parity, ngoài ra số lượng các bit trong một data cũng được quy định bởi khung truyền. Hình 1 là một ví dụ của một khung truyền theo UART, khung truyền này được bắt đầu bằng một start bit, tiếp theo là 8 bit data, sau đó là 1 bit parity dùng kiểm tra dữ liệu và cuối cùng là 2 bits stop. Start bit: start là bit đầu tiên được truyền trong một frame truyền, bit này có chức năng báo cho thiết bị nhận biết rằng có một gói dữ liệu sắp được truyền tới. Ở module USART trong AVR, đường truyền luôn ở trạng thái cao khi nghỉ (Idle), nếu một chip AVR muốn thực hiện việc truyền dữ liệu nó sẽ gởi một bit start bằng cách “kéo” đường truyền xuống mức 0. Như vậy, với AVR bit start là mang giá trị 0 và có giá trị điện áp 0V (với chuẩn RS232 giá trị điện áp của bit start là ngược lại). start là bit bắt buộc phải có trong khung truyền. Data: data hay dữ liệu cần truyền là thông tin chính mà chúng ta cần gởi và nhận. Data không nhất thiết phải là gói 8 bit, với AVR bạn có thể quy định số lượng bit của data là 5, 6, 7, 8 hoặc 9 (tương tự cho hầu hết các thiết bị hỗ trợ UART khác). Trong truyền thông nối tiếp UART, bit có ảnh hưởng nhỏ nhất (LSB – Least Significant Bit, bit bên phải) của data sẽ được truyền trước và cuối cùng là bit có ảnh hưởng lớn nhất (MSB – Most Significant Bit, bit bên trái). Parity bit: parity là bit dùng kiểm tra dữ liệu truyền đúng không (một cách tương đối). Có 2 loại parity là parity chẵn (even parity) và parity lẻ (odd parity). Parity chẵn nghĩa là số lượng số 1 trong dữ liệu bao gồm bit parity luôn là số chẵn. Ngược lại tổng số lượng các số 1 trong parity lẻ luôn là số lẻ. Ví dụ, nếu dữ liệu của bạn là 10111011 nhị phân, có tất cả 6 số 1 trong dữ liệu này, nếu parity chẵn được dùng, bit parity sẽ mang giá trị 0 để đảm bảo tổng các số 1 là số chẵn (6 số 1). Nếu parity lẻ được yêu cầu thì giá trị của parity bit là 1. Hình 1 mô tả ví dụ này

Page 76: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 76

với parity chẵn được sử dụng. Parity bit không phải là bit bắt buộc và vì thế chúng ta có thể loại bit này khỏi khung truyền (các ví dụ trong bài này tôi không dùng bit parity). Stop bits: stop bits là một hoặc các bit báo cho thiết bị nhận rằng một gói dữ liệu đã được gởi xong. Sau khi nhận được stop bits, thiết bị nhận sẽ tiến hành kiểm tra khung truyền để đảm bảo tính chính xác của dữ liệu. Stop bits là các bits bắt buộc xuất hiện trong khung truyền, trong AVR USART có thể là 1 hoặc 2 bits (Trong các thiết bị khác Stop bits có thể là 2.5 bits). Trong ví dụ ở hình 1, có 2 stop bits được dùng cho khung truyền.Giá trị của stop bit luôn là giá trị nghỉ (Idle) và là ngược với giá trị của start bit, giá trị stop bit trong AVR luôn là mức cao (5V). (Chú ý và gợi ý: khung truyền phổ biến nhất là : start bit+ 8 bit data+1 stop bit) Sau khi nắm bắt các khái niệm về truyền thông nối tiếp, phần tiếp theo chúng ta sẽ khảo sát cách thực hiện phương pháp truyền thông này trên chip AVR (cụ thể là chip Atmega32).

III. Truyền thông nối tiếp không đồng bộ với AVR (UART).

Vi điều khiển Atmega32 có 1 module truyền thông nối tiếp USART. Có 3 chân chính liên quan đến module này đó là chân xung nhịp - XCK (chân số 1), chân truyền dữ liệu – TxD (Transmitted Data) và chân nhận dữ liệu – RxD (Reveived Data). Trong đó chân XCK chỉ được sử dụng như là chân phát hoặc nhận xung giữ nhịp trong chế độ truyền động bộ. Tuy nhiên bài này chúng ta không khảo sát chế độ truyền thông đồng bộ, vì thế bạn chỉ cần quan tâm đến 2 chân TxD và RxD. Vì các chân truyền/nhận dữ liệu chỉ đảm nhiệm 1 chức năng độc lập (hoặc là truyền, hoặc là nhận), để kết nối các chip AVR với nhau (hoặc kết nối AVR với thiết bị hỗ trợ UART khác) bạn phải đấu “chéo” 2 chân này. TxD của thiết bị thứ nhất kết nối với RxD của thiết bị 2 và ngược lại. Module USART trên chip Atmega32 hoạt động “song công” (Full Duplex Operation), nghĩa là quá trình truyền và nhận dữ liệu có thể xảy ra đồng thời.

1. Thanh ghi:

Cũng như các thiết bị khác trên AVR, tất cả hoạt động và tráng thái của module USART được điều khiển và quan sát thông qua các thanh ghi trong vùng nhớ I/O. Có 5 thanh ghi được thiết kế riêng cho hoạt động và điều khiển của USART, đó là:

UDR: hay thanh ghi dữ liệu, là 1 thanh ghi 8 bit chứa giá trị nhận được và phát đi

của USART. Thực chất thanh ghi này có thể coi như 2 thanh ghi TXB (Transmit

Page 77: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 77

data Buffer) và RXB (Reveive data Buffer) có chung địa chỉ. Đọc UDR thu được

giá trị thanh ghi đệm dữ liệu nhận, viết giá trị vào UDR tương đương đặt giá trị vào

thanh ghi đệm phát, chuẩn bị để gởi đi. Chú ý trong các khung truyền sử dụng 5, 6

hoặc 7 bit dữ liệu, các bit cao của thanh ghi UDR sẽ không được sử dụng

UCSRA (USART Control and Status Register A): là 1 trong 3 thanh ghi điều

khiển hoạt động của module USART.

Thanh ghi UCSRA chủ yếu chứa các bit trạng thái như bit báo quá trình nhận kết thúc (RXC), truyền kết thúc (TXC), báo thanh ghi dữ liệu trống (UDRE), khung truyền có lỗi (FE), dữ liệu tràn (DOR), kiểm tra parity có lỗi (PE)…Bạn chú ý một số bit quan trọng của thanh ghi này: * UDRE (USART Data Register Empty) khi bit bày bằng 1 nghĩa là thanh ghi dữ liệu UDR đang trống và sẵn sàng cho một nhiệm vụ truyền hay nhận tiếp theo. Vì thế nếu bạn muốn truyền dữ liệu đầu tiên bạn phải kiểm tra xem bit UDRE có bằng 1 hay không, sau khi chắc chắn rằng UDRE=1 hãy viết dữ liệu vào thanh ghi UDR để truyền đi. * U2X là bit chỉ định gấp đôi tốc độ truyền, khi bit này được set lên 1, tốc độ truyền so cao gấp 2 lần so với khi bit này mang giá trị 0. * MPCM là bit chọn chế độ hoạt động đa xử lí (multi-processor).

UCSRB (USART Control and Status Register B): đây là thanh ghi quan trọng điều

khiển USART. Vì thế chúng ta sẽ khảo sát chi tiết từng bit của thanh ghi này.

Page 78: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 78

* RXCIE (Receive Complete Interrupt Enable) là bit cho phép ngắt khi quá trình nhận kết thúc. Việc nhận dữ liệu truyền bằng phương pháp nối tiếp không đồng bộ thường được thực hiện thông qua ngắt, vì thế bit này thường được set bằng 1 khi USART được dung nhận dữ liệu. * TXCIE (Transmit Complete Interrupt Enable) bit cho phép ngắt khi quá trình truyền kết thúc. * UDRIE (USART Data Register Empty Interrupt Enable) là bit cho phép ngắt khi thanh ghi dữ liệu UDR trống. * RXEN (Receiver Enable) là một bit quan trọng điều khiển bộ nhận của USART, đề kích hoạt chức năng nhận dữ liệu bạn phải set bit này lên 1. * TXEN (Transmitter Enable) là bit điều khiển bộ phát. Set bit này lên 1 bạn sẽ khởi động bộ phát của USART. * UCSZ2 (Chracter size) bit này kết hợp với 2 bit khác trong thanh ghi UCSRC quy định độ dài của dữ liệu truyền/nhận. Chúng ta sẽ khảo sát chi tiết khi tìm hiểu thanh ghi UCSRC. * RXB8 (Receive Data Bit 8) gọi là bit dữ liệu 8. Bạn nhớ lại rằng USART trong AVR có hỗ trợ truyền dữ liệu có độ dài tối đa 9 bit, trong khi thanh ghi dữ liệu là thanh ghi 8 bit. Do đó, khi có gói dữ liệu 9 bit được nhận, 8 bit đầu sẽ chứa trong thanh ghi UDR, cần có 1 bit khác đóng vai trò bit thứ chín, RXD8 là bit thứ chín này. Bạn chú ý là các bit được đánh số từ 0, vì thế bit thứ chín sẽ có chỉ số là 8, vì lẽ đó mà bit này có tên là RXD8 (không phải RXD9). * TXB8 (Transmit Data Bit 8), tương tự như bit RXD8, bit TXB8 cũng đóng vai trò bit thứ 9 truyền thông, nhưng bit này được dung trong lúc truyền dữ liệu.

UCSRC (USART Control and Status Register C): thanh ghi này chủ yếu quy định

khung truyền và chế độ truyền. Tuy nhiên, có một rắc rối nho nhỏ là thanh ghi này

lại có cùng địa chỉ với thanh ghi UBRRH (thanh ghi chứa byte cao dùng để xác lập

tốc độ baud), nói một cách khác 2 thanh ghi này là 1. Vì thế bit 7 trong thanh ghi

này, tức bit URSEL là bit chọn thanh ghi. Khi URSEL=1, thanh ghi này được chip

AVR hiểu là thanh ghi điều khiển UCSRC, nhưng nếu bit URSEL=0 thì thanh ghi

UBRRH sẽ được sử dụng.

Page 79: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 79

Các bit còn lại trong thanh ghi UCSRC được mô tả như sau: * UMSEL (USART Mode Select) là bit lựa chọn giữa 2 chế độ truyền thông đồng bộ và không đồng bộ. Nếu UMSEL=0, chế độ không đồng bộ được chọn, ngược lại nếu UMSEL=1, chế độ đồng bộ được kích hoạt. * Hai bit UPM1 và UPM0( Parity Mode) được dùng để quy định kiểm tra pariry. Nếu UPM1:0=00, parity không được sử dụng (mode này khá thông dụng), UPM1:0=01 không được sử dụng, UPM1:0=10 thì parity chẵn được dùng, UPM1:0=11 parity lẻ được sử dụng (xem thêm bảng 1). Bảng 1: chọn kiểm tra parity.

* USBS (Stop bit Select), bit Stop trong khung truyền bằng AVR USART có thể là 1 hoặc 2 bit, nếu USBS=0 thì Stop bit chỉ là 1 bit trong khi USBS=1 sẽ có 2 Stop bit được dùng. * Hai bit UCSZ1 và UCSZ2 (Character Size) kết hợp với bit UCSZ2 trong thanh ghi UCSRB tạo thành 3 bit quy định độ dài dữ liệu truyền. Bảng 2 tóm tắt các giá trị có thể có của tổ hợp 3 bit này và độ dài dữ liệu truyền tương ứng. Bảng 2: độ dài dữ liệu truyền.

* UCPOL (Clock Pority) là bit chỉ cực của xung kích trong chế độ truyền thông đồng bộ. nếu UCPOL=0, dữ liệu sẽ thay đổi thay đổi ở cạnh lên của xung nhịp, nếu UCPOL=1, dữ liệu thay đổi ở cạnh xuống xung nhịp. Nếu bạn sử dụng chế độ truyền thông không đồng bộ, hãy set bit này bằng 0..

Page 80: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 80

UBRRL và UBRRH (USART Baud Rate Register): 2 thanh ghi thấp và cao quy

định tốc độ baud.

Nhắc lại là thanh ghi UBRRH dùng chung địa chỉ thanh ghi UCSRC, bạn phải set bit này bằng 0 nếu muốn sử dụng thanh ghi UBRRH. Như bạn quan sát trong hình trên, chỉ có 4 bit thấp của UBRRH được dùng, 4 bit này kết hợp với 8 bit trong thanh ghi UBRRL tạo thành thanh ghi 12 bit quy định tốc độ baud. Chú ý là nếu bạn viết giá trị vào thanh ghi UBRRL, tốc độ baud sẽ tức thì được cập nhật, vì thế bạn phải viết giá trị vào thanh ghi UBRRH trước khi viết vào thanh ghi UBRRL. Giá trị gán cho thanh ghi UBRR không phải là tốc độ baud, nó chỉ được USART dùng để tính tốc độ baud. Bảng 3 hướng dẫn cách tính tốc độ baud dựa vào giá trị của thanh ghi UBRR và ngược lại, cách tính giá trị cần thiết gán cho thanh ghi UBRR khi đã biết tốc độ baud. Bảng 3: tính tốc độ baud.

Trong các công thức trong bảng 3, fOSC là tốc tần số xung nhịp của hệ thống (thạch anh hay nguồn xung nội…). Để tiện cho bạn theo dõi, tôi đính kèm bảng ví dụ cách đặt giá trị cho UBRR theo tốc độ baud mẫu. Bảng 4: một số tốc độ baud mẫu.

Page 81: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 81

Page 82: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 82

2. Sử dụng UART:.

Thông thường, để sử dụng module USART trên AVR bạn phải thực hiện 3 việc quan trọng, đó là: cài đặt tốc độ baud (thanh ghi UBRR), định dạng khung truyền (UCSRB, UCSRC) và cuối cùng kích hoạt bộ truyền, bộ nhận, ngắt…Như đã đề cập, trong tài liệu này tôi chủ yếu đề cập đến phương pháp truyền thông không đồng bộ, việc xác lập các thông số hoạt động chủ yếu dựa trên chế độ này.

Page 83: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 83

Trong hầu hết các ứng dụng, tốc độ baud và khung truyền thường không đổi, trong trường hợp này chúng ta có thể khởi tạo trực tiếp USART ở phần đầu trong main và sau đó chỉ cần truyền hoặc nhận dữ liệu mà không cần thay đổi các cài đặt. Tuy nhiên, nếu trường hợp giao tiếp “linh hoạt” ví dụ bạn đang chế tạo một thiết bị có khả năng giao tiếp với một thiết bị đầu cuối khác (như máy tính chẳng hạn), lúc này bạn nên cho phép người dùng thay đổi tốc độ baud hoặc các thông số khác để phù hợp với thiết bị đầu cuối. Đối với những ứng dụng kiểu này bạn nên viết 1 chương trình con để khởi động USART và có thể gọi lại nhiều lần khi cần thay đổi. Phần tiếp theo chúng ta sẽ viết một số chương trình ví dụ minh họa cách sử dụng module truyền thông USART từ đơn giản đến phức tạp. Các ví dụ sẽ được thực hiện cho chip Atmega32 với giả sử nguồn xung nhịp hệ thống là 8MHz.

2.1 Truyền dữ liệu.

Trước hết chúng ta sẽ thực hiện một ví dụ rất đơn giản để hiểu cách khởi động USART và truyền các gói dữ liệu 8 bit. Mạch điện mô phỏng trong hình 3. Giả sử chúng ta muốn định dạng cho khung truyền gồm 1 bit start, 8 bit dữ liệu, không kiểm tra parity và 1 bit stop. Tốc độ baud 57600 (57.6k). Dữ liệu cần truyền là các giá trị liên tục của bảng mã ASCII. Đoạn code trong list 1 trình bày cách thực hiện ví dụ này.

List 1. Khởi động và truyền dữ liệu không đồng bộ bằng USART

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

#include <avr/io.h> #include <avr/delay.h> //chuong trinh con phat du lieu void uart_char_tx(unsigned char chr){ while (bit_is_clear(UCSRA,UDRE)) {}; //cho den khi bit UDRE=1 UDR=chr; } int main(void){ //set baud, 57.6k ung voi f=8Mhz, xem bang 70 trang 165, Atmega32 datasheet UBRRH=0; UBRRL=8; //set khung truyen va kich hoat bo nhan du lieu UCSRA=0x00; UCSRC=(1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); UCSRB=(1<<TXEN);

Page 84: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 84

19 20 21 22 23 24 25 26

while(1){ for (char i=32; i<128; i++){ uart_char_tx(i); //phat du lieu _delay_ms(100); } } }

Trước hết tôi sẽ giải thích cách khởi động USART trong các dòng code từ 12 đến 18. Nếu bạn xem lại bảng 3 trong trang 9 của tài liệu này (hoặc bảng 70, trang 165 datasheet của chip atmega32), ứng với tần số xung nhịp 8Hhz, không sử dụng chế độ nhân đôi tốc độ (U2X=0), để đạt được tốc bộ baud 57600 thì giá trị cần gán cho thanh ghi UBRR là 8 (xem cột 2, bảng 3). Hai dòng 12 và 13 trong list 1 thực hiện gán 8 cho thanh ghi UBRR thông qua 2 thanh ghi UBRRH và UBRRL. Trong dòng 16, thanh ghi UCSRA được gán bằng 0. Nếu bạn xem lại phần giải thích bạn sẽ thấy thanh ghi UCSRA chủ yếu chứa các bit trạng thái, riêng 2 bit U2X và MPCM là 2 bit điều khiển, 2 bit này bằng 0 nghĩa là chúng ta không sử dụng chế độ nhân đôi tốc độ và không sử dụng truyền thông đa xử lí. Phần quan trọng nhất chính là đặt giá trị cho 2 thanh ghi USCRB và UCSRC. Với thanh ghi UCSRC (dòng 17) trước hết chúng ta phải set bit URSEL để báo rằng chúng ta không muốn truy cập thanh ghi UBRRH mà là thanh ghi UCSRC (2 thanh ghi này có cùng địa chỉ), tiếp theo chúng ta chỉ set 1 cho 2 bit UCSZ1 và UCSZ0, bạn xem lại bảng 2 để thấy rằng nếu UCSZ1=1, UCSZ0=1 cùng với việc bit UCSZ2=0(nằm trong thanh ghi UCSRB) thì độ dài dữ liệu truyền được chọn là 8 bit. Các bit trong thanh ghi UCSRC không được set sẽ mặc định mang giá trị 0, bao gồm UMSEL = 0 (chế độ truyền thông không đồng bộ), UPM1:0=00 ( không sử dụng kiểm tra parity, xem bảng 1), USBS=0 (1 bit stop) và UCPOL=0 (bit này không sử dụng khi truyền không đồng bộ). Sau cùng, trong dòng 18, chúng ta chỉ set bit TXEN =1 nghĩa là chỉ kích hoạt bộ phát dữ liệu, các thành phần khác như bộ nhận, các ngắt…không được sử dụng trong ví dụ này.

Trong các bài trước tôi đã giới thiệu bạn về trình phục vụ ngắt và trong phần này tôi sẽ trình bày cách viết một chương trình con bằng ngôn ngữ C trong WinAVR, đó là đoạn chương trình uart_char_tx ở dòng 5. Chương trình con là 1 đoạn code bao gồm các câu lệnh cùng thực hiện một nhiệm vụ chung cụ thể nào đó. Trong trường hợp này là nhiệm vụ truyền 1 tham số 8 bit ra đường TxD của USART thông qua thanh ghi UDR. Như trình bày trong phần mô tả bit UDRE của thanh ghi UCSRA, quá trình truyền chỉ được bắt đầu khi bit UDRE bằng 1, vì thế dòng code 6 làm nhiệm vụ kiểm tra bit UDRE, câu lệnh while

Page 85: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 85

(bit_is_clear(UCSRA,UDRE)) {}; được hiểu là quá trình lặp sẽ “lẩn quẩn” nếu bit UDRE bằng 0 (bit_is_clear). Khi bit UDRE bằng 1 thì dòng code 7 sẽ xuất biến chr ra thanh ghi UDR cũng là xuất ra chân TxD của module USART. Trong ngôn ngữ C có 2 cách cơ bản để viết chương trình con. Với cách 1 chương trình con được khai báo và viết trực tiếp phía trước chương trình chính main như cách mà tôi thực hiện trong ví dụ 1 này. Cách viết này đễ hiểu và thích hợp cho các đoạn chương trình con ngắn nhưng chúng có thể làm tổng quan chương trình của bạn trở nên rắc rối khi có quá nhiểu chương trình con viết trước main. Bạn có thể khắc phục nhược điểm này bằng cách đặt các chương trình con phía sau main như cách mà chúng ta đã làm với các trình phục vụ ngắt. Nếu theo đúng quy cách của ngôn ngữ C, khi đặt chương trình con sau main bạn phải khai báo tên chương trình phía trước main, nếu bạn đặt chương trình con uart_char_tx phía sau main thì phần trước main bạn sẽ đặt dòng khai báo trước: void uart_char_tx(unsigned char chr);. Tuy WinAVR cho phép bạn bỏ qua khai báo trước này nhưng tôi khuyên bạn nên viết đúng cách để tạo thói quen và cũng như để dễ chuyển chương trình sang các trình biên dịch C khác sau này nếu cần thiết. Phần cuối cùng trong đoạn code là gọi lại chương trình uart_char_tx để truyền các dữ liệu là các số từ 32 đến 127.

Để thực hiện mô phỏng bằng proteus bạn hãy vẽ một mạch điện đơn giản như trong hình 3. Chip Atmega32 có thể được tìm với từ khóa mega32. Trong mạch điện mô phỏng có một thiết bị đầu cuối ảo (Virtual Terminal) là một thiết bị kết nối và hiển thị kết quả truyền thông không đồng bộ, chúng ta dùng để kiểm tra dữ liệu được truyền bằng chip AVR. Bạn có thể tìm thiết bị này trong trong danh sách các dụ cụ ảo (virtual instruments), nhấn vào nút công cụ và sau đó chọn terminal trong danh sách để chọn thiết bị đầu cuối ảo. Kết nối thiết bị ảo với chip Atmega32 như trong hình 3, chú ý là phải “đấu chéo” 2 chân TxD và RxD. Bên cạnh việc gán chương trình cho chip AVR, bạn phải set thông số cho thiết bị ảo trước khi thực hiện mô phỏng. Hãy mở hộp thoại “edit component” của thiết bị ảo (bằng cách right click rồi left click trên thiết bị ảo). Theo mặc định thiết bị đầu cuối được định dạng khung truyền là 1 bit start+8 bit dữ liệu+1 bit stop tương tự như cách chúng ta cài đặt cho AVR trong vì dụ 1, vì thế bạn chỉ cần thay đổi tốc độ baud thành 57600 trong hộp thoại “edit component” là hoàn tất (xem hình 4). Khi chạy mô phỏng, thiết bị đầu cuối ảo sẽ hiển thị các ký tự ASCII của các số từ 32 đến 127.

Page 86: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 86

Hình 3. Mô phỏng ví dụ 1.

Hình 4. Cài đặt thông số cho thiết bị ảo.

2.2 Nhận dữ liệu.

Page 87: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 87

Quá trình nhận dữ liệu chỉ xảy ra khi bit RXEN trong thanh ghi UCSRB được set bằng 1 và tất nhiên chân nhận dữ liệu RxD phải được nối với một nguồn phát (chân TxD của một chip UART khác chẳng hạn). Các thông số truyền thông như tốc độ baud và khung truyền trong bộ nhận phải được cài đặt như của bộ phát. Nếu không có lỗi trong quá trình truyền và nhận dữ liệu, sau khi nhận dữ liệu sẽ được chứa trong thanh ghi UDR và bit RXC (Reveice Complete) trong thanh ghi UCSRA sẽ tự động được set lên 1. Sau khi thanh ghi UDR được đọc, bit RXC lại tự động reset về 0 để chuẩn bị cho quá trình nhận dữ liệu kế tiếp. Như thế về cơ bản chúng ta có 2 cách đọc dữ liệu nhận về. Cách thứ nhất là cách hỏi vòng (polling), kiểm tra nếu bit RXC = 1 thì đọc giá trị thanh ghi UDR (và đọc cả bit RXB8 trong thanh ghi UCSRB nếu frame truyền 9 bit được dùng). Cách thứ hai là sử dụng ngắt “nhận hoàn tất” (Receive Complete Interrupt), bằng cách set bit cho pháp ngắt nhận hoàn tất, tức bit RXCIE trong thanh ghi UCSRB, và bit cho phép ngắt toàn cục (bit I, xem lại bài 3) thì một ngắt sẽ xảy ra khi dữ liệu đã được nhận và chứa trong thanh ghi UDR, chúng ta chỉ cần đọc giá trị của thanh ghi UDR trong trình phục vụ ngắt là xong. Theo kinh nghiệm, sử dụng ngắt là phương pháp tốt nhất cho đa số các trường hợp nhận dữ liệu UART, vì chúng ta không cần quan tâm thời điểm mà dữ liệu gởi đến, tránh lãng phí thời gian dành cho việc “hỏi vòng”. Vì thế trong phần tiếp theo tôi sẽ trình bày một ví dụ minh họa quá trình nhận dữ liệu bằng phương pháp ngắt. Để phục vụ cho ví dụ này, chúng ta sẽ khảo sát một mạch mô phỏng gồm 2 chip Atmega32 nối với nhau qua các đường TxD và RxD. Chip thứ là chip phát dữ liệu, nhiệm vụ của chip này là phát chuỗi dữ liệu từ 32 đến 127 như chip Atmega32 trong ví dụ 1. Chân phát TxD của chip 1 sẽ được nối với chân nhận RxD của chip thứ 2 (chip thứ 2 được gọi là chip nhận dữ liệu). Chip thứ 2 sau khi nhận dữ liệu sẽ phát dữ liệu này ra chân TxD của chính nó để có thể hiển thị lên thiết bị đầu cuối ảo cho chúng qua quan sát và so sánh kết quả. Bạn xem mạch điện mô phỏng trong hình 5 để hiểu rõ hơn. Chúng ta sử dụng đoạn code trong ví dụ 1 cho chip thứ nhất vì thế chỉ cần viết đoạn code nhận và phát lại dữ liệu cho chip thứ hai. List 2 trình bày đoạn code cho chip thứ hai..

List 2. Nhận dữ liệu USART không đồng bộ bằng phương pháp ngắt.

1 2 3 4 5 6 7 8

#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> //chuong trinh con phat du lieu void uart_char_tx(unsigned char chr){ while (bit_is_clear(UCSRA,UDRE)) {}; //cho den khi bit UDRE=1 UDR=chr; }

Page 88: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 88

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

volatile unsigned char u_Data; int main(void){ //set baud, 57.6k ung voi f=8Mhz, xem bang 70 trang 165, Atmega32 datasheet UBRRH=0; UBRRL=8; //set khung truyen va kich hoat bo nhan du lieu UCSRA=0x00; UCSRC=(1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); UCSRB=(1<<RXEN)|(1<<TXEN)|(1<<RXCIE);//cho phep ca 2 qua trinh nhan va//truyen, va cho phep ngat sau khi nhan xong sei(); //cho phep ngat toan cuc while(1){ } } ISR(SIG_UART_RECV){ //trinh phuc vu ngat USART hoan tat nhan u_Data=UDR; uart_char_tx(u_Data); }

Đoạn code trong ví dụ nhận và phát dữ liệu không khác đoạn code trong ví dụ 1 là mấy. Ở dòng thứ 3 tôi include file header interrupt.h vì chúng ta sẽ sử dụng ngắt để nhận dữ liệu. Chúng ta khai báo một biến u_Data dạng 8 bit không dấu để lưu dữ liệu nhận được, do biến này sẽ được truy cập trong trình phục vụ ngắt nên chúng ta đặt attribute volatile (dòng 9). Điểm quan trọng khi khởi động UART trong ví dụ này là dòng code 18, nếu trong ví dụ 1 chúng ta chỉ khởi động duy nhất bộ phát bằng cách set bit TXEN trong thanh ghi UCSRB (UCSRB=(1<<TXEN);) thì trong ví dụ này chúng ta set thêm 2 bit cho phép nhận RXEN và cho phép ngắt RXCIE trong thanh ghi UCSRB. Bit RXEN khởi động bộ nhận và bit RXCIE khởi động chế độ ngắt khi dữ liệu đã nhận trong UDR, tuy nhiên để có thể sử dụng ngắt, chúng ta cần set them bit I trong thanh ghi trạng thái bằng dòng code 20 (sei();). Phần quan trọng nhất trong đoạn code trên là trình phục ngắt nhận dữ liệu ISR. Khi dữ liệu đã được nhận đầy trong UDR, trình ngắt ISR(SIG_UART_RECV) sẽ được thực hiện, chúng ta sẽ đọc giá trị vừa nhận được vào biến u_Data (dòng 26) và sau đó phát giá trị này ra chân TxD để hiển thị lên thiết bị đầu cuối ảo bằng dòng lệnh 27.

Phần mạch điện mô phỏng được trình bày trong hình 5. Chương trình cho chip TRANSMITTER là chương trình trong ví dụ 1 và chương trình cho chip

Page 89: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 89

RECEIVER là chương trình trong đoạn code trên. Bạn phải set xung clock cho cả 2 chip là 8MHz và set tốc độ baud cho thiết bị đầu cuối ảo là 56700. Nếu khi chạy mô phỏng, thiết bị đầu cuối hiển thị các ký tự ASCII của các số từ 32 đến 127 như trong hình 5 thì mọi thứ đã được thực hiện chính xác.

Hình 5. Truyền và nhận bằng UART.

Bài 6 – Chuyển đổi ADC

Page 90: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 90

I. Bạn sẽ đi đến đâu.

Bài học này, như tên của nó, sẽ giới thiệu cách sử dụng bộ chuyển đổi tương tự - số (analog to digital converter - ADC). Công cụ chính cũng là 2 bộ phần mềm quen thuộc WinAVR và Proteus.

Sau bài này, tôi hy vọng bạn có thể hiểu và thực hiện được: - Nguyên lý chuyển đổi AD. - Chuyển đổi ADC đơn kênh trên AVR. - Sử dụng chuyển đổi ADC đơn kênh trên AVR, hiển thị số 4 digit bằng LED 7 đoạn.

II. Chuyển đổi dữ liệu tương tự (analog) sang dữ liệu số (digital).

Trong các ứng dụng đo lường và điều khiển bằng vi điều khiển bộ chuyển đổi tương tự-số (ADC) là một thành phần rất quan trọng. Dữ liệu trong thế giới của chúng ta là các dữ liệu tương tự (analog). Ví dụ nhiệt độ không khí buổi sáng là 25oC và buổi trưa là 32oC, giữa hai mức giá trị này có vô số các giá trị liên tục mà nhiệt độ phải “đi qua” để có thể đạt mức 32oC từ 25oC, đại lượng nhiệt độ như thế gọi là một đại lượng analog. Trong khi đó, rõ ràng vi điều khiển là một thiết bị số (digital), các giá trị mà một vi điều khiển có thể thao tác là các con số rời rạc vì thực chất chúng được tạo thành từ sự kết hợp của hai mức 0 và 1. Ví dụ chúng ta muốn dùng một thanh ghi 8 bit trong vi điều khiển để lưu lại các giá trị nhiệt độ từ 0oC đến 255 oC, như chúng ta đã biết, một thanh ghi 8 bit có thể chứa tối đa 256 (28) giá trị nguyên từ 0 đến 255, như thế các mức nhiệt độ không nguyên như 28.123 oC sẽ không được ghi lại. Nói cách khác, chúng ta đã “số hóa” (digitalize) một dữ liệu analog thành một dữ liệu digital. Quá trình “số hóa” này thường được thực hiện bởi một thiết bị gọi là “bộ chuyển đổi tương tự - số hay đơn giản là ADC (Analog to Digital Converter).

Có rất nhiều phương pháp chuyển đổi ADC, tôi không có ý định giải thích cụ thể các nguyên lý chuyển đổi này trong bài học về AVR, tuy nhiên tôi sẽ giới thiệu một cách chuyển đổi rất cơ bản và phổ biến để các bạn phần nào nắm được cách mà một bộ ADC làm việc. Phương pháp chuyển đổi mà tôi nói là phương pháp chuyển đổi trực tiếp (direct converting) hoặc flash ADC. Các bộ chuyển đổi ADC theo phương pháp này được cấu thành từ một dãy các bộ so sánh (như opamp), các bộ so sánh được mắc song song và được kết nối trực tiếp với tín hiệu analog cần chuyển đổi. Một điện áp tham chiếu (reference) và một mạch chia áp được sử dụng để tạo ra các mức điện áp so sánh khác nhau cho mỗi bộ so sánh. Hình 1 mô tả một bộ chuyển đổi flash ADC có 4 bộ so sánh, Vin là tín hiệu analog cần chuyển đổi và giá trị sau chuyển đổi là các con số tạo thành từ sự kết hợp các mức nhị phân trên các chân Vo. Trong hình 1, bạn thấy rằng do anh hưởng của mạch chia

Page 91: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 91

áp (các điện trở mắc nối tiếp từ điện áp +15V đến ground), điện áp trên chân âm (chân -) của các bộ so sánh sẽ khác nhau. Trong lúc chuyển đổi, giả sử điện áp Vin lớn hơn điện áp “V-“ của bộ so sánh 1 (opamp ở phía thấp nhất trong mạch) nhưng lại nhỏ hơn điện áp V- của các bộ so sánh khác, khi đó ngõ Vo1 ở mức 1 và các ngõ Vo khác ở mức 0, chúng ta thu được một kết quả số. Một cách tương tự, nếu tăng điện áp Vin ta thu được các tổ hợp số khác nhau. Với mạch điện có 4 bộ so sánh như trong hình 1, sẽ có tất cả 5 trường hợp có thể xảy ra, hay nói theo cách khác điện áp analog Vin được chia thành 5 mức số khác nhau. Tuy nhiên, bạn chú ý là các ngõ Vo không phải là các bit của tín hiệu số ngõ ra, chúng chỉ là đại diện để tổ hợp thành tín hiệu số ngõ ra, dễ hiểu hơn chúng ta không sử dụng được các bit Vo trực tiếp mà cần một bộ giải mã (decoder). Trong bảng 1 tôi trình bày kết quả sau khi giải mã ứng với các tổ hợp của các ngõ Vo.

Hình 1. Mạch flash ADC với 4 bộ so sánh.

Bảng 1 Giá trị số ngõ ra sau khi giải mã.

Page 92: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 92

Độ phân giải (resolution): như trong ví dụ trên, nếu mạch điện có 4 bộ so sánh, ngõ ra digital sẽ có 5 mức giá trị. Tương tự nếu mạch điện có 7 bộ so sánh thì sẽ có 8 mức giá trị có thể ở ngõ ra digital, khoảng cách giữa các mức tín hiệu trong trường hợp 8 mức sẽ nhỏ hơn trường hợp 4 mức. Nói cách khác, mạch chuyển đổi với 7 bộ so sánh có giá trị digital ngõ ra “mịn” hơn khi chỉ có 4 bộ, độ “mịn” càng cao tức độ phân giải (resolution) càng lớn. Khái niệm độ phân giải được dùng để chỉ số bit cần thiết để chứa hết các mức giá trị digital ngõ ra. Trong trường hợp có 8 mức giá trị ngõ ra, chúng ta cần 3 bit nhị phân để mã hóa hết các giá trị này, vì thế mạch chuyển đổi ADC với 7 bộ so sánh sẽ có độ phân giải là 3 bit. Một cách tổng quát, nếu một mạch chuyển đổi ADC có độ phân giải n bit thì sẽ có 2n mức giá trị có thể có ở ngõ ra digital. Để tạo ra một mạch chuyển đổi flash ADC có độ phân giải n bit, chúng ta cần đến 2n-1 bộ so sánh, giá trị này rất lớn khi thiết kế bộ chuyển đổi ADC có độ phân giải cao, vì thế các bộ chuyển đổi flash ADC thường có độ phân giải ít hơn 8 bit. Độ phân giải liên quan mật thiết đến chất lượng chuyển đổi ADC, việc lựa chọn độ phân giải phải phù hợp với độ chính xác yêu cầu và khả năng xử lý của bô điều khiển. Trong 2 mô tả một ví dụ “số hóa” một hàm sin analog thành dạng digital.

Hình 2. Analog và digital của hàm sin.

Page 93: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 93

Điện áp tham chiếu (reference voltage): Cùng một bộ chuyển đổi ADC nhưng có người muốn dùng cho các mức điện áp khác nhau, ví dụ người A muốn chuyển đổi điện áp trong khoảng 0-1V trong khi người B muốn dùng cho điện áp từ 0V đến 5V. Rõ ràng nếu hai người này dùng 2 bộ chuyển đổi ADC đều có khả năng chuyển đổi đến điện áp 5V thì người A đang “phí phạm” tính chính xác của thiết bị. Vấn đề sẽ được giải quyết bằng một đại lượng gọi là điện áp tham chiếu - Vref (reference voltage). Điện áp tham chiếu thường là giá trị điện áp lớn nhất mà bộ ADC có thể chuyển đổi. Trong các bộ ADC, Vref thường là thông số được đặt bởi người dùng, nó là điện áp lớn nhất mà thiết bị có thể chuyển đổi. Ví dụ, một bộ ADC 10 bit (độ phân giải) có Vref=3V, nếu điện áp ở ngõ vào là 1V thì giá trị số thu được sau khi chuyển đổi sẽ là: 1023x(1/3)=314. Trong đó 1023 là giá trị lớn nhất mà một bộ ADC 10 bit có thể tạo ra (1023=210-1). Vì điện áp tham chiếu ảnh hưởng đến độ chính xác của quá trình chuyển đổi, chúng ta cần tính toán để chọn 1 điện áp tham chiếu phù hợp, không được nhỏ hơn giá trị lớn nhất của input nhưng cũng đừng quá lớn.

II. Chuyển đổi ADC trên AVR.

Chip AVR ATmega32 của Atmel có tích hợp sẵn các bộ chuyển đổi ADC với độ phân giải 10 bit. Có tất cả 8 kênh đơn (các chân ADC0 đến ADC7), 16 tổ hợp chuyển đổi dạng so sánh, trong đó có 2 kênh so sánh có thể khuyếch đại. Bộ chuyển đổi ADC trên AVR không hoạt động theo nguyên lý flash ADC mà tôi đề cập ở phần trên, ADC trong AVR là loại chuyển đổi xấp xỉ lần lượt (successive approximation ADC).

ADC trên AVR cần được “nuôi” bằng nguồn điện áp riêng ở chân AVCC, giá trị điện áp cấp cho AVCC không được khác nguồn nuôi chip (VCC) quá +/-0.3V. Nhiễu (noise) là vấn đề rất quan trọng khi sử dụng các bộ ADC, để giảm thiểu sai số chuyển đổi do nhiễu, nguồn cấp cho ADC cần phải được “lọc” (filter) kỹ càng. Một cách đơn giản để tạo nguồn AVCC là dùng một mạch LC kết nối từ nguồn VCC của chip như minh họa trong hình 3, đây là cách được gợi ý bởi nhà sản xuất AVR.

Page 94: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 94

Hình 3. Tạo nguồn AVCC từ VCC.

Điện áp tham chiếu cho ADC trên AVR có thể được tạo bởi 3 nguồn: dùng điện áp tham chiếu nội 2.56V (cố định), dùng điện áp AVCC hoặc điện áp ngoài đặt trên chân VREF. Một lần nữa, bạn cần chú ý đến noise khi đặt điện áp tham chiếu, nếu dùng điện áp ngoài đặt trên chân VREF thì điện áp này phải được lọc thật tốt, nếu dùng điện áp tham chiếu nội 2.56V hoặc AVCC thì chân VREF cần được nối với một tụ điện. Việc chọn điện áp tham chiếu sẽ được đề cập chi tiết trong phần sử dụng ADC. Các chân trên PORTA của chip ATmega32 được dùng cho bộ ADC, chân PA0 tương ứng kênh ADC0 và chân PA7 tương ứng với kênh ADC7.

Page 95: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 95

1. Thanh ghi.

Có 4 thanh trong bộ ADC trên AVR trong đó có 2 thanh ghi data chứa dữ liệu sau khi chuyển đổi, 2 thanh ghi điều khiển và chứa trạng thái của ADC.

- ADMUX (ADC Multiplexer Selection Register): là 1 thanh ghi 8 bit điều khiển việc chọn điện áp tham chiếu, kênh và chế độ hoạt động của ADC. Chức năng của từng bit trên thanh ghi này sẽ được trình bày cụ thể như sau:

Bit 7:6- REFS1:0 (Reference Selection Bits): là các bit chọn điện áp tham chiếu

cho ADC, 1 trong 3 nguồn điện áp tham chiếu có thể được chọn là: điện áp ngoài

từ chân VREF, điện áp tham chiếu nội 2.56V hoặc điện áp AVCC. Bảng 2 tóm tắt

giá trị các bit và điện áp tham chiếu tương ứng.

Bảng 2: Chọn điện áp tham chiếu

Bit 5-ADLAR (ADC Left Adjust Result): là bit cho phép hiệu chỉnh trái kết quả

chuyển đổi. Sở dĩ có bit này là vì ADC trên AVR có độ phân giải 10 bit, nghĩa là

kết quả thu được sau chuyển đổi là 1 số có độ dài 10 bit (tối đa 1023), AVR bố trí

2 thanh ghi data 8 bit để chứa giá trị sau chuyển đổi. Như thế giá trị chuyển đổi sẽ

không lắp đầy 2 thanh ghi data, trong một số trường hợp người dùng muốn 10 bit

kết quả nằm lệch về phía trái trong khi cũng có trường hợp người dùng muốn kết

Page 96: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 96

quả nằm về phía phải. Bit ADLAR sẽ quyết định vị trí của 10 bit kết quả trong 16

bit của 2 thanh ghi data. Nếu ADLAR=0 kết quả sẽ được hiệu chỉnh về phía phải

(thanh ghi ADCL chứa trọn 8 bit thấp và thanh ghi ADCH chứa 2 bit cao trong 10

bit kết quả), và nếu ADLAR=1 thì kết quả được hiệu chỉnh trái (thanh ghi ADCH

chứa trọn 8 bit cao nhất, các bit từ 9 đến 2, và thanh ADCL chứa 2 bit thấp nhất

trong 10 bit kết quả (bạn xem hình cách bố trí 2 thanh ghi ADCL và ADCH bên

dưới để hiểu rõ hơn).

Bits 4:0-MUX4:0 (Analog Channel and Gain Selection Bits): là 5 bit cho phép

chọn kênh, chế độ và cả hệ số khuyếch đại cho ADC. Do bộ ADC trên AVR có

nhiều kênh và cho phép thực hiện chuyển đổi ADC kiểu so sánh (so sánh điện áp

giữa 2 chân analog) nên trước khi thực hiện chuyển đổi, chúng ta cần set các bit

MUX để chọn kênh và chế độ cần sử dụng. Bảng 3 tóm tắt các chế độ hoạt động

của ADC thông qua các giá trị của các bit MUX. Trong bảng này, ứng với các giá

trị từ 00000 đến 00111 (nhị phân), các kênh ADC được chọn ở chế độ đơn kênh

(tín hiệu input lấy trực tiếp từ các chân analog và so sánh với 0V), giá trị từ 01000

đến 11101 tương ứng với chế độ chuyển đổi so sánh.

Bảng 3: Chọn chế độ chuyển đổi.

Page 97: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 97

- ADCSRA (ADC Control and Status RegisterA): là thanh ghi chính điều khiển hoạt động và chứa trạng thái của module ADC.

Page 98: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 98

Từng bit của thanh ghi ADCSRA được mô tả như bên dưới:

Bit 7 - ADEN(ADC Enable): viết giá trị 1 vào bit này tức bạn đã cho phép module

ADC được sử dụng. Tuy nhiên khi ADEN=1 không có nghĩa là ADC đã hoạt động

ngay, bạn cần set một bit khác lên 1 để bắt đầu quá trình chuyển đổi, đó là bit

ADSC.

Bit 6 - ADSC(ADC Start Conversion): set bit này lên 1 là bắt đầu khởi động quá

trình chuyển đổi. Trong suốt quá trình chuyển đổi, bit ADSC sẽ được giữ nguyên

giá trị 1, khi quá trình chuyển đổi kết thúc (tự động), bit này sẽ được trả về 0. Vì

vậy bạn không cần và cũng không nên viết giá trị 0 vào bit này ở bất kỳ tình huống

nào. Để thực hiện một chuyển đổi, thông thường chúng ta sẽ set bit ADEN=1 trước

và sau đó set ADSC=1.

Bit 4 – ADIF(ADC Interrupt Flag): cờ báo ngắt. Khi một chuyển đổi kết thúc, bit

này tự động được set lên 1, vì thế người dùng cần kiểm tra giá trị bit này trước khi

thực hiện đọc giá trị chuyển đổi để đảm bảo quá trình chuyển đổi đã thực sự hoàn

tất.

Bit 3 – ADIE(ADC Interrupt Enable): bit cho phép ngắt, nếu bit này được set bằng

1 và bit cho phép ngắt toàn cục (bit I trong thanh ghi trạng thái của chip) được set,

một ngắt sẽ xảy ra khi một quá trình chuyển đổi ADC kết thúc và các giá trị

chuyển đổi đã được cập nhật (các giá trị chuyển đổi chứa trong 2 thanh ghi ADCL

và ADCH).

Page 99: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 99

Bit 2:0 – ADPS2:0(ADC Prescaler Select Bits): các bit chọn hệ số chia xung nhịp

cho ADC. ADC, cũng như tất cả các module khác trên AVR, cần được giữ nhịp

bằng một nguồn xung clock. Xung nhịp này được lấy từ nguồn xung chính của

chip thông qua một hệ số chia. Các bit ADPS cho phép người dùng chọn hệ số chia

từ nguồn clock chính đến ADC. Tham khảo bảng 4 để biết cách chọn hệ số chia.

Bảng 4: Hệ số chia xung nhịp cho ADC.

- ADCL và ADCH (ADC Data Register): 2 thanh ghi chứa giá trị của quá trình chuyển đổi. Do module ADC trên AVR có độ phân giải tối đa 10 bits nên cần 2 thanh ghi để chứa giá trị chuyển đổi. Tuy nhiên tổng số bít của 2 thanh ghi 8 bit là 16, con số này nhiều hơn 10 bit của kết quả chuyển đổi, vì thế chúng ta được phép chọn cách ghi 10 bit kết quả vào 2 thanh ghi này. Bit ADLAR trong thanh ghi ADMUX quy định cách mà kết quả được ghi vào.

ADLAR=0:

ADLAR=1:

Page 100: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 100

Thông thường, 2 thanh ghi data được sắp xếp theo định dạng ADLAR=0, ADCL chứa 8 bit thấp và 2 bit thấp của ADCH chứa 2 bit cao nhất của giá trị thu được. Chú ý thứ tự đọc giá trị từ 2 thanh ghi này, để tránh đọc sai kết quả, bạn cần đọc thanh ghi ADCL trước và ADCH sau, vì sau khi ADCH được đọc, các thanh ghi data có thể được cập nhật giá trị tiếp theo.

- SFIOR(Special FunctionIO Register C): thanh ghi chức năng đặc biệt, 3 bit cao trong thanh ghi này quy định nguồn kích ADC nếu chế độ Auto Trigger được sử dụng. Đó là các bit ADTS2:0 (Auto Trigger Source 2:0). Các loại nguồn kích được trình báy trong bảng 5.

Bảng 5: Nguồn kích ADC trong chế độ Auto Trigger.

2. Sử dụng ADC- Chuyển đổi đơn kênh.

Khái niệm đơn kênh được hiểu là đại lượng cần chuyển đổi là các điện áp đặt trực tiếp trên các chân analog của chip, giá trị điện áp này được so sánh với 0V của chip, hay nói một cách khác, điện áp cần chuyển đổi và chip AVR có “mass chung”. Chúng ta sẽ minh họa cách sử dụng ADC trên AVR ở chế độ đơn kênh bằng ví dụ đọc và hiển thị giá trị ADC trên các LED 7 đoạn. Như minh họa trong hình 4, chúng ta sẽ dùng 4 LED để hiển thị 4 chữ số của kết quả, do chúng ta đều biết ADC trên AVR có độ phân giải 10 bit nên kết quả chuyển đổi tối đa là 1023, 4 LED là đủ để hiển thị kết quả này. 4 chip 7447 được dùng để điều khiển 4 LED, chúng ta cần 16 đường để xuất dữ liệu hiển thị lên 4 LED vì thế PORTB và PORTC sẽ được dùng cho mục đích này. 4 bit cao của PORTC(PC4:7) chứa chữ số hàng nghìn của kết quả, 4 bit thấp PC0:3 chứa chữ số hàng trăm, 4 bit cao của PORTB(PB4:7) dùng xuất chữ số hàng chục và 4 bit PB0:3 dành cho chữ số hàng đơn vị. Đại lượng cần chuyển đổi là điện áp trên chân ADC0 (kênh 0 của ADC,

Page 101: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 101

chân 0 trong PORTA chip ATmega32), điện áp được tạo ra bằng một biến trở RV1. Thay đổi giá trị biến trở, điện áp rơi trên ADC0 thay đổi và được cập nhật trực tiếp trên các LED. Giá trị hiển thị trên LED không phải là giá trị điện áp mà là giá trị tương đối sau khi chuyển đổi. Trong ví dụ này, tôi sẽ trình bày dạng tổng quát, việc đọc ADC và hiển thị LED được viết trong các chương trình con tương ứng. Bằng cách này, các bạn có thể dễ dàng sửa đổi và mở rộng ví dụ sau này.

Hình 4. Đọc ADC đơn kênh.

List 1 trình bày đoạn code minh họa đọc ADC đơn kênh và hiển thị kết quả trên LED 7 đoạn.

List 1. Đọc ADC đơn kênh và hiển thị bằng LED 7 đoạn.

Page 102: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 102

Tôi tạm thời chia đoạn chương trình thành 4 phần, phần 1 là các định nghĩa (dòng 4 đến 7), phần 2 là chương trình con đọc ADC đơn kênh (dòng 10 đến 14),

Page 103: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 103

phần 3 là chương trình con hiển thị môt giá trị 4 chữ số lên 4 LED 7 đoạn (từ dòng 17 đến 30) và phần 4 là chương trình chính. Chúng ta sẽ tìm hiểu theo từng phần.

- Phần 1: ba dòng 4, 5 và 6 chúng ta định nghĩa 3 biến đại diện tên của 3 mode điện áp tham chiếu có thể dùng cho ADC. Xem lại bảng 2 chúng ta biết rằng điện áp tham chiếu được chọn thông qua 2 bit REFS trong thanh ghi ADMUX, có 3 loại điện áp có thể được chọn. Biến AREF_MODE tương ứng với trường hợp chúng ta muốn lấy điện áp trên chân AREF làm điện áp tham chiếu, đối chiếu bảng 2 chúng ta cần set 2 bit REFS bằng 0, và dòng 4 “ #define AREF_MODE 0” thực hiện việc này. Tương tự, biến INT_MODE đại diện cho trường hợp điện áp tham chiếu nội 2.56V và được định nghĩa cho phép set 1 bit REFS lên 1 “#define INT_MODE (1<<REFS1)|(1<<REFS0)”. Biến AVCC_MODE đại diện trường hợp điện áp tham chiếu lấy từ chân AVCC. Cuối cùng, biến ADC_VREF_TYPE được định nghĩa là biến chọn mode mà chúng ta thực sự muốn dùng cho ADC, trong ví dụ này tôi chọn điện áp tham chiếu lấy từ chân AVCC vì thế tôi định nghĩa “#define ADC_VREF_TYPE AVCC_MODE”. Bit ADC_VREF_TYPE sẽ được gán cho thanh ghi ADMUX khi khởi động ADC trong chương trình chính.

- Phần 2-chương trình con đọc ADC đơn kênh “uint16_t read_adc(unsigned char adc_channel)”: tên chương trình là read_adc và adc_channel là tham số cần truyền cho chương trình con, tham số này là chỉ số kênh muốn đọc (từ kênh 0 đến kênh 7). Giá trị trả về là một số nguyên không dấu 16 bit (kiểu unsigned int của C), tuy nhiên trong ví dụ này tôi dùng kiểu dữ liệu uint16_t thay cho unsigned int, uint16_t là một cách định nghĩa kiểu dữ liệu nguyên không dấu 16 bit của riêng thư viện gcc-avr. Dòng đầu tiên của đoạn chương trình con (dòng 11) là khai báo kênh muốn đọc bằng cách ghép giá trị kênh cho thanh ghi ADMUX “ADMUX |=adc_channel;”. Xem lại cấu trúc thanh ghi ADMUX, trong thanh ghi này, ngoài các bit chọn nguồn điện áp tham chiếu REFS thì 5 bit thấp MUX4:0 cho phép chọn kênh ADC cần đọc. Tham khảo them bảng 3 chúng ta thấy rằng 8 giá trị đầu tiên của các bit MUX4:0 (từ 00000 đến 00111 nhị phân) tương ứng với 8 kênh đơn ADC0:7. Chính sự sắp xếp này cho phép chúng ta ghép trực tiếp giá trị kênh muốn đọc vào thanh ghi ADMUX thông qua dòng lệnh ADMUX |=adc_channel. Chúng ta dùng phép OR “|” vì chúng ta chỉ muốn thay đổi giá trị cá bit MUX mà không muốn làm ảnh hưởng đến giá trị các bit khác trong thanh ghi ADMUX khi chọn kênh. Một chú ý quan trọng là giá trị của tham số adc_channel chỉ trong khoảng từ 0 đến 7 tương ứng với 8 chế độ đọc đơn kênh ADC trong bảng 3. Sau khi kênh đã được chọn, dòng 12 set bit ADCS trong thanh ghi ADCSRA để bắt đầu quá trình chuyển đổi “ADCSRA|=(1<<ADSC);”. Như đã đề cập trong khi khảo sát chức năng của bit ADIF trong thanh ghi ADCSRA, sau khi quá trình chuyển đổi kết thúc bit ADIF sẽ được tự động set lên 1, vì thế dòng code 13 được dùng để chờ cho bit này lên 1, tức chờ cho quá trình chuyển đổi kết

Page 104: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 104

thúc. Câu lệnh “loop_until_bit_is_set(ADCSRA,ADIF);” được hiểu là lặp cho đến khi bit ADIF trong thanh ghi ADCSRA được set lên 1, lệnh “loop_until_bit_is_set” này được định nghĩa sẵn trong thư viện gcc-avr. Nếu quá trình chuyển đỗi đã kết thúc, kết quả chuyển đổi sẽ được chứa trong 2 thanh ghi ADCL và ADCH, 2 thanh ghi này được tự động gọp thành thanh ghi 16 bit ADCW (ADC WORD), dòng 14 “return ADCW” trả về kết quả chuyển đổi.

- Phần 3-chương trình con hiển thị số có 4 chữ số lên 4 LED 7 đoạn “void LED7_out(uint16_t val)” : val là số cần hiển thị, chúng ta khai báo 4 biến tạm “dvi, chuc, tram, nghin” đại diện cho các chữ số đơn vị, chục, trăm và nghìn ở dòng 18. Đồng thời, một biến tạm temp_val được dùng để lưu giá trị tạm thời của số val như trong dòng 19 “temp_val=val;”, cách làm này nhằm tránh thay đổi giá trị của bản thân val trong quá trình thao tác. Các dòng code từ 21 đến 26 thực hiện quá trình tách số val ra thành 4 các chữ số hàng đơn vị, chục, trăm và nghìn. Đây chỉ là phương pháp đại số thông thường nên tôi sẽ không giải thích thêm cho đoạn này. Hai dòng 28 và 29 xuất giá trị ra 4 LED 7 đoạn. Bốn LED 7 đoạn được điều khiển bởi các IC chuyển mã 7447, giá trị input choc các IC 7447 là các số BCD 4 bit. Vì thế, để xuất 4 chữ số ra 4 LED thông qua 7447 chúng ta cần 4x4=16 bit, trong ví dụ này tôi dùng PORTB và PORTC cho nhiệm vụ này. Bốn bit cao của PORTC sẽ chứa chữ số hàng nghìn, bốn bit thấp chứa chữ số hàng trăm, bốn bit cao của PORTB chứa chữ số hàng chục và bốn bit thấp PORTB chứa số đơn vị. Dòng code 28 “PORTB=(chuc<<4)+dvi;” xuất 2 chữ số chục và đơn vị ra PORTB, trong đó hàm “chuc<<4” nghĩa là dịch chữ số hàng chục sang trái 4 vị trí để đưa chữ số này lên 4 bit cao của PORTB, sau đó cộng chữ số đơn vị vào 4 bit thấp và cuối cùng là xuất ra PORTB. Tương tự chúng ta có thể xuất 2 chữ số hàng nghìn và hàng trăm ra PORTC thông qua dòng code 29 “PORTC=(nghin<<4)+tram”.

- Phần 4-chương trình chính: do hầu hết các nhiệm vụ đã được thực hiện trong các đoạn chương trình con nên chương trình chính trong ví dụ này khá đơn giản. Hai dòng code 32 và 33 set các thông số cho ADC, dòng 32 “ADCSRA=(1<<ADEN)|(1<<ADPS2)|(1<<ADPS0);” set các bit trong thanh ghi điều khiển ADCSRA, ADC được cho phép hoạt động bởi bit ADEN, các bit ADPS2:0 để chọn prescaler xung clock (xem lại phần mô tả thanh ghi ADCSRA), trong ví dụ này tôi chọn prescaler = 32 (bạn có thể chọn giá trị khác). Dòng 33 “ADMUX=ADC_VREF_TYPE;” cho phép chọn điện áp tham chiếu bằng cách gán biến ADC_VREF_TYPE mà chúng ta đã định nghĩa trong dòng code 7 cho thanh ghi ADMUX. Bạn cần chú ý là sau khi thực hiện 2 dòng code này, ADC chỉ mới ở tư thế “sẵn sàng” nhưng vẫn chưa hoạt động, ADC sẽ hoạt động khi chúng ta gọi chương trình con đọc adc. Trong vòng lặp while của chương trình chính chúng ta lần lượt đọc giá trị ADC ở kênh 0 bằng cách gọi chương trình con “read_adc(0)” ở dòng lệnh 39 “ADC_val=read_adc(0);” sau đó hiển thị ra LED 7

Page 105: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 105

đoạn ở dòng 40 “LED7_out(ADC_val);” và cuối cùng là delay 1 khoảng thời gian nhỏ (100ms) trước khi lặp lại quá trình đọc và hiển thị.

Mô phỏng ví dụ: Tạo 1 project bằng Programmer Notepad và type đoạn code trên vào file source (xem phần tạo Project với WinAVR). Biên dịch và chạy mô phỏng với mạch điện trong hình 4. Điều chỉnh giá trị biến trở RV1 để thay đổi giá trị điện áp input của ADC kênh 0 và xem giá trị hiển thị trên các LED 7 đoạn. Hãy thay đổi giá trị biến ADC_VREF_TYPE trong dòng code 7 sang các mode khác như INT_MODE, biên dịch và mô phỏng lại chương trình, quan sát và so sánh sự khác nhau giữa các mode điện áp tham chiếu. Bạn sẽ dễ dàng nhận thấy rằng khi chọn điện áp tham chiếu nội 2.56V, khi tăng biến trở đến khoảng giữa thì kết quả chuyển đổi sẽ là 1023(giá trị lớn nhất của số 10 bit) và nếu tiếp tục tăng biến trở giá trị này sẽ không thay đổi. Điều này có nghĩa là nếu điện áp input lớn hơn điện áp tham chiếu thì kết quả chuyển đổi sẽ là 1023.

Phần chuyển đổi ADC ở chế độ so sánh sẽ được trình bày trong 1 dịp khác ở phần ứng dụng.

Bài 7 – Giao tiếp SPI

I. Giới thiệu.

Bài này giúp các bạn biết cách sử dụng cách truyền thông nối tiếp đồng bộ SPI. Công cụ chính cũng là 2 bộ phần mềm AVRStudio (+gcc-avr) và Proteus. Thực chất ngôn ngữ lập trình vẫn là gcc-avr nhưng tôi không dùng Programmer Notepad để biết code như thông thường, thay vào đó tôi dùng AVRStudio làm trình biên tập, bạn tham khảo thêm phần “Lập trình C bằng AVRStudio” trong bài hướng dẫn sử dụng AVRStudio để biết thêm cách thực hiện. Tôi sẽ dùng chip ATmega32 làm minh họa. Sau bài này, tôi hy vọng bạn có thể hiểu và thực hiện được:

Nguyên lý truyền thông nối tiếp SPI. Sử dụng module SPI trong AVR ở các chế độ Master và Slave.

II. Chuẩn truyền thông SPI,

SPI (Serial Peripheral Bus) là một chuẩn truyền thông nối tiếp tốc độ cao do hang Motorola đề xuất. Đây là kiểu truyền thông Master-Slave, trong đó có 1 chip

Page 106: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 106

Master điều phối quá trình tuyền thông và các chip Slaves được điều khiển bởi Master vì thế truyền thông chỉ xảy ra giữa Master và Slave. SPI là một cách truyền song công (full duplex) nghĩa là tại cùng một thời điểm quá trình truyền và nhận có thể xảy ra đồng thời. SPI đôi khi được gọi là chuẩn truyền thông “4 dây” vì có 4 đường giao tiếp trong chuẩn này đó là SCK (Serial Clock), MISO (Master Input Slave Output), MOSI (Master Ouput Slave Input) và SS (Slave Select). Hình 1 thể hiện một kết SPI giữa một chip Master và 3 chip Slave thông qua 4 đường.

SCK: Xung giữ nhịp cho giao tiếp SPI, vì SPI là chuẩn truyền đồng bộ nên cần 1 đường giữ nhịp, mỗi nhịp trên chân SCK báo 1 bit dữ liệu đến hoặc đi. Đây là điểm khác biệt với truyền thông không đồng bộ mà chúng ta đã biết trong chuẩn UART. Sự tồn tại của chân SCK giúp quá trình tuyền ít bị lỗi và vì thế tốc độ truyền của SPI có thể đạt rất cao. Xung nhịp chỉ được tạo ra bởi chip Master.

MISO– Master Input / Slave Output: nếu là chip Master thì đây là đường Input còn nếu là chip Slave thì MISO lại là Output. MISO của Master và các Slaves được nối trực tiếp với nhau.. MOSI – Master Output / Slave Input: nếu là chip Master thì đây là đường Output còn nếu là chip Slave thì MOSI là Input. MOSI của Master và các Slaves được nối trực tiếp với nhau.

SS – Slave Select: SS là đường chọn Slave cần giap tiếp, trên các chip Slave đường SS sẽ ở mức cao khi không làm việc. Nếu chip Master kéo đường SS của một Slave nào đó xuống mức thấp thì việc giao tiếp sẽ xảy ra giữa Master và Slave đó. Chỉ có 1 đường SS trên mỗi Slave nhưng có thể có nhiều đường điều khiển SS trên Master, tùy thuộc vào thiết kế của người dùng.

Page 107: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 107

.

Hình 1. Giao diện SPI.

Hoạt động: mỗi chip Master hay Slave có một thanh ghi dữ liệu 8 bits. Cứ mỗi xung nhịp do Master tạo ra trên đường giữ nhịp SCK, một bit trong thanh ghi dữ liệu của Master được truyền qua Slave trên đường MOSI, đồng thời một bit trong thanh ghi dữ liệu của chip Slave cũng được truyền qua Master trên đường MISO. Do 2 gói dữ liệu trên 2 chip được gởi qua lại đồng thời nên quá trình truyền dữ liệu này được gọi là “song công”. Hình 2 mô tả quá trình truyền 1 gói dữ liệu thực hiện bởi module SPI trong AVR, bên trái là chip Master và bên phải là Slave.

Page 108: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 108

Hình 2. Truyền dữ liệu SPI.

Cực của xung giữ nhịp, phase và các chế độ hoạt động: cực của xung giữ nhịp (Clock Polarity) được gọi tắt là CPOL là khái niệm dùng chỉ trạng thái của chân SCK ở trạng thái nghỉ. Ở trạng thái nghỉ (Idle), chân SCK có thể được giữ ở mức cao (CPOL=1) hoặc thấp (CPOL=0). Phase (CPHA) dùng để chỉ cách mà dữ liệu được lấy mẫu (sample) theo xung giữ nhịp. Dữ liệu có thể được lấy mẫu ở cạnh lên của SCK (CPHA=0) hoặc cạnh xuống (CPHA=1). Sự kết hợp của SPOL và CPHA làm nên 4 chế độ hoạt động của SPI. Nhìn chung việc chọn 1 trong 4 chế độ này không ảnh hưởng đến chất lượng truyền thông mà chỉ cốt sao cho có sự tương thích giữa Master và Slave.

III. Truyền thông SPI trên AVR.

Module SPI trong các chip AVR hầu như hoàn toàn giống với chuẩn SPI mô tả trong phần trên. Vì thế, nếu đã hiểu cách truyền thông SPI thì sẽ khống quá khó để thực hiện việc truyền thông này với AVR. Phần bên dưới tôi trình bày một số điểm quan trọng khi điều khiển SPI trên AVR.

Các chân SPI: Các chân giao tiếp SPI cũng chính là các chân PORT thông thường, vì thế nếu muốn sử dụng SPI chúng ta cần xác lập hướng cho các chân này. Trên chip ATmega32, các chân SPI như sau:

SCK – PB7 (chân 8) MISO – PB6 (chân 7) MOSI – PB5 (chân 6) SS – PB4 (chân 5)

Khi chip AVR được sử dụng làm Slave, bạn cần set các chân SCK input, MOSI input, MISO output và SS input. Nếu là Master thì SCK output, MISO output, MOSI input và khi này chân SS không quan trọng, chúng ta có thể dùng chân này để điều khiển SS của Slaves hoặc bất kỳ chân PORT thông thường nào.

Page 109: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 109

Thanh ghi: SPI trên AVR được vận hành bởi 3 thanh ghi bao gồm thanh ghi điều khiển SPCR , thanh ghi trạng thái SPSR và thanh ghi dữ liệu SPDR.

SPCR (SPI Control Register): là 1 thanh ghi 8 bit điều khiển tất cả hoạt động của SPI.

* Bit 7- SPIE (SPI Interrupt Enable) bit cho phép ngắt SPI. Nếu bit này được set bằng 1 và bit I trong thanh ghi trạng thái được set bằng 1 (sei), 1 ngắt sẽ xảy ra sau khi một gói dữ liệu được truyền hoặc nhận. Chúng ta nên dùng ngắt (nhất là đối với chip Slave) khi truyền nhận dữ liệu với SPI. * Bit 6 – SPE (SPI Enable). set bit này lên 1 để cho phép bộ SPI hoạt động. Nếu SPIE=0 thì module SPI dừng hoạt động. * Bit 5 – DORD (Data Order) bit này chỉ định thứ tự dữ liệu các bit được truyền và nhận trên các đường MISO và MOSI, khi DORD=0 bit có trọng số lớn nhất của dữ liệu được truyền trước (MSB) ngược lại khi DORD=1, bit LSB được truyền trước. Thật ra khi giao tiếp giữa 2 AVR với nhau, thứ tự này không quan trọng nhưng phải đảm bảo các bit DORD giống nhau trên cả Master và Slaves. bit này chỉ định thứ tự dữ liệu các bit được truyền và nhận trên các đường MISO và MOSI, khi DORD=0 bit có trọng số lớn nhất của dữ liệu được truyền trước (MSB) ngược lại khi DORD=1, bit LSB được truyền trước. Thật ra khi giao tiếp giữa 2 AVR với nhau, thứ tự này không quan trọng nhưng phải đảm bảo các bit DORD giống nhau trên cả Master và Slaves. * Bit 4 – MSTR (Master/Slave Select) nếu MSTR =1 thì chip được nhận diện là Master, ngược lại MSTR=0 thì chip là Slave.. * Bit 3 và 2 – CPOL và CPHA đây chính là 2 bit xác lập cực của xung giữ nhịp và cạnh sample dữ liệu mà chúng ta đã khảo sát trong phần đầu. Sự kết hợp 2 bit này tạo thành 4 chế độ hoạt động của SPI. Một lần nữa, chọn chế độ nào không quan trọng nhưng phải đảm bảo Master và Slave cùng chế độ hoạt động. Vì thế có thể để 2 bit này bằng 0 trong tất cả các chip. Hình 3 trình bày cách sample dữ liệu trong 4 chế độ của SPI trên AVR.

Page 110: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 110

CPHA=0.

CPHA=1.

Hình3 Các chế độ hoạt động của SPI.

* Bit 1:0 – CPR1:0 hai bit này kết hợp với bit SPI2X trong thanh ghi SPSR cho phép chọn tốc độ giao tiếp SPI, tốc độ này được xác lập dựa trên tốc độ nguồn xung clock chia cho một hệ số chia. Bảng 1 tóm tắt các tốc độ mà SPI trong AVR có thể đạt. Thông thường, tốc bộ này không được lớn hơn 1/4 tốc độ xung nhịp cho chip.

Page 111: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 111

SPSR (SPI Status Register): là 1 thanh ghi trạng thái của module SPI. Trong thanh ghi này chỉ có 3 bit được sử dụng. Bit 7 – SPIF là cờ báo SPI, khi một gói dữ liệu đã được truyền hoặc nhận từ SPI, bit SPIF sẽ tự động được set len 1. Bit 6 – WCOL là bít báo va chạm dữ liệu (Write Colision), bit này được AVR set lên 1 nếu chúng ta cố tình viết 1 gói dữ liệu mới vào thanh ghi dữ liệu SPDR trong khi quá trình truyền nhận trước chưa kết thúc. Bit 0 – SPI2X gọi là bit nhân đôi tốc độ truyền, bit này kết hợp với 2 bit SPR1:0 trong thanh ghi điều khiển SPCR xác lập tốc độ cho SPI.

SPDR (SPI Data Register): là thanh ghi dữ liệu của SPI. Trên chip Master, ghi giá trị vào thanh ghi SPDR sẽ kích quá trình tuyền thông SPI. Trên chip Slave, dữ liệu nhận được từ Master sẽ lưu trong thanh ghi SPDR, dữ liệu được lưu sẵn trong SPDR sẽ được truyền cho Master.

Sử dụng SPI trên AVR: SPI trên AVR hoạt động không khác nguyên lý chung của chuẩn SPI là mấy. Vận hành SPI trên AVR được thực hiện dựa trên việc ghi và đọc 3 các thanh ghi SPCR, SPSR và SPDR. Trước khi truyền nhận bằng SPI chúng ta cần khởi động SPI, quá trình khởi động thường bao gồm chọn hướng giao tiếp cho các chân SPI, chọn loại giao tiếp: Master hay Slave, chọn chế độ SPI (SPOL, SPHA) và chọn tốc độ giao tiếp. Truyền thông SPI luôn được khởi xướng bởi chip Master, khi Master muốn giao tiếp với 1 Slave nào đó, nó sẽ kéo chân SS của Slave xuống mức thấp (gọi là chọn địa chỉ) và sau đó viết dữ liệu cần truyền vào thanh ghi dữ liệu SPDR, khi dữ liệu vừa được viết vào SPDR xung giữ nhịp sẽ được tự động tạo ra trên SCK và quá trình truyền nhận bắt đầu. Đối với các chip Slave, khi chân SS bị kéo xuống nó sẽ sẵn sàng cho quá trình truyền nhận. Khi

Page 112: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 112

phát hiện xung giữ nhịp trên SCK, Slave sẽ bắt đầu sample dữ liệu đến trên đường MOSI và gởi dữ liệu di trên MISO.

Để minh họa cho cách truyền và nhận dữ liệu SPI trên AVR, tôi sẽ thực hiện một ví dụ truyền nhân 1 chiều với 1 chip Master và 3 chip Slaves. Tất cả các chip được dùng là ATmega32, chip Master sẽ điều khiển các chip Slaves thông qua 3 đường chọn chip PB0, PD1 và PD2. Công việc thực hiện trong ví dụ này như sau: Master sẽ lần lượt chọn 1 trong 3 chip Slaves và gởi các gói dữ liệu tương ứng đến chúng, chip Slave0 sẽ nhận được các con số từ 0 đến 80, Slave1 nhận 80 đến 160 và Slave2 nhận dữ liệu từ 160 đến 240. Các Slave sẽ hiển thị giá trị mà mình nhận được trên các Text LCD kết nối với PORTD ở mỗi Slave. Sơ đồ mạch điện vẽ bằng Proteus cho ví dụ này được trình bày trong hình 4.

Page 113: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 113

Hình 4. Mô phỏng ví dụ giao tiếp SPI trên AVR.

Trong bài này, tôi sẽ dùng phần mềm AVRStudio kết hợp với gcc-avr trong WinAVR để lập trình bằng ngôn ngữ C cho AVR. Bạn hãy tham khảo thêm bài AVRStudio để biết cách tạo 1 Project lập trình C cho AVR bằng AVRStudio. Hãy

Page 114: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 114

tạo 2 Project riêng, 1 Project có tên SPI_Master cho chip Master và 1 Project có tên SPI_Slave dùng chung cho cả 3 Slaves. Copy file myLCD.h dùng cho điều khiển Text LCD được tạo trong bài “Text LCD” vào cả 2 thư mục chứa 2 Projects mới tạo. Viết đoạn code trong list 0 vào file SPI_Master.c và đoạn code trong list 1 vào file SPI_Slave.c.

List 1. Đoạn code cho SPI Master.

Page 115: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 115

Page 116: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 116

Tôi sẽ giải thích sơ lượt một số điểm chính trong đoạn code cho chip Master. Các phần định nghĩa từ dòng thứ 10 đến dòng 17 chỉ có tác dụng làm cho chương trình dễ đọc hiểu hơn và có tính tương thích cao hơn, ví dụ nếu bạn muốn sử dụng ví dụ này cho các chip khác bạn chỉ cần thay đổi các định nghĩa này mà không phải thay đổi trong nội dung các chương trình con. Chúng ta định nghĩa để chọn PORTB điều khiển các đường chọn chip SS của Slave (gọi là các đường địa chỉ), dòng 18 định nghĩa Slave(i) là thứ tự chân trên PORT dùng cho chip Slave thứ i. Dễ hiểu hơn, đường SS trên Slave0 sẽ được kết nối và điều khiển bởi chân 0 của PORTB (chân PB0 và tương tự cho các Slaves còn lại. Biến wData định nghĩa trên dòng 20 là một mảng 3 phần tử chứa các con số 8 bits sẽ truyền đến các Slaves.

Chương trình con “void SPI_MasterInit(void)”: Chương trình này khởi động cho chip Master, việc khởi động trước hết là set hướng cho các chân SPI. Đối với Master, các chân tạo xung giữ nhịp SCK và chân truyền dữ liệu MOSI cần được set Output như trong dòng 24, các chân SPI còn lại là input. Dòng 25 giúp kéo điện trở kéo lên ở chân nhận dữ liệu MISO của Master. Dòng lệnh 26 “SPCR=(1<<SPIE)|(1<<SPE)|(1<<MSTR)|(1<<CPHA)|(1<<SPR1)|(1<<SPR0); ” thật sự khởi động SPI với việc set bit SPIE: cho phép ngắt SPI=1, bit SPE=1 cho phép SPI hoạt động, MSTR=1 xác lập chip là chip Master. CPHA=1 tức chân SCK sẽ ở mức thấp khi SPI không hoạt động, trong khi CPOL=0 (không set CPOL thì mặc định là 0) thì dữ liệu sẽ được sample (lấy mẫu) ở cạnh xuống của xung SCK. Cuối cùng cả 2 bit SPR1 và SPR0 đều được set lên 1, tốc độ SPI sẽ bằng tốc độ nguồn cung nuôi chip chia cho 128 (xem bảng 1). Dòng code 29 set hướng Output cho các chân dùng làm chân địa chỉ chọn chip Slaves (các chân PB0, PB1, PB2), sau đó kéo các chân này lên mức cao để disable tất cả các Slaves (sau này sẽ kích hoạt sau).

Chương trình con “void SPI_Transmit(uint8_t i, uint8_t data)”: chương trình truyền dữ liệu qua SPI của chip Master, chương trình có 2 tham số là địa chỉ chip Slave (biến i) và dữ liệu cần truyền (biến data). Trước khi truyền dữ liệu, Master sẽ thực hiện việc chọn Slave, dòng 35 “cbi(ADDRESS_PORT, Slave(i));” thực hiện việc này. Thực chất dòng này là kéo chân “i” của PORTB xuống mức thấp, cũng là kéo chân SS của Slave xuống mức thấp. Dòng 36 gán giá trị cần truyền cho thanh ghi dữ liệu “SPDR=data”, sau khi gán giá trị cho SPDR, xung clock sẽ tự động được Master tạo ra trên SCK, quá trình truyền bắt đầu. Quá trình truyền kết thúc thì bit cờ SPIF trong thanh ghi trạng thái SPSR được set lên 1, dòng 36 thực hiện việc chờ bit cờ SPIF để kết thúc quá trình truyền. Khi kết thúc truyền 1 byte cho Slave, set chân SS của Slave lên mức cao để vô hiệu hóa SPI, dòng 37.

Page 117: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 117

Chương trình chính: chương trình chính cho chip Master SPI tương đối đơn giản, trước hết chúng ta cần gọi chương trình con khởi động SPI ở dòng 43. Trong vòng lặp vô tận while, lần lượt gởi các giá trị đến các Slaves. Dòng 46 gọi chương trình con gởi giá trị biến wData[0] đến Slave0, dòng 50 truyền biến wData[1] cho Slave1 và dòng 54 truyền biến wData[2] cho Slave2

List 2.Đoạn code cho Slave SPI.

Page 118: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 118

Page 119: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 119

Đoạn code trong list 2 là đoạn code cho chip Slaves, chú ý dòng 3 chúng ta include file header “interrupt.h” vì việc nhận dữ liệu SPI của SLave được thực hiện bằng ngắt SPI. Các định nghĩa biến trong các dòng code từ 8 đến 15 tương tự như trong chương trình cho chip Master. Tôi sẽ tập trung giải thích các điểm khác biệt cho Slaves.

Chương trình con “void SPI_SlaveInit(void)”: Chương trình này khởi động cho chip Slave, cũng giống như trường hợp của Master, việc khởi động trước hết là set hướng cho các chân SPI. Đối với Slave, chỉ có chân truyền dữ liệu MISO là cần được set Output như trong dòng 19, các chân SPI còn lại là input. Dòng 20 giúp kéo điện trở kéo lên ở các chân nhận dữ liệu MOSI của Slave, và chân chọn Slave SS. Việc tiếp theo là cài đặt các thanh ghi SPI như trong dòng lệnh 21, “SPCR=(1<<SPIE)|(1<<SPE)|(1<<CPHA)|(1<<SPR1)|(1<<SPR0); ”, nếu quan sát dòng lệnh 26 trong List 1 chop chip Master, dòng này không khác là mấy, quá trình khởi động SPI cho Slave tương tự Master với một điểm khác duy nhất là bit MSTR, bit này không được set lên 1 đối với Slaves.

Trình phục vụ ngắt “ISR(SPI_STC_vect)”: SPI trên AVR chỉ có duy nhất một sự kiện gây ra ngắt đó là khi quá trình truyền-nhận kết thúc. Tên vector ngắt SPI trong ngôn ngữ lập trình avr-gcc là “SPI_STC_vect. Trong ví dụ này, khi một ngắt SPI xảy ra ở Slave, chúng ta sẽ đọc thanh ghi SPDR và sau đó hiển thị giá trị đọc được trên LCD. Dòng 37, rData=SPDR, gán thanh ghi SPDR cho biến rData. Từ dòng 38 đến 42 là cách hiển thị giá trị đọc về trên Text LCD bằng thư viện myLCD (xem bài Text LCD). Dòng 39 chúng ta khai báo 1 biến tạm dạng mảng động, dis, làm buffer chứa giá trị ascii của các ký tự cần hiển thị lên LCD. Chú ý là giá trị nhận về là 1 con số 8 bit, muốn hiển thị giá trị này lên LCD chúng ta không thể hiển thị trực tiếp bằng lệnh putChar_LCD vì hàm putChar_LCD xem tham số nhập vào là mã Ascii, ví dụ chúng ta nhận về số rData=65, nếu dùng hàm putChar_LCD(rData) thì trên LCD chỉ thấy ký tự ‘A’ vì 65 là mã Ascii của ký tự ‘A’. Để LCD hiển thị “65” chúng ta xem 65 là một chuỗi các ký tự, trước hết cần chuyển số 65 thành các ký tự ‘6’ và ‘5’, hàm “sprintf(dis,"%i",rData)” trong dòng code 40 thực hiện việc định dạng lại biến rData thành chuỗi các ký tự và chứa trong buffer dis, “%i” là “cờ” định dạng, báo cho hàm sprintf xem rData là một số nguyên. Sau dòng 40, ví dụ rData=65, thì dis=”65”. Dòng 42 in chuỗi dis lên LCD: print_LCD(dis);.

Chương trình chính: chương trình chính cho chip Slave không làm nhiều việc vì các việc chính như nhận và hiển thị đã được thực hiện trong trình phục vụ ngắt SPI. Dòng 27 sei() cho phép ngắt toàn cục, điều này là cần thiết để ngắt SPI có thể xảy ra, dòng 28 gọi chương trình con khởi động SPI cho Slave, sau đó khởi

Page 120: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 120

động LCD ở dòng 29 và kết thúc. Không có việc gì cần thực hiện trong vòng lặp while().

Bài 8 – Giao tiếp TWI – I2C

I. Bạn sẽ đi đến đâu.

Bài này giới thiệu cách giao tiếp bằng truyền thông nối tiếp đồng bộ Two-Wire Serial (TWI) tương thích với chuẩn I2C. Trong bài này chúng ta sẽ khảo sát 2 mode truyền và nhận trên chip Master cùng với 2 mode truyền và nhận trên chip Slave. Công cụ chính cũng là 2 bộ phần mềm WinAVR và Proteus. Vi điều khiển ATmega32 sẽ được dùng làm minh họa.

Sau bài này, tôi hy vọng bạn có thể hiểu và thực hiện được:

- Nguyên lý truyền thông nối tiếp TWI và I2C. - Sử dụng module TWI trong AVR ở các chế độ Master. - Sử dụng module TWI trong AVR ở các chế độ Slave. - Ví dụ giao tiếp giữa các AVR bằng TWI.

II. Giao diện TWI – I2C.

TWI (Two-Wire Serial Intereafce) là một module truyền thông nối tiếp đồng bộ trên các chip AVR dựa trên chuẩn truyền thông I2C. I2C là viết tắc của từ Inter-Integrated Circuit là một chuẩn truyền thông do hãng điện tử Philips Semiconductor sáng lập và xây dựng thành chuẩn năm 1990. Phiên bản mới nhất của I2C là V3.0 phát hành năm 2007. Để hiểu thêm về I2C bạn có thể tham khảo các tài liệu “I2C Specification http://www.hocavr.com/index.php/thaoluan?func=view&catid=5&id=16#16” từ trang web của NXP- http://www.nxp.com (lập bởi Philips). Trong phạm vi bài học này tôi chỉ giới thiệu giao thức TWI được giới thiệu trong datasheet của các chip AVR từ Atmel. Tuy nhiên, về cơ bản TWI trong AVR hoàn toàn tương thích I2C, do đó tìm hiểu TWI của AVR không chỉ giúp bạn giao tiếp giữa các AVR với nhau mà có thể dùng TWI để điều khiển bất kỳ một thiết bị nào theo chuẩn I2C (các chip nhớ, bộ chuyển đổi ADC, DCA, đồng hồ thời gian thực…). TWI (I2C) là một truyền thông nối tiếp đa chip chủ (tạm dịch của cụm từ multi-master serial computer bus). Khái niệm “multi-master” (tôi sẽ dùng từ tiếng anh multi-master thay vì dùng “đa chip chủ”) được hiểu là trong trên cùng một bus có thể có nhiều hơn một thiết bị làm Master, đồng thời một Slave có thể trở thành

Page 121: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 121

một Master nếu nó có khả năng. Ví dụ trong một mạng TWI của nhiều AVR kết nối với nhau, bất kỳ một AVR nào đều có thể trở thành Master ở một thời điểm nào đó. Tuy nhiên nếu một mạng dùng một AVR điều khiển các chip nhớ (như EEPROM AT24C1024 chẳng hạn) thì khái niệm “multi-master” không tồn tại vì các chip nhớ được thiết kế sẵn là Slave, không có khả năng trở thành master. TWI (I2C) được thực hiện trên 2 đường SDA (Serial DATA) và SCL (Serial Clock) trong đó SDA là đường truyền/nhận dữ liệu và SCL là đường xung nhịp. Căn cứ theo chuẩn I2C, các đường SDA và SCL trên các thiết bị có cấu hình “cực góp mở” (open-drain hoặc open-collector, tham khảo các mạch số dùng transistor để hiểu thêm), nghĩa là cần có các “điện trở kéo lên” (pull-up resistor) cho các đường này. Ở trạng thái nghỉ (Idle), 2 chân SDA và SCL ở mức cao. Hình 1 mô tả một mô hình mạng TWI (I2C) cơ bản.

Hình 1. Mạng TWI (I2C) với nhiều thiết bị và 2 điện trở kéo lên cho SDA, SCL.

Tiếp theo chúng ta tìm hiểu một số khái niệm và đặc điểm của TWI. Các khái niệm và đặc điểm tôi đề cập dưới đây được dùng cho cả TWI và I2C, nếu có sự khác biệt tôi sẽ giải thích thêm. Master: là chip khởi động quá trình truyền nhận, phát đi địa chỉ của thiết bị cần giao tiếp và tạo xung giữ nhịp trên đường SCL. Slave: là chip có một địa chỉ cố định, được gọi bởi Master và phục vụ yêu cầu từ Master. SDA- Serial Data: là đường dữ liệu nối tiếp, tất cả các thông tin về địa chỉ hay dữ liệu đều được truyền trên đường này theo thứ tự từng bit một. Chú ý là trong chuẩn I2C, bit có trọng số lớn nhất (MSB) được truyền trước nhất, đặc điểm này ngược lại với chuẩn UART. SCL –Serial Clock: là đường giữ nhịp nối tiếp. TWI (I2C) là chuần truyền thông nối tiếp đồng bộ, cần có 1 đường tạo xung giữ nhịp cho quá trình truyền/nhận, cứ mỗi xung trên đường giữ nhịp SCL, một bit dữ liệu trên đường SDA sẽ được lấy mẫu (sample). Dữ liệu nối tiếp trên đường SDA được lấy mẫu khi đường SCL ở mức cao trong một chu kỳ giữ nhịp, vì thế đường SDA không

Page 122: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 122

được đổi trạng thái khi SCL ở mức cao (trừ START và STOP condition). Chân SDA có thể được đổi trạng thái khi SCL ở mức thấp.

START Condition-Điều kiện bắt đầu: từ trạng thái nghỉ, khi cả SDA và SCL ở mức cao nếu Master muốn thực hiện một “cuộc gọi”, Master sẽ kéo chân SDA xuống thấp trong khi SCL vẫn cao. Trạng thái này gọi là START Condition (chúng ta gọi tắt là S). STOP Condition-Điều kiện kết thúc: sau khi thực hiện truyền/nhận dữ liệu, nếu Master muốn kết thúc quá trình nó sẽ tạo ra một STOP condition. STOP condition được Master thực hiện bằng cách kéo chân SDA lên cao khi đường SCL đang ở mức cao. STOP condition chỉ được tạo ra sau khi địa chỉ hoặc dữ liệu đã được truyền/nhận. REPEAT START – Bắt đầu lặp lại: khoảng giữa START và STOP condition là khoảng bận của đường truyền, các Master khác không tác động được vào đường truyền trong khoảng này. Trường hợp sau khi kết thúc truyền/nhận mà Master không gởi STOP condition lại gởi thêm 1 START condition gọi là REPEAT START. Khả năng này thường được dùng khi Master muốn lấy dữ liệu liên tiếp từ các Slaves. Hình bên dưới mô tả các Master tạo ra START, STOP và REPEAT START.

Page 123: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 123

Address Packet Format – Định dạng gói địa chỉ: trên mạng TWI (I2C), tất cả các thiết bị (chip) đều có thể là Master hay Slave. Mỗi thiết bị có một địa chỉ cố định gọi là Device address. Khi một Master muốn giao tiếp với một Slave nào đó, nó trước hết tạo ra một START condition và tiếp theo là gởi địa chỉ Device address của Slave cần giao tiếp trên đường truyền, vì thế xuất hiện khái niệm “gói địa chỉ” (Address Packet). Gói địa chỉ trong TWI (I2C) có định dạng 9 bits trong đó 7 bit đầu (gọi là SLA, được gởi liền sau START condition) chứa địa chỉ Slave, một bit READ/WRITE và một bit ACK-Ackknowledge (xác nhận). Do bit địa chỉ có độ dài 7 bits nên về mặt lý thuyết, trên 1 mạng TWI (I2C) có thể tồn tại tối đa 27=128 thiết bị có địa chỉ riêng biệt. Tuy nhiên, có một số địa chỉ không được sử dụng như các địa chỉ có định dạng 1111xxx (tức các địa chỉ lớn hơn hoặc bằng 120 không được dùng). Riêng địa chỉ 0 được dùng cho “cuộc gọi chung” (General call). Bit READ/WRITE (R/W) được truyền tiếp sau 7 bit địa chỉ là bit báo cho Slave biết Master muốn “đọc” hay “ghi” vào Slave. Nếu bit này bằng 0 (gọi là W) thì quá trình “Ghi” dữ liệu từ Master đến Slave được yêu cầu, nếu bit này bằng 1 (gọi là R) thì Master muốn “đọc” dữ liệu từ Slave về. Tám bits trên (SLA+R/W) được Master phát ra sau khi phát START condition, nếu một Slave trên mạng nhận ra rằng địa chỉ mà Master yêu cầu trùng khớp với Device address của chính mình, nó sẽ “đáp trả” lại Master bằng cách phát ra 1 tín hiệu “xác nhận” ACK bằng cách kéo chân SDA xuống thấp trong xung thứ 9. Ngược lại, nếu không có Slave đáp ứng lại, chân SDA vẫn ở mức cao trong xung giữ nhịp thứ 9 thì gọi là tín hiệu “không xác nhận” – NOT ACK, lúc này Master cần có những ứng xử phù hợp tùy theo mỗi trường hợp cụ thể, ví dụ Master có thể gởi STOP condition và sau đó phát lại địa chỉ Slave khác…Như vậy, trong 9 bit của gói địa chỉ thì chỉ có 8 bit được gởi bởi Master, bit còn lại là do Slave. Ví dụ Master muốn yêu cầu “đọc” dữ liệu từ Slave có địa chỉ 43, nó cần phát đi một byte như sau trên đường truyền: (43<<1)+1, trong đó (43<<1) là dịch số 43 về bên trái 1 vị trí vì 7 bit địa chỉ nằm ở các vị trí cao trong gói địa chỉ, sau đó cộng giá trị này với “1” tức là quá trình “đọc” được yêu cầu.

Page 124: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 124

General call – Cuộc gọi chung: khi Master phát đi gói địa chỉ có dạng 0 (thực chất là 0+W) tức nó muốn thực hiện một cuộc gọi chung đến tất cả các Slave. Tất nhiên, cho phép hay không cho phép cuộc gọi chung là do Slave quyết định. Nếu các Slave được cài đặt cho phép cuộc gọi chung, chúng sẽ đáp lại Master bằng ACK. Cuộc gọi chung thường xảy ra khi Master muốn gởi dữ liệu chung đến các Slaves. Chú ý là cuộc gọi chung có dạng 0+R là vô nghĩa vì không thể có chuyện Master nhận dữ liệu từ tất cả các Slave cùng thời điểm. Data Packet Format – Định dạng gói dữ liệu: sau khi địa chỉ đã được phát đi, Slave đã đáp lại Master bằng ACK thì quá trình truyền/nhận dữ liệu sẽ diễn ra giữa cặp Master/Slave này. Tùy vào bit R/W trong gói địa chỉ, dữ liệu có thể được truyền theo hướng từ Master đến Slave hay từ Slave đến Master. Dù di chuyển theo hướng nào, gói dữ liệu luôn bao gồm 9 bits trong đó 8 bits đầu là dữ liệu và 1 bit cuối là bit ACK. Tám bits dữ liệu do thiết bị phát gởi và bit ACK do thiết bị nhận tạo ra. Ví dụ khi Master thực hiện quá trình gởi dữ liệu đến Slave, nó sẽ phát ra 8 bits dữ liệu, Slave nhận và phát lại ACK (kéo SDA xuống 0 ở xung thứ 9), sau đó Master sẽ quyết định gợi tiếp byte dữ liệu khác hay không. Nếu Slave phát tín hiệu NOT ACK (không tác động SDA ở xung thứ 9) sau khi nhận dữ liệu thì Master sẽ kết thúc quá trình gởi bằng cách phát đi STOP condition. Hình bên dưới mô tả định dạng gói dữ liệu trong TWI (I2C).

Page 125: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 125

Phối hợp gói địa chỉ và dữ liệu: một quá trình truyền/nhận TWI (I2C) thường được bắt đầu từ Master, Master phát đi một START condition sau đó gởi gói địa chỉ SLA+R/W trên đường truyền. Tiếp theo nếu có một Slave đáp ứng lại, dữ liệu có thể truyền/nhận liên tiếp trên đường truyền (1 hoặc nhiều byte liên tiếp). Khung truyền thông thường được mô tả như hình bên dưới.

Multi-Master Bus –Đường truyền đa chip chủ: như đã trình bày ở trên, TWI (I2C) là chuẩn truyền thông đa chip chủ, nghĩa là tại một thời điểm có thể có nhiều hơn 1 chip làm Master nếu các chip này phát ra START condition cùng lúc. Nếu các Master có cùng yêu cầu và thao tác đối với Slave thì chúng có thể “cùng tồn tại” và quá trình truyền/nhận có thể thành công. Tuy nhiên, trong đa số trường hợp sẽ có một số Master bị “thất lạc” (lost). Một Master bị lost khi nó truyền/nhận 1 mức cao trên SDA trong khi các Master khác truyền/nhận 1 mức thấp. Truyền thông đa chip chủ tương đối phức tạp và vì thế tôi sẽ không đề cập trường hợp này trong lúc thực hiện ví dụ giao tiếp trong bài học này. Nắm được các khái niệm và đặc điểm trên của truyền thông TWI (I2C) là bạn đã sẵn sàng để điều khiển module TWI trên AVR. Phần tiếp theo tôi sẽ hướng dẫn cách thao tác module TWI trên AVR thông qua một ví dụ cụ thể.

III. TWI trên AVR.

1. Thanh ghi:

Page 126: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 126

TWI trên AVR được vận hành bởi 5 thanh ghi bao gồm thanh ghi tốc độ giữ nhịp TWBR, thanh ghi điều khiển TWCR , thanh ghi trạng thái TWSR, thanh ghi địa chỉ TWAR và thanh ghi dữ liệu TWDR.

- TWBR (TWI Bit Rate Register): là 1 thanh ghi 8 bit quy định tốc độ phát xung giữ nhịp trên đường SCL của chip Master.

Tốc độ phát xung giữ nhịp được tính theo công thức:

Trong đó CPU Clock frequency là tần số hoạt động chính của AVR, TWBR là giá trị thanh thi TWBR và TWPS là giá trị của 2 bits TWPS1 và TWPS0 nằm trong thanh thi trạng thái TWSR. Hai bits này được gọi là bit prescaler, thông thường người ta hay set TWPS1:0 =00 để chọn Prescaler là 1 (40=1). Bảng 1 tóm tắt tốc độ xung giữ nhịp tạo ra trên SCL đối với các giá trị của tham số: Bảng 1. Tốc độ xung giữ nhịp tham khảo.

Page 127: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 127

- TWCR (TWI Control Register): là thanh ghi 8 bit điều khiển hoạt động của TWI.

Bit 7- TWINT (TWI Interrupt Flag): là một cờ báo rất quan trọng. TWINT được tự

động set lên 1 khi TWI kết thúc một quá trình bất kỳ nào đó (như phát/nhận

START, phát nhận địa chỉ…). Chú ý là bit này không tự động được xóa bởi phần

cứng như các cờ báo trong các module khác. Vì thế, khi lập trình điều khiển TWI

chúng ta luôn phải xóa TWINT trước khi muốn thực hiện một quá trình nào đó.

Một điểm quan trọng cần lưu ý là bit TWINT được xóa khi chúng ta viết giá trị 1

vào nó. Trong khi lập trình cho TWI, chúng ta thường xóa TWINT bằng cách viết

1 vào nó, sau đó liên tục kiểm tra TWINT, nếu bit này được set lên 1 thì quá trình

đã hoàn thành.

Bit 6 – TWEA (TWI Enable Acknowledge Bit): tạm hiểu là bit kích hoạt tín hiệu

xác nhận. Đối với chip Slave, nếu bit này được set thì tín hiệu xác ACK sẽ được

gởi trong các trường hợp sau: địa chỉ do Master phát ra trùng khớp với địa chỉ của

Slave; một cuộc gọi chung đang xảy ra và Slave này cho phép cuộc gọi chung; dữ

liệu đã được Slave nhận từ Master. Như thế, khi set một chip ở chế độ Slave,

chúng ta cần set bit này để nó có thể đáp ứng lại Master bất cứ khi nào được gọi.

Đối với chip Master, tín hiệu ACK chỉ được phát trong 1 trường hợp duy nhất đó là

khi Master nhận dữ liệu từ Slave, Master phát ACK để báo cho Slave là mình đã

nhận được và muốn tiếp tục nhận từ Slave.

Page 128: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 128

Bit 5 – TWSTA (TWI START Condition Bit): là bit tạo START condition. Khi

một chip muốn trở thành Master để thực hiện 1 cuộc gọi, bit này cần được set và

một START condition được tạo ra trên đường truyền nếu đường truyền đang rảnh.

Nếu đường truyền không rảnh, TWI sẽ chờ cho đến khi nó rảnh (nhận ra 1 STOP

condition) và tiếp tục gởi START condition. Chú là là bit nay cần được xóa bởi

phần mềm sau khi START condition đã được gởi (viết 0 vào bit này để xóa nó).

Bit 4 – TWSTO (TWI STOP Condition Bit): là bit tạo STOP condition cho TWI.

Khi Master muốn kết thúc một cuộc gọi, nó sẽ phát STOP condition bằng cách viết

giá trị 1 vào bit TWSTO. Slave cũng có thể tác động vào bit này, nếu một cuộc gọi

bị lỗi, viết 1 vào TWSTO trên Slave sẽ reset đường truyền về trạng thái rảnh ban

đầu.

Bit 3 – TWWC (TWI Write Collision Flag): khi cờ TWINT đang ở mức thấp tức

TWI đang bận, nếu chúng ta viết dữ liệu vào thanh ghi dữ liệu (TWDR) thì một lỗi

xảy ra, khi đó bit TWWC tự động được set lên 1. Vì thế, trong quá trình truyền dữ

liệu, bit TWINT cần được giữ mức cao khi ghi dữ liệu vào thanh ghi TWDR và sau

đó xóa khi dữ liệu đã sẵn sàng.

Bit 2 – TWEN (TWI Enable Bit): bit kích hoạt TWI trên AVR, khi TWEN được

set lên 1, TWI sẵn sàng hoạt động.

Bit 1 – Reserve: không sử dụng.

Bit 0 – TWIE (TWI Interrupt Enable Bit): bit cho phép ngắt TWI, khi bit nay được

set bằng 1 đồng thời bit I trong thanh ghi trạng thái chung được set, một ngắt TWI

xảy ra khi bit TWINT được set bởi phần cứng. Ngắt TWI có thể xảy ra sau bất kỳ

Page 129: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 129

hoạt động nào liên quan đến TWI. Do đó cần sử dụng ngắt hợp lý. Thông thường,

ngắt chỉ được sử dụng cho Slave, đối với Master ngắt không cần thiết vì Master

chủ động khởi động một cuộc gọi.

Một điều cần chú ý là các bit trong thanh ghi TWCR không cần được set cùng lúc, tùy vào từng giai đoạn trong quá trình giao tiếp TWI các bit có thể được set riêng lẻ.

- TWSR (TWI Status Register): là 1 thanh ghi 8 bit trong đó có 5 bit chứa code trạng thái của TWI và 2 bit chọn prescaler.

Có rất nhiều bước, nhiều tình huống xảy ra khi giao tiếp bằng TWI cho cả Master và Slave. Ứng với mỗi trường hợp TWI sẽ tạo ra 1 code trong thanh ghi TWSR . Lập trình cho TWI cần xét code trong 5 bit cao của thanh ghi TWSR và đưa ra các ứng xử hợp lý ứng với từng code.

- TWDR (TWI Data Register): là thanh ghi dữ liệu chính của TWI. Trong quá trình nhận, dữ liệu nhận về sẽ được lưu trong TWDR. Trong quá trình gởi, dữ liệu chứa trong TWDR sẽ được chuyển ra đường SDA.

- TWAR (TWI Address Register): là thanh ghi chứa device address của chip Slave. Cấu trúc thanh ghi được trình bày trong hình dưới.

Nhớ lại địa chỉ Slave được tạo thành từ 7 bits, trên thanh ghi TWAR 7 bits địa chỉ này nằm ở 7 vị trí cao. Trước khi sử dụng TWI như Slave, chúng ta phải gán địa chỉ cho chip, việc viết địa chỉ thường được thực hiện bằng lệnh TWAR = (Device_address<<1)+TWGCE. Trong đó TWGCE (TWI General Call Enable) là bit cho phép cuộc gọi chung. Như tôi đề cập bên trên, Slave co quyền cho phép Master thực hiện cuộc gọi chung với nó hay không. Nếu TWGCE=1, Slave sẽ đáp ứng lại cuộc gọi chung nếu có, nếu TWGCE=0 thì Slave sẽ bỏ qua cuộc gọi chung.

2. Hoạt động của TWI:

Page 130: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 130

TWI trên AVR được gọi là byte-oriented (tạm dịch là hướng byte) và interrupt-based (dựa trên ngắt). Bất kỳ một sự kiện nào trong quá trình truyền/nhận TWI cũng có thể gây ra 1 ngắt TWI. TWI trên AVR vì thế hoạt động tương đối độc lập với chip. Tuy nhiên, cần khai thác ngắt trên AVR một cách hơp lý. Ví dụ, đối với Master, chúng ta không cần sử dụng ngắt vì chip này hoàn toàn chủ động trong việc truyền và nhận. Riêng với Slave, sử dụng ngắt để tránh bỏ lỡ các cuộc gọi là cần thiết. Tất cả các AVR trên mạng TWI đều có thể là Master hay Slave, cả Master và Slave đều có thể truyền và nhận dữ liệu. Vì thế, có tất cả 4 mode trong hoạt động của TWI trên AVR. Chúng ta sẽ lần lượt khảo sát các mode này như sau: Master Transmitter (chip chủ truyền), Master Receiver (Chip chủ nhận), Slave Reicever (chip tớ nhận) và Slave Transmitter (Chip tớ truyền). Trước khi khảo sát các chế độ hoạt động của TWI chúng ta qui ước một số ký hiệu thường dùng (đây cũng là các ký hiệu dùng trong datasheet của các chip AVR).

S: START condition – điều kiện bắt đầu Rs: REPEAT START – bắt đầu lặp lại R: READ Bit, bit này bằng 1 được gởi kèm với gói địa chỉ W: WRITE Bit, bit này mang giá trị 0, gởi kèm gói địa chỉ ACK: Ackowledge, bit xác nhận, chân SDA được kéo xuống 0 ở xung thứ 9 NACK: Not Acknowledge, không xác nhận, SDA ở mức cao ở bit thứ 9 Data: 8 bits dữ liệu P: STOP condition – điều kiện kết thúc. SLA: Slave address, địa chỉ của Slave cần giao tiếp.

A. Master Transmitter mode – Master truyền dữ liệu:

Trong chế độ này, Master truyền 1 hoặc một số byte dữ liệu đến một hoặc các Slave. Để bắt đầu, Master tạo ra một START condition trên đường SDA, nếu đường truyền đang rảnh, Master sẽ tiếp tục phát đi địa chỉ của Slave cần giao tiếp cùng với bit W (ghi) theo định dạng như sau: SLA+W. Nếu Slave đáp lại bằng một ACK trong xung giữ nhịp thứ 9, Master sẽ tiếp tục gởi 1 hoặc liên tiếp các byte dữ liệu trên SDA. Cứ sau mỗi byte dữ liệu, Master sẽ kiểm tra ACK từ Slave. Nếu Slave gởi một NACK hoặc Master không muốn gởi thêm dữ liệu đến Slave nó sẽ phát đi một STOP condition hoặc một REPEAT START (Rs). Nếu STOP được phát, cuộc gọi kết thúc, nếu Rs được phát, một cuộc gọi mới bắt đầu, sau Rs là địa chỉ của Slave mới…Đó là về mặt lý thuyết, trên thực tế làm sao để kiểm tra môt START condition có được gởi chưa? làm sao biết có nhận được ACK sau khi phát địa chỉ hoặc dữ liệu? Tất cả được TWI mã hóa thành các code chứa trong thanh ghi TWSR (chỉ 5 bit cao). Chúng ta chỉ thanh ghi này và đối chiếu với bảng code quy

Page 131: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 131

định sẵn để biết trạng thái đường truyền và đưa ra quyết định tiếp theo. Hình 2 mô tả một quá trình Master truyền dữ liệu, các khả năng có thể xảy ra và giá trị tương ứng của thanh ghi TWSR. Ý nghĩa các code trong thanh ghi TWSR trong lúc Master truyền dữ liệu có thể tham khảo thêm datasheet của chip.

Page 132: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 132

Hình 2. Master truyền dữ liệu.

Page 133: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 133

Từ hình 2, chúng ta nhận thấy khi Master truyền dữ liệu, dãy code 0x08 -> 0x18 -> 0x28 ->… -> 0x28 (-> 0x30) là dãy code thành công nhất. Code 0x08 báo rằng START codition được truyền thành công, code 0x18 báo địa chỉ truyền thành công và đã có Slave xác nhận bằng ACK, code 0x28 tức dữ liệu được Master truyền thành công và Slave đã nhận được, báo ACK lại cho Master, code 0x30 tức dữ liệu đã được truyền nhưng Slave không xác nhận lại, lúc này Master có thể phát đi một STOP codition sau code 0x30. Ngoài ra còn một số code khác tương ứng với các trường hợp khác như gởi địa chỉ thất bại (code 0x20), Master bị lost (code 0x38)…Đối với mỗi loại ứng dụng, cách “hành xử” sẽ khác nhau đối với các trường hợp thất bại này. Trong bài này, tôi sẽ bỏ qua tất cả các trường hợp thất bại, nếu một trong các code thất bại xảy ra chúng ta sẽ thoát khỏi cuộc gọi và đưa đường truyền về trạng thái nghỉ.

B. Master Receiver mode – Master nhận dữ liệu:

Trong chế độ này, Master nhận một hoặc một số byte dữ liệu từ một Slave. Để bắt đầu, Master tạo ra một START condition trên đường SDA, nếu đường truyền đang rảnh, Master sẽ tiếp tục phát đi địa chỉ của Slave cần giao tiếp cùng với bit R (đọc) theo định dạng như sau: SLA+R. Nếu Slave đáp lại bằng một ACK trong xung giữ nhịp thứ 9, Master sẽ bắt đầu sample dữ liệu trên SDA. Cứ sau mỗi byte dữ liệu, nếu Master muốn nhận tiếp byte khác nó phải phát ra 1 ACK ở xung thứ 9 báo cho Slave. Khi Master muốn kết thúc quá trình nhận nó sẽ phát một NOT ACK sau khi nhận dữ liệu, liền sau đó Master phát STOP để kết thúc cuộc gọi hoặc phát đi một REPEAT START nếu nó muốn tiếp tục gọi các Slaves khác. Hình 3 mô tả một quá trình Master nhận dữ liệu, các khả năng có thể xảy ra và giá trị code tương ứng của thanh ghi TWSR. Ý nghĩa các code trong thanh ghi TWSR trong lúc Master truyền dữ liệu có thể tham khảo thêm datasheet của chip.

Page 134: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 134

Hình 3. Master nhận dữ liệu.

Từ hình 3, trong quá trình Master nhận dữ liệu, dãy code 0x08 -> 0x40 -> 0x50 ->… -> 0x58 là dãy code thành công nhất. Code 0x08 báo rằng START codition được truyền thành công, code 0x40 báo địa chỉ + R đã được truyền thành công và đã có Slave xác nhận bằng ACK, code 0x50 báo dữ liệu được Master nhận thành công và Master cũng đã phát một ACK bit sau khi nhận, code 0x58 xảy ra khi Master nhận dữ liệu thành công nhưng nó không phát ACK mà phát NOT ACK, báo cho Slave rằng Master không muốn nhận thêm dữ liệu, tiếp theo Master

Page 135: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 135

sẽ phát một STOP condition hoặc một REPEAT START. Các trường hợp khác chúng ta không khảo sát.

C. Slave Receiver mode – Slave nhận dữ liệu:

Hình 4 mô tả một quá trình Slave nhận dữ liệu, các khả năng có thể xảy ra và giá trị code tương ứng của thanh ghi TWSR. Chế độ Slave nhận dữ liệu xảy ra khi Master thực hiện một cuộc gọi phát dữ liệu (SLA+W). Như quan sát trong hình 4, Slave chỉ nhận ra cuộc gọi này khi địa chỉ của nó trùng với địa chỉ của Master (Own address mode) hoặc khi Master thực hiện một cuộc gọi chung. Khi đó, bit TWINT của Slave sẽ được set lên 1. Nếu Slave cho phép ngắt TWI (bit TWIE trong thanh ghi TWCR được set từ lúc đầu) thì một ngắt xảy ra báo có một sự kiện TWI. Nếu code trong thanh ghi TWSR là 0x60 thì một cuộc gọi địa chỉ riêng được yêu cầu và Slave cũng đã đáp ứng lại Master bằng một ACK, Slave sau đó bắt đầu nhận dữ liệu từ đường SDA. Cứ sau một byte dữ liệu Slave phải xác nhận một ACK nếu nó còn muốn tiếp tục nhận. Nếu vì một lý do nào đó mà Slave không thể tiếp tục nhận nó có thể phát một NOT ACK sau một byte dữ liệu. Cuộc gọi kết thúc khi Slave nhận được STOP condition, tương ứng code 0xA0. Cuộc gọi chung cũng diễn ra hoàn toàn tương tự cuộc gọi địa chỉ riêng nhưng code có giá trị khác. Khi viết chương trình cho Slave trong chế độ nhận dữ liệu, chúng ta cần xét cả 2 trường hợp cuộc gọi địa chỉ riêng và cuộc gọi chung.

Page 136: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 136

Hình 4. Slave nhận dữ liệu.

D. Slave Transmitter mode – Slave truyền dữ liệu:

Đây là chế độ cuối cùng trong 4 chế độ của AVR TWI. Hình 5 mô tả một quá trình Slave truyền dữ liệu, các khả năng có thể xảy ra và giá trị code tương ứng của thanh ghi TWSR. Chế độ Slave phát dữ liệu xảy ra khi Master muốn nhận dữ liệu

Page 137: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 137

từ Slave, Master thực hiện một cuộc gọi nhận dữ liệu (SLA+R). Như quan sát trong hình 5, Slave chỉ nhận ra cuộc gọi này khi địa chỉ của nó trùng với địa chỉ của Master (Own address mode). Khi đó, bit TWINT của Slave sẽ được set lên 1. Nếu Slave đáp lại bằng một ACK ở xung nhịp thứ 9, code trong thanh ghi TWSR sẽ là 0xA8, Slave sau đó bắt đầu phát dữ liệu lên đường SDA. Cứ sau mỗi byte dữ liệu, Master sẽ xác nhận một ACK nếu nó còn muốn tiếp tục nhận, code 0xB8 sẽ xuất hiện trong trường hợp này. Nếu Master không muốn tiếp tục nhận dữ liệu từ Slave, một NOT ACK sẽ được phát và code 0xC0 xuất hiện, Slave kết thúc quá trình phát dữ liệu. Một trường hợp đặc biệt khi bit TWEA (bit ACK) trong thanh ghi TWCR của Slave được reset về 0 trước khi Slave truyền dữ liệu, trường hợp Slave muốn báo rằng nó đã hết dữ liệu để truyền, byte tiếp theo cũng là byte cuối cùng. Sau khi Master nhận byte này, nó có thể xác nhận 1 ACK cho Slave (vì thật ra Master không hề biết Slave đang truyền byte cuối), code trên Slave trong trường hợp này là 0xC8 và Slave sẽ tự hết thúc quá trình truyền mà không cần chờ Master. Khi lập trình cho Slave trong chế độ phát, cần phải có sự “thỏa hiệp” với Master trước để tránh code 0xC8 vì code này không có nhiều ý nghĩa.

Hình 5. Slave truyền dữ liệu.

Page 138: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 138

Kỹ thuật chính dùng cho Master khi truyền hay nhận cuộc gọi là hỏi vòng và chờ (polling and waiting). Ứng với mỗi code nhận về từ thanh ghi TWSR (hay ứng với mỗi trạng thái của cuộc gọi) mà Master set các bit tương ứng trong thanh ghi điều khiển TWCR và sau đó chờ bit TWINT được set (quá trình kết thúc) để tiếp tục đọc và xét code TWSR. Quá trình chờ và xét này lặp lại cho đến khi Master kết thúc cuộc gọi bằng STOP condition. Tuy nhiên Slave thì khác, Slave không chủ động thực hiện cuộc gọi mà nó phải chờ yêu cầu từ Master để phục vụ. Vì thế, nếu dùng “hỏi vòng” cho Slave thì sẽ tốn thời gian chờ vô ích và đôi khi còn bỏ lỡ các cuộc gọi. Đối với Slave, ngắt là phương pháp bắt cuộc gọi tối ưu nhất. Trong bài học này, việc truyền và nhận của Slave sẽ được thực hiện trong các trình phục vụ ngắt TWI.

IV. Điều khiển AVR TWI.

Phần này tôi hướng dẫn lập trình điều khiển module TWI AVR bằng WinAVR. Các hình 2, 3, 4 và 5 cần được tham khảo kèm kỹ vì code trong phần này được phát triển từ các hình này. Để đơn giản, chúng ta sẽ viết các hàm giao tiếp TWI trong 1 file riêng gọi là “myTWI.h”, đây có thể coi là thư viện cho TWI dùng trong trang web này. Như đã trình bày, chuẩn I2C thì duy nhất nhưng cách sắp xếp dữ liệu của các chip I2C thì rất đa dạng. Vì thế, khi muốn giao tiếp với một chip I2C nào bạn nhất thiết phải đọc datasheet của chip đó để hiểu định dạng dữ liệu. Các hàm trong thư viện myTWI chỉ phục vụ giao tiếp giữa các AVR với nhau, nếu muốn sử dụng chúng giao chip với một chip EEPROM 24C1004 chẳng hạn, bạn phải viết thêm các hàm mở rộng khác dựa trên các hàm này. Nội dung file myTWI.h được chia thành 3 phần, phần đầu là các định nghĩa biến, tham số chung, phần 2 gồm các hàm truyền/nhận cho Master và phần 3 là trình phục vụ ngắt TWI cho Slave. List 1 trình bày các định nghĩa chung trong file “myTWI.h”. List 1. Định nghĩa chung.

Page 139: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 139

Page 140: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 140

Phần này chủ yếu định nghĩa các code trạng thái trong quá trình thao tác TWI trên AVR mà chúng ta đã biết khi khảo sát các chế độ hoạt động của TWI. Thật ra bạn có thể tham khảo các hình 2-5 và các bảng code trong datasheet của AVR và sử dụng các code trạng thái trực tiếp trong lúc lập trình, tôi định nghĩa như trên chỉ để tiện theo dõi trong lúc lập trình. Các dòng từ 12 đến 25 định nghĩa các code trạng thái cho Slave (cả truyền và nhận). Chúng ta cũng định nghĩa một số biến toàn cục dùng cho Slave, biến SLAVE_wData[100] là một mảng 100 phần tử dùng chứa dữ liệu mà Slave sẽ truyền, biến Tran_Num là chỉ số của phần tử trong mảng SLAVE_wData sẽ được truyền đi. Biến SLAVE_buff[100] là dữ liệu nhận về từ TWI và Rec_Num là chỉ số của dữ liệu sau cùng do TWI nhận về (dữ liệu SLAVE_buff[Rec_Num]). Biến Device_Addr chứa địa chỉ mà khi là Slave của chính AVR chúng ta đang lập trình. Tương tự, các dòng từ 47 đến 57 định nghĩa code trạng thái cho Master mode. Trước đó, chúng ta cũng định nghĩa các giá trị tốc độ phát xung giữ nhịp sẽ gán cho thanh ghi TWBR (dòng 37, 38). Hai biến TWI_R và TWI_W đại diện cho 2 bit R/W được truyền trong gói địa chỉ (báo cho Slave biết Master muốn truyền hay nhận dữ liệu). Một số macro trong các dòng 42 đến 45 bao gồm START, STOP condition và xóa bit TWINT bằng cách gán các giá trị tương ứng cho thanh ghi điều khiển TWI. Cuối cùng là chương trình con void TWI_Init(void) khởi động TWI. Quá trình khởi động bao gồm set tốc độ xung giữ nhịp cho Master (dòng 61, 62), gán địa chỉ device (dòng 63) và xác lập TWI sẵn sàng ở chế độ Slave. Xem lại thanh ghi TWAR, do 7 bit địa chỉ nằm ở vị trí cao nên chúng ta cần phải dịch trái địa chỉ 1 vị trí trước khi gán cho TWAR (Device_Addr <<1), đồng thời set bit 0 trong TWAR để cho phép nhận cuộc gọi chung khi được yêu cầu. Dòng 64 khởi động TWI với bit ACK sẵn sàng và cho phép xảy ra ngắt TWI. Như thế, sau khi khởi động TWI sẵn sàng ở chế độ Slave.

List 2. Code cho Master.

Page 141: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 141

Page 142: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 142

Hàm TWI_Master_Send_array(uint8_t Addr, uint8_t Data[], uint8_t len) thực hiện truyền 1 dãy các byte dữ liệu trong mode Master. Tham số Addr là địa chỉ của Slave cần giao tiếp, Data[] là mảng dữ liệu và len là chiều dài (số byte) của dữ liệu cần truyền. Việc đầu tiên khi chúng ta vào Master mode là “tắt” ngắt TWI bằng cách xóa bit TWIE (dòng 3). Trình tự Master truyền dữ liệu hoàn toàn tương tự trình tự trong hình 2. Dòng 5, TWCR=TWI_START, Master bắt đầu phát 1 START condition. Nếu xem lại định nghĩa của macro TWI_START trong list 1 bạn sẽ thấy dòng TWCR=TWI_START tương đương TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN) tức chúng ta thực hiện xóa bit TWINT (bit này phải luôn được xóa trước khi muốn thực hiện viêc gì) bằng cách ghi 1 vào TWINT, set bit START (bit TWSTA) và cho phép TWI hoạt động bằng bit TWEN. Dòng code 6 chờ cho đến khi bit TWINT được phần cứng set lên 1 (kết thúc), sau đó chúng ta kiểm tra code trong thanh ghi trạng thái TWSR. Chú ý là chỉ có 5 bit cao trong thanh ghi TWSR chứa trạng thái nên chúng ta cần dùng giải thuật mặt nạ che các bit thấp lại, TWSR & 0xF8 chính là cách để che 3 bit thấp của TWSR. So sánh code đọc được với code tương ứng trong hình 1, trong trường hợp này chúng ta so sánh với _START_Sent, chính là so sánh với 0x80 (xem lại định nghĩa của _START_Sent trong list 1). Nếu các code không trùng nhau, một lỗi truyền xảy ra và chúng ta sẽ thoát khỏi chương trình truyền, giá trị trả về chính là code có lỗi (xem dòng code 7). Các dòng code từ 10 đến 13 thực hiện truyền địa chỉ + W, chú ý trong lúc phát, dữ liệu cần phát phải được ghi sẵn vao thanh ghi dữ liệu TWDR trước khi xóa bit TWINT (dòng 10 và 11). Sau khi truyền địa chỉ chúng ta truyền mảng dữ liệu liên tiếp và cuối cùng là phát STOP condition, TWCR=TWI_STOP tương đương TWCR=(1<<TWINT)|(1<<TWSTO)|(1<<TWEN). Cần khởi động lại TWI để đưa nó về chế độ Slave trước khi thoát khỏi chương trình con truyền dữ liệu của chế độ Master (dòng 24). Hàm TWI_Master_Read_array(uint8_t Addr, uint8_t Data[], uint8_t len) thực hiện nhận dữ liệu về Master. Cách giải thích cho hàm này không khác nhiều so với hàm đọc dữ liệu nên bạn đọc tự tìm hiểu. Một điểm cần chú ý là khi nhận 1 dãy byte chúng ta nên đọc n-1 byte đầu bình thường, có trả ACK cho Slave và byte cuối cùng sẽ được nhận riêng, trả NOT ACK để báo cho Slave rằng Master không muốn nhận thêm(đoạn code từ dòng 55 đến 59 dùng đọc byte cuối cùng).

List 3. Code cho Slave.

Page 143: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 143

Page 144: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 144

Như tôi đã trình bày, toàn bộ quá trình truyền và nhận của Slave được thưc hiện trong chương trình phục vụ ngắt TWI. Khi ngắt TWI xảy ra, trình phục vụ ngắt sẽ đọc và kiểm tra code trong thanh ghi TWSR để thực hiện các công việc phù hợp. Bạn đọc lại tham khảo thêm hình 4 và hình 5 cùng với các code trong những “case” tương ứng của List 3 để hiểu đoạn chương trình này. Điểm lưu ý lớn nhất mà tôi muốn nói là các biến được dùng cho chế độ Slave truyền và nhận. Tôi dùng 2 mảng SLAVE_wData và SLAVE_buff để chứa biến truyền và nhân. Hai biến Tran_Num và Rec_Num là chỉ số của byte hiện hành. Vì thế SLAVE_wData[Tran_Num] chính là byte tiếp theo sẽ được truyền đi nếu Slave được yêu cầu truyền, và SLAVE_buff[Rec_Num] là byte cuối cùng mà Slave nhận về trong chế độ Slave nhận dữ liệu. Hãy khái thác các biến này trong các chương trình ứng dụng. Để minh họa cho các sử dụng các hàm trong thư viện myTWI, tôi thực hiện một mạch điện mô phỏng mạng TWI gồm 3 chip ATmega32. Chip thứ nhất là Master, 2 chip còn lại là Slaves. Tôi tạo 2 Project, một cho Master và một cho 2 Slaves dùng chung. PORTD được set input có điện trở kéo lên. Tôi dùng 2 chân PD6 và PD7 để chọn địa chỉ cho 2 Slaves, Slave thứ nhất tôi nối chân PD6 xuống GND, do đó chip này có địa chỉ Device_Addr là PD7:PD6=10=2 (thập phân). Slave còn lại tôi để 2 chân PD6 và PD7 trống nên địa chỉ của nó là PD7:PD6=11=3. Trong chương trình của Slave có phần đọc 2 chân PD6:PD7 và gán cho biến Device_Addr mà chúng ta đã khai báo trong List 1, như vậy có thể dùng các này để set địa chỉ cho Slaves mà chúng ta gọi là “set địa chỉ cứng”. Trên chip Master, một swich được nối với chân PD0 để chọn Slave cần giao tiếp, nếu switch đóng thì SLAVE có địa chỉ 2 được chọn, nếu switch mở thì SLAVE có địa chỉ 3 được chọn để giao tiếp. Một nút nhấn được nối với ngắt INT0 của chip Master, khi nhấn nút này chương trình còn đọc dữ liệu từ Slave được gọi, tùy theo switch đóng hay mở mà Slave tương ứng được gọi để gởi dữ liệu cho Master. Dữ liệu nhận về sẽ hiển thị trên 1 Character LCD. Hình 6 là sơ đồ mạch điện mô phỏng bằng phần mềm Proteus và List 4, List 5 lần lượt trình bày đoạn code cho chương trình chính của Master và Slave.

Page 145: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 145

Hình 6. Demo TWI.

List 4. Chương trình chính cho Master.

Page 146: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 146

Page 147: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 147

Ví dụ của Master minh họa cách dùng 2 hàm Master truyền và nhận mảng dữ liệu. Ở dòng 27 tôi dùng hàm TWI_Master_Send_array để gởi 40 phần tử của mảng Data đến Slave có địa chỉ 2, TWI_Master_Send_array(2,Data,40). Tương tự, dòng 31 gởi 50 phần tử của mảng Data đến Slave có địa chỉ 3. Khi button trên mạch mô phỏng được nhấn, ngắt INT0 xảy ra, trong trình phục vụ ngắt INT0 chúng ta dùng hàm TWI_Master_Read_array để đọc dữ liệu từ một trong 2 Slaves, xem dòng code 43: TWI_Master_Read_array(Slave_Addr,rData,1). Địa chỉ của Slave cần đọc sẽ do switch nối với chân PD0 quyết định (xem dòng 42). Địa chỉ của Slave đang giao tiếp sẽ hiển thị trên dòng 1 của LCD, dữ liệu được hiển thị trên dòng 2.

List 5. Chương trình chính cho Slaves.

Page 148: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 148

Page 149: TÀI LIỆU TỪ TRANG HOCAVR.COM

[CAO SONG TOÀN] [email protected]

Cơ điện tử 1-K4 0976958769-0948849945 Page 149

Chương trình demo của Slaves minh họa các chế độ Slave truyền và nhận dữ liệu. Tuy nhiên do các quá trình truyền và nhận dữ liệu của Slave được thực hiện trong trình phục vụ ngắt TWI được viết sẵn trong file myTWI.h. trong chương trình chính của Slave chúng ta không cần phải gọi bất kỳ hàm nào trong myTWI. Công việc cần làm trong chương trình demo cho Slave là khởi động TWI sau đó gán giá trị cho các biến toàn cục của Slave (dòng 21 gán giá trị cho mảng SLAVE_wData). Tôi có đính kèm ví dụ demo cho TWI, tôi thực hiện 2 Projetc trong 2 thư mục: TWI1 cho AMster và TWI2 cho Slave. Để chạy demo, chạy file TWI bằng Proteus, dùng switch SW1 để chọn Slave cần giao tiếp, nhấn button để nhận dữ liệu từ Slave. Thay đổi vị trí switch và kiểm tra kết quả.