Download docx - lap trinh 8051

Transcript

CHƯƠNG 1. Bộ đếm/ bộ định thời trong 8051

1.1. Mục tiêu

Kết thúc bài học này, bạn sẽ nắm được:

  Bộ đếm, bộ định thời là gì?  Các thanh ghi liên quan  Cách thức hoạt động của bộ đếm/bộ định thời  Các bước lập trình bộ đếm/bộ định thời

1.2. Giới thiệu

Bộ đếm/Bộ định thời: Đây là các ngoại vi được thiết kế để thực hiện một nhiệm vụ đơn giản: đếm các xung nhịp. Mỗi khi có thêm một xung nhịp tại đầu vào đếm thì giá trị của bộ đếm sẽ được tăng lên 01 đơn vị (trong chế độ đếm tiến/đếm lên) hay giảm đi 01 đơn vị (trong chế độ đếm lùi/đếm xuống).

Xung nhịp đưa vào đếm có thể là một trong hai loại:

  Xung nhịp bên trong IC: Đó là xung nhịp được tạo ra nhờ kết hợp mạch dao động bên trong IC và các linh kiện phụ bên ngoài nối với IC. Trong trường hợp sử dụng xung nhịp loại này, người ta gọi là các bộ định thời (timers). Do xung nhịp bên loại này thường đều đặn nên ta có thể dùng để đếm thời gian một cách khá chính xác.

  Xung nhịp bên ngoài IC: Đó là các tín hiệu logic thay đổi liên tục giữa 02 mức 0-1 và không nhất thiết phải là đều đặn. Trong trường hợp này người ta gọi là các bộ đếm (counters). Ứng dụng phổ biến của các bộ đếm là đếm các sự kiện bên ngoài như đếm các sản phầm chạy trên băng chuyền, đếm xe ra/vào kho bãi…

Một khái niệm quan trọng cần phải nói đến là sự kiện “tràn” (overflow). Nó được hiểu là sự kiện bộ đếm đếm vượt quá giá trị tối đa mà nó có thể biểu diễn và quay trở về giá trị 0. Với bộ đếm 8 bit, giá trị tối đa là 255 (tương đương với FF trong hệ Hexa) và là 65535 (FFFFH) với bộ đếm 16 bit.

            8051 có 02 bộ đếm/bộ định thời. Chúng có thể được dùng như các bộ định thờiđể tạo một bộ trễ thời gian hoặc như các bộ đếm để đếm các sự kiện xảy ra bên ngoài bộ VĐK. Trong bài này chúng ta sẽ tìm hiểu về cách lập trình cho chúng và sử dụng chúng như thế nào. Phần 1 là Lập trình bộ định thời, và phần 2 là Lập trình cho bộ đếm.

1.3. Các bộ định thời của 8051

            8051 có hai bộ định thời là Timer 0 và Timer 1, ở phần này chúng ta bàn về các thanh ghi của chúng và sau đó trình bày cách lập trình chúng như thế nào để tạo ra các độ trễ thời gian.

1.3.1. Các thanh ghi cơ sở của bộ định thời

            Cả hai bộ định thời Timer 0 và Timer 1 đều có độ dài 16 bit được truy cập như hai thanh ghi tách biệt byte thấp và byte cao. Chúng ta sẽ bàn riêng về từng thanh ghi.

1.3.2. Các thanh ghi của bộ Timer 0

            Thanh ghi 16 bit của bộ Timer 0 được truy cập như byte thấp và byte cao:

  Thanh ghi byte thấp được gọi là TL0 (Timer0 Low byte).  Thanh ghi byte cao được gọi là TH0 (Timer0 High byte).

Các thanh ghi này có thể được truy cập, hoặc được đọc như mọi thanh ghi khác chẳng hạn như A, B, R0, R1, R2 v.v...

Hình 1: Các thanh ghi của bộ Timer 0

1.3.3. Các thanh ghi của bộ Timer 1

            Giống như timer 0, bộ định thời gian Timer 1 cũng dài 16 bit và thanh ghi 16 bit của nó cũng được chia ra thành hai byte là TL1 và TH1. Các thanh ghi này được truy cập và đọc giống như các thanh ghi của bộ Timer 0 ở trên.

Hình 2: Các thanh ghi của bộ Timer 1.

1.3.4. Thanh ghi TMOD

            Cả hai bộ định thời Timer 0 và Timer 1 đều dùng chung một thanh ghi được gọi là TMOD: để thiết lập các chế độ làm việc khác nhau của bộ định thời.

Thanh ghi TMOD là thanh ghi 8 bit gồm  có:

  4 bit thấp để thiết lập cho bộ Timer 0.  4 bit cao để thiết lập cho Timer 1.

Trong đó:

  2 bit thấp của chúng dùng để thiết lập chế độ của bộ định thời.  2 bit cao dùng để xác định phép toán.

  Hình 3: Thanh ghi TMOD.

a. Các bit M1, M0

            Là các bit chế độ của các bộ Timer 0 và Timer 1. Chúng chọn chế độ của các bộ định thời: 0, 1, 2 và 3 như bảng dưới. Chúng ta chỉ tập chung vào các chế độ thường được sử dụng rộng rãi nhất là chế độ 1 và chế độ 2. Chúng ta sẽ sớm khám phá ra các đặc tính của các chế độ này sau khi khám phần còn lại của thanh ghi TMOD. Các chế độ được thiết lập theo trạng thái của M1 và M0 như sau:

M1 M0 Chế độ Chế độ hoạt động

0 0 0Bộ định thời 13 bit:8 bit  là bộ định thời/bộ đếm, 5 bit đặt trước.

0 1 1 Bộ định thời 16 bit: không có đặt trước.

1 0 2 Bộ định thời 8 bit: tự nạp lại.

1 1 3 Chế độ bộ định thời chia tách.

Bảng 1: Các chế độ hoạt động của bộ đếm/bộ định thời

b. Bit C/T (Counter/Timer)

            Bit này trong thanh ghi TMOD được dùng để quyết định xem bộ định thời được dùng như một máy tạo độ trễ hay bộ đếm sự kiện. Nếu bit C/T = 0 thì nó được dùng như một bộ định thời tạo độ trễ thời gian.

Ví dụ 1:TMOD = 0000 0001 (01H) : chế độ 1 của bộ định thời Timer 0 được chọn.TMOD = 0010 0000 (20H) : chế độ 2 của bộ định thời Timer 1 được chọn.TMOD = 0001 0010 (12H) : chế độ 1 của bộ định thời Timer 1 và chế độ 2 của Timer 0 được chọn.

            Nguồn đồng hồ cho chế độ trễ thời gian là tần số thạch anh của 8051. Điều đó có nghĩa là độ lớn của tần số thạch anh đi kèm với 8051 quyết định tốc độ nhịp của các bộ định thời trên 8051. Tần số của bộ định thời luôn bằng 1/12 tần số của thạch anh gắn với 8051.

Hình 4: Tần số của bộ đếm/bộ định thời

Ví dụ 2:

Tần số thạch anh

Tần số bộ định thời Chu kỳ bộ định thời

20MHz 20MHz/12=1,6666MHz 1/1,6666MHz=0,6us

12MHz 12MHz/12=1MHz 1/1MHz=1us

11,0592MHz 11,0592MHz/12=0,9216MHz 1/0,9216MHz=1,085us

Bảng 2: Một số tần số thông dụng

            Mặc dù các hệ thống 8051 có thể sử dụng tần số thạch anh từ 10 đến 40MHz, song ta chỉ tập trung vào tần số thạch anh 11,0592MHz. Lý do đằng sau một số lẻ như vậy là tốc độ baud đối với truyền thông nối tiếp của 8051. Tần số XTAL = 11,0592MHz cho phép hệ thống 8051 truyền thông với PC mà không có lỗi.

c. Bit c ng GATEổ

            Một bit khác của thanh ghi TMOD là bit cổng GATE. Để ý trên hình 3 ta thấy cả hai bộ định thời Timer0 và Timer1 đều có bit GATE. Vậy bit GATE dùng để làm gì? Mỗi bộ định thời thực hiện điểm khởi động và dừng. Một số bộ định thời thực hiện điều này bằng phần mềm, một số khác bằng phần cứng và một số khác vừa bằng phần cứng vừa bằng phần mềm. Các bộ định thời trên 8051 có cả hai:

  Việc khởi động và dừng bộ định thời được khởi động bằng phần mềm bởi cácbit khởi động bộ định thời TR là TR0 và TR1. Điều này có được nhờ các lệnh Set bit TR0 lên 1 (khởi động bộ định thời) hoặc Clear bit TR0 (dừng bộ định thời) đối với Timer 0, và tương tự TR1 đối với Timer 1. Các lệnh này có tác dụng khi bit GATE = 0 trong thanh ghi TMOD.

  Việc khởi động và ngừng bộ định thời bằng phần cứng từ nguồn ngoài bằng cách đặt bit GATE = 1 trong thanh ghi TMOD.

Tuy nhiên, để tránh sự lẫn lộn ngay từ bây giờ ta đặt GATE = 0 có nghĩa là không cần khởi động và dừng các bộ định thời bằng phần cứng từ bên ngoài.

Ví dụ 3:TMOD = 0000 0010: Bộ định thời là Timer0, chế độ 2, C/T = 0 dùng nguồn

XTAL, GATE = 0 dùng phần mềm để khởi động và dừng bộ định thời.

            Như vậy, bây giờ chúng ta đã có hiểu biết cơ bản về vai trò của thanh ghi TMOD, chúng ta sẽ xét từng chế độ của bộ định thời và cách chúng được lập trình như thế nào để tạo ra một độ trễ thời gian.

1.4. Lập trình cho chế độ 1

            Dưới đây là những bước hoạt động của timer ở chế độ 1:

  Đây là bộ định thời 16 bit, do vậy nó cho phép các giá trị 0000 đến FFFFHđược nạp vào các thanh ghi TL và TH của bộ định thời.

  Sau khi TL và TH được nạp một giá trị khởi tạo 16 bit thì bộ định thời phải được khởi động. Điều này được thực hiện bởi việc SET bit TR0 đối vớiTimer 0 và SET bit TR1 đối với Timer 1.

  Sau khi bộ định thời được khởi động, nó bắt đầu đếm lên. Nó đếm lên cho đến khi đạt được giới hạn FFFFH của nó. Sau đó, khi nó quay từ FFFFH về 0000thì nó bật lên bit cờ TF được gọi là cờ bộ định thời. Cờ bộ định thời này có thể được hiển thị. Khi cờ bộ định thời này được thiết lập, để dừng bộ định thời: ta thực hiện xóa các bit TR0 đối với Timer 0 hoặc TR1 đối với Timer 1. Ở đây cũng cần phải nhắc lại là đối với mỗi bộ định thời đều có cờ TF riêng của mình: TF0 đối với Timer 0 và TF1 đối với Timer 1.

  Sau khi bộ định thời đạt được giới hạn của nó là giá trị FFFFH, muốn lặp lại quá trình thì các thanh ghi TH và TL phải được nạp lại với giá trị ban đầu và cờTF phải được xóa về 0.

Hình 5: Timer/counter chế độ 1

1.4.1. Các bước lập trình ở chế độ 1

            Để tạo ra một độ trễ thời gian dùng chế độ 1 của bộ định thời thì cần phải thực hiện các bước dưới đây:

1.      Nạp giá trị TMOD cho thanh ghi báo độ định thời nào (Timer0 hay Timer1) được sử dụng và chế độ nào được chọn.

2.      Nạp các thanh ghi TL và TH với các giá trị đếm ban đầu.3.      Khởi động bộ định thời.4.      Duy trì kiểm tra cờ bộ định thời TF bằng một vòng lặp để xem nó được bật lên 1

không. Thoát vòng lặp khi TF được lên cao.5.      Dừng bộ định thời.6.      Xoá cờ TF cho vòng kế tiếp.7.      Quay trở lại bước 2 để nạp lại TL và TH.

Công thức tính toán độ trễ sử dụng chế độ 1 (16 bit) của bộ định thời đối với tần số thạch anh XTAL = f (MHz):

a) Tính theo số Hex b) Tính theo số thập phân

(FFFF - YYXX + 1)*12/f (s) trong đó YYXX là các giá trị khởi tạo của TH, TL tương ứng. Lưu ý rằng các giá trị YYXX là theo số Hex.

Chuyển đổi các giá trị YYXX của TH, TL về số thập phân để nhận một số thập phân NNNNN sau đó lấy (65536 – NNNNN)*12/f (s).

Bảng 3: Công thức tính độ trễ thời gian theo tần số XTAL (f)

Ví dụ 4:            Trong chương trình dưới đây ta tạo ra một sóng vuông với độ đầy xung 50% (cùng tỷ lệ giữa phần cao và phần thấp) trên chân P1.5. Bộ định thời Timer0 được dùng để tạo độ trễ thời gian:

#include<at89x51.h>                      //khai báo thư viện cho VĐK 89x51void delay(void);                             //khi báo nguyên mẫu hàm con tạo trễmain(){            P1_5=1;                                 //khởi tạo chân P1_5 ở mức cao            while(1)                                 //vòng lặp vô hạn            {                        delay();                      //chương trình con tạo trễ                        P1_5=~P1_5;            //đảo tín hiệu chân P1_5            }}void delay(void)                              //định nghĩa hàm delay{                        TMOD=0x01;           //chọn timer0, chế độ 1, 16Bit                        TL0=0xF2;                //nạp giá trị cho TL0                        TH0=0xFF;                //nạp giá trị cho TH0                        TR0=1;                       //khởi động timer0                        while(!TF0){}           //vòng lặp kiểm tra cờ TF0                        TR0=0;                       //ngừng timer0                        TF0=0;                       //xóa cờ TF0}

Trong chương trình chính (hàm main) thực hiện gọi hàm con delay() tạo trễ, và đảo liên tục tín hiệu đầu ra ở chân P1_5.            Trong chương trình con delay() trên đây chú ý các bước sau:

1.      TMOD được nạp.2.      Giá trị FFF2H được nạp và TH0 - TL03.      Bộ định thời Timer0 được khởi động bởi lệnh Set bit TR0.4.      Bộ Timer0 đếm lên 01 sau mỗi chu kỳ của timer. Khi bộ định thời đếm tăng qua các

trạng thái FFF3, FFF4 ... cho đến khi đạt giá trị FFFFH là nó quay về 0000H và bật cờ bộ định thời TF0 = 1. Tại thời điểm này vòng lặp kiểm tra cờ TF0 mới được thoát ra.

5.      Bộ Timer0 được dừng bởi lệnh clear bit TR0.6.      Cờ TF0 cũng được xóa, sẵn sàng cho chu trình tiếp theo.

Lưu ý rằng để lặp lại quá trình trên ta phải nạp lại các thanh ghi TH và TL và khởi động lại bộ định thời (đơn giản là ta gọi lại hàm delay()).

Hình 6: Một chu trình đếm của timer0

Tính toán độ trễ tạo ra bởi bộ định thời ở chương trình trên với tần số XTAL=11,0592MHz:            Bộ định thời làm việc với tần số đồng hồ bằng 1/12 tần số XTAL, do vậy ta có 11,0592MHz/12=0,9216MHz là tần số của bộ định thời. Kết quả là mỗi nhịp xung đồng hồ có chu kỳ T=1/0,9216MHz=1,085us. Hay nói cách khác, bộ Timer0 tăng 01 đơn vị sau 1,085s để tạo ra bộ trễ bằng số_đếm1,085s.            Số đếm bằng FFFFH - FFF2H = ODH (13 theo số thập phân). Tuy nhiên, ta phải cộng 1 vào 13 vì cần thêm  một nhịp đồng hồ để nó quay từ FFFFH về 0000H và bật cờ TF. Do vậy, ta có 14  1,085s = 15,19s cho nửa chu kỳ và cả chu kỳ là T = 2  15,19s = 30, 38s là thời gian trễ được tạo ra bởi bộ định thời.

            Tuy nhiên, trong tính toán độ trễ ở trên ta đã không tính đến tổng phí các lệnh cài đặt timer0, các lệnh kiểm tra trong vòng lặp, gọi hàm con… Chính các câu lệnh này làm cho độ trễ dài hơn, dẫn đến tần số của xung vuông ở đầu ra P1_5 không còn đúng như tính toán ở trên. Đây là nhược điểm của C trong lập trình VĐK. Tùy vào từng chương trình biên dịch, mỗi lệnh của C sẽ được biên dịch ra số lệnh ASM khác nhau, để tính toán chính xác ta phải tính cả tổng phí từng dòng lệnh ASM.

1.4.2. Tìm các giá trị cần được nạp vào bộ định thời

            Giả sử rằng chúng ta biết lượng thời gian trễ mà ta cần thì câu hỏi đặt ra là làm thế nào để tìm ra được các giá trị cần thiết cho các thanh thi TH và TL. Để tính toán các giá trị cần được nạp vào các thanh ghi TH và TL chúng ta hãy nhìn vào ví dụ sau với việc sử dụng tần số dao động XTAL = 11. 0592MHz đối với hệ thống 8051.

            Các bước để tìm các giá trị của các thanh ghi TH và TL:1.      Chia thời gian trễ cần thiết cho 1.085s2.      Thực hiện 65536 - n với n là giá trị thập phân nhận được từ bước 1.3.      Chuyển đổi kết quả ở bước 2 sang số Hex: ta có YYXX là giá trị Hexa ban đầu cần phải

nạp vào các thanh ghi bộ định thời.4.      Đặt TL = XX và TH = YY.

Ví dụ 5:            Giả sử tần số XTAL = 11.0592MHz. Hãy tìm các giá trị cần được nạp vào các thanh ghi vào các thanh ghi TH và TL nếu ta muốn độ thời gian trễ là 5ms.Lời giải:

            Vì tần số XTAL = 11.0592MHz nên bộ đếm tăng sau mỗi chu kỳ 1.085s. Điều đó có nghĩa là phải mất rất nhiều khoảng thời gian 1,085s để có được một xung 5ms. Để có được ta chia 5ms cho 1.085s và nhận được số n = 4608 nhịp. Để nhận được giá trị cần được nạp vào TL và TH thì ta tiến hành lấy 65536 trừ đi 4608 bằng 60928. Ta đổi số này ra số hex thành EE00H. Do vậy, giá trị nạp vào TH là EE Và TL là 00.

void delay(void)                              //định nghĩa hàm delay{                        TMOD=0x01;           //chọn timer0 chế độ 1 16Bit                        TL0=0x00;                //nạp giá trị cho TL0                        TH0=0xEE;               //nạp giá trị cho TH0                        TR0=1;                       //khởi động timer0                        while(!TF0){}           //vòng lặp kiểm tra cờ TF0                        TR0=0;                       //ngừng timer0                        TF0=0;                       //xóa cờ TF0}

Ví dụ 6:            Giả sử ta có tần số XTAL là 11,0592MHz. Hãy tìm các giá trị cần được nạp vào các thanh ghi TH và TL để tạo ra một sóng vuông tần số 2kHz.

Xét các bước sau:1. T = 1/f = 1/2KHz = 500us là chu kỳ của sóng vuông.

2.  Khoảng thời gian phần cao và phần thấp là: T/2 = 250s.3.      Số nhịp cần trong thời gian đó là:250us/1,085us = 230. Giá trị cần nạp vào các thanh ghi

cần tìm là 65536 - 230 = 65306 và ở dạng hex là FF1AH.4.      Giá trị nạp vào TL là 1AH, TH là FFH.

Chương trình cần viết là:

void delay(void)                              //định nghĩa hàm delay{                        TMOD=0x10;           //chọn timer1 chế độ 1 16Bit                        TL1=0x1A;               //nạp giá trị cho TL1                        TH1=0xFF;                //nạp giá trị cho TH1                        TR1=1;                       //khởi động timer1                        while(!TF1){}           //vòng lặp kiểm tra cờ TF1                        TR1=0;                       //ngừng timer1                        TF1=0;                       //xóa cờ TF1}

1.5. Chế độ 0

            Chế độ 0 hoàn toàn giống chế độ 1 chỉ khác là bộ định thời 16 bit được thay bằng13 bit. Bộ đếm 13 bit có thể giữ các giá trị giữa 0000 đến 1FFFF trong TH - TL. Do vậy khi bộ định thời đạt được giá trị cực đại của nó là 1FFFH thì nó sẽ quay trở về 0000và cờ TF được bật lên.

1.6. Lập trình cho chế độ 2

            Dưới đây là những bước hoạt động của timer ở chế độ 2:

  Nó là một bộ định thời 8 bit, do vậy nó chỉ cho phép các giá trị từ 00 đến FFHđược nạp vào thanh ghi TH của bộ định thời.

  Sau khi 2 thanh ghi TH và TL được nạp giá trị ban đầu thì bộ định thời phải được khởi động.

  Sau khi bộ định thời được khởi động, nó bắt đầu đếm tăng lên bằng cách tăng thanh ghi TL. Nó đếm cho đến khi đại giá trị giới hạn FFH của nó. Khi nó quay trở về 00 từ FFH, nó thiết lập cờ bộ định thời TF. Nếu ta sử dụng bộ định thời Timer0 thì đó là cờ TF0, còn Timer1 thì đó là cờ TF1.

  Khi thanh ghi TL quay trở về 00 từ FFH, cờ TF được bật lên 1 thì thanh ghi TLđược tự động nạp lại với giá trị sao chép từ thanh ghi TH. Để lặp lại quá trình chúng ta đơn giản chỉ việc xoá cờ TF và để cho nó chạy mà không cần sự can thiệp của lập trình viên để nạp lại giá trị ban đầu. Điều này làm cho chế độ 2 được gọi là chế độ tự nạp lại so với chế độ 1 (phải nạp lại các thanh ghi TH và TL).

Hình 7: Timer/counter chế độ 2

Cần phải nhấn mạnh rằng: chế độ 2 là bộ định thời 8 bit. Tuy nhiên, nó lại có khả năng tự nạp, khi tự nạp lại thì giá trị ban đầu của TH được giữ nguyên, còn TL được nạp lại giá trị sao chép từ TH.

Chế độ này có nhiều ứng dụng, bao gồm việc thiết lập tần số baud trong truyền thông nối tiếp.

1.6.1. Các bước lập trình cho chế độ 2

Để tạo ra một thời gian trễ sử dụng chế độ 2 của bộ định thời cần thực hiện các bước sau:

1.      Nạp thanh ghi giá trị TMOD để báo bộ định thời gian nào (Timer0 hay Timer1) được sử dụng và chế độ làm việc nào của chúng được chon.

2.      Nạp lại thanh ghi TH và TL với giá trị đếm ban đầu.3.      Khởi động bộ định thời.4.      Duy trì kiểm tra cờ bộ định thời TF bằng cách sử dụng một vòng lặp để xem nó đã

được bật chưa. Thoát vòng lặp khi TF lên cao.5.      Dừng bộ định thời.6.      Xoá cờ TF.7.      Quay trở lại bước 3. Vì chế độ 2 là chế độ tự nạp lại.

Ví dụ 7 minh hoạ những điều này:

Ví dụ 7:#include<at89x51.h>                      //khai báo thư viện cho VĐK 89x51        void delay(void);                             //khi báo nguyên mẫu hàm con tạo trễmain(){            TMOD=0x20;                       //chọn timer1, chế độ 2, 8Bit, tự nạp lại            TH1=0x00;                            //nạp giá trị cho TH1            TL1=0xFE;                            //nạp giá trị cho TL1            P1_5=1;                                 //khởi tạo chân P1_5 ở mức cao            while(1)                                 //vòng lặp vô hạn            {                        delay();                      //gọi chương trình con tạo trễ                        P1_5=~P1_5;            //đảo tín hiệu chân P1_5            }}void delay(void)                              //định nghĩa hàm delay{                        TR1=1;                       //khởi động timer1                        while(!TF1){}           //vòng lặp kiểm tra cờ TF1                        TR1=0;                       //ngừng timer1                        TF1=0;                       //xóa cờ TF1}

Hàm delay() trên sẽ tạo một độ trễ bằng 256 lần (FF - 00 + 1) chu kỳ của timer (không tính tổng phí các lệnh) kể từ chu trình thứ 2. Vì chu trình đầu tiên timer1 bắt đầu đếm ở vị trí 0xFE, kể từ chu trình sau thì thanh ghi TL1 mới sao chép được giá trị ở TH1.

1.7. Bộ đếm

            Ở phần trên đây ta đã sử dụng các bộ định thời của 8051 để tạo ra các độ trễ thời gian. Các bộ định thời này cũng có thể được dùng như các bộ đếm (counter) các sự kiện xảy ra bên ngoài 8051. Công dụng của bộ đếm sự kiện sẽ được tình bày ở phần này. Chừng nào còn liên quan đến công dụng của bộ định thời như bộ đếm sự kiện thì mọi vấn đề mà ta nói về lập trình bộ định thời ở phần trước cũng được áp dụng cho việc lập trình như là một bộ đếm ngoại trừ nguồn tần số.

Đối với bộ định thời/bộ đếm khi dùng nó như bộ định thời thì nguồn tần số là tần số thạch anh của 8051. Tuy nhiên, khi nó được dùng như một bộ đếm thì nguồn xung để tăng nội dung các thanh ghi TH và TL là từ bên ngoài 8051.

Ở chế độ bộ đếm, hãy lưu ý rằng các thanh ghi TMOD và TH, TL cũng giống như đối với bộ định thời được bàn ở phần trước, thậm chí chúng vẫn có cùng tên gọi. Các chế độ của các bộ đếm cũng giống nhau.

2.1 Bit C/T trong thanh ghi TMOD

            Xem lại phần trên về bit C/T trong thanh ghi TMOD: ta thấy rằng nó quyết định nguồn xung đồng hồ cho bộ đếm:

  Nếu bit C/T = 0 thì  bộ định thời nhận các xung đồng hồ từ bộ giao động thạch anh của 8051.

  Nếu bit C/T  = 1 thì bộ định thời được sử dụng như bộ đếm và nhận các xung đồng hồ từ nguồn bên ngoài của 8051.

Do vậy, nếu bit C/T = 1 thì bộ đếm tăng lên khi các xung được đưa đến chân P3.4 (T0) đối với counter0 và chân P3.5 (T1) đối với counter1.

Chân Chân cổng Chức năng Mô tả

14 P3.4 T0 Đầu vào ngoài của bộ đếm 0

15 P3.5 T1 Đầu vào ngoài của bộ đếm 1

Bảng 4: Các chân cổng P3 được dùng cho bộ đếm 0 và 1

Ví dụ 8:            Chương trình sau sử dụng bộ đếm 1, đếm các xung ở chân P3.5 và hiển thị số đếm được (trong thanh ghi TL1) lên cổng P2:

#include<at89x51.h>          //khai báo thư viện 89x51main()                                                //chương trình chính

{            TMOD=0x60;           //0x60=0110 000 : C/T=1, bộ đếm 1, chế độ 2 tự nạp            TH1=0x00;                //xóa bộ đếm ban đầu

            P3_5=1;                     //set chân vào cho bộ đếm            TR1=1;                       //khởi động bộ đếm 1

            while(1)                     //vòng lặp vô hạn            {                        P2=TL1;         //hiển thị số đếm được ra cổng P2            }}

            Trong ví dụ 8 chúng ta sử dụng bộ  counter1 như bộ đếm sự kiện để nó đếm lên mỗi khi các xung đồng hồ được cấp đến chân P3.5. Các xung đồng hồ này có thể biểu diễn số người đi qua cổng hoặc số vòng quay hoặc bất kỳ sự kiện nào khác mà có thể chuyển đổi thành các xung.

2.2 Thanh ghi TCON

            Trong các ví dụ trên đây ta đã thấy công dụng của các cờ TR0 và TR1 để bật/tắt các bộ đếm/bộ định thời. Các bit này là một bộ phận của thanh ghi TCON. Đây là thanh ghi 8 bit, như được chỉ ra trong hình 2:

  4 bit trên được dùng để lưu cất các bit TF và TR cho cả Timer/counter 0 và Timer/counter 1.

  4 bit thấp được thiết lập dành cho điều khiển các ngắt mà ta sẽ bàn ở các bài sau.

Hình 8: Thanh ghi TCON – Điều khiển bộ đếm/bộ định thời

2.3 Trường hợp khi bit GATE = 1 trong TMOD

            Trước khi kết thúc bài này ta cần bàn thêm về trường hợp khi bit GATE = 1 trong thanh ghi TMOD. Tất cả những gì chúng ta vừa nói trong bài này đều giả thiết GATE = 0. Khi GATE = 0 thì bộ đếm/bộ định thời được khởi động bằng các lệnh Set bit TR0 hoặcTR1. Vậy điều gì xảy ra khi bit GATE = 1?

Nếu GATE = 1 thì việc khởi động và dừng bộ đếm/bộ định thời được thực hiện từ bên ngoài qua chân P3.2 (INT0) và P3.3 (INT1) đối với Timer/counter 0 và

Timer/counter 1 tương ứng. Phương pháp điều khiển bằng phần cứng để dừng và khởi động bộ đếm/bộ định thời này có thể có rất nhiều ứng dụng.Ví dụ: chẳng hạn 8051 được dùng trong một sản phẩm phát báo động mỗi giây dùng bộ Timer0 theo nhiều việc khác. Bộ Timer0 được bật lên bằng phần mềm qua lệnh Set bit TR0 và nằm ngoài sự kiểm soát của người dùng sản phẩm đó. Tuy nhiên, khi nối một công tắc chuyển mạch tới chân P2.3 ta có thể dừng và khởi động bộ định thời, bằng cách đó ta có thể tắt báo động.

CHƯƠNG 2. Ngắt trong 8051

Mục tiêu

Kết thúc bài học này, bạn có thể:

  Phân biệt cơ chế ngắt với hỏi vòng  Nắm rõ các loại ngắt trong 8051        Ngắt timer/counter        Ngắt ngoài        Ngắt truyền thông nối tiếp  Lập trình các ngắt        Trình phục vụ ngắt là gì?        Cho phép ngắt và cấm ngắt        Thiết lập mức ưu tiên của các ngắt

Giới thiệu

            Ngắt (Interrupt) - như tên của nó, là một số sự kiện khẩn cấp bên trong hoặc bên ngoài bộ vi điều khiển xảy ra, buộc vi điều khiển tạm dừng thực hiện chương trình hiện tại, phục vụ ngay lập tức nhiệm vụ mà ngắt yêu cầu – nhiệm vụ này gọi là trình phục vụ ngắt (ISR: Interrupt Service Routine).

Trong bài này ta tìm hiểu khái niệm ngắt và lập trình các ngắt trong bộ vi điều khiển 8051.

1. Các ngắt của 8051

1.1  Phân biệt cơ chế ngắt với hỏi vòng

Lấy ví dụ: Bộ vi điều khiển đóng vai trò như một vị bác sĩ, các thiết bị kiểm soát bởi vi điều khiển được coi như các bệnh nhân cần được bác sĩ phục vụ.

Bình thường, vị bác sĩ sẽ hỏi thăm lần lượt từng bệnh nhân, đến lượt bệnh nhân nào được hỏi thăm nếu có bệnh thì sẽ được bác sĩ phục vụ, xong lại đến lượt bệnh nhân khác, và tiếp tục đến hết. Điều này tương đương với phương pháp thăm dò - hỏi vòng(Polling) trong vi điều khiển.

Cứ như thế, nếu chúng ta có 10 bệnh nhân, thì bệnh nhân thứ 10 dù muốn hay không cũng phải xếp hàng chờ đợi 09 bệnh nhân trước đó. Giả sử trường hợp bệnh nhân thứ 10 cần cấp cứu thì sao? Anh ta sẽ gặp nguy cấp trước khi đến lượt hỏi thăm của bác sĩ mất!  Nhưng, nếu anh ta sử dụng phương pháp “ngắt” thì mọi chuyện sẽ ổn ngay. Lúc đó vị bác sĩ sẽ ngừng mọi công việc hiện tại của mình, và tiến hành phục vụ trường hợp khẩn cấp này ngay lập tức, xong việc bác sĩ lại trở về tiếp tục công việc đang dở. Điều này tương đương với phương pháp ngắt (Interrupts) trong vi điều khiển.            Trở lại với bộ vi điều khiển của chúng ta: 1 bộ vi điều khiển có thể phục vụ cho nhiều thiết bị, có 2 cách để thực hiện điều này đó là sử dụng các ngắt (Interrupts) vàthăm dò (polling):

  Trong phương pháp sử dụng ngắt: mỗi khi có một thiết bị bất kỳ cần được phục vụ thì nó báo cho bộ vi điều khiển bằng cách gửi một tín hiệu ngắt. Khi nhận được tín hiệu ngắt thì bộ vi điều khiển ngừng tất cả những gì nó đang thực hiện để chuyển sang phục vụ thiết bị gọi ngắt. Chương trình ngắt được gọi là trình phục vụ ngắt ISR(Interrupt Service Routine) hay còn gọi là trình quản lý ngắt (Interrupt handler). Sau khi phục vụ ngắt xong, bộ vi xử lý lại quay trở lại điểm bị ngắt trước đó và tiếp tục thực hiện công việc.

  Trong phương pháp thăm dò: bộ vi điều khiển kiểm tra liên tục tình trạng của tất cả các thiết bị, nếu thiết bị nào có yêu cầu thì nó dừng lại phục vụ thiết bị đó. Sau đó nó tiếp tục kiểm tra tình trạng của thiết bị kế tiếp cho đến hết. Phương pháp thăm dò rất đơn giản, nhưng nó lại rất lãng phí thời gian để kiểm tra các thiết bị kể cả khi thiết bị đó không cần phục vụ. Trong trường hợp có quá nhiều thiết bị thì phương án thăm dò tỏ ra không hiệu quả, gây ra chậm trễ cho các thiết bị cần phục vụ.

Điểm mạnh của phương pháp ngắt là:

  Bộ vi điều khiển có thể phục vụ được rất nhiều thiết bị (tất nhiên là không tại cùng một thời điểm). Mỗi thiết bị có thể nhận được sự chú ý của bộ vi điều khiển dựa trênmức ưu tiên được gán cho nó. Đối với phương pháp thăm dò thì không thể gán mức ưu tiên cho các thiết bị vì nó kiểm tra tất cả mọi thiết bị theo kiểu hỏi vòng.

  Quan trọng hơn, trong phương pháp ngắt thì bộ vi điều khiển còn có thể che (làm lơ) một yêu cầu phục vụ của thiết bị. Điều này lại một lần nữa không thể thực hiện được trong phương pháp thăm dò.

  Lý do quan trọng nhất mà phương pháp ngắt được ưu chuộng là vì nó không lãng phí thời gian cho các thiết bị không cần phục vụ. Còn phương pháp thăm dò làm lãng phí thời gian của bộ vi điều khiển bằng cách hỏi dò từng thiết bị kể cả khi chúng không cần phục vụ.

Ví dụ trong các bộ định thời được bàn đến ở các bài trước ta đã dùng một vòng lặp kiểm tra và đợi cho đến khi bộ định thời quay trở về 0. Trong ví dụ đó, nếu sử dụng ngắt thì ta không cần bận tâm đến việc kiểm tra cờ bộ định thời, do vậy không lãng phí thời gian để chờ đợi, trong khi đó ta có thể làm việc khác có ích hơn.

1.2 Sáu ngắt trong 8051

            Thực tế chỉ có 5 ngắt dành cho người dùng trong 8051 nhưng các nhà sản xuất nói rằng có 6 ngắt vì họ tính cả lệnh RESET. Sáu ngắt của 8051 được phân bố như sau:

1.      RESET: Khi chân RESET được kích hoạt từ 8051, bộ đếm chương trình nhảy về địa chỉ 0000H.  Đây là địa chỉ bật lại nguồn.

2.      2 ngắt dành cho các bộ định thời: 1 cho Timer0 và 1 cho Timer1. Địa chỉ tương ứng của các ngắt này là 000BH và 001BH.

3.      2 ngắt dành cho các ngắt phần cứng bên ngoài: chân 12 (P3.2) và 13 (P3.3) của cổng P3 là các ngắt phần cứng bên ngoài INT0 và INT1 tương ứng. Địa chỉ tương ứng của các ngắt ngoài này là 0003H và 0013H.

4.      Truyền thông nối tiếp: có 1 ngắt chung cho cả nhận và truyền dữ liệu nối tiếp. Địa chỉ của ngắt này trong bảng vector ngắt là 0023H.

1.3 Trình phục vụ ngắt

            Đối với mỗi ngắt thì phải có một trình phục vụ ngắt (ISR) hay trình quản lý ngắt để đưa ra nhiệm vụ cho bộ vi điều khiển khi được gọi ngắt. Khi một ngắt được gọi thì bộ vi điều khiển sẽ chạy trình phục vụ ngắt. Đối với mỗi ngắt thì có một vị trí cố định trong bộ nhớ để giữ địa chỉ ISR của nó. Nhóm vị trí bộ nhớ được dành riêng để lưu giữ địa chỉ của các ISR được gọi là bảng vector ngắt. Xem Hình 1.

Hình 1: Bảng vector ngắt của 8051.

Trong lập trình C trên Keil c cho 8051, chúng ta khai báo trình phục vụ ngắttheo cấu trúc sau:

Void  Name (void) interrupt X                 //( X: là số thứ tự của ngắt ){         // chương trình phục vụ ngắt}

Khi đó địa chỉ ngắt sẽ được tự động tính bằng:

Interrupt Address = (X * 8) + 3

1.4 Quy trình khi thực hiện một ngắt

            Khi kích hoạt một ngắt bộ vi điều khiển thực hiện các bước sau:

  Nó hoàn thành nốt lệnh đang thực hiện và lưu địa chỉ của lệnh kế tiếp vào ngăn xếp.  Nó cũng lưu tình trạng hiện tại của tất cả các ngắt.  Nó nhảy đến một vị trí cố định trong bộ nhớ được gọi là bảng vector ngắt, nơi lưu giữ địa

chỉ của một trình phục vụ ngắt.  Bộ vi điều khiển nhận địa chỉ ISR từ bảng vector ngắt và nhảy tới đó. Nó bắt đầu thực

hiện trình phục vụ ngắt cho đến lệnh cuối cùng của ISR và trở về chương trình chính từ ngắt.

  Khi bộ vi điều khiển quay trở về nơi nó đã bị ngắt. Trước hết nó nhận địa chỉ của bộ đếm chương trình PC từ ngăn xếp bằng cách kéo 02 byte trên đỉnh của ngăn xếp vào PC. Sau đó bắt đầu thực hiện tiếp các lệnh từ địa chỉ đó.

1.5 Các bước cho phép và cấm ngắt

            Khi bật lại nguồn thì tất cả mọi ngắt đều bị cấm (bị che), có nghĩa là không có ngắt nào được bộ vi điều khiển đáp ứng trừ khi chúng được kích hoạt.

Các ngắt phải được kích hoạt bằng phần mềm để bộ vi điều khiển đáp ứng chúng. Có một thanh ghi được gọi là thanh ghi cho phép ngắt IE (Interrupt Enable) – ở địa chỉ A8H chịu trách nhiệm về việc cho phép và cấm các ngắt. Hình 2 trình bày chi tiết về thanh ghi IE.

Hình 2: Thanh ghi cho phép ngắt IE.

            Để cho phép một ngắt ta phải thực hiện các bước sau:

  Nếu EA = 0 thì không có ngắt nào được đáp ứng cho dù bit tương ứng của nó trong IEcó giá trị cao. Bit D7 - EA của thanh ghi IE phải được bật lên cao để cho phép các bit còn lại của thanh ghi hoạt động được.

  Nếu EA = 1 thì tất cả mọi ngắt đều được phép và sẽ được đáp ứng nếu các bit tương ứng của chúng trong IE có mức cao.

Để hiểu rõ điểm quan trọng này ta hãy xét ví dụ 1.

Ví dụ 1:            Hãy lập trình cho 8051:a) cho phép ngắt nối tiếp, ngắt Timer0 và ngắt phần cứng ngoài 1 (EX1)b) cấm ngắt Timer0c) sau đó trình bày cách cấm tất cả mọi ngắt chỉ bằng một lệnh duy nhất.Lời giải:

#include<at89x51.h>                     main()                                               

{            //a)            IE=0x96;        //1001 0110: lệnh này tương đương với 4 lệnh phía dưới                       EA=1;             //Cho phép sử dụng ngắt            ES=1;             //Cho phép ngắt cổng nối tiếp            ET0=1;           //Cho phép ngắt timer0            EX1=1;          //Cho phép ngắt ngoài 1                       //b)            ET0=0;           //Cấm ngắt timer0                       //c)

EA=0;             //Cấm tất cả các ngắt

            while(1)                                {                        //Chương trình chính                        //…            }}

2. Lập trình các ngắt bộ định thời

            Trong các bài trước ta đã biết cách sử dụng các bộ định thời Timer0 và Timer1bằng phương pháp thăm dò. Trong phần này ta sẽ sử dụng các ngắt để lập trình cho các bộ định thời của 8051.

2.1 Cờ quay về 0 của bộ định thời và ngắt

            Chúng ta đã biết rằng cờ bộ định thời TF được bật lên cao khi bộ định thời đạt giá trị cực đại và quay về 0 (Roll - over). Trong các bài trước chúng ta cũng chỉ ra cách kiểm tra cờ TF bằng một vòng lặp. Trong khi thăm dò cờ TF thì ta phải đợi cho đến khi cờ TFđược bật lên. Vấn đề với phương pháp này là bộ vi điều khiển bị trói buộc trong khi chờ cờ TF được bật và không thể làm được bất kỳ việc gì khác.

Sử dụng các ngắt sẽ giải quyết được vấn đề này và tránh được sự trói buộc bộ vi điều khiển. Nếu bộ ngắt định thời trong thanh ghi IE được phép thì mỗi khi nó quay trở về 0 bộ vi điều khiển sẽ bị ngắt, bất chấp nó đang thực hiện việc gì và nhảy tới bảng vector ngắt để phục vụ ISR. Bằng cách này thì bộ vi điều khiển có thể làm những công việc khác cho đến khi nó được thông báo rằng bộ định thời đã quay về 0. Xem hình 3 và ví dụ 2.

Hình 3: Ngắt bộ định thời TF0 và TF1.

Ví dụ 2:            Hãy viết chương trình nhận liên tục dữ liệu 8 Bit ở cổng P0 và gửi nó đến cổng P1 trong khi nó cùng lúc tạo ra một sóng vuông chu kỳ 200s trên chân P2.1. Hãy sử dụng bộTimer0 để tạo ra sóng vuông, tần số của 8051 là XTAL = 11.0592MHz.Lời giải:

Chu kỳ 200s, vậy nửa chu kỳ là 100s.Ta có: 100s/1,085s=92.Suy ra giá trị cần nạp cho timer0 là: -92 <=> A4H. Ta sử dụng timer0 8 bit.

#include<at89x51.h>                      //khai báo thu viện cho VÐK 89x51main(){            TMOD=0x02;           //chọn timer0, chế độ 2, 8Bit tự nạp lại            TL0=0xA4;               //nạp giá trị cho TL0            TH0=0xA4;              //nạp giá trị cho TH0            TR0=1;                        //khởi động timer0            IE=0x82;                     //cho phép ngắt timer0

            while(1)                    //vòng lặp vô hạn            {                        P1=~P0;         //Cập nhật giá trị cho cổng P1 từ P0.            }}void songvuong(void) interrupt 1   //Khai báo trình phục vụ ngắt cho timer0{            TR0=0;                       //Ngừng timer0            P2_1=~P2_1;                        //Đảo trạng thái chân P2_1.            TR0=1;                       //Khởi động timer0                                                //Không cần xóa cờ TF0, 8051 tự động xóa.}

Hình 4: Mô phỏng trên proteus: cập nhật liên tục cổng P1 từ P0, trong khi tạo xung ở chân P2.1

Hình 5: Sóng vuông hiển thị trên Oscilloscope

            Hãy để ý những điểm dưới đây của chương trình trong ví dụ 2:

1.      Chúng ta cho phép ngắt bộ Timer0 với lệnh IE=0x82; trong chương trình chínhmain().2.      Trong khi dữ liệu ở cổng P0 được nhận vào và chuyển liên tục sang cổng P1 thì mỗi khi

bộ Timer0 trở về 0, cờ TF0 được bật lên và bộ vi điều khiển thoát ra khỏi hàmmain() và đi đến địa chỉ 000BH để thực hiện ISR gắn liền với bộ Timer0.

3.      Trong trình phục vụ ngắt ISR của Timer0 ta thấy rằng không cần đến lệnh xóa cờ TF0của timer0. Lý do này là vì 8051 đã tự xoá cờ TF0 ngay khi thoát khỏi ISR.

Ví dụ 3:

            Hãy viết lại chương trình ở ví dụ 2 để tạo sóng vuông với mức cao kéo dài 1085s và mức thấp dài 15s với giả thiết tần số XTAL = 11.0592MHz. Hãy sử dụng bộ định thờiTimer1.Lời giải:            Vì 1085s/1.085s=1000 nên ta cần sử dụng chế độ 1 của bộ định thời Timer1.Các giá trị cần nạp cho timer1 là:1085/1.085=1000 , -1000FC18H15/1.085=14 , -14FFF2H

#include<at89x51.h>bit a=0; main(){            TMOD=0x10;          //chọn timer1, chế độ 1, 16Bit            TL1=0x18;               //nạp giá trị cho TL1            TH1=0xFC;             //nạp giá trị cho TH1            TR1=1;                       //khoi dong timer1            IE=0x88;                    //cho phép ngat timer1             while(1)                     //vòng lặp vô hạn            {                        P1=~P0;         //Cập nhật cổng P1            }}void songvuong(void) interrupt 3        //Khai báo trình phục vụ ngắt timer1{            TR1=0;                                   //Dừng timer1                       if(a==0)                                 //Nếu Xung vuông đang ở mức thấp            {                        P2_1=1;                     //Bật xung vuông lên cao                        a=1;                            //Đặt lại bit kiểm tra                        TL1=0x18;                //Nạp lại TL1: Ứng với mức trễ phần cao                        TH1=0xFC;               //Nạp lại TH1            }            Else                                         //Nếu Xung vuông đang ở mức cao            {                                 P2_1=0;                     //Lật xung xuống thấp                        a=0;                            //Đặt lại bit kiểm tra                        TL1=0xF2;                //Nạp lại TL1: Ứng với mức trễ phần thấp                       TH1=0xFF;                //Nạp lại TH1            }            TR1=1;                                   //Khởi động lại timer1                                                            //Không cần xóa cờ TF1, 8051 tự động xóa}

Hình 6: Sóng vuông hiển thị trên Oscilloscope

Lưu ý: Các xung được tạo ra ở các ví dụ trên không thật sự chính xác, vì chưa tính đến hao phí của các lệnh cài đặt.

3 Lập trình các ngắt phần cứng bên ngoài

            Bộ vi điều khiển 8051 có 2 ngắt phần cứng bên ngoài ở chân 12 (P3.2) và chân 13 (P3.3) gọi là ngắt INT0 và INT1.            Như đã nói ở trên thì chúng được phép và bị cấm bằng việc sử dụng thanh ghi IE. Nhưng cấu hình cho ngắt ngoài có phần phức tạp hơn.Có hai mức kích hoạt cho các ngắt phần cứng ngoài: Ngắt theo mức và ngắt theo sườn.

Hình 7: Ngắt ngoài INT0 và INT1

Dưới đây là mô tả hoạt động của mỗi loại.

3.1 Ngắt theo mức

            Ở chế độ ngắt theo mức thì các chân INT0 và INT1 bình thường ở mức cao và nếu một tín hiệu ở mức thấp được cấp tới thì chúng ghi nhãn ngắt. Sau đó bộ vi điều khiển dừng tất cả mọi công việc nó đang thực hiện và nhảy đến bảng vector ngắt để phục vụ ngắt. Đây là chế độ ngắt mặc định khi cấp nguồn cho 8051.

Tín hiệu mức thấp tại chân INTx phải được lấy đi trước khi thực hiện lệnh cuối cùng của trình phục vụ ngắt, nếu không một ngắt khác sẽ lại được tạo ra, và vi điều khiển sẽ thực hiện ngắt liên tục.

Để rõ hơn chúng ta hãy xem ví dụ 4.

Ví dụ 4:            Giả sử chân INT1 được nối đến công tắc bình thường ở mức cao. Mỗi khi nó  ấn xuống thấp phải bật một đèn LED ở chân P1.3 (bình thường Led tắt), khi nó được bật lên nó phải sáng vài giây. Chừng nào công tắc được giữ ở trạng thái thấp đèn LED phải sáng liên tục.Lời giải:

#include<at89x51.h>          //Khai báo thư viện cho VĐK 89x51main()                                                //Chương trình chính{            IE=0x84;                    //cho phép ngắt ngoài 1

            while(1)                     //vòng lặp vô hạn            {                                                //không làm gì            }}void nutan(void) interrupt 2          //Khai báo trình phục vụ ngắt ngoài 1{                                                          //(mặc định là ngắt theo mức)            int a=50000;                         //Biến đếm trễ            P1_3=0;                                 //Cho Led sáng            while(a--){}                          //Trễ cho Led sáng vài giây            P1_3=1;                                 //Tắt Led                                                            //Không cần xóa cờ ngắt}

Hình 8: Ấn công tắc xuống sẽ làm cho đèn LED sáng một thời gian.

Hình 9: Nhưng nếu công tắc được giữ ở trạng thái ấn thì đèn LED sáng liên tục.

Lưu ý:        Trong chương trình trên bộ vi điều khiển quay vòng liên tục trong vòng lặp while(1)của

chương trình chính. Mỗi khi công tắc trên chân P3.3 (INT1) được kích hoạt thì bộ vi điều khiển thoát khỏi vòng lặp và nhảy đến bảng vector ngắt tại địa chỉ 0013H. Trình ISR cho INT1 bật đèn LED lên giữ nó một lúc và tắt nó trước khi trở về. Nếu trong lúc nó thực hiện lệnh cuối cùng để quay trở về từ ISR mà chân INT1 vẫn còn ởmức thấp thì bộ vi điều khiển khởi tạo lại ngắt, ngắt lại xảy ra 1 lần nữa.

        Do vậy, để giải quyết vấn đề này thì chân INT1 phải được đưa lên cao trước thời điểm lệnh cuối cùng của ngắt được thực hiện.

        Có một cách khác để giải quyết triệt để vấn đề trên: đó là sử dụng ngắt theo sườn.Khi đó với mỗi 1 lần ấn phím, dù thế nào ngắt cũng chỉ thực hiện 1 lần.

        Trước khi tìm hiểu ngắt theo sườn là gì? Ta hãy xem qua ngắt theo mức hoạt động như thế nào.

  Trích mẫu ngắt theo mức

            Các chân P3.2 và P3.3 bình thường được dùng cho vào/ra nếu các Bit INT0 vàINT1 trong thanh ghi IE không được kích hoạt. Sau khi các ngắt phần cứng trong thanh giIE được kích hoạt thì bộ vi điều khiển duy trì trích mẫu trên chân INTx đối với tín hiệu mức thấp 1 lần trong 1 chu trình máy.

Theo bảng dữ liệu từ nhà sản xuất của bộ vi điều khiển thì “chân ngắt phải được giữ ở mức thấp cho đến khi bắt đầu thực hiện trình phục vụ ngắt ISR. Nếu chân INTx được đưa trở lại mức cao trước khi bắt đầu thực hiện ISR thì sẽ chẳng có ngắt nào xảy ra”. Do vậy, để bảo đảm việc kích hoạt ngắt phần cứng tại các chân INTx phải đảm bảo rằng thời gian tồn tại tín hiệu mức thấp là khoảng 4 chu trình máy và không được bé hơn, nếu không đủ lâu thì ngắt không được thực hiện.

Tuy nhiên trong quá trình kích hoạt ngắt theo mức thấp nên nó lại phải đưa lênmức cao trước khi ISR thực hiện lệnh cuối cùng và lại theo bảng dữ liệu từ nhà sản xuất thì “nếu chân INTx vẫn ở mức thấp sau lệnh cuối cùng của trình phục vụ ngắt thì một ngắt khác lại sẽ được kích hoạt”. Điều này do một thực tế là ngắt theo mứckhông được chốt.

Hình 10: Thời gian tối thiểu của xung ngắt theo mức thấp (XTAL = 11.0592MHz)

3.2 Ngắt theo sườn

Ngắt theo sườn là ngắt sẽ xảy ra khi có một sườn âm xuất hiện trên các chân ngắt của vi điều khiển. Điều này làm cho ngắt theo sườn khắc phục được nhược điểm của ngắt theo mức như ta đã thấy ở trên.            Để kích hoạt chế độ ngắt theo sườn thì chúng ta phải viết chương trình cài đặt cho các bit của thanh ghi TCON:

Hình 11: Thanh ghi TCON.

  Các Bit IT0 và IT1

            Các bit TCON.0 và TCON.2 được coi như là các bit IT0 và IT1 tương ứng. Đây là các bit xác định kiểu ngắt theo sườn xung hay theo mức xung của các ngắt phần cứng trên chân INT0 và INT1 tương ứng. Khi bật lại nguồn cả 2 bit này đều có mức 0 để biến chúng thành ngắt theo tín hiệu mức thấp. Lập trình viên có thể điều khiển một trong số chúng lên cao để chuyển ngắt phần cứng bên ngoài thành ngắt theo sườn.

  Các Bit IE0 và IE1

            Các bit TCON.1 và TCON.3 còn được gọi là IE0 và IE1 tương ứng. Các bit này được 8051 dùng để bám kiểu ngắt theo sườn xung, nếu các bit IT0 và IT1 bằng 0 thì có nghĩa là các ngắt phần cứng là ngắt theo mức thấp và các bit IE0 và IE1 sẽ không dùng đến. Các Bit IE0 và IE1 chỉ được 8051 dùng để chốt sườn xung từ cao xuống thấp trên các chân INT0 và INT1. Khi có chuyển dịch sườn xung trên chân INT0 (hay INT1) thì 8051 đánh dấu (bật lên cao) các bit IEx trên thanh ghi TCON và nhảy đến bảng vector ngắt và bắt đầu thực hiện trình phục vụ ngắt ISR. Trong khi 8051 thực hiện ISR thì không có một sườn xung nào được ghi nhận trên chân INT0 (hay INT1) để ngăn mọi ngắt trong ngắt. Chỉ trong khi thực hiện lệnh cuối của trình phục vụ ngắt ISR thì các bit IEx mới được 8051 tự động xóa, và các chân ngắt lại hoạt động bình thường.

Ta thấy rằng các bit  IE0 và IE1 được 8051 sử dụng bên trong để báo có một ngắt đang được xử lý hay không. Hay nói cách khác là lập trình viên không phải quan tâm đến các bit này.

  Các Bit TR0 và TR1

            Đây là những bit D4 và D6 (hay TCON.4 và TCON.6) của thanh ghi TCON. Các bit này đã được giới thiệu ở các bài trước, chúng được dùng để khởi động và dừng các bộ định thời Timer0 và Timer1 tương ứng.

  Các Bit TF0 và TF1

            Các bit này là D5 (TCON.5) và D7 (TCON.7) của thanh ghi TCON mà đã được giới thiệu ở các bài trước. Chúng được sử dụng bởi các bộ Timer0 và Timer1 tương ứng để báo rằng các bộ định thời bị tràn hay quay về không.

Để hiểu rõ sự khác biệt của ngắt theo sườn âm, ta xét ví dụ 5. Chú ý rằng sự khác nhau duy nhất giữa ví dụ 5 và ví dụ 4 là ở lệnh chuyển ngắt INT1 về kiểu ngắt theo sườn. Khi sườn âm của tín hiệu được cấp đến chân INT1 thì đèn LED sẽ bật lên một lúc. Đèn LED có thời gian sáng phụ thuộc vào độ trễ bên trong ISR của INT1. Trong ví dụ 4 do bản chất ngắt theo mức của ngắt thì đèn LED còn sáng chừng nào tín hiệu ở chân INT1vẫn còn ở mức thấp. Nhưng trong ví dụ 5 này để bật lại đèn LED thì xung ở chân INT1phải được đưa lên cao rồi sau đó bị hạ xuống thấp để tạo ra một sườn âm làm kích hoạt ngắt.

Ví dụ 5:

#include<at89x51.h>          //Khai báo thư viện cho VĐK 89x51main()                                                //Chương trình chính{            IE=0x84;                    //cho phép ngắt ngoài 1            IT1=1;                        //Thiết lập ngắt ngoài 1 theo sườn âm              while(1)                     //vòng lặp vô hạn            {                                                //không làm gì            }}void nutan(void) interrupt 2          //Khai báo trình phục vụ ngắt ngoài 1{                                                          //(mặc định là ngắt theo mức)            int a=50000;                         //Biến đếm trễ            P1_3=0;                                 //Cho Led sáng            while(a--){}                          //Trễ cho Led sáng vài giây            P1_3=1;                                 //Tắt Led                                                            //Không cần xóa cờ ngắt} 

Hình 12:mô phỏng ngắt ngoài 1 theo sườn âm:Dù công tắc được giữ, cũng chỉ có 1 ngắt xảy ra.

  Trình mẫu ngắt theo sườn

            Trước khi kết thúc phần này ta cần trả lời câu hỏi: vậy thì ngắt theo sườn được trích mẫu thường xuyên như thế nào? Trong các ngắt theo sườn, nguồn xung phải giữ ở mức cao tối thiểu là 1 chu kỳ máy, và xung thấp cũng phải kéo dài 1 chu kỳ máy nữađể đảm bảo bộ vi điều khiển nhìn thấy được sự chuyển dịch từ cao xuống thấp của sườn âm.

            Hình 13: Thời hạn xung tối thiểu để phát hiện ra các ngắt theo sườn âm với tần số XTAL = 11.0592MHz

            Sườn âm của xung được chốt bởi 8051 và được giữ bởi thanh ghi TCON. Các bitTCON.1 (IE0) và TCON.3 (IE1) giữ các sườn được chốt của chân INT0 và INT1 tương ứng như chỉ ra trên hình 11. Chúng hoạt động như các cờ “ngắt đang được phục vụ” (Interrupt-in-server). Khi một cờ “ngắt đang được phục vụ” bật lên thì nó báo rằng ngắt hiện nay đang được xử lý và trên chân INTx này sẽ không có ngắt nào được đáp ứng chừng nào ngắt này chưa được phục vụ xong. Đây giống như tín hiệu báo bận ở máy điện thoại.

Ngoài ra cần phải nhấn mạnh 2 điểm dưới đây khi quan tâm đến các bit IE0 và IE1của thanh ghi TCON:

        Khi các trình phục vụ ngắt ISR kết thúc: Các Bit IE0 và IE1 được tự động xoá để báo rằng ngắt được hoàn tất và 8051 sẵn sàng đáp ứng ngắt khác trên chân đó. Để ngắt khác được nhận và thì tín hiệu trên chân đó phải trở lại mức cao và sau đó nhảy xuống thấp để được phát hiện như một ngắt theo sườn âm.

        Trong thời gian trình phục vụ ngắt đang được thực hiện thì chân INTx bị làm ngơ, 8051 không quan tâm đến nó có bao nhiêu lần chuyển dịch từ cao xuống thấp. Trong thực tế điều này có được là do các bit IEx. Vì lý do này mà các bit IEx được gọi là các cờ báo “ngắt đang được phục vụ”, cờ này sẽ lên cao khi 1 sườn âm được phát hiện trên chân INTx và giữ ở mức cao trong toàn bộ quá trình thực hiện ISR. Nó chỉ bị xoá sau lệnh cuối cùng của ISR. Do vậy, ta cũng sẽ không bao giờ cần đến các lệnh xoá cờ này trong trình phục vụ ngắt đối với các ngắt cứng INT0 và INT1.

4 Lập trình ngắt truyền thông nối tiếp

            Trong các bài trước chúng ta đã nghiên cứu về truyền thông nối tiếp của 8051. Tất cả các ví dụ trong ấy đều sử dụng phương pháp thăm dò (polling). Ở chương này chúng ta sẽ khám phá phương pháp truyền thông nối tiếp dựa trên ngắt.

4.1 Các cờ RI và TI và các ngắt

            Như đã nói ở bài trước thì cờ ngắt truyền TI (Transfer interrupt) được bật lên khi bit cuối cùng của khung dữ liệu - bit stop được truyền đi, báo rằng thanh ghi SBUF sẵn sàng truyền byte kế tiếp. Trong trường hợp cờ RI (Receive Interrupt) thì nó được bật lên khi toàn bộ khung dữ liệu kể cả bit stop đã được nhận.

Chừng nào còn nói về truyền thông nối tiếp thì tất cả mọi khái niệm trên đây đều áp dụng giống như nhau cho dù sử dụng phương pháp thăm dò hay sử dụng phương pháp ngắt. Sự khác nhau duy nhất giữa hai phương pháp này là ở cách phục vụ quá trình truyền thông nối tiếp như thế nào:

  Trong phương pháp thăm dò thì chúng ta phải đợi cho cờ (TI hay RI) bật lên và trong lúc chờ đợi thì ta không thể làm gì được cả.

  Còn trong phương pháp ngắt thì ta được báo khi 8051 đã nhận được một byte hoặc nó sẵn sàng truyền byte kế tiếp và ta có thể làm các công việc khác trong khi chờ truyền thông nối tiếp được thực hiện.

            Trong 8051 chỉ có một ngắt dành riêng cho truyền thông nối tiếp. Ngắt này được dùng cho cả truyền và nhận dữ liệu. Nếu bit ngắt truyền thông ES - IE.4 trong thanh ghi IE được phép, thì khi 1 trong 2 cờ RI hoặc TI bật lên, 8051 sẽ nhận được ngắt và nhảy đến địa chỉ trình phục vụ ngắt dành cho truyền thông nối tiếp 0023H trong bảng vector ngắt để thực hiện nó. Trong trình ISR này chúng ta phải kiểm tra các cờ TI và RIđể xem cờ nào gây ra ngắt để đáp ứng một cách phù hợp (xem ví dụ 6).

            Hình 14: Ngắt truyền thông có thể do hai cờ TI và RI gọi.

4.2 Sử dụng cổng COM nối tiếp trong 8051

            Trong các ứng dụng, ngắt nối tiếp chủ yếu được sử dụng để nhận dữ liệu và không bao giờ được sử dụng để truyền dữ liệu nối tiếp. Điều này giống như việc báo chuông để ta biết và nhận điện thoại vì ta không thể biết trước được lúc nào có điện thoại, còn nếu muốn gọi điện thoại thì ta không cần đổ chuông để báo trước.

Ví dụ 6:            Hãy viết chương trình ngắt để 8051 nhận dữ liệu từ cổng nối tiếp COM và gửi đến cổng P0. Giả thiết tần số XTAL là 11.0592MHz và tốc độ baud 9600.Lời giải:

#include<at89x51.h>          //Khai báo thư viện cho 89c51main()                                    //Chương trình chính{            TMOD=0x20;           //Chọn Timer1, chế độ 2            TH1=0xFD;               //Cài đặt tốc độ baud 9600            SCON=0x50;            //0101 0000: Chọn chế độ 1, Cho phép nhận            TR1=1;                       //Khởi động Timer1            IE=0x90;                    //cho phép ngắt truyền thông nối tiếp            while(1)                     //Vòng lặp vô hạn            {                                             }}void nhandulieu(void) interrupt 4       //Khai báo ISR truyền thông nối tiếp{                                                                     if(RI==1)                   //Kiểm tra có phải là ngắt nhận dữ liệu không            {                        P0=SBUF;      //Gửi dữ liệu đến cổng P0                        RI=0;              //Xóa cờ nhận dữ liệu nối tiếp RI            }                                 }

Hình 15: Mô phỏng nhận các ký tự 0,1,2,3,4, từ máy tính, gửi đến Port0.             

Trong ví dụ trên ta chú ý đến vai trò của cờ RI. Trong trình phục vụ ngắt nối tiếp, ta phải kiểm tra cả cờ TI và cờ RI vì cả hai đều có thể gọi ngắt truyền thông nối tiếp, hay nói cách khác là chỉ có một ngắt cho cả truyền và nhận.

4.3 Xoá cờ RI và TI trước khi thoát khỏi ngắt truyền thông nối tiếp

            Để ý rằng lệnh cuối cùng trước khi trở về từ ISR là lệnh xoá các cờ RI và TI. Điều này tương phản với ngắt ngoài và ngắt bộ định thời là đều được 8051 xoá các cờ.

5. Các mức ưu tiên ngắt trong 8051

5.1 Các mức ưu tiên trong quá trình bật lại nguồn

            Khi 8051 được cấp nguồn thì các mức ưu tiên ngắt được gán theo Hình 16. Từ hình này ta thấy ví dụ nếu các ngắt phần cứng ngoài 0 và 1 được kích hoạt cùng một lúc thì ngắt ngoài 0 sẽ được đáp ứng trước. Chỉ sau khi ngắt INT0 đã được phục vụ xong thì INT1 mới được phục vụ vì INT1 có mức ưu tiên thấp hơn. Trong thực tế sơ đồ mức ưu tiên ngắt trong bảng chỉ là một quy trình thăm dò, trong đó 8051 thăm dò các ngắt theo trình tự cho trong hình 16 và đáp ứng chúng một cách phù hợp.

Hình 16: Mức ưu tiên các ngắt trong khi cấp lại nguồn. 

Hình 17: Thanh ghi mức ưu tiên ngắt IP: Bit ưu tiên = 1 là mức ưu tiên cao, Bit ưu tiên = 0 là mức ưu tiên thấp.

-         Bit D7 và D6 -- chưa dùng.-         Bit D5 hay PT2 là Bit ưu tiên ngắt Timer2 (dùng cho 8052)-         Bit D4 hay PS là Bit ưu tiên ngắt cổng nối tiếp-         Bit D3 hay PT1 là Bit ưu tiên ngắt Timer1-         Bit D2 hay PX1 là mức ưu tiên ngắt ngoài 1-         Bit D1 hay PT0 là mức ưu tiên ngắt Timer 0-         Bit D0 hay PX0 là mức ưu tiên ngắt ngoài 0

5.2 Thiết lập mức ưu tiên ngắt với thanh ghi IP

            Chúng ta có thể thay đổi trình tự trong hình 16 bằng cách gán mức ưu tiên cao hơn cho bất kỳ ngắt nào. Điều này được thực hiện bằng cách lập trình một thanh ghi gọi là thanh ghi mức ưu tiên ngắt IP (Interrupt Priority). Trên hình 17 là các bit của thanh ghi này. Khi bật lại nguồn thanh thi IP chứa hoàn toàn các số 0 để tạo ra trình tự ưu tiên ngắt theo Hình 16. Để một ngắt nào đó có mức ưu tiên cao hơn ta thực hiện đưa bit tương ứng lên cao.            Một điểm khác nữa cần được làm sáng tỏ là mức ưu tiên ngắt khi 2 hoặc nhiều bit ngắt trong thanh ghi IP được đặt lên cao: Trong trường hợp này thì trong khi các ngắt này có mức ưu tiên cao hơn các ngắt khác, chúng sẽ được phục vụ theo trình tự cho trong Hình 16.

5.3 Ngắt trong ngắt

            Điều gì xảy ra nếu 8051 đang thực hiện một trình phục vụ ngắt thuộc một ngắt nào đó thì lại có một ngắt khác được kích hoạt? Trong những trường hợp như vậy thì 1

ngắt có mức ưu tiên cao hơn có thể ngắt 1 ngắt có mức ưu tiên thấp hơn . Đây gọi là ngắt trong ngắt. Trong 8051 một ngắt ưu tiên thấp có thể bị ngắt bởi một ngắt có mức ưu tiên cao hơn chứ không bị ngắt bởi một ngắt có mức ưu tiên thấp hơn. Mặc dù tất cả mọi ngắt đều được chốt và giữ bên trong nhưng không có ngắt mức thấp nào được CPU quan tâm ngay tức khắc, nếu 8051 chưa kết thúc phục vụ các ngắt mức cao.

5.4 Thu chộp ngắt bằng phần mềm (Triggering)

            Có nhiều lúc ta cần kiểm tra một trình phục vụ ngắt bằng con đường mô phỏng. Điều này có thể được thực hiện bằng các lệnh đơn giản để thiết lập các ngắt lên cao và bằng cách đó buộc 8051 nhảy đến bảng vector ngắt. Ví dụ, nếu bit cho phép ngắt Timer1trong thanh ghi IE được bật lên 1 thì một lệnh như TF1=1; sẽ ngắt 8051 ngừng thực hiện công việc đang làm bất kỳ và buộc nó nhảy đến bảng vector ngắt timer1. Hay nói cách khác, ta không cần đợi cho Timer1 quay trở về 0 mới tạo ra ngắt. Chúng ta có thể gây ra một ngắt bằng các lệnh đưa các bit của ngắt tương ứng lên cao.

            Như vậy qua bài này chúng ta đã biết ngắt là một sự kiện bên trong hoặc bên ngoài gây ra ngắt bộ vi điều khiển để báo cho nó biết rằng thiết bị cần được phục vụ. Mỗi một ngắt có một chương trình đi kèm với nó được gọi là trình phục vụ ngắt ISR. Bộ vi điều khiển 8051 có 6 ngắt, trong đó co 5 ngắt người dùng có thể truy cập được. Đó là: 2 ngắt cho các thiết bị phần cứng bên ngoài INT0 và INT1, 2 ngắt cho các bộ định thời là TF0 vàTF1 và 1 ngắt dành cho truyền thông nối tiếp.            8051 có thể được lập trình cho phép hoặc cấm một ngắt bất kỳ cũng như thiết lập mức ưu tiên cho nó theo yêu cầu của thuật toán ứng dụng.

chương 3. Truyền thông nối tiếp với 8051

Mục tiêu

Kết thúc bài học này, bạn có thể hiểu:

  Truyền dữ liệu nối tiếp đồng bộ, không đồng bộ  Đóng khung dữ liệu trong truyền thông không đồng bộ  Chuẩn giao diện RS232  Nối ghép 8051 với chuẩn RS232  Các bước lập trình truyền thông nối tiếp cho 8051        Cài đặt khung truyền        Cài đặt tốc độ baud

Giới thiệu

Các máy tính truyền dữ liệu theo hai cách: Song song và nối tiếp. Trong truyền dữ liệu song song thường cần rất nhiều đường dây dẫn chỉ để truyền dữ liệu đến một thiết bị chỉ cách xa vài bước. Ví dụ của truyền dữ liệu song song là các máy in hoặc các ổ cứng, mỗi thiết bị sử dụng một đường cáp với nhiều dây dẫn. Mặc dù trong các trường hợp như vậy thì nhiều dữ liệu được truyền đi trong một khoảng thời gian ngắn bằng cách dùng nhiều dây dẫn song song, nhưng khoảng cách thì không thể lớn được. Vì các đường cáp dài làm suy giảm thậm chí làm méo tín hiệu. Ngoài ra, các đường cáp dài có giá thành cao. Vì những lý do này, để truyền dữ liệu đi xa thì  phải sử dụng phương pháp truyền nối tiếp.

1.      Các cơ sở của truyền thông nối tiếp

Trong truyền thông nối tiếp dữ liệu được gửi đi từng bit một, so với truyền song song thì là một hoặc nhiều byte được truyền đi cùng một lúc. Hình 1 so sánh giữa việc truyền dữ liệu nối tiếp và song song.

            Hình 1: Sơ đồ truyền dữ liệu nối tiếp so với sơ đồ truyền song song.

            Trong truyền thông nối tiếp, một đường dữ liệu duy nhất được dùng thay cho nhiều đường dữ liệu của truyền thông song song không chỉ giúp giảm giá thành, giúp hệ thống đơn giản hơn nhiều mà nó còn mở ra khả năng để hai máy tính ở cách xa nhau có truyền thông qua đường thoại.            Truyền thông dữ liệu nối tiếp sử dụng hai phương pháp là đồng bộ và không đồng bộ (dị bộ):

  Trong truyền đồng bộ: thì bộ truyền và bộ thu được đồng bộ hóa qua một đường tín hiệu đồng hồ bên ngoài. 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 nối 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 truyền 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 (dữ liệu và clock) cho 1 quá trình truyền hoặc nhận.

  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 đường 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. 

Trong 8051 có một bộ truyền dữ liệu không đồng bộ (UART - Universal Asynchronous serial Reveiver and Transmitter). Trước tiên chúng ta sẽ tìm hiểu các khái niệm quan trọng trong phương pháp truyền thông nối tiếp không đồng bộ:

1.1  Baud rate (tốc độ Baud)

Để 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” với nhau về khoảng thời gian 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.

1.2  Frame (khung truyền)

Dữ liệu đi vào ở đầu thu của đường dữ liệu trong truyền dữ liệu nối tiếp là một dãy các số 0 và 1, và rất khó để hiểu được ý nghĩa của các dữ liệu ấy nếu bên phát và bên thu không cùng thống nhất về một tập các luật, một thủ tục, về cách dữ liệu được đóng gói, bao nhiêu bit tạo nên một ký tự và khi nào dữ liệu bắt đầu và kết thúc. Bên cạnh tốc độ baud, khung truyền là một yếu tố 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 2 là một ví dụ của một khung truyền của UART (truyền thông nối tiếp không đồng bộ): khung truyền này được bắt đầu bằng 01 start bit, tiếp theo là 08 bit data, sau đó là 01 bit parity dùng kiểm tra dữ liệu và cuối cùng là 02 bits stop. Công việc này được gọi là đóng gói dữ liệu. Chúng ta sẽ đi vào tìm hiểu các thành phần có trong một khung truyền:

  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. Start là bit bắt buộcphải có trong khung truyền, và nó là một bit thấp (0).

  Data (dữ liệu)

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 8051 ta có thể quy định số lượng bit của data là 08 hoặc 09 bit. Trong truyền thông nối tiếp UART, bit có trọng số 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ó trọng số 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 có đú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 quy định 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. Sau khi truyền chuỗi dữ liệu kèm theo cả bit parity trên, bên nhận thu được và kiểm tra lại tổng số số “1” (bao gồm cả bit parity), nếu vi phạm quy định parity đã đặt trước thì ta khẳng định là dữ liệu nhận được là sai, còn nếu không vi phạm thì cũng không khẳng định được điều gì (mang tính tương đối). Hình 2 mô tả một ví dụ 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.

  Stop bits

Stop bits là 01 hoặc nhiều 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 bit bắt buộc xuất hiện trong khung truyền, trong 8051 có thể là 01 hoặc 02 bit, và chúng là các bit cao (1).

Trong ví dụ ở hình 2: có 2 stop bits được dùng cho khung truyền.

Hình 2: Một khung truyền trong truyền thông nối tiếp không đồng bộ

2.      Truyền thông nối tiếp trong 8051

2.1 Phần cứng

2.1.1 Các chân RxD và TxD trong 8051

            Trong 8051 có hai chân được dùng cho truyền và nhận dữ liệu nối tiếp. Hai chân này được gọi là TxD và RxD, là một phần của cổng P3 (đó là P3.0-chân 10 và P3.1-chân

11). Các chân này hoạt động với mức logic TTL (mức logic cao “1” được gán cho Vccvà mức logic thấp được gán cho 0v).

Vì các máy tính được sử dụng rất rộng rãi để truyền thông với các hệ thống vi điều khiển, do vậy ta chủ yếu tập trung vào truyền thông nối tiếp của 8051 với cổng COM – RS232 của PC.  2.1.2 Chuẩn giao diện RS232

            Để cho phép tương thích giữa các thiết bị truyền thông dữ liệu được sản xuất bởi các hãng khác nhau thì một chuẩn giao diện được gọi là RS232 đã được thiết lập bởi hiệp hội công nghiệp điện tử EIA vào năm 19960. Năm 1963 nó được sửa chỉnh và được gọi là RS232A và vào các năm 1965 và 1969 thì được đổi thành RS232B và RS232C. ở đây chúng ta đơn giản chỉ hiểu là RS232. Ngày nay RS232 là chuẩn giao diện I/O vào - ra nối tiếp được sử dụng rộng rãi nhất. Chuẩn này được sử dụng trong máy tính PC và hàng loạt các thiết bị khác nhau.

  Các chân của cổng RS232

            Hình 3 là sơ đồ chân của cáp RS232 và chúng thường được gọi là đầu nối DB - 25. Trong lý hiệu thì đầu nối cắm vào (đầu đực) gọi là DB - 25p và đầu nối cái được gọi là DB - 25s.

Hình 3: Đầu nối DB - 25 của RS232.

            Vì không phải tất cả mọi chân của cổng RS232 đều được sử dụng trong cáp của máy tính PC, nên IBM đưa ra phiên bản của chuẩn vào/ra nối tiếp chỉ sử dụng có 9 chân gọi là DB - 9 như trình bày ở bảng 1 và hình 4.

Hình 4: Đầu nối DB - 9 của RS232.

Số chân Mô tả

1

2

3

4

5

6

7

8

9

Data carrier detect (DCD)

Received data (RxD)

Transmitted data (TxD)

Data terminal ready (DTR)

Signal ground (GND)

Data set ready (DSR)

Request to send (RTS)

Clear to send (CTS)

Ring indicator (RI)

Tránh tín hiệu mạng dữ liệu

Dữ liệu được nhận

Dữ liệu được gửi

Đầu dữ liệu sẵn sàng

Đất của tín hiệu

Dữ liệu sẵn sàng

Yêu cầu gửi

Xoá để gửi

Báo chuông

Bảng 1: Các tín hiệu của các chân đầu nối DB - 9 trên máy tính.

2.1.4 Nối ghép 8051 tới RS232

Chuẩn RS232 được thiết lập trước họ logic TTL rất lâu do vậy điện áp đầu vào và đầu ra của nó không tương thích với mức TTL. Trong RS232 thì mức logic 1 được biểu diển từ điện áp - 3v đến -25v trong khi đó mức 0 thì ứng với điện áp + 3v đến +25v làm cho điện áp - 3v đến + 3v là không xác định. Vì lý do này để kết nối một chuẩn RS232 bất kỳ đến một hệ vi điều khiển 8051 thì ta phải sử dụng các bộ biến đổi điện áp (nhưMAX232) để chuyển đổi các mức điện áp RS232 về các mức điện áp TTL sẽ được

chấp nhận bởi các chân TxD và RxD của 8051 và ngược lại. Các IC MAX232 nhìn chung được coi như các bộ điều khiển đường truyền.

Một điểm mạnh của IC MAX232 là nó dùng điện áp nguồn +5v cùng với điện áp nguồn của 8051. Hay nói cách khác ta có thể nuôi 8051 và MAX232 với cùng một nguồn +5v, mà không phải dùng hai nguồn nuôi khác nhau.            IC MAX232 có hai bộ điều khiển đường truyền để nhận và truyền dữ liệu như trình bày trên hình 5. Các bộ điều khiển được dùng cho chân TxD được gọi là T1 và T2, cho chân RxD gọi là R1 và R2. Trong nhiều ứng dụng thì chỉ có 1 cặp được dùng. Ví dụ: ở hình dưới ta chỉ dùng đến T2 và R2 được dùng làm 1 cặp đối với TxD và RxD của 8051, còn cặp R1 và T1 thì không cần đến. 

Để ý rằng trong IC MAX232, T1 có gán T1in  (chân 11) và T1out (chân 14):

        Chân T1in là ở phía TTL và được nối tới chân RxD của bộ vi điều khiển.        Chân T1out là ở phía RS232 được nối tới chân RxD của đầu nối DB củaRS232.

Bộ điều khiển R1 cũng có gán R1in (chân 13) và R1out (chân 12):        Chân R1in (chân số 13) là ở phía RS232 được nối tới chân TxD ở đầu nốiDB của RS232.        Chân R1out (chân số 12) là ở phía TTL được nối tới chân RxD của bộ vi điều khiển.

Tương tự cho T2 và R2. Xem hình 5:

            Hình 5: Sơ đồ bên trong của MAX232 và Sơ đồ nối ghép 8051 -Max232 - cổng COM DB-9.

Bộ MAX232 đòi hỏi 4 tụ hóa giá trị từ 1 đến 22F. giá trị phổ biến nhất cho các tụ này là 22F.

2.2 Lập trình phần mềm

            Trong phần này chúng ta sẽ nghiên cứu về các thanh ghi truyền thông nối tiếp của 8051 và cách lập trình chúng để truyền và nhận dữ liệu nối tiếp.

2.2.1 Thanh ghi SBUF

            SBUF là thanh ghi 8 bit được dùng riêng cho truyền thông nối tiếp trong 8051. Đối với một byte dữ liệu muốn truyền qua đường TxD thì nó phải được đặt trong thanh ghi SBUF. Tương tự, SBUF cũng giữ một byte dữ liệu khi nó được nhận từ đường RxDcủa 8051:

        Khi một byte được ghi vào thanh ghi SBUF nó sẽ được đóng khung với các bit Start, Stop và được truyền nối tiếp quan chân TxD.

        Khi các bit được nhận nối tiếp từ RxD thì 8051 mở khung đó để loại trừ các bit Start, Stop để lấy ra một byte từ dữ liệu nhận được và đặt byte đó vào thanh ghi SBUF.

2.2.2 Thiết lập chế độ truyền bằng thanh ghi SCON

            Điều đầu tiên chúng ta phải làm là gì khi sử dụng cổng nối tiếp tích hợp của 8051? Rõ ràng là cấu hình cho nó. Điều này cho phép chúng ta báo với 8051 biết: bao nhiêu bit dữ liệu chúng ta muốn truyền, tốc độ truyền. Vậy làm thế nào xác định các điều đó? Nhờ thanh ghi SCON, là thanh ghi 8 bit được dùng để lập trình việc đóng khung dữ liệu, xác định các chế độ làm việc của truyền thông nối tiếp. SCON là thanh ghi có thể đánh địa chỉ theo bit.            Dưới đây là mô tả các bit khác nhau của thanh ghi SCON:

Hình 6: Thanh ghi điều khiển cổng nối tiếp SCON.

  Các bit SM0, SM1

            Đây là các bit D7 và D6 của thanh ghi SCON. Chúng được dùng để xác định các chế độ đóng khung dữ liệu, có 4 chế độ:

Hình 7: Các chế độ xác định bởi 2 bit SM0 và SM1

(*) Lưu ý: tốc độ truyền chỉ ra trong bảng này được tăng gấp đôi nếu bit PCON.7 (bit SMOD) được thiết lập lên 1, mặc định của hệ thống là PCON.7=0.

            Trong bốn chế độ trên ta chỉ quan tâm đến chế độ 1. Khi chế độ 1 được chọn thì dữ liệu được đóng khung thành 10 bit: gồm 1 bit Start, sau đó là 8 bit dữ liệu, và cuối cùng là 1 bit Stop. Quan trọng hơn là chế độ nối tiếp 1 cho phép tốc độ baud thay đổi và được thiết lập bởi Timer1 của 8051.

  Bit SM2

            Bit SM2 là bit D5 của thanh ghi SCON. Bit này cho phép khả năng đa xử lý của 8051. Đối với các ứng dụng của chúng ta, đặt SM2 = 0 vì ta không sử dụng 8051 trong môi trường đa xử lý.

  Bit REN

            REN (Receive Enable) là bit cho phép nhận (bit D4 của thanh ghi SCON). Khi bitREN cao thì nó cho phép 8051 nhận dữ liệu trên chân RxD của nó. Và kết quả là nếu ta muốn 8051 vừa truyền vừa nhận dữ liệu thì bit REN phải được đặt lên 1 . Bit này có thể được dùng để khống chế mọi việc nhận dữ liệu nối tiếp và nó là bit cực kỳ quan trọng trong thanh ghi SCON.

  Bit TB8 và RB8

            Bit TB8 và RB8 được dùng trong chế độ nối tiếp 2 và 3. Ta đặt TB8=0 và RB8=0vì nó không được sử dụng trong các ứng dụng của mình.            Nói thêm, trong chế độ 2 và 3 thì có 9 bit dữ liệu được truyền đi hoặc nhận về. BitTB8 sẽ chứa bit dữ liệu thứ 9 khi truyền, còn bit RB8 sẽ chứa bit dữ liệu thứ 9 khi nhận, trong chế độ nối tiếp 1 thì bit RB8 này nhận một bản sao của bit Stop khi một dữ liệu 8 bit được nhận, và ta cũng không cần quan tâm.

  Các bit TI và RI

            Các bit ngắt truyền TI và ngắt nhận RI là các bit D1 và D0 của thanh ghi SCON. Các bit này là cực kỳ quan trọng của thanh ghi SCON:

        Khi 8051 kết thúc truyền một ký tự 8 bit thì nó bật TI để báo rằng nó sẵn sàng truyền một byte khác. Bit TI được bật lên trước bit Stop.

        Khi 8051 nhận được dữ liệu nối tiếp qua chân RxD và nó tách các bit Start và Stop để lấy ra 8 bit dữ liệu để đặt vào SBUF, sau khi hoàn tất nó bật cờ RI để báo rằng nó đã nhận xong 1 byte và cần phải lấy đi kẻo dữ liệu bị mất. Cờ RIđược bật khi đang tách bit Stop.

2.2.3 Thiết lập tốc độ baud trong 8051

Một khi các chế độ cổng nối tiếp đã được cấu hình, việc tiếp theo là chương trình cần phải cấu hình tốc độ baud cho các cổng nối tiếp. Điều này chỉ áp dụng cho chế độ Serial Port 1 và 3. Còn ở chế độ 0 và 2, tốc độ truyền được xác định dựa trên tần số dao động của thạch anh:

  Trong chế độ 0: tốc độ truyền luôn luôn là tần số dao động chia cho 12. Điều này có nghĩa là nếu bạn đang sử dụng thạch anh tần số 11.059Mhz, tốc độ truyền của chế độ0 sẽ luôn luôn là 921.583 baud. Trong chế độ 2: tốc độ truyền luôn luôn là tần số dao động chia cho 64, do đó, với thạch anh tần số 11.059Mhz sẽ mang lại một tốc độ truyền 172.797 baud.

  Trong chế độ 1 và 3: tốc độ truyền được xác định bằng cách cài đặt Timer1. Phương pháp phổ biến nhất là cài đặt Timer1 ở chế độ tự động nạp lại 8-bit (chế độ 2) và thiết lập một giá trị nạp lại (cho TH1) để tạo ra một tốc độ truyền.

Như ta đã biết ở trước đây, thì 8051 chia tần số thạch anh cho 12 để lấy tần số  chu kỳ máy. Bộ UART truyền thông nối tiếp của 8051 lại chia tần số chu kỳ máy cho 32một lần nữa trước khi nó được dùng bởi bộ định thời Timer1 để tạo ra tốc độ baud:

Hình 8: Tần số của bộ truyền thông nối tiếp UART

2.2.3.1 Nhân đôi tốc độ baud trong 8051

            Có hai cách để tăng tốc độ baud truyền dữ liệu trong 8051:1.        Sử dụng tần số thạch anh cao hơn.2.        Thay đổi một bit trong thanh ghi điều khiển công suất PCON (Power Control) như chỉ ra dưới đây.

Hình 9: Thanh ghi PCON

            Phương án 1 là không khả thi trong nhiều trường hợp vì tần số thạch anh của hệ thống là cố định. Do vậy, ta sẽ tập trung thăm dò phương án 2: nhân đôi tốc độ baud bằng phần mềm trong 8051 với tần số thạch anh không đổi. Điều này được thực hiện nhờ thanh ghi PCON, đây là thanh ghi 8 bit. Trong 8 bit này thì có một số bit không được dùng để điều khiển công suất của 8051. Bit dành cho truyền thông nối tiếp là bit D7 (bitSMOD). Khi 8051 được bật nguồn thì bit SMOD của thanh ghi PCON ở mức thấp (0). Chúng ta có thể đặt nó lên 1 bằng phần mềm và do vậy nhân đôi được tốc độ baud. Tại sao có được điều đó? Ta hãy làm rõ tiếp:

  Khi SMOD = 0

            Khi SMOD = 0 thì 8051 chia 1/12 tần số thạch anh cho 32 và sử dụng nó cho bộTimer1 để thiết lập tốc độ baud. Đây là giá trị mặc định của SMOD khi 8051 bật nguồn.

  Khi SMOD = 1

            Khi SMOD = 1 thì 8051 chia 1/12 tần số thạch anh cho 16 (thay vì chia cho 32như khi SMOD = 0) và đây là tần số được Timer1 dùng để thiết lập tốc độ baud.

Để xác định giá trị cài đặt trong TH1 để tạo ra một tốc độ baud nhất định, chúng ta có thể sử dụng các phương trình sau đây (giả sử bit PCON.7=0):

TH1 = 256 - ((Crystal / (12*32)) / Baud) = 256 - ((Crystal / 384) / Baud)                 (1)

Nếu PCON.7=1 thì tốc độ truyền tăng gấp đôi, do đó phương trình trở thành:

TH1 = 256 - ((2*Crystal / (12*32)) / Baud) = 256 - ((Crystal / 192) / Baud)             (2)

Ví dụ 1:Nếu chúng ta có một tinh thể thạch anh tần số 11.059Mhz và chúng ta muốn cấu

hình cho cổng nối tiếp đạt tốc độ 19200 baud, thì ta sử dụng phương trình 1:

TH1 = 256 - ((Crystal / 384) / Baud)TH1 = 256 - ((11059000/384) / 19200)TH1 = 256 - ((28799) / 19200)TH1 = 256-1,5 = 254,5

Như bạn có thể thấy: để có được tốc độ 19200 baud trên một tinh thể thạch anh 11.059Mhz ta phải cài đặt TH1 một giá trị 254,5. Nhưng giá trị trong các thanh ghi lại là 1 số nguyên. Nếu chúng ta thiết lập là 254, chúng ta sẽ có tốc độ 14400 baud và nếu chúng ta thiết lập là 255, chúng ta sẽ có tốc độ 28800 baud. Như vậy dường như chúng ta không thể cài đặt chính xác tốc độ baud được ?!! 

Nhưng ta lại có một cách khác để cài đặt được tốc độ 19200 baud. Chúng ta đơn giản chỉ cần đặt bit PCON.7=1 (bit SMOD). Khi đó ta đã tăng gấp đôi tốc độ baudvà sử dụng phương trình 2 được đề cập ở trên. Vì vậy chúng ta có:

TH1 = 256 - ((Crystal / 192) / Baud)TH1 = 256 - ((11059000/192) / 19200)TH1 = 256 - ((57.699) / 19.200)TH1 = 256 - 3 = 253

Vậy: để có được tốc độ 19200 baud với một tinh thể thạch anh tần số11.059MHz chúng ta phải:1. Cấu hình chế độ Serial Port 1 hoặc 3.2. Cấu hình Timer 1 ở chế độ 2 (8-bit tự động nạp lại).3. Cài đặt TH1 giá trị 253 (FDH).4. Set bit PCON.7=1 (SMOD) để tăng gấp đôi tốc độ truyền (19200 baud).

2.2.4 Lập trình 8051 để truyền dữ liệu nối tiếp

            Để lập trình 8051 truyền các byte ký tự nối tiếp thì cần phải thực hiện các bước sau đây:

1.      Nạp thanh ghi TMOD giá trị 20H: báo rằng sử dụng Timer1 ở chế độ 2 để thiết lập chế độ baud.

2.      Nạp thanh ghi TH1 các giá trị phù hợp để thiết lập chế độ baud truyền dữ liệu nối tiếp.3.      Nạp thanh ghi SCON giá trị 50H báo chế độ nối tiếp 1 để đóng khung 8 bit dữ liệu, 1 bit

Start và 1 bit Stop.4.      Bật TR1=1 để khởi động Timer1.5.      Xoá bit cờ truyền dữ liệu: TI=0.6.      Byte ký tự cần phải truyền được ghi vào SBUF.7.      Bit cờ truyền TI được kiểm tra bằng một vòng lặp để đợi đến lúc dữ liệu được truyền

xong (cờ TI=1).8.      Để truyền ký tự tiếp theo quay trở về bước 5.

Các bạn hãy quan sát 2 ví dụ sau để thực hành:

Ví dụ 2:

            Hãy viết chương trình cho 8051 để truyền dữ liệu nối tiếp một ký tự “D” với tốc độ 4800 baud liên tục lên máy tính.

Lời giải:

Chương trình sử dụng ngôn ngữ C lập trình trên Keil C uVision3, mô phỏng trênProteus, hiển thị lên máy tính qua giao diện Hyper Terminal Hercules. (Proteus và Hercules sử dụng 2 cổng COM ảo được tạo ra và kết nối với nhau bởi chương trình Configure Virtual Serial Port Driver)

#include<at89x51.h>                      //khai báo thư viện cho 89c51void send(unsigned char a);           //khai báo nguyên mẫu hàm gửi 1 ký tựmain()                                                            //Chương trình chính{            TMOD=0x20;                       //Chọn Timer1, chế độ 2            TH1=0xFA;                           //Cài đặt tốc độ 4800 baud            SCON=0x50;                                    //0101 0000: Chọn chế độ 1, Cho phép nhận            TR1=1;                                   //Khởi động Timer1

            while(1)                                 //Vòng lặp vô hạn            {                        send('D');                   //Gọi hàm gửi 1 ký tự lên máy tính            }}void send(unsigned char a)                        //Định nghĩa hàm gửi 1 ký tự{            SBUF=a;                                //Ghi 1 byte dữ liệu vào thanh ghi SBUF            while(TI==0){}                    //vòng lặp để đợi cờ truyền TI lên 1            TI=0;                                      //Xóa cờ truyền TI sau khi truyền xong}

Hình 10: Sơ đồ nguyên lý mạch mô phỏng Ví Dụ 2 trên Proteus

Hình 11: Kết quả truyền lên máy tính Ví Dụ 2 qua giao diện Hercules

Ví dụ 3:

            Hãy viết chương trình để 8051 truyền dòng chữ “DienTuMayTinh.Com” liên tục với tốc độ 9600 baud (8 bit dữ liệu, 1 bit Stop) lên máy tính.

Lời giải:

#include<at89x51.h>                      //Khai báo thư viện cho 89c51#include<string.h>                          //Khai báo thư viện để sử dụng hàm strlen()void send(unsigned char a);           //khai báo nguyên mẫu hàm gửi 1 ký tựvoid sendchuoi(char *a);                //khai báo nguyên mẫu hàm gửi 1 chuỗimain()                                                            //Chương trình chính{

            TMOD=0x20;                       //Chọn Timer1, chế độ 2            TH1=0xFD;                           //Cài đặt tốc độ 9600 baud            SCON=0x50;                                    //0101 0000: Chọn chế độ 1, Cho phép nhận            TR1=1;                                   //Khởi động Timer1

            while(1)                                 //Vòng lặp vô hạn            {                        sendchuoi("DienTuMayTinh.Com");       //Gọi hàm gửi 1 chuỗi                        send(10);                                                       //Gửi dấu xuống dòng            }}void send(unsigned char a)                        //Định nghĩa hàm gửi 1 ký tự{            SBUF=a;                                //Ghi 1 byte dữ liệu vào thanh ghi SBUF            while(TI==0){}                    //vòng lặp để đợi cờ truyền TI lên 1            TI=0;                                      //Xóa cờ truyền TI sau khi truyền xong}void sendchuoi(char *a)                 //Định nghĩa hàm gửi 1 chuỗi ký tự{            int i,n;                                     //Khai báo biến cục bộ số nguyên: i,n            n=strlen(a);                           //Tính độ dài của chuỗi *a, lưu vào biến n            for(i=0;i<n;i++)                   //Vòng lặp để gửi lần lượt từng ký tự lên,            {                                              //cho đến khi hết chuỗi *a (ký tự thứ n-1).                        send(a[i]);                 //Gọi hàm gửi 1 ký tự.            }         }

Hình 12: Kết quả truyền lên máy tính Ví Dụ 3 qua giao diện Hercules

  Tầm quan trọng của cờ TI

            Để hiểu tầm quan trọng của cờ ngắt TI ta hãy xét trình tự các bước dưới đây mà 8051 phải thực hiện khi truyền một ký tự qua đường TxD:

1.    Byte ký tự cần phải truyền được ghi vào SBUF.2.    Truyền bit Start3.    Truyền ký tự 8 bit lần lượt từng bit một.4.    Bit Stop được truyền xong, trong quá trình truyền bit Stop thì cờ TI được bật (TI = 1)

bởi 8051 để báo sẵn sàng để truyền ký tự kế tiếp.5.    Bằng việc kiểm tra cờ TI ta biết chắc rằng ta không nạp quá nhanh vào thanh ghi

SBUF. Nếu ta nạp một byte vào SBUF trước ghi TI được bật thì phần dữ liệu của byte trước chưa truyền hết sẽ bị mất. Ta phải đợi 8051 bật cờ TI để báo đã truyền xong một byte và nó sẵn sàng truyền byte kế tiếp.

6.    Trước khi SBUF được nạp một byte mới thì cờ TI phải được xóa để kiểm tra cho lần truyền dữ liệu tiếp theo.

Từ phần trình bày trên đây ta kết luận rằng: bằng việc kiểm tra bit cờ ngắt TI ta biết được 8051 có sẵn sàng để truyền một byte khác không. Quan trọng hơn cần phải nói ở đây là bit cờ TI được bật bởi 8051 khi nó hoàn tất việc truyền một byte dữ liệu, còn việc xoá nó thì phải được lập trình viên thực hiện. Cũng cần lưu ý rằng, nếu ta ghi một byte vào thanh ghi SBUF trước khi cờ TI được bật thì sẽ có nguy cơ mất phần dữ liệu đang truyền. Bit cờ TI có thể kiểm tra bằng một vòng lặp hoặc có thể sử dụng ngắt, và ta sẽ bàn ở bài ngắt sau.

2.2.5 Lập trình 8051 để nhận dữ liệu nối tiếp

            Để lập trình 8051 nhận các byte ký tự nối tiếp thì phải thực hiện các bước sau đây:

1.    Nạp giá trị 20H vào thanh ghi TMOD: báo sử dụng bộ Timer1, chế độ 2 (8 bit, tự động nạp lại) để thiết lập tốc độ baud.2.    Nạp TH1 các giá trị phù hợp để thiết lập tốc độ baud.3.    Nạp giá trị 50H vào thanh ghi SCON để báo sử dụng chế độ truyền nối tiếp 1: dữ liệu

được đóng gói bởi 8 bit dữ liệu, 1 bit Start và 1 bit Stop.4.    Bật TR1=1 để khởi động Timer1.5.    Xoá cờ nhận RI: RI=0.6.    Bit cờ nhận RI được kiểm tra bằng một vòng lặp để đảm bảo toàn bộ ký tự đã được

nhận đủ (khi RI=1).7.    Khi RI được thiết lập thì trong SBUF đã có 1 byte. Các nội dung của nó cần được

đọc ngay để tránh mất mát.8.    Để nhận một ký tự tiếp theo quay trở về bước 5.

Hãy quan sát ví dụ sau để thực hành:

Ví dụ 4:

            Hãy lập trình cho 8051 để nhận các byte dữ liệu nối tiếp tốc độ 9600 baud và bật các Led trên Port 2 tương ứng: Máy tính gửi xuống số 1: 1 Led sáng, số 2: 2 Led sáng, … , số 8: 8 Led sáng, nếu các ký tự khác thì tắt tất cả các Led.

Lời giải:

#include<at89x51.h>                      //Khai báo thư viện cho 89c51char c;main()                                                            //Chương trình chính{

            TMOD=0x20;                       //Chọn Timer1, chế độ 2            TH1=0xFD;                           //Cài đặt tốc độ 9600 baud            SCON=0x50;                                    //0101 0000: Chọn chế độ 1, Cho phép nhận            TR1=1;                                   //Khởi động Timer1                       while(1)                                 //Vòng lặp vô hạn            {                        while(RI==1)                        //Vòng lặp kiểm tra cờ nhận RI                        {                                  //Nếu RI=1 tức là đã nhận đủ 1 byte.                                    c=SBUF;        //lưu dữ liệu nhận được vào biến c                                    RI=0;              //Xóa cờ nhận RI.                        }                                             switch(c)                    //Kiểm tra ký tự vừa nhận được: tương ứng

                        {                                  //trường hợp nào thì thực thi lệnh tươngứng.                                    case '1':                                                P2=0xFE;                                    break;                                    case '2':                                                P2=0xFC;                                    break;                                    case '3':                                                P2=0xF8;                                    break;                                    case '4':                                                P2=0xF0;                                    break;                                    case '5':                                                P2=0xE0;                                    break;                                    case '6':                                                P2=0xC0;                                    break;                                    case '7':                                                P2=0x80;                                    break;                                    case '8':                                                P2=0x00;                                    break;                                    default:                      //mặc định là tắt tất cả Led.                                                P2=0xFF;                                    break;                        }            }

}

Hình 13: Kết quả mô phỏng Ví Dụ 4 (quá trình nhận lần lượt các ký tự 0,1,2,...,8 truyền xuống từ máy tính).

  Tầm quan trọng của cờ RT

            Khi nhận các bit qua chân RxD của nó thì 8051 phải trải qua các bước sau:

1.        Nó nhận bit Start báo rằng bit sau nó là bit dữ liệu đầu tiên cần phải nhận.2.        Ký tự 8 bit được nhận lần lượt từng bit một. Khi bit cuối cùng được nhận thì một byte được hình thành và đặt vào trong SBUF.3.        Khi bit Stop được nhận thì 8051 bật RT = 1 để báo rằng toàn bộ ký tự được nhận và phải lấy đi trước khi nó bị byte mới nhận về ghi đè lên.4.        Bằng việc kiểm tra bit cờ RI khi nó được bật lên chúng ta biết rằng một ký tự đã được nhận và đang nằm trong SBUF. Sao nội dung SBUF vào nơi an toàn trong một thanh ghi hay bộ nhớ khác trước khi nó bị mất.5.        Sau khi SBUF được ghi vào nơi an toàn thì cờ RI được xoá về 0 để chuẩn bị kiểm tra chu trình tiếp theo.

Từ mô tả trên đây ta rút ra kết luận rằng bằng việc kiểm tra cờ RI ta biết 8051 đã nhận được một byte ký tự chưa. Sai khi cờ RI=1, nếu ta không sao được nội dung của thanh ghi SBUF vào nơi an toàn thì có nguy cơ ta bị mất ký tự vừa nhận được. Quan trọng hơn là phải nhớ rằng cờ RI được 8051 bật lên nhưng lập trình viên phải xoá nó sau khi nhận được dữ liệu. Cũng nên nhờ rằng, nếu ta sao nội dung SBUF vào nơi an toàn trước khi RI được bật thì ta đã mạo hiểm sao dữ liệu chưa đầy đủ. Bit cờ RI có thể được kiểm tra bởi một vòng lặp hoặc bằng ngắt mà ta sẽ bàn ở bài sau.

2.2.6 Nhận dữ liệu nối tiếp dựa trên các ngắt  

            Ta phải thấy rằng thật lãng phí thời gian để các bộ vi điều khiển phải luôn kiểm tra các cờ TI và RI. Do vậy, để tăng hiệu suất của 8051 ta có thể lập trình các cổng truyền thông nối tiếp của nó bằng các ngắt. Nội dung này sẽ được đề cập đến ở bài tiếp theo.

  Như vậy qua bài học này chúng ta đã biết truyền thông nối tiếp trong 8051 sử dụng phương pháp không đồng bộ, bằng cách đóng khung dữ liệu giữa các bit Start, bit Stop. Thanh ghi SBUF được sử dụng để vận chuyển dữ liệu, còn muốn thiết lập các chế độ truyền ta sử dụng thanh ghi SCON, để cài đặt tốc độ baud ta sử dụng Timer1, các cờTI và RI là rất quan trọng vì nó báo cho ta biết lúc nào đã truyền hoặc nhận xong dữ liệu. Để truyền thông nối tiếp với máy tính qua cổng COM thì chúng ta phải chuyển đổi các mức điện áp cho phù hợp bằng cách sử dụng IC Max232.

Ở các ví dụ trên, chúng ta cũng đã biết cách gửi 1 ký tự hoặc một chuỗi ký tự lên máy tính. Vậy còn muốn gửi lên giá trị của biến, chúng ta sẽ thực hiện như thế nào? Hoàn toàn tương tự như gửi các chuỗi, ví dụ sau đây sẽ thực hiện công việc này, bao gồm gửi các biến số nguyên, và số thực.

  Gửi giá trị của biến

#include <at89x51.h>                     //Khai báo thư viện 89x51#include <string.h>                         //Khai báo thư viện xử lý chuỗiunsigned long a=4294967295;      //Biến unsigned long, miền giá trị: 0->4294967295float b=511.9999;                           //Biến float.void send(unsigned char a);           //Khai báo nguyên mẫu hàm gửi 1 ký tựvoid sendsonguyen(unsigned long n);      //hàm gửi 1 số nguyênvoid sendsothuc(float n);                           //hàm gửi 1 số thựcmain()                                                //Chương trình chính{            TMOD=0x20;                       //Chọn Timer1, chế độ 2            TH1=0xFD;                           //Cài đặt tốc độ 9600 baud            SCON=0x50;                        //0101 0000: Chọn chế độ 1, Cho phép nhận            TR1=1;                                   //Khởi động Timer1                       while(1)                                 //Vòng lặp vô hạn            {                        sendsonguyen(a);     //Gửi biến số nguyên                        send(10);                   //Gửi dấu xuống dòng

                        sendsothuc(b);          //Gửi biến số thực                        send(10);                   //Gửi dấu xuống dòng            }}void send(unsigned char a)            //Ðịnh nghĩa hàm gửi 1 ký tự{    SBUF=a;                                        //Ghi 1 byte dữ liệu vào thanh ghi SBUF    while(TI==0){}                            //vòng lặp đợi cờ truyền dữ liệu TI bật lên 1    TI=0;                                              //Xóa cờ TI sau khi truyền dữ liệu xong}void sendsonguyen(unsigned long n)       // Ðịnh nghĩa hàm gửi 1 số nguyên{         

if(n!=0)                                             //Trường hợp số nguyên #0            {                        unsigned char a[11];                        //mảng chứa các ký tự số sau khi tách số                        int i;                                        //biến chỉ số cho vòng for                        for(i=0;n>0;i++)                  //Vòng lặp tách các chữ số thành ký tự                        {                                    a[i]=(n%10)+48;      //tách lấy chữ số hàng đơn vị,mã hóa ASCII                                    n=n/10;                      //loại bỏ chữ số hàng đơn vị                        }                        a[i]=NULL;                           //ký tự cuối cùng của chuỗi phải là NULL                for(i=strlen(a);i>=0;i--)              //Vòng lặp gửi lần lượt từng ký tự lên,                {                                                      //cho đến khi hết chuỗi a[].                        send(a[i]);                             //Gọi hàm gửi 1 ký tự.                           }            }            else send('0');                                       //Trường hợp số nguyên =0: chỉ cần gửi số 0}void sendsothuc(float n)                             // Ðịnh nghĩa hàm gửi 1 số thực{            unsigned long a=n/1;                       //Tách lấy phần nguyên của số thực            unsigned long b=(n-a)*10000;      //Tách lấy phần thập phân của số thực            sendsonguyen(a);                             //Gọi hàm để gửi phần nguyên            if(b!=0)                                              //Trường hợp tồn tại phần thập phân.            {                        send('.');                                 //Gửi ký tự ‘.’                        sendsonguyen(b);                //Gọi hàm để gửi phần thập phân            }                                              //Nếu không có phần thập phân thì không làm gì

}

Hình 14: Kết quả nhận được trên giao diện Hercules ở máy tính