ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

STM32F2 SPI转4路UART驱动:WK2114寄存器与中断实战

STM32F2 SPI转4路UART驱动:WK2114寄存器与中断实战 简介面向嵌入式开发者的WK2114串口扩展驱动代码针对STM32F2系列微控制器UART接口数量不足的常见问题提供将WK2114配置为UART1扩展的轻量实现适用于物联网终端、工业自动化等多串口通信场景。压缩包共2个文件以C源码与头文件为主大小约4KB其中C文件承载芯片初始化、波特率与数据格式设置、控制信号及收发数据等驱动逻辑头文件给出函数原型、常量与结构体声明便于工程直接集成和模块调用。已有1264人学习下载。读者可据此理解WK2114与STM32F2的串行通信配合方式掌握基于HAL库或寄存器操作扩展UART的驱动开发思路并借助发送、接收接口及终端测试方法完成联调验证学习多串口系统的初始化配置、数据收发与排错流程提升嵌入式串行通信设计的灵活性。1. 一颗 SPI 芯片把 2 路 UART 变成 6 路的现实解法板子已经定型主控是 STM32F2串口只引出了两路可设备清单上排着扫码枪、称重模块、两路 RS485 传感器和一路调试口。改主控意味着重画板子、重新过认证工期根本不允许。这种时候一片 WK2114 就能把问题按住它挂在 SPI 上一路 SPI 换出 4 路独立 UART每路带 256 字节收发 FIFO主控这边只多占一根片选和一根中断线。这份驱动处理的正是从 SPI 时序、寄存器寻址、波特率分频到中断分发、环形缓冲这一整条链路目标是让上层业务像用片上串口一样去用这 4 路通道。它适合板子已定型又要扩串口的 STM32F2 项目也适合想搞明白“SPI 转多路 UART”这类芯片驱动到底该怎么写的人。2. 硬件与寄存器模型WK2114 在 STM32F2 上的接线和地址空间动手写代码之前先把线接对、把地址空间想清楚。串口扩展芯片的驱动之所以容易翻车八成问题不在 C 代码里而在 SPI 模式选错、片选时序不对、或者压根没搞明白“全局寄存器”和“子串口寄存器”是两套地址。这一章把硬件连接和寄存器模型拆开讲后面所有函数都是围绕这两件事转的。2.1 引脚连法与 SPI 模式选择WK2114 与主控的连线不多但每一根都有讲究。下面这张表是 STM32F2 上最常见的接法具体引脚号按你自己的板子改逻辑关系不变。WK2114 引脚STM32F2 侧说明SCLKSPI1_SCKSPI 时钟主机输出SDIMOSISPI1_MOSI主控发往芯片SDOMISOSPI1_MISO芯片返回给主控CS#普通 GPIO软件片选必须软件控制一次事务拉低一次INT#GPIO EXTI低电平有效下降沿触发RST#普通 GPIO上电复位低有效VCC / GND3.3V / GND就近放 0.1uF 加 10uF片选一定要用软件 GPIO不要用 SPI 外设的硬件 NSS。原因很直接WK2114 的一次寄存器访问是“命令字节 数据字节”两个字节组成的一个完整事务硬件 NSS 在每次写入 DR 之后就可能抬起来中间那个字节边界会把事务切断表现就是读出来的值永远差一位或者干脆是 0xFF。SPI 参数这块芯片侧支持的是标准模式实际工程里用 SPI Mode 0CPOL0CPHA0、MSB First、8 位数据、软件片选最稳。速率上我一般的做法是调试阶段先压到 1MHz等寄存器读写全部回读正确、四路串口都能收发再逐步提到 68MHz。别一上来就拉满SPI 时钟太快时 MISO 上的建立时间不够读回来的数据会呈现“高位对、低位飘”的玄学现象查起来非常费时间。中断引脚 INT# 按低电平有效处理配置成下降沿触发的外部中断。要注意这个脚是“电平 事件”混合语义芯片内部有未处理的中断时它会一直保持低所以中断服务函数里必须把源头的中断标志读干净否则退出中断后引脚还是低的你会看到程序反复进中断主循环一行都跑不动。注意RST# 不要图省事直接接复位电路或者上拉省略。芯片上电到内部逻辑稳定需要时间驱动里主动拉低再释放一次能省掉大量“第一次上电偶尔不工作”的问题。2.2 寄存器地址空间全局寄存器和子串口寄存器怎么区分WK2114 的寄存器分两级这是整个驱动的地基。第一级是全局寄存器管整颗芯片的使能、总中断、通道选择第二级是子串口寄存器每个 UART 通道各有一套管波特率、线控格式、FIFO、中断使能。访问子串口寄存器不能直接给地址必须先往全局的通道选择寄存器里写一个位图把目标通道选中然后再访问子串口地址。地址字节的最高位拿来做读写方向低位做寄存器偏移。驱动里我用一个头文件把这件事固定下来手册上名字不一样也没关系改这一个文件就行/* wk2114_reg.h —— 地址定义集中在这里换批次只改本文件 */ /* 全局寄存器bit7 1 表示读bit6..0 为偏移 */ #define WK_GENA 0x00 /* 全局使能 */ #define WK_GCHS 0x01 /* 子串口通道选择位图 */ #define WK_GIER 0x02 /* 全局中断使能 */ #define WK_GIFR 0x03 /* 全局中断标志 */ #define WK_GCFG 0x04 /* 全局配置FIFO 触发点等 */ /* 子串口寄存器需先写 WK_GCHS 选中通道 */ #define WK_SBAUD 0x00 /* 波特率分频高低字节 */ #define WK_SLCR 0x01 /* 线控字长/停止位/校验 */ #define WK_SFCR 0x02 /* FIFO 控制清空/使能 */ #define WK_SIER 0x03 /* 子串口中断使能 */ #define WK_SIFR 0x04 /* 子串口中断标志 */ #define WK_SFLR 0x05 /* FIFO 计数发送剩余 / 接收可用 */ #define WK_STHR 0x06 /* 写发送 FIFO */ #define WK_SRHR 0x06 /* 读接收 FIFO */ #define WK_SPI_READ 0x80 /* 命令字节的读标志 */WK_GCHS是一个位图写0x01选通道 0写0x08选通道 3也可以一次选中多路但读 FIFO 时这么做没有意义因为数据寄存器是共用的地址。WK_SFLR是实际编码里最容易被忽略的一个它同时告诉你发送 FIFO 还剩多少空位、接收 FIFO 里堆了多少字节写发送逻辑和中断收数据都靠它。提示手册上如果给的是“全局寄存器 0x000x0F、子串口寄存器 0x000x0F”这类分区描述用最高位区分读写、用通道选择寄存器切子串口是这类芯片通用的做法。地址对不上时先拿逻辑分析仪抓一次真实波形比对着手册猜快得多。2.3 一次寄存器读写的完整时序一次写操作是CS 拉低 → 发命令字节地址读写位为 0→ 发数据字节 → 等 SPI 完全发送完成 → CS 拉高。一次读操作是CS 拉低 → 发命令字节地址或上 0x80→ 发一个哑字节 0x00 换回数据 → 等完成 → CS 拉高。这里有个很关键的细节CS 拉高之前必须确认 SPI 移位寄存器已经把最后一个 bit 移出去了。STM32 HAL 里HAL_SPI_Transmit返回只代表数据进了 DR、传到了移位寄存器不代表时钟已经打完。如果你紧接着就拉高 CS最后那一位会被截断芯片收到的是一个残缺字节写进去的值就变成了另一个数——这种 bug 表现出来是“某些值能写对、某些值写不对”非常像玄学。正确的收尾方式是发送后轮询状态寄存器等 BSY 清零再动 CSstatic void wk_cs_low(wk_dev_t *dev) { HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_RESET); } static void wk_cs_high(wk_dev_t *dev) { HAL_GPIO_WritePin(dev-cs_port, dev-cs_pin, GPIO_PIN_SET); } /* 等待 SPI 总线彻底空闲避免 CS 提前拉高截断最后一个 bit */ static void wk_spi_wait_idle(wk_dev_t *dev) { while (__HAL_SPI_GET_FLAG(dev-hspi, SPI_FLAG_BSY) ! RESET) { /* 空转通常几微秒内结束 */ } }dev-hspi是挂在 SPI1 上的句柄cs_port/cs_pin是软件片选。把这两个东西收进一个设备结构体是为了后面支持多片级联——两片 WK2114 就是两个wk_dev_t实例代码一份都不用改。3. 驱动分层与上电初始化从目录结构到 4 个通道全部打通驱动能不能被别人接手一半看分层。全塞进一个.c里也能跑但只要换块板子、换个 SPI 外设或者要加第二片芯片就得从头改一遍。所以这份源码按“移植层 / 核心层 / 应用层”三层来切初始化流程也按这个顺序排。3.1 目录结构与每层的职责边界bsp/ wk2114/ wk2114_reg.h # 寄存器地址与位定义换批次只改这里 wk2114.h # 设备结构体、对外 API wk2114_port.c # SPI 收发、片选、复位、延时的具体实现 wk2114.c # 寄存器读写、初始化、波特率、FIFO 收发 wk2114_irq.c # INT 引脚中断入口与四通道分发 app/ serial/ serial_ring.c # 每通道一个环形缓冲收发各一份 serial_ring.h serial_api.c # open/read/write/ioctl 风格的上层接口wk2114_port.c只认两样东西一个 SPI 句柄和一组 GPIO。它对外只暴露wk_port_spi_xfer()、wk_port_reset()、wk_port_delay_us()这几个函数核心层完全不出现HAL_前缀。这样做的直接好处是换到别的 MCU 平台时只重写这一个文件wk2114.c一行不动。wk2114.c是核心管寄存器读写、初始化、波特率计算、FIFO 收发。它不知道环形缓冲的存在也不关心中断是怎么进来的只提供“把这段数据塞进某个通道”和“从某个通道取一段数据”的能力。wk2114_irq.c是中断分发把 INT 引脚的一次下降沿翻译成“通道 N 有数据可读”或者“通道 N 发送 FIFO 空了”再回调到应用层。这三层的边界清楚后面加功能才不会互相踩。3.2 上电初始化序列初始化顺序不能乱尤其是“先复位再配置、先关中断再设波特率”这两条。乱序的典型后果是配置写到一半被上电毛刺打断波特率寄存器处于半写状态串口出来的是乱码而你还以为是分频算错了。int wk2114_init(wk_dev_t *dev) { /* 1. 硬复位RST 拉低至少 1ms释放后再等 5ms 让内部逻辑稳定 */ wk_port_reset(dev); wk_port_delay_us(1000); wk_cs_high(dev); wk_port_delay_us(5000); /* 2. 全局软复位 使能先关掉所有中断再干活 */ wk_write_global(dev, WK_GIER, 0x00); wk_write_global(dev, WK_GENA, 0x01); /* 3. 逐通道配置关子串口中断 - 设波特率 - 8N1 - 清 FIFO - 开子串口中断 */ for (uint8_t ch 0; ch WK_CH_NUM; ch) { wk_write_sub(dev, ch, WK_SIER, 0x00); if (wk_set_baud(dev, ch, 115200) ! 0) { return -2; /* 分频误差过大直接报错 */ } wk_write_sub(dev, ch, WK_SLCR, 0x03); /* 8 数据位1 停止位无校验 */ wk_write_sub(dev, ch, WK_SFCR, 0x07); /* 清收发 FIFO使能 FIFO */ wk_write_sub(dev, ch, WK_SIER, WK_IRQ_RX | WK_IRQ_TX_EMPTY); } /* 4. 全局中断使能最后再开 MCU 侧的 EXTI */ wk_write_global(dev, WK_GIER, 0x0F); /* 四通道全开 */ wk_port_irq_enable(dev); /* 使能 INT 引脚下降沿中断 */ return 0; }几个参数值得单独说。WK_SLCR写0x03对应 8N1如果要 7 位数据加偶校验改这一字节就行不用动别的逻辑。WK_SFCR的0x07是“清发送 FIFO 清接收 FIFO 使能 FIFO”的组合位初始化时清一次很有必要因为芯片复位后 FIFO 里可能有残留的随机值不清的话第一次读会读到脏数据。WK_GIER最后写、而且放在 EXTI 使能之前是为了保证中断进来的那一刻全局和子串口两级的使能都已经就位。提示如果板子上有多片 WK2114把wk2114_init()里的dev换成不同实例挨个调一遍就行。片选、SPI 句柄、中断引脚都放在实例里初始化逻辑完全复用。3.3 上电自检写进去的值必须读得回来初始化函数返回 0 不代表芯片真的在工作。SPI 线接反、片选接错脚、芯片没焊好这些情况初始化照样“成功”因为写操作不会报错。所以初始化末尾一定要做一次回读校验把这个问题在产线或者上电阶段就暴露出来。/* 回读校验写一个特征值到全局配置寄存器再读回来比对 */ int wk2114_self_test(wk_dev_t *dev) { uint8_t old wk_read_global(dev, WK_GCFG); wk_write_global(dev, WK_GCFG, 0x5A); if (wk_read_global(dev, WK_GCFG) ! 0x5A) { return -1; /* SPI 通路不通或芯片未就绪 */ } wk_write_global(dev, WK_GCFG, old); /* 还原避免影响原有配置 */ return 0; }WK_GCFG选它做自检是因为这个寄存器基本是读写自由的配置位写坏了也能还原。特征值用0x5A而不是0xFF或0x00是为了区分“读到全 0”和“读到全 1”这两种典型故障——前者通常是 MISO 没接或者从机没响应后者多半是 MISO 被上拉、SPI 模式不匹配。这两种情况在排查章节里还会细说。3.4 上层接口长什么样应用层不该看到寄存器。serial_api.c里给每个通道包一层接近 POSIX 的接口业务代码最后看到的是这样接口参数说明serial_open通道号、波特率、模式返回句柄内部完成通道配置serial_write句柄、缓冲区、长度非阻塞返回实际写入字节数serial_read句柄、缓冲区、长度、超时从环形缓冲取取不到就等serial_ioctl句柄、命令、参数改波特率、清缓冲、查丢包计数serial_write设计成非阻塞是有意为之WK2114 每个通道的发送 FIFO 只有 256 字节业务侧来一包 1KB 的数据一次根本塞不进去。返回实际写入长度剩下的交给环形缓冲加发送中断续传主循环就不会被串口拖住。4. 核心代码拆解寄存器读写、波特率分频、FIFO 收发与中断分发这一章是整份驱动的重心。四个模块——寄存器访问、波特率、发送、接收与中断——每一个都有容易写错的地方下面逐段拆。4.1 地址打包与寄存器读写所有访问最终都会收敛到两个函数上。把“命令字节 数据字节”这个事务封死在里面上层就不用关心 CS 什么时候拉高拉低。/* 单次寄存器事务cmd 为地址读操作时或上 WK_SPI_READ返回读到的字节 */ static uint8_t wk_xfer8(wk_dev_t *dev, uint8_t cmd, uint8_t wr_data, bool is_read) { uint8_t rx 0; wk_cs_low(dev); wk_port_spi_xfer(dev, cmd, NULL, 1); /* 先发命令字节 */ if (is_read) { uint8_t dummy 0x00; wk_port_spi_xfer(dev, dummy, rx, 1); /* 哑字节换数据 */ } else { wk_port_spi_xfer(dev, wr_data, NULL, 1); /* 直接发数据 */ } wk_spi_wait_idle(dev); /* 等 BSY 清零再抬 CS */ wk_cs_high(dev); return rx; } uint8_t wk_read_global(wk_dev_t *dev, uint8_t reg) { return wk_xfer8(dev, reg | WK_SPI_READ, 0, true); } void wk_write_global(wk_dev_t *dev, uint8_t reg, uint8_t v) { wk_xfer8(dev, reg ~WK_SPI_READ, v, false); }cmd是地址字节读操作把最高位置 1写操作清 0。wr_data只在写的时候有意义读的时候传 0 即可。is_read决定第二个字节是“发哑字节收数据”还是“直接发数据”。这里必须强调wk_spi_wait_idle()的位置它一定在wk_cs_high()之前。省略这一句在 1MHz 时钟下可能看不出问题一旦提到 6MHz 就会开始丢配置而且是概率性的非常难查。4.2 子串口寄存器寻址与并发保护子串口寄存器不是独立地址而是“先选通道、再访问”。裸写会有一个隐患中断里正在读通道 1 的 FIFO主循环此时把通道选择改成了通道 2中断回来后继续读读到的就是通道 2 的数据。这类串数据的 bug 在现场极难复现因为要恰好撞上时序。/* 选中通道 - 访问子串口寄存器整段必须原子 */ uint8_t wk_read_sub(wk_dev_t *dev, uint8_t ch, uint8_t reg) { uint8_t v; uint32_t primask __get_PRIMASK(); __disable_irq(); /* 关总中断防通道选择被抢改 */ wk_write_global(dev, WK_GCHS, (uint8_t)(1u ch)); v wk_xfer8(dev, reg | WK_SPI_READ, 0, true); __set_PRIMASK(primask); /* 恢复原中断状态别无条件开中断 */ return v; }关中断的粒度要控制在“一次寄存器事务”这个级别不能把整个初始化循环都包进去否则会明显拉长中断响应延迟。用__get_PRIMASK()保存现场、__set_PRIMASK()恢复而不是简单粗暴地__enable_irq()是因为这个函数可能在已经关中断的上下文里被调用无条件开中断会把外层的临界区破坏掉。如果项目跑的是 RTOS这里换成互斥量加短临界区也可以但临界区那一段不能省——互斥量挡得住任务挡不住中断。4.3 波特率分频计算与误差判断波特率算错是乱码的头号原因而且它往往不是“算错”是“时钟源和代码里假设的不一致”。WK2114 一般外挂晶振驱动里必须有一个宏和实际焊接的晶振对上。#define WK_CLK_HZ 11059200u /* 必须与板上晶振一致改板子记得改这里 */ /* 返回 0 成功-1 表示该波特率下误差超过 2% */ int wk_set_baud(wk_dev_t *dev, uint8_t ch, uint32_t baud) { /* 常见分频关系div fclk / (baud * 16)加半个除数做四舍五入 */ uint32_t div (WK_CLK_HZ baud * 8u) / (baud * 16u); if (div 0 || div 0xFFFFu) { return -1; } /* 反算实际波特率评估误差 */ uint32_t real_baud WK_CLK_HZ / (div * 16u); int32_t err_ppm (int32_t)((real_baud baud) ? ((real_baud - baud) * 1000000u / baud) : ((baud - real_baud) * 1000000u / baud)); if (err_ppm 20000) { /* 2% 以上长帧一定会出错 */ return -1; } wk_write_sub(dev, ch, WK_SBAUD, (uint8_t)(div 0xFF)); /* 低字节 */ wk_write_sub(dev, ch, WK_SBAUD 1, (uint8_t)((div 8) 0xFF)); /* 高字节 */ return 0; }div的公式是这类芯片最常见的fclk / (baud × 16)形式16 是内部过采样倍数。四舍五入那半个除数baud * 8别省省掉之后在 921600 这种高波特率下误差会被放大。误差判断设成 2%20000 ppm是个工程经验值串口一帧 10 位累计相位偏差超过半个位宽就会采错2% 在 10 位帧上刚好是安全边界附近再大就危险。如果驱动跑起来波特率明显不对但公式看着没错第一件事是确认晶振实际频率。板上焊的是 12MHz 而宏里写 11.0592MHz算出来的分频值偏高实际波特率就会整体偏移表现为“能收到数据但全是乱码”而不是完全收不到。4.4 发送路径查剩余空间再写 FIFO发送不能一把梭。FIFO 只有 256 字节塞不下就得返回实际写入量由上层续传。/* 返回实际写入的字节数0 表示 FIFO 满 */ uint16_t wk_write(wk_dev_t *dev, uint8_t ch, const uint8_t *buf, uint16_t len) { uint8_t free_cnt wk_read_sub(dev, ch, WK_SFLR); /* 低 8 位为发送剩余空间 */ if (free_cnt 0) { return 0; } if (len free_cnt) { len free_cnt; } for (uint16_t i 0; i len; i) { wk_write_sub(dev, ch, WK_STHR, buf[i]); /* 写入发送 FIFO */ } return len; }WK_SFLR的低字节给的是发送 FIFO 剩余空位高字节给的是接收 FIFO 里的字节数两个方向共用同一个寄存器读的时候按位段拆。这里每次写一个字节都走一次完整 SPI 事务看起来效率不高但胜在简单可靠量大的时候可以改成“一次拉低 CS 连续写 N 个 STHR”也就是后面二次开发里说的 burst 优化。4.5 中断分发从 INT 引脚到具体通道的完整链路中断这条链路是两级INT 引脚拉低 → 读全局中断标志 → 定位到具体通道 → 读该通道的中断标志 → 处理 → 清标志。少清一级引脚就一直保持低电平程序会卡在中断里出不来。void wk2114_irq_handler(wk_dev_t *dev) { uint8_t gifr wk_read_global(dev, WK_GIFR); /* 第一级哪几个通道有中断 */ if (gifr 0x00 || gifr 0xFF) { return; /* 全 0 无中断全 1 多半是 SPI 异常 */ } for (uint8_t ch 0; ch WK_CH_NUM; ch) { if ((gifr (1u ch)) 0) { continue; } uint8_t sifr wk_read_sub(dev, ch, WK_SIFR); /* 第二级通道内什么事件 */ if (sifr WK_IRQ_RX) { uint8_t cnt wk_read_sub(dev, ch, WK_SFLR 1); /* 高字节 可读字节数 */ while (cnt--) { uint8_t b wk_read_sub(dev, ch, WK_SRHR); serial_ring_push(dev, ch, b); /* 进环形缓冲中断里不做业务 */ } } if (sifr WK_IRQ_TX_EMPTY) { serial_ring_on_tx_ready(dev, ch); /* 通知上层可以续传了 */ } wk_write_sub(dev, ch, WK_SIFR, sifr); /* 写回清标志别漏 */ } }gifr读到0xFF时直接返回是个防御性写法正常情况全局标志不会全为 1出现这个值大概率是 SPI 读到了悬空电平继续往下走会把随机数据当成真实中断处理反而更乱。接收这部分一定要“一次读空”。芯片的中断是边沿加电平的组合如果这次只读了几个字节就退出FIFO 里还有货但引脚已经因为标志被清而抬高了下一批数据进来之前你不会再收到中断表现就是“偶尔丢一串数据”。用WK_SFLR的高字节拿到可读数量循环读干净是最稳的做法。中断里只做搬数据不做协议解析、不做打印、不发信号量之外的任何耗时动作。业务逻辑全部放在任务或者主循环里从环形缓冲取这是保证高波特率下不丢包的前提。5. 避坑与排查SPI 读回 0xFF、串口乱码和多通道串数据的现场处置这一章记录的是实际调试里最常撞上的几类问题。它们有个共同点代码编译通过、逻辑看着没错但现象很迷惑。按“现象 → 原因 → 解决”的顺序整理遇到类似症状可以直接对号入座。5.1 SPI 通路类读回全 0xFF 或全 0x00现象自检函数永远返回 -1读任何寄存器都是0xFF或者相反永远是0x00写进去什么读出来还是什么。原因读回0xFF通常是 MISO 悬空或者被上拉从机根本没在驱动这根线常见于片选接错引脚、CS 极性搞反、SPI 模式CPOL/CPHA和芯片要求不一致。读回0x00且写入无效多半是 CS 一直没拉低事务压根没发出去或者 MOSI 和 MISO 接反了——主控在听自己发出去的东西。解决先拿逻辑分析仪抓 CS、SCLK、MOSI、MISO 四根线看一次事务里 CS 低电平期间是不是正好 16 个时钟。如果时钟数不对问题在 SPI 配置如果时钟对但 MISO 没动静把 SPI 时钟降到 100kHz 再试还不动就查硬件连接和芯片供电。我一般的排查顺序是先降速、再查模式、最后查线三步之内基本能定位。5.2 通道串扰类四路数据全跑到一个口上现象四个通道同时接设备通道 0 收到了本该属于通道 2 的数据或者某个通道间歇性收到空数据。单独测一路完全正常四路一起跑就乱。原因子串口寄存器的通道选择被并发改写了。典型场景是中断里正在读通道 1 的 FIFO主循环此时调用了另一个通道的发送函数中间把WK_GCHS改掉了中断回来继续按原来的地址读读到的就是别的通道。这个问题在单通道测试时永远不会出现。解决把所有“选通道 访问子串口寄存器”的操作整体放进临界区用保存并恢复 PRIMASK 的方式而不是无条件开关中断。如果用了 RTOS临界区之外再加一层互斥量挡住任务级并发。改完之后建议做一次压力测试四个通道同时以最高波特率对发跑半小时看有没有串数据这个测试能把九成并发问题逼出来。5.3 波特率与物理层乱码、丢字节、RS485 切不过来现象一数据能收到但全是乱码换几个波特率都一样。原因分频公式里的时钟频率和板上实际晶振对不上或者误差超过 2% 却没被判断逻辑拦住。也有一种情况是上位机自己的波特率就不准两边都有偏差叠加起来超了容限。解决先用回环把芯片自己的 TX 和 RX 短接自发自收排除对端因素。然后用示波器量一个字节的位宽反算实际波特率和设定值对比。偏差超过 2% 就回去查WK_CLK_HZ宏十有八九是这里写错了。高波特率921600 以上建议把误差阈值收紧到 1.5%。现象二接 RS485 收发器时前几个字节正常后面开始丢或者对方收到的帧头对、帧尾缺。原因方向控制脚切得太早。数据写进发送 FIFO 只代表进了芯片缓冲不代表已经从引脚上移出去了。主控这边一看wk_write()返回就立刻把 485 方向切回接收最后几个字节就被截断了。解决方向脚不能由写函数的返回值驱动要等发送 FIFO 真正空掉。判断依据是发送空中断或者查询WK_SFLR的发送计数归零再留一个字节时间的余量按波特率算115200 下一个字节约 87 微秒才切方向。这个余量我一般给两个字节时间宁可慢一点不要丢帧。5.4 中断类进一次就出不来或者干脆不进现象一程序卡在中断里出不来主循环完全没机会跑。原因中断标志没清干净。INT 引脚是电平语义只要芯片内部还有未处理的中断源引脚就一直是低。如果只清了子串口标志没清全局标志或者只处理了接收没处理发送空中断引脚就会一直拉着中断反复触发。解决进中断后先把全局标志读出来存着按位逐个通道处理每个通道处理完把子串口标志写回清除最后确认全局标志也清了。调试阶段可以在中断入口翻转一个空闲 GPIO用示波器看翻转频率正常应该是和数据量相关的稀疏脉冲如果是持续的方波就说明在死循环触发。现象二数据明明进来了中断一次都不进。原因EXTI 没使能、NVIC 优先级配置漏了、或者全局中断使能寄存器在初始化最后被别的配置覆盖了。还有一种隐蔽情况INT 引脚配置成了上拉输入但没配 EXTI 模式读电平是对的就是不会触发中断。解决先在中断服务函数第一行翻转 GPIO确认到底进没进没进就去查 EXTI 的 IMR/EMR 寄存器和 NVIC 使能位。软件层面还可以加一个兜底主循环里定期轮询一次WK_GIFR如果发现有标志但中断没触发说明中断链路有问题同时也能保证数据不丢。这个兜底在调试期很有用量产版本可以留着代价很小。6. 二次开发把单颗 4 通道做成多片级联和透传框架单颗 WK2114 跑通之后往下走通常是两个方向通道数不够要级联多片或者要把这套东西做成一个通用的串口透传框架。这两件事的改造量差别很大下面分开说。多片级联其实在wk_dev_t这个结构体设计的时候就已经预留好了。片选、SPI 句柄、INT 引脚全部放在实例里加一片就是多定义一个实例、多接一根片选和一根中断线。要注意的是中断线可以共用把两片的 INT 都接到同一个 EXTI 上中断里遍历所有实例读一遍WK_GIFR哪个有标志处理哪个。省引脚代价是中断里多几次 SPI 事务在 8MHz 时钟下完全可接受。改造点工作量收益多片实例化共用中断线半天通道数按 4 的倍数扩代码零复制寄存器 burst 读一次 CS 读空 FIFO一天中断里 SPI 事务数降到 1/N高波特率下明显每通道独立环形缓冲 状态机两天支持不定长帧、超时断帧、半双工切换丢包与溢出计数上报两小时现场问题可量化不用靠猜burst 读是性价比最高的一项。现在的实现是每读一个字节都走一次完整事务CS 拉低拉高一次、两个字节的 SPI 时间。改成“命令字节发出后保持 CS 低连续读 N 个字节”N 等于 FIFO 里的字节数事务数从 N 降到 1。在 921600 波特率、四通道同时收数据的场景下这个改动能把中断占用时间压掉一大截。/* burst 读一次 CS 内把接收 FIFO 读空减少事务开销 */ uint16_t wk_read_burst(wk_dev_t *dev, uint8_t ch, uint8_t *buf, uint16_t max_len) { uint32_t primask __get_PRIMASK(); __disable_irq(); wk_write_global(dev, WK_GCHS, (uint8_t)(1u ch)); uint8_t cnt wk_read_sub_nolock(dev, ch, WK_SFLR 1); /* 已持锁用不加锁版本 */ if (cnt max_len) { cnt (uint8_t)max_len; } if (cnt) { uint8_t cmd WK_SRHR | WK_SPI_READ; wk_cs_low(dev); wk_port_spi_xfer(dev, cmd, NULL, 1); wk_port_spi_xfer(dev, NULL, buf, cnt); /* 连续读回 cnt 个字节 */ wk_spi_wait_idle(dev); wk_cs_high(dev); } __set_PRIMASK(primask); return cnt; }这里有个细节wk_read_sub_nolock()是不加锁的版本因为外层已经关了中断再嵌套一次保存恢复虽然不会出错但会让代码读起来很绕。工程上的惯例是把“带锁”和“不带锁”两个版本都提供出来带锁版本调不带锁版本调用方自己判断上下文。max_len是调用方缓冲区的容量cnt取两者较小值避免溢出。透传框架的做法是每通道挂一个状态机加两个环形缓冲收方向在中断里只管推数据主循环里按帧超时断帧断完把整帧交给业务回调发方向维护一个待发队列发送空中断触发后续传。这套结构跑起来之后业务层完全不用碰 WK2114 的任何寄存器换芯片、加通道都不影响上层。验证方法我一般分三步走。第一步是芯片级回环把某个通道的 TX 和 RX 短接自发自收一万字节比对内容第二步是板级对拷两个通道互连一个发一个收跑一小时看丢包计数第三步是满负荷压测四通道同时以最高波特率对发同时用示波器看 INT 引脚和 SPI 时钟确认中断响应没有明显抖动。三步都过这套驱动才敢往现场放。注意压测时一定要打开丢包和 FIFO 溢出计数。数据对不对肉眼看不出来计数器涨了才是真的有问题。从那以后我每次 bring-up 一块新板子第一件事都是拿逻辑分析仪抓一遍 SPI 波形再写代码而不是先写代码再回头查波形——省下来的时间远比抓波形那十分钟多。这套 WK2114 驱动的分层和自检思路照着改一改也能套到其他 SPI 转多串口的芯片上。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表