TRƯỜNG ĐẠI HỌC BÁCH KHOA TPHCM
Khoa Khoa học & Kỹ thuật Máy tính
BÁO CÁO ĐỒ ÁN KTMT
Đề tài
Thiết kế và xây dựng ứng dụng
với ARM Cotex-M3 trên nền CoOS-RTOS
GVHD: Lê Trọng Nhân
SVTH: Cao Văn Hùng
Hồ Đăng Bảo
Lê Đình Khánh
Mai Xuân Minh
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
TPHCM, tháng 1 năm 2010
Page 2
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Giới Thiệu Đề Tài
Với mục tiêu làm quen với cách sử dụng HĐH thời gian thực (Real Time Operating
System), Nhóm đã tự xây dựng cho mình hoàn chỉnh phần cứng cũng như ứng dụng chạy trên
nền CooCox CoOS – một RTOS miễn phí được xây dựng cho dòng ARM-Cotex-M3
Các module mà nhóm đã hoàn thành
• Board phần cứng chạy với chip LPC1766 với các chức năng ngoại vi như ADC, DAC,
USB, UART, JTAG, GPIO, TFT LCD
• Toàn bộ Driver điều khiển LCD TFT 240x320 điểm ảnh
• Phát triển library FAT để truy xuất file theo định dạng FAT từ thẻ nhớ
• Xây dựng chương trình chơi nhạc WAV player, với wave file được đọc từ thẻ nhớ
• Xây dựng chương trình BMP Viewer, với BMP file được đọc từ thẻ nhớ MicroSDCard
• Toàn bộ giao diện được hiển thị trên LCD Graphic 2.4 inch và tương tác với người dùng
bằng module Touching
Page 3
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Mục Lục
TRƯỜNG ĐẠI HỌC BÁCH KHOA TPHCM 1
Khoa Khoa học & Kỹ thuật Máy tính 1
1
1
BÁO CÁO ĐỒ ÁN KTMT 1
Đề tài 1
Thiết kế và xây dựng ứng dụng 1
với ARM Cotex-M3 trên nền CoOS-RTOS 1
1 Phần 1. Tìm hiểu CooCox CoOS RTOS 5
2 Phần 2. Các module hộ trợ cho việc phát triển ứng dụng trên CooCox CoOS 29
3 Phần 3. Xây dựng ứng dụng 56
4 Phần 4. Tổng Kết 66
Tài liệu tham khảo 68
Phục Lục 69
Page 4
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1 Phần 1. Tìm hiểu CooCox CoOS RTOS
1.1 Mục tiêu
Tìm hiểu kernel CooCox CoOS (open source): cách quản lí các task, quản lí thời gian,
quản lí bộ nhớ và điểu khiển việc đồng bộ, giao tiếp giữa các task.
1.2 Kernel CoOS
Đây là một nhân hệ điều hành thời gian thực, đa nhiệm, nguồn mở, tương thích với họ
LPC1700 dùng chip vi xử lí ARM Cotex-M3.
1.2.1 Vấn đề Task Management
CoOS dùng cơ chế scheduling preemptive và round robin. Một task là một công việc cụ
thể mà CPU phải làm, một application có thể gồm nhiều task. Đối với hệ điều hành đa nhiệm,
vào một thời điểm, có nhiều công việc cần được CPU thực hiện. Vấn đề quản lí các task như thứ
tự thực hiện, thời gian thực hiện từng task, …sao cho đạt được hiệu suất cao là vấn đề cốt lõi của
mọi hệ điều hành đa nhiệm.
1.2.1.1 Các trạng thái của task
Running: Khi một task đang được CPU thực hiện thì nó ở trạng thái running.
Ready : Khi một task đang chờ CPU thì nó ở trạng thái ready. Một task khi được tạo
ra sẽ ở vào trạng thái ready.
Waiting : Một task đang chờ một sự kiện nào đó xảy ra (chẳng hạn IO) sẽ ở vào trạng
thái waiting.
Dormant: Một task bị xóa khỏi bộ nhớ, không bao giờ được thực hiện nữa sẽ ở vào
trạng thái dormant (terminated).
1.2.1.2 Task Control Block – Nơi lưu giữ thông tin một task
Khi hệ điều hành đa nhiệm chuyển CPU từ task này qua cho task khác (vì thời gian
dành cho task cũ đã hết), cần có một khối dữ liệu để lưu các thông tin về task cũ (chẳng hạn câu
lệnh đang thực hiện) để nó có thể được thực hiện tiếp sau này. Khối dữ liệu này là gọi là một
Task Control Block (TCB) có kiểu là struct.
typedef struct TCB
{
OS_STK *stkPtr;
/*CoOS tạo cho mỗi task một stack để lưu trạng thái của CPU. Điều này đồng
nghĩa với việc lưu 16 thanh ghi đa dụng 32 bit của CPU (trong đó có thanh ghi
Progam Counter cho biết đang thực hiện lệnh nào của task). Do cần thêm 4 byte
để phát hiện stack bị over flow, tối thiểu mỗi stack phải có sức chứa 68 byte*/
U8 prio;
/*priority của task, là một số unsigned 8 bit*/
Page 5
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
U8 state;
/*Trạng thái của task */
OS_TID taskID;
/*ID của task, dùng để xác định một task*/
#if CFG_MUTEX_EN > 0
OS_MutexID mutexID;
#endif
/*Nếu như có sử dụng cơ chế Mutex thì định nghĩa mutexID là ID xác định
Mutex. Cách viết #if … #endif này giúp tiết kiệm bộ nhớ */
#if CFG_EVENT_EN > 0
OS_EventID eventID; /*!< Event ID.*/
#endif
#if CFG_ROBIN_EN >0
U16 timeSlice; /*!< Task time slice. */
#endif
#if CFG_EVENT_EN > 0
struct TCB *waitNext; /*!<Next TCB in the Event waiting list */
struct TCB *waitPrev; /*!<PrevTCB in the Event waiting list.*/
#endif
#if CFG_FLAG_EN > 0
void* pnode; /*!< Pointer to node of event flag.*/
#endif
U32 delayTick; /*!< The number of ticks which delay. */
struct TCB *TCBnext; /*!< The pointer to next TCB.*/
struct TCB *TCBprev; /*!< The pointer to prev TCB. */
}OSTCB,*P_OSTCB;
Các TCB được tổ chức thành một danh sách liên kết một chiều, các TCB đã cấp phát cho
các task nằm ở đầu danh sách, TCB cấp phát cuối cùng trỏ vào TCB trống đầu tiên. Nếu TCB
cấp phát cuối cùng trỏ vào NULL, thì có nghĩa là đã hết TCB trống và task sẽ không được tạo ra
nữa.
Lúc mới khởi động hệ thống, các TCB đều trống, và được tổ chức như sau:
1.2.1.3 Ready Task List – Hàng đợi của các task ở trạng thái ready
Hàng đợi này được tổ chức theo một danh sách liên kết 2 chiều, mỗi mắt xích là một
TCB của một task. Khi một task được tạo ra, TCB nó sẽ được xếp vào danh sách liên kết theo
Page 6
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
nguyên tắc FIFO có tính đến priority, tức là xếp vào cuối cụm các mắt xích TCB có cùng priority
với nó.
Task có TCB ở đầu hàng đợi sẽ là task được ưu tiên cao nhất, TCB đó gọi là RdyTCB.
1.2.1.4 Task Scheduling - Định thời cho các task
Đối với các task khác priority, CoOS sử dụng cơ chế định thời Preemptive, tức là một
task ready có thể giành quyền thực thi từ một task running nếu nó priority cao hơn. Đối với các
task cùng priority, CoOS dùng cơ chế định thời Round robin, tức là luân chuyển CPU giữa các
task, mỗi task được cấp CPU một khoảng thời gian nhất định gọi là time slice. Các time slice cho
các task có thể khác nhau, như trong hình sau minh họa 3 task A, B, C có time slice lần lượt là 1,
2, 3.
CoOS làm công việc định thời khi một trong các điều kiện sau xảy ra:
a. Một task có priority cao hơn task running được chuyển vào trạng thái ready. Ở trường hợp
này, hệ điều hành sẽ thực hiện cơ chế preemptive (tranh giành).
b. Task running chuyển sang trạng thái waiting hay dormant. Ở trường hợp này, hệ điều hành chỉ
đơn giản lấy task ready đầu hàng đợi ra, đưa nó vào trạng thái running.
c. Task running đã dùng hết time slice của nó và có task khác trong ready list có cùng priority
với task running. Ở trường hợp này, hệ điều hành sẽ thực hiện cơ chế round robin.
Cụ thể là hàm định thời Schedule sẽ được gọi khi có một tick interrupt (hết time slice)
hay có một sự chuyển trạng thái xảy ra.
/*a. Is higher PRI task coming in? */
if(RdyPrio < RunPrio ) //priority number bé hơn nghĩa là priority cao hơn
{
TCBNext = pRdyTCB; //chuyển ngữ cảnh, đưa RdyTCB vào trạng thái running
pCurTcb->state = STATE_READY; //chuyển trạng thái
pRdyTcb->state = STATE_RUNNING;
InsertToTCBRdyList (pCurTCB);
RemoveFromTCBRdyList (pRdyTCB);
}
Page 7
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
/*b. Does Running task status change */
else if(pCurTcb->state != TASK_RUNNING)
{
TCBNext = pRdyTCB; //chuyển ngữ cảnh, đưa RdyTCB vào trạng thái running
pRdyTcb->state = STATE_RUNNING;
RemoveFromTCBRdyList (pRdyTCB);
}
/* c. Is it the time for robinning */
else if((RunPrio == RdyPrio) && (OSCheckTime == OSTickCnt))
{
TCBNext = pRdyTCB; //chuyển ngữ cảnh, đưa RdyTCB vào trạng thái running
pCurTcb->state = STATE_READY; //chuyển trạng thái
pRdyTcb->state = STATE_RUNNING;
InsertToTCBRdyList (pCurTCB);
RemoveFromTCBRdyList (pRdyTCB);
}
1.2.1.5 Cách thức xử lí Critical Section của CoOS
Critical Section là đoạn code có câu lệnh làm thay đổi các tài nguyên dùng chung giữa
các task và do đó không được chuyển ngữ cảnh khi đang thực thi trong Critical Section. Các
cách xử lí Critical Section là: cấm các interrupt hoặc cấm các system call có thể gây chuyển ngữ
cảnh trong Critical Section. CoOS chọn cách disable bộ định thời thay vì disable các interrupt để
đảm bảo tính đáp ứng của hệ thống, vốn rất quan trọng trong các hệ thống thời gian thực.
void Task1(void* pdata)
{
CoSchedLock ( ); // Enter Critical Section
// Critical Code
CoSchedUnlock ( ); // Exit Critical Section
}
1.2.1.6 Các interrupt trong CoOS
CoOS phân loại 2 kiểu interrupt, dựa vào tiêu chí nó có gọi các hàm API của CoOS hay
không. Các interrupt không gọi API không ảnh hưởng gì đến hệ điều hành, do đó CoOS không
đặt đòi hỏi gì các interrupt này. Đối với các interrupt có gọi API, CoOS yêu cầu bạn phải gọi các
API thích hợp bên trong interrupt. Ví dụ:
void WWDG_IRQHandler(void)
{
CoEnterISR ( ); // Enter the interrupt
isr_SetFlag(flagID); // API function
; // Interrupt service routine
CoExitISR ( ); // Exit the interrupt
}
Mọi API có thể được gọi trong ISR bắt đầu bởi isr_, như isr_PostSem ( ), isr_PostMail
( ), isr_PostQueueMail ( ) và isr_SetFlag ( ). Việc gọi một API khác bên trong ISR sẽ dẫn đến
tình trạng system chaos.
Page 8
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Khi gọi một API tương ứng trong ISR, hệ thống cần xác định xem bộ định thời đang bị
khóa hay không. Nếu như không khóa, hệ thống có thể gọi nó một cách bình thường. Ngược lại,
hệ thống sẽ gửi một service request phù hợp đến list các service request và đợi đến khi bộ định
thời được mở khóa mới đáp ứng nó.
1.2.2 Vấn đề quản lí thời gian
1.2.2.1 System Tick
CoOS sử dụng interrupt systick để hiện thực system tick (tick: một khoảng thời gian rất
ngắn). Bạn cần thiết lập tần số của system tick trong file config.h. CFG_CPU_FREQ là tần số
của CPU clock. CFG_SYSTICK_FREQ là tần số của system tick bạn muốn. CoOS hỗ trợ tần số
từ 1 đến 1000Hz, mặc định là 100Hz.
SYSTICK là gì?
SYSTICK là một Timer được tích hợp vào NVIC và được sử dụng để phát sinh một
SYSTICK exception (exception kiểu #15). Exception là một điều kiện đặc biệt có thể làm thay
đổi dòng chảy bình thường của chương trình. NVIC là một bộ phận được tích hợp với vi xử lí
Cotex M3, có khả năng hỗ trợ đến 240 ngắt ngoài với số mức ưu tiên lên tới 256 và có thể quy
định lại mức ưu tiên.
SYSTICK là một hardware timer, nên không phụ thuộc vào các task đang chạy và do đó
hệ thống không bị task đó chiếm quyền. Để làm được điều này, timer cần có khả năng tạo ra
interrupt và được bảo vệ khỏi những user task để không có một user application nào có thể thay
đổi timer.
CoOS tăng giờ hệ thống lên 1 trong mỗi system tick ISR. Bạn có thể xem giờ hệ thống
bằng cách gọi hàm CoGetOSTime(). CoOS cũng kiểm tra xem delayed list và timer list có rỗng
hay không trong system tick ISR mà không tăng system time lên 1. Nếu list không trống, giảm
delayed time của của phần tử đầu trong list đi 1 đơn vị, và kiểm tra xem có phải là waiting time
của phần tử đầu trong list đã hết hay không. Nếu hết rồi, gọi hàm xử lí tương ứng, nếu không,
chuyển qua bước tiếp theo.
CoOS gọi hàm định thời để xác định xem hệ thống có cần phải chạy bộ định thời hay
không khi thoát từ system tick ISR.
Đoạn mã sau xử lí một system tick interrupt.
void SysTick_Handler(void)
{
OSSchedLock++; /* Lock the scheduler. */
OSTickCnt++; /* Increment system time. */
if(DlyList != NULL) /* Have task in delayed list? */
{
DlyList->delayTick ; /* Decrease delay time of the list head. */
if(DlyList->delayTick == 0) /* Delay time == 0? */
{
isr_TimeDispose(); /* Call hander for delay time list */
}
}
#if CFG_TMR_EN > 0
if(TmrList != NULL) /* Have timer be in working? */
{
TmrList->tmrCnt ; /* Decrease timer time of the list head. */
if(TmrList->tmrCnt == 0) /* Timer time == 0? */
{
Page 9
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
isr_TmrDispose(); /* Call hander for timer list. */
}
}
#endif
OSSchedLock ; /* Unlock scheduler. */
if(OSSchedLock==0)
{
Schedule(); /* Call task scheduler */
}
}
1.2.2.2 Delay Management
CoOS quản lí mọi delay và timeout của task thông qua delayed list. Khi bạn gọi các hàm
CoTickDelay(), CoTimeDelay() hay CoResetTaskDelayTick() hay các hàm API khác có liên
quan đến delay, CoOS sẽ sắp xếp các thời gian delay từ ngắn đến dài và sau đó chèm vào
delayed list. Biến delayTick trong TCB lưu lại sự khác biệt giá trị của thời gian delay giữa task
hiện thời và task trước. Phần tử đầu của list là giá trị thời gian delay hay thời gian timeout, trong
khi những phần tử tiếp theo là sự sai biệt giá trị với phần tử trước nó. Để dễ hiểu, hình dung task
A, B, C có thời gian delay lần lượt là 10, 18, 5, khi đó chúng được sắp vào delayed list như sau:
Hệ thống sẽ giảm giá trị đầu list đi 1 mỗi system tick interrupt, và chuyển nó vào ready
list nếu nó đã đạt giá trị 0. Khi chuyển các task có giá trị delayTick (xem hình) đạt 0, hệ thống
phải xác định xem cần phải xử lí theo delay hay theo timeout. Đối với delay, CoOS, đưa nó vào
ready list sau khi nó đã đưa ra khỏi delayed list. Đối với timeout, CoOS sẽ kiểm tra xem sự kiện
nào dẫn tới overtime trước rồi mới chuyển task từ waiting list sang ready list.
CoOS không bảo đảm độ chính xác dưới những điều kiện sau:
i. Có một task có độ ưu tiên cao hơn task Achiếm CPU khi task A đang bị delayed, thời
gian delay sẽ được xác định bằng thời gian chạy của task có độ ưu tiên cao hơn.
ii. Có một task có độ ưu tiên bằng task A chiếm CPU khi task A đang bị delayed, thời
gian delay được tính bằng số task có độ ưu tiên bằng task A ở trong ready list và
chiều dài của time slice cũng như thời điểm kết thúc delay time.
1.2.2.3 Software Timer
Software Timer (ST) là một timer chính xác cao, lấy nguồn là system tick. CoOS hỗ trợ
32 ST, mỗi ST có thể ở mode periodic (tuần hoàn) hay mode one-shot (một lần).
Khi bạn tạo ra một ST bằng hàm CoCreateTmr(), ST được tạo ra ở trạng thái stopping,
bạn phải gọi hàm CoStartTmr() để bật nó chạy. Khi một ST được tạo ra, CoOS gán cho nó một
Page 10
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Timer Control Block để lưu lại trạng thái của ST. CoOS quản lí các ST bằng một danh sách liên
kết 2 chiều, với cách tổ chức tương tự delayed list. Ví dụ 3 timer A, B, C được thiết lập giá trị
10, 18, 5 sẽ được tổ chức như sau:
Khi một ST bắt đầu chạy, nó sẽ hoàn toàn không phụ thuộc vào các module khác ngoại
trừ system tick interrupt. CoOS giảm giả trị của ST đầu list đi 1 mỗi system tick interrupt.
Khi giá trị trong ST đạt 0, ST kiểu periodic sẽ thiết lập lại timer dựa vào giá trị tmrReload
mà bạn quy định và đưa trở lại vào timer list. Ngược lại, ST kiểu one-shot sẽ bị đưa ra khỏi list
và trạng thái của nó được đưa vào stop.
1.2.3 Memory Management – Quản lí bộ nhớ
1.2.3.1 Static Memory Allocation – Cấp phát bộ nhớ tĩnh
Việc cấp phát bộ nhớ tĩnh được áp dụng khi bạn không biết chắc bạn cần bao nhiêu dung
lượng bộ nhớ khi biên dịch, và bộ nhớ tĩnh cũng không thể được giải phóng hay phân phối lại
trong lúc hệ thống đang chạy. Ưu điểm của bộ nhớ tĩnh so với bộ nhớ động là nó không chiếm
tài nguyên CPU, và luôn luôn được cấp phát thành công (vì nếu không đã bị lỗi biên dịch). Vì
thế, nó nhanh và an toàn hơn.
CoOS cấp bộ nhớ tĩnh cho các module sau: TCB, ECB (event CB), FCB (flag CB), flag
node (FLAG_NODE), … Sau đây là ví dụ về cấp bộ nhớ tĩnh cho TCB.
1.2.3.2 Dynamic Memory Management – Quản lí bộ nhớ động
Việc cấp bộ nhớ động là cần thiết khi dung lượng bộ nhớ cần thiết không thể được xác
định trong khi biên dịch mà phụ thuộc vào môi trường runtime khi hệ thống đang chạy. Việc cấp
bộ nhớ tĩnh là dựa trên kế hoạch; trong khi việc cấp bộ nhớ động là dựa trên yêu cầu tức thời.
Page 11
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Bộ nhớ động linh hoạt hơn và sử dụng hiệu quả bộ nhớ. Tuy nhiên nó chiếm CPU và có
thể làm phân mảnh bộ nhớ, và không phải lúc nào cũng cấp phát được.
Hai hàm cơ bản liên quan đến bộ nhớ động là malloc() và free(). Khi bạn gọi malloc(), hệ
thống sẽ thực hiện những bước sau:
i. Tìm khối bộ nhớ thỏa mãn yêu cầu. Thuật toán thường dùng nhất là first matching,
tức là gán ngay khối nhớ đầu tiên thỏa mãn yêu cầu. Thuật toán này tối thiểu thời
gian tìm kiếm, song lại làm phân mảnh bộ nhớ.
ii. Chia khối nhớ ra làm hai, phần đúng bằng dung lượng được yêu cầu và phần còn lại.
iii. Chuyển phần thứ nhất cho người dùng, trả khối thứ hai về cho free list.
Hệ thống đưa khối nhớ bạn trả lại vào free list và làm thao tác kết khối nếu nó nằm kề với
một khối nhớ free khác. Sau đây là sơ đồ
Từ sơ đồ trên, task thấy rằng free list sẽ được chia thành nhiều mảnh nhỏ sau nhiều lần
cấp phát và giải phóng bộ nhớ, dẫn đến tình trạng phân mảnh bộ nhớ. Hệ thống lúc này phải dò
tìm đến cuối free list mà vẫn không tìm được khối bộ nhớ phù hợp để gán cho ứng dụng.
Để khắc phục tình trạng trên, CoOS đưa ra hai cơ chế phân vùng bộ nhớ: cơ chế chiều dài
cố định và cơ chế chiều dài thay đổi.
1.2.3.2.1 Phân vùng chiều dài cố định
Hệ thống chia khối nhớ lớn ra làm nhiều block có chiều dài cố định và bằng nhau, sau đó
liên kết chúng lại thành một list. Bạn có thể cấp hay giải phóng phân vùng nhớ có chiều dài cố
định, một phân vùng gồm nhiều block nhớ có độ dài bằng nhau. Cách này đảm bảo thời gian cấp
phát hay giải phóng cố định và tránh được phân mảnh. Sau đây là sơ đồ một phân vùng nhớ có
chiều dài cố định.
Page 12
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
CoOS có thể quản lí 32 phân vùng như thế, các vùng có thể khác nhau về kích thước (tùy
yêu cầu của người dùng). Bạn tạo phân vùng bằng hàm hệ thống CoCreateMemPartition(). Sau
khi đã tạo thành công phân vùng, bạn cấp phát hay giải phóng bộ nhớ bằng cách gọi hàm
CoGetMemoryBuffer() và CoFreeMemoryBuffer(). Để lấy số thứ tự của khối nhớ trống, bạn gọi
hàm CoGetFreeBlockNum(). Trong ví dụ sau, một phân vùng gồm 20 khối nhớ 128 byte được
tạo ra, sau đó một khối nhớ trống được cấp cho ứng dụng, và cuối cùng khối nhớ đó được giải
phóng.
1.2.3.2.2 Phân vùng chiều dài thay đổi
Cách tổ chức trên đòi hỏi phải thao tác trên cả hai list. Cũng có thể tổ chức phân vùng
nhớ thành một list để giảm bớt các thao tác mà CPU phải thực hiện. CoOS gọi cách tổ chức này
là một KHEAP (kernel heap).
Page 13
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Cách tổ chức như trên có điểm lợi là khi giải phóng vùng nhớ, hệ điều hành không phải
tìm kiếm chỗ chèn từ đầu list. Nó chỉ cần tìm block trống ở đằng trước và chèn vào list, tăng tính
hiệu quả của bộ nhớ. Trong hình vẽ trên, block 1 và block 2 đã có sẵn thông tin về block trống
đằng trước nó, do đó rất dễ để đưa nó vào free list. Đối với block 3 và 4, cần thêm một thao tác
dò ngược để tìm block trống đằng trước. Ví dụ:
Bộ nhớ được quản lí theo từng khối 4 byte, nếu như số byte yêu cầu cấp phát không chia
hết cho 4 thì làm tròn lên rồi mới cấp phát. Do đó 2 bit cuối của địa chỉ bộ nhớ không có ý nghĩa.
CoOS tận dụng bit cuối cùng làm bit thông tin về block nhớ: 0 là free block và 1 là block đã
được cấp phát.
Sau đây là các thông tin liên quan đến KHEAP:
Page 14
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Sau khi đã enable KHEAP, bạn có thể sử dụng các hàm CoKCreate() (tạo heap),
CoKMalloc(U32 size) (cấp phát size byte), CoKFree() (giải phóng heap).
1.2.3.3 Kiểm tra Stack Overfloaw
Như đã nói trong phần task control block, mỗi task sở hữu 1 stack. Nếu stack bị tràn,
thông tin được lưu ra ngoài stack, và có thể trùng lắp lên thông tin của task khác. Do đó để hệ
thống chạy đúng, cần kiểm tra stack của task trong quá trình scheduling. CoOS lưu địa chỉ của
stack bottom trong TCB (biến stack) và ghi vào stack bottom một giá trị đặc biệt
(MAGIC_WORD) để kiểm tra stack overflow.
Khi xảy ra overflow, hàm CoStkOverFlowHook sẽ được gọi, bạn cần hiện thực hàm này:
Page 15
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1.2.4 Intertask Synchronization – Vấn đề đồng bộ các task
Intertask Synchronization có nghĩa là một task chỉ có thể thực thi tiếp sau khi nó nhận
được tín hiệu đồng bộ từ một task khác hay là từ một ISR. Cách hiện thực Intertask
Synchronization có thể là semaphore, mutex hay flag.
1.2.4.1 Semaphore
Semaphore là một phương pháp phổ biến trong việc đồng bộ các task. Semaphore là một
biến số s, đại diện cho một loại tài nguyên nào đó. Giá trị lớn nhất của s là số tài nguyên cùng
loại có thể cấp cho các task. Có hai phương thức cơ bản tác động lên một semaphore s là P ( viết
tắt của từ probeer te verlagen trong tiếng Hà Lan, quê hương của Dijkstra, nghĩa là kiểm tra để
giảm) và V( verhogen, nghĩa là tăng).
Các API liên quan:
CoCreateSem (): tạo semaphore
CoPendSem (): thực hiện thao tác P nói trên, nó sẽ phải đợi cho đến khi s > 0.
CoAcceptSem(): tương tự như CoPendSem nhưng thay vì phải đợi, nó sẽ trả về giá trị báo error
ngay lập tức.
CoPostSem (): thực hiện thao tác V.
Isr_CoPostSem (): như CoPostSem nhưng áp dụng cho hàm interrupt.
Đoạn code sau minh họa việc tạo semaphore:
Đoạn code sau minh họa việc sử dụng semaphore:
Page 16
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1.2.4.2 Mutex
Mutex là một semaphore chỉ nhận hai giá trị là 0 và 1. Vì vậy, Mutex được dùng để cho
phép chỉ một task có thể nhận tài nguyên vào một thời điểm, chẳng hạn một biến chung chia sẻ
giữa các task. Đoạn code chứa các câu lệnh truy cập vào tài nguyên đó gọi là Critical Section.
Điểm đặc thù của khái niệm Mutex so với semaphore hai trạng thái là Mutex thường
được thêm vào tính năng chống priority inversion.
Priority intersion là một hiện tượng xảy ra trong quá trình định thời. Đó là khi một high-
priority task phải chờ tài nguyên từ một low-priority task, trong khi low-priority task lại phải chờ
một medium-priority task. Low-priority task bị medium-priority task chiếm quyền, trong khi
high-priority task không thể chiếm quyền low-priority task vì low-priority task đang ở trong
Critical Section. Điều này dẫn tới việc high-priority task phải chờ rất lâu mới được thực hiện.
Hiện tượng này đặc biệt nghiêm trọng trong hệ thống realtime bởi vì high-priority task thường là
những task đòi hỏi tính đáp ứng cao, thời gian đáp ứng nhỏ hơn nhiều so với medium hay low-
priority task.
Để giải quyết priority inversion, thường người ta nâng priority của low-priority task lên
để nó không bị medium-priority task chiếm quyền. Có hai phương pháp cơ bản sau:
i. Ceilling priority: Đặt cho tài nguyên một priority (ceilling priority). Priority này là priority cao
nhất trong số priority của các task có thể chiếm tài nguyên. Khi một task chiếm tài nguyên,
priority của nó được tạm thời gán bằng ceilling priority. Sau khi trả tài nguyên, priority của nó
trở về giá trị ban đầu.
ii.Priority inheritance: Khi một task chiếm tài nguyên, nó thừa kế priority (nhận priority) của
task có priority cao nhất trong số các task yêu cầu tài nguyên. Sau khi trả tài nguyên, priority của
Page 17
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
nó trở về giá trị ban đầu. Phương pháp này linh động hơn ở chỗ nếu không có high-priority task
nào đang yêu cầu tài nguyên thì low-priority task vẫn giữ priority cũ của nó.
Hình vẽ sau minh họa phương pháp priority inheritance với 3 task A, B, C có priority
tương ứng là high, medium và low.
Các API liên quan:
CoCreateMutex (): Tạo một mutex.
CoEnterMutex (), CoLeaveMutex (): vào, ra mutex section.
Đoạn code sau minh họa cách sử dụng các API trên:
Page 18
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1.2.4.3 Flag
Flag được sử dụng khi một task phải đồng bộ với nhiều sự kiện. Mỗi sự kiện tương ứng
với một flag. Task đó có thể chờ một trong các sự kiện (OR các flag) hay chờ tất cả các sự kiện
(AND các flag). Khi một/các sự kiện chưa xảy ra (flag ở trạng thái not ready) thì task vẫn chưa
được định thời.
CoOS hiện thực hai loại flag: reset tự động và reset bằng tay. Để phân biệt hai loại flag
này, xét 3 task A, B, C cùng chờ một flag I. Nếu I reset tự động, khi I ready, task A được đưa
vào ready list, sau đó hệ thống tự động đưa I về trạng thái not-ready và B, C vẫn ở trạng thái
waiting.
Nếu I reset bằng tay, khi I ready, các task A, B, C sẽ được đưa vào ready list. Bạn gọi
CoClearFlag để đưa I về not-ready.
Các API liên quan:
CoCreateFlag (): tạo flag.
CoWaitForSingleFlag (), CoWaitForMultipleFlag (): đợi 1 hay nhiều flag. Hai đoạn code sau
minh họa các API nói trên:
Page 19
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Page 20
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1.2.5 Intertask Communication – Vấn đề giao tiếp giữa các task
1.2.5.1 Mailbox
Một mail message là một pointer trỏ tới thông tin cần giao tiếp, nhờ các service của hệ
điều hành, được đặt vào một mailbox. Các task khác có thể nhận mail này cũng nhờ core service.
Mailbox do một task tạo ra, và do đó là sở hữu của một task. Mailbox chỉ lưu được một mail tại
một thời điểm. Sơ đồ một mailbox:
Mailbox trong CoOS gồm hai phần: phần thông tin cần trao đổi được biểu thị bởi con trỏ
void và phần waiting list gồm các task đang chờ mailbox. List này có thể là FIFO hay
preemptive priority, được user quy định khi tạo mailbox.
Các API liên quan: CoCreateMBox: tạo Mailbox, CoPostMail, isr_PostMail: dùng để
gửi mail, CoPendMail, CoAcceptMai: dùng để nhận mail. Đoạn code sau minh họa các API trên:
Page 21
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1.2.5.2 Message queue
Một Message queue là một mảng các mailbox, khác với mailbox chỉ lưu 1 mail, nó có thể
lưu được nhiều mail, được xác định bởi user khi tạo queue. Các tính chất và API của Message
queue hoàn toàn tương tự Mailbox. Đoạn code sau minh họa cách sử dụng Message queue:
Page 22
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
1.3 Ứng dụng các API của CoOS
1.3.1 Các API liên quan đến OS.
1.3.1.1 CoInitOS()
Prototype:
void CoInitOS (void);
Chức năng:
Khởi tạo các thành phần của OS như TCB List, Event List, Heap, Idle Task.
Tham số:
Không.
Giá trị trả về:
Không.
Ví dụ:
int main()
{
Page 23
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
SystemInit();
…
…
CoInitOS();
CoCreateTask( init_task,0,INIT_TASK_PRIO,
&init_task_stk[STK_SIZE-1],STK_SIZE);
CoStartOS();
while (1);
}
1.3.1.2 CoStartOS()
Prototype:
void CoStartOS (void);
Chức năng:
Bật cho hệ thống chạy bằng cách đưa task đầu trong Ready List ra làm Running Task.
Tham số:
Không.
Giá trị trả về:
Không.
Ví dụ:
int main()
{
SystemInit();
…
…
CoInitOS();
CoCreateTask( init_task,0,INIT_TASK_PRIO,
&init_task_stk[STK_SIZE-1],STK_SIZE);
CoStartOS();
while (1);
}
1.3.2 Các API liên quan đến Task.
1.3.2.1 CoCreateTask()
Prototype:
OS_TID CoCreateTask ( FUNCPtr task,
void* argv,
U8 prio,
OS_STK* stk,
U16 stkSz);
Chức năng:
Tạo Task.
Tham số:
task: con trỏ hàm trỏ đến hàm tương ứng với task cần tạo.
argv: danh sách các tham số của hàm.
prio: mức ưu tiên của task, prio càng nhỏ càng được ưu tiên.
stk: địa chỉ bắt đầu của stack của task, mỗi phần tử của stack là 1 word (4 bytes).
stkSz: kích thước của stack của task, tính theo word (4 bytes).
Giá trị trả về:
Page 24
RTOS – ARM Cortex M-3 GVHD : Lê Trọng Nhân
Task ID nếu thành công, -1 nếu không tạo được task.
Ví dụ:
void taskB (void *pdata)
{
LPC_GPIO0->FIOPIN|=1<<4;
while (1) {
delay(24000000);
LPC_GPIO0->FIOPIN^=1<<4;
}
}
int main()
{
…
CoInitOS();
CoCreateTask(taskB,0,B_TASK_PRIO,&b_task_stk[128-1],128);
CoStartOS();
while (1);
}
1.3.2.2 CoExitTask()
Prototype:
void CoExitTask (void);
Chức năng:
Thoát một task, có nghĩa là tự task đó sẽ đưa nó vào trạng thái dormant (termitated) .
Tham số:
Không.
Giá trị trả về:
Không.
Ví dụ:
void init_task (void *pdata)
{
pdata = pdata;
CoCreateTask(taskBMP,0,BMP_TASK_PRIO,&bmp_task_stk[STK_SIZE-1],STK_SIZE);
CoExitTask();
/*!< Delete "init_task" task. */
}
1.3.2.3 CoDelTask()
Prototype:
StatusType CoDelTask (OS_TID Task ID);
Chức năng:
Xóa một task, có nghĩa là task gọi hàm này sẽ đưa một task khác vào trạng thái dormant
(termitated).
Tham số:
Task ID: ID của task cần xóa.
Giá trị trả về:
E_INVALID_ID, ID không hợp lệ.
E_PROTECTED_TASK, Được bảo vệ bởi hệ thống, không xóa được.
Page 25