RT-thread 设备驱动组件之IIC总线设备

本文主要介绍RT-thread中IIC总线设备驱动,涉及到的主要文件有:驱动框架文件(i2c_core.c,i2c_dev.c,i2c-bit-ops.c,i2c_dev.h,i2c.h);底层硬件驱动文件(i2c_soft.c,i2c_soft.h)。这里的i2c_soft.c和i2c_soft.h是指利用MCU的GPIO口模拟IIC总线时序,而不是利用MCU的硬件IIC接口。应用IIC总线设备驱动时,需要在rtconfig.h中添加宏定义#define RT_USING_I2C。若使用GPIO口模拟IIC总线,则还需要添加宏定义#define RT_USING_I2C_BITOPS。

一、IIC总线设备驱动框架

先看i2c.h中定义的一些数据结构:

#define RT_I2C_WR                0x0000
#define RT_I2C_RD               (1u << 0)
#define RT_I2C_ADDR_10BIT       (1u << 2)  /* this is a ten bit chip address */
#define RT_I2C_NO_START         (1u << 4)
#define RT_I2C_IGNORE_NACK      (1u << 5)
#define RT_I2C_NO_READ_ACK      (1u << 6)  /* when I2C reading, we do not ACK */

struct rt_i2c_msg
{
    rt_uint16_t addr;
    rt_uint16_t flags;
    rt_uint16_t len;
    rt_uint8_t  *buf;
};

struct rt_i2c_bus_device;

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);
};

/*for i2c bus driver*/
struct rt_i2c_bus_device
{
    struct rt_device parent;
    const struct rt_i2c_bus_device_ops *ops;
    rt_uint16_t  flags;
    rt_uint16_t  addr;
    struct rt_mutex lock;
    rt_uint32_t  timeout;
    rt_uint32_t  retries;
    void *priv;
};

i2c_dev.h中相关数据结构(struct rt_i2c_priv_data用于i2c_bus_device_control()函数中RT_I2C_DEV_CTRL_RW控制标志):

#define RT_I2C_DEV_CTRL_10BIT        0x20
#define RT_I2C_DEV_CTRL_ADDR         0x21
#define RT_I2C_DEV_CTRL_TIMEOUT      0x22
#define RT_I2C_DEV_CTRL_RW           0x23

struct rt_i2c_priv_data
{
    struct rt_i2c_msg  *msgs;
    rt_size_t  number;
};

i2c-bit-ops.h中主要定义了模拟IIC总线时序时需要的数据结构:

struct rt_i2c_bit_ops
{
    void *data;            /* private data for lowlevel routines */
    void (*set_sda)(void *data, rt_int32_t state);
    void (*set_scl)(void *data, rt_int32_t state);
    rt_int32_t (*get_sda)(void *data);
    rt_int32_t (*get_scl)(void *data);

    void (*udelay)(rt_uint32_t us);

    rt_uint32_t delay_us;  /* scl and sda line delay */
    rt_uint32_t timeout;   /* in tick */
};

在i2c_dev.c主要实现IIC设备驱动统一接口函数:i2c_bus_device_read(),i2c_bus_device_write(),i2c_bus_device_control()以及rt_i2c_bus_device_device_init()。

rt_err_t rt_i2c_bus_device_device_init(struct rt_i2c_bus_device *bus,
                                       const char               *name)
{
    struct rt_device *device;
    RT_ASSERT(bus != RT_NULL);

    device = &bus->parent;

    device->user_data = bus;

    /* set device type */
    device->type    = RT_Device_Class_I2CBUS;
    /* initialize device interface */
    device->init    = RT_NULL;
    device->open    = RT_NULL;
    device->close   = RT_NULL;
    device->read    = i2c_bus_device_read;
    device->write   = i2c_bus_device_write;
    device->control = i2c_bus_device_control;

    /* register to device manager */
    rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);

    return RT_EOK;
}

i2c_core.c中实现IIC总线设备注册,以及使用IIC总线进行数据传输,如:rt_i2c_transfer(),rt_i2c_master_send(),rt_i2c_master_recv()。

rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,
                                    const char               *bus_name)
{
    rt_err_t res = RT_EOK;

    rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_FIFO);

    if (bus->timeout == 0) bus->timeout = RT_TICK_PER_SECOND;

    res = rt_i2c_bus_device_device_init(bus, bus_name);

    i2c_dbg("I2C bus [%s] registered\n", bus_name);

    return res;
}

i2c-bit-ops.c中主要实现了利用GPIO模拟IIC总线时序的相关接口函数,如:i2c_start(),i2c_restart(),i2c_stop(),i2c_waitack(),i2c_writeb(),i2c_readb(),i2c_send_bytes(),i2c_send_ack_or_nack(),i2c_recv_bytes(),i2c_send_address(),i2c_bit_send_address()等。并且实现了i2c_bit_xfer():

static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{
    i2c_bit_xfer,
    RT_NULL,
    RT_NULL
};
rt_err_t rt_i2c_bit_add_bus(struct rt_i2c_bus_device *bus,
                            const char               *bus_name)
{
    bus->ops = &i2c_bit_bus_ops;

    return rt_i2c_bus_device_register(bus, bus_name);
}

二、底层硬件驱动

本文采用的是模拟IIC,即用GPIO口模拟IIC时序。在i2c_soft.c中主要实现struct rt_i2c_bit_ops中的指针函数:

void stm32_set_sda(void *data, rt_int32_t state)
{
    if(state == 1)
        GPIO_SetBits(I2C1_GPIO , I2C1_GPIO_SDA);   //GPIOB->BSRRL = I2C1_GPIO_SDA
    else if(state == 0)
        GPIO_ResetBits(I2C1_GPIO , I2C1_GPIO_SDA); //GPIOB->BSRRH = I2C1_GPIO_SDA
}

void stm32_set_scl(void *data, rt_int32_t state)
{
    if(state == 1)
        GPIO_SetBits(I2C1_GPIO , I2C1_GPIO_SCL);   //GPIOB->BSRRL = I2C1_GPIO_SCL
    else if(state == 0)
        GPIO_ResetBits(I2C1_GPIO , I2C1_GPIO_SCL); //GPIOB->BSRRH = I2C1_GPIO_SCL
}

rt_int32_t stm32_get_sda(void *data)
{
    return (rt_int32_t)GPIO_ReadInputDataBit(I2C1_GPIO , I2C1_GPIO_SDA);//return(GPIOB->IDR  & I2C1_GPIO_SDA)
}

rt_int32_t stm32_get_scl(void *data)
{
    return (rt_int32_t)GPIO_ReadInputDataBit(I2C1_GPIO , I2C1_GPIO_SCL);//return(GPIOB->IDR  & I2C1_GPIO_SCL)
}

void stm32_udelay(rt_uint32_t us)
{
    rt_uint32_t delta;
    /* ¼ÆËãusÑÓʱËùÐè¼ÆÊýÖµ£»sysTick->LOAD=21000, RT_TICK_PER_SECOND=1000 */
    us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));
    /* »ñÈ¡µ±Ç°àÖ઼ÆÊýÖµ */
    delta = SysTick->VAL;
    /* ÑÓʱus */
    while (delta - SysTick->VAL< us);
}

void stm32_mdelay(rt_uint32_t ms)
{
      stm32_udelay(ms * 1000);
}

static const struct  rt_i2c_bit_ops stm32_i2c_bit_ops =
{
    (void*)0xaa,     //no use in set_sda,set_scl,get_sda,get_scl
    stm32_set_sda,
    stm32_set_scl,
    stm32_get_sda,
    stm32_get_scl,
    stm32_udelay,
    20,
    5
};

最后,实现IIC总线硬件初始化(包括RCC时钟配置和GPIO配置,最重要的是将stm32_i2c_bit_ops初始化为IIC总线设备结构体的priv变量,即stm32_i2c.priv = (void *)&stm32_i2c_bit_ops):

int rt_hw_i2c_init(void)
{
    static struct rt_i2c_bus_device stm32_i2c;//"static" add by me. It must be add "static", or it will be hard fault

    RCC_Configuration();
    GPIO_Configuration();

    rt_memset((void *)&stm32_i2c, 0, sizeof(struct rt_i2c_bus_device));
    stm32_i2c.priv = (void *)&stm32_i2c_bit_ops;
    rt_i2c_bit_add_bus(&stm32_i2c, "i2c1");   

    return 0;
}
INIT_BOARD_EXPORT(rt_hw_i2c_init);//rt_hw_i2c_init will be called in rt_components_board_init()

三、IIC总线设备初始化

这里以cs43l22数字音频放大器为例:

static rt_err_t cs43l22_init(const char * i2c_bus_name)
{
    i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(i2c_bus_name);
    if(i2c_bus == RT_NULL)
    {
     rt_kprintf("\ni2c_bus %s for cs43l22 not found!\n", i2c_bus_name);
     return -RT_ENOSYS;
  }

    /* oflag has no meaning for spi device , so set to RT_NULL */
    if(rt_device_open(&i2c_bus->parent, RT_NULL) != RT_EOK)
    {
         rt_kprintf("\ni2c_bus %s for cs43l22 opened failed!\n", i2c_bus_name);
         return -RT_EEMPTY;
    }

    EVAL_AUDIO_Init(OUTPUT_DEVICE_AUTO, volume, I2S_AudioFreq_48k); 

    /* it must be at the back of EVAL_AUDIO_Init, which reset the cs43l22 */
  uint8_t chip_id = Codec_ReadRegister(i2c_bus, 0x01);
    rt_kprintf("(chip_id of cs43l22 is 0x%02x)", chip_id);

    return 0;
}

int rt_cs43l22_init(void)
{
    rt_sem_init(&sem_cs43l22, "cs43l22", 1, RT_IPC_FLAG_FIFO);

    cs43l22_init("i2c1");

    return 0;
}
INIT_APP_EXPORT(rt_cs43l22_init);

注意事项:

1、在应用IIC总线设备驱动时,需要用到rt_device_read或rt_device_write,因此在初始化函数中需要调用rt_device_open将IIC总线设备打开。

2、下面利用rt_device_read和rt_device_write操作寄存器(每一次调用rt_device_read或rt_device_write都包括了i2c_start,i2c_bit_send_address,i2c_recv_bytes/i2c_send_bytes,i2c_stop这4个步骤):

static uint32_t Codec_WriteRegister(struct rt_i2c_bus_device * i2c_bus, uint8_t RegisterAddr, uint8_t RegisterValue)
{
  uint32_t result = 0;

  rt_uint16_t flags = 0x00;
  rt_uint16_t DevAddr = (rt_uint16_t)CODEC_ADDRESS >> 1;
  rt_off_t pos = (rt_off_t)((flags << 16) | DevAddr);

  rt_uint8_t buffer[2];
  buffer[0] = RegisterAddr;
  buffer[1] = RegisterValue;

  rt_device_write(&i2c_bus->parent, pos, buffer, sizeof(buffer));

#ifdef VERIFY_WRITTENDATA
  /* Verify that the data has been correctly written */
  result = (Codec_ReadRegister(i2c_bus, RegisterAddr) == RegisterValue)? 0:1;
    if(result == 0)
        rt_kprintf("\nthe reg 0x%02x verify passed\n",RegisterAddr);
    else
        rt_kprintf("\nthe reg 0x%02x verify failed\n",RegisterAddr);
#endif /* VERIFY_WRITTENDATA */

  /* Return the verifying value: 0 (Passed) or 1 (Failed) */
  return result;
}
static uint8_t Codec_ReadRegister(struct rt_i2c_bus_device * i2c_bus, uint8_t RegisterAddr)
{
  rt_uint16_t flags = 0x00;
  rt_uint16_t DevAddr = (rt_uint16_t)CODEC_ADDRESS >> 1;
  rt_off_t pos = (rt_off_t)((flags << 16) | DevAddr);

  rt_uint8_t buffer;
  buffer = RegisterAddr;

  rt_device_write(&i2c_bus->parent, pos, &buffer, 1);
  rt_device_read(&i2c_bus->parent, pos, &buffer, 1);

  /* Return the byte read from Codec */
  return buffer;
}

在上面两个函数中,有符号整型32位pos的高16位表示flags,低16位表示IIC器件地址。flags取值如i2c.h文件中宏定义所示。

这里说明一个问题:在rt_i2c_master_send和rt_i2c_master_recv函数中均有“msg.flags = flags & RT_I2C_ADDR_10BIT;”这一语句,该句用于标志IIC器件地址是否为10位地址,但是这条语句会将其他预置好的标志全部清除,如RT_I2C_NO_START,RT_I2C_IGNORE_NACK或RT_I2C_NO_READ_ACK。所以,在一般情况下,flags标志只能事先预置RT_I2C_ADDR_10BIT,若IIC器件地址为7位,则直接设置flags为0。

3、根据i2c_bit_send_address()函数中:

 else
    {
        /* 7-bit addr */
        addr1 = msg->addr << 1;
        if (flags & RT_I2C_RD)
            addr1 |= 1;
        ret = i2c_send_address(bus, addr1, retries);
        if ((ret != 1) && !ignore_nack)
            return -RT_EIO;
    }

可得,若IIC器件地址为7位,则pos低16位所表示的地址值DevAddr不包括读写标志位(最低位)。而cs43l22数据手册中的8位地址值包含了读写标志位,因此设置DevAddr为CODEC_ADDRESS >> 1。

时间: 2024-10-13 18:25:51

RT-thread 设备驱动组件之IIC总线设备的相关文章

20150225 IMX257 总线设备驱动模型编程之总线篇

20150225 IMX257 总线设备驱动模型编程之总线篇 2015-02-25 19:40 李海沿 从现在开始,我们开始来实现 总线-设备-驱动模型中的总线.. 我们这个程序的目标是在 sysfs文件系统的/sys/bus/ 目录下面建立一个文件夹. 一.总线介绍 1. 总线数据结构bus_type struct bus_type 结构体的定义如下: struct bus_type { const char *name; --总线名 struct bus_attribute *bus_att

arm-linux字符设备驱动开发之---简单字符设备驱动

一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等.2.块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等. 每一个字符设备或块设备都在/dev目录下对应一个设备文件.linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和

RT-thread 设备驱动组件之SPI设备

本文主要介绍RT-thread中的SPI设备驱动,涉及到的文件主要有:spi_dev.c,spi_core.c,spi.h,spi_hard.c,spi_hard.h. 一.SPI设备框架 先来看spi.h中的一些数据结构: ** * SPI message structure */ struct rt_spi_message { const void *send_buf; void *recv_buf; rt_size_t length; struct rt_spi_message *next

linux设备驱动之platform平台总线工作原理(三)

设备为数据,驱动为加工着 1.以led-s3c24xx.c为例来分析platform设备和驱动的注册过程 其中关于led的驱动数据结构为: static struct platform_driver s3c24xx_led_driver = { .probe = s3c24xx_led_probe, .remove = s3c24xx_led_remove, .driver = { .name = "s3c24xx_led", .owner = THIS_MODULE, }, }; s

linux设备驱动之platform平台总线工作原理(二)

5.5.5.platform平台总线工作原理2 5.5.5.1.平台总线体系的工作流程 (1)第一步:linux内核系统启动时在bus系统中注册platform. 1.什么叫做bus系统,操作系统中有一套管理总线的体系,内核里有一个子系统,就叫做总线子系统.就是内核来管理总线的.bus系统在内核启动时建立起来,比platform建立的时间还要早,bus系统的是由内核编写的人提供的,我们将来分析代码的时候不需要去分析他.在bus系统起来以后,就需要在bus系统中注册这个platform平台总线的b

RT-thread 设备驱动组件之pin设备

在RT-thread 2.0.0正式版中引入了pin设备作为杂类设备,其设备驱动文件pin.c在rt-thread-2.0.1\components\drivers\misc中,主要用于操作芯片GPIO, 如点亮led,按键等.同时对于相应的芯片平台,需要自行编写底层gpio驱动,如gpio.c. 一.在pin.c中定义了一个静态的pin设备对象static struct rt_device_pin _hw_pin,其中结构体类型struct rt_device_pin在pin.h中定义为: /

乾坤合一~Linux设备驱动之USB主机和设备驱动

如果不能陪你到最后 是否后悔当初我们牵手 如果当初没能遇见你 现在的我 在哪里逗留 所有的爱都是冒险 那就心甘情愿 等待我们一生中 所有悬念 我一往情深的恋人 她是我的爱人 她给我的爱就像是 带着露水的清晨 我多想给她我的真 我心疼的爱人 我愿为她守候寂寞 就像这夜晚 深沉 这一章从主机侧角度看到的USB 主机控制器驱动和设备驱动从主机侧的角度而言,需要编写的USB 驱动程序包括主机控制器驱动和设备驱动两类,USB 主机控制器驱动程序控制插入其中的USB 设备,而USB 设备驱动程序控制该设备如

USB设备驱动开发之远程访问USB设备(一)

By Fanxiushu 2016 05-15  转载或引用本文,请注明原始作者. 使用过vmware的人都应该知道,vmware虚拟机有这样的一个功能, 当在宿主机上插入一个USB设备的时候,通过设置,可以在vmware的虚拟机系统里边能访问到这个USB设备, 而且访问这个USB设备,就跟真的把这个USB设备插入到这个虚拟系统中一样,跟真实的几乎没任何区别. 再看一种情况,假设有两台机器C和S,C 机器是你正在使用的机器, S机器在远端,你只能通过远程控制S. S机器的配置和功能都很强大,大部

USB设备驱动开发之远程访问USB设备(二 USB设备虚拟端)

By Fanxiushu 2016-05-22 转载或引用请注明原始作者 接上文, 在处理好USB数据采集端的问题之后,接下来进入核心的部分,虚拟USB设备端的开发工作. 上文简单介绍过,需要开发虚拟总线驱动来模拟USB设备. 所谓虚拟总线驱动,就是安装于System系统设备下的一个驱动,由PnP管理器创建出一个虚拟的总线PDO设备, 我们的虚拟总线驱动Attach到这个PDO上,形成一个FDO功能设备驱动, 然后在我们的驱动中,根据需要创建出若干个 Child PDO设备, 这些 Child