Professional Documents
Culture Documents
ORG
Dữ liệu vào - ra
Xử lý
Hướng xử lý
Bảng 1. Mô tả biểu tượng – chức năng cơ bản của lưu đồ giải thuật.
Để xây dựng lưu đồ giải thuật, bạn cần phải trả lời các câu hỏi:
+Mục đích của chương trình, xây dựng để làm gì, cần phải có chức năng
gì?
+Cần các hàm, các chương trình con như thế nào để đạt được từng chức
năng đã đặt ra?
Từ câu trả lời, bạn đã có khái quát cấu trúc của chương trình, sau đó,
bằng các biểu tượng ở bảng 1, ta có thể lập thành lưu đồ, cuối cùng là chi tiết
hóa bằng mã lệnh. Nếu là người có nhiều kinh nghiệm, có thể các bước trên đã
nằm sẵn trong kỹ năng của bạn và chỉ việc viết code theo định hướng có sẵn
trong tư duy, tuy nhiên nếu là người mới thì làm việc có trình tự và quy tắc
giúp ta đạt được mục đích 1 cách nhanh chóng và hiệu quả nhất có thể.
Ví dụ: ai đó yêu cầu bạn dùng AVR làm cho 1 dãy đèn led gồm 8 bóng
phát sáng lần lượt từng bóng từ phải sang trái và mỗi trạng thái duy trì 1s và cứ
như thế xoay vòng mãi.
+Đầu tiên, đề yêu cầu điều khiển ngoại vi là led, do đó dữ liệu của chân
điều khiển phải là dữ liệu xuất, ta cấu hình hướng dữ liệu xuất, việc cấu hình
này chỉ cần làm 1 lần.
+Thứ 2, cần điều khiển 8 bóng led thì 1 PORT vi điều khiển AVR là đầy
đủ. Vì vậy ta sẽ thao tác dữ liệu trên thanh ghi PORT (PORTA chẳng hạn).
+Thứ 3, nếu xem dữ liệu dữ liệu thanh ghi là 8 bit tương đương với 8
led, từng bóng lần lượt phát sáng, ta thấy tại mỗi thời điểm chỉ có 1 trong 8 bit
ở mức cao, và hình như bit cao này, sau 1 giây lại dịch trái 1 bit. Từ đây ta có
thể suy ra, chỉ cần cho dữ liệu lúc ban đầu bằng 1, sau đó, mỗi 1s lại nhân nó
lên 2 lần (dịch trái 1 bit tương đương nhân lên 2 lần), và sẽ lặp lại 8 lần. Như
vậy ta có thể xây dựng lưu đồ như sau:
Bắt đầu
Cho PORTA=1
Số lần=0
Trì hoãn 1s
PORTA x 2
Đúng Sai
Số lần=8?
Tùy mỗi người mà cách giải quyết 1 vấn đề sẽ khác nhau, tuy nhiên,
nếu làm nhiều và rút kinh nghiệm ta sẽ có các hướng giải quyết hợp lý nhất.
Bởi vì tài nguyên vi điều khiển là hạn hẹp nên việc cân nhắc sử dụng tài
nguyên đó như thế nào cho hợp lý rất đáng được quan tâm.
Ví dụ 1:
/* CHUONG TRINH DICH LED
NAM TRONG DVD TAI LIEU LAP TRINH C CHO VI DIEU KHIEN AVR
TAI LIEU NAY CO NGUON TU WEBSITE WWW.EEELAB.ORG
*/
#include <delay.h>
#define led_port PORTD //DINH NGHIA PORTB LA
led_port
char i; // KHAI BAO BIEN i, KIEU
DU LIEU CUA I LA CHAR
//CHUONG TRINH CHINH
void main() {
DDRD=0xff;
while(1){
for(i=0;i<=7;i++){
led_port=0x01;
led_port=led_port<<i;
delay_ms(500);
}
}
ngữ C, nội dung chú thích phải được viết trong cặp dấu /* và */ (nếu chú thích
trên nhiều dòng), hoặc đặt sau cặp dấu // (nếu chú thích trên 1 dòng).
Chú ý: Trình biên dịch MikroC có chức năng khóa 1 đoạn mã lệnh bằng
cặp dấu chú thích, khi cần khóa 1 đoạn lệnh, ta bôi đen đoạn lệnh đó và bấm
vào nút {…} hoặc bấm tổ hợp phím Ctr+Shift+dấu chấm, khi muốn phục hồi
lại đoạn lệnh đó, ta bấm vào nút {…} hoặc tổ hợp phím Ctr+Shift+ dấu phẩy.
3. Chỉ thị tiền xử lý.
- Chỉ thị sự gộp vào của các tập tin nguồn khác: #include
- Chỉ thị việc định nghĩa các macros hoặc ký hiệu: #define
Chỉ thị đầu tiên được sử dụng trước hết là nhằm gộp vào nội dung của
các tập tin cần có (header file), không thể thiếu trong việc sử dụng một cách tốt
nhất các hàm của thư viện chuẩn. Cú pháp :
#include tên_thư_viện.h
Đối với MikroC, chỉ thị include các thư viện của phần mềm vào chương
trình được thực hiện tự động qua công cụ Library Manager. Nếu dùng các thư
viện tự định nghĩa, ta vẫn dùng lệnh include để gộp tập tin thư viện.
Chỉ thị thứ hai là dùng định nghĩa các biến, cú pháp :
#define tên đối_tượng_cần_gán_tên
c. Kiểu số thực
float 4 -1.5*1045..+3.4*1038
double 4 -1.5*1045..+3.4*1038
Kiểu Tên_mảng [ ]= {Các giá trị cách nhau bởi dấu phẩy};
Nếu kích thước của mảng được chỉ định thì số giá trị không được vượt
quá kích thước của mảng này. Ngược lại, nếu không chỉ định thì trình biên dịch
tự động cập nhật giá trị này cho phù hợp.
Nếu khai báo 1 mảng kiểu char (character – ký tự). Thì ta có thể viết gọn
lại dưới dạng chuỗi.
Ví dụ:
const char msg1[] ={“T”, “E”,“S”,“T”} ;
const char msg2] = “TEST ” ;
Hai hằng số trên có giá trị như nhau.
3. Cấu trúc.
Chứa 1 tập hợp các phần tử được đặt tên bởi lập trình viên, các phần tử
này có thể có 1 kiểu bất kỳ.
Khai báo và khởi tạo cấu trúc:
struct Tên_cấu_trúc {
Kiểu Phần_tử 1 ;
Kiểu Phần_tử 2 ;
……..
Kiểu Phần_tử n ;
}
Ví dụ:
struct I2CSendByte{
char control;
char dat;
}
Khai báo biến thuộc kiểu cấu trúc: Có thể khai báo lúc khai báo cấu trúc
hoặc khai báo sau khi khai báo cấu trúc đều được.
Ví dụ: Khai báo biến lúc khai báo cấu trúc:
struct I2CSendByte{
char control;
char dat;
}d1,d2;
struct I2CSendByte {
char control;
char dat;
};
} s,*sptr=&s;
s.i=3; // gán giá trị 3 cho phần tử I trong cấu trúc
sptr ->d=1.23; // gán gián tiếp giá trị 1.23 cho phần tử d.
V. Biểu thức
Biểu thức là một sự kết hợp giữa các toán tử (operator) và các toán hạng
(operand) theo đúng một trật tự nhất định.
Mỗi toán hạng có thể là một hằng, một biến hoặc một biểu thức khác.
Trong trường hợp, biểu thức có nhiều toán tử, ta dùng cặp dấu ngoặc
đơn () để chỉ định toán tử nào được thực hiện trước.
1. Các toán tử số học
Bảng tóm tắt các toán tử số học
Toán tử Ý nghĩa Độ ưu tiên
Các toán tử 2 toán hạng
+ Phép cộng 12
- Phép trừ 12
* Phép nhân 13
/ Phép chia lấy phần nguyên 13
% Phép chia lấy phần dư (không hỗ trợ kiểu float) 13
Các toán tử 1 toán hạng
+ Cộng, không làm thay đổi toán hạng 14
- Đổi dấu toán hạng 14
++ Tăng, cộng 1 đơn vị vào giá trị toán hạng. 14
-- Giảm, trừ 1 đơn vị vào giá trị toán hạng. 14
Toán tử ++ thêm 1 vào toán hạng của nó và – trừ bớt 1. Nói cách
khác:
x = x + 1 giống như ++x
x = x – 1 giống như x—
Cả 2 toán tử tăng và giảm đều có thể tiền tố (đặt trước) hay hậu tố
(đặt sau) toán hạng. Ví dụ: x = x + 1 có thể viết x++ (hay ++x)
Tuy nhiên giữa tiền tố và hậu tố có sự khác biệt khi sử dụng trong
1 biểu thức. Khi 1 toán tử tăng hay giảm đứng trước toán hạng của nó, C thực
hiện việc tăng hay giảm trước khi lấy giá trị dùng trong biểu thức. Nếu toán tử
đi sau toán hạng, C lấy giá trị toán hạng trước khi tăng hay giảm nó. Tóm lại:
x = 10
y = ++x //y = 11, x=11
Tuy nhiên:
x = 10
y = x++ //y = 10, x=11
p q p&&q p||q !p
0 0 0 0 1
0 1 0 1 1
1 0 0 1 0
1 1 1 1 0
Các ví dụ:
0x1234 & 0x5678; // Bằng 0x1230
Vì:
0x1234 : 0001 0010 0011 0100
&
0x5678 : 0101 0110 0111 1000
0001 0010 0011 0000
Tương tự:
0x1234 | 0x5678; // Bằng 0x567C
0x1234 ^ 0x5678; // Bằng 0x444C
~0x1234 ; // Bằng 0xEDCB
Toán tử thao tác bit phân biệt nhau dựa vào ví dụ sau:
0x2222 & 0x5555 = 0x0000;
0x2222 & 0x5555 = 1; /*AND logic giữa 2 giá trị khác 0 (đúng AND
đúng = 1) */
~0x1234 = 0xEDCB;
!0x1234=0; // Đảo giá trị khác 0 (đúng) thành giá trị sai =0
5. Toán tử gán
a. Gán thông thường
Không giống như các ngôn ngữ khác, C xem việc gán là 1 toán tử,
không phải là 1 lệnh.
Toán tử gán đơn giản:
Biểu thức 1 = Biểu thức 2;
Biểu thức 1 là một đối tượng hay một vị trí bộ nhớ có giá trị được gán
bằng giá trị của biểu thức 2. Biểu thức 1 phải là một biến có thể chứa giá trị,
biểu thức 2 là biểu thức bất kỳ.
Ví dụ:
a=a+5;
a+=5; // Hai câu lệnh này hoàn toàn giống nhau.
+Biến con trỏ không chứa dữ liệu mà chỉ chứa địa chỉ của dữ liệu hay chứa
địa chỉ của ô nhớ chứa dữ liệu mà con trỏ đang trỏ tới.
+Kích thước của biến con trỏ không phụ thuộc vào kiểu dữ liệu, con trỏ
phải có khả năng chứa được địa chỉ ô nhớ lớn nhất của bộ nhớ.
b. Khai báo
Cú pháp: Kiểu * Tên_con_trỏ
Ý nghĩa: Khai báo một biến có tên là Tên_con_trỏ dùng để chứa địa chỉ của
các biến có kiểu Kiểu.
Ví dụ 1: Khai báo 2 biến a,b có kiểu int và 2 biến pa, pb là 2 biến con trỏ kiểu
int.
Ghi chú: Nếu chưa muốn khai báo kiểu dữ liệu mà con trỏ ptr đang chỉ đến, ta
sử dụng:
void *ptr;
Sau đó, nếu ta muốn con trỏ ptr chỉ đến kiểu dữ liệu gì cũng được. Tác dụng
của khai báo này là chỉ dành ra 2 bytes trong bộ nhớ để cấp phát cho biến con trỏ ptr.
c. Thao tác
Toán tử & dùng để định vị con trỏ đến địa chỉ của một biến đang làm việc.
Cú pháp: Tên_biến_con_trỏ=&Tên_biến
Giải thích: Ta gán địa chỉ của biến Tên_biến cho con trỏ Tên_biến_con_trỏ.
Ví dụ: Gán địa chỉ của biến a cho con trỏ pa, gán địa chỉ của biến b cho con trỏ
pb.
pa=&a; pb=&b;
Khi gán địa chỉ của biến tĩnh cho con trỏ cần phải lưu ý kiểu dữ liệu của
chúng.
d. Truy xuất
Để truy cập đến nội dung của ô nhớ mà con trỏ chỉ tới, ta sử dụng cú
pháp:
*Tên_biến_con_trỏ
Với cách truy cập này thì * Tên_biến_con_trỏ có thể coi là một biến có kiểu
được mô tả trong phần khai báo biến con trỏ.
Ví dụ: Ví dụ sau đây cho phép khai báo, gán địa chỉ cũng như lấy nội
dung vùng nhớ của biến con trỏ:
int x=100; // Khởi tạo giá trị đầu cho x
int *ptr; // Khai báo con trỏ ptr
ptr=&x;// Dùng con trỏ ptr trỏ vào x
int y= *ptr ;// Lấy nội dung của x gán cho y thông qua con trỏ ptr
Vào
Giải thích: Nếu biểu thức điều kiện đúng (khác 0)
thì thực hiện khối lệnh 1 và thoát khỏi if.
Nếu biểu thức điều kiện if sai (bằng0) thì bỏ
Bt điều Sai qua khối lệnh và thoát khỏi if.
kiện
Lưu ý: khối lệnh là tập hợp gồm từ 2 lệnh trở lên
phải đặt trong dấu ngoặc nhọn {}.
Đúng
Ví dụ:
Khối lệnh if(a==b) a++; //Nếu a=b thì a tăng 1
if (a!=b){
a++;
Ra b=a;
}
c. Dạng ELSE IF
Quyết định sẽ thực hiện 1 trong n khối lệnh cho trước.
Cú pháp:
if(biểu thức điều kiện 1)
khối lệnh 1;
else if (biểu thức điều kiện 2)
khối lệnh 2;
…
else if (biểu thức điều kiện n-1)
khối lệnh n-1;
else
khối lệnh n;
Vào
Sai
BTĐK1
Sai
BTĐK2
Sai
BTĐK(n-1)
Sai
BTĐKn
Ra
Giải thích: Nếu biểu thức điều kiện thứ n đúng thì thực hiện khối lệnh n,
nếu tất cả biểu thức điều kiện đều cho giá trị sai thì thoát mà không làm gì
cả.
So sánh lưu đồ giải thuật của 2 dạng switch - case (có default và không có
default).
Vào Vào
có break ? có break ?
có break ? có break ?
có break ? có break ?
= giá trị
ngẫu nhiên
Ra default
có break ?
Ra
Tóm lại cấu trúc switch case sẽ dựa vào kết quả tính toán của biểu thức
để chọn lựa khối lệnh phù hợp. Khi sử dụng switch case ta chiếm dụng nhiều
tài nguyên bộ nhớ hơn nếu dùng if vì vậy, cần cân nhắc nếu quỹ bộ nhớ của vi
điều khiển trong ứng dụng không còn nhiều.
Vào
Sai
Ra
Giải thích:
- Biểu thức 1: thông thường là một phép gán để khởi tạo giá trị ban đầu
cho biến điều kiện.
- Biểu thức 2: là một biểu thức kiểm tra điều kiện đúng sai để dừng vòng
lặp.
- Khi biểu thức 2 vắng mặt thì nó được coi là luôn luôn đúng
- Biểu thức 3: thông thường là một phép gán để thay đổi giá trị của biến
điều kiện.
- Trong mỗi biểu thức có thể có nhiều biểu thức con. Các biểu thức con
được phân biệt bởi dấu phẩy.
Công việc: được thể hiện là 1 câu lệnh hay 1 khối lệnh. Thứ tự thực hiện
của câu lệnh for như sau:
B1: Tính giá trị của biểu thức 1.
B2: Tính giá trị của biểu thức 2.
- Nếu giá trị của biểu thức 2 là sai (=0): thoát khỏi câu lệnh for.
- Nếu giá trị của biểu thức 2 là đúng (!=0): <Công việc> được
thực hiện.
B3: Tính giá trị của biểu thức 3 và quay lại B2.
Cú pháp:
while (Biểu thức điều kiện)
<công việc
Lưu đồ:
Vào
Sai
Ra
Giải thích:
Trước tiên, biểu thức điều kiện được kiểm tra: nếu biểu thức này có giá
trị sai(=0) thì lập tức thoát vòng lập. Nếu biểu thức này có giá trị đúng (!=0)
thì thực hiện khối lệnh công việc, sau đó lặp lại việc kiểm tra biểu thức điều
kiện.
Do việc kiểm điều kiện nằm ở đầu vòng lặp nên có thể vòng lặp sẽ
không thực hiện được lần nào.
Lưu đồ:
Vào
Thực hiện
công việc
Đúng
Kiểm biểu
thức điều kiện
Sai
Ra
Giải thích:
Khi bắt đầu vào vòng lặp thì khối lệnh công việc được thực hiện trước,
sau đó chương trình sẽ tiến hành tính toán và kiểm tra biểu thức điều kiện, nếu
biểu thức này vẫn đúng(!=0) thì vòng lặp sẽ tiếp tục thực hiện khối lệnh công
việc, nếu biểu thức điều kiện sai(=0) thì lập tức thoát khỏi vòng lặp.
Do việc kiểm điều kiện nằm ở phía sau khối lệnh nên vòng lặp do while
thực hiện ít nhất 1 lần.