Professional Documents
Culture Documents
com
// Chú thích
Hoặc
Đây là dòng chú thích. Tất cả các dòng bắt đầu bằng hai dấu sổ (//) được coi
là chú thích mà chúng không có bất kì một ảnh hưởng nào đến hoạt động của
chương trình. Các lập trình viên dùng chúng để chú thích cho chương trình.
Trong trường hợp này, dòng chú thích là một giải thích ngắn gọn những gì
mà chương trình chúng ta làm. Còn trong dấu (/* */) bạn có thể chú thích
bao nhiêu dòng tuỳ thích.
b) Chỉ thị #include và #define
Đầu file .c ta phải khai báo
#include <AT89X52.H>
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 1
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
hoặc
#include "AT89X52.H"
Để báo cho trình biên dịch biết ta đang sử dụng loại chip nào, ở đây là
AT89C/S52
Chỉ thị #define cho phép ta đặt tên các hằng số, các vùng nhớ RAM, các bit
đặc biệt.
Lưu ý: Keil C quy định cách sử dụng các bit tương ứng với các chân của chip
như sau:
P1.0 ---- P1_0
P1.1 ---- P1_1
Tức khi sử dụng P1.1 trong chương trình ta phải dùng P1_1
Một ưu điểm nổi bật của C là các bạn có thể tạo ra các bộ thư viện .
Ví dụ sau là tạo thư viện thuvien.h (đuôi .h bạn có thể tạo bằng cách save as
.. *.h ở Keil C ).
#ifndef _thuvien_H
#define _thuvien_H
....//mã chương trình
#endif
+ cộng
- trừ
* nhân
/ chia
% lấy phần dư (trong phép chia)
VD: i = 5%2; // i = 1
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 2
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
- Các toán tử gán phức hợp (+=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=)
a += 5; a = a + 5;
a -= 5; a = a – 5;
a /= b; a = a / b;
a *= b; a = a * b;
a *= (b + c); a = a * (b+c);
Tăng và giảm ++ - -
a++; a = a + 1;
a+=1; a = a + 1;
Ví dụ:
B=3;
A=++B; // Lúc này A = 4, B = 4
// Trình dịch xử lý tăng B thêm 1 xong mới gán A = B.
Khác với:
B=3;
A=B++; // Lúc này A = 3, B = 4
// Trình dịch xử lý gán A = B xong mới tăng B thêm 1.
== Bằng
!= Khác
> Lớn hơn
< Nhỏ hơn
>= Lớn hơn hoặc bằng
<= Nhỏ hơn hoặc bằng
Ví dụ:
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 3
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Chú ý: = (một dấu bằng) khác hoàn toàn khác với == (hai dấu bằng). (==)
nhằm so sánh còn (=) gán giá trị của biểu thức bên phải cho biến ở bên trái.
! NOT
&& AND
|| OR
Ví dụ:
!(5 == 5) trả về false vì biểu thức bên phải (5 == 5) có giá trị true.
!(6 <= 4) trả về true vì (6 <= 4)có giá trị false.
!true trả về false.
!false trả về true.
((5 == 5) && (3 > 6)) trả về false (true && false).
((5 == 5)
& AND
| OR
^ XOR
~ NOT
<< SHL: dịch sang trái
>> SHR: dịch sang phải
- Thứ tự ưu tiên của các toán tử
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 4
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
- Kiểu bit: người dùng có thể truy nhập trực tiếp đến 1 bit trong vùng
RAM có thể đánh địa chỉ bit.
- Kiểu sbit: dùng truy nhập vùng nhớ chứa các thanh ghi đặc biệt (SFR),
ví dụ bit 7(LSB) của thanh ghi 0x90 được truy nhập với tên P1_7, tức
chân P1.7 của chip.
- Kiểu sfr để định nghĩa các thanh ghi trong vùng nhớ đặc biệt, vùng
SFR. Các định nghĩa các biến có kiểu sfr sẽ được tìm thấy trong file
header khai báo chip đang được sử dụng trong chương trình, file
AT89X51.H
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 5
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
/***********************************************************/
// Các chương trình con
/***********************************************************/
/***********************************************************/
// Các hàm
/***********************************************************/
int QuetPhim(void) // chuong trinh con dang ham
{
...
return(MaPhim);
}
/***********************************************************/
// Chương trình chính
/***********************************************************/
Chương trình con là nơi các bạn viết các chương trình nhỏ, rất tiện cho các đoạn
lệnh gặp lại nhiều lần. Chương trình con có thể được gọi ở bất kì đâu kể cả
chương trình chính, các chương trình con khác và các chương trình phục vụ ngắt
(ISR).
Hàm là chương trình con trả về cho mình một giá trị. Cuối hàm bắt buộc phải có
lệnh return(x);
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 6
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
if (condition) statement;
Ví dụ:
if (x==100) x++;
- Cú pháp:
if (condition) {
statement1;
statement2;
...
// Nếu xử lý có nhiều hơn 1 lệnh
}
Ví dụ:
if (x==100) {
i++;
j++;
}
- Cú pháp:
Ví dụ:
if (x == 100) x++;
else x--; // Nếu x=100 thì tăng x thêm 1 nếu không thì giảm x đi 1.
- Cú pháp:
if (condition) {
statement1;
statement2;
...
// Nếu xử lý có nhiều hơn 1 lệnh
}
else {
statement3;
statement4;
...
// Nếu xử lý có nhiều hơn 1 lệnh
{
Ví dụ:
if (x==100) {
x++;
x-=2;
}
else
{
x--;
x-=2;
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 7
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Ví dụ:
while(1) {
};
Tạo vòng lặp mãi mãi, rất hay sử dụng trong lập trình VĐK. Chương trình chính sẽ được
viết trong dấu ngoặc.
hoặc
do {
statement1;
statement2;
...
} while (condition);
Ví dụ:
do x++ while (x<10);
hoặc
do {
x++;
i++;
j++;
} while (x<10);
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 8
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Chức năng chính vòng lặp for là lặp lại statement chừng nào condition còn mang giá trị
đúng, như trong vòng lặp while. Nhưng thêm vào đó, vòng lặp for cung cấp chỗ dành cho
lệnh khởi tạo và lệnh tăng. Vì vậy vòng lặp này được thiết kế đặc biệt lặp lại một hành
động với một số lần xác định.
Đầy đủ:
for (n=0;n<10;n++) {
}
hoặc
for (;n<10;) {
}
hoặc
for (;n<10;n++){
}
Bằng cách sử dụng dấu phẩy, chúng ta có thể dùng nhiều lệnh trong bất kì trường nào
trong vòng for, như là trong phần khởi tạo. Ví dụ chúng ta có thể khởi tạo một lúc nhiều
biến trong vòng lặp:
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 9
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Ví dụ:
unsigned char i, j;
...
j= 0;
for (i=1; i<10; i++) {
if (i==6) break;
j++;
}
Kết thúc vòng lặp i sẽ có giá trị là 6 vì khi i=6 thì điều kiện if xảy ra và vòng lặp for sẽ bị
dừng tại điểm đó.
Lệnh continue
Lệnh continue làm cho chương trình bỏ qua phần còn lại của vòng lặp và nhảy sang lần
lặp tiếp theo.
Ví dụ:
unsigned char i, j;
...
j= 0;
for (i=1; i<10; i++) {
if (i==6) continue;
j++;
}
Sau đoạn mã trên i=9; j=8 do bỏ qua lần i=6 (không tăng j khi i=6);
Lệnh goto
Lệnh này cho phép nhảy vô điều kiện tới bất kì điểm nào trong chương trình. Nói chung
bạn nên tránh dùng nó trong chương trình C vì đây là một lệnh có thể phá vỡ cấu trúc của
chương trình gây ra những hiện tượng khó hiểu nếu địa chỉ nhảy đến nằm ngoài chương
trình con chứa lệnh goto này.
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 10
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Ví dụ:
unsigned char i, j;
{
...
j= 0;
for (i=1; i<10; i++) {
if (i==6) goto There;
j++;
}
...
There:
...
// Ít nhất phải có 1 lệnh sau nhãn trước khi kết thúc chương trình
}
Trong các lệnh trên, lệnh break hay được dùng để thoát khỏi vòng lặp. Các lệnh khác
thường rất ít được sử dụng.
Ví dụ:
while(1){
LCD_GotoXY(1,1);
StrToLCD("Quay thuan");
QuayThuan();
if(!Stop) {
LCD_Clear();
StrToLCD("Dung");
Stop();
break;
}
}
Cú pháp:
switch (expression)
{
case constant1:
{
block of instructions 1 // Khối lệnh 1
break;
}
case constant2:
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 11
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Nó hoạt động theo cách sau: switch tính biểu thức và kiểm tra xem nó có bằng constant1
hay không, nếu đúng thì nó thực hiện “block of instructions 1” cho đến khi tìm thấy từ
khoá break, sau đó nhảy đến phần cuối của cấu trúc lựa chọn switch.
Còn nếu không, switch sẽ kiểm tra xem biểu thức có bằng constant2 hay không. Nếu
đúng nó sẽ thực hiện block of instructions 2 cho đến khi tìm thấy từ khoá break.
Cuối cùng, nếu giá trị biểu thức không bằng bất kì hằng nào được chỉ định ở trên (bạn có
thể chỉ định bao nhiêu câu lệnh case tuỳ thích), chương trình sẽ thực hiện các lệnh trong
phần default: nếu nó tồn tại vì phần này không bắt buộc phải có.
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 12
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
III. Mảng
Mảng là một chuỗi các dữ liệu nằm nối tiếp nhau trong bộ nhớ mà ta có thể truy cập đến
từng phần tử riêng rẽ của nó theo số thứ tự của nó trong mảng.
Ví dụ: Một mảng gồm 5 số nguyên có tên là A có thể biểu diễn như sau:
Mỗi ô chứa 1 phần tử của mảng và được đánh số thứ tự bắt đầu từ 0.
Ví dụ:
int A[5]; // Khai báo mảng A gồm 5 phần tử kiêu số nguyên
Lưu ý: So_Phan_Tu_Cua_Mang phải là một hằng số vì đây không phải là một mảng
động nên số phần tử cần phải được xác định.
Khi khai báo một mảng, nếu ta không khới tạo các giá trị ban đầu cho nó thì
các phần tử trong mảng sẽ tự động mặc định giá trị là 0. Ta có thể
khởi tạo các giá trị ban đầu của mảng bằng cách như sau:
Khai báo này sẽ tạo ra một mảng trong bộ nhớ RAM như sau:
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 13
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Tại bất cứ đâu trong chương trình ta có thể truy cập (đọc- ghi) các phần tử
của mảng bằng dạng sau.
Ten[index]
Ví dụ muốn lưu giá trị 75 vào phần tử thứ 3 của mảng billy, ta làm như sau:
billy[2] = 75;
và, ví dụ muốn gán giá trị thứ 3 của mảng cho một biến a khác ta làm như sau:
a= billy[2];
Lưu ý ở đây, phần tử thứ 3 của mảng billy có số thứ tự là 2 vì các phần tử
trong mảng được đánh số từ 0.
Một chú ý nữa là ta nên lưu ý khi truy cập các phần tử của mảng, không được
truy cập các phần tử có chỉ số lớn hơn hoặc bằng tổng số phần tử trong
mảng. Ví dụ mảng A chỉ có 5 phần tử được đánh số từ 0 đến 4, ta truy cập
phần tử thứ 5, tức A[5] của mảng thì sẽ gây ra nhiều lỗi rất khó hiểu vì khi
khai báo thì mảng chỉ có 5 phần tử, trình biên dịch sẽ chỉ cấp phát 5 vùng
nhớ cho các phần tử trong mảng, vùng nhớ kế tiếp với mảng có thể chứa 1
biến nào đó, việc đọc ghi phần tử thứ 5 trong mảng tình cờ sẽ đọc ghi giá trị
biến nằm kế mảng đó, gây sai trong chương trình. Tuy nhiên với lỗi này thì
trình biên dịch không báo vì nó không gây lỗi khi biên dịch. Do đó ta cần phải
cẩn thận khi truy cập các phần tử của mảng.
billy[0] = a;
billy[a] = 75;
b = billy [a+2];
billy[billy[a]] = billy[2] + 5;
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 14
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
// arrays example
void main () {
for (n=0; n<5; n++) KetQua += A[n];
} // KetQua = 12206
jimmy là một mảng 2 chiều với 3 mảng 1 chiều, mỗi mảng chứa 5 phần tử.
jimmy[1][3]=10;
a = jimmy[1][3];
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 15
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Ví dụ 1:
unsigned char data X = 0; // Khai báo biến X kiểu số nguyên (integer) nằm trong RAM
từ 00 đến 7FH.
unsigned int idata Y = 0; // Khai báo biến Y kiểu số nguyên (integer) nằm trong RAM
từ 80H đến FFH, vùng nhớ này chỉ có trong các chip có 256 byte RAM nội trở lên như
AT89C52, AT89S52, AT89S8252, AT89C55WD ...
Chỗ khác nhau khi khai báo 1 biến nằm trong vùng data hay idata là ở kiểu đánh địa chỉ.
Một biến nằm trong vùng nhớ data là biến được truy cập trực tiếp còn biến nằm trong
vùng idata là biến được truy cập gián tiếp. Khi lập trình bằng C chúng ta không cần quan
tâm đến điều này, tuy nhiên khi cần thiết mới khai báo 1 biến nằm trong vùng idata vì nó
sẽ làm chậm chương trình. Ta xem ví dụ sau:
unsigned int data i = 0;
C:0x000F E4 CLR A
C:0x0010 F508 MOV 0x08,A
C:0x0012 F509 MOV 0x09,A
Khi thao tác xử lý các biến nằm trong vùng idata sẽ phải tốn nhiều lệnh hơn, do đó
chương trình sẽ bị chậm hơn. Một lời khuyên là chỉ đặt các biến cần tốc độ xử lý không
cao nằm trong vùng nhớ này.
Ví dụ 2:
unsigned char code KetQua = 100; // Đây là một cách khai báo hằng số, vì từ khoá
code xác định KetQua nằm trong vùng nhớ chương trình (ROM). KetQua sẽ là một hằng
số nên ta chỉ có thể thao tác đọc nó chứ không thay đổi được nó (không cho phép ghi).
Ví dụ 3:
unsigned char xdata KetQua = 0;
Chú ý rằng, bạn có thể không cần khai báo cụ thể vị trí của một biến nằm trong vùng
nhớ nào, chỉ cần khai báo như sau, trình biên dịch của Keil C mặc định biến này sẽ nằm
trong vùng nhớ data.
unsigned int Y = 0;
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 16
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Trong trường hợp này trình biên dịch tự động phân bổ vùng nhớ RAM cho biến Y cho
đến lúc nào số biến mà bạn sử dụng nhiều vượt quá dung lượng vùng nhớ data, khi đó
trình biên dịch sẽ báo cho ta biết không đủ bộ nhớ, ta phải chuyển bớt các biến ra vùng
nhớ idata bằng cách khai báo như ví dụ 1.
2. Bảng vector ngắt và cách khai báo một chương trình con phục vụ ngắt - ISR
Trong 8051 có 5 nguyên nhân sinh ra ngắt: ngắt ngoài 0, timer0, ngắt ngoài 1, timer1,
ngắt nối tiếp.
Các bạn lại mở file regx51.h ra ở phần cuối của file như sau:
/*------------------------------------------------
Interrupt Vectors:
Interrupt Address = (Number * 8) + 3
------------------------------------------------*/
#define IE0_VECTOR 0 /* 0x03 External Interrupt 0 */
#define TF0_VECTOR 1 /* 0x0B Timer 0 */
#define IE1_VECTOR 2 /* 0x13 External Interrupt 1 */
#define TF1_VECTOR 3 /* 0x1B Timer 1 */
#define SIO_VECTOR 4 /* 0x23 Serial port */
Địa chỉ của ngắt trong bảng vector ngắt = 8 * số thứ tự ngắt + 3, số thứ tự ngắt = 0,1,2,3,4
như kí hiệu trong file đó. Như vậy địa chỉ trong RAM từ 0x03 đến 0x30 là dành cho bảng
vector ngắt.
Chương trình con phục vụ ngắt là một hàm không có tham số, không có kiểu trả về. Vì
thực chất các biến mà hàm này thao tác chính là các biến toàn cục (các thanh ghi, các
port). Các chương trình con phục vụ ngắt này sẽ tự động được CPU gọi khi điều kiện
ngắt xảy ra, thực chất khi điều kiện ngắt xảy ra thì CPU sẽ tạm dừng chương trình đang
thực hiện, nhảy đến chương trình con phục vụ ngắt tương ứng, thực hiện hết chương trình
ngắt đó và trở về lại tại điểm mà lúc nãy nó bị dừng bởi ngắt.
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 17
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Trong đó: những chữ in nghiêng là bắt buộc phải có và using b có thể có hoặc không có.
TenChuongTrinhPhucVuNgat: tùy ý
a : là thứ tự của ngắt trong bảng vector ngắt
a = 0 : ngắt ngoài 0
a = 1 : ngắt timer 0
a = 2 : ngắt ngoài 1
a = 3 : ngắt timer 1
a = 4 : ngắt nối tiếp
b: là bank được chọn dùng để thực hiện hàm ngắt. Do 8051 có 4 bank là bank 0, 1, 2, 3.
Do đó : b có thể là một trong các giá trị 0,1,2,3.
Lưu ý: nếu không viết thêm using b thì mặc định là hàm ngắt thực hiện tại bank0.
Ví dụ: Tạo một xung ở chân P1.0 và khi nhận được dữ liệu nối tiếp thì chuyển sang port
P2. Tần số thạch anh là 11.0592 MHz. Dùng AT89C51
Code:
# include "regx51.h"
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 18
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
Lưu ý: là để VĐK nhảy đến bảng vector ngắt thì ta phải cho phép ngắt đó.
Các bit trong thanh ghi IE đã được Keil C định nghĩa trong thư viện regx51.h, các bit này
có tên giống hệt tên các bit được ghi trong datasheet.
/***********************************************************/
// Các chương trình con
/***********************************************************/
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 19
Lập trình C cho vi điều khiển bằng Keil C caonam@gmail.com
/***********************************************************/
// Các hàm
/***********************************************************/
int QuetPhim(void) // chuong trinh con dang ham
{
...
return(MaPhim);
}
/***********************************************************/
// Chương trình con phục vụ ngắt
/***********************************************************/
void ISR() interrupt 1 // chuong trinh con dang ham
{
...
}
/***********************************************************/
// Chương trình chính
/***********************************************************/
Biên soạn: Cao Nguyễn Khoa Nam – Khoa Điện – Trường Cao đẳng công nghệ 20