【ZYNQ】IIC 简介及 EMIO 模拟 IIC 驱动示例

119 阅读4分钟

IIC 简介

IIC (Inter-Integrated Circuit) 总线是一种由 NXP (原PHILIPS) 公司开发的两线式串行通信总线,多用于主控制器和从设备之间的主从式通信,且任意时刻只能有一个主机。

IIC总线由数据线 SDA 和时钟线 SCL 构成,可实现在 CPU 与被控 IC 之间、IC 与 IC 之间双向通信,高速 IIC 总线一般可达 400kbps 以上。

为驱动实验室自研基于 DAC60502 的双通道电流输出模块,基于 ZYNQ 平台,将 PS 端的 GPIO 资源通过 EMIO 引出到 PL 端,使用 PL 端引脚资源连接 IIC 从设备,既使用 EMIO 模拟 IIC 实现。

模拟 IIC 示例代码

  • iic_io.c
/**
 * Copyright (c) 2022-2023,HelloAlpha
 * 
 * Change Logs:
 * Date           Author       Notes
 */
#include "iic_io.h"

extern XGpioPs GpioPs;

void SCL_OUT(void)
{
    XGpioPs_SetDirectionPin(&GpioPs, SCL_PIN, GPIO_MODEL_OUTPUT);
    XGpioPs_SetOutputEnablePin(&GpioPs, SCL_PIN, GPIO_OUTPUT_ENABLE);
}

void SCL_HIGH(void)
{
    XGpioPs_WritePin(&GpioPs, SCL_PIN, GPIO_SET);
}

void SCL_LOW(void)
{
    XGpioPs_WritePin(&GpioPs, SCL_PIN, GPIO_RESET);
}

void SDA_IN(void)
{
    XGpioPs_SetDirectionPin(&GpioPs, SDA_PIN, GPIO_MODEL_INPUT);
}

uint32_t SDA_READ(void)
{
    return XGpioPs_ReadPin(&GpioPs, SDA_PIN);
}

void SDA_OUT(void)
{
    XGpioPs_SetDirectionPin(&GpioPs, SDA_PIN, GPIO_MODEL_OUTPUT);
    XGpioPs_SetOutputEnablePin(&GpioPs, SDA_PIN, GPIO_OUTPUT_ENABLE);
}

void SDA_HIGH(void)
{
    XGpioPs_WritePin(&GpioPs, SDA_PIN, GPIO_SET);
}

void SDA_LOW(void)
{
    XGpioPs_WritePin(&GpioPs, SDA_PIN, GPIO_RESET);
}

int IIC_Init(void)
{
    SCL_OUT();
    SCL_HIGH();
    SDA_OUT();
    SDA_HIGH();

    return 0;
}
  • iic_io.h
/**
 * Copyright (c) 2022-2023,HelloAlpha
 *
 * Change Logs:
 * Date           Author       Notes
 */
#ifndef __IIC_IO_H__
#define __IIC_IO_H__

#include "xgpiops.h"

#define SCL_PIN           54
#define SDA_PIN           55

#define GPIO_RESET      	  0
#define GPIO_SET        	  1

#define GPIO_MODEL_INPUT    0
#define GPIO_MODEL_OUTPUT   1

#define GPIO_OUTPUT_DISABLE  0
#define GPIO_OUTPUT_ENABLE   1

void SCL_OUT(void);
void SCL_HIGH(void);
void SCL_LOW(void);

void SDA_IN(void);
uint32_t SDA_READ(void);

void SDA_OUT(void);
void SDA_HIGH(void);
void SDA_LOW(void);

int IIC_Init(void);

#endif
  • iic_ctrl.c
/**
 * Copyright (c) 2022-2023,HelloAlpha
 *
 * Change Logs:
 * Date           Author       Notes
 */
#include "iic_ctrl.h"
#include "sleep.h"

#define I2C_DELAY(...)	usleep(__VA_ARGS__)

void IIC_Start(void)
{
    SDA_OUT();
    SDA_HIGH();
    SCL_HIGH();
    I2C_DELAY(4);
    SDA_LOW();
    I2C_DELAY(4);
    SCL_LOW();
    I2C_DELAY(2);
}
 
void IIC_Stop(void)
{
    SDA_OUT();
    SCL_LOW();
    SDA_LOW();
    I2C_DELAY(4);
    SCL_HIGH();
    SDA_HIGH();
    I2C_DELAY(4);
}
 
// 1,接收应答失败
// 0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
    uint8_t ucErrTime=0;

    SDA_OUT();
    SDA_HIGH();
    I2C_DELAY(1);
    SCL_HIGH();
    I2C_DELAY(1);
    SDA_IN();
    while(SDA_READ())
    {
        ucErrTime++;
        if(ucErrTime > 250)
        {
            IIC_Stop();
            return 1;
        }
    }
    SCL_LOW();
    return 0;  
}

//产生ACK应答
void IIC_Ack(void)
{
    SCL_LOW();
    SDA_OUT();
    SDA_LOW();
    I2C_DELAY(2);
    SCL_HIGH();
    I2C_DELAY(2);
    SCL_LOW();
}
//不产生ACK应答
void IIC_NAck(void)
{
    SCL_LOW();
    SDA_OUT();
    SDA_HIGH();
    I2C_DELAY(2);
    SCL_HIGH();
    I2C_DELAY(2);
    SCL_LOW();
}				

void IIC_Write_Byte(uint8_t data)
{
    uint8_t  i = 0;
 
    SDA_OUT();
    SCL_LOW();
    for(i=0; i<8; i++)
    {
        if(data & 0x80 )
            SDA_HIGH();
        else
            SDA_LOW();
        data <<= 1;
        I2C_DELAY(2);
        SCL_HIGH();
        I2C_DELAY(2);
        SCL_LOW();
        I2C_DELAY(2);
    }
}

// ack = 1,发送 ACK
// ack = 0,发送 nACK
uint8_t IIC_Read_Byte(uint8_t ack)
{
    uint8_t i = 0, data = 0;
 
    SDA_IN();
    for(i = 0; i < 8; i++)
    {
        SCL_LOW();
        I2C_DELAY(2);
         SCL_HIGH();
        data <<= 1;
        if(SDA_READ())
            data++;
        I2C_DELAY(1);
    }
    if(!ack)
        IIC_NAck();
    else
        IIC_Ack();
    return data;
}
 
void IIC_Write_UINT16(uint8_t dev_addr, uint8_t wr_addr, uint16_t data)
{
    IIC_Start();
    IIC_Write_Byte(dev_addr);
    IIC_Wait_Ack(); 
    IIC_Write_Byte(wr_addr);
    IIC_Wait_Ack(); 
    IIC_Write_Byte(data >> 8);
    IIC_Wait_Ack(); 
    IIC_Write_Byte(data & 0xFF);
    IIC_Wait_Ack();  	
    IIC_Stop();
    I2C_DELAY(800);
}

uint16_t IIC_Read_UINT16(uint8_t dev_addr, uint8_t rd_addr)
{
    uint16_t data;

    IIC_Start();
    IIC_Write_Byte(dev_addr);
    IIC_Wait_Ack();
    IIC_Write_Byte(rd_addr);
    IIC_Wait_Ack(); 

    IIC_Start();  	
    IIC_Write_Byte(dev_addr + 1);
    IIC_Wait_Ack();
    data = IIC_Read_Byte(1);
    data = (data <<8 ) + IIC_Read_Byte(0);
    IIC_Stop();

    return data;
}
  • iic_ctrl.h
/**
 * Copyright (c) 2022-2023,HelloAlpha
 *
 * Change Logs:
 * Date           Author       Notes
 */
#ifndef __I2C_CTRL_H__
#define __I2C_CTRL_H__

#include "iic_io.h"

void IIC_Start(void);
void IIC_Stop(void);
uint8_t IIC_Wait_Ack(void);
void IIC_Ack(void);	    
void IIC_NAck(void);
void IIC_Write_Byte(uint8_t value );
uint8_t IIC_Read_Byte(uint8_t addr);
void IIC_Write_UINT16(uint8_t dev_addr, uint8_t wr_addr, uint16_t data);
uint16_t IIC_Read_UINT16(uint8_t dev_addr, uint8_t rd_addr);

#endif

用软件模拟 IIC,最大的好处就是方便移植,同一个代码兼容所有 MCU,任何一个微控制器 只要有 IO 口,就可以很快的移植过去,而且不需要特定的 IO 口。但是相对于硬件 IIC 来说,模拟 IIC 通信速率要慢得多。

  • 自问:那为啥不用硬件 IIC
  • 自答:就想用模拟的嘿嘿嘿