
简介这是一份基于 STM32Cube HAL 库的 WK2124 SPI 扩展四串口驱动移植工程面向使用 STM32F103C8 且需要补充串口资源的嵌入式开发者用于解决 MCU 原生串口不足、外设需多路 UART 通信的常见问题工程已实测通过。压缩包内共 1187 个文件以源码为主836 个 .c 文件与 255 个 .h 文件组成完整驱动和用户程序配合 .icf 链接脚本、.uvprojx Keil 工程、.ioc CubeMX 配置以及少量汇编与静态库文件构成一套可直接打开的 HAL 版开发项目整体约 18.33MB。作者还额外放进 ARM CMSIS-DSP 数学库便于在四串口通信基础上进一步做信号处理目前已有 801 人学习下载。对正在学习 STM32Cube HAL 库或计划移植 WK2124 的开发者来说研读这些文件即可掌握 SPI 主模式初始化、WK2124 寄存器配置、收发中断处理等关键点并可作为二次开发起点。1. SPI 扩展串口的正确打开方式一份 WK2124 STM32 HAL 移植成功的完整 Demo做嵌入式的人迟早会遇到一个场景板子上要挂的串口设备比 MCU 的 USART 外设还多。某开发者当年在调试一块多路传感器采集板时4 路串口屏加 GPS 模块直接让 STM32 的两个 USART 捉襟见肘最后靠 WK2124 这颗 SPI 扩展串口芯片把问题解决。这份开源出来的 Demo 工程就是 WK2124 在 STM32 上基于 Cube HAL 库从驱动移植到回环测试全部跑通的样板。它适合正被串口资源卡脖子的人也适合想抄一套 SPI 外设驱动框架的熟手。新手拿到能直接编译下载验证硬件老手能从中看到寄存器页管理、中断链路和波特率配置的完整实现。2. SPI 扩展串口的工程骨架WK2124 驱动资源与文件分层2.1 为什么是 WK2124一颗芯片换四路硬件串口WK2124 本质上是 SPI 接口的 4 通道 UART 扩展芯片。MCU 通过 SPI 总线向芯片写入寄存器芯片负责把数据从对应通道的 TX/RX 引脚发出去。对比用 GPIO 模拟串口WK2124 的方案有几个实打实的优势波特率由芯片内部硬件分频不占 CPU 就能保证时序准确每路通道自带收发 FIFO在高波特率下不容易丢字节接收数据时靠 INT 中断引脚通知 MCUCPU 不必轮询。这个 Demo 选择 STM32 的 HAL 库来对接贴合多数人现在的工程习惯。HAL 封装的 SPI 接口把底层寄存器差异挡在驱动之外移植到其它型号的 STM32 时只需要改 spi 句柄和引脚宏定义。工程里明确标注了「移植测试成功」意味着从初始化、读写寄存器到中断收发的整套链路都经过实际验证拿来就能用。2.2 工程目录结构驱动文件放哪层改动找哪几个文件解压 zip 后是一个完整的 CubeMX 工程目录结构大致如下WK2124_STM32_DEMO/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── spi.h │ │ └── gpio.h │ └── Src/ │ ├── main.c │ ├── spi.c │ ├── gpio.c │ └── stm32f1xx_it.c ├── Drivers/ │ ├── BSP/ │ │ ├── wk2124.h │ │ └── wk2124.c │ └── CMSIS/ ├── MDK-ARM/ │ ├── WK2124_STM32_DEMO.uvprojx │ └── ... └── WK2124_STM32_DEMO.ioc真正需要维护的驱动代码只有 BSP 目录下的wk2124.c和wk2124.h两个文件。main.c里是 Demo 的测试入口演示了初始化后往某个通道发送数据的过程。stm32f1xx_it.c里挂的是 EXTI 中断回调WK2124 的 INT 引脚触发中断后从这里进入驱动层的接收处理函数。这个分层的好处是底层 SPI 和 GPIO 由 CubeMX 生成WK2124 的协议逻辑全部隔离在 BSP 层迁移到别的项目直接复制这两个文件即可。2.3 驱动对外接口初始化、收发、中断三个面wk2124.h里声明的接口不多但麻雀虽小五脏俱全。初始化、单字节收发、字符串收发、中断处理都覆盖到了void wk2124_init(void); uint8_t wk2124_read_reg(uint8_t reg); void wk2124_write_reg(uint8_t reg, uint8_t val); void wk2124_set_baudrate(uint8_t channel, uint32_t baud); void wk2124_send_byte(uint8_t channel, uint8_t data); uint8_t wk2124_receive_byte(uint8_t channel); void wk2124_int_handler(void);开发者拿到驱动后最关心的就三件事初始化能不能正常握手、发数据走哪个函数、收到数据怎么通知我。wk2124_init里做了芯片软复位和 SPI 通路自检回读寄存器失败时会停在断言处方便定位硬件接线问题。wk2124_int_handler是中断接收的入口被外部 EXTI 中断调用它内部会依次检查 4 个通道的中断标志把 FIFO 里的数据搬进各自的接收缓存。这套接口设计没有绑定具体的业务逻辑二次开发时可以直接复用。3. 核心驱动拆解从寄存器读写到四通道中断收发的完整链路3.1 SPI 底层读写三字节帧与片选时序WK2124 的寄存器读写依赖 SPI 帧驱动的所有功能都建立在wk2124_read_reg和wk2124_write_reg这两个函数上。本 Demo 采用三字节帧首字节带读写标志和寄存器地址中间字节填充数据第三字节留给芯片回状态。片选 CS 由 GPIO 手动控制每次事务开始拉低、结束拉高。void wk2124_write_reg(uint8_t reg, uint8_t val) { uint8_t tx[3]; tx[0] reg 0x7F; // 写命令最高位置 0 tx[1] val; tx[2] 0x00; wk2124_cs_low(); HAL_SPI_Transmit(hspi1, tx, 3, 100); wk2124_cs_high(); } uint8_t wk2124_read_reg(uint8_t reg) { uint8_t tx[3], rx[3]; tx[0] 0x80 | (reg 0x7F); // 读命令最高位拉高 tx[1] 0x00; tx[2] 0x00; wk2124_cs_low(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 3, 100); wk2124_cs_high(); return rx[2]; // 第三字节携带读回的数据 }这里有几个细节值得注意。第一SPI 时钟极性配置成 Mode 0CPOL0CPHA0这是 WK2124 最容易跑通的时序参数如果通信不稳定再尝试 Mode 1。第二写寄存器时第三字节发送 0x00 是为了凑够完整的 SPI 帧芯片在第三字节的上升沿完成数据锁存。第三CS 每次事务后必须拉高否则芯片会把连续两次访问当成同一个事务寄存器地址会错位。这些坑在第五章还会详细展开。3.2 通道寄存器页管理别让 FCR 变成坑WK2124 的寄存器分为全局区和通道区。通道区的寄存器地址是复用的想访问某个通道的寄存器得先通过 FCR 寄存器把通道页切过去。不少移植者第一次用这颗芯片就栽在这里写通道 1 的波特率寄存器没问题写通道 2 时发现读回的寄存器值还是通道 1 的。static void wk2124_select_channel(uint8_t channel) { // 通过 FCR 寄存器的低两位切换通道寄存器页 uint8_t fcr wk2124_read_reg(WK2124_FCR); fcr (fcr 0xF0) | (channel 0x03); wk2124_write_reg(WK2124_FCR, fcr); }每次切换通道时先读回 FCR 的当前值再用位操作改写低两位避免把高位里保存的 FIFO 触发级别配置冲掉。真正省事的是把页切换封装成一个静态函数所有写通道级寄存器的代码都走wk2124_select_channel而不是在业务代码里裸写 FCR。这个 Demo 在wk2124_set_baudrate和中断处理里都调用了它保证中断里读了通道 1 的 FIFO 后再读通道 2 不会拿到残留数据。3.3 中断驱动接收从 EXTI 到 FIFO 的完整链路WK2124 收到串口数据后会拉低 INT 引脚MCU 的 EXTI 中断随即触发。驱动里的接收逻辑不是直接读某个固定寄存器而是先读 IIR中断标识寄存器判断中断来源再按通道号从 FIFO 里取数据。void EXTI15_10_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(WK2124_INT_PIN)) { __HAL_GPIO_EXTI_CLEAR_IT(WK2124_INT_PIN); wk2124_int_handler(); } } void wk2124_int_handler(void) { uint8_t iir wk2124_read_reg(WK2124_IIR); uint8_t channel (iir 2) 0x03; // 解析中断来源通道 wk2124_select_channel(channel); uint8_t lsr wk2124_read_reg(WK2124_LSR); if (lsr 0x01) { // 接收数据就绪标志 uint8_t data wk2124_receive_byte(channel); // 把 data 放入 channel 对应的环形缓冲区 } }这段代码的巧妙之处是把 IIR 解析和通道切换绑在一起。IIR 的低位直接映射到中断通道号省去了逐个通道遍历的耗时。LSR 的 bit0 是数据就绪位读取后自动清除防止同一帧数据被反复读取。实际使用时环形缓冲区要按 4 个通道各准备一份否则高负载下会互相覆盖。Demo 里在main.c中定义了一个二维数组rx_buf[4][128]来承接各通道的数据。3.4 初始化与波特率配置软复位加回读自检初始化流程分三步SPI 外设复位、芯片软复位、回读版本号验证通路。WK2124 的软复位通过向 GCR 寄存器写入复位位实现复位后需要延时等待芯片内部逻辑稳定否则后续寄存器写操作会丢失。void wk2124_init(void) { // 1. 软件复位芯片 wk2124_write_reg(WK2124_GCR, 0x01); HAL_Delay(10); // 2. 验证 SPI 通路读回芯片版本寄存器 uint8_t version wk2124_read_reg(WK2124_GCR); if (version 0x00) { // 通信异常检查接线或 SPI 配置 } // 3. 逐通道配置默认波特率 115200 for (uint8_t ch 0; ch 4; ch) { wk2124_set_baudrate(ch, 115200); } }波特率配置的核心是分频计算。WK2124 芯片通常外接 14.7456MHz 晶振公式为分频值 时钟频率 / (16 * 波特率)。以 115200 为例算出的分频值接近 8写入 DLL/DLM 两个寄存器即可。这里要警惕如果板子上的晶振不是 14.7456MHz算出的波特率会偏差串口助手收到的全是乱码这也是第五章要讲的典型坑。4. 从 CubeMX 到串口回显跑通 STM32 WK2124 的实测步骤4.1 硬件接线表四根 SPI 线加一根中断线WK2124 与 STM32 的连接非常干净典型接线如下WK2124 引脚STM32 引脚说明SCLKSCK如 PA5SPI 时钟MOSIMOSI如 PA7主发从收MISOMISO如 PA6主收从发CS任意 GPIO如 PB12片选软件控制INT任意 EXTI 引脚如 PA8中断通知VDD3.3V注意芯片是否支持 5VGNDGND共地需要注意 WK2124 的电源电压范围部分型号支持 2.5V 到 5V但板子和 MCU 电平不匹配时需要加电平转换。INT 引脚必须接到支持外部中断的 GPIO 上CubeMX 里要对该引脚单独配置 EXTI 模式。CS 不建议直接用硬件 NSS因为 HAL 在管理 NSS 时会自动拉高拉低多片级联时容易混乱。4.2 CubeMX 工程配置SPI 模式与中断优先级打开.ioc文件检查以下几个关键配置项。SPI 外设设为 Full-Duplex Master时钟极性选择 Low 和 1 Edge 对应 Mode 0速率先保守设到 1 Mbps跑通后再逐步提高。CS 引脚设为 GPIO Output 推挽模式初始电平为 High。INT 引脚设为 GPIO_EXTI 模式触发方式选下降沿。/* SPI1 初始化代码片段 */ hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB;中断优先级建议设置为高于所有串口中断。原因是 WK2124 的 FIFO 深度有限如果接收中断被其它高优先级任务长时间抢占芯片 FIFO 溢出会直接丢数据。CubeMX 生成代码后还需要在stm32f1xx_it.c里补上 EXTI 中断处理函数并在main.c里先调用wk2124_init()再进入主循环。4.3 验证步骤回环测试与 PC 串口比对工程编译下载后把 WK2124 的通道 0 TX 和 RX 用杜邦线短接然后在main.c里写一段循环发送测试代码。STM32 通过 SPI 把字符串发给 WK2124 通道 0由于 TX/RX 短接芯片会把自己发出去的数据收回来触发 INT 中断回调函数里把收到的数据通过 STM32 的 USART1 转发到 PC 串口助手。串口助手若能连续显示相同内容说明 SPI 读写、通道切换和中断接收三条链路全部正常。while (1) { wk2124_send_string(0, hello wk2124\r\n); HAL_Delay(500); }如果回环测试一切正常再把短接的杜邦线拆掉接入真实的串口设备比如串口屏或 GPS 模块验证对外设的收发是否符合预期。这一步能暴露芯片与外部设备的电平匹配问题因为某些串口设备是 5V 电平直接连 3.3V 的 WK2124 会出现偶尔收错字节的现象需要加电平转换或分压电阻。5. 移植常见问题排查SPI 无响应、乱码、丢数据的四条记录5.1 读寄存器全是 0x00请求被芯片无视现象初始化时回读芯片版本号一直为 0代码卡在断言处。原因第一优先级怀疑 SPI 时钟极性和相位不匹配。WK2124 的 SPI 从机对时序要求比普通 Flash 芯片严格某些器件只有在特定的 CPOL/CPHA 组合下才能稳定响应。解决用逻辑分析仪抓取 MOSI 线上的波形对比芯片手册里的时序图。确认 SCLK 空闲电平和高低电平采样边沿后在 CubeMX 里切换 SPI 模式重试。本 Demo 默认 Mode 0 跑通但个别批次的芯片对 Mode 3 更友好驱动里最好做一个模式宏方便切换。5.2 串口收到数据全是乱码波特率对不上现象WK2124 与 PC 串口助手通信显示的内容可读但夹杂大量乱码字符。原因波特率分频计算用的时钟源和实际晶振不一致。WK2124 芯片的时钟可能来自外部晶振、内部 RC 或者由 SPI 写入的配置时钟不同板子的实际工作频率差异很大。解决用示波器测量 WK2124 通道 TX 引脚的输出波形读出一个完整位的时间算出实际波特率。如果偏差超过 2%检查wk2124_set_baudrate里的时钟频率宏定义把它改成当前板子的实际晶振频率。另外要确认计算公式里的 16 倍过采样因子如果芯片手册写的是 8 倍结果会差一倍。5.3 只有通道 0 正常其它通道寄存器写不进现象通道 0 能收发数据通道 1、2、3 配置波特率后无响应。原因这是 WK2124 经典的寄存器页切换问题。初始化流程里先配置通道 0接着配置通道 1 时忘记通过 FCR 切换页导致波特率写进的是通道 0 的寄存器通道 1 实际运行在默认的未知波特率上。解决所有通道级寄存器访问统一走wk2124_select_channel辅助函数。每次进入函数先保存当前 FCR 值切完通道写完寄存器后立即恢复原来的 FCR 配置。这能避免主循环里的业务代码在不知情的情况下把一个通道的配置写到另一个通道上。调试时还可以在每次切换后读回目标寄存器做断言验证页切换是否生效。5.4 数据偶尔丢失高波特率下丢包严重现象115200 波特率下持续收发大数据包PC 端收到的数据中间有缺失。原因接收中断服务函数里执行了过多的 SPI 读写操作尤其是读一个 FIFO 字节要完整走一遍三字节 SPI 帧中断占用时间过长。另一个原因是 EXTI 中断被其它高优先级中断打断WK2124 的 FIFO 溢出。解决中断服务函数里只负责把 FIFO 数据搬进环形缓冲区业务处理放到主循环。同时把 WK2124 的 EXTI 中断优先级提到最高不要让串口中断、定时器中断抢占它。如果仍然丢包可以用 DMA 方式搬运 SPI 接收数据把 SPI 读操作从 CPU 中断里卸载下来。6. 改造方向把 WK2124 驱动收敛成可复用的串口组件驱动跑通只是第一步工程上真正头疼的是怎么把这套代码接进现有系统。比较实用的改造是抽象一层uart_ops接口把 WK2124 和芯片自带的 USART 统一起来上层业务代码不必关心后端到底是哪个器件。typedef struct { void (*init)(void); void (*set_baud)(uint8_t ch, uint32_t baud); void (*send)(uint8_t ch, uint8_t *buf, uint16_t len); uint16_t (*recv)(uint8_t ch, uint8_t *buf, uint16_t len); } uart_ops_t; const uart_ops_t wk2124_ops { .init wk2124_init, .set_baud wk2124_set_baudrate, .send wk2124_send_bytes, .recv wk2124_recv_bytes, };有了这层抽象对接 RTOS 就是水到渠成的事。把wk2124_recv_bytes放到独立的接收任务里阻塞等待信号量配置wk2124_send_bytes走消息队列发送就能做成一个标准的字符设备驱动。多片 WK2124 级联的时候为每颗芯片分配一个独立的 CS 引脚把片选和中断回调参数打包成结构体注册进上层的设备表按chip_id区分操作对象即可。每次移植这类 SPI 外设驱动某开发者都习惯先画一张寄存器访问时序图再动代码。页寄存器切换、中断标志清除、CS 时序这些细节画在纸上比记在脑子里可靠得多也方便团队里其他人接手。希望这份 WK2124 的 Demo 解析能帮你在串口资源紧张的时候多一条路子。本文还有配套的精品资源点击获取