ARTICLE DETAIL

资讯详情

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

S32K144 LPI2C主从模式配置与多设备通信实战指南

S32K144 LPI2C主从模式配置与多设备通信实战指南 S32K144最让人上头的一点就是它那颗 LPI2C 模块。以前写单片机I2C要么用GPIO软件模拟要么用传统I2C外设遇到从机时钟拉伸、总线被拉死、多设备冲突就得跟寄存器死磕半天。S32K144的LPI2C不一样Master/Slave模式都能配有两路LPI2C可独立使用还带FIFO、中断和超时机制做多设备通信是真的省心。这篇文章把我配置LPI2C主从模式、把多个传感器和存储器挂到同一条总线上跑通的完整过程以及中途踩过的所有坑都整理成文给准备在S32K144上做多设备通信的朋友做个参考。先说清楚适合谁看如果你刚拿到S32K144开发板想在一条I2C总线上同时挂几个传感器、EEPROM或者IO扩展芯片或者你之前只用SDK封装函数想弄明白LPI2C底层到底怎么配置主从模式再或者你已经在调试却发现从机总是不应答、总线莫名其妙卡死那这篇文章很适合你。1. LPI2C选型与多设备通信方案设计1.1 为什么用LPI2C而不直接拿GPIO软件模拟很多做单片机的朋友习惯用GPIO模拟I2C程序里自己翻转SDA和SCL来拼起始、停止、ACK这些时序。软件模拟的好处是引脚灵活想挂哪就挂哪但坏处也很明显主循环一旦被中断打断时序容易乱跑400kHz时对延时精度要求高一旦程序里有个耗时任务通信质量就直线往下掉。LPI2C是S32K144硬件自带的低功耗I2C外设控制器自动处理起始条件、停止条件、地址匹配、ACK/NACK检测还有收发FIFO。这意味着MCU不需要在每传输一个字节时都去盯信号翻转只要往FIFO里填数据或者读数据就行。LPI2C还支持DMA触发数据量大时可以直接搬数据CPU空闲下来干别的事。S32K144上具体有两个LPI2C外设也就是LPI2C0和LPI2C1。如果你的项目同时需要主节点和从节点两种角色可以用LPI2C0做MasterLPI2C1做Slave两者互不干扰。LPI2C模块在寄存器层面通过MCR寄存器的MST位切换主从角色从硬件设计上就比软件模拟I2C稳固得多。我在项目里之所以选LPI2C还有一个实际原因板子上外设多除了I2C还有CAN、UART、SPI引脚复用得精打细算。LPI2C引脚可以灵活复用每个LPI2C模块都有多组可选引脚这样布局走线就宽裕不少。1.2 Master/Slave模式下硬件架构简析理解LPI2C主从模式之前先把模块内部的几个角色关系搞清楚。LPI2C不是一堆寄存器堆在一起它内部其实分了两套逻辑主机接口和从机接口。主机接口负责主动控制总线发起START、发送地址、发送数据、接收数据、产生STOP监控ACK/NACK状态。从机接口则被动等待监听地址匹配匹配成功后接收数据或发送数据。当MCR的MST位写1时模块运行在主机模式MST写0时模块进入从机模式。有些资料会说LPI2C的Master和Slave可以“同时工作”实际上指的是芯片上有两个独立LPI2C模块每个模块在同一时刻只能选一个角色。别指望同一个LPI2C外设既当主又当从除非你用中断频繁切换模式但那样很容易造成时序混乱不建议在产品代码里这么干。LPI2C内部还带了数据匹配功能可以作为地址过滤器使用。比如你要在总线上监听某个地址的数据可以配置匹配寄存器当地址和数据符合条件时触发中断或DMA请求。这个特性做多设备监控时非常有用。S32K144的LPI2C还支持10位地址但实际项目里7位地址完全够用。一条标准I2C总线理论上最多能挂127个从设备实际受限于总线电容和地址冲突常见做法是控制在8个设备以内。1.3 地址规划和总线负载评估多设备通信的第一步不是写代码而是先规划地址表。我这次在S32K144总线上挂了四个设备地址分配如下设备芯片型号从机地址数据量用途温湿度传感器SHT300x446字节采集温湿度EEPROMAT24C020x508字节/页存配置参数IO扩展器PCAL64160x202字节控制外部IOOLED显示屏SSD13060x3C显存状态显示这个地址表不是拍脑袋定的每个设备都有固定地址或者通过硬件引脚配置地址。例如AT24C02的A0/A1/A2引脚接地后地址就是0x50PCAL6416的A0/A1/A2引脚配置组合也影响了地址。如果两个设备地址一样总线就会出问题主设备发送地址后两个从机都可能应答把控SDA线甚至造成总线短路风险。规划好地址后还要评估总线负载。I2C总线是开漏结构所有设备共用一个上拉电阻网络。设备数量越多总线电容越大上拉电阻取值就需要相应调整。工程上常用经验值通信速率建议上拉电阻适用场景100kHz标准模式4.7kΩ低速传感器、少量设备400kHz快速模式2.2kΩ中等速率、多设备1MHz快速模式1kΩ短距离、高速传输我这次用400kHz快速模式上拉电阻选了2.2kΩ。如果线缆比较长或者设备特别多优先降低速率到100kHz而不是盲目调小上拉电阻否则低电平可能超过从机允许的VOL阈值通信照样会出错。2. 模式配置前必须处理的引脚与时钟2.1 引脚复用MUX与开漏配置S32K144的引脚默认不是LPI2C功能必须在PORT模块的PCR寄存器里把引脚复用成LPI2C对应的ALT功能。不同封装、不同引脚对应的ALT值不一样具体要查芯片引脚复用表。我这里以LPI2C0挂到PTB2/PTB3为例做个示意// 把 PTB2/PTB3 配成 LPI2C0 的复用功能 // 具体 ALT 值以你所用芯片封装的数据手册为准 PORTB-PCR[2] PORT_PCR_MUX(5); // LPI2C0_SCL PORTB-PCR[3] PORT_PCR_MUX(5); // LPI2C0_SDA这里有个非常容易踩坑的地方I2C是开漏总线引脚配置时要确保外部有上拉电阻。如果板上没有外接上拉有的S32K124引脚可以用内部上拉但内部上拉阻值偏大多设备高速通信容易边缘变缓我还是建议硬件上加上2.2k到4.7k的外部上拉。另外要注意PCR里的其他位比如是否使能了拉普或者拉低。有些默认状态可能是下拉I2C空闲时需要SCL和SDA都为高如果引脚被内部下拉拉低总线永远都忙。我一开始就是没清内部下拉总线状态一直不正常排查了半天。如果你用的是S32K1xx SDK初始化引脚也可以直接用Port驱动接口比如PORT_SetPinMux()。但底层原理是一样的核心就是把PCR中MUX字段设对。2.2 时钟树与波特率分频计算LPI2C功能时钟来源于PCC时钟分配模块。S32K144里每个外设都有对应的PCC寄存器可以选不同的时钟源。LPI2C一般推荐从SOSC、FIRC或者SPLL分频过来的时钟具体选哪个取决于你系统时钟树怎么搭。LPI2C波特率计算公式主线是SCL频率 LPI2C功能时钟 / (2 * (CLKLO CLKHI))CLKLO和CLKHI分别对应SCL低电平和SCL高电平的计数长度它们在LPI2C的MCFGR1寄存器里配置。我这次系统里LPI2C功能时钟选了12MHz目标速率400kHz算一下计数 12MHz / 400kHz / 2 15所以CLKHICLKLO加起来要等于15。我让CLKHI7CLKLO8代入式子SCL 12MHz / (2 * (7 8)) 400kHz正好命中。如果你用100kHz标准模式那总数就是60CLKHI和CLKLO可以各取30。注意CLKLO和CLKHI的取值范围有限超出范围就要先通过MCFGR1里的PRESCALE字段做预分频。计算逻辑不复杂但容易漏了PRESCALE导致实际频率对不上。还要强调一点从机的最高通信速率是固定的比如某传感器只支持100kHz那就算主控能跑400kHz总线整体也只能降到100kHz。总线速率取决于最慢的一个设备不是取平均值。2.3 初始化顺序与复位策略LPI2C配置顺序有讲究。推荐流程是打开PCC外设时钟配置引脚复用复位LPI2C模块配置主从模式和滤波器参数使能LPI2C模块很多人上来直接改MCR里的MST位参数配模式模块还处于使能状态配置很容易失效。我习惯先关模块PCC-PCCn[PCC_LPI2C0_INDEX] PCC_PCCn_CGC_MASK; // 使能 LPI2C0 时钟 // 复位模块 LPI2C0-MCR LPI2C_MCR_RST_MASK; LPI2C0-MCR 0x00;复位完之后模块处于关闭状态这时候再写MCR、MCFGR这些寄存器才不容易出问题。初始化完成后最后再置位MEN使能模块。这里有一个实际体会不要在初始化完成前把模块提前使能否则模块检测到总线上有异常电平就会把BUSY位置上后面所有通信都会卡在“等待总线空闲”。如果出现这种情况先把模块重新复位再按顺序走一遍初始化。3. Master模式配置与多从机访问3.1 主模式初始化参数与寄存器设置主模式配置的核心思路是把LPI2C切到主机模式设定波特率、滤波、超时时间然后通过命令寄存器MTDR来发起事务。一个简单的寄存器级初始化示意如下// 关闭模块 LPI2C0-MCR ~LPI2C_MCR_MEN_MASK; // 切主机模式 LPI2C0-MCR | LPI2C_MCR_MST_MASK; // 配置预分频和时钟高低电平计数 LPI2C0-MCFGR1 LPI2C_MCFGR1_PRESCALE(0U) | LPI2C_MCFGR1_CLKHI(7U) | LPI2C_MCFGR1_CLKLO(8U); // 重新使能模块 LPI2C0-MCR | LPI2C_MCR_MEN_MASK;需要注意的是MCR中的MST位不只是模式选择模块在主机模式下会根据MST位的写入沿自动产生START或STOP时序。这个行为跟传统外设不一样所以操作的时候要格外小心别在事务中途随意翻转MST位。实际SDK封装得比较友好用NXP的S32K1xx SDK代码可以简化成lpi2c_master_config_t masterConfig; LPI2C_DRV_MasterInit(0, masterConfig, 12000000U); LPI2C_DRV_MasterSetBaudRate(0, 400000U);但理解底层寄存器还是有用的因为在调试异常时序时SDK函数不会直接告诉你哪一步出了问题。3.2 一次完整读写的命令序列主模式下发一次写操作完整流程可以拆成五步等待总线空闲发送START条件并携带从机地址和写位等待从机ACK应答逐字节发送数据每个字节等待ACK发送STOP条件LPI2C主机模式下上述步骤通过写MTDR命令寄存器完成。第一个命令通常是START加地址命令码大概类似0b010再拼上7位地址和读写位。收到地址后主机要检查状态寄存器的ACK标志// 示意发送 START 地址 写位 LPI2C0-MTDR LPI2C_MTDR_CMD(0b010) | LPI2C_MTDR_ADDR(0x50U) | LPI2C_MTDR_RW(0U); // 等待地址发送完成并检查ACK具体状态位名以参考手册为准 while ((LPI2C0-MSR 0x... ) 0) {}写数据时同样往MTDR里写命令命令类型是发送数据。注意LPI2C有FIFO可以一次预写多个字节但要控制写入数量别让FIFO溢出。读流程稍微不同发送地址加读位后主机需要为每个字节提供时钟。LPI2C主接收数据时每收完一个字节在主端最后一个字节时不应该ACK要主动发送NACK然后发STOP。多字节读取时倒数第二个字节之后就要为最后一个字节准备NACK这是I2C协议的规定。这里有一个不少新手会犯的错往MTDR里写地址时不手动把地址左移一位。I2C地址字节的结构是“7位从机地址 1位读写标志”比如从机地址0x50写操作的地址字节是0xA0读操作是0xA1。如果直接拿0x50当地址字节发送从机根本不会应答。LPI2C驱动内部通常会帮你处理移位但你要是自己操作寄存器这个细节绝对不能忘。3.3 多从机轮询机制与地址切换经验多从机通信就是主设备在总线上按地址轮询各个从机。代码层面看起来很简单无非是把读写函数包一层参数传不同的从机地址。但实际工程里要处理几个问题。第一个是访问节奏。不同从机对访问间隔的要求完全不同。比如SHT30温湿度传感器每次发起测量后要等几十毫秒才能读结果AT24C02写入后需要几毫秒的写周期PCAL6416这种IO扩展器则可以随时读写。如果所有设备都在一个100ms周期里轮询温湿度和IO扩展器没问题EEPROM写入时一旦触发写周期紧接着的访问就会NACK。我在实际代码里用了一个简单的状态机把外设按“快速响应、慢速响应、写入后延时”三类分开调度。每个从机访问之间插入不同延时避免主机连发导致从机没准备好。第二个是多从机总线上需要有超时重试机制。I2C硬件本身没有硬件超时LPI2C虽然有超时计数器但也要在配置里显式使能并设定参数。一旦某个从机死机或者总线被拉低主机可能一直卡在等待ACK的状态这时候靠超时中断跳出来然后对总线做恢复处理比无限死等强得多。4. Slave模式配置与主机请求响应4.1 从模式地址匹配与中断响应如果把S32K144配置成LPI2C从机常见场景是这颗芯片作为子控制器等待上级主控读取数据或下发控制指令。从模式配置的主线是关模块、设置从机地址、使能从机中断、再开模块。一个从模式初始化的示意// 关闭模块 LPI2C0-MCR ~LPI2C_MCR_MEN_MASK; // 设置从机地址比如 0x40 LPI2C0-SADR LPI2C_SADR_ADDR0(0x40U); // 使并从机地址匹配中断 LPI2C0-SIER | LPI2C_SIER_AMFIE_MASK; // 使能模块 LPI2C0-MCR | LPI2C_MCR_MEN_MASK;从机模式使能后总线上发生地址匹配时LPI2C会置地址匹配标志并触发中断。这个中断入口里要做的事情很明确判断主机是读还是写读就往从机发送FIFO里填充数据写就从接收FIFO里读取数据。有一点需要注意从机在响应主机读请求前如果发送FIFO还没有数据从机会把SCL拉低制造所谓的“时钟拉伸”让主机等一等。这是I2C协议允许的但对S32K144从机来说如果MCU处理中断不及时SCL会被长时间拉低主机那边就会表现为通信超时。因此从机模式的等待延迟尽可能短中断优先级要拔高。4.2 从机收发缓冲与背靠背数据管理LPI2C从机有自己的收发FIFO深度有限大概几个字节。数据吞吐量不大时没问题但主机一次性发几十个字节过来从机FIFO很容易溢出。我在做从机数据接收时习惯在地址匹配中断后立刻读取FIFO里的数据并且清空FIFO确认标志。如果一次事务里主机连续发很多字节MCU中断服务程序要保证每收到一个字节就及时读走不能等到全部接收完再处理。发送方向也是一样的道理。主机发起读请求之前从机要提前把应答数据准备好。如果主机一次读32字节而发送FIFO深度只有4字节MCU需要在发送FIFO空标志置位时不断填充数据一旦填充不及时从机就会时钟拉伸等待主机端速度被拖慢。实际项目里更多用DMA配合从机收发减少中断频繁进出的CPU开销。LPI2C的DMA请求可以在每个FIFO空或满时触发数据量大时优势明显。我从单字节中断换成DMA模式之后CPU占用率降了将近一半。4.3 多设备组网中的从机角色补充说明多设备组网中S32K144的从机角色还有一个常见用途做网关或者协议转换器。比如上级主控通过I2C读取S32K144的状态S32K144内部再通过其他接口采集传感器数据形成一个桥接节点。这种场景下从机地址的选择非常重要。总线上每一个设备地址必须唯一所以设计初期就要把设备地址纳入整体规划。如果确实遇到地址冲突一种办法是把S32K144的从机地址做成可配置的例如通过DIP开关或者EEPROM存储地址参数这样同一硬件可以在不同设备上复用。从机模式的另一个细节是总线上如果同时有多个主机从机必须能在一次地址匹配被两个主机交替访问。I2C协议本身不允许总线同时被两个主机占用多主机通过仲裁决定谁先访问。S32K144作为从机时不用自己处理仲裁逻辑但需要做好多次快速地址匹配的准备防止漏处理。从机模式下可以把高字节地址掩码寄存器利用起来让同一个从地址响应对应的地址范围。但我不建议一上来就玩地址掩码优先把单个地址的事务跑稳再考虑复杂匹配。5. 多设备通信联调的完整过程5.1 先把单从机调通最小工程对照多设备通信联调最忌讳一上来把四个设备全挂上。我习惯先只挂一个从机把单设备读写跑通再逐步增加。我的调通步骤是这样的先选EEPROM AT24C02做第一个设备因为时序简单、容易验证。第一步向地址0x50发一个设备地址扫描也就是发送START加0xA0地址字节看从机是否返回ACK。如果逻辑分析仪上能看到ACK说明地址、引脚、上拉、时钟都没问题。接下来做一次写操作往EEPROM地址0x00写入一个字节0x5A等待5ms写周期然后从0x00读回数据。读到0x5A就算打通了第一层。这段过程里最简单有效的工具是逻辑分析仪。很多逻辑分析仪自带I2C解码器抓完波形后能直接解析出地址字节和数据内容比在示波器上数脉冲快太多。5.2 挂载多个从机的联调顺序单设备打通后再逐步增加设备。我第二次挂的是IO扩展器PCAL6416第三次挂温湿度传感器SHT30最后才是OLED显示屏。为什么OLED放最后因为OLED需要初始化序列一次要发不少命令和数据调试时波形比较乱适合放在链路稳定之后再处理。多设备同时挂上后我用了一个循环扫描脚本每隔500ms依次读取每个设备的状态序号操作预期结果1读SHT30温湿度数据6字节温度湿度数据符合常识2写AT24C02再读回读回数据和写入一致3写PCAL6416输出寄存器外部LED点亮4写OLED显示字符屏幕刷新正常如果某一步失败优先拔掉其他设备只保留出问题的设备缩小排查范围。这种做法很笨但非常有效能很快把问题定位到“设备本身”“软件时序”还是“总线干扰”。5.3 用逻辑分析仪验证多设备响应波形联调时我最常看的是逻辑分析仪抓下来的总线时序。I2C波形看起来简单但解码时有几个关键点。首先是起始条件SCL保持高电平时SDA从高电平跳变到低电平。停止条件相反SCL高电平时SDA从低电平跳变到高电平。如果总线上出现连续的乱七八糟的沿很可能是有设备在错误时间驱动了SDA。其次是地址字节。逻辑分析仪的I2C解码器会显示7位地址、读写标志和ACK/NACK状态。比如我发0xA0解码器会显示“Addr0x50 W”后面跟一个ACK。如果显示NACK说明这个地址没有从机应答。最后是读操作时的ACK时序。主机读数据时最后一个字节必须由主机回NACK表示“后面不读了”然后主机发STOP。如果主机在最后一个字节回了ACK从机会继续把SDA驱动低导致停止条件不完整总线状态异常。逻辑分析仪采样率建议不低于8MHz否则400kHz的快速模式波形边缘不够平滑解码容易出错。长期调试时我给采样率设到25MHz数据量虽然大但波形干净很多。6. 常见问题与排查技巧实录6.1 总线一直忙或SDA被拉低多设备I2C最常见的问题就是总线忙发送事务时发现BUSY位一直置1。排查时先拿万用表量SCL和SDA电平。正常空闲状态两个引脚都应该是高电平。如果SDA或者SCL其中一个被拉低说明有设备还在占用总线或者某个从机没有正常释放总线。我遇到过的情况包括某个传感器上电异常SDA输出被拉死还有一个EEPROM的WP引脚悬空导致写入过程卡死。解决方法是给问题设备单独复位或者干脆断开它的电源排查完再接上。如果是因为通信过程中主机异常终止导致从机处于半通信状态可以用GPIO模拟9个SCL时钟脉冲来“解锁”SDA。具体做法是SDA保持释放SCL手动翻转9次让处于半途的从机完成当前字节传输并释放SDA。这个办法对绝大多数I2C设备有效。6.2 地址匹配不上、总是不ACK从机地址不ACK的排查顺序先看逻辑分析仪实际发出去的地址字节对不对再看从机供电和复位引脚再看它的硬件地址配置引脚有没有焊接正确。有一类问题是硬件上的。比如AT24C02的A0/A1/A2引脚悬空芯片内部可能读到不确定电平导致实际地址不是0x50而是别的值。还有不少OLED模块上有I2C地址选择电阻有的模块默认0x3C焊接改动后变成0x3D。另一类是软件上的。自己操作MTDR寄存器时如果没有把7位地址左移一位地址字节就变成了0x28而不是0x50从机当然不会ACK。建议先统一用SDK提供的驱动函数它内部会帮你处理地址移位等调通后再自己控制寄存器也不迟。6.3 时钟拉伸引发的主从同步异常S32K144作为主机时如果从机在响应前需要处理时间会把SCL拉低制造时钟拉伸。LPI2C主机模块本身支持这个操作但如果从机拉低时间太长主机会一直等待。我在调试一个温湿度传感器时发现它每次测量后需要等待几十毫秒才能读数据。如果读得太快传感器会一直拉低SCL等待内部转换完成。这时候代码不能用同步阻塞的方式傻等需要加超时。LPI2C模块里通常有总线超时配置合理设置超时时间超时后复位总线并重试。从机模式下的时钟拉伸问题则是另一回事。S32K144作为从机如果来不及填充发送FIFO它会自动拉低SCL这个机制是好的但要是MCU的中断响应被更高优先级任务堵住SCL会一直低电平主机那边就表现为总线卡死。解决办法是给LPI2C从机中断一个足够高的优先级或者在中断里只做FIFO填充不做复杂数据处理。6.4 电平与上拉失配5V/3.3V混合系统的坑S32K144是3.3V供电很多市场上常见的模块却是5V逻辑比如老式EEPROM板子、某些OLED屏。直接把5V设备接到3.3V总线上轻则通信不稳定重则烧引脚。看数据手册时要注意引脚是否有5V容忍标记。S32K144很多引脚是5V容忍但并不是所有引脚都带FT标记。如果确认不支持就必须用双向电平转换芯片比如PCA9306或者用带电平转换的I2C模块。上拉电阻的阻值同样会影响电平。上拉电阻选太小比如1kΩ设备低电平输出时灌电流过大可能超过从机的最大灌电流选太大比如10kΩ400kHz下上升沿变得平缓容易造成误采样。我调试多设备时先按2.2kΩ起步如果设备多了再加上总线电容速率降到100kHz很少出电平问题。7. 实战心得与后续扩展7.1 推荐配置顺序和代码规范经历过几次折腾后我整理了一套固定的LPI2C配置顺序每次都用这个流程踩坑概率低很多先确认硬件上的外部上拉电阻没有上拉就不调软件打开PCC外设时钟配置引脚复用为LPI2C功能复位LPI2C模块设置主从模式、分频、滤波、超时使能LPI2C模块先跑一个“地址扫描”确认总线上所有设备再开始具体事务通信代码方面我习惯把所有LPI2C操作封装成独立函数比如LPI2C_WriteReg、LPI2C_ReadReg、LPI2C_CheckDevicePresent主循环里不要出现大段裸寄存器操作。每个函数里都带超时判断和返回值状态这样出问题时能快速定位是哪个设备、哪一步失败。7.2 从单主多从到多主仲裁的演进方向调通单主多从后如果项目还要再上一个台阶可以考虑多主机架构。LPI2C支持多主机仲裁两个主设备同时发起总线时SDA为低的一方会赢得仲裁输掉的一方需要放弃当前传输并等待总线空闲后重试。多主机实际工程里要注意两点一是每个主机都要有重发机制因为仲裁失败是常态而不是异常二是总线上需要I2C协议规范里的“总线空闲”检测两个主机不能在总线上同时动作。LPI2C的状态寄存器里有总线忙标志发送前等待该标志清零再发起START能大幅降低仲裁冲突概率。7.3 后续可以尝试的扩展玩法LPI2C两路都用完后如果还要扩展更多总线可以外接I2C多路复用器比如TCA9548A它可以把一条总线扩展成8路每路再挂一批设备彻底解决地址冲突问题。还有一种玩法是用FlexIO模拟一路额外的I2C主机S32K144的FlexIO做这个并不算难只是配置起来要比LPI2C麻烦一些。FlexIO的好处是完全可编程甚至可以实现非标准的单线协议。如果追求极致性能可以把LPI2C收发都改成DMA驱动配合环形缓冲区收发数据CPU占用能压得非常低。我后来在量产项目里就是这么做的通信稳定性比中断驱动模式好不少尤其是在主循环任务很多、中断响应不及时的场景下。所以如果你手上正好有S32K144花点时间把LPI2C这块啃透后面做多设备通信会顺手很多。
返回列表