☰
STM32F2 SPI扩展双UART:WK2114裸机驱动实践
2026/10/26 18:40:49 网站建设 项目流程

简介:这份面向嵌入式开发者的WK2114串口扩展驱动资料包,围绕STM32F2系列微控制器UART接口不足的问题,给出通过UART1扩展WK2114芯片的实现思路,适用于物联网终端、工业自动化等多串口通信场景的学习与二次开发。压缩包共2个文件,包含1个C源码和1个H头文件,体积约4KB;源码侧重芯片初始化、波特率与数据格式配置、控制信号及收发数据函数实现,头文件提供函数原型、常量与结构体声明,便于工程集成和模块调用。已有1264人学习,说明其在串口扩展驱动入门与参考方面具有一定关注度。读者可从中了解WK2114与STM32F2协同工作的驱动框架、HAL库或寄存器操作方式,以及发送、接收缓冲处理的代码组织方法,并据此搭建联调测试环境、排查串口通信问题,提升多串口系统设计的灵活性。

1. 串口不够用:WK2114 在一颗 STM32F2 上把 1 路 SPI 变成 2 路 UART

去年做一台多路数据采集的板子,STM32F2 的 3 个 USART 全被占满,还差两路接计量模块和扫码枪,重新选型来不及,只能在现有 SPI 上想办法。这套 WK2114 驱动就是那时候落地的:主机侧只占一路 SPI 加一根中断线,芯片帮我们扩出两路完整 UART,每路有独立波特率、独立收发 FIFO,8N1 之外还能配校验和停止位,手册标称的速率上限足够覆盖 115200,往上跑到 921600 也有余量。它适合的场景很明确——主控串口见底、又不想换 MCU 的板子,尤其是 SPI 总线本来就闲着、只挂了一两片外设的产品。这套代码是裸机版,不依赖任何 RTOS,接到 FreeRTOS 上也只是把环形缓冲换成队列的事。

2. 硬件链路与驱动分层:SPI 四线 + IRQ 如何撑起两路 UART

2.1 接线表与电气约定

先把物理层钉死,后面所有玄学问题都能少一半。WK2114 是 SPI 从机,主机侧要接的就是四根线加两根控制线,我在板子上固定成下面这套接法,换板子只改 bsp 里的引脚宏。

信号方向STM32F2 侧说明
SCK主→从SPI1_SCK空闲低电平,模式 0
MOSI主→从SPI1_MOSI主机发命令和数据
MISO从→主SPI1_MISO读寄存器时用
CS主→从普通 GPIO 输出软件控制,不用硬件 NSS
IRQ从→主EXTI 输入,上拉开漏输出,低电平有效
RST主→从普通 GPIO 输出低电平复位

CS 我坚持用软件 GPIO,理由是硬件 NSS 在帧边界上不够干脆:一次寄存器事务中间如果被别的 SPI 设备插队,CS 的时序就乱了。软件拉低再拉高,配合临界区,帧的起止完全由代码说了算。IRQ 线上必须加外部上拉,芯片内部是开漏,靠 MCU 的内部上拉在长走线上容易边沿变缓,中断响应出现偶发丢失,这个坑我在别的板子上踩过。

SPI 参数起步不要贪快,先 1 MHz、8 位、MSB 优先、模式 0 跑通,再用示波器看 SCK 和 MISO 的波形质量往上提。WK2114 的 SPI 时钟上限和你的走线长度、上拉阻值都有关系,直接抄别人的 8 MHz 配置翻车是常事。

提示:RST 和 CS 上电默认电平要明确,MCU 复位期间 GPIO 是高阻,建议各加一颗 10k 电阻到确定电平,避免上电瞬间芯片进入不可预期的状态。

2.2 驱动分层:port 层与 core 层怎么切

这套驱动我切成三层,好处是换 MCU 只动最下面一层,换芯片型号只动中间一层。

bsp/ bsp_spi.c/.h /* SPI1 初始化、8 位全双工收发 */ bsp_gpio.c/.h /* CS / RST 控制、IRQ 引脚配置 */ drivers/wk2114/ wk2114_reg.h /* 寄存器偏移与位定义 */ wk2114_port.h /* 平台适配接口声明 */ wk2114_port.c /* 上面两个 bsp 的薄封装 */ wk2114.c/.h /* 芯片核心:初始化、读写、FIFO、ISR */ wk2114_ringbuf.c/.h /* 每通道一个的收发环形缓冲 */ app/ ext_uart_task.c /* 业务层:取数据、组帧、解析 */

core 层只认wk2114_port.h里那几个函数:port_spi_xfer()、port_cs()、port_reset()、port_delay_ms()。这样在 STM32F2 上跑通之后,我把同一份wk2114.c原样搬到另一颗 Cortex-M0 的板子上,只重写了 40 行 port 层就通了。如果一上来就把 SPI 操作和寄存器逻辑揉在一个文件里,第二次移植基本上等于重写。

另一个分层决策是:芯片驱动不碰业务,一个字节都不解析。驱动只负责「把字节从 FIFO 搬到环形缓冲」和「把环形缓冲里的字节塞进 FIFO」两件事。协议解析、超时判断、断帧处理全在 app 层。这条边界划清楚之后,扫码枪换协议、计量模块改波特率,都不用回头改驱动。

2.3 寄存器页模型:全局页、子串口页与 SPAGE 切换

WK2114 的寄存器是分页的,这是读手册时最容易看漏的一节。芯片内部有一个全局页(页号 0)和若干子串口页(页号 1 到 N,N 就是通道数),同一组偏移地址在不同页下含义完全不同。切页靠全局页里的 SPAGE 寄存器写入页号来完成。

我把常用偏移固化成宏,写在wk2114_reg.h里:

/* 全局页(SPAGE = 0) */ #define WK_REG_GENA 0x00u /* 总使能与工作模式 */ #define WK_REG_GRST 0x01u /* 软复位 */ #define WK_REG_GMUT 0x02u /* 主时钟/输出配置 */ #define WK_REG_SPAGE 0x03u /* 子串口页选择:0=全局,1..N=子串口 */ /* 子串口页(SPAGE = 1..N),页内偏移 */ #define WK_SUB_SCR 0x00u /* 波特率分频 */ #define WK_SUB_LCR 0x01u /* 数据位/校验/停止位 */ #define WK_SUB_FIER 0x02u /* FIFO 中断使能 */ #define WK_SUB_FCR 0x03u /* FIFO 控制:清空、触发水位 */ #define WK_SUB_SIER 0x04u /* 子串口中断使能 */ #define WK_SUB_SIFR 0x05u /* 子串口中断标志,写 1 清除 */ #define WK_SUB_TFCNT 0x06u /* 发送 FIFO 剩余空间 */ #define WK_SUB_RFCNT 0x07u /* 接收 FIFO 中已有字节数 */ #define WK_SUB_FSR 0x08u /* FIFO 状态 */ #define WK_SUB_LSR 0x09u /* 线路状态:溢出、帧错、奇偶错 */ #define WK_SUB_FDAT 0x0Au /* 数据口,读写都走它 */

页机制的代价是:每次访问子串口之前都要确认当前页。我在wk_dev_t里缓存了一个cur_page,写之前比较,不一样就先发一次切页事务。缓存这个字段能省掉大量重复的切页开销,但也引入了一个隐患——如果别的地方绕过 core 层直接调 SPI 改了 SPAGE,缓存就失效了。所以我规定 port 层之上不允许出现裸 SPI 调用,这条规矩救过我好几次。

注意:不同批次、不同手册版本的寄存器偏移可能不一致,尤其是 FSR、LSR 这些状态位。拿到芯片第一件事是把手头的偏移表和手册逐条对一遍,别直接抄网上的表。

3. 底层读写与初始化:从软复位到第一帧数据

3.1 SPI 事务封装:命令字节、切页与连续地址自增

所有寄存器访问都收敛到一个函数里,这是整个驱动的地基。

/* 返回值:0 成功,负值表示参数或时序错误 */ static int wk_xfer(wk_dev_t *dev, uint8_t page, uint8_t reg, uint8_t *buf, uint16_t len, int is_read) { uint8_t cmd; if (!dev || !buf || len == 0u) { return -1; } /* 1. 切页:页号变了才发,省掉重复开销 */ if (dev->cur_page != page) { uint8_t pg = page; wk_port_cs(dev, 1); /* 拉低选中 */ cmd = (uint8_t)(WK_REG_SPAGE & 0x7Fu); /* bit7=0 表示写 */ wk_port_spi_xfer(dev, &cmd, NULL, 1); wk_port_spi_xfer(dev, &pg, NULL, 1); wk_port_cs(dev, 0); /* 拉高结束 */ dev->cur_page = page; } /* 2. 地址 + 数据,同一段 CS 低电平内完成 */ wk_port_cs(dev, 1); cmd = (uint8_t)((is_read ? 0x80u : 0x00u) | (reg & 0x7Fu)); if (is_read) { wk_port_spi_xfer(dev, &cmd, NULL, 1); /* 发地址 */ wk_port_spi_xfer(dev, NULL, buf, len); /* 连续读,地址自增 */ } else { /* 写操作:地址字节和数据字节要拼在一帧里 */ uart_drv_spi_write_frame(dev, cmd, buf, len); } wk_port_cs(dev, 0); return 0; } /* 写帧的内部实现,单独抽出来是因为它和读帧的字节流方向不同 */ static void uart_drv_spi_write_frame(wk_dev_t *dev, uint8_t cmd, const uint8_t *buf, uint16_t len) { uint16_t i; wk_port_spi_xfer(dev, &cmd, NULL, 1); for (i = 0; i < len; i++) { wk_port_spi_xfer(dev, (uint8_t *)&buf[i], NULL, 1); /* 连续写时地址由芯片内部自增,不需要重复发地址 */ } }

命令字节的最高位是读写方向位,低 7 位是页内偏移,这一点和大部分 SPI 从机器件一致。连续访问时地址自动加一,所以读写 FIFO 只需要发一次地址,后面一路灌数据就行,这一点对吞吐影响很大——接收 64 字节如果每字节都重发地址,SPI 事务开销直接翻倍。

wk_port_cs()参数是「是否拉低」,我特意反着写,是因为一开始写成cs(0)表示拉低,后来改代码时看反了,调了半天才发现 CS 根本没动,血泪经验,命名上宁可啰嗦。

3.2 上电初始化:复位、波特率分频、FIFO 与中断使能

初始化顺序有讲究,乱序会出现「配置写进去了但不生效」的假象。我固定的流程是:硬件复位 → 软复位 → 全局使能 → 逐通道配置 → 最后开中断。

#define WK_XTAL_HZ 12000000u /* 芯片主时钟,按板上晶振改 */ #define WK_CH_NUM 2u /* WK2114 是两通道 */ int wk2114_init(wk_dev_t *dev, uint32_t baud) { uint8_t val; uint8_t ch; wk_port_reset(dev); /* RST 拉低再拉高,硬件复位 */ wk_port_delay_ms(10); /* 软复位,清掉所有寄存器的残留值 */ val = 0x01u; wk_xfer(dev, 0, WK_REG_GRST, &val, 1, 0); wk_port_delay_ms(5); /* 全局使能,退出复位态 */ val = 0x01u; wk_xfer(dev, 0, WK_REG_GENA, &val, 1, 0); for (ch = 1; ch <= WK_CH_NUM; ch++) { if (wk_channel_config(dev, ch, baud) != 0) { return -2; } } return 0; } static int wk_channel_config(wk_dev_t *dev, uint8_t ch, uint32_t baud) { uint16_t div; uint8_t buf[2]; uint8_t val; /* 波特率分频:先按线性公式算,误差大就查手册的分频表修正 */ div = (uint16_t)((WK_XTAL_HZ + baud / 2u) / baud); buf[0] = (uint8_t)(div & 0xFFu); buf[1] = (uint8_t)(div >> 8); wk_xfer(dev, ch, WK_SUB_SCR, buf, 2, 0); /* 8 数据位、无校验、1 停止位 */ val = 0x03u; wk_xfer(dev, ch, WK_SUB_LCR, &val, 1, 0); /* 清空收发 FIFO,接收触发水位设成 8 字节 */ val = 0x03u; wk_xfer(dev, ch, WK_SUB_FCR, &val, 1, 0); /* 开接收中断和线路状态中断 */ val = 0x03u; wk_xfer(dev, ch, WK_SUB_FIER, &val, 1, 0); val = 0x01u; wk_xfer(dev, ch, WK_SUB_SIER, &val, 1, 0); return 0; }

分频值这里我用了线性公式,实际手册给的是分频表,115200 这种常用波特率算出来的误差通常在可接受范围,但如果你要用 250000 这类非标波特率,务必拿示波器量 TXD 的位宽,误差超过 2% 就得回去查表。晶振频率写成宏,换板子只改一个数。

初始化里最容易漏的是 FCR 的清 FIFO 位——它是自动清零的,写完立刻恢复,不需要手动回读确认。但如果你在清 FIFO 之后马上往里写数据,有可能被清掉,中间隔几个微秒更稳。

3.3 中断引脚与 ISR 骨架:一次 IRQ 扫完两个通道

IRQ 是电平有效、开漏输出,芯片内部有中断挂起就会一直拉低。MCU 侧配成下降沿触发,但如果 ISR 里没把中断清干净,退出中断后引脚还是低,会立刻再进一次,表现出来就是「程序卡在中断里出不来」。

/* 在 stm32f2xx_it.c 里挂到对应的 EXTI 线上 */ void EXTIx_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Linex) != RESET) { EXTI_ClearITPendingBit(EXTI_Linex); wk2114_irq_handler(&g_wk_dev); /* 只做搬运,不做解析 */ } } void wk2114_irq_handler(wk_dev_t *dev) { uint8_t ch; uint8_t flag; for (ch = 1; ch <= WK_CH_NUM; ch++) { if (wk_xfer(dev, ch, WK_SUB_SIFR, &flag, 1, 1) != 0) { continue; } if (flag == 0u) { continue; /* 这一路没事件 */ } /* 写 1 清除,先清再处理,避免处理期间又被自己打断 */ wk_xfer(dev, ch, WK_SUB_SIFR, &flag, 1, 0); if (flag & 0x01u) { wk_rx_isr(dev, ch); /* 接收事件 */ } if (flag & 0x02u) { wk_tx_isr(dev, ch); /* 发送完成/水位事件 */ } if (flag & 0x04u) { wk_err_isr(dev, ch); /* 溢出、帧错、校验错 */ } } }

「先清标志再处理」这个顺序是故意的。如果先处理再清,处理过程中 FIFO 又进数据产生新事件,清标志的时候会把新事件一起清掉,表现为偶发丢字节,而且极难复现。清完再进去搬 FIFO,即使搬运过程中又来了新数据,ISR 退出后 IRQ 线还是低,会再进一次,不会丢。

如果两个通道同时有事件,这个循环会一起处理完,不会出现某个通道饿死的情况。代价是 ISR 执行时间变长,所以里面的活必须轻——只搬数据,不做任何协议判断。

4. 收发链路:环形缓冲、FIFO 水位与 485 方向切换

4.1 接收路径:ISR 只搬字节,协议解析交给主循环

接收的入口是 RFCNT 寄存器,它告诉你 FIFO 里现在有多少字节。读它、循环读 FDAT、塞进环形缓冲,就这三步。

static void wk_rx_isr(wk_dev_t *dev, uint8_t ch) { uint8_t cnt; uint8_t tmp[32]; uint8_t i; wk_ring_t *rb = &dev->ch[ch - 1].rx_ring; if (wk_xfer(dev, ch, WK_SUB_RFCNT, &cnt, 1, 1) != 0 || cnt == 0u) { return; } if (cnt > sizeof(tmp)) { cnt = sizeof(tmp); /* 单次最多搬 32 字节,防止 ISR 过长 */ } /* 一次事务连续读出,地址自增,不用逐字节发地址 */ if (wk_xfer(dev, ch, WK_SUB_FDAT, tmp, cnt, 1) != 0) { return; } for (i = 0; i < cnt; i++) { if (wk_ring_put(rb, tmp[i]) != 0) { dev->ch[ch - 1].rx_overflow++; /* 只有统计,不丢帧不阻塞 */ } } }

环形缓冲是单生产者单消费者模型:ISR 里只调用wk_ring_put(),主循环里只调用wk_ring_get(),两边都不加锁,靠 head/tail 指针的原子读写保证正确。这种写法在 Cortex-M 上成立的前提是指针是 32 位对齐访问,编译器不会把它拆成两次半字写。如果换成 8 位机,就得进临界区。

单次最多搬 32 字节是我加的限制,原因是 ISR 里一次 SPI 事务太长会影响其他中断的响应。RFCNT 读出超过 32 字节说明数据积压了,这时候剩下的部分会在 ISR 退出后再进来一次继续搬,不会丢——前提是 FIFO 没溢出。FIFO 溢出会置位 LSR 里的溢出标志,我在wk_err_isr()里把它累加到一个计数器里,调试时先看这个数,比盲目加打印快得多。

主循环侧的取数接口就一行:

/* 返回实际读到的字节数,0 表示当前没有数据 */ int wk2114_read(wk_dev_t *dev, uint8_t ch, uint8_t *buf, uint16_t len);

业务层拿到字节之后怎么组帧、怎么判断帧头帧尾、超时多久判为断帧,全部在这一层之上,驱动不参与。

4.2 发送路径:用 FIFO 水位续命,别在主循环里死等

发送最容易写错的地方是「发完一个字节等一个字节」。芯片有 FIFO,正确姿势是一次往里灌到水位,然后让中断通知你什么时候还能灌。

/* 尝试把环形缓冲里的数据推入芯片 FIFO,返回推入的字节数 */ int wk2114_write(wk_dev_t *dev, uint8_t ch, const uint8_t *buf, uint16_t len) { uint8_t space; uint16_t n = 0; wk_ch_t *c = &dev->ch[ch - 1]; /* 查询发送 FIFO 剩余空间 */ if (wk_xfer(dev, ch, WK_SUB_TFCNT, &space, 1, 1) != 0 || space == 0u) { return 0; } if (len > space) { len = space; } while (n < len) { if (wk_ring_get(&c->tx_ring, &c->tx_tmp) != 0) { break; } n++; } /* 这里为了可读性做了简化,实际是先把 ring 里的数据成批拷进 tmp 数组再一次写 FDAT */ if (n > 0u) { wk_xfer(dev, ch, WK_SUB_FDAT, c->tx_buf, n, 0); } return (int)n; }

TFCNT返回的是剩余空间而不是已用空间,这个语义在手册里写得很清楚,但第一次看容易搞反,结果就是 FIFO 明明空着却一直不往里写。判断条件写成space == 0才对。

发送完成的判定有两种:FIFO 空标志,或者发送完成中断。前者适合轮询,后者适合中断驱动。我一般用「水位中断 + 主循环补数据」的组合:FIFO 里剩余空间低于阈值时触发中断,ISR 里只置一个标志,真正往 FIFO 灌数据在主循环里做。这样 ISR 足够短,也避免了在中断里访问环形缓冲的复杂时序。

4.3 RS485 半双工:自动方向与手动 GPIO 两种做法

板子上如果挂的是 485 收发器,方向控制是必踩的坑。WK2114 本身支持自动方向控制,通过配置寄存器让某根输出脚在发送时自动翻转,省掉 MCU 干预。这条路走通之后很省心,但有两个前提:一是使能位和极性要配对,二是收发器的 DE/RE 接法要和芯片的输出逻辑对上。

自动方向配不通的时候,退回到手动 GPIO 也行:

/* 手动方向控制:发完最后一个字节后,必须等移位寄存器空再切回接收 */ static void rs485_send_frame(wk_dev_t *dev, uint8_t ch, const uint8_t *buf, uint16_t len) { rs485_de_high(); /* 切到发送 */ wk2114_write(dev, ch, buf, len); /* 等 FIFO 空 + 最后一个字节移出,两个条件都要满足 */ while (wk_tx_fifo_empty(dev, ch) == 0) { ; } wk_port_delay_us(baud_char_time_us(dev->baud)); /* 一个字符时间的余量 */ rs485_de_low(); /* 切回接收 */ }

关键在最后那个延时。FIFO 空只代表数据都交给移位器了,最后一个字节的停止位还没发完,这时候切回接收,对方收到的最后一字节会被截断,现象是「偶尔最后一字节是乱码」,而且波特率越低越明显。延时一个字符时间是最低要求,保守一点给两个。

注意:这个while是阻塞的,只在初始化阶段或者低频发送时用。如果业务里要连续发大包,改成状态机 + 定时器,别在主线里死等。

5. 避坑与排查:能读到芯片却收不到数据,问题多半在这五处

5.1 SPI 层:读得到 ID,读不到数据

现象:初始化时读某个寄存器返回固定值,看起来通信正常,但一配波特率就失败,或者写进去读出来对不上。

原因:SPI 模式或 CS 时序不对。最常见的是 CPOL/CPHA 配成了模式 3,主机在第一个边沿采样,从机还没把数据推上来,读到的全是 0xFF 或 0x00。另一种是 CS 在一帧中间被拉高,切页和数据被拆成两段,芯片把后半段当成新命令解析。

解决:先把 SPI 降到 500 kHz,用逻辑分析仪抓一帧完整的写-读,确认 CS 拉低期间时钟数、数据位序都对得上。确认无误再往上提速。CS 的操作一定要包在临界区里,别让其他 SPI 设备插进来。

5.2 中断层:程序卡在 ISR 里出不来

现象:一开中断,主循环就再也不执行了,调试器暂停发现一直在 EXTI 里。

原因:中断标志没清干净。IRQ 是电平有效,SIFR 里只要还有一位没清,引脚就保持低电平,退出中断立刻重新进入。尤其是有多个中断源时,只清了接收位忘了清错误位,一样会卡死。

解决:ISR 里先读 SIFR,把整个字节原样写回去清除(写 1 清零的寄存器,写回读到的值最稳妥),再按位处理。处理完之后再读一次确认归零,非零就再清一次。这个「再读一次」的兜底动作,我在每个带电平中断的驱动里都会加。

5.3 FIFO 层:数据能收到,但每隔一段就丢几个字节

现象:低速通信正常,一旦波特率提到 115200 并且连续发大包,就固定间隔丢字节,丢的位置还不固定。

原因:接收 FIFO 溢出。ISR 响应不够快,或者单次搬运的字节数太少,FIFO 里的数据超过了容量。另外接收触发水位设得太高,比如设成 32 字节才触发中断,而 FIFO 只有 16 字节深,永远等不到中断。

解决:把接收触发水位降到 8 字节甚至 4 字节,让中断更早进来;ISR 里一次把 RFCNT 读空,别只搬固定几个字节。同时在错误中断里累加溢出计数,跑一段时间看这个数是不是 0,这是判断接收链路健不健康最直接的指标。

5.4 时钟层:波特率怎么算都对不上

现象:能收数据但全是乱码,或者对方收到的是错位字符。

原因:WK_XTAL_HZ宏和板上实际晶振不一致。板子换了个 8 MHz 的晶振,代码里还写着 12 MHz,分频值算出来偏差 50%,自然全乱。另一种是分频寄存器高低字节写反了。

解决:拿示波器量 TXD 引脚,发一个 0x55(二进制 01010101),直接看方波周期对不对得上波特率。对不上就回去核对晶振宏和字节序。这个办法比任何打印都直观,我现在调试串口第一件事就是发 0x55。

5.5 应用层:两路串口互相干扰

现象:单独跑一路正常,两路同时跑就出问题,比如 A 路的回复发到了 B 路。

原因:切页缓存失效。某处代码绕过 core 层直接操作了 SPI,改掉了 SPAGE,但cur_page还记着旧值,后续所有访问都写到了错误的子串口。

解决:在 core 层加一个断言,每次wk_xfer前后校验cur_page和实际读回的 SPAGE 是否一致,不一致就报错。另一个办法更彻底——禁止 port 层之上出现裸 SPI 调用,用代码评审和静态检查守住这条线。

6. 二次开发:从 2 路扩到 4 路、接 DMA、搬进 RTOS

这个驱动我后来改过三个方向,都是小改动,前提是前面那套分层没被破坏。

扩通道是最简单的。同系列有 4 通道的型号,寄存器布局完全一致,只改WK_CH_NUM宏和初始化循环的上界,业务侧的通道索引也跟着变。真正要动的地方是中断处理里的循环次数和业务层给每个通道分配缓冲区的数组长度,我在wk_dev_t里把通道数组定义成WK_CH_NUM长度,扩的时候编译器会帮你把越界的地方都指出来。

接 DMA 要改的是wk_port_spi_xfer()。SPI 收发改成 DMA 双缓冲,core 层的逻辑一行都不用动,因为接口签名没变。这里有个顺序问题要注意:DMA 完成中断和 WK2114 的 IRQ 是两个中断源,搬运过程中如果 IRQ 进来又要访问 SPI,会冲突。我的做法是给 SPI 总线加一个互斥标志,谁占用谁置位,另一边退让重试。

搬进 RTOS 最简单,把wk2114_read()外面包一层消息队列,ISR 里发信号,任务里取数据解析。

改造方向改动文件改动量需要注意
扩到 4 通道wk2114.h、业务层数组改宏 + 数组长度通道索引从 1 开始
接 DMAwk2114_port.c重写 xfer加总线互斥,防重入
移植 RTOSext_uart_task.c加队列和任务ISR 里只发信号不解析

最后说个教训。早期为了图快,我把切页逻辑写在了业务层,每个函数开头先写一次 SPAGE,当时觉得挺直观。结果第二个开发者接手,加了个新功能没写切页,查了两天才定位到。从那以后我每次做这类分页寄存器芯片的驱动,都强制把页号封装在读写函数内部,业务层连 SPAGE 这个寄存器名都看不到。接口收窄一点,后面的人就少踩一个坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询