基于AT24C02的IIC硬件原理图
硬件IIC配置过程
利用CubeMX配置硬件IIC主要注意以下几点:100/400K的速率、设备地址0xA0写地址,0xA1读地址。
我们打开CubeMX对IIC进行配置,配置之前我们查看AT24C02芯片对写入时序的要求如下:
IIC设备上可以挂多个设备,这跟IIC的从机地址有关,AT24C02这款芯片通过地址线的搭配可以连接8个从机。
上述原理图地址线全接地是0XA0。
主要用的HAL库函数用到两个函数:
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
使用之前将上述函数进行封装方便后期使用:
在建立的at24cxx.c中外部声明结构体句柄extern I2C_HandleTypeDef hi2c1;根据HAL库中对IIC的读写操作函数的格式,并对其做封装处理,目的是将该函数的底层配置在这个封装里配置好,在引用该函数时不必再重复书写。
/******************写操作******************/
int AT_Write(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len)
{
if(pbuf == NULL)//数据为空,返回-1
{
return -1;
}
HAL_I2C_Mem_Write(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff);
}
/******************读操作******************/
int AT_Read(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len)
{
if(pbuf == NULL)
{
return -1;
}
HAL_I2C_Mem_Read(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff);
}
在At24cxx.h文件中对上述函数进行声明,并定义读写地址。
#define DEV_ADDR 0xA0
int AT_Write(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len);
int AT_Read(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len);
在main.c函数中直接操作就可以了
AT_Write(DEV_ADDR,0,(uint8_t*)"HELLO",5);
HAL_Delay(100);//加延时为后续的读操作预留时间
AT_Read(DEV_ADDR,0,table,5);
另外注意:在i2c.c中,由CobeMX生成的HAL_I2C_MspInit函数将IIC时钟的使能__HAL_RCC_I2C1_CLK_ENABLE()放在了后面,这会导致IIC不工作,所以应将其调整到IO口配置之前。
模拟IIC配置过程
当设备的IIC引脚用于其他用途时,可以采用模拟IIC来驱动IIC接口的芯片,模拟IIC的编写主要根据时序图进行编写。
接着上述的CobeMX工程,将配置的IIC引脚改成GPIO_OUTPUT,并将其改名为IIC_SDA,IIC_CLK。
根据EEPROM手册
故要完成模拟IIC要具备几个条件
开始、等待数据传输、读操作、写操作、等待响应、结束传输。
我们首先建立两个文件myiic.c和myiic.h
GPIO写时序无非是设置其高低点评和读状态,首先针对这两个引脚进行定义
#define IIC_CLK_SET() HAL_GPIO_WritePin(GPIOB,IIC_SCL_Pin,GPIO_PIN_SET)
#define IIC_CLK_RESET() HAL_GPIO_WritePin(GPIOB,IIC_SCL_Pin,GPIO_PIN_RESET)
#define IIC_SDA_SET() HAL_GPIO_WritePin(GPIOB,IIC_SDA_Pin,GPIO_PIN_SET)
#define IIC_SDA_RESET() HAL_GPIO_WritePin(GPIOB,IIC_SDA_Pin,GPIO_PIN_RESET)
#define IIC_SDA_READ() HAL_GPIO_ReadPin(GPIOB,IIC_SDA_Pin)
根据上述的时序图我们建立其时序逻辑来仿真上述时序:
SDA有两种状态,输出状态和读状态
//SDA方向设置
//n=1 输出
//n=0 读
static void IIC_SDA_Dir(uint8_t n)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = IIC_SDA_Pin;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
if(n)
{
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
}
else
{
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
}
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//静态函数不能被其它文件所用;其它文件中可以定义相同名字的函数,不会发生冲突。
static void _IIC_Delay()
{
__nop();//运行一个机器周期
}
void IIC_Start()
{
IIC_CLK_SET();
IIC_SDA_SET();
IIC_SDA_RESET();
_IIC_Delay();
IIC_CLK_RESET();
}
void IIC_Stop()
{
IIC_CLK_RESET();
IIC_SDA_RESET();
IIC_CLK_SET();
_IIC_Delay();
IIC_SDA_SET();
}
int IIC_WaitACK()
{
uint8_t timeout = 0;
IIC_SDA_Dir(0);//读状态
IIC_CLK_RESET();
_IIC_Delay();
IIC_CLK_SET();
while(IIC_SDA_READ())//读此时SDA电平为1
{ //如果为高电平
_IIC_Delay();
timeout++;
if(timeout > 200)
{
IIC_SDA_Dir(1); //转换为输出态
IIC_Stop();
return -1;
}
}
IIC_SDA_Dir(1);
IIC_CLK_RESET();
return 0;
}
void IIC_WriteByte(uint8_t byte)
{
uint8_t i = 0;
IIC_CLK_RESET();
for(i = 0; i < 8; i++)
{
IIC_CLK_SET();
if(byte & 0x80)//写第一帧数据
{
IIC_SDA_SET();
}
else
{
IIC_SDA_RESET();
}
byte <<= 1;
_IIC_Delay();
IIC_CLK_RESET();
}
}
uint8_t IIC_ReadByte()
{
uint8_t i = 0, tmp = 0;
IIC_SDA_Dir(0);
IIC_CLK_RESET();
_IIC_Delay();
for(i = 0; i < 8; i++ )
{
IIC_CLK_SET();
tmp <<= 1;
if(IIC_SDA_READ())
{
tmp |= 0x1;//将当前状态存入tmp
}
_IIC_Delay();
IIC_CLK_RESET();
}
return tmp;
}
上述的IIC功能时序问题我们已经根据时序图用代码写出来了。
接下来就是利用上述的IIC各个功能模块拼凑成EEPROM芯片能够读取和写入的指令。同样写入或者读取一组数据要按照芯片手册的要求来编写。根据上述时序图可以写成:
static void AT_WriteByte(uint8_t dev_addr, uint8_t mm_addr,uint8_t b)
{
IIC_Start();
IIC_WriteByte(dev_addr);//写设备地址
if(IIC_WaitACK() < 0)
{
return;
}
IIC_WriteByte(mm_addr);//写内存地址
if(IIC_WaitACK() < 0)
{
return;
}
IIC_WriteByte(b);
if(IIC_WaitACK() < 0)
{
return;
}
IIC_Stop();
}
static uint8_t AT_ReadByte(uint8_t dev_addr, uint8_t mm_addr)
{
int temp = 0;
IIC_Start();
IIC_WriteByte(dev_addr);
if(IIC_WaitACK() < 0)
{
return 0;
}
IIC_WriteByte(mm_addr);
if(IIC_WaitACK() < 0)
{
return 0;
}
IIC_Start();
IIC_WriteByte(dev_addr | 0x1);
if(IIC_WaitACK() < 0)
{
return 0;
}
temp = IIC_ReadByte();
IIC_Stop();
return temp;
}
最后为了兼并上述HAL库的硬件IIC程序,采用条件编译的方式兼顾两种程序
int AT_Write(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len)
{
#if _HW_IIC
if(pbuf == NULL)
{
return -1;
}
HAL_I2C_Mem_Write(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff);
#else
uint8_t i=0;
for(i = 0; i < 8; i++)
{
AT_WriteByte(dev_addr, mm_addr + i, pbuf[i]);
HAL_Delay(10);
}
return 0;
#endif
}
int AT_Read(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len)
{
#if _HW_IIC
if(pbuf == NULL)
{
return -1;
}
HAL_I2C_Mem_Read(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff);
#else
uint8_t i=0;
for(i = 0; i < 8; i++)
{
pbuf[i] = AT_ReadByte(dev_addr, mm_addr + i);
HAL_Delay(10);
}
return 0;
#endif
}
如果上述分析不易理解,我还将上述程序进行了图片化分析便于迅速理解:
本期IIC的介绍就到这里了,感谢大家的收看。
往期回顾:基于HAL库的SPI读FLASH