Sau này khi ngôn ngữ C ra đời, nhu cầu dụng ngôn ngữ C để thay cho ASM trong việc mô tả các lệnh lập trình cho Vi điều khiển một cách ngắn gọn và dễ hiểu hơn đã dẫn đến sự ra đời của nhi
Trang 1SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 56
I Tổng quan về CCS
I.1 Tại sao sử dụng CCS?
- Sự ra đời của một loại vi điều khiển đi kèm với việc phát triển phần mềm ứng dụng cho việc lập trình cho con vi điều khiển đó Vi điều khiển chỉ hiểu và làm việc với hai con số 0 và
1 Ban đầu để việc lập trình cho VĐK là làm việc với dãy các con số 0 và 1
- Sau này khi kiến trúc của Vi điều khiển ngày càng phức tạp, số lượng thanh ghi lệnh nhiều lần, việc lập trình với dãy các số 0 và 1 không còn phù hợp nữa, đòi hỏi ra đời một ngôn ngữ mới thay thế Và ngôn ngữ lập trình Assembly Ở đây ta không nói nhiều đến Assmebly Sau này khi ngôn ngữ C ra đời, nhu cầu dụng ngôn ngữ C để thay cho ASM trong việc mô tả các lệnh lập trình cho Vi điều khiển một cách ngắn gọn và dễ hiểu hơn đã dẫn đến sự ra đời của nhiều chương trình soạn thảo và biên dịch C cho Vi điều khiển : Keil C, HT-PIC, MikroC, CCS…Đồ án này sử dụng CCS để lập trình vì tính năng mạnh và lập trình đơn giản hơn so với MBLab
I.2 Giới thiệu về CCS
CCS là trình biên dịch lập trình ngôn ngữ C cho Vi điều khiển PIC của hãng Microchip Chương trình là sự tích hợp của 3 trình biên dịch riêng biệt cho 3 dòng PIC khác nhau đó là:
- PCB cho dòng PIC 12 bit opcodes
- PCM cho dòng PIC 14 bit opcodes
- PCH cho dòng PIC 16 và 18 bit
Tất cả 3 trình biên dịch này được tích hợp lại trong một chương trình bao gồm cả trình soạn thảo văn biên dịch là CCS
Giống như nhiều trình biên dịch C khác cho PIC, CCS giúp cho người sử dụng nắm bắt nhanh được vi điều khiển PIC và sử dụng PIC trong các dự án Các chương trình điều khiển sẽ được thực hiện nhanh chóng và đạt hiệu quả cao thông qua việc sử dụng ngôn ngữ lập trình cấp cao – Ngôn ngữ C
II.Viết một chương trình trong CCS:
Đây là ví dụ về cấu trúc 1 chương trình trong CCS :
#include < 16F877 h >
#device PIC6f877 *=16 ADC=10
#use delay(clock=20000000)
Int16 a,b;
Trang 2SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 57
{ }
#INT_TIMER1
Void xu_ly_ngat_timer ( )
{ }
Void Main ( )
{ }
Đầu tiên là các chỉ thị tiền xử lý : # có nhiệm vụ báo cho CCS cần sử dụng những gì trong chương trình C như dùng VXL gì, có dùng giao tiếp PC qua cổng COM không, có dùng ADC không, có dùng DELAY không, có biên dịch kèm các file hay không Các khai báo biến Các hàm con do ta viết : xu_ly_ADC () , _ Các hàm phục vụ ngắt theo sau bởi 1 chỉ thị tiền xử lý cho biết dùng ngắt nào _Chương trình chính
Một chương trình C có thể được viết luôn trong hàm main (), nếu chúng rất ngắn và đơn giản Nhưng khi chương trình bắt đầu dài ra, phức tạp lên 1 chút thì phải phân chia trong các hàm con Các hàm này có thể là :
1/ Hàm không trả về trị
Ví dụ : Void xu_ly( )
{ Z = x+y ; }
Hàm trên chỉ thực hiện các lệnh trong thân hàm , khi gọi hàm này chỉ đơn giản viết :Xu_ly( ) ; 1/ Hàm có trả về trị
Ví dụ : int xu_ly ( int a , int b)
{
Return (a+b) ; }
Hàm trên sẽ trả về tổng (a+b) khi sử dụng , ví dụ tính bằng 2 biến e ,f , chương trình như sau ( trong hàm main() ) :
Main()
{ Int e ,f ,g ;
e=7 ;
f= 4;
g = xu_ly(e ,f ); }
- Mỗi hàm con nên được viết để thực hiện 1 chức năng chuyên biệt nào đó Bên trong 1 hàm con có thể gọi 1 hay nhiều hàm khác Cách thức hoạt động như viết 1 chương trình C trên máy tính
Trang 3SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 58
- Nếu chương trình lớn hơn nữa có thể làm file c rất dài và do đó rất khó kiểm soát, nên sẽ cần phân chia ra các file c trong đó file chính chứa hàm main sẽ được biên dịch Các file c khác chứa các hàm phục vụ chuyên biệt như : cho LCD , Trong file chính chỉ cần thêm dòng
#include < file x.c > là tất cả hàm cần dùng chứa trong file x sẽ được biên dịch vào file hex chung Các ví dụ trong thư mục của CCS nếu có sử dụng LCD sẽ chèn 1 dòng #include < lcd.c> và do đó sẽ gọi được các hàm trong file này mà không cần phải viết lại Điều này có nghĩa là ta có thể viết các file c chứa mã tổng quát có thể dùng chung cho nhiều project, tức là tái sử dụng mã , thay vì phải viết lại chuyên biệt cho từng project Đây là cách làm chuyên nghiệp cho những project lớn
III Sử dụng các biến và hàm, các cấu trúc lệnh, hiển thị tiền xử lí
III.1.Các khai báo và sử dụng biến, hằng, mảng
Các loại biến sau được hỗ trợ :
Int1 số 1 bit = true hay false ( 0 hay 1)
Int8 số nguyên 1 byte ( 8 bit)
Int16 số nguyên 16 bit
Int32 số nguyên 32 bit
Char ký tự 8 bit
Float số thực 32 bit
Short mặc định như kiểu int1
Byte mặc định như kiểu int8
Int mặc định như kiểu int8
Long mặc định như kiểu int16
III.2 Các cấu trúc lệnh ( Statement )
Gồm các lệnh như :
_while (expr) stmt : xét điều kiện trước rồi thực thi biểu thức sau
_ do stmt while (expr) : thực thi biểu thức rồi mới xét điều kiện sau
_Return : dùng cho hàm có trả về trị , hoặc không trả về trị cũng được, khi đó chỉ cần dùng: return ; ( nghĩa là thoát khỏi hàm tại đó )
_Break : ngắt ngang ( thoát khỏi ) vòng lặp while
Trang 4SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 59
III.3.Chỉ thị tiền xử lí
#ASM và #ENDASM:
Cho phép đặt 1 đoạn mã ASM giữa 2 chỉ thị này , Chỉ đặt trong hàm CCS định nghĩa sẵn 1 biến 8 bit _RETURN_ để bạn gán giá trị trả về cho hàm từ đoạn mã Assembly _C đủ mạnh
để thay thế Assmemly Vì vậy nên hạn chế lồng mã Assembly vào vì thường gây ra xáo trộn dẫn đến sau khi biên dịch mã chạy sai, trừ phi bạn nắm rõ Assembly và đọc hiểu mã
Assembly sinh ra thông qua mục C/Asm list _Khi sử dụng các biến không ở bank hiện tại , CCS sinh thêm mã chuyển bank tự động cho các biến đó Nếu sử dụng #ASM ASIS thì CCS không sinh thêm mã chuyển bank tự động, phải tự thêm vào trong mã ASM
#INCLUDE :
Cú pháp : #include <filename>
Hay #include “ filename”
Trang 5SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 60
Filename : tên file cho thiết bị *.h , *.c Nếu chỉ định file ở đường dẫn khác thì thêm đường dẫn vào Luôn phải có để khai báo chương trình viết cho VĐK nào, và luôn đặt ở dòng đầu tiên
#BIT , #BYTE , #LOCATE Và # DEFINE:
#BIT id = X Y
Với id : tên biến X : biến C ( 8,16,32,…bit) hay hằng số địa chỉ thanh ghi Y : vị trí bit trong x -> tạo biến 1 bit đặt ở byte x vị trí bit y, tiện dùng kiểm tra hay gán trị cho bit thanh ghi Điểm khác biệt so với dùng biến 1 bit từ khai báo int1 là : int1 tốn 1 bit bộ nhớ, đặt ở thanh ghi đa mục đích nào đó do CCS tự chọn, còn #BIT thì không tốn thêm bộ nhớ do id chỉ là danh định đại diện cho bit chỉ định ở biến x, thay đổi giá trị id ( 0 / 1 ) sẽ thay đổi giá trị bit tương ứng Y thay đổi trị X
#BYTE id = x
X: địa chỉ id : tên biến C Gán tên biến id cho địa chỉ (thanh ghi ) x, sau đó muốn gán hay kiểm tra địa chỉ x chỉ cần dùng id Không tốn thêm bộ nhớ, tên id thường dùng tên gợi nhớ chức năng thanh ghi ở địa chỉ đó Lưu ý rằng giá trị thanh ghi có thể thay đổi bất kỳ lúc nào do hoạt động chương trình nên giá trị id cũng tự thay đổi theo giá trị thanh ghi đó Không nên dùng id cho thanh ghi đa mục đích như 1 cách dùng biến int8 vì CCS có thể dùng các thanh ghi này bất kỳ lúc nào cho chương trình
# LOCATE id = x
Làm việc như #byte nhưng có thêm chức năng bảo vệ không cho CCS sử dụng địa chỉ đó vào mục đích khác
Sử dụng #LOCATE để gán biến cho 1 dãy địa chỉ kề nhau ( cặp thanh ghi ) sẽ tiện lợi hơn thay vì phải dùng 2 biến với #byte
# DEFINE id text
Text : chuỗi hay số Dùng định nghĩa giá trị VD :
#define a 12345
# DEVICE :
# DEVICE chip option chip : tên VĐK sử dụng , không dùng tham số này nếu đã khai báo tên chip ở # include option : toán tử tiêu chuẩn theo từng chip:
* = 5 dùng pointer 5 bit ( tất cả PIC )
* = 8 dùng pointer 8 bit ( PIC14 và PIC18 )
* = 16 dùng pointer 16 bit ( PIC14 ,PIC 18) ADC = x sử dụng ADC x bit ( 8 , 10 , bit tuỳ chip ) , khi dùng hàm read_adc( ) , sẽ trả về giá trị x bit
Trang 6SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 61
ICD = true : tạo mã tương thích debug phần cứng Microchip
HIGH_INTS = TRUE : cho phép dùng ngắt ưu tiên cao
_Khai báo pointer 8 bit , bạn sử dụng được tối đa 256 byte RAM cho tất cả biến chương trình _Khai báo pointer 16 bit , bạn sử dụng được hết số RAM có của VDK
_Chỉ nên dùng duy nhất 1 khai báo #device cho cả pointer và ADC
VD : #device * = 16 ADC = 10
# ORG :
# org start , end
# org segment
#org start , end { } Start , end: bắt đầu và kết thúc vùng ROM dành riêng cho hàm theo sau ,
hoặc để riêng không dùng
# USE :
# USE delay ( clock = speed ) Speed : giá trị OSC mà bạn dùng VD: dùng thạch anh dao động 40Mhz thì : #use delay( clock = 40000000) _Chỉ khi có chỉ thị này thì trong chương trình bạn mới được dùng hàm delay_us ( ) và delay_ms( ) #USE fast_io ( port) Port : là tên port :từ A-G ( tuỳ chip ) _Dùng cái này thì trong chương trình khi dùng các lệnh io như output_low() , nó sẽ set chỉ với 1 lệnh , nhanh hơn so với khi không dùng chỉ thị này –b -
- Trong hàm main( ) bạn phải dùng hàm set_tris_x( ) để chỉ rõ chân vào ra thì chỉ thị trên mới
có hiệu lực , không thì chương trình sẽ chạy sai _Không cần dùng nếu không có yêu cầu gì đặc biệt VD : # use fast_io( A )
#USE I2C ( options ) _Thiết lập giao tiếp I2C Option bao gồm các thông số sau, cách nhau bởi dấu phẩy : Master : chip ở chế độ master
Slave : chip ở chế độ slave
SCL = pin : chỉ định chân SCL
SDA = pin : chỉ định chân SDA
ADDRESS =x : chỉ định địa chỉ chế độ slave
FAST : chỉ định FAST I2C
SLOW : chỉ định SLOW I2C
RESTART_WDT : restart WDT trong khi chờ I2C_READ( )
FORCE_HW : sử dụng chúc năng phần cứng I2C ( nếu chip hỗ trợ )
NOFLOAT_HIGH : không cho phép tín hiệu ở float high, tín hiệu được lái từ thấp lên cao SMBUS : bus dùng không phải bus I2C, nhưng là cái gì đó tương tự
#use I2C (slave , sda= pin_C4 , scl= pin_C3 , address = 0xa00 , FORCE_HW )
Trang 7SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 62
#USE RS232 ( options ) _Thiết lập giao tiếp RS232 cho chip ( có hiệu lực sau khi nạp chương trình cho chip, không phải giao tiếp RS232 đang sử dụng để nạp chip )
Option bao gồm :
BAUD = x : thiết lập tốc độ baud rate : 19200 , 38400 , 9600 ,
PARITY = x : x= N ,E hay O , với N : không dùng bit chẵn lẻ
XMIT = pin : set chân transmit ( chuyển data)
RCV = pin : set chân receive ( nhận data ) _Các thông số trên hay dùng nhất , các tham số khác sẽ bổ sung sau
VD :
#use rs232(baud = 19200,parity = n, xmit = pin_C6, rcv = pin_C7)
* Một số chỉ thị tiền xử lý khác :
#CASE : cho phép phân biệt chữ hoa / thường trong tên biến, dành cho những ai quen lập trình C #OPT n :với n=0 – 9 : chỉ định cấp độ tối ưu mã, không cần dùng thì mặc định là 9 ( very tối ưu ) #PRIORITY ints : với ints là danh sách các ngắt theo thứ tự ưu tiên thực hiện khi có nhiều ngắt xảy ra đồng thời, ngắt đứng đầu sẽ là ngắt ưu tiên nhất, dùng ngắt nào đưa ngắt đó vô Chỉ cần dùng nếu dùng hơn 1 ngắt Xem cụ thể phần ngắt VD : #priority int_CCP1, int_timer1 // ngắt CCP1 ưu tiên nhất
IV.Các hàm xử lí số, xử lí Bit, Delay
IV.1 Các hàm xử lý số
Bao gồm các hàm: Sin() cos() tan() Asin() acos() atan() Abs() : lấy trị tuyệt đối Ceil( ) :làm tròn theo hướng tăng Floor ( ) :làm tròn theo hướng giảm Exp ( ) : tính e^x Log ( ) : Log10 ( ) : Pow ( ) : tính luỹ thừa Sqrt ( ) :căn thức _Các hàm này chạy rất chậm trên các VDK không
có bộ nhân phần cứng ( PIC 14 ,12 ) vì chủ yếu tính toán với số thực và trả về cũng số thực (
32 bit ) và bằng phần mềm
IV.2 Các hàm xử lý bit và phép toán
Shift_right ( address , byte , value ) / Right
Dịch phải (trái ) 1 bit vào 1 mảng hay 1 cấu trúc Địa chỉ có thể là địa chỉ mảng hay địa chỉ trỏ tới cấu trúc ( kiểu như &data) Bit 0 byte thấp nhất là LSB
Bit_clear ( var , bit )
Bit_set ( var , bit )
Bit_clear ( ) dùng xóa ( set = 0 ) bit được chỉ định bởi vị trí bit trong biến var
Bit_set ( ) dùng set=1 bit được chỉ định bởi vị trí bit trong biến var
var : biến 8 , 16 , 32 bit bất kỳ
Trang 8SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 63
bit : vị trí clear ( set ) : từ 0-7 ( biến 8 bit) , 0-15 ( biến 16 bit ) , 0-31 (biến 32 bit ) _Hàm không trả về trị
VD : Int x; X=11 ; //x=1011 Bit_clear ( x ,1 ) ; // x= 1001b = 9
Bit_test ( var , bit ) :
- Dùng kiểm tra vị trí bit trong biến var
Hàm trả về 0 hay 1 là giá trị bit đó trong var
var : biến 8, 16 ,32 bit
bit : vị trí bit trong var
Swap ( var ) : _var : biến 1 byte
- Hàm này tráo vị trí 4 bit trên với 4 bit dưới của var, tương đương var =( var>>4 ) Hàm không trả về trị
Make8 ( var , offset ) :
- Hàm này trích 1 byte từ biến var
var : biến 8,16,32 bit offset là vị trí của byte cần trích ( 0,1,2,3)
Hàm trả về giá trị byte cần trích VD :
Int16 x = 1453 ; // x=0x5AD
Y = Make(x, 1) ; //Y= 5 = 0x05
Make16 ( varhigh , varlow ) :
- Trả về giá trị 16 bit kết hợp từ 2 biến 8 bit varhigh và varlow Byte cao là varhigh , thấp là varlow
Make32 ( var1 , var2 , var3 , var4 ) :
- Trả về giá trị 32 bit kết hợp từ các giá trị 8 bit hay 16 bit từ var1 tới var4 Trong đó var2 đến var4 có thể có hoặc không Giá trị var1 sẽ là MSB , kế tiếp là var2 , Nếu tổng số bit kết hợp ít hơn 32 bit thì 0 được thêm vào MSB cho đủ 32 bit VD:
Int a=0x01 , b=0x02 , c=0x03 , d=0x04 ; // các giá trị hex
Int32 e ;
e = make32 ( a , b , c , d ); // e = 0x01020304
e = make32 ( a , b , c , 5 ) ; // e = 0x01020305
e = make32 ( a, b, 8 ); // e = 0x00010208
e = make32 ( a ,0x1237 ) ; // e = 0x00011237
IV.3 Các hàm Delay
Để sử dụng các hàm delay , cần có khai báo tiền xử lý ở đầu file , VD : sử dụng OSC
20 Mhz , cần khai báo : #use delay ( clock = 20000000 )
Trang 9SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 64
Hàm delay không sử dụng bất kỳ timer nào Chúng thực ra là 1 nhóm lệnh ASM để khi thực thi từ đầu tới cuối thì xong khoảng thời gian mà bạn quy định Tuỳ thời gian delay yêu cầu dài ngắn mà CCS sinh mã phù hợp có khi là vài lệnh NOP cho thời gian rất nhỏ Hay
1 vòng lặp NOP Hoặc gọi tới 1 hàm phức tạp trong trường hợp delay dài Các lệnh nói chung
là vớ vẩn sao cho đủ thời gian quy định là được Nếu trong trong thời gian delay lại xảy ra ngắt thì thời gian thực thi ngắt không tính vào thời gian delay, xong ngắt nó quay về chạy tiếp các dòng mã cho tới khi xong hàm delay Do đó thời gian delay sẽ không đúng
Có 3 hàm phục vụ :
1 / delay_cycles (count ) Count : hằng số từ 0 – 255, là số chu kỳ lệnh 1 chu kỳ lệnh bằng 4 chu kỳ máy
- Hàm không trả về trị Hàm dùng delay 1 số chu kỳ lệnh cho trước VD :
delay_cycles ( 25 ) ; // với OSC = 20 Mhz , hàm này delay 5 us
2 / delay_us ( time ) Time : là biến số thì = 0 – 255 , time là 1 hằng số thì = 0 -65535 _Hàm không trả về trị
- Hàm này cho phép delay khoảng thời gian dài hơn theo đơn vị us
- Quan sát trong C / asm list bạn sẽ thấy với time dài ngắn khác nhau, CSS sinh mã khác nhau
3 / delay_ms (time ) Time = 0-255 nếu là biến số hay = 0-65535 nếu là hằng số
- Hàm không trả về trị
- Hàm này cho phép delay dài hơn nữa
V Xử lí ADC, Các hàm I/O
V.1 Xử lý ADC
PIC có nhiều chân phục vụ xử lý ADC với nhiều cách thức khác nhau Để dùng ADC , bạn phải có khai báo #DEVICE cho biết dùng ADC mấy bit ( tuỳ chip hỗ trợ, thường là 8 hay
10 bit hoặc hơn) Bạn cần lưu ý là: 1 VDK hỗ trợ ADC 10 bit thì giá trị vào luôn là 10 bit, nhưng chia cho 4 thì còn 8 bit Do đó 1 biến trở chiết áp cấp cho ngõ vào ADC mà bạn chọn chế độ 10 bit thì sẽ rất nhạy so với chế độ 8 bit ( vì 2 bit cuối có thay đổi cũng không ảnh hưởng giá trị 8 bit cao và do đó kết quả 8 bit ADC ít thay đổi ), nếu chương trình có chế độ kiểm tra ADC để cập nhật tính toán hay dùng ngắt ADC, thì nó sẽ chạy hoài thôi Dùng ADC
8 bit sẽ hạn chế điều này Do đó mà CCS cung cấp chọn lựa ADC 8 hay 10 bit tùy mục đích
sử dụng
V.2 Các hàm vào ra trong IC
1 / Output_low ( pin ) , Output_high (pin ) :
_Dùng thiết lập mức 0 ( low, 0V ) hay mứ c 1 ( high , 5V ) cho chân IC , pin chỉ vị trí chân
Trang 10SVTH: Lê Cao Thượng – Trần Thành Nghĩa Trang 65
_Hàm này sẽ đặt pin làm ngõ ra, xem mã asm để biết cụ thể
_Hàm này dài 2-4 chu kỳ máy Cũng có thể xuất xung dùng set_tris_X() và #use fast_io VD : chương trình sau xuất xung vuông chu kỳ 500ms, duty =50% ra chân B0 ,nối B0 với 1 led sẽ làm nhấp nháy led
#include <16F877.h>
#use delay( clock=20000000)
Main()
{ while(1)
{ output_high(pin_B0) ;
Delay_ms(250) ; // delay 250ms
Output_low (pin_B0);
Delay_ms (250 ); } }
2 / Output_bit ( pin , value ) :
Pin : tên chân ; Value : giá trị 0 hay 1
- Hàm này cũng xuất giá trị 0 / 1 trên pin, tương tự 2 hàm trên Thường dùng nó khi giá trị ra tuỳ thuộc giá trị biến 1 bit nào đó, hay muốn xuất đảo của giá trị ngõ ra trước đó
3 / Output_float ( pin ) :
- Hàm này set Pin như ngõ vào
4 / Input ( pin ) :
- Hàm này trả về giá trị 0 hay 1 là trạng thái của chân IC Giá trị là 1 bit
5 / Output_X ( value ) :
- X là tên port có trên chip Value là giá trị 1 byte
- Hàm này xuất giá trị 1 byte ra port Tất cả chân của port đó đếu là ngõ ra
6 / Input_X ( ) : _X : là tên port ( a, b ,c ,d e )
- Hàm này trả về giá trị 8 bit là giá trị đang hiện hữu của port đó
7 / Port_B_pullups ( value ) : _Hàm này thiết lập ngõ vào port B pullup Value =1 sẽ kích hoạt tính năng này và value =0 sẽ ngừng
- Chỉ các chip có port B có tính năng này mới dùng hàm này
8 / Set_tris_X ( value ) : Hàm này định nghĩa chân IO cho 1 port là ngõ vào hay ngõ ra Chỉ được dùng với #use fast_IO Sử dụng #byte để tạo biến chỉ đến port và thao tác trên biến này chính là thao tác trên port
Value là giá trị 8 bit Mỗi bit đại diện 1 chân và bit=0 sẽ set chân đó là ngõ vào, bit= 1 set chân đó là ngõ ra