Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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:
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:
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.
Đ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:
Con Led bây giờ đã bật on, chúng ta thử tắt nó xem:
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
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ú:
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:
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.
Thì COUNT sẽ tương đương với giá trị 85h. Thật là quỷ quyệt, có phải không ?!
Dson 6
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.
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.
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.
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.
;****Add a delay
call Delay
;****Delay finished, now turn the LED off****
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
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.
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:
Để 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 !.
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.
Start
movlw 02h ;Turn the LED on by first putting it
Movwf PORTA ;into the w register and then on the Port
Đầ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.
;****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.
call Delay
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
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.
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à:
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
Dson 18
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
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ờ.
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
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
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
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:
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:
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.
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ì !.
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.
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.
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.
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.
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:
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
;*******************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?.
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.
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:
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
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.
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.
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 ;
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.
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.