☰
STM32串口不够用?WK2124 SPI扩展四路UART移植实战
2026/10/19 4:39:31 网站建设 项目流程

简介:一份基于STM32Cube HAL库的WK2124 SPI扩展四串口移植工程,面向需要在STM32F103等MCU上扩展多路UART的嵌入式开发者,适用于物联网网关、工业控制器与数据采集设备等场景。压缩包共1187个文件,体积18.33MB,以C源码和H头文件为主体,含51个汇编启动文件、28个IAR链接脚本及少量静态库;目录按源码、链接脚本、工程配置分类存放,还包含CubeMX的.ioc文件与IAR/Keil项目文件,便于直接打开对比研究。代码内容覆盖SPI主模式初始化、WK2124寄存器配置、串口中断处理、收发测试与错误排查思路,还附带CMSIS/DSP算法库,便于在Cortex-M3上做信号处理或数学运算扩展。作者已标注移植测试成功,F103C8平台上的HAL库适配代码具备直接参考价值;目前已有801人学习下载,适合需要复用SPI转四串口方案或深入学习STM32外设驱动的开发者,可直接参照工程快速搭建原型或对比排错。

1. 串口不够用,为什么我最后选了 WK2124 加 SPI 扩展

做嵌入式项目做到一半,最难受的不是逻辑写不出来,而是硬件引脚不够用。特别是用 STM32 做带多个外设的产品,主控串口总共就那么几个,又要接调试、又要接蓝牙、又要接上位机,端口一挤就翻车。当时项目里需要在一条 SPI 总线上挂一块扩展芯片,把一路串口扩成四路独立 UART,最开始想过用普通 IO 模拟串口,但波特率稍微一高就丢数据,稳定性完全没法保证。换了 WK2124 之后,几行初始化代码就能把四路串口全部拉起来,整套方案从硬件布线到驱动移植都清爽很多。这篇方案就是围绕「WK2124 在 STM32 上基于 Cube HAL 库的移植」展开的,适合正在做毕设、课设或者实际产品选型的同学参考,照着这套设计思路,能少走很多弯路。

2. 总体架构与硬件设计:SPI 从设备、四路 UART 与中断线的分工

2.1 WK2124 到底是个什么角色

WK2124 本质上是一个 SPI 接口转四路 UART 的桥接芯片,主控通过 SPI 总线向它写入寄存器配置,由它把数据从 UART 通道收进来、缓冲好,再通过 SPI 读回来。反过来,主控想往某一路串口发数据,也是先写 SPI,芯片内部再把数据从对应通道发出去。所以对 STM32 来说,它看到的只是一个 SPI 从设备,外加一个中断引脚用来通知「有数据来了」。

这个设计有个很实际的好处:主控不需要为每一路串口分配独立的 UART 外设和中断,只要有一条可用的 SPI 总线,就可以管理四路串口。在 F103 这类中低端芯片上,SPI 外设是现成的,HAL 库也管好了底层收发,我们只需要关心数据格式和应用逻辑,开发难度一下就降下来了。

硬件接线通常这样规划:

  • SPI 四线:SCLK、MOSI、MISO、CS,接到 STM32 对应的 SPI 引脚
  • WK2124 的 INT 引脚接 STM32 的一个 EXTI 外部中断引脚,用于接收数据到达通知
  • WK2124 的 RST 引脚可由 STM32 GPIO 控制,实现软复位
  • 四路 UART 的 TX/RX 分别接到外部设备

用 CubeMX 配置的时候,我会把 SPI 设为全双工主机模式,速度先按 1Mbps 左右设置,等调通了再往上提。CS 片选可以交给硬件 NSS,也可以手动用 GPIO 控制,两个做法我都试过。如果你用的是带 NSS 的引脚且芯片只有一个从设备,直接开硬件 NSS 没问题;但如果你后续想挂多个 SPI 从设备,手动 GPIO 控制会更灵活,因为 WK2124 的片选时序一旦不对,寄存器读写会直接跑飞。

2.2 寄存器读写是整套驱动的地基

WK2124 的寄存器寻址方式类似标准 SPI 设备:主机发出地址字节,芯片返回或者接收数据。不同寄存器控制不同功能,比如线路控制寄存器决定波特率分频、数据位、停止位、校验位,FCR 寄存器控制 FIFO 开关和触发深度,还有中断使能寄存器负责开关每一路的接收中断。

通常的做法是先在驱动里封装两个基础函数:读寄存器和写寄存器。所有上层操作都建立在它们之上。写寄存器时,CS 拉低,发送地址字节(含读写标志位),然后发送数据字节,最后 CS 拉高。读寄存器时,CS 拉低,发送地址字节,再读取数据字节,CS 拉高。

这里有一个细节容易坑到人:寄存器地址在不同版本的数据手册里定义可能略有差别,移植前一定要以芯片手册为准,把地址宏定义和手册逐条核对一遍。我在第一次移植时就是直接用了某个项目里的头文件,结果寄存器地址偏了一位,读出来的状态永远不对,查了整整一个下午。

2.3 四路串口的波特率配置逻辑

WK2124 内部有独立的波特率发生器,每一路 UART 都可以单独设置,但波特率分频系数不是直接写入一个「9600」这样的数值,而是根据输入时钟和分频寄存器算出来的。所以驱动里要提供一个波特率换算函数,把目标波特率换算成分频值。

换算公式通常由芯片手册给出,不同内核版本可能带不同的预分频选项。实际项目中,我会把分频计算做成一个独立模块,输入参数是系统时钟和波特率,输出是分频寄存器值。这样上层调用时就很简单:

wk2124_uart_config(WK2124_PORT1, 115200, DATA_BITS_8, STOP_BITS_1, PARITY_NONE);

如果某一路设备只是打印调试信息,波特率 115200 就够了,没必要所有端口都拉到 921600,因为 SPI 总线的吞吐量是共享的,四路串口同时高波特率收发时,主控来不及读,FIFO 一满就会丢帧。

2.4 中断线设计:为什么必须单独接 INT 引脚

如果不用 INT 引脚,主控就只能靠轮询去读 WK2124 的寄存器状态,判断哪一路有数据。轮询的问题在于:你不知道数据什么时候来,只能不停地读状态寄存器,SPI 总线被占用不说,数据到达后响应还有延迟。

接了 INT 引脚之后,WK2124 在接收数据时会把 INT 拉低,STM32 的 EXTI 中断触发,我们在中断回调里判断具体是哪一路产生了事件,再通过 SPI 读取数据。这个设计把实时性问题和 CPU 占用问题一起解决了。

我一般会把 INT 配置成下降沿触发,并使能对应的 EXTI 中断。中断服务函数里不要直接做耗时的 SPI 传输,先把事件标记写入一个标志位,或者在中断里启动 DMA,更稳妥的做法是:

  • 中断里关闭对应的中断使能,防止重复触发
  • 把读取任务交给主循环或者一个状态机
  • 读完之后再重新打开中断

这套「中断通知、主循环处理」的思路在用 WK2124 的项目里很常见,尤其是四路串口同时工作的时候,如果不做处理,中断里反复进 SPI 读写,主程序的主循环可能长期无法执行低优先级任务。

3. 基于 Cube HAL 的驱动移植:初始化、收发与中断回调的落地实现

3.1 驱动文件划分

移植的第一步不是写代码,而是把驱动文件的结构定好。我一般会拆成两层:底层平台适配层和芯片驱动层。平台适配层负责 SPI 读写、GPIO 控制、延时和中断处理,这些函数都在当前 STM32 工程里实现,不同芯片可以各自实现一套;芯片驱动层只关心 WK2124 的寄存器和逻辑,不直接接触 HAL 句柄。

这样做的好处是:如果你以后换了另一颗 STM32,只需要改适配层几个函数,芯片驱动层基本不用动。我在某个项目里把这段代码从 F103 搬到了 G431 上,只改了 SPI 句柄的传递方式和中断引脚,其他模块全部原样复用,半天就完成了迁移。

适配层通常会暴露这样的接口:

uint8_t wk_spi_read_byte(uint8_t reg); void wk_spi_write_byte(uint8_t reg, uint8_t data); void wk_set_cs(uint8_t level); void wk_delay_ms(uint32_t ms);

芯片驱动层在此基础上提供面向应用的函数,比如打开某个串口、发送一包数据、读取接收缓冲。发送数据时,如果字符数超过 FIFO 的触发值,应该分批写入,否则一次写太多字节可能超过芯片内部缓冲。

3.2 初始化流程的完整步骤

初始化 WK2124 的流程,我在多个项目里反复调过之后,稳定下来是这个顺序:

  • 拉低 RST,延时 20ms,再拉高,等待芯片启动
  • 读取芯片 ID 寄存器,确认 SPI 通信正常
  • 配置四路 UART 的线路参数和波特率
  • 配置 FIFO 触发深度和中断使能
  • 清空接收缓冲
  • 打开 INT 引脚对应的 EXTI 中断

其中第二步非常重要,读芯片 ID 是验证整个链路是否正常的试金石。如果 ID 读不到或者读成 0xFF,先不要继续往下配寄存器,优先检查接线、SPI 模式和 CS 时序。很多时候初始化失败并不是代码逻辑问题,而是 SPI 的极性和相位没有匹配上。

WK2124 要求的 SPI 模式需要在驱动里写清楚,CubeMX 配置时必须选对 CPOL 和 CPHA,否则初始化直接不通。我自己在移植时踩过这个坑,CubeMX 默认选择的模式有时候和芯片要求不一致,所以在初始化之前一定要确认 SPI 参数。

初始化部分代码大致是这样:

void wk2124_init(SPI_HandleTypeDef *hspi) { wk_hspi = hspi; wk_set_cs(1); wk_set_rst(0); wk_delay_ms(20); wk_set_rst(1); wk_delay_ms(50); uint8_t id = wk_read_reg(WK2124_REG_CHIP_ID); if (id != WK2124_EXPECTED_ID) { // 打印错误信息,停止初始化 return; } for (int ch = 0; ch < 4; ch++) { wk_uart_config(ch, 115200, DATA_BITS_8, STOP_BITS_1, PARITY_NONE); wk_uart_fifo_reset(ch); } wk_int_enable_all(); }

这段代码里的wk_read_reg内部会调用平台适配层的 SPI 读写函数,并且负责处理 CS 拉高拉低。ID 校验失败后直接返回,不要继续往下初始化,因为后续所有写寄存器操作都可能落在一条不稳定的 SPI 总线上,出了错也排查不出来。

3.3 数据发送:从应用层到 SPI 写入

发送数据的流程相对直观:应用层传入通道号和缓冲区,驱动层根据通道号组装寄存器地址,然后按 FIFO 触发值分批写入。需要注意,芯片内部每个通道的发送寄存器地址是独立的,写之前还要查一下当前通道的发送 FIFO 空满状态。

如果不查状态直接猛写,可能出现两种问题:FIFO 溢出导致数据丢失,或者写入混乱导致字节错位。我习惯先把状态寄存器读出来,判断当前还能写多少个字节,再决定这一批写多少。代码里可以这样实现:

int wk2124_send(uint8_t ch, const uint8_t *data, uint16_t len) { uint16_t sent = 0; while (sent < len) { uint8_t status = wk_read_reg(WK2124_REG_LSR(ch)); if (status & LSR_TX_EMPTY) { uint16_t chunk = (len - sent > WK2124_FIFO_SIZE) ? WK2124_FIFO_SIZE : (len - sent); wk_write_fifo(ch, data + sent, chunk); sent += chunk; } // 如果状态不满足,可以加一个超时退出,防止死循环 } return sent; }

这里的chunk大小不要超过芯片 FIFO 容量,建议用一个常量限制。实际使用中,如果发送频率较高,可以在循环里加一个次数上限,超出后返回超时错误,避免因为芯片异常导致主控陷入死循环。

3.4 数据接收:中断通知加主动读取

接收数据有两种方式:查询方式和中断方式。查询方式适合低速、低频场景;中断方式适合多通道高并发场景。我在项目里用的是中断方式,因为四路串口如果全部靠查询,SPI 总线忙不过来。

中断方式的关键在于 STM32 的 EXTI 回调函数中如何识别是哪个通道触发了事件。WK2124 会提供中断状态寄存器,里面记录了每一路 UART 是否有数据到达。中断触发后,按通道号依次读取:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == WK_INT_PIN) { uint8_t int_status = wk_read_reg(WK2124_REG_INT_STATUS); for (int ch = 0; ch < 4; ch++) { if (int_status & (1 << ch)) { // 标记该通道有待处理数据 wk_rx_flag[ch] = 1; } } } }

注意这里的中断回调里只做标记,数据读取放在主循环中执行。因为 SPI 读取本身需要时间,如果四个通道同时有数据,中断里连续做四次 SPI 读取会导致中断服务时间过长,影响其他实时任务。标记位设置之后,主循环里定期检查这些标记,有标记就从 WK2124 的 FIFO 中把数据读出来,存入各自的环形缓冲,再交给协议层解析。

3.5 环形缓冲:多通道数据收发的缓冲层

WK2124 每个通道的 FIFO 深度有限,如果主循环处理不及时,后到的数据会把 FIFO 挤掉。为了避免丢数据,必须在主控侧给每个通道配一个环形缓冲,中断里标记、主循环里搬数据、协议层从环形缓冲取数据。这样即使协议层处理较慢,数据也能在主控内存里暂存。

环形缓冲的读写位置各自维护,读指针由协议层消费时更新,写指针由底层接收时更新。接收时要注意保护临界区,如果主循环和中断都会修改写指针,那必须在关中断的窗口中更新指针,否则可能出现读写竞争。当然,如果这些数据经过的优先级相对单一,那么用一个简单数组加读写索引就够用,不需要引入复杂的锁机制。

我建议每个通道至少分配 512 字节的环形缓冲,具体大小根据数据量和波特率估算。估算公式并不复杂:波特率除以 10 就是每秒最大字节数,再乘以协议处理的最大延迟时间,就能算出缓冲下限。比如 115200 波特率,每秒约 11520 字节,如果主循环最坏情况下 50ms 才来取一次数据,缓冲至少要 576 字节,取 512 就有风险,取 1024 更稳妥。

4. 移植与调试避坑:SPI 模式、中断响应和波特率失配的排查手册

4.1 SPI 模式不对,寄存器读出来全是一堆 0xFF

现象:初始化时读取芯片 ID,返回值始终是 0xFF 或者随机乱跳,写寄存器完全没有反应,状态寄存器也读不到有效值。

原因:WK2124 的 SPI 时序要求和 STM32 SPI 外设配置不匹配,最常见的是 CPOL 和 CPHA 选择错误。CubeMX 默认生成的工程往往带的是某个模板参数,并不一定等于 WK2124 手册要求的模式。还有一种可能是 MISO/MOSI 接反了,这在中低速 SPI 下很容易被忽略,因为 0xFF 的读取结果看起来很像「设备不存在」。

解决:第一步,对照 WK2124 数据手册的 SPI 时序图,确认芯片在时钟空闲时是高电平还是低电平,数据在时钟边沿的哪个相位采样,然后把 CubeMX 里的 SPI 模式改成对应值。第二步,用示波器或者逻辑分析仪抓 SCLK、MOSI、MISO 三根线,确认主控发出的时钟确实产生在对应引脚上。第三步,如果是自己画的板子,检查 PCB 走线是不是把 MOSI 和 MISO 交叉了,这在北京某次项目测试里出现过,最初的排查方向完全在软件上,后来才发现是两层板布线时交换了丝印。

调试这个现象最快的方法是在初始化函数最前面加一个简单的 SPI 回环测试:把 MOSI 短接到 MISO,先发送一个已知字节再读回来,如果能正确读回,说明 SPI 底层链路没问题,问题就在 WK2124 配置或者硬件连接上。

4.2 中断引脚不触发,数据到了但主控完全没感知

现象:外部设备往 WK2124 某个通道发数据,UART 侧的收发明明正常(可以用串口工具验证),但 STM32 的 EXTI 中断一次都没进去,主循�回收不到任何数据。

原因:最常见的是 WK2124 的 INT 引脚是开漏输出,需要外部上拉电阻才能正常工作。如果板子上漏了上拉,或者上拉电阻选的过大,INT 引脚的变化沿不够陡峭,EXTI 可能检测不到。另一个原因是 EXTI 的触发边沿配置错误,WK2124 是拉低表示有中断事件,但 CubeMX 里默认配成了上升沿触发,方向完全反了。

解决:先检查硬件原理图,确认 INT 引脚上有 10kΩ 左右的上拉电阻。然后用万用表或者示波器观察 INT 引脚电平,在外部发数据时应该能看到引脚拉低;如果看不到电平变化,说明 WK2124 侧中断配置没打开,回头检查我们的中断使能寄存器是否真的写成功。最后再把 GPIO 的 EXTI 触发方式改为下降沿触发。还有一个容易忽视的点,EXTI 中断优先级和抢占等级要正确配置,如果它的优先级太低且被其他高频中断占住了,也会出现「看不到回调」的假象。

4.3 四路串口数据互相串扰,A 通道的数据跑到了 B 通道

现象:外部设备往通道 1 发送数据,STM32 却从通道 2 的读取流程中拿到了这包数据,或者数据本身能收到一部分,但夹杂着其他通道的内容。

原因:这类问题十有八九是寄存器的地址偏移用错了,或者 FIFO 的读写地址宏定义没有按照通道号递增来组织。WK2124 的寄存器设计通常会把四个通道的寄存器放在连续地址段里,如果你在代码里写死了通道 0 的地址,后面三个通道直接用「地址 + 偏移」的方式计算,那一旦偏移量算错,通道 1 的读取请求命中的可能就是通道 2 的 FIFO。还有一种可能性是中断状态寄存器的位定义理解错误,把第一位误认为是通道 1,而实际上第一位对应的是通道 0。

解决:重新对照数据手册,把四个通道的发送 FIFO、接收 FIFO、线路状态寄存器地址全部列出来,逐个核对宏定义。调试时可以做一个最简单的验证:只让外部设备往通道 0 发数据,主控依次读四个通道的接收 FIFO,看数据到底落在哪个通道的文件中,这样能很快定位是地址错误还是中断状态位判断错误。

4.4 波特率偏高后丢数据严重,115200 正常,921600 就断流

现象:单路串口 115200 波特率下收发很稳定,改成 921600 之后,数据开始偶尔丢失,有时候一丢就是连续十几个字节,尤其两个通道同时收发时更加明显。

原因:波特率提高后,WK2124 的 FIFO 被填满的速度变快,而 SPI 侧的数据搬运速度跟不上,数据只能在 FIFO 里排队。如果主控的 SPI 读取频率不足,FIFO 溢出就会发生。另外,921600 波特率要求 WK2124 的时钟和分频配置落在合适区间,如果分频值没有落在手册推荐范围内,实际波特率偏差可能超过允许的容限,接收端就可能采错位,表现为偶发错字节。

解决:先从计算入手,确认目标波特率在 WK2124 支持的范围内,把分频值算出来之后反向验证实际波特率和目标值的偏差,偏差超过 2% 就要考虑换一个分频组合。然后检查 STM32 的 SPI 时钟是否足够高,SPI 通讯频率比 UART 数据率至少高出几倍才能保证实时搬运。如果仍然丢数据,把每路 FIFO 触发深度改小,让主控更早收到中断通知,也能减少溢出概率。最后可以启用 DMA 传输来搬 SPI 接收数据,DMA 能在没有 CPU 参与的情况下连续搬运,极大缓解高波特率下的响应压力。

4.5 复位时序不对,上电后第一包数据总是丢失

现象:系统上电后,WK2124 初始化代码执行完,前几笔串口数据总是发不出去或者收不到,要等程序运行一小会儿才恢复正常。

原因:WK2124 的复位时序比普通芯片更长,或者上电之后芯片内部还没完全进入稳定状态,主机就在忙着写寄存器。这是典型的时序预算问题,电源上电和 RST 释放之间需要在数据手册要求的延时之外再留余量。另一个间接原因是 STM32 和 WK2124 的电源域不是同时上电,WK2124 上电晚了一步,但主控代码已经执行到初始化。

解决:把初始化延时放宽。RST 拉低之后至少保持 10ms,释放之后至少等待 20ms 再做第一次寄存器读写。我通常在初始化之前还会做一个「握手过程」:先读一次芯片 ID,如果读不到,延时 100ms 再读一次,总共重试三次。这个冗余设计成本很低,但能覆盖绝大多数上电时序不确定的情况。如果你是热启动(不停电直接复位),可以缩短延时,但冷启动时一定要留够时间。

5. 验收方法与答辩演示:怎么证明这套移植真的跑通了

5.1 最小验收清单

验收不能只看「能打印日志」,要量化测试,让每一条都有独立的数据支撑。我一般会按下面的清单逐条打勾:

  • 初始化后读取芯片 ID 一致,记录 ID 值
  • 四路 UART 分别配置为不同波特率,发送测试字符串,另一端能正确接收
  • 两两通道做回环测试:通道 0 发送给通道 1,通道 1 接收并回显
  • 长时间压力测试:单路持续收发 12 小时,统计丢帧数和错帧数
  • 短包、长包边界测试:1 字节、FIFO 深度临界值、超过 FIFO 深度的数据包

每一项通过的标准要提前定好。比如压力测试,丢帧率低于十万分之一可以接受;错帧数必须为 0,因为错帧说明数据完整性存在问题,不是偶发现象。

5.2 用串口工具和回环插头做现场验证

现场答辩时最直观的验证方式就是回环。拿一个 USB 转串口工具,接到 WK2124 的通道 0 上,主控定时通过 WK2124 发送一串含序号的数据,比如[0x01] hello wk2124 000001,USB 转串口工具连到电脑上的串口助手里,能看到连续打印且序号连续递增,这就证明了发送链路没问题。

然后反转方向,串口助手发送一串字符给 WK2124 通道 0,主控把收到的数据回显到调试串口,串口助手同时打开两个串口口,就能看到收发一致。这个演示比单纯看寄存器值更有说服力,因为它把 SPI、WK2124 内部收发、STM32 中断响应、应用层处理全部串起来了。

如果想演示四路同时工作,可以准备四个 USB 转串口模块分接四路,每个模块接不同的电脑或者不同的串口工具,同时向四路发送数据,主控分别回显。这个方案演示效果好,但需要确认你的板子提供足够的电源能力,四个模块同时工作电流不小。

5.3 答辩时怎么讲更能打动人

答辩或者汇报的时候,不要把时间花在读芯片型号和引脚名称上,评审最关心的是:为什么用 WK2124、怎么证明它能稳定工作、如果换成别的芯片会怎样。我建议按「选型对比 → 难点拆解 → 实测数据 → 可扩展性」的顺序来组织陈述。

选型对比可以简化为一个表格,把 SPI 扩展串口与 STM32 自身多 UART 外接做对比:

维度STM32 原生 UARTWK2124 SPI 扩展
占用引脚每路约 2 个引脚SPI 4 个引脚 + 2 个控制引脚
通道数量芯片型号决定,通常 3-6 路最多 4 路,可并联多片
灵活性固定硬件,难以调整通道参数可以独立配置
适用场景少量串口设备多路串口并发、引脚受限

难点拆解重点讲两个:一是 SPI 时序配置和寄存器地址核对,二是中断通知配合主循环读取的缓冲机制。实测数据部分把压力测试结果拿出来,直接说「12 小时收发 400 万帧,错帧 0,丢帧 8 帧」,比任何代码都有说服力。最后补充一句可扩展性:同样的驱动框架,后续可以换用四通道以上的芯片,适配层不需要大改。

5.4 我自己的验收习惯

每次做完 WK2124 的移植工程,我都会强制自己跑一遍完整的回环脚本再收工,脚本会模拟高低波特率、单路多路、短包长包这些场景,跑完输出一份统计报告。这个习惯是在某一次现场交付翻车之后养成的,当时在实验室里测了半小时没问题,到了现场客户的设备上一开机就丢数据,最后查出来是现场供电纹波影响了 SPI 信号质量,而这在现场调试之前根本不可能复现。从那以后,每一版驱动我都会在至少三种环境下重复验收,不是信不过代码,是信不过环境里那些不可控的变量。

希望这篇方案能帮你把 WK2124 的 SPI 扩展串口项目顺利落地,如果按这个思路走下来,你会发现整件事比想象中要简单很多。

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

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

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

立即咨询