Pic 16f877a Tutorial 01.03

112
RB7/PGD 1 RB6/PGC1 RB5 RB4 RB3/PGM 40 RB2 40 RB11 40 RB0/INT1 VDD1 40 VSS 1 40 RD7/PSP7 1 RD6/PSP6 1 RD5/PSP5 1 RD4/PSP4 1 RC7/RX/DT 1 RC6/TX/CK 1 RC5/SDO 1 RC4/SDI/SDA 1 RD3/PSP3 1 RD2/PSP2 1 RA0/AN0 MCLR/VPP RA1/AN1 RA3/AN3/VREF+ RA4/T0CKI/C1OUT RA5/AN4/SS/C2OUT RE0/RD/AN5 RE1/WR/AN6 RE2/CS/AN7 VDD VSS OSC1/CLK OSC2/CLKO RC0/T1OSO/T1CKI RC1/T1OSI/CCP2 RC2/CCP1 RC3/SCK/SCL RD0/PSP0 RD1/PSP1 RA2/AN2/VREF-/CVREF 1 40 2 39 3 38 4 37 5 36 6 35 7 34 8 33 9 32 10 31 11 30 12 29 13 28 14 27 15 26 16 25 17 24 18 23 19 22 20 21 PIC16F874A/877A

Transcript of Pic 16f877a Tutorial 01.03

RB7/PGD 1

RB6/PGC1

RB5

RB4

RB3/PGM 40

RB2 40

RB11 40

RB0/INT1

VDD1 40

VSS 1 40

RD7/PSP7 1

RD6/PSP6 1

RD5/PSP5 1

RD4/PSP4 1

RC7/RX/DT 1

RC6/TX/CK 1

RC5/SDO 1

RC4/SDI/SDA 1

RD3/PSP3 1

RD2/PSP2 1

RA0/AN0

MCLR/VPP

RA1/AN1

RA3/AN3/VREF+

RA4/T0CKI/C1OUT

RA5/AN4/SS/C2OUT

RE0/RD/AN5

RE1/WR/AN6

RE2/CS/AN7

VDD

VSS

OSC1/CLK

OSC2/CLKO

RC0/T1OSO/T1CKI

RC1/T1OSI/CCP2

RC2/CCP1

RC3/SCK/SCL

RD0/PSP0

RD1/PSP1

RA2/AN2/VREF-/CVREF

1 40

2 39

3 38

4 37

5 36

6 35

7 34

8 33

9 32

10 31

11 30

12 29

13 28

14 27

15 26

16 25

17 24

18 23

19 22

20 21

PIC16F874A/877A

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 1/14

Tutorial 01.03

Gửi đến: Đoàn Hiệp, Doãn Minh Đăng, [email protected]

Nội dung: BÀI 1: PIC16F877A TỪ DỄ TỚI KHÓ

MICROSOFT WORD

Tóm tắt:

Tutorial post lên luồng “PIC16F877A TỪ DỄ TỚI KHÓ” thuộc chuyên mục “CƠ BẢN VỀ VI ĐIỀU KHIỂN VÀ PIC”. Bài đầu tiên bao gồm nội dung sau:

Ứng dụng đơn giản nhất dành cho vi điều khiển PIC16F877A, đó là xuất dữ liệu ra một port

nào đó của vi điều khiển. Các bước tiến hành bao gồm: _ Bước 1: Xây dựng mạch test. _ Bước 2: Xây dựng chương trình. _ Bước 3: Nhận xét và kết luận.

Một số đặc điểm về các port điều khiển của vi điều khiển PIC16F877A. Chương trình và sơ đồ nguyên lí mạch test đi kèm.

1. Điều khiển các port I/O Đây là một trong những ứng dụng đơn giản nhất giúp ta làm quen với vi điều khiển.

Trong ứng dụng này ta sẽ xuất một giá trị nào đó ra một PORT của vi điều khiển, chẳng hạn như PORTB. Giá trị đưa ra PORTB sẽ được kiểm tra bằng cách gắn các LED vào các chân I/O của PORT đó.

1.1. Xây dựng mạch test cho ứng dụng

Trước tiên ta cùng xây dựng mạch test cho ứng dụng này. Ngoại trừ vi điều khiển PIC16F877A, các thành phần còn lại trong mạch đều rất thông dụng và dễ dàng tìm thấy trên thị trường, do đó hãy thi công mạch test này để ta có thể xem xét các hiệu ứng cụ thể của vi điều khiển một cách trực quan và nghiêm túc, vì sau bài này, các bạn sẽ thấy rằng ta không thể ngồi một chỗ đọc sách hay tài liệu mà có thể lường trước được hết những hiệu ứng mà vi điều khiển tạo ra, thậm chí là các hiệu ứng từ ứng dụng đơn giản nhất này.

Do đây là bài đầu tiên, cho nên các bước tiến hành sẽ rất nghiêm túc và thận trọng. Nào, bắt đầu!

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 2/14

Ta có vi điều khiển PIC16F877A với sơ đồ chân như sau:

PIC16F877A

89

10

12

14

15161718

1920

2122

23242526

27282930

31

1

13

234567 33

34353637383940

11

32RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

GND

OSC2/CLKOUT

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1

RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SDO

RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

GND

MCLR/VPP

OSC1/CLKIN

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT RBO/INT

RB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

VDD

VDD

Hình 1.1 Vi điều khiển PIC16F877A.

Bây giờ ta hãy cấp nguồn cho vi điều khiển họat động, và câu hỏi đặt ra là cấp nguồn như thế nào? Tất nhiên, nguồn cung cấp sẽ là nguồn 5V, vấn đề ở đây là, vi điều khiển PIC16F877A có đến hai chân cấp nguồn VCC và hai chân GND. Các bạn có cảm thấy bối rối và thắc mắc là tại sao lại có đến 4 chân cấp nguồn như vậy không? Và sau đây là câu trả lời, ta phải cấp nguồn vào tất cả các chân nguồn trên, như vậy thì vi điều khiển mới hoạt động được. Và mạch nguyên lí sau khi cấp nguồn như sau:

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 3/14

HI

PIC16F877A

89

10

12

14

15161718

1920

2122

23242526

27282930

31

1

13

234567 33

34353637383940

11

32RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

GND

OSC2/CLKOUT

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1

RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SDO

RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

GND

MCLR/VPP

OSC1/CLKIN

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT RBO/INT

RB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

VDD

VDD

0

0HI

Hình 1.2 Vi điều khiển PIC16F877A sau khi cấp nguồn.

Tiếp theo, ngoài nguồn cung cấp, ta cần phải cung cấp xung hoạt động cho vi điều khiển. PIC16F877A và các vi điều khiển nói chung cho phép nhiều cách cung cấp xung hoạt động khác nhau. Ở đây ta sẽ dùng thạch anh làm nguồn xung, và công việc của ta là gắn thạch anh vào hai chân 13 và 14 của vi điều khiển. Tuy nhiên các bạn cũng biết rằng, các xung dao động do thạch anh tạo ra cũng không thực sự ổn định một cách tuyệt đối, và cách khắc phục là gắn thêm các tụ lọc vào thạch anh. Như vậy, cần phải gắn các tụ như thế nào và giá trị bao nhiêu? Câu trả lời nằm trong cái datasheet. Các bạn lật cái datasheet PIC16F87xA do Microchip cung cấp ra. Trang 145, hình 4.1 hướng dẫn cách gắn các tụ C1, C2 vào thạch anh, và trang 146, bảng 14-2 hướng dẫn cách chọn giá trị cho tụ. Ỡ đây la dùng thạch anh 4 MHz nên tụ C1 và C2 sẽ có giá trị 15 pF. Một điểm đáng chú ý nữa là chất lượng thạch anh tại thị trường Việt Nam không thực sự tốt, cho nên để tăng sự ổn định, ta sẽ dùng tụ 30 pF. Xong! Và sau đây là mạch nguyên lí sau khi gắn thêm thạch anh:

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 4/14

HI

PIC16F877A

89

10

12

14

15161718

1920

2122

23242526

27282930

31

1

13

234567 33

34353637383940

11

32RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

GND

OSC2/CLKOUT

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1

RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SDO

RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

GND

MCLR/VPP

OSC1/CLKIN

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT RBO/INT

RB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

VDD

VDD

C1

30 pF

0

4 MHz

0

HI0

C2

30 pF

Hình 1.3 PIC16F877A sau khi cấp nguồn và thêm thạch anh.

Bây giờ là mạch reset cho vi điều khiển sử dụng chế độ reset từ chân MCLR của vi điều khiển (chân số 1). Ta đã biết vi điều khiển sẽ được reset khi chân MCLR chuyển từ mức logic 1 xuống mức logic 0 và ta sử dụng một công tắc cơ khí để thực hiện viêc chuyển đổi đó, như vậy ta mới có thể tác động cho vi điều khiển reset bằng tay. Lại một câu hỏi nữa, phải thiết kế mạch như thế nào để thưc hiện được công việc đó? Dễ thôi, ta có thể thiết kế như hình 1.4. Bình thường công tắc hở, chân MCLR của vi điều khiển mang mức logic 1 (vì được nối với nguồn qua điện trở hạn dòng R1). Điện trở R1 phải có giá trị nhỏ hơn 40K để bảo đảm điện áp cung cấp cho vi điều khiển. Khi ấn công tắc, chân MCLR được nối với GND nên mang mức logic 0, khi đó vi điều khiển sẽ được reset.

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 5/14

4 MHz

HI

R1

0

HI

SW1

12

0

0

0

HI

PIC16F877A

89

10

12

14

15161718

1920

2122

23242526

27282930

31

1

13

234567 33

34353637383940

11

32RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

GND

OSC2/CLKOUT

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1

RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SDO

RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

GND

MCLR/VPP

OSC1/CLKIN

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT RBO/INT

RB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

VDD

VDD

C1

30 pF

C2

30 pF

Hình 1.4 PIC16F877A sau khi tiếp tục thêm vào mạch seset.

Đến đây xem như ta đã hoàn thành những thành phần cơ bản nhất cho một mạch test

dành cho vi điều khiển PIC16F877A. Ta tiếp tục phát triển các thành phần tiếp theo để test các port của vi điều khiển.

PORTB của vi điều khiển sẽ được test đầu tiên. Mục đích của mạch test là kiểm tra xem

các giá trị ta xuất ra port bằng chương trình có đúng hay không, và để phát hiện được các giá trị đó một cách trực quan, ta sử dụng 8 LED gắn vào 8 chân trong PORTB của vi điều khiển. Khi ta xuất giá trị mang mức logic 1 ra môt chân nào đó trong PORTB của vi điều khiển, LED tương ứng gắn với chân đó sẽ sáng lên (do lúc này điện áp ở chân của vi điều khiển là 5V) và ngược lại, nếu giá trị xuất ra mang mức logic 0 thì LED sẽ không sáng (do lúc này điện áp ở chân của vi điều khiển là 0V). Tuy nhiên, ta cần chú ý đến một điểm quan trọng nữa, đó là để LED sáng bình thường, điện áp đặt vào hai đầu của LED vào khoảng 1,8V đến 2,2V, trong khi điện áp tại chân I/O của vi điều khiển khi ta xuất ra mức logic 1 sẽ

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 6/14

là 5V. Do đó cần mắc thêm điện trở hạn dòng cho LED (có thể dùng điện trở có giá trị 0.33 K).

Dựa vào các điểm đã phân tích ở trên ta có thể xây dựng được mạch nguyên lí hoàn

chỉnh cho ứng dụng test PORTB như sau:

R5

D2

4 MHz

HI

R6

D7

D5

D4

0

R2R1

0

R9

D3

HI

SW1

12

0 R7

D8

0

D1

0

R3

HI

R4

PIC16F877A

89

10

12

14

15161718

1920

2122

23242526

27282930

31

1

13

234567 33

34353637383940

11

32RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

GND

OSC2/CLKOUT

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1

RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SDO

RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

GND

MCLR/VPP

OSC1/CLKIN

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT RBO/INT

RB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

VDD

VDD

R8

C1

30 pF

D6

C2

30 pF

Hình 1.5 Mạch nguyên lí hoàn chỉnh cho ứng dụng test PORTB.

Như vậy đến đây ta đã hoàn tất việc thiết kế phần cứng cho ứng dụng. Trong phần tiếp

theo ta tiếp tục bàn đến việc viết chương trình cho ứng dụng trên.

1.2. Xây dựng chương trình xuất dữ liệu ra PORTB vi điều khiển PIC16F877A

Trước tiên, để viết được chương trình, ta cần tìm hiểu một số đặc điểm về cấu trúc của vi điều khiển PIC16F877A và cú pháp của một số lệnh sử dụng trong chương trình.

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 7/14

1.2.1 Một số đặc điểm về cấu trúc PORTB vi điều khiển PIC16F877A.

Ta cần chú ý đến các điểm sau:

- PORTB của vi điều khiển PIC16F877A cũng như các port điều khiển khác đều cho phép truyền nhận dữ liệu theo hai hướng, có nghĩa là ta được phép đọc và xuất dữ liệu ra port điều khiển. Hướng truyền nhận được thiết lập bằng cách đưa giá trị thích hợp vào thanh ghi TRISB. Mỗi bit trong thanh ghi điều khiển hướng xuất/nhập cho một chân của port (bit 7 của thanh ghi TRISB điều khiển chân RB7, bit 6 của thanh ghi TRISB điều khiển chân RB6, ... ). Nếu một bit trong thanh ghi TRISB mang mức logic 0 thì vi điều khiển sẽ hiểu rằng chân điều khiển bởi bit đó là chân xuất dữ liệu và ngược lại, nếu một bit trong thanh ghi TRISB mang mức logic 1 thì vi điều khiển sẽ hiểu rằng chân điều khiển bởi bit đó là chân nhập dữ liệu. Ví dụ, ta muốn thiết lập chân RB3, RB2, RB1, RB0 của PORTB là nhập, chân PB7, RB6, RB5, RB4 của PORTB là xuất, khi đó giá trị tương ứng đưa vào thanh ghi TRISB sẽ là ‘00001111’.

- Dữ liệu nhập vào hay xuất ra PORTB sẽ được chứa trong thanh ghi PORTB. Ví dụ, giả sử như tất cả các chân của PORTB đều là chân xuất dữ liệu, khi đó muốn đưa tất cả các chân của PORTB lên mức logic 1, ta chỉ việc đưa vào thanh ghi PORTB giá trị ‘11111111’. Nếu tất cả các chân trong PORTB đều là chân nhập dữ liệu, muốn biết được trạng thái mức logic của từng chân ta chỉ việc đọc giá trị của thanh ghi PORTB.

- Trong cấu trúc bộ nhớ dữ liệu của PIC16F877A, thanh ghi PORTB nằm ở BANK 0, còn thanh ghi TRISB nằm ở BANK 1. Ta đã biết muốn truy xuất giá trị của một thanh ghi nào đó trong bộ nhớ dữ liệu của vi điều khiển PIC, trước tiên cần chọn BANK dữ liệu chứa thanh ghi đó, và việc chọn BANK dữ liệu được điều khiển bởi hai bit RP1:RP0 của thanh ghi STATUS. Cụ thể như sau:

RP1:RP0 = 00 chọn BANK 0.

RP1:RP0 = 01 chọn BANK 1.

RP1:RP0 = 10 chọn BANK 2.

RP1:RP0 = 11 chọn BANK 3.

Các đặc điểm này sẽ là cơ sở cho việc hình thành chương trình xuất dữ liệu ra PORTB của vi điều khiển PIC16F877A.

TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 8/14

1.2.2 Các lệnh sử dụng cho chương trình

Phần này sẽ đè cập đến các lệnh sử dụng trong chương trình xuất dữ liệu ra PORTB của vi điều khiển PIC16F877A. Ta cần sử dụng các lệnh sau:

- Lệnh BSF

Cú pháp: BSF thanhghi,bit

(tham số “bit” mang giá trị từ 0 đến 7).

Chức năng: lệnh này dùng để đưa bit có số thứ tự chứa trong tham số “bit” của thanh ghi chứa trong tham số “thanhghi” lên mức logic 1.

Ví dụ: BSF PORTB,7

(bit 7 của thanh ghi PORTB sau lệnh này sẽ mang mức logic 1).

- Lệnh BCF

Cú pháp: BCF thanhghi,bit

(tham số bit mang giá trị từ 0 đến 7)

Chức năng: lệnh này dùng để đưa bit có số thứ tự chứa trong tham số “bit” của thanh ghi chứa trong tham số “thanhghi” về mức logic 0.

Ví dụ: BCF PORTB,7

(bit 7 của thanh ghi PORTB sau lệnh này sẽ mang mức logic 0).

- Lệnh MOVLW

Cú pháp: MOVLW hangso

(tham số “hangso” mang giá trị từ 0 đến 255)

Chức năng: đưa giá trị của tham số “hangso” vào thanh ghi W. Ta có một số định dạng về tham số “hangso” như sau:

Định đạng số hex: thêm kí tự “0x” vào trước tham số “hangso”.

Ví dụ: MOVLW 0x5F

(đưa giá trị hex 5F vào thanh ghi W).

Định dang số thập phân: thêm kí tự “d” vào trước tham số “hangso”.

Ví dụ: MOVLW d’15’

(đưa giá trị thập phân 15 vào thanh ghi W).

Định dạng số nhị phân: thêm kí tự “b” trước tham số “hangso”

Ví dụ: MOVLW b’10101010’

(đưa giá trị nhị phân 10101010 vào thanh ghi W).

TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 9/14

- Lệnh MOVWF

Cú pháp: MOVWF thanhghi

Tác dụng: đưa giá trị chứa trong thanh ghi W vào thanh ghi được chỉ định bởi tham số “thanhghi”.

Ví dụ: MOVWF PORTB

(đưa giá trị chứa trong thanh ghi W vào thanh ghi PORTB).

- Lệnh CLRF

Cú pháp: CLRF thanhghi

Tác dung: xóa thanh ghi được chỉ định bởi tham số “thanhghi”.

Ví dụ: CLRF PORTB

(xóa thanh ghi PORTB).

- Lệnh GOTO

Cú pháp: GOTO label

Tác dụng: nhảy tới label được chỉ định bởi tham số “label”.

Ví dụ: GOTO next

(nhảy tới label “next”).

Ta thấy rằng trong tập lệnh của vi điều khiển PIC, không có lệnh nào cho phép đưa trực tiếp một giá trị nào đó vào một thanh ghi mà phải thông qua thanh ghi trung gian là thanh ghi W. Ví dụ, ta muốn đưa giá trị b’00000000’ vào thanh ghi TRISB (thao tác này có nghĩa là thiết lập tất cả các chân của PORTB là chân xuất dữ liệu), ta có thể dùng hai lệnh như sau:

MOVLW b’00000000’

MOVWF TRISB

Tương tự ta có thể dùng “cặp lệnh” trên để đưa một giá trị 8 bit bất kì vào một thanh ghi bất kì trong bộ nhớ dữ liệu.

Tuy nhiên đây là một trường hợp đặc biệt. Việc đưa vào thanh ghi TRISB giá trị b’00000000’ cũng đồng nghĩa với việc xóa thanh ghi TRISB, do đó ta có thể thay thế hai lệnh trên băng một lệnh duy nhất:

CLRF TRISB

TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 10/14

Đến đây ta đã có được các thông tin cần thiết cho việc viết chương trình điều khiển.

Chương trình cụ thể được trình bày ở phần tiếp theo.

1.2.3 Chương trình test PORTB vi điều khiển PIC16F877A.

;-------------------------------------------------------------------------------------------

; Ghi chú về chương trình

;-------------------------------------------------------------------------------------------

; Chương trình 1.1

; PORTBTEST.ASM

; Chương trinh dùng đề test PORTB của vi điều khiển PIC16F877A

;--------------------------------------------------------------------------------------------------

; Phần khai báo vi điều khiển

;-------------------------------------------------------------------------------------------------

processor 16f877a ; khai báo vi điều khiển sử dụng chương

; trình này

include <p16f877a.inc> ; header file đính kèm

__CONFIG _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _XT_OSC & _WRT_OFF & _LVP_ON & _CPD_OFF

; khai báo “configuration bit”

;-------------------------------------------------------------------------------------------------

; Chương trình bắt đầu tại đây

;-------------------------------------------------------------------------------------------------

ORG 0x000 ; địa chỉ bắt đầu chương trình

GOTO start

start

BCF STATUS,RP1

BSF STATUS,RP0 ; chọn BANK 1

CLRF TRISB ; khởi tạo PORTB

; PORTB là cổng xuất dữ liệu

BCF STATUS,RP0 ; chọn BANK 0

TVTHIEN
Highlight

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 11/14

MOVLW b’10001111’ ; giá trị cần xuất ra PORTB

MOVWF PORTB ; PORTB <- 8Fh

loop

GOTO loop ; vòng lặp vô hạn để dừng chương trình

END ; kết thúc chương trình

1.3. Một số nhận định

Như vậy ta đã hoàn tất một ứng dụng dành cho PORTB của vi điều khiển PIC16F877A. Bây giờ hãy kiểm tra kết quả thực hiện của chương trình bằng cách sử dụng mạch test ta vừa thiết kế. Theo tính toán trên lí thuyết, do ta xuất ra PORTB giá trị b’10001111’ nên các LED gắn vào các chân RB7, RB3, RB2, RB1, RB0 sẽ sáng, còn các LED gắn vào các chân RB6, RB5, RB4 sẽ tắt. Còn kết quả do vi điều khỉển PIC tạo ra thì sao? Đây, các LED được khoanh tròn là các LED mà vi điều khiển PIC16F877A làm cho sáng lên:

Hình 1.6 Kết quả thự thi chương trình 1.1 của PIC16F877A.

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 12/14

Ta thấy có một điểm bất hợp lí ở đây. Đó là LED gắn vào chân RB3 của vi điều khiển lại không sáng. Lí do tại sao? Đó là do chân RB3 của PIC16F877A còn có thêm một chức năng là chân nạp chương trình cho vi điều khiển ở chế độ nạp LVP (Low Voltage Programming). Khi chế độ nạp này được kích hoạt, chân RB3 của vi điều khiển sẽ không được hoạt động như chân I/O bình thường, cho nên mặc dù ta có xuất dữ liệu ra chân RB3 thì vi điều khiển vẫn không thể hiện tác động đó ra chân RB3. Vậy làm sao để khắc phục? Rất đơn giản, ta chỉ cần tắt chế độ nạp LVP bằng cách khai báo ... &_LVP_OFF .... khi khai báo các “Configuration bits” ở phần khai báo vi điều khiển.

Đến đây hẳn các bạn đã hiểu tại sao ta phải thi công mạch test và kiểm tra kết quả thực thi chương trình của vi điều khiển một cách trực quan, cho dù chương trình ứng dụng có đơn giản như thế nào đi nữa, có như vậy ta mới tìm hiểu sâu hơn được các khía cạnh, các vấn đề của vi điều khiển, đồng thời san bằng khoảng cách giữa lí thuyết và thực tế.

Bây giờ ta thử không xóa hết thanh ghi TRISB mà đưa vào thanh ghi TRISB một giá trị khác, chẳng hạn như giá trị b’10000000’. Các bạn đã biết phải làm thế nào rồi chứ? Chọn BANK 1 của bộ nhớ dữ liệu và sử dụng “cặp lệnh”:

MOVLW b’10000000’

MOVWF TRISB

Kết quả thực thi chương trình là LED gắn vào chân RB7 của PORTB không sáng. Lí do như sau, ta vừa khởi tạo chân RB7 của PORTB là chân nhập dữ liệu (bit 7 của thanh ghi TRISB mang giá trị logic 1), cho nên mặc dù ta có xuất dữ liệu ra chân RB7 (giá trị xuất ra vẫn là b’10001111’) thì chân đó vẫn không xuất được dữ liệu ra bên ngoài, nhiệm vụ của chân RB7 lúc này chỉ là nhập dữ liệu từ bên ngoài. Như vậy tại một thời điểm, một chân I/O của vi điều khiển chỉ có thể thực hiên một trong hai nhiệm vụ xuất dữ liệu (Output) hoặc nhận dữ liệu (Input) tùy theo chức năng mà ta khởi tạo (đưa dữ kiệu thích hợp vào thanh ghi TRISB).

Tương tự ta có thể xuất ra PORTB một giá trị khác bằng cách thay đổi giá trị đưa vào thanh ghi PORTB.

Các port còn lại của vi điều khiển PIC16F877A cũng như các vi điều khiển PIC khác đều có cấu trúc tương tự, tức là có thanh ghi TRISx để điều khiển chức năng (Input hay Output) và thanh ghi PORTx chứa dữ liệu của port đó. Dựa vào đặc điểm này ta có thể viết chương trình điều khiển các port còn lại của PIC16F877A xuất dữ liệu ra bên ngoài theo cấu trúc như chương trình 1.1. Đây là thao tác nên thưc hiện để kiểm tra lại các đặc tính của từng port trong vi điều khiển PIC16F877A, đồng thời giúp các bạn làm quen với cấu trúc chương trình cũng như cách viết chương trình dùng cho vi điều khiển PIC.

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 13/14

Sau đây là một vài đặc điểm về các port của vi điều khiển PIC16F877A mà các bạn

nên chú ý:

- Chân RA4 của PORTA là chân có cực thu để hở, cho nên khi test PORTA, ta cần gắn thêm điện trở kéo lên cho chân này để đảm bảo kết quả hiển thị ra LED.

- PORTA và PORTE mặc định khi khởi động là các chân I/O của tín hiệu analog, cho nên trước khi muốn sử dụng các chân này như các chân I/O bình thường ta cần tiến hành thêm một bước khởi tạo nữa. Tuy nhiên trong bài đầu tiên ta chỉ xuất dữ liệu ra LED nên chưa cần quan tâm đến bước khởi tạo này (kết quả hiển thị vẫn không có gì khác biệt so với tính toán trên lí thuyết), ta sẽ bàn kĩ đến các bước khởi tạo và đặc tính analog của các chân trong PORTA và PORTE trong các bài sau.

- Các chân trong PORTB, PORTC, PORTD cũng có các chức năng khác ngoài chức năng I/O, tuy nhiên khi khởi động các chân này được mặc định là các chân I/O bình thường nên trong quá trình test port ta không còn trở ngại gì nữa.

Đây là bài đầu tiên đơn giản nhất nhưng hẳn các bạn cũng đã nhận thấy rằng có rất nhiêu thông tin cần được xử lí đến nơi đến chốn một cách thực tế và với sự nghiêm túc cần thiết. Việc thực hiện thành công ứng dụng đơn giản nhưng cũng rất quan trọng này có thể xem là một “bước ngoặc” trong quá trình bạn làm quen với vi điều khiển PIC16F877A nói riêng và họ vi điều khiển PIC nói chung.

Bên cạnh đó các bạn cũng nên làm quen dần với cách ghi chú chương trình, công viẹc này có vẻ thừa thải nhưng tác dụng mà nó mang lại là rất tích cực. Thứ nhất, ta có thể dần định hướng cho mình một cấu trúc chương trình viết cho vi điều khiển PIC. Thứ hai, bạn có thể biết được mình vừa ra lệnh cho vi điều khiển PIC thực hiện công việc gì với lệnh mình vừa viết ra. Thứ ba, người khác khi dọc chương trình của bạn cũng cảm thấy thân thiện và dễ hiểu hơn.

Ứng dụng này phải được thực hiện thành công trước khi ta xây dựng các ứng dụng khác phức tạp hơn. Đây cũng là mục đích xây dựng của loạt bài “PIC16F877A từ dễ tới khó”, ta sẽ bắt đầu từ ứng dụng đơn giản nhất này, và lấy nó làm cơ sở để xây dựng các ứng dụng ngày càng phức tạp hơn. Hy vọng các bạn có thể biết được thêm một số thông tin nào đó về vi điều khiển PIC16F877A sau bài đầu tiên này.

Hết bài 1!

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo: Nguyễn Trung Chính Tài liệu: TUT01.03

Ngày: 7/12/2005 Trang: 14/14

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  1/17 

Tutorial 02.03  

Gửi đến:  Đoàn Hiệp, Doãn Minh Đăng, [email protected] 

Nội dung:  Bài 2: CHƯƠNG TRÌNH DELAY 

  MICROSOFT WORD

 Tóm tắt: Tutorial post lên picvietnam, topic “PIC16F877A TỪ DỄ TỚI KHÓ” thuộc luồng “CƠ BẢN VỀ 

VI ĐIỀU KHIỂN VÀ PIC” với nội dung: ‐  Vài nét sơ lược về mục đích và tác dụng của chương trình delay. - Phân tích source code một số chương trình delay. - Tập trung phân tích, khai thác chương trình delay của Nigel như một dạng chương trình delay 

được chuẩn hóa.  Tutorial này sử dụng khá nhiều các kiến thức trong tutorial của Nigel.  

1. Vài nét sơ lược về chương trình delay. 

1.1. Chu kì  xung clock và chu kì lệnh 

Trong phần này  ta sẽ bàn đến một vài kiến  thức cơ sở phục vụ cho việc viết chương trình delay. Cụ thể là tìm hiểu về chu kì xung clock và chu kì lệnh trong vi điều khiển PIC. 

Ta đã biết để vi điều khiển hoạt động được cần phải cung cấp một nguồn xung clock từ bên ngoài. Đối với vi điều khiển PIC, nguồn xung clock có thể  là một mạch dao động RC đơn giản, một thạch anh,...Tất nhiên, yêu cầu của nguồn xung clock phải là càng ổn định càng tốt.  

Thông  thường, nguồn xung sử dụng cho vi điều khiển nói chung và PIC nói riêng  là thạch anh với các ưu điểm giá thành không cao, khá ổn định và rất thuận tiện trong việc tính  toán,  thiết kế mạch ứng dụng và chương  trình cho vi điều khiển. Trong bài này,  ta cũng sử dụng thạch anh làm nguồn xung cho vi điều khiển. 

Mỗi thạch anh có một tần số dao động cố định, ta gọi tần số đó là f0, thông thường f0 có các tần số 4 MHz, 10 MHz, 20 MHz, ... Tùy theo mỗi loại vi điều khiển mà yêu cầu đối với f0 có thể khác nhau. Đối với vi điều khiển PIC16F877A, tần số dao động f0   phải nhỏ hơn hoặc bằng 20 MHz, đây cũng  là  tần số hoạt động  tối đa mà đa số các vi điều khiển PIC thuộc dòng mid‐range có khả năng đáp ứng được. Chu kì dao động của thạch anh ta gọi là t0 và được tính theo công thức: 

t0 = 1/f0    (1)                                      Rất cơ bản! Không có gì cần chú thích thêm cho công thức này. 

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  2/17 

 Ta cũng đã biết rằng có hai lối kiến trúc dùng để tổ chức một vi điều khiển, đó là kiến 

trúc Von‐Neuman và kiến trúc Havard. Vi điều khiển PIC được tổ chức theo lối kiến trúc Havard. Ta không đi sâu vào các  lối kiến trúc này, mà chỉ cần biết rằng với lối kiến trúc Havard, mỗi  lệnh sẽ được  thực  thi xong  trong một khoảng  thời gian  là một chu kì  lệnh. Khoảng thời gian này luôn cố định và phụ thuộc vào chu kì của xung clock.  

Ta có một “định nghĩa” mang  tính  ... đại khái như sau: chu kì  lệnh của vi điều khiển PIC  là khoảng thời gian mà vi điều khiển PIC thực thi xong một lệnh. Ta gọi thời gian của một chu kì lệnh là ti.  

Để thực thi xong một lệnh, vi điều khiển PIC cần đến 4 chu kì xung clock. Như vậy thời gian thực thi xong một lệnh sẽ được tính: 

ti = 4t0     (2) 

Thay công thức (1) vào công thức (2) ta có được công thức tính thời gian của một lệnh (một chu kì lệnh) như sau: 

ti = 4/f0    (3) 

Ví dụ: nếu ta sử dụng thạch anh loại 4 MHz thì thời gian thực thi một lệnh của vi điều khiển là: 

ti = 4/(4×106) = 1 µs     

Để thuận tiện cho việc tính toán và thiết kế chương trình delay, ta sẽ sử dụng loại thạch anh 4 MHz cho mạch ứng dụng, vì như các bạn đã thấy, thời gian thực thi một lệnh của vi điều khiển lúc dó là 1 µs. Quá chẵn!  

1.2. Mục đích và tác dụng của chương trình delay 

Như ta đã thấy ở mục 1.1, thời gian thực thi lệnh của một vi điều khiển là rất nhanh so với tốc độ cảm nhận sự vật hiện tượng của con người. Điều này gây nhiều khó khăn cho việc “giao tiếp” giữa con người với một vi điều khiển cũng như khó khăn trong việc cảm nhận bằng giác quan kết quả các thao tác của một vi điều khiển.  

Ví du, ta dùng vi điều khiển để điều khiển một LED chớp tắt liên tục. Với thao tác này vi điều khiển chỉ cần hai chu kì lệnh là hoàn tất một chu kì chớp tắt, và thời gian của mỗi chu kì sẽ là 2 µs (khi sử dụng thạch anh 4 MHz), và trong một giây, LED sẽ chớp tắt 500000 lần. Trong khi mắt người chỉ có thể  nhận biết được 24 hình ảnh trong một giây. Điều này có nghĩa là, một người ngoài hành tinh, với con mắt có tốc độ xử  lí hình ảnh nhanh hơn, khi chứng kiến hiện tương trên sẽ nói rằng: “Eh, người trái đất, tôi thấy có cái gì đó đang chớp tắt”. Còn người trái đất, với tốc độ xử lí hình ảnh của mắt là 24 hình trong 1 giây, khi chứng kiến hiện tượng trên sẽ nói rằng: “Không, người ngoài hành tinh, tôi thấy nó sáng liên tục đó chứ!”. 

TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  3/17 

Như vậy,  làm sao để mắt người cảm nhận được LED đang chớp tắt, cách duy nhất  là 

phải giảm số lần chớp tắt trong 1 giây nhỏ hơn 24, các thao tác để vi điều khiển hiển thị cho con người thấy được hiện tượng trên lần lượt sẽ là: 

- Bật LED sáng lên - Chờ một chút cho tới khi mắt nhận được hình ảnh LED sáng. - Tắt LED - Chờ một chút cho tới khi mắt nhận được hình ảnh LED tắt. - Lặp lại các thao tác trên. Như ta đã biết, do vi điều khiển không có cái lệnh gọi là “chờ một chủt”, cho nên khái 

niệm chương trình delay mới được phát sinh để thực hiện quá trình chờ đó.  

Có thể nói chương trình delay đóng một vai trò quan trọng trong các thao tác hiển thị. Bên cạnh đó, chương trình delay còn có vai trò quan trọng trong việc giao tiếp với các thiết bị khác, khi mà tốc độ xử lí của vi điều khiển và các thiết bị không đồng nhất. Ngoài ra, ta còn  sử dụng  chương  trình delay  trong nhiều    tình huống  thực  tế  cần  ra  lệnh  cho vi  điều khiển phải chờ. 

 2. Xây dựng chương trình delay   

2.1. Các lệnh sử dụng cho chương trình delay 

Ngoài các lệnh đã được đề cập đến trong bài 1, ta cần sử dụng thêm các lệnh sau cho chương trình delay: 

Lệnh DECFSZ   

Cú pháp:  DECFSZ  thanh_ghi,noi_den 

    Lệnh 1 

    Lệnh 2 

Tác dụng: Giảm giá trị chứa trong tham số “thanh_ghi” và so sánh với 0.  

- Nếu giá trị sau khi giảm khác 0, lệnh 1 được thực thi.  

- Nếu giá trị sau khi giảm bằng 0, lệnh 1 không được thực thi và được thay bằng lệnh NOP (không làm gì cả).  

Tham số “noi_den” dùng để xác định nơi lưu giá trị thanh ghi “thanh_ghi” sau khi giảm. Khi không sử dụng  tham số “noi_den”,  trình biên dịch sẽ mặc định  là kết quả được chứa trong thanh ghi W. 

- Nếu tham số “noi_den” bằng 1, kết quả được chứa trong thanh ghi “thanh_ghi”. 

- Nếu tham số “noi_den” bằng 0, kết quả được chứa trong thanh ghi W. 

TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  4/17 

Lệnh RETURN 

Cú pháp:   RETURN 

Tác dụng:  trở về chương trình chính từ chương trình con. 

  Lệnh RETLW 

  Cú pháp:  RETLW  tham_so  (0≤ tham_so ≤ 255) 

  Tác dụng:  trở về chương trình chính từ chương trình con với giá trị tham_so được chứa trong thanh ghi W. 

2.2. Thuật toán cho chương trình delay 

Ta đã biết ở phần 1, chương trình delay là chương trình dùng để ra lệnh cho vi điều khiển ... “chờ một chút” (tạm thời định nghĩa một cách ... đại khái như vậy). Điều này cũng đồng nghĩa với việc ra lệnh cho vi điều khiển làm một công việc vô nghĩa nào đó trong một khoảng thời gian do ta quyết định. 

Trong tập lệnh của vi điều khiển PIC, ta có lệnh NOP. Lệnh này có tác dung ra lệnh cho vi điều khiển  ... không  làm gì cả, và  thời gian  thực  thi  lệnh này cũng  là 1 chu kì lệnh. Nhu vậy, ta có cần thiết phải xây dựng thuật toán cho chương trình delay, vì chỉ cần ... “NOP” liên tục là xong? Hoàn toàn không đơn giản như vậy, vì khi đó ta sẽ gặp phải các vấn đề sau: 

- Thứ nhất, cái thuật toán có vẻ ... không bình thường. 

- Thứ hai, viết  chương  trình như vậy  thì  rất mỏi  tay  (muốn  ra  lệnh  cho vi  điều khiển chờ 1 ms, bạn phải viết đi viết  lại cái  lệnh NOP  ... 1000  lần nếu sử dụng loại thạch anh 4 MHz). 

- Thứ ba, dung lượng bộ nhớ chương trình bị phí phạm một cách ... quá đáng.  

Rõ ràng là ta không thể viết chương trình delay theo cách đó. Và việc khắc phục tất cả các nhược điểm nêu trên cũng là các tiêu chí đặt ra cho một chương trình delay, đó là: ngắn gon và thuận tiện cho việc sử dụng. 

Một phương pháp thường sử dụng để viết các chương trình delay là cho vi điều khiển ... nhảy  tới nhảy  lui mấy cái  label. Tuy nhiên để   kiểm soát được  thời gian delay do chương trình tạo ra, ta cần tính toán các giá trị trong chương trình một cách phù hợp. 

Sau đây ta sẽ di sâu vào các thuật toán dùng để viết chương trình delay này. 

2.2.1 Thuật toán 1 

Trong thuật toán này ta sử dụng lệnh DECFSZ để xây dựng chương trình delay. 

Đoạn chương trình 1: xét một đoạn code như sau 

 

TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  5/17 

MOVLW  d’20’    ; đưa giá trị  20 vào thanh ghi W 

MOVWF  delay‐reg  ; delay‐reg <‐ 20 

loop   

  DECFSZ  delay‐reg,1  ; giảm giá trị trong thanh ghi delay‐reg và so sánh  

; với 0, kết quả chứa trong thanh ghi “delay‐reg” 

  GOTO             loop    ; nếu giá tri thanh ghi “delay_reg” khác 0 

; thì nhảy tới label “loop” 

  …………………    ; các lệnh tiếp theo sau đoạn chương trình delay sau  

; khi giá trị trong thanh ghi “delay‐reg” đã giảm về 0. 

Đoạn  chương  trình  delay  được  thể  hiện  trong  vòng  lặp  “loop”.  Ta  thấy  lệnh “DECFSZ   ...” cần một chu kì lệnh để thực thi , lệnh “GOTO  ...”  cần  2  chu  kì lệnh, khi đó giá trị trong thanh ghi “delay‐reg” sẽ bị giảm đi một đơn vị. Như vậy để giá trị trong thanh ghi “delay‐reg” giảm một đơn vị, ta cần (1 + 2 ) = 3 chu kì  lệnh và quãng thời gian cần thiết để giá trị trong thanh ghi “delay‐reg” giảm một đơn vị sẽ là 3ti (ti như đã đề cập đến trong phần trên là thời gian của một chu kì lệnh). 

Trong ví dụ trên, do ta đưa vào thanh ghi delay‐reg giá trị 20 cho nên số lần giảm giá trị thanh ghi ”delay‐reg” sẽ là (20 + 1) = 21. Ta có thể tính được thời gian delay T do đoạn chương trình trên tạo ra sẽ là: 

T = 3×(20+1)×ti

Ví dụ, nếu  ta  sử dụng  loại  thạch  anh  4 MHz  thì một  chu kì  lệnh  sẽ  có  thời gian ti=1µs, do đó đoạn chương trình trên sẽ tạo ra khoảng thời gian delay: 

T = 3×(20+1)×1 µs = 63 µs 

Một cách tổng quát, ta có thể suy ra được công thức tính thời gian delay cho đoạn chương trình trên như sau: 

T = 3×(N+1)ti      (4) 

Trong đó N là giá trị đưa vào thanh ghi “delay‐reg”. 

Đến đây ta đã có thể hình dung được một cách sơ lược cách tính toán thời gian delay T của một chương  trình delay. Thời gian T này sẽ phụ  thuộc vào cấu  trúc giải  thuật chương trình delay và thời gian một chu kì lệnh ti. 

Một điểm cần chú ý thông thường các thanh ghi ta sử dụng là thanh ghi 8 bit, cho nên giá trị tối đa có thể đưa vào một thanh ghi là 255. Vậy thời gian delay lớn nhất mà đoạn chương trình delay trên có thể tạo ra là: 

Tmax = 3x(255+1)ti

TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  6/17 

Muốn  tạo  thời gian delay  lâu hơn,  ta phải  tăng  số  lượng  các vòng  lặp  lên.  Đoạn 

chương trình sau minh họa cách tăng số lượng vòng lặp cho chương trình delay: 

Đoạn chương trình 2: 

MOVLW  d’255’     MOVWF  delay‐reg1  ; đưa giá trị 255 vào thanh ghi “delay‐reg1” 

loop   DECFSZ  delay‐reg1,1  ; giảm giá trị thanh ghi “delay‐reg1”và so sánh với 0         ; giá trị sau khi giảm lưu vào thanh ghi “delay‐reg1” 

  GOTO   loop1    ; nếu chưa bằng 0 nhảy tới label “loop1”   GOTO   next    ; nếu đã băng 0 chương trình delay hoàn tất loop1           

MOVLW  d’255’       MOVWF  delay‐reg2  ; đưa vào thanh ghi “delay‐reg2” giá trị 255 loop2   DECFSZ  delay‐reg2,1  ; giảm giá trị thanh ghi “delay‐reg2” và so sánh với 0           ; giá trị sau khi giảm lưu vào thanh ghi “delay‐reg2” 

GOTO   loop2    ; nếu chưa bằng 0 thì nhảy đến label “loop2”   GOTO   loop    ; nếu bằng 0 thì nhảy đến label “loop” next  ………………… ; các lệnh tiếp theo sau chương trình delay 

Ta xét đoạn chương trình từ  label “loop1”  trước. Đoạn chương trình này tương tự như đoạn chương  trình 1, cho nên cách  tính  thời gian delay  trong đoạn chương  trình này không có gì thay đổi. Giá trị N trong công thức 4 sẽ tương ứng với giá trị N2 đưa vào thanh ghi “delay‐reg2” (255). Ta gọi T2 là thời gian delay do đoạn chương trình này tạo ra thì T2 sẽ đựơc tính như sau: 

T2 = 3×(N2 + 1)ti    (5)

Khi giá trị trong thanh ghi “delay‐reg2” giảm về 0 thì các lệnh từ label “loop” được thực thi. Ở thời điểm này giá trị trong thanh ghi “delay‐reg1” sẽ giảm đi một đơn vị và tiếp tục thực thi vòng lặp “loop1”. Như vậy sau một khoảng thời gian T2, giá tri trong thanh ghi “delay‐reg1” sẽ giảm đi một đơn vị, và nếu ta gọi N1 là giá trị đưa vào thanh ghi “delay‐reg1” thì số lần giảm giá trị trong thanh ghi “delay‐reg1” sẽ là (N1 + 1). Như vậy thời gian delay T do đoạn chương trình 2 tạo ra là: 

T = (N1 + 1)T2 = 3×(N1 + 1)×(N2 + 1)×ti    (6) 

Dựa theo các giá trị đưa vào trong đoạn chương trình 2 ta có thể tính được thời gian delay do đoạn chương trình trên tạo ra như sau: 

T = 3×(255+1)×(255+1)ti = 196608ti

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  7/17 

Nếu sử dụng  loại  thạch anh 4 MHz  thì  thời gian delay do đoạn chương  trình  trên 

tạo ra là 196608 μs.  

Như vậy, tùy vào thời gian delay cần thiết và tùy vào loại thạch anh sử dụng trong mạch mà ta có thể đưa các giá trị N1 và N2 vào các thanh ghi “delay‐reg1” và “delay‐reg2” một cách thích hợp dựa vào công thức (6). 

Ví dụ:  tính  toán  các giá  trị  đưa vào  thanh ghi “delay‐reg1” và “delay‐reg2”  để  thời gian delay do đoạn chương trình 2 tạo ra là 90 ms. Giả sử ta đang sử dụng loại thạch anh 4 MHz. 

Ta giải bài toán như sau: do loại thạch anh ta sử dụng có tần số 4 MHz nên ti = 1 μs. Do đó ta có  

(N1+1)×(N2+1) = T/3ti = 90×10‐3/(3×1×10‐6) = 30×103

Nếu chọn giá trị đưa vào thanh ghi “delay‐reg2”  là N2 = 199 thì giá trị N1 đưa vào thanh ghi “delay‐reg1” sẽ là: 

N1 = 30×103/(199+1) ‐ 1 = 149 

Một điểm cần chú ý  là bên cạnh việc thỏa mãn công thức  (6), các giá trị N1 và N2 phải thỏa mãn điều kiện: 

0< N1 < 256 và 0 < N2 < 256    (7) 

Thuật toán trên cho phép ta giải quyết khá triệt để các vấn đề dành cho một chương trình delay. Tuy nhiên, nhược điểm của thuật toán trên là: trong trường hợp cần nhiều thời gian delay khác nhau, ta phải viết nhiều chương trình delay khác nhau tương ứng. Thuật  toán 2 cho chương  trình delay được phát  triển dựa  trên  thuật  toán 1 cho phép khắc phục nhược điểm trên và sẽ được trình bày cụ thể ở phần tiếp theo. 

2.2.2 Thuật toán 2 

Các bạn có thể dễ dàng nhận ra đây là thuật toán cho chương trình delay được sử dụng trong tutorial của Nigel. Phần này sẽ phân  tích cụ thể giải thuật và source code của đoạn chương trình delay này. Và để thể hiện thái độ tôn trọng tác giả, tutorial này vẫn giữ nguyên mã lệnh như trong tutorial của Nigel.  

Giả sử ta đang sử dụng loại thạch anh 4 MHz. Ta xét đoạn code sau: 

Đoạn chương trình 3: 

MOVLW  d’90’ 

MOVWF  count1  ; đưa giá trị 90 vào thanh ghi count1 

d1   

  MOVLW  d’199’ 

  MOVWF  counta  ; đưa giá trị 199 vào thanh ghi counta 

  MOVLW  d’1’ 

TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  8/17 

  MOVWF  countb  ; đưa giá trị 1 vào thanh ghi countb 

delay_0     

  DECFSZ  counta,1  ; giảm giá trị trong than ghi counta và so sánh với 0 

  GOTO   $+2    ; nếu chưa bằng 0, nhảy tới lệnh “GOTO   delay_0” 

  DECFSZ  countb,1  ; nếu bằng 0, giảm giá trị trong thanh ghi countb 

  GOTO   delay_0  ; countb sau khi giảm có giá trị bằng 0 nên lệnh này  

          ; không được thực thi  

  DECFSZ  count1,1  ; giảm giá trị trong thanh ghi count1 

  GOTO   d1    ; nhảy về label d1 

  ‐‐‐‐‐‐‐‐‐‐‐‐‐      ; các lệnh tiếp theo của chương trình chính sau đoạn  

          ; chương trình delay 

Trước tiên ta lưu ý đến lệnh “GOTO    $+2”. Lệnh này có tác dụng nhảy tới lệnh thứ hai kể từ dòng lệnh “GOTO    $+2”, tức là nhảy đến lệnh “GOTO    delay_0”. Hoàn toàn tương  tự  ta có  thể dùng  lệnh có cấu  trúc tương  tự để nhảy đến bất cứ dòng  lệnh nào trong chương trình thông qua việc thay thế hằng số sau dấu “$”. 

Ta xét đoạn code bắt đầu  từ  label “delay_0”  trước. Lệnh DECFSZ     counta,1” mất một chu kì  lệnh để  thực  thi. Nếu giá  trị chứa  trong  thanh ghi counta chưa bằng 0  thì lệnh “GOTO   $+2” được thực thi. Lệnh này mất hai chu kì lệnh. Tiếp theo, lệnh “GOTO  delay_0” được thực thi. Lệnh này cũng mất hai chu kì lệnh. Sau đó, giá trị trong thanh ghi counta  tiếp  tục được giảm. Đến đây  ta nhận  thấy rằng, để giảm một giá  trị  trong thanh ghi counta, ta mất hết 5 chu kì lệnh (1 chu kì lệnh cho lệnh DECFSZ  counta,1”, 2 chu kì lệnh cho lệnh “GOTO  $+2” và 2 chu kì lệnh cho lệnh “GOTO  delay_0”), và do giá trị đưa vào thanh ghi counta là 199 nên thời gian cần thiết để thanh ghi counta giảm hết giá trị về 0 là: 

Ta = 5×(199+1)×ti

Do ta đang sử dụng laọi thạch anh 4 MHz nên Ta  sẽ mang giá trị 1000 μs hay 1 ms. 

Khi giá trị trong thanh ghi counta bằng 0, lệnh “GOTO  $+2” sẽ không được thực thi mà thay vào đó là lệnh NOP, tiếp theo lệnh “DECFSZ    countb,1” sẽ được thực thi. Ta thấy giá trị đưa vào thanh ghi countb là 1 nên sau khi giảm countb sẽ bằng 0 nên lệnh “GOTO  delay_0” sẽ được thay bằng lệnh NOP và tiếp theo, lệnh “DECFSZ count1,1” sẽ được thực thi. Sau đó chương trình quay trở về label “d1” để thực hiện việc nạp lại các giá trị cho thanh ghi counta, countb và tiếp tục thực thi đoạn coede tư label “delay_0”. 

Như vậy việc đưa giá trị 1 vào thanh ghi countb thực chất chỉ  là để thực hiện quá trình chuyển tiếp mỗi khi thanh ghi counta giảm về 0. Và đoạn code từ label “delay_0” thực chất là để tạo ra thời gian delay gần đúng 1ms do ta đã bỏ qua một số chu kì lệnh 

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  9/17 

trong bước chuyển tiếp (lưu ý một lần nữa là ta đang sử dụng loại thạch anh 4 MHz), sau đó giá trị trong thanh ghi count1 được giảm 1 đơn vị. Vòng lặp cứ tiếp tục cho đến khi giá  trị  trong  thanh ghi  count1  được giảm về 0. Khi  đó  lệnh “GOTO     d1” không được  thực  thi nữa và quá  trình  tạo  thời gian delay kết  thúc, các  lệnh  tiếp  theo  trong chương trình chính sẽ tiếp tục được thực thi. 

Đến đây ta có thể nhận thấy rằng cứ mỗi 1 ms thì giá trị trong thanh ghi count1 sẽ giảm đi 1 đơn vị. Do đó, muốn tạo ra bất cứ một thời gian delay nào là bội số của 1 ms, ta chỉ việc đưa giá trị tương ứng vào thanh ghi count1. Trong ví dụ ở đoạn chương trình 3, do ta đưa vào thanh ghi count1 giá trị 90 nên thời gian delay sẽ là 90 ms. Hoàn toàn tương tự cho việc tạo ra thời gian delay 10 ms, 50 ms, 100 ms, 150 ms, 200 ms, …ta cũng dễ dàng nhận thấy là thời gian delay tối đa do đoạn chương trình trên tạo ra là 255 ms. Với các thao tác thông thường dành cho vi điều khiển, có thể nói đây là thời gian delay đủ lớn để ta có thể sử dụng. 

Thuật toán 2 tuy dài hơn và sử dụng nhiều thanh ghi hơn so với thuật toán 1 nhưng nó có nhiều ưu điểm hơn thuật toán 1 do tính linh động và dễ sử dụng của nó. Ta có thể sử dụng đoạn chương  trình delay này như một chương  trình delay mẫu cho việc xây dựng các ứng dụng cho vi điều khiển PIC. 

Trong  trường hợp sử dụng  lọai  thạch anh có  tần số cao hơn,  ta có  thể kết hợp hai thuật toán 1 và 2 để tạo ra thời gian delay mong muốn.  

3. Ứng dụng  Trong các phần  trên,  ta đã có  thể hình dung được mục đích,  tác dụng và một số giải 

thuật cho việc xây dựng một chương trình delay. Bây giờ là lúc sử dụng các kiến thức đó cho các ứng dụng cụ thể.  

• Ứng dụng 1: 

Ta  sẽ  phát  triển  ứng  dụng  đầu  tiên  cho  chương  trình  delay  từ mạch  nguyên  lí  và chương trình đã được xây dựng trong bài 1. Trong bài 1, ta đã thực hiện việt xuất các giá trị  ra PORTB và kiểm  chứng bằng  các LED gắn vào PORTB. Bây giờ  ta  sẽ viết chương trình cho tất cả các LED gắn vào PORTB chớp tắt sau mỗi khoảng thời gian 100 ms.  

Giải thuật cho chương trình chắc cũng không có gì phải đáng bàn, các bước thực hiện lần lượt sẽ là: 

- Bật tất cả các LED ở PORTB 

- Delay 100 ms 

- Tắt tất cả các LED ở PORTB 

- Delay 100 ms 

- Lặp lại các thao tác trên 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  10/17 

Chương trình sẽ được viết như sau:  Chương trình 2.1: ;============================================================================= 

; WWW.PICVIETNAM.COM 

; Lap trinh:      NGUYEN TRUNG CHINH 

; Ngay bat dau:              23 thang 01 nam 2006 

; Ngay hoan thanh:            23 thang 01 nam 2006 

; Kiem tra chuong trinh:      Doan Hiep, Doan Minh Dang,  

;        [email protected] 

; Ngay kiem tra:     

; Su dung vi dieu khien Microchip:  PIC16F877A 

  title    “chuongtrinh2‐1.asm” 

processor  16f877a       

  include  <p16f877a.inc>   

  __CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & 

 _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF 

 

; Cap nhat va bo sung: 

; Mo ta chuong trinh:   Chuong trinh dung de dieu khien tat ca cac LED gan vao  

;        PORTB chop tat lien tuc sau moi khoang thoi gian 100 ms. 

;         Khong su dung chuong trinh con 

; Mo ta phan cung:    8 LED duoc gan vao PORTB thong qua cac dien tro, cac  

;        thanh phan di kem bao gom thach anh, mach reset va nguon 

;=============================================================================  

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khoi tao cac bien 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

count1    EQU  0x20    ; cac bien dung cho doan chuong trinh delay   

counta    EQU  0x21       

countb    EQU  0x22   

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  11/17 

;============================================================================= 

;CHUONG TRINH CHINH 

;============================================================================= 

  ORG    0x000 

  GOTO   start 

start 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khoi tao PORTB 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

  BCF    STATUS,RP1 

  BSF    STATUS,RP0   ; chon BANK 1 

 

  CLRF    TRISB     ; PORTB <‐ output 

 

  BCF    STATUS,RP0   ; chon BANK 0 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Vong lap chinh 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

loop 

  MOVLW  0XFF 

  MOVWF  PORTB    ; bat tat ca cac LED o PORTB 

 

  MOVLW  dʹ100ʹ      ; doan chuong tirnh tao thoi gian delay 100 ms 

  MOVWF  count1 

 

d1_1   

  MOVLW  dʹ199ʹ 

  MOVWF  counta 

  MOVLW  dʹ1ʹ 

  MOVWF  countb 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  12/17 

 

delay_01   

  DECFSZ  counta,1 

  GOTO   $+2 

  DECFSZ  countb,1 

  GOTO   delay_01 

  DECFSZ  count1,1 

  GOTO   d1_1      ; het doan chuong trinh delay 

 

  CLRF    PORTB    ; tat cac LED o PORTB 

 

  MOVLW  dʹ100ʹ      ; doan chuong trinh delay 100 ms 

  MOVWF  count1 

 

d1_2   

  MOVLW  dʹ199ʹ 

  MOVWF  counta 

  MOVLW  dʹ1ʹ 

  MOVWF  countb 

 

delay_02   

  DECFSZ  counta,1 

  GOTO   $+2 

  DECFSZ  countb,1 

  GOTO   delay_02 

  DECFSZ  count1,1 

  GOTO   d1_2      ; het doan chuong trinh delay 1 ms 

 

  GOTO   loop      ; tro ve vong lap chinh cua chuong trinh 

  END 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  13/17 

  

Với  chương  trình  trên,  ta  có  thể  quan  sát  các  hiện  tượng  do  vi  điều  khiển  tạo  ra  ở PORTB thông qua các LED. Do thời gian delay  là 100 ms cho nên trong 1 giây trạng thái của LED sẽ thay đổi 10 lần. Điều này cho phép ta quan sát được bằng mắt thường. bây giờ, các  bạn  hãy  thử  giảm  thời  gian  delay  xuống  nhỏ  dần  (giảm  giá  trị  đưa  vào  thanh  ghi count1), để xem hiện tượng gì sẽ xảy ra. Khi thời gian delay giảm đến một giá trị nào đó, ta sẽ có cảm giác rằng các LED không còn chớp tắt nữa, mà sẽ sáng một cách liên tục. 

Ta dễ dàng nhận thấy một nhược điểm trong chương trình trên là phải viết đi viết lại chương  trình delay  đến hai  lần, và một  lần nữa, vấn  đề về dung  lượng bộ nhớ  chương trình được đặt ra. Một giải pháp để khắc phục nhược điểm trên, đó là chương trình con. 

Một chương trình con có thể tạm hiểu là một đoạn code nào đó được lặp đi lặp lại nhiều lần trong chương trình chính, và thay vi phải viết đi viết lại đoạn code đó nhiều lần, ta tổ chức đoạn code đó thành một chương trình con và gọi đoạn code đó từ chương trình chính thông qua  lệnh “CALL”. Một chương trình con sẽ bắt đầu bằng 1  label và kết thúc bằng lệnh RETURN hoặc lệnh RETLW. 

Có  thể nói chương  trình con giúp  ta có nhiều phương án hơn  trong việc  tổ chức một chương trình viết cho vi điều khiển.  

Bây giờ,  ta sẽ  tổ chức  lại chương  trình 2.1  thành một chương  trình mới bằng cách sử dụng chương  trình con cho  đoạn code  tạo  thời gian delay 100 ms. Chương  trình mới  sẽ được viết như sau: 

 Chương trình 2.2: 

;============================================================================= 

; WWW.PICVIETNAM.COM 

; Lap trinh:      NGUYEN TRUNG CHINH 

; Ngay bat dau:              23 thang 01 nam 2006 

; Ngay hoan thanh:            23 thang 01 nam 2006 

; Kiem tra chuong trinh:      Doan Hiep, Doan Minh Dang,  

;        [email protected] 

; Ngay kiem tra:     

; Su dung vi dieu khien Microchip:  PIC16F877A 

  title    “chuongtrinh2‐2.asm” 

processor  16f877a       

  include  <p16f877a.inc>   

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  14/17 

  __CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON &  

      _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF 

; Cap nhat va bo sung: 

; Mo ta chuong trinh:   Chuong trinh dung de dieu khien tat ca cac LED gan vao  

;        PORTB chop tat lien tuc sau moi khoang thoi gian 100 ms. 

;         Co su dung chuong trinh con 

; Mo ta phan cung:    8 LED duoc gan vao PORTB thong qua cac dien tro, cac  

;        thanh phan di kem bao gom thach anh, mach reset va nguon 

;=============================================================================  

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khoi tao cac bien 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

count1    EQU  0x20    ; cac bien dung cho doan chuong trinh delay   

counta    EQU  0x21       

countb    EQU  0x22   

;============================================================================= 

;CHUONG TRINH CHINH 

;============================================================================= 

  ORG    0x000 

  GOTO   start 

start 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khoi tao PORTB 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

  BCF    STATUS,RP1 

  BSF    STATUS,RP0   ; chon BANK 1 

 

  CLRF    TRISB     ; PORTB <‐ output 

 

  BCF    STATUS,RP0   ; chon BANK 0 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  15/17 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Vong lap chinh 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

loop 

  MOVLW  0XFF 

  MOVWF  PORTB  ; bat tat ca cac LED o PORTB 

  CALL   delay_100ms ; goi chuong trinh con tao thoi gian delay 100 ms 

  CLRF    PORTB  ; tat cac LED o PORTB 

  CALL   delay_100ms ; goi chuong trinh con tao thoi gian delay 100 ms 

GOTO   loop    ; tro ve vong lap chinh cua chuong trinh 

;============================================================================= 

; CHUONG TRINH CON 

;============================================================================= 

delay_100ms       ; label bat dau chuong trinh con  

MOVLW  dʹ100ʹ    ; doan code cho chuong trinh con delay 100 ms 

  MOVWF  count1 

d1 

  MOVLW  dʹ199ʹ 

  MOVWF  counta 

  MOVLW  dʹ1ʹ 

  MOVWF  countb 

delay_0 

  DECFSZ  counta,1 

  GOTO   $+2 

  DECFSZ  countb,1 

  GOTO   delay_0 

  DECFSZ  count1,1 

  GOTO   d1     

  RETURN      ;ket thuc chuong trinh con, tro ve chuong trinh chinh 

  END 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  16/17 

Ta nhận  thấy  rằng với  cách  tổ  chức  chương  trình  sử dụng  chương  tình  con,  chương 

trình chính cũng trở nên ngắn gọn, dễ hiểu và rõ ràng hơn rất nhiều.  

Tiếp  theo,  ta sẽ  tiến  thêm một bược nữa bằng cách  tự  làm khó mình với yêu cầu mới như sau:  

• Ứng dụng 2: 

Yêu cầu của ứng dụng này cũng  tương  tự như ứng dụng 1,  tuy nhiên  ta  sẽ  thay đổi nhiều thời gian delay khác nhau, cụ thể như sau: 

- Bật tất cả các LED  

- delay 100 ms 

- Tắt tất cả các LED  

- Delay 200ms 

Rõ ràng là trong đoạn chương trình chính, giải thuật không có nhiều khác biệt. Thay vì sau khi tắt LED, ta gọi chương trình con delay_100ms thì bây giờ sẽ gọi chương trình con delay_200ms (tất nhiên là ta phải viết chương trình con này thì mới có cái để gọi). Vấn đề ở đây là viết như thế nào cho ngắn nhất.  

Để đáp ứng được yêu cầu của bài này,  ta chỉ cần  thay đổi một vài chi  tiết nhỏ  trong chương trình con delay. Cụ thể như sau:   

delay_100ms       ; label bat dau chuong trinh con delay_100ms 

MOVLW  dʹ100ʹ     

GOTO   delay 

  Delay_200ms       ; label bat dau chuong trinh con delay_200ms 

    MOVLW  d’200’ 

    GOTO   delay 

  delay 

  MOVWF  count1 

d1 

  MOVLW  dʹ199ʹ 

  MOVWF  counta 

  MOVLW  dʹ1ʹ 

  MOVWF  countb 

delay_0 

  DECFSZ  counta,1 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT02.03 

Ngày:  2/9/2006  Trang:  17/17 

  GOTO   $+2 

  DECFSZ  countb,1 

  GOTO   delay_0 

  DECFSZ  count1,1 

  GOTO   d1     

  RETURN      ;ket thuc chuong trinh con, tro ve chuong trinh chinh 

Như ta đã biết ở phần 2, với thuật toán 2, muốn thay đổi thời gian delay cho chương trình delay, ta chỉ việc thay đổi giá trị đưa vào thanh ghi count1. Ở đây ta cũng làm thao tác tương tự. Đoạn code từ label “delay” được giữ nguyên không thay đổi. Khi gọi chương trình  con  delay_100ms,  giá  trị  100sẽ  được  đưa  vào  thanh  ghi W,  sau  đó  nhảy  tới  label “delay” để đưa giá trị đó vào thanh ghi “count1” để tiếp tục thực hiện việc tạo thời gian delay. Các thao tác được tiến hành tương tự như khi gọi chương trình con delay_200ms và lúc đó giá trị được đưa vào thanh ghi W sẽ là 200. 

Hoàn toàn tương tự ta có thể tạo ra một loạt những chương trình delay 1 ms, 2 ms, 5, ms, … để sử dụng một cách dễ dàng tùy theo yêu cầu về chương trình delay của ứng dụng cụ thể. 

Đến đây xem như ta đã phát triển một cách khá hoàn thiện về các giải pháp cho chương tình delay  thông qua việc xây dựng chương trình con delay và hiểu được cách tạo nhiều thời gian delay khác nhau trong cùng một chương trình mà không cần phải viết đi viết lại nhiều chương trình delay. 

Bây giờ là lúc rút ra một vài kết luận trước khi kết thúc bài 2 và chuẩn bị cho bài 3. 

4. Kết luận Chương  trình delay  đóng một  vai  trò  khá  quan  trọng  trong  việc phát  triển  các  ứng 

dụng cho vi điều khiển. Chương trình delay được sử dụng nhiều trong các thao tác hiển thị và trong các ứng dụng cần ra lệnh cho vi điều khiển phải chờ.  

Các thuật toán dùng để xây dựng chương trình delay phải thỏa mãn các tiêu chí ngắn gọn và thuận tiện cho việc sử dụng, đồng thời giúp ta kiểm soát được thời gian delay do đoạn chương trình tạo ra. 

Thời gian delay do chương trình delay tạo ra sẽ phụ thuộc vào giải thuật sử dụng cho chương trình delay và loại thạch anh sử dụng cho vi điều khiển. 

 Chương  trình  con giúp  ta  có  được nhiều phương án  tổ  chức một  chương  trình  ứng dụng một cách linh hoạt, gọn gàng và dễ hiểu hơn.  

  Hết bài 2!   

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  1/14 

Tutorial 03.02  

Gửi đến:  [email protected] 

Nội dung:  Bài 3: KĨ THUẬT BẢNG 

  MICROSOFT WORD

 Tóm tắt: Tutorial post lên picvietnam, topic “PIC16F877A TỪ DỄ TỚI KHÓ” thuộc luồng “CƠ BẢN VỀ 

VI ĐIỀU KHIỂN VÀ PIC” với nội dung: ‐  Phân tích giải thuật kĩ thuật bảng và một số ứng dụng hiển thị với LED.   

1. Đặt vấn đề Trước khi phân tích giải thuật của kĩ thuật bảng ta thực hiện một ứng dụng nhỏ, coi như 

vừa ôn lại bài cũ, vừa dặt ra các vấn đề cho bài mới. Ứng dụng này sử dụng mạch nguyên lí đã được xây dựng ở bài 1.  

Ứng dụng 1: Cho một LED chạy từ trái sang phải sau mỗi khoảng thời gian delay 100 ms. 

Ta đã xây dựng một mạch ứng dụng bao gồm các LED được gắn vào PORTB của vi điều khiển PIC thông qua các điện trở, và muốn LED nào sáng, cần xuất giá trị logic 1 ra chân tương ứng của PORTB. Để thuận tiện cho việc theo dõi, sơ đồ mạch sẽ đươc đưa lại trong hình dưới đây.  

R5

D2

4 MHz

HI

R6

D7

D5

D4

0

R2R1

0

R9

D3

HI

SW1

12

0 R7

D8

0

D1

0

R3

HI

R4

PIC16F877A

89

10

12

14

15161718

1920

2122

23242526

27282930

31

1

13

234567 33

34353637383940

11

32RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

GND

OSC2/CLKOUT

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1

RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SDO

RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

GND

MCLR/VPP

OSC1/CLKIN

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT RBO/INT

RB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

VDD

VDD

R8

C1

30 pF

D6

C2

30 pF

Hình 1: Sơ đồ nguyên lí mạch ứng dụng. 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  2/14 

Ta đã biết được cách thiết lập giá trị cho các chân I/O của vi điều khiển (bài 1), cách viết 

chương trình delay (bài 2), và muốn các LED dịch từ trái sang phải, ta sử dụng một trong hai lệnh sau: 

Lệnh RLF 

Cú pháp:  RLF  thanh_ghi,noi_den 

Tác dụng: dịch trái các bit trong thanh ghi “thanh_ghi” thông qua cờ carry C (thanh ghi chức năng STATUS). Kết quả sau khi dịch được lưu vào thanh ghi ”thanh_ghi”nếu tham số “noi_den” mang gia trị 1 hoặc thanh ghi W nếu tham số “noi_den” mang giá trị 0. Có thể hình dung cách dịch của lệnh này theo hình dưới. 

 Lệnh RRF 

  Cú pháp:  RRF  thanh_ghi,noi_den 

Tác dụng: dịch phải các bit trong thanh ghi “thanh_ghi” thông qua cờ carry C (thanh ghi chức năng STATUS). Kết quả sau khi dịch được lưu vào thanh ghi ”thanh_ghi”nếu tham số “noi_den” mang gia trị 1 hoặc thanh ghi W nếu tham số “noi_den” mang giá trị 0. Có thể hình dung cách dịch của lệnh này theo hình dưới. 

  

Đến đây xem như ta đã có đầy đủ các thông tin để  viết chương trình. Giải thuật cũng khá đơn giản, các bước tiến hành lần lượt như sau: 

‐  Đưa vào thanh ghi PORTB giá trị 10000000b (cho LED đầu tiên sáng). 

‐  Dịch phải giá trị trong thanh ghi PORTB (LED sáng cũng được dịch tương ứng). 

‐  Delay 100 ms. 

‐  Lặp lại bước 2. 

Và đây, chương trình của ứng dụng: 

Chương trình 3.1: 

;============================================================================= 

; WWW.PICVIETNAM.COM 

; Lap trinh:      NGUYEN TRUNG CHINH 

; Ngay bat dau:                23 thang 01 nam 2006 

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  3/14 

; Ngay hoan thanh:            23 thang 01 nam 2006 

; Kiem tra chuong trinh:       [email protected] 

; Ngay kiem tra:     

; Su dung vi dieu khien Microchip:  PIC16F877A 

title    “chuongtrinh3‐1.asm” 

processor  16f877a       

include  <p16f877a.inc>   

__CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON &  _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF 

; Cap nhat va bo sung: 

; Mo ta chuong trinh:   Chuong trinh dung de dieu khien một LED sang dịch sang phai sau  

;        moi khoang thoi gian 100 ms.         

; Mo ta phan cung:  8 LED duoc gan vao PORTB thong qua cac dien tro, cac  

;        thanh phan di kem bao gom thach anh, mach reset va nguon 

;================================================================================  

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khoi tao cac bien 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

count1  EQU  0x20    ; cac bien dung cho doan chuong trinh delay   

counta  EQU  0x21       

countb  EQU  0x22   

;============================================================================= 

; CHƯƠNG TRÌNH CHÍNH 

;============================================================================= 

ORG    0x000       

GOTO   start 

start          ; chương trình chính bắt đầu tại đây 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khởi tạo PORT B 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  4/14 

BCF      STATUS,RP1 

BSF    STATUS,RP0   ; chọn BANK1 

CLRF    TRISB     ; PORT B <‐ outputs 

BCF    STATUS,RP0   ; chọn BANK0 

MOVLW  b’10000000’    ; bật LED đầu tiên sáng lên 

MOVWF  PORTB    ; PORTB <‐ b‘10000000’ 

  ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

  ; Vòng lặp chính của chương trình 

  ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

loop   

CALL   delay100ms    ; gọi chương trình con delay100ms 

RRF    PORTB,1    ; dịch phải thanh ghi PORTB 

; kết quả sau khi dịch lưu vào thanh ghi PORTB 

GOTO   loop      ; vòng lặp vô hạn 

;================================================================================ 

; CHƯƠNG TRÌNH CON 

;================================================================================ 

delay100ms          ; chương trình con delay 100 ms 

MOVLW  d’100’ 

MOVWF  count1 

d1   

MOVLW  0xC7 

MOVWF  counta 

MOVLW  0x01 

MOVWF  countb 

delay_0     

DECFSZ  counta,1 

GOTO   $+2 

DECFSZ  countb,1 

GOTO   delay_0 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  5/14 

DECFSZ  count1,1 

GOTO   d1       

RETURN        ; trở về chương trình chính 

END          ; kết thúc chương trình 

;================================================================================ 

Xong! Đến đây ta có thể cảm nhận được rằng dường như việc phát triển các ứng dụng đã trở nên dễ dàng hơn trước. Tương tự ta có thể điều khiển các LED dịch từ phải sang trái (thay lệnh RRF bằng lênh RLF).  

Bây giờ, giả sử yêu cầu của ứng dụng không phải là dịch trái hay dịch phải, mà là dịch theo một qui luật phức tạp nào đó hoặc chẳng cần qui luật dịch nào cả. Rõ ràng các lệnh RLF hay RRF không đủ khả năng giải quyết được vấn đề, hoặc nếu có thì giải thuật cho chương trình sẽ rất phức tạp vì phải tìm ra qui luật dịch LED mà yêu cầu đặt ra. Tuy nhiên mọi khó khăn dường như đều có cách giải quyết riêng của nó, và tập lệnh của PIC cung cấp cho ta một công cụ để giải quyết khó khăn trên: lệnh RETLW. Có thể  cho rằng đây là một lệnh rất quan trọng trong việc phát triển các ứng dụng và giải thuật cho vi điều khiển PIC. Kĩ thuật bảng cũng được xây dựng dựa trên lệnh này. Phần tiếp theo sẽ phân tích giải thuật của kĩ thuật này. 

 

2. Kĩ thuật bảng 

2.1. Các thông tin cơ sở để xây dựng kĩ thuật bảng 

2.1.1 Các lệnh hỗ trợ cho kĩ thuật bảng.  

Trong bài trước ta từng biết đến lệnh RETLW với vai trò như lệnh RETURN (trở về chương trình chính từ chương trình con), ta cũng đã biết được cách sử dụng lệnh này như thế nào. Để thuận tiện cho việc theo dõi, lệnh RETLW cũng sẽ được nhăc lại ở đây cùng với một số lệnh được sử dụng cho việc phát triển kĩ thuật bảng.  

Lệnh RETLW 

Cú pháp:  RETLW  k  (0 ≤ k≤255) 

Tác dụng: trở về chương trình chính từ chương trình con với giá trị k được chứa trong thanh ghi W. 

Lệnh ADDLW 

Cú pháp:   ADDLW   k   (0 ≤ k≤255) 

Tác dụng: cộng giá trị k vào giá trị chứa trong thanh ghi W, kết quả chứa trong thanh ghi W. 

Lệnh  này  được  hỗ  trợ  bởi  các  bit  trạng  thái C, DC,  Z  trong  thanh  ghi 

TVTHIEN
Highlight
TVTHIEN
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  6/14 

STATUS. Chức năng các  lệnh này sẽ được bàn đến kĩ hơn khi cần phát  triển các ứng dụng liên quan đến các bit trạng thái này. 

Lệnh ADDWF 

Cú pháp:    ADDWF  thanh_ghi,noi_den   

Tác dụng: Cộng giá trị chứa trong thanh ghi W vào thanh ghi “thanh_ghi”. Kết quả được chứa trong thanh ghi “thanh_ghi” nếu tham số “noi_den” mang giá trị 1 hoặc thanh ghi W nếu tham số “noi_den” mang giá trị 0. 

Lệnh này cũng được hỗ trợ bởi các bit trạng thái C, DC, Z trong thanh ghi STATUS. 

Lệnh BTFSS 

Cú pháp:   

BTFSS   thanh_ghi,bit   (0≤bit≤7) Lệnh 1 Lệnh 2 ……….. 

Tác  dụng:  kiểm  tra  bit  được  chỉ  định  bởi  tham  số  “bit”  trong  thanh  ghi “thanh_ghi”. Nếu bit đó bằng 0, lệnh 1 được thực thi. Nếu bit đó khác 0, lệnh 1 được bỏ qua và thay vào đó là lệnh NOP. 

Lệnh BTFSC 

Cú pháp:     

BTFSC   thanh_ghi,bit   (0≤bit≤7) Lệnh 1 Lệnh 2 ……………. 

Tác  dụng:  kiểm  tra  bit  được  chỉ  định  bởi  tham  số  “bit”  trong  thanh  ghi “thanh_ghi”.  Nếu bit đó bằng 1, lệnh 1 được thực thi. Nếu bit đó bằng 0, Lệnh tiếp theo được bỏ qua và thay vào đó bằng lệnh NOP. 

Leänh INCF Cú pháp:  INCF     thanh_ghi,noi_den     

Tác dụng: tang giá trị thanh ghi “thanh_ghi” len 1 đơn vị. Kết quả được lưu vào thanh ghi W nếu tham số “noi_den” bằng 0 hoặc thanh ghi “thanh_ghi” nếu tham số “noi_den” bằng 1. 

Lệnh này được hỗ trợ bởi bit Z trong thanh ghi STATUS. 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  7/14 

Lệnh DECF 

Cú pháp: DECF   thanh_ghi,noi_den 

Tác dụng: giảm giá trị thanh ghi “thanh_ghi” 1 đơn vị. Kết quả được chứa trong thanh ghi W nếu “noi_den” bằng 0 hoặc thanh ghi “thanh_ghi” nếu “noi_den” bằng 1. 

Lệnh này được hỗ trợ bởi bit Z trong thanh ghi STATUS. Lệnh MOVF 

Cú pháp:  MOVF   thanh_ghi,noi_den 

Tác dụng: đưa giá trị trong thanh ghi “thanh_ghi” vào thanh ghi W nếu tham số “noi_den” bằng 0 hoặc thanh ghi “thanh_ghi” nếu tham số “noi_den” bằng 1.  

Việc đưa giá trị trong thanh ghi “thanh_ghi” vào thanh ghi “thanh_ghi” có vẻ vô nghĩa. Thông thường công việc này dùng để  thiết lập các giá trị cho bit Z (thanh ghi STATUS). Ta sẽ bàn đến kĩ hơn thao tác này khi xây dựng các ứng dụng liên quan đến nó. 

Lệnh XORLW 

Cú pháp:  XORLW    k   (0≤k≤255) 

Tác dụng: thực hiện phép toán XOR giữa giá trị k vả giá trị trong thanh ghi W. Kết quả được chứa trong thanh ghi W. Nếu kết quả phép toán bằng 0, bit Z (thanh ghi STATUS) sẽ mang giá trị 1. Nếu kết quả phép toán khác 0, bit Z sẽ mang giá trị 0. 

Xem bảng sau để biết được kết quả logic khi thực hiện phép toán XOR giữa hai tham số A và B: 

A  B  Kết quả 

0  0  0 

0  1  1 

1  0  1 

1  1  0  

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  8/14 

Lệnh XORWF Cú pháp:   XORWF    thanh_ghi,noi_den 

Tác dụng: thực hiện phép toán XOR giữa các giá trị chứa trong thanh ghi W và thanh ghi  thanh_ghi . Kết quả được chứa trong thanh ghi W nếu tham số « noi_den » mang giá trị 0 hoặc thanh ghi « thanh_ghi » nếu tham số « noi_den » mang giá trị 1.  

Lệnh này cũng được hỗ trợ bởi bit Z trong thanh ghi STATUS. 

   

  Ta có một số nhận xét về phép toán XOR như sau: 

  ‐  Khi hai biến A và B mang giá trị giống nhau, kết quả của phép toán là 0 

  ‐  Khi hai biến A và B mang giá trị khác nhau, kết của quả phép toán bằng 1 

Như vậy, giả sử A và B bằng nhau, thì kết quả phép toán (A XOR B) sẽ bằng 0, khi đó, trong trường hợp cấu tạo phần cứng của vi điều khiển PIC, bit Z sẽ mang mức logic 1. Cần chú ý là tập lệnh PIC không có phép toán so sánh, nên lệnh XORLW và bit Z của thanh ghi STATUS được sử dụng để  xây dựng giải thuật thực hiện việc so sánh giữa hai số. 

 

2.1.2 Thanh ghi PC.  

Ta đã biết không như vi xử lí hay vi họ điều khiển 8051, do PIC được thiết kế theo lối kiến trúc Havard nên bộ nhớ chương trình và bộ nhớ dữ liệu được tách riêng. Và để thao tác được với bộ nhớ chương trình trong vi điều khiển PIC, thanh ghi PC được đưa vào để dùng cho việc truy xuất bộ nhớ chương trình. Đây là thanh ghi chứa địa chỉ các lệnh tiếp theo sẽ được thực thi trong bộ nhơ chương trình.  

Đối với vi điều khiển PIC16F877A, thanh ghi PC là thanh ghi 13 bit, bao gồm hai thanh ghi PCL (chứa 8 bit thấp) và thanh ghi PCH (chứa các bit cao còn lại). Tại sao là 13 bit?? Câu trả lời là phải dùng 13 bit để mã hóa hết được địa chỉ của bộ nhớ chương trình có dung lượng là 8K word của PIC16F877A (8K word = 23x210 word = 213  word). Như vậy mới bảo đảm có thể truy xuất đến bất kì ô nhớ nào trong bộ nhớ chương trình của vi điều khiển.  

Trước mắt ta sẽ không cần quan tâm tới thanh ghi PCH (vì thanh ghi này không nằm trong bộ nhớ dữ liệu) mà chỉ quan tâm tới thanh ghi PCL. Thanh ghi này có trong bộ nhớ dữ liệu, diều đó có nghĩa nó cho phép các thao tác như một thanh ghi thông thường (ghi, xóa, truy xuất, ..). Thay đổi giá trị trong thanh ghi PCL sẽ làm thay đổi quá trình truy xuất bộ nhớ chương trình của vi điều khiển. Việc thao tác trên thanh ghi PCL cần hết sức cẩn trọng vì nó sẽ làm thay đổi quá trình thực thi lệnh và làm ảnh 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  9/14 

hưởng nghiêm trọng đến giải thuật cũng như tác dụng của chương trình. Tuy nhiên nếu kiểm sát được, ta có thể điều khiển một cách linh động dòng chảy của chương trình khi vi điều khiển hoạt động. Cần kiểm soát một cách chặt chẽ việc thao tác trên thanh ghi PCL, đó cũng là yêu cầu quan trọng của kĩ thuật bảng.  

Có thể tìm hiểu thêm thông tin về thanh ghi này trong datasheet của nhà sản xuất. Ta cũng nên tìm hiểu các để biết thêm và để kiếm chứng lại các thông tin trong bai, đồng thời giúp ta có thêm được một số kĩ năng trong việc khai thác thông tin trong  datasheet của một sản phẩm điện tử. Trong bài này ta chỉ cần tìm hiểu các thông tin đủ để phục vụ cho bài. 

 

2.2. Phân tích giải thuật kĩ thuật bảng 

Ta sẽ phân tích kĩ thuật này thông qua một ứng dụng. Yêu cầu của ứng dụng như ứng dụng 1 mà ta đã thực hiện thành công ở phần 1, đó là cho một LED chạy từ trái sang phải, nhưng lần này thay vì sử dụng lệnh RLF hay RRF, ta sẽ sử dụng kĩ thuật bảng. Việc ứng dụng kĩ thuật bảng trong trường hợp này không mang tính chất tối ưu hóa giải thuật, mà chỉ mang tính chất tìm hiểu một kĩ thuật viết chương tình mới. 

Chương trình trong ví dụ 1 được viết lại như sau: 

  Chương trình 3.2: 

;============================================================================= 

; WWW.PICVIETNAM.COM 

; Lap trinh:      NGUYEN TRUNG CHINH 

; Ngay bat dau:              23 thang 01 nam 2006 

; Ngay hoan thanh:            23 thang 01 nam 2006 

; Kiem tra chuong trinh:      [email protected] 

; Ngay kiem tra:     

; Su dung vi dieu khien Microchip:  PIC16F877A 

  title    “chuongtrinh3‐2.asm” 

processor  16f877a       

  include  <p16f877a.inc>   

  __CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & 

 _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF 

; Cap nhat va bo sung: 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  10/14 

; Mo ta chuong trinh:   Chuong trinh dung de dieu khien cac LED gan vao  

;        PORTB lần lượt chạy từ trái sang phải sau moi khoang thoi  

;        gian 100 ms. 

;        Su dung ki thuat bang 

; Mo ta phan cung:    8 LED duoc gan vao PORTB thong qua cac dien tro, cac  

;        thanh phan di kem bao gom thach anh, mach reset va nguon 

;=============================================================================  

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khoi tao cac bien 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

count1  EQU  0x20      ; Dùng cho chương trình delay 

counta  EQU  0x21      ; Dùng cho chương trình delay 

countb  EQU  0x22      ; Dùng cho chương trình delay 

count    EQU  0x23      ;  dùng để tra bảng dữ liệu 

;============================================================================= 

;CHUONG TRINH CHINH 

;============================================================================= 

  ORG    0x000 

  GOTO   start 

start 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Khởi tạo PORT B 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

  BCF      STATUS,RP1 

  BSF    STATUS,RP0   ; chọn BANK1 

  CLRF    TRISB     ; PORTB <‐ outputs 

  BCF    STATUS,RP0   ; chọn BANK0 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Vòng lặp chính của chương trình 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  11/14 

Loop1 

  CLRF    count      ; reset thanh ghi  

Loop2 

  MOVF  count, 0    ; đưa giá trị thanh ghi “count” vào thanh ghi W 

  CALL   table      ; gọi chương trình con “table” 

  MOVWF  PORTB    ; xuất giá trị chứa trong thanh ghi W ra PORTB 

  CALL   delay100ms     

  INCF                 count, 0    ; tăng giá trị thanh ghi “count” 

            ; kết quả chứa trong thanh ghi W 

  XORLW  d’8’      ; thực hiện phép toán XORgiữa thanh ghi W và  

            ; giá trị 8, kết quả chứa trong thanh ghi W 

  BTFSC  STATUS,Z    ; kiểm tra bit Z (Zero) 

  GOTO   Loop1     ; nhảy về label Loop1 nếu Z = 1  

  INCF    count, 1    ; thực thi lệnh này nếu Z = 0 

  GOTO   Loop2 

;=========================================================================== 

; Các chương trình con 

;=========================================================================== 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Chương trình con cho kĩ thuật bảng 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

table             

  ADDWF         PCL,1     ; cộng giá trị trong thanh ghi W vào thanh ghi 

; PCL, kết quả chứa trong thanh ghi PCL 

  RETLW  b’10000000’    ; dữ liệu của bảng  

  RETLW  b’01000000’ 

  RETLW  b’00100000’ 

  RETLW  b’00010000’ 

  RETLW  b’00001000’ 

  RETLW  b’00000100’ 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  12/14 

  RETLW  b’00000010’ 

  RETLW  b’00000001’ 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

; Chương trình con delay 100 ms 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

delay100ms 

  MOVLW  d’100’ 

  MOVWF  count1 

d1   

  MOVLW  0xC7 

  MOVWF  counta 

  MOVLW  0x01 

  MOVWF  countb 

delay_0     

  DECFSZ  counta,1 

  GOTO   $+2 

  DECFSZ  countb,1 

  GOTO   delay_0 

  DECFSZ  count1,1 

  GOTO   d1       

  RETURN         

  END   

;=============================================================================  

Ta cùng phân tích giải thuật của chương trình dựa trên các thông tin đã được cung cấp ở phần 2.1. Sau mỗi lần lệnh “CALL  table” đuợc gọi, lệnh RETLW trong chương trình con “table” sẽ mang giá trị từ bảng dữ liệu chứa trong thanh ghi W trở về chương trình chính. Dữ liệu này được xuất ra thanh ghi PORT B để điều khiển việc bật tắt các LED gắn vào PORT B. Sau mỗi lần như vậy, giá trị trong thanh ghi count sẽ được tăng lên 1 đơn vị thông qua lệnh “INCF  count,1”. Giá trị trong thanh ghi “count” lại được đưa vào thanh ghi W thông qua lệnh “MOVF  count,0” để cộng vào thanh ghi PCL thông qua lệnh “ADDWF  count,1” ở chương trình con “table” để điều khiển chương trình nhảy tới đúng địa chỉ cần lấy dữ liệu trở về chương trình chính thông qua lệnh “RETLW    …”. 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  13/14 

Để đề phòng trường hợp giá trị biến “count” cộng vào thanh ghi PCL vượt quá ví trí 

của bảng dữ liệu (trường hợp này xảy ra khi biến “count” mang giá trị lớn hơn 8, vì bảng dữ liệu chỉ chứa 8 giá trị), khi đó dòng chương trình thực thi sẽ không còn đúng như ta mong muốn, biến count sau mỗi lần tăng sẽ được so sánh với giá trị “8” thông qua lệnh “XORLW” và bit trạng thái Z được kiểm tra thông qua lệnh “BTFSC   STATUS,Z” (giái thuật cho việc so sánh đã được đề cập ở phần trên). Nếu giá trị trong thanh ghi count chưa bằng 8, bit Z chưa mang giá trị 0, lệnh “GOTO  Loop1” được bỏ qua và quá trình lấy dữ liệu lai được tiếp tục thông qua các lệnh tiếp theo sau đó. Nếu thanh ghi count đã bằng 8, bit Z sẽ mang giá trị 1, lệnh “GOTO  Loop1” sẽ được thực thi, biến count khi đó được reset về 0 thông qua lệnh “CLRF   count” và quá trình lấy dữ liệu xuất ra PORT B của vi điều khiển tiếp tục được thực thi. Quá trình này được bảo đảm thực thi không ngừng chừng nào vi điều khiển còn hoạt động. 

Cần chú ý đến việc sắp xếp các giá trị trong bảng dữ liệu một cách phù hợp với mục đích điều khiển. Trong trường hợp này, do ta muốn LED chạy từ trái sang phải nên trong bảng dữ liệu, giá trị 1 cũng được di chuyển dần từ trái sang phải. 

Bây giờ, ta có thể điều khiển LED chạy theo bất cứ yêu cầu nào thông qua một thao tác đơn giản: thay đổi các giá trị trong bảng dữ liệu. Đó cũng là ưu thế rõ rệt của phương pháp bảng dữ liệu so với các lệnh RLF hay RRF. Tuy nhiên, tùy trường hợp cụ thể mà ta có thể chọn được giải pháp hiệu quả nhất tương ứng.  

Đã đến lúc các bạn phát huy tính sáng tạo của mình để điều khiển mấy con LED nhảy múa theo ý muốn. Nhịp điệu để nhảy sẽ được quyết định bởi thời gian delay, và hình tượng của điệu nhảy sẽ được thể hiện thông qua việc chớp tắt mấy con LED. Đây cũng là thao tác nên thực hiện để giúp làm quen với kĩ thuật mới này. 

Như thường lệ, sau đây sẽ là một số kết luận cho bài 3. 

 

3. Kết luận Kĩ thuật bảng được xây dựng dựa trên các thông tin về cấu tạo vi điều khiển PIC, đó là các 

thông tin về thanh ghi PC (Program Counter) và nguyên tăc hoạt động của nó. Bên cạnh đó là thông tin về lệnh RETLW và cách ứng dụng của nó.  

Sử dụng kĩ thuật bảng cho phép điều khiển một cách linh hoạt việc thay đổi dữ liệu trong thanh ghi W và thông qua đó, các dữ liệu sử dụng trong chương trình cũng sẽ được truy xuất một cách linh hoạt hơn.  

Giải thuật kĩ thuật bảng cũng đòi hỏi việc sắp xếp và tổ chức chương trình ở một cấp độ cao hơn, bằng chứng là yêu cầu khắt khe trong việc kiểm soát việc thực thi các lệnh trong chương trình và các lệnh hỗ trợ cho kĩ thuật này cũng khá nhiều. Bên cạnh đó là yêu cầu trong việc tổ chức và sắp xếp một cách hợp lí dữ liệu trong bảng. 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT03.02 

Ngày:  3/6/2006  Trang:  14/14 

Ứng dụng của kĩ thuật bảng trong việc xây dựng các giải thuật cho chương trình viết cho vi 

điều khiển PIC cũng rất đa dạng và thường xuyên. Nhận định này sẽ được thể hiện rõ ràng hơn trong các bài sau. Có thể nói đây là một kĩ thuật quan trọng. 

 Thông qua bài này, ta cũng cảm nhận được một nhược điểm của họ vi điều khiển PIC cũng như các vi điều khiển RISC, đó là các lệnh hỗ trợ cho vi điều khiển không nhiều, dẫn đến số lượng các công cụ hỗ trợ ban đầu cũng không nhiều và gây nhiều khó khăn cho việc lập trình ứng dụng. Tuy nhiên các lệnh của vi điều khiển RISC hoàn toàn có đủ khả năng để  xây dựng các ứng dụng trên vi điều khiển, vấn đề là phải tìm ra các phương pháp giải quyết thích hợp dựa trên các công cụ ban đầu đó.  

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  1/29 

Tutorial 04.02  

Gửi đến:  [email protected]

Nội dung:  BÀI 4: PARALLEL SLAVE PORT (PSP) VÀ ỨNG DỤNG 

  MICROSOFT WORD

 Tóm tắt: Tutorial post lên luồng “PIC16F877A TỪ DỄ TỚI KHÓ” thuộc chuyên mục “CƠ BẢN VỀ VI 

ĐIỀU KHIỂN VÀ PIC”. Bài 4 bao gồm nội dung sau: ‐ Cấu tạo phần cứng PSP và các thanh ghi điều khiển. ‐ Xây dựng module điều khiển PSP ( phần cứng và  phần mềm).   

1. Sơ lược về cấu tạo và chức năng của PSP  Parallel slave Port (PSP) là một khối chức năng on‐chip được tích hợp trong phần cứng 

của một số vi điều khiển PIC. Bên cạnh các khối chức năng rất đa dạng dùng cho giao tiếp nối tiếp (I2C, SPI, CAN, USB, …), PSP là khối chức năng duy nhất trong vi điều khiển PIC dùng cho giao tiếp song song 8 bit. 

Với sự  tham gia của khối PSP, các chức năng giao  tiếp của vi điều khiển PIC  trở nên hoàn thiện hơn, giống như một máy tính, với các cổng nối tiếp và một cổng song song. Ta có thể tạm so sánh các khối giao tiếp nối tiếp của PIC như cổng COM hoặc cổng USB của máy tính, còn khối giao tiếp song song 8 bit PSP có thể so sánh như cổng LPT (cổng song song) của máy tính. 

Một điểm tương đồng giữa PSP và các giao tiếp nối tiếp khác trong vi điều khiển PIC, đó là PSP cũng là giao tiếp theo mô hình master‐ slave. Một master được phép điều khiển các giao tiếp với một hoặc nhiều slave. master có nhiệm vụ đưa ra các yêu cầu giao tiếp, ví dụ như giao tiếp với slave nào, nội dung giao tiếp, định hướng chiều dữ liệu (đọc hay ghi dữ  liệu), … và slave có nhiệm vụ đáp ứng các yêu cầu đó của master. Tùy  theo phương thức giao  tiếp,  ta  có  các qui  định  riêng về  cách “ra  lệnh”  của master,  cách  đáp  ứng  của slave cũng như cách truyền nhận dữ liệu. 

PSP cũng có các yêu cầu riêng về hoạt động giao tiếp. Tuy nhiên, có một điểm cần lưu ý ở đây, là PSP của vi điều khiển PIC chỉ hoạt động được ở vai trò của một slave. Các hoạt động giao tiếp trên PSP sẽ được điều khiển hoàn toàn bởi một master. 

Trên đây là một vài điểm sơ lược về PSP. Trong phần tiếp theo ta sẽ đi sâu vào cấu tạo và hoạt động của PSP, cũng như xây dựng một số ứng dụng cơ bản cho PSP. 

 

Administrator
Highlight
Administrator
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  2/29 

1.1 Cấu tạo phần cứng của PSP trong vi điều khiển PIC16F877A 

PSP được tích hợp trong khá nhiều vi điều khiển PIC. Tùy theo vi điều khiển mà cách bố trí các chân chức năng và các thanh ghi điều khiển cho PSP trong một vi điều khiển có thể khác nhau, tuy nhiên về bản chất, cấu tạo của PSP là không đổi. Trong bài này, vi điều khiển PIC16F877A được lựa chọn để tìm hiểu và xây dựng các ứng dụng cho PSP. 

PSP có các chân chức năng được tích hợp trong port D và port E của PIC16F877A. port D là các chân dữ liệu dùng để truyền nhận dữ liệu song song 8 bit. port E là các chân điều khiển quá trình truyền nhận, bao gồm các chân RD* (ReaD, chân RE0), WR* (WRite, chân RE1) và CS* (Chip Select, chân RE2). Lưu ý là các chân này tích cực ở trạng thái logic 0. Có nghĩa là, ở trạng thái không tác động, các chân này phải được đưa lên mức logic 1, nếu muốn một chân nào đó tác động lên khối PSP, ta điều khiển chân đó trở về trạng thái logic 0.  

Khi được cho phép hoạt động ở chế độ PSP, các chân điều khiển nêu trên sẽ không còn được cho phép hoạt động ở chế độ I/O (port D) hoặc chế độ I/O Analog (port E) nữa. Lúc này, port D và port E sẽ được điều khiển bởi các thiết bị ngoại vi khác (một vi điều khiển khác đóng vai trò là một master chẳng hạn) để truyền nhận dữ liệu song song 8 bit. Vai trò cụ thể của chúng như sau: 

‐ Port D là ngõ xuất nhập dữ liệu và hoàn toàn được điều khiển bởi khối PSP. Vai trò của thanh ghi TRISD trong trường hợp này sẽ được bỏ qua.  

‐ Port E là các chân điều khiển và phải được thiết lập các chế  độ hoạt động thích hợp, đó là chế độ ngõ vào Digital. Chế độ này được điều khiển bởi thanh ghi TRISE và thanh ghi ADCON1. 

Bên cạnh các chân điều khiển, PSP còn được hỗ trợ ngắt PSP và các bit trạng thái dùng để điều khiển hoạt động của khối. Các thành phần hỗ trợ này được trình bày trong bảng sau(ta chỉ quan tâm tới các bit điều khiển liên quan tới PSP): 

Thanh ghi 

Địa chỉ 

Chức năng 

PORTD  08h  Chứa dữ liệu truy xuất lên PSP PORTE  09h  Điều khiển các chân RE2:RE0 của PORT E 

Chứa các bit điều khiển PSP và các bit điều khiển hướng truy xuất các chân của PORT E. Bit  Chức năng 7  IBF: Input Buffer Full status bit 

IBF = 1 : có dữ liệu ở bộ đệm ngõ vào IBF = 0 : không có dữ liệu ở bộ đệm ngõ vào 

TRISE  

89h 

6  OBF: Output Buffer Full status bit OBF = 1 : Có dữ liệu ở bộ đệm ngõ ra 

Administrator
Highlight
Administrator
Highlight
Administrator
Highlight
Administrator
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  3/29 

OBF = 0 : Không có dữ liệu ở bộ đệm ngõ ra 

5  IBOV : Input Buffer OVerflow detect bit IBOV = 1 : bộ đệm dữ liệu ngõ vào bị tràn IBOV = 0 : bộ đệm dữ liệu ngõ vào không bị tràn 

4  PSPMODE : Parallel slave Port MODE PSPMODE = 1 : cho phép khối PSP hoạt động PSPMODE = 0 : không cho phép khối PSP hoạt động  

3  Không quan tâm 

2 :0  Các bit điều khiển hướng truy xuất của PORT E. TRISE<2 :0> = 1 : Input TRISE<2 :0> = 0 : Output 

Chứa cờ ngắt các ngắt ngoại vi 

Bit  Chức năng 

PIR1  0Ch 

7  

PSPIF : Parallel Slave Port Interrupt Flag bit PSPIF = 1 : Xảy ra ngắt PSP PSPIF = 0 : chưa xảy ra ngắt PSP

Chứa các bit cho phép các ngăt ngoại vi Bit  Chức năng  

PIE1 

 

 

 

8Ch 

7  

PSPIE : Parallel Slave Port Interrupt Enable bit PSPIE = 1 : Cho phép ngắt ngoại vi PSP PSPIE = 0 : Không cho phép ngắt ngoại vi PSP 

ADCON1  9Fh  Chứa các bit điều khiển ADC. Cần thiết lập các giá trị thích hợp cho thanh ghi này để các cổng I/O của port E là Digital input 

 

1.2 Quá trình truyền nhận dữ liệu qua PSP 

Ở chế độ PSP, port D đóng vai trò là nơi đọc và ghi dữ liệu được điều khiển bởi một master. Để thực hiện được cả hai vai trò là truyền và nhận dữ liệu, port D được bố trí hai bộ chốt dữ liệu nhập và xuất tách biệt với nhau. Các chốt dữ liệu này được điều khỉển trực tiếp bởi  các  chân  điều khiển RD*, WR* và CS*. Tùy  theo mức  logic  trên  các  chân  điều khiển này mà quá trình đọc hay ghi dữ liệu được tiến hành.  

Ta sẽ dựa vào giản đồ xung để tìm hiểu quá trình truyền nhận dữ liệu của PSP. Trước tiên là quá trình ghi dữ liệu lên PSP: 

Administrator
Highlight

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  4/29 

 Hình 1: Giản đồ xung thể hiện quá trình ghi dữ liệu lên PSP. 

 

Trước tiên dữ liệu cần ghi sẽ được đưa vào cổng dữ liệu của PSP (port D). Quá trình ghi dữ  liệu chỉ bắt đầu diễn ra khi cả hai chân WR* và CS* cùng ở mức  logic  thấp. Khi một trong hai  chân WR* hoặc CS*  trở về mức  logic  cao,  các bit  IBF và PSPIF  sẽ  đồng  thời chuyển  trạng  thái  từ mức  logic 0  lên mức  logic 1 và ngắt ngoại vi PSP  (nếu đã được cho phép  trước đó bằng cách set bit PSPIE  trong  thanh ghi PIE1) sẽ được kích hoạt. Bit  IBF  (thanh ghi TRISE) chuyển lên mức logic 1 dùng để  báo hiệu rằng dữ liệu bộ đệm ngõ vào đã đầy. Bit PSPIF chuyển  lên mức  logic 1 dùng để chỉ  thị  trạng  thái ngắt PSP. Bit PSPIF phải  được  xóa bằng  chương  trình  để nhận biết  được  trường hợp  xảy  ra ngắt  tiếp  theo. Trong khi bit IBF chỉ được xóa khi dữ liệu từ buffer đệm được đọc vào. Trong trường hợp dữ liệu cũ chưa được đọc vào mà dữ liệu mới đã muốn ghi lên PSP, bit IBOV sẽ chuyển lên mức logic 1. 

Xét quá trình đọc dữ liệu từ PSP: 

 Hình 2: Giản đồ xung quá trình đọc dữ liệu từ PSP. 

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  5/29 

Giản đồ xung quá trình đọc dữ liệu từ PSP có thể phức tạp hơn so với giản đồ xung của 

quá  trình  ghi dữ  liệu. Tuy nhiên nếu  để ý phân  tích  kĩ,  ta  vẫn  có  thể hình dung  được phương thức hoạt động của quá trình này. Đầu tiên, ta thấy chỉ khi nào hai chân CS* và RD* cùng ở mức logic thấp thì quá trình đọc dữ liệu mới bắt đầu. Khi đó, bit OBF từ mức logic 1 sẽ chuyển về mức logic 0, và dữ liệu cần đọc sẽ xuất hiện tại ngõ ra của port D. Bit OBF  trở về mức  logic 0  để chỉ  thị  trạng  thái bộ đệm dữ  liệu  ra  đã  rỗng,  điều  đó có 2 ý nghĩa: 

‐ Thứ nhất, dữ liệu cần đọc phải được đưa vào PSP trước đó. Khi có sự tác động của hai chân CS* và RD*, PSP chỉ làm một công việc đơn giản, đó là mở buffer đệm để cho phép dữ liệu dã được ghi vào trước đó xuất hiện ở ngõ ra port D. 

‐ Thứ hai, khi dữ liệu chưa được đọc, buffer đệm đã có sẵn dữ liệu nên bit OBF sẽ ở mức logic 1. Khi dữ liệu được đọc, buffer đệm rỗng nên mức logic của OBF sẽ bằng 0. Muốn bit OBF trở về mức logic 1, ta thực hiện thao tác ghi dữ liệu mới cần đọc lên buffer đệm. 

 Đến giai đoạn này, việc đọc dữ liệu đã hoàn tất, công việc còn lại là đánh dấu kết thúc quá  trình đọc dữ  liệu bằng cách đưa một  trong hai chân RD* hoặc CS*  trở về mức  logic cao, khi đó cờ ngắt PSPIF được set và ngắt ngoại vi PSP (nếu đã được cho phép trước đó) xảy ra. Bit PSPIF phải được xóa bằng chương trình để nhận biết được trường hợp xảy ra ngắt tiếp theo. 

 

2. Xây dựng các module ứng dụng cho PSP. Mục đích của công việc này  là ứng dụng PSP  trong giao  tiếp dữ  liệu với các  thiết bị 

ngoại vi. Trong các ứng dụng này, để đơn giản, ta sẽ sử dụng 2 vi điều khiển PIC16F877A, một vi điều khiển được cho phép hoạt động ở chế độ PSP, vi điều khiển còn lại đóng vai trò là một master để điều khiển PSP (chú ý là PSP chỉ hoạt động với vai trò là một slave). 

Ta  sẽ  xây  dựng  các  ứng  dụng  này  theo  từng  bước,  từ  thiết  kế  phần  cứng  đến  viết chương trình. 

 

2.1 Ứng dụng đọc dữ liệu từ PSP 

Trong ứng dụng này, ta sẽ thực hiện công việc đọc dữ liệu từ PSP dựa trên các thông tin đã được chuẩn bị trong phần trước. 

 

2.1.1 Xây dựng phần cứng cho ứng dụng 

Trước tiên là việc thiết kế phần cứng cho ứng dụng. Rõ ràng ta cần nối tất cả các chân liên quan đến PSP đến vi điều khiển master, để bảo đảm việc có thể  kiểm soát hoàn toàn hoạt động của PSP.  

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  6/29 

 

 

 

 

 

 

Để kiểm tra dữ liệu đọc vào có đúng hay không, ta sử dụng một công cụ thông dụng là các LED được nối vào một port khác của master.  

Bên cạnh đó ta cũng cần đến các thành phần hỗ trợ cho các vi điều khiển (mạch reset, mạch ổn định xung dao động cho thạch anh,…) 

Dựa trên một vài ý tưởng như vậy, ta có thể xây dựng mạch nguyên lí cho ứng dụng như hình 3. 

Trong mạch ứng dụng ở hình 3, PSP của PIC16F877A slave sẽ được điều khiển bởi các chân RE2 :RE0 của PIC16F877A master. Các chân dữ liệu của slave được nối với các chân của port D. Như vậy khối PSP của cả 2 PIC master và slave đều được nối chung với nhau (xét trên quan điểm cấu tạo phần cứng của PIC16F877A). Tuy nhiên vai trò của mỗi PIC là hoàn toàn khác biệt, đó là PIC master sẽ điều khiển PSP của PIC slave. Như vậy PIC slave được cho phép hoạt động ở chế độ PSP, còn PIC master sẽ không được cho phép hoạt động ở chế  độ PSP. 

Thực  chất  ta hoàn  toàn  có  thể  sử dụng  các port  điều khiển khác  của master  để điều khiển PSP slave (dùng Port B và Port A chẳng hạn) mà không nhất thiết phải là port D và port E. 

Ngoài ra, ta chỉ sử dụng một PSP slave nên việc điều khiển chân CS* (Chip Select)  là không cần thiết và có thể nối trực tiếp chân CS* của PSP slave xuống mass. Trong trường hợp nhiều PSP slave được sử dụng, chân CS* được dùng để lựa chọn việc PSP slave nào được cho phép truy xuất. Ở đây, do yêu cầu của ứng dụng là chỉ mang tính chất tìm hiểu phương thức hoạt động của PSP, và để ứng dụng mang tính tổng quát cao hơn, chân CS* vẫn sẽ được điều khiển.   

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  7/29 

RD4

RD1

RD6

30 pF

R3

RD3

R7

D2

RD0

RE2

R10

330 X 8

0

RD7

RD1

30 pF

R5

R9

RE0RE0

D8

0

RD3

RD2

SW2

HI

D6

RD5

R110 K

4 MHz

PIC16F877A MASTER

1

234567

89

10

11

1213

14

15161718

19202122

23242526

27282930

31

32

3334353637383940MCLR/VPP

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT

RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

VDD

VSSOSC1/CLKI

OSC2/CLKO

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SD0RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

VSS

VDD

RB0/INTRB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

30 pF

D1

SW1

RD5

R4

RD0

PIC16F877A PSP SLAVE

1

234567

89

10

11

1213

14

15161718

19202122

23242526

27282930

31

32

3334353637383940MCLR/VPP

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT

RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

VDD

VSSOSC1/CLKI

OSC2/CLKO

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SD0RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

VSS

VDD

RB0/INTRB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

D3

4 MHz

RD4

0

D7

0

D4

R8

RD6

D5

030 pF

RD7

HI

0

R6

HI

0

RE1RE2

R210 K

HI

RD2

RE1

 Hình 3 : Sơ đồ nguyên lí ứng dụng đọc dữ liệu từ PSP sử dụng PIC16F877A. 

 

2.2 Viết chương trình điều khiển cho ứng dụng 

Ở đây ta cần viết chương trình cho cả hai vi điều khiển master và slave. Có thể nói đây là một công việc không  đơn giản. Ta  sẽ giải quyết vấn  đề bằng cách  lần  lượt  đứng  trên phương thức điều khiển của master và cách « phục vụ » của slave để hình dung ra các hoạt động cho cả hai vi điều khiển.  

Trước hết là trong vai trò của một master. Master sẽ yêu cầu PSP slave xuẩt ra dữ liệu cần đọc cho master bằng cách điều khiển các chân CS* và RD* của PSP slave xuống mức logic thấp. Sau đó đọc dữ liệu vào và kết thúc truy xuất dữ liệu bằng cách đưa một trong hai chân RD* hoặc CS* trở về mức logic cao. Như vậy hoạt động của master khá đơn giản. 

Bây giờ ta sẽ đóng vai trò là một PSP slave để hình dung được những thao tác cần làm của PSP slave. Khi hai chân CS* và RD* cùng ở mức logic thấp, phần cứng của PSP sẽ lập tức đưa dữ  liệu  từ buffer đệm ra  thẳng ngõ ra của PSP  (ngõ ra  là port D) và bit OBF sẽ được set. Khi một trong hai chân CS* hoặc RD* trở về mức logic cao thì ngắt ngoại vi PSP sẽ được kích hoạt, đồng thời quá trình đọc dữ  liệu của master cũng đã kết thúc. Đây chỉ mới là những phản ứng của phần cứng PSP khi được điều khiển bởi master, mà chưa có sự can thiệp của chương trình. Như vậy, một khi PSP slave đã có sẵn dữ liệu ở buffer đệm thì mọi  thao  tác  truy  xuất  sẽ hoàn  toàn  được  điều khiển bởi master, và  công việc  của PSP slave, đơn giản chỉ là chuẩn bị dữ liệu tiếp theo cho master, và công việc này tốt nhất nên 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  8/29 

được  tiến hành  trong  chương  trình ngắt  (ngay  sau khi master kết  thúc  thao  tác  đọc dữ liệu). Công việc chuẩn bị dữ liệu mới này sẽ được hỗ trợ bởi các bit OBF và PSPIF. 

Dựa trên những phân tích như trên, ta dã có thể viết được chương trình cho cả hai vi điều khiển. Các chương trình cụ thể như sau : 

Chương trình 1M : dùng cho master. 

processor   16f877a include  <p16f877a.inc> __CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON &_XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o pha^`n cu*’ng ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

#define  READ   PORTE,0 #define  WRITE  PORTE,1 #define  CS    PORTE,2  

#define  LEDPORT  PORTB ;==========================================================================;       CHUONG TRINH CHINH 

ORG    0x000 CLRF              STATUS MOVLW  0x00 MOVWF  PCLATH GOTO             start 

;==========================================================================start ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c PORT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

BCF    STATUS,RP1            ; BANK 1     BSF    STATUS,RP0  CLRF              TRISB                         ; kho*?i ta.o ca’c ngo~ ra CLRF              TRISE MOVLW  0x06                            ; kho*?i ta.o ca’c ngo~ I/O la` digital I/O MOVWF  ADCON1  

BCF    STATUS,RP0  

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  9/29 

CLRF              PORTB BSF    READ                         ; ‐du*a ta^’t ca ? ca’c cha^n –die^`u  BSF    WRITE                       ; khie^ ?n le^n mu*’c logic cao BSF    CS 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Vo`ng la*.p chi’nh cu?a chuo*ng tri`nh ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ loop 

CALL             readPSP                     MOVWF  LEDPORT                  ; xua^’t du*~ lie^.u –do.c –duo*.c ra LED GOTO             loop 

;==========================================================================;  CHUONG TRINH CON ;==========================================================================;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Chuo*ng tri`nh con ʺreadPSPʺ ; Du`ng –de^ ? –do.c du*~ lie^.u tu*` slave ve^` master ; Ke^’t qua? –do.c ve^` chu*’a trong thanh ghi W ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ readPSP 

BCF    CS                             ; ba*’t –da^`u –do.c du*~ lie^.u BCF    READ                        BSF    STATUS,RP0  MOVLW  0xFF                          ; kho*?i ta.o PORT D la` ca’c ngo~ va`o MOVWF  TRISD                         BCF    STATUS,RP0  MOVF  PORTD,0                 ; ‐do.c du*~ lie^.u va`o  BSF    CS                            ; ke^’t thu’c –do.c du*~ lie^.u BSF    READ  RETURN  END 

;========================================================================= 

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  10/29 

Chương trình 1S : dùng cho slave 

  processor   16f877a   include  <p16f877a.inc> 

__CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF  ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

DATAOUT    EQU    0x20          ; chu*’a du*~ lie^.u ca^`n –do.c                                                                                ; bo* ?i master W_save    EQU    0x21          ; ca’c thanh ghi du`ng –de^?  PCLATH_save  EQU    0x22          ; thao ta’c khi va`o nga*’t va`        STATUS_save  EQU    0x23          ; ; thoa’t nga*’t FSR_save    EQU    0x24 

;======================================================================== ; CHUONG TRINH NGAT 

ORG    0x004 GOTO   ISR 

;======================================================================== ISR 

MOVWF  W_save                          ; ‐doa.n chuo*ng tri`nh ba*’t –da^`u SWAPF  STATUS,W                   ; va`o nga*’t CLRF              STATUS MOVWF  STATUS_save MOVF  PCLATH,W MOVWF  PCLATH_save CLRF              PCLATH MOVF  FSR,W MOVWF  FSR_save  BTFSS             PIR1,PSPIF          ; kie^?m tra co*` nga*’t PSPIF GOTO             exit_int                 ; ne^’u nga*’t ngoa.i vi PSP kho^ng xa?y ra                                                       ;  ‐> thoat BCF    PIR1,PSPIF          ; ne^’u co’, xo’a co*` nga*’t va` ba*’t ‐da^`u                                                        ; xu* ? li’ nga*’t BSF                 STATUS,RP0       ; thanh ghi TRISE na*`m o*? BANK 1  BTFSC  TRISE,OBF           ; du*~ lie^.u trong buffer –da~                                                        ; ‐duo*.c –do.c chu*a ?? 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  11/29 

GOTO              exit_int                 ; ne^’u chu*a –duo*.c –do.c, thoa’t nga*t  BCF                 STATUS,RP0     MOVF  DATAOUT,0         ; ne^’u –da~ ‐duo*.c –do.c roi,                                                          ; ‐du*a du*~ lie^.u mo*’i va`o MOVWF  PORTD          ; buffer –de^.m 

exit_int             BCF                 STATUS,RP0 

MOVF  FSR_save,W MOVWF  FSR MOVF  PCLATH_save,W MOVWF  PCLATH SWAPF  STATUS_save,W MOVWF  STATUS SWAPF  W_save,1 SWAPF  W_save,0 RETFIE 

;======================================================================== ;         CHUONG TRINH CHINH 

ORG    0x000 CLRF              STATUS MOVLW  0x00 MOVWF  PCLATH GOTO              start 

;======================================================================== ORG    0x050 

start ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c PORT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

BCF    STATUS,RP1 BCF    STATUS,RP0  CLRF              PORTD  BSF    STATUS,RP0  MOVLW  bʹ00010111ʹ                 ; RE2:RE0 la` ca’c ngo~ va`o MOVWF  TRISE                          ; cho phe’p PSPMODE MOVLW  0x06                             ; Ta^’t ca ? ca’c cha^n la` Digital I/O 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  12/29 

MOVWF  ADCON1 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho*?i ta.o nga*’t PSP ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

BSF    PIE1,PSPIE  BCF    STATUS,RP0  BSF    INTCON,GIE BSF    INTCON,PEIE 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

MOVLW  bʹ01010101ʹ MOVWF  DATAOUT MOVWF  PORTD 

;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Vo`ng la*.p chi’nh cu?a chuo*ng tri`nh ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

GOTO              $  END 

;======================================================================== 

 

Do dữ  liệu  ta khởi  tạo  trong  thanh ghi “DATAOUT” ban đầu  là b’01010101’ nên khi chương trình được thực thi đúng, các LED ở port B của master sẽ sáng tắt tương ứng với dữ liệu đọc vào. 

 

2.2 Ứng dụng ghi dữ liệu lên PSP 

Ta sẽ tiếp tục tìm hiểu các thao tác ghi dữ liệu lên PSP. Các bước tiến hành cũng tương tự như ứng dụng đọc dữ liệu từ PSP. 

 

2.2.1 Xây dựng phần cứng cho ứng dụng 

Việc  thực hiện  thành công ứng dụng  trước  tạo được nhiều  thuận  lợi hơn cho  ta  thực hiện ứng dụng  tiếp  theo này. Rõ ràng độ  tin cậy của mạch điều khiển PSP đã được đảm bảo, ta chỉ việc chuyển các LED từ port B của master sang slave để kiểm tra xem dữ liệu 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  13/29 

ghi lên PSP slave có đúng hay không. Sơ đồ nguyên lí cụ thể của ứng dụng này như trong hình 4.  

RD7

HI

PIC16F877A MASTER

1

234567

89

10

11

1213

14

15161718

19202122

23242526

27282930

31

32

3334353637383940MCLR/VPP

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT

RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

VDD

VSSOSC1/CLKI

OSC2/CLKO

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SD0RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

VSS

VDD

RB0/INTRB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

SW1

0

R10

330 X 8

D6

R5

0

R9

0

RD3

RE1RD6

R7

RE1 RD7

4 MHz

RD3

RE2RD5

R3

0

RD4

R110 K

30 pF

RE2

HI

R6

HI

RD4

RE0

D5

RD2

RD5

D7

RD1

RE0

PIC16F877A PSP SLAVE

1

234567

89

10

11

1213

14

15161718

19202122

23242526

27282930

31

32

3334353637383940MCLR/VPP

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT

RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

VDD

VSSOSC1/CLKI

OSC2/CLKO

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SD0RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

VSS

VDD

RB0/INTRB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

RD6

D2D3

RD1

D1SW2

D8

RD0

RD2

30 pF

R4

R80

RD0

R210 K

0

4 MHz

30 pF

D4

30 pF

HI

0

 Hình 4 : Sơ đồ nguyên lí ứng dụng ghi dữ liệu lên PSP sử dụng PIC16F877A. 

 

2.2.2 Viết chương trình điều khiển cho ứng dụng 

Ta tiến hành các bước phân tích như trong ứng dụng trước.  

Đầu  tiên  là xem xét các hoạt động của master. Thao  tác ghi dữ  liệu  lên PSP được bắt đầu bằng việc master đưa dữ liệu cần ghi ra port D, sau đó điều khiển cho cả hai chân WR* và SC* xuống mức logic thấp. Công việc còn lại là kết thúc thao tác ghi dữ liệu bằng cách đưa một trong hai chân WR* hoặc CS* trở về mức logic 1.  

Hoạt động của PSP slave lần lượt được tiến hành như sau. Khi nhận được tín hiệu yêu cầu ghi dữ  liệu  của master  (WR* và CS*  ở mức  logic 0), dữ  liệu  lập  tức  được  đưa vào buffer đệm. Khi nhận được tín hiêu kết thúc thao tác ghi dữ liệu (một trong hai chân WR* hoặc CS*  trở về mức  logic cao), bit  IBF và PSPIF được set, ngắt ngoại vi  (nếu được cho phép trước đó) sẽ được kích hoạt. Nhiệm vụ của phần cứng PSP đến đây là kết thúc. Công việc còn lại của chương trình điều khiển là đọc dữ liệu từ bộ đệm vào và xử lí dữ liệu đó. Trong trường hợp của ứng dụng này, ta sẽ đọc dữ liệu đó vào và xuất ra port B của slave để kiểm tra xem dữ liệu nhận được hay chưa, và nhận được đúng hay sai. Thao tác này tốt nhất cũng nên được tiến hành trong chương trình ngắt (ngay khi dữ  liệu vừa được nhận vào), như vậy sẽ tránh được trường hợp tràn bộ đệm. Hiện tượng này xảy ra khi dữ liệu cũ 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  14/29 

chưa được đọc vào mà dữ liệu mới đã chuẩn bị ghi lên. Bit chỉ thị trạng thái này là IBOV (thanh ghi TRISE).  

Các chương trình ứng dụng như vậy có thể được viết như sau: 

Chương trình 2M : dùng cho master 

  processor   16f877a   include  <p16f877a.inc>   __CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF  ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o pha^`n cu*’ng ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   #define  READ   PORTE,0   #define  WRITE  PORTE,1   #define  CS    PORTE,2 ;======================================================================== ;         CHUONG TRINH CHINH   ORG    0x000   CLRF              STATUS   MOVLW  0x00   MOVWF  PCLATH   GOTO             start ;======================================================================== start ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c PORT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   BCF    STATUS,RP1   BSF    STATUS,RP0    CLRF              TRISE    MOVLW  0x06   MOVWF  ADCON1    BCF    STATUS,RP0    BSF    READ   BSF    WRITE 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  15/29 

  BSF    CS ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Vo`ng la*.p chi’nh cu?a chuo*ng tri`nh ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ loop   CALL             writePSP   MOVWF  PORTB   GOTO             loop ;======================================================================== ;  CHUONG TRINH CON ;======================================================================== ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Chuo*ng tri`nh con ʺwritePSPʺ ; Du`ng –de^ ? ghi du*~ lie^.u le^n PSP slave  ; Ke^’t qua? –do.c ve^` chu*’a trong thanh ghi W ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ writePSP   BCF    CS   BCF    WRITE    BSF    STATUS,RP0    CLRF              TRISD    BCF    STATUS,RP0    MOVLW  bʹ01010101ʹ               ; du*~ lie^.u ca^`n ghi le^n PSP slave   MOVWF  PORTD    BSF    CS   BSF    WRITE   RETURN    END ;======================================================================== 

 

Chương trình 2S : dùng cho slave 

   processor   16f877a 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  16/29 

  include  <p16f877a.inc>   __CONFIG  _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF  ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ W_save    EQU    0x20 PCLATH_save  EQU    0x21 STATUS_save  EQU    0x22 FSR_save    EQU    0x23 ;======================================================================== ;                                                   CHUONG TRINH NGAT   ORG    0x004   GOTO             ISR ;======================================================================== ISR   MOVWF  W_save                     ; ‐doa.n chuo*ng tri`nh va`o nga*’t   SWAPF  STATUS,W   CLRF              STATUS   MOVWF  STATUS_save   MOVF  PCLATH,W   MOVWF  PCLATH_save   CLRF              PCLATH   MOVF  FSR,W   MOVWF  FSR_save    BTFSS             PIR1,PSPIF              ; kie^?m tra co*` nga*’t PSP   GOTO             exit_int   BCF    PIR1,PSPIF              BSF                 STATUS,RP0    BTFSS   TRISE,IBF                ; du*~ lie^.u –da~ co’ o*? trong buffer                                                                        ; _de^.m chu*a ??   GOTO   exit_int                     ; ne^’u chu*a, thoa’t nga*’t                BCF                 STATUS,RP0          ; ne^’u ro^`i, ‐du*a du*~ lie^.u nha^.n                                                                        ; –duo*.c ra PORT B   MOVF  PORTD,0   MOVWF  PORTB     

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  17/29 

exit_int                                                          ; thoa’t kho?i nga*’t             BCF                 STATUS,RP0   MOVF  FSR_save,W   MOVWF  FSR   MOVF  PCLATH_save,W   MOVWF  PCLATH   SWAPF  STATUS_save,W   MOVWF  STATUS   SWAPF  W_save,1   SWAPF  W_save,0   RETFIE ;======================================================================== ;         CHUONG TRINH CHINH   ORG    0x000   CLRF              STATUS   MOVLW  0x00   MOVWF  PCLATH   GOTO             start ;========================================================================   ORG    0x050 start ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c PORT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   BCF    STATUS,RP1   BCF    STATUS,RP0    CLRF              PORTD   CLRF              PORTB    BSF    STATUS,RP0    MOVLW  bʹ00010111ʹ                ; RE2 :RE0 la` ca’c ngo~ va`o   MOVWF  TRISE                         ; cho phe’p PSPMODE   MOVLW  0x06   MOVWF  ADCON1   CLRF              TRISB ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho*?i ta.o nga*’t PSP ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   BSF    PIE1,PSPIE 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  18/29 

  BCF    STATUS,RP0   BSF    INTCON,GIE   BSF    INTCON,PEIE ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Vo`ng la*.p chi’nh cu?a chuo*ng tri`nh ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   GOTO              $              END ;======================================================================== 

 

2.3 Ứng dụng tổng hợp 

  Trong các phần trước, ta đã xây dựng được các chương trình phục vụ cho các thao tác cơ bản trên PSP, bao gồm đọc và ghi dữ liệu. Để củng cố lại và nắm vững các thao tác trên, ta sẽ thực hiện một ứng dụng tổng hợp. Yêu cầu của ứng dụng như sau: 

‐ Master sẽ được bổ sung thêm hai công tăc ấn “WRITEBUT” và “READBUT”.     

‐ Nếu nút “WRITEBUT” được  ấn, master sẽ ghi dữ  liệu  lên PSP slave, sau đó slave xuất dữ  liệu nhận được ra các LED ở PORT B. Sau mỗi  lần ghi, giá trị ghi  lên PSP slave sẽ tăng lên một đơn vị. 

‐ Nếu nút “READBUT” được ấn, master sẽ đọc dữ liệu từ PSP slave, sau đó xuất dữ liệu nhận được ra các LED ở PORT B. Sau mỗi  lần đọc, giá trị đọc từ PSP slave sẽ tăng lên một đơn vị. 

 

2.3.1 Xây dựng phần cứng cho ứng dụng 

Phần cứng sẽ có một số thay đổi nhỏ như sau: 

‐ PORT B của mỗi PIC đều phải được nối với các LED để kiểm tra dữ liệu nhận được. 

‐ master được bổ sung thêm hai nút nhấn. Ta chọn các chân RA0 và RA1 để đọc trạng thái của các nút ấn này. 

Sau đây là sơ đồ nguyên lí cụ thể của ứng dụng: 

 

 

 

 

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  19/29 

 

 

 

 

 

 

 

RD3

RD0

RE2

0

30 pF

0

RD2

RD6

R110 K

RD7

10 K

RE1

RD0

RD4

30 pF

10 K

READBUT

0

RD1

RD5

0

330 X 8

30 pF

HI

SW2

0

0

HIRD7

HIH

I

4 MHz

SW1

HI

0

RD5

SW4

0

0

RE2

RE0

PIC16F877A PSP SLAVE

1

234567

89

10

11

1213

14

15161718

19202122

23242526

27282930

31

32

3334353637383940MCLR/VPP

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT

RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

VDD

VSSOSC1/CLKI

OSC2/CLKO

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SD0RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

VSS

VDD

RB0/INTRB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

RD1

WRITEBUT

30 pF

SW3

R210 K

HI

330 X 8

RE1

0

RD6

RD3

PIC16F877A MASTER

1

234567

89

10

11

1213

14

15161718

19202122

23242526

27282930

31

32

3334353637383940MCLR/VPP

RA0/AN0RA1/AN1RA2/AN2/VREF-/CVREFRA3/AN3/VREF+RA4/TOCKI/C1OUTRA5/AN4/SS/C2OUT

RE0/RD/AN5RE1/WR/AN6RE2/CS/AN7

VDD

VSSOSC1/CLKI

OSC2/CLKO

RC0/T1OSO/T1CKIRC1/T1OSI/CCP2RC2/CCP1RC3/SCK/SCL

RD0/PSP0RD1/PSP1RD2/PSP2RD3/PSP3

RC4/SDI/SDARC5/SD0RC6/TX/CKRC7/RX/DT

RD4/PSP4RD5/PSP5RD6/PSP6RD7/PSP7

VSS

VDD

RB0/INTRB1RB2

RB3/PGMRB4RB5

RB6/PGCRB7/PGD

WRITEBUT

4 MHz

RD4

READBUT

RE0

RD2

 Hình 5 : Sơ đồ nguyên lí  ứng dụng tổnh hợp. 

 

2.3.2 Viết chương trình cho ứng dụng 

Chương trình cho ứng dụng này, về cơ bản, là dựa trên các chương trình trước, ta chit thêm vào các thao tác tương ứng đối với các phím nhấn (chương trình của master) hoặc kết hợp các bước thao tác đọc và ghi dữ liệu trong chương trình ngắt (đối với chương trình của slave). 

Giải thuật của các chương trình trong ứng dụng này sẽ được trình bày dưới các luu đồ sau: 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  20/29 

 

 

 

 

KHOI TAO

READBUTDUOC AN??

GOI CHUONG TRINHCON

readPSP

XUAT DU LIEUDOC DUOCRA PORT B

WRITEBUTDUOC AN??

TANG GIA TRITHANH GHIDATAOUT

GOI CHUONG TRINHCON

writePSP

Y

Y

START

N

N

 Hình 6 : Lưu đồ giải thuật chương trình chính của master. 

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  21/29 

Trong  đó  chương  trình  con  “writePSP”  dùng  để  xuât  giá  trị  chứa  trong  thanh  ghi 

“DATAOUT” ra PSP slave. Chương trình con “readPSP” dùng để đọc dữ liệu từ PSP vào và lưu dữ liệu đọc được vào trong thanh ghi ”DATAIN”. 

Đối với chương trình cho slave, chương trình chính chỉ thực hiện các thao tác khởi tạo, phần việc còn lại được thực hiện trong chương trình ngắt. Lưu đồ giải thuật chương trình ngắt như sau: 

 

STARTINTERRUPT

PSPIF = 1??

IBF = 1 ??

DOC DU LIEUVAO THANH GHI

DATAIN

XUAT RA PORT B

OBF = 0 ??

TANG GIA TRITHANH GHIDATAOUT

DUA VAOBUFFER DEM

EXIT INTERRUPT

Y

Y

Y

XOA CO NGATPSPIF

N

N

N

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  22/29 

Hình 7 : Lưu đồ giải thuật chương trình ngắt của PSP slave. 

 

Ta cần phải kiểm tra cả hai bit trạng thái IBF và OBF để xác định thao tác cần thực thi tương ứng với yêu cầu của master. 

Sau đây là chương trình cụ thể của ứng dụng: 

Chương trình 3M : dùng cho master 

  processor   16f877a   include  <p16f877a.inc>   __CONFIG  _CP_OFF  &  _WDT_OFF  &  _BODEN_OFF  &  _PWRTE_ON  & _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF  ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o pha^`n cu*’ng ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   #define  READ   PORTE,0   #define  WRITE  PORTE,1   #define  CS    PORTE,2    #define  READBUTTON  PORTA,0   #define  WRITEBUTTON  PORTA,1 ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ count1                        EQU               0x20 counta                        EQU               0x21 countb                        EQU               0x22  DATAIN    EQU    0x23 DATAOUT    EQU    0x24 ;=========================================================================== ;         CHUONG TRINH CHINH   ORG    0x000   CLRF              STATUS   MOVLW  0x00   MOVWF  PCLATH   GOTO             start ;=========================================================================== start ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  23/29 

; Khoi tao cac PORT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   BCF    STATUS,RP1   BSF    STATUS,RP0    CLRF              TRISE   MOVLW  bʹ00000011ʹ   MOVWF  TRISA   CLRF              TRISB    MOVLW  0x06   MOVWF  ADCON1    BCF    STATUS,RP0    BSF    READ   BSF    WRITE   BSF    CS    CLRF              PORTB ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   CLRF              DATAIN   CLRF              DATAOUT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Vo`ng la*.p chi’nh cu?a chuo*ng tri`nh ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ loop   BTFSC  READBUTTON   GOTO             noread wait1                                                                     ; do*.i cho –de^’n khi nu’t a^’n                        CALL             delay_50ms                      ;  ‐duo*.c tha? ra    BTFSS             READBUTTON   GOTO             wait1    CALL             readPSP   MOVF  DATAIN,0   MOVWF  PORTB noread     BTFSC  WRITEBUTTON 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  24/29 

  GOTO   loop wait2                                                                     ; do*.i cho –de^’n khi nu’t a^’n                        CALL             delay_50ms                      ;  ‐duo*.c tha? ra     BTFSS             WRITEBUTTON   GOTO             wait2    INCF              DATAOUT,1   CALL              writePSP   GOTO   loop   ;=========================================================================== ;  CHUONG TRINH CON ;=========================================================================== ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Chuo*ng tri`nh con ʺwritePSPʺ ; Du`ng –de^ ? ghi du*~ lie^.u tu*` master le^n PSP slave  ; Gia’ tri. Ca^`n ghi chu*’a trong thanh ghi DATAOUT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ writePSP   BCF    CS   BCF    WRITE    BSF    STATUS,RP0    CLRF              TRISD    BCF    STATUS,RP0    MOVF  DATAOUT,0   MOVWF  PORTD    BSF    CS   BSF    WRITE   RETURN ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Chuo*ng tri`nh con ʺreadPSPʺ ; Du`ng –de^ ? –do.c du*~ lie^.u tu*` slave ve^` master ; Ke^’t qua? –do.c ve^` chu*’a trong thanh ghi DATAIN ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ readPSP   BCF    CS   BCF    READ 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  25/29 

   BSF    STATUS,RP0   MOVLW  0xFF   MOVWF  TRISD    BCF    STATUS,RP0   MOVF  PORTD,0   MOVWF  DATAIN    BSF    CS   BSF    READ   RETURN ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Chuo*ng tri`nh con ʺdelay_50msʺ ; Du`ng –de^? ta.o tho*`i gian delay 50 ms ; Chuo*ng tri`nh du`ng ‐^?de ho^~ tro*. cho qua’ tri`nh xu*? Li’ phi’m nha^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ delay_50ms   MOVLW  dʹ50ʹ   MOVWF  count1 d2               MOVLW  0xC7   MOVWF  counta   MOVLW  0x01   MOVWF  countb delay_1   DECFSZ  counta,1   GOTO             $+2   DECFSZ  countb,1   GOTO             delay_1   DECFSZ  count1,1   GOTO            d2         RETURN      END ;=========================================================================== 

 

Chương trình 3S : dùng cho slave 

 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  26/29 

               processor   16f877a   include  <p16f877a.inc>   __CONFIG  _CP_OFF  &  _WDT_OFF  &  _BODEN_OFF  &  _PWRTE_ON  & _XT_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF  ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khai ba’o bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ DATAOUT    EQU    0x20 DATAIN    EQU    0x21  W_save    EQU    0x22 PCLATH_save  EQU    0x23 STATUS_save  EQU    0x24 FSR_save    EQU    0x25  ;=========================================================================== ;           CHUONG TRINH NGAT   ORG    0x004   GOTO             ISR ;=========================================================================== ISR   MOVWF  W_save   SWAPF  STATUS,W   CLRF              STATUS   MOVWF  STATUS_save   MOVF  PCLATH,W   MOVWF  PCLATH_save   CLRF              PCLATH   MOVF  FSR,W   MOVWF  FSR_save    BTFSS             PIR1,PSPIF   GOTO   exit_int   BCF    PIR1,PSPIF    BSF    STATUS,RP0    BTFSS             TRISE,IBF   GOTO   no_data_in 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  27/29 

     BCF    STATUS,RP0   MOVF  PORTD,0   MOVWF  DATAIN   MOVWF  PORTB no_data_in       BSF    STATUS,RP0    BTFSC  TRISE,OBF     GOTO             exit_int    BCF    STATUS,RP0    INCF              DATAOUT,1   MOVF  DATAOUT,0   MOVWF  PORTD exit_int   BCF    STATUS,RP0   MOVF  FSR_save,W   MOVWF  FSR   MOVF  PCLATH_save,W   MOVWF  PCLATH   SWAPF  STATUS_save,W   MOVWF  STATUS   SWAPF  W_save,1   SWAPF  W_save,0   RETFIE ;=========================================================================== ;         CHUONG TRINH CHINH   ORG    0x000   CLRF              STATUS   MOVLW  0x00   MOVWF  PCLATH   GOTO   start ;===========================================================================   ORG    0x050 start ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Khoi tao cac PORT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   BCF    STATUS,RP1 

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  28/29 

  BCF    STATUS,RP0    CLRF              PORTD   CLRF              PORTB    BSF    STATUS,RP0    MOVLW  bʹ00010111ʹ   MOVWF  TRISE    MOVLW  0x06   MOVWF  ADCON1    CLRF              TRISB ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho*?i ta.o nga*’t PSP ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   BSF    PIE1,PSPIE    BCF    STATUS,RP0    BSF    INTCON,GIE   BSF    INTCON,PEIE ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Kho* ?i ta.o ca’c bie^’n ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   CLRF              DATAIN   CLRF              DATAOUT ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ; Vo`ng la*.p chi’nh cu?a chuo*ng tri`nh ;‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐   GOTO   $    END 

 

3 Kết luận PSP là khối giao tiếp  song song 8 bit duy nhất được tích hợp trong cấu trúc của đa số 

các vi điều khiển PIC.  

Người báo cáo:  Nguyễn Trung Chính  Tài liệu:  TUT04.02 

Ngày:  5/24/2006  Trang:  29/29 

PSP cho phép  truyền nhận dữ  liệu 8 bit cho phép một  tốc  độ  truyền dữ  liệu cao, bên 

cạnh đó phương thức giao tiếp cũng rất đơn giản và dễ sử dụng.   

Tuy nhiên, có thể nhận thấy một số điểm hạn chế của PSP như sau: 

‐ Số lượng chân điều khiển nhiều: để thực hiện giao tiếp bằng PSP, ta cần đến 8 chân dữ liệu và 3 chân điều khiển. Điều này gây ra nhiều hạn chế  khi số lượng chân điều khiển của một thiết bị ngoại vi là có giới hạn, đồng thời gây tốn kém khi cần nối dài đường truyền dữ liệu. 

‐ Độ  tin  cậy  của  thông  tin  không  cao:  việc  truyền  nhận  dữ  liệu  được  diễn  ra mà không có được sự kiểm soát của các thiết bị truyền nhận. master hay slave không thể biết được rằng dữ liệu có được truyền đến đầu bên kia hay không, và không có sự phản hồi về thông tin của thiết bị truyền nhận. Điều này cũng gây nhiều khó khăn trong quá trình truyền nhận dữ liệu, đặc biệt là khi khoảng cách đường truyền được nối dài. 

‐ Sự hạn chế của Buffer đệm: mỗi lần truyền nhận, ta chỉ có thể truyền nhận từng byte. Nếu truyền hai hay nhiều byte cùng một lúc sẽ gây ra hiện tượng tràn buffer đệm. 

‐ Dễ xảy ra tình trạng mất dữ liệu. master hay slave chỉ có nhiệm vụ truyền và nhận mà không thể  kiểm soát được lưu lượng của dữ liệu cũng như không thể biết được dữ liệu có bị mất trên đường truyền hay không. Hiện tượng dữ liệu bị ghi đè lên có thể được phát hiện bởi bit IBOV, nhưng không có cách phục hồi dữ liệu bị ghi đè.  

Mặc  dù  có  nhiều  nhược  điểm,  nhưng  PSP  cũng  có  được một  ưu  thế  nổi  bật  trong trường hợp cần truyền dữ liệu trong khoảng cách ngắn và tốc độ cao. Trong khoảng cách ngắn, hầu hết các nhược điểm của PSP được khắc phục và có thể đạt được tốc độ cũng như độ tin cậy cao.   

 

Hết bài 4 !! 

   

 

 

 

Dson 1

Thanh ghi (Register): Thanh ghi được đặt trong PIC, nó có thể được ghi, đọc. Hãy tưởng tượng các thanh ghi giống như các mẩu giấy mà chúng ta có thể đọc hay viết thông tin lên nó. Hình bên dưới mô tả file thanh ghi (register file) được ánh xạ vào PIC16F84. PIC được chia làm 2 phần, Bank0 và Bank1. Bank1 dùng để điều khiển các hoạt động của PIC, ví dụ như nói cho nó biết những bit nào trên PortA là đi vào (Input) và những bit nào xuất ra (Output). Bank0 dùng để thao tác trên dữ liệu, ví dụ ta muốn làm cho bit nào đó trên PortA lên mức cao, đầu tiên ta ta phải chuyển đến Bank1 để set 1 bit của 1 chân cụ thể nào đó trên PortA trở thành Output, sau đó ta chuyển đến Bank0 và gởi mức 1 tới chân đó. Những thanh ghi thông thường nhất trên Bank1 mà chúng ta sẽ sử dụng là các thanh ghi STATUS, TRISA and TRISB. Đầu tiên chúng ta hãy quay vào Bank1, thanh ghi TRISA cho phép ta chọn chân nào đó trên PortA làm ngõ Output hay Input, thanh ghi TRISB cho phép ta chọn chân nào đó trên PortB làm ngõ Output hay Input, thanh ghi STATUS cho phép chọn sử dụng Bank0 hay Bank1.

STATUS: Để thay đổi từ Bank0 sang Bank1 ta sử dụng thanh ghi trạng thái STATUS, set bit5 của thanh ghi trạng thái lên1 để chọn Bank1 hoặc xoá bit5 về 0 để chọn Bank0, thanh ghi STATUS có địa chỉ 03H.

TRISA và TRISB: 2 thanh ghi TRISA and TRISB đặt tại địa chỉ 85H và 86H, để lập trình cho các chân trên 2 thanh ghi này thông thường người ta gởi mức 0 hay 1 đến các bit tương ứng trên thanh ghi, có thể làm điều này trong cả 2 dạng hoặc là bằng số binary (bin) hay hex. Dùng kiểu binary thì rõ ràng hơn là kiểu hex nhưng mà trông lượm thượm hơn !. Trên PortA ta có 5 chân tương ứng 5 bit, nếu muốn đặt 1 trong 5 chân này thành Output ta phải gởi 1 đến bit tương ứng với nó, những bít này có tên bit đúng chính xác với tên của nó, nói cách khác bit0 là RA0, bit1 là RA1, bit2 là RA2…. Hãy xem ví dụ: Nếu ta muốn set RA0, RA3 và RA4 thành Output và RA1, RA2 thành Inputs, ta phải gởi 00110 (=06h), nên nhớ bit thấp nằm bên phải, xem hình: Port A Pin RA4 RA3 RA2 RA1 RA0 Bit Number 4 3 2 1 0 Binary 0 0 1 1 0

Administrator
Highlight
Administrator
Highlight
Administrator
Highlight

Dson 2

Tương tự chúng ta cũng làm như vậy cho TRISB.

PORTA và PORTB: Để làm cho 1 trong những chân Output lên mức cao ta gởi 1 đến bit tương ứng trên thanh ghi PORTA hoặc PORTB, giống như cách làm trên thanh ghi TRISA và TRISB, có thể kiểm tra lại trên từng chân của Port.

Thanh ghi W: Thanh ghi W là là thanh ghi mụch đích chung mà có thể đặt lên nó bất kỳ giá trị nào ta muốn, khi gán cho thanh ghi W một giá trị nào đó, ta có thể cộng nó với 1 giá trị khác hoặc có thể copy nó (Mov). Nếu bạn gán 1 giá trị nào đó lên thanh ghi W thì nội dung trước đó của nó sẽ bị ghi đè lên. Xem ví dụ sử dụng PortA: Đầu tiên chúng ta cần chọn Bank0 hoặc Bank1 bằng cách set trên thanh ghi STATUS, địa chỉ của STATUS là 03H và hãy set bit5 của nó lên 1 theo cách sau:

BSF 03h,5

BSF có nghĩa là Bit Set F, từ F nghĩa là chúng ta sẽ sử dụng một vị trí nào đó trong memory hoặc trong thanh ghi, 2 con số “03H” sau câu lệnh BSF nghĩa là địa chỉ của thanh ghi STATUS, con số “5” tức là bit5 của nó, như vậy ý nghĩa của câu lệnh trên là set bit5 của STATUS lên 1. Bây giờ chúng ta thao tác trong Bank1.

MOVLW b'00110'

Ta đã đặt giá trị binary 00110 vào trong thanh ghi mụch đích chung W, chữ b có nghĩa là binary, dĩ nhiên ta cũng có thể viết lại trong dạng số hex, nó như sau:

MOVLW 06h

MOVLW có nghĩa là là ‘Move Literal Value Into W’ tạm dịch là di chuyển giá trị của Literal vào thanh ghi W, để rõ ràng hơn ta có thể nói là “ đặt giá trị trực tiếp sau đây (06H) vào trong thanh ghi W “ Bây giờ ta tiếp tục đặt giá trị đó vào trong thanh ghi TRISA để thiết lập trạng thái cho Port:

MOVWF 85h

Lệnh này có nghĩa là “MOV nội dung của W vào (thanh ghi có) địa chỉ 85h”, trong trường hợp này con trỏ địa chỉ sẽ trỏ tới TRISA, thanh ghi TRISA bây giờ chứa giá trị 00110, xem lại mô tả các câu lệnh bằng hình sau: Port A Pin RA4 RA3 RA2 RA1 RA0 Binary 0 0 1 1 0 Input/Output O O I I O Bây giờ chúng ta sẽ thiết lập các chân trên PORTA, hãy quay về Bank0 để thao tác trên các dữ liệu.

BCF 03h,5

Dson 3

Lệnh BCF thì đối nghịch với BSF, nó có nghĩa là “ Bit Clear F” tạm dịch là xoá bit nào đó trong vùng memory hay trong thanh ghi nào đó, trong trường hợp này là thanh ghi STATUS (vì địa chỉ của nó là 03H) và lệnh này xoá bit5 của STATUS. Bên dưới là đoạn code:

BSF 03h,5 ; vào Bank 1 MOVLW 06h ; Đặt giá trị 00110 vào W MOVWF 85h ; Move 00110 vào trong TRISA BCF 03h,5 ; Quay trở về Bank 0

Hãy đọc kỹ đoạn code trên cho đến khi nào bạn hiểu nó đang làm cái gì. Ghi lên Port:

Trong phần trên chúng ta đã nói đến làm thế nào để thiết lập các chân của Port trở thành Input hay Output, trong phần này ta sẽ nói tiếp làm sao có thể gởi data tới Port và trong phần kế tiếp chúng ta sẽ kết thúc với một đoạn code làm cho đèn Led chớp tắt với cả sơ đồ mạch để có thể hiểu rõ con Pic làm việc chính xác đến mức độ nào, đừng có thử compile và nạp đoạn code vào con Pic của bạn vì nó chỉ là ví dụ mà thôi. Đầu tiên hãy setup bit2 của Port A thành Output.

Bsf 03h,5 ; Vào Bank 1 Movlw 00h ; Đặt giá trị 00000 vào trong W Movwf 85h ; Copy 00000 vào trong TRISA, tất

; cả các chân bây giờ sẽ trở ;thànhOutput bcf 03h,5 ; Quay trở về Bank0

Đoạn code trên là những gì đã nói trong phần trước, chỉ khác là bây giờ ta set tất cả các chân của PortA trở thành Output bằng cách gởi giá trị 0 đến thanh ghi w (thanh ghi W là loại thanh ghi có 3 trạng thái tri-state register). Bây giờ những gì mà ta muốn con Pic phải làm là bật tất cả Led lên, để làm điều này ta phải gởi mức 1 đến các chân Led, hãy xem làm như thế nào đây:

movlw 02h ; Ghi 02h vào thanh ghi W. nó là 00010 nếu ; viết theo dạng binary, như vậy nó đặt 0 vào ; bit 2 (chân 18) trong khi giữ các chân khác ở ; ;mức 0.

movwf 05h ;Bây giờ copy nội dung của W (02H) vào ;PortA (địa chỉ là 05H).

Con Led bây giờ đã bật on, chúng ta thử tắt nó xem:

movlw 00h ; Ghi 00h vào thanh ghi W. nó là 00000 nếu ; viết theo dạng binary, như vậy nó đặt 0 vào ; tất cả các chân.

movwf 05h ; Bây giờ copy nội dung của W ( 02H) vào ; PortA

Bây giờ Led đã bị tắt. Để làm cho led sáng, tắt liên tục chúng ta phải làm cho chương trình quay trở lại điểm bắt đầu bằng cách đặt nhãn cho chương trình và nói cho nó biết đó là điểm bắt đầu mà nó phải quay lại thực hiện lần nữa. Rất đơn giản, hãy đặt 1 cái nhãn có tên là START ngay tại điểm bắt đầu của đoạn code.

Dson 4

Start movlw 02h ; Write 02h to the W register. In binary

; this is 00010, which puts a ‘1’ on pin2 ; while keeping the other pins to ‘0’

movwf 05h ; Now move the contents of W (02h) ; onto the PortA, whose address is 05h

movlw 00h ; Write 00h to the W register. This puts a ; 0’ on all pins.

movwf 05h ; Now move the contents of W (0h) onto ; the Port A, whose address is 05h

goto Start ; Goto where we say Start Bây giờ hãy xem lại đoạn code:

Bsf 03h,5 Movlw 00h Movwf 85h bcf 03h,5

Start movlw 02h Movwf 05h Movlw 00h Movwf 05h Goto Start

Chúng ta chỉ nhìn thấy toàn những con số, bạn muốn hiểu được nó thì phải nhớ hết tất cả những địa chỉ của các thanh ghi, các Port …. Nhưng ngay cả khi bạn nhớ được tất cả thì một đoạn code ngắn nhất như trên cũng có thể làm bạn bối rối, để giải quyết vấn đề này hãy gán cho các con số địa chỉ bằng 1 cái tên bằng lệnh EQU. EQU đơn giản là thay một cái gì đó bằng một cái gì đó !, nó không phải là câu lệnh của con PIC mà nó là câu lệnh của assembler, với lệnh EQU bạn có thể gán bất kỳ địa chỉ thanh ghi nào bằng 1 cái tên gợi nhớ hoặc gán một cái tên cho một hằng số trong đoạn chương trình. Hãy thử gán vài hằng số bằng những cái tên bạn sẽ thấy nó dể đọc đến như thế nào. STATUS equ 03h ; this assigns the word STATUS to the value of 03h,

; which is the address of the STATUS register. TRISA equ 85h ; This assigns the word TRISA to the value of 85h,

; which is the address of the Tri-State register for ; PortA

PORTA equ 05h ;This assigns the word PORTA to 05h which is the ; address of Port A.

Bây giờ hãy thiếp lập các giá trị hằng số và đặt chúng vào chương trình, các giá trị hằng số phải được định nghĩa trước khi đặt vào chương trình và hãy nhớ phải luôn đặt chúng vào vị trí bắt đầu của chương trình. Bây giờ hãy xoá hết các ghi chú sau các câu lệnh, bạn thử nhìn xem có dể dàng hiểu được đoạn code trên khi không có các dòng ghi chú:

STATUS equ 03h TRISA equ 85h PORTA equ 05h

Dson 5

Bsf STATUS,5 movlw 00h movwf TRISA bcf STATUS,5

Start movlw 02h Movwf PORTA movlw 00h movwf PORTA goto Start

Hy vọng rằng bạn có thể hiểu được đoạn code trên ngay cả khi không có các ghi chú cho các câu lệnh.

Delay Loops. Có một chút rắc rối về đoạn code chớp tắt đèn led mà ta đã xem bên trên. Mỗi lệnh thực thi mất 1 chu kỳ xung clock, nếu ta sử dụng thạch anh 4MHz thì mỗi lệnh mất 4/4MHz hay 1us, trong đoạn code trên, ta sử dụng 5 lệnh như vậy mất 5us để thực thi hoàn toàn, quá nhanh để mắt người có thể nhìn thấy đèn led chớp tắt trong khỏang thời gian ngắn ngủi như vậy, cái mà ta cần là làm cho khoảng thời gian giữa sáng và tắt của led kéo dài ra, nói cách khác là làm trễ (Delay). Cơ bản của 1 chương trình Delay là cho đếm ngược lại giá trị đã đặt trước đó, và khi nó đến zero (0) thì ta cho dừng bộ đếm, giá trị zero báo cho biết dừng chương trình delay và sẽ tiếp tục thực thi lại nếu ta muốn. Đầu tiên chúng ta phải định nghĩa một hằng số dùng trong bộ đếm, tạm thời chúng ta gọi là hằng số COUNT, kế đến chúng ta phải xác định bộ đếm sẽ thực hiện đếm bao nhiêu, số lớn nhất mà ta có thể dùng cho bộ đếm là 255 hoặc số hex là FFh. Lệnh EQU gán 1 giá trị cho 1 thanh ghi, điều này có nghĩa là bất kỳ con số nào mà ta gán cho COUNT thì COUNT sẽ có giá trị bằng với nội dung của địa chỉ đó. Nếu thử gán giá trị FFh cho COUNT ta sẽ nhận được thông báo lỗi khi compile chương trình bởi vì địa chỉ FFH đã được dùng cho mụch đích khác và chúng ta không thể truy cập tới nó, như vậy chúng ta phải gán một con số như thế nào cho hợp lệ ?, bạn đừng lo lắng, sẽ có cách giải quyết. Nếu chúng ta gán COUNT cho 1 địa chỉ nào đó, ví dụ 08h, nó sẽ trỏ tới vị trí thanh ghi mụch đích chung, nhưng mà giá trị mặc nhiên sau khi mở nguồn của những vị trí không dùng đến là FFh vì vậy nếu COUNT trỏ tới 08h thì nó sẽ có giá trị FFh. Bây giờ tôi lại đang nghe bạn “khóc” rằng làm sao mà gán COUNT bằng một số nào đó có giá trị trùng với 1 trong các địa chỉ của các thanh ghi đã sử dụng?, không sao, nếu vậy thì điều mà chúng ta phải làm là MOV giá trị của bạn tới vị trí này, giả sử nếu bạn muốn COUNT có giá trị là 85h, chúng ta không thể làm: COUNT EQU 85h Bởi vì 85h là vị trí của thanh ghi xuất (out) 3 trạng thái (Tri-State register) của PORTA. Cái mà chúng ta phải làm là: Movlw 85h ; Đầu tiên đặt giá trị 85h vào thanh ghi W. Movwf 08h ; Kế đến copy giá trị tới thanh ghi 08h.

Bây giờ, khi chúng ta nói: COUNT equ 08h

Thì COUNT sẽ tương đương với giá trị 85h. Thật là quỷ quyệt, có phải không ?!

Administrator
Highlight

Dson 6

Tiếp tục, đầu tiên ta định nghĩa cho một hằng số COUNT equ 08h kế đến giảm COUNT xuống 1 cho đến khi nó = 0, chỉ cần 1 lệnh đơn để làm việc này với sự hỗ trợ của lệnh GOTO và một cái nhãn, lệnh đơn được dùng là: DECFSZ COUNT,1

Lệnh DECFSZ sẽ giảm thanh ghi ( trong trường hợp này là COUNT) xuống một đơn vị được điền sau dấu phẩy (,), trong ví dụ này đơn vị là 1. Nếu nó giảm tới zero chương trình sẽ bỏ qua lệnh kế tiếp để nhảy đến thực thi lệnh thứ 2. Mất nhiều lời để giải thích cho 1 lệnh đơn có phải không?, hãy xem cái gì xảy ra khi ta đặt nó vào chương trình. COUNT equ 08h LABEL decfsz COUNT,1 Goto LABEL Carry on here. : : : Điều mà chúng ta phải làm đầu tiên là gán hằng số COUNT = 255, kế đến đặt 1 cái nhãn ngay bên cạnh lệnh defsz. Lệnh decfsz COUNT,1 sẽ giảm giá trị của COUNT xuống 1 và lưu giá trị đã giảm trở vào trong COUNT, nó cũng sẽ kiểm tra xem COUNT = 0 chưa, nếu chưa nó sẽ cho chương trình thực thi lệnh kế tiếp, trong ví dụ này nó sẽ thực thi lệnh GOTO để quay về lại điểm bắt đầu ( là LABEL), nếu COUNT = 0 thì nó sẽ cho chương trình bỏ qua lệnh kế tiếp và nhảy đến lệnh thứ 2, trong ví dụ này chương trình sẽ nhảy đến nơi có chữ ‘Carry on here’. Như bạn đã thấy, chúng ta đã làm cho chương trình lưu lại một thời gian trước khi nó tiếp tục làm việc gì đó tiếp theo, cái này gọi là vòng trễ (Delay loop), nếu chúng ta muốn thời gian trễ lớn hơn chúng ta phải làm một vòng trễ kiểu khác, nhưng mà cũng dể dàng để hiểu ra rằng có nhiều Loop hơn thì thời gian sẽ trễ lâu hơn, chúng ta cần ít nhất là 2 Loop như trên nếu muốn nhìn thấy đèn Led chớp. Bây giờ hãy đặt chúng vào trong chương trình và kết thúc chương trình, nhớ thêm các ghi chú. ;*****Set up the Constants**** STATUS equ 03h ;Address of the STATUS register TRISA equ 85h ;Address of the tristate register for Port A PORTA equ 05h ;Address of Port A COUNT1 equ 08h ;First counter for our delay loops COUNT2 equ 09h ;Second counter for our delay loops ;****Set up the Port**** bsf STATUS,5 ;Switch to Bank 1 movlw 00h ;Set the Port A pins movwf TRISA ;to Output.

Dson 7

bcf STATUS,5 ;Switch back to Bank 0 ;****Turn the LED on**** Start movlw 02h ;Turn the LED on by first putting

movwf PORTA ;it into the w register and then ;on the Port

;****Start of the delay loop 1**** Loop1 decfsz COUNT1,1 ;Subtract 1 from 255

Goto Loop1 ;If COUNT is zero, carry on. Decfsz COUNT2,1 ;Subtract 1 from 255 Goto Loop1 ;Go back to the start of our loop.

;This delay counts down from ;255 to zero, 255 times

;****Delay finished, now turn the LED off**** movlw 00h ;Turn the LED off by first putting movwf PORTA ; it into the w register and then onthe Port ;****Add another delay**** Loop2 decfsz COUNT1,1 ;This second loop keeps the

Goto Loop2 ;LED turned off long enough for decfsz COUNT2,1 ;us to see it turned off goto Loop2 ;

;****Now go back to the start of the program

goto Start ;go back to Start and turn LED ;on again

;****End of the program****

end ;Needed by some compilers, ;and also just in case we miss ;the goto instruction.

Bạn có thể cpmpile chương trình này và nạp nó vào con PIC, dĩ nhiên là bạn sẽ muốn thử cho nó hoạt động, ở đây có sẵn sơ đồ mạch cho bạn. Xin chúc mừng, bạn vừa mới viết xong 1 chương trình cho con PIC và đã làm cho nó hoạt động theo mong đợi. Cho đến bây giờ bạn đã học được 7 trong số 35 lệnh của con PIC rồi đấy, nhưng mà như vậy bạn vẫn chưa thể điều khiển được các Port I/O của nó. Tại sao bạn không thử thay đổi Delay Loop cho nó nhanh hơn để biết giá trị Delay Loop tối thiểu mà mắt người có thể nhìn thấy đèn Led chớp tắt và thay đổi tốc độ chớp tắt của Led, ví dụ mỗi lần là 1 giây. Trong trường hợp này bạn cần phải thử thay đổi các giá trị hằng số COUNT khác nhau của mỗi Delay Loop. Trong phần tiếp theo chúng ta sẽ bàn đến cái gì gọi là thủ tục con (subroutine) để giúp chúng ta tiếp tục viết các chương trình nhỏ và thông thường nhất.

Thủ tục con (subroutine):

Administrator
Highlight

Dson 8

Một thủ tục con là một phần của một đoạn code hay một phần của một chương trình mà bạn có thể gọi nó thực thi bất kỳ lúc nào cần thiết. Một thủ tục con được sử dụng khi mà bạn muốn thực thi một chức năng nào đó nhiều hơn 1 lần, tức là làm đi làm lại chức năng đó, ví dụ như Delay Loop. Cái thuận tiện của một thủ tục con là bạn có thể thay đổi giá trị bên trong nó sau mỗi lần thực thi, ví dụ bạn có thể thay đổi 10 lần gía trị của nó nếu cần thiết, nhưng quan trọng nhất của một thủ tục con là bạn có thể tiết kiệm bộ nhớ chương trình chiếm đóng trong con Pic. Hãy xem một subroutine sau: ROUTINE

COUNT equ 255 LABEL decfsz COUNT,1

Goto LABEL RETURN Đầu tiên chúng ta phải đặt cho subroutine một cái tên, tôi chọn tên ROUTINE, sau đó viết đoạn chương trình mà tôi muốn nó thực hiện, tôi viết lại chương trình Led chớp tắt như phần trên, cuối cùng tôi kết thúc subroutine bằng lệnh RETURN. Bạn có thể đặt subroutine này bất cứ nơi nào trong chương trình chính (MAIN) và khi muốn nó thực thi bạn chỉ cần gọi nó bằng lệnh CALL theo sau là tên của subroutine. Subroutine sẽ thực thi đoạn code bên trong nó cho đến khi nó gặp lệnh RETURN thì dừng lại, chương trình sẽ tự động quay về chương trình chính đúng tại nơi mà nó gọi subroutine và thực thi lệnh kế tiếp sau lệnh CALL. Bạn có thể CALL nhiều lần để thực thi cùng một subroutine nếu bạn muốn, đó là lý do tại sao người ta sử dụng subroutine để giảm độ dài của chương trình. Tuy nhiên có hai thứ mà bạn phải nghĩ đến, thứ nhất là bất kỳ hằng số nào cũng phải được khai báo trước khi bạn sử dụng nó nhưng mà trong trường hợp subroutine bạn có thể khai báo ngay trong bản thân nó hoặc ngay tại đầu chương trình chính như thông thường, tuy nhiên tôi lại khuyên bạn nên khai báo mọi thứ tại đầu chương trình chính vì như bạn đã biết, để mọi thứ ở cùng một nơi thì dể tìm kiến hơn, có phải không?. Vấn đề thứ hai rất quan trọng là bạn phải bảo đảm đặt subroutine sau lệnh RETURN của chương trình chính trừ phi trong chương trình chính bạn dùng lệnh GOTO để nhảy qua subroutine, nếu không nó sẽ thực thi bất kỳ lệnh nào mà nó bắt gặp bất kể bạn có muốn hay không bởi vì con Pic không phân biệt được đâu là chương trình chính đâu là subroutine. Hãy xem lại đoạn chương trình chớp Led nhưng mà lần này ta sử dụng subroutine cho Delay Loop bạn sẽ thấy chương trình đơn giản đến mức nào và xem subroutine làm việc ra sao. ;*****Set up the Constants**** STATUS equ 03h ;Address of the STATUS register TRISA equ 85h ;Address of the tristate register for Port A PORTA equ 05h ;Address of Port A COUNT1 equ 08h ;First counter for our delay loops COUNT2 equ 09h ;Second counter for our delay loops ;****Set up the Port****

Dson 9

bsf STATUS,5 ;Switch to Bank 1 movlw 00h ;Set the Port A pins movwf TRISA ;to Output. Bcf STATUS,5 ;Switch back to Bank 0 ;****Turn the LED on**** Start movlw 02h ;Turn the LED on by first putting it

movwf PORTA ;into the w register and then on the Port

;****Add a delay call Delay ;****Delay finished, now turn the LED off**** movlw 00h ;Turn the LED off by first putting it movwf PORTA ;into the w register and then on the Port ;****Add another delay**** call Delay ;****Now go back to the start of the program

goto Start ;go back to Start and turn LED on again ;****Here is our Subroutine Delay Loop1

decfsz COUNT1,1 ;This second loop keeps the LED Goto Loop1 ;turned off long enough for us to Decfsz COUNT2,1 ;see it turned off Goto Loop1 ; Return

;****End of the program**** end ;Needed by some compilers, and

;also ;just in case we miss the goto instruction.

Rõ ràng kích thước chương trình đã giảm đi nhiều khi sử dụng subroutine cho Delay Loop, mỗi lần ta muốn thực hiện Delay để làm cho Led ON hoặc cho Led Off, ta chỉ cần gọi subroutine Delay. Tại điểm kết thúc subroutine chương trình sẽ quay trở về ngay sau dòng lệnh CALL. Nếu không sử dụng subroutine chương trình chớp Led trên có thể cần đến 120byte bộ nhớ chương trình, nhưng khi sử dụng subroutine nó chỉ còn cần 103byte, thật ra số byte chênh lệch như vậy cũng không phải là vấn đề quan trọng lắm, nhưng mà bạn chỉ có 1024byte để chứa chương trình trong con Pic thì việc tiết kiệm được số byte như vậy quả là không uổng công nặn óc để làm subroutine, có phải không Trong phần kế tiếp chúng ta sẽ tìm hiểu làm sao mà đọc được Port.

Đọc Port (Reading from the I/O Ports): Cho đến bây giờ bạn đã có thể ghi lên Port để làm cho Led chớp tắt, còn tiếp theo chúng ta sẽ tìm cách đọc lại nội dung trên chân I/O của Port. Trước tiên cần kết nối các chân Port tới mạch bên ngoài và theo dõi hoạt động tại đây.

Administrator
Highlight

Dson 10

Nếu bạn còn nhớ những thứ đã nói đến trong các phần trước, để setup I/O Port chúng ta phải chuyển từ Bank0 sang Bank1, hãy làm cái này trước: STATUS equ 03h ;Address of the STATUS register TRISA equ 85h ;Address of the tristate register for Port A PORTA equ 05h ;Address of Port A Bsf STATUS,5 ;Switch to Bank 1 Để gán cho Port trở thành Output, chúng gởi 0 vào thanh ghi TrisA và để nó trở thành Input ta phải gởi 1 đến thanh ghi TrisA, quá đơn giản !.

Movlw 01h ;Set the Port A pins Movwf TRISA ;to Input. Bcf STATUS,5 ;Switch back to Bank 0

Bây giờ chúng ta đặt bit0 của PortA trở thành Input, cái mà ta phải làm bây giờ là kiểm tra lại xem chân này đang ở mức cao hay thấp (mức1 hay mức 0), để làm được điều này ta sử dụng lệnh BTFSC và lệnh BTFSS. Lệnh BTFSC có nghĩa là làm động tác thử xem 1 bit được chỉ định trên thanh ghi có = 0 hay không, nếu là 0 thì bỏ qua lệnh kế tiếp. Lệnh BTFSS thì ngược lại, nó có nghĩa là làm động tác thử xem 1 bit được chỉ định trên thanh ghi có = 1 hay không, nếu là 1 thì bỏ qua lệnh kế tiếp. Chúng ta sẽ sử dụng lệnh nào?, cái này còn tuỳ thuộc vào bạn mong đợi chương trình đọc được cái gì trên Port. Ví dụ: Nếu bạn đang mong đợi ngõ Input là 1 thì hãy dùng lệnh BTFSS, hãy xem cái này: Code here : BTFSS PortA,0 Goto start Carry on here : : Chương trình sẽ chỉ nhảy đến dòng “Carry on here” nếu bit0 của PortA = 1. Bây giờ bạn hãy viết lại chương trình đèn Led chớp ở 1 tốc độ cố định, nhưng mà nếu đóng 1 cái Switch nào đó thì đèn Led sẽ chớp chậm hơn ½.. Bạn hoàn toàn có thể tự làm được mà, đừng có nhìn vào đoạn Code bên dưới xem sao. Chúng ta sử dụng cùng một mạch giống như phần trên nhưng mà thêm một cái Switch có một đầu nối vào chân RA0 của con Pic còn đầu kia mắc lên nguồn. ;*****Set up the Constants**** STATUS equ 03h ;Address of the STATUS register TRISA equ 85h ;Address of the tristate register for Port A PORTA equ 05h ;Address of Port A COUNT1 equ 08h ;First counter for our delay loops COUNT2 equ 09h ;Second counter for our delay loops ;****Set up the Port**** bsf STATUS,5 ;Switch to Bank 1 movlw 01h ;Set the Port A pins: movwf TRISA ;bit 1to Output, bit 0 to Input.

Dson 11

Bcf STATUS,5 ;Switch back to Bank 0 ;****Turn the LED on**** Start

movlw 02h ;Turn the LED on by first putting it Movwf PORTA ;into the w register and then on the Port

;****Check if the switch is closed BTFSC PORTA,0 ;Get the value from PORT A

;BIT 0. If it is a zero call Delay ;a zero, carry on as normal.If is is a 1,

;then add an extra delay routine ;****Add a delay call Delay ;****Delay finished, now turn the LED off**** movlw 00h ;Turn the LED off by first putting it movwf PORTA ;into the w register and then on the Port ;****Check if the switch is still closed BTFSC PORTA,0 ;Get the value from PORT ABIT 0. If it is a zero, Call Delay ;carry on as normal.If is a 1, then add anextra delay

;routine ;****Add another delay**** call Delay ;****Now go back to the start of the program goto Start ;go back to Start and turn LED on again ;****Here is our Subroutine Delay Loop1

Decfsz COUNT1,1 ;This second loop keeps the LED Goto Loop1 ;turned off long enough for us to decfsz COUNT2,1 ;see it turned off goto Loop1 ; return

;****End of the program**** end ;Needed by some compilers, and also

;just in case we miss the goto instruction. Đầu tiên chương trình bật Led on, kế đến kiểm tra xem cái Switch có đóng không, nếu nó đóng chương trình sẽ gọi Delay subroutine, thời gian Delay giống y như trước nhưng mà gọi subroutine thực thi 2 lần, nó sẽ làm tương tự như vậy cho trường hợp Led Off. Bây giờ bạn hãy compile chương trình và cho con Pic chạy thử, nhưng mà tôi có một lời cảnh cáo bạn rằng, toàn bộ những thứ mà bạn làm sẽ không gây ấn tượng cho bất kỳ ai không thích thú với lập trình cho vi xử lý, vì vậy cũng đừng có thất vọng nếu mà bạn đem khoe với những người thân trong gia đình rồi chỉ cho họ làm sao cho con Led chớp châm đi ….họ sẽ chỉ giả vờ ngạc nhiên thích thú mà thôi !, đó là những kinh nghiệm xương máu của tôi !. Nếu bạn theo sát từ đầu đến giờ thì bạn đã biết tổng cộng 10 trong số 35 lệnh của con Pic 16F84 rồi đấy và tất cả những thứ mà bạn biết chỉ đơn giản là làm cho con Led chớp tắt !, thật phí phạm thời gian có phải không ?, còn tôi thì nghĩ thật là phí phạm bộ nhớ của con Pic nếu phải viết chương trình dài như vậy chỉ để chớp tắt, nhan chậm đèn Led !, phải có cách gì đó làm cho hay hơn.

Dson 12

Hãy xem ví dụ bên dưới, nó mới thật sự là một chương trình làm đèn Led chớp tắt, nhanh chậm. movlw 02h movwf PORTA movlw 00h movlw PORTA Đầu tiên ta nạp vào thanh ghiW giá trị 02h, sau đó copy nó sang thanh ghi PortA để bật Led on. Để tắt nó, ta Nạp gía trị 00h vào thanh ghi W sau đó copy nó tới thanh ghi PortA. Ở giữa chương trình ta phải gọi subroutine để cho đèn chớp tắt, chúng ta phải viết 2 lệnh MOV cho Led Off và 2 lệnh MOV cho Led on, 2 lệnh MOV sẽ thực hiện lần lượt ghi data vào thanh ghi W rồi chuyển vào PORTA. Sau đó ta gọi 2 lần Delay subroutine, 1 lần Delay cho Led on và 1 lần cho Led Off. Có cách nào khác đơn giản hơn không ?, có đấy, đó là sử dụng lệnh XORF. Lệnh XORF thực hiện hàm XOR cho data chứa trong thanh ghi, chắc là không cần phải giải thích hàm XOR cho bạn phải không ?. Như vậy để bật Led On/Off chúng ta chỉ cần 2 dòng Lệnh. MOVLW 02h XORWF PORTA,1 Đầu tiên nạp vào W giá trị 02h sau đó thực hiện lệnh XORF cho data trên PortA với giá trị 1, nếu hiện tại PortA có giá trị 1 thì nó sẽ thay đổi thành 0 còn nếu PortA đang là 0 sau khi lệnh XORF thực hiện nó sẽ trở thành 1. Hãy xem mô tã lại những gì mà chúng ta đã nói: PORTA 00010 xorwf 00000 xorwf 00010 xorwf 00000 xorwf 00010 Thật ra chúng ta không cần phải nạp mỗi lần cùng một giá trị vào trong thanh ghi W bởi vì có thể làm điều này ngay lúc bắt đầu chương trình, chỉ cần quay trở về lệnh lật ngược PortA lại mà thôi. Ngoài ra chúng ta cũng không cần phải gán một giá trị cho thanh ghi PortA, tại sao vậy?, bởi vì khi mới cấp nguồn cho con Pic thì PortA mặc nhiên đã = 1 rồi chúng ta chỉ cần lật qua lật lại cho PortA =0 rồi =1 mà thôi, ngay cả khi ban đầu PortA = 0 thì chúng ta cũng sẽ vẫn làm như vậy. Hãy xem 2 đoạn code mới, đoạn code thứ nhất viết theo kiểu như ban đầu, đoạn code thứ hai là viết lại nhưng dùng lệnh XORF. ;*****Set up the Constants**** STATUS equ 03h ;Address of the STATUS register TRISA equ 85h ;Address of the tristate register for Port A PORTA equ 05h ;Address of Port A COUNT1 equ 08h ;First counter for our delay loops COUNT2 equ 09h ;Second counter for our delay loops ;****Set up the Port**** bsf STATUS,5 ;Switch to Bank 1

Dson 13

movlw 00h ;Set the Port A pins movwf TRISA ;to Output. Bcf STATUS,5 ;Switch back to Bank 0 movlw 02h ;Set up our w register with 02h ;****Turn the LED on and off**** Start Xorwf PORTA,1 ;Toggle the LED

;****Add a delay call Delay ;****Now go back to the start of the program goto Start ;go back to Start and turn LED on again ;****Here is our Subroutine Delay Loop1

decfsz COUNT1,1 ;This second loop keeps the LED Goto Loop1 ;turned off long enough for us to decfsz COUNT2,1 ;see it turned off goto Loop1 ; return

;****End of the program**** end ;Needed by some compilers, and also

;just in case we miss the goto instruction. ;*******Flashing LED With Switch: ;*******Set up the Constants**** STATUS equ 03h ;Address of the STATUS register TRISA equ 85h ;Address of the tristate register for Port A PORTA equ 05h ;Address of Port A COUNT1 equ 08h ;First counter for our delay loops COUNT2 equ 09h ;Second counter for our delay loops ;****Set up the Port**** bsf STATUS,5 ;Switch to Bank 1 movlw 01h ;Set the Port A pins: movwf TRISA ;bit 1to Output, bit 0 to Input. Bcf STATUS,5 ;Switch back to Bank 0 movlw 02h ; Set up our w register with 02h ;****Turn the LED on and off**** Start

xorwf PORTA,1 ;Toggle the LED ;****Check if the switch is closed BTFSC PORTA,0 ;Get the value from PORT A BIT 0.If it is a

;zero call Delay ;carry on as normal. If is a 1, then add an

;extra delay routine ;****Add a delay call Delay ;****Check if the switch is still closed

BTFSC PORTA,0 ;Get the value from PORT A BIT 0. If it is a ;zero,

call Delay ;carry on as normal. If is a 1, then add an

Dson 14

;extra delay routine ;****Add another delay**** call Delay ;****Now go back to the start of the program goto Start ;go back to Start and turn LED on again ;****Here is our Subroutine Delay Loop1

Decfsz COUNT1,1 ;This second loop keeps the LED goto Loop1 ;turned off long enough for us to decfsz COUNT2,1 ;see it turned off goto Loop1 ; return

;****End of the program**** end ;Needed by some compilers, and also

;just in case we miss the goto instruction. Chỉ cần dùng các lệnh đơn giản chúng ta có thể giảm kích thước của chương trình. Thực sự ta đã giảm được bao nhiêu byte khi viết lại các chương trình bằng các lệnh đơn giản, hãy xem thống kê: Program Change Size (Bytes) Flashing LED Original 120 Flashing LED Subroutine Added 103 Flashing LED XOR Function Used 91 LED With Switch Original 132 LED With Switch XOR Function Used 124. Chúng ta không chỉ học vài lệnh mới mà còn học cách làm sao giảm kích thước của chương trình.

Toán hạng Logic và Số học: Trong phần trên chúng ta được giới thiệu lệnh XORF và cách sử dụng nó, trong phần này sẽ nói tiếp các toán hạng và lệnh Logic mà con Pic có hỗ trợ. Bây giờ ta sẽ nói làm sao thao tác trên các bit riêng rẽ, thực hiện vài thuật toán thông thường trên dữ liệu. Sẽ không có ví dụ nữa nhưng mà sẽ giải thích cặn kẽ làm thế nào dùng các toán hạng trong các đoạn code nhỏ.

Lệnh ANDLW và ANDWF: Con Pic cho ta 2 món được chế biến từ hàm AND, đó là lệnh ANDLW và ANDWF. Lệnh ANDLW cho phép ta AND nội dung trong thanh ghi W với một con số xác định, cú pháp là:

ANDLW <number> <number> là cái mà ta sẽ AND với nội dung trong thanh ghi W, kết quả AND sẽ lưu trong thanh ghi W. Lệnh ANDWF cho phép ta AND thanh ghi W với một thanh ghi khác, ví dụ như với PortA, cú pháp là:

ANDWF <register>,d

Dson 15

<register> là thanh ghi mà ta chỉ định, ví dụ PortA, d nói cho Pic biết nơi lưu kết quả. Nếu d=0 thì kết quả lưu vào thanh ghi W và d=1 thì kết quả lưu vào thanh ghi đứng trước d. Hai đoạn code bên dưới sẽ mô tã 1 ví dụ cho mỗi hàm AND. Đầu tiên kiểm tra trạng thái PortA là nơi mà ta cần biết ngõ vào có = 1100 hay không và đặt kết quả vào trong thanh ghi W. Movlw 1100 ANDWF 05h,0 Ví dụ thứ hai sẽ kiểm tra nội dung trong W. ANDLW 1100

Lệnh IOR: Lệnh IOR đơn giản như là một hàm OR, khi một trong hai bit =1 hoặc cả hai đều = 1 mà OR với nhau sẽ cho kết quả = 1, ngược lại sẽ =0.

Lệnh ADDLW và ADDWF: ADD là một hàm cộng 2 số với nhau, nếu kết quả lớn hơn 8bit thì cờ CARRY sẽ được set lên 1 ngược lại nó =0. Cờ CARRY có địa chỉ byte 03h và nằm tại bit0. Một lần nữa con Pic cho ta 2 món chế biến từ hàm ADD, đó là ADDLW và ADDWF, bạn cũng có thể đoán rằng nó cũng tương tự như các hàm ở trên. Lệnh ADDLW cộng nội dung của thanh ghi W với một số xác định, cú pháp là: ADDLW <number> Lệnh ADDWF cộng nội dung của thanh ghi W với một thanh ghi bất kỳ, kết quả lưu trong W, cú pháp là: ADDWF <register>,d <register> là thanh ghi mà chúng ta chỉ định và d nói cho con Pic biết nơi lưu kết quả. Nếu d=0 kết quả lưu trong thnh ghi W, d=1 kết quả lưu trong thanh ghi ta chỉ định (tức là <register>).

Lệnh SUBLW and SUBWF: Hàm SUB, tôi dám đánh cược bạn không thể đoán được hàm này làm cái gì ?!, thôi được rồi, xem như bạn đã đoán ra, hàm SUB này trừ 1bit với 1bit khác. Một lần nữa con Pic lại cho ta 2 món được chế biến từ hàm SUB, đó là SUBLW and SUBWF, cú pháp thì giống y như là những món của hàm ADD nhưng mà thay vì cộng thì nó trừ.

Lệnh INCF và INCFSZ: Nếu chúng ta muốn cộng 1 với một số trong Pic, đơn giản ta sử dụng hàm ADD và số 1, cái bất tiện là đầu tiên ta phải bỏ con số 1 vào trong thanh ghi W, sau đó dùng lệnh ADDLW 1 để tăng nó lên 1. Nếu ta chỉ muốn cộng số 1 vào một thanh ghi bất kỳ thì còn tồi tệ hơn, đầu tiên phải đặt số 1 vào thanh ghi W, sau đó dùng lệnh ADDWF <register>,1. Ví dụ ta muốn cộng số 1 với nội dung của địa chỉ 0Ch, ta phải viết đoạn code sau: Movlw 01 addwf 0c,1 Có một cách tốt hơn cách này đó là dùng lệnh INCF trong con Pic, cú pháp là: INCF <register>,d

Dson 16

Với <register> là thanh ghi, hoặc địa chỉ mà ta chỉ định, còn d thì nói cho con Pic biết nơi đặt kết quả. Nếu d=0 thì kết quả lưu trong thanh ghi W, nếu d=1 kết quả sẽ được lưu trong thanh ghi chỉ định nằm trước nó (tức là <register>) Bằng cách này ta có thể tiết kiệm ½ bộ nhớ của Pic. Nếu ta muốn kết quả lưu trong W thì sử dụng ví dụ trên sau đó thêm một lệnh khác để MOV nội dung trong địa chỉ 0Ch trở vào trong thanh ghi W sau đó đặt vào thanh ghi 0Ch bất cứ cái gì. Có một lệnh increment khác, đó là INCFSZ, lệnh này sẽ tăng thanh ghi mà ta chỉ định lên 1, nhưng nếu thanh ghi này =0 sau khi thực thi lệnh ( xảy ra khi cộng 1 vào FFh) thì con Pic sẽ bỏ qua lệnh kế tiếp, đoạn code bên dưới sẽ mô tả lệnh này: Loop

Incfsz 0C Goto Loop

: : Rest of program. Trong đoạn code trên, địa chỉ 0Ch sẽ tăng lên 1 sau đó chương trình nói cho con Pic quay về nhãn Loop và gia tăng 0Ch lên lần nữa, nó làm tiếp tục như vậy cho tới khi 0Ch =127 (FFh). Lần này khi tăng lên 1, nội dung của 0Ch sẽ =0. Lệnh INCFSZ sẽ nói cho con Pic bíêt hãy bỏ qua lệnh kế tiếp, trong trường hợp ví dụ trên nó bỏ qua lệnh GOTO Loop để thực thi tiếp đoạn code còn lại.

Lệnh DECFSZ: Lệnh DECFSZ đã bàn trong các ví dụ trước, bây giờ ta sẽ không nhắc lại nữa.

Lệnh COMF: Lệnh cuối cùng trong nhóm này là lệnh COMF, nó đảo ngược (Compliment) tất cả các bit trong thanh ghi được chỉ định, cú pháp là:

COMF <register>,d. Với <register> là thanh ghi mà ta muốn đảo và d nói cho con Pic bíêt nơi lưu kết quả. Nếu d=0 kết quả lưu trong thanh ghi W, nếu d=1 thì kết quả lưu trong thanh ghi chỉ định nằm trước d ( tức là <register>). Xem mô tả sau đây: 0C = 11001100 COMF 0C,1 0C = 00110011 Cái này rất tiện lợi khi mà bạn muốn nhanh chóng bật các chân của Port từ Output trở thành Input hoặc ngược lại.

Toán hạng trên Bit: Các toán hạng dùng cho Bit cho phép chúng ta thao tác trên các bit đơn lẽ trong byte, nó cho phép MOV, SET và CLEAR bit trong thanh ghi hoặc những địa chỉ được chỉ định, phần cuối của tutorial này ta sẽ trình bày một chương trình làm cho con Led sáng chạy theo nhiều cách khác nhau.

Lệnh BCF: Trong các phần trước chúng ta đã xem một số lệnh thực thi trên bit, trong phần này ta sẽ xem một số lệnh còn lại tác động lên bit như thế nào. BCF là lệnh xoá 1 bit được chỉ định trong thanh ghi, cú pháp là:

Administrator
Highlight
Administrator
Highlight

Dson 17

BCF <register>,<bit> Chúng ta đã sử dụng lệnh này trong phần trước để thay đổi từ Bank1 sang Bank0 bằng cách xoá bit trong thanh ghi STATUS, chúng ta cũng có thể Clear 1 bit về 0 tại bất kỳ bit nào trong bất kỳ thanh ghi nào, ví dụ, nếu bạn muốn Clear bit thứ 3 trong thanh ghi 0Ch có nội dung = 11001101, bạn có thể làm như sau:

BCF 0C,03 Lệnh BSF:

Lệnh BSF ngược lại, nó có thể Set 1 bit lên 1 tại bất kỳ bit nào trong bất kỳ thanh ghi nào, ta đã dùng cái này trong phần trước để nhảy từ Bank0 sang Bank1, cú pháp là: BSF <register>,<bit> Cách dùng BSF giống y như cách dùng BCF.

Lệnh BTFSC: Chúng ta đã có thể Set bit và Clear bit trong thanh ghi, nhưng mà nếu bạn chỉ muốn thử xem bit nào đó trong thanh ghi là = 1 hay = 0 thì sao, rất đơn giản, hảy dùng lệnh BTFSC, nó được gọi là “Bit Test Register F and Skip If It Is Clear”, tạm dịch là “lệnh thử kiểm tra bit trong thanh ghi và bỏ qua lệnh kế nếu bit = 0”, quá rõ ràng rồi, không cần phải giải thích gì thêm nữa phải không ?!, ta sẽ dùng lệnh này để kiểm tra một cái cờ (flag) nào đó ví dụ như cờ Carry, nó tránh cho ta khỏi phải đọc thanh ghi STATUS để tìm xem trạng thái của từng bit như thế nào. Ví dụ, nếu bạn muốn thử bit cờ Carry =1 chưa sau khi bạn cộng 2 byte với nhau, bạn hãy thử làm cái này:

BTFSC 03h,0 Nếu cờ Carry=1 thì chương trình thực thi tiếp lệnh đứng kế tiếp, nếu Carry=0 nó sẽ bỏ qua lệnh kế tiếp, xem đoạn code sau: Loop : : : BTFSC 03,0 Goto Loop Trong đoạn code trên, con Pic sẽ đi ra khỏi Loop nếu bit0 trong thanh ghi STATUS ( hay cờ Carry) bị xoá về 0, nói cách khác nếu cờ Carry=0 lệnh GOTO sẽ được thực hiện.

Lệnh BTFSS: Lệnh này có nghĩa là “Bit Test Register F, And Skip If Set” tạm dịch là kiểm tra bit trong thanh ghi F và bỏ qua lệnh kế nếu=1. Nó giống như là lệnh BTFSC nhưng mà chỉ khác là con Pic sẽ bỏ qua lệnh kế tiếp nếu bit=1.

Lệnh CLRF: Lệnh này sẽ Clear nội dung trong thanh ghi hiện hành về 0, cú pháp là:

CLRF <register> Chúng ta đã dùng lệnh này trước đây để Clear ngõ ra Output của Port về 0 bằng cách dùng câu lệnh:

CLRF 05h

lệnh CLRW: Lệnh này giống y như lệnh CLRF nhưng mà chỉ khác là nó chỉ Clear thanh ghi W, cú pháp thì hoàn toàn đơn giản:

CLRW

Administrator
Highlight
Administrator
Highlight
Administrator
Highlight
Administrator
Highlight
Administrator
Highlight

Dson 18

Lệnh RLF và RRF: Lệnh này sẽ dịch bit trong thanh ghi sang vị trí bên trái (RLF) hoặc bên phải (RRF) của thanh ghi đó, ví dụ bạn có 00000001 và bạn dùng lệnh RLF thì bạn sẽ nhận được 00000010. Bây giờ hãy xem cái gì sẽ xảy ra nếu bạn có 10000000 và tiếp tục thực thi lệnh RLF?, đừng có hốt hoảng, bit 1 của bạn sẽ đi sang cờ Carry, nếu bạn lại tiếp tục RLF thì bit 1 sẽ quay trở về vị trí 0 trong byte. Mọi thứ sẽ diễn ra đúng như vậy đối với lệnh RRF nhưng mà bit sẽ di chuyển theo chiều bên phải. Ví dụ bên dưới biểu diễn lệnh RLF, bạn có nhình thấy chữ C là ký hiệu của cờ Carry, các con số 7654321 là thứ tự từ cao xuống thấp của 8bit trong thanh ghi. C 76543210 0 00000001 RLF 0 00000010 RLF 0 00000100 RLF 0 00001000 RLF 0 00010000 RLF 0 00100000 RLF 0 01000000 RLF 0 10000000 RLF 1 00000000 RLF 0 00000001

Chương trình Test: Bây giờ bạn sẽ xem một ví dụ, nếu muốn bạn có thể compile và cho nó chạy thử. Chương trình này làm cho đèn chạy bắt đầu từ bit0 của PortA sang tới bit8 của PortB rồi quay về thực thi lại từ đầu. Bạn hãy kết nối các con Led vào chân Port rồi cho chạy chương trình, bạn sẽ nhìn thấy các bit hoạt động như những gì mà ta đã nói từ trước đến giờ. TIME EQU 9FH ; Variable for the delay loop. PORTB EQU 06H ; Port B address. TRISB EQU 86H ; Port B Tristate address. PORTA EQU 05H ; Port A address. TRISA EQU 85H ; Port A Tristate address. STATUS EQU 03H ; Page select register. COUNT1 EQU 0CH ; Loop register. COUNT2 EQU 0DH ; Loop register. BSF STATUS,5 ; Go to page 1 MOVLW 00H ; and set up MOVWF TRISB ; both Ports A and B MOVLW 00H ; to Output, MOVWF TRISA ; then return to

Administrator
Highlight
Administrator
Highlight

Dson 19

BCF STATUS,5 ; page 0. MOVLW 00H ; Clear Port A. MOVWF PORTA ; ; Start of main program RUN MOVLW 01H ; Set the first bit MOVWF PORTB ; on Port B. CALL DELAY ; Wait a while CALL DELAY ;

; Move the bit on Port B left, then pause. RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 CALL DELAY CALL DELAY RLF PORTB,1 ; This moves the bit into the carry flag ; Now move onto Port A, and move the bit left. RLF PORTA,1 ; This moves the bit from the zero flag into PortA CALL DELAY CALL DELAY RLF PORTA,1 CALL DELAY CALL DELAY RLF PORTA,1 CALL DELAY CALL DELAY RLF PORTA,1 CALL DELAY CALL DELAY

; Move the bit back on Port A RRF PORTA,1 CALL DELAY CALL DELAY

Dson 20

RRF PORTA,1 CALL DELAY CALL DELAY RRF PORTA,1 CALL DELAY CALL DELAY RRF PORTA,1 ; This moves the bit into the zero flag

; Now move the bit back on Port B RRF PORTB,1 CALL DELAY CALL DELAY RRF PORTB,1 CALL DELAY CALL DELAY RRF PORTB,1 CALL DELAY CALL DELAY RRF PORTB,1 CALL DELAY CALL DELAY RRF PORTB,1 CALL DELAY CALL DELAY RRF PORTB,1 CALL DELAY CALL DELAY RRF PORTB,1 CALL DELAY CALL DELAY ; Now we are back where we started, ; GOTO RUN ; let's go again. ; Subroutine to give a delay between bit movements. DELAY

MOVLW TIME ; Get the delay time, MOVWF COUNT1 ; and put it into a variable.

LOOP1; DECFSZ COUNT1 ; Decrement 1 from the delay time until it GOTO LOOP1 ; reaches zero. MOVWF COUNT1 ; Get the delay time again,

LOOP2 ; and repeat the count down. DECFSZ COUNT1 ; GOTO LOOP2 ; RETURN ; End of subroutine.

END ;

Dson 21

Bảng dữ liệu ( Data Table): Có một điểm rất đặc biệt trong tập lệnh mà nó cho phép bạn truy xuất dữ liệu theo kiểu tra bảng (data table). Một bảng dữ liệu thông thường là một danh sách liệt kê các giá trị của dữ liệu, mỗi giá trị được đọc phụ thuộc vào việc phải thoả mãn vài tiêu thức nào đó. Ví dụ, bạn có một con Pic và bạn muốn đếm số lần ngõ vào Input được nâng lên mức cao trong thời gian 1giây là bao nhiêu sau đó hiễn thị lên Led 7 đoạn. Mỗi lần thời gian bắt đầu tính, con Pic sẽ đếm số lần ngõ Input vào được nâng lên mức cao trong thời gian 1 giây, sau 1 giây nó hiễn thị con số nó đếm được tương ứng với số lần ngõ vào Input được nâng lên mức cao. Cái này rất tiện dụng bởi vì chúng ta không biết được hiện tại con số đếm đã là bao nhiêu cho đến khi con Pic dừng lại, bằng cách sử dụng bảng tra dữ liệu chúng ta có thể cho phép con Pic quýêt định con số nào nó cần hiễn thị. Bây giờ trước khi giải thích bảng tra dữ liệu làm việc ra sao ta sẽ bàn xem con Pic bám theo chổ nào trong chương trình trong lúc chương trình đang chạy. Nếu bạn đã từng lập trình trong BASIC thì đở mệt nhọc hơn, còn nếu không bạn cũng đừng lo lắng, bạn sẽ vẫn tìm thấy các khái niệm ở đây. Hãy tưỡng tượng chúng ta có một chương trình BASIC như chương trình bên dưới: 10 LET K=0 11 K=K+1 12 IF K>10 THEN GOTO 20 ELSE GOTO 11 20 PRINT K 21 END Chương trình bắt đầu tại dòng số 10, mỗi lần K =0 nó tiến tới dòng 11, sau khi cộng thêm 1 cho K nó di chuyển đến dòng 12. Ở đây chúng ta hỏi K có lớn hơn 10 không ?, nếu đúng nó tiếp tục đi tới dòng 20, nếu sai nó quay trở lại dòng 11, dòng 20 sẽ xuất giá trị của K và dòng 21 sẽ kết thúc chương trình. BASIC sử dụng con số thứ tự dòng để giúp cho lập trình viên bám theo chương trình một khi những cái nhãn nhận dạng không cho phép sử dụng trong BASIC. Con Pic có sử dụng những cái nhãn để nhảy qua lại các vị trí hay không?, chúng ta dùng những cái nhãn nhận dạng vì vậy chúng ta biết những thứ gì, ở đâu và nói cho con Pic biết con đường nó phải đi. Cái mà thực sự con Pic đã dùng đó là bộ đếm dòng lệnh bên trong còn gọi là bộ đếm chương trình Program Counter. Program Counter viết

Administrator
Highlight

Dson 22

tắt là PC dò tìm các vị trí trong bộ nhớ để tìm kiếm vị trí hiện tại của câu lệnh mà chương trình đang thực thi. Khi chúng ta nói cho con Pic bíêt phải đi đến cái nhãn nào đó, nó bíêt vị trí của cái nhãn này trong bộ nhớ và nó gia tăng PC lên cho tới khi nó đọc được vị trí đó. Điều này giống y như cái cách mà chúng ta đọc chương trình trong BASIC Bên dưới là đoạn code và các vị trí bộ nhớ hay nói cách khác chính là nội dung trong PC, kế bên là các dòng lệnh.

PC Instruction 0000 movlw 03 0001 movwf 0C 0002 Loop decfsc 0C 0003 goto Loop 04 end

Trong ví dụ trên, ta set PC tới 0000. tại vị trí này ta có lệnh movlw 03. Khi con Pic thực thi lệnh này nó tăng PC lên và vì vậy nó đọc tiếp lệnh kế, ở đây con Pic lại thấy lệnh movwf 0C, nó lại tăng PC lên một lần nữa, lần này nó thấy lệnh decfsc 03, nếu nội dung trong địa chỉ 0C không = 0 con Pic sẽ tăng PC lên 1 và đọc lệnh kế tiếp, lệnh Goto loop nói con Pic hãy quay lại vị trí 0002. Nếu nội dung trong 0C là 0 thì con Pic nói PC phải tăng lên 2 hay nói cách khác là bỏ qua lệnh kế tiếp nó, như vậy nó sẽ đến vị trí 0004, tại đây là điểm kết thúc của chương trình. Các vị trí được thiết lập bởi assembler, và chúng ta không cần lo lắng con Pic đang làm cái gì cho tới khi chúng ta cần kiểm soát nó như trong trường hợp của bảng tra dữ liệu. Cách tốt nhất để giải thích bảng dữ liệu làm việc ra sao là hãy chấm dứt ngay cái ví dụ này và xem cái bên dưới đây !. PC equ 02 Movlw 03 Call table : table addwf PC retlw 01 retlw 02 retlw 03 retlw 04 retlw 05 retlw 06 retlw 07 return Lệnh đầu tiên gán cái nhãn PC có địa chỉ của Program Counter (02h), sau đó chúng ta đặt giá trị của thanh ghi 03h vào trong thanh ghi W. Bây giờ ta làm một lệnh gọi bảng tra dữ liệu. Dòng đầu tiên trong subroutine bảng dữ liệu sẽ công nội dung của thanh ghi W (03h) với PC, điều này làm cho PC tăng lên 3, tương đương với việc PC sẽ đi xuống 3 dòng. Khi PC xuống dòng thứ 3 con Pic trông thấy lệnh reltw, lệnh này chuyển giá trị đứng sau nó vào thanh ghi W rồi quay trở về lại subroutine. Lệnh RETLW có nghĩa là quay về và trả giá trị phía sau nó về thanh ghi W. Lưu ý là có 2 động tác được thực hiện trong lệnh RETLW. Khi ta đang đứng trong một subroutine ta cần có một lệnh quay về để thoát ra khỏi subroutine đó là lệnh RET.

Dson 23

Phía sau lệnh RETLW là một con số, con số này là thứ mà ta sẽ đặt vào trong thanh ghi W, trong trường hợp này nó là số 03. Chúng ta có thể gán cho thanh ghi W bất kỳ giá trị nào nhưng phải chắc chắn rằng con số này sau khi cộng với PC trong subroutine bảng tra dữ liệu sẽ tìm ra được một lệnh RETLW, trong ví dụ trên, điều này có nghĩa là ta có thể có bất kỳ con số nào từ 1 đến 7, nếu ta đi lọt ra ngoài subroutine thì có thể sẽ làm cho con Pic không thể thực hiện bất kỳ phần nào của chương trình nữa. Chính vì điều này mà người ta hay đặt bảng tra dữ liệu ở cuối của chương trình, như vậy nếu bị lọt ra khỏi subroutine thì sẽ đến điểm kết thúc chương trình (End).

Ngắt (Interrupt): Chủ đề nói về các Ngắt (Interrupts) hầu như là dài nhất và khó hiểu nhất, không phải dể để giải thích về ngắt cho người nào đó hiểu, nhưng mà hy vọng sau khi kết thúc phần này chúng ta có thể áp dụng ngắt vào trong chương trình của chúng ta. Chúng ta sẽ chia phần này thành 2 phần nhỏ, mụch đích là để cho bạn nghỉ giải lao !. Đầu tiên, Ngắt (interrupt) là cái gì vậy ?, nó thật sự có ý nghĩa giống như tên gọi của nó vậy, một Interrupt là một tác vụ xử lý hay là một tín hiệu xử lý mà nó có thể bắt con Pic dừng lại những gì đang làm để làm một công việc khác. Một ví dụ dể hiểu, hãy lấy sinh hoạt hàng ngày của bạn, giả sử bạn đang ngồi ở nhà, rồi bạn đang tán gẫu với ai đó, thình lình chuông điện thoại reo, bạn ngưng cuộc nói chuyện lại, nhặt điện thoại lên và nói chuyện với người gọi đến. Khi bạn kết thúc cuộc nói chuyện bằng điện thoại bạn lại quay trở về và tiếp tục tán gẩu với người đã nói chuyện với bạn trước khi điện thoại reo. Bây giờ bạn hãy tưởng tượng, chương trình chính là quá trình tán gẫu của bạn với người bạn ngồi ở nhà, điện thoại reo tạo ra một Interrupt và thủ tục (routine) Interrups là cuộc nói chuyện với người ở đầu dây bên kia, khi kết thúc cuộc nói chuyện bằng điện thoại bạn quay về “chương trình chính” để tiếp tục tán gẫu, Ví dụ này giải thích chính xác một Interrups tạo ra một tiến trình xử lý như thế nào. Một chương trình chính đang chạy, thực hiện một vài chức năng nào đó trên mạch điện, nhưng khi Interrupt xảy ra chương trình chính sẽ tạm ngưng và ngay lúc đó một thủ tục khác được thực hiện, khi thủ tục này kết thúc con Pic sẽ lại quay về chương trình chính. Con Pic có 4 Interrupt, nó có thể được chia thành 2 nhóm, 2 Interrupts phục vụ cho các thiết bị kết nối ngoại vi và 2 Interrupts cho bên trong nó. Trước tiên ta hãy nói về 2 Interrupts bên ngoài, 2 Interrups bên trong Pic sẽ nói đến trong phần Timers và lưu trữ Data. Nếu bạn quan sát trên sơ đồ chân của Pic bạn sẽ thấy chân số 6 có ghi là RB0/INT, RB0 là bit0 của PortB, ký hiệu INT là ký hiệu chức năng Interrupt ngoài. Ngoài ra các chân từ 10 đến 13 ( bit 4 tới 7 của PortB) cũng có thể sử dụng cho Interrupt. Trước khi sử dụng Interrupt hay dùng nó như là Port in out thông thường chúng ta cần phải làm 2 việc. Đầu tiên ta cần nói cho con Pic biết rằng ta sẽ sử dụng Interrupt, kế đến ta cần xác định chân nào của PortB sẽ dùng như Interrupt. Trong con Pic có 1 thanh ghi gọi là INTCON, địa chỉ là 0Bh, trong thanh ghi này có 8bit có thể thiết lập chế độ cho phép hay không cho phép. Bit7 của INTCON được gọi là GIE có nghĩa là Global Interrngupt Enable tạm dịch là chân cho phép sử dụng toàn bộ Interrup. Nếu set bit này lên 1 con Pic sẽ cho phép sử dụng Interrupt. Bit4 của INTCON gọi là INTE có nghĩa là INTerrupt Enable tạm dịch là cho phép Interrupt, set bit này lên 1 sẽ cho phép chân RB0 trở thành chân Interrupt. Bit3 còn gọi là bit RBIE nếu được set=1 sẽ báo cho con Pic biết ta sẽ sử dụng từ bit4 cho đến bit7 của PortB. Bây giờ thì con Pic đã biết và theo dõi khi nào chân này lên cao hay xuống thấp, nó biết cần phải dừng chương trình chính lại khi nào để quay ra phục vụ thủ tục của Interrupt. Bây giờ chúng ta cần nói cho con Pic biết sẽ khởi động Interrupt bằng cạnh lên (từ 0V lên 5V) hay cạnh xuống ( từ 5V xuống 0V) của tín hiệu vào chân Interrupt. Nói cách

Administrator
Highlight
Administrator
Highlight
Administrator
Highlight

Dson 24

khác, ta muốn con Pic phục vụ Interrupt khi tín hiệu vào thay đổi từ thấp lên cao hay từ cao xuống thấp. Mặc nhiên sau khi bật nguồn con Pic sẽ thiết lập chế độ Interrupt cạnh lên, có nghĩa là interrup xảy ra khi tín hiệu vào thay đổi từ thấp lên cao (cạnh lên) Thanh ghi OPTION ở địa chỉ 81h chính là thanh ghi thiết lập chế độ cho Interrupt tích cực ở cạnh lên hay cạnh xuống của tín hiệu vào, bit6 của thanh ghi OPTION được gọi là INTEDG, nếu setbit6=1 sẽ thiết lập interrupt tích cực ở cạnh lên của tín hiệu vào (trạng thái default) , nếu Clear bit6=0 sẽ thiết lập interrupt tích cực ở cạnh xuống của tín hiệu vào. Nếu bạn muốn con Pic thiết lập interrupt xảy ra ở cạnh lên của tín hiệu thì bạn không cần phải làm gì trên bit6 của thanh ghi OPTION. Thật không may mắn, thanh ghi OPTION lại nằm trên Bank1, vì vậy bạn phải làm động tác di chuyển từ Bank0 sang Bank1 sau đó Set bit6 trên thanh ghi OPTION rồi lại quay trở về Bank0. Có một mánh lới để làm tất cả chuyện này trên Bank1 như là thiết lập các chân Port, quay trở vào Bank0 !. Được rồi, cho đến giờ chúng ta đã biết chân nào của con PIC sẽ trở thành Interrupt và tích cực cạnh nào của tín hiệu, cái gì sẽ xảy ra trong chương trình và Interrupt xảy ra khi nào. Có 2 thứ xảy ra, thứ nhất là có 1 cờ ‘flag’ được set để nói cho con Pic biết rằng có 1 Interrupt đã xảy ra, thứ hai bộ đếm chương trình (program counter) trỏ đến một địa chỉ đặc biệt trong con Pic, hãy xem từng vấn đề như thế nào.

Cờ Ngắt (Interrupt Flag): Trong thanh ghi INTCON bit1 chính là cờ báo Interrupt gọi là INTF, khi có Interrupt xảy ra, cờ này sẽ được set lên 1, trước khi có Interrupt xảy ra nó =0. Trong khi cờ Interrupt được set lên 1 thì con Pic sẽ không thể và không bao giờ đáp ứng bất kỳ một Interrupt nào nữa. Cái cờ được set lên 1 và con Pic sẽ thực thi chương trình (routine) của Interrupt, nếu cái cờ vì lý do gì đó không thể set lên 1 và con Pic đang thực thi chương trình Interrupt thì tín hiệu đổ vào liên tục tại chân Interrupt sẽ liên tục gây ra Interrupt trên con Pic làm cho nó phải liên tục quay trở về điểm bắt đầu của chương trình (routine) Interrupt và sẽ không bao giờ nó có thể kết thúc được chương trình Interrupt này. Bây giờ quay lại ví dụ về chuyện tán gẫu và cuộc nói chuyện điện thoại của bạn, nó giống như là bạn vừa nhặt điện thoại lên định nói chuyện thì chuông lại reo lần nữa vì có ai đó cũng đang muốn nói chuyện với bạn!. Tại sao không phải là sau khi kết thúc cuộc chuyện trò với người thứ nhất bạn lại nhặt điện thoại lên một lần nữa để nói chuyện với ngưòi thứ hai, có phải tốt hơn không!, tôi đoán đó là lý do tại sao mà điện thoại không thể reo trong khi bạn đã nhấc ống nghe. Có một trở ngại nhỏ trên cái cờ này, mặc dù con Pic tự động set cờ này lên 1 nhưng nó lại từ chối trách nhiệm Clear cái cờ này về 0 ! vì vậy mà trách nhiệm cao cả này được trao cho người lập trình viên !, nếu không thì sẽ không bao giờ có interrupt xảy ra nữa. Cái này thì dể dàng thôi và tôi chắn chắn rằng bạn sẽ làm được.

Địa chỉ bộ nhớ: Memory Location Lần đầu tiên mở nguồn hoặc khi reset con Pic, Bộ đếm chương trình (Program Counter) trỏ đến địa chỉ 000h, đó chính là điểm bắt đầu của bộ nhớ chương trình. Tuy nhiên hki có Interrupt xảy ra thì PC sẽ trỏ đến địa chỉ 0004h, vì vậy khi viết chương trình mà có s73 dụng Interrupt thì đầu tiên chúng ta phải nói cho con Pic nhảy (jump) đến địa chỉ 0004h và tách riêng chương trình Interrupt ( bắt đầu tại 0004h) với các chương trình khác, điều này thì rất dễ làm có phải không ?. (dầu tiên chúng ta khởi động chương trình bằng lệnh ORG, lệnh này nghĩa là Origin, or start tạm dịch là điểm khởi đầu hay điểm khởi động, theo sau ORG là một địa chỉ xác định. Bởi vì con Pic khởi động tại 0000h nên chúng ta viết:

Administrator
Highlight
Administrator
Highlight
Administrator
Highlight

Dson 25

ORG 000h Kế đến chúng ta cần nhảy qua khỏi địa chỉ 0004h, bạn hãy dùng lệnh GOTO để làm điều này và theo sau GOTO là 1 cái nhãn mà nó sẽ trỏ tới điểm bắt đầu của đoạn code của chương trình chính. Sau đó ta đặt tiếp một ORG khác, vì ta đang nói đến Interrupt nên bạn phải đặt ORG 0004h Theo sau lệnh ORG 0004h chúng ta sẽ viết chương trình Interrupt hoặc có thể đặt 1 lệnh GOTO để nhảy đến chương trình Interrupt đặt ở đâu đó. Viết chương trình Interrupt theo sau ORG 0004h Hay dùng lệnh GOTO để nhảy đến 1 chương trình Interrupt đặt ở đâu đó thật sự là vấn đề để chọn lựa. Để chấm dứt 1 chương trình Interrupt ta cần đặt lệnh RTFIE tại cuối chương trình Interrupt đó, RTFIE có nghĩa là return from the interrupt routine tạm dịch quay trở về từ chương trình Interrupt, khi con Pic nhìn thấy lệnh RTFIE nó báo cho Program Counter biết để dời tới vị trí lần cuối cùng nó đứng trong chương trình chính trước khi Interrupt xảy ra, hãy xem một đoạn code ngắn bên dưới đây: ORG 0000h ;PIC starts here on power up and reset GOTO start ;Goto our main program ORG 0004h ;The PIC will come here on an interrupt : ;This is our interrupt routine that we : ;want the PIC to do when it receives : ;an interrupt RETFIE ;End of the interrupt routine start ;This is the start of our main program. Có 2 điều quan trọng mà bạn cần chú ý khi sử dụng Interrupt: Thứ nhất, nếu bạn sử dụng cùng một thanh ghi cho chương trình chính và cho Interrupt thì rất có thể nội dung của thanh ghi này bị thay đổi khi Interrupt xảy ra, ví dụ: bạn sử dụng thanh ghi W để gởi Data tới PortA trong chương trình chính và cũng dùng thanh ghi W trong Interrupt để di chuyển nội dung từ nơi này đến nơi khác, nếu bạn không cẩn thận thì thanh ghi W sẽ chứa giá trị cuối cùng trong chương trình Interrupt (khi interrupt xảy ra), và rồi, khi bạn quay về chương trình chính bạn lại gởi nội dung này vào PortA thay vì một nội dung khác trước khi Interrupt xảy ra. Cách đơn giản để tránh thảm hoạ này là bạn hãy lưu thanh ghi W vào vị trí tạm nào đấy và dùng nó lại sau khi chương trình Interrupt kết thúc. Thứ hai, đó là thời gian nghỉ bắt buộc giữa 2 lần interrupt xảy ra liên tiếp, như bạn biết, con Pic có một bộ dao động bên trong hoạt động bằng cách mắc với bên ngoài hoặc dùng thạch anh hoặc dùng mạch RC, tần số dao động này được chia 4 bên trong để tạo ra xung Clock làm nhịp cho 1 chu kỳ lệnh, Ví dụ: nếu thạch anh là 4MHz kết nối với con Pic thì 1 chu kỳ lệnh là: 4MHz/4 = 1MHz Bây giờ hãy xem hướng dẫn sử dụng cho con Pic của nhà sản xuất, phải có ít nhất là 3 đến 4 chu kỳ lệnh giữa 2 interrupt, tôi chọn và khuyên bạn cũng nên chọn 4 chu kỳ lệnh giữa 2 interrupt cho chắc ăn !. Lý do mà con Pic cần thời gian nghĩ giữa 2 lần Interrupt là nó phải làm đủ thứ chuyện như là nhảy đến địa chỉ Interrupt, set cờ interrupt, thoát ra khỏi chương trình interrupt. Như vậy, dựa trên những gì đã bàn trong phần trên, bạn phải lưu ý khi sử dụng mạch kết nối với các thiết bị ngoại vi kích hoạt interrupt của con Pic. Bây giờ lại có một thứ cần phải nhớ, đó là khi bạn sử dụng từ bit4 đến bit7 của PortB như Interrupt thì bạn không thể chọn riêng từng chân trên PortB để nó làm việc như Interrupt, nếu bạn cho phép (enable) những chân này thì bạn đã cho phép tất cả Trong phần tiếp theo chúng ta sẽ viết chương trình cho Interrupt

Dson 26

Interrupts – Chương trình Interrupt: Chương trình mà ta sẽ viết là đếm số lần 1 cái Switch bật on rồi hiễn thị con số đó. CHương trình sẽ đếm từ 0 đến 9, hiễn thị lên 4 Led dưới dạng Binary, ngõ vào interrupt là RB0. Đầu tiên ta cần phải báo cho con Pic nhảy đến địa chỉ mà bộ đếm chương trình sẽ trỏ đến khi Interrupt xảy ra, hãy lưu ý chúng ta sẽ sử dụng 1 cách khác để biểu diễn số Hex. Trước đây chúng ta hay viết F9h với h có nghĩa là hexadecimal, bây giờ chúng ta viết lại là 0xF9, và cái này chính là dạng mà chúng ta sẽ viết từ giờ trở đi. Org 0x00 ;This is where the PC points to on power up and reset Goto main ;Goto our main program Org 0x04 ;This is where our interrupt routine will start Retfie ;This tells the PIC that the interrupt routine has

;finished and the PC will point back to the main program main ;This is the start of our main program Bây giờ chúng ta cần nói cho con Pic biết rằng chúng ta sẽ sử dụng Interrupt và sử dụng RB0 (chân 6) như là chân Interrupt. bsf INTCON,7 ;GIE – Global interrupt enable (1=enable) bsf INTCON,4 ;INTE - RB0 interrupt enable (1=enable) Kế đến chúng ta xoá cờ Interrupt, mặc dù chúng ta đã nói khi mở nguồn lần đầu tiên thì cờ Interrupt mặc nhiên bị xoá về 0, nhưng mà tôi chưa bao giờ tin vào bất kỳ điều gì !. bcf INTCON,1 ;INTF - Clear flag bit just in case Và bây giờ setup 2 Port, nhớ rằng khi chúng ta sử dụng RB0 như một Interrupt thì ta phải setup nó như một ngõ vào Input. Bsf STATUS,5 ;Switch to Bank 1 Movw 0x01 ; Movwf TRISB ;Set RB0 as Input Movlw 0x10 ; Movwf TRISA ;Set the first 4 pins on PortA as Output Bcf STATUS,5 ;Come back to Bank 0 Chúng ta sẽ sử dụng biến COUNT để lưu số lần Switch On, bạn có thể hỏi tại sao chúng ta không làm đơn giản là tăng giá trị của PortA rồi đọc lại giá trị này, nhưng bạn sẽ biết lý do tại sao mà tôi sử dụng biến COUNT khi viết chương trình Interrupt. loop movf COUNT,0 ;Move the contents of COUNT into W movwf PORTA ;Now move it to Port A goto loop ;Keep on doing this end ;End of our program Chương trình chính đã có, bây giờ ta nói cho con Pic biết cái gì sẽ làm khi Interrutp xảy ra, trong trường hơp này Interrupt của chúng ta sẽ là cái Switch. Chúng ta muốn con Pic cộng thêm 1 vào biến COUNT mỗi lần cái Switch đóng lại. Nhưng mà PortA có 5 bit, nếu chúng ta chỉ đơn giản tăng Port lên 1 thì chúng ta sẽ có số đếm tối đa là 31.

Administrator
Highlight

Dson 27

Có 2 lý do mà tôi chọn không tăng lên đến 31. Thứ nhất chúng ta dùng Led 7 đoạn, mà thông thường nó chỉ biểu diễn được từ 0 đến 15 ( từ 0 đến Fh). Thứ hai, tôi cũng muốn biểu diễn vài thuật toán thông thường để bạn hiểu những thứ sẽ trình bày trong phần cuối của cuốn sách này. Cái đầu tiên chúng ta cần làm là lưu nội dung của thanh ghi W vào chỗ tạm thời vì chúng ta sẽ dùng W để tải nội dung của COUNT vào PortA, nếu không làm vậy có thể ta sẽ tải nội dung khác lên PortA chứ không phải COUNT. Movwf TEMP ;Store w register in a temporary location Kế tiếp ta muốn công 1 vào biến COUNT: Incf COUNT,1 ;Increment COUNT by 1, and put the result back into

;COUNT Kế đến chúng ta muốn kiểm tra xem COUNT đã lớn hơn 9 chưa bằng cách là lấy COUNT trừ cho 10. Movlw 0x0A ;Move the value 10 into w Subwf COUNT,0 ;Subtract w from COUNT, and put the result in w Trong các phần trước bạn đã biết, nếu ta lấy một số nhỏ trừ cho số lớn hơn thì cờ Carry sẽ set lên 1, ngoài ra cờ Carry cũng sẽ được set lên 1 khi chúng ta trừ 2 số bằng nhau. Btfss STATUS,0 ;Check the Carry flag. It will be set if

;COUNT is equal to, or is greater than w, ;and will be set as a result of the subwf instruction

Chúng ta muốn, nếu COUNT lớn hơn 9 thì đặt lại giá trị 0 cho nó, ngược lại sẽ quay về chương trình chính để xuất giá trị COUNT ra PortA Lệnh BTFSS như bạn biết là nó sẽ bỏ qua lệnh kế nếu cờ Carry =1. Trong truờng hợp này nếu COUNT=10:

goto carry_on ;If COUNT is <10, then we can carry on goto clear ;If COUNT is >9, then we need to clear it

carry_on bcf INTCON,0x01 ;We need to clear this flag to enable

;more interrupts movfw TEMP ;Restore w to the value before the interrupt retfie ;Come out of the interrupt routine

clear

clrf COUNT ;Set COUNT back to 0 bcf INTCON,1 ;We need to clear this flag to enable

;more interrupts retfie ;Come out of the interrupt routine Bây giờ hãy ráp lại tất cả các đoạn code lại với nhau. Bên dưới là 1 chương trình hoàn chỉnh, mạch điện trình bày sau chương trình này, mỗi lần bạn cho Switch On đèn Led sẽ đếm theo số Binary từ 0000 đến 1010 rồi quay trở về 0000.

Dson 28

org 0x00 ;This is where we come on power up and reset ;*******************SETUP CONSTANTS******************* INTCON EQU 0x0B ;Interrupt Control Register PORTB EQU 0x06 ;Port B register address PORTA EQU 0x05 ;Port A register address TRISA EQU 0x85 ;TrisA register address TRISB EQU 0x86 ;TrisB register address STATUS EQU 0X03 ;Status register address COUNT EQU 0x0c ;This will be our counting variable TEMP EQU 0x0d ;Temporary store for w register Goto main ;Jump over the interrupt address ;***************INTERRUPT ROUTINE*************** org 0x04 ;This is where PC points on an interrupt movwf TEMP ;Store the value of w temporarily incf COUNT,1 ;Increment COUNT by 1, and put the result

;back into COUNT movlw 0x0A ;Move the value 10 into w subwf COUNT,0 ;Subtract w from COUNT, and put the result in w btfss STATUS,0 ;Check the Carry flag. It will be set if

;COUNT is equal to, or is greater than w, and will be set ;as a result of the subwf instruction

goto carry_on ;If COUNT is <10, then we can carry on goto clear ;If COUNT is >9, then we need to clear it carry_on bcf INTCON,0x01 ;We need to clear this flag to enable more interrupts movfw TEMP ;Restore w to the value before the interrupt retfie ;Come out of the interrupt routine clear clrf COUNT ;Set COUNT back to 0 bcf INTCON,1 ;We need to clear this flag to enable more interrupts retfie ;Come out of the interrupt routine ;*******************Main Program********************* main ;*******************Set Up The Interrupt Registers**** bsf INTCON,7 ;GIE – Global interrupt enable (1=enable) bsf INTCON,4 ;INTE - RB0 Interrupt Enable (1=enable) bcf INTCON,1 ;INTF - Clear FLag Bit Just In Case ;*******************Set Up The Ports****************** bsf STATUS,5 ;Switch to Bank 1 movlw 0x01 movwf TRISB ;Set RB0 as Input movlw 0x10 movwf TRISA ;Set R 0 to RA3 on PortA as Output bcf STATUS,5 ;Come back to Bank 0 ;*******************Now Send The Value Of COUNT To Port A loop movf COUNT,0 ;Move the contents of Count into W movwf PORTA ;Now move it to Port A goto loop ;Keep on doing this end ;End Of Program

Dson 29

Sơ đồ mạch: Bên dưới là sơ đồ mạch mà nó sẽ làm việc với đoạn code bên trên, có 2 thứ mà sơ đồ mạch đã “ném” ra cho bạn, thứ nhất là mạch này không có tụ điện trong mạch dao động, cái này là một chút mẹo vặt, bởi vì chúng ta sử dụng các điện dung tản mạn giữa chân dao động của con Pic và mass trên mạch điện để thay thế các tụ điện mắc trong mạch dao động, như vậy điện trở và điện dung tản mạn trên mạch tạo thành khung dao động RC, nó có thể sẽ bị thay đổi tuỳ theo cấu hình của mạch điện. Thứ hai có một mạch chống rung cho các cái Switch, cái này thật sự cần thiết, vì khi bạn ấn Switch nó sẽ bị rung, lúc đóng lúc hở và con Pic có thể hiểu nhầm rằng bạn đã ấn Switch rất nhiều lần. Với mạch chống rung này, khi bạn ấn Switch tụ điện sẽ nạp khi bạn nhả Switch ra tụ điện sẽ xả từ từ, thời gian xả của tụ điện sẽ bỏ qua các lần rung của Switch.

Watchdog Timer: Bây giờ chúng ta bàn về một bộ định thời bên trong Pic gọi là Watchdog Timer, vậy Watchdog Timer là cái gì? Giả sử bạn viết một chương trình, bạn mong đợi chương trình này sẽ chạy nếu không có gì trục trặc xảy ra thì nó sẽ không bao giờ dừng lại, như vậy bạn phải làm một vòng lặp để khi chương trình chạy đến điểm cuối thì nó lại quay trở về điểm bắt đầu. Nhưng mà hãy xem một trường hợp: Giả sử chương trình kiểm tra một chân input, nếu nó lên mức cao thì con Pic sẽ tiếp tục kiểm tra một chân input thứ hai có lên mức cao hay không, nếu chân input thứ hai không lên mức cao, con Pic sẽ ngồi đó chờ và nó sẽ chỉ thoát ra khỏi chỗ ngồi của nó nếu chân input thứ hai lên mức cao. Bây giờ hãy xem một trường hợp khác, giả sử như bạn viết một chương trình, bạn compiled nó thành công, và ngay cả bạn đã cho chạy mô phỏng từng bước, từng bước một trên máy tính, bằng MPLAB chẳng hạn, có vẽ như mọi chuyện đều tốt, bạn đem nạp vào con Pic. Sau một thời gian chạy thử, con Pic thình lình bị kẹt vào nơi nào đó trong chương trình mà không thể thoát ra được trạng thái hiện tại. Điều gì là cần thiết để giải quyết hai trường hợp trên, reset lại hay vẫn để cho nó bị kẹt không thoát ra được ?, đó là mụch đích của mạch watchdog. Mạch watchdog thì không phải là mới mẽ gì, có rất nhiều microprocessors và microcontrollers đã có mạch watchdog, nhưng mà nó làm việc ra sao?.

Administrator
Highlight

Dson 30

Bên trong con Pic có một mạch RC, mạch này cung cấp 1 xung Clock độc lập với bất kỳ xung Clock nào cung cấp cho Pic. Khi Watchdog Timer (viết tắt là WDT) được cho phép (enabled), nó sẽ đếm bắt đầu từ 00 và tăng lên 1 cho đến FFh, khi nó tăng từ FFh đến 00 ( FFh+1) thì con Pic sẽ bị Reset bất kể đang làm gì, chỉ có 1 cách là ngăn không cho WDT đếm tới 00. Khi con Pic bị kẹt không thể thoát ra khỏi tình trạng hiện tại thì WDT vẫn tiếp tục đếm mà không bị bất kỳ điều gì ngăn cấm nó đếm tới FF và đến FF+1, vì vậy nó sẽ reset con Pic làm cho chương trình phải khởi động lại từ đầu. Để sử dụng WDT chúng ta cần làm 3 việc. Thứ nhất, cần thời gian bao lâu để reset WDT?. Thứ hai, làm sao xoá WDT?. Cuối cùng, chúng ta phải nói cho con Pic biết chương trình cho phép WDT hoạt động. Bây giờ bạn hãy xem từng cái một: Trong Datasheet của con Pic có nói rằng, WDT có thời gian từ lúc Start cho đến khi kết thúc là 18ms, tuy nhiên nó cũng phụ thuộc vào vài yếu tố, nguồn cung cấp, nhiệt độ của con Pic bởi vì mạch dao động của WDT là RC. Tuy nhiên chúng ta cũng có thể làm cho thời gian dài hơn. Bên trong con Pic có một cái gọi là Prescaler tạm dịch là đặt tỷ lệ, chúng ta có thể lập trình để chia xung Clock của mạch RC, chúng ta chia RC Clock càng nhiều thì thời gian WDT reset càng dài. Prescaler nằm trên thanh ghi OPTION có địa chỉ 81h từ bit0 đến bit2, bên dưới là bảng chia tỷ lệ thời gian WDT. Bit 2 1 0 Rate WDT Time

0 0 0 1:1 18mS 0 0 1 1:2 36mS 0 1 0 1:4 72mS 0 1 1 1:8 144mS 1 0 0 1:16 288mS 1 0 1 1:32 576mS 1 1 0 1:64 1.1Seconds 1 1 1 1:128 2.3Seconds

Hãy nhớ rằng các khoảng thời gian này không phụ thuộc vào tần số xung Clock bên ngoài, nó xác định bằng thời gian thực chứ không phải đếm chu kỳ xung clock. Hãy xem ví dụ WDT sẽ reset con Pic trong khoảng ½ giây khi con Pic bị kẹt. Giá trị gần nhất mà ta có theo bảng trên là 576mS hoặc 0.576 seconds. Đầu tiên chúng ta gởi giá trị b’101’ tới thanh ghi OPTION, như sau: movlw b’101’ ;This is 0x05 in Hex movwf 81h ;This is the Option Register Quá đơn giản !, bây giờ, có một mẹo nhỏ. Mặc nhiên prescaler được gán cho một bộ định thời khác, vì vậy ta phải thay đổi toàn bộ WDT. Trước tiên phải reset một bộ đếm khác tới giá trị 0, sau đó chuyển sang Bank1 để gán prescaler cho WDT và thiết lập thời gian rồi sau đó lại quay về Bank0, đoạn code bên dưới với xx là giá trị ta sẽ chọn cho prescaler. Bcf STATUS,0 ;make sure we are in Bank 0 Clrf 01h ;address of the other timer – TMR0

Dson 31

Bsf STATUS,0 ;switch to Bank 1 Clrwdt ;reset the WDT and prescaler movlw b’1xxx’ ;Select the new prescaler value and assign movwf OPTION ;it to WDT bcf STATUS,0 ;come back to Bank 0 Lệnh CLRWDT là để xoá WDT, chúng ta phải làm điều này trước khi nó reset con Pic, chúng ta cần tính toán nơi nào trong chương trình mà bộ đếm của WDT sẽ tràn để đặt lệnh CLRWDT trước thời điểm này để bảo đảm con Pic không reset. Nếu chương trình của bạn dài, có thể phải đặt hơn 1 lệnh CLRWDT trong chương trình. Ví dụ bạn sử dụng giá trị default mặc nhiên là 18ms thì phải bảo đảm rằng chương trình sẽ nhìn thấy lệnh CLRWDT sau mỗi 18ms. Bây giờ chúng ta phải tìm cho ra đoạn code của chúng thực thi trong thời gian thực là bao lâu, nguyên lý thì rất đơn giản nhưng mà có thể làm cho bạn dựng cả tóc lên đấy !.

Thời gian thực thi Lệnh (Instruction Timing): Như bạn đã biết, xung nhịp bên trong của Pic được gọi là chu kỳ lệnh, nếu dùng thạch anh 4MHz thì 1 chu kỳ lệnh là 1/(4MHz/4) = 1uS, một số lệnh chỉ thực thi mất 1 chu kỳ trong khi một số lệnh khác mất 2 chu kỳ để thực thi hoàn toàn, bạn hãy xem trong tập lệnh của Pic để biết thêm chi tiết. Cách để nhớ thì hoàn toàn đơn giản, giả sử tất cả các lệnh đều mất 1 chu kỳ, nhưng mà nếu lệnh đó làm cho chương trình nhảy tới nơi nào đó thì sẽ mất 2 chu kỳ, ví dụ: lệnh MOVWF mất 1 chu kỳ bởi vì lệnh này chỉ mang data từ nơi này sang nơi khác, lệnh GOTO mất 2 chu kỳ bởi vì nó làm cho Program Counter nhảy tới nơi nào đó trong chương trình, Lệnh RETURN cũng mất 2 chu kỳ bởi vì nó làm cho PC quay trở về đầu chương trình. Tuy nhiên có 4 lệnh mà nó có thể mất 1 hoặc 2 chu kỳ, đó là DECFSZ, INCFSZ, BTFSC và BTFSS, những lệnh này có một điểm chung đó là nó sẽ bỏ qua lệnh kế tiếp nếu nó thoả một điều kiện nào đó, ví dụ: Lệnh DECFSZ sẽ giảm giá trị trong thanh ghi F xuống 1, nếu kết quả khác 0 thì lệnh kế tiếp được thực thi, vì vậy nó mất 1 chu kỳ, nhưng nếu kết quả là 0 thì lệnh kế tiếp bị bỏ qua để thực thi lệnh đứng sau kế, trong trường hợp này lệnh thực thi mất 2 chu kỳ lý do là nó thay đổi giá trị của PC, nó cần 1 chu kỳ để thực hiện hàm và 1 chu kỳ nữa để thay đổi PC đến vị trí thoả điều kiện của hàm. Để rõ ràng hơn, hãy xem ví dụ bên dưới

Movlw 02 movwf COUNT

loop decfsz COUNT goto loop end

Lệnh đầu tiên mov giá trị 02 vào thanh ghi W, nó mất 1 chu kỳ, lệnh thứ hai cũng tương tự, 1 chu kỳ. Lệnh thứ 3, đầu tiên nó giảm COUNT xuống 1, cái này mất 1 chu kỳ, sau đó nó thử xem COUNT =0 chưa, trong trường hợp đầu tiên thì chưa xảy ra COUNT =0 , vì vậy nó đi tiếp tới lệnh kế, lệnh thứ 4 nhảy đến một cái nhãn, vì vậy nó mất 2 chu kỳ. Chúng ta quay trở lại lệnh thứ 3

decfsz COUNT lần này sau khi giảm COUNT xuống 1 thì COUNT =0, lệnh kế tiếp sẽ bị bỏ qua và nó nhảy đến End chấm dứt chương trình, hành động bỏ qua lệnh kế tiếp được thực hiện trong 1 chu kỳ khác, vì vậy khi ta đặt 02 vào COUNT thì chương trình này mất 7 chu kỳ, nếu thạch anh là 4MHZ thì:

Dson 32

1/(4MHz/4) = 1uS / chu kỳ 7 chu kỳ mất 7 x 1uS = 7uS Như vậy khi viết chương trình liên quan đến thời gian thực thi, bạn phải tính toán cẩn thận khi dùng các lệnh DECFSZ, INCFSZ, BTFSC và BTFSS. Bên trong con Pic có một thứ gọi là ‘Fuses’ tạm dịch là cầu chì, nó không giống như cầu chì fuses bảo vệ của ổ điện nhà mà nó giống như một cái Switch điện tử được đóng hay mở bởi lập trình viên. Làm sao mà những cái Fuses này được đóng hay mở để cho WDT hoạt động, có 2 cách để làm. Cách thứ nhất là viết 2 dòng lệnh tại phần đầu chương trình để nói cho Pic biết enable hay disable cái fuses nào đó. Cách thứ hai là nói cho con Pic biết cái fuses nào được enable. . We will look at getting your program to instruct the programming software in a later tutorial, when we look at including other files and macros. To tell the programming software manually, varies from program to program. The documentation that came with the programmer should tell you how to do this. As I am using the PICALLW software, which is linked on my main page, I will explain how to do change fuses within this program. The fuses are configured by pressing the F3 key, or clicking on the ‘Config’ button. Then you can select the fuse you want enabled, in this case the WDT, by clicking on the box next to it. Sample Program Let us write a program, where we will turn on the WDT, and let the PIC perform a function. We will first of all periodically clear the WDT, to show that the program works, and then remove the CLRWDT command to show that the PIC will indeed reset. The program I have chosen is the one used in tutorial 9 where we cause a row of LEDs to light up one at a time from left to right, then right to left. The circuit is shown below, and with the RC values shown will give us a clock frequency of 8KHz. This clock speed will allow us to actually see the LEDs moving one by one. I chose this program because it is slow enough for us to play with the WDT, and you can easily see when the PIC is reset. I have removed the original comments, and I have replaced them with a description of the WDT lines, a running total of the time from the start (assuming a 8KHz clock), and the number of clock cycles at each line. TIME equ 9FH ; Variable for the delay loop. PORTB equ 06H ; Port B address. TRISB equ 86H ; Port B Tristate address. PORTA equ 05H ; Port A address. TRISA equ 85H ; Port A Tristate address. STATUS equ 03H ; Page select register. COUNT1 equ 0CH ; Loop register. COUNT2 equ 0DH ; Loop register. bsf STATUS,5 ; 1 cycle, 0.5mS movlw 00H ; 1 cycle, 1.0mS movwf TRISB ; 1 cycle, 1.5mS movlw 00H ; 1 cycle, 2.0mS movwf TRISA ; 1 cycle, 2.5mS bcf STATUS,5 ; 1 cycle, 3.0mS movlw 00H ; 1 cycle, 3.5mS

Dson 33

movwf PORTA ; 1 cycle, 4.0mS ; Start of main program RUN movlw 01H ; 1 cycle, 4.5mS movwf PORTB ; 1 cycle, 5.0mS call DELAY ; 2 cycles, 486mS call DELAY ; 2 cycles, 967mS ; Move the bit on Port B left, then pause. rlf PORTB,1 ; 1 cycle, 967.5mS call DELAY ; 2 cycles, 1.45S call DELAY ; 2 cycles, 1.93S rlf PORTB,1 ; 1 cycle, 1.93S call DELAY ; 2 cycles, 2.41S call DELAY ; 2 cycles, 2.89S rlf PORTB,1 ; 1 cycle, 2.89S call DELAY ; 2 cycles, 3.37S call DELAY ; 2 cycles, 3.85S rlf PORTB,1 ; 1 cycle, 3.85S call DELAY ; 2 cycles, 4.34S call DELAY ; 2 cycles, 4.82S rlf PORTB,1 ; 1 cycle, 4.82S call DELAY ; 2 cycles, 5.30S call DELAY ; 2 cycles, 5.78S rlf PORTB,1 ; 1 cycle, 5.78S call DELAY ; 2 cycles, 6.26S call DELAY ; 2 cycles, 6.74S rlf PORTB,1 ; 1 cycle, 6.74S call DELAY ; 2 cycles, 7.22S call DELAY ; 2 cycles, 7.70S rlf PORTB,1 ; 1 cycle, 7.70S ; Now move onto Port A, and move the bit left. rlf PORTA,1 ; 1 cycle, 7.70S call DELAY ; 2 cycles, 8.19S call DELAY ; 2 cycles, 8.67S rlf PORTA,1 ; 1 cycle, 8.67S call DELAY ; 2 cycles, 9.15S call DELAY ; 2 cycles, 9.63S rlf PORTA,1 ; 1 cycle, 9.63S call DELAY ; 2 cycles, 10.11S call DELAY ; 2 cycles, 10.59S rlf PORTA,1 ; 1 cycle, 10.59S call DELAY ; 2 cycles, 11.07S call DELAY ; 2 cycles, 11.55S ; Move the bit back on Port A rrf PORTA,1 ; 1 cycle, 11.55S call DELAY ; 2 cycles, 12.04S call DELAY ; 2 cycles, 12.52S rrf PORTA,1 ; 1 cycle, 12.52S call DELAY ; 2 cycles, 12.99S call DELAY ; 2 cycles, 13.48S rrf PORTA,1 ; 1 cycle, 13.48S

Dson 34

call DELAY ; 2 cycles, 13.96S call DELAY ; 2 cycles, 14.44S rrf PORTA,1 ; 1 cycle, 14.44S ; Now move the bit back on Port B rrf PORTB,1 ; 1 cycle, 14.44S call DELAY ; 2 cycles, 14.92S call DELAY ; 2 cycles, 15.40S rrf PORTB,1 ; 1 cycle, 15.40S call DELAY ; 2 cycles, 15.89S call DELAY ; 2 cycles, 16.37S rrf PORTB,1 ; 1 cycle, 16.37S call DELAY ; 2 cycles, 16.84S call DELAY ; 2 cycles, 17.33S rrf PORTB,1 ; 1 cycle, 17.33S call DELAY ; 2 cycles, 17.81S call DELAY ; 2 cycles, 18.29S rrf PORTB,1 ; 1 cycle, 18.29S call DELAY ; 2 cycles, 18.77S call DELAY ; 2 cycles, 19.25S rrf PORTB,1 ; 1 cycle, 19.25S call DELAY ; 2 cycles, 19.73S call DELAY ; 2 cycles, 20.22S rrf PORTB,1 ; 1 cycle, 20.22S call DELAY ; 2 cycles, 20.70S call DELAY ; 2 cycles, 21.18S goto RUN ; 2 cycles, 21.18S ; Subroutine to give a delay between bit movements. ;Total of 957 cycles, 480mS DELAY movlw TIME ; 1 cycle movwf COUNT1 ; 1 cycle LOOP1 ; decfsz COUNT1 ; 9F x 1 cycle + 1 cycle = 160 cycles goto LOOP1 ; 9E x 2 cycles = 316 cycles movwf COUNT1 ; 1 cycle LOOP2 ; decfsz COUNT1 ; 9F x 1 cycle + 1 cycle = 256 cycles goto LOOP2 ; 9E x 2 cycles = 316 cycles return ; 2 cycles END ; With an 8KHz clock, it takes just under 1 second for the next LED illuminates, and it takes a total of about 21 seconds to run from one end to the other and back again i.e. to go through the routine once only. The delay routine takes 480mS, and we are calling it twice before moving the bit on the Ports. Now, we need to periodically reset the WDT. The largest time we can set the WDT is 2.3 seconds, and the next one down form this is 1.1 seconds. We have two options here. We could make a call to a subroutine to clear

Dson 35

the WDT after the two delays have finished, or we could incorporate the CLRWDT within the delay itself. I have decided, for no real reason at all, to incorporate the CLRWDT within the delay loop. TIME equ 9FH ; Variable for the delay loop. PORTB equ 06H ; Port B address. TRISB equ 86H ; Port B Tristate address. PORTA equ 05H ; Port A address. TRISA equ 85H ; Port A Tristate address. STATUS equ 03H ; Page select register. COUNT1 equ 0CH ; Loop register. COUNT2 equ 0DH ; Loop register. OPT equ 81h ; Option Register to control the WDT ;*************Set up the Ports, WDT and prescaler****************** clrf 01h ;Clear TMR0 bsf STATUS,5 ;Switch to Bank 1 clrwdt ;reset the WDT and prescaler movlw b’1101’ ;Select the new prescaler value and assign movwf OPT ;it to WDT movlw 00H ; Now set up the Ports movwf TRISB ; movlw 00H ; movwf TRISA ; bcf STATUS,5 ;Come back to Bank 0 movlw 00H ; movwf PORTA ; ;*************Start of main program***************************** RUN movlw 01H ; movwf PORTB ; call DELAY ; call DELAY ; ; *************Move the bit on Port B left, then pause.************** rlf PORTB,1 ; call DELAY ; call DELAY ; rlf PORTB,1 ; call DELAY ; call DELAY ; rlf PORTB,1 ; call DELAY ; call DELAY ; rlf PORTB,1 ; call DELAY ; call DELAY ; rlf PORTB,1 ; call DELAY ; call DELAY ;

Dson 36

rlf PORTB,1 ; call DELAY ; call DELAY ; rlf PORTB,1 ; call DELAY ; call DELAY ; rlf PORTB,1 ; ; *************Now move onto Port A, and move the bit left.*********** rlf PORTA,1 ; call DELAY ; call DELAY ; rlf PORTA,1 ; call DELAY ; call DELAY ; rlf PORTA,1 ; call DELAY ; call DELAY ; rlf PORTA,1 ; call DELAY ; call DELAY ; ;************** Move the bit back on Port A************************ rrf PORTA,1 ; call DELAY ; call DELAY ; rrf PORTA,1 ; call DELAY ; call DELAY ; rrf PORTA,1 ; call DELAY ; call DELAY ; rrf PORTA,1 ; ;****************** Now move the bit back on Port B****************** rrf PORTB,1 ; call DELAY ; call DELAY ; rrf PORTB,1 ; call DELAY ; call DELAY ; rrf PORTB,1 ; call DELAY ; call DELAY ; rrf PORTB,1 ; call DELAY ; call DELAY ; rrf PORTB,1 ; call DELAY ; call DELAY ; rrf PORTB,1 ; call DELAY ; call DELAY ; rrf PORTB,1 ;

Dson 37

call DELAY ; call DELAY ; goto RUN ; ; ******************Subroutine to give a delay between bit movements.****** DELAY movlw TIME ; movwf COUNT1 ; LOOP1 ; decfsz COUNT1 ; goto LOOP1 ; movwf COUNT1 ; LOOP2 ; decfsz COUNT1 ; goto LOOP2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; This part resets the WDT ;; ;;Comment out or remove this command to see the WDT ;; ;; in action. It should reset the PIC ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; clrwdt ;This simply resets the WDT. ;***************Return from our original DELAY routine*************** return ; END ; If you comment out, or remove the CLRWDT command, you will find that the PIC will not go past lighting the second LED. This is because the WDT is resetting the PIC. With the CLRWDT in place, the program works as it should.