55 linux内核里基于GPIO口的I2C控制器驱动

本文介绍如何在SOC I2C控制器不稳定或不足时,利用GPIO实现I2C控制器功能。通过Linux内核提供的平台驱动,仅需配置平台设备描述即可启用。文章详细解析了所需的平台数据设置,并给出了具体的实现代码示例。

当SOC里的I2C控制器不稳定,或I2C控制器不够用时,我们可以基于GPIO实现I2C控制器的功能.

在linux内核里已提供了相应的代码,是一个平台驱动,只需写平台设备描述相关资源即可.

    make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
    Device Drivers  ---> 
        <*> I2C support  ---> 
            <*>   I2C device interface
            I2C Hardware Bus support  --->  
                <*> GPIO-based bitbanging I2C //基于GPIO实现的I2C控制器驱动

驱动源码在”drivers/i2c/busses/i2c-gpio.c”里

static struct platform_driver i2c_gpio_driver = {
    .driver     = {
        .name   = "i2c-gpio", //匹配名为"i2c-gpio"的平台设备
        .owner  = THIS_MODULE,
        .of_match_table = of_match_ptr(i2c_gpio_dt_ids),
    },  
    .probe      = i2c_gpio_probe,
    .remove     = __devexit_p(i2c_gpio_remove),
};

//通过阅读平台驱动里的probe函数可以得知平台设备需要提供的参数
static int __devinit i2c_gpio_probe(struct platform_device *pdev)
{
    struct i2c_gpio_private_data *priv;
    struct i2c_gpio_platform_data *pdata;
    struct i2c_algo_bit_data *bit_data;
    struct i2c_adapter *adap;
    int ret;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    adap = &priv->adap;
    bit_data = &priv->bit_data;
    pdata = &priv->pdata;

    if (pdev->dev.of_node) { //此内核没用到设备树,这里不成立
        ret = of_i2c_gpio_probe(pdev->dev.of_node, pdata);
        if (ret)
            return ret;
    } else {
        if (!pdev->dev.platform_data) //可以通过平台设备的platform_data成员提供struct i2c_gpio_platform_data类型的数据
            return -ENXIO;
        memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));
    }
    ....

    if (pdata->udelay)
        bit_data->udelay = pdata->udelay;
    else if (pdata->scl_is_output_only)
        bit_data->udelay = 50;          /* 10 kHz */
    else
        bit_data->udelay = 5;           /* 100 kHz */

    if (pdata->timeout)
        bit_data->timeout = pdata->timeout;
    else
        bit_data->timeout = HZ / 10;        /* 100 ms */
    ...
    adap->nr = (pdev->id != -1) ? pdev->id : 0; //i2c_adapter对象的号使用平台设备的id
    ret = i2c_bit_add_numbered_bus(adap);




struct i2c_gpio_platform_data {
    unsigned int    sda_pin; // SDA脚的IO口
    unsigned int    scl_pin; // SCL脚的IO口
    int     udelay; //通过此成员确定SCL的时钟频率, SCL frequency is (500 / udelay) kHz
    int     timeout; //多久没收到应答后认为传输失败
    unsigned int    sda_is_open_drain:1; //是否有上拉, 没有则设1 
    unsigned int    scl_is_open_drain:1;

    unsigned int    scl_is_output_only:1; //时钟信号是否只由主机提供
};

/////////////////////////////////////////////////

把PA9(SDA), PA10(SCL)这两个IO口实现I2C控制器的接口

myi2c9.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c-gpio.h>
#include <mach/gpio.h>

struct i2c_gpio_platform_data pdata = {
    .sda_pin = GPIOA(9),
    .scl_pin = GPIOA(10),
    .udelay =  2, // 250Khz
    .timeout = 0, //使用默认值,此内核里是100ms
};

struct platform_device mypdev = {
    .name = "i2c-gpio",
    .id = 9, // i2c_adapter对象的号就是9
    .dev = {
        .platform_data = &pdata, 
    },
};

module_driver(mypdev, platform_device_register, platform_device_unregister);
MODULE_LICENSE("GPL");

加模块后,应在”/dev”目录下有i2c-9设备文件
在”/sys/bus/i2c/devices”目录下有i2c-9子目录

<think> 我们根据用户的问题,结合引用内容,为用户提供I2C读写EEPROM代码的流程。由于引用中提到了两种方式:使用Linux系统提供的用户空间接(如sysfs)和直接通过硬件I2C控制器编程(通常在内核驱动或裸机程序中使用),这我们分别介绍两种方式的流程。但用户问题中并没有明确是哪种环境,考虑到引用[1]和[3]都涉及STM32或硬件I2C,而引用[2]提到了Linux用户空间,所以我们先回答较为常见的嵌入式Linux环境下用户空间通过sysfs访问EEPROM的流程,然后再简要介绍STM32(裸机或HAL库)的I2C读写EEPROM流程。 由于用户要求结构清晰,我们分步骤描述流程。 ### 一、Linux用户空间通过sysfs读写EEPROM的流程 在Linux系统中,如果EEPROM设备已经被正确驱动(通常使用at24驱动),那么它会出现在sysfs文件系统中,用户可以直接通过文件读写的方式访问EEPROM。流程如下: 1. **确认EEPROM设备节点** 通常,EEPROM设备节点位于`/sys/bus/i2c/devices/<i2c-bus>-<device-address>/eeprom`。例如,如果EEPROM挂在I2C总线0上,设备地址为0x50,那么路径可能是`/sys/bus/i2c/devices/0-0050/eeprom`。 2. **读写操作** - **读取**:使用`cat`命令或编程语言(如C、Python)的文件读取函数读取该文件。 - **写入**:使用`dd`命令或编程语言的文件写入函数写入该文件。注意,由于EEPROM的写操作需要时间(典型5ms),连续写入时需考虑延迟。 3. **注意事项** - 写操作可能需要先擦除(但EEPROM通常支持字节写,不需要整个扇区擦除,但写入前要确保目标地址处于可写状态)。 - 注意读写偏移和长度,避免越界。 示例代码(shell命令): ```bash # 读取整个EEPROM内容到文件 cat /sys/bus/i2c/devices/0-0050/eeprom > eeprom.bin # 从偏移地址0x10处读取16个字节 dd if=/sys/bus/i2c/devices/0-0050/eeprom bs=1 skip=16 count=16 2>/dev/null # 向偏移地址0x10处写入16个字节(先准备一个16字节的文件data.bin) dd if=data.bin of=/sys/bus/i2c/devices/0-0050/eeprom seek=16 bs=1 ``` ### 二、STM32硬件I2C读写EEPROM流程(以HAL库为例) 对于STM32微控制器,通常使用硬件I2C外设,并通过HAL库函数进行操作。流程如下: 1. **初始化I2C外设** - 配置I2C的时钟、速度模式(标准模式100kbps,快速模式400kbps等)、自身地址(主模式不需要)、时钟使能等。 2. **写操作流程(主发送器模式)** - 发送起始条件(HAL_I2C_Master_Transmit或更底层的函数)。 - 发送设备地址(7位地址+写标志0)。 - 发送要写入的EEPROM内部地址(16位或8位,取决于EEPROM型号)。 - 发送一个或多个数据字节。 - 发送停止条件。 3. **读操作流程(主接收器模式)** - 先执行一个写操作(发送设备地址和要读取的内部地址),但不发送停止条件,而是发送重复起始条件(Restart)。 - 然后发送设备地址(读标志1)并开始接收数据。 - 接收一个或多个字节后发送停止条件。 4. **注意事项** - EEPROM写入需要等待时间(5-10ms),在连续写入多个字节时,需要检查ACK或使用轮询方式等待上一次写入完成。 - 有些EEPROM支持页写(一次最多写一页,如16字节、32字节等),超过页边界需要分多次写入。 示例代码片段(STM32 HAL库): ```c // 写一个字节到地址0x0010 uint8_t devAddr = 0xA0; // EEPROM设备地址(左移一位,最低位为0表示写) uint16_t memAddr = 0x0010; // 存储器地址 uint8_t data = 0x55; // 使用HAL库的存储器写函数(内部处理了地址发送) HAL_I2C_Mem_Write(&hi2c1, devAddr, memAddr, I2C_MEMADD_SIZE_16BIT, &data, 1, 100); // 延时等待EEPROM内部写入完成(至少5ms) HAL_Delay(5); // 从地址0x0010读取一个字节 uint8_t readData; HAL_I2C_Mem_Read(&hi2c1, devAddr, memAddr, I2C_MEMADD_SIZE_16BIT, &readData, 1, 100); ``` ### 三、软件模拟I2C读写EEPROM流程(适用于无硬件I2C或需要灵活GPIO的情况) 1. **GPIO初始化** 将SCL和SDA引脚配置为开漏输出(或普通输出,但需要外部上拉电阻),并初始化电平。 2. **实现I2C基本时序函数** - 起始条件(SDA在SCL高电平时由高变低) - 停止条件(SDA在SCL高电平时由低变高) - 发送一个字节(从高位开始,每bit在SCL低电平时改变SDA,在SCL高电平时保持稳定) - 接收一个字节(同样在SCL高电平时读取SDA) - 发送ACK/NACK 3. **读写流程** 与硬件I2C的读写流程类似,只是每个步骤都需要用上述基本时序函数来实现。 ### 总结 以上是三种常见的I2C读写EEPROM代码流程。用户可根据实际平台选择适合的方法。在嵌入式Linux中,推荐使用sysfs接,简单且避免编写驱动。在单片机开发中,使用硬件I2C(如STM32的HAL库)效率更高,而软件模拟I2C则更灵活。 §§相关问题§§ 1. 在Linux系统中,如何为新的EEPROM设备加载驱动? 2. STM32硬件I2C通信中如何处理总线错误? 3. 软件模拟I2C和硬件I2C在性能上有多大差异? </think> ### I2C读写EEPROM代码流程详解 以下是基于STM32硬件I2C(主模式)的EEPROM读写流程,结合查询事件方式实现,流程分为初始化、写操作和读操作三个阶段: --- #### **1. I2C与EEPROM初始化流程** **(1) 配置I2C硬件参数** ```c I2C_InitTypeDef I2C_InitStruct = {0}; I2C_InitStruct.I2C_ClockSpeed = 100000; // 标准模式100kHz I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // I2C模式 I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // Tlow/Thigh=2 I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 主机地址(不重要) I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 使能ACK响应 I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址 I2C_Init(I2C1, &I2C_InitStruct); // 应用配置 I2C_Cmd(I2C1, ENABLE); // 使能I2C外设 ``` **(2) EEPROM地址配置** - EEPROM设备地址格式:`1010[A2][A1][A0]`(7位地址,例:0xA0) - 存储器地址:16位(支持最大64KB空间) --- #### **2. 写操作流程(主发送器模式)** ```c // 步骤1:发送起始条件 I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待EV5事件 // 步骤2:发送EEPROM设备地址(写模式) I2C_Send7bitAddress(I2C1, EEPROM_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待EV6事件 // 步骤3:发送存储器地址(高字节+低字节) I2C_SendData(I2C1, (MemoryAddress >> 8) & 0xFF); // 高字节 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 等待EV8_1事件 I2C_SendData(I2C1, MemoryAddress & 0xFF); // 低字节 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待EV8事件 // 步骤4:写入数据 I2C_SendData(I2C1, DataByte); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 步骤5:发送停止条件 I2C_GenerateSTOP(I2C1, ENABLE); // 步骤6:等待EEPROM内部写入完成(约5ms) HAL_Delay(5); // 或轮询ACK ``` > **关键事件说明**: > - **EV5**:起始条件发送完成 > - **EV6**:设备地址发送完成并收到ACK > - **EV8**:数据字节发送完成并收到ACK[^1] --- #### **3. 读操作流程(主接收器模式)** ```c // 步骤1~3:同写操作(发送设备地址+存储器地址) // ... // 步骤4:发送重复起始条件(切换到读模式) I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // EV5 // 步骤5:发送设备地址(读模式) I2C_Send7bitAddress(I2C1, EEPROM_ADDR, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // EV6 // 步骤6:接收数据(单字节示例) while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); // EV7 uint8_t data = I2C_ReceiveData(I2C1); // 步骤7:发送停止条件 I2C_GenerateSTOP(I2C1, ENABLE); ``` > **关键事件说明**: > - **EV7**:数据接收完成并可读取[^1][^3] --- #### **4. Linux用户空间读写流程(通过sysfs)** 若使用Linux系统驱动(如`at24`驱动),可直接操作设备文件: ```bash # 读取EEPROM偏移地址0x10的16字节 dd if=/sys/bus/i2c/devices/0-0050/eeprom bs=1 skip=16 count=16 # 写入数据到0x20 echo -n "data" > /sys/bus/i2c/devices/0-0050/eeprom seek=32 # 偏移量32=0x20[^2] ``` --- ### 关键注意事项 1. **硬件vs软件I2C** - 硬件I2C效率高但依赖固定引脚 - 软件I2CGPIO模拟)灵活但速度慢[^3] 2. **时序要求** - 每次写操作后需延时5ms等待EEPROM内部编程 3. **错误处理** - 需检查超时和总线错误事件(如`I2C_EVENT_MASTER_TIMEOUT`)
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值