简介这是CC1101无线收发芯片的完整工程源码包面向需要低频长距离无线通信参考的嵌入式开发者。程序已调通发送与接收链路一并可用适用于智能家居、工业控制、物联网等场景。压缩包共31个文件整体约658KB以C源码、头文件、IAR工程配置、汇编模块和调试信息为主包含CC1100/CC1101驱动、LCD显示、RTC定时等模块目录结构清晰便于直接查看和移植。描述中“发送接收一起直接调试”说明工程已提供可运行的收发例程能减少参数配置和底层调试时间。已有380人学习下载适合正在调CC1101或想参考此类无线方案的开发者从中获得初始化代码、SPI读写流程和完整工程组织方式快速验证自己的硬件设计。1. 为什么“调通了”三个字最有含金量做无线传感器或便携数据采集的人对 CC1101 这枚 Sub-1GHz 收发芯片应该不陌生。它在 433/868/915MHz 免执照频段工作传得比 2.4GHz 远穿墙能力也更好但很多人把参考驱动一贴、编译通过就以为调好了结果上电收不到数据或距离只有几米。说“调通”指的不是 SPI 能读写寄存器而是天线匹配、频点配置、收发链路、中断回调、休眠唤醒全都验证过丢包率在可接受范围。这篇笔记写给正在做 433MHz 无线链路、照着参考程序却收不到包的人把人从“能编译”推进到“能上线”。2. 把 CC1101 接起来之前先看懂芯片的脾气2.1 一颗做 Sub-1GHz 的专用收发器为什么值得选CC1101 是 Sub-1GHz 频段的可编程收发器不是普通射频前端。它在内部集成了频率合成器、调制解调器、数据包处理引擎以及 64 字节的收发 FIFO。换个直白的说法你要做的不是把基带信号喂给一个模拟芯片而是通过 SPI 告诉它“频点、速率、包格式”然后往 FIFO 里塞数据剩下的前导码、同步字、CRC、白化、退出策略都由芯片自己完成。这个架构对 MCU 的要求非常低一个最普通的 GPIO 模拟 SPI 的 8 位单片机就能把它驱动得非常好。选型时主要看这几个点。第一是频段它覆盖 300-348MHz、387-464MHz、779-928MHz国内项目绝大多数落在 433MHz 或 868MHz一套驱动在不同工程之间几乎可以平移。第二是可配数据率范围从 0.6kbps 到 600kbps低速应用往往故意把速率降到 1.2kbps 左右来换灵敏度。第三是调制方式GFSK/2-FSK/4-FSK/MSK/ASK/OOK 都支持默认最常见的组合是 GFSK 或 2-FSK。第四是电源管理它有 WOR 定时唤醒模式休眠电流可以做到微安级别这对电池供电的采集节点几乎是刚需。对比常见的 2.4GHz 方案比如某无线数传模块CC1101 的优势集中体现在“远距离低功耗”这件事上。433MHz 的频率低自由空间路径损耗比 2.4GHz 小绕射能力也更强同功率下实测距离经常是 2.4GHz 的 2 到 3 倍。代价是数据率上不去而且天线尺寸大。如果你的应用是每秒上报一次温度32 字节净荷那 CC1101 在 1.2kbps 数据率下每个包的空中时间也只有几十毫秒功耗优势非常明显。所以芯片资料里那一百多个寄存器真正决定链路行为的其实是一组关联项频率寄存器、数据率寄存器、频偏寄存器、包控制寄存器和几个状态机控制寄存器。很多“调不通”的案例问题根本不在 MCU 端而是这组寄存器之间的约束没有配对。比如把数据率改高了却忘了同步增大多级调制参数就会表现出“单发单收偶尔能通、一发多收就乱码”。这不是玄学是芯片内部解调链路协商失败。2.2 最小硬件连接SPI、GDO0/GDO2 与天线都不含糊硬件上 CC1101 对外只有两件事SPI 接口和两个通用 IOGDO0/GDO2再加天线和晶振。先看引脚表引脚方向作用连接建议SI / SO / SCLK / CSnMCU 输出/输出/输出/输出SPI 四线接到 MCU 硬件 SPI 或 GPIO 模拟3.3V 电平GDO0双向可配置为中断输出常用作 RX/TX 事件信号接 MCU 外部中断引脚下降沿或上升沿触发GDO2双向可配置为时钟输出、状态输出不用时钟输出时可悬空或接测试点RF_P / RF_N射频差分射频口需按 433MHz 做匹配网络再接天线XOSC_Q1/Q2模拟26MHz 晶振晶振负载电容要按手册选精度影响频偏再强调一下 SPI 时序上的一个习惯地址字节和数据字节之间不要引入过长延迟CS 拉低后整个事务要一气呵成。虽然芯片没有对 CS 低电平期间的延迟做严格限制但有些 MCU 在 GPIO 模拟 SPI 时会在两个字节之间插入过多延时实测偶尔会把选通命令解析成寄存器写让人误以为程序卡死。稳妥的做法是 SPI 时钟不超过 10MHz字节与字节之间最多放一条空语句的延时绝不把 CS 拉高再拉低重新开始一个事务。最容易被轻看的是 GDO0。它是芯片内部事件往外吐信号的唯一窗口调通的关键就是让 GDO0 发生在你想要的时机。常见配置是发送时 GDO0 在“同步字发送完毕”时拉高在“数据包发送完毕”时拉低接收时在“同步字检测到”或“RX FIFO 达到字节阈值”时拉高。把 GDO0 接在 MCU 的外部中断脚上代码就不需要轮询最影响实时性的状态寄存器。天线部分433MHz 的 1/4 波长单极天线大约 17.2 厘米。如果板子空间不够用弹簧天线或陶瓷天线务必保留 π 型匹配网络的位置网络的值不是抄个参考设计就完事要对着频谱仪或至少对着 RSSI 数值去微调。还有一个常见注意点晶体振荡器的负载电容要按芯片数据手册给定的推荐值选不要随手拿两颗 15pF 就焊上晶振频偏几十 ppm 放大到 433MHz 就会变成十几 kHz 的频偏接收机灵敏度立刻掉好几个 dB。电源侧同样不能省。CC1101 的供电域是 1.8-3.6V但射频 PA 工作瞬间电流能到二三十毫安如果供电链路里串联了 10 欧姆以上的细线或者滤波电感压降过大发送时会偶发功率不足表现就是“近距离能通、远距离突然丢包”。常规做法是给 VDD 引脚就近放一颗 10µF 和一颗 100nF 的去耦电容并保证地平面在芯片底下是完整的不要被信号线打断。3. 驱动代码这样写寄存器配置不是玄学3.1 SPI 读写与选通命令的三段式实现先写最底层的三个函数。CC1101 的 SPI 接口是 16 位宽的先发一个地址字节再发一个数据字节选通命令则是单字节。MCU 这边我用标准外设库的示例写法但你会发现这套逻辑换成任意平台都一样。static uint8_t cc1101_spi_transfer(uint8_t data) { while (!(SPI1-SR SPI_SR_TXE)); // 等待发送缓冲区空 SPI1-DR data; while (!(SPI1-SR SPI_SR_RXNE)); // 等待接收完成 return SPI1-DR; } void cc1101_write_reg(uint8_t reg, uint8_t val) { cc1101_cs_low(); cc1101_spi_transfer(reg); // 写寄存器地址bit70 cc1101_spi_transfer(val); cc1101_cs_high(); } uint8_t cc1101_read_reg(uint8_t reg) { cc1101_cs_low(); cc1101_spi_transfer(0x80 | reg); // 读标记 bit71 uint8_t val cc1101_spi_transfer(0); cc1101_cs_high(); return val; } void cc1101_strobe(uint8_t cmd) { cc1101_cs_low(); cc1101_spi_transfer(cmd); // 选通命令直接发 cc1101_cs_high(); }逻辑说明就三点。第一写寄存器时地址字节不需要带读标记芯片靠 bit7 区分读和写。第二读寄存器时先发地址再读一字节这个读出来的第一个字节其实是地址字节的回应第二个字节才是寄存器值所以上面的实现里第二次 spi_transfer(0) 才是真正的数据读取。第三选通命令是单字节例如 0x34 对应 SRX、0x35 对应 STX、0x36 对应 SIDLECS 拉低到拉高之间芯片会进入状态机跳转期间不需要再额外等待。连续读写要单独处理。读 FIFO 或者一次性初始化一堆寄存器时burst 模式能省一半时间靠的是把地址字节的 bit6 置 1。读多个寄存器时CS 保持拉低连续传输地址和数据即可写多个寄存器时更简单发完带 burst 标记的地址后连续发数据。唯一要小心的是 burst 写 FIFO 和 burst 读 FIFO 的边界——它不像普通寄存器那样有固定长度FIFO 的字节数需要先通过状态寄存器确认否则会多读或少读。void cc1101_write_burst(uint8_t addr, const uint8_t *buf, uint8_t len) { cc1101_cs_low(); cc1101_spi_transfer(0x80 | 0x40 | addr); // 读标记burst标记 while (len--) { cc1101_spi_transfer(*buf); } cc1101_cs_high(); }写完这段通常我会把 GDO0 的 GPIO 配置一起放进去GDO0 输出接到 MCU 外部中断初始化时把中断配置为双沿触发。为什么是双沿因为发送完成和接收完成这两个事件一个用上升沿表达、一个用下降沿表达双沿触发意味着同一个引脚能处理两件事。这个细节很多初版代码会漏漏掉的结果就是某个方向永远等不到中断。3.2 归一化配置表改频点、改速率只需要动四个寄存器接着是把配置工具生成的寄存器初始值整理成一张表运行时一次性写入。下面这张是 433MHz、38.4kbps、GFSK 的常用配置摘录完整表很长这里只列会直接影响链路行为的几项。寄存器典型值说明IOCFG00x2EGDO0 配置同步字事件/阈值事件输出FREQ2/1/00x10 0xA7 0x8A载波频率 433.00MHzMDMCFG40xC9数据率相关的信道滤波器带宽MDMCFG30x93数据率 38.4kbpsMDMCFG20x03GFSK使能同步字MDMCFG10x22前导码 4 字节FIFO 阈值MCSM00x18RX/TX 过程均自动退出回到 IDLEPKTCTRL00x87可变长度模式使能 CRCPKTCTRL10x01关闭地址过滤接收时自动清理SYNC1/00x9B 0x2D同步字收发两端必须一致PKTLEN0x3C最长包 60 字节这张表不是抄下来就能通它涉及的是一组相互约束的关系。频率由 FREQ2/1/0 决定算法是 频率 寄存器值 × 晶振频率 ÷ 65536反过来算就是 寄存器值 频率 × 65536 ÷ 晶振。以 26MHz 晶振配 433MHz 为例算出来的理想值是 0x10A87A你看到其他工程里写成 0x10A78F 也不用慌差几个 LSB 是取整方向不同空口频率偏差只有几百赫兹远小于接收机带宽。数据率由 MDMCFG4 的高 4 位和 MDMCFG3 配合决定具体公式在数据手册表里。多数人记不住公式记住结论就行38.4kbps 用 0xC9/0x9319.6kbps 附近是 0xD9/0x85降到 1.2kbps 时 RX 滤波器得同步改窄通常走到这一步我会直接把整个配置段重新生成而不是只改两个寄存器。因为滤波器带宽、去加重、AGC 参数都跟着数据率联动单独改一个寄存器很容易把链路调坏。初始化函数本身很简单把表循环写进去即可但有两个习惯建议从一开始就建立。第一把整张配置表按用途切成段频率段、速率段、包格式段、状态机段加注释标明“这段对应多少 Hz、多少 kbps”而不是留一张两百行的无注释数组。第二初始化完成后回读几个关键寄存器哪怕只读 FREQ2 和 MDMCFG3确认 SPI 通路没有虚焊。很多“程序没跑起来”其实是硬件连错了回读能把这个变量立刻排除掉。void cc1101_init(void) { for (uint8_t i 0; i sizeof(cfg_table) / sizeof(cfg_table[0]); i) { cc1101_write_reg(cfg_table[i][0], cfg_table[i][1]); } cc1101_strobe(CC1101_SIDLE); // 校准发送校准命令等待芯片状态稳定 cc1101_strobe(CC1101_SCAL); // 校准完成后进入 IDLE }注意校准命令 SCAL 执行时芯片会进入校准状态完成后自动回 IDLE一定要在配置写完后再发否则校准过程可能会被后续的寄存器写入打断。4. 收发流程跑到“调通”状态中断、FIFO 与载波侦听4.1 发送链路把数据灌进 TX FIFO等 GDO0 给信号发送的完整序列是回 IDLE清空 TX FIFO把数据作为 burst 写入 TX FIFO发 STX 让芯片进入发送状态然后等 GDO0。下面是实际工程里可以直接改的发送函数void cc1101_send(const uint8_t *data, uint8_t len) { cc1101_strobe(CC1101_SIDLE); cc1101_strobe(CC1101_SFTX); // 清空 TX FIFO避免残留脏数据 // 可变长度模式第一字节写包长度后面才是净荷 cc1101_write_burst(CC1101_TXFIFO, len, 1); cc1101_write_burst(CC1101_TXFIFO, data, len); cc1101_strobe(CC1101_STX); // GDO0 上升沿表示同步字已经发出下降沿表示包发送完毕 while (!cc1101_gdo0_read()); while (cc1101_gdo0_read()); cc1101_strobe(CC1101_SIDLE); }几个容易忽略的点。第一SFTX 清空 FIFO 有一个前提芯片必须在 IDLE 状态才能执行所以前面的 SIDLE 不能省。第二可变长度模式下写入 FIFO 的第一个字节是这包数据的长度这个长度字节算的是净荷长度还是含长度字节本身取决于 PKTCTRL0 的 LENGTH_CONFIG 位常见配置是“不算长度字节”。第三STX 以后不要立刻去读任何状态寄存器发送状态跳转需要一小段稳定时间直接查 GDO0 电平最省事。MCU 侧还可以把这个等待做成中断版GDO0 上升沿中断里记录“包已开始发送”下降沿中断里置位“发送完成”标志。这样做的好处是 MCU 不用阻塞在 while 循环里可以在芯片发送空中包期间去干别的事。对 38.4kbps 的速率来说一个 32 字节包在空中大概 8 到 10 毫秒这段时间足够 MCU 处理其他外设事件。这个等待做成中断版之后逻辑会更健壮但无论哪种写法都要给循环加超时计数GDO0 如果因为配置错误一直不跳死等会把整个系统挂住超时后回到 SIDLE 重新走发送流程即可。发送完成后的状态检查也值得做。MCSM0 如果配置成发送完成后自动回 IDLE那么发送完成瞬间芯片已经不在 TX 状态了此时不需要手动 SIDLE如果配置成停留在 TX就必须主动 SIDLE否则下一包发送时 FIFO 里还残留状态。这就是为什么上面代码在 GDO0 拉低后又补了一发 SIDLE——它幂等多执行一次不会出问题但漏执行会直接影响下一包。4.2 接收链路中断里取数据别漏掉 FIFO 边界接收侧是另一个思路初始化完成后直接 SRX 进入接收模式之后所有事情都由 GDO0 中断驱动。当同步字被捕获或者 RX FIFO 字节数达到 IOCFG0 里设定的阈值时GDO0 会拉高MCU 进中断去取数。uint8_t cc1101_poll_get_packet(uint8_t *buf) { uint8_t rxbytes 0; uint8_t status[2]; // 读取 RXBYTES 状态寄存器bit7 是 RX FIFO 溢出标志 rxbytes cc1101_read_status(CC1101_RXBYTES) 0x0F; if (rxbytes 3) { return 0; // 至少要有 2 字节状态 1 字节数据 } // 先读 2 字节状态RSSI 和 CRC/状态 cc1101_read_burst(CC1101_RXFIFO, status, 2); if (!(status[1] 0x80)) { // CRC 错误清 FIFO 后重新进接收 cc1101_strobe(CC1101_SIDLE); cc1101_strobe(CC1101_SFRX); cc1101_strobe(CC1101_SRX); return 0; } cc1101_read_burst(CC1101_RXFIFO, buf, rxbytes - 2); cc1101_strobe(CC1101_SIDLE); cc1101_strobe(CC1101_SFRX); cc1101_strobe(CC1101_SRX); return rxbytes - 2; }读 FIFO 时前两个字节是状态信息不是数据这一点是新手翻车重灾区。第一个状态字节的低 7 位是 RSSI 值第二个状态字节的 bit7 是 CRC 校验结果。如果你按“读多少字节就得到多少字节”去用收到的有效数据全被挤掉了两字节表现为每包都多出两个乱码头。接收侧还有一个容易被忽略的参数RX FIFO 阈值。阈值设在 IOCFG0 和 FIFOTHR 两个寄存器里常见值是 4 字节或 32 字节。设太小时中断频繁MCU 容易被频繁打断设太大时同步字已经到来但 FIFO 还没攒够中断会晚到。对 60 字节以下的小包我一般给 4 字节阈值也就是同步字之后很快就能触发中断取数据时一次性读完。对长包或低数据率阈值设到 32 字节更合理。退出接收的逻辑也要和 MCSM0 匹配。如果 MCSM0 配置成收到完整包后自动回 IDLE那你取完数据必须手动 SRX 重新进入接收如果配置成停在 RX取完数可以直接继续收。上面的示例代码里每次取完都做 SIDLE→SFRX→SRX是为了保证 FIFO 里没有残留代价是重新进入接收要几十微秒对这个量级的包收发完全没有影响。4.3 用 RSSI 做空口探测信道占用一张表看清RSSI 值在调通阶段的价值比想象中大。它不需要抓到有效包只要芯片处于 RX 状态就会持续测量并更新所以可以用它来验证天线、验证环境干扰、验证两个节点的空间隔离度。int8_t cc1101_get_rssi_dbm(void) { // 读 RSSI 状态寄存器后 200us 再取值更稳定 cc1101_strobe(CC1101_SRX); delay_us(200); uint8_t raw cc1101_read_status(CC1101_RSSI); if (raw 128) { return (int8_t)((raw - 256) / 2 - 74); } return (int8_t)(raw / 2 - 74); }RSSI 原始值到 dBm 的换算就是这条大于等于 128 时先减 256 再除以 2 再减 74否则直接除以 2 减 74。读这个状态寄存器要带 burst 读标记因为它在状态寄存器空间里。延时那 200 微秒不是随便写的芯片内部的 RSSI 滤波器需要时间稳定连续快速读取你会看到数值在跳延时后读到的才是稳定值。调通阶段可以做一张“信道占用表”固定一个位置放接收机拿着发射板绕着场地走每走一步读一次 RSSI走出来你就知道自己的链路余量有多少。室内十米距离如果 RSSI 在 -50dBm 到 -70dBm 之间说明链路健康如果掉到 -90dBm 以下天线或者匹配网络一定有问题不要急着改软件。这个习惯帮我节省了大量排错时间因为它是唯一一个能把射频问题从软件问题里剥离出来的手段。CCA空闲信道评估也是基于 RSSI 的。MCSM1 的 CCA_MODE 位可以选择“始终空闲”“RSSI 低于阈值视为空闲”“只要没在接收就视为空闲”等模式阈值由寄存器 RSSI_THR 设置。多机共用一个频点的时候发送前先做个 CCA 能显著降低碰撞概率代价是每次发送要晚几十微秒。这个功能非常适合类似于无线 RS485 总线这类半双工轮询场景。5. 调通路上的五个典型坑现象、原因、解法5.1 第一包数据永远丢前两个字节现象接收端收到的第一包数据开头总是多出两个字节的乱码或者有效内容前移两位后面数据长度对不上。原因多数工程是在读取 RX FIFO 时把 FIFO 开头自带的两个状态字节当成了数据。这两个字节分别是 RSSI 值和链路状态字CRC 结果就藏在第二个状态字节的最高位里读取时不能直接跳过而要专门读出来判断。解决读取时先读两字节状态判断 CRC再读后面的净荷或者在 PKTCTRL1 里关闭状态字节追加但这样你就拿不到每包的 RSSI 和 CRC 信息了一般不推荐。正确做法就是代码里先读 status再决定这个包要不要丢弃。5.2 GDO0 电平一直不跳中断压根没进来现象发射端 STX 后循环等 GDO0 拉高但引脚一直是低电平接收端也是同样配置完 RX 以后信号线死平。原因IOCFG0/IOCFG2 里 GDO0 的功能没配对。GDO0 是双向口复位后默认配置可能是三态MCU 侧如果把它当普通输入读电平永远不确定如果当外部中断事件永远触发不了。还有一个常见原因GDO0 缺少外部上拉或下拉电阻某些 MCU 的输入浮空。解决把 IOCFG0 设置成“RX FIFO 低于阈值/同步字检测”输出0x2E发送侧用 GDO0 的同步字事件接收侧用 RX FIFO 事件。同时给 GDO0 加一颗 10k 上拉电阻到 VDD让空闲电平明确。改完用示波器量一下STX 后引脚必须有稳定跳变这是判断是否调通的最快标志。5.3 连续发送一段时间后 TX FIFO 下溢现象程序跑几分钟后发射端状态寄存器里出现 TXFIFO_UNDERFLOW 事件甚至芯片进入错误状态后续包全部发不出去。原因数据没来得及填进 FIFO芯片就进入了 TX 状态并开始读 FIFO。常见于 SPI 时钟慢而数据率高的场景或者填充 FIFO 后立刻 STX 而中间没有做任何等待。CC1101 在 TX 过程中如果 FIFO 内数据不够会触发下溢并自动中止发送。解决发送前先 SIDLE SFTX确认 FIFO 已清空写入 FIFO 后不要急着 STX可以先读一下 TXBYTES 状态寄存器确认写入成功再发 STX。高数据率下把 SPI 时钟提上去或者用硬件 SPI 而不是 GPIO 翻转模拟。极端情况下可以开启自动重发机制让芯片在 FIFO 下溢后自动重发本包。5.4 收发双方配置一样距离却差一半现象两个节点用的同一份配置表单发单收正常但拉距离时明显比参考值短或者隔一堵墙就丢包。原因寄存器表虽然一样但射频前端差异巨大。天线匹配网络元件值焊接偏差、参考地平面不完整、晶振实际频率偏差过大、接收端 AGC 参数不匹配都会造成灵敏度下降。这类问题软件上已经无解必须回到射频侧的测量上去。解决先用 RSSI 做基准测试两个节点放在固定距离记录接收端 RSSI 与理论值对比偏差超过 5dBm 就说明前端有问题。重点检查天线匹配网络到 RF_P/RF_N 的走线阻抗晶体换更高精度等级AGCCTRL 参数对照配置工具重新生成。不要尝试通过加大发射功率硬扛那会把真实问题掩盖掉。5.5 WOR 休眠唤醒后第一包永远收不到现象节点平时休眠定时唤醒后开始接收但上位机在这一刻发过来的包第一包总是丢第二包才正常。原因WOR 唤醒后芯片从 SLEEP 状态回到 RX需要一个晶振起振和频率校准的过程这个时间可以达到几百微秒甚至更长。如果上位机在节点还没完成校准前就开始发包包已经过去节点自然收不到。解决唤醒后不要直接 SRX先 SIDLE等状态机稳定读 MARCSTATE 确认芯片进入 IDLE再执行一次校准命令 SCAL校准完成后回读 MARCSTATE 直到表示 IDLE最后 SRX。或者更省事唤醒后强制等待 1ms 再进接收。代价是多耗一点电但换来的是一包不丢。6. 验证“调通”的土办法无线串口、循环计数与 RSSI 对照最后讲三个不依赖频谱仪就能完成验证的办法。第一个是对通测试。把两块已经调好的板子一块接 USB 转串口一块也接 USB 转串口通过无线链路把串口数据直通。按一次按键发一个自增计数另一端回显连续发 1000 次统计回显数量和乱码数量。这个测试跑完链路的基本可信度就有了。注意测试时要在不同位置跑因为固定位置的反射会掩盖真实链路质量。第二个是 RSSI 对照测试。把接收端固定发射端分别放在 1 米、10 米、30 米位置记录每个位置的 RSSI 读数。三组数据应该呈现平滑递减的曲线如果中间某个点突然掉到底那个位置多半是驻波凹点或板子天线方向性问题移动几十厘米就能验证。这个测试比看丢包率更敏感因为丢包率是阈值判定RSSI 是连续量能精确反映链路余量。第三个是压力测试。把数据率调到配置表允许的最高档连续大包轰炸 10 分钟同时统计 CRC 错误包数量。如果 CRC 错误率超过千分之一基本可以断定射频前端或者电源有问题跟代码没有关系。反过来如果 CRC 率很低而有效包率低问题一定在软件流程比如 FIFO 管理、中断丢失。还有一个我自己的习惯每一版调通的参数都会在配置表文件头部写一行注释标明“频率 433MHz、速率 38.4kbps、晶振 26MHz、GDO0 下降沿收包”下次要么直接复用要么照着这行注释改参数。没有这行注释三个月后回来看代码对着两百行配置数组完全想不起来当时是为什么调的只能重新踩一遍坑。这个习惯比任何调试技巧都管用。希望帮到你。本文还有配套的精品资源点击获取