|
本系列文章主要介绍如何使用Silicon Labs微控制器的SMBus外设设计一个I2C接口。共包含两个部分: ● 第一部分:I2C的基本概念 ● 第二部分:I2C设计示例实践
本篇文章主要介绍基于Silicon Labs微控制器的SMBus外设的I2C接口的固件架构和示例代码。
I2C状态机 在上一篇文章中,我们讨论了以精心组织的状态机的形式实现I2C固件的重要性,其中从一个状态到另一个状态的进展对应于特定I2C事务所需的事件序列。我们还强调了SMBus中断标志是确保硬件事件和固件例程之间正确交互的关键。以下是主机/写操作的事件序列和状态机图:
轮询还是中断? 当然可以通过轮询(即“手动”检查)SMBus中断标志来实现该流程图,并且一旦硬件设置了标志就继续I2C执行。这种方法可能稍微简单一些,但总的来说,轮询完全不如中断驱动的架构。首先,基于中断的技术鼓励设计人员编写可靠、结构良好、可扩展的代码。此外,轮询是低效的,因为处理器在整个事务期间不可用于其他任务;这对于I2C来说尤其成问题,因为该协议经常使用低时钟频率。
例如,假设您有一个主/写事务,您需要将5个字节传输到从设备。在包含“从地址+ R / W”字节后,总共有6个字节。每个字节需要9个时钟周期(字节本身为8个,ACK / NACK为1个),总共54个时钟周期。假设您使用的是100 kHz时钟,根据官方I2C规范,它实际上是在“标准模式”下运行时允许的最大时钟频率。此事务所需的总时间为 Ttrans = 1100kHz×54个周期= 0.54ms
看上去觉得这时间不是很长,但对于运行在25 MHz的EFM8微控制器来说,可以在120 ns或更短的时间内执行大多数指令。假设“固件中断”部分 - 例如,检查ACK / NACK位、访问数据阵列、清除事务中每个事件的中断标志 - 需要大约20个汇编指令。我们可以通过假设这个6字节的事务需要7个这样的20个指令处理干预来进行粗略的处理时间估计。 TCPU =(7×20指令)×120ns = 0.017ms ⇒TCPU/Ttrans= 0.017 ms/0.54 ms = 0.031
因此,与I2C相关的处理器执行仅需要约3%的总事务时间。换句话说,如果您使用中断驱动的体系结构,则97%的事务时间可用于其他处理任务。在这个高性能、低功耗嵌入式设备时代,效率的提高尤其重要。现今,单个微控制器可能需要与多个设备连接,同时还与主机通信并最大限度地降低功耗。
从流程图到代码 Silicon Labs提供的详细I2C流程图使得从图表到固件的转换变得相当容易。大多数I2C操作都发生在中断服务程序(ISR)中,如下所示: - //-----------------------------------------------------------------------------
- // SMBUS0_ISR
- //-----------------------------------------------------------------------------
- //
- // SMBUS0 ISR Content goes here. Remember to clear flag bits:
- // SMB0CN0::SI (SMBus Interrupt Flag)
- //
- //-----------------------------------------------------------------------------
- SI_INTERRUPT (SMBUS0_ISR, SMBUS0_IRQn)
- {
- SFRPAGE_SAVE = SFRPAGE;
- SFRPAGE = SMB0_PAGE;
- switch(I2C_State)
- {
- //Master Read===================================================
- case ...
- ...
- ...
- ...
- //Master Write===================================================
- case ...
- ...
- ...
- ...
- }
- SFRPAGE = SFRPAGE_SAVE;
- }
复制代码
此摘录显示了ISR的整体结构。 请注意,SFR(特殊功能寄存器)保存在ISR的开头并在结束时恢复。 这是一种很好的做法,但只有在您的设备没有自动将SFR保存/恢复纳入中断处理程序时才是必须的。 ISR的其余部分由与需要处理的任何I2C事务类型中的事件相对应的代码块组成。 根据I2C_State变量的值执行适当的代码块。 我们使用预处理器定义来帮助我们跟踪各种状态: - #define MstR_STA_SENT 1
- #define MstR_ADDR_SENT 2
- #define MstR_READ_BYTE 3
- #define MstR_DATA_READY 4
- #define MstW_STA_SENT 10
- #define MstW_ADDR_SENT 11
- #define MstW_BYTE_SENT 12
复制代码
请注意,事务类型(master / write或master / read)包含在状态的整体特征中。 这就是为什么我们只能使用一个switch语句来实现各种类型I2C事务的固件程序。 但是,通过这种安排,您的一个全面的switch语句可能会变得有点笨拙,因此在您的ISR中,最好使用注释来可视化地组织不同的部分(例如,主/读,主/写,从/读,从/写) 。
以下代码摘录提供了有关如何实现主/读和主/写功能的指导。 - switch(I2C_State)
- {
- //Master Read===================================================
- //start condition transmitted
- case MstR_STA_SENT:
- SMB0CN0_STA = 0; //clear start-condition bit
- SMB0CN0_STO = 0; //make sure that stop-condition bit is cleared
- SMB0DAT = (I2C_SlaveAddr<<1)|BIT0; //combine slave address with R/nW = 1
- I2C_State = MstR_ADDR_SENT; //set state variable to next state
- SMB0CN0_SI = 0; //clear interrupt flag
- break;
- //master transmitted "address + R/W" byte
- case MstR_ADDR_SENT:
- if(SMB0CN0_ACK == I2C_NACK) //if slave did not ACK
- {
- //cancel transmission and release bus, as follows:
- SMB0CN0_STO = 1; //transmit stop condition
- I2C_State = IDLE; //set current state as IDLE
- }
- else //if slave ACKed
- {
- if(I2C_NumReadBytes == 1) //if only one byte will be read
- {
- //master NACKs next byte to say "stop transmitting"
- SMB0CN0_ACK = I2C_NACK;
- }
- else //if more than one byte will be read
- {
- //master ACKs next byte to say "continue transmitting"
- SMB0CN0_ACK = I2C_ACK;
- }
- RcvdByteCount = 0; //this variable will be an index for storing received bytes in an array
- I2C_State = MstR_READ_BYTE; //set next state
- }
- SMB0CN0_SI = 0; //clear interrupt flag
- break;
- //master received a byte
- case MstR_READ_BYTE:
- I2C_RcvData[RcvdByteCount] = SMB0DAT; //store received byte
- RcvdByteCount++; //increment byte counter (which is also the array index)
- SMB0CN0_SI = 0; //clear interrupt flag
- if(RcvdByteCount == I2C_NumReadBytes) //if this was the final byte
- {
- //release bus, as follows:
- SMB0CN0_STO = 1; //transmit stop condition
- SMB0CN0_SI = 0; //clear interrupt flag
- I2C_State = MstR_DATA_READY; //this state tells the while loop in main() that the received data is ready
- }
- else if(RcvdByteCount == (I2C_NumReadBytes-1)) //if the next byte is the final byte
- {
- SMB0CN0_ACK = I2C_NACK; //master NACKs next byte to say "stop transmitting"
- }
- else
- {
- SMB0CN0_ACK = I2C_ACK; //master ACKs next byte to say "continue transmitting"
- }
- break;
- }
复制代码- switch(I2C_State)
- {
- //Master Write===================================================
- //start condition transmitted
- case MstW_STA_SENT:
- SMB0CN0_STA = 0; //clear start-condition bit
- SMB0CN0_STO = 0; //make sure that stop-condition bit is cleared
- SMB0DAT = (I2C_SlaveAddr<<1); //combine slave address with R/nW = 0
- I2C_State = MstW_ADDR_SENT; //set state variable to next state
- SMB0CN0_SI = 0; //clear interrupt flag
- break;
- //master transmitted "address + R/W" byte
- case MstW_ADDR_SENT:
- if(SMB0CN0_ACK == I2C_NACK) //if slave did not ACK
- {
- //cancel transmission and release bus, as follows:
- SMB0CN0_STO = 1; //transmit stop condition
- I2C_State = IDLE; //set current state as IDLE
- }
- else //if slave ACKed
- {
- SMB0DAT = *I2C_WriteBufferPtr; //write first byte to SMBus data register
- I2C_State = MstW_BYTE_SENT; //set next state
- }
- SMB0CN0_SI = 0; //clear interrupt flag
- break;
- //master transmitted a byte
- case MstW_BYTE_SENT:
- if(SMB0CN0_ACK == I2C_NACK) //if slave NACKed
- {
- //stop transmission and release bus, as follows:
- SMB0CN0_STO = 1; //transmit stop condition
- I2C_State = IDLE; //set current state as IDLE
- }
- //if slave ACKed and this was the final byte
- else if(I2C_WriteBufferPtr == I2C_FinalWriteAddress)
- {
- SMB0CN0_STO = 1; //transmit stop condition
- I2C_State = IDLE; //set current state as IDLE
- }
- //if slave ACKed and this was not the final byte
- else
- {
- I2C_WriteBufferPtr++; //increment pointer that points at data to be transmitted
- SMB0DAT = *I2C_WriteBufferPtr; //write next byte to SMBus data register
- }
- SMB0CN0_SI = 0; //clear interrupt flag
- break;
- }
复制代码
在主/读过程中,请注意在接收到相关字节之前(通过SMBus控制寄存器中的ACK / NACK位)设置ACK / NACK响应。 因此,此特定实现应与启用的硬件ACK功能一起使用。 |