I2C协议是嵌入式开发中常用的一种总线协议,使用方便占用IO少,多个从设备可以挂在同一个主机总线上。在一些特殊的情况下,需要多个I2C接口,硬件模块不够用;或者硬件设计原因无法使用硬件I2C,这时候就需要使用普通IO模拟I2C,通常模拟为主机。使用GPIO模拟I2C方便灵活,不受硬件资源限制,移植方便。
1. 模拟I2C主机代码
这里使用的是Stm32L4的开发板,基于HAL库实现了GPIO模拟I2C主机,可以支持模拟多个I2C接口,不同接口可以有不同的SCL频率。
头文件为:
#ifndef __IIC_SIMU_H
#define __IIC_SIMU_H
#ifdef __cplusplus
extern "C" {
#endif /*_cplusplus*/
/* Includes */
#include "stm32l4xx_hal.h"
#include "common_def.h"
#include "user_config.h"
typedef struct
{
uint32_t I2C_Timing;
GPIO_TypeDef* SCL_GPIOx;
uint32_t SCL_Pin;
GPIO_TypeDef* SDA_GPIOx;
uint32_t SDA_Pin;
} I2C_PortDef;
#define I2C_TIMING_100K 4
#define I2C_TIMING_200K 2
#define I2C_WRITE 0u
#define I2C_READ 1u
#define I2C_BYTE_TIMEOUT 10u
#define I2C_PIN_MODE GPIO_MODE_OUTPUT_OD
#define ENTER_CRITICAL()
#define EXIT_CRITICAL()
/* Exported functions */
void I2C_SimInit(I2C_PortDef* port);
HAL_StatusTypeDef I2C_SimWrite(I2C_PortDef* port, uint8_t addr,
uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef I2C_SimRead(I2C_PortDef* port, uint8_t addr,
uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef I2C_SimMemWrite(I2C_PortDef* port, uint8_t addr,
uint8_t *memAddr, uint32_t memAddrLen,
uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef I2C_SimMemRead(I2C_PortDef* port, uint8_t addr,
uint8_t *memAddr, uint32_t memAddrLen,
uint8_t *data, uint32_t len, uint32_t Timeout);
#ifdef __cplusplus
}
#endif /*_cplusplus*/
#endif /* __IIC_SIMU_H */
实现文件为:
#include "iic_sim.h"
void SysCtlDelay(uint32_t ulCount)
{
__asm(" subs r0, #1\n"
" bne.n SysCtlDelay\n"
" bx lr");
}
void _I2C_DelayUs(uint32_t us)
{
SysCtlDelay(us*(SystemCoreClock/3000000));
}
void _I2C_RCC_ENABLE(GPIO_TypeDef* GPIOx)
{
if (GPIOx == GPIOA)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
} else if (GPIOx == GPIOB) {
__HAL_RCC_GPIOB_CLK_ENABLE();
} else if (GPIOx == GPIOC) {
__HAL_RCC_GPIOC_CLK_ENABLE();
}
#if defined(GPIOD)
else if (GPIOx == GPIOD) {
__HAL_RCC_GPIOD_CLK_ENABLE();
}
#endif
#if defined(GPIOE)
else if (GPIOx == GPIOE) {
__HAL_RCC_GPIOE_CLK_ENABLE();
}
#endif
#if defined(GPIOF)
else if (GPIOx == GPIOF) {
__HAL_RCC_GPIOF_CLK_ENABLE();
}
#endif
#if defined(GPIOG)
else if (GPIOx == GPIOG) {
__HAL_RCC_GPIOG_CLK_ENABLE();
}
#endif
}
void _I2C_PinOutPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = I2C_PIN_MODE;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Pin = Pin;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
void _I2C_PinInPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Pin = Pin;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
void I2C_SimInit(I2C_PortDef* port)
{
GPIO_InitTypeDef GPIO_InitStruct;
_I2C_RCC_ENABLE(port->SCL_GPIOx);
_I2C_RCC_ENABLE(port->SDA_GPIOx);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
GPIO_InitStruct.Mode = I2C_PIN_MODE;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Pin = port->SDA_Pin;
HAL_GPIO_Init(port->SDA_GPIOx, &GPIO_InitStruct);
GPIO_InitStruct.Mode = I2C_PIN_MODE;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Pin = port->SCL_Pin;
HAL_GPIO_Init(port->SCL_GPIOx, &GPIO_InitStruct);
}
/*
* Start: SDA High->Low, when SCL High
*/
void I2C_SimStart(I2C_PortDef* port)
{
_I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
_I2C_DelayUs(port->I2C_Timing);
/* Drive bus */
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
}
/*
* Stop: SDA Low->High, when SCL High
*/
void I2C_SimStop(I2C_PortDef* port)
{
_I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing);
}
HAL_StatusTypeDef I2C_SimWaitAck(I2C_PortDef* port, uint32_t WaitMs)
{
uint32_t start;
_I2C_PinInPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
start = HAL_GetTick();
_I2C_DelayUs(port->I2C_Timing>>1u);
while(HAL_GPIO_ReadPin(port->SDA_GPIOx, port->SDA_Pin) != GPIO_PIN_RESET)
{
if((HAL_GetTick()-start) > WaitMs)
{
I2C_SimStop(port);
return HAL_TIMEOUT;
}
}
_I2C_DelayUs(port->I2C_Timing>>1u);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
return HAL_OK;
}
/*
* ACK: SDA Low, when SCL Low->High->Low
*/
void I2C_SimSendAck(I2C_PortDef* port)
{
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
_I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
}
/*
* NACK: SDA High, when SCL Low->High->Low
*/
void I2C_SimSendNack(I2C_PortDef* port)
{
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
_I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
}
void I2C_SimSendByte(I2C_PortDef* port, uint8_t chr)
{
uint8_t i;
ENTER_CRITICAL();
_I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
for (i=0;i<8u;i++)
{
if (chr & 0x80)
{
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
}
_I2C_DelayUs(port->I2C_Timing>>1u);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
_I2C_DelayUs(port->I2C_Timing>>1u);
chr <<= 1u;
}
EXIT_CRITICAL();
}
uint8_t I2C_SimGetByte(I2C_PortDef* port)
{
uint8_t i;
uint8_t chr = 0u;
ENTER_CRITICAL();
_I2C_PinInPut(port->SDA_GPIOx, port->SDA_Pin);
HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
for (i=0;i<8u;i++)
{
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
_I2C_DelayUs(port->I2C_Timing>>1u);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
_I2C_DelayUs(port->I2C_Timing>>1u);
chr <<= 1u;
if (HAL_GPIO_ReadPin(port->SDA_GPIOx, port->SDA_Pin) == GPIO_PIN_SET)
{
chr |= 1u;
}
_I2C_DelayUs(port->I2C_Timing>>1u);
HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
_I2C_DelayUs(port->I2C_Timing>>1u);
}
EXIT_CRITICAL();
return chr;
}
HAL_StatusTypeDef I2C_WaitTill(I2C_PortDef* port, uint32_t start, uint32_t Timeout)
{
if ((HAL_GetTick()-start) > Timeout)
{
I2C_SimStop(port);
return HAL_TIMEOUT;
}
return HAL_OK;
}
/*
* I2c write data
* addr: slave address
* Timeout: timeout(ms)
*/
HAL_StatusTypeDef I2C_SimWrite(I2C_PortDef* port, uint8_t addr,
uint8_t *data, uint32_t len, uint32_t Timeout)
{
uint32_t i;
uint32_t start;
start = HAL_GetTick();
I2C_SimStart(port);
I2C_SimSendByte(port, (addr<<1u)|I2C_WRITE);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
for (i=0;i<len;i++)
{
I2C_SimSendByte(port, data[i]);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
I2C_SimStop(port);
return HAL_OK;
}
HAL_StatusTypeDef I2C_SimRead(I2C_PortDef* port, uint8_t addr,
uint8_t *data, uint32_t len, uint32_t Timeout)
{
uint32_t i;
uint32_t start;
start = HAL_GetTick();
I2C_SimStart(port);
I2C_SimSendByte(port, (addr<<1u)|I2C_READ);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
for (i=0;i<len;i++)
{
data[i] = I2C_SimGetByte(port);
if (i == (len-1))
{
I2C_SimSendNack(port);
} else {
I2C_SimSendAck(port);
}
if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
I2C_SimStop(port);
return HAL_OK;
}
HAL_StatusTypeDef I2C_SimMemWrite(I2C_PortDef* port, uint8_t addr,
uint8_t *memAddr, uint32_t memAddrLen,
uint8_t *data, uint32_t len, uint32_t Timeout)
{
uint32_t i;
uint32_t start;
start = HAL_GetTick();
I2C_SimStart(port);
I2C_SimSendByte(port, (addr<<1u)|I2C_WRITE);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
/* Send register addrss */
for (i=0;i<memAddrLen;i++)
{
I2C_SimSendByte(port, memAddr[i]);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
/* Send data */
for (i=0;i<len;i++)
{
I2C_SimSendByte(port, data[i]);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
I2C_SimStop(port);
return HAL_OK;
}
HAL_StatusTypeDef I2C_SimMemRead(I2C_PortDef* port, uint8_t addr,
uint8_t *memAddr, uint32_t memAddrLen,
uint8_t *data, uint32_t len, uint32_t Timeout)
{
uint32_t i;
uint32_t start;
start = HAL_GetTick();
I2C_SimStart(port);
I2C_SimSendByte(port, (addr<<1u)|I2C_WRITE);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
/* Send register addrss */
for (i=0;i<memAddrLen;i++)
{
I2C_SimSendByte(port, memAddr[i]);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
I2C_SimStart(port);
I2C_SimSendByte(port, (addr<<1u)|I2C_READ);
if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
{
return HAL_TIMEOUT;
}
/* Get data */
for (i=0;i<len;i++)
{
data[i] = I2C_SimGetByte(port);
if (i == (len-1))
{
I2C_SimSendNack(port);
} else {
I2C_SimSendAck(port);
}
if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
{
return HAL_TIMEOUT;
}
}
I2C_SimStop(port);
return HAL_OK;
}
2.测试代码
这里使用24C08模块作为从设备,通过模拟I2C进行数据读写,通过逻辑分析仪分析数据正确性。测试代码为:
I2C_PortDef i2c_port;
i2c_port.I2C_Timing = I2C_TIMING_100K;
i2c_port.SCL_GPIOx = GPIOC;
i2c_port.SCL_Pin = GPIO_PIN_8;
i2c_port.SDA_GPIOx = GPIOC;
i2c_port.SDA_Pin = GPIO_PIN_9;
I2C_SimInit(&i2c_port);
uint8_t aa[4] = {0x55, 0xaa, 0x5a, 0xa5};
uint8_t memaddr = 0;
I2C_SimMemWrite(&i2c_port, 0x50,
&memaddr, 1,
aa, 4, 30);
HAL_Delay(100);
uint8_t bb[4];
memaddr = 2;
I2C_SimMemRead(&i2c_port, 0x50,
&memaddr, 1,
bb, 4, 30);
通过逻辑分析仪抓到的数据可以看到I2C的时序是正确的:
写EEPROM
读EEPROM
3.总结
通过GPIO模拟I2C主机灵活方便,可以同时模拟多个I2C端口,缺点是相较硬件实现容易受到其他代码的影响,无法使用DMA等高级功能。然而由于I2C使用的场景大部分情况下速度都比较慢,GPIO模拟I2C仍是一种比较实用的方式。