Chủ Nhật, 26 tháng 2, 2012

Hướng Dẫn CCS Cho PIC - Bài 7: Các Ngắt Trong PIC



Bài trước:+Bài 6: Giao Tiếp SPI

Bài 7: Các Ngắt Trong PIC



I /  CƠ CHẾ HOẠT ĐỘNG CỦA NGẮT : 
1 / Ngắt 1 cấp : 
+ Trên PIC 14 , 12 ,10 ,tất cả các ngắt chỉ có 1 cấp ưu tiên . Nghĩa là ngắt nào đang được phục vụ thì không thể bị ngắt bởi 1 ngắt khác xảy ra . Cơ chế sinh mã cho ngắt của CCS như sau : nhảy đến địa chỉ ngắt , thường là 004h , sao lưu thanh ghi W,  STATUS , PCLATCH , FSR,  và nhiều thứ vớ vẫn khác,   sau đó nó mới hỏi vòng xem cờ ngắt nào xảy ra  thì nhảy đến hàm phục vụ ngắt đó . thực hiện xong thì phục hồi tất cả thanh ghi trên , rồi mới “RETFIE” – thoát ngắt . Số chu kỳ thực thi từ chỗ ngắt đến khi nhảy vào hàm ngắt cỡ 20 chu kỳ lệnh !, nhảy ra cũng cỡ đó .
+ Điều gì xảy ra nếu chương trình dùng nhiều ngắt và khi có ngắt thì có 2 ngắt trở lên xảy ra đồng thời ? Nghĩa là : 2 ngắt xảy ra cùng lúc , hay khi ngắt A kích hoạt và CCS đang lưu các thanh ghi ( chưa tới hỏi vòng cờ ngắt ) thì ngắt B xảy ra , dĩ nhiên ngắt B không thể kích vector ngắt nhảy tới 004h vì bit cho phép ngắt toàn cục ( GIE ) bị khóa tự động khi có ngắt , chỉ có cờ ngắt B bật mà thôi. Sau khi lưu các thanh ghi , chương trình  kiểm tra cờ ngắt , rõ ràng là nếu bit nào được kiểm tra trước thì  phục vụ trước , dù nó xảy ra sau . Để tránh phục vụ không đúng chỗ , bạn dùng #priority để xác định ưu tiên ngắt ( xem phần chỉ thị tiền xử lý ) . Ngắt ưu  tiên nhất sẽ luôn được hỏi vòng trước .Sau khi xác định cờ ngắt cần phục vụ , nó sẽ thực thi hàm ngắt tương ứng .Xong thì xoá cờ ngắt đó và thoát ngắt . Phục vụ ngắt nào xong thì chỉ xoá cờ ngắt đó .Nếu A ưu tiên hơn B  thì sau khi làm A , chương trình  xoá cờ ngắt A , nhưng cờ B không xoá ( vì đâu có phục vụ ) , nên khi thoát ra ngắt A , nó sẽ lại ngắt tiếp ( vì cờ B đã bật ), lại hỏi vòng cờ ngắt từ đầu : nếu cờ A chưa bật thì xét B, lúc này B bật nên phục vụ B , xong thì xoá cờ B và thoát ngắt .
+ Môt chương trình dùng nhiều ngắt phải lưu ý điều này , tránh trường hợp : ngắt xảy ra liên tục (tràn
ngắt ) , 1 ngắt bị đáp ứng trễ  , ngắt không đúng , . . .



2 / Ngắt 2 cấp : 
+ Chỉ có trên PIC 18 ( và dsPIC ) . Có 2 khái niệm : ngắt ưu tiên thấp (low priority) và ngắt ưu tiên cao ( high priority ) . 2 vector thực thi ngắt tương ứng thường là 0008h (high) và 0018h ( low ) . Một  ngắt thấp đang được phục vụ sẽ bị ngưng và phục vụ ngắt cao ở 0008h nếu ngắt cao xảy ra . Ngược lại , ngắt cao đang xảy ra thì không bao giờ bị ngắt bởi ngắt thấp .
+ Nếu viết hàm ngắt bình thường , không đòi hỏi ưu tiên gì thì CCS sinh mã để tất cả hàm ngắt đều là ngắt ưu tiên cao . Quy trình thực hiện ngắt sẽ như ngắt 1 cấp trên . #priority vẫn được dùng . Số chu kỳ thực thi từ 0008h đến khi nhảy vào thực thi hàm ngắt khoảng 30 chu kỳ , xong hàm ngắt tới khi kết thúc ngắt cũng mất khoảng 30 chu kỳ lệnh .
+ Để sử dụng ngắt 2 cấp ,  khai báo #device phải có high_ints=true . Và hàm ngắt nào muốn ưu tiên cao thì thêm FAST hay HIGH theo sau chỉ thị tiền xử lý hàm đó .
Lưu ý : khi dùng FAST thì không nên dùng HIGH cho các ngắt khác thì mới có ý nghĩa và chỉ  có duy nhất 1 ngắt được ưu tiên FAST , nhưng  có thể có nhiều ngắt đặt ở mức HIGH .
VD :
#int_timer1  FAST 
 Void xu_ly ( ) 
  { . . . 
 } 

 #int_timer2  HIGH 
 Void dinh_thi () 
  { . . . 
 } 

 #int_timer5 HIGH  
 Void vong_lap() 
  { . . . 
 } 

+ Cơ chế sinh mã như sau : có ngắt thấp thì nhảy tới 0018h , sao lưu W, STATUS , FSR0/1/2 ,. . . rồi mới hỏi vòng cờ ngắt thấp . chạy xong hàm ngắt  thì phục hồi tất cả và “RETFIE 0 “ .
+ Riêng ngắt cao đánh dấu FAST không sinh mã sao lưu gì cả mà nhảy thẳng vào hàm ngắt chạy luôn . PIC 18 và dsPIC có cơ chế lưu siêu tốc là FAST STACK REGISTER ( xem datasheet ) . Khi xảy ra ngắt bất kỳ,  W, S , BSR tự động lưu vào thanh ghi trên , PC counter lưu vào stack . xong ngắt thì pop ra . Vấn đề ở chỗ : khi ngắt thấp xảy ra , FAST STACK REGISTER tự động lưu W ,S , BSR , PC -> stack . Trong khi thực hiện hàm phục vụ ngắt thì trường hợp W, S , BSR thay đổi là có thể ( vì vậy mới sao lưu chứ ) . nhưng nếu xảy ra ngắt cao vào thời điểm đó ? FAST STACK REGISTER sẽ bị ghi đè => mất data . Do đó , cơ chế sinh mã của CCS cần phải luôn đúng , nghĩa là : luôn tự sao lưu riêng W ,S , BSR, và các thanh ghi FSR nữa , khi thực  thi ngắt thấp . Còn ngắt cao FAST khi chạy xong sẽ “RETFIE 1 “ – tự động phục hồi W, S , BSR từ  FAST STACK REGISTER . Có 2 trường hợp : 1 là chỉ có ngắt cao , thì không có vấn đề gì  . 2 là ngắt cao ngắt 1 ngắt thấp đang chạy . Phân tích sẽ thấy rằng cho dù bị ngắt trong khi đang sao lưu ,hay chưa kịp sao lưu , hay đã sao lưu vào các biến riêng rồi , cuối cùng chương trình cũng quay ra đúng địa chỉ ban đầu với các thanh ghi W, S , BSR như cũ .
+ Tuân thủ nguyên tắc ngắt cao thực thi tức thời nên CCS chỉ cho 1 ngắt cao FAST duy nhất bất kỳ hoạt động ,  nên không sinh mã hỏi vòng , sao lưu thêm gì cả .
+ Nếu bạn muốn có nhiều ngắt ưu tiên cao , thì dùng HIGH , chương trình sao lưu bình thường như với ngắt thấp , nhưng khi đó ngắt đánh dấu FAST cũng mất tác dụng , CCS xem như là HIGH và xử lý bình thường .

_Như vậy dùng FAST hay HIGH đều có ý nghĩa riêng của nhà  lập trình .

II / KHAI BÁO NGẮT :
_Mỗi dòng VDK có số lượng nguồn ngắt ngắt khác nhau : PIC 14 có 14 ngắt , PIC 18 có 35 ngắt .
_Muốn biết CCS hỗ trợ những ngắt nào cho VDK của bạn , mở file *.h tương ứng , ở cuối file là
danh sách các ngắt mà CCS hỗ trợ nó . Cách khác là vào CCS -> View -> Valid interrupts , chọn
VDK muốn xem , nó sẽ hiển thị danh sách ngắt có thể có cho VDK đó .
_Sau đây là danh sách 1 số  ngắt với chức năng tương ứng :
#INT_GLOBAL : ngắt chung , nghĩa là khi có ngắt xảy ra , hàm theo sau chỉ thị này được thực
thi , bạn sẽ không được khai báo thêm chỉ thị ngắt nào khác khi sử dụng chỉ thị này . CCS không sinh
bất kỳ mã lưu nào , hàm ngắt bắt đầu ngay tại vector ngắt . Nếu bật nhiều cờ cho phép ngắt , có thể
bạn sẽ phải hỏi vòng để xác định ngắt nào . Dùng chỉ thị này tương đương viết hàm ngắt 1 cách thủ
công  mà thôi , như là viết hàm ngắt với ASM vậy .


#INT_AD  : chuyển đổi A /D đã hoàn tất , thường  thì không nên dùng
#INT_ADOF  : I don’t know
#INT_BUSCOL : xung đột bus
#INT_BUTTON : nút nhấn ( không biết hoạt động thế nào )
#INT_CCP1  : có Capture hay compare trên CCP1
#INT_CCP2  : có Capture hay compare trên CCP2
#INT_COMP  : kiểm tra bằng nhau trên Comparator
#INT_EEPROM : hoàn thành ghi EEPROM
#INT_EXT  : ngắt ngoài
#INT_EXT1  : ngắt ngoài 1
#INT_EXT2  : ngắt ngoài 2
#INT_I2C  : có hoạt động I 2C
#INT_LCD  : có hoạt động LCD
#INT_LOWVOLT : phát hiện áp thấp
#INT_PSP  : có data vào cổng Parallel slave
#INT_RB  : bất kỳ thay đổi  nào trên chân B4 đến B7
#INT_RC  : bất kỳ thay đổi  nào trên chân C4 đến C7
#INT_RDA  : data nhận từ RS 232 sẵn sàng
#INT_RTCC  : tràn Timer 0
#INT_SSP  : có hoạt động SPI hay I 2C
#INT_TBE  : bộ đệm chuyển RS 232 trống
#INT_TIMER0 : một tên khác của #INT_RTCC
#INT_TIMER1 : tràn Timer 1
#INT_TIMER2 : tràn Timer 2
#INT_TIMER3 : tràn Timer 3
#INT_TIMER5 : tràn Timer 5
#INT_OSCF               : lỗi OSC
#INT_PWMTB          : ngắt cuả PWM time base
#INT_IC3DR             : ngắt đổi hướng ( direct ) của IC 3
#INT_IC2QEI            : ngắt của QEI
#INT_IC1                   : ngắt IC 1


+ Hàm đi kèm phục vụ ngắt không cần tham số vì không có tác dụng .
+ Sử dụng NOCLEAR sau #int_xxx để CCS không xoá cờ ngắt của hàm đó .

+ Để cho phép ngắt đó hoạt động phải dùng lệnh enable_interrupts ( int_xxxx)enable_interrupts (global ) .
+ Khoá FAST theo sau #int_xxxx để cho ngắt đó là ưu tiên cao , chỉ được 1 ngắt thôi , chỉ có ở PIC 18 và dsPIC .
VD : #int_timer0  FAST  NOCLEAR 


III / CÁC HÀM THIẾT LẬP HOẠT ĐỘNG NGẮT : 
1 /  enable_interrupts ( level ) 
+ level là tên các ngắt đã cho ở trên hay là GLOBAL để cho phép ngắt ở cấp toàn cục .
+ Mọi ngắt của VDK đều có 1 bit cờ ngắt , 1 bit cho phép ngắt . Khi có ngắt thì bit cờ ngắt bị set =1, nhưng ngắt có họat động được hay không tuỳ thuộc  bit  cho phép ngắt . enable_interrupts (int_xxx ) sẽ bật bit cho phép ngắt . Nhưng tất cả các ngắt đều không thể thực thi nếu bit cho phép ngắt toàn cục = 0, enable_interrupts( global ) sẽ bật bit này .
VD : để cho phép ngắt timer0 và timer1  hoạt động:
enable_interrupts (int_timer0); 
enable_interrupts (int_timer1 ) ; 
enable_interrupts ( global );  // chỉ cần dùng 1 lần trừ phi muốn có thay đổi đặc biệt

2 / disable_interrupts ( level ) 
+ level giống như trên .
+ Hàm này vô hiệu 1 ngắt bằng cách set bit cho phép ngắt = 0 .
+ disable_interrupts ( global ) set bit cho phép ngắt toàn cục =0 , cấm tất cả các ngắt .
+ Không  dùng hàm này trong hàm phục vụ ngắt vì không có tác dụng , cờ ngắt luôn  bị xoá tự động .

3 / clear_interupt ( level ) 
+ level không có GLOBAL .
+ Hàm này xoá cờ ngắt của ngắt được chỉ định bởi level .

4 / ext_int_edge ( source , edge ) 
+ Hàm này thiết lập nguồn ngắt ngoài EXTx là cạnh lên hay cạnh xuống .
+ source : nguồn ngắt . Trên PIC 18 có 3 nguồn ngắt trên 3 chân EXT0, EXT1, EXT2 ứng với source =0,1, 2 . Các PIC khác chỉ có 1 nguồn EXT nên source = 0 .
+ edge : chọn cạnh kích ngắt , edge = L_TO_H nếu chọn  cạnh lên ( từ mức thấp  chuyển lên mức cao ) hay H_TO_L nếu chọn cạnh xuống .


IV / CÁC CHƯƠNG TRÌNH VD VỀ NGẮT :
  1 /  #INT_RB : 
+ Sau đây là 1 chương trình điển hình về sử dụng ngắt khi có sự thay đổi trên chân B4-B7 .
+ Mô tả : mỗi khi nhấn nút bất kỳ trên B4-B7 , sẽ kích ngắt RB , hàm phục vụ ngắt có tên  RB_LED được thực thi , hàm này đơn giản là xuất ra LED ở vị trí tương ứng nhưng trên portD từ D4 – D7 .
+ VDK là 16F877 .


#include < 16F877.h > 
#device  PIC16F877 *=16 
#use delay (clock = 20000000 )  //thêm khai báo này nếu ctrình có dùng hàm delay,OSC=20 Mhz 
#byte portb = 0x06      //tạo tên danh định portb thay thế địa chỉ portB là 06h 
#byte portd = 0x08      //tạo tên danh định portd thay thế địa chỉ portD là 08h 

#INT_RB 
Void RB_LED ( )      // hàm phục vụ ngắt 
   portd=portb; 

void main ( ) 
{  set_tris_b ( 0xF0 ) ;    // portB = 11110000 , B4-B7 là ngõ vào , B0-B3 là ngõ ra 
    set_tris_d ( 0x00 ) ;    // portD = 00000000 , D0-D7 đều là ngõ ra 
    enable_interrupts ( INT_RB ) ;  // cho phép ngắt RB 
    enable_interrupts ( GLOBAL ) ;  // cho phép ngắt toàn cục 
// do chương trình không làm gì khác ngoài việc chờ ngắt nên vòng while này trống không  
while( true )  
  { //có thể thêm mã xử lý ở đây . . . 
  } 
} //main






(Nguồn:  TRẦN XUÂN TRƯỜNG)

0 nhận xét:

Đăng nhận xét