拓冰建站拓冰建站
首页 / 资讯中心 / 正文

WT2003Hx B1指令:紧急语音插播与断点续播实战

做语音提示类产品的人早晚会撞上这么一个需求设备正慢条斯理地播着背景解说突然来了一个告警必须立刻把当前这条压下去把告警这句话喊出来喊完之后原来那条还得从被掐断的地方接着往下说。听起来简单真做起来能把你卡上一周。我最近用唯创的 WT2003Hx 系列语音芯片做了一版这套逻辑核心就是用它串口协议里的 B1 指令配合一套状态机和几个坑位排查把紧急语音中断与恢复播放这条链路跑通了。在 WT2003H-16S 上实测稳定端到端响应大约 40ms插播结束后的恢复点误差控制在一句话之内。这篇东西写给两类人一类是刚拿到 WT2003Hx 手册、还没搞明白 B1 到底怎么发包的嵌入式新手另一类是背景音已经播起来了但一插播就乱套要么不恢复、要么爆音、要么偶发死机的老手。帧结构、校验算法、状态机、时序计算、电源处理和一堆实测踩过的坑我会全部摊开讲代码都是能直接抄的完整版本。需要先说明一点不同型号、不同批次的手册在字节定义上可能略有出入下面这套是我实际跑通的版本动手前务必拿自己手上的型号手册核对一次命令字节和长度定义。1. 先把需求拆干净插播到底难在哪1.1 三个真实场景暴露的是同一类问题第一个场景是工业控制柜的语音提示。柜子在播当前温度 42 摄氏度运行正常这种长句突然超温了得马上喊超温报警请立即检查散热。喊完以后再回到刚才那句但它已经播到一半了不能重头来。第二个场景是共享设备。用户扫码后设备在讲操作步骤讲到第三步的时候旁边有人按了急停急停提示音必须插进来。用户的心理预期是我先听急停急停听完了第三步接着听。第三个场景是导览设备。背景讲解音频很长中途有按钮触发的点播。早期版本我用的是停掉背景音播完点播再重播背景音用户直接投诉说听了一段重复内容体验很差。这三个场景的共同点就一句话插播内容要能抢占音频通道插播结束后原内容要从断点续上。 它本质上是两个需求叠在一起一个是抢占一个是续播。很多人只做了抢占忘了续播结果就是上面说的那种投诉。1.2 为什么停止 重新播放这种土办法在工程上站不住最容易想到的方案是检测到紧急事件发停止指令播插播文件播完后重新发播放指令播背景音。这个方案我第一版就是这么写的跑通之后问题一堆。首先是丢上下文。 用户听到一半的内容被重置了长音频越明显。一条 30 秒的提示音播到 20 秒被打断回来又从第 0 秒开始等于白等了 20 秒。其次是切换延迟大。 停止指令要发一帧播放指令又要发一帧中间还有芯片内部的状态切换实测从决定插播到插播出声要 80ms 以上。紧急场景下这个延迟是能被人耳感知到的尤其是告警音慢半拍会显得设备很迟钝。再次是音质断裂。 停止和重启之间会有一个明显的静音凹坑配合功放的上下电时序很容易在断点处产生咔的一声。用户对爆音的敏感度远高于对音质的敏感度一次爆音就能让整个产品显得廉价。最后是并发处理无解。 如果插播期间又来一个更紧急的事件怎么办土办法里没有优先级的概念只能靠加标志位硬凑代码会迅速烂掉。1.3 B1 指令的能力边界它能做什么不能做什么B1 这条指令的存在意义就是把暂停当前、切到新内容、播完回填这三步收敛成芯片内部的一个原子动作。MCU 只要发一帧 7 个字节的指令剩下的事情芯片自己做。我实测下来它能做到的是插播内容立刻出声、插播期间原播放进度被芯片内部保存、插播播完后自动回到原来的位置继续。整个过程的断点衔接基本听不出来比 MCU 侧手动拼接要干净得多。但它的边界也要清楚。第一它不支持插播队列你连发两条 B1后一条大概率会顶掉前一条而不是排队播完。第二它不告诉你插播播完了B1 是单向指令没有回执除非你的型号支持应答帧那一定要开。第三插播音频和背景音频是分时复用同一个 DAC 通道不是真正意义上的混音所以做不出背景音压低继续播、告警音叠在上面的效果。如果你要的是那种电台式的闪避效果WT2003Hx 这套方案做不到得换带双通道混音的型号或者外挂一颗音频 DSP。搞清这三条边界后面的状态机设计思路就顺了。提示动手前先确认你的型号手册里 0xB1 这个命令字节的确切定义有些型号把插播叫插入播放有些叫指定文件插入命令字节也可能不是 B1。本文以 0xB1 为插播命令展开其余型号按其手册替换即可帧结构和状态机逻辑完全通用。2. WT2003Hx 的串口协议与 B1 指令帧拆解2.1 通信接口怎么选标准 UART、一线串口还是 IO 触发WT2003Hx 一般提供三种控制方式选错了后面全是麻烦。标准 UART 两线TX/RX是我最推荐的。 波特率 9600 到 115200 都支持时序宽松用 MCU 的硬件串口发就行代码量最小调试起来也最直观——拿个 USB 转串口直接怼上去就能手动发帧验证。唯一的代价是多占一个 IO。一线串口 只占一个 IO但位宽定义在不同型号手册里差别挺大有的是用高电平占位周期的比例来编码 0 和 1有的用的是固定脉宽区分。我一般不推荐新手碰因为一旦时序偏了芯片就是完全不响应你还看不出是哪一位错了。如果非要省这个 IO建议先用示波器量一下官方 demo 板的实际波形把位宽抄下来再写代码。IO 触发模式 是速度最快的方案。把要插播的音频预先绑定到某个触发脚上拉低或拉高一段时间就播。省掉了整条串口链路响应能压到 10ms 以内。缺点是不灵活文件换不了、组合不了。我现在的做法是混合 高频、必须最快的告警用 IO 触发其余用 UART 发 B1。这个组合拳在实际项目里非常好用。2.2 帧结构逐字节拆解WT2003Hx 的串口帧是一个很典型的帧头 长度 命令 参数 校验 帧尾结构。我手上这版的字段定义如下字段字节数取值示例说明起始码10x7E固定帧的起点长度10x04从命令字节到校验字节的总字节数命令10xB1插播指令参数高10x00插播文件编号高字节参数低10x05插播文件编号低字节校验10xBA长度到参数逐字节累加取低 8 位结束码10xEF固定帧的终点整帧 7 个字节。长度字段这里填 0x04是因为命令 1 参数 2 校验 1 4。注意这个定义是有坑的有些型号手册里的长度不含校验字节那就得填 0x03。这两个版本我都见过发出去没反应的时候第一个要试的就是把长度改一改。为什么要设计校验因为串口线上是可能有干扰的尤其是在电机、继电器旁边跑的设备。如果一帧被干扰错了芯片可能会执行一条完全意想不到的指令比如把音量拉满、或者去播一个不存在的文件号导致卡死。加一字节累加校验成本几乎为零收益很大。2.3 B1 指令的参数该怎么填参数就是要插播的文件编号两字节高位在前。文件编号是你烧录音频到外挂 SPI Flash 时分配的序号通常从 0 或者 1 开始。举个例子要插播 5 号文件长度 0x04命令 0xB1参数 0x00 0x05校验 0x04 0xB1 0x00 0x05 0xBA取低 8 位完整帧0x7E 0x04 0xB1 0x00 0x05 0xBA 0xEF再举个例子插播 18 号文件0x12校验 0x04 0xB1 0x00 0x12 0xC7完整帧0x7E 0x04 0xB1 0x00 0x12 0xC7 0xEF两个例子算出来的校验值不一样这就是校验的意义。你可以拿这两组数据当自测用例代码写完之后先算一遍对上号了再去连硬件。注意文件编号一定要先确认有效范围。发一个超出 Flash 实际容量的文件号部分型号会直接卡在等待状态BUSY 脚再也不翻转表现就是设备死了。这类问题非常难查因为它看起来像硬件故障。2.4 校验和的两种算法与验证方法上面用的是累加和取低 8 位这是最常见的一种。另一种是累加和取反加一也就是补码形式两者结果不同别搞混。#include stdint.h /* 累加和取低 8 位 */ uint8_t wt_checksum_sum(const uint8_t *buf, uint8_t len) { uint16_t sum 0; for (uint8_t i 0; i len; i) { sum buf[i]; } return (uint8_t)(sum 0xFF); } /* 累加和取反加一 */ uint8_t wt_checksum_neg(const uint8_t *buf, uint8_t len) { uint16_t sum 0; for (uint8_t i 0; i len; i) { sum buf[i]; } return (uint8_t)((~sum 1) 0xFF); }怎么验证哪个是对的最省事的办法是拿厂家提供的上位机调试工具手动发一条指令用逻辑分析仪或者带协议解析的示波器抓一下 TX 线上的实际波形把厂家工具发出来的字节流抄下来跟你代码算出来的比对。这五分钟的功夫能帮你省掉后面两天的瞎猜。还有一个更快的土办法用 USB 转串口模块直接把这两种校验的帧都发一遍哪种能让芯片出声哪种就是对的。前提是你已经确认接线和波特率没问题。3. MCU 端完整实现从发包到状态机3.1 硬件连接与最小系统清单先把最小系统列清楚接线错误是不发声音这类问题的第一大来源。连接项WT2003Hx 侧MCU 侧备注串口接收RXUART_TX交叉连接串口发送TXUART_RX只在需要回执时才接忙信号BUSY任意 GPIO带中断强烈建议接用于判断播放状态电源VCC3.3V 或 5V按型号手册别超压地GNDGND必须共地音频输出DAC/PWM功放或喇叭直推喇叭注意功率BUSY 脚是我强烈建议接上的。它的作用是告诉 MCU我现在在忙。虽然 B1 插播没有回执但 BUSY 至少能让你知道芯片有没有在工作状态是排查问题的第一手信息。接线时有两个细节要注意。串口线尽量短超过 10cm 又在强干扰环境里建议串一个 100Ω 的电阻并加对地小电容做缓冲。另外如果 MCU 是 5V 系统而芯片是 3.3VTX 线上最好加个电平转换或者至少串个电阻分压别硬怼。3.2 发送 B1 插播帧的完整代码下面这段是可以直接编译的完整实现包含了帧组装、校验和发送。#include stdint.h #include stdbool.h #define WT_FRAME_HEAD 0x7E #define WT_FRAME_TAIL 0xEF #define WT_CMD_INSERT 0xB1 /* 由你的 HAL 实现发送 len 字节 */ extern void uart_write(const uint8_t *data, uint16_t len); /* 由你的 HAL 实现返回毫秒级系统时间 */ extern uint32_t millis(void); static uint8_t wt_calc_sum(const uint8_t *buf, uint16_t len) { uint16_t sum 0; for (uint16_t i 0; i len; i) { sum buf[i]; } return (uint8_t)(sum 0xFF); } /* 发送 B1 插播指令file 为插播文件编号 */ bool wt_send_insert(uint16_t file) { uint8_t frame[7]; uint16_t idx 0; frame[idx] WT_FRAME_HEAD; frame[idx] 0x04; /* 长度命令2参数校验 */ frame[idx] WT_CMD_INSERT; /* 0xB1 */ frame[idx] (uint8_t)(file 8); /* 文件编号高字节 */ frame[idx] (uint8_t)(file 0xFF); /* 文件编号低字节 */ frame[idx] wt_calc_sum(frame[1], 4); /* 校验覆盖长度~参数 */ frame[idx] WT_FRAME_TAIL; uart_write(frame, idx); return true; }调用就是wt_send_insert(5);一行搞定。但工程上不能这么裸奔还得做三件事加发送间隔保护、加超时重发、加错误计数。发送间隔保护很好理解。连续发帧的时候一定要保证两帧之间至少间隔 5ms保险起见留 10ms。我用一个静态变量记最后一次发送的时间戳进来先判断间隔够不够不够就延后或者直接拒绝本次请求。static uint32_t s_last_tx_ms 0; #define WT_MIN_FRAME_GAP_MS 10 bool wt_send_insert_safe(uint16_t file) { uint32_t now millis(); if ((now - s_last_tx_ms) WT_MIN_FRAME_GAP_MS) { return false; /* 间隔不足让调用方稍后重试 */ } s_last_tx_ms now; return wt_send_insert(file); }超时重发是另一层保护。B1 没有回执怎么知道芯片收到了我用的办法是看副作用发完 B1 之后启动一个定时器如果 200ms 内 BUSY 脚状态一点变化都没有就判定这次发送失败重发一次。如果连重发两次都没动静就置一个故障标志通过指示灯或者日志报出来。这套机制在电磁环境恶劣的现场设备上救过我好几次。3.3 插播状态机的设计与实现这是整个方案的心脏。没有状态机代码会长成一堆 if-else 的意大利面而且一定会漏掉某个边界情况。我用五个状态状态含义VS_IDLE空闲什么都没播VS_BG_PLAYING背景音正在播VS_INSERTING插播中VS_WAIT_RESUME插播应已结束等待背景音恢复VS_FAULT异常需要重初始化状态迁移的规则是VS_BG_PLAYING收到插播请求 → 发 B1 → 进VS_INSERTING插播计时到期 → 进VS_WAIT_RESUME确认 BUSY 正常 → 回VS_BG_PLAYING。难点在最后一步怎么知道插播播完了。B1 没有回执BUSY 脚在整条链路里一直是忙的状态没法区分是背景音在忙还是插播在忙。我的做法是用插播音频的实际时长来定时。所有插播音频都是固定文件时长是已知的在 MCU 里做一张时长表typedef enum { VS_IDLE 0, VS_BG_PLAYING, VS_INSERTING, VS_WAIT_RESUME, VS_FAULT } vs_state_t; static vs_state_t s_state VS_IDLE; static uint16_t s_insert_file 0; static uint32_t s_insert_start 0; static uint8_t s_resume_probe 0; /* 插播文件时长表毫秒编号 0 占位不用其余按实测填写 */ static const uint16_t s_insert_dur[] { 0, 1850, /* 1 号一级告警 */ 960, /* 2 号操作提示 */ 2400, /* 3 号故障说明 */ }; #define INSERT_DUR_MAX ((uint16_t)(sizeof(s_insert_dur)/sizeof(s_insert_dur[0]) - 1)) #define RESUME_MARGIN_MS 150 /* 留出芯片切换和恢复的余量 */ bool voice_request_insert(uint16_t file) { if (file 0 || file INSERT_DUR_MAX) { return false; /* 编号越界直接拒绝防止芯片卡死 */ } if (s_state VS_INSERTING) { return false; /* 已经在插播不排队由上层裁决 */ } if (!wt_send_insert_safe(file)) { return false; } s_insert_file file; s_insert_start millis(); s_resume_probe 0; s_state VS_INSERTING; return true; } void voice_tick(uint32_t now) { switch (s_state) { case VS_INSERTING: if ((now - s_insert_start) (uint32_t)(s_insert_dur[s_insert_file] RESUME_MARGIN_MS)) { s_state VS_WAIT_RESUME; s_resume_probe 0; } break; case VS_WAIT_RESUME: /* 再等 100ms用 BUSY 脚复核一次是否真的在播 */ if (s_resume_probe 10) { if (voice_busy_read()) { s_state VS_BG_PLAYING; /* 背景音已恢复 */ } else { s_state VS_IDLE; /* 背景音已结束或有异常 */ } } break; default: break; } }voice_busy_read()就是读一下 BUSY 脚的电平。这个复核步骤很有必要因为芯片内部恢复播放可能有几毫秒的抖动如果直接在时长到期的那一刻就认为恢复了偶尔会误判。3.4 时序参数计算与端到端延迟预算做紧急告警类功能延迟是要算清楚的不能凭感觉说挺快的。先算单帧发送时间。波特率 96008 数据位、无校验、1 停止位每字节 10 位10 / 9600 ≈ 1.042ms。一帧 7 字节就是7 × 1.042 ≈ 7.3ms。如果换成 115200每字节10 / 115200 ≈ 0.0868ms一帧 7 字节只要0.61ms几乎可以忽略。所以只要你的 MCU 和走线扛得住波特率尽量往高了设。再算端到端延迟环节典型耗时MCU 检测事件到组帧完成 1ms串口发送 7 字节 96007.3ms芯片解析指令并切换音源10 ~ 20ms读 Flash 首帧并解码5 ~ 15msDAC 输出经功放出声1 ~ 3ms合计约 25 ~ 45ms这个数字是可以接受的人能感知到的音频延迟大概在 60ms 以上。如果你要压到 20ms 以内只有两条路提波特率加上用 IO 触发模式。还有一个容易忽略的参数是帧间隔。我给的最小值是 10ms。如果连发指令间隔不够有些型号会把第二帧当成第一帧的续传数据直接丢弃或者解析错乱。拿到新板子的时候我会用一个循环连发 100 帧 B1看有没有丢帧以此确认最小安全间隔。4. 优先级与多级插播真正产品化的那一层4.1 用优先级队列管住插播风暴单条插播跑通了产品化还差一步多个事件同时来怎么办。我的做法是在 B1 之上再包一层优先级仲裁。先把插播内容分级优先级典型内容是否可被打断打断后是否恢复P0安全告警、急停否不恢复直接进入新告警P1操作反馈、错误提示仅 P0 可打断恢复P2背景解说、状态播报P0、P1 均可打断恢复规则的核心是P0 不允许被任何东西打断而且 P0 打断 P1/P2 之后不恢复被打断的内容。 因为安全提示的语境已经变了再回去播当前温度 42 摄氏度没有意义反而会干扰用户判断。队列的实现很简单固定深度的循环队列每个元素存文件号和优先级。入队的时候如果队列满就丢弃优先级最低的那一条如果新来的优先级比队尾还低直接丢新来的。出队只在VS_IDLE或VS_BG_PLAYING状态时进行。#define Q_DEPTH 4 typedef struct { uint16_t file; uint8_t prio; } ireq_t; static ireq_t s_q[Q_DEPTH]; static uint8_t s_q_cnt 0; /* 入队队满时淘汰优先级最低的一条 */ void voice_enqueue(uint16_t file, uint8_t prio) { if (s_q_cnt Q_DEPTH) { s_q[s_q_cnt].file file; s_q[s_q_cnt].prio prio; s_q_cnt; return; } uint8_t worst 0; for (uint8_t i 1; i Q_DEPTH; i) { if (s_q[i].prio s_q[worst].prio) worst i; /* 数值越大优先级越低 */ } if (prio s_q[worst].prio) { s_q[worst].file file; s_q[worst].prio prio; } }这段逻辑看起来朴素但它把插播风暴这个最容易出问题的场景摁住了。我见过一个项目因为没做这层报警连续触发的时候芯片疯了一样反复切歌最后直接卡死。4.2 断点续播的降级方案分段拼播有些型号的 B1 只做插入不保证回到原位或者固件版本较老恢复点总是会往回跳一点。遇到这种情况我用的降级方案是分段拼播。具体做法是把一条 30 秒的长解说切成 30 个 1 秒的小片段编号连续。MCU 维护一个当前段号每段播完就播下一段。插播来了发 B1 播告警告警结束后从下一段开始播而不是从头。代价是有段间间隙。能不能听出来取决于芯片换文件的切换速度。实测 WT2003Hx 播相邻编号文件的间隙在 10ms 以内人耳基本听不出来只有在极其安静的环境下仔细听才会发现一点点不连贯。分段还有个额外好处粒度细了插播的响应点更准。最坏情况下用户只损失不到 1 秒的内容而不是整条音频。代价也很实在Flash 容量和烧录时间都上去了。切割的时候要保证片段首尾对齐别在字的中间切开否则会有明显的截断感。我一般用音频编辑软件手动切虽然慢但质量可控。用脚本自动切的话一定要先听几段验证效果。5. 实测踩坑与排查速查表5.1 常见故障速查表这张表是我这几个月攒下来的遇到问题先照着过一遍能省掉大半时间。现象可能原因排查动作发 B1 完全没反应长度字段定义不对 / 校验算法不对换 0x03 和 0x04 两种长度各试一次两种校验各试一次发 B1 完全没反应TX/RX 没交叉 / 没共地用万用表量通断用示波器看 TX 线有没有波形插播能响背景音不恢复芯片固件不支持自动恢复换成暂停记录段号续播的降级方案插播能响但恢复点往回跳时长表填短了状态机提前切状态把时长表的余量从 150ms 加到 300ms 再试插播声音断续、卡顿电源跌落功放瞬态电流拉垮电压加大滤波电容见下一节插播后有明显爆音音频切换时的直流跳变DAC 输出加 RC 低通和隔直电容连续插播变慢甚至卡死帧间隔不足 / 队列没做淘汰帧间隔提到 10ms 以上加优先级队列BUSY 脚一直不变文件编号越界芯片卡在等待检查文件编号是否在有效范围偶发出错但复现不了串口线受干扰缩短走线加缓冲电阻打开校验严格模式5.2 插播后不恢复的三个典型原因这个症状太常见了单独拎出来说。第一个原因是时长表填得太短。 芯片播插播音频的实际耗时往往比音频文件本身标称的时长要长一点因为里面有解码启动、DAC 切换的开销。如果你按标称时长设定时器状态机会在插播还没播完的时候就认为结束了然后去复核 BUSY——这时候 BUSY 当然是忙的逻辑就乱了。我的经验值是在标称时长上加 150 到 300ms 的余量宁可多一点。第二个原因是状态机没有处理插播期间又来请求。 如果插播中又发了一次 B1有的型号会把它当成新的插入第二次插播结束后第一次的恢复点就丢了。解决办法就是在voice_request_insert里挡住VS_INSERTING状态下的请求交给优先级队列处理。第三个原因是 BUSY 脚的极性搞反了。 有的型号播放时 BUSY 输出高有的是低手册上写的是哪一种一定要看清楚。搞反了之后你的播放中判断全部反过来表现就是时好时坏特别迷惑人。我一般会在初始化的时候让芯片播一条短提示音同时用万用表量 BUSY 脚的对地电压确认一下极性再写代码。5.3 爆音、电流声与电源处理音频类项目里电源和爆音这两个问题占了故障的一半以上而且都和软件没太大关系但用户会认为是软件的问题。先说电流。WT2003Hx 直推 8Ω 喇叭的时候峰值电流能到 200mA 以上。如果你的电源是个 LDO输出能力只有 100mA那就等着听爆音吧。我的做法是芯片的 VCC 引脚旁边紧贴放一颗 100μF 的电解电容加一颗 0.1μF 的瓷片电容越大越好但要注意体积。这颗电容的作用就是在瞬态电流拉起来的时候顶一下别让电压掉下去。如果用的是外挂功放比如常见的 8002 之类还要注意功放的使能脚时序。上电的时候如果芯片音频输出已经有效而功放使能还没稳定就会啪一声。解决办法是把功放使能放在芯片初始化之后拉高掉电时反过来先关功放再断芯片电源。再说爆音本身。音源切换时的爆音本质是 DAC 输出端的直流电平发生跳变。硬件上加一个 RC 低通比如 1kΩ 加 10nF再加一颗隔直电容能压掉大部分。软件上看看手册有没有音量渐变或者淡入淡出的指令有的话在插播前后各加一小段渐变效果会更自然。还有个很容易忽略的点地和屏蔽。 音频地一定要和数字地分开走线最后单点汇合。我见过一个板子音频线跟电机的驱动线捆在一起走结果插播的时候能听到电机换向的啸叫声。改走线之后问题直接消失。5.4 几条不怎么写在手册里的经验第一条插播音频本身要做得比背景音响。 B1 插播是分时复用的没法做动态音量闪避ducking所以唯一的办法就是在录音阶段把插播文件本身的响度做上去。我的做法是插播音频的归一化峰值比背景音高 3 到 6dB。这样插播一出来人耳立刻能感觉到这件事更重要体验差别很大。第二条插播文件越短越好。 紧急提示控制在 2 秒以内超过 3 秒用户就开始不耐烦了。如果内容确实多宁可分成两条短提示也不要一条长告警。短文件还有个好处时长表的余量占比小状态机的时序判断更准。第三条务必留一个静默重启的兜底路径。 不管代码写得多严谨现场总会有奇奇怪怪的干扰。我在VS_FAULT状态里做了一件事连续 3 次插播失败之后发一条停止指令等 500ms再从当前段号重新开始播。这个兜底逻辑在整个项目生命周期里救过两次现场代价几乎为零。第四条用日志把每一次插播记录下来。 记录时间戳、文件编号、优先级、触发时的状态。现场出问题的时候这几行日志能让你在五分钟内定位到是软件逻辑问题还是硬件问题比拿示波器在现场蹲半天高效得多。存储空间不够的话存在 RAM 里循环覆盖通过串口导出就行。第五条也是我最想强调的一条一定要拿真实的告警场景测一遍。 我在实验室里用按键触发测了几百次都正常结果到现场用真实的传感器信号触发发现传感器的抖动导致 200ms 内连续来了 5 次中断请求。这就是优先级队列和去抖动逻辑真正体现价值的地方。软件消抖我加的是 150ms 的窗口窗口内的重复请求只认第一条这个参数是根据现场传感器的实际抖动特性调出来的。这套方案我在三个项目上复用过了从工业控制柜到共享设备逻辑基本没改改的只是文件编号和时长表。真正需要花心思的地方永远是那些看起来不起眼的边界情况插播没播完又来一个怎么办、串口被干扰了怎么办、电源掉了一下怎么办。这些处理好了功能才算是真的稳。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门