资讯详情

资讯详情

CC1101调通之后:寄存器配置、收发闭环与排查清单

简介面向智能家居、工业控制与物联网低速率长距离通信场景的CC1101无线收发器调试工程基于IAR Embedded Workbench环境搭建包含发射与接收完整链路的可运行代码适合具备单片机与SPI基础、希望快速验证Sub-1GHz通信的开发者参考。压缩包共31个文件、约658KB主体是C源文件与头文件承载CC1100/CC2500寄存器配置、LCD驱动与主流程逻辑同时配有IAR工程与调试配置、汇编启动及设置文件、批处理与说明文本构成可直接打开编译的完整工程结构。目前已有383人学习下载价值在于把工作频率、调制方式、数据速率等参数配置与收发流程固化为可对照的实例便于理解SPI时序、数据包处理与CRC校验的实现方式并以此为起点移植到水表抄表等实际采集项目减少从零搭建驱动与反复排错的投入。1. CC1101 程序调通之后为什么「能收发」只是第一关大多数人的 CC1101 程序调通发生在这样一块板子上两根杜邦线接 SPI一根接 GDO0发送端往 FIFO 塞几个字节接收端串口助手蹦出一样的内容于是长舒一口气把代码打了个包塞进工程。这个瞬间值得高兴但它证明的事情比想象中少——它只说明两块板子在当前这套供电、当前这个距离、当前这组寄存器值下碰巧对上了。真正上手做产品的人很快就会撞到另一堵墙同一份 CC1101 程序换一批板子距离掉一半换个电池包开始丢包夏天机壳里温度上到 60 度误包率翻十倍。这篇内容面向已经能把芯片点起来、但还没弄明白它为什么时灵时不灵的人从寄存器地图讲到收发闭环再讲到调通之后最容易翻车的几个具体地方。2. CC1101 寄存器地图一份配置表到底在配什么CC1101 的配置空间不大0x00 到 0x2E 是配置寄存器0x30 到 0x3D 是命令选通和状态寄存器0x3F 是 FIFO 访问口。绝大多数人的第一份 CC1101 程序配置表是从上位机工具里导出的一长串字节循环写进去能通就再也没打开看过。这没问题直到某天距离变短、包丢得莫名其妙你才会回头发现这张表里每个字节都在说话只是当时没人在听。先把三个最容易出事的区域拆开SPI 时序、频率字、调制与带宽。这三块配错表现都是「现象不明显、怀疑硬件」因为芯片本身不报错它只是安静地待在错误的频点上。2.1 先读对状态字节SPI 时序里最容易读错的一位CC1101 的 SPI 是模式 0空闲时时钟低电平、数据在上升沿采样、高位先行。命令字节的位定义是bit7 是读写方向1 读 0 写bit6 是突发标志1 表示连续访问多个字节bit5 到 bit0 才是真实地址。这里有个非常经典的坑读寄存器时第一个字节返回的不是数据而是芯片的状态字节第二个字节才是寄存器内容。新手照着「发地址收数据」的直觉写读回来的永远是状态字节然后发现 VERSION 寄存器的值是 0x0F 这种明显不对的东西开始怀疑焊接。/* 单寄存器写bit70bit60 */ void cc1101_write_reg(uint8_t addr, uint8_t val) { CC_CS_LOW(); spi_xfer(addr 0x3F); /* 只保留低 6 位地址 */ spi_xfer(val); /* 写入数据此时 SO 上同步吐出状态字节直接丢弃 */ CC_CS_HIGH(); } /* 单寄存器读必须发两个字节才能换回一个数据 */ uint8_t cc1101_read_reg(uint8_t addr) { uint8_t status, val; CC_CS_LOW(); status spi_xfer(addr | 0x80); /* 第 1 个字节回来的是状态字节 */ val spi_xfer(0x00); /* 第 2 个字节才是寄存器内容 */ CC_CS_HIGH(); (void)status; return val; } /* 突发读FIFO 和 PATABLE 必须用突发方式访问 */ void cc1101_read_burst(uint8_t addr, uint8_t *buf, uint8_t len) { CC_CS_LOW(); spi_xfer(addr | 0xC0); /* bit71 读bit61 突发 */ for (uint8_t i 0; i len; i) buf[i] spi_xfer(0x00); CC_CS_HIGH(); }三个函数里有三处需要留意的细节。第一处是写操作里那句addr 0x3F如果地址本身带了读位比如别人给的宏是 0x8D不清掉就会变成读命令写不进去而且不报错。第二处是读操作的两个字节第二个字节发什么都行习惯上发 0x00。第三处是突发的0xC0很多人只加了 0x80 忘了 0x40结果读 FIFO 只拿到第一个字节后面的全是 0xFF然后以为是数据丢了。状态字节本身也有用。它的高 4 位反映 FIFO 状态空、有数据、满、溢出低 4 位反映状态机此刻在哪——IDLE、RX、TX、校准中还是正在收前导码。调试阶段把每次 SPI 交互的状态字节打到串口上比上逻辑分析仪快得多这算是排查时性价比最高的一招。另外补一句命令选通是「只发一个地址字节」不跟数据。比如 STX 就是发 0x35 后直接拉高片选。命令选通的地址和状态寄存器的地址会撞车——MARCSTATE 是 0x35STX 也是 0x35——靠的就是 bit7 区分0x35 是写命令0xB5 是读状态。这一层如果不清楚代码里会混着出现两种写法看着都对实际只有一个生效。2.2 频率、信道与晶振FREQ2/1/0 到底怎么算频率字是 24 位拆在 FREQ2、FREQ1、FREQ0 三个寄存器里外加一个 CHANNR 做信道偏移。公式不复杂f_carrier (f_xtal / 2^16) × (FREQ CHANNR × 2^8)其中 FREQ 是那 24 位拼起来的整数。反推寄存器值就是XTAL 26_000_000 # CC1101 最常见的外接晶振也有用 27MHz 的务必看自己板子 def freq_to_regs(f_hz, chan0, xtalXTAL): 把目标载频换算成 (FREQ2, FREQ1, FREQ0) raw round(f_hz * (2 ** 16) / xtal) - chan * 256 if not (0 raw (1 24)): raise ValueError(频率字超出 24 位范围检查晶振频率是否填错) return (raw 16) 0xFF, (raw 8) 0xFF, raw 0xFF def regs_to_freq(f2, f1, f0, chan0, xtalXTAL): 反过来校验把写进寄存器的值还原成实际载频 return (xtal / (2 ** 16)) * (((f2 16) | (f1 8) | f0) chan * 256) print(freq_to_regs(433_920_000)) # 示例输出一组三个字节 print(regs_to_freq(0x10, 0xB0, 0x71)) # 看看写进去的到底落在哪个频点这两段代码的价值不在算在「校验」。写完配置后用regs_to_freq把实际写进去的三个字节反算一遍你就知道真实载频和你想的差多少。差几十赫兹无所谓差几十千赫兹就是抄错了晶振频率——26MHz 和 27MHz 的板子同一组频率字算出来能差将近 4%在 433MHz 上差不多 16MHz芯片根本不会理你。CHANNR 那部分也别忽略。信道偏移的步长是 2^8对应 26MHz 晶振下大约 101.6kHz这个步长是固定的不随你设的 RX 带宽变。如果 RX 带宽只开了 58kHz相邻两个信道之间就会互相串多信道组网的时候表现是「隔壁信道的数据突然出现在自己这里」。要么把带宽开大到覆盖信道间隔要么把信道间隔拉到带宽的两倍以上没有第三条路。还有晶振本身的精度问题。普通无源晶振常见 ±10ppm 到 ±30ppm433.92MHz 下 ±20ppm 就是 ±8.7kHz。收发两端各偏一点最坏能差出接近 20kHz。这个数字单独看不大但它要和你 RX 带宽里的频偏余量一起算——这是下一节要收的账。2.3 调制方式、符号率与 RX 带宽的三方博弈MDMCFG2 的低 3 位选调制方式000 是 2-FSK001 是 GFSK010 是 ASK/OOK011 是 4-FSK100 是 MSK。低功耗场景通常选 2-FSK 或 GFSK抗干扰要求高、速率低的场合也有人用 OOK 图个简单。符号率由 MDMCFG4 的低 4 位DRATE_E指数和 MDMCFG3DRATE_M尾数共同决定symbol_rate (256 DRATE_M) × 2^DRATE_E × f_xtal / 2^28RX 滤波带宽由 MDMCFG4 的高 2 位CHANBW_E和中 2 位CHANBW_M决定rx_bw f_xtal / (8 × (4 CHANBW_M) × 2^CHANBW_E)def symbol_rate(drate_e, drate_m, xtalXTAL): return (256 drate_m) * (2 ** drate_e) * xtal / (2 ** 28) def rx_bandwidth(chanbw_e, chanbw_m, xtalXTAL): return xtal / (8 * (4 chanbw_m) * (2 ** chanbw_e)) def pick_drate(target, xtalXTAL): 在 16×256 的组合里找最接近目标速率的 (E, M) best None for e in range(16): for m in range(256): r symbol_rate(e, m, xtal) err abs(r - target) / target if best is None or err best[0]: best (err, e, m, r) return best err, e, m, r pick_drate(4800) print(fDRATE_E{e} DRATE_M{m} 实际{r:.1f}bps 误差{err*100:.3f}%)这三个量之间有个硬约束很多人配表的时候只看速率不看带宽。2-FSK 的 Carson 带宽近似是BW_needed ≈ 2 × 频偏 符号率再叠加上两端晶振的相互偏差最坏 2 × f × ppm就是 RX 带宽的下限。举个例子4.8kbps 速率、±25kHz 频偏、433.92MHz、两端各 ±20ppm那么理论上需要 2×25k 4.8k 2×433.92M×20e-6 ≈ 54.8k 17.4k ≈ 72kHz实际应该开到 100kHz 以上留余量。反过来带宽开太大也有代价接收机噪声功率正比于带宽灵敏度会掉。我一般会按「理论需求 × 1.5」来选最接近的那一档然后在实测里看误包率如果误包率下不去再往上调一档而不是一开始就拉满。频偏寄存器 DEVIATN 的公式是deviation f_xtal / 2^17 × (8 DEVIATN_M) × 2^DEVIATN_E频偏设得太小接收端鉴频器分不开 0 和 1设得太大能量散到带宽外既浪费又干扰邻道。2-FSK 的常见经验是频偏取符号率的 0.5 到 1 倍之间速率越低越往小取。把这三节连起来看CC1101 的配置表其实是一组互相咬合的约束晶振决定频率字频率字和信道间隔决定你在哪符号率和频偏决定带宽带宽又反过来限制你能用几个信道。任何一处单独改都要把其他几处重新算一遍——这是调通之后最容易忽略的事。3. 用寄存器配置表把 CC1101 跑起来最小可复现初始化序列这一章给一份能照着抄的上电流程。前提条件说清楚主控通过 SPI 连上芯片片选、GDO0 至少各一根线接好晶振已经起振。至于配置值本身我建议一律用上位机配置工具按自己的频段、速率、频偏生成然后导出成表下面这张表是 433.92MHz、2-FSK、4.8kbps 附近的一组示例列出来的目的是让你知道每个字节管什么而不是让你原样抄。换频段、换晶振、换速率值必须重算。3.1 上电复位、IDLE 与手动校准的三个动作上电顺序错了后面全白搭。我的固定动作是拉低片选 → 等晶振起振数据手册给的稳定时间一般 1ms 足矣保守点给 10ms→ 发 SRES 软复位 → 等 MARCSTATE 回到 IDLE → 再开始写配置表。#define CC1101_SRES 0x30 #define CC1101_SCAL 0x33 #define CC1101_SIDLE 0x36 #define CC1101_SFRX 0x3A #define CC1101_SFTX 0x3B #define CC1101_MARCSTATE 0xB5 /* 0x35 | 0x80读状态寄存器 */ /* 只发一个字节的命令选通 */ void cc1101_strobe(uint8_t cmd) { CC_CS_LOW(); spi_xfer(cmd 0x3F); /* 命令选通不带读写位 */ CC_CS_HIGH(); } /* 等状态机进入 IDLE超时返回失败 */ bool cc1101_wait_idle(uint32_t timeout_us) { while (timeout_us--) { if ((cc1101_read_reg(CC1101_MARCSTATE) 0x1F) 0x01) return true; delay_us(1); } return false; } bool cc1101_init(void) { delay_ms(10); /* 等晶振起振别省这一步 */ cc1101_strobe(CC1101_SRES); delay_ms(1); if (!cc1101_wait_idle(5000)) return false; for (uint8_t i 0; i sizeof(cfg_table) / 2; i) cc1101_write_reg(cfg_table[i][0], cfg_table[i][1]); /* 写完配置后手动校准一次让 PLL 锁定在当前频点 */ cc1101_strobe(CC1101_SCAL); delay_ms(1); return cc1101_wait_idle(5000); }cc1101_wait_idle里那个 0x1F是有讲究的MARCSTATE 只有低 5 位是状态机编码高位是保留位不清掉可能永远等不到 0x01。这个坑我踩过一次表现是初始化函数一直返回失败但芯片其实是好的。关于校准得说清楚什么时候必须手动做。MCSM0 的 FS_AUTOCAL 字段控制从 IDLE 进 RX/TX 时是否自动校准默认建议设成「每次 IDLE 到 RX/TX 都校准」或者「每 4 次校准一次」。但自动校准有个前提芯片必须真的经过了一次 IDLE。如果你从 RX 直接切 TX 又切回 RX中间没回 IDLE校准就不会触发PLL 可能还锁在上一个频点上。所以切信道、改频率之后老老实实 SIDLE → 写频率字 → SCAL → 再进 RX比省那 1ms 划算得多。温度漂移是另一个隐形因素。晶振的频率会随温度变机壳内温度从 25 度升到 60 度普通晶振能漂十几 ppm。这就是为什么有些设备「刚开机好好的跑一小时开始丢包」——不是程序的问题是频点跑了。对策要么定期重新校准要么把 RX 带宽留足余量。3.2 PATABLE 与 FREND0发射功率不是写个数字就完事发射功率由两个东西共同决定PATABLE 里的 8 个字节和 FREND0 低 3 位的 PA_POWER 索引。PATABLE 是个 8 字节数组每个字节对应一个功率档的 PA 配置值PA_POWER 指向当前用哪一档。/* PATABLE 必须用突发写地址 0x3E */ void cc1101_write_patable(const uint8_t *tbl, uint8_t len) { CC_CS_LOW(); spi_xfer(0x3E | 0x40); /* 0x3E 是 PATABLE 地址bit6 置 1 走突发 */ for (uint8_t i 0; i len; i) spi_xfer(tbl[i]); CC_CS_HIGH(); } /* 433MHz 一组示例值索引 0→7 大致对应 -30dBm 到 10dBm 实际值必须查数据手册对应频段的表并用功率计确认 */ static const uint8_t patable_433[8] { 0x12, 0x0E, 0x1D, 0x34, 0x60, 0x84, 0xC8, 0xC0 };这里必须说明PATABLE 的值和频段强相关433MHz 一组、868/915MHz 另一组同一个字节值在两个频段上的实际输出功率能差 5dB 以上。拿错表的结果是「程序跑得好好的就是距离短」而且短得不明显很容易被当成天线问题。真正的坑在 FREND0。PA_POWER 是 3 位索引指向 PATABLE 的第几个字节。如果你写了个漂亮的功率表进去FREND0 却还是复位默认值 0x10PA_POWER 0指向 PATABLE[0]那就等于把最弱的那一档设成了当前功率。433MHz 表里索引 0 往往是 -30dBm 左右配上普通天线打个十几米就没了。这个现象特别迷惑人频谱仪上看载波是有的示波器上看SPI 是对的就是距离不对。/* 选最大功率档PA_POWER 7 */ static const uint8_t cfg_table[][2] { /* ... 前面一堆配置 ... */ {0x22, 0x17}, /* FREND0: PA_POWER7指向 patable_433[7] */ };改完记得用功率计或者频谱仪看一眼实际输出。没有仪器的话退而求其次的办法是拉距离做对照实验同一块板子PA_POWER 从 0 改到 7如果 RSSI 读数有明显跳变说明生效了如果一点变化没有多半是 PATABLE 根本没写进去或者写成了单字节模式只写了第一个字节。3.3 收包通道GDO0 引脚、包长模式与 FIFO 阈值GDO0 是一根通用输出引脚可以映射成很多种内部信号接收时最常用的是「检测到同步字就拉高包结束拉低」——这个信号直接当外部中断用比轮询状态机省 CPU。/* IOCFG0 0x06同步字命中拉高包尾拉低适合做收包中断 */ /* IOCFG0 0x01RX FIFO 有数据就拉高适合流式读 */ #define CC1101_IOCFG0 0x02 cc1101_write_reg(CC1101_IOCFG0, 0x06);包长模式在 PKTCTRL0 的低 2 位00 固定长度长度写在 PKTLEN、01 可变长度第一个字节是长度接收方自动解析、10 无限长度、11 保留。绝大多数小数据包场景用可变长度省事数据量固定且对时序敏感的用固定长度省一个字节开销。FIFO 只有 64 字节FIFOTHR 的 FIFO_THR 字段决定什么时候拉 GDO 信号通知主控来取数据。默认 0x07 表示收到 32 字节就通知。如果你的包只有十几字节把这个阈值调小一点比如 0x04能更早触发中断降低延迟如果你的包接近 64 字节保持默认或者调大都行。包长这块有个硬边界必须记住单个包的有效载荷不能超过 64 字节FIFO 大小超过就得用无限包长模式分几次续写或者干脆在应用层拆包。很多「偶尔丢一个包」的问题根子就在包长 80 字节、FIFO 装不下。配完这三块一个能让芯片正常工作的最小集合就够了。但「能工作」和「能收对」之间还差一个收发闭环下一章来收。4. 收发闭环怎么写从 FIFO 读写到包校验的完整流程初始化只解决了「芯片醒着」。收发闭环要做的是确定此刻能不能发、发完怎么知道发出去了、收到的东西怎么判断是不是自己的、CRC 错了怎么办。这几件事在每个项目里的写法都不一样但状态机检查的顺序是通用的。4.1 发送端TXFIFO 写入前后的状态机检查发送的完整流程是切 IDLE → 清 TX FIFO → 突发写数据 → 发 STX → 等包发完 → 回 IDLE。中间任何一步顺序错了表现都是「数据发出去了但对方收不到」或者「第一次能发第二次发不出去」。bool cc1101_send(const uint8_t *payload, uint8_t len) { if (len 0 || len 64) return false; /* FIFO 只有 64 字节 */ /* 1. 必须先回 IDLE否则 FIFO 写入可能被状态机吞掉 */ cc1101_strobe(CC1101_SIDLE); if (!cc1101_wait_idle(2000)) return false; /* 2. 清空 TX FIFO避免上一包的残留拼在新包前面 */ cc1101_strobe(CC1101_SFTX); /* 3. 可变长度模式下第一个字节是长度要自己塞进去 */ CC_CS_LOW(); spi_xfer(0x3F | 0x40); /* 0x3F 是 TXFIFObit6 走突发 */ spi_xfer(len); /* 长度字节 */ for (uint8_t i 0; i len; i) spi_xfer(payload[i]); CC_CS_HIGH(); /* 4. 启动发射 */ cc1101_strobe(0x35); /* STX */ /* 5. 等状态机离开 TX说明包已经发完 */ for (uint16_t i 0; i 5000; i) { if ((cc1101_read_reg(CC1101_MARCSTATE) 0x1F) 0x01) return true; delay_us(1); } return false; }第 1 步的 SIDLE 不能省。如果芯片此刻在 RX直接写 FIFO 再 STX芯片会先退出 RX 再进 TX中间那次状态切换可能触发自动校准而你写入的数据已经在 FIFO 里等着了——大部分时候没事偶尔会下溢。这种「偶尔」是最难查的。第 2 步的 SFTX 也不能省。TX FIFO 是个环形缓冲没清干净就开始写上一包的尾部会留在里面新包从中间接上去接收端解出来就是乱码。我见过一个项目单次发送永远正常连续发三次必错一次查了两天才定位到是漏了 SFTX。第 5 步的等待方式有两种轮询 MARCSTATE或者等 GDO0 下降沿。轮询简单但占 CPU中断省 CPU 但要注意 GDO0 的极性配置。数据量大的场景建议用中断。还有一个容易忽略的点TXOFF_MODEMCSM1 的 bit3:2。它决定发完之后芯片自动去哪个状态——回 IDLE、回 RX、还是留在 TX。做半双工收发切换的时候把它设成「发完回 IDLE」比发完回 RX 更好控制因为回 RX 会自动开始收你可能还没准备好。4.2 接收端GDO0 中断与 RXBYTES 轮询的两种写法接收端的核心是两件事怎么知道来包了怎么把 FIFO 里的数据完整取出来。/* 方式一中断驱动GDO0 上升沿进入适合低功耗 */ void EXTI_Handler(void) /* GDO0 接的是外部中断脚 */ { rx_flag 1; } bool cc1101_receive(uint8_t *buf, uint8_t *out_len) { uint8_t rxbytes, len; /* 先看 RXBYTESbit6:0 是可用字节数bit7 是溢出标志 */ rxbytes cc1101_read_reg(0xFB); /* 0x3B | 0x80 */ if (rxbytes 0x80) { /* FIFO 溢出整包作废 */ cc1101_strobe(CC1101_SIDLE); cc1101_strobe(CC1101_SFRX); cc1101_strobe(0x34); /* SRX重新开始 */ return false; } if (rxbytes 0) return false; /* 可变长度模式先读 1 字节长度再按长度读载荷 */ cc1101_read_burst(0x3F, len, 1); if (len 0 || len 64 || (rxbytes - 1) len) { cc1101_strobe(CC1101_SFRX); return false; } cc1101_read_burst(0x3F, buf, len); *out_len len; return true; } /* 方式二纯轮询主循环里每 1ms 查一次实现简单 */ void rx_poll_task(void) { uint8_t buf[64], len; while (cc1101_receive(buf, len)) { handle_packet(buf, len); } }RXBYTES 的 bit7 是溢出标志这一位必须判。FIFO 溢出意味着有数据被丢掉了此时读出来的长度和实际载荷对不上继续解析只会得到垃圾。正确做法是清 FIFO、重新进 RX把这一包整个放弃。可变长度模式下还有个细节长度字节本身占用了 FIFO 的一个位置所以判(rxbytes - 1) len是有必要的防止长度字段被干扰破坏成一个很大的值然后去读一堆不存在的数据。如果开启了 APPEND_STATUSPKTCTRL1 的对应位FIFO 末尾会额外追加 2 个字节RSSI 和 LQI/CRC_OK。这时候len要再加 2 去读读出来的最后两字节才有意义。CRC_OK 是 LQI 那个字节的 bit7为 0 表示 CRC 校验失败这个包应该直接丢掉。中断和轮询怎么选电池供电、对功耗敏感的用中断主控大部分时间睡觉GDO0 上升沿唤醒插电设备、主循环本来就跑得勤的用轮询代码简单不用处理中断嵌套和标志位竞争。别为了省那点 CPU 把自己绕进中断竞态里这是血泪经验。4.3 同步字、前导码与地址过滤让总线上只留下自己的包433MHz 这个频段最大的问题不是距离是拥挤。同一片区域里可能有几十个设备在发你的芯片会把它们全收下来然后靠 CRC 一个个丢掉CPU 白跑。同步字是第一道筛子。SYNC1/SYNC0 两个字节可以自定义配合 MDMCFG2 的同步字长度设置15/16、16/16、30/32、32/32。默认的 0xD391 谁都在用改成一个自己的两字节组合误检率立刻降一个量级。注意同步字长度设成 30/32 或 32/32 时SYNC1 是高位、SYNC0 是低位一起组成 32 位。前导码是第二道。MDMCFG1 里可以设前导码长度为 2、3、4、6、8、12、16、24 字节。前导码越长接收端越容易对齐但空中占用时间也越长。短包场景 4 字节够用长包或者环境嘈杂的时候上到 6 到 8 字节更保险。地址过滤是第三道也是最省事的一道。PKTCTRL1 的低 3 位是地址检查模式设成「检查地址并丢弃不匹配」之后只有包里的地址字节等于 ADDR 寄存器芯片才会把它放进 FIFO其余的直接在硬件层丢掉主控根本不知道有这回事。/* 地址过滤三步走 */ cc1101_write_reg(0x09, 0x2A); /* ADDR本机地址自定义 */ cc1101_write_reg(0x07, 0x02); /* PKTCTRL1: 低 3 位010检查并丢弃不匹配 */ cc1101_write_reg(0x08, 0x05); /* PKTCTRL0: 可变长度 CRC 使能 */配置成可变长度加地址过滤之后包结构就变成长度字节 | 地址字节 | 载荷 | CRC 两字节。应用层拿到的len是包含地址字节的解析时记得从第二个字节开始才是业务数据。三道筛子叠起来CPU 被无用包打断的次数能降一个数量级这在多设备共存的环境里是质变。但要提醒一句地址过滤只在硬件层丢包如果你的同步字和别人撞了、地址也撞了那还是会漏进来。所以同步字和地址最好一起改别只改一个。5. 调通之后最容易翻车的五个地方CC1101 排查清单这一章把本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →