角度:系统设计层 —— 从单点驱动到"门禁 + 考勤"完整业务的多任务编排
实验平台:鸿蒙智联场景应用实验箱(STM32L431 + LiteOS)+ ATK-AS608 指纹模块
对应工程:B05 生物识别实验/ZHIoT_Sensor_AS608/task_user/Hardware/AS_608/handle.c

一、为什么裸机写法撑不起这个业务

如果只是"读指纹 → 串口打印结果",裸机 main 里塞个 while 循环就够了。但这个工程的业务清单长得多:

  • 按键录入/删除指纹(阻塞式多步交互,一按下去要等用户按好几次手指)
  • 随时可能发生的触摸识别(用户手指不挑时间)
  • 舵机开门 + 5 秒自动关门(时间敏感)
  • LCD 界面刷新(截图、画图标、写字都慢)
  • 蜂鸣器提示音(400ms 阻塞)
  • 云端状态上报(走 WiFi 模组,慢且可能失败)

这些事的节奏完全不同:开门动作等不了屏幕刷新,指纹录入更不能被蜂鸣器卡住。把所有逻辑塞进一个 while(1),要么交互卡顿,要么用满天飞的标志位互相撕扯。工程的做法是引入 LiteOS,把业务切成三个任务:

int main(void)
{
    HAL_Init();                    // 硬件初始化
    sysclkinit();                  // 系统时钟
    task_view_init();              // 显示资源初始化
    task_control_init();           // 控制资源初始化
    task_finger_init();            // 指纹模块初始化(GPIO + LPUART1)
    LOS_KernelInit();              // 内核初始化

    creat_control_task();          // 控制任务:云端指令、状态上报
    creat_view_task();             // 显示任务:UI 绘制
    creat_finger_task();           // 指纹任务:识别/录入/删除业务
    LOS_Start();                   // 交出 CPU
    while (1) { }
}

指纹任务的创建参数值得留意——优先级 1(高),栈只有 0x700 字节:

task_init_param.usTaskPrio   = 1;
task_init_param.pcName       = "finger_task";
task_init_param.uwStackSize  = 0x700;

优先级给高,是因为"手指已经按上来了"是用户正盯着设备的时刻,响应不能拖;栈给小,是因为任务函数本身局部变量不多,真正的大缓冲区(串口收发各 400 字节)放在了模块驱动层的静态全局区,不占任务栈。

二、任务间怎么说话:标志位 + 共享状态的最小通讯

三个任务之间没有用队列,靠的是一组 volatile 语义的共享变量(定义在 task_inter.h 一侧):

变量生产者消费者作用
Scene.state指纹任务显示任务界面数据有更新,请求重绘
g_ViewAS608State指纹任务显示任务最近一次识别成功/失败
Scene.event_sendflag指纹任务控制任务有事件需要上报云端
g_pltSendData指纹任务控制任务打卡人姓名、时间等上报数据体
timebuf / time_flagRTC/控制任务指纹任务打卡时间戳与"时间已取到"信号

指纹识别成功后的一段代码把这套配合演得很完整:

ensure = PS_HighSpeedSearch(CharBuffer1, 0, 300, &seach);
if (ensure == 0x00) {
    memcpy(&g_pltSendData.I_punch_the_clock_name, namebuf, sizeof(namebuf)); // 谁打的卡
    memcpy(&g_pltSendData.I_punch_the_clock_time, timebuf, sizeof(timebuf)); // 什么时候
    repeattimetick = HAL_GetTick();          // 记录时间戳 → 兼作 2 秒防重复触发窗口
    Finger_Get_Time();                       // 请求刷新时间

    while (0 == time_flag) {                 // 等时间就绪,2 秒看门狗兜底
        if ((HAL_GetTick() - wdttimetick) >= 2000) {
            printf("\r\n get time err");
            break;
        }
        LOS_TaskDelay(50);
    }
    Scene.state = 1;                         // 通知显示任务刷新
    g_ViewAS608State = 1;
    Beep_Ringing();                          // 蜂鸣器响 400ms(任务内短暂阻塞)
}

这套"标志位通讯"在裸机时代就被用烂了,放到 RTOS 下依然成立,但有个前提:单生产者-单消费者、且数据体足够小。一旦任务多了、消息结构复杂了,就该换事件/队列。实验工程规模刚好卡在标志位还撑得住的边界上——这也是读这份代码能学到的分寸感。

三、触摸检测:软件消抖的三次采样计数

指纹模块的 WAKEUP 引脚(PC6)在手指触摸时拉高,但机械接触的抖动和误碰是常态。工程里没有开外部中断,而是用了任务内轮询 + 计数消抖

static unsigned int cnt = 0;
static char press_state = 0, old_press_state = 0;

if (PS_Sta)  { if (cnt < 3) cnt++; }     // 检测到高电平,计数+1,封顶3
else         { if (cnt)      cnt--; }    // 低电平,计数-1,归零

if (0 == cnt)      press_state = 0;      // 判定未按下
else if (3 == cnt) press_state = 1;      // 连续3次高电平才判定按下

if ((1 == press_state) && (old_press_state != press_state)) {
    press_FR();                           // 边沿触发:只在"未按→按下"跳变时识别一次
}
old_press_state = press_state;

三个细节:

  1. 计数不是清零而是递减——短暂抖动不会立刻把"按住"误判成"松开",相当于迟滞比较器;
  2. 任务周期 10ms,3 次计数 ≈ 30ms 稳定窗口,典型机械/皮肤接触抖动都能滤掉;
  3. 边沿触发 + 2 秒冷却repeat_deteack() 检查距上次识别成功是否超 2 秒)——手指一直按着不会反复刷成功,同一根手指快速二次触摸也会被压掉,天然防"贴着传感器蹭开门"。

另外,任务入口处还有一段首次开机逻辑,直接把库清空:

if (first_flag) {
    first_flag = 0;
    Del_FR();          // 上电清空指纹库(实验板语义:每次开机从干净状态开始)
    LOS_TaskDelay(100);
}

这是实验工程和产品工程的典型差异:实验板每次开机清库方便反复练习;真产品肯定要把模板留在 FLASH 里、绝不能上电就删——读代码时要把"作者为什么这么写"和"这样写能不能上产品"分开看。

四、门禁动作:舵机、超时与状态回环

门禁业务(handle.c 版本)把"开门"建模成一个带自动回位的状态机。开门 = 舵机转动 + 记录时间戳 + 更新 UI + 上报云端;关门 = 反向一套动作:

void door_open_close_operation(door_status_t val)
{
    static door_status_t cur_status = DOOR_CLOSED;

    if ((val != DOOR_OPEN) && (val != DOOR_CLOSED)) return;  // 非法值拦截
    if (val == cur_status) return;                           // 幂等:状态不变直接返回

    if (val == DOOR_OPEN) {
        as608_set_door_status(DOOR_OPEN);
        Servo_Start();                    // 舵机转 → 门闩开
        open_time = HAL_GetTick();        // 记住开门时刻
        UI_update_door_info(DOOR_OPEN);
        ptl_send_door_status(DOOR_OPEN);  // 云端同步
    } else {
        as608_set_door_status(DOOR_CLOSED);
        Servo_Reset();
        UI_update_door_info(DOOR_CLOSED);
        ptl_send_door_status(DOOR_CLOSED);
    }
    cur_status = val;
}

主循环里配了一个无感的自动关门:

while (1) {
    if (Finger_press() == 0) {                       // 识别通过
        door_open_close_operation(DOOR_OPEN);
    }
    if (cur_time - open_time > 5000) {               // 开门满 5 秒
        if (as608_get_door_status() == DOOR_OPEN) {
            door_open_close_operation(DOOR_CLOSED);  // 自动关门
        }
    }
    LOS_TaskDelay(10);
}

这个函数值得学的三个防御性设计:非法值拦截、状态幂等(重复开门请求直接忽略)、回环闭环(谁开门谁负责让状态可逆)。门禁类业务最怕状态漂移——UI 显示开着、舵机实际关着——把所有状态变更收口到一个函数里,是治这种病的药方。

五、录入交互:阻塞式流程放在哪个任务里才不害别人

录入指纹(Add_FR)是个天然的阻塞流程:提示 → 等手指 → 采图 → 再等 → 比对 → 合并 → 存储,中途任何一步失败还要回退重试,全程可能十几秒。如果它跑在显示任务里,屏幕会冻结;跑在控制任务里,云端上报会断。

工程的答案:录入/删除挂在指纹任务里,由按键触发,靠 RTOS 的任务切换把"阻塞"局部化——

static void AS_608_Function(void)
{
    key_num = keyscan();
    if (key_num == KEY1) { Add_FR(); }     // 录指纹:阻塞十几秒,但只阻塞本任务
    if (key_num == KEY2) { Del_FR(); }     // 删指纹
    if (key_num == KEY4) {                 // 读剩余容量
        PS_ValidTempleteNum(&ValidN);
    }
    Finger_press();                        // 顺带做触摸识别
}

录入期间显示任务照常刷新(Scene.state 置位的重绘请求不会丢),控制任务照常心跳。代价是指纹任务在这十几秒里无法响应新触摸——但这恰恰符合业务直觉:你正在录指纹,没人在此刻期待门禁响应

注意 Add_FR 内部每次状态切换之间都插了 LOS_TaskDelay(800),这不只是为了等人按手指——PS_GetImage 等指令本身靠 JudgeStr 最多自旋等 2 秒,穿插的主动让出 CPU(TaskDelay)保证了同优先级链路上其他任务不被饿死。在 RTOS 里写"等待",永远优先考虑让出,而不是空转。

六、云端链路:识别结果如何变成平台上的打卡记录

数据面的最后一段路走的是 ESP8285 WiFi 模组 + 华为云 OC 协议栈(Components/esp8285Components/oc)。指纹任务只负责把数据放进 g_pltSendData 并举起 Scene.event_sendflag,真正的 MQTT 组包、JSON 序列化(cJSON)、HMAC-SHA256 安全认证(sha256.cHMACSHA.c)都由控制任务完成。

[指纹任务] 识别成功 → 填 g_pltSendData{姓名, 时间} → event_sendflag=1
                                                       │
[控制任务] 轮询发现 flag → cJSON 组包 → ESP8285 AT 指令 → MQTT 发布
                                                       │
                                              云平台显示打卡记录

分层的好处在这个实验里看得非常具体:指纹任务对"网络"零依赖——WiFi 没配好、MQTT 掉线,识别开门照样工作,只是云端少条记录;反过来调云端的网络问题,也完全不碰指纹业务代码。把"本地确定性行为"(开门)和"云端尽力而为行为"(上报)解耦,是物联网设备设计的通用范式。

七、如果把它做成真产品

实验版离产品之间隔着几道明摆着的坎,顺手列一份改造清单:

维度实验版现状产品化改造方向
指纹库生命周期上电 Del_FR() 清库模板持久化,上电只校验不清除
身份-模板映射命中 pageID 即视为固定用户建立用户注册表,pageID 动态绑定
标志位通讯全局变量直传事件/消息队列,解除耦合
开门安全识别即开,5 秒自动关增加防胁迫策略(如特定指位报警)、开门日志审计
时间来源RTC + 2 秒看门狗NTP 定期校时,打卡时间可信
上报可靠性单次发送本地缓存重传,弱网不丢打卡记录
触摸防伪光学模块加活体检测,或升级半导体电容指纹

八、小结

这个实验最有价值的地方,不是"会用 AS608",而是它演示了一个标准嵌入式业务的分层方法:驱动层管协议,业务任务管流程,标志位管协同,云端链路管延伸。LiteOS 在这里的作用不是炫技,而是让"长阻塞交互、实时动作、慢速 IO"三类异质工作互不拖累。

加上上篇的协议拆解,B05 的两层皮——怎么和模块说话、怎么组织业务——就都齐了。下一篇系列转向 B07 光强传感器实验:这次没有 UART 指令集,主角换成两条线(SCL/SDA)上的位级时序。


参考文件:B05 生物识别实验/ZHIoT_Sensor_AS608/(task_user/finger_task.c、task_user/handle.c、Hardware/AS_608/)、嵌入式/ATK-AS608指纹识别模块/AS60x指纹识别SOC用户手册V10.pdf

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐