5 IIC的实际使用
5.1 MCU做主机读写eeprom数据
5.1.1 轮询
- IIC初始化
- 使能IIC主机模式
- 配置波形
- 设置空闲超时时间
- 轮询发送数据
IIC通信时,除了遵循基本的IIC协议,还要遵循通信设备的应用层协议,以复旦微的FM24C256E型号的eeprom为例,和这块eeprom通信时,主要目的是读写eeprom的内容,所以要明确需要往哪个地址读写数据,需要使用子地址(从机地址指的是从机地址,子地址指的是从机设备中的地址)。相关手册中定义了读写协议如下:
在通信时,是将要发送的数据放入主机发送数据寄存器(MTDR)中,这个寄存器有两个字段,分别是命令数据(CMD)和发送数据(DATA)。一般这个寄存器和数据接收寄存器(MRDR)都是fifo结构的,可以往里放fifo深度的数据,遵循先进先出原则。以MTDR为例,可以理解为放入fifo中的数据是要发送的数据,从fifo中出来的数据已经变成了波形,能被示波器或逻辑分析仪抓到(只是便于理解,都是数字信号)。
以往eeprom的0x0000地址写16个byte数据,数据内容为[0x00, 0x01…0x0F]为例:
需要首先往MTDR寄存器写入开始信号+从机地址+ 读(100b,代表开始一个start信号,并发送data数据(7位地址加写入位))
u8Addr = (uint8)((uint8)(pRequest->u8SlaveAddress << 1) + I2C_DIR_WRITE);
eRetType = FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_STARTANDTRANSMIT, u8Addr);
//FCIIC_MasterWriteHw()实现功能为将CMD和DATA拼接后写入MEDR
然后往往MTDR寄存器写入子地址,从前面通信协议得知子地址长度为2byte,循环写2次
for (i = pRequest->subAddressSize; i > 0 ; --i)
{
eRetType = FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_TRANSMIT, FCIIC_MTDR_DATA(pSubAddr[i - 1]));
if (E_NOT_OK == eRetType)
{
break;
}
}
循环写入需要存入eeprom中的数据,要写入16byte的数据,故循环写入16次。(写入的长度)
for (u16LoopCount = 0; u16LoopCount < u16LoopMax; u16LoopCount++)
{
eRetType = FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_TRANSMIT, pRequest->BufferPtr[u16LoopCount]));
/* if u8RetVal == 1 means send timeout */
if (E_NOT_OK == eRetType)
{
break;
}
}
最后写入stop信号
FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_STOP, 0x00);
信号波形如图
读取的时候,发送完子地址信息后,继续发送一帧数据:起始信号+从机地址+读取方向;然后还有一帧数据,代表需要读取数据的长度(根据这个决定SCL的长度,没有相应波形产生)。
if (I2C_DIR_READ == pRequest->u8Direction)
{
u8Addr = (uint8)((uint8)(pRequest->u8SlaveAddress << 1) + I2C_DIR_READ);
eRetType = FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_STARTANDTRANSMIT, u8Addr);
if (E_OK == eRetType)
{
eRetType = FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_RECEIVE, (uint8)(pRequest->u16Len - 1));
}
}
5.1.2 中断
中断和轮询相比,需要配置中断寄存器,打开发送数据中断使能(TDIE)和接收数据中断使能(RDIE),打开相应中断。如果发送数据寄存器(MDTR)的fifo是空的(或者低于设定的阈值),发送数据标志位就会被置上,代表需要往fifo中填充数据,中断打开会就会进入发送数据中断,在中断中不断往MDTR寄存中填充数据,完成发送,填充数据的流程和轮询一致。(如果是读,方向相反,当接收数据寄存器中不为空时,进中断,读取fifo中的数据)。
5.1.3 DMA
DMA和轮询相比,就是往MDTR中填充数据的时候,提前配置好DMA,通过DMA的方式填充,避免手动挨个填充。(读取方向相反)
for (u16LoopCount = 0; u16LoopCount < u16LoopMax; u16LoopCount++)
{
eRetType = FCIIC_MasterWriteHw(u8I2cIdx, FCIIC_TX_CMD_TRANSMIT, pRequest->BufferPtr[u16LoopCount]));
/* if u8RetVal == 1 means send timeout */
if (E_NOT_OK == eRetType)
{
break;
}
}
//即将这部分代码替换为下面的代码,就是开启DMA的数据发送(需要提前配置好DMA,这里只是开启DMA的搬运)
FCIIC_MasterDmaStart(u8I2cIdx, pRequest);
5.2 MCU做从机
5.2.1 中断
- IIC初始化
- 使能IIC从机模式
- 设置从机地址
- 开启监听
- 开启地址匹配接收中断((当主机发送的地址和IIC外设设置的从机地址一致时,会进中断)
- 此时主机发送数据,从机地址匹配就会进入地址匹配中断(IIC),查看IIC从机状态寄存器,可以发现AMF(地址匹配标志位)置1。
- 中断处理(IIC):根据中断原因做出回应
- 发送ack(作为地址和读写判断位的回应)
- 根据接收到的第一个byte数据中的读写位判断读写方向
- 根据读写方向使能对应的IIC中断
- 如果是读,说明MCU做从机应当发送数据,使能从机发送中断
- 如果是写,方向相反
- 中断处理(IIC): 进入读写中断
- 中断发送数据或读取数据
- 中断处理(IIC):进入结束信号中断
- 清状态,关读写中断
工具测试可参照DMA从机方式,中断和DMA的区别是DMA需要提前配置好一帧数据长度(配置DMA时需要使用),中断不需要,中断是以stop信号进入stop中断结束。
5.2.2 DMA
- IIC初始化
- 使能IIC从机模式
- 使能DMA发送接收数据
- 设置从机地址
- 开启监听
- 关闭发送接收数据中断使能(打开后发送fifo是空的,会一直进中断)
- 配置DMA(DMA发送长度(所以IIC做从机使用DMA方式需要提前知道数据长度),DMA传输目标地址等等)
- 使能地址匹配接收中断(当主机发送的地址和IIC外设设置的从机地址一致时,会进中断)
- 使能停止信号检测中断
- 此时主机发送数据,从机地址匹配就会进入地址匹配中断(IIC),查看IIC从机状态寄存器,可以发现AMF(地址匹配标志位)置1。
- 中断处理(IIC):根据中断原因做出回应
- 发送ack(作为地址和读写判断位的回应)
- 根据接收到的第一个byte数据中的读写位判断读写方向
- 根据读写方向使能对应的DMA通道
- 如果是读,说明MCU做从机应当发送数据,将发送数据buffer中的数据搬到IIC发送fifo寄存器中完成发送
- 如果是写,方向和读相反
- 中断处理(DMA):当DMA搬运结束后触发DMA中断
- 发送(接收)完成
- 中断处理(IIC):接收到结束信号后进入IIC中断
- 判断是否发送完成,发送接收长度是否和需求长度一致。
使用IIC工具做主机,从地址和MCU设置保持一致,读取数据,读取数据为buffer中设置的数据,同时抓取波形。
然后发送数据,同时抓取波形
再次读取数据,同时抓取波形,和写入数据一致:
写入数据和读取数据一致,通信成功。
5.3 主机从机区别
- 初始化配置不同
- 主机需要配置波特率,波形等等;从机不需要配置,波特率是由主机决定的
- 从机需要配置从机地址;主机不需要
- 发送读取数据由主机决定
- 主机随时开始读取发送数据,从机只能开启监听等待(打开从地址匹配中断,从地址匹配后开始响应)