Bài 5 - Giao tiếp UART

( 111 Votes )
There are no translations available.

Nội dung Các bài cần tham khảo trước
  1. Giới thiệu.
  2. Truyền thông nối tiếp không đồng bộ.
  3. Truyền thông nối tiếp không đồng bộ với AVR (UART).
    1. Thanh ghi.
    2. Sử dụng UART.
    Download ví dụ

 

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

       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.

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

parallel vs serial

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). 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à đọ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ế hiệu quả hơn truyền thông đồng bộ (không cần nhiều lines truyền). 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 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 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

UDR register

  • UCSRA (USART Control and Status Register A): là 1 trong 3 thanh ghi điều khiển hoạt động của module USART.

UCSRA Register

       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.
UCSRB Register
* 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.
UCSRC Register
       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.
Parity modes
* 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.
Character size
* 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..
  • UBRRL và UBRRH (USART Baud Rate Register): 2 thanh ghi thấp và cao quy định tốc độ baud.
Baud Register
       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.
Baud computation
       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.
Baud Sample1
Baud Sample2
Baud Sample3
Baud Sample4

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. 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
19
20
21
22
23
24
25
26

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

Protues sim. ex1

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

Sim setting1

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

2.2 Nhận dữ liệu.

       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
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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;
}
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 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.

Protues sim. ex2

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