嵌入式开发记录-day31 I2C设备 -- 触摸屏、i2c子系统

1、准备

  1、写在前面的:
    1、在Linux下IIC设备以及总线‘配置都需要向8051那样需要使用IO产生特定的波形;
    2、需要在Linux的API中配置特殊设备地址,不同的设备有不同的地址;
    3、需要将IIC设备添加到Linux总线上;以及内核里面;
  2、linux内核API
    1、注册IIC设备:i2c_board_info // 按照其他格式仿写
    驱动注册及驱动卸载函数:i2c_add_driver、i2c_del_driver
    读写IIC设备函数:i2c_fransfer、i2c_msg
  3、查看已经生成好的设备地址:
    1、查看IIC设备地址:ls /sys/bus/i2c/devices/
    2、设备地址与原理图对应:3-0038(设备地址)---->I2C_3_SCL(addr:在datasheet中查找0x38)
    3、查看i2c设备的名称:cat /sys/bus/i2c/devices/3-0038/name

  4、烧写驱动前需要准备的:
    本次实验将触摸屏当做IIC设备,因此烧写驱动之前需要先把原来的驱动去掉,重新编译、烧写镜像;
    1、make menuconfig:Device Drivers-->input device support-->
    Touchscreens-->FT5X0X based touchscreens(去掉)
    vim arch/arm/mach-exynos/mach-itop4412.c 平台文件
  5、在平台文件下添加I2c设备:在i2c_devs3[]中添加
  {
    I2C_BOARD_INFO("i2c_test", 0x70>>1),
  },
  6、烧写镜像后,查看i2c设备名称cat /sys/bus/i2c/devices/3-0038/name 应该是i2c_test
  7、i2c驱动注册和卸载
  8、module_init()与late_initcall()异同点
    1、都是以模块的方式注册驱动
    2、module_init()加载的时间更早一点,late_initcall()加载时间更晚一些;

  9、本IIC教程支持7寸9寸的电容触摸,所以暂时做不了本实验、可以使用AT24C08存储器实验教程来实验

2、加载触摸屏幕IIC驱动

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include <linux/regulator/consumer.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <plat/ft5x0x_touch.h>

//i2c_test_probe
static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    printk("==%s:\n", __FUNCTION__);
    return 0;
}

static int __devexit i2c_test_remove(struct i2c_client *client)
{
    i2c_set_clientdata(client, NULL);
    printk("==%s:\n", __FUNCTION__);
    return 0;
}
// 
static const struct i2c_device_id i2c_test_id[] = {
    { "i2c_test", 0 },
    { }
};


// i2c驱动结构体
static struct i2c_driver i2c_test_driver = {
    .probe        = i2c_test_probe,  // 初始化加载函数
    .remove        = __devexit_p(i2c_test_remove), // 驱动卸载函数
    .id_table    = i2c_test_id,  // i2c设备IP地址列表
    .driver    = {
        .name    = "i2c_test",  // 驱动名称
        .owner    = THIS_MODULE, // 驱动所有者
    },
};


void i2c_io_init(void)
{
    // 主要完成触屏上电的一些初始化操作  其他的IIC设备一般不需要这一步骤
    int ret;    // GPL0_2---->屏幕IIC电平转换芯片使能端TP1_EN
    ret = gpio_request(EXYNOS4_GPL0(2), "TP1_EN");
    if (ret) {
        printk(KERN_ERR "failed to request TP1_EN for "
                "I2C control\n");
        //return err;
    }
    // 将GPL0_2端口设置为输出、并设置为输出模式
    /* gpio_set_valuce()  :只设置端口的电平
       gpio_direction_output:设置输出模式后,还会设置电平
    */
    gpio_direction_output(EXYNOS4_GPL0(2), 1);
    s3c_gpio_cfgpin(EXYNOS4_GPL0(2), S3C_GPIO_OUTPUT);  // 这句可以不用写
    gpio_free(EXYNOS4_GPL0(2));  // 假设释放后维持高电平
    mdelay(5);  // 延时5ms
    
    // GPL0_3---->XEINT3 // 配置与触屏的中断有关
    ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3");
    if (ret) {
        gpio_free(EXYNOS4_GPX0(3));
        ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3");
    if(ret)
    {
        printk("ft5xox: Failed to request GPX0_3 \n");
    }
    }
    // GPL0_3 拉低
    gpio_direction_output(EXYNOS4_GPX0(3), 0);
    mdelay(200); // 拉低200ms 忙等待200ms无法操作此GPIO
    gpio_direction_output(EXYNOS4_GPX0(3), 1);  // 拉高
    s3c_gpio_cfgpin(EXYNOS4_GPX0(3), S3C_GPIO_OUTPUT);
    gpio_free(EXYNOS4_GPX0(3));
    msleep(300);   // 
}

int i2c_test_init(void)
{
    printk("==%s:\n", __FUNCTION__);
    i2c_io_init();
    printk("==%s:\n", __FUNCTION__);
    return i2c_add_driver(&i2c_test_driver);
}

void i2c_text_exit(void)
{
    printk("==%s:\n", __FUNCTION__);
    i2c_del_driver(&i2c_test_driver);
}

// module_init(keyirq_init);
late_initcall(i2c_test_init);
module_exit(i2c_text_exit);
MODULE_LICENSE("Dual BSD/GPL");

3、编译测试

// 加载驱动之前 首先查看这句驱动名称,若不对则修改有问题,正常继续下面
cat /sys/bus/i2c/devices/3-0038/name  应该是i2c_test 

// 加载模块
[root@iTOP-4412]# insmod IICTest.ko                                                                                       
[  131.394724] ==i2c_test_init:
[  131.905048] ==i2c_test_init:
[  131.906483] ==i2c_test_probe:
[root@iTOP-4412]# rmmod IICTest                                                                                           
[  203.942878] ==i2c_text_exit:
[  203.944427] ==i2c_test_remove:

4、包含的头文件

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include <linux/regulator/consumer.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <plat/ft5x0x_touch.h>
View Code

 5、读取单个寄存器的值

  1、在操作从机(slave)的时候,需要说明是读操作还是写操作,并且说明读、些哪一个寄存器,需要传入一个地址;

  2、了解上面这个之后再继续

6、读取单个寄存器

  1 #include <linux/kernel.h>
  2 #include <linux/module.h>
  3 #include <linux/i2c.h>
  4 #include <linux/input.h>
  5 #include <linux/delay.h>
  6 #include <linux/slab.h>
  7 #include <linux/gpio.h>
  8 #include <linux/platform_device.h>
  9 #ifdef CONFIG_HAS_EARLYSUSPEND
 10 #include <linux/earlysuspend.h>
 11 #endif
 12 #include <linux/regulator/consumer.h>
 13 #include <mach/gpio.h>
 14 #include <plat/gpio-cfg.h>
 15 #include <plat/ft5x0x_touch.h>
 16 
 17 // 读寄存器数据
 18 static int i2c_test_read_reg(struct i2c_client *client,u8 addr, u8 *pdata) 
 19 {
 20     u8 buf1[4] = { 0 };
 21     u8 buf2[4] = { 0 };
 22     // 数据结构体初始化
 23     struct i2c_msg msgs[] = {
 24         {
 25             .addr    = client->addr,    //0x38
 26             .flags    = 0,    //
 27             .len    = 1,    // 要写的数据的长度
 28             .buf    = buf1, // 发送数据缓冲区
 29         },
 30         {
 31             .addr    = client->addr,
 32             .flags    = I2C_M_RD,/* read data, from slave to master */
 33             .len    = 1,      // 接收/读数据长度
 34             .buf    = buf2,   // 接收缓冲区
 35         },
 36     };
 37     int ret;
 38     buf1[0] = addr;   // 设备的固件地址0xa6    主机查找设备 根据i2c设备的固件地址?
 39     ret = i2c_transfer(client->adapter, msgs, 2);
 40     if (ret < 0) {
 41         pr_err("read reg (0x%02x) error, %d\n", addr, ret);
 42     } else {
 43         *pdata = buf2[0];  // 读取数据成功 将数据返回
 44     }
 45     return ret;
 46 }
 47 
 48 static int i2c_test_read_fw_reg(struct i2c_client *client,unsigned char* val)
 49 {
 50     int ret;
 51     *val = 0xff;   // 定义获取数据的临时变量
 52     ret = i2c_test_read_reg(client,0xa6, val);
 53     // 0xa6 i2c 设备的固件地址firmware address
 54     printk("ts reg 0xa6 val is %d\n",*val);
 55     return ret;
 56 }
 57 //i2c_test_probe
 58 // 从机地址 i2c设备的ID号
 59 static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id)
 60 {
 61     unsigned char val;
 62     printk("==%s:\n", __FUNCTION__);
 63     i2c_test_read_fw_reg(client,&val);  // 读取数据
 64     return 0;
 65 }
 66 // 传入一个从机地址
 67 static int __devexit i2c_test_remove(struct i2c_client *client)
 68 {
 69     i2c_set_clientdata(client, NULL);     // 设置从机的数据为空
 70     printk("==%s:\n", __FUNCTION__);
 71     return 0;
 72 }
 73 // 
 74 static const struct i2c_device_id i2c_test_id[] = {
 75     { "i2c_test", 0 },
 76     { }
 77 };
 78 
 79 // i2c驱动结构体
 80 static struct i2c_driver i2c_test_driver = {
 81     .probe        = i2c_test_probe,  // 初始化加载函数
 82     .remove        = __devexit_p(i2c_test_remove), // 驱动卸载函数
 83     .id_table    = i2c_test_id,  // i2c设备IP地址列表
 84     .driver    = {
 85         .name    = "i2c_test",  // 驱动名称
 86         .owner    = THIS_MODULE, // 驱动所有者
 87     },
 88 };
 89 static void i2c_io_init(void)
 90 {
 91     // 主要完成触屏上电的一些初始化操作  其他的IIC设备一般不需要这一步骤
 92     int ret;    // GPL0_2---->屏幕IIC电平转换芯片使能端TP1_EN
 93     ret = gpio_request(EXYNOS4_GPL0(2), "TP1_EN");
 94     if (ret) {
 95         printk(KERN_ERR "failed to request TP1_EN for "
 96                 "I2C control\n");
 97         //return err;
 98     }
 99     // 将GPL0_2端口设置为输出、并设置为输出模式
100     /* gpio_set_valuce()  :只设置端口的电平
101        gpio_direction_output:设置输出模式后,还会设置电平
102     */
103     gpio_direction_output(EXYNOS4_GPL0(2), 1);
104     s3c_gpio_cfgpin(EXYNOS4_GPL0(2), S3C_GPIO_OUTPUT);  // 这句可以不用写
105     gpio_free(EXYNOS4_GPL0(2));  // 假设释放后维持高电平
106     mdelay(5);  // 延时5ms
107     
108     // GPL0_3---->XEINT3 // 配置与触屏的中断有关
109     ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3");
110     if (ret) {
111         gpio_free(EXYNOS4_GPX0(3));
112         ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3");
113     if(ret)
114     {
115         printk("ft5xox: Failed to request GPX0_3 \n");
116     }
117     }
118     // GPL0_3 拉低
119     gpio_direction_output(EXYNOS4_GPX0(3), 0);
120     mdelay(200); // 拉低200ms 忙等待200ms无法操作此GPIO
121     gpio_direction_output(EXYNOS4_GPX0(3), 1);  // 拉高
122     s3c_gpio_cfgpin(EXYNOS4_GPX0(3), S3C_GPIO_OUTPUT);
123     gpio_free(EXYNOS4_GPX0(3));
124     msleep(300);   // 
125 }
126 
127 static int i2c_test_init(void)
128 {
129     printk("==%s:\n", __FUNCTION__);
130     i2c_io_init();
131     printk("==%s:\n", __FUNCTION__);
132     return i2c_add_driver(&i2c_test_driver);
133     
134 }
135 
136 void i2c_text_exit(void)
137 {
138     printk("==%s:\n", __FUNCTION__);
139     i2c_del_driver(&i2c_test_driver);
140 }
141 
142 // module_init(keyirq_init);
143 late_initcall(i2c_test_init);
144 module_exit(i2c_text_exit);
145 MODULE_LICENSE("Dual BSD/GPL");
View Code

7、其中用的结构体注释参考

https://www.cnblogs.com/deng-tao/p/6130080.html

8、对应用层提供访问接口

  1 #include <linux/kernel.h>
  2 #include <linux/module.h>
  3 #include <linux/i2c.h>
  4 #include <linux/input.h>
  5 #include <linux/delay.h>
  6 #include <linux/slab.h>
  7 #include <linux/gpio.h>
  8 #include <linux/platform_device.h>
  9 #ifdef CONFIG_HAS_EARLYSUSPEND
 10 #include <linux/earlysuspend.h>
 11 #endif
 12 #include <linux/regulator/consumer.h>
 13 #include <mach/gpio.h>
 14 #include <plat/gpio-cfg.h>
 15 #include <plat/ft5x0x_touch.h>
 16 
 17 struct i2c_client *this_client;
 18 // 读寄存器数据
 19 static int i2c_tes_read_reg(struct i2c_client *client,u8 addr, u8 *pdata) 
 20 {
 21     u8 buf1[4] = { 0 };
 22     u8 buf2[4] = { 0 };
 23     // 数据结构体初始化
 24     struct i2c_msg msgs[] = {
 25         {
 26             .addr    = client->addr,    //0x38
 27             .flags    = 0,    //
 28             .len    = 1,    // 要写的数据的长度
 29             .buf    = buf1, // 发送数据缓冲区
 30         },
 31         {
 32             .addr    = client->addr,
 33             .flags    = I2C_M_RD,/* read data, from slave to master */
 34             .len    = 1,      // 接收/读数据长度
 35             .buf    = buf2,   // 接收缓冲区
 36         },
 37     };
 38     int ret;
 39     buf1[0] = addr;   // 设备的固件地址0xa6    主机查找设备 根据i2c设备的固件地址?
 40     ret = i2c_transfer(client->adapter, msgs, 2);
 41     if (ret < 0) {
 42         pr_err("read reg (0x%02x) error, %d\n", addr, ret);
 43     } else {
 44         *pdata = buf2[0];  // 读取数据成功 将数据返回
 45     }
 46     return ret;
 47 }
 48 
 49 static void i2c_test_read_fw_reg(struct i2c_client *client,unsigned char* val)
 50 {
 51     int ret;
 52     *val = 0xff;   // 定义获取数据的临时变量
 53     ret = i2c_test_read_reg(client,0xa6, val);
 54     // 0xa6 i2c 设备的固件地址firmware address
 55     printk("ts reg 0xa6 val is %d\n",*val);
 56     return ret;
 57 }
 58 
 59 static int i2c_open_func(struct file *filp)
 60 {
 61     return 0;
 62 }
 63 
 64 static int i2c_release_func(struct file *filp)
 65 {
 66     return 0;
 67 }
 68 
 69 static ssize_t i2c_read_func(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
 70 {
 71     int ret;
 72     u8 reg_data;
 73     
 74     ret = copy_from_user(&reg_data,buffer,1);  // 获取要读那个寄存器的地址
 75     
 76     struct i2c_msg msgs[] = {
 77         {
 78             .addr    = this_client->addr,    //0x38
 79             .flags    = 0,    //
 80             .len    = 1,    //要写的数据的长度
 81             .buf    = &reg_data,         // 写入需要读的寄存器的地址
 82         },
 83         {
 84             .addr    = this_client->addr,
 85             .flags    = I2C_M_RD,    // 读操作
 86             .len    = 1,           // 读数据的长度
 87             .buf    = &reg_data,   // 读出的数据
 88         },
 89     };
 90     ret = i2c_transfer(this_client->adapter, msgs, 2);   // 返回应该2   表示2个msg传输成功
 91     if (ret < 0) {
 92         pr_err("read reg error!\n");
 93     }
 94     // 上传数据到用户
 95     ret = copy_to_user(buffer,&reg_data,1);
 96     
 97     return ret;
 98 }
 99 
100 // 写操作 先写一个寄存器的地址
101 // 再一个值
102 static ssize_t i2c_write_func(struct file *filp, char __user *buffer, size_t count, loff_t *ppos)
103 {
104     int ret;
105     u8 buf[2];
106     struct i2c_msg msgs[1];
107     
108     ret = copy_from_user(&buf,buffer,2);    // 从用户段获取写入寄存器的地址   以及数据
109     
110     msgs[0].addr    = this_client->addr;    //0x38
111     msgs[0].flags    = 0;    //
112     msgs[0].len    = 2;    //第一个是要写的寄存器地址,第二个是要写的内容
113     msgs[0].buf    = buf;  // 要写的地址数据
114 
115     ret = i2c_transfer(this_client->adapter, msgs, 1);  // ret == 1 表示传输1个msg 成功
116     if (ret < 0) {
117         pr_err("write reg 0x%02x error!\n",buf[0]);
118     }
119     // 猜测这句应该可以不用  待验证
120     ret = copy_to_user(buffer,buf,1);
121     
122     return ret;
123 }
124 
125 // 文件操作
126 static struct file_operations i2c_ops = {
127     .owner     = THIS_MODULE,
128     .open     = i2c_open_func,    // 打开
129     .release= i2c_release_func, // 释放
130     .write  = i2c_write_func,   // 写操作
131     .read     = i2c_read_func,    // 读操作
132 };
133 
134 // i2c杂项设备结构体
135 static struct miscdevice i2c_dev = {
136     .minor    = MISC_DYNAMIC_MINOR,
137     .fops    = &i2c_ops,
138     .name    = "i2c_control",
139 };
140 
141 //i2c_test_probe
142 // 从机地址 i2c设备的ID号
143 static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id)
144 {
145     unsigned char val;
146     this_client = client;
147     printk("==%s:\n", __FUNCTION__);
148     i2c_test_read_fw_reg(client,&val);  // 读取数据
149     
150     misc_register(&i2c_dev);   // 在想设备注册
151     
152     return 0;
153 }
154 // 传入一个从机地址
155 static int __devexit i2c_test_remove(struct i2c_client *client)
156 {
157     i2c_set_clientdata(client, NULL);
158     misc_deregister(&i2c_dev);       // 杂项设备卸载
159     printk("==%s:\n", __FUNCTION__);
160     return 0;
161 }
162 // 
163 static const struct i2c_device_id i2c_test_id[] = {
164     { "i2c_test", 0 },
165     { }
166 };
167 
168 
169 // i2c驱动结构体
170 static struct i2c_driver i2c_test_driver = {
171     .probe        = i2c_test_probe,  // 初始化加载函数
172     .remove        = __devexit_p(i2c_test_remove), // 驱动卸载函数
173     .id_table    = i2c_test_id,  // i2c设备IP地址列表
174     .driver    = {
175         .name    = "i2c_test",  // 驱动名称
176         .owner    = THIS_MODULE, // 驱动所有者
177     },
178 };
179 
180 
181 static void i2c_io_init(void)
182 {
183     // 主要完成触屏上电的一些初始化操作  其他的IIC设备一般不需要这一步骤
184     int ret;    // GPL0_2---->屏幕IIC电平转换芯片使能端TP1_EN
185     ret = gpio_request(EXYNOS4_GPL0(2), "TP1_EN");
186     if (ret) {
187         printk(KERN_ERR "failed to request TP1_EN for "
188                 "I2C control\n");
189         //return err;
190     }
191     // 将GPL0_2端口设置为输出、并设置为输出模式
192     /* gpio_set_valuce()  :只设置端口的电平
193        gpio_direction_output:设置输出模式后,还会设置电平
194     */
195     gpio_direction_output(EXYNOS4_GPL0(2), 1);
196     s3c_gpio_cfgpin(EXYNOS4_GPL0(2), S3C_GPIO_OUTPUT);  // 这句可以不用写
197     gpio_free(EXYNOS4_GPL0(2));  // 假设释放后维持高电平
198     mdelay(5);  // 延时5ms
199     
200     // GPL0_3---->XEINT3 // 配置与触屏的中断有关
201     ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3");
202     if (ret) {
203         gpio_free(EXYNOS4_GPX0(3));
204         ret = gpio_request(EXYNOS4_GPX0(3), "GPX0_3");
205     if(ret)
206     {
207         printk("ft5xox: Failed to request GPX0_3 \n");
208     }
209     }
210     // GPL0_3 拉低
211     gpio_direction_output(EXYNOS4_GPX0(3), 0);
212     mdelay(200); // 拉低200ms 忙等待200ms无法操作此GPIO
213     gpio_direction_output(EXYNOS4_GPX0(3), 1);  // 拉高
214     s3c_gpio_cfgpin(EXYNOS4_GPX0(3), S3C_GPIO_OUTPUT);
215     gpio_free(EXYNOS4_GPX0(3));
216     msleep(300);   // 
217 }
218 
219 static int i2c_test_init(void)
220 {
221     printk("==%s:\n", __FUNCTION__);
222     i2c_io_init();
223     printk("==%s:\n", __FUNCTION__);
224     return i2c_add_driver(&i2c_test_driver);
225     
226 }
227 
228 static void i2c_text_exit(void)
229 {
230     printk("==%s:\n", __FUNCTION__);
231     i2c_del_driver(&i2c_test_driver);
232 }
233 
234 // module_init(keyirq_init);
235 late_initcall(i2c_test_init);
236 module_exit(i2c_text_exit);
237 MODULE_LICENSE("Dual BSD/GPL");
View Code

  这个主要注意读写操作的逻辑顺序

9、其他注释

  给应用提供读取i2c设备的接口:
  1、在probe函数中注册杂项设备,连接到文件操作结构体,实现对i2c设备的打开读写操作
  2、 int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
  参考这篇文章
  https://www.cnblogs.com/jiangzhaowei/p/10906800.html

10、应用读写IIC设备

 1 #include <stdio.h>
 2 
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 6 #include <unistd.h>
 7 #include <sys/ioctl.h>
 8 
 9 int main(int argc,char **argv)
10 {
11     int fd,ret;
12     char *i2c_device = "/dev/i2c_control";
13     unsigned char buffer[2];
14     
15     printf("open %s!\n",i2c_device);
16     if((fd = open(i2c_device,O_RDWR|O_NDELAY))<0)
17         printf("APP open %s failed",i2c_device);
18     else{
19         printf("APP open %s success!\n",i2c_device);
20     }
21     
22 //读一个数据0xa6
23     buffer[0] = 0xa6;
24     ret = read(fd,buffer,1);
25     if(ret<0)
26         printf("i2c read failed!\n");
27     else{
28         printf("i2c read reg 0xa6 data is 0x%02x!\n",buffer[0]);
29     }
30     
31 //01先从0x00读出一个数据,02写一个数据到0x00,03再读出来对比
32     //01
33     buffer[0] = 0x00;
34     read(fd,buffer,1);
35     printf("i2c read reg 0x00 data is 0x%02x!\n",buffer[0]);
36     //02
37     buffer[0] = 0x00;
38     buffer[1] = 0x40;
39     ret = write(fd,buffer,2);
40     if(ret<0){
41         printf("i2c write failed!\n");
42         goto exit;
43     }
44     //03
45     buffer[0] = 0x00;
46     read(fd,buffer,1);
47     printf("i2c read reg 0x00 data is 0x%02x!\n",buffer[0]);
48     
49     close(fd);
50     
51 exit:
52     close(fd);
53     return -1;
54 }

11、I2C子系统:

  首先参考:14、i2c子系统 - Lioker - 博客园 (cnblogs.com)

 

posted @ 2020-08-18 22:14  笑不出花的旦旦  阅读(270)  评论(0编辑  收藏  举报