PDA

View Full Version : Macro delay cho Pic18, sai số 1 chu kỳ lệnh


thaile
18-08-2008, 02:05 PM
Thông thường sau khi viết chương trình bằng ASM, để sử dụng lại đoạn code đã viết cho một chương trình khác ta thường copy và paste. Bài viết này hướng dẫn viết một macro thư viện, tiện cho việc sử dụng lại các module đã viết. Và mình sẽ bắt đầu với module delay cho Pic18

Một số điểm lưu ý với macro delay này:
1. Thời gian: 1 cycles = 4 xung clock. Ví dụ: Pic hoạt động ở tần số 20Mhz tức là một giây Pic thực hiện 20*10^6 xung clock.
2. Lệnh Goto address: Lệnh này là 1 lệnh 2 words, thời gian thực hiện là 2 cycles. Khi dịch ra hex nó bao gồm 1 word... mà mình không biết (hi hi) và 1 word là lệnh NOP. Ta quan tâm đến cái NOP này. Ví dụ:
Goto $+d'4'
movlw d'10'
Do MPASM định offset theo byte. 2 words=4bytes. Lệnh này sẽ nhảy đến vị trí lệnh kế tiếp là lệnh movlw d'10', mất 2 cycles
Nếu thay lệnh trên là "Goto $+d'2' thì nó sẽ nhảy đến cái word thứ 2 của nó là NOP. Do đó lúc này để đến được lệnh movlw d'10' nó mất 3 cycles.
3. Lệnh decfsz f,d,a: Trừ thanh ghi f cho 1. Bỏ qua lệnh kế tiếp nếu kết quả bằng 0. Nếu d=0, kết quả chứa trong WREG, d=1 thì chứa trong thanh ghi f. a=0 sử dụng access bank, a=1 sử dụng BSR để tham khảo bank hiện tại.
Thời gian thực hiện: f!=0--> 1cycles. f=0 -->Lệnh tiếp theo nếu là 1 word thì 2 cycles; còn nếu lệnh tiếp theo là 2 word, thời gian thực hiện sẽ là 3 cycles. Ví dụ: với d1=1
decfsz d1,d'1',d'0'
Goto address
Sau khi trừ thì d1=0 nó sẽ bỏ qua lệnh kế tiếp tức là word thứ 1 của lệnh goto, nó sẽ thực hiện word thứ 2 của lệnh goto... Nhưng mà hehe... mình đã nói ở trên word thứ 2 là NOP. Cho nên lúc này nó cần tới 3 cycles. Như vậy, trong cả 2 trường hợp là d1=0 và d1!=0 thì tổng thời gian thực hiện đoạn lệnh nhỏ ở trên là 3 cycles.

Rồi, đủ rồi... Here we go.
Các link tham khảo:

http://www.piclist.com/techref/piclist/codegen/delay.htm
http://www.piclist.com/techref/microchip/PIC16DelayTutorial.htm

Chúng ta sẽ làm gì đầu tiên? Đầu tiên tất nhiên là suy nghĩ roài. Haizzz, giả sử như-việc viết một chương trình delay mà khi được gọi đến nó sẽ delay đúng thời gian mà ta qui định-là khả thi. Như vậy, lúc này nó bao gồm thêm một lệnh call hoặc rcall và 1 lệnh return. 2 lệnh này mỗi lệnh thực hiện trong 2 cycles. Như vậy để sử dụng được delay thì khoảng thời gian ít nhất phải là 4 cycles. Việc này tạm thời gác lại vì nó hoàn toàn có thể giải quyết được bằng macro. Vấn đề bây giờ là tìm được thuật toán để delay một khoảng thời gian thích hợp với thuật toán đó (hix...). Xét đoạn chương trình sau:
decfsz d1,d'1',d'0'
Goto $-d'2'
Nếu d1!=0 thì nó sẽ nhảy về lệnh decfsz. Như vậy đoạn code mất tổng cộng là 3*d1 cycles. Để sử dung được lệnh này làm chương trình con tất nhiên phải có thêm:

delay_1l ; nhãn của chương trình con
decfsz d1,d'1',d'0'
Goto $-d'2'
return
....
....
movlw gtth1
movwf d1
call delay_1l

Lúc này tổng số chu kì là 1+1+(1+2)*d1+2+2=3*d1+6. Với d1 nhỏ nhất sẽ là 1, lớn nhất là 256 (d'256'=h'100',vì d1 là số 8 bit nên lúc này d1 sẽ là 00). Khoảng thời gian delay thích hợp cho chương trình này lúc này là: 9 cycles <= cycles_need <= 774 cycles. Sai số lớn nhất là 2, với các cycles_need không chia hết cho 3.

Xét đoạn chương trình tiếp theo:

decfsz d1,d'1',d'0'
Goto $-d'2'
decfsz d2,d'1',d'0'
Goto $-d'8'

Lúc này sau khi thực hiện hết 3*d1 cycles, nó sẽ thực hiện lệnh decfsz d2. Đến lệnh goto $-d'8' lại quay về đầu đoạn code. Với 1 chu kì lớn đầu tiên sẽ mất (3*d1+3) cycles. Với chu kì lớn thứ 2, giá trị d1 lúc này là 00 (tương đương d'256', như đã nói ở trên). Cho nên từ chu kì lớn thứ 2 sẽ mất: (3*256+3)=771. Tổng thơi gian để hoàn tất tất cả các chu kì lớn: (3d1+3)+ 771(d2-1)= 771d2+3d1-768. Bước tiếp theo thực hiện y chang ở trên luôn:

delay_2l ; nhãn của chương trình con

decfsz d1,d'1',d'0'
Goto $-d'2'
decfsz d2,d'1',d'0'
Goto $-d'8'
return
....
....
movlw gtth1
movwf d1
movlw gtth2
movwf d2
call delay_2l

Lưu ý là d1,d2 phải lớn hơn 0. Nếu ta cho nó bằng 0 thì với chương trình delay nó sẽ tương đương với d'256'
Thời gian delay thích hợp cho đoạn chương trình delay_2l là: 14 cycles<=cycles_need<=197384 cycles.
.................................................. .............
Bây giờ nói đến macro. Cái macro dùng để làm gì? Nói nôm na thì nó được sử dụng để sinh mã đúng với mong muốn của người lập trình. Trong trường hợp này là: nếu cycles_need có giá trị trong một đoạn nào đó thì ta đặt một đoạn code delay tương ứng.
_ Với 0<= cycles_need<= 8: ta sử dụng các lệnh goto và nop để tạo delay
_ Với 9<= cycles_need<=774: ta sử dụng delay_1l
_ Với 775<= cycles_need<=197384: ta sử dụng delay_2l
Tham khảo về macro, các directives if...else...end, while...endw và các chỉ dẫn dành cho biên dịch trong phần help.

Macro delay_cycles sẽ gọi 1 macro tương ứng tùy theo giá trị của cycles cần delay

delay_cycles macro cycles
if ((d'0'<=cycles)&&(cycles<=d'8'))
delay_0l cycles
endif
if (d'9'<=cycles)&&(cycles<=d'774')
delay_1l cycles
endif
if (d'775'<=cycles)&&(cycles<=d'197384')
delay_2l cycles
endif
endm

Macro delay_0l được tham khảo đến khi ((d'0'<=cycles)&&(cycles<=d'8')).

delay_0l macro cycles
primary=cycles/d'3'
remainder=cycles%d'3'
while primary>d'0'
goto $+d'2'
primary-=d'1'
endw
if remainder==d'0'
endif
if remainder==d'1'
nop
endif
if remainder==d'2'
goto $+d'4'
endif
endm

Tương tự Macro delay_1l và delay_2l được tham khảo tới tùy vào giá trị cycles thích hợp.

delay_1l macro cycles
primary1=cycles/d'3'
remainder=cycles%d'3'
movlw primary1
movwf d1
decfsz d1
goto $-d'2'
delay_0l remainder
endm


delay_2l macro cycles
primary2=(cycles+d'768')/d'771'
remainder=(cycles+d'768')%d'771'
if remainder<d'3'
primary2-=d'1'
primary1=d'256'
remainder+=d'3'
else
primary1=remainder/d'3'
remainder=remainder%d'3'
endif
movlw primary2
movwf d2
movlw primary1
movwf d1
decfsz d1
goto $-d'2'
decfsz d2
goto $-d'8'
delay_0l remainder
endm

Đến đây thì phần việc của chúng ta vẫn chưa xong, vì ta mới tạo ra một macro sinh mã delay mỗi lần được gọi đến và chưa tối ưu hóa để chương trình sinh mã nhỏ nhất. Xét đoạn macro sau:

label macro [arg1,arg2...]
ifndef loc_label
bra end_loc_label
#define loc_label
location_label
.... ; Đặt đoạn code cố định của macro
....
....
return d'0'
endif
end_loc_label
.... ; Đoạn code thay đổi của macro
....
call location_label
endm

Ví dụ với delay_1l thì đoạn code cố định là:

decfsz d1
goto $-d'2'
decfsz d2
goto $-d'8'

Đoạn code thay đổi là:

movlw primary2
movwf d2
movlw primary1
movwf d1

Việc làm này mang ý nghĩa muốn sử dụng lại các đoạn code đã sử dụng rồi để đỡ tốn dung lượng chương trình. Với chương trình nhỏ như delay thì doạn code cố định nhỏ, nên có lẽ là không cần thiết, nhưng mình muốn giới thiệu nó cho mọi người khi gặp đoạn code lớn. Ưu điểm là khi lần đầu tiên ta gọi Macro thì nó sẽ đặt đoạn code cần thiết ngay tại điểm đó, để khi sử dụng cho lần sau thì nó sẽ gọi đến mà không cần phải sinh thêm mã một cách không cần thiết.

Phần việc còn lại là hoàn thiện đoạn Macro delay_0l, delay_1l, delay_2l , thêm vào delay_3l với thời gian delay lớn hơn. Thêm macro delay_us, delay_ms, và delay_s bằng cách quy đổi thời gian cần delay ra cycles. Muốn vậy thì phải thêm 1 macro được người dùng sử dụng để cho biết tần số của Pic18 được sử dụng, ta đạt là macro use_delay

use_delay macro _fosc
fosc=_fosc
endm
delay_us macro us
cycles=(us*fosc)/d'4'
delay_cycles cycles
endm

delay_ms macro ms
cycles=(d'1000'*ms*fosc)/d'4'
delay_cycles cycles
endm

delay_s macro s
cycles=(d'1000000'*s*fosc)/d'4'
delay_cycles cycles
endm

Cuối cùng, sau khi hoàn thiện ta có được các macro delay: delay_cycles, delay_us, delay_ms, delay_s cho pic18 chính xác với sai số chỉ 1 chu kì, và sinh mã tiết kiệm dung lượng.
Có 3 lưu ý nhỏ khi sử các Macro này:
1> Sai số 1 cycles là do khi qui đổi từ thời gian us, ms, s; sang cycles.
2> Sau khi sử dụng các Macro này thì thanh ghi WREG bị thay đổi
3> Do chỉ sử dung "movwf f_reg" nên theo mặc định trong MPASM thì chỉ sử dụng access bank dể tham khảo đến thanh ghi d1,d2,d3. Do đó cần phải đặt 3 thanh ghi này ở bank thích hợp
4> Thời gian delay tối đa là 50,529,034 cycles. Nếu muốn thêm bạn có thể viết lại hoặc viết thêm macro delay_4l
5> Cách sử dụng Macro trên cho Pic18: sử dụng đơn giản, trước tiên phải chỉ định tần số hoạt động của Pic18, tất nhiên là không quên phần include file vào
use_delay d'20' ; nếu tần số hoạt động là 20Mhz
delay_us d'50' ; delay khoảng thời gian là 50 us
Do không thể giải thích chi tiết về các dòng lệnh trong macro nên nếu bạn nào có thắc mắc thì cứ tự nhiên đặt câu hỏi.

Thân

namqn: bạn chú ý tránh sử dụng Unicode tổ hợp.