RTT IIC驱动分析和IIC应用开发
  Rz9UHli2sw5h 2023年11月12日 23 0

IIC

应用层使用-摘录值RTT官方

访问 I2C 总线设备

一般情况下 MCU 的 I2C 器件都是作为主机和从机通讯,在 RT-Thread 中将 I2C 主机虚拟为 I2C总线设备,I2C 从机通过 I2C 设备接口和 I2C 总线通讯,相关接口如下所示:

函数

描述

rt_device_find()

根据 I2C 总线设备名称查找设备获取设备句柄

rt_i2c_transfer()

传输数据

查找 I2C 总线设备

在使用 I2C 总线设备前需要根据 I2C 总线设备名称获取设备句柄,进而才可以操作 I2C 总线设备,查找设备函数如下所示,

rt_device_t rt_device_find(const char* name);

参数

描述

name

I2C 总线设备名称

返回

——

设备句柄

查找到对应设备将返回相应的设备句柄

RT_NULL

没有找到相应的设备对象

一般情况下,注册到系统的 I2C 设备名称为 i2c0 ,i2c1等,使用示例如下所示:

#define AHT10_I2C_BUS_NAME      "i2c1"  /* 传感器连接的I2C总线设备名称 */
struct rt_i2c_bus_device *i2c_bus;      /* I2C总线设备句柄 */

/* 查找I2C总线设备,获取I2C总线设备句柄 */
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);

数据传输

获取到 I2C 总线设备句柄就可以使用 rt_i2c_transfer() 进行数据传输。函数原型如下所示:

rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
                          struct rt_i2c_msg         msgs[],
                          rt_uint32_t               num);

参数

描述

bus

I2C 总线设备句柄

msgs[]

待传输的消息数组指针

num

消息数组的元素个数

返回

——

消息数组的元素个数

成功

错误码

失败

和 SPI 总线的自定义传输接口一样,I2C 总线的自定义传输接口传输的数据也是以一个消息为单位。参数 msgs[] 指向待传输的消息数组,用户可以自定义每条消息的内容,实现 I2C 总线所支持的 2 种不同的数据传输模式。如果主设备需要发送重复开始条件,则需要发送 2 个消息。

[!NOTE] 注:此函数会调用 rt_mutex_take(), 不能在中断服务程序里面调用,会导致 assertion 报错。

I2C 消息数据结构原型如下:

struct rt_i2c_msg
{
    rt_uint16_t addr;    /* 从机地址 */
    rt_uint16_t flags;   /* 读、写标志等 */
    rt_uint16_t len;     /* 读写数据字节数 */
    rt_uint8_t  *buf;    /* 读写数据缓冲区指针 */
}

从机地址 addr:支持 7 位和 10 位二进制地址,需查看不同设备的数据手册 。

[!NOTE] 注:RT-Thread I2C 设备接口使用的从机地址均不包含读写位,读写位控制需修改标志 flags。

标志 flags 可取值为以下宏定义,根据需要可以与其他宏使用位运算 “|” 组合起来使用。

#define RT_I2C_WR              0x0000        /* 写标志,不可以和读标志进行“|”操作 */
#define RT_I2C_RD              (1u << 0)     /* 读标志,不可以和写标志进行“|”操作 */
#define RT_I2C_ADDR_10BIT      (1u << 2)     /* 10 位地址模式 */
#define RT_I2C_NO_START        (1u << 4)     /* 无开始条件 */
#define RT_I2C_IGNORE_NACK     (1u << 5)     /* 忽视 NACK */
#define RT_I2C_NO_READ_ACK     (1u << 6)     /* 读的时候不发送 ACK */
#define RT_I2C_NO_STOP         (1u << 7)     /* 不发送结束位 */

使用示例如下所示:

#define AHT10_I2C_BUS_NAME      "i2c1"  /* 传感器连接的I2C总线设备名称 */
#define AHT10_ADDR               0x38   /* 从机地址 */
struct rt_i2c_bus_device *i2c_bus;      /* I2C总线设备句柄 */

/* 查找I2C总线设备,获取I2C总线设备句柄 */
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);

/* 读传感器寄存器数据 */
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
    struct rt_i2c_msg msgs;

    msgs.addr = AHT10_ADDR;     /* 从机地址 */
    msgs.flags = RT_I2C_RD;     /* 读标志 */
    msgs.buf = buf;             /* 读写数据缓冲区指针 */
    msgs.len = len;             /* 读写数据字节数 */

    /* 调用I2C设备接口传输数据 */
    if (rt_i2c_transfer(bus, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}

I2C 从设备数据读写API

以下两个读写函数封装自 rt_i2c_transfer() 函数,用于读写I2C从设备的数据,更加简单易用,推荐使用。

向 I2C 从设备发送数据:

rt_size_t rt_i2c_master_send(struct rt_i2c_bus_device *bus,
                             rt_uint16_t               addr,
                             rt_uint16_t               flags,
                             const rt_uint8_t         *buf,
                             rt_uint32_t               count);

参数

描述

bus

I2C 总线设备句柄

addr

I2C 从设备地址

flags

标志位,可为上文提到的除 RT_I2C_WR RT_I2C_RD之外的其他标志位,可以进行 “\

buf

待数据数据缓冲区

count

待发送数据大小(单位:字节)

返回

——

消息数组的元素个数

成功

错误码

失败

从 I2C 从设备读取数据,数据会放在缓冲区中:

rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus,
                             rt_uint16_t               addr,
                             rt_uint16_t               flags,
                             rt_uint8_t               *buf,
                             rt_uint32_t               count);

参数

描述

bus

I2C 总线设备句柄

addr

I2C 从设备地址

flags

标志位,可为上文提到的除 RT_I2C_WR RT_I2C_RD之外的其他标志位,可以进行 “\

buf

数据缓冲区

count

缓冲区大小(单位:字节,要大于等于最大接收到的数据长度)

返回

——

消息数组的元素个数

成功

错误码

失败

小技巧

有时,I2C数据需要通过多次函数拼接而成,通过如下方法,可以实现拼接发送一条I2C数据,数据内容为 prefix_buffer + buffer:

rt_i2c_master_send(_i2c_bus_dev, _addr, RT_I2C_NO_STOP, prefix_buffer, prefix_len); /* 只发送起始位,不发送停止位 */
rt_i2c_master_send(_i2c_bus_dev, _addr, RT_I2C_NO_START, buffer, len); /* 不发送起始位,只发送停止位 */

I2C 总线设备使用示例

I2C 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:

  1. 首先根据 I2C 设备名称查找 I2C 名称,获取设备句柄,然后初始化 aht10 传感器。
  2. 控制传感器的两个函数为写传感器寄存器 write_reg() 和读传感器寄存器 read_regs(),这两个函数分别调用了 rt_i2c_transfer() 传输数据。读取温湿度信息的函数 read_temp_humi() 则是调用这两个函数完成功能。
/*
 * 程序清单:这是一个 I2C 设备使用例程
 * 例程导出了 i2c_aht10_sample 命令到控制终端
 * 命令调用格式:i2c_aht10_sample i2c1
 * 命令解释:命令第二个参数是要使用的I2C总线设备名称,为空则使用默认的I2C总线设备
 * 程序功能:通过 I2C 设备读取温湿度传感器 aht10 的温湿度数据并打印
*/

#include <rtthread.h>
#include <rtdevice.h>

#define AHT10_I2C_BUS_NAME          "i2c1"  /* 传感器连接的I2C总线设备名称 */
#define AHT10_ADDR                  0x38    /* 从机地址 */
#define AHT10_CALIBRATION_CMD       0xE1    /* 校准命令 */
#define AHT10_NORMAL_CMD            0xA8    /* 一般命令 */
#define AHT10_GET_DATA              0xAC    /* 获取数据命令 */

static struct rt_i2c_bus_device *i2c_bus = RT_NULL;     /* I2C总线设备句柄 */
static rt_bool_t initialized = RT_FALSE;                /* 传感器初始化状态 */

/* 写传感器寄存器 */
static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t *data)
{
    rt_uint8_t buf[3];
    struct rt_i2c_msg msgs;
    rt_uint32_t buf_size = 1;

    buf[0] = reg; //cmd
    if (data != RT_NULL)
    {
        buf[1] = data[0];
        buf[2] = data[1];
        buf_size = 3;
    }

    msgs.addr = AHT10_ADDR;
    msgs.flags = RT_I2C_WR;
    msgs.buf = buf;
    msgs.len = buf_size;

    /* 调用I2C设备接口传输数据 */
    if (rt_i2c_transfer(bus, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}

/* 读传感器寄存器数据 */
static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
    struct rt_i2c_msg msgs;

    msgs.addr = AHT10_ADDR;
    msgs.flags = RT_I2C_RD;
    msgs.buf = buf;
    msgs.len = len;

    /* 调用I2C设备接口传输数据 */
    if (rt_i2c_transfer(bus, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}

static void read_temp_humi(float *cur_temp, float *cur_humi)
{
    rt_uint8_t temp[6];

    write_reg(i2c_bus, AHT10_GET_DATA, RT_NULL);      /* 发送命令 */
    rt_thread_mdelay(400);
    read_regs(i2c_bus, 6, temp);                /* 获取传感器数据 */

    /* 湿度数据转换 */
    *cur_humi = (temp[1] << 12 | temp[2] << 4 | (temp[3] & 0xf0) >> 4) * 100.0 / (1 << 20);
    /* 温度数据转换 */
    *cur_temp = ((temp[3] & 0xf) << 16 | temp[4] << 8 | temp[5]) * 200.0 / (1 << 20) - 50;
}

static void aht10_init(const char *name)
{
    rt_uint8_t temp[2] = {0, 0};

    /* 查找I2C总线设备,获取I2C总线设备句柄 */
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name);

    if (i2c_bus == RT_NULL)
    {
        rt_kprintf("can't find %s device!\n", name);
    }
    else
    {
        write_reg(i2c_bus, AHT10_NORMAL_CMD, temp);
        rt_thread_mdelay(400);

        temp[0] = 0x08;
        temp[1] = 0x00;
        write_reg(i2c_bus, AHT10_CALIBRATION_CMD, temp);
        rt_thread_mdelay(400);
        initialized = RT_TRUE;
    }
}

static void i2c_aht10_sample(int argc, char *argv[])
{
    float humidity, temperature;
    char name[RT_NAME_MAX];

    humidity = 0.0;
    temperature = 0.0;

    if (argc == 2)
    {
        rt_strncpy(name, argv[1], RT_NAME_MAX);
    }
    else
    {
        rt_strncpy(name, AHT10_I2C_BUS_NAME, RT_NAME_MAX);
    }

    if (!initialized)
    {
        /* 传感器初始化 */
        aht10_init(name);
    }
    if (initialized)
    {
        /* 读取温湿度数据 */
        read_temp_humi(&temperature, &humidity);

        rt_kprintf("read aht10 sensor humidity   : %d.%d %%\n", (int)humidity, (int)(humidity * 10) % 10);
        if( temperature >= 0 )
        {
            rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(temperature * 10) % 10);
        }
        else
        {
            rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(-temperature * 10) % 10);
        }
    }
    else
    {
        rt_kprintf("initialize sensor failed!\n");
    }
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(i2c_aht10_sample, i2c aht10 sample);

驱动分析

IIC总线设备继承自io设备驱动框架,RTT对IIC就只有2层的封装

IIC设备总线,在RTT内部有软件IIC和硬件IIC

设备驱动注册

rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,
                                    const char               *bus_name)

iic bus 抽象模型

struct rt_i2c_bus_device
{
    struct rt_device parent;//继承自设备驱动
    const struct rt_i2c_bus_device_ops *ops;//iic bus 操作结构体
    rt_uint16_t  flags;
    rt_uint16_t  addr;
    struct rt_mutex lock;//bus 涉及iic设备独占的问题
    rt_uint32_t  timeout;
    rt_uint32_t  retries;
    void *priv;
};

iic bus 操作函数集

struct rt_i2c_bus_device_ops
{
    rt_size_t (*master_xfer)(struct rt_i2c_bus_device *bus,
                             struct rt_i2c_msg msgs[],
                             rt_uint32_t num);
    rt_size_t (*slave_xfer)(struct rt_i2c_bus_device *bus,
                            struct rt_i2c_msg msgs[],
                            rt_uint32_t num);
    rt_err_t (*i2c_bus_control)(struct rt_i2c_bus_device *bus,
                                rt_uint32_t,
                                rt_uint32_t);
};

接收函数

RTT 基于N32的硬件IIC实现的


static int rt_i2c_read(rt_uint32_t i2c_periph, rt_uint16_t slave_address, rt_uint8_t* p_buffer, rt_uint16_t data_byte)
{
    I2CTimeout = I2CT_LONG_TIMEOUT;
    /* wait until I2C bus is idle 等待总线空闲*/
    while(I2C_GetFlag((I2C_Module*)i2c_periph, I2C_FLAG_BUSY))
    {
        if ((I2CTimeout--) == 0)
            return 9;
    };
    //使能自动ack
    I2C_ConfigAck((I2C_Module*)i2c_periph, ENABLE);
    
    /** Send START condition 发送开始信号*/ 
    I2C_GenerateStart((I2C_Module*)i2c_periph, ENABLE);

    I2CTimeout = I2CT_LONG_TIMEOUT;
    /* wait until SBSEND bit is set 等待开始信号发送完成*/
    while (!I2C_CheckEvent((I2C_Module*)i2c_periph, I2C_EVT_MASTER_MODE_FLAG)) // EV5
    {
        if ((I2CTimeout--) == 0)
            return 10;
    };

    /* send slave address to I2C bus 发送地址和操作类型*/
    I2C_SendAddr7bit((I2C_Module*)i2c_periph, slave_address, I2C_DIRECTION_RECV);
    
    I2CTimeout = I2CT_LONG_TIMEOUT;
    //等待地址和操作类型发送完成
    while (!I2C_CheckEvent((I2C_Module*)i2c_periph, I2C_EVT_MASTER_RXMODE_FLAG)) // EV6
    {
        if ((I2CTimeout--) == 0)
            return 6;
    };

    /* while there is data to be read 持续读取多个数据*/
    while(data_byte)
    {
        /* wait until the RBNE bit is set and clear it等待接收缓冲准备就绪或非空 */
        if(I2C_GetFlag((I2C_Module*)i2c_periph, I2C_FLAG_RXDATNE))
        {
            /* read a byte 读取一个数据*/
            *p_buffer = I2C_RecvData((I2C_Module*)i2c_periph);

            /* point to the next location where the byte read will be saved */
            p_buffer++; 

            /* decrement the read bytes counter */
            data_byte--;
            if(1 == data_byte)
            {
                /* disable acknowledge 最后一个数据不在自动产生ack*/
                I2C_ConfigAck((I2C_Module*)i2c_periph, DISABLE);
                /* send a stop condition to I2C bus 最后一个数据接收后自动产生停止信号*/
                I2C_GenerateStop((I2C_Module*)i2c_periph, ENABLE);
            }
        }
    }

    /* wait until the stop condition is finished等待停止信号发送完成 */
    while(I2C_GetFlag((I2C_Module*)i2c_periph, I2C_FLAG_STOPF))
    {
        if ((I2CTimeout--) == 0)
            return 7;
    };

    /* enable acknowledge 使能自动ack*/
    I2C_ConfigAck((I2C_Module*)i2c_periph, ENABLE);

    I2C_ConfigNackLocation((I2C_Module*)i2c_periph,I2C_NACK_POS_CURRENT);

    return 0;
}

发送函数

static int rt_i2c_write(rt_uint32_t i2c_periph, uint16_t slave_address, uint8_t* p_buffer, uint16_t data_byte)
{
    uint8_t* sendBufferPtr = p_buffer;
    I2CTimeout             = I2CT_LONG_TIMEOUT;
    //等待iic设备空闲
    while (I2C_GetFlag((I2C_Module*)i2c_periph, I2C_FLAG_BUSY))
    {
        if ((I2CTimeout--) == 0)
            return 4;
    };
    //自动ack
    I2C_ConfigAck((I2C_Module*)i2c_periph, ENABLE);
    //发送开始信号
    I2C_GenerateStart((I2C_Module*)i2c_periph, ENABLE);
    //等待开始信号发送完成
    I2CTimeout = I2CT_LONG_TIMEOUT;
    while (!I2C_CheckEvent((I2C_Module*)i2c_periph, I2C_EVT_MASTER_MODE_FLAG)) // EV5
    {
        if ((I2CTimeout--) == 0)
            return 5;
    };
    //发送地址和发送指令
    I2C_SendAddr7bit((I2C_Module*)i2c_periph, slave_address, I2C_DIRECTION_SEND);
    I2CTimeout = I2CT_LONG_TIMEOUT;
    //等待指令和地址发送完成
    while (!I2C_CheckEvent((I2C_Module*)i2c_periph, I2C_EVT_MASTER_TXMODE_FLAG)) // EV6
    {
        if ((I2CTimeout--) == 0)
            return 6;
    };

    // send data持续发送多个数据
    while (data_byte-- > 0)
    {
        //发送一个数据
        I2C_SendData((I2C_Module*)i2c_periph, *sendBufferPtr++);
        I2CTimeout = I2CT_LONG_TIMEOUT;
        //等待数据发送完成
        while (!I2C_CheckEvent((I2C_Module*)i2c_periph, I2C_EVT_MASTER_DATA_SENDING)) // EV8
        {
            if ((I2CTimeout--) == 0)
                return 7;
        };
    };
    //等待数据发送完成
    I2CTimeout = I2CT_LONG_TIMEOUT;
    while (!I2C_CheckEvent((I2C_Module*)i2c_periph, I2C_EVT_MASTER_DATA_SENDED)) // EV8-2
    {
        if ((I2CTimeout--) == 0)
            return 8;
    };
    //产生一个停止信号
    I2C_GenerateStop((I2C_Module*)i2c_periph, ENABLE);
    return 0;
}

iic总线设备驱动分析

iic 和硬件设备先关的驱动

RTT IIC驱动分析和IIC应用开发_句柄

RTT封装的IIC驱动框架调用流程

RTT IIC驱动分析和IIC应用开发_数据_02

软件模式iic设备总线引脚更改

在文件drv_ic.c中更改

RTT IIC驱动分析和IIC应用开发_#define_03

软件iic注册

RTT IIC驱动分析和IIC应用开发_#define_04

硬件iic总线设备引脚修改

RTT IIC驱动分析和IIC应用开发_句柄_05

iic总线的使用

  1. 定义eeprom读函数-驱动内部会每个数据包会重新 发起开始和停止信号

RTT IIC驱动分析和IIC应用开发_句柄_06


/* SPI Dev device interface, compatible with RT-Thread 0.3.x/1.0.x */
static rt_size_t rt_eeprom_read(rt_device_t dev,
                                     rt_off_t    pos,
                                     void       *buffer,
                                     rt_size_t   size)
{    
    static struct rt_i2c_bus_device *i2c_bus;
    
    int ret = 0;
    int retries = 0;
	
#if defined(RT_USING_I2C1)
    i2c_bus = rt_i2c_bus_device_find("i2c1");
#endif

#if defined(RT_USING_I2C2)
    i2c_bus = rt_i2c_bus_device_find("i2c2");
#endif
	
    if((dev->flag & RT_DEVICE_FLAG_RDONLY) == RT_DEVICE_FLAG_RDONLY)
    {
        struct rt_i2c_msg msgs[] =
        {
            {
                .addr   = EEPROM_ADDRESS,
                .flags  = RT_I2C_WR,
                .len    = 1,
                .buf    = (rt_uint8_t*)&pos,
            },
            {
                .addr   = EEPROM_ADDRESS,
                .flags  = RT_I2C_RD,
                .len    = size,
                .buf    = buffer,
            },
        };

        while (retries < IIC_RETRY_NUM)
        {
            ret = rt_i2c_transfer(i2c_bus, msgs, 2);
            if (ret == 2)break;
            retries++;
        }

        if (retries >= IIC_RETRY_NUM)
        {
            rt_kprintf("%s i2c read error: %d", __func__, ret);
            return 0;
        }

        return ret;
    }
    else
    {
        return 0;
    }
}
  1. 定义eeprom写函数
static rt_size_t rt_eeprom_write(rt_device_t dev,
                                      rt_off_t    pos,
                                      const void *buffer,
                                      rt_size_t   size)
{
    static struct rt_i2c_bus_device *i2c_bus;
#if defined(RT_USING_I2C1)
    i2c_bus = rt_i2c_bus_device_find("i2c1");
#endif

#if defined(RT_USING_I2C2)
    i2c_bus = rt_i2c_bus_device_find("i2c2");
#endif
	
    if((dev->flag & RT_DEVICE_FLAG_RDONLY) == RT_DEVICE_FLAG_RDONLY)
    {
        I2C_EE_WriteBuffer(i2c_bus, (uint8_t*)buffer, pos, size);   
        return size;
    }
    else
    {
        return 0;
    }
}
  1. 注册一个eeprom

RTT IIC驱动分析和IIC应用开发_#define_07

eeprom 使用IIC示例

AT24C02

  1. 8个字节每页,累计32个页
  2. 通讯频率MAX = 400K
  3. AT24C02大小 2K

RTT IIC驱动分析和IIC应用开发_句柄_08

  1. 芯片地址

RTT IIC驱动分析和IIC应用开发_数据_09

  1. 写时序

RTT IIC驱动分析和IIC应用开发_句柄_10

  1. 读时序

RTT IIC驱动分析和IIC应用开发_数据_11

RTT IIC驱动分析和IIC应用开发_数据_12

.h

/*****************************************************************************
 * Copyright (c) 2022, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/

/**
 * @file i2c_eeprom.h
 * @author Nations
 * @version V1.2.1
 *
 * @copyright Copyright (c) 2022, Nations Technologies Inc. All rights reserved.
 */
#ifndef __I2C_EEPROM_H__
#define __I2C_EEPROM_H__

#include <rtthread.h>
#include "n32l40x.h"
#include <stdio.h>
#include "i2c.h"

typedef enum i2c_state
{
    COMM_DONE       = 0, /// done successfully
    COMM_PRE        = 1,
    COMM_IN_PROCESS = 2,
    COMM_EXIT       = 3 /// exit since failure
} I2C_STATE;

typedef enum i2c_direction
{
    Transmitter = 0x00,
    Receiver    = 0x01
} I2C_DIRECTION;

/**
 * PROCESS MODE
 * 0=polling
 * 1=interrupt
 * 2=DMA
 */
#define PROCESS_MODE 0

#define TEST_EEPROM_SIZE 256
#define TEST_EEPROM_ADDR 0x00
#define I2C_Speed        400000
#define EEPROM_ADDRESS   0xA0
#define I2C_PageSize     8 /// eeprom IC type AT24C08
#define sEE_FLAG_TIMEOUT ((uint32_t)0x1000)
#define sEE_LONG_TIMEOUT ((uint32_t)(100 * sEE_FLAG_TIMEOUT))

#define IIC_RETRY_NUM 2

/** Maximum number of trials for sEE_WaitEepromStandbyState() function */
#define sEE_MAX_TRIALS_NUMBER 150
#define FALSE                 0
#define TRUE                  1

void I2C_EE_Init(void);
void I2C_EE_WriteBuffer(struct rt_i2c_bus_device *i2c_bus, u8* pBuffer, u16 WriteAddr, u16 NumByteToWrite);
void I2C_EE_WriteOnePage(struct rt_i2c_bus_device *i2c_bus, u8* pBuffer, u16 WriteAddr, u16 NumByteToWrite);
void I2C_EE_PageWrite(struct rt_i2c_bus_device *bus, u8* pBuffer, u16 WriteAddr, u16 NumByteToWrite);
void I2C_EE_WriteOnePageCompleted(void);

void I2C_EE_WaitOperationIsCompleted(void);
void I2C_EE_WaitEepromStandbyState(struct rt_i2c_bus_device *i2c_bus);

void i2c1_evt_handle(void);
void i2c1_err_handle(void);
void i2c1_send_dma_handle(void);
void i2c1_receive_dma_handle(void);

rt_err_t rt_eeprom_register(rt_uint8_t flag);
#endif /* __I2C_EEPROM_H__ */

.c

/*****************************************************************************
 * Copyright (c) 2022, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/

/**
 * @file i2c_eeprom.c
 * @author Nations
 * @version V1.2.1
 *
 * @copyright Copyright (c) 2022, Nations Technologies Inc. All rights reserved.
 */
/**
 * @file i2c_eeprom.c
 * @author Nations
 * @version V1.2.1
 *
 * @copyright Copyright (c) 2022, Nations Technologies Inc. All rights reserved.
 */
#include <rtthread.h>
#include "n32l40x.h"
#include "n32l40x_dma.h"
#include "i2c_eeprom.h"
#include "string.h"
#include "stdbool.h"
#include "i2c.h"
#include "drv_i2c.h"

/** @addtogroup I2C_EEPROM
 * @{
 */

/* when EEPROM is writing data inside,it won't response the request from the master.check the ack,
if EEPROM response,make clear that EEPROM finished the last data-writing,allow the next operation */
static bool check_begin = FALSE;
static bool OffsetDone  = FALSE;

u32 sEETimeout = sEE_LONG_TIMEOUT;

static u8 MasterDirection = Transmitter;
static u16 SlaveADDR;
static u16 DeviceOffset = 0x0;

u16 I2C_NumByteToWrite   = 0;
u8 I2C_NumByteWritingNow = 0;
u8* I2C_pBuffer          = 0;
u16 I2C_WriteAddr        = 0;

static u8 SendBuf[8]          = {0};
static u16 BufCount           = 0;
static u16 Int_NumByteToWrite = 0;
static u16 Int_NumByteToRead  = 0;
/* global state variable i2c_comm_state */
volatile I2C_STATE i2c_comm_state;

/**
 * @brief  Timeout callback used by the I2C EEPROM driver.
 */
u8 sEE_TIMEOUT_UserCallback(void)
{
    rt_kprintf("error!!!\r\n");
    /* Block communication and all processes */
    while (1)
    {
    }
}

/**
 * @brief  Writes buffer of data to the I2C EEPROM.
 * @param pBuffer pointer to the buffer  containing the data to be
 *                  written to the EEPROM.
 * @param WriteAddr EEPROM's internal address to write to.
 * @param NumByteToWrite number of bytes to write to the EEPROM.
 */
void I2C_EE_WriteBuffer(struct rt_i2c_bus_device *i2c_bus, u8* pBuffer, u16 WriteAddr, u16 NumByteToWrite)
{
    struct rt_i2c_bus *rt_i2c = (struct rt_i2c_bus *)i2c_bus;
    
    if (I2C_GetFlag((I2C_Module*)(rt_i2c->i2c_periph), I2C_FLAG_BUSY))
    {
        return;
    }
    I2C_pBuffer        = pBuffer;
    I2C_WriteAddr      = WriteAddr;
    I2C_NumByteToWrite = NumByteToWrite;
    
    while (I2C_NumByteToWrite > 0)
    {
        I2C_EE_WriteOnePage(i2c_bus, I2C_pBuffer, I2C_WriteAddr, I2C_NumByteToWrite);
        I2C_EE_WaitEepromStandbyState(i2c_bus);
        I2C_EE_WriteOnePageCompleted();
    }
}

/**
 * @brief  Writes a page of data to the I2C EEPROM, general called by
 *         I2C_EE_WriteBuffer.
 * @param pBuffer pointer to the buffer  containing the data to be
 *                  written to the EEPROM.
 * @param WriteAddr EEPROM's internal address to write to.
 * @param NumByteToWrite number of bytes to write to the EEPROM.
 */
void I2C_EE_WriteOnePage(struct rt_i2c_bus_device *i2c_bus, u8* pBuffer, u16 WriteAddr, u16 NumByteToWrite)
{
    u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

    Addr        = WriteAddr % I2C_PageSize;
    count       = I2C_PageSize - Addr;
    NumOfPage   = NumByteToWrite / I2C_PageSize;
    NumOfSingle = NumByteToWrite % I2C_PageSize;

    I2C_NumByteWritingNow = 0;
    /** If WriteAddr is I2C_PageSize aligned */
    if (Addr == 0)
    {
        /** If NumByteToWrite < I2C_PageSize */
        if (NumOfPage == 0)
        {
            I2C_NumByteWritingNow = NumOfSingle;
            I2C_EE_PageWrite(i2c_bus, pBuffer, WriteAddr, NumOfSingle);
        }
        /** If NumByteToWrite > I2C_PageSize */
        else
        {
            I2C_NumByteWritingNow = I2C_PageSize;
            I2C_EE_PageWrite(i2c_bus, pBuffer, WriteAddr, I2C_PageSize);
        }
    }
    /** If WriteAddr is not I2C_PageSize aligned */
    else
    {
        /* If NumByteToWrite < I2C_PageSize */
        if (NumOfPage == 0)
        {
            I2C_NumByteWritingNow = NumOfSingle;
            I2C_EE_PageWrite(i2c_bus, pBuffer, WriteAddr, NumOfSingle);
        }
        /* If NumByteToWrite > I2C_PageSize */
        else
        {
            if (count != 0)
            {
                I2C_NumByteWritingNow = count;
                I2C_EE_PageWrite(i2c_bus, pBuffer, WriteAddr, count);
            }
        }
    }
}

/**
 * @brief  Writes more than one byte to the EEPROM with a single WRITE
 *         cycle. The number of byte can't exceed the EEPROM page size.
 * @param pBuffer pointer to the buffer containing the data to be
 *                  written to the EEPROM.
 * @param WriteAddr EEPROM's internal address to write to (1-16).
 * @param NumByteToWrite number of bytes to write to the EEPROM.
 */
void I2C_EE_PageWrite(struct rt_i2c_bus_device *i2c_bus, u8* pBuffer, u16 WriteAddr, u16 NumByteToWrite)
{
    rt_uint8_t data[9] = {0};
    
    data[0] = (rt_uint8_t)WriteAddr;
    if(NumByteToWrite>8)
    {
        NumByteToWrite = 8;
    }
    rt_memcpy(data+1, pBuffer, NumByteToWrite); 
    struct rt_i2c_msg msgs[] =
    {
        {
            .addr   = EEPROM_ADDRESS,
            .flags  = RT_I2C_WR,
            .len    = NumByteToWrite+1,
            .buf    = data,
        },
    };
    rt_i2c_transfer(i2c_bus, msgs, 1);    
}

/**
 * @brief  Process Write one page completed.
 */
void I2C_EE_WriteOnePageCompleted(void)
{
    I2C_pBuffer += I2C_NumByteWritingNow;
    I2C_WriteAddr += I2C_NumByteWritingNow;
    I2C_NumByteToWrite -= I2C_NumByteWritingNow;
}

/**
 * @brief  wait operation is completed.
 */
void I2C_EE_WaitOperationIsCompleted(void)
{
    sEETimeout = sEE_LONG_TIMEOUT;
    while (i2c_comm_state != COMM_DONE)
    {
        if ((sEETimeout--) == 0)
            sEE_TIMEOUT_UserCallback();
    }
}

/**
 * @brief  I2c1 event interrupt Service Routines.
 */
void i2c1_evt_handle(void)
{
    uint32_t lastevent = I2C_GetLastEvent(I2C1);
    switch (lastevent)
    {
    /** Master Invoke */
    case I2C_EVT_MASTER_MODE_FLAG: /// EV5
        if (!check_begin)
        {
            i2c_comm_state = COMM_IN_PROCESS;
        }
        if (MasterDirection == Receiver)
        {
            if (!OffsetDone)
            {
                I2C_SendAddr7bit(I2C1, SlaveADDR, I2C_DIRECTION_SEND);
            }
            else
            {
                /** Send slave Address for read */
                I2C_SendAddr7bit(I2C1, SlaveADDR, I2C_DIRECTION_RECV);
                OffsetDone = FALSE;
            }
        }
        else
        {
            /** Send slave Address for write */
            I2C_SendAddr7bit(I2C1, SlaveADDR, I2C_DIRECTION_SEND);
        }
        break;
    /** Master Receiver events */
    case I2C_EVT_MASTER_RXMODE_FLAG: /// EV6
        break;
    case I2C_EVT_MASTER_DATA_RECVD_FLAG: /// EV7
        *I2C_pBuffer = I2C_RecvData(I2C1);
        I2C_pBuffer++;
        Int_NumByteToRead--;
        if (Int_NumByteToRead == 1)
        {
            /** Disable Acknowledgement */
            I2C_ConfigAck(I2C1, DISABLE);
            I2C_GenerateStop(I2C1, ENABLE);
        }
        if (Int_NumByteToRead == 0)
        {
            I2C_ConfigInt(I2C1, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, DISABLE);
            i2c_comm_state = COMM_DONE;
        }
        break;
    /** Master Transmitter events */
    case I2C_EVT_MASTER_TXMODE_FLAG: /// EV8 just after EV6
        if (check_begin)
        {
            check_begin = FALSE;
            I2C_ConfigInt(I2C1, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, DISABLE);
            I2C_GenerateStop(I2C1, ENABLE);
            i2c_comm_state = COMM_DONE;
            break;
        }
        I2C_SendData(I2C1, DeviceOffset);
        OffsetDone = TRUE;
        break;
    case I2C_EVT_MASTER_DATA_SENDING: ///  EV8 I2C_EVENT_MASTER_DATA_TRANSMITTING
        if (MasterDirection == Receiver)
        {
            I2C_GenerateStart(I2C1, ENABLE);
        }
        break;
    case I2C_EVT_MASTER_DATA_SENDED: /// EV8-2
        if (MasterDirection == Transmitter)
        {
            if (Int_NumByteToWrite == 0)
            {
                I2C_GenerateStop(I2C1, ENABLE);
                sEETimeout = sEE_LONG_TIMEOUT;
                while (I2C_GetFlag(I2C1, I2C_FLAG_BUSY))
                {
                    if ((sEETimeout--) == 0)
                        sEE_TIMEOUT_UserCallback();
                }
                check_begin = TRUE;
                I2C_GenerateStart(I2C1, ENABLE);
            }
            else
            {
                I2C_SendData(I2C1, SendBuf[BufCount++]);
                Int_NumByteToWrite--;
            }
        }
        break;
    }
}

/**
 * @brief  I2c1 error interrupt Service Routines.
 */
void i2c1_err_handle(void)
{
    if (I2C_GetFlag(I2C1, I2C_FLAG_ACKFAIL))
    {
        if (check_begin) /// EEPROM write busy
        {
            I2C_GenerateStart(I2C1, ENABLE);
        }
        else if (I2C1->STS2 & 0x01) /// real fail
        {
            I2C_GenerateStop(I2C1, ENABLE);
            i2c_comm_state = COMM_EXIT;
        }
        I2C_ClrFlag(I2C1, I2C_FLAG_ACKFAIL);
    }

    if (I2C_GetFlag(I2C1, I2C_FLAG_BUSERR))
    {
        if (I2C1->STS2 & 0x01)
        {
            I2C_GenerateStop(I2C1, ENABLE);
            i2c_comm_state = COMM_EXIT;
        }
        I2C_ClrFlag(I2C1, I2C_FLAG_BUSERR);
    }
}

#ifdef RT_USING_I2C_DMA
/**
 * @brief  I2c1 dma send interrupt Service Routines.
 */
void i2c1_send_dma_handle()
{
    if (DMA_GetFlagStatus(DMA1_FLAG_TC6, DMA1))
    {
        if (I2Cx->STS2 & 0x01) /// master send DMA finish, check process later
        {
            /** DMA1-6 (I2Cx Tx DMA)transfer complete INTSTS */
            I2C_EnableDMA(I2Cx, DISABLE);
            DMA_EnableChannel(DMA1_CH6, DISABLE);
            /** wait until BTF */
            while (!I2C_GetFlag(I2Cx, I2C_FLAG_BYTEF))
                ;
            I2C_GenerateStop(I2Cx, ENABLE);
            /** wait until BUSY clear */
            while (I2C_GetFlag(I2Cx, I2C_FLAG_BUSY))
                ;
            i2c_comm_state = COMM_IN_PROCESS;
        }
        else /// slave send DMA finish
        {
        }
        DMA_ClearFlag(DMA1_FLAG_TC6, DMA1);
    }
    if (DMA_GetFlagStatus(DMA1_FLAG_GL6, DMA1))
    {
        DMA_ClearFlag(DMA1_FLAG_GL6, DMA1);
    }
    if (DMA_GetFlagStatus(DMA1_FLAG_HT6, DMA1))
    {
        DMA_ClearFlag(DMA1_FLAG_HT6, DMA1);
    }
}

/**
 * @brief  I2c1 dma receive interrupt Service Routines.
 */
void i2c1_receive_dma_handle(void)
{
    if (DMA_GetFlagStatus(DMA1_FLAG_TC7, DMA1))
    {
        if (I2Cx->STS2 & 0x01) /// master receive DMA finish
        {
            I2C_EnableDMA(I2Cx, DISABLE);
            I2C_GenerateStop(I2Cx, ENABLE);
            i2c_comm_state = COMM_DONE;
        }
        else /// slave receive DMA finish
        {
        }
        DMA_ClearFlag(DMA1_FLAG_TC7, DMA1);
    }
    if (DMA_GetFlagStatus(DMA1_FLAG_GL7, DMA1))
    {
        DMA_ClearFlag(DMA1_FLAG_GL7, DMA1);
    }
    if (DMA_GetFlagStatus(DMA1_FLAG_HT7, DMA1))
    {
        DMA_ClearFlag(DMA1_FLAG_HT7, DMA1);
    }
}

#endif /* RT_USING_I2C_DMA */

/**
 * @brief  Wait eeprom standby state.
 */
void I2C_EE_WaitEepromStandbyState(struct rt_i2c_bus_device *i2c_bus)
{
    struct rt_i2c_bus *rt_i2c = (struct rt_i2c_bus *)i2c_bus;
    __IO uint16_t tmpSR1    = 0;
    __IO uint32_t sEETrials = 0;
    
    I2C_Module* I2Cx;
    
    I2Cx = (I2C_Module*)(rt_i2c->i2c_periph);    

    /** While the bus is busy */
    sEETimeout = sEE_LONG_TIMEOUT;
    while (I2C_GetFlag(I2C1, I2C_FLAG_BUSY))
    {
        if ((sEETimeout--) == 0)
            sEE_TIMEOUT_UserCallback();
    }

    /** Keep looping till the slave acknowledge his address or maximum number
       of trials is reached (this number is defined by sEE_MAX_TRIALS_NUMBER) */
    while (1)
    {
        /** Send START condition */
        I2C_GenerateStart(I2Cx, ENABLE);

        /** Test on EV5 and clear it */
        sEETimeout = sEE_LONG_TIMEOUT;
        while (!I2C_CheckEvent(I2Cx, I2C_EVT_MASTER_MODE_FLAG))
        {
            if ((sEETimeout--) == 0)
                sEE_TIMEOUT_UserCallback();
        }

        /** Send EEPROM address for write */
        I2C_SendAddr7bit(I2Cx, EEPROM_ADDRESS, I2C_DIRECTION_SEND);
        /** Wait for ADDR flag to be set (Slave acknowledged his address) */
        sEETimeout = sEE_LONG_TIMEOUT;
        do
        {
            /** Get the current value of the STS1 register */
            tmpSR1 = I2Cx->STS1;

            /** Update the timeout value and exit if it reach 0 */
            if ((sEETimeout--) == 0)
                sEE_TIMEOUT_UserCallback();
        }
        /** Keep looping till the Address is acknowledged or the AF flag is
           set (address not acknowledged at time) */
        while ((tmpSR1 & (I2C_STS1_ADDRF | I2C_STS1_ACKFAIL)) == 0);

        /** Check if the ADDR flag has been set */
        if (tmpSR1 & I2C_STS1_ADDRF)
        {
            /** Clear ADDR Flag by reading STS1 then STS2 registers (STS1 have already
               been read) */
            (void)I2Cx->STS2;

            /** STOP condition */
            I2C_GenerateStop(I2Cx, ENABLE);

            /** Exit the function */
            return;
        }
        else
        {
            /** Clear AF flag */
            I2C_ClrFlag(I2Cx, I2C_FLAG_ACKFAIL);
        }

        /** Check if the maximum allowed numbe of trials has bee reached */
        if (sEETrials++ == sEE_MAX_TRIALS_NUMBER)
        {
            /** If the maximum number of trials has been reached, exit the function */
            sEE_TIMEOUT_UserCallback();
        }
    }
}

/**
 * @brief  Configures the different system clocks.
 */
static rt_err_t rt_eeprom_init(rt_device_t dev)
{    
    return RT_EOK;
}

static rt_err_t rt_eeprom_open(rt_device_t dev, rt_uint16_t oflag)
{
    return RT_EOK;
}

static rt_err_t rt_eeprom_close(rt_device_t dev)
{
    return RT_EOK;
}

/* SPI Dev device interface, compatible with RT-Thread 0.3.x/1.0.x */
static rt_size_t rt_eeprom_read(rt_device_t dev,
                                     rt_off_t    pos,
                                     void       *buffer,
                                     rt_size_t   size)
{    
    static struct rt_i2c_bus_device *i2c_bus;
    
    int ret = 0;
    int retries = 0;
    
#if defined(RT_USING_I2C1)
    i2c_bus = rt_i2c_bus_device_find("i2c1");
#endif

#if defined(RT_USING_I2C2)
    i2c_bus = rt_i2c_bus_device_find("i2c2");
#endif
    
    if((dev->flag & RT_DEVICE_FLAG_RDONLY) == RT_DEVICE_FLAG_RDONLY)
    {
        struct rt_i2c_msg msgs[] =
        {
            {
                .addr   = EEPROM_ADDRESS,
                .flags  = RT_I2C_WR,
                .len    = 1,
                .buf    = (rt_uint8_t*)&pos,
            },
            {
                .addr   = EEPROM_ADDRESS,
                .flags  = RT_I2C_RD,
                .len    = size,
                .buf    = buffer,
            },
        };

        while (retries < IIC_RETRY_NUM)
        {
            ret = rt_i2c_transfer(i2c_bus, msgs, 2);
            if (ret == 2)break;
            retries++;
        }

        if (retries >= IIC_RETRY_NUM)
        {
            rt_kprintf("%s i2c read error: %d", __func__, ret);
            return 0;
        }

        return ret;
    }
    else
    {
        return 0;
    }
}

static rt_size_t rt_eeprom_write(rt_device_t dev,
                                      rt_off_t    pos,
                                      const void *buffer,
                                      rt_size_t   size)
{
    static struct rt_i2c_bus_device *i2c_bus;
#if defined(RT_USING_I2C1)
    i2c_bus = rt_i2c_bus_device_find("i2c1");
#endif

#if defined(RT_USING_I2C2)
    i2c_bus = rt_i2c_bus_device_find("i2c2");
#endif
    
    if((dev->flag & RT_DEVICE_FLAG_RDONLY) == RT_DEVICE_FLAG_RDONLY)
    {
        I2C_EE_WriteBuffer(i2c_bus, (uint8_t*)buffer, pos, size);   
        return size;
    }
    else
    {
        return 0;
    }
}



static rt_err_t rt_eeprom_control(rt_device_t dev,
                                       int         cmd,
                                       void       *args)
{
        
    return RT_EOK;
}

rt_err_t rt_eeprom_register(rt_uint8_t flag)
{
    static struct rt_device w25_dev;

    w25_dev.type = RT_Device_Class_Unknown;

    w25_dev.rx_indicate = RT_NULL;
    w25_dev.tx_complete = RT_NULL;

    w25_dev.init    = rt_eeprom_init;
    w25_dev.open    = rt_eeprom_open;
    w25_dev.close   = rt_eeprom_close;
    w25_dev.read    = rt_eeprom_read;
    w25_dev.write   = rt_eeprom_write;
    w25_dev.control = rt_eeprom_control;

    return rt_device_register(&w25_dev, "eeprom", RT_DEVICE_FLAG_RDWR | flag);
}


/**
 * @}
 */


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月12日 0

暂无评论

推荐阅读