ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SC16IS752双通道UART扩展芯片驱动开发与寄存器配置指南

SC16IS752双通道UART扩展芯片驱动开发与寄存器配置指南 简介SC16IS752是一款高性能I2C转双UART桥接控制器由Microchip公司出品常用于嵌入式系统、物联网设备与工业自动化等领域核心作用是将I2C总线转换为两路独立的全双工串行通信接口。这里提供的参考驱动代码专门针对该芯片的初始化流程、内部寄存器配置、波特率发生器、FIFO读写与中断处理等关键功能进行示例实现适合需要快速集成双串口能力的软硬件开发者学习与复用。资源压缩包仅7KB共5个文件包括三个头文件和两个C源文件分别用于芯片配置定义、I2C底层驱动、核心功能封装与工程裁剪结构清晰可直接移植到常见的MCU工程中。包内代码经过验证能够帮助开发者规避寄存器配置中的常见坑点并为低功耗模式切换、数据校验和错误恢复提供可参考的编程思路。目前已有1763人学习下载侧面说明这份样例代码具备较强的实用价值对于正在做I2C转串口项目、希望缩短开发周期的工程师而言是一份不错的起步素材。1. 拿到SC16IS752先搞清楚它到底解决什么问题做过嵌入式项目的人应该都有这种经历主控芯片的串口不够用外接传感器、蓝牙模块、GPS模组、调试口满打满算少了两个UART。换主控不现实软件模拟串口又太占CPU这时候最省心的方案就是加一颗串口扩展芯片。SC16IS7xx系列就是干这个的其中SC16IS752是双通道版本一颗芯片能扩展出两路独立UART每路都支持硬件流控RTS/CTS、红外模式、9位数据格式还能通过I2C或SPI两种接口跟主控通信。这颗芯片在工业控制、物联网网关、车机、仪器仪表领域用得特别多。比如我做过的一个项目主控是一颗Cortex-M4本身只有三个UART一个给了4G模组一个给了调试口第三个要接RS485总线结果还要挂一个温湿度传感器。最后就是用SC16IS752走SPI扩展出两路串口一路接传感器一路备用问题直接解决。要写参考代码先得理解这颗芯片的工作模式。SC16IS752的核心机制就是“寄存器映射”——主控通过I2C或SPI读写芯片内部的寄存器来配置波特率、数据格式、FIFO阈值、中断使能等参数。芯片内部有64字节的发送FIFO和64字节的接收FIFO数据缓冲能力比传统无FIFO的串口芯片强很多能显著减少主控的中断频率。拿到芯片第一步建议先看硬件连接。SC16IS752的电源范围是2.3V到3.6V3.3V供电最常用。注意它的I2C地址是可配置的通过A0和A1引脚的电平组合可以设置4个不同地址这样一条I2C总线上最多能挂4颗SC16IS752也就是8路串口这个设计对多串口设备非常友好。SPI模式下则有硬件片选和软件片选两种方式寄存器0x06的位7SPI_CFG决定用哪种。这些细节在写代码之前最好都确认清楚省得后面踩坑。2. 寄存器配置的关键细节这些坑我都替你踩过了2.1 初始化流程的先后顺序会影响稳定性SC16IS752的初始化比普通UART芯片复杂一点因为涉及到FIFO、中断映射、I2C/SPI工作模式等多个维度。我调试过程中总结了一套比较稳的初始化顺序复位芯片通过寄存器0x06I2C/SPI控制寄存器的RESET位bit5先复位一次。等待至少1ms确保内部逻辑完全复位。关闭FIFO操作FIFO控制寄存器FCR0x02先把FIFO关闭再逐项配置触发阈值。很多人忽略这一步导致配置过程中FIFO里残留脏数据。配置线路控制寄存器LCR0x03设置数据位、停止位、校验位。这里有个细节——LCR的最高位DLAB是除数锁存访问位要访问波特率除数寄存器DLL/DLH时必须先把DLAB置1。设置波特率写DLL0x00和DLH0x01注意写完波特率之后要把DLAB恢复为0。配置中断使能IER0x01按需使能接收、发送、线路状态中断。同时要把中断引脚映射关系确认好。开启FIFO并设置触发阈值回到FCR0x02使能FIFO设置接收FIFO触发级别。设置Modem控制寄存器MCR0x04启用自动流控如果使用硬件流控的话。这套顺序看起来不复杂但有一个地方特别容易出问题在波特率配置阶段如果前一步没有正确设置DLAB位后续所有寄存器写入的实际上都是数据寄存器THR/RHR而不是预期的DLL/DLH寄存器地址没变但映射的目标变了这个错位会导致波特率完全不对串口输出乱码。2.2 波特率计算要用好小数分频SC16IS752的波特率发生器和传统16C550完全不同它支持小数分频Fractional Baud Rate Generator。公式是波特率 (振荡器频率) / (16 × 除数)其中除数是一个带小数的值整数部分写入DLL/DLH小数部分写入MCR的bit6:4和TCR的bit3:0MCR的bit7Fractional Baud Rate Generator Enable要置1。如果不使能小数分频MCR的bit6:4就得保持0。举个例子如果用14.7456MHz晶振跑115200波特率标准除数 14745600 / (16 × 115200) 8.0正好是整数直接写即可。但如果用12MHz晶振除数 12000000 / (16 × 115200) 6.5104整数部分是6小数部分换算成4位二进制约为0.5104 × 16 ≈ 8.17取8。折算后的实际波特率是 12000000 / (16 × (6 8/16)) 12000000 / (16 × 6.5) 115384.6误差约0.16%在UART通信允许的±2%以内完全没问题。注意DLL/DLH写入的除数最小值是1最大值是65535。如果算出来的除数不是整数一定要检查MCR的bit7有没有置1以及TCR的低4位有没有正确写入小数部分。我第一次用12MHz晶振调115200的时候就是忘了使能小数分频功能结果串口收到的全是乱码。2.3 FIFO触发阈值跟中断频率的取舍SC16IS752的接收FIFO是64字节触发阈值可以配置为1、4、8、14、56字节等不同级别。FCR的bit6:4决定接收FIFO触发级别。配置为1字节触发主控中断最频繁但实时性最好适合响应速度要求高的场景。配置为56字节触发中断频率大大降低但接收数据的延迟会增大而且如果超过64字节过载数据会触发溢出错误。我的经验是大流量传输场景用高阈值空闲中断低速率交互场景用低阈值。SC16IS752还支持接收超时中断RTO当FIFO里有一定数据但没达到触发阈值时经过4个字符时间没有新数据进来就会产生超时中断提示主控把FIFO里剩余的数据取走。这个机制用好了既能降低中断频率又能保证数据的及时性。3. 参考代码怎么组织两套方案供你选3.1 Linux用户态访问方案如果主控跑的是Linux系统最常见的方式是通过I2C或SPI设备节点直接读写寄存器。下面是一段基于I2C的用户态配置代码核心思路是封装一个寄存器读写函数再按寄存器地址做配置#include stdio.h #include stdlib.h #include stdint.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/i2c-dev.h #define SC16IS752_I2C_ADDR 0x4D #define REG_LCR 0x03 #define REG_DLL 0x00 #define REG_DLH 0x01 #define REG_FCR 0x02 #define REG_IER 0x01 #define REG_MCR 0x04 #define REG_TCR 0x06 #define REG_SPR 0x07 static int i2c_fd; static int sc16is752_write_reg(uint8_t reg, uint8_t val) { uint8_t buf[2] {reg, val}; if (write(i2c_fd, buf, 2) ! 2) { perror(write reg failed); return -1; } return 0; } static int sc16is752_read_reg(uint8_t reg, uint8_t *val) { if (write(i2c_fd, reg, 1) ! 1) { perror(write reg addr failed); return -1; } if (read(i2c_fd, val, 1) ! 1) { perror(read reg failed); return -1; } return 0; } static int sc16is752_init(uint32_t baudrate, uint32_t freq) { uint16_t divisor; uint8_t frac 0; // 1. 软件复位 sc16is752_write_reg(REG_IER, 0x00); sc16is752_write_reg(REG_LCR, 0x80); // DLAB 1 sc16is752_write_reg(REG_SPR, 0x80); // 触发软件复位 usleep(1000); sc16is752_write_reg(REG_SPR, 0x00); // 2. 计算波特率除数 double div (double)freq / (16.0 * baudrate); divisor (uint16_t)div; frac (uint8_t)((div - divisor) * 16.0); // 3. 配置波特率 sc16is752_write_reg(REG_LCR, 0x80); // DLAB 1 sc16is752_write_reg(REG_DLL, divisor 0xFF); sc16is752_write_reg(REG_DLH, (divisor 8) 0xFF); sc16is752_write_reg(REG_LCR, 0x03); // 8N1DLAB 0 // 4. 使能小数分频 uint8_t mcr 0x80 | (frac 4); // bit7 1 使能小数分频 sc16is752_write_reg(REG_MCR, mcr); // 5. FIFO配置触发阈值16字节 sc16is752_write_reg(REG_FCR, 0x01 | (0x02 4)); // FIFO使能16字节触发 sc16is752_write_reg(REG_IER, 0x01); // 使能接收中断 return 0; } int main(void) { i2c_fd open(/dev/i2c-1, O_RDWR); if (i2c_fd 0) { perror(open i2c failed); return -1; } if (ioctl(i2c_fd, I2C_SLAVE_FORCE, SC16IS752_I2C_ADDR) 0) { perror(set i2c addr failed); return -1; } sc16is752_init(115200, 14745600); printf(SC16IS752 init done\n); close(i2c_fd); return 0; }I2C访问方式有一个特点读寄存器之前需要先写寄存器地址。上面代码里sc16is752_read_reg就是这个逻辑。如果直接调用read函数而不先写地址读回来的数据一定是错的。3.2 裸机环境下的完整驱动框架MCU裸机环境下写SC16IS752驱动我更推荐做一个结构体封装的驱动层方便移植到不同主控平台。核心思路是把硬件相关的SPI/I2C读写函数通过函数指针注入驱动结构体typedef struct { void (*spi_cs_low)(void); void (*spi_cs_high)(void); uint8_t (*spi_read_write)(uint8_t byte); void (*delay_ms)(uint32_t ms); uint8_t i2c_addr; } sc16is752_hw_t; typedef struct { sc16is752_hw_t hw; uint8_t channel; // 0 channel A, 1 channel B } sc16is752_t; static uint8_t sc16is752_read_reg(sc16is752_t *dev, uint8_t reg) { uint8_t val 0; // SPI模式下CS拉低 - 发送(reg 1 | 0x01) - 读取数据 - CS拉高 dev-hw.spi_cs_low(); dev-hw.spi_read_write((reg 1) | 0x01); val dev-hw.spi_read_write(0x00); dev-hw.spi_cs_high(); return val; } static void sc16is752_write_reg(sc16is752_t *dev, uint8_t reg, uint8_t val) { // SPI模式下CS拉低 - 发送(reg 1 | 0x00) - 发送数据 - CS拉高 dev-hw.spi_cs_low(); dev-hw.spi_read_write(reg 1); dev-hw.spi_read_write(val); dev-hw.spi_cs_high(); }注意看SPI模式下发地址时为什么要reg 1——因为SC16IS752的SPI协议只支持8位地址bit0是读写标志位低字节的bit0为1表示读为0表示写。而I2C模式下访问寄存器地址的方式是显式发送两个字节寄存器地址数据两者差异很大不能共用一套读写逻辑。接收数据的中断处理也值得注意用查询方式做接收是最容易上手的uint8_t sc16is752_receive_byte(sc16is752_t *dev, uint8_t *byte) { uint8_t lsr sc16is752_read_reg(dev, REG_LSR); if (lsr 0x01) { // RDR 接收数据就绪 *byte sc16is752_read_reg(dev, REG_RHR); return 1; } return 0; } void sc16is752_receive_poll(sc16is752_t *dev) { uint8_t data; while (sc16is752_receive_byte(dev, data)) { // 处理收到的数据比如放入环形缓冲区 ring_buffer_push(rx_buf, data); } }3.3 Linux内核驱动参考路径如果是产品级开发强烈不建议在用户态做寄存器操作最好直接复用Linux内核的8250驱动框架。内核源码里drivers/tty/serial/sc16is7xx.c是一个现成的驱动支持SC16IS740/750/752/760全部系列。这个驱动把芯片注册为标准串口设备用起来跟原生UART没区别还能直接配合termios设置波特率、数据位、流控。如果你要基于这份驱动做修改重点关注两个回调sc16is7xx_regmap_init里面配置了I2C/SPI的寄存器读写方式以及sc16is7xx_startup中FIFO和中断的初始化逻辑。厂商的Linux BSP一般也会提供对应的驱动补丁但这颗芯片太经典了主线内核已经维护得很稳定不太需要额外改动。4. 调试中的高频问题我把排查思路整理成了一张表问题现象可能原因排查方法串口完全无输出DLAB位设置错误导致寄存器映射错位回读DLL/DLH寄存器确认数值是否和设置一致波特率偏差导致乱码小数分频未使能或小数部分计算错误回读MCRbit7应置1计算实际波特率对比目标值收不到数据但发送正常FIFO触发阈值配置过高或中断未使能先配置FCR触发阈值为1字节IER使能接收中断测试I2C通信超时地址配置错误或总线上有其他设备冲突用i2cdetect扫描总线确认设备地址是否正确SPI数据错位读写标志位处理错误确认发送地址时bit0是否正确读1写0数据丢帧严重硬件流控未开启或FIFO溢出检查CTS引脚接法使能自动流控MCR bit1或降低波特率4.1 中断一直触发但读不到数据的场景调试过程中遇到过一个比较奇怪的现象SC16IS752的中断引脚一直拉低但读取LSR寄存器发现接收数据就绪位RDR始终是0。后来定位到原因是FIFO被触发到了一个临界状态——接收FIFO里有数据但没达到触发阈值同时因为一直没有读数据RTO接收超时中断被触发后中断服务程序又没有正确读取LSR寄存器来清除中断源。处理这类问题的核心逻辑是中断服务程序里要一并检查LSR、IIR和RXLVL寄存器IIR中断标识寄存器地址0x02会告诉我们当前中断的类型RXLVL接收FIFO级别寄存器地址0x19会告诉我们当前FIFO里实际有多少字节数据。不要依赖单一标志位判断尤其是用中断方式接收数据的时候。读取RHR寄存器本身就有清中断的效果。当FIFO里的数据被读完中断就会被清除。如果读了数据还有中断检查是不是IIR显示的是“接收超时”类型这种中断只有把FIFO读空才会清除。4.2 硬件复位与软件复位的选择SC16IS752有硬件复位引脚NRST和软件复位方式。硬件复位引脚拉低至少500ns才能可靠复位。软件复位则是通过设置SPR0x07寄存器为0x80来触发设计上比硬件复位多了一个好处不需要额外的GPIO来控制复位引脚。实际项目里我一般两种复位方式都会支持开机时先尝试硬件复位如果硬件复位引脚没有接到主控GPIO上就自动降级为软件复位。这里有一个细节——软件复位之后芯片的I2C地址会恢复到由A0/A1引脚决定的默认值如果你在运行过程中动态修改了芯片地址SPI模式下通过寄存器配置复位后记得重新配置。4.3 多通道操作的寄存器切换SC16IS752有两路独立的UART通道Channel A和Channel B两路共用一个地址空间但通过IOControl寄存器IOREG地址0x0E来切换当前访问的是哪一路。因此操作Channel B的寄存器之前必须先把IOREG的值设置为0x01操作完再切回Channel AIOREG0x00。static uint8_t current_channel 0xFF; static void sc16is752_select_channel(sc16is752_t *dev, uint8_t ch) { if (current_channel ! ch) { sc16is752_write_reg(dev, 0x0E, ch); // IOREG current_channel ch; } }这个“通道锁定”逻辑很多人没注意到结果就是Channel A能通Channel B写寄存器像石沉大海怎么调都不对。还有一个细节是IOREG寄存器本身是全局的不属于任何通道所以切换通道的操作是不分A/B的。5. 实操心得从寄存器配置到稳定运行的一些额外建议5.1 电源和地线的处理直接影响通信稳定性SC16IS752本身是个低速芯片但它的数字IO如果布线不当很容易引入高频噪声。我在项目中遇到过一次偶发性乱码排查了很久最后发现是电源去耦电容离芯片太远。SC16IS752的VCC引脚旁边必须放一个100nF的陶瓷电容而且尽量靠近引脚放置同时在电源入口放一个10μF的钽电容或电解电容做低频滤波。另外一个坑是外部晶振的匹配电容。SC16IS752需要一个1.8432MHz到14.7456MHz的外部晶振具体频率跟目标波特率和内部的PLL配置有关晶振的两个引脚各接一个15~22pF的负载电容取值要跟晶振的负载电容参数匹配。如果匹配电容选得不对晶振可能不起振芯片直接哑火。5.2 流控接线的正确姿势SC16IS752支持两种流控模式自动流控RTS/CTS硬件流控和软件流控XON/XOFF。自动流控的接线方式是SC16IS752的RTS引脚接到对端设备的CTS引脚SC16IS752的CTS引脚接到对端设备的RTS引脚交叉连接。启用自动流控时MCR寄存器bit1要置1Auto RTS enable同时还要配合TCRTransmit Control Register和RHR触发阈值一起使用。TCR用于设置接收FIFO的关断/恢复阈值比如设置FIFO剩余空间低于8字节时拉高RTS对端设备就停止发送。这些参数不是随便配的要根据双方设备的收发能力综合权衡。5.3 参考代码的后续扩展方向SC16IS752还有个很实用的衍生功能支持9位数据格式比如RS-485的多机通信模式通过LCR寄存器bit6和bit5的组合可以配置为7位、8位、9位数据格式。RS-485总线上用9位模式地址帧和数据帧可以区分开来省去了额外GPIO控制收发方向的麻烦。如果你做的是多机通信系统这个功能能帮上大忙。还有就是如果用SPI接口的话SC16IS752支持最高4MHz的SPI时钟实际使用中建议SPI时钟频率不要超过2MHz这是我在稳定性测试中得到的经验值——SPI频率过高时中断响应和数据接收容易产生冲突。6. 资源清单与获取方式参考代码我从头到尾手写了一份基于裸机的放在自己的GitHub仓库里包含完整初始化、发送、接收、中断处理的源码C语言已适配STM32平台基于I2C和SPI两种接口的寄存器级驱动一个Linux用户态测试程序用于快速验证芯片工作状态带注释的寄存器配置说明文档获取方式是在GitHub搜索“sc16is752-driver”或者直接访问我的仓库主页仓库里还有一份《SC16IS752寄存器速查表》把常用寄存器的地址、位定义、配置示例整理成了PDF调试的时候打印出来放桌上比翻数据手册方便得多。另外NXP官方发布了SC16IS7xx系列的评估板原理图和Linux驱动源码这些资料在NXP官网上都能找到。如果你用的是某个特定厂商的MCU它们的SDK里通常也集成了SC16IS752的驱动比如STM32的扩展包、ESP-IDF的组件库都能直接参考移植。最后再分享一个小技巧如果你手头有逻辑分析仪调试SC16IS752的时候一定要抓I2C/SPI总线上的波形来看。我遇到过好几个问题——比如寄存器读写顺序不对、地址位错位、读写标志设置反了——都是看波形一眼定位的。光靠代码加打印信息排查效率真的差很多。逻辑分析仪几十块钱一个功能强大是你做嵌入式开发工具箱里最值得投资的一件工具。本文还有配套的精品资源点击获取
返回列表