SkylarkOS I2C 使用指南

优质
小牛编辑
133浏览
2023-12-01

I²C 控制器支持下列功能︰

  • 支持主从模式
  • 支持 7 位和 10 位寻址模式
  • 中断或轮询驱动多个字节数据传输
  • Three speeds: Standard mode (100 Kb/s), Fast mode (400 Kb/s), High-speed mode (3.4 Mb/s)

I2C使用说明

以8009为例,支持4路I2C接口,两路挂在CK域,两路挂在ARM域。CK域无法访问ARM域,ARM域的可以访问CK域的。

kernel下的设备树描述在:/kernel/arch/arm/boot/dtc/leo.dtsi

i2c0: i2c@02202000:arm域第一组
i2c1: i2c@02203000:arm域第二组
ck_i2c0: i2c@0205000:ck域第一组
ck_i2c1: i2c@0206000:ck域第二组

使用的时候,在Linux设备树下,新加的I2C设备根据硬件连接信息挂在适合的设备树下面

如何新加一个I2C设备

我们以一个apt的I2C设备为例子进行讲解

该设备的7位设备地址为:0x2d

设备挂在在ck_i2c1上,那么我们修改设备树如下:/kernel/arch/arm/boot/dts/leo_gx8009b_ssd_lp_v1_4.dts
加上:

+&ck_i2c1{
+    status = "okay";
+    mcu: apt32f101@2d{
+        compatible = "apt,apt32f101";
+        #address-cells = <1>;
+        #size-cells = <0>;
+        reg = <0x2d>;
+    };
+};
+

然后配置管脚复用,配置为I2C的复用,参考:SkylarkOS GPIO使用指南

下面给出个linux驱动的例子

/**************************************************************************************************************************************
 * 
 * File Name    : apt32f101.c
 * Date         : 2018-09-25  
 * function     : APT32F101 MCU driver.
 * 
 **************************************************************************************************************************************/

#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/types.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/input-polldev.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/freezer.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/pm_runtime.h>
#include <linux/atomic.h>
#include <linux/of_device.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/dmi.h>
#include <linux/idr.h>
#include <linux/mutex.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/kdev_t.h>
#include <linux/devcoredump.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/kthread.h>

#include "apt32f101.h"

#define SYSTEM_STATUS_CHECK_TIME (3 * 100) 

#define D_SUPPORT_KTHREAD

#define DEBUGE_LOG 0
#if DEBUGE_LOG
#define LOG_DBG printk
#else
#define LOG_DBG
#endif


#define D_SYSCMD_CODE_MOTOR     0x11 
#define D_SYSCMD_CODE_EYELED    0x12 
#define D_SYSCMD_CODE_EARLED    0x13 
#define D_SYSCMD_CODE_STATUS    0x14 

#define D_SYSSTATUS_GET_BAT            (D_SYSCMD_CODE_STATUS << 8 | 0x01)    
#define D_SYSSTATUS_GET_CHARGE        (D_SYSCMD_CODE_STATUS << 8 | 0x02)  
#define D_SYSSTATUS_DO_POWEROFF        (D_SYSCMD_CODE_STATUS << 8 | 0x06)  

#if 0
static const keymap_t keymap[] = 
{
    {KEYCODE_KEY1, 111},
    {KEYCODE_KEY2, 112},
    {KEYCODE_KEY3, 113},
    {KEYCODE_KEY4, 254},

}; 
#else
static const keymap_t keymap[] = 
{
    {111, 111},
    {112, 112},
    {113, 113},
    {117, 117},
    {118, 118},
    {254, 254},

};

#endif

struct apt32f101_priv *priv_p;
struct apt32f101_priv *priv;

struct apt32f101_priv {
    int irq;
    const struct apt32f101_chipdef *cdef;
    struct i2c_client *client;
    struct input_dev *input_dev;
#ifdef D_SUPPORT_KTHREAD    
    struct task_struct  *pthread;
#endif    

};

#if 0
struct apt32f101_chipdef {
    u8    key_reg; 
    u8    battery_reg; 
    u8  motor_cmd_reg;  
    u8  sys_status_reg; 
};
#else
struct apt32f101_chipdef {
    u8    power_key_reg; 
    u8    battery_reg; 
    u8  function_key_reg;  
    u8  sys_status_reg; 
};
#endif

static const struct apt32f101_chipdef apt32f101_cdef = {
    .power_key_reg = 0x00,  
    .battery_reg = 0x01, 
    .function_key_reg = 0x02, 
    .sys_status_reg = 0x03, 
};

struct timer_list systimer;

static int write_d8(void *client, u8 val)
{
    return i2c_smbus_write_byte(client, val);
}

static int write_r8d8(void *client, u8 reg, u8 val)
{
    return i2c_smbus_write_byte_data(client, reg, val);
}

static int write_r8d16(void *client, u8 reg, u16 val)
{
    return i2c_smbus_write_word_data(client, reg, val);
}

static int read_d8(void *client)
{
    return i2c_smbus_read_byte(client);
}

static int read_r8d8(void *client, u8 reg)
{
    return i2c_smbus_read_byte_data(client, reg);
}

static int read_r8d16(void *client, u8 reg)
{
    return i2c_smbus_read_word_data(client, reg);
}

static int apt32f101_smbus_read(struct apt32f101_priv *priv, u8 reg)
{
    int ret;

    //LOG_DBG( KERN_INFO &priv->client->dev, "read register 0x%02X=", reg);
    //LOG_DBG( KERN_INFO "read register 0x%02X=", reg);
    ret =  i2c_smbus_read_byte_data(priv->client, reg);
    if (ret) {
        dev_err(&priv->client->dev,
            "register read to 0x%02X failed (error %d)",
            reg, ret);
    }
    return ret;
}

static int apt32f101_smbus_write(struct apt32f101_priv *priv, u8 reg, u8 val)
{
    int ret;

    //LOG_DBG(KERN_INFO &priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
    //LOG_DBG(KERN_INFO"writing register 0x%02X=0x%02X", reg, val);

    ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
    if (ret) {
        dev_err(&priv->client->dev,
            "register write to 0x%02X failed (error %d)",
            reg, ret);
    }
    return ret;
}


static long apt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    u8 cmd_buf[8] = {0};

    cmd_buf[0] = (char)((cmd >> 8) & 0xFF); 
    cmd_buf[1] = (char)(cmd & 0xFF);
    switch(cmd_buf[0]) {
        case D_SYSCMD_CODE_STATUS:
        {
            if (cmd == D_SYSSTATUS_GET_BAT) {
                u16 bat = 0;
                //bat = (read_r8d8(priv_p->client, SYSREG_BAT_ADC_H) << 8) & 0x0f00;
                //bat |= read_r8d8(priv_p->client, SYSREG_BAT_ADC_L);
                bat = (read_r8d8(priv_p->client, 0x01) << 8) &0xf00; //楂樹綅
                bat |= read_r8d8(priv_p->client, 0x02);
                LOG_DBG(KERN_INFO"apt_ioctl bat=%d\n", bat);
                return bat;
            }

            if (cmd == D_SYSSTATUS_GET_CHARGE) {
                return read_r8d8(priv_p->client, SYSREG_ACIN);
            }

            if (cmd == D_SYSSTATUS_DO_POWEROFF) {
                write_r8d8(priv_p->client, 0x00, 0x02);
            }
        }
            break;

        default:
            LOG_DBG("APTF32101 IOCTL UNKNOW CMD\n");
            break;
    }

    return 0;    
}    

int apt_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
    int ret = 0;
    return ret; 
}

int apt_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)  
{
    int ret = 0;
    return ret;
}

static int apt_open(struct inode *inode, struct file *file)
{
    int ret;
    LOG_DBG(KERN_INFO"%s:%d\n", __FUNCTION__, __LINE__);

    ret = nonseekable_open(inode, file);
    if( ret < 0 )
        return ret;

    file->private_data = priv_p;
    return 0;
}

static int apt_release(struct inode *inode, struct file *file)
{
    LOG_DBG(KERN_INFO"%s:%d\n", __FUNCTION__, __LINE__);
    return 0;
}


static struct file_operations apt_fops = {
    .owner = THIS_MODULE,
    .open = apt_open,
    .read = apt_read,
    .write = apt_write,
    .release = apt_release,
    .unlocked_ioctl = apt_ioctl,
};

static struct miscdevice apt_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "apt32f101xx",
    .fops = &apt_fops,
};



unsigned int get_keycode_keymap(unsigned char key)
{
    int i=0;
    for(i=0; i < sizeof(keymap)/sizeof(keymap_t); i++)
    {
        if(keymap[i].key_val == key)
            return keymap[i].key_code;
    }

    return KEY_RESERVED;
}

static void apt_timer_func(unsigned long arg)
{
    systimer.expires = jiffies + SYSTEM_STATUS_CHECK_TIME;
    add_timer(&systimer);
}



#ifdef D_SUPPORT_KTHREAD
int apt_thread_func(void *data) 
{
    u8 code, pressed, change = 0;
    u8 key_value_mcu = 0;
    u8 buf[2] ={0};
    //u8 report_falg =0;

    do{
        //key_value_mcu = read_r8d8(priv_p->client, 0x00);//0x00 1涓猙yte
        //msleep(5);
        key_value_mcu = read_r8d8(priv_p->client, 0x00);//0x00 1涓猙yte
        if(key_value_mcu == 3){
            msleep(400);
            key_value_mcu = read_r8d8(priv_p->client, 0x00);
        }else if(key_value_mcu == 5){
            msleep(500);
            key_value_mcu = read_r8d8(priv_p->client, 0x00);
        }

        if(key_value_mcu == 3){
            code = 111;
        }else if(key_value_mcu ==4){
            code = 117;
        }else if(key_value_mcu == 5){
            code = 113;
        }else if(key_value_mcu == 6){
            code = 118;
        }else{
            code = 255;
        }

        buf[0] = code;
        if(code != KEYCODE_DUMP) {
            // key pressed
            pressed = 1;
            change = code;
            LOG_DBG(KERN_INFO" key pressed apt_thread_func key_value_mcu =%d change =%d\n",key_value_mcu,change);
            input_report_key(priv_p->input_dev, get_keycode_keymap(change), 1);
            input_sync(priv_p->input_dev);
            write_r8d8(priv_p->client, 0x00, 0x00);

        }else if (pressed) {
            // key release
            //msleep(5);//delay 8ms
            pressed = 0;
            LOG_DBG(KERN_INFO"key release apt_thread_func key_value_mcu =%d change =%d\n",key_value_mcu,change);
            input_report_key(priv_p->input_dev, get_keycode_keymap(change), 0);
            input_sync(priv_p->input_dev);

        } else {
            // no event
        }
        msleep(100);

    } while(!kthread_should_stop());

    return 0;
}
#endif

static const struct of_device_id of_apt32f101_match[] = {
    {.compatible = "apt,apt32f101" , 0}, //.data = &apt32f101_cdef
    { }
};

MODULE_DEVICE_TABLE(of, of_apt32f101_match);

static int aptf32101_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;
    int i=0;
    LOG_DBG(KERN_INFO"1aptf32101_probe\n");

    const struct apt32f101_chipdef *cdef;
    const struct of_device_id *of_dev_id;
    struct device *dev = &client->dev;
    struct input_dev *input_dev;

    if (!client->dev.of_node)
    {
        LOG_DBG(KERN_INFO"apt32f101 device tree node not found.\n");
        return -ENODEV;
    }

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
    {
        return -ENXIO;
    }

    of_dev_id = of_match_device(of_apt32f101_match, dev);
    if (!of_dev_id)
        return -EINVAL;

    priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->client = client;
    priv->cdef = cdef;
    i2c_set_clientdata(client, priv);
    input_dev = devm_input_allocate_device(&client->dev);
    if (!input_dev) {
        dev_err(&client->dev, "failed to allocate the input device\n");
        return -ENOMEM;
    }

    __set_bit(EV_KEY, input_dev->evbit);
    if (of_property_read_bool(client->dev.of_node, "autorepeat"))
        __set_bit(EV_REP, input_dev->evbit);

    input_dev->name = "apt32f101-key";
    input_dev->dev.parent = &client->dev;

    input_dev->id.bustype = BUS_I2C;
    input_dev->id.vendor = 0x0001;
    input_dev->id.product = 0x0001;
    input_dev->id.version = 0x0002;
    input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);

    //input_set_drvdata(input_dev, priv);

    //input_set_capability(input_dev, EV_KEY, KEY_HOME);


    for(i=0; i < sizeof(keymap)/sizeof(keymap_t); i++)
    {
        input_set_capability(input_dev, EV_KEY, keymap[i].key_code);
    }


    priv->input_dev = input_dev;
    priv_p = priv;
    ret = input_register_device(input_dev);
    if (ret) {
        dev_err(&client->dev, "failed to register input device\n");
        return ret;
    }


    // miscdevie init
    ret = misc_register(&apt_device);
    if(ret)
    {
        LOG_DBG(KERN_ERR"miscdevice register error.\n");
        return -1;
    }

    // timer init 
    init_timer(&systimer);
    systimer.function = &apt_timer_func;
    systimer.data = (unsigned long)&apt_device;;
    systimer.expires = jiffies + SYSTEM_STATUS_CHECK_TIME;
    add_timer(&systimer);
#ifdef D_SUPPORT_KTHREAD
    priv_p->pthread =  kthread_run(apt_thread_func, NULL, "apt32_thread-%d", 1);
    if (IS_ERR(priv_p->pthread)) {
        priv_p->pthread = NULL;
    }
#endif
    //write_r8d8(priv_p->client, SYSREG_LINUX_STARTED, D_ARM_LINUX_OS_STARTED_MAGIC);

    return 0;
}

static int aptf32101_remove(struct i2c_client *client)
{

LOG_DBG(KERN_INFO"aptf32101_remove\n");

#ifdef D_SUPPORT_KTHREAD    
    if (!IS_ERR(priv_p->pthread)) {
        kthread_stop(priv_p->pthread);
    }
#endif

    input_unregister_device(priv_p->input_dev);
    misc_deregister(&apt_device);
    del_timer(&systimer);

    return 0;

}

static const struct i2c_device_id aptf32101_id[] = {
    { "apt32f101"},
    { }
};

MODULE_DEVICE_TABLE(i2c, aptf32101_id);

static struct i2c_driver apt32f101_driver = {
    .driver = {
        .name    = "apt32f101xx",
        .of_match_table = of_match_ptr(of_apt32f101_match),
    },
    .probe        = aptf32101_probe,
    .remove        = aptf32101_remove,
    .id_table    = aptf32101_id,
};


static int __init apt32f101_init(void)
{
    int ret;
    LOG_DBG( KERN_INFO "apt32f101_init\n");
    ret = i2c_add_driver(&apt32f101_driver);
    return ret;
}

static void __exit apt32f101_exit(void)
{
    LOG_DBG( KERN_INFO "apt32f101_exit\n");
    return i2c_del_driver(&apt32f101_driver);
}

module_init(apt32f101_init);
module_exit(apt32f101_exit);

应用层操作的例子:

#include "voltameter.h"

unsigned short get_battery_val(int fd)
{
    unsigned short ret;
    ret = ioctl(fd, D_SYSSTATUS_GET_BAT, 0);
    if (ret < 0) {
        perror("ioctl gpt_hal_get_battery_val error.\n");
        ret = 0;
    }

    return ret;
}

int send_mcu_poweroff(int fd)
{
    int ret;
    ret = ioctl(fd, D_SYSSTATUS_DO_POWEROFF, 0);
    if (ret < 0) {
        perror("ioctl send_mcu_poweroff error.\n");
    }

    return ret;
}


int get_average(int fd)
{
    int buf_val[5]={0};
    int i = 0;
    int averrave_val =0;
    for(i= 0;i<5;i++)
    {
     buf_val[i] =get_battery_val(fd);
    }
    averrave_val = ( buf_val[0]+buf_val[1]+buf_val[2]+buf_val[3]+buf_val[4])/5;

    return averrave_val;

}


int val_class(int val)
{
    int battery_level = 0;
    if (val <= E_LEVEL_0) {            // 3.35V 低电量关机

        battery_level = E_BATTERY_LEVEL_0;

    } else if (val <= E_LEVEL_1 && val > E_LEVEL_0) { 

        battery_level = E_BATTERY_LEVEL_1;

    } else if (val <= E_LEVEL_2 && val >E_LEVEL_1 ) { // 3.65V

        battery_level = E_BATTERY_LEVEL_2;

    } else if (val <= E_LEVEL_3 && val > E_LEVEL_2 ) { // 3.73V

        battery_level = E_BATTERY_LEVEL_3;

    } else if (val <= E_LEVEL_4 && val > E_LEVEL_3) { // 3.87V

        battery_level = E_BATTERY_LEVEL_4;

    } else if (val <= E_LEVEL_5 && val > E_LEVEL_4) { // 4.20V

        battery_level = E_BATTERY_LEVEL_5;

    } else {

        battery_level = E_BATTERY_LEVEL_5;

    }

    return battery_level;

}

int main()
{
    int ret = -1;
    int val = 0;
    int buf_val[5]={0};
    int i= 0;
    int level = 0;
    //int motor_fd = 0;
    motor_fd = open(MOTOR_DEVICE_PATH, O_RDWR);
    if(motor_fd < 0)
    {
        printf("open /dev/motor error.\n");
        return -1;
    } else {
        printf("open %s success!\n", MOTOR_DEVICE_PATH);
    }

    while(1){
        for (i= 0;i< 5;i++){
            buf_val[i]= get_battery_val(motor_fd);
            val =val + buf_val[i];
            printf("this val[%d] =%d!\n", i,buf_val[i]);
        }
        val =( buf_val[0]+buf_val[1]+buf_val[2]+buf_val[3]+buf_val[4])/5;
        printf("average  val=%d!\n", val);
        level=val_class(val);
        printf("batterylevel=%d!\n", level);

    }
    return 0;

}

常见问题:

1 当有这种 log:"timeout, ipd: 0x00, state: 1"出现时,请检查硬件上拉是否给电;

2 如果调用 i2c_transfer 返回值为-6 时候,表示为 NACK 错误,即对方设备无应答响应,这种情况一般为外设的问题,常见的有以下几种情况

  • I2C 地址错误,解决方法是测量 I²C 波形,确认是否 I²C 设备地址错误
  • I2C slave 设备不处于正常工作状态,比如未给电,错误的上电时序等
  • 时序不符合 I2C slave 设备所要求也会产生 Nack 信号,比如下面的第三点

3 当外设对于读时序要求中间是 stop 信号不是 repeat start 信号的时候,需要调用两次 i2c_transfer, I²C read 拆分成两次