硬件平台统一的notification_manager提示信号管理方法

背景

在一个硬件系统里面,像蜂鸣器,LED指示灯,震动马达等,如果每一种提示信号都写一个单独的逻辑或者创建一个任务,那有些代码功能就会重复,对于一个MCU,RAM不太大的情况就需要占更多的空间。
而且有的地方,会用到不同的提示元器件,且逻辑不太相同,如果针对每一种场景,单独写逻辑的话,这样方式来实现提示,可能新一加个逻辑就需要改动较多的代码,且内部的判断逻辑会容易混淆。
如果碰到不同任务去使用的时候,且提示信号还有优先级要考虑的话,代码判断逻辑就又要多一层了。如果需要对提示元器件的使能进行控制,代码分散在各个地方也是一个比较麻烦的事情。
像蜂鸣器这类提示元件的工作方式都挺类似,像持续ON,响几次(周期性动作),响几次结束,长周期提示,这些动作可以覆盖基本的提示方式,当然一个独特的方式还是需要单独使用一些代码来实现,也可以在具体的响应中,去调用统一的提示信号的操作,去实现想要的效果。

抽象

工作类型

一般来说,可以将提示信号的状态,分为无,连续,顺序,周期
无,即没有动作,或者说是关闭的状态
连续的,主要指的是持续地开启,蜂鸣器一直响
顺序的,响几声就关闭,类似于开,关,开,关
周期的,以一种顺序,一直按照设置往复运动,直到接收到其他指令

typedef enum{
    PTYPE_NONE = 0,
    PTYPE_CONTINUOUS,
    PTYPE_DURATION_SEQ,
    PTYPE_PERIODIC_BURST,
    PYTPE_MAX
}pattern_type_t;

typedef struct pattern_t{
    pattern_type_t type;
	// for SEQ
    const uint32_t *durations_ms; // point to array [on, off, on ,off]
    uint8_t n_durations;
    uint16_t repeats; // 0 => forever
    // for PERIODIC_BURST
    uint16_t burst_conut;
    uint32_t burst_on_ms;
    uint32_t burst_off_ms;
    uint32_t period_ms;
    //priority hint
    uint8_t default_prioriry;
}pattern_t;

定义提示信号的类型及对每种信号的工作方式抽象到pattern_t这个结构体中,pattern_t中的成员包含所有情况,但针对每一种模式,只使用相关的成员变量即可,不需要使用全部成员变量

定义示例

//连续工作
static const pattern_t PAT_BUZZER_CONTINUOUS = {
    .type = PTYPE_CONTINUOUS,
    .default_prioriry = 3,
};

//顺序工作,开关2次,开,关,开,关,间隔200ms
static const uint32_t buzzer_once_seq[] = {200, 200};
static pattern_t pat_buzzer_twinkle_freq = {
    .type = PTYPE_DURATION_SEQ,
    .durations_ms = buzzer_twinkle_freq_seq,
    .n_durations = 2,
    .repeats = 2,
    .default_prioriry = 3,
};

//周期工作 on off ... on period,最后一个周期只有ON,没有OFF
static const pattern_t PAT_BUZZER_PERIODIC = {
    .type = PTYPE_PERIODIC_BURST,
    .burst_conut = 3,
    .burst_on_ms = 100,
    .burst_off_ms = 80,
    .period_ms = 2000,
    .default_prioriry = 3,
};

操作类型

提示操作类型,主要可分为,运行定义的提示操作,停止设备(当前周期运行完成),强行停止设备,修改设备的开关状态标志
应用任务中,在需要的时候,给提示管理器,发送相应的操作命令,来改变提示器的提醒状态,如果同优级则覆盖当前的操作,否则将被忽略。

typedef enum{
    CMD_PLAY_PATTERN = 0,
    CMD_STOP_DEVICE,
    CMD_FORCE_STOP_DEVICE,
}cmd_type_t;

每次调用周期先判断是否有相关的指令接收,如果有指令就对接令进行处理,转换当前的提示设备状态,或者停止当前的提示状态
根据时间片轮询更新提示设备的状态,遍历每个设备的相关状态

实例代码

这里是使用FreeRTOS实现,裸机也是可以,只要定时轮咨整个提示信号状态就可以

定义相关参数类型

typedef enum{
    DEV_BUZZER = 0,
    DEV_LED_RED,
    DEV_LED_GREEN,
    DEV_VIBRATOR,
    DEV_MAX
}device_id_t;

typedef struct notifier_req_t{
    cmd_type_t type;
    device_id_t dev;
    const pattern_t *pattern;
    uint8_t priority;
}notifier_req_t;

typedef struct device_state_t{
    uint8_t active;
    const pattern_t *pat;
    uint8_t prioriry;
    //runtime for DURATION_SEQ
    uint8_t seg_idx;
    uint32_t seg_remaining;
    uint16_t repeats_left;
    uint8_t output_on;

    //runtime for PERIODIC_BURST
    uint16_t burst_remaining;
    uint32_t burst_segment_timer;
    uint32_t period_timer;

    uint8_t stop_pending;
}device_state_t;

关联设备控制

使用分配函数指针的形式,将提示信号统一管理与实际设备控制进行相互关联

typedef void (*device_control_fn_t)(uint8_t);
static device_control_fn_t dev_control[DEV_MAX] = {0};
static void device_control_init(void)
{
    for(int i = 0; i < DEV_MAX; i++)
        dev_control[i] = NULL;

    dev_control[DEV_BUZZER] = app_buzzer_dev_control;
    dev_control[DEV_LED_RED] = app_led_red_control;
    dev_control[DEV_LED_GREEN] = app_led_green_control;
}

命令操作响应

根据重新接收到的信号去初始化,或者调整当前的设备状态,在下一轮设备状态轮询去改变设备状态

static void notifier_cmd_response(void)
{
    notifier_req_t req;
    device_id_t dev;
    while (xQueueReceive(notifier_queue, &req, 0) == pdTRUE){
        switch (req.type){
        case CMD_PLAY_PATTERN:
            if(req.dev > DEV_MAX)
                return;
            if(notify_priority_override(&dev_state[req.dev], req.priority)){
                device_start_pattern(&dev_state[req.dev], req.pattern, req.priority);
                device_control(req.dev, dev_state[req.dev].output_on);
            }
            break;
        case CMD_STOP_DEVICE:
            dev = req.dev;
            if(dev < DEV_MAX)
                dev_state[dev].stop_pending = 1; 
            break;
        case CMD_FORCE_STOP_DEVICE:
            dev = req.dev;
            if(dev < DEV_MAX){
                memset(&dev_state[dev], 0, sizeof(dev_state[dev]));
                device_control(req.dev, dev_state[req.dev].output_on);
            }
            break;
        default:
            break;
        }
    }
}

提示信号动作初始化

根据当前的信号指令,初始化相关提示逻辑跳转的初始状态

static void device_start_pattern(device_state_t *st, const pattern_t *pat, uint8_t prio)
{
    memset(st, 0, sizeof(*st));
    st->active = 1;
    st->pat = pat;
    st->prioriry = prio ? prio : pat->default_prioriry;
    st->output_on = 0;
    st->stop_pending = 0;

    if(pat->type == PTYPE_CONTINUOUS){
        st->output_on = 1;
        st->seg_remaining = 0xFFFFFFFFu;
    }
    else if(pat->type == PTYPE_DURATION_SEQ){
        st->seg_idx = 0;
        st->output_on = 1;
        st->seg_remaining = pat->durations_ms[0];
        st->repeats_left = (pat->repeats == 0) ? 0xFFFFu : pat->repeats;
    }
    else if(pat->type == PTYPE_PERIODIC_BURST){
        st->burst_remaining = pat->burst_conut;
        st->burst_segment_timer = pat->burst_on_ms;
        st->output_on = 1;
        st->period_timer = pat->period_ms;
    }
}

整体任务及各设备根据当前类型进行逻辑判断及跳转

static void notifier_task(void *pParameter)
{
    TickType_t last_wake = xTaskGetTickCount();
    const TickType_t tick_ticks = pdMS_TO_TICKS(NOTIFIER_TICK_MS);

    while(1){
        notifier_cmd_response();

        for(device_id_t d = 0; d < DEV_MAX; d++){
            device_state_t *p_dev_state = &dev_state[d];
            if((!p_dev_state->active) || (p_dev_state->pat == NULL))
                continue;

            switch(p_dev_state->pat->type){
            case PTYPE_CONTINUOUS:
                if(p_dev_state->stop_pending)
                    device_pending_stop(d);
                else{
                    p_dev_state->output_on = 1;
                    device_control(d, p_dev_state->output_on);
                }
                break;
            case PTYPE_DURATION_SEQ:
                if(p_dev_state->seg_remaining >= (uint32_t)NOTIFIER_TICK_MS)
                    p_dev_state->seg_remaining -= NOTIFIER_TICK_MS;
                else{
                    p_dev_state->seg_idx++;
                    if(p_dev_state->seg_idx < p_dev_state->pat->n_durations){
                        p_dev_state->output_on = p_dev_state->output_on ^ 0x01;
                        p_dev_state->seg_remaining = p_dev_state->pat->durations_ms[p_dev_state->seg_idx];;
                        device_control(d, p_dev_state->output_on);
                    }
                    else{
                        if(p_dev_state->stop_pending)
                            device_pending_stop(d);
                        else if(p_dev_state->repeats_left == 0xFFFFu){
                            p_dev_state->seg_idx = 0;
                            p_dev_state->seg_remaining = p_dev_state->pat->durations_ms[0];
                            p_dev_state->output_on = 1;
                            device_control(d, p_dev_state->output_on);
                        }
                        else if(p_dev_state->repeats_left > 1){
                            p_dev_state->repeats_left--;
                            p_dev_state->seg_idx = 0;
                            p_dev_state->seg_remaining = p_dev_state->pat->durations_ms[0];
                            p_dev_state->output_on = 1;
                            device_control(d, p_dev_state->output_on);
                        }
                        else{
                            p_dev_state->active = 0;
                            p_dev_state->pat = NULL;
                            p_dev_state->output_on = 0;
                            device_control(d, p_dev_state->output_on);
                        }
                    }
                }
                break;
            case PTYPE_PERIODIC_BURST:
                if(p_dev_state->output_on){
                    if(p_dev_state->burst_segment_timer >= (uint32_t)NOTIFIER_TICK_MS)
                        p_dev_state->burst_segment_timer -= NOTIFIER_TICK_MS;
                    else{
                        if(p_dev_state->burst_remaining > 1){
                            p_dev_state->burst_remaining--;
                            p_dev_state->output_on = 0;
                            p_dev_state->burst_segment_timer = p_dev_state->pat->burst_off_ms;
                            device_control(d, p_dev_state->output_on);
                        }
                        else{
                            p_dev_state->burst_remaining = 0;
                            p_dev_state->output_on = 0;
                            p_dev_state->period_timer = p_dev_state->pat->period_ms;
                            device_control(d, p_dev_state->output_on);
                        }
                    }
                }
                else{
                    if(p_dev_state->burst_remaining > 0){
                        if(p_dev_state->burst_segment_timer >= (uint32_t)NOTIFIER_TICK_MS)
                            p_dev_state->burst_segment_timer -= NOTIFIER_TICK_MS;
                        else{
                            p_dev_state->output_on = 1;
                            p_dev_state->burst_segment_timer = p_dev_state->pat->burst_on_ms;
                            device_control(d, p_dev_state->output_on);
                        }
                    }
                    else{
                        if(p_dev_state->period_timer >= (uint32_t)NOTIFIER_TICK_MS)
                            p_dev_state->period_timer -= NOTIFIER_TICK_MS;
                        else{
                            if(p_dev_state->stop_pending)
                                device_pending_stop(d);
                            else{
                                //periodic restart
                                p_dev_state->burst_remaining = p_dev_state->pat->burst_conut;
                                p_dev_state->output_on = 1;
                                p_dev_state->burst_segment_timer = p_dev_state->pat->burst_on_ms;
                                device_control(d, p_dev_state->output_on);
                            }
                        }
                    }
                }
                break;
            default:
                break;
            }
        }

        vTaskDelayUntil(&last_wake, tick_ticks);
    }
}

发送指令

void app_notification_send_cmd(device_id_t dev, const pattern_t *pattern, uint8_t priority)
{
    if(dev >= DEV_MAX || pattern == NULL)
        return;

    notifier_req_t req;
    req.type = CMD_PLAY_PATTERN;
    req.dev = dev;
    req.pattern = pattern;
    req.priority = priority;

    xQueueSend(notifier_queue, &req, 10);
}

//示例,蜂鸣响一次
app_notification_send_cmd(DEV_BUZZER, &PAT_BUZZER_ONCE, 2);

优先级

每当新接收提示信号指令,先判定接收信号的优先级,如果高于或等于当前提示操作,就执行新的提示操作,否则忽略。
这样可以很好地,执行优先较高的提示信号,且应用时的代码不需要对提示信号做相应的判断调整修改,只要重要的任务执行高优先的提示信号就可以进行切换
提示的时候比较好地区分优先级,不同单独再去考虑是否打断的问题,省去新的判定逻辑

static uint8_t notify_priority_override(device_state_t *cur_st, uint8_t new_prio)
{
    if(!cur_st->active)
        return 1;
    return new_prio >= cur_st->prioriry;
}

DURATION_SEQ , PERIODIC两种机制

PERIODIC循环机制,其实用SEQ,将repeats设置为0,也可以达到一直循环的效果,从实现的角度来说,可以把PERIODIC机制并入到DURATION_SEQ模式中
但两种的语义并不同
DURATION_SEQ 按键一次顺序完成一系列动作
PERIODIC 间隔多长时间做一系列操作
保留两种实现方式比较好,多一种模式可以让使用的时候的接口,有多一个选择,且更好理解一些。
还有一点不同,等单个小循环结束再停止的机制,有一点细微的差别

总结

所有设备操作都在一个任务里面,不会因为不同任务,导致IO的控制的失控,且相对节省一定的任务空间
添加新的设备可以常规的功能,可以直接复用当前提示信号的管理代码,不需要重新添加逻辑
有这样的统一管理机制,为提示信号控制提供很大的帮助与便利。

posted @ 2025-11-26 14:08  cau_par  阅读(47)  评论(0)    收藏  举报