表驱法

← 返回软件架构 | ← 小项目架构

参考:D:\skill收集\embedded-project-scaffold\embedded-project-scaffold\SKILL.md

app_event.h

#ifndef APP_EVENT_H
#define APP_EVENT_H
 
typedef enum {
    EVENT_NONE = 0,
    EVENT_START,
    EVENT_STOP,
    EVENT_FAULT,
    EVENT_MAX
} AppEvent;
 
#endif

app_message.h

#ifndef APP_MESSAGE_H
#define APP_MESSAGE_H
 
#include <stdint.h>
 
#define APP_MESSAGE_DATA_LEN 64U
 
typedef enum {
    MSG_SRC_UART1 = 0,
    MSG_SRC_CAN1_RX,
    MSG_SRC_ADC_READY,
    MSG_SRC_MAX
} MsgSource_t;
 
typedef struct {
    MsgSource_t source_id;
    uint16_t    length;
    uint8_t     data[APP_MESSAGE_DATA_LEN];
} EventMessage_t;
 
#endif

msg_queue.h

#ifndef MSG_QUEUE_H
#define MSG_QUEUE_H
 
#include "app_message.h"
 
#define MSG_QUEUE_SIZE 8U
#define QUEUE_OK       0U
#define QUEUE_EMPTY    1U
#define QUEUE_FULL     2U
 
typedef struct {
    EventMessage_t buffer[MSG_QUEUE_SIZE];
    uint8_t        head;
    uint8_t        tail;
    uint8_t        count;
} MsgQueue_t;
 
extern MsgQueue_t g_msgQueue;
 
void Queue_Init(MsgQueue_t *queue);
uint8_t Queue_Push(MsgQueue_t *queue, const EventMessage_t *msg);
uint8_t Queue_Pop(MsgQueue_t *queue, EventMessage_t *msg);
 
#endif

msg_queue.c

#include "msg_queue.h"
 
MsgQueue_t g_msgQueue;
 
void Queue_Init(MsgQueue_t *queue)
{
    if (queue == NULL) {
        return;
    }
 
    queue->head  = 0U;
    queue->tail  = 0U;
    queue->count = 0U;
}
 
uint8_t Queue_Push(MsgQueue_t *queue, const EventMessage_t *msg)
{
    if ((queue == NULL) || (msg == NULL)) {
        return QUEUE_FULL;
    }
 
    if (queue->count >= MSG_QUEUE_SIZE) {
        return QUEUE_FULL;
    }
 
    queue->buffer[queue->head] = *msg;
    queue->head = (uint8_t)((queue->head + 1U) % MSG_QUEUE_SIZE);
    queue->count++;
 
    return QUEUE_OK;
}
 
uint8_t Queue_Pop(MsgQueue_t *queue, EventMessage_t *msg)
{
    if ((queue == NULL) || (msg == NULL)) {
        return QUEUE_EMPTY;
    }
 
    if (queue->count == 0U) {
        return QUEUE_EMPTY;
    }
 
    *msg = queue->buffer[queue->tail];
    queue->tail = (uint8_t)((queue->tail + 1U) % MSG_QUEUE_SIZE);
    queue->count--;
 
    return QUEUE_OK;
}

app_event_flag.h

#ifndef APP_EVENT_FLAG_H
#define APP_EVENT_FLAG_H
 
#include <stdint.h>
 
#define APP_EVENT_MESSAGE_READY 0x01U
 
void Set_EventFlag(uint32_t flag);
void Clear_EventFlag(uint32_t flag);
uint32_t Get_EventFlag(uint32_t flag);
 
#endif

app_event_flag.c

#include "app_event_flag.h"
 
static volatile uint32_t g_appEventFlags = 0U;
 
void Set_EventFlag(uint32_t flag)
{
    g_appEventFlags |= flag;
}
 
void Clear_EventFlag(uint32_t flag)
{
    g_appEventFlags &= ~flag;
}
 
uint32_t Get_EventFlag(uint32_t flag)
{
    return (g_appEventFlags & flag);
}

app_state.h

#ifndef APP_STATE_H
#define APP_STATE_H
 
#include "app_event.h"
#include "app_message.h"
 
typedef enum {
    STATE_IDLE = 0,
    STATE_RUNNING,
    STATE_ERROR,
    STATE_MAX
} AppState;
 
typedef void (*StateAction)(const EventMessage_t *msg);
 
typedef struct {
    AppState    current_state;
    AppEvent    event;
    AppState    next_state;
    StateAction action;
} Transition;
 
void app_state_init(void);
void app_state_process_event(AppEvent event, const EventMessage_t *msg);
AppState app_state_get_current(void);
 
#endif

app_state.c

#include "app_state.h"
 
static void action_on_start(const EventMessage_t *msg);
static void action_on_stop(const EventMessage_t *msg);
static void action_on_fault(const EventMessage_t *msg);
 
static AppState g_currentState = STATE_IDLE;
static volatile uint8_t g_lastCommand = 0U;
static volatile MsgSource_t g_lastFaultSource = MSG_SRC_MAX;
 
static const Transition g_transitionTable[] = {
    { STATE_IDLE,    EVENT_START, STATE_RUNNING, action_on_start },
    { STATE_RUNNING, EVENT_STOP,  STATE_IDLE,    action_on_stop  },
    { STATE_RUNNING, EVENT_FAULT, STATE_ERROR,   action_on_fault },
    { STATE_ERROR,   EVENT_STOP,  STATE_IDLE,    action_on_stop  },
};
 
void app_state_init(void)
{
    g_currentState = STATE_IDLE;
}
 
void app_state_process_event(AppEvent event, const EventMessage_t *msg)
{
    unsigned int i;
    unsigned int tableSize = (unsigned int)(sizeof(g_transitionTable) / sizeof(g_transitionTable[0]));
 
    for (i = 0U; i < tableSize; i++) {
        if ((g_transitionTable[i].current_state == g_currentState) &&
            (g_transitionTable[i].event == event)) {
            g_currentState = g_transitionTable[i].next_state;
 
            if (g_transitionTable[i].action != 0) {
                g_transitionTable[i].action(msg);
            }
            return;
        }
    }
}
 
AppState app_state_get_current(void)
{
    return g_currentState;
}
 
static void action_on_start(const EventMessage_t *msg)
{
    if ((msg != 0) && (msg->length > 0U)) {
        g_lastCommand = msg->data[0];
    }
}
 
static void action_on_stop(const EventMessage_t *msg)
{
    (void)msg;
    g_lastCommand = 0U;
}
 
static void action_on_fault(const EventMessage_t *msg)
{
    if (msg != 0) {
        g_lastFaultSource = msg->source_id;
    }
}

msg_dispatcher.h

#ifndef MSG_DISPATCHER_H
#define MSG_DISPATCHER_H
 
#include "app_message.h"
 
typedef void (*MsgHandler_t)(const EventMessage_t *msg);
 
void msg_dispatcher_dispatch(const EventMessage_t *msg);
void do_read_message(void);
 
#endif

msg_dispatcher.c

#include "msg_dispatcher.h"
#include "app_state.h"
#include "msg_queue.h"
 
static void Parse_UART1_Data(const EventMessage_t *msg);
static void Parse_CAN1_Data(const EventMessage_t *msg);
static void Parse_ADC_Data(const EventMessage_t *msg);
 
static const MsgHandler_t HandlerTable[MSG_SRC_MAX] = {
    [MSG_SRC_UART1]     = Parse_UART1_Data,
    [MSG_SRC_CAN1_RX]   = Parse_CAN1_Data,
    [MSG_SRC_ADC_READY] = Parse_ADC_Data,
};
 
void msg_dispatcher_dispatch(const EventMessage_t *msg)
{
    if (msg == 0) {
        return;
    }
 
    if ((msg->source_id < MSG_SRC_MAX) &&
        (HandlerTable[msg->source_id] != 0)) {
        HandlerTable[msg->source_id](msg);
    }
}
 
void do_read_message(void)
{
    EventMessage_t msg;
 
    while (Queue_Pop(&g_msgQueue, &msg) == QUEUE_OK) {
        msg_dispatcher_dispatch(&msg);
    }
}
 
static void Parse_UART1_Data(const EventMessage_t *msg)
{
    if ((msg == 0) || (msg->length == 0U)) {
        return;
    }
 
    switch (msg->data[0]) {
        case 0xA1U:
            app_state_process_event(EVENT_START, msg);
            break;
 
        case 0xA2U:
            app_state_process_event(EVENT_STOP, msg);
            break;
 
        default:
            app_state_process_event(EVENT_FAULT, msg);
            break;
    }
}
 
static void Parse_CAN1_Data(const EventMessage_t *msg)
{
    if ((msg == 0) || (msg->length == 0U)) {
        return;
    }
 
    if (msg->data[0] == 0xE0U) {
        app_state_process_event(EVENT_FAULT, msg);
    }
}
 
static void Parse_ADC_Data(const EventMessage_t *msg)
{
    uint16_t adcValue;
 
    if ((msg == 0) || (msg->length < 2U)) {
        return;
    }
 
    adcValue = (uint16_t)(((uint16_t)msg->data[0] << 8) | msg->data[1]);
 
    if (adcValue > 3000U) {
        app_state_process_event(EVENT_FAULT, msg);
    }
}

hal_uart.c

#include "app_event_flag.h"
#include "app_message.h"
#include "msg_queue.h"
 
uint16_t HAL_UART_Read(uint8_t *buffer, uint16_t maxLen);
 
void UART1_IRQHandler(void)
{
    EventMessage_t msg;
 
    msg.source_id = MSG_SRC_UART1;
    msg.length    = HAL_UART_Read(msg.data, APP_MESSAGE_DATA_LEN);
 
    if (msg.length > 0U) {
        if (Queue_Push(&g_msgQueue, &msg) == QUEUE_OK) {
            Set_EventFlag(APP_EVENT_MESSAGE_READY);
        }
    }
}

main.c

#include "app_event_flag.h"
#include "app_state.h"
#include "msg_dispatcher.h"
#include "msg_queue.h"
 
int main(void)
{
    Queue_Init(&g_msgQueue);
    app_state_init();
 
    while (1) {
        if (Get_EventFlag(APP_EVENT_MESSAGE_READY) != 0U) {
            Clear_EventFlag(APP_EVENT_MESSAGE_READY);
            do_read_message();
        }
    }
 
    return 0;
}

复制顺序

  1. app_event.h
  2. app_message.h
  3. msg_queue.h
  4. msg_queue.c
  5. app_event_flag.h
  6. app_event_flag.c
  7. app_state.h
  8. app_state.c
  9. msg_dispatcher.h
  10. msg_dispatcher.c
  11. hal_uart.c
  12. main.c

这套模板怎么走

  1. 中断把原始数据读出来,封装成 EventMessage_t
  2. 中断把消息压进 g_msgQueue
  3. 中断置位 APP_EVENT_MESSAGE_READY
  4. 主循环检测标志后调用 do_read_message()
  5. do_read_message() 从队列取消息
  6. msg_dispatcher_dispatch()source_idHandlerTable
  7. 对应的 Parse_XXX_Data() 把原始数据翻译成 EVENT_START / EVENT_STOP / EVENT_FAULT
  8. app_state_process_event()g_transitionTable
  9. 命中后执行带 msg 参数的回调函数

关键点

  • 信息处理能力来自这里:
typedef void (*StateAction)(const EventMessage_t *msg);

不是 void (*action)(void)

  • 消息分发表在这里:
static const MsgHandler_t HandlerTable[MSG_SRC_MAX]
  • 状态转移表在这里:
static const Transition g_transitionTable[]

新增一路外设时,只改这 4 处

  1. app_message.h 里给 MsgSource_t 加枚举项
  2. msg_dispatcher.c 里新增一个 Parse_XXX_Data()
  3. msg_dispatcher.c 里给 HandlerTable 加一项
  4. 对应外设 ISR 里按同样方式封装 EventMessage_t 入队

新增一个状态事件时,只改这 3 处

  1. app_event.h 里加事件枚举
  2. app_state.c 里新增动作函数
  3. app_state.c 里给 g_transitionTable 加一行

一句话

表驱法要能处理信息,最关键的不是只有状态表,而是要把“消息结构体、消息队列、消息分发表、带消息参数的状态回调、中断入队、主循环出队”整套链路一起写完整。