Hiển thị các bài đăng có nhãn code điện tử. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn code điện tử. Hiển thị tất cả bài đăng

Thứ Sáu, 19 tháng 10, 2012

Code mẫu cho PIC - Nháy LED đơn

Nhấp nháy LED có thể coi là một chương trình “Kinh điển”. Mỗi người khi bắt tay vào
học VĐK thì bài học đầu tiên là làm nhấp nháy một hay vài con LED trên chân VĐK. Trong
tài liệu này tôi cũng chọn bài tập đó để bắt đầu.
Mục đích của bài như trên đã nói: Làm nhấp nháy 8 LED tại PORTB của PIC 16F877A,
thời gian trễ do người lập trình định trước.
Những điều thu được qua bài học:
‐ Vẽ một mạch điện tử hoàn chỉnh dùng OrCad 9.2
‐ Tạo một Dự án trong CCS (cái này đã nói trong phần 2)
‐ Tệp định nghĩa các thanh ghi của PIC do người dùng tạo ra
‐ Thiết lập chế độ vào ra cho một cổng của PIC
‐ Sử dụng hàm tạo trễ thời gian

Dưới đây là sơ đồ phần cứng. Trong sơ đồ các LED được mắc chung lên dương nguồn
thông qua điện trở. Gia trị điện trở thay đổi trong khoảng 100Ω cho đến 560Ω tùy theo độ
sáng của LED mà ta muốn và cũng để đảm bảo dòng qua mỗi LED không quá 20mA khi
nguồn cấp là 5V. Như vậy để làm sáng LED ta chỉ việc đưa mức 0 ra các chân PIC và ngược
lại để tắt ta đưa mức 1.


Sơ đồ mạch nháy 8 LED tại PORTB





Sơ đồ mạch nguồn cho PIC



Mã nguồn chương trình nạp vào PIC


//================================================= =======
// Ten chuong trinh : Mach nhay den LED
// Mo ta phan cung : Dung PIC16F877A ‐ thach anh 20MHz
// : LED giao tiep voi PORTB
// : Cuc am cua LED noi voi PORTB
// : RB0 ‐ RB7 la cac chan output
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
// Chu thich : dung che do Power On Reset, PORTB = 00000000
// : chuong trinh viet cho PIC Tutorial
// : chuong trinh nay hoan toan mien phi va co the dung cho
// : moi muc dich khac nhau
//================================================= =======


#include <16f877a.h>
#include <def_877a.h>
#device *=16 ADC=8
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,
NOLVP, NOCPD, NOWRT
#use delay(clock=20000000)


void main()

{
// Thiet lap che do cho PORTB
TRISB = 0x00; // Tat ca PORTB deu la cong xuat du lieu
PORTB = 0xFF; // Tat het cac LED
While(1)
{
PORTB = 0; // Cho các LED sáng
delay_ms(250); // Tạo thời gian trễ 250ms
PORTB = 0xFF;
delay_ms(250);
}
}


Qua ví dụ đơn giản trên bạn hiểu cách xuất dữ liệu ra một cổng của PIC và dùng các
hàm tạo trễ.
Thủ tục thiết lập vào ra cho một cổng hay một chân của PIC
‐ Ghi giá trị vào thanh ghi điều khiển chế độ của cổng tương ứng là TRISx
o Bit 0 ứng với chân xuất dữ liệu
o Bit 1 ứng với nhận dữ liệu
o Thanh ghi TRISx có thể câu hình theo từng bit
‐ Khi muốn xuất dữ liệu, ví dụ ra PORTB, câu lệnh là: PORTB = gia_tri;
‐ Khi muôn nhận dữ liệu từ PORTB, câu lệnh là: data_in = PORTB;
Về các hàm tạo trễ, trong CCS hỗ trọ sẵn 3 loại hàm tạo trễ là:
‐ delay_cycles(gia_tri): gia_tri là thời gian trễ tính theo số chu kỳ máy
‐ delay_us(gia_tri): Tạo trễ Micro giây
‐ delay_ms(gia_trị): Tạo trễ Mili giây
Bản chất của các hàm tạo trễ là đưa Vi điều khiển vào một vòng lặp chẳng làm gì cả cho
đủ số thời gian mà ta cần. Ngoài việc dùng hàm tạo trễ có sẵn ta có thể tự viết hàm tạo trễ
dùng bộ Timer.

Code mẫu cho PIC - Đo nhiệt độ, Hiển thị lên LCD 16x2

Bộ chuyển đổi từ tương tự sang số là một khối mạch điện tử quan trọng, có mặt trong rất nhiều thiết kế điện tử. Các bộ ADC thực tế được đóng gói trong những IC chuyên dụng, do nhiều hãng sản xuất. Điểm quan trong cần lưu ý ở các bộ ADC này là độ phân giải và tốc độ lấy mẫu tìn hiệu. Độ phân giải của bộ ADC có thể là 8‐bít, 10‐bít, 12‐bít, 16‐bít, 24‐bít… Tốc độ lấy mẫu của ADC có thể nhanh hay chậm, tùy từng ứng dụng mà ta chọn tốc độ thích hợp.
Vi điều khiển PIC là một trong những dòng Vi điều khiển có phần giao tiếp ngoại vi mạnh và đa dạng. Bên trong PIC đã được tích hợp sẵn một bộ ADC có độ phân giải tối đa là 10‐bít (tùy chon là 8‐bit hay 10‐bit). Với bộ ADC trong PIC ta có thể làm được khá nhiều công việc, dưới đây tôi trình bày một ứng dụng của bộ ADC trong việc thiết kế mạch đo nhiệt độ sử dụng sensor nhiệt LM335.

Dưới đây là phần code mạch đo nhiệt dộ, hiển thị trên LCD.


//================================================= =======
// Ten chuong trinh : Mach do nhiet do
// Mo ta phan cung : Dung PIC16F877A ‐ thach anh 20MHz
// : LCD giao tiep voi PORTD
// : Dau ra LM335 dua vao chan AN0
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
// Chu thich : hoac cac chu thich khac
// : dung che do Power On Reset
// : chuong trinh viet cho PIC Tutorial
//================================================= =======
#include <16F877A.h>
#include <def_877a.h>
#device *=16 adc=10
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,
NOLVP, NOCPD, NOWRT
#use delay(clock=20000000)
#use rs232(baud=115200,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=9)
#include <lcd_lib_4bit.c> // Thu vien ham cho LCD
int8 low,high,key,mode,min,max,mode1,i;
int1 do_F;
void convert_bcd(int8 x);
void bao_dong();
void test();
//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
void main()
{

float value;
on_off =1;
min =15; //nhiet do min default
max =35; //nhiet do max default
do_F =0 ;
i = 50 ;
mode =0 ;
mode1 = 0 ;
trisa = 0xFF;
trisb = 0x01;
trisd = 0x00;
LCD_init();
Printf(LCD_putchar,ʺLop DT8 ‐ BKHNʺ);
LCD_putcmd(0xC0);
Printf(LCD_putchar,ʺKhoi tao...ʺ);
// Khoi tao cho ngat ngoai
enable_interrupts (INT_EXT);
ext_int_edge(H_TO_L);
enable_interrupts (GLOBAL);
// Khoi tao che do cho bo ADC
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_INTERNAL);
delay_us(10);
// Lay mau nhiet do lan dau tien
value=(float)read_adc();
value = (value ‐ 558.5)/2.048; // For 5V supply
// value = (value ‐ 754.8)/2.048; // For 3.7V Supply
// value = (value ‐ 698.2)/2.048; // For 4V supply
convert_bcd((int8)value); // Tach so tram, chuc, donvi de hien thi len LED 7
delay_ms(1000);
LCD_putcmd(0xC0);
Printf(LCD_putchar,ʺKhoi tao xongʺ);
while(1)
{
if (i==50)
{
value = read_adc();

value=(value‐558.5)/2.048;
if (do_F==1) value=1.8*value+32;
convert_bcd((int8)value);
printf(ʺ\n\rNhiet do phong: %uʺ,value);// Gui gia tri len may tinh
LCD_putcmd(0xC0);
printf(LCD_putchar,ʺ Temp = ʺ);
LCD_putchar(high); LCD_putchar(low);
if (do_F==0) printf(LCD_putchar,ʺ Cʺ);
else printf(LCD_putchar,ʺ Fʺ);
i=0;
}
i++;
if(((int8)value > 40) || ((int8)value < 15)) on_off=1;
else
{
on_off = 0;
LCD_Putcmd(0xCF);
LCD_putchar(ʺ ʺ);
blink=0;
}
if (on_off==1)
{
if (blink==0) {
LCD_Putcmd(0xCF);LCD_putchar(ʺ!ʺ);blink=1;delay_ms(250);}
else {LCD_Putcmd(0xCF);LCD_putchar(ʺ ʺ);blink=0;delay_ms(250);}
}
}
}//end main‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
#INT_EXT
void test()
{
if (do_F == 1) do_F=0;
else do_F=1;
}
void convert_bcd(int8 x)
{
low=x%10; //chia lay phan du, so hang don vi

high=x/10; //tach hang tram va hang chuc
low = low + 0x30;
high = high + 0x30;
}
void bao_dong(){
int8 i;
if (blink == 0) blink = 1;
else blink=0;
for(i=0;i<50;i++)
{
LCD_Putcmd(0xCF);
if (blink==0) LCD_putchar(ʺ!ʺ);
else LCD_putchar(ʺ ʺ);
}
}

Dưới đây là một sơ đồ dùng PIC và LM335 để đo nhiệt độ, hiển thị trên LCD. Trong chương trình bạn thấy có hàm chuyển đổi nhiệt độ từ ía trị độ K về độ C. Nguyên nhân có hàm đó là do con LM335 thay đổi 10mV/K, ta cần hiển thị là độ C. Nhận thấy 0oC = 273K, như vậy tại 0oC con LM335 sẽ xuất ra một điện áp là 2.73V và với điện áp này, ADC trong PIC sẽ cho giá trị số là: (2.73*1023)/5 = 558 . 5585 . Như vậy khi tính toán giá trị nhiệt độ ta cần trừ đi giá trị 558.558 này. Công thức đầy đủ là:

Do_C= [(adc_value)- 558.558] / 2.048Giá trị 2.048 có là do ta dùng ADC 10‐bit, điện áp lấy mẫu là 5V, như vậy mỗi mức lượng tử sẽ tương ứng với 5V/1024 = 4.883mV.
LM335 thay dổi 10mV/K do đó ứng với sụ thay đổi 1 độ C sẽ thay đổi 2.048 mức lượng tử (10mV/4.883mV = 2.048). Công thức trên là cho ADC 10‐bit, với các bộ ADC 8‐bit hay 12‐bit việc tính toán chuyển đổi giá trị cũng tương tự.


Mạch đo nhiệt độ LM335 hiển thị trên LCD 16x2

Code mẫu cho PIC - Truyền thông nối tiếp RS232

Việc giao tiếp giữa Vi điều khiển và máy tính là bài lập trình khá quan trọng khi ta làm việc với các dòng Vi điều khiển khác nhau. Với Vi điều khiển PIC cũng vậy, trong mỗi IC PIC đều có tích hợp một khối giao tiếp máy tính USART. Ta sử dụng khối giao tiếp này để truyền dữ liệu lên máy tính và xử lý dữ liệu đó tùy vào mục đích của người lập trình. Để nhận dữ liệu do Vi điều khiển truyền lên máy tính ta có thể sử dụng các phần mềm giao tiếp COM có sẵn hay viết một chương trình mới, sử dụng các ngôn ngữ lập trình như C++, VB hay Delphi… Trong chương trình ví dụ dưới đây tôi sử dụng công cụ sẵn có của CCS là Serial Port Monitor để truyền và nhận dữ liệu từ PIC.

Sơ đồ mạch điện ORCAD. Mạch sử dụng IC MAX232 để kết nối đến cổng COM của
máy tính. Mạch đơn giản chỉ nhằm mục đích giới thiệu khối giao tiếp máy tính của PIC và
cách lập trình cho nó trong CCS.


Trong chương trình ta có sử dụng hàm xử lý ngắt nối tiếp để xử lý ký tự nhân được từ máy
tính. Khi có ngắt xảy ra, ta gọi hàm getc() sẽ trả về ký tự vừa nhận được. Trên màn hình LCD
sẽ hiển thị ký tự mà ta gõ từ bàn phím máy tính.




Mạch giao tiếp máy tính, hiển thị LCD



Mã nguồn chương trình:


#include <16f877a.h>
#include <def_877a.h>
#use delay(clock=20000000)
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,
NOLVP, NOCPD, NOWRT
// Khai báo sử dụng giao tiếp nối tiếp RS232
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=9)


#include <lcd_lib_4bit.c>


int8 count=0;
char string_in[16];


#INT_RDA // Hàm xử lý ngắt nối tiếp
Receive_isr() {
char c;
int8 i;
count++;
c = getc();
putc(c);
if (c==ʹcʹ | c==ʹCʹ)
{
LCD_putcmd(0x01); //Clear Screen
c=ʹcʹ;
count=0;
}
if ((count<=16) && (c!=ʹcʹ)) LCD_putchar(c);
if (count > 16)
{
count=0;
LCD_putcmd(0xC0);
}
}
void main()
{

enable_interrupts(int_rda);
enable_interrupts(GLOBAL);

lcd_init(); // Khởi tạo cho LCD
lcd_putcmd(0x01);
lcd_putcmd(line_1);
printf(ʺEnter a String.ʺ);
printf(ʺOr anything you want!ʺ);
while (1) {}
}


Mô tả chương trình: Trên đây là chương trình giao tiếp với máy tính, ta thấy trong CCS để sử dụng giao tiếp nối tiếp ta chỉ cần khai báo #use rs232(). Các hàm giao tiếp với máy tính mà CCS hỗ trợ là:
‐ putc(char ky_tu) : Gửi một ký tự ASCII lên máy tính
‐ getc() : Hàm trả về một ký tự nhận được từ máy tính
‐ printf(string): hàm gửi một chuỗi ký tự lên máy tính

Code mẫu cho PIC - Sử dụng Ngắt trong PIC

Trong Vi điều khiển PIC có nhiều nguồn ngắt. Để biết cụ thể ta có thể vào mục View >> Valid Interrupts . Khi đó một của sổ sẽ hiện ra liệt kê đầy đủ các nguồn ngắt của từng con PIC.




Các nguồn ngắt trong PIC





Để viết một hàm phục vụ ngắt ta chỉ việc thêm khai báo #INT_tên_ngắt vào
trước hàm phục vụ cho ngắt đó. Khi đó trình dich sẽ hiểu đó là địa chỉ hàm cho ngắt, khi có ngắt tương ứng xảy ra thì nó sẽ nhảy đến vị trí đó . Lấy ví dụ khi ta muốn xử lý ngắt ngoài, hàm sẽ được viết như sau:



#INT_EXT
Ext_isr()
{
// Nhập mã tại đây
}








Dưới đây là chương trình nháy led theo nhiều kiểu khác nhau, sử dụng 1 phím bấm nối với chân ngắt ngoài RB0 để chọn kiểu nháy. Có 8 kiểu nháy LED khác nhau, Khi đến kiểu nháy thứ 8, nếu ta nhấn thì sẽ trở về chế độ ban đẩu. Ban đầu biến mode = 0 và tất cả các LED đều tắt Mỗi khi nhấn phím bấm, biến mode sẽ tăng lên 1 đơn vị. Giá trị biến mode tương ứng với chương trình nháy được thực hiện. Khi mode = 9 thì sẽ được gán về mode = 0. Các kiểu nháy khác nhau là do ta bật tắt các LED trên cổng D theo các cách khác nhau. Lấy ví dụ khi ta muôn các LED nháy xen kẽ nhau ta chỉ việc gửi ra cổng D giá trị AAh (10101010) và 55h (01010101).

Nháy LED nhiều chế độ dùng Ngắt



Mã nguồn chương trình:


#include <16F877A.h>
#include <def_877a.h>
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,
NOLVP, NOCPD, NOWRT
#use delay(clock=20000000)


int8 mode,i;
byte temp;


#INT_EXT
EXT_ISR() {

mode++;
if (mode==9) mode = 0;
}
// End of INT

void program1();
void program2();
void program3();
void program4();
void program5();
void program6();
void program7();
void program8();

void main() {

trisd = 0x00;
trisb = 0xFF;
portd=0xff;
enable_interrupts(int_EXT);
ext_int_edge(H_TO_L); // Chọn ngắt theo sườn âm
enable_interrupts(GLOBAL);
mode = 0;
while (1) {
switch(mode) {
case 1: program1(); break;
case 2: program2(); break;
case 3: program3(); break;
case 4: program4(); break;
case 5: program5(); break;
case 6: program6(); break;
case 7: program7(); break;
case 8: program8(); break;
}
}
}

void program1() {

PortD = 0x00;
delay_ms(250);
Portd = 0xFF;
delay_ms(250);
}
void program2() { // LED sáng chạy từ trái qua phải
temp = 0xFF;
for (i=0;i<=8;i++) {
portd = temp;
delay_ms(250);
temp >>= 1;
}
}
void program3() { // LED sáng chạy từ phải qua trái
temp = 0xFF;
for (i=0;i<=8;i++) {
portd = temp;
delay_ms(250);
temp <<= 1;
}
}
void program4() {
portd = 0xAA;
delay_ms(500);
portd = 0x55;
delay_ms(500);
}
void program5() {
Portd = 0x7E; delay_ms(150);
Portd = 0xBD; delay_ms(250);
Portd = 0xDB; delay_ms(150);
Portd = 0xE7; delay_ms(150);
Portd = 0xDB; delay_ms(150);
Portd = 0xBD; delay_ms(150);
Portd = 0x7E; delay_ms(150);
}
void program6() {
temp = 0xFF;
for (i=0;i<=8;i++) {
portd = temp;
delay_ms(250);

temp = temp >> 1;
}
}
void program7() {

Portd = 0xFE; delay_ms(150);
Portd = 0xFD; delay_ms(150);
Portd = 0xFB; delay_ms(150);
Portd = 0xF7; delay_ms(150);
Portd = 0xEF; delay_ms(150);
PortD = 0xDF; delay_ms(150);
Portd = 0xBF; delay_ms(150);
Portd = 0x7F; delay_ms(150);
}

void program8() {
Portd = 0x7F; delay_ms(150);
Portd = 0xBF; delay_ms(150);
PortD = 0xDF; delay_ms(150);
Portd = 0xEF; delay_ms(150);
Portd = 0xF7; delay_ms(150);
Portd = 0xFB; delay_ms(150);
Portd = 0xFD; delay_ms(150);
Portd = 0xFE; delay_ms(150);
}

Code mẫu cho PIC - Kết hợp: ADC đo nhiệt độ, điện áp, hiển thị LCD và giao tiếp với máy tính

Chương trình thực hiện đo nhiệt độ, điện áp, hiển thị kết quả lên màn hình LCD 16x2, và truyền giá trị lên máy tính.

Mô phỏng trên Proteus:




mô phỏng trên Proteus

Mã nguồn:


#include <16f877a.h>
#include <def_877a.h>
#device *=16 ADC=10
#use delay(clock=20000000)
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT, NOLVP, NOCPD, NOWRT

#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#include <lcd_lib_4bit.c>
//=======================================================================
void send(int8 a);
void HIENTHI_LCD(int8 t);
void HIENTHI_LCD_float(int8 t);
int8 count=0;
int8 i,value,vl[10];
//=======================================================================
#INT_RDA //ngat khi nhan du lieu.
Receive_isr()
{
char c;
c=getc();
switch(c)
{
case '0': //xoa man hinh LCD
{
LCD_putcmd(0x01);
}
break;
//o day co the su dung ma thap phan! (ngoai tru cac ky tu dieu khien)
case '-': //lui con tro hien thi LCD 1 don vi.
{
LCD_putcmd(0x10);
}
break;
case '1': //truyen len may tinh: gia tri do duoc.
{
Set_ADC_channel(0); //kenh 0 chan so2
delay_us(10);
for(i=0;i<10;i++)
{
vl[i]=read_adc();
delay_us(10);
}
value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/20.48;
send(value);
}
break;
case '2': //truyen len may tinh: gia tri do duoc.
{
Set_ADC_channel(1); //kenh 1 chan so3
delay_us(10);
if(read_adc()==0)
{
for(i=0;i<10;i++)
{
vl[i]=read_adc();
delay_us(10);
}
value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
}
else
{
value=read_adc()+6;
for(i=0;i<10;i++)
{
vl[i]=read_adc()+6;
delay_us(10);
}
value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
}
send(value);
}
break;
case '9': //thoat khoi ham ngat, cho ADC làm viec.
{
count=0; //ket thuc viec truyen ky tu len LCD
LCD_putcmd(0x01);
}
break;
default : //truyen ky tu xuong LCD ^^.
{
count++;
if(count==1) LCD_putcmd(0x01);
if(count==16) LCD_putcmd(0xC0);
if(count==32)
{
LCD_putcmd(0x01);
count=0;
}
LCD_putchar(c);
}
}
}
//=======================================================================
void main()
{
//trisD=0x0C; //D0,D1 LA CONG VAO, D2-D7 LA CONG RA.

enable_interrupts(int_rda); //cho phep ngat noi tiep nhan.
enable_interrupts(GLOBAL);


LCD_init(); //Khoi tao LCD.
LCD_putcmd(0xC0);
Printf(LCD_putchar,"Khoi tao...");
delay_ms(250);

setup_adc_ports(AN0_AN1_AN3); //Khoi tao che do cho bo ADC.
setup_adc(ADC_CLOCK_INTERNAL);
delay_us(10);

while(1)
{
//do nhiet do
{
Set_ADC_channel(0); //kenh 0 chan so2
delay_us(10);
value=read_adc();
value=value/2.048;

LCD_putcmd(0x80);
LCD_putchar("Nhiet do: ");
LCD_putcmd(0x89);
HIENTHI_LCD(value);
LCD_putcmd(0x8C);
LCD_putchar("oC");
delay_ms(0.1);
}
//do diep ap
{
Set_ADC_channel(1); //kenh 1 chan so3
delay_us(10);
if(read_adc()==0)
{
for(i=0;i<10;i++)
{
vl[i]=read_adc();
delay_us(10);
}
value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
}
else
{
value=read_adc()+6;
for(i=0;i<10;i++)
{
vl[i]=read_adc()+6;
delay_us(10);
}
value=(vl[0]+vl[1]+vl[2]+vl[3]+vl[4]+vl[5]+vl[6]+vl[7]+vl[8]+vl[9])/10;
}

LCD_PutCmd(0xC0);
LCD_putchar("DIEN AP : ");
LCD_putcmd(0xC9);
//HIENTHI_LCD(value);
HIENTHI_LCD_float(value);
LCD_putcmd(0xCD);
LCD_putchar("V ");
}
}
}
//=======================================================================
void HIENTHI_LCD(int8 t)
{
unsigned char v;
if(t<10)
LCD_putchar(t+48);
else
if(t<100)
{
LCD_putchar(t/10+48);
LCD_putchar(t%10+48);
}
else
{
v=t/10;
LCD_putchar(v/10+48);
LCD_putchar(v%10+48);
LCD_putchar(t%10+48);
}
}
void send(int8 a)
{
if(a<10)
{
putc(a+48);
}
if(a>9&&a<100)
{
unsigned char c=a/10;
unsigned char d=a%10;
putc(c+48);
putc(d+48);
}
if(a>99)
{
unsigned char t=a/100;
unsigned char c=a/10-10*t;
unsigned char d=a%10;
putc(t+48);
putc(c+48);
putc(d+48);
}
}
void HIENTHI_LCD_float(int8 t)
{
int8 v;
if(t<10)
{
LCD_putchar(48);
LCD_putchar(46);
LCD_putchar(t+48);
LCD_putchar(32);
}
else
if(t<100)
{
LCD_putchar(t/10+48);
LCD_putchar(46);
LCD_putchar(t%10+48);
LCD_putchar(32);
}
else
{
v=t/10;
LCD_putchar(v/10+48);
LCD_putchar(v%10+48);
LCD_putchar(46);
LCD_putchar(t%10+48);
}
}
/*
void send_float(float a)
{
if(a<10)
{
putc(a+48);
}
if(a>9&&a<100)
{
unsigned char c=a/10;
unsigned char d=a%10;
putc(c+48);
putc(d+48);
}
if(a>99)
{
unsigned char t=a/100;
unsigned char c=a/10-10*t;
unsigned char d=a%10;
putc(t+48);
putc(c+48);
putc(d+48);
}
}*/

//=====================================================

Code mẫu cho PIC - Giao tiếp SPI song công giữa 2 VĐK PIC

Giao tiếp SPI song công giữa 2 PIC: PIC Master ở trên truyền dữ liệu từ PortB (công tắc trên) qua PIC Slave ở dưới để hiển thị ra PortD (LED dưới) , PIC Slave cũng lấy dữ liệu từ PortB (công tắc dưới) của mình, truyền qua PIC Master để hiển thị ra PortD (LED trên).

Chương trình chạy mô phỏng trên ISIS - Proteus:



mô phỏng SPI trên Proteus



Mã nguồn:


Code Master:



#include <16f877a.h>
#include <def_877a.h>
#device *=16 ADC=8
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,NOLVP, NOCPD, NOWRT
#use delay(clock=20000000)
#use rs232(baud=38400,parity=N,xmit=PIN_C6,rcv=PIN_C7)
#use fast_io(B)
#use fast_io(D)
#use fast_io(A)
#define REG_Write 0x80

#INT_SSP
void spi()
{
PORTD=spi_read(PORTB);
delay_ms(10);
}
void main()
{
port_b_pullups(TRUE);
setup_spi(spi_master|spi_l_to_h|spi_clk_div_16);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
SET_TRIS_B(0xff);
SET_TRIS_D(0x00);
SET_TRIS_A(0x00);
while(1)
{
delay_ms(100);
output_low(PIN_A5);//Chân C2 dùng Select chip.
delay_ms(10);//Tao tre de Slave chuan bi.
spi_write(PORTB);
output_high(PIN_A5);
}
}


Code Slave:



#include <16f877a.h>
#include <def_877a.h>
#device *=16 ADC=8
#FUSES NOWDT, HS, NOPUT, NOPROTECT, NODEBUG, NOBROWNOUT,NOLVP, NOCPD, NOWRT
#use delay(clock=20000000)
#use rs232(baud=38400,parity=N,xmit=PIN_C6,rcv=PIN_C7)
#use fast_io(B)
#use fast_io(D)

#INT_SSP
void spi()
{
PORTD=spi_read(PORTB);//Vua nhan vua truyen.
delay_ms(10);
}

void main()
{
port_b_pullups(TRUE);
setup_spi(spi_slave|spi_l_to_h|spi_clk_div_16);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
TRISB=0xff;
TRISD=0x00;
while(1)
{
}
}

[Lập trình PIC] Sử dụng Biến và Hàm, Cấu trúc lệnh, Chỉ thị tiền xử lý trong CCS

I / KHAI BÁO VÀ SỬ DỤNG BIẾN , HẰNG , MẢNG :


1 /Khai báo 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


+ Thêm signed hoặc unsigned phía trước để chỉ đó là số có dấu hay không dấu .Khai báo như trên mặc định là không dấu . 4 khai báo cuối không nên dùng vì dễ nhầm lẫn . Thay vào đó nên dùng 4 khai báo đầu .




VD :
Signed int8 a ; // số a là 8 bit dấu ( bit 7 là bit dấu ).
Signed int16 b , c , d ;
Signed int32 , . . .


+ Phạm vi biến :


Int8 :0 , 255 signed int8 : -128 , 127
Int16 : 0 ,2^15-1 signed int16 : -2^15 , 2^15-1
Int32 : 0 , 2^32-1 signed int32 : -2^31 , 2^31-1


+ Khai báo hằng :
VD :
Int8 const a=231 ;


+ Khai báo 1 mảng hằng số :
VD :
Int8 const a[5] = { 3,5,6,8,6 } ; //5 phần tử , chỉ số mảng bắt đầu từ 0 : a[0]=3


+ Một mảng hằng số có kích thước tối đa tuỳ thuộc loại VĐK:


*NếuVĐK là PIC 14 ( VD :16F877 ) : bạn chỉ được khai báo 1 mảng hằng số có kích thước tối đa là 256byte .


Các khai báo sau là hợp lệ :
Int8 const a[5]={ . . .}; // sử dụng 5 byte , dấu . . . để bạn điền số vào
Int8 const a[256]={ . . .}; // 256 phần tử x 1 byte = 256 byte
Int16 const a[12] = { . . . }; // 12 x 2= 24 byte
Int16 const a[128] = { . . . }; // 128 x 2= 256 byte
không hợp lệ :


Int16 const a[200] = { . . . }; // 200 x 2 =400 byte


*Nếu VĐK là PIC 18 : khai báo mảng hằng số thoải mái , không giới hạn kích thước .


+ Lưu ý : nếu đánh không đủ số phần tử vào trong ngoặc kép như đã khai báo , các phần tử còn lại sẽ là 0 . Truy xuất giá trị vượt quá chỉ số mảng khai báo sẽ làm chương trình chạy vô tận .
VD :
int8 const a [7] = { 0 , 3,5 ,9 } // các phần tử a[4] ,a[5],a[6] đều =0


+ Mảng hằng số thường dùng làm bảng tra (ví dụ bảng tra sin ) , viết dễ dàng và nhanh chóng , gọn hơn so với khi dùng ASM để viết .


+ Khai báo 1 biến mảng : kích thước tuỳ thuộc khai báo con trỏ trong #device và loại VDK:


*PIC 14 : Nếu bạn khai báo con trỏ 8 bit : VD: # device *=8 : không gian bộ nhớ chỉ có 256 byte cho tất cả các biến chương trình bất chấp VĐK của bạn có hơn 256 byte RAM (Vd : 368 , . . .) và biến mảng có kích thước tối đa tuỳ thuộc độ phân mảnh bộ nhớ , với 16F877 có 368 byte ram , thường thì kích thước không quá 60 byte ,có khi dưới 40 byte , nếu khai báo lớn hơn sẽ gặp lỗi vô duyên : "not enough ram for all variable" trong khi thực sự VDK còn rất nhiều RAM . Nếu khai báo con trỏ 16 bit : VD : #device *=16 , không gian bộ nhớ là đầy đủ ( trừ đi 1 ít RAM do CCS chiếm làm biến tạm ) .VD :với 16F877 bạn dùng đủ 368 byte RAM . Nhưng kích thước mảng cũng không quá 60 byte .


* PIC 18 : kích thước mảng không giới hạn, xài hết RAM thì thôi . Với khai báo con trỏ 8 bit , bạn chỉ được xài tối đa 256 byte RAM , nếu khai báo con trỏ 16 bit , bạn xài trọn bộ nhớ RAM thực sự . VD: Khai báo biến mảng : int16 a[125] ; // biến mảng 126 phần tử , kích thước 252 byte ram .






2 / Cách sử dụng biến :


+ Khi sử dụng các phép toán cần lưu ý : sự tràn số , tính toán với số âm , sự chuyển kiểu và ép kiểu .


A ) Một vài ví dụ về tràn số , làm tròn :
VD :
Int8 a=275; // a =275-256=19
Int8 const a=275 //a=19
Int8 a=40 , b=7 , c;
C=a * b ; //c=280-256=24
C=a / b ; //c=5


+ Bạn có thể ép kiểu , thường là tiết kiệm ram , hay muốn tiết kiệm thời gian tính , . . ..VD :
Int8 a =8 , b=200;
Int16 c ;
C= ( int16) a * b ;
// c= 1600 , a chuyển sang 16 bit , 16bit*8bit => b tự động chuyển sang 16 bit , kết quả là 16 bit trong c , lưu ý biến a , b vẫn là 8 bit .


+ 8bit * 8bit => phép nhân là 8 bit , KQ là 8 bit
+ 16bit * 8 bit => phép nhân là 16 bit , KQ là 16 bit
+ 32bit * 16 bit => phép nhân là 32 bit , KQ là 32 bit
+ 16bit * 16 bit => phép nhân là 16 bit , KQ là 16 bit
. . . v . v . . .


+ Có thể ép kiểu kết quả : VD : 16b*8b => 16bit , nếu gán vào biến 8 bit thì KQ sẽ cắt bỏ 8 bit cao .


B )Phạm vi sử dụng biến :


+ Giống như C trong lập trình C cho máy tính . Biến có thể được khai báo như toàn cục hay cục bộ . Biến khai báo trong hàm sẽ là cục bộ và sẽ chỉ dùng được trong hàm đó , kể cả trong hàm main() . Ngoài ra còn có thể khai báo ngay trong 1 khối lệnh , và cũng chỉ tồn tại trong khối lệnh đó . Do vậy nếu dùng MPLAB để mô phỏng , thì khi nhảy vào hàm hay khối lệnh có chứa khai báo biến đó thì biến đó mới có giá trị , có khi nhảy ra ngoài hàm thì biến đó sẽ là” out of scope” khi ta quan sát chúng trong cửa sổ Watch.
+ Chi tiết về phạm vi biến xem tài liệu lập trình C trên máy tính .
+ CCS có hỗ trợ cả con trỏ , tuy nhiên ít dùng .
+ CCs không hỗ trợ lập trình hướng đối tượng như C++ . Tuy vậy CCS có hỗ trợ các biến cấu trúc .


3 / Các phép toán , sự thực thi và vấn đề tối ưu mã , chương trình:







+ Trên đây là thời gian cần cho 1 phép toán .
+ Khi chương trình của bạn nhỏ xíu và có thể kiểm soát được , và thời gian thực thi là không quan trọng ,đồng thời có thể không cần mô phỏng thì bạn có thể dùng cả kiểu float nếu thấy tiện .
+ Khi chương trình lớn , cần mô phỏng , và thời gian thực thi là quan trọng thì các điều sau đây nên làm :
+ Không xài biến kiểu float , vì khi mô phỏng không thấy được giá trị thực của nó .Để khử số thập phân kiểu float , hãy nhân hay chia cho 2^k .
VD : số kiểu float : m có thể biểu diễn ở dạng : n / 2^8 , với m biết trước , n nguyên được tính trước
bằng cách : n= m* 2^8 , lấy được 2 chữ số sau dấu phẩy (2^8=256 ) . Do đó với 1 bảng tra sin 361
phần tử từ 0->360 độ , nếu lấy chính xác tới 2 dấu phẩy thì các giá trị sin nhân thêm cho 2^8 , cắt bỏ
phần thập phân và lưu vào mảng hằng số int16 , sau đó khi truy xuất tới các giá trị này để sử dụng
thì hãy chia cho 256 bằng cách dịch phải 8 bit .


+ Các phép tính nhân chia cho 2^k rất nhanh vì ta dùng phép toán dịch bit .
VD :
Z=Y*2^5 ; thì thay bởi z = y<<5 ; nhanh gấp 20 lần .
Z= y / 2^5; thay bởi z = y >>5 ; nhanh gấp 20 lần .


Trong đó phép dịch nguyên byte ( 8bit, 16 bit ) là nhanh nhất . VD : z= y>>8 ; z=y <<16 ;


+ Không dùng phép trừ mà dẫn đến kết quả có thể âm vì số âm sẽ không hiển thị được khi mô phỏng ( số hiển thị sẽ là dương và dĩ nhiên giá trị sẽ khác hẳn ) .Biến đổi sao cho phép trừ luôn cho kết quả dương thì mới hiển thị chính xác .
VD : công thức điều chế sin PWM có dạng : z = T * (1 + ma * y )
Trong đó : ma <1 , y : giá trị hàm sin : -1< y < 1 . Biến đổi như sau :
y= (y +1) – 1 = y’ -1



=> z = T* ( 1-ma ) + T * ma * y’ trong đó ( 1-ma ) >=1 . và 0< y’ <2
=> z = [ T * ( 256 – MA ) ]>>8 + [T * MA * Y’ ] >> 15
Trong đó MA = ma<<8 và Y’ = y’ << 7 ;
=> chỉ cần lập bảng tra sin trong đó là các giá trị sin là số nguyên = ( y + 1) * 128 ;






II / CÁC CẤU TRÚC LỆNH : ( statement )


+ Gồm các lệnh như: while . . do , case , . . .







Lưu ý : các mục trong [ ] là có thể có hoặc không .


+ 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. _Continue : quay trở về đầu vòng lặp while .







III /CHỈ THỊ TIỀN XỬ LÝ :


+ Xem chi tiết tất cả ở phần HELP , mục pre_processor . Ở đây sẽ giới thiệu 1 số chỉ thị thường dùng nhất :


1 / #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 , bạn phải tự thêm vào trong mã ASM .


+ Lưu ý : mã Assembly theo đúng mã tập lệnh VDK , không phải mã kiểu MPLAB .
VD :
int find_parity (int data)
{
int count;
#asm
movlw 0x8
movwf count
movlw 0
loop:
xorwf data,w
rrf data,f
decfsz count,f
goto loop
movwf _return_
#endasm
}


2 / #INCLUDE :


+ Cú pháp : #include <filename> Hay #include “ filename”
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 .
VD :
#include <16F877.H> // chương trình sử dụng cho VĐK 16F877
#include < C:\INCLUDES\COMLIB\MYRS232.C >




3 / #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.
VD:
#bit TMR1Flag = 0xb.2 //bit cờ ngắt timer1 ở địa chỉ 0xb.2 (PIC16F877)




Khi đó TMR1Flag = 0 => xoá cờ ngắt timer1
Int16 a=35; //a=00000000 00100011
#bit b= a.11 //b=0 , nếu b=a.0 thì b chỉ vị trí LSB ( bit thấp nhất , bên trái)
Sau đó : b=1; //a=00001000 00100011 = 2083


+ Lưu ý không dùng được : if ( 0xb.2 ) mà phải khai báo như trên rồi dùng : if(TMR1Flag)


+ #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 , nếu muốn dùng riêng , hãy dùng #LOCATE.
VD:
#byte port_b = 0xc6; // 16F877 :0xc6 là địa chỉ portb
Muốn port b có giá trị 120 thì : port_b=120;
#byte status = 0xc3;


+ # 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 . VD: # LOCATE temp = 0xc20 // 0xc20 :thanh ghi đa mục đích
Cách sau tương tự :
Int8 temp ;
#locate temp = 0xc20


+ 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 .
VD : CCP1 có giá trị là cặp thanh ghi 0x15 ( byte thấp ) và 0x16 ( byte cao ) . Để gán trị cho CCP1 :
Int16 CCP1;
#locate CCP1= 0x15 // byte thấp của CCP1 ở 0x15 , byte cao của CCP1 ở 0x16
Gán trị cho CCP1 sẽ tự động gán vào cả 2 thanh ghi
CCP1 = 1133 ; // = 00000100 01101101 => 0x15 = 00000100 , 0x16 = 01101101


+ # DEFINE id text
Text : chuỗi hay số . Dùng định nghĩa giá trị .
VD : #define a 12345




4 / # 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 .


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


5 / # 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.
VD :
Org 0x30 , 0x1F
Void xu_ly( )
{
} // hàm này bắt đầu ở địa chỉ 0x30


org 0x1E00
anotherfunc( )
{
} //hàm này bắt đầu tuỳ ý ở 0x1E00 đến 0x1F00


Org 0x30 , 0x1F { }
// không có gì cả đặt trong vùng ROM này


+ Thường thì không dùng ORG .


6 / # 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.
+ 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ự .
VD :
#use I2C ( master , sda=pin_B0 , scl = pin_B1 )
#use I2C (slave , sda= pin_C4 , scl= pin_C3 , address = 0xa00 , FORCE_HW )




#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)




7 /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






MỘT SỐ VẤN ĐỀ QUAN TRỌNG KHÁC – xem chi tiết trong phần HELP :
+ Biểu thức : xem HELP->Expressions , trong đó : biểu thị số trong C:
123 : số decimal 0x3 , 0xB1 : số hex 0b100110 : số binary
‘a’ : ký tự
“abcd” : chuỗi , ký tự null được thêm phía sau
_Các toán tử C : xem Operators
>= , < = , = = , != ( không bằng )
&& : and || : or ! : not ( đảo của bit , không phải đảo của byte )


>>n : dịch trái n bit << n : dịch phải n bit
++ , - - , += , - = , . . .

[Lập trình PIC] Các Hàm Xử Lý Số, Xử Lý Bit, Delay trong CCS

Các Hàm Xử Lý Số, Xử Lý Bit, Delay trong CCS


I / 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 ( ) :tính log
Log10 ( ) : 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 .VD: hàm sin mất 3.5 ms ( thạch anh = 20Mhz )để cho KQ . Do đó nếu không đòi hỏi tốc độ thì dùng các hàm này cho đơn giản , như là dùng hàm sin thì khỏi phải lập bảng tra.
+ Xem chi tiết trên HELP CCS.




II / CÁC HÀM XỬ LÝ BIT VÀ CÁC PHÉP TOÁN :
+ Bao gồm các hàmsau :
Shift_right() shift_left()
Rotate_right() rotate_left()
Bit_clear() bit_set() bit_test() Swap()
Make8() make16() make32()


1 / Shift_right ( address , byte , value )
Shift_left ( address , byte , value )
+ 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 .




2 / Rotate_right () , rotate_left ()
+ Nói chung 4 hàm này ít sử dụng .


3 / Bit_clear ( var , bit )
it_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ỳ .
+ 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


4 / 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 .
+ Giả sử bạn có biến x 32 bit đếm từ 0 lên và muốn kiểm tra xem nó có lớn hơn 4096 không ( 4096= 2^12 =1000000000000b) :
If ( x >= 4096) . . . // phép kiểm tra này mất ~5 us
Trong 1 vòng lặp , việc kiểm tra thường xuyên như vậy sẽ làm mất 1 thời gian đáng kể . Để tối ưu , chỉ cần dùng : if ( bit_test ( x, 12 ) Ỉ chỉ mất ~ 0.4 us . ( 20 Mhz thạch anh ) .
+ Kiểm tra đếm lên tới những giá trị đặc biệt ( 2^ i) thì dùng hàm này rất tiện lợi.


5 / 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 ) | ( var << 4 )
+ Hàm không trả về trị .
VD :
X= 5 ; //x=00000101b
Swap ( x) ; //x = 01010000b = 80


6 / 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


7 / 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 .


8 / 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






III / 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 , bạn cần khai báo : #use delay ( clock = 20000000 )
+ 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 .
VD :
Int a = 215;
Delay_us ( a ) ; // delay 215 us
Delay_us ( 4356 ) ; // delay 4356 us
Delay_ms ( 2500 ) ; // delay 2 . 5 s

Bộ Chuyển Đổi ADC - Các Hàm Vào/Ra trong CCS

I /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 .


Cấu hình bộ ADC :
+ Thông dụng nhất khi dùng ADC là sử dụng 1 biến trở , điều chỉnh bởi 1 nút vặn , qua đó thu được 1 điện áp nhỏ hơn điện áp tham chiếu ( Vref – áp max ) , đưa vào chân biến đổi ADC , kết quả cho 1 giá trị số ADC 8 bit ( 0-255 ) hay ADC 10 bit (0-1023 ) . Thường thì áp Vref lấy bằng Vdd ( 5V ).
+ Trên các PIC có ngõ AVdd và AVss ( PIC 18 ) , thường thì bạn luôn nối AVdd tới Vdd , AVss tới Vss để đảm bảo họat động cho lập trình qua ICD 2 .


Các hàm sau phục vụ ADC :
1 / Setup_ADC ( mode ) :
+ Không trả về trị . Dùng xác định cách thức hoạt động bộ biến đổi ADC . Tham số mode tuỳ thuộc file thiết bị *.h có tên tương ứng tên chip bạn đang dùng , nằm trong thư mục DEVICES của CCS . Muốn biết có bao nhiêu tham số có thể dùng cho chip đó , bạn mở file tương ứng đọc , tìm tới chỗ các định nghĩa cho chức năng ADC dùng cho chip đó tương ứng với hàm này . Sau đây là các giá trị mode của 16F877 , ( 1 số khác có thể không có hoặc có thêm như 16F877A có thêm 1 số thứ là ADC_CLOCK_DIV_2/4/8/16/32/64 . . .) :


ADC_OFF : tắt hoạt động ADC ( tiết kiệm điện , dành chân cho hoạt động khác ) .
ADC_CLOCK_INTERNAL : thời gian lấy mẫu bằng xung clock IC ( mất 2-6 us ) thường là chung cho các chip .
ADC_CLOCK_DIV_2 : thời gian lấy mẫu bằng xung clock / 2 ( mất 0.4 us trên thạch anh 20MHz )
ADC_CLOCK_DIV_8 : thời gian lấy mẫu bằng xung clock / 8 ( 1.6 us )
ADC_CLOCK_DIV_32 : thời gian lấy mẫu bằng xung clock / 32 ( 6.4 us )





2 / Setup_ADC_ports ( value )
+ Xác định chân lấy tín hiệu analog và điện thế chuẩn sử dụng . Tùy thuộc bố trí chân trên chip , số chân và chân nào dùng cho ADC và số chức năng ADC mỗi chip mà value có thể có những giá trị khác nhau. Xem file tương ứng trong thư mục DEVICES để biết số chức năng tương ứng chip đó . Để tương thích chương trình viết cho phiên bản cũ , 1 số tham số có 2 tên khác nhau ( nhưng cùng chức năng do định nghĩa cùng địa chỉ ) , ở đây dùng phiên bản 3.227 .Lưu ý : Vref : áp chuẩn , Vdd : áp nguồn .


Sau đây là các giá trị cho value ( chỉ dùng 1 trong các giá trị ) của 16F877 :
ALL_ANALOGS .........: dùng tất cả chân sau làm analog : A0 A1 A2 A3 A5 E0 E1 E2 (Vref=Vdd)
NO_ANALOG ..............................................................: không dùng analog , các chân đó sẽ là chân I /O .
AN0_AN1_AN2_AN4_AN5_AN6_AN7_VSS_VREF .: A0 A1 A2 A5 E0 E1 E2 VRefh=A3
AN0_AN1_AN2_AN3_AN4 .....................................: A0 A1 A2 A3 A5
( tên thì giống nhau cho tất cả thiết bị nhưng 16F877 chỉ có portA có 5 chân nên A0 , A1 , A2 , A5 được dùng , A6 , A7 không có )
AN0_AN1_AN3 .......................................................: A0 A1 A3 , Vref = Vdd
AN0_AN1_VSS_VREF ..............................................: A0 A1 VRefh = A3
AN0_AN1_AN4_AN5_AN6_AN7_VREF_VREF ......: A0 A1 A5 E0 E1 E2 VRefh=A3 , VRefl=A2 .
AN0_AN1_AN2_AN3_AN4_AN5 ............................: A0 A1 A2 A3 A5 E0
AN0_AN1_AN2_AN4_AN5_VSS_VREF .................. : A0 A1 A2 A5 E0 VRefh=A3
AN0_AN1_AN4_AN5_VREF_VREF .........................: A0 A1 A5 E0 VRefh=A3 VRefl=A2
AN0_AN1_AN4_VREF_VREF .................................. : A0 A1 A5 VRefh=A3 VRefl=A2
AN0_AN1_VREF_VREF ...........................................: A0 A1 VRefh=A3 VRefl=A2
AN0 ..............................................................................: A0
AN0_VREF_VREF ..................................................... : A0 VRefh=A3 VRefl=A2


VD : setup_adc_ports (AN0_AN1_AN3 ) ; // A0 , A1 , A3 nhận analog , áp nguồn +5V cấp cho IC sẽ là điện áp chuẩn .


3 / Set_ADC_channel ( channel ) :
+ Chọn chân để đọc vào giá trị analog bằng lệnh Read_ADC ( ) . Giá trị channel tuỳ số chân chức năng ADC mỗi chip .Với 16F877 , channel có giá trị từ 0 -7 :
0-chân A0, 1-chân A1, 2-chân A2, 3-chân A3, 4-chân A5, 5-chân E0, 6-chân E1, 7-chân E2
+Hàm không trả về trị . Nên delay 10 us sau hàm này rồi mới dùng hàm read_ADC ( )để bảo đảm kết quả đúng . Hàm chỉ hoạt động với A /D phần cứng trên chip.


4 / Read_ADC ( mode ) :
+ Dùng đọc giá trị ADC từ thanh ghi (/ cặp thanh ghi ) chứa kết quả biến đổi ADC . Lưu ý hàm này sẽ hỏi vòng cờ cho tới khi cờ này báo đã hoàn thành biến đổi ADC ( sẽ mất vài us ) thì xong hàm .
+ Nếu giá trị ADC là 8 bit như khai báo trong chỉ thị #DEVICE , giá trị trả về của hàm là 8 bit , ngược lại là 16 bit nếu khai báo #DEVICE sử dụng ADC 10 bit trở lên .
+ Khi dùng hàm này , nó sẽ lấy ADC từ chân bạn chọn trong hàm Set_ADC_channel( )trước đó . Nghĩa là mỗi lần chỉ đọc 1 kênh Muốn đổi sang đọc chân nào , dùng hàmset_ADC_channel( ) lấy chân đó . Nếu không có đổi chân , dùng read_ADC( ) bao nhiêu lần cũng được .


+ mode có thể có hoặc không , gồm có :
ADC_START_AND_READ : giá trị mặc định
ADC_START_ONLY : bắt đầu chuyển đổi và trả về
ADC_READ_ONLY : đọc kết quả chuyển đổi lần cuối






#DEVCE 8 bit 10 bit 11 bit 16 bit
ADC=8 0-255 0-255 00-255 00-255
ADC=10 x 0-1023 x x
ADC=11 x x 0-2047 x
ADC=16 0-65280 0-65472 0-65504 0-65535
+ 16F877 chỉ hỗ trợ ADC 8 và 10 bit .
VD :
setup_adc( ADC_CLOCK_INTERNAL );
setup_adc_ports( ALL_ANALOG );
set_adc_channel(1);
while ( input(PIN_B0) )
{
delay_ms( 5000 );
value = read_adc();
printf("A/D value = %2x\n\r", value);
}
read_adc(ADC_START_ONLY);
sleep();
value=read_adc(ADC_READ_ONLY);


+ Lưu ý : trên PIC 18 , cấu trúc ADC tương đối phức tạp , đa năng hơn như là cho phép lấy 2 mẫu cùng lúc , . . . cũng sử dụng với các hàm trên , có nhiều thông số trong file *.h , sẽ đề cập sau .


5 / _ Ví dụ :
+ Chương trình sau lấy ADC 8 bit , đọc và xuất ra dãy led ở port B , và xuất ra màn hình máy tính .
+ Kết nối chân trên 16F877 : RA0 là chân lấy Analog vào , áp chuẩn là nguồn +5V , mass=0 V










#include <16F877.h >
#use delay( clock=20000000 )
#device *= 16 ADC = 8 // sử dụng ADC 8 bit , giá trị ADC vào từ 0-255
#use rs232(baud=19200,parity=n,xmit=pin_C6,rcv=pin_C7)
Int8 adc ;

Main( )
{
Setup_ADC ( ADC_internal ) ;
Setup_ADC_ports (AN0);
Set_ADC_channel ( 0 ) ;
Delay_us (10 ); // delay 10 us
While (true )
{ adc = read_adc ( ) ;
Output_B ( adc ) ; // xuat ra port B gia tri bien adc
Printf( “ gia tri adc la : %u “ , adc ) ; // in ra man hinh
}
}
// giá trị biến adc từ 0-255 , dùng chương trình Serial port Monitor trong mục Tools của CCS để
giám sát giá trị . Nhớ thiết lập tốc độ là 19200 như khai báo trên .






II / CÁC HÀM VÀO RA TRONG C :
+ Bao gồm các hàm sau :
Output_low() Output_high()
Output_float() Output_bit()
Input() Ouput_X()
Input_X() port_b_pullups()
Set_tris_X()


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 .
+ 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 đó .
VD : Chương trình xuất xung vuông chu kỳ 500ms ,duty =50%
int1 x; // Khai báo x, mặc định = 0
Main() //Trong hàm main :
{ while (1 )
{ output_bit( pin_B0 , !x ) ;
Delay_ms(250 );
}
}




3 / Output_float ( pin ) :
+ Hàm này set pin như ngõ vào , cho phép pin ở mức cao như 1 cực thu hở.


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 .
VD :
Output_B ( 212 ) ; // xuất giá trị 11010100 ra port B


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 đó .VD : m=input_E();


7 / Port_B_pullups ( value ) :
+ Hàm này thiết lập ngõ vào port B pullup ( điện trở kéo lên) . 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 .
VD : chương trình sau cho phép thao tác trên portB 1 cách dễ dàng:
#include < 16F877.h >
#use delay(clock=20000000)
#use Fast_IO( B )
#byte portB = 0x6 // 16F877 có port b ở địa chỉ 6h
#bit B0 = portB. 0 // biến B0 chỉ đến chân B0
#bit B1=portB.1 // biến B1 chỉ đến chân B1


#bit B2=portB.2 // biến B2 chỉ đến chân B2
#bit B3=portB.3 // biến B3 chỉ đến chân B3
#bit B4=portB.4 // biến B4 chỉ đến chân B4
#bit B5=portB.5 // biến B5 chỉ đến chân B5
#bit B6=portB.6 // biến B6 chỉ đến chân B6
#bit B7=portB.7 // biến B7 chỉ đến chân B7
Main()
{ set_tris_B ( 126 ) ; //portB=01111110 b
// B0 là ngõ vào , thường làm ngắt ngoài
//B1 . . . B6 là ngõ ra , Vd làm 6 ngõ ra điều chế PWM
//B7 là ngõ vào , Vd là nhận tín hiệu cho phép chẳng hạn
if ( B7 ) //nếu ngõ vào chân B7 là 1 thì xuất 3 cặp xung đối nghịch
{ B1 = 1 ;
B2 = 0 ;
B3 = 1 ;
B4 = 0 ;
B5 = 1 ;
B6 = 0 ;
}
Else B1=B2=B3=B4=B5=B6= 0;
}


+ Lưu ý :
+ Set_tris_B (0 ) : port B =00000000 : tất cả chân portB là ngõ ra
+ set_tris_B ( 1 ) : portB = 00000001 : chỉ B0 là ngõ vào , còn lại là ngõ ra
+ set_tris_B ( 255 ) : portB=11111111: tất cả chân portB là ngõ vào. Bạn nên dùng giá trị ở dạng nhị phân cho dễ . VD : set_tris_B ( 00110001b ) ;


+ Đến đây là bạn có thể viết nhiều chương trình thú vị rồi đó. Vd như là dùng ADC để điều chỉnh tốc độ nhấp nháy của dãy đèn led , truyền giá trị 8 bit từ chip này sang chip khác , . . .
+ VD: Chương trình sau dùng ADC qua chân A0 để điều chỉnh tốc độ nhấp nháy dãy đèn led nối vào portB, có thể dùng fast_io hay hàm output_B () để xuất giá trị đều được . chương trình dùng hàm. Nếu ngõ vào chân C0 =0 thì tiếp tục nhận ADC và xuất ra portB, C0=1 thì không xuất
#include <16F877.h>
#device *=16 ADC= 8
#use delay( clock =20000000)
Int8 ADC_delay ;


Void hieu_chinh ( )
{ ADC_delay = read_adc ( 0 ) ;
Output_B ( 0) ; //portB=00000000
Delay_ms ( ADC_delay );
Output_B ( 255 ) ; // portB= 11111111
Delay_ms ( ADC_delay );
}


Main()
{
setup_adc_ports(AN0_AN1_AN3); // A0 , A1 và A3 là chân analog , ta chỉ cần dùng A0 lấy
tín hiệu
setup_adc(adc_clock_internal);
set_adc_channel ( 0 ); // chọn đọc ADC từ chân A0
while(1)
{ hieu_chinh ( ) ;
If ( input ( pin_C0 )
{ output_B (0 );
Break ; // thoát khỏi vòng lặp while nhỏ
}
}
}

[Lập trình PIC] Truyền Thông Nối Tiếp RS232 - Xử Lý Chuỗi Trong CCS

+ Phần này sẽ giúp bạn viết chương trình có sử dụng giao tiếp với máy tính (PC) . Điều này rất cần thiết khi bạn muốn VĐK khi hoạt động có thể truyền dữ liệu cho PC xử lý , hoặc nhận giá trị từ PC để xử lý và điều khiển ( dùng PC điều khiển động cơ , nhiệt độ , hay biến PC thành dụng cụ đo các đại lượng điện, Oscilocope , . . .) .
+ Viết chương trình lập trình cho VĐK để giao tiếp máy tính là công việc rất phức tạp khi viết bằng ASM , rất khó hiểu đối với những người mới bắt đầu lập trình . Đặc biệt là khi viết cho những con VĐK không hỗ trợ từ phần cứng ( 8951 thì phải (?) ) . Thật may là phần lớn PIC hiện nay đều hỗ trợ phần này nên việc lập trình có dễ dàng hơn . Nhưng nếu chương trình của bạn yêu cầu truyền hay nhận nhiều loại dữ liệu ( số 8 , 16 ,32 bit , dương , âm , chuỗi , . . .) thì việc viết chương trình xử lý và phân loại chúng là điều “ kinh dị “ .




+ Nếu bạn đã lập trình ASM cho vấn đề này rồi thì bạn sẽ thấy sao dễ dàng quá vậy khi giải quyết vấn đề này với C khi dùng CCS . Rất đơn giản ! CCS cung cấp rất nhiều hàm phục vụ cho giao tiếp qua cổng COM và vô số hàm xử lý chuỗi . Chương này sẽ giải quyết điều đó .
+ Một yếu tố quan trọng là khi nào thì VĐK biết PC truyền data => có thể lập trình bắt tay bằng phần mềm hay đơn giản là dùng ngắt . Các ví dụ về ngắt , xem phần ngắt .







I / TRUYỀN THÔNG VỚI PC QUA CỔNG COM :
+ Để sử dụng giao thức này , phải có 2 khai báo như ví dụ sau :
#use delay (clock = 40000000 ) // nếu VDK đang dùng OSC 40Mhz
#use rs232 (baud=19200 , parity=n , xmit=pin_C6 , rcv=pin_C7 )
// baud= 19200 , không chẵn lẻ , chân truyền C6 , chân nhận C7


+Các hàm liên quan :
Printf ( )
Getc ( ) putc ( )
Getch ( ) putchar ( )
Getchar ( ) fputc ( )
Fgetc ( ) puts ( )
Gets ( ) fputs ( )
Fgets ( )
Kbhit ( )
Assert ( ) => mới trên CCS 3.222
Perror ( ) => mới trên CCS 3.222
Set_uart_speed ( )
Setup_uart ( )


+ Tất cả các hàm trên đòi hỏi phải khai báo chỉ thị tiền xử lý #use RS232 ( . . . . .) . Chi tiết chỉ thị này xem phần Chỉ thị tiền xử lý .
+ Hàm perror ( ) đòi hỏi thêm #include<errno.h > . Hàm assert() đòi hỏi thêm #include<assert.h> .


1 / printf ( string )
Printf ( cstring , values . . . )
+ Dùng xuất chuỗi theo chuẩn RS232 ra PC .
+ string là 1 chuỗi hằng hay 1 mảng ký tự ( kết thúc bởi ký tự null ) .
+ value là danh sách các biến , cách nhau bởi dấu phẩy .
+ Bạn phải khai báo dạng format của value theo kiểu %wt .Trong đó w có thể có hoặc không, có giá trị từ 1-9 chỉ rõ có bao nhiêu ký tự được xuất ra ( mặc định không có thì có bao nhiêu ra bấy nhiêu ), hoặc 01-09 sẽ chèn thêm 0 cho đủ ký tự hoặc 1.1-1.9 cho trường hợp số thực . còn t là kiểu giá trị .
+ t có thể là :
C : 1 ký tự
S : chuỗi hoặc ký tự
U : số 8 bit không dấu
x : số 8 bit kiểu hex ( ký tự viết thường ,VD : 1ef )
X : số 8 bit kiểu hex ( ký tự viết hoa ,VD : 1EF )
D : số 8 bit có dấu
e : số thực có luỹ thừa VD : e12

f : số thực
Lx : số hex 16 /32 bit ( ký tự viết thường )
LX : hex 16 /32 bit ( ký tự viết hoa )
Lu : số thập phân không dấu
Ld : số thập phân có dấu
% : ký hiệu %
VD :
Specifier Value=0x12 Value=0xfe
%03u 018 254
%u 18 254
%2u 18 *
%5 18 254
%d 18 -2
%x 12 Fe
%X 12 FE
%4X 0012 00FE
* Result is undefined - Assume garbage.
VD :
Int k =6 ;
Printf ( “ hello “ );
Printf ( “ %u “ , k );


2 / KBHIT ( ) :
+ Thường thì chúng ta dùng RC6 và RC7 cho RX và TX trong giao tiếp cổng COM , VDK PIC trang bị phần cứng phục vụ việc này với thanh ghi gởi và nhận và các bit bào hiệu tương ứng . Do đó khi dùng RS232 hỗ trợ từ phần cứng thì KHBIT ( ) trả về TRUE nếu 1 ký tự đã được nhận ( trong bộ đệm phần cứng ) và sẵn sàng cho việc đọc , và trả về 0 nếu chưa sẵn sàng .
+ Hàm này có thể dùng hỏi vòng xem khi nào có data nhận từ RS232 để đọc .

[Lập trình PIC] Giao Tiếp SPI

I / GIAO TIẾP SPI : 

+ Đây là giao tiếp dễ dùng nhất , đơn giản nhất , tốc độ cao nhất trong nhóm . hoạt động theo cơ chế hand-shaking, bắt tay . Giả sử có 2 VDK , thì 1 là master , 1 là slave . Khi master truyền 1 byte cho slave , nó phát 8 xung clock qua đường clock nối tới slave , đồng thời truyền 8 bit data từ chân SDO tới chân SDI của slave. Không kiểm tra chẵn lẻ , lỗi . Do đó Vdụ nếu đang truyền được 3 bit mà master reset hay hở dây clock thì data bị mất , slave sẽ không nhận đủ 8 bit và do đó nếu tiếp tục nhận nó sẽ lấy 5 bit ở byte kế tiếp đưa vào thanh ghi nhận để đủ 8 bit ( và để kích ngắt ) . Từ đó trở đi là mọi giá trị nhận là sai bét trừ phi chấm dứt và sau đó thiết lập lại giao tiếp này ( ở cả hai ) .
+ Giao tiếp này cần ít nhất 2 dây trở lên . Nếu 1 VDK chỉ cần gởi data thì chỉ cần dây clock và SDO .VDK nhận sẽ dùng SDI và dây clock . Dây clock là nối chung .
+ Nếu có gởi và nhận ở cả 2 VDK thì : dây clock chung , master có SDO nối tới SDI của slave , SDO của slave nối tới SDI của master .
+ Nếu master cần truyền data cho nhiều slave trở lên thì SDO master nối tới các SDI của slave .
+ Chân SS là slave select .
+ SPI hoạt động từ phần cứng , vì nó có sẵn thanh ghi gởi và nhận , nhận đủ giá trị thì có cờ ngắt phục vụ .
+ Danh sách các hàm :

1 / Setup_spi (mode ) 

Setup_spi2 (mode )
+ Dùng thiết lập giao tiếp SPI . Hàm thứ 2 dùng với VDK có 2 bộ SPI .
+ Tham số mode :là các hằng số sau , có thể OR giữa các nhóm bởi dấu |
I SPI_MASTER , SPI_SLAVE , SPI_SS_DISABLED
II SPI_L_TO_H , SPI_H_TO_L
III SPI_CLK_DIV_4 , SPI_CLK_DIV_16 , SPI_CLK_DIV_64 , SPI_CLK_T2
+ Nhóm I xác định VDK là master hay slave ,slave select
+ Nhóm II xác định clock cạnh lên hay xuống .
+ Nhóm III xác định tần số xung clock , SPI_CLK_DIV_4 ngĩa là tần số = FOSC / 4 , tương ứng 1 chu kỳ lệnh / xung .
+ Hàm không trả về trị .
+ Ngoài ra ,tuỳ VDK mà có thêm 1 số tham số khác , xem file * .h .


2 / Spi_read ( data )
Spi_read2 ( data )
+ data có thể có thêm và là số 8 bit . Hàm thứ 2 cho bộ SPI thứ 2 .
+ Hàm trả về giá trị 8 bit: value = spi_read ( )
+ Hàm trả về giá trị đọc bởi SPI . Nếu value phù hợp SPI_read ( ) thì data sẽ được phát xung ngoài và data nhận được sẽ được trả về . Nếu không có data sẵn sàng , spi_read ( ) sẽ đợi data .
+ Hàm chỉ dùng cho SPI hardware ( SPI phần cứng ) .


3 / Spi_write ( value ) 

Spi_write2 ( value )


+ Hàm không trả về trị . value là giá trị 8 bit .
+ Hàm này gửi value ( 1 byte ) tới SPI , đồng thời tạo 8 xung clock .
+ Hàm chỉ dùng cho SPI hardware ( SPI phần cứng ) .


4 / Spi_data_is_in ( ) 

Spi_data_is_in2 ( )
+ Hàm trả về TRUE ( 1 ) nếu data nhận được đầy đủ ( 8 bit ) từ SPI , trả về false nếu chưa nhận đủ .
+ Hàm này dùng kiểm tra xem giá trị nhận về SPI đã đủ 1 byte chưa để dùng hàm spi_read ( ) đọc data vào biến .

[Lập trình PIC] Các Ngắt Trong PIC

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) và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

Lập Trình RoBot Tự Động Đơn Giản Với VĐK PIC16F877A

Tóm tắt

Tài liệu hướng dẫn lập trình cho robot tự động dò đường theo vạch trắng và điều khiển các cơ cấu (nâng hạ, gắp nhả quà) một cách cơ bản nhất.Vi điều khiển được sử dụng trong tài liệu là PIC16F877A của Microchip.Lập trình bằng ngôn ngữ C với trình biên dịch CCS.


1 TÓM TẮT VỀ THIẾT KẾ ROBOT TỰ ĐỘNG

Robot tự động trong các cuộc thi Robocon gồm 3 thành phần chính: Cơ khí, Mạch điện tử, Lập trình.

1.1 Cơ khí

Một robot đơn giản gồm 2 động cơ truyền động cho 2 bánh xe bên trái và bên phải giúp robot di chuyển. Phía trước có thể là 1 hoặc 2 bánh tự do (bánh tự lựa, omni, mắt trâu,…). Để thực hiện được các công việc như nâng hạ trục, gắp nhả đẩy quá, robot được trang bị thêm các động cơ khác để truyền động cho các cơ cấu này.
Tất cả các bộ phận trên được bố trí trên một khung bằng nhôm, sắt,…
Phần hướng dẫn chi tiết về thiết kế cơ khí sẽ được trình bày trong một tài liệu khác. Tài liệu này chỉ tập trung vào phần lập trình.

Mô hình robot dò dường đơn giản




1.2 Mạch điện tử



Sơ đồ hoạt động của robot tự động



1.2.1 Mạch ngõ vào (cảm biến, nút ấn, công tắc hành trình)
Với robot đơn giản, ngõ vào thường là mức logic lấy từ cảm biến quang (quang trở, quang diode), nút ấn hoặc công tắc hành trình. Từ đó mạch vi điều khiển xử lý các tín hiệu này để xuất ngõ ra (thường là động cơ DC) chophù hợp.
Cảm biến quang phải được che chắn cẩn thận để hạn chế ảnh hưởng từ cácnguồn ánh sáng bên ngoài.



Mạch cảm biến, nút ấn và công tắc hành trình




1.2.2 Mạch vi điều khiển

Mạch sử dụng vi điều khiển PIC16F877A của Microchip. Mạch nhận tín hiệu từ ngõ vào, xử lý và xuất ngõ ra qua một mạch cách ly bằng opto ra mạch công suất.





Mạch vi điều khiển


1.2.3 Mạch công suất điều khiển động cơ DC
Một số mạch thông dụng:











Mạch công suất sử dụng 1 FET và 1 relay








Mạch cầu H điều khiển động cơ với Half Bridge Driver IR2184



1.3 Lập trình

Đây là phần chính của tài liệu này. Ngôn ngữ lập trình được sử dụng là C, với trình biên dịch CCS cho vi điều khiển PIC của Microchip.
Kiến thức ban đầu: Lập trình C căn bản
Các tài liệu tham khảo:

Tài liệu CCS tiếng Việt.


Đây là các tài liệu cần đọc qua trước khi vào lập trình cho PIC để có thể biết các hàm nhận tín hiệu ngõ vào, xuất tín hiệu ngõ ra, ngắt, timer, counter, PWM,…
Các hàm quan trọng sẽ được nhắc lại ở phần 2.


2 LẬP TRÌNH CHO ROBOT TỰ ĐỘNG DÒ ĐƯỜNG ĐƠN GIẢN
Chương trình giúp robot chạy theo vạch trắng trên nền màu sậm.


2.1 Phần cứng
 8 cảm biến quang dò đường
 Mạch công suất điều khiển 2 động cơ
 Mạch vi điều khiển:
o PORTD: nối với tín hiệu ra của 8 cảm biến
o Động cơ trái:
 Chân C0: điều khiển chiều (DIR_LEFT)
 Chân C1: điều khiển cho phép chạy (EN_LEFT)
o Động cơ phải:
 Chân C3: điều khiển chiều (DIR_RIGHT)
 Chân C2: điều khiển cho phép chạy (EN_RIGHT)
(Các ngõ vào và ngõ ra có thế nối với bất cứ PORT và chân nào của vi điều khiển)


2.2 Nguyên tắc điều khiển
2.2.1 Điều khiển động cơ






Tương tự đối với các động cơ điều khiển các cơ cấu.


2.2.2 Hướng di chuyển của robot






2.2.3 Xử lý tín hiệu cảm biến (xem hình vẽ)
Mục đích của việc dò đường là hướng cho robot đi theo 1 vạch thẳng màu trắng trên một nền màu đậm (đen, xanh,…)
Cảm biến được đặt ở giữa robot.
o Khi cảm biến số 3,4 nằm trên vạch trắng (mức 1): robot chạy thẳng
o Khi robot lệch sang trái: quay phải để điều chỉnh robot về đúng vạch
o Khi robot lệch sang phải: quay trái để điều chỉnh robot về đúng vạch






Các mức độ lệch ra khỏi vạch trắng của robot


2.3 Chương trình điều khiển


#include <16f877A.h>
#include <def_877A.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP
#use delay(clock=20000000)
/* ĐỊNH NGHĨA CÁC CHÂN VÀ PORT */
#define DIR_LEFT RC0
#define EN_LEFT RC1
#define DIR_RIGHT RC3
#define EN_RIGHT RC2

#define SENSOR PORTD
/* KHAI BÁO CÁC CHƯƠNG TRÌNH CON */
void motor_left_forward();
void motor_left_reverse();
void motor_left_stop();
void motor_right_forward();
void motor_right_reverse();
void motor_right_stop();
void forward();
void reverse();
void stop();
void turn_left();
void turn_right();
/* CÁC CHƯƠNG TRÌNH CON */
// Động cơ trái chạy thuận
void motor_left_forward()
{
DIR_LEFT=1; // chiều thuận
EN_LEFT=1; // cho phép chạy
}
// Động cơ trái chạy ngược
void motor_left_reverse()
{
DIR_LEFT=0; // chiều ngược
EN_LEFT=1; // cho phép chạy
}
// Động cơ trái dừng
void motor_left_stop()
{
EN_LEFT=0; // không cho phép chạy
}
// Động cơ phải chạy thuận
void motor_right_forward()
{
DIR_RIGHT=1;// chiều thuận
EN_RIGHT=1; // cho phép chạy
}
// Động cơ phải chạy ngược
void motor_right_reverse()
{
DIR_RIGHT=0;// chiều ngược
EN_RIGHT=1; // cho phép chạy
}
// Động cơ phải dừng
void motor_right_stop()
{
EN_RIGHT=0; // không cho phép chạy
}
// Chạy thẳng
void forward()
{
motor_left_forward();
motor_right_forward();
}
// Chạy lùi
void reverse()
{
motor_left_reverse();
motor_right_reverse();
}
// Dừng
void stop()
{
motor_left_stop();
motor_right_stop();
}
// Quay trái
void turn_left()
{
motor_left_forward();
motor_right_reverse(); // hoặc motor_right_stop();
}
// Quay phải
void turn_right()
{
motor_left_reverse(); // hoặc motor_left_stop();
motor_right_forward();
}
/* CHƯƠNG TRÌNH CHÍNH */
void main ()
{
TRISC=0x00; // PORTC là ngõ ra ( động cơ)
TRISD=0x00; // PORTD là ngõ vào (cảm biến quang)
PORTC=0x00; // Khởi tạo giá trị ban đầu 0x00 cho PORTC
while(1)
{
switch (SENSOR)
{
case 0b00011000: forward(); break;
case 0b00001100: turn_left(); break;
case 0b00000110: turn_left(); break;
case 0b00000011: turn_left(); break;
case 0b00000001: turn_left(); break;
case 0b00110000: turn_right(); break;
case 0b01100000: turn_right(); break;
case 0b11000000: turn_right(); break;
case 0b10000000: turn_right(); break;
}
}
}

3 CẢI TIẾN CHƯƠNG TRÌNH DÒ ĐƯỜNG

3.1 Điều khiển tốc độ động cơ với các trạng thái lệch khỏi vạch trắng

3.1.1 Nguyên lý

Đối với chương trình dò đường đơn giản, khi robot lệch trái hoặc lệch phải, robot sẽ quay phải hoặc quay trái để điều chỉnh cho dù lệch ít hay lệch nhiều. Như vậy, trong quá trình di chuyển, robot sẽ lắc liên tục vì phải quay trái, quay phải liên tục. Do đó, với các mức độ lệch ra khỏi vạch trắng khác nhau, ta điều chỉnh tốc độ 2 bánh trái, phải cho phù hợp để quá trình di chuyển theo vạch của robot được “nhuyễn” và “mượt” hơn.
Bảng giá trị tham khảo tốc độ động cơ trái và động cơ phải tương ứng với các trạng thái lệch khỏi vạch trẳng của robot:






3.1.2 Điều khiển tốc độ động cơ DC bằng phương pháp PWM

Đối với điều khiển tốc độ động cơ DC trong robot, phương pháp được sử dụng phổ biến nhất là điều chế độ rộng xung (Pulse Width Modulation) hay được gọi tắt là điều xung, băm xung hoặc PWM.
Nguyên lý của phương pháp này là bật tắt nhanh nguồn điện cấp vào động cơ tạo ra một tín hiệu xung. Khi việc bật tắt ở tần số đủ lớn (thường sử dụng từ 1kHz đến 20kHz), động cơ sẽ chạy với 1 tốc độ ổn định nhờ moment quay.
Thời gian cấp nguồn cho động cơ là T-on, thời gian tắt nguồn động cơ là T-off. Việc thay đổi thời gian T-on và T-off làm thay đổi điện áp hiệu dụng cấp cho động cơ. Đối với động cơ DC, tốc độ động cơ tương đối tỉ lệ thuận với điện áp cấp cho động cơ. Vì vậy, bằng cách thay đổi độ rộng của xung, ta đã thay đổi được tốc độ của động cơ DC.
Đại lượng biểu diễn mối quan hệ giữa T-on và T-off được gọi là duty cycle:


duty_cycle = Ton / ( Ton + Toff )

Ví dụ: Ta cấp nguồn động cơ trong 0.8ms, sau đó tắt 0.2ms.
Như vậy: T-on = 0.8ms; T-off = 1ms. Tần số PWM là:
f = 1 / ( Ton + Toff ) = 1 / ( 0.8ms + 0.2ms ) = 1/1ms = 1KHz
duty_cycle = Ton / ( Ton + Toff ) = 0.8 / ( 0.8 + 0.2 ) = 0.8 = 80%



Vì tốc độ động cơ DC tỉ lệ với duty cycle nên tốc độ động cơ đạt tương đương 80% tốc độ tối đa.





Tính toán duty cycle để điều khiển tốc độ động cơ DC


3.1.3 Điều xung PWM dùng vi điều khiển

 Điều xung PWM bằng phần mềm:
Điều xung PWM một cách đơn giản là đưa 1 chân nào đó của vi điều khiển lên mức 1, sau đó đưa xuống mức 0. Công việc này được lặp đi lặp lại liên tục sẽ tạo ra xung, và tốc độ của động cơ sẽ tương ứng với duty cycle.
Ví dụ: Điều xung trên chân A0 :
Code
RA0=1;
Delay_ms(Ton);
RA0=0;
Delay_ms(Toff);


Tuy nhiên, nếu thực hiện bằng cách này thì vi điều khiển sẽ luôn dành thời gian cho việc điều xung PWM. Do đó, các công việc khác như nhận tín hiệu từ cảm biến, điều khiển các cơ cấu sẽ bị ảnh hưởng.


 Điều xung PWM bằng phần cứng
Để giải quyết vấn đề việc điều xung PWM bằng phần mềm chiếm phần lớn thời gian hoạt động của vi điều khiển, PIC16F877A có hỗ trợ 2 kênh điều xung bằng phần cứng ở 2 chân C1 (CCP2) và C2(CCP1) sử dụng TIMER2. Nghĩa là, khi ta khai báo điều xung PWM ở một tần số và duty cycle nào đó thì vi điều khiển sẽ thực hiện công việc xuất xung một cách liên tục và tự động cho đến khi ta thay đổi các giá trị đã khai báo. Khi đó, ta có thể làm các công việc khác một cách dễ dàng mà không phải mất thời gian cho việc duy trì xung PWM.


Các hàm hỗ trợ việc điều xung bằng phần cứng của CCS:
Ghi chú: Chỉ đề cập đến các đối số của các hàm được phục vụ cho việc điều xung PWM.
o setup_timer_2 (mode, period, postscale)
 mode: T2_DIV_BY_1, T2_DIV_BY_4, T2_DIV_BY_16
period: 0-255
postscale: 1
 Tần số điều xung PWM:
f = fosc / [ 4*mode*(period+1) ]

o setup_ccp1(mode) và setup_ccp2(mode)
 mode:
CCP_PWM: chọn chế độ PWM.
CCP_OFF: tắt chế độ PWM.
o set_pwm1_duty(value) và set_pwm2_duty(value)
 Nếu value là giá trị kiểu int 8bit:
duty_cycle = value / ( period+1 )
 Nếu value là giá trị long int 16bit:
duty_cycle = value&1023 / [4*( period+1 )]

 Nếu không cần điều xung quá “mịn” thì nên điều xung ở giá trị value 8bit cho đơn giản.
Ví dụ: Ta muốn điều xung PWM với tần số 10kHz với tần số thạch anh (fosc) sử dụng là 20MHz (value 8bit).
f=fosc/[4*mode*(period+1)] <=> 10000 =20000000/[ 4*mode*(period+1) ] <=> mode(period+1) = 500

Với mode = [1,4,16] và period = 0-255 ta có thể chọn:
o mode = 4; period = 124
o mode = 16; period = 32
Để cho việc điều xung được “mịn” (chọn được nhiều giá trị duty cycle) ta chọn mode = 4 và period = 124.
Như vậy, để duty_cycle từ 0% đến 100% ta cho value từ 0 đến 125.
o value = 30 => duty_cycle = 30 / ( 124+1 ) = 0.32 = 32%
o value = 63 => duty_cycle = 63 / ( 124+1 ) = 0.504 = 50.4%
o value = 113 => duty_cycle = 113 / ( 124+1 ) = 0.904 = 90.4%



Code:
setup_timer_2(T2_DIV_BY_4,124,1);
setup_ccp1(CCP_PWM);
set_pwm1_duty(30);


Sử dụng CCP1 và CCP2 cho động cơ trái và động cơ phải, ta có thể điều khiển được tốc độ của 2 động cơ phù hợp trạng thái lệch khỏi vạch trắng của robot.
 Các chương trình con tham khảo:
Để việc lập trình được dễ dàng, ta nên tạo các chương trình con xử lý tốc độ. Sau đây là chương trình tham khảo của hàm speed.
o Speed (tốc độ động cơ trái, tốc độ động cơ phải)
 Tốc độ: -100 đến 100 (chạy ngược 100% đến chạy thuận 100%)
 Ví dụ: speed(80,60) => động cơ trái chạy 80%, phải 60%

// Các hàm hỗ trợ
void left_motor_forward(int value)
{
MOTOR_LEFT_DIR=0;
setup_timer_2(T2_DIV_BY_4,124,1); // Dieu xung 10kHz
setup_ccp2(CCP_PWM);
set_pwm2_duty(value);
}
void right_motor_forward(int value)
{
MOTOR_RIGHT_DIR=0;
setup_timer_2(T2_DIV_BY_4,124,1); // Dieu xung 10kHz
setup_ccp1(CCP_PWM);
set_pwm1_duty(value);
}
void left_motor_reverse(int value)
{
MOTOR_LEFT_DIR=1;
setup_timer_2(T2_DIV_BY_4,124,1); // Dieu xung 10kHz
setup_ccp2(CCP_PWM);
set_pwm2_duty(value);
}
void right_motor_reverse(int value)
{
MOTOR_RIGHT_DIR=1;
setup_timer_2(T2_DIV_BY_4,124,1); // Dieu xung 10kHz
setup_ccp1(CCP_PWM);
set_pwm1_duty(value);
}
void left_motor_stop()
{
setup_ccp1(CCP_OFF);
}
void right_motor_stop()
{
setup_ccp1(CCP_OFF);
}
// Chương trình xử lý tốc độ 2 động cơ
// 0:Stop,100:FORWARD 100%,-100:Reverse 100%
void speed (signed int left_motor_speed, signed int right_motor_speed)
{
int left_pwm_value=0,right_pwm_value=0;
/* Left motor */
if( left_motor_speed >= 0 )
{
left_pwm_value = 1.25*left_motor_speed; // (125*left_motor_speed/100)
left_motor_forward(left_pwm_value);
}
else
{
left_motor_speed = -left_motor_speed;
left_pwm_value = 1.25*left_motor_speed; // (125*left_motor_speed/100)
left_motor_reverse(left_pwm_value);

}
/* Right motor */
if( right_motor_speed >= 0 )
{
right_pwm_value = 1.25*right_motor_speed; // (125*left_motor_speed/100)
right_motor_forward(right_pwm_value);
}
else
{
right_motor_speed = -right_motor_speed;
right_pwm_value = 1.25*right_motor_speed; // (125*left_motor_speed/100)
right_motor_reverse(right_pwm_value);
}
}





3.2 Nhận biết vạch ngang
Trong quá trình di chuyển của robot, sẽ có các vạch ngang màu trắng. Nhờ các vạch ngang này, robot biết được mình đang đi đến đâu và sẽ thực hiện công việc gì tiếp theo (quay trái, quay phải, nâng hạ trục, gắp nhả quà,…)
 Một cách đơn giản, khi robot đến vạch trắng, tất cả 8 cảm biến sẽ lên mức 1 ứng với giá trị đọc được của cảm biến là 0b11111111 hay 0xFF. Ta dùng một biến đếm để biết thứ tự của vạch ngang đang gặp và thực hiện công việc mong muốn.

Code:
int n // đặt n là số vạch ngang đã nhận
if (SENSOR==0xFF)
{
n++; // tăng giá trị n lên 1 khi gặp vạch ngang
switch (n)
{
case 1: (công việc 1) break;
case 2: (công việc 2) break;
….
case n: (công việc n) break;
}
}
else (chương trình dò đường)



 Tuy nhiên, vì nhiều lý do khác nhau (nhiễu do ánh sáng bên ngoài, các cảm biến có độ nhạy không đều nhau hoặc robot tiếp cận với vạch ngang không theo phương vuông góc), một vài cảm biến khi tiếp xúc với màu trắng nhưng không nhận biết (vẫn giữ trạng thái mức 0 – màu sậm). Điều này gây khó khăn cho việc nhận biết vạch ngang. Vì vậy, khi 4/8 cảm biến ở mức 1, ta nhận đó là một vạch ngang để khắc phục các ảnh hưởng này.

Code:
//KIEM TRA VACH NGANG: 1: co vach, 0:khong co vach
int check_cross_line()
{
int temp_sensor=0,led_in_line=0,i=0;
temp_sensor=SENSOR;
for (i=0;i<8;i++)
{

if ((temp_sensor&0x01)==0x01) led_in_line++;
if (led_in_line==4) break;
temp_sensor=temp_sensor>>1;
}
if (led_in_line==4)
return 1;
else
return 0;
}



 Do chương trình nhận vạch ngang được gọi liên tục để kiểm tra có vạch ngang xuất hiện hay không nên sẽ dẫn đến tình trạng khi robot đến vạch ngang, biến đếm số vạch tăng thêm 1. Sau đó, khi robot chưa kịp chạy qua vạch ngang mà hàm kiểm tra được gọi dẫn đến việc biến đếm tăng liên tục.
Việc này khiến cho robot thực hiện sai công việc.
Hướng giải quyết tình huống này như sau:
o Khi robot gặp vạch ngang: chạy thẳng
o Khi hết vạch ngang: biến đếm số vạch tăng thêm 1
o Thực hiện công việc tương ứng
Code:

while (check_cross_line() == 1) // gặp vạch ngang
{
speed(100,100); // chạy thẳng
}
number_cross_line++; // tăng biến đếm số vạch ngang thêm 1



3.3 Xử lý khi robot lệch hoàn toàn khỏi vạch
Trường hợp robot lệch ra khỏi vạch (quay quá mạnh hoặc bị va chạm), giá trị cảm biến đọc được là 0x00, như vậy robot sẽ bị mất phương hướng.
Để giải quyết trường hợp này, ta đặt một biến trạng thái là biến toàn cục.
Gọi biến này là line_status:
o Line_status=0: giữa vạch;
o Line_status=1: lệch trái;
o Line_status=2: lệch phải;
Khi đọc giá trị cảm biến để dò đường , ta gán luôn giá trị cho biến này. Như vậy khi robot lệch hẳn ra khỏi vạch, ta vẫn biết được trạng thái trước đó để biết quay trái hoặc quay phải để hướng robot di chuyển về
phía line.
Code

switch (SENSOR)
{
case 0b00000000: // lệch ra hẳn khỏi vạch
{
if (line_status==1) // trạng thái cũ là lệch trái
turn_right(); break; // quay phải để di chuyển về phía vạch
if (line_status==2) // trạng thái cũ là lệch phải
turn_left(); break; // quay trái để di chuyển về phía vạch
}
// Trạng thái lệch thông thường
case 0b00011000: forward(); line_status=0; break;
case 0b00001100: turn_left(); line_status=1; break;

case 0b00000110: turn_left(); line_status=1; break;
case 0b00000011: turn_left(); line_status=1; break;
case 0b00000001: turn_left(); line_status=1; break;
case 0b00110000: turn_right(); line_status=2 ;break;
case 0b01100000: turn_right(); line_status=2 ;break;
case 0b11000000: turn_right(); line_status=2 ;break;
case 0b10000000: turn_right(); line_status=2 ;break;
}



3.4 Ứng dụng encoder
3.4.1 Kiến thức cơ bản về encoder
Trong Robot, ta thường sử dụng incremental encoder (encoder tương đối) hay còn gọi là rotary encoder. Mục đích của việc sử dụng encoder trong robot là đếm số vòng quay để tính số vòng quay của động cơ (bánh xe), từ đó suy ra quãng đường di chuyển và tốc độ của robot.

Encoder thường được sử dụng trong Robot



3.4.2 Sử dụng PIC để nhận và đếm xung từ encoder
Để nhận xung từ encoder, ta có thể sử dụng ngắt ngoài, ngắt timer hoặc đơn giản là tham dò mức logic của các chân vi điều khiển một cách liên tục. Phần sau đây giới thiệu cách nhận và đếm xung của PIC16F877A dùng ngắt ngoài B0 (nối với kênh A của encoder) và chân B1 (nối với kênh B của encoder). Ta có thể làm tương tự đối với các cách nhận xung khác.


 Khởi tạo ngắt ngoài theo cạnh lên tại chân B0:
Code:
ext_int_edge(0,L_TO_H); // Ngắt cạnh lên tại RB0
enable_interrupts(INT_EXT); // Cho phép ngắt ngoài
enable_interrupts(GLOBAL); // Cho phép ngắt toàn cục
 Chương trình con phục vụ ngắt:
Code:
#int_EXT
void EXT_isr(void) //Chương trình được gọi khi có tác động cạnh lên tại chân B0
{
if (RB1==1) pulse++; // Nếu kênh B mức cao thì tăng giá trị xung thêm 1
else pulse--; // Nếu kênh B mức cao thì giảm giá trị xung xuống 1
}


 Từ giá trị xung tính được tại các thời điểm ta có thể tính ra các thông số mong muốn.
DBS M05479
Quang Cao