按键检测与事件分发 (Button Detection & Event Dispatch)功能:

通过轮询机制监测 GPIO 电平变化,利用时间戳差值算法,精准区分短按、长按及长按释放三种事件,并实现软件消抖,确保人机交互的可靠性。

逻辑:

  1. 软件消抖与边沿检测:
    • 核心机制:在检测到按键按下(低电平)时,不立即响应,而是先执行 vTaskDelay(10),再次读取电平。
    • 原理:机械按键在闭合瞬间会产生 5ms~10ms 的机械抖动。通过 10ms 的延时再次确认,可以有效滤除抖动干扰,防止一次按压被误判为多次触发。只有两次读取均为低电平,才判定为有效的“按下沿”。
  1. 基于时间戳的时长判别:
    • 核心机制:利用 millis() 获取系统运行时间,在按键按下时记录 clicktime,在按键抬起或保持时计算 millis() - clicktime。
    • 原理:
      • 短按判定:当检测到按键抬起(上升沿)时,计算持续时间。若小于 SHORT_PRESS_TIME (1000ms),判定为短按。
      • 长按判定:在按键保持按下的过程中,不断轮询持续时间。一旦超过阈值,立即触发长按事件。
  1. 长按事件的“单次触发”保护:
    • 核心机制:引入 longPressIsStart 标志位。
    • 原理:长按检测逻辑位于按键保持的循环中。如果没有这个标志位,只要用户按住不放,每 1ms 轮询一次都会触发一次 key_long_click_handle(),导致功能重复执行(例如音量疯狂加)。该标志位确保长按事件在按住期间只触发一次,直到按键释放复位。
  1. 长按释放检测:
    • 核心机制:在按键抬起的检测逻辑中,增加时长判断。
    • 原理:如果按键抬起时,持续时间已经超过了长按阈值,说明用户刚刚结束了一次长按操作。此时调用 key_long_click_free_handle(),可用于实现“长按确认,松手执行”的高级交互逻辑。

逻辑是长时间按住,触发一次长按,长时间不按住,分别处理长按和短按(以以秒为单位)

多任务系统架构与业务逻辑实现 (Multi-tasking System Architecture & Business Logic Implementation)功能:

基于 FreeRTOS 构建了一个分层、实时的打印控制系统。通过定义三个不同优先级的任务(打印、上报、按键),实现了业务逻辑的解耦与并发处理。系统利用状态机管理打印流程,通过硬件抽象层(HAL)处理传感器数据,并采用“水仓模型”优化数据流,确保了打印的流畅性、状态上报的及时性以及人机交互的稳定性。

逻辑:

  1. 高优先级打印任务 (High-Priority Printing Task):
    • 核心机制:task_printer (优先级 2) 以 1ms 的极高频率运行,内部集成蓝牙初始化与打印状态机。
    • 原理:
      • 流控策略(水仓模型):代码通过 #ifdef 提供了灵活的启动策略。默认策略采用“蓄满即打”逻辑,即当蓝牙接收缓冲区数据量超过阈值(get_ble_rx_leftline() > 200)时才触发打印。这种机制有效平滑了无线传输的波动,防止因数据包到达不连续导致的打印卡顿。
      • 状态流转:严格遵循 INIT -> START -> FINISH 的状态流转。只有当打印机处于空闲状态时才允许切换至打印状态,确保硬件动作的原子性。
      • 实时维护:在循环中调用 ble_status_data_clean(),及时清理串口缓冲区,防止垃圾数据干扰指令解析。
  1. 中优先级上报任务 (Medium-Priority Reporting Task):
    • 核心机制:task_report (优先级 1) 以 100ms 为周期运行,负责系统状态的维护与同步。
    • 原理:
      • 定时心跳:通过 get_state_timeout() 检查时间片,调用 read_all_hal() 刷新传感器数据(电量、温度等)。
      • 事件驱动上报:除了定时上报,还引入了中断驱动机制。当 read_paper_irq_need_report_status() 检测到缺纸中断时,立即触发 ble_report(),确保手机端能毫秒级响应硬件故障,无需等待下一个 100ms 周期。
  1. 低优先级按键任务 (Low-Priority Button Task):
    • 核心机制:task_button (优先级 0) 以 20ms 为周期运行,调用 key_check_run() 进行非阻塞式按键检测。
    • 原理:
      • 功能分离:短按触发测试模式(printer_test 标志位);长按用于控制走纸(电机启停)。
      • 安全互锁:在 key_long_click_handle 中实施了严格的状态检查。只有在“不缺纸”且“打印机空闲”的双重条件下才允许电机动作,否则触发声光报警(run_beep, run_led),防止误操作损坏设备。
      • 防抖与去重:利用 is_long_click_start 标志位,确保长按事件在按住期间只触发一次,避免重复执行。
  1. 系统初始化与调度 (System Initialization & Scheduling):
    • 核心机制:init_task 函数负责构建整个系统的运行环境。
    • 原理:
      • 资源预分配:在创建任务前,依次初始化设备状态、定时器、HAL 层、消息队列及外设。
      • 优先级分配:根据任务的实时性要求,科学分配优先级(打印 2 > 上报 1 > 按键 0)。
      • 栈空间管理:为打印任务分配了更大的栈空间(256),以容纳复杂的打印逻辑和蓝牙协议栈开销,而常规任务分配 128 即可,体现了对内存资源的精细化管理。

系统调用任务关系理解:

🎬 任务运行“微电影”:前 25ms 的生死时速

我们设定三个角色:

  • 👑 国王 (task_printer, Prio 2, 1ms周期):最霸道,醒着的时候别人不能动。
  • 👮 警察 (task_report, Prio 1, 100ms周期):地位中等,只有国王睡觉时才能干活。
  • 🧹 清洁工 (task_button, Prio 0, 20ms周期):地位最低,国王和警察都不在时,才轮到他。

第一幕:0ms - 启动时刻

  • 0.00ms:系统启动。调度器一看,三个任务都想干活。
    • 调度决策:选优先级最高的 task_printer。
    • 动作:task_printer 开始处理打印数据。
  • 0.05ms:task_printer 干完活了(假设处理很快),调用 vTaskDelay(1)。
    • 状态:task_printer 去睡觉了,预约 1.00ms 醒来。
    • 调度决策:国王睡了。剩下警察(Prio 1)和清洁工(Prio 0)。选 task_report。
  • 0.10ms:task_report 开始汇报状态,调用 vTaskDelay(100)。
    • 状态:task_report 去睡觉了,预约 100.00ms 醒来。
    • 调度决策:警察也睡了。只剩 task_button。
  • 0.15ms:task_button 终于拿到 CPU 了!检测一下按键,调用 vTaskDelay(20)。
    • 状态:task_button 去睡觉了,预约 20.00ms 醒来。
    • 调度决策:大家都睡了!进入空闲任务 (Idle Task),CPU 休息。

第二幕:1ms - 国王的苏醒与打断

  • 1.00ms:SysTick 中断! task_printer 的闹钟响了。
    • 状态:task_printer 醒来,变成“就绪态”。
    • 调度决策:此时 CPU 正在空闲(或者假设刚才 task_button 还没睡),调度器发现 task_printer (Prio 2) 醒了。
    • 动作:抢占! CPU 立刻运行 task_printer。
  • 1.05ms:task_printer 处理完这一帧的数据,再次 vTaskDelay(1)。
    • 状态:task_printer 睡去,预约 2.00ms 醒来。
    • 调度决策:国王又睡了。此时 task_report 和 task_button 还在睡。CPU 再次空闲。

第三幕:20ms - 清洁工的短暂春天

  • 2.00ms ~ 19.05ms:
    • 这期间,task_printer 像节拍器一样,每 1ms 醒来一次,干活 0.05ms,然后睡去。
    • 关键点:因为 task_report (100ms) 和 task_button (20ms) 都在长眠,所以 task_printer 醒来时,CPU 是空闲的,它直接运行,不需要打断任何人。
  • 20.00ms:SysTick 中断! task_button 的闹钟响了。
    • 状态:task_button 醒来。
    • 调度决策:此时 task_printer 刚在 20.00ms 醒来(假设它也是整毫秒对齐)。
    • 冲突:task_button (Prio 0) 想运行,但 task_printer (Prio 2) 也在运行。
    • 结果:task_button 只能干瞪眼。它虽然醒了,但 CPU 被国王霸占着。
  • 20.05ms:task_printer 干完活了,睡去(预约 21.00ms)。
    • 调度决策:国王睡了。此时 task_button 是就绪态(它 20.00ms 就醒了,等了 0.05ms)。
    • 动作:CPU 终于切换给 task_button。
  • 20.10ms:task_button 赶紧检测按键,然后 vTaskDelay(20)。
    • 状态:task_button 睡去,预约 40.00ms 醒来。
    • 调度决策:又没人了,CPU 空闲。

第四幕:100ms - 警察的巡查

  • 100.00ms:SysTick 中断!
    • task_printer 醒来(Prio 2)。
    • task_report 醒来(Prio 1)。
    • task_button 早就在 80ms、100ms 醒来过又睡了。
  • 100.00ms - 100.05ms:task_printer 抢占 CPU,干活,睡觉。
  • 100.05ms - 100.10ms:task_printer 睡了。调度器发现 task_report (Prio 1) 醒了。
    • 动作:运行 task_report。它汇报完状态,然后 vTaskDelay(100),睡到 200ms。
  • 100.10ms 之后:task_button 如果有机会醒来,才能运行。

📌 总结:你的系统运行规律

通过这个流程,你可以看到三个明显的特征:

  1. 高频心跳 (task_printer):

它像一个高频脉冲,每 1ms 刺破一次系统的宁静。无论其他任务在干嘛,它只要醒了,就能立刻拿到 CPU。这保证了你的打印机绝对不会因为软件卡顿而丢步或断流。

  1. 碎片化执行 (task_button & task_report):

低优先级任务的时间是碎片化的。它们只能利用 

task_printer 睡觉的那 0.95ms 空隙来运行。

    • 好消息:你的 task_button 逻辑很简单(读 GPIO),执行时间极短(微秒级),所以哪怕只有 0.95ms 的空隙,也足够它跑完几百次了。
    • 坏消息:如果你的 task_report 里有 printf 这种超级慢的函数(可能耗时几毫秒),它就会拖死系统。因为它一旦开始运行,task_printer 在下一秒醒来时虽然能抢占它,但频繁的上下文切换会消耗大量资源。
  1. 完美的“错峰出行”:

你的设计里,

task_report (100ms) 和 

task_button (20ms) 的休眠时间都远大于 

task_printer (1ms)。

这意味着:绝大多数时间,CPU 要么在给 task_printer 服务,要么就在空闲(Idle)。 这是一个非常健康、负载很低的实时系统状态。

两种数据接收方式:

一种是边发送边打印,通过while循环判断get_ble_rx_leftline()>0,来判断是否队列中还有数据,直到get_ble_rx_leftline()=0,打印最后一行完,进入 if (move_and_start_std(true, ALL_STB_NUM)),结束打印状态。

另一种是接收数据行大于200,才开始打印,不过这个程序使用的是第一种流式打印。

void handle_printer_start_condition(device_state_t *pdevice) 
{
    bool should_start = false;
#ifdef START_PRINTER_WHEN_FINISH_RAED
    should_start = pdevice->read_ble_finish;
#else
    should_start = (get_ble_rx_leftline() > 200);
#endif

    if (should_start && (pdevice->printer_state == PRINTER_STATUS_FINISH || 
                        pdevice->printer_state == PRINTER_STATUS_INIT))
    {
        // 统一的状态设置和硬件操作
    }
}
 

void handle_printer_start_condition(device_state_t *pdevice) 
{
    bool should_start = false;
#ifdef START_PRINTER_WHEN_FINISH_RAED
    should_start = pdevice->read_ble_finish;
#else
    should_start = (get_ble_rx_leftline() > 200);
#endif

    if (should_start && (pdevice->printer_state == PRINTER_STATUS_FINISH || 
                        pdevice->printer_state == PRINTER_STATUS_INIT))
    {
        // 统一的状态设置和硬件操作
    }
}
 

Logo

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

更多推荐