浅析光模块固件之PC-MCU-Driver构架下的二级I2C从机的透传编程(再续)

非VIP欲索取图文版pdf,请电邮14518918@qq.com

先来个前情回顾。

如下图,目前大部分常规光模块,都是MCU芯片下挂一颗Driver芯片,故而其内部的数据通讯模式,是上位机PC通过USB转I2C芯片(比如CH341T)构成I2C主机,去访问光模块内置的MCU的I2C从机(比如EFM8LB12),然后MCU再开一个I2C主机接口去访问更下层的Driver芯片(比如GN1196)。

本文主要讨论的,是基于PC-MCU-Driver构架下的二级I2C从机的透传机制。下图是MCU的memory map,注意,GN1196的AA [00...6E]几乎都映射到了MCU的A2Lower[00...6E],GN1196的AATable80[80...FF]、AATable81[80...FF]、AATableFF[80...FF]也都映射到了MCU的A2Table80[80...FF]、A2Table81[80...FF]、A2TableFF[80...FF]。透传机制的好处是显而易见的,比如当硬件工程师改写MCU的A2Table80[80...FF]时,MCU会接着改写GN1196的AATable80[80...FF]寄存器;比如当硬件工程师回读MCU的A2Table80[80...FF]时,实际回读的是MCU轮询到的GN1196的AATable80[80...FF]寄存器。

我习惯为Memory Map中的每个block(128B),都在RAM中开辟一个联合体变量来做缓存。注意看,C51的大数组,必须用xdata关键字将之定位到扩展RAM区即XRAM,不然128B的内部RAM的data区域就会在编译时爆掉并报错L107 Overflow(更具体的,请参考文档《神秘的C51之data、idata、pdata和xdata 20250216》)。

复制代码
union _UN_A0_LOWER_  xdata uA0Lower;    //SFF8472协议A0上半页128B联合体  
union _UN_A2_LOWER_  xdata uA2Lower;    //SFF8472协议A2上半页128B联合体  
union _UN_A2_TABLE3_ xdata uA2Table01h; //A2下半页A2Table01 128B数组对应USER EEPROM  
union _UN_A2_TABLE3_ xdata uA2Table03h; //A2下半页A2Table03 128B联合体MCU主控  
union _UN_A2_TABLE3_ xdata uA2Table80h; //A2下半页A2Table01 128B对应AATable80  
union _UN_A2_TABLE3_ xdata uA2Table81h; //A2下半页A2Table01 128B对应AATable81  
union _UN_A2_TABLE3_ xdata uA2TableFFh; //A2下半页A2TableFF 128B对应AATableFF  

I2C从机中断服务函数中,涉及PC-MCU-Driver透传模式的全局变量就比较多了。首先需要在源文件common.c/common.h中,定义和申明出几个相关的全局变量,这样其他的.c文件只要include了这个common.h头文件,就能直接使用这些全局变量了:

复制代码
uint8_t xdata vDriverMappingBuffer[128]; //上位机写Driver映射区的数组缓存  
INT8U  vDriverMappingWritenFlag;  //[50], 上位机写Driver映射区的标志位  
INT8U  vDriverMappingOffset;      //[51], 上位机写Driver映射区的偏移地址起始值  
INT8U  vDriverMappingLength;      //[52], 上位机写Driver映射区的字节长度  
INT8U  vDriverMappingTableIndex;  //[53], 上位机写Driver映射区的页码序号  

然后我们就来观察这些透传相关变量,在I2C从机中断服务函数中,是如何进行七十二变的?

以I2C主机的n个字节随机连续写时序为例,应该是:

START + SlaveAdd + RegAddr + Data1 + Data2 + ... + Datan + STOP

那么在EFM8LB11的I2C从机收到START+SlaveAdd+RegAddr时会有一个SMB_SRDAT中断标志位生效,就会进入中断服务程序,代码就要赋值vDriverMappingOffset=RegAddr,并清零其他变量:

复制代码
// Slave Receiver: Data received  
case  SMB_SRDAT:  
    if (I2C_STATE_ADDR_W == vI2cState)    // First byte is RegOffsetAddr  
    {  
        RegOffsetAddr = SMB0DAT;   // Save RegOffsetAddr  
        SMB0CN0_ACK = 1;  
        RegData_arr[SlaveAddr & 0x07] = Get_Next_Val(SlaveAddr, RegOffsetAddr); //预取数据  
        vI2cState = I2C_STATE_W_OFFSET; //I2C从机状态机=I2C_STATE_W_OFFSET  
        vDriverMappingOffset = RegOffsetAddr; //记录上位机下发的寄存器地址起始值  
        vDriverMappingLength = 0;             //上位机写Driver映射区的字节长度清零  
        vDriverMappingWritenFlag = 0;         //上位机写Driver映射区的标志位清零  
        vDriverMappingTableIndex = 0;         //上位机写Driver映射区的页码序号清零  
    }  
    else                                // Second byte is data,then save data  
    {  
        ......  
}  
    break;  

接着在MCU的I2C从机收到START+SlaveAdd+RegAddr+Data1时会有一个SMB_SRDAT中断标志位生效,就会进入中断服务程序,代码就应该让长度变量vDriverMappingLength加1,同时把收到的数据拍入缓存vDriverMappingBuffer。同理,MCU的I2C从机收每收到一个Data时,都会有一个SMB_SRDAT中断标志位生效,都会进入中断服务程序,代码就都应该让长度变量vDriverMappingLength加1,同时把收到的数据拍入缓存vDriverMappingBuffer:

复制代码
// Slave Receiver: Data received  
case  SMB_SRDAT:  
    if (I2C_STATE_ADDR_W == vI2cState)    // First byte is RegOffsetAddr  
    {  
        ......  
    }  
    else                                // Second byte is data,then save data  
    {  
        RegData = SMB0DAT;  
        SMB0CN0_ACK = 1;                 // ACK received data  
        Update_Buff(SlaveAddr, RegOffsetAddr, RegData);  
        RegOffsetAddr++;  
        RegData_arr[SlaveAddr & 0x07] = Get_Next_Val(SlaveAddr, RegOffsetAddr); //预取数据  
        vI2cState = I2C_STATE_W_DATA; //I2C从机状态机=I2C_STATE_W_DATA  
        //将收到的数据存入缓存vDriverMappingBuffer[],然后上位机写Driver映射区的字节长度vDriverMappingLength++  
        vDriverMappingBuffer[vDriverMappingLength++] = RegData;  
    }  
    break;  

最后START+SlaveAdd+RegAddr+Data1+Data2+ ... +Dtatn+STOP时,会有一个SMB_SRSTO中断标志位生效,代码需要对生效vDriverMappingWritenFlag=1,并记录当前的TableIndex到vDriverMappingTableIndex:

复制代码
// Stop received while SMB was a Slave Receiver  
case  SMB_SRSTO:  
    SMB0CN0_STO = 0;           // SMB0CN0_STO must be cleared by software  
    vI2cState = I2C_STATE_STOP; //I2C从机状态机=I2C_STATE_STOP  
    //如果主机写的是A0下半页Table[4](UX3461寄存器映射RAM区),意味着接下来应该将之转写到UX3461  
    if ((vDriverMappingOffset>128) && (4==uA0Lower.sStr.Index))  
    {  
        //上位机写Driver映射区的标志位置1,到主循环中引发MCU转写Driver的操作  
        vDriverMappingWritenFlag = 1;  
        //记录上位机写Driver映射区的页码序号=当前TableIndex  
        vDriverMappingTableIndex = uA0Lower.sStr.Index;  
    }  
    break;  

光看上面文字,还是比较零散,画成下面的流程框图,可能会容易理解些:

第一版固件设计中,如果主机写了Driver控制寄存器映射到的A2Table80/81,则有vComboMappingWritenFlag = 1;当MCU接收到STOP之后,MCU从I2C_ISR()退出来回到while(1)主循环。while(1)主循环看到vComboMappingWritenFlag = 1就会把vDriverMappingBuffer[ ]转录到Driver的AATable80/81,完了还要回读Driver的AALower+AATable80/81/FF并刷新到A2Lower+A2Table80/81/FF。

但是第一版设计,实时性并不是很搞,需要上位机等待更长的idle时间。因为主循环While(1)并不只有转录和回读Driver代码,还有其他很多操作。比如上位机写了AATable80的LOSth,就不能很快回读A2[6E].bit1的LOS,必须等待一段时间,等MCU在while(1)主循环中完成回读Driver的AALower+AATable80/81/FF并刷新A2Lower+A2Table80/81/FF,才能发起回读A2[6E]的操作,否则A2[6E]可能都没有被MCU刷新。于是需要优化流程,这就有了第二版。中心思想是当MCU接收到STOP之后,如果vComboMappingWritenFlag = 1就立即触发一个软中断,然后退出I2C_ISR()后就立即跳转到Soft_ISR()中,先转录数据到Driver的AALower或AATable80或AATable81,再回读Driver并刷新A2Lower或A2Table80或AATable81。所以第二版的while(1)主循环中,就没有转录代码了,但保留了回读Driver的AALower+AATable80/81/FF并刷新到A2Lower+A2Table80/81/FF的代码。

关于软中断更具体的实现方案,请参考《EFM8的中断编程 20260330》。这里只展示软中断服务函数中,转录和回读Driver的代码:

复制代码
// Timer0中断服务程序 (ISR)  
void Timer0_ISR(void) interrupt TIMER0_IRQn  
{   uint8_t loop;  
    // 注意,TF0进入ISR后硬件会自动清零中断标志位,所以ISR中无需手工再写1清0中断标志位  
    // 这里可立即执行你的中断服务代码  
    // (可选) 如果需要再次触发Timer0中断,可以在这里手动置位 TF0  
    // TCON |= TCON_TF0__BMASK;  
    uA2Lower.sStr.PowerLeveling ++;  
  
    uA2Table03h.sStr.vDriverMappingWritenFlag = 0;//标志位清零  
    loop=0;  
    //把被主机改写的RAM区(映射到Driver的寄存器片段,从vDriverMappingOffset开始的vDriverMappingLength个字节),写进外挂Driver寄存器  
uA2Table03h.sStr.I2Cmerror = WriteRAMSectiontoDriver(vDriverMappingBuffer, 
uA2Table03h.sStr.vDriverMappingTableIndex,  
uA2Table03h.sStr.vDriverMappingOffset, 
uA2Table03h.sStr.vDriverMappingLength);  
  
    while(uA2Table03h.sStr.I2Cmerror)//error=0代表成功写,error非0需要重新写  
    {  
        uA2Table03h.sStr.I2Cmerror = WriteRAMSectiontoDriver(vDriverMappingBuffer,
uA2Table03h.sStr.vDriverMappingTableIndex,  
       uA2Table03h.sStr.vDriverMappingOffset, 
uA2Table03h.sStr.vDriverMappingLength);  
        loop++;  
        if(loop==3)//转录3次都没成功,就放弃重写超时退出  
            return;  
    }  
  
  
    //回读GN1196 A2 LOWER寄存器  
    memset(i2cRegBuf, 0xFF, sizeof(i2cRegBuf));  
  
    //I2C主设备随机读GN1196从机AA[00~7F]到i2cRegBuf[00..7Fh]  
    uA2Table03h.sStr.I2Cmerror = I2Cm_RandomReadBuffer(0xAA, 0x00, 128, i2cRegBuf);  
    //复制AA[100..103]到uA2Lower  
    //拷贝GN1196的TX BIAS和TX POWER到A2 LOWER  
    memcpy(uA2Lower.pbyBuf+100, i2cRegBuf+100, 4);  
    //跨过RxPWR_DDM,这部分MCU自己算不用GN1196的上报值  
    //拷贝LASER_TEMP和STATUS_CONTROL  
    memcpy(uA2Lower.pbyBuf+106, i2cRegBuf+106, 5);  
}  
相关推荐
惶了个恐2 小时前
嵌入式硬件第六弹——ARM(3)
arm开发·stm32·嵌入式硬件·arm
学工科的皮皮志^_^2 小时前
RS485学习
经验分享·笔记·单片机·嵌入式硬件·学习
wearegogog1232 小时前
基于STM32的数据采集+心率检测仪设计
stm32·单片机·嵌入式硬件
没有医保李先生2 小时前
mcu中cpu通用和特殊寄存器
单片机·嵌入式硬件
C^h2 小时前
RT thread中断管理学习记录
单片机·嵌入式硬件·学习
桌面运维家2 小时前
Windows 10 USB鼠标失灵:驱动、电源问题排查指南
windows·单片机·计算机外设
思茂信息3 小时前
基于 CST 的方向图可重构天线仿真分析
网络·人工智能·单片机·算法·重构·cst·电磁仿真
senijusene3 小时前
从启动到中断:基于i.MX6UL的ARM Cortex-A7中断系统详解
arm开发·嵌入式硬件
天选硬件打工人3 小时前
第二十九篇:【硬件工程师筑基系列 6-2】样板上电前全检查与安全上电流程 | 避免炸板的核心防线
单片机·嵌入式硬件·安全·硬件架构·硬件工程·射频工程