ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

WK2114串口扩展芯片Linux驱动开发与调试实践

WK2114串口扩展芯片Linux驱动开发与调试实践 简介WK2114串口拓展芯片驱动面向STM32F2嵌入式开发者用于解决单颗微控制器UART接口数量不足的问题通过将WK2114挂接在UART1上可扩展出多路独立串口满足物联网、工业自动化等场景下同时连接多台外部设备的通信需求。资源包仅有2个文件包含1个C实现文件与1个头文件压缩包整体约4KBC文件内封装了芯片初始化、波特率与数据位设置、收发数据及控制信号处理等完整函数头文件提供函数原型、常量和结构体定义方便直接在STM32F2的HAL库工程中调用。压缩包结构简单没有多余示例适合已有STM32基础并希望快速获得串口扩展能力的开发者查看与移植。已有1259人学习内容涉及WK2114芯片原理、STM32F2的UART1配置、C语言驱动编写、HAL库使用以及串口联调方法拿到后既可作为现成驱动使用也可对照代码理解扩展串口的软件设计思路为后续多串口项目提供可靠参考。1. 项目背景与实现思路1.1 为什么主控串口不够用要用WK2114做嵌入式开发的朋友应该都有这种经历产品原型阶段主控板上的串口数量看着挺充裕一到量产或对接第三方设备时串口立刻不够用了。最常见的是工业网关、数据采集器、无人机飞控地面站这类设备一路串口接GPS一路接数传一路接调试终端还得留一路给传感器模块如果主控本身只引出两三个UART后面所有外设都得挤在同一个串口上做协议分时复用调试起来非常痛苦。我当时遇到的情况是主控SoC只有两路可用UART但现场需要接四个RS232设备而且每路的波特率、数据位、校验位要求都不一样。改板换主控不现实成本和时间都扛不住。于是我把目光放到了串口拓展芯片上。市面常见的方案有WK2114、WK2124、SC16IS752这类选来选去最后用了WK2114原因是这个系列在国内渠道好拿货、价格适中而且驱动模型和经典的16550 UART兼容性很好软件栈迁移成本低。WK2114本质上是一个通过SPI接口与主控通信的桥接芯片对外扩展出两路独立的UART内部集成了收发FIFO、波特率发生器、中断控制器寄存器布局也参考了成熟的UART设计。主控侧只需要占用一组SPI接口和一根中断引脚就能获得两个功能完整的串口这个性价比远比换主控或者外接USB转串口方案更划算。1.2 选型对比WK2114和WK2124、SC16IS752的取舍在动手画原理图之前我把几款常见串口扩展芯片放在一起比过一轮。WK2114是双通道方案WK2124是四通道方案两者接口和寄存器风格一脉相承软件驱动可以复用。SC16IS752则是NXP的老牌产品也是SPI/I2C接口转双UART文档齐全但价格偏高而且目前渠道货不如从前好拿。项目WK2114WK2124SC16IS752扩展串口数2路4路2路主机接口SPISPISPI/I2C内置FIFO有有有寄存器兼容性16550风格16550风格通用成本较低中等较高备货情况国内渠道充足国内渠道充足一般选WK2114的核心理由是只用两路扩展正好够用不必为多余的通道买单。而且这个系列在国产方案里属于稳定性做得不错的数据手册里对SPI时钟频率、FIFO触发阈值、中断状态位都有明确描述不像某些小厂芯片文档写得很模糊手册里甚至留白只能靠猜。选了WK2114之后下一个核心问题就是驱动怎么做。官方提供过Windows下的驱动和部分Linux补丁但实际项目中我们用的是自编译内核不能直接拿官方驱动用而且官方Linux补丁版本比较老设备树写法也和新内核不完全兼容。所以这套驱动基本是自己从头写的接下来我把硬件设计和驱动实现的关键细节展开聊。2. 硬件设计与寄存器关键点2.1 引脚定义与最小系统设计WK2114的封装不算复杂主要引脚包括SPI的四根信号线SCLK、CS、MOSI、MISO、中断输出引脚INT、复位引脚、晶振输入输出以及两路UART的TXD/RXD和相关流控信号。电源方面是3.3V供电所有数字IO也按3.3V电平设计如果主控引脚是1.8V电平中间需要加电平转换芯片不能直接直连。最小系统搭建时有几个细节决定后面调试是否顺利。第一晶振的选择直接决定波特率能否整除。WK2114内部有波特率发生器需要外部提供时钟源。如果手头有14.7456MHz晶振那么分频到115200、57600、9600这些常见波特率都很容易整数分频误差为零。如果用了12MHz晶振算出来的分频数往往是小数实际波特率就会偏离标称值极端情况下通信误码率会高到没法用。我最终选了14.7456MHz无源晶振搭配两个22pF负载电容。第二中断引脚INT的上拉电阻不能省。芯片的中断输出是开漏结构外部必须接上拉电阻到3.3V阻值选4.7kΩ到10kΩ都行。我第一次打样时漏画了这颗上拉电阻结果中断信号一直是低电平主控这边中断风暴不断排查了半天才发现是硬件漏件。第三复位引脚RC延时电路要保证上电时序。复位引脚需要拉低一段时间再释放常见做法是接一个10kΩ电阻到3.3V再接0.1uF电容到地利用RC充电延时提供上电复位时间。如果复位时间太短芯片可能启动到一半寄存器状态不确定。2.2 寄存器结构与波特率计算原理WK2114的寄存器设计沿用了16550 UART的经典布局核心寄存器包括接收保持寄存器RHR、发送保持寄存器THR、中断使能寄存器IER、中断标识寄存器IIR、FIFO控制寄存器FCR、线路控制寄存器LCR、线路状态寄存器LSR、调制解调器状态寄存器MSR。这套结构和PC上经典16550串口几乎一样凡是写过Linux串口驱动或写过单片机串口底层代码的人看到这些寄存器名字不会感到陌生。波特率发生器需要访问分频寄存器DLL和DLM设置方法和16550一样先把LCR的最高位DLAB置1然后写入16位分频系数再恢复LCR。分频系数的计算公式很简单分频系数 芯片时钟频率 / 目标波特率 × 16以14.7456MHz时钟、目标波特率115200为例分频系数 14745600 / (115200 × 16) 14745600 / 1843200 8分频系数为整数8意味着每个位周期由8个时钟周期驱动误差为0。如果目标波特率是9600分频系数就是96同样是整数。这就是为什么晶振选14.7456MHz这么顺手。实际操作中驱动初始化时必须先读回分频寄存器确认写入是否成功因为部分国产芯片在连续快速写DLL和DLM时存在状态同步问题。我遇到过写一次没生效、必须重写两次的情况问题最终是加了写后延时才稳定下来。2.3 FIFO和中断的工作机制WK2114的每个UART通道都内置了发送FIFO和接收FIFOFIFO深度虽然不如高端PCIe串口卡那么大但已经能有效减少主控中断频率。FIFO需要在上电后通过FCR寄存器显式使能默认状态下FIFO是关闭的如果不使能芯片就工作在非FIFO模式每收发一个字节都会触发一次中断在高波特率下主控容易被拖垮。FCR寄存器可以配置接收FIFO的中断触发阈值常见配置有1字节、4字节、8字节、14字节。这个阈值的选择取决于主控的处理能力如果主控很忙中断响应经常延迟阈值选高一点更稳妥减少被打断的次数如果协议要求低延迟响应比如Modbus主站需要快速响应从站数据阈值就选低一点。中断方面WK2114的中断源包括接收数据可用、发送保持寄存器为空、接收FIFO超时、线路错误。IIR寄存器可以读出当前最高优先级的中断源驱动在中断服务函数里通过读IIR来判断本次中断是“收到数据了”还是“可以继续发送”还是“线路出错了”然后分别处理。这种机制的好处是用一根中断线就能区分多种事件省引脚。3. 驱动开发过程与代码实现3.1 驱动整体框架怎么选主控平台是ARM架构Linux系统内核版本比较新。WK2114的驱动选型有两条路第一条路是直接在Linux内核里注册一个platform驱动把WK2114抽象成一个串行设备然后通过内核的tty框架对外提供/dev/ttyWK0、/dev/ttyWK1节点。这种方式接入系统最彻底应用层可以像操作普通串口一样用open/read/write不感知底层硬件差异。第二条路是写一个独立的内核模块自行管理SPI数据传输和中断然后通过misc设备或字符设备暴露接口由用户空间程序直接操作。这种方式实现简单但失去了内核tty框架的现成能力比如termios对波特率、数据位的标准配置接口还有内核串口层的缓冲区管理都得自己实现一遍工作量并不小。我最后选了第一条路用内核标准的uart_driver框架来写配合spi-device机制做底层寄存器访问。这样驱动代码结构清晰应用层完全透明用户可以把WK2114扩展出来的串口当成普通串口使用。3.2 设备树描述与platform驱动骨架设备树里我将WK2114描述为SPI总线上的一个从设备指定SPI片选、最高SPI时钟频率、工作模式和中断引脚。下面是设备树节点的核心片段spi0 { status okay; pinctrl-names default; pinctrl-0 spi0_pins; wk2114: wk21140 { compatible wk,wk2114; reg 0; spi-max-frequency 2000000; spi-mode 0; interrupts GIC_SPI 68 IRQ_TYPE_EDGE_RISING; interrupt-parent intc; wk2114,uart-clk 14745600; status okay; }; };SPI模式选择的是模式0即CPOL0、CPHA0。实测下来这个芯片对模式0和模式3都兼容但我统一用模式0减少团队协作时的理解成本。SPI时钟频率我限制在2MHz官方手册给的最大值更高但实际测试中发现频率过高时MISO上的信号边沿变差偶尔会出现读寄存器错位2MHz是稳定性和性能都比较均衡的点。platform驱动注册时需要实现probe、remove这两个核心回调。probe函数里的工作流程是解析设备树中的中断号和时钟频率、初始化SPI设备、通过uart_register_driver注册串口驱动最后通过uart_add_one_port注册两个端口。static int wk2114_probe(struct spi_device *spi) { struct wk2114_dev *wk; int ret; wk kzalloc(sizeof(*wk), GFP_KERNEL); if (!wk) return -ENOMEM; wk-spi spi; spi_set_drvdata(spi, wk); ret wk2114_parse_dt(spi, wk); if (ret) goto err_free; ret wk2114_soft_reset(wk); if (ret) goto err_free; ret uart_register_driver(wk2114_uart_driver); if (ret) goto err_free; ret uart_add_one_port(wk2114_uart_driver, wk-ports[0]); if (ret) goto err_unregister; ret uart_add_one_port(wk2114_uart_driver, wk-ports[1]); if (ret) goto err_unregister; return 0; err_unregister: uart_unregister_driver(wk2114_uart_driver); err_free: kfree(wk); return ret; }这里最需要注意的是uart_port结构体的设置。每个端口都要有独立的port结构体、独立的锁、独立的发送缓冲区。两个端口共用同一个SPI总线和同一个芯片但寄存器地址不同操作时必须串行化否则两个端口同时写寄存器会导致SPI总线数据交错逻辑完全混乱。3.3 初始化序列复位、FIFO使能、波特率配置芯片上电后的寄存器状态不确定驱动probe时必须先做完整初始化。我的初始化顺序是软件复位、关闭中断、设置FIFO触发阈值、配置串口参数、清空残留数据、打开中断。软件复位的实现方式是向指定寄存器写入复位命令然后等待芯片返回复位完成标志。这块时序与具体芯片版本有关让我补充一下实际调试时我直接写了一个简单的寄存器读写验证函数先向SPR寄存器写入0xA5再读回能读回相同值才说明SPI通信链路正常否则直接报错不必继续往下跑。下面是初始化串口参数的核心函数处理了波特率分频计算和LCR设置static int wk2114_set_baud(struct wk2114_port *p, u32 baud) { struct wk2114_dev *wk p-wk; u32 divisor; u8 lcr; if (!baud) return -EINVAL; divisor DIV_ROUND_CLOSEST(wk-uart_clk, baud * 16); if (divisor 0 || divisor 0xFFFF) return -EINVAL; /* DLAB 1 */ lcr wk2114_read_reg(wk, p-port, WK2114_REG_LCR); wk2114_write_reg(wk, p-port, WK2114_REG_LCR, lcr | 0x80); wk2114_write_reg(wk, p-port, WK2114_REG_DLL, divisor 0xFF); wk2114_write_reg(wk, p-port, WK2114_REG_DLM, (divisor 8) 0xFF); /* DLAB 0 */ lcr ~0x80; wk2114_write_reg(wk, p-port, WK2114_REG_LCR, lcr); /* 读回校验 */ if (wk2114_read_reg(wk, p-port, WK2114_REG_DLL) ! (divisor 0xFF)) { dev_warn(wk-spi-dev, baud divisor check failed\n); return -EIO; } return 0; }为什么我强调读回校验因为SPI链路在电气上不稳定时写入的寄存器值可能没有被真正锁存。如果不校验驱动以为波特率设置成功了但实际芯片运行在错误的波特率上对方设备回传的数据全是乱码。调试阶段这种问题非常隐蔽。3.4 中断处理与收发路径实现中断服务函数是驱动里最要紧的部分。WK2114发送中断和接收中断共用一根INT引脚所以进入中断后必须先读IIR确定具体中断源再决定处理流程否则只做收发会导致中断丢失。接收中断的处理思路读取线路状态寄存器LSR检查是否有溢出错误或帧错误。如果没有错误就循环读取接收FIFO中的数据直到FIFO为空或读取数量达到阈值上限把数据写入tty层的接收缓冲区最后调用tty_flip_buffer_push唤醒上层读取。发送中断的处理思路当发送FIFO为空或低于阈值时芯片触发发送保持寄存器空中断。驱动从uart_port的发送环形缓冲区取数据写入THR寄存器。如果发送缓冲区为空则关闭发送中断避免持续中断浪费CPU。这个关闭发送中断的操作很关键否则芯片一直认为发送寄存器空一直触发中断造成中断风暴。以下是中断处理逻辑的核心代码片段省略了锁和缓冲区细节便于阅读static irqreturn_t wk2114_irq_handler(int irq, void *data) { struct wk2114_dev *wk data; u8 iir; while ((iir wk2114_read_reg(wk, 0, WK2114_REG_IIR)) 0x01) { switch ((iir 1) 0x07) { case 0x04: /* 接收数据可用 */ wk2114_receive_chars(wk, 0); break; case 0x02: /* 发送保持寄存器空 */ wk2114_transmit_chars(wk, 0); break; case 0x00: /* Modem状态变化暂不处理 */ wk2114_read_reg(wk, 0, WK2114_REG_MSR); break; case 0x06: /* 线路状态错误 */ /* 读取LSR并清错误标志 */ wk2114_read_reg(wk, 0, WK2114_REG_LSR); break; default: return IRQ_HANDLED; } } return IRQ_HANDLED; }注意这段代码只处理了0号端口的中断判断。WK2114的两个端口交替中断时驱动需要轮询两个端口的IIR因为芯片只有一个INT输出如果第一个端口处理完中断且清除了中断标志但第二个端口仍有待处理中断INT线会一直保持有效所以while循环必须同时检查两个端口。这个细节我没有展开完整代码但在工程中是要重点照顾的。3.5 关于官方SDK和寄存器地址映射的提醒厂商SDK里的寄存器地址偏移一般以某个基地址为基准不同型号之间略有差异。例如某些型号的UART通道0和通道1寄存器偏移不同整片寄存器区域还有全局控制寄存器。自己写驱动时不能想当然以为两路串口的寄存器布局完全一致必须对照芯片手册逐个偏移核对。我在写驱动时对照了WK2114参考手册和SC16IS752手册发现两者寄存器布局逻辑非常接近但不完全一样。如果你之前只写过SC16IS752的驱动迁移到WK2114时千万不能只改芯片型号名称就完事要重点检查波特率分频寄存器地址和FIFO控制寄存器的位定义。4. 调试实录遇到的四个典型问题4.1 串口输出乱码排查了半天是晶振频率不匹配第一版驱动调通后我用串口助手发数据发现主控发送给WK2114再转发出去的数据是乱码接收方向也一样。一开始怀疑SPI读写寄存器有问题但读回DLL/DLM都是预期值寄存器配置看起来正确。后来拿逻辑分析仪抓UART波形发现一个位周期的时间明显偏长才意识到问题出在晶振频率上板子上实际焊接的是12MHz晶振但驱动里配置的uart_clk是14.7456MHz分频系数算错了实际波特率偏离标称值约18%。正确的做法是uart_clk必须与板子上实际晶振频率一致最好在设备树里做唯一配置源避免驱动里硬编码。调试时先用示波器或逻辑分析仪实测晶振引脚频率确认无误后再开始调驱动。4.2 中断风暴问题出在中断类型配置第一次接入中断时系统启动后CPU占用率直接跑到100%串口完全无法工作。排查后发现两个原因叠加一是中断类型配置成了电平触发但芯片的中断引脚在事件处理完成后会释放如果驱动没有在中断里及时清除中断状态电平触发模式会立刻再次触发二是开漏输出上拉电阻虚焊INT引脚浮空产生大量毛刺边沿触发。建议使用边沿触发模式并且确保INT引脚外部上拉可靠。中断服务函数里要连环清除寄存器状态不能只看IIR高优先级位就返回必须把所有挂起的中断源都处理干净确认INT引脚恢复高电平。4.3 FIFO阈值选错导致数据粘包项目里有一个应用需要在WK2114的串口上接收不定长数据帧。最初的FIFO触发阈值配置为14字节意味着每次接收中断触发时FIFO里已经有14字节数据。接收不定长数据帧时如果一帧长度是10字节那么必须等后续数据补满14字节或者FIFO超时才会触发中断应用层拿到数据时已经晚了很久。解决办法是把FIFO触发阈值调低到1字节让每个字节到达都触发中断。虽然中断频率上去了但这个应用的数据量不大CPU完全可以承受。如果数据量很大可以改成4字节或8字节阈值并配合FIFO超时中断来处理不定长帧。这里没有绝对最优只有适合场景。4.4 SPI时钟速率过高导致寄存器读写随机失败我最初按数据手册最大SPI时钟频率来配置设备树实测发现高频率下偶发寄存器读写错误表现为波特率配置偶尔失效、串口数据偶发丢字节。用示波器看MISO波形发现数据在采样点附近跳变说明走线寄生电容影响了信号建立时间。把spi-max-frequency降到2MHz后问题消失。如果你的PCB走线很短而且WK2114离主控很近可以尝试更高频率如果走线较长还是保守一点。5. 写在最后一点心得与扩展方向写WK2114这个驱动的过程其实比我想象中要曲折。最开始的方案是想直接改官方补丁结果发现内核版本对不上设备树语法也变过索性自己从头写了一个干净的驱动反而对芯片的理解加深了很多。现在回头看有几个经验值得分享给做类似项目的朋友第一拿到芯片先确认最小系统能不能通过寄存器读写自检再开始写驱动逻辑。寄存器读写自检通过不代表芯片完全正常但这一关都过不了后面所有调试都是浪费时间。第二SPI低速调试、高速运行。调试阶段尽量把SPI时钟压到1MHz把问题范围缩小到驱动逻辑和芯片寄存器配置上稳定后再逐步提升时钟频率直到找到临界点。第三驱动代码里所有可调参数尽量通过设备树暴露比如FIFO阈值、SPI频率、uart_clk频率不要硬编码在源码里。这样换板子、换晶振、换场景时只需要改设备树不需要重新交叉编译内核模块。这个项目做完后我又把同样的驱动框架移植到了另一个项目的四通道扩展芯片上硬件上只改了设备树里扩展通道数量和寄存器偏移软件层基本可以复用。如果你的平台不是Linux而是FreeRTOS或者裸机实现思路也一样只是不需要对接内核tty层直接写一个简单字符设备接口或者回调函数即可。如果你也在调WK2114或其他串口扩展芯片卡在某个疑难问题上欢迎留言讨论说不定正好是我踩过的坑。本文还有配套的精品资源点击获取
返回列表