news 2026/10/28 9:17:59

RA4M2与DA14531低功耗蓝牙数据采集方案实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RA4M2与DA14531低功耗蓝牙数据采集方案实战

1. 项目缘起与整体方案拆解

1.1 为什么选 RA4M2 搭配 DA14531 这套组合

手头这个项目最初的诉求很朴素:做一块低功耗的无线数据采集板,前端传感器数据通过串口汇总,再经由蓝牙把数据实时送到手机 App 上显示,同时手机端也能下发一些配置指令回去。选型阶段我对比过几种常见路线,最后落在瑞萨 RA4M2 加 Dialog(现属瑞萨)DA14531 这个组合上,理由其实挺实在的。

RA4M2 属于 RA4 系列里的主流型号,Cortex-M33 内核,主频 100MHz,带 TrustZone,片上 Flash 和 SRAM 都比较宽裕,最关键的是它的串口外设(SCI)数量多、配置灵活,FIFO 深度也够,跑高速 UART 不容易丢数据。而 DA14531 这颗蓝牙 SoC 在低功耗 BLE 圈子里口碑一直不错,官方标称的收发电流很低,休眠电流更是压到了微安级别,特别适合电池供电、需要长时间待机的场景。它自带一个 Cortex-M0+ 内核专门跑蓝牙协议栈,对外提供 UART 接口跟主控通信,这种“主控 + 蓝牙协处理器”的架构把应用逻辑和射频协议栈彻底解耦,开发起来边界很清晰。

市面上也有把蓝牙功能直接集成进主控的方案,比如某些带 BLE 的 MCU,但这类芯片往往在射频性能和协议栈成熟度上要打折扣,尤其是需要过认证或者对连接稳定性要求高的场合,独立蓝牙 SoC 的优势就体现出来了。DA14531 的协议栈是官方长期维护的,配对、绑定、重连这些流程都经过大量产品验证,省去了自己啃协议栈底层的时间。

提示:DA14531 有两个常见封装版本,DA14531-00000FX2 是标准版,DA14531-00000OG2 带 ROM 版本差异,选型时注意看数据手册里的固件加载方式,别买错了导致烧录工具对不上。

1.2 整体数据流与模块划分

整个系统的数据流其实是一条很清晰的链路:传感器 → RA4M2 的 UART 外设 → RA4M2 应用层做协议封装 → 第二路 UART 发给 DA14531 → DA14531 通过 BLE 发给手机。反向则是手机下发指令 → DA14531 收到后从 UART 吐出 → RA4M2 解析 → 执行动作或回传状态。

这里有个容易踩的坑:RA4M2 和 DA14531 之间的 UART 是双向的,但 DA14531 的 UART 在默认固件下往往被用作“透明传输”通道,也就是说它收到什么就通过 BLE 发什么,BLE 收到什么就从 UART 吐什么。这种透传模式开发最快,但如果你想在蓝牙侧做一些本地判断(比如过滤无效指令、做数据缓存),就需要改 DA14531 的固件,用它的 SDK 写自定义逻辑。我这次为了快速验证,先用了官方透传固件,后面再考虑加自定义处理。

模块划分上我分了三层:底层驱动层负责 RA4M2 的两路 UART 初始化、中断接收、环形缓冲区管理;中间协议层负责把传感器数据打包成固定帧格式,加上帧头、长度、校验;应用层负责跟 DA14531 的握手、心跳维持、断线重连。这样分层的好处是,后面如果换蓝牙模块,只需要改中间层和底层的一小部分,应用逻辑基本不动。

1.3 方案选型的几个关键取舍

第一个取舍是 UART 波特率。DA14531 的 UART 最高支持到 921600,但实际跑下来,115200 是最稳的,再往上在长线或者干扰环境下误码率会上升。我最终定在 115200,8 数据位、1 停止位、无校验,这是最通用的配置,手机端和 PC 端调试工具都默认支持。

第二个取舍是流控。RA4M2 的 SCI 支持硬件流控(RTS/CTS),DA14531 也支持,但需要额外接两根线。如果数据量不大、缓冲区够深,软件流控或者干脆不用流控也能跑。我这次因为数据帧都不长(每帧不超过 64 字节),就没接流控线,靠环形缓冲区 + 中断及时取数来避免溢出。但如果你的应用要传大块数据(比如固件升级),强烈建议把 RTS/CTS 接上。

第三个取舍是电源域。DA14531 可以独立供电,也可以由 RA4M2 的 GPIO 控制其复位和唤醒。我选择让 RA4M2 用一个 GPIO 控制 DA14531 的复位脚,这样在系统异常时主控可以硬复位蓝牙模块,比软件复位可靠得多。另外 DA14531 的唤醒脚也接到 RA4M2 的外部中断上,蓝牙侧有数据时能主动唤醒主控,避免主控一直轮询浪费电。

2. 核心细节解析与实操要点

2.1 RA4M2 的 UART 外设配置要点

RA4M2 的串口外设叫 SCI(Serial Communications Interface),每个通道都可以配置成 UART 模式。我用了两路:SCI0 接传感器,SCI1 接 DA14531。配置的时候有几个参数必须搞清楚,否则会出现“能发不能收”或者“收数据乱码”的问题。

首先是时钟源。RA4M2 的 SCI 时钟来自 PCLK,分频系数通过 BRR 寄存器设置。以 115200 波特率、PCLK 48MHz 为例,BRR 的计算公式是:BRR = PCLK / (波特率 × 分频因子)。分频因子通常取 16(异步模式),所以 BRR = 48000000 / (115200 × 16) ≈ 26.04。取整后实际波特率会有微小误差,26 对应 115384,误差 0.16%,完全在容忍范围内。如果你用的 PCLK 不是 48MHz,一定要重新算,误差超过 2% 就可能通信失败。

其次是 FIFO 配置。RA4M2 的 SCI 带 16 级 FIFO,我一般把接收 FIFO 触发深度设成 4 或者 8,这样中断不会太频繁,也不会因为 FIFO 满了丢数据。发送 FIFO 触发深度设成 0(即空就触发),配合中断发送,效率比较高。

第三是中断优先级。接收中断的优先级要高于发送中断,因为接收是被动的,数据来了不取就丢;发送是主动的,可以等。在 NVIC 里把 RXI 中断设成优先级 1,TXI 设成优先级 2,这样接收永远优先响应。

/* RA4M2 SCI1 UART 初始化示例(基于 FSP 配置思路) */ void uart1_init(void) { /* 引脚配置:P410 为 TXD1,P411 为 RXD1 */ R_IOPORT_PinCfg(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_10, IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_SCI1_3); R_IOPORT_PinCfg(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_11, IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_SCI1_3); /* 打开 SCI1 时钟 */ R_MSTP->MSTPCRB &= ~(1U << 30); /* 配置波特率 115200,PCLK 48MHz */ SCI1->BRR = 26; SCI1->SMR = 0x00; /* 异步模式,8N1 */ SCI1->SCR = 0x00; SCI1->SCMR = 0x00; /* 不使用智能卡模式 */ SCI1->SEMR = 0x00; /* 关闭噪声滤波,需要时再开 */ SCI1->SIMR1 = 0x00; SCI1->SPMR = 0x00; /* 使能发送和接收 */ SCI1->SCR |= (1U << 4) | (1U << 5); /* TE=1, RE=1 */ /* 使能接收中断 */ SCI1->SCR |= (1U << 6); /* RIE=1 */ NVIC_SetPriority(SCI1_RXI_IRQn, 1); NVIC_EnableIRQ(SCI1_RXI_IRQn); }

上面这段代码是直接操作寄存器的写法,实际用 FSP(Flexible Software Package)的话,在 Smart Configurator 里点几下就能生成,但理解底层寄存器有助于排查问题。比如有一次我遇到接收中断不触发,查了半天发现是 SCR 寄存器的 RIE 位没置上,FSP 生成的代码里默认是开的,但我手动改过配置把它覆盖了。

2.2 DA14531 的 UART 透传固件与引脚连接

DA14531 出厂时 Flash 是空的,需要先烧录官方提供的透传固件。瑞萨官网有DA14531_DSPS或者proximity之类的例程,但最省事的是找那个uart_to_ble的透传 demo。烧录工具用官方提供的 SmartBond Flash Programmer,通过 JTAG 或者 UART 下载都行。我一般用 JTAG,因为可以在线调试,UART 下载虽然接线少,但一旦固件跑飞了就得重新上电进 boot 模式,比较麻烦。

引脚连接上,DA14531 的 UART 默认映射到 P0_0(TX)和 P0_1(RX),但这两个脚在芯片启动时可能有其他复用功能,需要确认固件里有没有重映射。我这次用的固件把 UART 映射到了 P0_5 和 P0_6,所以接线时不能想当然按默认来。最稳妥的办法是看固件源码里的GPIO_ConfigurePin调用,或者用示波器抓一下上电后哪个脚有波形。

连接关系如下表:

RA4M2 引脚DA14531 引脚信号方向说明
P410 (TXD1)P0_6 (RX)RA4M2 → DA14531主控发数据给蓝牙
P411 (RXD1)P0_5 (TX)DA14531 → RA4M2蓝牙发数据给主控
P412 (GPIO)RSTRA4M2 → DA14531主控控制蓝牙复位
P413 (GPIO)WAKEUPDA14531 → RA4M2蓝牙唤醒主控
3.3VVCC电源供电
GNDGND地共地

注意:DA14531 的 IO 电压是 3.3V,但它的内核电压是 1.8V 左右,芯片内部有 LDO。如果你用外部 1.8V 给 IO 供电,通信电平就不匹配了,必须加电平转换。我直接用的 3.3V,跟 RA4M2 一致,省了转换电路。

2.3 BLE 服务与特征值的规划

DA14531 透传固件默认会创建一个自定义服务,里面有两个特征值:一个用于手机写数据(Write),一个用于手机读/通知数据(Notify)。UUID 可以在固件里改,我为了跟手机 App 对接方便,用了固定的 128 位 UUID,避免每次重新配对。

手机端连接后,先使能 Notify 特征值的 CCCD(Client Characteristic Configuration Descriptor),然后就可以接收 DA14531 从 UART 收到的数据了。手机写数据时,往 Write 特征值写,DA14531 会从 UART 吐出来给 RA4M2。

这里有个细节:BLE 的 MTU 默认是 23 字节,去掉 3 字节 ATT 头,实际能传 20 字节。如果 RA4M2 发的一帧数据超过 20 字节,DA14531 会拆成多个 BLE 包发送,手机端收到的就是分片的。所以我在协议层把每帧数据控制在 20 字节以内,或者手机端自己做粘包处理。Android 上可以请求更大的 MTU(比如 247),但需要手机和 DA14531 都支持,iOS 的 MTU 协商机制又不太一样,为了兼容性,我干脆把帧长压到 18 字节,留 2 字节余量。

3. 实操过程与核心环节实现

3.1 硬件连线与上电检查

拿到板子后第一步不是写代码,而是把线接对、把电供稳。我习惯先用万用表量一遍所有电源脚的对地阻值,确认没有短路再上电。RA4M2 和 DA14531 都是 3.3V 供电,我用了一个低压差稳压器从 5V 降到 3.3V,输出电流能力至少 500mA,因为 DA14531 在射频发射瞬间电流会冲到十几毫安,加上 RA4M2 本身几十毫安,余量要留够。

上电后先不急着跑程序,用示波器看 DA14531 的晶振有没有起振。DA14531 需要外部 16MHz 晶振(或者用内部 RC,但精度差一些),如果晶振没起振,蓝牙根本跑不起来。我遇到过一块板子晶振负载电容焊错了,导致起振不稳定,蓝牙时断时续,换了电容就好了。

然后测 RA4M2 的复位脚,确认上电后能正常释放。RA4M2 的复位脚内部有上拉,但如果你外部加了电容,要确保 RC 时间常数不要太大,否则复位释放太慢会影响启动。

3.2 RA4M2 端 UART 收发代码实现

接收部分我用的是中断 + 环形缓冲区。中断里只做一件事:把接收到的字节塞进缓冲区,然后更新写指针。主循环里检查读指针和写指针的差值,有数据就取出来处理。这样中断服务程序极短,不会阻塞其他中断。

#define UART1_RX_BUF_SIZE 256 static volatile uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; static volatile uint16_t uart1_rx_wr = 0; static volatile uint16_t uart1_rx_rd = 0; void sci1_rxi_isr(void) { /* 读取接收数据寄存器 */ uint8_t data = SCI1->RDR; uint16_t next = (uart1_rx_wr + 1) % UART1_RX_BUF_SIZE; if (next != uart1_rx_rd) { uart1_rx_buf[uart1_rx_wr] = data; uart1_rx_wr = next; } /* 如果缓冲区满,丢弃新数据,实际项目中可以加溢出计数 */ } uint16_t uart1_read(uint8_t *buf, uint16_t len) { uint16_t count = 0; while (count < len && uart1_rx_rd != uart1_rx_wr) { buf[count++] = uart1_rx_buf[uart1_rx_rd]; uart1_rx_rd = (uart1_rx_rd + 1) % UART1_RX_BUF_SIZE; } return count; }

发送部分我用了阻塞发送,因为数据量不大,每次发一帧也就十几字节,阻塞几毫秒可以接受。如果后面要发大块数据,再改成中断发送或者 DMA。

void uart1_send(const uint8_t *buf, uint16_t len) { for (uint16_t i = 0; i < len; i++) { while (!(SCI1->SSR & (1U << 7))) { /* 等待发送 FIFO 有空位 */ } SCI1->TDR = buf[i]; } /* 等待最后一个字节发送完成 */ while (!(SCI1->SSR & (1U << 6))) { ; } }

3.3 协议帧格式设计与校验

为了让手机端能可靠地解析数据,我设计了一个简单的帧格式:

字段长度说明
帧头2 字节固定 0xAA 0x55
命令字1 字节区分数据类型,如 0x01 传感器数据,0x02 配置指令
长度1 字节数据域长度,0~16
数据域N 字节实际负载
校验和1 字节从命令字到数据域所有字节的异或值

总长度最大 2+1+1+16+1 = 21 字节,超过 BLE 单包 20 字节的限制。所以我把数据域最大长度压到 15,总长 20,刚好一包发完。校验和用异或虽然简单,但对于短帧来说足够发现单字节错误,如果对可靠性要求更高,可以换成 CRC8。

手机端解析时,先找帧头,然后读长度,再收够数据后算校验和,对得上就处理,对不上就丢弃并重新找帧头。这种状态机式的解析在 Android 和 iOS 上都很好实现。

3.4 手机端 BLE 调试与数据验证

开发阶段我用了两款工具:Android 上用 nRF Connect,iOS 上用 LightBlue。这两个工具都能扫描、连接、查看服务、读写特征值、使能通知,非常方便。先用它们验证 DA14531 的透传功能是否正常:手机连上后,往 Write 特征值写一串字符,看 RA4M2 的串口调试助手里能不能收到;反过来,RA4M2 发数据,看手机端 Notify 能不能收到。

验证通过后,再写自己的 App。Android 端用BluetoothGatt类,iOS 端用CoreBluetooth。关键点是:连接后先discoverServices,找到目标服务后再discoverCharacteristics,然后对 Notify 特征值写 CCCD 使能通知。写数据时注意writeCharacteristic的返回值,如果返回 false 说明上次写还没完成,需要等回调。

实操心得:Android 上 BLE 的onCharacteristicChanged回调是在 Binder 线程里执行的,如果你要在里面更新 UI,必须切回主线程。我一开始没注意,直接在里面改了 TextView,结果程序崩溃,查了半天才发现是线程问题。

4. 常见问题与排查技巧实录

4.1 UART 通信异常排查速查表

现象可能原因排查方法解决方法
完全收不到数据TX/RX 接反交换两根线试试确认交叉连接
收到乱码波特率不匹配示波器测位宽统一两端波特率
偶尔丢数据缓冲区溢出加溢出计数加大缓冲区或加流控
中断不触发中断未使能查 NVIC 和 SCR使能 RIE 和 NVIC
数据错位帧头识别错误打印原始数据加帧头同步逻辑
蓝牙连不上固件未烧录看广播包重新烧录透传固件
连接后很快断开供电不足测电压纹波加大电容或换 LDO
手机收不到通知CCCD 未使能查 GATT 日志写 CCCD 为 0x0001

4.2 蓝牙连接不稳定的几个真实案例

第一个案例:手机能搜到广播,但一连就断。用示波器看 DA14531 的电源脚,发现连接瞬间电压从 3.3V 掉到 2.8V,明显是供电能力不足。换了一个输出电流更大的 LDO,问题解决。DA14531 在射频发射时电流脉冲很陡,如果 LDO 响应慢或者输出电容太小,电压就会塌陷。

第二个案例:连接后数据传一会儿就断。查日志发现是 MTU 协商失败。Android 默认 MTU 是 23,我请求了 247,但 DA14531 的透传固件里 MTU 最大值设的是 23,协商不成功导致连接参数更新失败。后来把手机端请求 MTU 的代码去掉,用默认值就稳定了。如果你确实需要大 MTU,得改 DA14531 固件里的MAX_MTU宏。

第三个案例:iOS 上能连,Android 上连不上。原因是 Android 对 BLE 地址类型敏感,DA14531 默认用的是公共地址,但有些 Android 手机要求用随机地址。在 DA14531 固件里把地址类型改成随机静态地址后,Android 就能正常连接了。

4.3 低功耗优化的几个关键点

如果这个项目最终要跑电池,低功耗是绕不开的。我实测下来,RA4M2 在 100MHz 全速跑的时候电流大概 20mA,DA14531 在连接状态下平均电流 1mA 左右,广播状态下 0.5mA,休眠状态下不到 10uA。所以优化重点在 RA4M2 这边。

第一个措施是降频。数据采集不需要 100MHz,降到 24MHz 后电流降到 8mA 左右,UART 波特率 115200 在 24MHz 下也能正常跑(BRR 重新算一下就行)。

第二个措施是空闲时进睡眠。RA4M2 支持多种低功耗模式,我用了 Sleep 模式,UART 接收中断能唤醒。进 Sleep 前把不用的外设时钟关掉,电流能降到 2mA 以下。

第三个措施是让 DA14531 控制连接间隔。BLE 连接间隔越短,功耗越高。透传固件默认的连接间隔可能是 30ms,我改成了 100ms,数据实时性稍微降一点,但平均电流明显下降。如果数据是突发性的,还可以让 DA14531 在空闲时主动断开,有数据时再广播重连,不过这样手机端体验会差一些,需要权衡。

注意:改连接间隔需要在 DA14531 固件里改conn_interval_min和conn_interval_max,同时手机端也要支持这个参数。有些 Android 手机会强制用自己的连接间隔,这时候只能以手机端的为准。

4.4 固件烧录与恢复的避坑经验

DA14531 烧录固件时最容易出的问题是“烧一半断了,芯片变砖”。其实它没那么容易砖,因为芯片内部有 ROM bootloader,只要上电时特定引脚拉高就能进下载模式。具体是哪个引脚,不同封装不一样,DA14531 一般是 P0_1 或者 P0_2,看数据手册的“Boot Mode”章节。

我习惯在板子上留一个跳线帽,正常运行时短接,需要下载时拔掉,省得每次找镊子短接。另外烧录工具 SmartBond Flash Programmer 有时候会识别不到芯片,先检查 JTAG 的 TCK 和 TMS 有没有接对,再检查复位脚是不是被拉低了。如果都不行,给芯片断电再上电,重新打开烧录工具,一般就能识别。

还有一个坑是固件版本兼容性。DA14531 的 SDK 更新比较频繁,不同版本的透传固件里 UART 引脚映射可能不一样。我建议在项目开始时就把固件源码和对应的 SDK 版本号记下来,后面换电脑或者重装环境时直接对照,省得重新摸索。

5. 项目扩展与后续优化方向

5.1 从透传升级到自定义 GATT 服务

透传模式虽然开发快,但所有数据都走同一个特征值,手机端不好区分数据类型。后面我打算在 DA14531 固件里加自定义 GATT 服务,把传感器数据、配置指令、设备状态分成三个特征值,手机端按需订阅。这样不仅逻辑清晰,还能针对不同特征值设置不同的通知频率,进一步省电。

改固件需要用到 Dialog 的 SDK,核心是修改user_custs1_def.c里的属性表,增加特征值定义,然后在user_custs1_impl.c里处理读写回调。SDK 的例程比较全,照着custs1的 demo 改就行,但要注意 DA14531 的 RAM 比较小,特征值加太多会编译不过,需要精简。

5.2 增加 OTA 固件升级能力

产品化之后,OTA 是刚需。DA14531 支持通过 BLE 做 OTA,官方有SUOTA(Software Update Over The Air)方案。原理是手机把新固件分包发给 DA14531,DA14531 收到后写入 Flash 的备份区,然后重启从备份区启动。这个过程需要 RA4M2 配合,因为 DA14531 的 Flash 容量有限,大固件可能需要 RA4M2 先存下来再转发。

OTA 的难点在于断点续传和校验。如果传到一半手机断了,下次连接要能从断点继续,而不是从头再来。官方 SUOTA 方案里有现成的状态机,但需要手机 App 配合实现。我打算下一步先把 SUOTA 的 demo 跑通,再集成到自己的 App 里。

5.3 多设备连接与 Mesh 的可行性

现在这个方案是点对点连接,一个手机连一个设备。如果要做多设备组网,DA14531 本身不支持 Mesh,但可以走“手机作为中心,轮流连接多个设备”的方案,或者换用支持 Mesh 的蓝牙芯片。不过 Mesh 的协议栈复杂很多,功耗也更高,对于电池供电的节点来说不一定划算。

如果只是少量设备(比如 3~5 个),我倾向于用“广播 + 扫描”的方式:设备把数据放在广播包里,手机扫描收集,不需要建立连接。这样功耗极低,但广播包只能带 31 字节,且是单向的。适合只上报数据的场景,不适合双向控制。

5.4 调试工具链的完善

目前调试主要靠串口打印和手机 App,效率一般。后面打算加一个 USB 转 UART 的调试口,用 FT231X 或者 CP2102 都行,把 RA4M2 的日志实时传到 PC 上。另外 Wireshark 配合 Nordic 的 sniffer 可以抓 BLE 空口包,分析连接参数、广播间隔、数据包重传等情况,对于排查蓝牙稳定性问题非常有帮助。

抓包的时候注意,sniffer 只能抓它所在信道的数据,BLE 有 37 个数据信道,跳频很快,所以抓到的包可能不完整。一般看广播包和连接建立过程够用了,如果要抓完整的数据流,需要设置跟随连接,让 sniffer 锁定某个连接的跳频序列。

这个项目从选型到跑通大概花了两周时间,中间踩的坑主要集中在电源、引脚映射和 MTU 协商上。RA4M2 和 DA14531 这套组合的稳定性我是满意的,后面会继续把 OTA 和自定义服务加上,让它更接近一个可量产的状态。如果你也在做类似的低功耗蓝牙数据采集,希望这些经验能帮你少走点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 9:17:58

Versal HBM系列:FPGA如何借力高带宽存储与NoC突破性能天花板

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 9:17:23

LeetCode与牛客网C/C++刷题源码:题库结构、环境配置与高频题解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 9:16:56

RM3100地磁传感器实战:从手册到驱动到PCB校准避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 9:16:45

Codex 进化史:从代码补全到软件工程智能体

1. 从"补全代码"到"接活干项目"&#xff1a;Codex这一路到底变了什么聊 Codex 之前&#xff0c;得先说一个很多人都有的误解&#xff1a;以为它就是一个更聪明的代码补全工具&#xff0c;放进 IDE 里写注释、续写函数、生成单元测试就完事了。早在 2021 年…

作者头像 李华
网站建设 2026/10/28 9:13:41

UEFI开机图标替换实战:HackBGRT安装、配置与救砖

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/28 9:13:38

JavaWeb图书馆管理系统源码:JSP+Servlet+JDBC借阅实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华