[Lập trình nhúng] Lập trình 8051 với C/C++

[Lập trình nhúng] Lập trình 8051 với C/C++


A. Giới thiệu họ Vi điều khiển 8051

I - Tổng quan về họ 8051

1.1 Tóm tắt về lịch sử của 8051

Vào năm 1981. Hãng Intel giới thiệu một số bộ vi điều khiển được gọi là 8051. Bộ vi điều khiển này có 128 byte RAM, 4K byte ROM trên chíp, hai bộ định thời, một cổng nối tiếp và 4 cổng (đều rộng 8 bit) vào ra tất cả được đặt trên một chíp. Lúc ấy nó được coi là một “hệ thống trên chíp”. 8051 là một bộ xử lý 8 bit có nghĩa là CPU chỉ có thể làm việc với 8 bit dữ liệu tại một thời điểm. Dữ liệu lớn hơn 8 bit được chia ra thành các dữ liệu 8 bit để cho xử lý. 8051 có tất cả 4 cổng vào - ra I/O mỗi cổng rộng 8 bit (xem hình 1). Mặc dù 8051 có thể có một ROM trên chíp cực đại là 64 K byte, nhưng các nhà sản xuất lúc đó đã cho xuất xưởng chỉ với 4K byte ROM trên chíp. Điều này sẽ được bàn chi tiết hơn sau này.
8051 đã trở nên phổ biến sau khi Intel cho phép các nhà sản xuất khác sản xuất và bán bất kỳ dạng biến thế nào của 8051 mà họ thích với điều kiện họ phải để mã lại tương thích với 8051. Điều này dẫn đến sự ra đời nhiều phiên bản của 8051 với các tốc độ khác nhau và dung lượng ROM trên chíp khác nhau được bán bởi hơn nửa các nhà sản xuất.  Điều này quan trọng là mặc dù có nhiều biến thể khác nhau của 8051 về tốc độ và dung lương nhớ ROM trên chíp, nhưng tất cả chúng đều tương thích với 8051 ban đầu về các lệnh. Điều này có nghĩa là nếu ta viết chương trình của mình cho một phiên bản nào đó thì nó cũng sẽ chạy với mọi phiên bản bất kỳ khác mà không phân biệt nó từ hãng sản xuất nào.

Đặc tínhSố lượng
ROM trên chíp
RAM
Bộ định thời
Các chân vào - ra
Cổng nối tiếp
Nguồn ngắt
4K byte
128 byte
2
32
1
6

Bảng 1: Các đặc tính cơ bản của 8051.

1.2 Bộ ví điều khiển 8051
        Bộ vi điều khiển 8051 là thành viên đầu tiên của họ 8051. Hãng Intel ký hiệu nó như là MCS51. Bảng 1 trình bày các đặc tính của 8051.

1.3 Các thành viên khác của họ 8051
            Có hai bộ vi điều khiển thành viên khác của họ 8051 là 8052 và 8031.

a- Bộ vi điều khiển 8052
            Bộ vi điều khiển 8052 là một thành viên khác của họ 8051, 8052 có tất cả các đặc tính chuẩn của 8051 ngoài ra nó có thêm 128 byte RAM và một bộ định thời nữa. Hay nói cách khác là 8052 có 256 byte RAM và 3 bộ định thời. Nó cũng có 8K byte ROM. Trên chíp thay vì 4K byte như 8051.

Đặc tính
8051
8052
8031
ROM trên chíp
4K byte
8K byte
OK
RAM
128 byte
256 byte
128 byte
Bộ định thời
2
3
2
Chân vào ra
32
32
32
Cổng nối tiếp
1
1
1
Nguồn ngắt
6
8
6
Bảng 2: so sánh các đặc tính của các thành viên  họ 8051.

Như nhìn thấy từ bảng 2 thì 8051 là tập con của 8052. Do vậy tất cả mọi chương trình viết cho 8051 đều chạy trên 8052 nhưng điều ngược lại là không đúng.

b- Bộ vi điều khiển 8031
            Một thành viên khác nữa của 8051 là chíp 8031. Chíp này thường được coi như là 8051 không có ROM trên chíp vì nó có OK byte ROM trên chíp. Để sử dụng chíp này ta phải bổ xung ROM ngoài cho nó. ROM ngoài phải chứa chương trình mà 8031 sẽ nạp và thực hiện. So với 8051 mà chương trình được chứa trong ROM trên chíp bị giới hạn bởi  4K byte, còn ROM ngoài chứa chương trinh được gắn vào 8031 thì có thể lớn đến 64K byte. Khi bổ xung cổng, như vậy chỉ còn lại 2 cổng để thao tác. Để giải quyết vấn đề này ta có thể bổ xung cổng vào - ra cho 8031. Phối phép 8031 với bộ nhớ và cổng vào - ra chẳng hạn với chíp 8255 được trình bày ở chương 14. Ngoài ra còn có các phiên bản khác nhau về tốc độ của 8031 từ các hãng sản xuất khác nhau.

1.4 Các bộ vi điều khiển 8051 từ các hãng khác nhau
            Mặc dù 8051 là thành viên phổi biến nhất của họ 8051 nhưng chúng ta sẽ  thấy nó trong kho linh kiện. Đó là do 8051 có dưới nhiều dạng kiểu bộ nhớ khác nhau như UV - PROM, Flash và NV - RAM mà chúng đều có số đăng ký linh kiện khác nhau. Việc bàn luận về các kiểu dạng bộ nhớ ROM khác nhau sẽ được trình bày ở chương 14. Phiên bản UV-PROM của 8051 là 8751. Phiên bản Flash ROM được bán bởi nhiều hãng khác nhau chẳng hạn của Atmel corp với tên gọi là AT89C51 còn phiên bản NV-RAM của 8051 do Dalas Semi Conductor cung cấp thì được gọi là DS5000. Ngoài ra còn có phiên bản OTP (khả trình một lần) của 8051 được sản xuất bởi rất nhiều hãng.

a- Bộ vi điều khiển  8751
            Chíp 8751 chỉ có 4K byte bộ nhớ UV-EPROM trên chíp. Để sử dụng chíp  này để phát triển yêu cầu truy cập đến một bộ đốt PROM cũng như bộ xoá UV-  EPROM để xoá nội dung của bộ nhớ UV-EPROM bên trong 8751 trước khi ta có thể lập trình lại nó. Do một thực tế là ROM trên chíp đối với 8751 là UV-EPROM nên cần phải mất 20 phút để xoá 8751 trước khi nó có thể được lập trình trở lại. Điều này đã dẫn đến nhiều nhà sản xuất giới thiệu các phiên bản Flash Rom và UV-RAM  của 8051. Ngoài ra còn có nhiều phiên bản với các tốc độ khác nhau của 8751 từ nhiều hãng khác nhau.

b- Bộ vi điều khiển AT8951 từ Atmel Corporation
            Chíp 8051 phổ biến này có ROM trên chíp ở dạng bộ nhớ Flash. Điều này là lý tưởng đối với những phát triển nhanh vì bộ nhớ Flash có thể được xoá trong vài giây trong tương quan so với 20 phút hoặc hơn mà 8751 yêu cầu. Vì lý do này mà AT89C51 để phát triển một hệ thống dựa trên bộ vi điều khiển yêu cầu một bộ đốt ROM mà có hỗ trợ bộ nhớ Flash. Tuy nhiên lại không yêu cầu bộ xoá ROM. Lưu ý rằng trong bộ nhớ Flash ta phải xoá toàn bộ nội dung của ROM nhằm để lập trình lại cho nó. Việc xoá bộ nhớ Flash được thực hiện bởi chính bộ đốt PROM và đây chính là lý do tại sao lại không cần đến bộ xoá. Để loại trừ nhu cầu đối với một bộ đốt PROM hãng Atmel đang nghiên cứu một phiên bản của AT 89C51 có thể được lập trình qua cổng truyền thông COM của máy tính IBM  PC .

Số linh kiện
ROM
RAM
Chân I/O
Timer
Ngắt
Vcc
Đóng vỏ
AT89C51
4K
128
32
2
6
5V
40
AT89LV51
4K
128
32
2
6
3V
40
AT89C1051
1K
64
15
1
3
3V
20
AT89C2051
2K
128
15
2
6
3V
20
AT89C52
8K
128
32
3
8
5V
40
AT89LV52
8K
128
32
3
8
3V
40
Bảng 3: Các phiên bản của 8051 từ Atmel (Flash ROM).

Chữ C trong ký hiệu AT89C51 là CMOS.

            Cũng có những phiên bản đóng vỏ và tốc độ khác nhau của những sản phẩm trên đây. Xem bảng 1.6. Ví dụ để ý rằng chữ “C” đứng trước số 51 trong AT 89C51 -12PC là ký hiệu cho CMOS “12” ký hiệu cho 12 MHZ và “P” là kiểu đóng vỏ DIP và chữ “C” cuối cùng là ký hiệu cho thương mại (ngược với chữ “M” là quân sự ). Thông thường AT89C51 - 12PC rất lý tưởng cho các dự án của học sinh, sinh viên.

Mã linh kiện
Tốc độ
Số chân
Đóng vỏ
Mục đích
AT89C51-12PC

42MHZ
40
DTP
Thương mại
Bảng 4: Các phiên bản 8051 với tốc độ khác nhau của Atmel.

c- Bộ vi điều khiển DS5000 từ hãng Dallas Semiconductor
            Một phiên bản phổ biến khác nữa của 8051 là DS5000 của hãng Dallas Semiconductor. Bộ nhớ ROM trên chíp của DS5000 ở dưới dạng NV-RAM. Khả năng đọc/ ghi của nó cho phép chương trình được nạp vào ROM trên chíp trong khi nó vẫn ở trong hệ thống (không cần phải lấy ra). Điều này còn có thể được thực hiện thông qua cổng nối tiếp của máy tính IBM PC. Việc nạp chương trình trong hệ thống (in-system) của DS5000 thông qua cổng nối tiếp của PC làm cho nó trở thành một hệ thống phát triển tại chỗ lý tưởng. Một ưu việt của NV-RAM là khả năng thay đổi nội dung của ROM theo từng byte tại một thời điểm. Điều này tương phản với bộ nhớ Flash và EPROM mà bộ nhớ của chúng phải được xoá sạch trước khi lập trình lại cho chúng.

Mã linh kiện
ROM
RAM
Chân I/O
Timer
Ngắt
Vcc
Đóng vỏ
DS5000-8
DS5000-32
DS5000T-8
DS5000T-8
8K
32K
8K
32K
128
128
128
128
32
32
32
32
2
2
2
2
6
6
6
6
5V
5V
5V
5V
40
40
40
40
Bảng 5: Các phiên bản 8051 từ hãng Dallas Semiconductor.

Chữ “T” đứng sau 5000 là có đồng hồ thời gian thực.
            Lưu ý rằng đồng hồ thời gian thực RTC là khác với bộ định thời Timer. RTC tạo và giữ thời gian l phút giờ, ngày, tháng - năm  kể cả khi tắt nguồn.
            Còn có nhiều phiên bản DS5000 với những tốc độ và kiểu đóng gói khác nhau.( Xem bảng 1.8). Ví dụ DS5000-8-8 có 8K NV-RAM và tốc đọ 8MHZ. Thông thường DS5000-8-12 hoặc DS5000T-8-12 là lý tưởng đối với các dự án của sinh viên.

Mã linh kiện

NV- RAM
Tốc độ
DS5000-8-8
DS5000-8-12
DS5000-32-8
DS5000T-32-12
DS5000-32-12
DS5000-8-12
8K
8K
32K
32K
32K
8K
8MHz
12MHz
8MHz
8MHz (with RTC)
12MHz
12MHz (with RTC)
Bảng 6:Các phiên bản của DS5000 với các  tốc độ khác nhau

d- Phiên bản OTP của 8051
            Các phiên bản OTP của 8051 là các chíp 8051 có thể lập trình được một lần và được cung cấp từ nhiều hãng sản xuất khác nhau. Các phiên bản Flash và NV-RAM thường được dùng để phát triển sản phẩm mẫu. Khi một sản pohẩm được thiết kế và được hoàn thiện tuyệt đối thì phiên bản OTP của 8051 được dùng để sản hàng loạt vì nó rẻ hơn rất nhiều theo giá thành một đơn vị sản phẩm.

e- Họ 8051 từ Hãng Philips
            Một nhà sản xuất chính của họ 8051 khác nữa là Philips Corporation. Thật vậy, hãng này có một dải lựa chọn rộng lớn cho các bộ vi điều khiển họ 8051. Nhiều sản phẩm của hãng đã có kèm theo các đặc tính như các bộ chuyển đổi ADC, DAC,  cổng I/0 mở rộng và cả các phiên bản OTP và Flash. 


Một số tài liệu và khoá học bổ ích dành cho bạn: 

# Giáo Trình: Kỹ Thuật Lập Trình C/C++ Căn Bản Và Nâng Cao [Click để xem]

# Khoá học online: Học lập trình C/C++ TỪ A - Z [Click để xem]



II - Giới thiệu Kiến trúc Vi điều khiển 8051

2.1 Tổ chức bộ nhớ

            Các vi điều khiển thuộc họ 8051 đều tổ chức thành 2 không gian chương trình và dữ liệu, hình 1 và hình 2 sẽ mô tả điều này. Kiến trúc vi xử lý 8 bit của 8051 này cho phép truy nhập và tính toán nhanh hơn đối với không gian dữ liệu nhờ việc phân chia 2 không gian bộ nhớ chương trình và dữ liệu như trên. Tuy nhiên bộ nhớ ngoài được truy nhập bởi hệ thống 16 bit địa chỉ vẫn có thể thực hiện nhờ thanh ghi con trỏ. 

    Bộ nhớ chương trình (ROM, EPROM) là bộ nhớ chỉ đọc, có thể mở rộng tối đa 64Kbyte. Với họ vi điều khiển 89xx, bộ nhớ chương trình được tích hợp sẵn trong chip có kích thước nhỏ nhất là 4kByte. Với các vi điều khiển không tích hợp sẵn bộ nhớ chương trình trên chip, buộc phải thiết kế bộ nhớ chương trình bên ngoài. Ví dụ sử dụng EPROM: 2764 (64Kbyte), khi đó chân PSEN phải ở mức tích cực (5V).








Hình 1: Cấu trúc vi điều khiển 89C51



            Bộ nhớ dữ liệu (RAM) tồn tại độc lập so với bộ nhớ chương trình. Họ vi điều khiển 8051 có bộ nhớ dữ liệu tích hợp trên chip nhỏ nhất là 128byte và có thể mở rộng với bộ nhớ dữ liệu ngoài lên tới 64kByte. Với những vi điều khiển không tích hợp ROM trên chip thì vẫn có RAM trên chip là 128byte. Khi sử dụng RAM ngoài, CPU đọc và ghi dữ liệu nhờ tín hiệu trên các chân RD và WR. Khi sử dụng cả bộ nhớ chương trình và bộ nhớ dữ liệu bên ngoài thì buộc phải kết hợp chân RD và PSEN bởi cổng logic AND để phân biệt tín hiệu truy xuất dữ liệu trên ROM hay RAM ngoài.

 Bộ nhớ chương trình:

Hình 2: Cấu trúc bộ nhớ chương trình



Hình 3: Địa chỉ các ngắt trên bộ nhớ chương trình

            Hình 2 mô tả cấu trúc bộ nhớ chương trình. Sau khi khởi động, CPU bắt đầu thực hiện chương trình ở vị trí 0000H. Hình 3 mô tả địa chỉ ngắt mặc định trên bộ nhớ chương trình. Mối khi xảy ra ngắt, con trỏ của CPU sẽ nhảy đến đúng địa chỉ ngắt tương ứng và thực thi chương trình tại đó. Ví dụ ngắt ngoài 0 sẽ có địa chỉ là 0003H, khi xảy ra ngắt ngoài 0 thì con trỏ chương trình sẽ nhảy đến đúng địa chỉ 0003H để thực thi chương trình tại đó. Nếu trong chương trình ứng dụng không xử dụng đến ngắt ngoài 0 thì địa chỉ 0003H vẫn có thể dùng cho mục đích khác (sử dụng cho bộ nhớ chương trình).

Bộ nhớ dữ liệu:

Hình 4: Cấu trúc bộ nhớ dữ liệu

            Hình 4 mô tả cấu trúc bộ nhớ dữ liệu trong và bộ nhớ dữ liệu ngoài của họ vi điều khiển 8051. CPU sẽ dùng đến các chân RD và WR khi truy cập đến bộ nhớ dữ liệu ngoài.
            Hình 5 mô tả cấu trúc bộ nhớ dữ liệu trong chip, được chia thành 3 khối là 128 byte thấp, 128 byte cao và 128 byte đặc biệt.


Hình 5: Cấu trúc bộ nhớ trong

            Hình 6 mô tả cấu trúc 128 byte thấp của bộ nhớ dữ liệu của họ vi điều khiển 8051. 32 byte đầu tiên (00H-1FH) được sử dụng cho 4 bộ 8 thanh ghi R0-R7. Hai bit của thanh ghi đặc biệt PSW sẽ lựa chọn 1 trong 4 bộ thanh ghi mà vi điều khiển sẽ dùng trong khi thực thi chương trình. 


Hình 6: Cấu trúc 128 byte thấp của bộ nhớ dữ liệu trong

            8051 chứa 210 vị trí bit được định địa chỉ trong đó 128 bit chứa trong các byte ở địa chỉ từ 20H đến 2FH (16 byte x 8 bit = 128 bit) và phần còn lại chứa trong các thanh ghi đặc biệt. Ngoài ra 8051 còn có các port xuất/nhập có thể định địa chỉ từng bit, điều này làm đơn giản việc giao tiếp bằng phần mềm với các thiết bị xuất/nhập đơn bit. 
            Vùng RAM đa mục đích có 80 byte đặt ở địa chỉ từ 30H đến 7FH, bên dưới vùng này từ địa chỉ 00H đến 2FH là vùng nhớ có thể được sử dụng tương tự. Bất kỳ vị trí nhớ nào trong vùng RAM đa mục đích đều có thể được truy xuất tự do bằng cách sử dụng các kiểu định địa chỉ trực tiếp hoặc gián tiếp. 
            Bất kỳ vị trí nhớ nào trong vùng RAM đa mục đích đều có thể được truy xuất tự do bằng cách sử dụng các kiểu định địa chỉ trực tiếp hoặc gián tiếp. 
            Cũng như các thanh ghi từ R0 đến R7, ta có 21 thanh ghi chức năng đặc biệt SFR chiếm phần trên của Ram nội từ địa chỉ 80H đến FFH. Cần lưu ý là không phải tất cả 128 địa chỉ từ 80H đến FFH đều được định nghĩa mà chỉ có 21 địa chỉ được định nghĩa. 


Hình 7: 128 byte cao của bộ nhớ dữ liệu

2.2 Các thanh ghi đặc biệt

            8051 có 21 thanh ghi chức năng đặc biệt SFR chiếm phần trên của Ram nội từ địa chỉ 80H đến FFH. Cần lưu ý là không phải tất cả 128 địa chỉ từ 80H đến FFH đều được định nghĩa mà chỉ có 21 địa chỉ được định nghĩa. Hình 8 mô tả các thanh ghi đặc biệt trong vùng nhớ dữ liệu 80H đến FFH và giá trị của chúng sau khi Reset.


Hình 8: Các thanh ghi đặc biệt

Thanh ghi chính:

            Thanh ghi tính toán  chính của vi điều khiển 8051 ACC (Accumulator). Là thanh ghi đặc biệt của 8051 dùng để thực hiện các phép toán của CPU, thường kí hiệu là A. 

Thanh ghi phụ:

            Thanh ghi tính toán phụ của vi điều khiển 8051 là B. Thanh ghi B ở địa chỉ F0H được dùng chung với thanh chứa A trong các phép toán nhân, chia. Lệnh MUL  AB nhân 2 số 8 bit không dấu chứa trong A và B và chứa kết quả 16 bit vào cặp thanh ghi B, A (thanh chứa A cất byte thấp và thanh ghi B cất byte cao).
            Lệnh chia DIV  AB chia A bởi B, thương số cất trong thanh chứa A và dư số cất trong thanh ghi B. Thanh ghi B còn được xử lý như một thanh ghi nháp. Các bit được định địa chỉ của thanh ghi B có địa chỉ từ F0H đến F7H. 

Thanh ghi trạng thái chương trình (PSW):

            Thanh ghi trạng thái chương trình PSW (địa chỉ: D0H) là thanh ghi mô tả toàn bộ trạng thái chương trình đang hoạt động của hệ thống. Bảng 7 và Bảng 8 sẽ mô tả thanh ghi này.

7
6
5
4
3
2
1
0
CY
AC
F0
RS1
RS0
OV
-
P
Bảng 7: Thanh ghi trạng thái chương trình PSW

Bit
Ký Hiệu
Địa Chỉ
Mô tả Bit
PSW.7
CY
D7H
Cờ nhớ (Carry Flag): được Set nếu có Bit nhớ từ Bit 7 trong phép cộng hoặc có Bit mượn cho Bit 7 trong phép trừ.
PSW.6
AC
D6H
Cờ nhớ phụ: được Set trong phép cộng nếu có Bit nhớ từ Bit 3 sang Bit 4 hoặc kết quả trong 4 Bit thấp nằm trong khoảng 0AH->0FH.
PSW.5
FO
D5H
Cờ O: dành cho người sử dụng.
PSW.4
RS1
D4H
Chọn dãy thanh ghi (Bit 1)
PSW.3
RS0
D3H
Chọn dãy thanh ghi (Bit 0)
00=Bank 0: Địa chỉ 00H->07H
01=Bank 1: Địa chỉ 08H->0FH
10=Bank 2: Địa chỉ 10H->17H
11=Bank 3: Địa chỉ 18H->1FH
PSW.2
OV
D2H
Cờ tràn (Overflow Flag): được Set khi phép toán có dấu có kết quả > +127 hoặc < -128.
PSW.1
-
D1H
Chưa dùng
PSW.0
P
D0H
Cờ kiểm tra chẵn lẻ: được Set hoặc Clear bởi phần cứng sau mỗi 1 chu kỳ lệnh, để chỉ ra rằng có 1 số chẵn hoặc số lẻ Bit 1 trong thanh chứa.
Bảng 8: Chi tiết các bit trong thanh ghi PSW

Thanh ghi ngăn xếp (Stack Pointer):

            Con trỏ stack SP (stack pointer) là 1 thanh ghi 8 bit ở địa chỉ 81H. SP chứa địa chỉ của dữ liệu hiện đang ở đỉnh của stack. Các lệnh liên quan đến satck bao gồm lệnh cất dữ liệu vào stack và lệnh lấy dữ liệu ra khỏi stack. Việc cất vào stack làm tăng SP trước khi ghi dữ liệu và việc lấy dữ liệu ra khỏi stack sẽ giảm SP. Vùng stack của 8051 được giữ trong RAM nội  và được giới hạn đến các địa chỉ truy xuất được bởi kiểu định địa chỉ gián tiếp. Các lệnh PUSH và POP sẽ cất dữ liệu vào stack và lấy dữ liệu từ stack, các lệnh gọi chương trình con (ACALL, LCALL) và lệnh trở về (RET, RETI) cũng cất và phục hồi nội dung của bộ đếm chương trình PC (Program counter)

Con trỏ dữ liệu DPTR:

            Con trỏ dữ liệu DPTR (data pointer) được dùng để truy xuất bộ nhớ chương trình ngoài hoặc bộ nhớ dữ liệu ngoài. DPTR là một thanh ghi 16 bit có địa chỉ là 82H (DPL, byte thấp) và 83H (DPH, byte cao).

Thanh ghi các cổng P0-P3:

            Các port xuất/nhập của 8051 bao gồm  Port 0 tại địa chỉ 80H, Port 1 tại địa chỉ 90H, Port 2 tại địa chỉ A0H và Port 3 tại địa chỉ B0H. Tất cả các port đều được định địa chỉ từng bit nhằm cung cấp các khả năng giao tiếp mạnh.

Thanh ghi bộ đệm truyền thông nối tiếp (Serial Data Buffer):

            Bộ đệm truyền thông được chia thành hai bộ đệm, bộ đệm truyền dữ liệu và bộ đệm nhận dữ liệu. Khi dữ liệu được chuyển vào thanh ghi SBUF, dữ liệu sẽ được chuyển vào bộ đệm truyền dữ liệu và sẽ được lưu giữ ở đó cho đến khi quá trình truyền dữ liệu qua truyền thông nối tiếp kết thúc. Khi thực hiện việc chuyển dữ liệu từ SBUF ra ngoài, dữ liệu sẽ được lấy từ bộ đệm nhận dữ liệu của truyền thông nối tiếp.

Thanh ghi của bộ định thời/bộ đếm:

            8051 có 2 bộ đếm/định thời (counter/timer) 16 bit để định các khoảng thời gian hoặc để đếm các sự kiện. Các cặp thanh ghi (TH0, TL0) và (TH1, TL1) là các thanh ghi của bộ đếm thời gian. Bộ định thời 0 có địa chỉ 8AH (TL0, byte thấp) và 8CH (TH0, byte cao). Bộ định thời 1 có địa chỉ 8BH (TL1, byte thấp) và 8DH (TH1, byte cao).  
            Hoạt động của bộ định thời được thiết lập bởi thanh ghi chế độ định thời TMOD (Timer Mode Register) ở địa chỉ 88H. Chỉ có TCON được định địa chỉ từng bit.

Các thanh ghi điều khiển:

            Các thanh ghi điều khiển đặc biệt như IP, IE, TMOD, TCON, SCON và PCON là các thanh ghi điều khiển và ghi nhận trạng thái của hệ thống ngắt, bộ đếm/định thời, truyền thông nối tiếp. Chi tiết của các thanh ghi này sẽ được mô tả sau

----------


B. Hướng Dẫn Sử Dụng Keil C Lập Trình 8051



I – Cài đặt Keil C uVision3
Tải file hướng dẫn cài đặt, crack  Keil C uVision3 ở dưới và làm theo hướng dẫn.

Keil C uVision3 8.05 Full.

II – Các bước tạo Project
Ta thực hiện 6 bước:1. Tạo mới 1 project.2. Tạo File.C.3. Add File.C vào Project.4. Biên dịch và tạo File Hex.5. Gỡ lỗi chương trình (nếu có).6. Nạp chương trình cho Vi điều khiển.

1. Tạo mới 1 Project

Chạy chương trình Keil C:



Vào Project->New Project:



Chọn đường dẫn để lưu và đặt tên cho project:


Sau đó ấn Save.

Tiếp theo ta chọn loại Chip: ở đây ta lựa chọn Atmel->chọn AT89C51





Ấn Yes và ta đã hoàn thành bước 1 – tạo 1 project mới.



2. Tạo File.C
Tiếp theo ta cần tạo 1 File.C để viết chương trình cho Chip lên file đó.

Vào File->New:



Sau đó chọn Save:


Chọn đường dẫn vào cùng thư mục với Project vừa tạo ở bước 1. Sau đó đặt tên, với phần đuôi mở rộng là .C:



Chọn Save, và ta đã hoàn thành xong bước 2 – Tạo File.C.

3. Add File.C vào Project
Ta cần phải liên kết File.C với Project vừa tạo với nhau:

Trong giao diện Keil C, ở không gian làm việc của Project: Chuột phải vào phần “Source Group 1” -> Add files to Group “Source Group 1”:



Chọn đường dẫn đến thư mục Project -> chọn file.C vừa tạo ở bước 2 -> Add:




Và ta đã hoàn tất giai đoạn tạo Project, chuẩn bị viết code nào J




4. Biên dịch và tạo file Hex
Ta thử viết 1 đoạn code tạo hiệu ứng nháy Led đơn giản ở port0 sau đây vào file.c trong project:

#include<at89x52.h>

unsigned char nhayled[20]={0x05,0x0e,0x1d,0x3e,0x7d,0xfe,0x01,0x82,0xc1,0xe2,0xf1,0xfa,0xfd,0x02,0xfd,0x02,0xfd,0x02,0xfd,0x02};

//cac hieu ung
void delay(unsigned int i)
{
while(i--)
{
unsigned char j=121;
while(j--){}
}
}

main(){
unsigned char k;
while(1) {
for(k=0;k<20;k++)
{
P0=nhayled[k];
delay(250);
}
}
}






Tùy chỉnh Options:
-Sửa tần số thạch anh: 



-Tạo file Hex:



Biên dịch chương trình: Ấn vào biểu tượng Build. Nếu thành công sẽ có thông báo:
 “Creating hex file from …”, 0 Error. Và ta sẽ có 1 file Hex được tạo ra cùng thư mục với project.



5. Gỡ lỗi chương trình
Nếu chương trình còn có lỗi, để tiến hành gỡ lỗi chương trình (Debug) ta thực hiện từng bước:


Ở đây chương trình chỉ tác động lên Port0 nên ta sẽ mở khung quan sát Port0 lên.

Tiếp theo: để chạy từng dòng lệnh, ta ấn F10 hoặc F11:
-F10 sẽ không chạy vào hàm con.
-F11 sẽ chạy cả hàm con.


Quan sát đầu ra ở Port0:



Sau khi sửa lỗi và hoàn thiện, việc còn lại ta chỉ cần nạp file Hex vào cho Vi điều khiển.

6. Nạp chương trình cho Vi điều khiển
Tùy từng mạch nạp chuyên dụng mà ta cần cài đặt Driver, và phần mềm nạp Chip khác nhau.
Chúng ta sẽ tìm hiểu trong bài sau nhé!


C. Các chân, cổng vào/ra


Ø  Nắm được cấu trúc các chân của 8051Ø  Biết rõ tác dụng của chúng, cách sử dụng

1. Mô tả các chân của 8051
           
Mặc dù các thành viên của họ 8051 (ví dụ 8751, 89C51, DS5000) đều có các kiểu đóng vỏ khác nhau, chẳng hạn như hai hàng chân DIP (Dual In-Line Pakage) dạng vỏ dẹt vuông QFP (Quad Flat Pakage) và dạng chíp không có chân đỡ LLC (Leadless Chip Carrier) thì chúng đều có 40 chân cho các chức năng khác nhau như vào/ra I/0, đọc RD, ghi WR, địa chỉ, dữ liệu và ngắt. Cần phải lưu ý rằng một số hãng cung cấp một phiên bản 8051 có 20 chân với số cổng vào-ra ít hơn cho các ứng dụng yêu cầu thấp hơn. Tuy nhiên, vì hầu hết các nhà phát triển chính sử dụng chíp đóng vỏ 40 chân với hai hàng chân DIP nên ta chỉ tập trung mô tả phiên bản này.

Hình 1: Sơ đồ bố trí chân của 8051.

            Trên hình 1 là sơ đồ bố trí chân của 8051. Ta thấy rằng trong 40 chân thì có 32 chân dành cho các cổng P0P1P2 và P3 với mỗi cổng có 8 chân. Các chân còn lại được dành cho nguồn VCC, đất GND, các chân dao động XTAL1 và XTAL2, chân Reset RST, chân cho phép chốt địa chỉ ALE, chân truy cập địa chỉ ngoài EA, cho phép cất chương trình PSEN. Trong 8 chân này thì 6 chân VCC­ , GNDXTAL1XTAL2RST và EAđược các họ 8051 sử dụng. Hay nói cách khác là chúng phải được nối để cho hệ thống làm việc. Còn hai chân khác là PSEN và ALE được sử dụng chủ yếu trong các họ 8031.

1.1  Chân VCC

Chân số 40 là VCC , có chức năng cấp điện áp nguồn cho chíp. Nguồn điện áp là+5V.

1.2  Chân GND

Chân số 20 là GND, được nối với đất.

1.3  Chân XTAL1 và XTAL2

8051 có một bộ dao động trên chíp nhưng nó yêu cầu có một xung đồng hồ ngoài để chạy nó. Một bộ dao động thạch anh sẽ được nối tới các chân đầu vào XTAL1 (chân 19) và XTAL2 (chân 18). Bộ dao động thạch anh được nối tới XTAL1 và XTAL2 cũng cần hai tụ gốm giá trị khoảng 30pF. Một phía của tụ điện được nối xuống đất như được trình bày trên hình 2a.
Cần phải lưu ý rằng có nhiều tốc độ khác nhau của họ 8051. Tốc độ được coi như là tần số cực đại của bộ dao động được nối tới chân XTAL. Một bộ vi điều khiển 8051 yêu cầu một tinh thể thạch anh có tần số không lớn hơn 20MHz. Khi 8051 được nối tới một bộ dao động tinh thể thạch anh và cấp nguồn thì ta có thể quan sát tần số trên chânXTAL2 bằng máy hiện sóng. Nếu ta quyết định sử dụng một nguồn tần số khác bộ dao động thạch anh, chẳng hạn như là bộ dao động TTL thì nó sẽ được nối tới chân XTAL1, còn chân XTAL2 thì để hở không nối như hình 2b.


Hình 2: a) Nối XTAL tới thạch anh          b) Nối XTAL tới nguồn đồng bộ ngoài.

1.4  Chân RST

RST là chân số 9 - Reset. Nó là một chân đầu vào có mức tích cực cao (bình thường ở mức thấp). Khi cấp xung cao tới chân này thì bộ vi điều khiển sẽ được Reset và kết thúc mọi hoạt động. Điều này thường được coi như là sự tái bật nguồn. Khi kích hoạt tái bật nguồn sẽ làm mất mọi giá trị trên các thanh ghi. Hình 3 liệt kê các thanh ghi đặc biệt của 8051 và giá trị của chúng sau khi Reset.


Hình 3: Giá trị một số thanh ghi sau RESET.

Lưu ý rằng giá trị của bộ đếm chương trình PC là 0 khi tái lập để ép CPU nạp mã lệnh đầu tiên từ bộ nhớ ROM tại vị trí ngăn nhớ 0000. Điều này có nghĩa là ta phải đặt dòng đầu tiên của mã nguồn tại vị trí ngăn nhớ 0 của ROM vì đây là mã mà sau khi CPU thức tỉnh sẽ tìm lệnh đầu tiên. Hình 4 trình bày cách nối chân RST với mạch Reset.


Hình 4: Mạch Reset.

Nhằm làm cho đầu vào Reset có hiệu quả thì xung cấp cho nó phải kéo dài tối thiểu 2 chu kỳ máy trước khi nó xuống thấp.
Trong 8051: 1 chu kỳ máy được tính bằng 12 chu kỳ dao động.

1.5  Chân EA

EA có nghĩa là truy cập ngoài (External Access): là chân số 31 trên vỏ kiểu DIP. Nó là một chân đầu vào và phải được nối hoặc với Vcc hoặc GND. Hay nói cách khác là nó không được để hở.
Các thành viên họ 8051 như 8751, 98C51 hoặc DS5000 đều có ROM trên chíp lưu cất chương trình. Trong các trường hợp như vậy thì chân EA được nối tới Vcc. Đối với các thành viên của họ như 8031 và 8032 mà không có ROM trên chíp thì mã chương trình được lưu cất ở trên bộ nhớ ROM ngoài và chúng được nạp cho 8031/32. Do vậy, đối với 8031 thì chân EA phải được nối đất để báo rằng mã chương trình được cất ở ngoài.
Các chân mô tả ở trên đều phải được nối mà không cần thành phần nào được sử dụng. Còn hai chân dưới đây được sử dụng chủ yếu trong hệ thống vi điều khiển 8031.

1.6  Chân PSEN

PSEN là chân đầu ra cho phép cất chương trình (Program Store Enable) trong hệ thống. Trên vi điều khiển 8031, chương trình được cất ở bộ nhớ ROM ngoài thì chân này được nối tới chân OE của ROM.

1.7  Chân ALE

Chân cho phép chốt địa chỉ ALE là chân đầu ra tích cực cao. Khi nối 8031 tới bộ nhớ ngoài thì cổng P0 dùng để trao đổi cả địa chỉ và dữ liệu. Hay nói cách khác 8031 dồn cả địa chỉ và dữ liệu qua cổng P0 để tiết kiệm số chân. Chân ALE được sử dụng để phân kênh địa chỉ và dữ liệu.

1.8  Các chân cổng vào/ra và các chức năng của chúng

Bốn cổng P0P1P2 và P3 đều sử dụng 8 chân và tạo thành cổng 8 bít. Tất cả các cổng khi Reset đều được cấu hình như các đầu ra, sẵn sàng để được sử dụng như các cổng đầu ra. Muốn sử dụng cổng nào trong số các cổng này làm đầu vào thì nó phải được lập trình.

1.8.1 Cổng P0

Cổng P0 chiếm tất cả 8 chân (từ chân 32 đến 39). Nó có thể được dùng như cổng đầu ra, để sử dụng các chân của cổng P0 vừa làm đầu ra, vừa làm đầu vào thì mỗi chân phải được nối tới một điện trở kéo bên ngoài 10kW. Điều này là do một thực tế là cổngP0 là một máng mở khác với các cổng P1P2 và P3. Khái niệm máng mở được sử dụng trong các chíp MOS về chừng mực nào đó nó giống như collector hở đối với các chíp TTL.
Trong bất kỳ hệ thống nào sử dụng 8751, 89C51 hoặc DS5000 ta thường nối cổngP0 tới các điện trở kéo (Xem hình 5), bằng cách này ta có thể sử dụng được cổng P0 cho cả 2 trường hợp đầu ra và đầu vào. Với những điện trở kéo ngoài được nối, khi Reset cổngP0 được cấu hình như một cổng đầu ra.


Hình 5: Cổng P0 với các điện trở kéo.

Cổng P0 là đầu vào: Với các điện trở được nối tới cổng P0 nhằm để tạo nó thành cổng đầu vào thì nó phải được lập trình bằng cách ghi 1 tới tất cả các bit của P0.

Vai trò kép của cổng P0: Như trên hình 1, cổng P0 được gán là các bit địa chỉAD0 - AD7 cho phép nó được sử dụng vừa cho địa chỉ, vừa cho dữ liệu. Khi nối 8051/31 tới bộ nhớ ngoài thì cổng P0 cung cấp cả địa chỉ và dữ liệu, 8051 dồn dữ liệu và địa chỉ qua cổng P0 để tiết kiệm số chân. ALE được sử dụng để tách địa chỉ và dữ liệu với sự trợ giúp của IC chốt dữ liệu 74LS373.

1.8.2 Cổng P1

Cổng P1 cũng chiếm tất cả 8 chân (từ chân 1 đến chân 8) nó có thể được sử dụng như đầu vào hoặc đầu ra. So với cổng P0 thì cổng này không cần đến điện trở kéo vì nó đã có các điện trở kéo bên trong. Trong quá trình Reset thì cổng P1 được cấu hình như một cổng đầu ra.
            Cổng P1 là đầu vào: Tương tự P0, để biến cổng P1 thành đầu vào thì nó phải được lập trình bằng cách ghi 1 đến tất cả các bit của nó.

1.8.3 Cổng P2

Cổng P2 cũng chiếm 8 chân (các chân từ 21 đến 28). Nó có thể được sử dụng như đầu vào hoặc đầu ra, giống như cổng P1, cổng P2 cũng không cần điện trở kéo vì nó đã có các điện trở kéo bên trong. Khi Reset, thì cổng P2 được cấu hình như một cổng đầu ra.

Cổng P2 là đầu vào: Để tạo cổng P2 như đầu vào thì nó phải được lập trình bằng cách ghi các số 1 tới tất cả các chân của nó.

Vai trò kép của P2: Trong các hệ thống 8751, 89C51 và DS5000 thì P2 được dùng  như đầu ra. Tuy nhiên trong hệ thống 80312 thì cổng P2 có thể được dùng cùng vớiP0 để tạo ra địa chỉ 16 bit đối với bộ nhớ ngoài. Như chỉ ra trên hình 1 cổng P2 cũng được chỉ định như là các bit địa chỉ A8 - A15 báo chức năng kép của nó. Vì một bộ 8031 có khả năng truy cập 64k byte bộ nhớ ngoài, nên nó cần một đường địa chỉ 16 bít. Trong khi P0 cung cấp 8 bit thấp qua A0 - A7. Công việc của P2 là  cung cấp các bít địa chỉ A8 -A15. Hay nói cách khác khi 8031 được nối tới bộ nhớ ngoài thì P2 được dùng cho 8 bít cao của địa chỉ 16 bit và nó không thể dùng cho vào/ra.

 Từ những trình bày trên đây ta có thể kết luận rằng trong các hệ thống vi điều khiển 8751, 89C51 hoặc DS5000 thì ta có các cống P0P1, P2 và P3 cho các thao tác vào ra và như thế là có thể đủ cho các ứng dụng với hầu hết các bộ vi điều khiển. Ngoài ra cổng P3 còn để dành cho các chức năng đặc biệt khác mà ta sẽ cùng bàn dưới  đây.

1.8.4 Cổng P3

 Cổng P3 chiếm tổng cộng  là 8 chân từ chân 10 đến chân 17. Nó có thể được sử dụng như đầu vào hoặc đầu ra. Cống P3 không cần các điện trở kéo cũng như P1 và P2. Mặc dù cổng P3 được cấu hình như một cống đầu ra khi Reset, nhưng đây không phải là cách nó được sử dụng phổ biến nhất.
Cống P3 được bổ sung các chức năng quan trọng, đặc biệt. Bảng 2 cung cấp các chức năng khác của cống P3. Thông tin này áp dụng cho cả 8051 và 8031:

Bít của cống P3
Chức năng
chân số
P3.0
P3.1
P3.2
P3.3
P3.4
P3.5
P3.6
P3.7
Nhận dữ liệu (RXD)
Phát dữ liệu (TXD)
Ngắt 0(INT0)
Ngắt 1(INT1)
Bộ định thời 0 (TO)
Bộ định thời 1 (T1)
Ghi (WR)
Đọc (RD)
10
11
12
13
14
15
16
17
Bảng 2: Các chức năng khác của cống P3

·     Các bit P3.0 và P3.1 cung cấp tín hiệu nhận và phát dữ liệu trong truyền thông dữ liệu nối tiếp.
·        Các bit P3.2 và P3.3 được dành cho các ngắt ngoài.
·        Bit P3.4 và P3.5 được dùng cho các bộ định thời 0 và 1.
·        Cuối cùng các bit P3.6 và P3.7 để ghi và đọc các bộ nhớ ngoài khi được nối tới các hệ thống 8031

--------------------


D. Bộ đếm/ bộ định thời trong 8051


Ø  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

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. 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.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.1.1 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.1.2 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.1.3 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.

1.1.3.1 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: 012 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
0
Bộ đị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

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

1.1.3.3 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 1Cá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.2 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.2.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 (ms) 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 (ms).
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 89x51
void 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 - TL0
3.      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,085ms để tạo ra bộ trễ bằng số_đếm´1,085ms.
            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,085ms = 15,19ms cho nửa chu kỳ và cả chu kỳ là T = 2 ´ 15,19ms = 30, 38ms 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.2.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.085ms
2.      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.085ms. Điều đó có nghĩa là phải mất rất nhiều khoảng thời gian 1,085ms để có được một xung 5ms. Để có được ta chia 5ms cho 1.085ms 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 = 250ms.
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.3 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.4 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.4.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.


2. 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à THTL 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 89x51
main()                                                //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


E. Truyền thông nối tiếp với 8051

  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 độ baudkhung 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 MAX232T1 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 22mF. giá trị phổ biến nhất cho các tụ này là 22mF.

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 StartStop 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 StartStop để 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âmJ.

Ø  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  3. Còn ở chế độ  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 ?!! L
Nhưng ta lại có một cách khác để cài đặt được tốc độ 19200 baud.J 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 89c51
void 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ỗi
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
            {
                        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 Start
3.    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 89c51
char 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ỗiví 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.

Ø  Gi giá tr ca biến

#include <at89x51.h>                     //Khai báo thư viện 89x51
#include <string.h>                         //Khai báo thư viện xử lý chuỗi
unsigned long a=4294967295;      //Biến unsigned long, miền giá trị: 0->4294967295
float 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ên
void sendsothuc(float n);                           //hàm gửi 1 số thự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
            {
                        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



Tìm kiếm nội dung khác: