• 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
    ", 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("
    i2c_bus %s for cs43l22 not found!
    ", 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("
    i2c_bus %s for cs43l22 opened failed!
    ", 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("
    the reg 0x%02x verify passed
    ",RegisterAddr);
        else
            rt_kprintf("
    the reg 0x%02x verify failed
    ",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。

  • 相关阅读:
    10款AJAX/CSS/HTML的在线表单生成器
    SQLServer中使用索引视图(物化视图)
    Github for Windows使用介绍
    微软一站式示例代码库
    SQL中存储过程中使用事务,并且加入异常处理机制.
    .NET 性能分析工具
    公众号和app和web都是客户端,都可以对接一个后台
    服务器session,Tomcat有自己的session维护机制,apache有自己的session维护机制
    主账户经验
    spring mvc中的@propertysource
  • 原文地址:https://www.cnblogs.com/King-Gentleman/p/4658615.html
Copyright © 2020-2023  润新知