【C应用】基于HAL库的硬件IIC和基于时序的模拟IIC

基于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.cmyiic.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

  • 14
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
IIC指的是Inter-Integrated Circuit的缩写,是一种用于集成电路之间通信的串行通信接口协议。而HAL硬件抽象层的缩写,是一种软件接口,用于向上层软件屏蔽底层硬件的细节,使开发人员可以更方便地编写应用程序。 24CXX是一款序列EEPROM芯片系列,其中的XX代表了具体的容量,比如24C02表示容量为2K,24C16表示容量为16K。 所以,将IIC模拟HAL 24CXX的意思是通过IIC协议来模拟实现对24CXX系列芯片的读写操作。在实际的应用中,可以使用IIC接口去连接24CXX芯片,并通过编写相应的软件代码来实现对其读写的功能。 首先,需要初始化IIC通信的参数,包括IIC的控制线和时钟频率。然后,通过IIC的起始信号发送设备地址和读写操作类型,以确定要访问的特定芯片。 接下来,根据具体的读写操作,可以通过IIC发送相应的命令和数据,通过SCL时钟线和SDA数据线与24CXX芯片进行通信。对于读操作,可以从24CXX芯片读取数据,并在需要时进行处理。对于写操作,可以将数据写入24CXX芯片的相应位置。 通过IIC模拟HAL 24CXX,可以实现对24CXX芯片的读写操作,包括读取存储的数据、写入新的数据或修改已有数据等功能。这样的实现可以帮助开发人员更好地使用24CXX系列芯片的存储能力,并在各种应用场景中发挥作用,如数据日志记录、数据存储、设备状态保存等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米杰的声音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值