
Có thể hiểu state machine là một hệ thống mà kết quả đầu ra không những phụ thuộc và đầu vào mà còn phụ thuộc vào trạng thái của hệ thống.
Một hệ thống thường có những trạng thái hữu hạn, để chuyển từ trạng thái này sang trạng thái khác cần những sự kiện chuyển trạng thái.
Ví dụ
Hình trên mô tả một State machine đơn giản. Hệ thống sẽ kết thúc khi nhập 2 ký tự // liên tiếp. Ở ký tự đầu tiên, hệ thống chưa cho output nhưng sẽ chuyển qua state 2.
Nếu đang ở state 2 mà nhập thêm “/” thì output ngược lại sẽ quay về trạng thái 1.
Một state machine có thể được mô tả bởi các thành phần sau:
- Một tập các trạng thái của hệ thống.
- Trạng thái khởi tạo.
- Tập hợp các sự kiện Input.
- Tập hợp các sự kiện Output.
- Tập hợp các hành động hay các sự kiện output ứng với mỗi trạng thái của hệ thống (gọi là state event handler).
- Các sự kiện chuyển trạng thái (state transtion).
Thông thường, state machine cho phép người dùng mô tả các trạng thái phức tạp thông qua nhiều trạng thái của các hệ thống phân cấp (composit state).
Một hệ thống state machine phân cấp dựa trên quan hệ cha con ( parent – chid):
- Tương tự như một sub-state nằm trong một nhóm state mô tả trạng thái hiện tại của hệ thống.
- Composite states cũng có chuyển trạng thái, entry, exit …
- Sub-state kế thừa từ composit state.
- Trạng thái hiện tại (active state) thể hiện một đường từ trạng thái ban đầu cho đến vị trí hiện tại.
Để cài đặt state Machine, thông thường người ta định nghĩa ra các trạng thái của hệ thống. Trong mỗi vòng lặp sẽ có sử dụng switch() để check trạng thái hiện tại của hệ thống và các điều kiện chuyển tiếp. Nếu điều kiện chuyển tiếp thỏa mãn thì gọi hàm xử lý và update sang trạng thái mới.
Có thể tham khảo code mẫu sau
typedef enum
{
STATE_1 = 0,
STATE_2,
STATE_3
} my_state_t;
my_state_t state = STATE_1;
void foo(char input)
{
...
switch(state)
{
case STATE_1:
if(input)
state = STATE_2;
break;
case STATE_2:
if(input)
state = STATE_3;
else
state = STATE_1;
break;
case STATE_3:
...
break;
}
...
}
Ứng dụng State Machine để cài đặt thư viện Button
Ý tưởng của bộ thư viện Button là có thể cài đặt để xử lý được 3 loại tác động cơ bản bao gồm: Click(), DoubleClick() và Press() (giữ phím).
Biểu diễn state machine của nút bấm như hình dưới
Ở trạng thái khởi tạo (IDLE), khi buttun được nhấn, hệ thống chuyển sang trạng thái 1. Trạng thái này sẽ chờ trong một khoảng thời gian nhất định (timeout), trong khoảng thời gian này, nếu button được thả ra, hệ thống chuyển qua trạng thái 2. Nếu hết thời gian timeout mà button chưa được thả thì sẽ kết luận là sự kiện giữ phím – Press(). Tương tự đối với các trạng thái khác.
Ý tưởng để cài đặt hệ thống trên như sau:
Để thực hiện việc detect ra các sự kiện nút bấm ta cần biết trạng thái hiện thời của hệ thống.
Trạng thái này gồm 2 thành phần là
- Giá trị logic của chân GPIO nối với nút bấm để xác định Button đang ” up” hay “down”.
- Thời gian kể từ lúc chuyển trạng thái để xác định đã hết thời gian time out hay chưa.
Giá trị logic hoàn toàn có thể dựa vào các hàm hỗ trợ đọc giá trị của chân GPIO.
Vấn đề là xác định thời gian. Trong nhiều hệ thống nhúng, và cả trong các bộ thư viện của Andruno, để xác định thời gian trong hệ thống, người ta dùng một biến toàn cục để đếm số lượng “tick”. “Tick” ở đây có thể hiểu là một đơn vị thời gian của hệ thống. Sau mỗi khoảng thời gian nhất định thì sẽ tăng lên một “tick”, thông thường thời gian tăng một “tick” vào khoảng 1ms.
Có thể cài đặt tăng “tick” bằng cách sử dụng ngắt timer để sau mỗi 1ms thì tăng tick lên một đơn vị.
Cài đặt thư viện Button
Bước đầu tiên là định nghĩa các trạng thái của hệ thống. Dựa vào hình trên, ta định nghĩa ra các trạng thái tương ứng như sau:
typedef enum tagSTATE
{
IDLE,
WAIT_BUTTON_UP,
WAIT_PRESS_TIMEOUT,
WAIT_CLICK_TIMEOUT,
WAIT_DCLICK_TIMEOUT
}state;
Đối với Button, ta tiếp cận theo hướng hướng đối tượng. Nếu coi Button như một đối tượng thì nó bao gồm các thuộc tính và phương thức như sau:
typedef struct tagButton
{
WORD nTick; // Current tick of button.
WORD gpioPin; // GPIO PIN connect witch button.
BOOL enable; // Enable or disable button.
BYTE state; // State of button.
SYSTEMCALLBACK fnCallbackIDLE; // Function for handle IDE event
SYSTEMCALLBACK fnCallbackClick; // Function for handle Click event
SYSTEMCALLBACK fnCallbackDoubleClick; // Function for handle DoubleClick event
SYSTEMCALLBACK fnCallbackHold; // Function for handle Hold event
}BUTTON, *PBUTTON; // Define Button object and pointer to Button.
Lưu ý, do ngôn ngữ C không hỗ trợ hướng đối tượng nên các phương thức sẽ được khai báo bằng các con trỏ hàm. Như ở ví dụ trên, SYSTEMCALLBACK là một macro định nghĩa con trỏ hàm sau:
#define SYSTEMCALLBACK VOID (*fnCallback) (*void);
Các tham số về thời gian timeout được định nghĩa như sau:
#define MAX_BUTTON_COUNT 10 // can handle max 10 button, can adjust to meet requirement
#define CLICK_TICK 200 // 200ms
#define DOUBLE_CLICK_TICK 200 // 200ms
#define HOLD_TICK 500 // 500ms
Để xử lý được nhiều button, ta sẽ định nghĩa ra một mảng chứa các con trỏ hàm, mỗi phần tử trong hàm này sẽ trỏ đến một đối tượng Button.
// Array store pointer to button
INTERNAL PBUTTON g_pButtons[MAX_BUTTON_COUNT] = {{NULL}};
Khi muốn sử dụng button, ta chỉ việc định nghĩa Button và gán vào một vị trí trong mảng trên. Mỗi vòng lặp, hệ thống sẽ thực hiện quét các phần tử trong mảng và lần lượt xác định trạng thái các button và gọi hàm callback tương ứng.
Đối với thư viện Button, ta cần xây dựng các hàm sau.
/*-----------------------------------------------------------------------------*/
/* Function prototypes */
/*-----------------------------------------------------------------------------*/
/*
initialization Button and regist callback function to handle event
*/
VOID ButtonInit(PBUTTON button, WORD GPIO_PIN, SYSTEMCALLBACK onIDLE, SYSTEMCALLBACK onClick, SYSTEMCALLBACK onDoubleClick, SYSTEMCALLBACK onHold);
/*
Enable Button, regist to array g_pButtons[]and start listion event, recall while button cancel;
*/
BOOL ButtonStart(PBUTTON pBtnRegister);
/*
Detroy Button, delete from g_pButtons[].
*/
VOID ButtonCancel(BUTTON btnCancel);
/*
Sweep g_pButtons[] and check state of each button and call the callback function to handle event.
*/
VOID ButtonProcessEvent(VOID);
Cài đặt cụ thể các hàm, mọi người có thể tham khảo theo link github.
Trong bài viết này, mình sẽ tập trung vào cài đặt xử lý các trạng thái Button theo state machine.
Ở mỗi vòng lặp, hệ thống sẽ quét mảng g_pButtons để xác định các phần tử mảng đã được gán với Button chưa, đồng thời, các button này có được enable hay không. Nếu các điều kiện này thỏa mãn sẽ thực hiện switch trạng thái Button.
if(g_pButtons[nIndex] != NULL && g_pButtons[nIndex]->enable == 1)
{
switch(g_pButtons[nIndex]->state)
{
case IDLE:
{
(bla..bla)
}
}
}
Ở trạng thái IDE, nếu Button được ấn thì sẽ chuyển sang trạng thái WAIT_BUTTON_UP ( Tương ứng trạng thái 1 trên sơ đồ) và bắt đầu đếm thời gian để xác định timeout.
case IDLE:
{
// Waiting for One pin being pressed.
if(BUTTON_LOGIC_LEVEL(nIndex) == LOW)
{
g_pButtons[nIndex]->state = WAIT_BUTTON_UP;
g_pButtons[nIndex]->nTick = CURRENT_TICK + HOLD_TICK;
}
break;
}
Ở trạng thái WAIT_BUTTON_UP, nếu Button được thả ra và chưa hết thời gian time out, hệ thống sẽ chuyển sang trạng thái WAIT_CLICK_TIMEOUT (Tương ứng trạng thái 2). Nếu timeout, hệ thống sẽ nhận ra đây là sự kiện Hold phím và gọi hàm xử lý tương ứng.
case WAIT_BUTTON_UP:
{
// waiting for One pin being released.
if(BUTTON_LOGIC_LEVEL(nIndex) == HIGH && g_pButtons[nIndex]->nTick > CURRENT_TICK)
{
g_pButtons[nIndex]->state = WAIT_CLICK_TIMEOUT;
g_pButtons[nIndex]->nTick = CURRENT_TICK + CLICK_TICK;
break;
}
// Detect hold event.
if(BUTTON_LOGIC_LEVEL(nIndex) == HIGH && g_pButtons[nIndex]->nTick <= CURRENT_TICK)
{
if(g_pButtons[nIndex]->fnCallbackHold != NULL)
{
g_pButtons[nIndex]->fnCallbackHold(NULL); // Process hold event.
__delay_cycles(500000);
}
g_pButtons[nIndex]->state = IDLE;
g_pButtons[nIndex]->nTick = 0;
break;
}
break;
Phân tích tương tự đối với các trạng thái 3 và 6 trên sơ đồ, ta có các cài đặt tương ứng là:
// Waiting for One pin being pressed the second time or timeout.
case WAIT_CLICK_TIMEOUT:
{
// Waiting for One pin being pressed the second time or timeout.
if(BUTTON_LOGIC_LEVEL(nIndex) == LOW && g_pButtons[nIndex]->nTick > CURRENT_TICK)
{
g_pButtons[nIndex]->state = WAIT_DCLICK_TIMEOUT;
g_pButtons[nIndex]->nTick = CURRENT_TICK + DOUBLE_CLICK_TICK;
break;
}
// Detect Click event
if( g_pButtons[nIndex]->nTick <= CURRENT_TICK)
{
if(g_pButtons[nIndex]->fnCallbackClick != NULL)
{
g_pButtons[nIndex]->fnCallbackClick(NULL); // Process click event.
__delay_cycles(500000);
}
g_pButtons[nIndex]->state = IDLE;
g_pButtons[nIndex]->nTick = 0;
break;
}
break;
}
case WAIT_DCLICK_TIMEOUT:
{
// If logic Level = HIGH (release Button) or timer out => call the callback function to handle event.
// Change state to IDLE
if(BUTTON_LOGIC_LEVEL(nIndex) == HIGH)
{
if(g_pButtons[nIndex]->fnCallbackDoubleClick != NULL)
{
g_pButtons[nIndex]->fnCallbackDoubleClick(NULL); // Process click event.
__delay_cycles(500000);
}
g_pButtons[nIndex]->state = IDLE;
g_pButtons[nIndex]->nTick = 0;
break;
}
break;
Để sử dụng thư viện trên, ta khai báo các biến button sau đó gán hàm xử lý sự kiện tương ứng và đưa vào mảng button.
Ví dụ:
BUTTON btnS2, btnS1;
ButtonInit(&btnS2, BIT3, NULL, onClick, onDClick, onHold);
ButtonInit(&btnS1, BIT7, NULL, onClick1, onDClick1, onHold1);
ButtonStart(&btnS2);
ButtonStart(&btnS1);
.....
while(TRUE)
{
ButtonProcessEvent();
....
}
Code đầy đủ của thư viện, các bạn tham khảo ở link github.
Nguồn: kienltb.wordpress
Mong Muốn Có Thêm Cơ Hội Trong Công Việc
Và Trở Thành Một Người Có Giá Trị Hơn
Bạn Chưa Biết Phương Thức Nào Nhanh Chóng Để Đạt Được Chúng
Hãy Để Chúng Tôi Hỗ Trợ Cho Bạn. SEMICON
Hotline: 0972.800.931 - 0938.838.404 (Mr Long)