Lập trình C trong embedded, đặc biệt với các vi điều khiển như ARM thì nó hơi khác một chút so với lập trình C thông thường, do nó có phải thao tác nhiều tới các thanh ghi, hôm nay mình sẽ nói lại tổng quan một chút về lập trình nhúng và các ví dụ cụ thể khi dùng C trong Embedded như thế nào
Mục tiêu
- Các thành phần của C, syntax rule
- Các kiểu dữ liệu cơ bản: char, short,long (unsigned and signed)
- Tìm hiểu về hàm main trong chương trình được thực hiện như thế nào
- Tìm hiểu về variable(biến) và expression(biểu thức)
- Hiểu về cách sử dụng printf và scanf với I/O
- Hiểu điều kiện if và while và cách sử dụng chúng trong các biểu thức điều kiện
- Hiểu được hàm và cách sử dụng chúng với input và output
Giới thiệu về C
Về lịch sử của C mình tạm không bàn tới, nhưng có một lưu ý là lập trình cấu trúc thường có 3 dạng cơ bản
- Sequential (tuần tự): chương trình thực hiện tuần tự, việc A trước, sau đó đến việc B
- Decision (quyết định): chương trình dựa trên các điều kiện để thực hiện công việc, ví dụ với điều kiện 1 thì làm việc A, điều kiện 2 thì làm việc B
- Iterative (lặp lại): chương trình thực hiện lặp lại 1 công việc cho đến khi hoàn thành, ví dụ như việc phải phải liên tục làm việc(đi bộ) cho tới khi đạt được kết quả (đến siêu thị)
- Interrupts (ngắt) : chương trình thực hiện 1 nhiệm vụ khi có sự tác động tới hardware, ví dụ để nhận được dữ liệu từ GPS thì MCU phải thực hiện ngắt nhận dữ liệu
Flowchart
Nói về flowchart thì trong lúc học ở trường về môn lập trình C thì hầu như lúc nào cũng phải làm phần này, trong các bài kiểm tra cuối kỳ chắc chắn thì nó cũng nằm trong 1 câu nào đó của bài thi. Tại sao lại cần có phần này, để đơn giản các bạn có thể hình dung flowchart nó giống như bản thiết kế của một ngôi nhà vậy, để có thể xây nhà thì bạn cần phải có thiết kế mọi thứ ra, sau đó mới bắt đầu xây dựa trên thiết kế đúng không ? Trước khi lập trình thì cũng y như thế, chúng ta phải xác định được cần phải làm gì và các bước để thực hiện việc đó.
Trong khi vẽ flowchart thì có 7 khối thông dụng thường sử dụng như hình bên trên
- Entry point: là phần bắt đầu của phần mềm, thông thường nó là phần begin của mỗi flowchart
- Conditional: tương ứng với câu lệnh rẽ nhánh if
- Input/Output: xác định đầu ra đầu vào hệ thống
- Process: quá trình xử lý 1 việc gì đó liên tục
- Connectors: mũi tên đi vào tương ứng với thuật toán, mũi tên đi ra tương ứng với lệnh jump hoặc goto
- Function: thường dùng để mô tả một function(có trả về parameter), subroutine(thường dùng trong asembly) hoặc procedure(thường không trả về param)
- Exit point: tương ứng với end
Khi thiết kế phần mềm cho vi điều khiển khoảng 1000 dòng code trở lên hoặc một hệ thống lớn với cả triệu, tỉ dòng code thì rất khó để duy trì một cấu trúc nhất quán(consisten structure), do đó cần phải có cấu trúc rõ ràng cho chương trình (structured program).
Với một structured programs trong C thường có 3 dạng cơ bản
- Sequence
- Conditional
- While-loop
Thiết kế máy nướng bánh
Trong ví dụ này ta sẽ sử dụng flowchart để thiết kế thuật toán điều khiển máy nướng bánh.
Ở flowchart bên trên ta có thể biết được cách hoạt động của máy như sau:
- Có 1 nút để người dùng khởi động máy, nút này luôn ở trạng thái chờ, nghĩa là luôn kiểm tra xem người dùng có nhấn hay chưa(pressed/not pressed), nếu có nhấn thì thực hiện nướng bánh (cook)
- Trong khi máy nướng bánh thì hoạt động bên trong là luôn kiểm tra nhiệt độ hiện tại của máy, nếu nhỏ hơn nhiệt độ làm chín bánh(desired) thì tự động tăng nhiệt lên, khi quá nhiệt thì tắt không tăng nhiệt nữa
Vậy trước khi viết một phần mềm (ta thường gọi là viết một chương trình C) thì cần ít nhất là 4 bước
- Xác định input/output là gì, hệ thống sẽ hoạt động như thế nào, các ngưỡng giá trị và tầm quan trọng của chúng
- Đưa ra danh sách các dữ liệu cần thiết, xác định được cấu trúc dữ liệu là như thế nào, ý nghĩa của nó, làm sao để thu thập và thay đổi nó
- Phát triển các thuật toán, chuỗi hoạt động mà chúng ta cần thực hiện để hệ thống hoạt động bằng flowchart hoặc pseudo code
- Debug để tăng chất lượng và hiệu quả của code
Thuật ngữ thường gặp
Trước khi nói tới cấu trúc của C thì mình nói qua một chút về mối quan hệ giữa C lại liên quan tới vi điều khiển và bằng cách nào để người ta đưa code C vào trong vi điều khiển
Compiler(trình biên dịch): là thứ dùng để chuyển ngôn ngữ bậc cao (C) về object code(định dạng mã máy có thể đọc được), để hiểu đơn giản thì compiler này nó cũng giống như phần mềm lacviet dịch tiếng anh(C) về tiếng việt(mã máy). Bạn có thể tìm hiểu thêm có thể xem bài compiler
Ví dụ: C code (z = x+y;) → Assembly code (ADD R2,R1,R0) → Machine code (0xEB010200)
Assembler: là thứ dùng để chuyển ngôn ngữ assembly về object code, các bạn có thể xem qua bài lập trình hợp ngữ với ARM để hiểu thêm về assembly, assembler
Ví dụ: Assembly code ADD R2,R1,R0
→ Machine code 0xEB010200
Interpreter(trình thông dịch): thực hiện trực tiếp trên ngôn ngữ bậc cao, tuy nhiên nó sẽ chậm hơn là compile code
Ví dụ: khi gười dùng gõ một lệnh nào đó trên máy tính chẳng hạn thì Interpreter sẽ thực thi ngay lập tức lệnh mà họ vừa gõ vào, đơn giản dễ thấy nhất là dùng debugger với các command của nó
Linker: là phần mềm để build hệ thống bằng cách kết nối(linking) các thành phần của software lại với nhau
Ví dụ: Ta thường thấy trong một chương trình uart thì cần có 3 file phải compile và link với nhau là startup.s, uart.c, main.c
Loader: thực hiện đưa object code vào bộ nhớ (memory), trong hệ thống nhúng thì thường loader sẽ đưa object code vào trong flash ROM.
Debugger: là thiết bị gồm phần cứng và phần mềm dùng để đảm bảo được hệ thống hoạt động một cách chính xác, yêu cầu quan trọng đối với debugger là phải gỡ lỗi tốt và khả năng quan sát được thông tin.
Cấu trúc và cách tổ chức
Khác với lập trình hợp ngữ assembly thì C có thể được xem là ngôn ngữ khá là tự do và thoải mái, chúng ta có thể viết một chương trình theo nhiều style khác nhau ví dụ dưới đây là 3 phong cách code khác nhau cho một chương trình trả về một số bất kỳ
Style 1
unsigned long M=1;
unsigned long Random(void){M=1664525*M+1013904223;return(M);}
Style 2
unsigned long M=1;
unsigned long
Random(void){
M = 1664525*M
+1013904223;
return(M);
}
Style 3
unsigned long M=1;
unsigned long Random(void){
M = 1664525*M+1013904223;
return(M);}
Trong chương trình trên thì M là biến(variable), Random là hàm(function) và toán tử(operator) là phép nhân *, ngoài ra còn có sử dụng dấu cách, dấu chấm phẩy, ngoặc nhọn ngoặc vuông ..
Các dấu chấm câu thường được dùng trong lập trình C các bạn có thể tham khảo
Punctuation
Dấu | Ý nghĩa |
---|---|
; | Kết thúc câu lệnh |
: | Định nghĩa nhãn label |
, | Phân chia các thành phần của 1 list |
( ) | Bắt đầu và kết thúc của một chuỗi parameter |
{ } | Bắt đầu và kết thúc của compund statement |
[ ] | Bắt đầu và kết thúc của array index |
" " | Bắt đầu và kết thúc của một chuỗi |
' ' | Bắt đầu và kết thúc của character constant |
Những dấu chấm câu (Punctuation marks) rất quan trọng trong C, nó là khởi nguồn của những lỗi thường gặp nhất khi lập trình, kể cả đối với người có hay không có kinh nghiệm.
Cấu trúc chương trình C
Tiếp tục một ví dụ minh họa cụ thể về cấu trúc của chương trình C cho vi điều khiển để ta có thể hình dung rõ hơn
//**** 0. Documentation Section
// This program calculates the area of square shaped rooms
// Author: Ramesh Yerraballi & Jon Valvano
// Date: 6/28/2013
//
// 1. Pre-processor Directives Section
#include <stdio.h> // Diamond braces for sys lib: Standard I/O
#include "uart.h" // Quotes for user lib: UART lib
// 2. Global Declarations section
// 3. Subroutines Section
// MAIN: Mandatory routine for a C program to be executable
int main(void) {
UART_Init(); // call subroutine to initialize the uart
printf("This program calculates areas of square-shaped rooms\n");
}
Nhìn vào ví dụ trên ta có thể hình dung ra một chương trình C gồm 4 phần
- Phần 0 được gọi là documentation section sẽ mô tả thông tin nội dung của file này, thường thì là file này viết ra cho mục đích gì, ai là người code, thời gian code và các thông tin cập nhật
- Phần 1 được gọi là preprocessor directives(tiền chỉ thị), thường thì ta thấy trong chương trình C là các phần như #include để có thể kết nối với các modul khác. Ở đây khi dùng thư viện trong <> nghĩa là dùng các thư viện của hệ thống, còn dùng “” nghĩa là dùng các thư viện của user(cụ thể là uart.h)
- Phần 2 được gọi là global declarations đây là phần khai báo các biến toàn cục (global variables) và các hàm sẽ được sử dụng trong chương trình(function prototypes), phần này mình sẽ nói cụ thể hơn ở các bài sau
- Phần 3 được gọi là functions đây là chương trình chính, mỗi chương trình C sẽ có duy nhất một hàm main, hàm này sẽ xác định đâu là chỗ bắt đầu thực hiện
Một cái cần lưu ý khi lập trình C nữa là comment, có 2 loại comment:
- Một là loại giải thích cách sử dụng phần mềm (thường kí hiệu // hoặc /**/) dùng để mô tả, chú thích code, loại này có thể nằm ở trên cùng (phần 0) để mô tả về code.
- Hai là loại được dùng để hỗ trợ developer có thể thay đổi, debug và mở rộng khi cần phải nâng cấp, thường thấy nhất là các comment nằm bên phải của mỗi dòng code
Preprocessor directives (chỉ thị tiền xử lý) thường bắt đầu với #, như tên gọi của nó là tiền xử lý nghĩa là khi biên dịch nó sẽ thực hiện xử lý cái này đầu tiên. Chúng ta sẽ tạo ra một macro sử dụng #define để xác định hằng số
Ví dụ: #define SIZE 10
nghĩa là ở bất kỳ chỗ nào có SIZE thì được coi như sự thay thế cho giá trị 10
Một directive khác nữa là #include
cho phép bạn có thể thêm toàn bộ một file tại một ví trị trong chường trình
Ví dụ: #include “tm4c123gh6pm.h”
nghĩa là chỉ thị #include
sẽ thêm file tm4c123gh6pm.h
vào trong chương trình, file này sẽ định nghĩa tất cả các tên port I/O cho TM4C123
Variable và Expression
Biến và kiểu dữ liệu
Kiểu dữ liệu | Kích thước | Ngưỡng |
---|---|---|
unsigned char | 8-bit unsigned | 0 đến +255 |
signed char | 8-bit signed | -128 đến +127 |
unsigned int | phụ thuộc vào compiler | |
int | phụ thuộc vào compiler | |
unsigned short | 16-bit unsigned | 0 đến +65535 |
short | 16-bit signed | -32768 đến +32767 |
unsigned long | unsigned 32-bit | 0 đến 4294967295L |
long | signed 32-bit | -2147483648L đến 2147483647L |
float | 32-bit float | ±10^-38 đến ±10^+38 |
double | 64-bit float | ±10^-308 đến ±10^+308 |
Đang xem 1 đến 10 trong tổng số 10 mục
Ở bảng trên ta sẽ thấy được các kiểu dữ liệu trong C và ngưỡng cũng như giá trị của nó. Có một số kiểu dữ liệu phụ thuộc vào compiler ví dụ như trong compiler của keil C thì chỉ có duy nhất kiểu char mà không quan tâm tới signed hoặc unsigned, hay như kiểu int được Keil C coi là 32 bit.
Biến mà được khai báo bên ngoài hàm thì được gọi là external variable, external variable thường gặp nhất là global variable, có 2 lý do để sử dụng global variable, thứ nhất là data permanence, nghĩa là dữ liệu lúc nào cũng sẵn có, thứ hai là việc chia sẻ thông tin. Ví dụ khi modul đó có đưa dữ liệu vào global variable thì modul khác có thể xem được dữ liệu đó
Ngược lại thì ta có local variable, biến này thường được khai báo ngay bên trong hàm
Ví dụ:
int main(void) {
unsigned long side; // Chieu dai cua can phong
unsigned long area; // Dien tich can phong
UART_Init(); // Gọi ham de init uart
side = 3;
area = side*side;
printf("\nDien tich cua can phong chieu dai %ld m la %ld m2\n",side,area);
}
Ở ví dụ trên thì side và area là biến local, nó được định nghĩa ngay sau dấu {, khác với biến global là tĩnh(static) thì biến local thường là động (dynamic), ngoài ra quy định về tên của biến global là không được trùng tên, còn biến local thì có thể dùng thoải mái, ví dụ như trong function a bạn có khai báo biến int i thì trong function b bạn hoàn toàn có thể khai báo biến int i.
Tiếp tục tìm hiểu tiếp xem đoạn code ở bên trên đang làm cái gì? Bước đầu khai báo 2 biến local là side và area xong thì bước tiếp theo sẽ gọi hàm UART_Init() để cấu hình UART in dữ liệu ra màn hình (nó giống như lệnh Serial.begin của Arduino), sau đó là gán side = 3 và tính diện tích của căn phòng hình vuông theo công thức S = a *a, cuối cùng là in thông tin ra màn hình để chúng ta có thể quan sát được
Nếu như bạn để ý sẽ có một loại C khác là C99 thì kiểu dữ liệu của nó sẽ hơi khác một chút.
Kiểu dữ liệu | Kích thước | Ngưỡng |
---|---|---|
uint_8 | 8-bit unsigned | 0 đến +255 |
int_8 | 8-bit signed | -128 đến +127 |
uint_16 | 16-bit unsigned | 0 đến +65535 |
int_16 | 16-bit signed | -32768 đến +32767 |
uint_32 | unsigned 32-bit | 0 đến 4294967295L |
int_32 | signed 32-bit | -2147483648L đến 2147483647L |
Expression
Ví dụ: Chương trình dưới đây là một số phép toán cơ bản được dùng trong C
void main(void){
long x,y,z; // 3 biến x y z là local variables
x=1; y=2; // set giá trị của x và y
z = x+4*y; // tính z
x++; // Tăng x, cái này giống x=x+1;
y--; // Tương tự giảm y, giống y=y-1;
x = y<<2; // Dịch trái, tương tự x=4*y;
z = y>>2; // Dịch phải, tương tự x=y/4;
y += 2; // Tăng y, tương tự y=y+2;
}
Toán tử ở trong C các bạn có thể xem ở bảng sau
Toán tử | Ý nghĩa | Toán tử | Ý nghĩa | |
---|---|---|---|---|
= | Assignment statement | == | Equal to comparison | |
? | Selection | <= | Less than or equal to | |
< | Less than | >= | Greater than or equal to | |
> | Greater than | != | Not equal to | |
! | Logical not (true to false, false to true) | << | Shift left | |
~ | 1’s complement | >> | Shift right | |
+ | Addition | ++ | Increment | |
- | Subtraction | -- | Decrement | |
* | Multiply or pointer reference | && | Boolean and | |
/ | Divide | || |
Boolean or | |
% | Modulo, division remainder | += | Add value to | |
| |
Logical or | -= | Subtract value to | |
& | Logical and, or address of | *= | Multiply value to | |
^ | Logical exclusive or | /= | Divide value to | |
. | Used to access parts of a structure | |= |
Or value to | |
&= | And value to | |||
^= | Exclusive or value to | |||
<<= | Shift value left | |||
>>= | Shift value right | |||
%= | Modulo divide value to | |||
-> | Pointer to a structure |
Thường có một số vấn đề về thứ tự(precedence) khi thực hiện các phép toán phức tạp. Ví dụ: z = x+4y thì 4y sẽ được thực hiện trước vì phép tính nhân sẽ được ưu tiên hơn phép + và -, nếu bạn cảm thấy bối rối về điều này thì có thể xem bảng dưới
Precedence | Operators | Associativity |
---|---|---|
Highest | () []. | Left to right |
++(prefix) | Right to left | |
* | Left to right | |
+ | Left to right | |
<< | Left to right | |
< | Left to right | |
== | Left to right | |
& | Left to right | |
^ | Left to right | |
| |
Left to right | |
&& | Left to right | |
|| |
Left to right | |
? : | Right to left | |
= | Right to left | |
Lowest | , | Left to right |
Function
Function cho phép chúng ta tổ chức lại cấu trúc của phần mềm, thường khi hoàn thành một chức năng của một nhiệm vụ thì ta cho nó vào một function. Vậy cú pháp của một function sẽ như thế nào ?
Function Syntax
Một function là một chuỗi các hoạt động trong phần mềm, nó có thể có một hoặc nhiều tham số(parameter). Có 2 thứ quan trọng khi lập trình C cần phải phân biệt đó là declaration và definition
Function Declaration (prototype) sẽ xác định tên, tham số đầu vào, đầu ra, cấu trúc dữ liệu của nó sẽ là kiểu và định dạng, trong khi đó Function definition sẽ xác định chính xác các hoạt động được thực thi khi nó được gọi. Function definition sẽ tạo ra các object code ddeerr CPU có thể đưa nó vào trong memory để thực hiện các hoạt động dự định sẵn, cấu trúc dữ liệu của nó sẽ là một vùng nhớ trong memory.
Phần gây khó hiểu sẽ là definition sẽ lặp lại declaration. Với C compiler sẽ thực hiện biên dịch 1 lần qua toàn bộ code, và bắt buộc chúng ta phải khai báo data/function trước khi chúng ta truy cập và gọi chúng.
Thôi vào ví dụ luôn cho mọi người dễ hình dung được những cái mình nói trên nó là thế nào trong code
Với cách tiếp cận là top-down thì trước tiên declare function, sử dụng function, và cuối cùng là define function như đoạn code dưới đây
unsigned long Calc_Area(unsigned long s);
int main(void) {
unsigned long side; // độ dài bức tường đơn vị met
unsigned long area; // diện tích căn phòng đơn vị met vuông
UART_Init(); // gọi subroutine để init uart
printf("This program calculates areas of square-shaped rooms\n");
side = 3;
area = Calc_Area(side);
printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
side = side+2;
area = Calc_Area(side);
printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}
// Tính diện tích căn phòng
// Input: độ dài căn phòng (kiểu unsigned long) đơn vị mét
// Output: diện tích căn phòng (kiểu unsigned long) đơn vị mét vuông
unsigned long Calc_Area(unsigned long s) {
unsigned long result;
result = s*s;
return(result);
}
Với cách tiếp cận bottom-down thì đầu tiên là define function, sau đó là dùng function. Ở cách này thì definiton sẽ làm chức năng là declare cấu trúc và define những gì nó làm
// Tính diện tích căn phòng hình vuông
// Input: Kích thước căn phòng (kiểu unsigned long) đơn vị mét
// Output: Diện tích căn phòng (kiểu unsigned long) đơn vị mét vuông
unsigned long Calc_Area(unsigned long s) {
unsigned long result;
result = s*s;
return(result);
}
int main(void) {
unsigned long side; // Kích thước căn phòng
unsigned long area; // Diện tích căn phòng
UART_Init(); // gọi subroutine để init uart
printf("This program calculates areas of square-shaped rooms\n");
side = 3;
area = Calc_Area(side);
printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
side = side+2;
area = Calc_Area(side);
printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}
Function Parameters
Với hàm sum trong chương trình dưới đây có 2 input và 1 output, điều thú vị ở đây là trong assembly và C thì sau khi các hoạt động trong chương trình con được thực hiện thì nó sẽ return lại vị trí ban đầu nơi chương trình con được gọi.
Các bạn có thể xem hình dưới đây để hình dung được cách mà chương trình chạy tương ứng với mã Assembly với code C và tác động lên các thanh ghi
Address | Machine Code | Label | Instruction | Comments |
---|---|---|---|---|
0x00000660 EB010200 | sum | ADD | R2,R1,R0 | ;z=x+y |
0x00000664 4610 | MOV | R0,R2 | ;return value | |
0x00000666 4770 | BX | LR | ||
0x00000668 F44F60FA | main | MOV | R0,#2000 | ;first parameter |
0x0000066C F44F61FA | MOV | R1,#2000 | ;second parameter | |
0x00000670 F7FFFFF6 | BL | sum | ;call function | |
0x00000674 4603 | MOV | R3,R0 | ;a=sum(2000,2000) | |
0x00000676 F04F0400 | MOV | R4,#0x00 | ;b=0 | |
0x0000067A 4620 | loop | MOV | R0,R4 | ;first parameter |
0x0000067C F04F0101 | MOV | R1,#0x01 | ;second parameter | |
0x00000680 F7FFFFEE | BL | sum | ;call function | |
0x00000684 4604 | MOV | R4,R0 | ;b=sum(b,1) | |
0x00000686 E7F8 | B | loop |
Address là vị trí của ROM sẽ lưu trữ các instruction
Machine code là các instruction thực tế dưới dạng mã hex
Label là kí hiệu vị trí trong chương trình, ta thường dùng label để gọi các function và nhảy tới các vị trí khác trong cùng một chương trình
Mỗi Instruction sẽ có một opcode và một hoặc nhiều toán hạng’
Comment được thêm vào để giải thich những gì chương trình đang thực hiện
Với hàm không có tham số thì sẽ thường sẽ dùng void. Ví dụ hàm unsigned long Calc_Area(unsigned long s)
không có tham số s thì sẽ được khai báo là unsigned long Calc_Area(void)
Vòng lặp và điều kiện
Chúng ta sẽ đi qua một ví dụ đơn giản đầu tiên về vòng lặp và câu lệnh điều kiện
unsigned long error;
// Tính diện tích căn phòng
// Input: kích thước căn phòng (unsigned long)
// Output: diện tích căn phòng (unsigned long)
// Notes: ...
unsigned long Calc_Area(unsigned long s) {
unsigned long result;
if(s <= 25){
result = s*s;
}else{
result = 0; // lỗi
error = error +1;
}
return(result);
}
Chương trình khai báo biến global là error và thiết lập giá trị khởi tạo của nó bằng 0. Mục đích là để đảm bảo function được sử dụng đúng. Mục đích của việc thiết kế phần mềm ban đầu là kiểm tra giá trị đầu vào của function và đảm bảo rằng giá trị đó có ý nghĩa. Kiểu unsigned long có thể hiển thị số lên tới 4 tỷ, điều này có nghĩa là hệ thống sẽ không hoạt động được nếu như chúng ta cố gắng tính toán kích thước của một căn phòng với chiều dài là 4 tỷ. Mà làm gì có phòng nào có chiều dài lớn như vậy, ở trong chương trình thì kích thước lớn nhất là 25m, nếu nhỏ hơn giá trị này thì sẽ return true và ngược lại sẽ return false. Để làm được việc này thì cần phải sử dụng lệnh rẽ nhánh là if, else.
int main(void) {
unsigned long side; // room wall meters
unsigned long area; // size squared meters
UART_Init(); // call subroutine to initialize the uart
printf("This program calculates areas of square-shaped rooms\n");
side = 1;
while(side < 50){
area = Calc_Area(side);
printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
side = side+1;
}
}
Ở chương trình tiếp theo giống như câu lệnh if ở trên thì câu lệnh while cũng là câu lệnh điều kiện (return về true hoặc false), có điểm khác là câu lệnh sẽ được thực hiện liên tục cho đến khi điều kiện kiểm tra trở thành false, trong ví dụ trên thì sẽ liên tục tăng giá trị của side lên 1 và in ra giá trị đó cho tới khi side = 50 thì không in nữa
int main(void) {
unsigned long side; // Chiều dài của phòng đơn vị mét
unsigned long area; // Diện tích phòng đơn vị mét vuông
UART_Init(); // gọi subroutine để init uart
printf("This program calculates areas of square-shaped rooms\n");
for(side = 1; side < 50; side = side+1){
area = Calc_Area(side);
printf("\nArea of the room with side of %ld m is %ld sqr m\n",side,area);
}
}
Với cấu trúc của for sẽ có dạng như sau for(part1;part2;part3){body;}
.
Trong chương trình trên part1 side = 1
sẽ được thực hiện 1 lần ngay từ đầu, sau đó sẽ là part2 được thực thi. Nếu điều kiện đúng với size < 50
thì chương trình tính toán bên trong sẽ được thực thi. Sau đó sẽ là thực thi part3, side = side + 1
. Part2 luôn luôn được thực hiện để kiểm tra và trả về kết quả là true hoặc false. Chương trình bên trong vòng for (body) và part3 sẽ được thực hiện cho tới khi kết quả là false
Có một lưu ý là khi sử dụng for với 2 trường hợp dưới đây là khác nhauif(n1>100) n2=100; n3=0;
if(n1>100) {n2=100; n3=0;}
Với n3 = 0
sẽ luôn được thực thi trong trường hợp 1, còn ở trường hợp 2 thì n3=0
chỉ khi n1>100
Thao tác nhập từ bàn phím
Ở trong hầu hết các chương trình C dành cho vi điều khiển thì có sử dụng khá nhiều thao tác xuất nhập sử dụng printf và scanf, với 2 lệnh này chúng ta có thể nhập thông tin vào và xuất thông tin ra máy tính để debug
while(side != 0){
printf("Give room side(zero to quit):");
scanf("%ld", &side);
area = Calc_Area(side);
if(area != 0){
printf("\nArea with side of %ld m is %ld sqr m\n",side,area);
} else {
printf("\n Size cannot exceed 25 meters\n");
}
}
Ví dụ trong chương trình trên ta dùng printf để in ra thông tin và scanf để nhập kích thước side từ bàn phím
Từ khóa trong C
Dưới đây là một số từ khóa thường dùng trong C, các bạn có thể xem bảng tham khảo, mình để nguyên tiếng anh để các bạn có thể dễ dàng tìm kiếm với các từ khóa có liên quan
Keyword | Meaning |
---|---|
asm | Specify a function is written in assembly code (specific to ARM Keil™ uVision® ) |
auto | Specifies a variable as automatic (created on the stack) |
break | Causes the program control structure to finish |
case | One possibility within a switch statement |
char | Defines a number with a precision of 8 bits |
const | Defines parameter as constant in ROM, and defines a local parameter as fixed value |
continue | Causes the program to go to beginning of loop |
default | Used in switch statement for all other cases |
do | Used for creating program loops |
double | Specifies variable as double precision floating point |
else | Alternative part of a conditional |
extern | Defined in another module |
float | Specifies variable as single precision floating point |
for | Used for creating program loops |
goto | Causes program to jump to specified location |
if | Conditional control structure |
int | Defines a number with a precision that will vary from compiler to compiler |
long | Defines a number with a precision of 32 bits |
register | Specifies how to implement a local |
return | Leave function |
short | Defines a number with a precision of 16 bits |
signed | Specifies variable as signed (default) |
sizeof | Built-in function returns the size of an object |
static | Stored permanently in memory, accessed locally |
struct | Used for creating data structures |
switch | Complex conditional control structure |
typedef | Used to create new data types |
unsigned | Always greater than or equal to zero |
void | Used in parameter list to mean no parameter |
volatile | Can change implicitly outside the direct action of the software. |
while | Used for creating program loops |
Thực hành
Ở trên mình có nói cả lý thuyết và các ví dụ cụ thể, tuy nhiên để mắt thấy tai nghe, nhìn được dòng code của mình chạy thế nào thì vẫn cần có project mẫu để tiết kiệm bớt thời gian, mình đã up sẵn 1 project bằng keil C tại link Embedded C, chương trình có init sẵn uart và in ra dòng lệnh Result tại góc dưới bên phải sau khi ấn debug(ctrl + F5) và Run (F5)
Với các code bên trên bạn có thể bỏ vào project này sau đó build lại và kiểm tra kết quả
Tạm kết
Vậy là mình đã đi được các khái niệm cơ bản về lập trình C nhúng trong vi điều khiển, về các khái niệm về flowchart, cấu trúc và cách tổ chức của một chương trình C và các ví dụ minh họa với biến, hàm, các cấu trúc rẽ nhánh và lặp,.., hi vọng các bạn có cái nhìn tổng quan về việc dùng C trong lập trình vi điều khiển