设备正在播一句长长的操作指引,突然检测到一个故障,你要它立刻喊一句"检测到异常,请立即停止操作"。这句紧急提示播完,原来那句指引从被打断的地方接着往下说,前面播过的部分不会重来一遍。
WT2003HX 的 B1 插播指令就是干这个的。协议看着简单,七个字节的事,但真在板子上跑通,有几处容易卡住。这篇文章按实际开发的顺序讲一遍。
插播到底做了什么
说明书对 B1 的描述是这样的,发送本条指令时,就暂停正在播放的曲目,然后执行本条指令所指定的播放曲目,当播放完后,接着播放原来暂停的曲目。
三个动作串成一条链。暂停当前的,播插进去的,插播放完自动回到原来那句继续。整个过程 MCU 只发一条指令,不用自己记断点,也不用播完再补一条 A0 把原曲目重新调出来。芯片内部帮你记着。
这里要和 AA 暂停指令分清楚。AA 是"暂停"和"继续"合在一条指令里,播放状态下发它暂停,暂停状态下发它继续,来回切。B1 不用你管恢复那一下,它自己干完。做紧急播报,用 B1 比用 AA 加 A0 手动来回切省事得多,也少两个容易出错的环节。
帧结构,以及说明书里那处对不上的地方
B1 的完整帧是这样。
起始码 7E,长度,命令 B1,标志位,曲目高位,曲目低位,校验码,结束码 EF。
翻到说明书 V1.15 的 B1 那一节,表格里长度那一栏写的是 05,紧跟在下面的示例却是 7E 06 B1 00 00 01 B8 EF。长度到底是 5 还是 6,第一次看会懵。
答案是 6。这个坑有出处。说明书末尾的修订记录里,V1.12 那一条写着,B1 插播播放增加标志位,日期 2023 年 9 月 14 日。标志位是后加进去的一个字节,表格那栏没跟着改,示例却更新了。
遇到这种自相矛盾的地方,别猜,算一遍校验码就清楚了。WT2003H 的校验码是从长度字节开始,一直加到校验码前一位的累加和。拿示例来验。
06 加 B1 加 00 加 00 加 01,等于 B8。
跟示例里那个 B8 对上了。如果长度真按 05 算,累加和会是 B7,跟示例不一致。所以按 6 个字节拼,标志位占一个字节,示例是权威的那一方。
手算一遍校验码这个习惯值得保留。它不只是验长度,后面每一次拼新指令,算一遍就能挡掉一大半"发下去没反应"的排查时间。
三个前提,少一个都不响
第一条,也是最容易忽略的一条。说明书在 B1 那节开头就写了,该指令只能在播放状态下插播,停止状态下插播无效。
芯片停着的时候发 B1,它不会播。这是说明书写死的行为,不算故障。紧急播报的场景里,设备大概率正放着背景音乐或者循环提示音,一般碰得上播放状态。真要保险,发 B1 之前先发一条 C2 查询当前工作状态,返回 01 是播放,02 是停止,03 是暂停。看到 01 再插播,心里有底。
第二条,一次只能有一路插播。第一次插播命令未播放完时,发第二次插播命令时,命令无效。要等第一次插播音乐播放完,才能再插。
这条会直接影响代码怎么排。假设一个设备有两个传感器都可能触发报警,两个报警在两秒内先后到来,第二条 B1 会被芯片丢掉。要么在 MCU 这边做个队列,等当前插播放完再发下一条,要么接受这个丢包,反正第一条已经在喊了。选哪种看业务,关键是别以为发了就一定响。
第三条,看返回码。发完 B1,芯片回 7E 04 B1 00 B5 EF,中间那个 00 是结果码。00 表示执行成功,01 表示 Flash 命令出错不执行,02 表示无此文件,05 表示设备不在线。
返回 01,先怀疑帧拼错了,长度和校验码是重灾区。返回 02,检查曲目号是不是超了 Flash 里实际的文件数。返回 05,往外挂 Flash 的电路上找。
曲目号怎么填
曲目高位和曲目低位合起来是 16 位的曲目序号,用十六进制表示。说明书给了两个例子,第 300 首是 0x012C,高位 01 低位 2C。第 67 首是 0x43,高位 00 低位 43。
拆成两字节这步在代码里就是右移八位取高位,与上一个 0xFF 取低位。别把十进制直接塞进去,第 300 首的十进制 300 拆成高低位会变成 01 2C 以外的东西。
还有个前提,A0 那节写着,文件排序按照索引顺序,索引顺序出厂前设定。也就是说曲目号跟着 Flash 里的存放顺序走,跟文件名没有对应关系。音是出厂前写进去的,写的时候什么顺序,播放索引就是什么顺序。这一条和后面换音流程里的排序规则是同一件事。
代码怎么写
按说明书给的 51 范例风格,插播函数大概这样。
void play_insert_song(unsigned int song_num)
{
unsigned char txbuf[8];
unsigned char j;
unsigned int sum = 0;
txbuf[0] = 0x7E;
txbuf[1] = 0x06; // 长度,含标志位共6字节
txbuf[2] = 0xB1; // 插播命令
txbuf[3] = 0x00; // 标志位
txbuf[4] = (song_num >> 8) & 0xFF; // 曲目高位
txbuf[5] = song_num & 0xFF; // 曲目低位
for (j = 1; j < 6; j++)
sum += txbuf[j]; // 从长度字节累加到校验码前一位
txbuf[6] = sum & 0xFF;
txbuf[7] = 0xEF;
Sendhex(txbuf, 8);
}
调用的时候传曲目号就行,play_insert_song(1) 插播第一首。
上电时序那边别忘了。芯片上电前 100ms 做初始化,接下来 200ms 等下载器升级指令,没等到才进正常工作状态。单片机要等上电 300ms 以后再发码,发早了芯片还没听。
和组合播放 B0 的区别
B0 也能中断当前播放再播一串,最多支持 20 首。它和 B1 有个关键差别。
说明书在 B0 那节写着,第一次组播命令未播放完时,发第二次组播命令时,组合播放曲目按照新的组合指令重新开始播放。B0 收到第二条会推倒重来。B1 收到第二条直接丢弃。
一个重来,一个丢弃,行为完全不同。紧急播报要的是"别盖掉正在喊的那句",所以 B1 的丢弃反而是对的。B0 适合一次性组合播报,比如"欢迎光临,请到三号窗口办理"这种把几段拼成一句的场景。
适合谁用,边界在哪
B1 适合所有"临时插一句,插完接着来"的场合。设备故障报警、电梯到层提示插在楼层语音里、医疗设备检测到异常、排队叫号机临时插播通知。这些场景的共同点是紧急那句必须马上出声,原来那句又不能丢。
边界也说清楚。插播期间不能插播,这一条决定了高频触发的场景要自己在 MCU 侧排队。芯片停着的时候插播不响,需要先用 C2 判断状态。曲目号按存放顺序来,换过音就得重新对一遍索引。
我自己的看法是,B1 这类指令好用,是因为它把"记断点、切曲目、恢复播放"这一串原本要 MCU 操心的事收进了芯片里。少三段代码,就少三个漏掉某个分支的机会。紧急语音这种场合,少一个分支比多一个功能有意义。
|
|