20. 20
關於volatile關鍵字 (III)
面試官會測試你是否真正懂得volatile的重要性:
1) 一個變數可以既是const又是volatile嗎?為什麼。
答:是的。例如唯讀的狀態暫存器,它是volatile因為它可能被意想不到
地改變,它是const因為程式不應該試圖去修改它。
2) 一個指標可以是volatile嗎?為什麼。
答:雖然不很常見,但是的。例如ISR修改指向一個buffer的指標時。
3) 下面的函數有什麼錯誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
int square(volatile int *ptr)
{
int a, b;
a = *ptr;
b = *ptr;
return a * b;
}
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a*a;
}
求(*ptr)2
41. 41
較好的訊息處理程式
// 重要原則: 宣告模組中處理各個訊息的靜態函數(類似物件的method)
// 1. 這些函數只有這個模組會呼叫 2. 每個訊息有其專用的處理函數
static int xxx_msg_1_processor(struct message *new_msg);
static int xxx_msg_2_processor(struct message *new_msg);
static int xxx_msg_3_processor(struct message *new_msg);
...
static int xxx_msg_default_processor(struct message *new_msg);
/*************************************************************
foo模組訊息處理主程式: foo_module_basic_message_processor
*************************************************************/
int foo_module_basic_message_processor(struct message *new_msg)
{
int msg_type = new_msg->message_type;
switch(msg_type)
{
case MSG_TYPE_0001:
return xxx_msg_1_processor(new_msg);
case MSG_TYPE_0002:
return xxx_msg_2_processor(new_msg);
case MSG_TYPE_0003:
return xxx_msg_3_processor(new_msg);
...
...
default: // foo模組預設的訊息處理函數
xxx_msg_1_processor(new_msg);
}
return MSG_PASS; // 繼續讓其他模組處理這個訊息
}
/****************************************************
foo模組訊息1處理程式: foo_msg_1_processor
*****************************************************/
static int foo_msg_1_processor(struct message *new_msg)
{
... // 處理第1類訊息的程式碼
// 已經處理完畢, 系統無需再將此訊息傳送給其他模組
return MSG_PROCESSED;
}
42. 42
API設計文件
API設計文件:
告訴使用者如何使用函式、說明函式功能、調用順序、限制或
注意事項等。
API文件應包含:
該模組的功能說明與使用範圍
資料結構與常數說明
各函式的功能、參數與
回傳值意義說明
使用範例
注意事項與限制
相關函式
• Prototype: void * skip_upper_frame(void)
• Declaration: Skip upper frame as figure below
• Parameters: none
• Return Value: Return NULL if there’s no upper frame;
else return the SP of current frame, since the SP is
not changed, the returned value is useless for
applications.
44. 44
市面上常見的Embedded OS (EOS)
Open Source的EOS 需付費的EOS
μC/OS μC/OS II
Embedded Linux VxWorks
ECos Nucleus
μCLinux pSOS
RTEMS QNX
FreeRTOS Windows XP Embedded
μITRON Windows CE (Win. Mobile)
Andorid Symbian OS
… OS-9 / iOS
45. 45
RTOS簡介 (I)
RTOS是一種嵌入式系統的即時OS,負責管理以下資源
分享與配置:
CPU Time
Round Robin vs. Priority, Preemptive vs. Non-preemptive
Memory
I/O
事件驅動型的任務(event-driven tasks)
任務間通訊 (Inter-Task Communication, IPC)
任務調度與狀態(Tasks Scheduling, States)
隱藏硬體細節 (Hiding Hardware Details)
74. 74
Semaphore與Mutex
互斥鎖 (Mutex):
Binary Semaphore
僅兩種狀態:available或busy
訊號鎖 (Semaphore):
Binary (Mutex)或 > 2種狀態 (counting):
counting semaphore - 遞增或遞減
範例:共享“n”個資源或裝置
semaphore = 可用資源的數量
bit getSemaphore( uint8 *semaphore )
{
bit result = 0; // Assume failure.
bit oldEA = EA; // Save current state.
EA = 0; // Disable interrupts.
if ( *semaphore > 0 ) { // Any resources
// avail?
*semaphore = *semaphore - 1; // Grab one.
result = 1; // Good to go.
}
EA = oldEA; // Restore interrupts.
// NOTE: Could give up processor
// if we didn’t get the semaphore.
return result;
}
75. 75
共享記憶體 (Shared Memory)
前述的IPC機制總是可以用共享記憶體完成
缺點:
非執行緒安全(not thread-safe),除非有硬體鎖
無法控制以避免誤解
優點:
速度快
沒有RTOS overhead
Ring Buffer can be thread safe with one producer and one consumer.
76. 76
任務排程 (Task Scheduling)
Scheduler安排CPU Time
決定下一個執行的Task
內文切換 (context switch)
uC/OS uses:
Priority Based, number 0-63
High Priority (0) Supercedes Low Priority (63)
High Priority Tasks can Interrupt Low Ones
Overhead: Time Wasted, Not Spent on Task
88. 88
Super Loop架構 (I)
void main(void)
{
// Prepare run function X
X_Init();
while(1) // 'for ever' (Super Loop)
{
X(); // Run function X
}
}
89. 89
Super Loop架構 (II)
void main(void)
{
Init_System();
while(1) // 'for ever' (Super Loop)
{
X(); // Call the function (10 ms duration)
Delay_50ms(); // Delay for 50 ms
}
}
91. 91
Task、Function與Scheduling
在嵌入式系統設計中,你常會聽到「task design」 、
「task execution times」與「multi-tasking」等字眼。通
常,「task」指的是「a function that is executed on a
periodic basis」。
控制單一task的執行時間又稱為「scheduling the task
(工作排程)」,是由系統中的排程器所控制。排程器會
用ISR來實現(或被ISR呼叫),而ISR則是在timer溢位中斷
發生時被調用。
92. 92
用3個Timer來使3個Task定時工作
/*------------------------------------------------------------------*-
Multi-timer ISR program framework
-*------------------------------------------------------------------*/
#include <AT89C52.h>
#define INTERRUPT_Timer_0_Overflow 1
#define INTERRUPT_Timer_1_Overflow 3
#define INTERRUPT_Timer_2_Overflow 5
void Timer_0_Init(void);
void Timer_1_Init(void);
void Timer_2_Init(void);
void main(void)
{
Timer_0_Init(); // Set up Timer 0
Timer_1_Init(); // Set up Timer 1
Timer_2_Init(); // Set up Timer 2
EA = 1; // Globally enable interrupts
while(1);
}
void Timer_0_Init(void) { 略 }
void Timer_1_Init(void) { 略 }
void Timer_2_Init(void) { 略 }
void X(void) interrupt INTERRUPT_Timer_0_Overflow
{
// This ISR is called every 1 ms
// Place required code here...
}
void Y(void) interrupt INTERRUPT_Timer_1_Overflow
{
// This ISR is called every 2 ms
// Place required code here...
}
void Z(void) interrupt INTERRUPT_Timer_2_Overflow
{
// This ISR is called every 5 ms
// Place required code here...
}
93. 93
排程器 (Scheduler)
排程器是的OS基礎,也有人把排程器視作一種最最最
簡單的OS。
從很底層的眼光來看,排程器就是一個定時器的ISR,
而這個ISR是由許多tasks共享。
使用排程器讓3個task工作(只使用1個timer)
void main(void)
{
Scheduler_Init(); // Set up the scheduler
// Add the tasks (1ms tick interval)
Scheduler_Add_Task(Function_A, 0, 2); // Function_A will run every 2 ms
Scheduler_Add_Task(Function_B, 1, 10); // Function_B will run every 10 ms
Scheduler_Add_Task(Function_C, 3, 15); // Function_C will run every 15 ms
Scheduler_Start();
while(1) {
Scheduler_Dispatch_Tasks();
}
}
101. 101
先佔式排程器的鎖
#define UNLOCKED 0
#define LOCKED 1
bit Lock; // Global lock flag
// . . .
// Ready to enter critical section
// – wait for lock to become clear
// (FOR SIMPLICITY, NO TIMEOUT CAPABILITY IS SHOWN)
while(Lock == LOCKED);
// Lock is clear
// Enter critical section
// Set the lock
Lock = LOCKED;
// CRITICAL CODE HERE //
// Ready to leave critical section
// Release the lock
Lock = UNLOCKED;
// . . .
#define UNLOCKED 0
#define LOCKED 1
bit Lock; // Global lock flag
// . . .
// Ready to enter critical section
// – wait for lock to become clear
//
while(Lock == LOCKED);
// Lock is clear
// Enter critical section
// Set the lock
Lock = LOCKED;
// CRITICAL CODE HERE //
// Ready to leave critical section
// Release the lock
Lock = UNLOCKED;
// . . .
110. 110
排程器的資料結構與Task陣列
typedef struct // This struct saves information of a task. .
{ // A task costs 7 Bytes. (AT89C52 has 256-Bytes RAM)
void (*pTask)(void); // Pointer to the task (task is function, this pTask is a function pointer)
uint16 delay_ticks; // Delay in ticks until the function will be run
uint16 period_ticks; // Interval (ticks) between subsequent runs.
uint8 execute_me; // Incremented (by scheduler) when task is due to execute, it’s a fire flag.
} task_t;
7-bytes 7-bytes 7-bytes 7-bytes 7-bytes 7-bytes 7-bytes
task_t gScheduler_tasks[SCHEDULER_MAX_TASKS];
111. 111
myOS.h
/**************************************************************************************************
Filename: myOS.h
Revised: $Date: 2013-12-9 $
Description: This file contains the information of my simple embedded OS
**************************************************************************************************/
#include "hal_types.h"
#include "hal_drivers.h"
#include "hal_board_cfg.h"
/********** CONSTANTS *********/
#define SYSTEM_TICK 1000
// Maximum number of tasks, you can adjust this for your application
#define SCHEDULER_MAX_TASKS 3
#define RETURN_NORMAL 0
#define RETURN_ERROR 1
// Error Code
#define ERROR_SCH_TOO_MANY_TASKS 0x01
#define ERROR_SCH_CANNOT_DELETE_TASK 0x02
/********** TYPEDEFS *********/
typedef struct // This struct saves information of a task. .
{ // A task costs 7 Bytes. (AT89C52 has 256-Bytes RAM)
void (*pTask)(void); // Pointer to the task (task is function, this pTask is a function pointer)
uint16 delay_ticks; // Delay in ticks until the function will be run
uint16 period_ticks; // Interval (ticks) between subsequent runs.
uint8 execute_me; // Incremented (by scheduler) when task is due to execute
} task_t;
112. 112
/****************************************************************************
* FUNCTIONS - API
***************************************************************************/
// Initialize the OS
extern void myOS_init(void);
// Start the OS
extern void myOS_start(void);
// Dispatch the tasks
extern void myOS_task_dispatcher(void);
// Add a task to the OS scheduler
extern uint8 myOS_add_task(void (*pFunc)(), const uint16, const uint16);
// Delete a task from the OS scheduler
extern uint8 myOS_delete_task(const uint8);
// Reports the OS status
extern void myOS_status_reporter(void);
113. 113
系統初始化與啟動函式
/***********************************************************************************************
* @fn myOS_init
* @brief Initialize myOS. Clear the tasks list
* @param None
* @return None
*********************************************************************************************/
void myOS_init(void)
{
uint8 task_id;
for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) {
// Delete tasks and this generates an error code (since array is empty)
myOS_delete_task(task_id);
}
gError_code = 0; // Reset error code
// The system tick is generated by Timer2. The required Timer 2 overflow is 1 ms.
// Call HalTimer2Config to put it in 16-bits auto-reload mode,
// and the callback function = myOS_scheduler_update
HalTimer2Config(HAL_TIMER2_MODE_16BITS_AUTO, HAL_INT_ENABLE, myOS_scheduler_update);
}
/**********************************************************************************************
* @fn myOS_start
* @brief Start the OS. Enable interrupts and Timer2.
* @param None
*********************************************************************************************/
void myOS_start(void)
{
HAL_ENABLE_INTERRUPTS(); // Enable interrupts
HalTimer2Start(SYSTEM_TICK); // Start Timer2 with STSTEM_TICK (1000us = 1ms)
}
myOS.c
114. 114
添加task的函式
/***************************************************************************************************
* @fn myOS_add_task
* @brief Add a task (function) to be executed at regular intervals or after a user-defined delay
* @param *pFunc: function pointer to the task
* DELAY_TICK: to execute a task after DELAY_TICKS (1 tick = 1 ms)
* PERIOD_TICK: to execute a task in every PERIOD_TICK (set to 0 if execute it only once)
* @return task_id - the index of the scheduled task in the list
***************************************************************************************************/
uint8 myOS_add_task(void (*pFunc)(), const uint16 DELAY_TICK, const uint16 PERIOD_TICK)
{
uint8 task_id = 0;
// Find an empty space in the array (if there is one), and get the index of that position
while ((gScheduler_tasks[task_id].pTask != 0) && (task_id < SCHEDULER_MAX_TASKS)) {
task_id++;
}
if (task_id == SCHEDULER_MAX_TASKS) { // Check if end of the list is reached?
// If it is, set gError_code to show "task list full" and return an error code
gError_code = ERROR_SCH_TOO_MANY_TASKS;
return gError_code;
}
// We're here since there is a space in the task array
gScheduler_tasks[task_id].pTask = pFunc;
gScheduler_tasks[task_id].delay_ticks = DELAY_TICK;
gScheduler_tasks[task_id].period_ticks = PERIOD_TICK;
gScheduler_tasks[task_id].execute_me = 0;
return task_id; // return position of task (to allow later deletion)
}
115. 115
刪除task的函式
/*********************************************************************************************
* @fn myOS_delete_task
* @brief This function is called to delete a task by the task_id.
* @param TASK_ID - ID of the task
* @return Return_code - OK or Not OK
********************************************************************************************/
uint8 myOS_delete_task(const uint8 TASK_ID)
{
uint8 Return_code;
if (gScheduler_tasks[TASK_ID].pTask == 0) {
// No task at here, set gError_code to show nothing to delete
// Also return an error code
gError_code = ERROR_SCH_CANNOT_DELETE_TASK;
Return_code = RETURN_ERROR;
} else {
Return_code = RETURN_NORMAL;
}
gScheduler_tasks[TASK_ID].pTask = 0x0000; // 8051's Program Counter is 16-bits wide
gScheduler_tasks[TASK_ID].delay_ticks = 0;
gScheduler_tasks[TASK_ID].period_ticks = 0;
gScheduler_tasks[TASK_ID].execute_me = 0;
return Return_code; // return status
}
116. 116
排程器更新函式
/**********************************************************************************************
* @fn myOS_scheduler_update
* @brief Scheduler's ISR. He will be called in every 1ms. It choose a task to run and
* update the schedule time-ticks of each task.
* @param None
* @return None
*********************************************************************************************/
void myOS_scheduler_update(void)
{
// Clear T2 Flag, this is done in hal_timer2.c. Just comment it out here
// T2CON &= ~(T2CON_T2EX_IntFlag_Bit);
uint8 task_id;
for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) {
if (gScheduler_tasks[task_id].pTask) {
if (gScheduler_tasks[task_id].delay_ticks == 0) {
gScheduler_tasks[task_id].execute_me += 1;
// If the task is set to run periodically, then schedule the task to run again
if (gScheduler_tasks[task_id].period_ticks) {
gScheduler_tasks[task_id].delay_ticks = gScheduler_tasks[task_id].period_ticks;
}
} else {
// If the task is not yet ready to run: just decrement the delay
// (This implements the tick-time counter)
gScheduler_tasks[task_id].delay_ticks -= 1;
}
}
}
}
117. 117
任務分派器
/*********************************************************************************************
* @fn myOS_task_dispatcher
* @brief When a task (function) is due to run, myOS_task_dispatcher() will run it.
* This function must be called (repeatedly) from the main loop.
* @param None
* @return None
*********************************************************************************************/
void myOS_task_dispatcher(void)
{
uint8 task_id;
// Dispatches (runs) the next task (if one is ready)
for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) {
if (gScheduler_tasks[task_id].execute_me > 0) {
(*gScheduler_tasks[task_id].pTask)();
gScheduler_tasks[task_id].execute_me -= 1;
// Periodic tasks will automatically run again ( Re-scheduled in myOS_scheduler_update() )
// If this is a 'one shot' task, remove it from the array here
if (gScheduler_tasks[task_id].period_ticks == 0) myOS_delete_task(task_id);
}
}
myOS_status_reporter(); // Call the reporter to report system status
myOS_go_sleep(); // The scheduler enters idle mode at this point
}
137. 137
以狀態為中心(State-centric)的狀態機
平敘地說,狀態機由很
大的if-else或switch程式
敘述所構成:
while (1) {
look for event
switch (state) {
case (green light):
if (event is stop command)
turn off green light
turn on yellow light
set state to yellow light
start timer
break;
case (yellow light):
if (event is timeout)
turn off yellow light
turn on red light
set state to red light
break;
case (red light):
if (event is go command)
turn off red light
turn on green light
set state to green light
break;
default (unhandled state)
error!
}
}
case (state):
if event valid for this state
handle event
prepare for new state
set new state
138. 138
隱藏狀態轉移的細節
另一種實作狀態機的方法是將「狀態轉移的資訊」
抽離狀態機。理論上這是比較好的做法,因為這樣
可以維持較好的封裝性(encapsulation)並減少狀態
內文的相依性。
「next state」函式會處理每一個state並且將系統推
向它該進入的next state。
case (state):
make sure current state is actively doing what it needs
if event valid for this state
call next state function
case (green light):
if (green light not on) turn on green light
if (event is stop)
turn off green light
call next state function
break;
next state function:
switch (state) {
case (green light):
set state to yellow light
break;
case (yellow light):
set state to red light
break;
case (red light):
set state to green light
break;
139. 139
以事件為中心(Event-centric)的狀態機
還有一種方式來實作狀態機,那就是以事件來驅動
狀態的變化
switch (event)
case (stop):
if (state is green light)
turn off green light
go to next state
// else do nothing
break;
Function to handle stop event
if (state == green light) {
turn off green light
go to next state
}
case (event):
if state transition for this event
go to new state
152. 152
void myOS_scheduler_update(void)
{
// Clear T2 Flag, this is done in hal_timer2.c. Just comment it out here
// T2CON &= ~(T2CON_T2EX_IntFlag_Bit);
uint8 task_id;
for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) {
if (gScheduler_tasks[task_id].pTask) {
if (gScheduler_tasks[task_id].delay_ticks == 0) {
if(gScheduler_tasks[task_id].co_op) {
gScheduler_tasks[task_id].execute_me += 1;
} else {
gScheduler_tasks[task_id].execute_me += 1;
(*gScheduler_tasks[task_id].pTask)();
gScheduler_tasks[task_id].execute_me -= 1;
// If the task is "one-shot", remove it from the array
if (gScheduler_tasks[task_id].period_ticks == 0) myOS_delete_task(task_id);
}
// If the task is set to run periodically, then schedule the task to run again
if (gScheduler_tasks[task_id].period_ticks) {
gScheduler_tasks[task_id].delay_ticks = gScheduler_tasks[task_id].period_ticks;
}
} else {
// This implements the tick-time counter
gScheduler_tasks[task_id].delay_ticks -= 1;
}
}
}
}
實作混合式排程器
typedef struct
{
void (*pTask)(void);
uint16 delay_ticks;
uint16 period_ticks;
uint8 execute_me;
uint8 co_op;
} task_t;
153. 153
修改添加任務函式
/***************************************************************************************************
* @fn myOS_add_task
* @brief Add a task (function) to be executed at regular intervals or after a user-defined delay
***************************************************************************************************/
uint8 myOS_add_task(void (*pFunc)(), const uint16 DELAY_TICK
, const uint16 PERIOD_TICK, const uint8 CO_OP)
{
uint8 task_id = 0;
// Find an empty space in the array (if there is one), and get the index of that position
while ((gScheduler_tasks[task_id].pTask != 0) && (task_id < SCHEDULER_MAX_TASKS)) {
task_id++;
}
if (task_id == SCHEDULER_MAX_TASKS) { // Check if end of the list is reached?
// If it is, set gError_code to show "task list full" and return an error code
gError_code = ERROR_SCH_TOO_MANY_TASKS;
return gError_code;
}
// We're here since there is a space in the task array
gScheduler_tasks[task_id].pTask = pFunc;
gScheduler_tasks[task_id].delay_ticks = DELAY_TICK;
gScheduler_tasks[task_id].period_ticks = PERIOD_TICK;
gScheduler_tasks[task_id].execute_me = 0;
gScheduler_tasks[task_id].co_op = CO_OP;
return task_id; // return position of task (to allow later deletion)
}
154. 154
任務分配器 (不用更改)
/************************************************************************************************
* @fn myOS_task_dispatcher
* @brief When a task (function) is due to run, myOS_task_dispatcher() will run it.
* This function must be called (repeatedly) from the main loop.
* @param None
* @return None
************************************************************************************************/
void myOS_task_dispatcher(void)
{
uint8 task_id;
// Dispatches (runs) the next task (if one is ready)
for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) {
if (gScheduler_tasks[task_id].execute_me > 0) { // It means the task is ready to run
(*gScheduler_tasks[task_id].pTask)(); // Call that function
gScheduler_tasks[task_id].execute_me -= 1; // Reset/Decrease execute_me flag
// Periodic tasks will automatically run again ( Re-scheduled in myOS_scheduler_update() )
// If this is a 'one shot' task, remove it from the array here
if (gScheduler_tasks[task_id].period_ticks == 0) myOS_delete_task(task_id);
}
}
myOS_status_reporter(); // Call the reporter to report system status
myOS_go_sleep(); // The scheduler enters idle mode at this point
}