Các lỗi hay gặp khi lập trình mplab

Mở đầu Sau nhiều năm có mặt tại Việt Nam, hiện nay dòng vi điều khiển PIC vẫn thể hiện ưu thế của nó. Từ các ứng dụng nhỏ tới các ứng dụng lớn ta đều có thể chọn loại PIC phù hợp với ứng dụng của mình mà giá thành vẫn phù hợp. Ở Việt Nam hiện nay phát triển các ứng dụng thường sử dụng các trình biên dịch ngôn ngữ C cho vi điều khiển như CSS, Keil C compiler… Bởi vì lí do đơn giản là các hàm, các thư vi ện mà các trình dịch này cung cấp tương đối đầy đủ và khá dễ sử dụng. Tuy nhiên việc phát triển các trình ứng dụng lớn và mang tính chuyên nghiệp hơn thì các trình dịch này hầu như vẫn chưa mang lại. 1 đặc tính nữa là trình dịch này không phải do chính hãng sản xuất phần cứng Microchip cung cấp do vậy tính mới và tính chuyên nghiệp sẽ không được cao như chính do nhà sản xuất cung cấp. MPLAB là môi trường lập trình tích hợp do chính Microchip cung cấp, đi kèm với nó ngôn ngữ lập trình phù hợp với từng dòng vi điều khiển. Pic16 có ngôn ngữ C16, Pic 18 có ngôn ngữ C18, Pic 30 có C30…Theo tôi thấy các thư viện hàm, các ví dụ ứng dụng do Microchip cung cấp khá đầy đủ và chuyên nghiệp. Hiện nay việc phát triển các ứng dụng trên nền MPLAB ở nước ta vẫn chưa phổ biến. Với mong muốn góp 1 phần nhỏ và rút ngắn thời gian học tập của các bạn mới làm quen với môi trường này. Trong tài liệu này tôi xin giới thiệu những nét chính về ngôn ngữ C18 và các ứng dụng trên dòng Pic18 sử dụng ngôn ngữ này

1

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Mục lục Phần I: Ngôn Ngữ C18 ........................................................................................................................ 3 1.

Kiểu dữ liệu và các giới hạn ................................................................................................. 3

2.

Sự mở rộng của C18 ................................................................................................................. 9

3.

pragma sectiontype ............................................................................................................... 11

4.

Pragma interruptlow và

Pragma interrupt ............................................................. 17

5.

Pragma tmpdata [se ction-name] .................................................................................... 26

6.

pragma varloc ate bank variable -name và

pragma varlocate "section name" variable-name ........................................................................Error! Bookmark not defined. 7.

pragma conf ig ........................................................................................................................ 31

8.

Processor-Specif ic Header Files ....................................................................................... 33

9.

Processor-Specific Register Definitions Files ............................................................................. 37

Phần II: Lập trình PI C18 b ằng MPLAB C18 ....................................................................38 1.

2.

Sử dụng MPLAB ...................................................................................................................... 38 1.1.

Quản lí Project ................................................................................................................. 38

1.2.

Các bước tạo ra f ile.he x ............................................................................................... 40

1.3.

Cửa sổ tiện ích s ử dụng trong MPLAB .................................................................. 54

Ví dụ lập trình Pic18 bằng C18 ........................................................................................ 55 2.1.

I/O cơ bản và delay ........................................................................................................ 56

2.2.

RESET ................................................................................................................................. 63

2.3.

Sleep ..................................................................................................................................... 70

2

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Phần I: Ngôn Ngữ C18 Ngôn ngữ này được xây dựng trên nền ngôn ngữ C cơ sở. Chính vì vậy đối với những ai đã quen câu lệnh lập trình C rồi thì việc nắm bắt và sử dụng nó sẽ trở nên đơn giản hơn rất nhiều. 1. Kiểu dữ liệu và các giới hạn Giống như ngôn ngữ lập trình C cơ sở và các ngôn ngữ lập trình khác kiểu dữ liệu là cơ sở trong lập trình. C18 có các kiểu dữ liệu sau: • Kiểu số nguyên: bảng sau trình bày các kiểu số nguyên [ tên, độ rộng, giới hạn max-min ]sử dụng trong C18

Hình 1.1

Kiểu char là kiểu mặc định trong C18 nếu ta ko khai báo kiểu dữ liệu phía trước. 3

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

• Kiểu dấu phảy động C18 hỗ trợ khai báo 2 kiểu dấu phảy động là float và double với chi tiết về độ động, giới hạn min-max [ hình 1.2]

Hình 1.2

Ví dụ: ta khai báo Char var1; Float var2

// độ rộng 8 bit // độ rộng 32bit

 C18 lưu dữ liệu kiểu endianness Endianness là khái niệm cơ bản trong điện tử mà ta đã làm quen trong cấu trúc máy tính: nó có nghĩa là lưu những byte ít quan trọng nhất ở vùng địa chỉ thấp nhất, và ngược lại những byte quan trọng nhất thì ở vùng cao nhất. Ví dụ: trong trình dịch C18 ta có đoạn mã sau

Hình 1.3 4

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Ta có thể thấy rằng biến l có byte DD thấp nhất thì được lưu vào trong vùng nhớ thấp nhất, byte AA cao nhất thì được lưu vào vùng nhớ cao nhất

 Các lớp của vùng lưu trữ MPLAB C18 hỗ trợ các lớp lưu trữ chuẩn ANSI như auto, extern, register, static và typedef. Chúng ra nhắc lại 1 chút về các chuẩn lưu trữ này: - Auto: là biến ở trong 1 hàm, tức là nó chỉ xuất hiện trong hàm, khi 1 hàm thực thi xong nó sẽ mất - Extern: là kiểu biến dùng chung cho ở những file khác nhau, tức là biến được khai báo kiểu extern trong file1.* có thể sử dụng trong file2.* khi trong file2.* có lệnh

include Ví dụ: File1.c: extern int x;

// biến ‘x’ kiểu extern

char c; function[int m] {

int var;

// biến auto

} File2.c

include

5

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

…. x=1234;

// sử dụng x mà ko cần khai báo lại

- Register: khai báo này sẽ báo cho trình dịch biết biến được sử dụng nhiều và vì thế biến đó được lưu trong thanh ghi với mục đích làm truy cập dữ liệu nhanh. Khai báo này chỉ được sử dụng trong biến formal của hàm và biến auto, nếu khai báo biến extern dạng này thì trình dịch sẽ bỏ qua và coi như không có khai báo register register int x;

// khai báo này trình dịch

register char c; // sẽ coi như ko có khai báo register function[register unsigned m, register long n]

//

khai

báo

sử

dụng

trong biến formal

{ register int i;

// khai báo sử dụng trong biến auto

... } - Static: là loại biến được cấp phát tĩnh. Nó khác với biến auto ở chỗ là lifetime [thời gian tồn tại] trong toàn bộ thời gian chạy của chương trình. Nhưng lại khác so với biến extern ở chỗ là nó được cấp phát tĩnh [allocated statically] còn biến extern được cấp phát động [allocated dynamically]. Khái niệm cấp phát tĩnh và cấp phát động giống với khái niệm trong cấu trúc máy tính. Tôi xin nói qua đ ể bạn dễ hình dung: Cấp phát tĩnh có nghĩa là biến đó được lưu trong bộ nhớ stack [ko phải bộ nhớ heap] và nó được cấp phát vùng nhớ trong khi biên dịch chương

6

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

trình, tức là biến đó được cấp phát vùng nhớ trước khi chương trình của ta được chạy. Còn ngược lại, cấp phát động thì biến được cấp phát bộ nhớ trong khi thực hiện chương trình. Để trực quan ta xem ví dụ:

include

void func[] { static int x = 0; // x is initialized only once across three calls of func[]

printf["%d\n", x]; // outputs the value of x x = x + 1; }

int main[int argc, char * const argv[]] { func[];

// prints 0

func[];

// prints 1

func[];

// prints 2

return 0; /* nếu biến ta khai báo x ko phải là biến static thì sẽ hiển thị lên màn hình là 0 0 0 chứ ko phải là 0 1 2 như trên. Chỉ đơn giản vậy thôi. Ta kết thúc vấn đề này ở đây*/ }

Các bạn muốn chi tiết thêm về các khai báo và ý nghĩa vùng lưu trữ này có thể xem trong các sách lập trình C. Tôi thấy cuốn sách ‘C Programming Language’ của tác giả W.Kernighan & M.Ritchie viết rất hay, các bạn có thể download trên mạng hoặc tại đây. Ngoài các định nghĩa vùng lưu trữ chuẩn ở trên MPLAB còn cung cấp thêm 1 khai báo lưu trữ nữa là overlay. Kiểu này chỉ áp dụng khi trình dịch hoạt động trong chế độ Non-extended và chỉ áp dụng cho biến địa phương [biến auto]. Như ở trên tôi trình bày phần biến static, vấn đề gặp phải của biến x là khi gọi lại hàm biến x giữ lại giá trị

7

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

được tính toán [tức là ko khởi tạo lại giá trị =0 khi gọi hàm] nhưng đôi khi ta không muốn điều đó, vì thế biến kiểu overlay chính là giải quyết vấn đề này. Tóm lại biến kiểu overlay là biến cấp phát tĩnh nhưng khi gọi hàm thì nó sẽ được khởi tạo lại. Ta xem ví dụ dưới sẽ hiểu rõ: void f [void] { overlay int x = 5; x++; /* ở đây biến x được cấp phát tĩnh như biến x ở ví dụ trên nhưng sẽ được khởi tạo lại bằng 5 khi gọi lại hàm chứ ko giữ lại giá trị. Khá dễ hiểu, chúng ta kết thúc vấn đề này ở đây */ }

Ta cũng có section [ đoạn dữ liệu lưu ở trong bộ nhớ] có thể khai báo đặc tính kiểu overlay . Tác dụng của khai báo này như thế nào tôi sẽ nhắc lại phía dưới.  Đối số của hàm Đối số của hàm trong C18 có thể là lớp auto hoặc static. Mặc định là auto. Nếu là tham số static thì trình dịch phải hoạt động trong chế độ Non-extended  Qualifiers vùng lưu trữ MPLAB C18 giới thiệu các qualifier far, near, rom, ram. Phạm vi và vùng nhớ dựa trên Qualifiers theo bảng sau: Rom Far

Near

Ram Mọi nơi trong bộ nhớ dữ

Mọi nơi trong bộ nhớ

liệu [mặc định]

Trong bộ nhớ chương trình với địa Trong bộ nhớ có thể truy chỉ nhỏ hơn 64k

cập

8

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Ví dụ: Nếu ta khai báo: near ram char var_mta // có nghĩa là biến var-mta lưu trong bộ nhớ ram có thể truy cập được. Tới đây lại xuất hiện khái niệm mới về như thế nào là bộ nhớ có thể cập được và không thể. Để hiểu rõ khái niệm này các bạn có thể đọc trong cuốn PICmicro™ Mid-Range MCU Family Reference Manual. Có thể download tại đây. 2. Sự mở rộng của C18  C18 hỗ trợ các cấu trúc nặc danh bên trong các union. 1 cấu trúc nặc danh có dạng:

1 cấu trúc nặc danh định nghĩa 1 đối tượng không tên. Tên của các thành viên cấu trúc phải được phân biệt từ những tên khác nhau trong phạm vi cấu trúc được khai báo. Các thành viên được sử dụng trực tiếp trong phạm vi đó không có cú pháp truy cập cho các thành viên thông thường  Tóm lại: ta có thể hiểu điều này giống như lập trình C thông thường ta có struct lồng vào trong union nhưng sự khác biệt là struct ở đây có thể nặc danh [ tức là ko tên]. Việc truy cập tới các biến trong union như cấu trúc C thông thường. Thể hiện ở ví dụ sau:

9

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

 Nhúng code assembly vào trong chương trình MPLAB C18 cung cấp trình dịch assembler sử dụng cú pháp giống với trình dịch assembler MPASM. Để sử dụng đoạn mã assem ta phải khai báo như sau: _asm [label:] [ [arg1[, arg2[, arg3] ]]] _endasm

Tuy nhiên do là mã assembly nhúng trong C cho nên nó có những điểm khác với trình dịch MPASM và giống với code C: • Các comment phải giống trong C: comment phải sau ‘//’ chứ ko phải là ‘;’ • Ko có mặc định toán hạng mà chúng phải ghi rõ trong lệnh, nếu ko khi dịch chương trình sẽ báo lỗi • Hệ số mặc định là cơ số 10 • Sử dụng các ký hiệu của C. ví dụ: số hexa phải được viết là 0x1234 chứ ko phải là H ‘1234’ như assem thông thường • Sau label có dấu ‘:’ 10

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

• Cú pháp định địa chỉ theo chỉ số không được hỗ trợ ở đây [nghĩa là không được dùng []] mà phải chỉ rõ bit truy cập. Ví dụ như assem thường ta viết CLRF[2] nhưng giờ phải viết CLRF 2,0 Cú pháp và tất cả các luật nhúng code assem trong code C vừa trình bày ở trên được thể hiện ở ví dụ sau: _asm /* User assembly code */ MOVLW 10

// Move decimal 10 to count

MOVWF count, 0 /* Loop until count is 0 */ start: DECFSZ count, 1, 0 GOTO done BRA start done: _endasm

3.

pragma sectiontype Cú pháp: # pragma udata

[attribute-list] [section-name [=address]]

|

# pragma idata

|

# pragma romdata

|

# pragma code

[attribute-list] [section-name [=address]] [ overlay ] [ s e c t i o n - n a m e [ = a d d r e s s ] ]

[ overlay ] [ s e c t i o n - n a m e [ = a d d r e s s ] ]

Trong đó: [attribute-list] section-name [=address]:

: là thuộc tính của section tôi sẽ nói rõ ở phần dưới

: tên của section [ do người lập trình đặt] địa chỉ đầu tiên của section

Tất cả thuộc tính, tên, địa chỉ của section có thể có hoặc ko [đặt trong dấu ngoặc vuông], nếu khi lập trình không chỉ rõ [specified] thì

11

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

trình dịch sẽ tự động làm thay. Tôi sẽ trình bày chi tiết từng phần ở dưới Khai báo này sẽ gán đặc tính và địa chỉ cho section [đoạn]. Trong phần này tôi xin được giữ nguyên thuật ngữ section [chỉ vì lí do dịch sang ‘đoạn’ nghe hơi chuối]. 1 section là 1 phần của ứng dụng đặt tại địa chỉ bộ nhớ nào đó. 1 section có thể chứa code hoặc data và có thể được đặt trong bộ nhớ chương trình hoặc bộ nhớ dữ liệu. Có 2 kiểu section cho mỗi kiểu bộ nhớ: • Bộ nhớ chương trình - code: chứa các lệnh có thể thực thi - romdata: chứa các biến và hằng [ các biến được khai báo với qualifier rom] • Bộ nhớ dữ liệu - udata: chứa các biến user cấp phát tĩnh, ko được khởi tạo - idata: chứa các biến user cấp phát tĩnh và được khởi tạo 1 section absolute là section có địa chỉ rõ ràng thông qua khai báo =address. 1 section assigned được gán là section đặc trưng với khai báo SECTION 1 section unassigned không phải là section absolute và assigned Nghe có vẻ khó hiểu. Ví dụ sau thể hiện các đối tượng được đặt trong bộ nhớ như thế nào để bạn có thể hiểu được sự khác nhau của các đoạn dữ liệu:

12

Nguyễn Văn Hùng rom int ri;

[email protected]

Học viện kỹ thuật quân sự

// rõ ràng biến ri phải được lưu trong vùng nhớ rom. Cụ thể romdata //vì nó ko phải là code có thể chạy. tương tự ta có bảng dưới

rom char rc = 'A'; int ui; char uc; int ii = 0; char ic = 'A'; void foobar [void] { static rom int foobar_ri; static rom char foobar_rc = 'Z'; ... } void foo [void] { static int foo_ui; static char foo_uc; ... } void bar [void] { static int bar_ii = 5; static char bar_ic = 'Z'; ... }

Với đoạn chương trình trên thì các biến được lưu trong bộ nhớ như thế nào. Ta có bảng các đối tượng [các biến, biến hàm..] được lưu trữ trong bộ nhớ tương ứng: Đối tượng

Vùng nhớ

Đối tượng

Vùng nhớ

ri

romdata

ui

udata

rc

romdata

uc

udata

roobar_ri

romdata

foo_ui

udata

roobar_rc

romdata

foo_uc

udata

13

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

ii

idata

bar_ic

idata

ic

idata

foo

code

bar_ii

idata

bar

code

Tới đây ta có thể hiểu được các kiểu bộ nhớ của Pic18 mà ta có thể chủ động lưu trữ dữ liệu vào trong đó qua các khai báo như trên.  Khá là mệt khi ta phải nắm chắc được những điều này, với những chương trình nhỏ đôi khi điều đó là không cần thiết nhưng thực hiện những project lớn thì điều đó là cần thiết và nhất là nắm được những điều này mới hiểu được các đoạn mã gốc mà Microchip cung cấp cho chúng ta vì thế mới sử dụng được tốt. OK, let’s go… • Các section mặc định ở trên phần khai báo

pragma tôi có nói tên section có th ể ko được khai báo bởi người lập trình. Nếu điều đó xảy ra trình dịch sẽ tự động đặt tên cho section phụ thuộc vào đó là kiểu section nào theo bảng sau: Kiểu section

Tên mặc định

code

.code_filename

romdata

.romdata_filename

udata

.udata_filename

idata

.idata_filename

filename: là tên file của ta Ví dụ: nếu tên file chương trình của ta là first_pro và kiểu section là code thì đoạn mặc đinh sẽ có tên là .code_first_pro

14

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

• Thuộc tính [attribute] của section Thuộc tính của section gồm có access và overlay access: báo cho trình dịch biết rằng section được đặt trong vùng có thể truy cập của bộ nhớ dữ liệu [ xem chi tiết về như thế nào là bộ nhớ có thể truy cập trong PIC 18C MCU Family Reference Manual]. Biến đặt trong access section phải được khai báo với từ khóa near. Ví dụ:

pragma udata access my_access /* all accesses to these will be unbanked */ near unsigned char av1, av2;

overlay: là thuộc tính cho phép section khác đư ợc đặt tại cùng địa chỉ vật lí. Thuộc tính này có thể sử dụng cùng với thuộc tính access. Có 4 điều kiện phải thỏa mãn để có thể overlay: 1. Mỗi section phải được đặt trong file nguồn khác nhau 2. Cả 2 section phải có tên giống nhau 3. Nếu đoạn 1 được khai báo có thuộc tính access thì đoạn 2 cũng phải khai báo thuộc tính này 4. Nếu gán địa chỉ tuyệt đối cho 1 section, thì section còn lại cũng phải gán địa chỉ tuyệt đối đó Ví dụ: f il e1 . c:

pragma code overlay my_overlay_scn=0x1000 void f [void] { ...

15

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

}

f il e2 . c:

pragma code overlay my_overlay_scn=0x1000 void g [void] { ... }

Hoặc nó cũng sử dụng cho nhiều biến mà ko bao giờ được kích hoạt đồng thời: f il e1 . c:

pragma udata overlay my_overlay_data=0x1fc /* 2 bytes will be located at 0x1fc and 0x1fe */ int int_var1, int_var2;

f il e2 . c:

pragma udata overlay my_overlay_data=0x1fc /* 4 bytes will be located at 0x1fc */ long long_var;

Để có thêm thông tin về điều khiển section overlay bạn có thể xem thêm trong

MPA S M™

As s em b l er ,

L ibr ar i an Us er 's G u id e [DS33014].

MP LI N K™

O b jec t

L i nk e r ,

MP LI B™

[nhưng thực sự là khá khoai]

• address của section địa chỉ được gán sẽ là nơi lưu trữ đoạn mã.

pragma code my_code=0x2000

/*

đoạn mã này sẽ được lưu ở địa chỉ 0x 2000 cho tới khi gặp tiền xử lí bên dưới

………………………

*/

pragma code

16

O b j ec t

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

4.

Pragma interruptlow và

Pragma interrupt Ngắt là 1 phần quan trọng trong vi điều khiển

[VĐK] giúp cho

chương trình của chúng ta rẽ nhánh và thực hiện các công việc khác nhau, khi xảy ra ngắt VĐK con trỏ chương trình sẽ trỏ tới địa chỉ của chương trình ngắt tương ứng [ địa chỉ của chương trình ngắt tùy thuộc vào loại ngắt và loại VĐK, khi thực hiện lập trình với VĐK nào thì ta xem datasheet của nó là biết] và thực hiện chương trình ngắt được lưu ở đó, khi thực hiện chương trình ngắt xong nó sẽ trở lại thực hiện chương trình main[] tại địa chỉ lúc nó dừng lại [ địa chỉ này được lưu trong stack]. Vậy ta sẽ xem xét cách khai báo, khai báo mức ưu và thực hiện chương trình ngắt trong C18 như thế nào, go on…. start

Địa chỉ vector ngắt

Địa chỉ xảy ra ngắt

kết thúc ngắt

end Lưu đồ thực hiện ngắt

Pragma interrupt fname: khai báo hàm phục vụ ngắt mức ưu tiên cao [high-priority ISR]

17

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Pragma interruptlow fname: khai báo chương trình phục vụ ngắt mức ưu tiên thấp[low-priority ISR] • Cú pháp khai báo:

pragma

interrupt

f un c t i o n - n a m e

[ t m p -s e c t i on - n a me ] [ save= sa v e -

l i st ] [ nosave= n o s a v e -l i s t ]

#

|

pragma

interruptlow

fu n c t i o n - n a m e

[t m p - s e ct i o n -

n a me ] [ save= sa v e l i s t ][ nosave= n os a v e - l is t ]

Trong đó: function-name

: là tên hàm phục vụ ngắt

[tmp-section-name]:

tên section trong đó chứa các biến kiểu

temporary của ISR [ sav e= s a v e - li s t ] s a ve - l i s t

: Sử dụng để lưu các biến, các section. Trong đó

là danh sách các biến, các section được lưu.

[ nosav e= no s a v e -l i s t ]

biến, các section.

: sử dụng để chỉ rằng compiler ko cần lưu các

n o sa v e - l is t

là danh sách các biến, các section ko cần

lưu Ta sẽ tìm hiểu dần dần chi tiết từng đối tượng ở phía dưới. Với khai báo này trình dịch sẽ tạo ra biến kiểu temporary [thế nào là biến, section kiểu temporaty tôi sẽ chỉ rõ phía dưới ] trong udata section có tên là fname_tmp. Ví dụ: void foo[void]; ...

pragma interrupt foo void foo[void] { /* perform interrupt function here */

18

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

}

Với chương trình này các biến giành cho chương trình ngắt sẽ được đặt ở trong udata section có tên là foo_tmp. • Vector ngắt MPLAB C18 ko tự động đặt ISR tại vector ngắt. Thông thường là dùng lệnh Goto đặt tại vector ngắt để gọi ISR. Ví dụ:

include void low_isr[void]; // khai báo chương trình phục vụ ngắt void high_isr[void];

// như chương trình bình thường

/* Giả sử ta làm việc với loai Pic18 mà loại ngắt có địa chỉ vector ngắt là 0x18. Như tôi đã nói ở phần trên, vector ngắt này tùy vào loại

ngắt

loại

VĐK,

thế

khi

làm

việc

với

VĐK

nào

bạn

tra

trong datasheet */

pragma code low_vector=0x18 void interrupt_at_low_vector[void] { _asm GOTO low_isr _endasm }

pragma code

/* trả lại code section mặc định */

pragma interruptlow low_isr void low_isr [void] { /* ... */ } /* Giả sử ta làm việc với loai Pic18 mà loại ngắt có địa chỉ vector ngắt là 0x08 */

pragma code high_vector=0x08 void interrupt_at_high_vector[void] { _asm GOTO high_isr _endasm }

pragma code

/* trả lại code section mặc định */

19

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

pragma interrupt high_isr void high_isr [void] { /* ... */ }

Như tôi đã nói ở trên biến, section kiểu temporary [nhất thời] tức là nó chỉ xuất hiện nhất thời trong quá trình thực hiện chương trình. Ví dụ với chương trình sau:

Section kiểu temporary là dùng để lưu lại các biến trung gian trong quá trình tính toán, thực hiên chương trình và các biến tmp_1, tmp_2 trong chương trình là biến kiểu temporary. Và section kiểu temporary là section có thể dùng chung giữa các hàm với nhau [ theo thuật ngữ của C18 đó chính là section ki ểu overlay mà tôi trình bày ở trên].

20

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

ISR Context Saving [lưu trạng thái chương trình ngắt] sử dụng [ save= save-list]

Cú pháp:

pragma interrupt isr save=myint, section["mydata"] biến ‘myint’ và section có tên là 'mydata’.

// lệnh này sẽ lưu

Ok, câu hỏi đặt ra cho chúng ta là tại sao lại phải lưu trạng thái chương trình ngắt? Và câu hỏi đầu tiên sẽ là trạng thái chương trình ngắt là gì? Trong khoa học máy tính, context đồng nghĩa với trạng thái [state] của chương trình. Nhiều người còn cho rằng nó là tài nguyên của CPU mà chương trình đang sử dụng tại 1 thời điểm. Tài nguyên này có thể là các thanh ghi, bộ nhớ và ngoại vi. Tại sao phải lưu trạng thái chương trình ngắt??? Trong thực hiện chương trình có các tài nguyên có đặc tính vulnerable [tính dễ bị tổn thương, hay nói cách khác là nó d ễ bị thay đổi giá trị. Ở đây tôi xin được giữ nguyên thuật ngữ vulnerable mà ko dịch ra tiếng việt]. Tài nguyên này là loại tài nguyên mà thường dùng chung giữa ISR và code chương trình chính. Bởi vậy nếu ta ko tiến hành lưu lại các tài nguyên này trư ớc khi thực hiện code của ISR thì sau khi thực hiện chương trình nó sẽ thay đổi và những dữ liệu bị thay đổi này lại được code của chương trình chính thực hiện và như vậy chương trình chúng ta sau khi thực hiện ngắt sẽ chạy sai. Ví dụ: tài nguyên có tên là WREG có chức năng lưu những tính toán trung gian. Giả sử khi đang thực hiện chương trình chính mà xảy ra ngắt mà ta ko tiến hành lưu lại WREG thì chương trình ngắt sẽ thay đổi giá trị trong tài nguyên này. Vậy khi thực hiện xong chương trình ngắt 21

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

mà trở lại thực hiện chương trình main thì khi chương trình main đọc các dữ liệu trong này ra để thực hiện tính toán tiếp sẽ là những dữ liệu sai. Do vậy kết quả cuối cùng là chương trình của chúng ta thực hiện sai. Vậy cách giải quyết cho vấn đề này là như thế nào? Ta có thể ngay rằng muốn chương trình ngắt ISR không làm thay đổi giá trị các section .tmpdata gốc của chương trình chính thì ta phải có section .tmpdata riêng cho nó. Mà như tôi đ ã trình bày ở trên khai báo

pragma interrupt

sẽ tự động tạo ra section .tmpdata cho riêng

nó.  Bởi vậy nên ta phải tiến hành lưu trạng thái chương trình ngắt Tài nguyên có tính vulnerability trong thu ật ngữ của C18 chính là tài nguyên quản lí bởi trình dịch [compiler managed resource]. Có nghĩa rằng là các tài nguyên này d ễ bị thay đổi trong khi thực hiện chương trình ngắt làm sai chương trình chính vì thế cần phải quản lí [với ý nghĩa như vậy theo tôi các loại tài nguyên này có thể gọi là tài nguyên được che bởi trình dịch – compiler masked sources]. Bảng dưới là danh sách các tài nguyên quản lí bởi trình dịch:

22

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

7 thanh ghi đầu tiên được gọi là ‘vital’ có nghĩa rằng nó mặc định được lưu lại trước khi thực hiện ngắt, nay nói cách khác chương tr ình thực hiện ngắt chắc chắn sẽ thay đổi giá trị 7 thanh ghi này. Tiếp theo là các thanh ghi TBLPTR và TABLAT [ các thanh ghi được sử dụng để truy cập bộ nhớ chương trình]. Nếu bạn biết rằng ISR ko truy cập dữ liệu rom thì bạn có thể bảo vệ trình dịch ko lưu các thanh ghi này [phía dưới tôi sẽ trình bày cách làm thế nào để ko lưu các dữ liệu nếu ko cần thiết phải lưu]. Giống như vậy, nếu bạn biết ISR ko sử dụng con trỏ hàm, bạn có thể ko lưu các thanh ghi PCLATH và PCLATU MATHDATA là 1 section đặc biệt được sử dụng bởi thư việc math, ngoài ra nó được sử dụng trong chế độ truyền thống để thực thi những giao diện hàm. Section này nhỏ và thường bạn ko cần lo lắng về nó.

23

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Cuối cùng là section .tmpdata chứa những giá trị trung gian cho những tính toán phức tạp mà tôi đã trình bày như thế nào là section temporary [.tmpdata] ở phần trên. Section loại này lớn lên trong quá trình thực hiện chương trình mà chúng ta không kiểm soát được sự lớn lên của nó trong từng thời điểm. Ta đã thấy rằng tại sao phải lưu trạng thái chương trình ngắt. Nhưng vấn đề đi kèm với lưu trạng thái ngắt sẽ là thời gian phục vụ ngắt bị tăng lên. Bởi vì trước khi thực hiện code ngắt thì phải lưu lại trạng thái và sau khi thực hiện ngắt lại phải tiến hành khôi phục trạng thái đã lưu lại đó. Công việc này tốn khá nhiều thời gian nếu dữ liệu cần lưu lớn. Đặc biệt là trình dịch v3.0 và phiên bản cao hơn, trình dịch nhận dạng tài nguyên vulnerable 1 cách dè dặt[conservative], có nghĩa là nếu nó ko chứng mình được tài nguyên đó là tài nguyên vulnerable hay ko thì nó sẽ lưu tài nguyên đó. Ta xem ví d ụ sau:

24

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

ISR đầu tiên ko gọi hàm khác. Bởi vậy trình dịch có thể phân tích hàm isr và nhận ra các tài nguyên được sửa là các thanh ghi WREG, BSR, STATUS, TBLPTR và TABLAT. Vì thế trình dịch sẽ tự động lưu các thanh ghi này và cũng chỉ lưu các thanh ghi này. Trong chương trình thứ 2, có gọi hàm khác vì thế trình dịch không biết rằng nó đang và sẽ sửa cái gì trong hàm foo. Trong thực tế hàm foo có thể là module riêng rẽ. Bởi vậy trình dịch lưu tất cả tài nguyên quản lí bởi trình dịch [compiler managed resources]. Với cơ chế hoạt động này thì nếu section kiểu .tmpdata có dữ liệu lớn thì sẽ tốn nhiều thời gian lưu và khôi phục vì như tôi đã chỉ ra ở trên rằng section kiểu này tăng lên trong quá trình thực hiện chương trình và ta ko kiểm soát được điều này.

25

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Vậy nhiệm vụ tiếp theo của ta chính là phải tối ưu quá trình lưu dữ liệu, tức là lưu những dữ liệu cần thiết còn những dữ liệu ko cần thiết thì ko lưu. Nó được thực hiện bằng

[ nos ave= n os a v e - li s t ]

• Chỉ rõ biến và dữ liệu ko cần lưu trong trạng thái ngắt bằng [ nosave= nosave-list]

Như tôi đã trình bày ở trên khai báo này sẽ chỉ ra rằng các biến và section trong

nosave-list

sẽ ko được lưu.

pragma interrupt foo nosave=TBLPTR, TABLAT

// khai báo này sẽ ko lưu 2 thanh ghi TBLPTR, TABLAT, bởi vì 2 thanh ghi này ko được sử dụng trong chương trình main[] và nó chỉ sử dụng trong chương trình ngắt vì thế ko cần lưu lại [ko cần bảo vệ]

Với ngắt mức ưu tiên cao [khai báo bằng

pragma interrupt]

tài nguyên có thể không cần lưu bảo vệ là:

các vị trí

FSR0, TBLPTR, TBLPTRU, TABLAT,

PCLATH, PCLATU, PROD, section[".tmpdata"], or section["MATH_DATA"].

Với ngắt mức ưu tiên thấp [khai báo bằng

pragma interruptlow]

các

vị trí tài nguyên có thể là không cần lưu bảo vệ là những tài nguyên vừa kể trên và thêm

WREG, BSR, or STATUS.

Vấn đề tiếp theo là ta xác định section hoặc thanh ghi nào bị thay đổi, hoặc ko bị thay đổi để có thể save hoặc nosave. Chẳng nhẽ ta lại phải đọc mã máy mà trình dịch tạo ra để xác định xem section nào bị thay đổi. Nếu bắt buộc phải làm công việc đó thì chắc chắn rằng ta muốn tối ưu tài nguyên thì phải mất nhiều thời gian, công sức, và như vậy có lẽ chẳng ai sử dụng C18 để lập trình. Vậy cách giải quyết sẽ là như thế nào. Ta tìm hiểu vấn đề này tiếp sau: 5.

Pragma tmpdata [section-name]

26

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Câu lệnh này sẽ thay đổi section thành section loại dữ liệu .tmpdata Cú pháp:

pragma tmpdata [section-name]

[section-name]

tên của section dữ liệu mà trình dịch tạo các biến

temporary Ví dụ:

pragma tmpdata user_tmp

// câu lệnh bên trong sẽ tạo ra các biến dạng .tmpdata lưu trong section có tên là user_tmp

pragma tmpdata

Ta xem xét xem vấn đề tôi vừa gợi ý là làm thế nào để xác định section nào ko thay đổi để không cần phải lưu. Ta xét ví dụ sau:

27

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Lệnh

pragma tmpdata setporttmp….

pragma tmpdata : sẽ tạo thêm 1 section kiểu .tmpdata để lưu trữ dữ liệu trung gian khi thực hiện hàm SetPort[]. Ở lệnh tiếp theo

pragma interruptlow low_isr nonsave[“.tmpdata”] tạo ra section .tmpdata cho chương tr ình ngắt và bảo vệ section có tên là ‘setPorttmp’ bởi chính vì thế mà hàm setPort[] có thể được dùng cho cả chương trình chính mà ko bị lỗi khi thực hiện. Hay nói cách khác section ‘setPorttmp’ được dùng chung cho cả chương trình ngắt và chương trình chính Việc tận dụng tài nguyên ở mọi nơi trong chương trình là vấn đề khá quan trọng giúp chúng thực hiện được các Project lớn mà không tốn nhiều tiền chi cho tài nguyên [ Tài nguyên tôi muốn nói tới chính là bộ nhớ và tốc độ xử lí]. Dưới đây ta xem xét cách tối ưu sử dụng tài nguyên như thế nào..  Tiết kiệm tài nguyên khi xử lí nhiều ngắt mức ưu tiên cao Trong chương trình có nhiều ngắt ưu tiên cao [

pragma

interrupt

], vì các

chương trình ngắt mức ưu tiên cao thì chỉ có thể kích hoạt ở 1 thời điểm nên có thể dùng chung các section kiểu .tmpdata Ví dụ: void increment [int counter]; void isr1 [void]; void isr2 [void];

pragma interrupt isr1 isr_tmp nosave=section[".tmpdata"] void isr1 [void] { static int foo = 0; ... increment [foo]; ... }

pragma interrupt isr2 isr_tmp nosave=section[".tmpdata"]

28

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

void isr2 [void] { static int foo = 0; ... increment [foo]; ... }

pragma tmpdata isr_tmp void increment [int counter] { ... }

pragma tmpdata

Trong chương trình ví dụ trên ta thấy 2 chương trình ngắt mức ưu tiên cao dùng chung section .tpmdata tên là isr_tmp. B ởi vì các chương trình ngắt mức ưu tiên cao ở 1 thời điểm chỉ có thể kích hoạt 1 chương trình. Ta tưởng tượng rằng nếu ko có đặc tính này khi ngắt isr1 xảy ra và chương trình phục vụ ngắt của nó đang thực hiện, chương trình isr1[] chạy vẫn chưa xong thì ngắt 2 sẽ được kích hoạt thì section ‘isr_tmp’ bị thay đổi. Như thế sau khi thực hiện xong isr2[] chương trình quay lại thực hiện isr1[] và sử dụng các giá trị trong section ‘isr_tmp’ đã bị thay đổi  chương trình isr1[] thực hiện không chính xác. Vậy với ngắt có mức ưu tiên khác nhau ở trong cùng 1 chương trình mà chương trình ngắt ở mức ưu tiên thấp hơn thì chúng ta phải xử lí như thế nào để đảm bảo chương trình vẫn tận dụng được tài nguyên và chức năng chương trình vẫn được đảm bảo chính xác.  Ngắt lồng nhau [Nested Interrupts] Ta xét và phân tích ví dụ sau: void increment [int counter]; void isr1 [void]; void isr2 [void];

29

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

pragma interrupt isr1 isr_tmp save=section["isr_tmp"] nosave=section[".tmpdata"] void isr1 [void] { static int foo = 0; ... increment [foo]; ... }

pragma interruptlow isr2 isr_tmp save=section["isr_tmp"] nosave=section[".tmpdata"] void isr2 [void] { static int foo = 0; INTCONbits.GIE = 1; ... increment [foo]; ... }

pragma tmpdata isr_tmp void increment [int counter] { ... }

pragma tmpdata

Ta thấy rằng section ‘isr_tmp’ phải được lưu lại trước khi thực hiện chương trình ngắt vì ngắt isr2 là loại ngắt có mức ưu tiên thấp nên trong khi thực hiện chương trình ngắt isr2[] nó có thể bị ngắt đoạn. Đến đây chắc các bạn có thể tự phân tích được tại sao lại phải khai báo chương trình như thế, tôi ko phân tích rõ chương trình này, các bạn tự phân tích như ví dụ trên tôi vừa phân tích sẽ thấy và hiểu rõ.  Tip: tới đây bạn tự đặt câu hỏi tại sao lại phải lằng nhằng như vậy. Việc quản lí tài nguyên sao ko để trình dịch tự làm. Như tôi đã phân tích ở trên rằng hiểu rõ được những điều tôi vừa trình bày sẽ tận dụng được tối đa tài nguyên. Giả sử ta thực hiện 1 Project thì phải làm sao tận dụng tối đa tài nguyên ta có, t ức là làm sao mà tài nguyên nhỏ nhất ta dùng để giải quyết được bài toán đó. Như tôi đã nói ở trên tài nguyên ở đây là dung lượng bộ

30

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

nhớ và tốc độ xử lí chương trình. Nếu ta ko hiểu rõ và ko thực hiện được những khai báo để tận dụng tối đa thì có thể ta sẽ phải dùng VĐK có tài nguyên lớn hơn, và như thế tốn tiền, và đôi khi thậm chí là nhiều khi có tiền cũng ko mua được.

6.

pragma

config

Khai báo này cài đặt cấu hình để sử dụng trong các ứng dụng khác nhau. Và có thể sử dụng nhiều khai báo để cấu hình cho thiết bị Cú pháp: # pragma config

setting-list

setting-list: setting | setting-list, setting setting: setting-name = value-name setting-name = value-name:

ứng với từng thiết bị, để tra chúng ta sử

dụng tài liệu PIC18 Configuration Settings online help . Bạn có thể dowload tài liệu tại đây. Ví dụ với PIC18F2220 tra trong tài liệu ta có:

31

Nguyễn Văn Hùng

[email protected]

Dựa vào đó ta cấu hình cho PIC18F2220 như sau:

32

Học viện kỹ thuật quân sự

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

pragma config WDT = ON, WDTPS = 128

pragma config OSC = HS /*

cấu

hình

này

enable

Watchdog

Timer,

thiết

lập

postcaler

Watchdog là 1:128 và oscillator HS*/ void main [void] { ... }

7. Processor-Specific Header Files Là loại file chứa những khai báo cho những thanh ghi chức năng đặc biệt. Ví dụ, trong file header của PIC18C452, PORTA được khai báo: extern volatile near unsigned char PORTA; extern volatile near union

{

struct { unsigned RA0:1; unsigned RA1:1; unsigned RA2:1; unsigned RA3:1; unsigned RA4:1; unsigned RA5:1; unsigned RA6:1; } ; struct { unsigned AN0:1; unsigned AN1:1; unsigned AN2:1;

33

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

unsigned AN3:1; unsigned T0CKI:1; unsigned SS:1; unsigned OSC2:1; } ; struct { unsigned :2; unsigned VREFM:1; unsigned VREFP:1; unsigned :1; unsigned AN4:1; unsigned CLKOUT:1; } ; struct { unsigned :5; unsigned LVDIN:1; } ; } PORTAbits ;

Khai báo đầu tiên nghĩa rằng PORTA là 1 byte [ kiểu unsigned char]. Modiffer extern cần thiết để khi gọi file header trong file khác thì biến mới có hiệu lực, near có nghĩa là biến PORTA được đặt trong bộ nhớ RAM có thể truy cập. Dùng khai báo này để thiết lập giá trị PortA. Ví dụ: PORTA = 0x34;

/* gán giá trị 0x34 tới cổng A */

Tiếp theo là khai báo thứ 2 là cấu trúc nặc danh mà tôi đã trình bày ở trên, vậy ta hiểu khai báo này như thế nào. Bạn có thể thấy các nhãn RA0, RA1….; AN0, AN1…giống với nhãn mà trong datasheet ghi. Tức là dùng khai báo này chúng ta có thể truy cập từng bit trong portA. Ví dụ: 34

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

PORTAbits.AN0 = 1; /* Sets chân có kí hiệu AN0 pin */ PORTAbits.RA0 = 1; /* Sets chân có kí hi ệu RA0 pin, có tác dụng giống với lệnh tr ên vì AN0 và RA0 cùng là kí hi ệu của 1 chân*/

Các file header này Microchip đ ã cung cấp cho chúng ta hầu như đầy đủ các dòng Pic18 sau khi ta cài đặt C18 trên máy. Để xem các file

header

này

ta

thể

vào

thư

mục

C:\Program

Files\Microchip\mplabc18\v3.38\h. Công việc của ta chỉ là dùng nó, để dùng nó trong chương trình ta dùng lệnh:

include // trong đó ‘xxxx’ là chỉ số VĐK tương ứng ta dùng Ví

dụ

khi

lập

trình

cho

pic18f4550

ta

khai

báo:

include Ngoài việc sử dụng tên trực tiếp khai báo đó ta có thể sử dụng lệnh

include kết hợp với lệnh –p option để sử dụng file

header của VĐK tương ứng  Trong các file header còn định nghĩa sẵn cho chúng ta các Macro assembly và ta sẵn sử dụng chúng trong lập trình. Danh sách các Macro và chức năng của chúng tôi liệt kê dưới bảng sau: Macro

Chức năng Không thực hiện gì, chỉ là delay 1 chu kì dao

Nop[]

động thạch anh, thường sử dụng trong viết hàm delay

ClrWdt[]

Clear watchdog timer

Sleep[]

Thực thi lệnh SLEEP

Reset[]

RESET thiết bị

35

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Rlcf[ var, dest, acces ]

Quay trái giá trị var qua bit mang

Rlncf[ var, dest, acces ]

Quay trái giá trị var ko qua bit mang

Rrcf[ var, dest, acces ]

Quay phải giá trị var qua bit mang

Rrncf[ var, dest, acces ]

Quay phải giá trị var ko qua bit mang

swapf[ var,

dest, acce s ]

Đổi vị trí nibble của giá trị var

- Nếu sử dụng Macro quá nhiều sẽ ảnh hưởng tới quá trình tối ưu ở các chức năng mà Macro đó đảm nhiệm - Var : phải là 8 bit và ko được đặt trong stack - Nếu dest =0, kết quả được đặt trong WREG, nếu dest=1 kết quả được đặt trong var. Nếu access =0 thì bank có thể truy cập được chọn, ghi đè giá trị BSR, nếu access=1 thì bank sẽ được chọn dựa trên giá trị của BSR Ví dụ: var=0b11110000;

// giá trị 8bit

Rlcf[ var, 1, 0 ]

// giá trị var=11100001 được lưu ở trong bank có thể

tru y cập, bit mang=1

Hình biểu thị thực hiện lệnh Rlcf[] Giá trị gốc

1 1 1 1 0 0 0 0 1 Bit mang

Giá trị đích

1 1 1 0 0 0 0 1

Các lệnh khác quá trình thực hiện tương tự như vậy.

36

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

8. Processor-Specific Register Definitions Files Các file định nghĩa thanh ghi của VĐK là file assembly ch ứa các định nghĩa về các thanh ghi. Các khai báo này chính là vi ệc dán nhãn cho các thanh ghi để ta sẵn sử dụng mà ko cần khai báo. Ví dụ: PortA của VĐK Pic18C452 được định nghĩa trong file là: SFR_UNBANKED0 UDATA_ACS H'f80' PORTA PORTAbits RES 1 ; 0xf80

Định nghĩa này có nghĩa là PortA có 1 nhãn là PORTA và PORTAbits và cả 2 đều được tham chiếu tới vị trí có địa chỉ 0xf80 [đây chính là địa chỉ của thanh ghi PortA trong VĐK Pic18c452] Ta có thể xem các định nghĩa này trong thư mục: C:\Program Files\Microchip\mplabc18\v3.38\src\extended\proc

37

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Phần II: Lập trình PIC18 bằng MPLAB C18 Dowload phần mềm MPLAB và C18 trên internet rồi tiến hành cài đặt chúng trên máy tính của bạn. Có 1 lưu ý các loại phần mềm đều có phiên bản của nó và tương ứng với loại phiên bản của MPLAB bạn phải xem nó hỗ trợ C18 tới phiên bản nào. Vì nếu không tương thích giữa 2 cái này khi thực hiện dịch chương trình sẽ báo lỗi và bạn chẳng biết lỗi ở chỗ nào. Download

MPLAB:

//ww1.microchip.com/downloads/en/DeviceDoc/MPLAB_IDE_8_7 6.zip Download

C18:

//ww1.microchip.com/downloads/en/DeviceDoc/mplabc18_v3.40_ windows_lite.exe Bạn download chúng về máy rồi tiến hành cài đặt như chương trình bình thường. Tôi không đi sâu về vấn đề này. 1. Sử dụng MPLAB 1.1. Quản lí Project Cũng như bất kì trình dịch nào khác MPLAB cũng có assembler, compiler, linker…Vậy ta xem MPLAB dùng chúng như th ế nào để tạo ra file chạy. Tiến trình tạo file chạy được thể hiện ở hình 2.1 [hình dưới]:

38

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.1

Từ file nguồn [source file] được viết theo cú pháp assembly hoặc cú pháp compile [trong tài liệu này ta nghiên cứu C18 mà tôi trình bày ở phần 1], kết hợp với các option build, các assembler và compiler sẽ dịch ra mã máy [hình 2.2]. Tiếp theo là tới công việc của Linker sẽ liên kết file do các trình dịch tạo ra + thư viện file object + linker script [tệp link, là tệp chỉ rõ bố trí bộ nhớ với từng VĐK khác nhau] để tạo ra file có thể thực thi [file.hex] và file giành cho debug.

39

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.2

1.2. Các bước tạo ra file.hex Dưới đây ta đi từng bước cách từ đầu tới cuối cách tạo file.hex [ là file nạp vào VĐK để chạy]  B1: chọn thiết bị Đây là bước chọn thiết bị mà ta tiến hành lập trình cho nó. Chon thiết bị có thể tiến hành sau và trước khi dịch chương trình. Nhưng theo khuyến cáo thì nên thực hiện bước này trước khi bắt đầu 1 project. ta có thể tiến hành lựa chọn lại thiết bị như sau: Configure/ select Device.

40

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.3

 B2: Tạo project: • B2.1: Mở môi trường phát triển tích hợp MPLAB: start/all program/Microchip/ MPLAB IDE v8.70/ MPLAB IDE

41

Nguyễn Văn Hùng

[email protected]

Hình 2.4

• B2.2: tạo 1 file: file/New hoặc ấn ctrl+N

Hình 2.5

42

Học viện kỹ thuật quân sự

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Đây là cửa sổ ta viết code lập trình. File sẽ được dùng để add vào project khi bạn tiến hành làm 1project [bước 3] Sau khi viết xong code tiến hành lưu lại file/save as hoặc ấn ctrl+s.

Hình 2.6

Chọn thư mục bạn muốn lưu file. Lưu ý tên file bạn phải có phần mở rộng .* sau tên. Ví dụ: file của bạn tên là ‘sample’ thì khi lưu bạn phải chỉ rõ là .c, .h hoặc .asm… • B2.3: tạo project: project/project wizard ấn ‘next’ tới cửa sổ lựa chọn thiết bị. ở đây ta cần phải lựa chọn thiết bị mà mình muốn phát triển. Như hình dưới:

43

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.7

Tới đây, câu hỏi đặt ra là, nếu tôi muốn thay đổi thiết bị phát triển trong project thì phải làm project lại từ đầu. Công việc đó thật mất công và thiếu tính chuyên nghiệp,

ta có thể tiến hành lựa chọn lại

thiết bị như sau: Configure/ select Device. Ta được:

44

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.8

Sau đó tiến hành lựa chọn thiết bị trong phần Device mà tôi khoanh tròn. Ở phần này ta cũng có thể thấy rằng MPLAB sẽ báo cho ta biết rằng thiết bị mà ta chọn được hỗ trợ tới đâu. Biểu tượng màu xanh tức là có hỗ trợ và ngược lại màu đỏ là không hỗ trợ. Ví dụ với pic18f4550 hộ trợ hầu hết các chương trình nạp chỉ có PICkit1 là không… Tiếp theo bạn ấn ‘next’. Ta được cửa sổ lựa chọn ngôn ngữ lập trình:

45

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.9

Lựa chọn ngôn ngữ lập trình trong phần ‘Active Toolsuite’. Nếu ta sử dụng C18 thì lựa chọn như trên, hoặc ta có thể lựa chọn loại ngôn ngữ khác. Trong phần ‘Toolsuite Contents’ ta có thể kích chuột vào từng phần và xem vị trí cài đặt các phần đó ở dưới ‘Location’ xem có giống thư mục ta đã cài không. Nếu không giống ta có thể chọn lại bằng ấn ‘Browse…’. Nhưng theo tôi điều này là không cần thiết cho lắm. vì thường là phần mềm sẽ tự cập nhật thư mục đã cài đặt chương trình Sau này, sau khi tạo xong project ta cũng có thể kiểm tra và sửa lại đường dẫn của chúng bằng cách: project/set language Tool locations…

46

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.10

Tiếp theo ấn ‘next’ ta sẽ được cửa sổ chọn thư mục lưu project và nhập tên project của ta vào:

47

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.10

Chọn thư mục lưu project bằng ‘Browse…’ > nhập tên file, ấn Save. sau đó ấn next:

48

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.11

ở đây ta có thể Add file đã tạo ra ở bước 1 hoặc ta cũng có thể Add sau khi đã tạo xong project. Nếu Add file ở đây, ta tiến hành chọn file ở phần tree_box bên trái, chọn vào file cần add sau đó ấn Add, hoặc ta cũng có thể xóa bỏ file đã add xong bằng cách chọn vào file bên phải rồi ấn Remove. 1file ta chú ý cần phải add đó là file .lkr. file này có tác d ụng báo cho trình dịch biết tổ chức bộ nhớ của loại vi điều khiển ta sử dụng. Với mỗi thiết bị có 1 file .lkr tương ứng. Ta chọn file trong phần thư mục: C:\ProgramFiles\Microchip\mplabc18\v3.38\bin\LKR Sau đó ấn next ta được cửa sổ hoàn thành tạo project:

49

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.11

ấn

‘finish’

để

hoàn

thành

tạo

project.

Chúng ta vừa hoàn thành xong cách tạo 1 project. Ok, từ giờ có thể thoải mái làm các điều mà mình muốn. Thoải mái thể hiện khả năng lập trình của mình nhé!!!  Test code bằng simulator Chương trình của chúng ta bị lỗi, hoặc ta muốn kiểm soát từng phần chương trình… Ta có thể sử dụng công cụ debug để giải quyết vấn đề này. MPLAB có thể tích hợp cả Debugger trong nó. Tiến hành debugger có thể thực hiện trên phần cứng hoặc phần mềm. Mình thường sử dụng debug bằng phần mềm vì lí do túi tiền..hjhj

50

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Để tiến hành chọn công cụ debugger: debugger/select Tool. Ta sẽ có 1 loạt các công cụ lựa chọn cho debugger tương ứng với loại mà ta có. Ví dụ ta có mạch debugger pickit3 thì sau khi cắm mạch picket3 vào máy tính ta chọn công cụ debug là pickit3[debugger/select Tool/ picket3], MPLAB sẽ tự kết nối với phần cứng và ta có thể thực hiện debug…Nếu ta không có mạch phần cứng sử dụng cho debugger thì ta chọn debug bằng phần mềm bằng cách chọn debugger/select Tool /MPLAB SIM. Tiến hành debug bằng phần mềm tương tự như với debug ở các trình dịch khác với các thao tác như là set breakpoint, step over, step in, step out….Sau khi chọn debugger/select Tool /MPLAB SIM ta được cửa sổ [hình 2.12]:

Hình 2.12

Ta có thanh công cụ giành cho debug với các chức năng sau [hình 2.13]:

51

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.13

Để chạy từng dòng lệnh ta ấn step into [F7] Để quan sát được các giá trị thanh ghi và các biến có trong chương trình ra dùng cửa sổ watch: View/Watch [hình 2.14]. Cách sử dụng cửa sổ này tôi trình bày trong phần các cửa sổ mở rộng bên dưới.

Hình 2.14

Mong muốn của ta trong khi debug chương trình là kiểm tra từng đoạn chương trình. Để thực hiện điều này ta dùng các Breakpoint. Khi ta ấn ‘Run’ thì chương trình chạy tới dòng có breakpoint sẽ dừng lại. Để set các Breakpoint ta click chuột phải vào dòng lệnh đó rồi chọn set breakpoint [hình 2.15] hoặc double-click vào dòng đó

52

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Hình 2.15

Vấn đề gặp phải tiếp theo là khi ta chạy từng dòng lệnh bằng ‘step into’ mà trong chương trình có vòng lặp delay [ thực hiện chức năng delay] thì ta sẽ phải ấn rất nhiều lần cho tới khi kết thúc vòng lặp đó. Điều đó thực sự là bất tiện và thủ công. Vậy ta để ý thấy có công cụ ‘step out’ sẽ giải quyết vấn đề này. Khi ta ở trong vòng lặp delay thì ta ấn ‘step out’ sẽ thoát khỏi vòng lặp này.

 Mở rộng:

53

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

MPLAB là môi trường phát triển tích hợp. Chính vì thế ta cũng có thể phát triển các ứng dụng bằng các trình dịch khác trên nền của môi trường này. Công việc phát triển bằng ngôn ngữ khác chỉ khác so với phần tạo project ở trên trong phần lựa chọn ngôn ngữ lập trình đã trình bày.

Ta có thể lựa chọn ngôn ngữ lập trình khác. Như ví dụ trên tôi phát triển bằng CCS. Bạn cũng có thể tiến hành bằng trình dịch khác mà bạn quen thuôc.  Lưu ý: Tất nhiên rằng trình dịch mà bạn muốn phát triển trong môi trường MPLAB đã phải được cài đặt trên máy của bạn rồi

1.3. Cửa sổ tiện ích sử dụng trong MPLAB Môi trường lập trình tích hợp MPLAB cung cấp cho chúng ta nhiều window để kiểm soát việc thực hiện chương trình, quan sát quá trình

54

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

thực hiện chương trình và nhất là khi thực hiện debug ta có thể dùng các window này để quan sát giá trị các biến, các thanh ghi.  Cửa sổ disassembly listing

 Cửa sổ hardware stack  2. Ví dụ lập trình Pic18 bằng C18 Phần này mình sẽ trình bày các ví dụ đơn giản sử dụng C18 trên môi trường MPLAB để thực hiện các chương trình cơ bản sử dụng các module trong vi điều khiển Pic18. Mục đích giúp hiểu về cách làm việc trên MPLAB, sử dụng C18 và điều khiển các module trong Pic18 mà Microchip đã tích hợp sẵn. Tôi giả sử rằng bạn đã có kiến thức cơ bản về phần cứng vì thế để không dài dòng mất thời gian và sai mục đích của tài liệu này tôi sẽ không nói tới phần cứng. Nếu chưa có kiến thức về phần cứng VĐK Pic18 thì bạn download tại đây.  Tip: Để xem chương trình có thực hiện đúng như mình mong muốn hay không tôi sử dụng mô phỏng bằng Proteus. Chương trình này là chương trình dùng mô phỏng thiết kế mạch phổ biến và không khó sử dụng. Bạn tiến hành download về vài cài đặt trong máy. Nếu có phần cứng thực thì càng tốt, những theo tôi là không cần thiết lắm.

55

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

2.1. I/O cơ bản và delay 2.1.1.

I/O

Đây là chức năng cơ bản nhất của các loại vi điều khiển. Để sử dụng các kí hiệu gợi nhớ cổng ta phải khai báo:

include [trong đó xxx là tên loại vi điều khiển ta sử dụng. ví dụ nếu ta sử dụng loại vi điều khiển p18f4550 thì khai báo

include]. Hoặc có thể khai báo cách khác như sau: defined[__18F4550]

include • Thiết lập pin là ‘in’ hay ‘out’: vi điều khiển VĐK Pic giống như tất cả các loại VĐK khác. pin nào được cấu hình ‘0’ thì đó là pin ‘out’. Ngược lại pin nào được cấu hình ‘1’ thì đó là pin ‘in’. Loại port

Kí hiệu tương ứng

PortA

TRISA

PortB

TRISB

PortC

TRISC Bảng 2.1

Ví dụ: ta cấu hình in/out cho PortA TRISA=0;

//A là cổng out

TRISA=0xf0 [hoặc TRISA=0b11110000]

//pinA0,..,A3

pinA4,..,A7 là in  Tương tự thiết lập cấu hình in/out với các Port khác • Xuất dữ liệu trên các Port 56

out,

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Để xuất dữ liệu trên các cổng ta phải cấu hình cổng đó là out và gán các giá trị tới các kí hiệu cổng tương ứng: Loại port

Kí hiệu tương ứng

PortA

PORTA

PortB

PORTB

PortC

PORTC Bảng 2.2

• Đọc dữ liệu trên các cổng Để đọc dữ liệu trên các cổng thì tương ứng cổng đó ta phải cấu hình là cổng vào và đọc giá trị tương ứng với kí hiệu cổng  Ví dụ cụ thể về xuất nhập dữ liệu trên các cổng tôi không làm trực tiếp ở đây. Để tường minh hơn tôi sẽ được trình bày chi tiết ở phần ví dụ phía dưới. 2.1.2.

Delay

Trong C18 hỗ trợ các hàm sẵn các hàm delay. Để sử dụng các hàm này ta phải khai báo

include • Hàm Delay1TCY[] = Nop[] : hàm này delay 1 chu kì dao động thạch anh. Tùy theo thạch anh tần số bao nhiêu mà delay bấy nhiêu. Ví dụ ta sử dụng thạch anh tần số f=4Mhz thì hàm này sẽ delay 1/[4.10^6]=0.25s • Hàm Delay10TCY[] : hàm này delay 10 chu kì dao động thạch anh • Hàm Delay10TCYx[unsigned char]: hàm này delay bội số của 10 chu kì dao động thạch anh

57

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Ví dụ: Delay10TCYx[5] : delay 5*10*[1/f] = 10/f [giây] unsigned char: nằm trong khoảng [0,255] • Delay100TCYx[unsigned char]: hàm này delay bội số của 100 chu kì dao động thạch anh unsigned char : nằm trong dải [0,255] • Delay1KTCYx[unsigned char]: hàm này delay bội số của 1000 chu kì dao động thạch anh unsigned char : nằm trong dải [0,255] • Delay10KTCYx[unsigned char]: hàm này delay bội số của 10.000 chu kì dao động thạch anh unsigned char : nằm trong dải [0,255]  Tất cả các hàm delay đều trả về giá trị Void Đây là phần đầu tiên vì thế tôi sẽ trình này chi tiết cách tạo project, add file, dịch chương trình và nạp chương trình mô phỏng trên proteus. Từ phần sau các bước tiến hành tương tự. 2.1.3.

Ví dụ

Tôi sử dụng pic18f4550 làm ví dụ và mô phỏng trên proteus:

58

Nguyễn Văn Hùng

[email protected]

• Bạn tạo file mới, sau đó nhập đoạn mã sau vào

include

include void main[] { TRISB=0x00;

//cau hinh PortB là cong out

PORTB=0x88;

//xuat dư lieu PortB

while[1] { PORTB=0X00;

//xuat gia tri ra ngoai

Delay100TCYx[250]; //delay 0.25ms PORTB=0X01; Delay100TCYx[250]; PORTB=0X02;

59

Học viện kỹ thuật quân sự

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Delay100TCYx[250]; //delay 0.25ms PORTB=0X03; Delay100TCYx[250]; } } Rồi tiến hành lưu vào thư mục mà bạn muốn trên máy tính của mình với tên io.c

• Tạo project như tôi đã trình bày ở trên với tên tùy ý. ở đây tôi lấy tên là io Trong phần add file bạn phải add file io.c vừa mới tạo ở trên và file 18f4550_g.lkr [xem lại phần add file ở trang 9]. Được như sau:

60

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

• Sau đó tiến hành dịch chương trình. Ta được file io.hex. • Nạp file này vào trình mô phỏng protues Double-click vào vi điều khiển trên proteus. Xuất hiện cửa sổ để nạp file io.hex

61

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Sau đó click vào phần tôi khoanh trong để dẫn tới thư mục bạn lưu project vì ở đó MPLAB đã dịch ra file io.hex và lưu ở đó. Sau khi nạp file xong bạn ấn nút mô phỏng như bình thường.  Ví dụ rất đơn giản có nhiệm vụ cấu hình PortB và xuất dữ liệu, delay sau 1 khoảng thời gian, xuất dữ liệu…

 Các bạn có thể download chương trình full ở đây: //sites.google.com/site/drocket42/home

62

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

2.2. RESET 2.2.1.

Cơ bản về RESET trong PIC18

PIC18fxxx có các loại RESET sau:  Reset Power-on [POR] Mạch reset bật nguồn như hình dưới:

-

Mạch này chỉ cần khi nguồn thấp. diode giúp tụ xả nhanh khi nguồn xuống thấp R=1 giới hạn dòng vào từ tụ C để ko đánh hỏng chân này

Xung bật nguồn được chip tạo ra bất cứ khi nào V D D tăng vượt mức ngưỡng. Điều này sẽ cho phép chip bắt đầu chế độ khởi tạo khi V D D đáp ứng các yêu cầu để hoạt động [ nghĩa là đảm bảo các tham số về điện áp, tần số, nhiệt độ…], nếu điều kiện ko đảm bảo hoạt động thì reset này sẽ giữ cho tới khi các tham số của điều kiện hoạt động được đảm bảo. Khi có ngắt này xảy ra thì bit POR được set thành ‘0’ và nó sẽ ko bị thay đổi vì các ngắt phần cứng khác, chính vì thế để thoát khỏi ngắt này thì user phải reset bằng phần mềm đi kèm với ngắt này  Reset MCLR trong khi hoạt động bình thường

63

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Vi điều khiển có chân reset MCLR , nếu ra muốn reset loại này thì phải giữ chân này ở mức low. [ Để đảm bảo chống nhiễu, tức là không reset khi có xung mức low mà ko phải do người dùng tạo ra Microchip tích hợp sẵn module chống nhiễu cho chân này] Ta cũng có thể disable ngắt này bằng cách set bit MCLRE=0 , khi chân này bị disable nó sẽ trở thành đầu vào số  Reset MCLR trong chế độ quản lí nguồn  Reset Watchdog timer [WDT]  Reset programmable Brown-out [BOR] PIC18 cung cấp 1 mạch BOR giúp chúng ta có thể tiết kiệm nguồn. BOR được điều khiển bằng BORV1:BORV0 và BOREN1:BOREN0. Ta cấu hình cho BOR theo bảng sau: Cấu hình BOR

Trạng

thái

của

Hoạt động BOR

BOREN1 BOREN0 SBOREN [RCON]

BOR disable; cho phép BOR 0

0

Unavailable

bằng bằng lập trình lại các bit cấu hình BOR cho phép bằng phần mềm;

0

1

Available

hoạt động BOR được điều khiển bằng SBOREN BOR cho phép bằng phần cứng

1

0

Unavailable

chạy trong chế độ run và idle, disable trong chế độ sleep

64

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

BOR cho phép bằng phần cứng; 1

1

Unavailable

phải được disable bằng lập trình lại các bit cấu hình

 Reset bằng lệnh RESET  Reset khi ngăn xếp đầy  Rest khi ngăn xếp underflow 2.2.2.

Ví dụ

Ví dụ này tôi giới thiệu và xử lí RESET power-up và RESET bằng chân MCLR. Vì đây là 2 loại RESET được dùng phổ biến

*

LED chỉ thị: 1 blink

-->RESET bật nguồn

65

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

2 blinks

--->RESET MCLR

3 blinks

---->

Brown out reset [BOR]

4 blinks

---->

RESET WDT [ watch dog timer

time out] Trong ví dụ mô phỏng bằng proteus tôi chỉ mô phỏng RESET bật nguồn và RESET MCLR. Khi bắt đầu chạy mô phỏng đèn chỉ thị sẽ sáng 1 lần [1 blink] có nghĩa là RESET bật nguồn. Khi ấn RESET MCLR đèn chỉ thị sẽ nháy sáng 2 lần. Code như sau: /* FileName: * Dependencies:

reset_source.c Header [.h] files if applicable, see below

* Processor:

PIC18F

* Compiler:

MCC18 v3.30 or higher

* Writer: Rocket42

*/

include

include "reset.h"

/*Configuration settings: * Oscillator is configured as INTRC * watch dog timer is disabled or enabled [comment out respective line] * Extended instruction mode is disabled * Low voltage programming is disabled */

66

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

pragma config X INST = OFF, LVP = OFF

pragma WDT = OFF //

pragma WDT = ON, WDTPS = 1024

define RESET

1

//Function Protot ypes void user_main[void]; void Blink_LED[unsigned int number_of_blink]; void Delay[void]; //Reset flags unsigned char R_POR, R_MC LR, R_BOR, R_WDT; //main function void main[void]{ // Clear Reset flags R_POR = 0; R_MCLR = 0; R_BOR = 0; R_WDT = 0;

// Configure PORTB as output LATB = 0x00; TRISB = 0x00; if[isPOR[] == RESET]{

//Reset source is POR

Blink_ LED[1]; R_POR = 1; } if[isMCLR[] == RESET]{

//Reset source is MCLR

Blink_ LED[2]; R_MCLR = 1; 67

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

}

if defined[BOR_ENABLED] if[isBOR[] == RESET]{

//Reset source is BOR

Blink_ LED[3]; R_BOR = 1; }

endif

if defined[WDT_ENABLED] if[isWDTTO[] == RESET]{ timer time out

//Reset source is WatchDeog

Blink_ LED[4]; R_WDT = 1; }

endif

StatusReset[]; user_main[]; application program

//Clear the Reset status //call

the

"user_main"

--

The

User

}

void user_main[void] { while[1]; reset happens

//An y user application to be executed and wait untill

}

void Blink_LED[unsigned int number_of_blink] {

68

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

unsigned int n=0;

// ************ BLINK LED ******************************* for[n = 0; n < number_of_blink; n++]{ Delay[];

// Turn on

LATB = 1; Delay[];

// Turn off

LATB = 0; } }

void Delay[void]{ unsigned int delay = 8000; this larger

// If faster oscillator is used, make

while[delay--]; }

 Tip: - Tôi sử dụng mô phỏng bằng PIC18f4550, nếu bạn sử dụng loại Pic18 khác thì chỉ cần chọn lại thiết bị trong trình dịch MPLAB như sau: configure/select device

69

Nguyễn Văn Hùng

[email protected]

Học viện kỹ thuật quân sự

Tiến hành lựa chọn thiết bị mình muốn sử dụng trong mô phỏng ở phần tôi khoanh tròn. Rồi nhấp ‘OK’ thế là hoàn thành - Các bạn có thể download chương trình full ở đây: //sites.google.com/site/drocket42/home

Chủ Đề