概述:

1.I2C概念

2.I2C硬件结构图

3.I2C总线初始化

4.I2C控制器device 节点添加及driver注册

5.I2C设备节点添加及driver注册

5.adapter设备及驱动添加要点及绑定过程

6.client设备及驱动添加要点及绑定过程

7.设备是如何使用I2C通讯的

一.I2C概念:

  I2C是philips提出的外设总线.

  I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线。

  因此,I2C总线被非常广泛地应用在EEPROM,实时钟,小型LCD等设备与CPU的接口中

二.I2C硬件结构图

2.1 I2C组织结构:

当前很多ARM内部封装了I2C控制芯片,通过cpu引脚会将控制芯片相关接口暴露出来,通过查看CPU引脚手册会发现存在几组I2C控制引脚(从软件角度讲一个控制器对应一个adapter),将支持I2C设备的SDA与SCL与其中一个控制芯片的对应引脚连接在一起即从硬件上将设备挂载到了I2C控制芯片上,cpu即可通过软件的相关操作来与设备进行通信了

2.2 I2c设备挂载图

2.3 控制器电路图(已集成进cpu内部)

2.3 设备电路图(以RTC为例)

I2C设备分两类:(当前RTC芯片支持I2C通信)

1.采用GPIO模拟

2.设备支持I2C即存在SDA和SCL引脚

如图2.2所示,控制器与设备是一对多的关系,那么控制器是如何寻找特定的设备呢?

从控制器的原理图我们看出控制器实际上只有SCL与SDA两个引脚,针对SDA采用分时复用,在和设备通讯时首先通过SDA发送设备的地址,然后再发送和设备交互的数据,此即与I2C协议挂钩了

三.I2C总线初始化

I2C总线核心在于i2c-core.c,其注册方式与一般的总线注册方式一样,唯一的区别在于我们注册了一个空的dummy_driver,具体原因留给读者自己分析。

注意I2c总线初始化调用的是postcore_initcall(i2c_init);,查看其具体的宏我们得知其优先级为2,请留意该优先级,因为与后续的I2c设备驱动注册优先级存在强相关,必须要先有I2c总线,才能将i2c设备挂载到I2c总线上(稍后我们进行具体分析)

#define pure_initcall(fn)          __define_initcall("0",fn,0)#define core_initcall(fn)          __define_initcall("1",fn,1)#define core_initcall_sync(fn)        __define_initcall("1s",fn,1s)#define postcore_initcall(fn)           __define_initcall("2",fn,2)#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)#define arch_initcall(fn)          __define_initcall("3",fn,3)#define arch_initcall_sync(fn)        __define_initcall("3s",fn,3s)#define subsys_initcall(fn)              __define_initcall("4",fn,4)#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)#define fs_initcall(fn)                     __define_initcall("5",fn,5)#define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)#define rootfs_initcall(fn)        __define_initcall("rootfs",fn,rootfs)#define device_initcall(fn)              __define_initcall("6",fn,6)#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)#define late_initcall(fn)           __define_initcall("7",fn,7)#define late_initcall_sync(fn)         __define_initcall("7s",fn,7s)

分析i2c_init,其只做了2件事,其一添加了一个空的driver,其二想内核注册了I2c总线,至此I2c总线注册完成(so easy!)

注册总线做了哪些事情呢:

分析bus_register得知,申请了一个i2c总线的结构体,并将父节点设置为bus,同时将i2c总线结构体挂入bus的维护链表,后续通过遍历该维护链表即可得知支持哪些总线,在sys/bus目录下创建i2c,i2c/driver,i2c/devices目录

//i2c-core.c
struct bus_type i2c_bus_type = {.name      = "i2c",.match       = i2c_device_match,.probe      = i2c_device_probe,.remove     = i2c_device_remove,.shutdown  = i2c_device_shutdown,
};
static struct i2c_driver dummy_driver = {.driver.name  = "dummy",.probe     = dummy_probe,.remove      = dummy_remove,.id_table   = dummy_id,
};
static int __init i2c_init(void)
{int retval;retval = of_alias_get_highest_id("i2c");down_write(&__i2c_board_lock);if (retval >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = retval + 1;up_write(&__i2c_board_lock);retval = bus_register(&i2c_bus_type);if (retval)return retval;is_registered = true;#ifdef CONFIG_I2C_COMPATi2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;}
#endifretval = i2c_add_driver(&dummy_driver);if (retval)goto class_err;if (IS_ENABLED(CONFIG_OF_DYNAMIC))WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));return 0;class_err:
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endifis_registered = false;bus_unregister(&i2c_bus_type);return retval;
}static void __exit i2c_exit(void)
{if (IS_ENABLED(CONFIG_OF_DYNAMIC))WARN_ON(of_reconfig_notifier_unregister(&i2c_of_notifier));i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
#endifbus_unregister(&i2c_bus_type);tracepoint_synchronize_unregister();
}/* We must initialize early, because some subsystems register i2c drivers* in subsys_initcall() code, but are linked (and initialized) before i2c.*/
postcore_initcall(i2c_init);
module_exit(i2c_exit);

Notes:查看i2c_core.c,看似十分庞大,其实只有3块,smbus(无需关注),i2c总线注册,i2c适配接口,其中后面2者才是我们需要关注的重点

四.I2C控制器device 节点添加及driver注册

4.1 控制器device节点添加入内核

文件路径:arch/arm/mach-s3c24xx/mach-mini2440.c

以platform总线形式注册了i2c的控制器adapter

调用流程图:注意体会arch_initcall与i2c驱动注册调用的postcore_initcall优先级别

arch_initcall(customize_machine);-customize_machine()-mini2440_init()-s3c_i2c0_set_platdata()-s3c_set_platdata()-i2c_register_board_info()-platform_add_devices()-platform_device_register()-device_initialize()-arch_setup_pdev_archdata()-platform_device_add()-device_add()

4.1.2 相关结构体分析

//设备的资源信息,例如设备的地址,中断号等
static struct resource s3c_i2c0_resource[] = {    //adapter设备对应的物理内存及内存空间,对应datasheet上模块的物理地址的基地址//用处:在驱动初始化硬件信息时用于对相关寄存器设置[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),//datasheet上划分给该adapter对应的中断号[1] = DEFINE_RES_IRQ(IRQ_IIC),
};
//adapter设备信息,如支持多个控制器,则下列为设备信息数组,driver侧也应该位数组
struct platform_device s3c_device_i2c0 = {.name        = "s3c2410-i2c",//设备名,用于驱动加载时匹配.id        = 0,//adapter标签,如支持多个控制器,则均有属于自己不同的id编号.num_resources    = ARRAY_SIZE(s3c_i2c0_resource),.resource    = s3c_i2c0_resource,
};
//adapter控制器通用设置,如具体控制器不同导致的差异可在自身资源再次标注后面予以修正
struct s3c2410_platform_i2c default_i2c_data __initdata = {.flags      = 0,.slave_addr    = 0x10,//i2c控制器作为从机时地址.frequency   = 100*1000,//时钟频率为100khz.sda_delay = 100,//sda间隔时间
};

4.1.3 s3c_i2c0_set_platdata分析

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
//1.将作为s3c芯片的特定硬件资源信息s3c_device_i2c0注入平台资源s3c_device_i2c0的dev->platform_data中
//2.将平台对应的gpio配置函数注入struct s3c2410_platform_i2c *npd;

if (!pd) {pd = &default_i2c_data;pd->bus_num = 0;}npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c), &s3c_device_i2c0);if (!npd->cfg_gpio)npd->cfg_gpio = s3c_i2c0_cfg_gpio;}

4.1.3 i2c_register_board_info分析

i2c_register_board_info()函数的for循环中,首先会申请I2C设备信息结构体,如果申请成功,将I2C总线号和设备信息赋值给设备信息结构体,并且将设备信息结构体的链表插入到__i2c_board_list中,此处尤为重要,在本文的开头中所提的函数i2c_scan_static_board_info(adap);,此函数就是通过__i2c_board_list链表找到上面注册的设备信息,结合gsc3280_i2c_devices_init()函数和i2c_devices_info结构体,此处for循环的len为3,即正常情况下需要创建三个devinfo结构体,for循环结束后,__i2c_board_list链表中也就有了三个I2C设备的链表项,在程序的其他地方如果需要使用这里注册的设备结构信息,只需要遍历链表__i2c_board_list,通过总线号即可找到相应的设备信息.此一般在板级初始化中调用

i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{int status;down_write(&__i2c_board_lock);/* dynamic bus numbers will be assigned after the last static one */if (busnum >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = busnum + 1;for (status = 0; len; len--, info++) {struct i2c_devinfo  *devinfo;devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");status = -ENOMEM;break;}devinfo->busnum = busnum;devinfo->board_info = *info;list_add_tail(&devinfo->list, &__i2c_board_list);}up_write(&__i2c_board_lock);return status;
}

4.1.3 platform_add_devices分析

adapter以平台总线方式添加,再次将所有挂在在平台总线上的设备逐一加入

static struct platform_device *mini2440_devices[] __initdata = {&s3c_device_ohci,&s3c_device_wdt,&s3c_device_i2c0,&s3c_device_rtc,&s3c_device_usbgadget,&mini2440_device_eth,&mini2440_led1,&mini2440_led2,&mini2440_led3,&mini2440_led4,&mini2440_button_device,&s3c_device_nand,&s3c_device_sdi,&s3c_device_iis,&uda1340_codec,&mini2440_audio,
};
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

Notes:platform_add_devices详见平台总线章节

4.2 控制器adapter驱动注册

文件路径:kernel/drivers/i2c/busses/i2c-s3c2410.c

驱动开发都是基于面向对象的思想的, 内核虽然给我们封装了很多"类", 但当我们开发一个具体的驱动的时候, 还是要对其进行"继承", 进而创建针对具体设备的资源对象, 资源对象管理着驱动中诸多函数的共用资源, 是整个驱动运行过程中资源管理者与桥梁, 主要包括:内核类+资源(io, irq,时钟, 寄存器)+状态表示+其他,所以, 设计驱动的工作中很重要的一个工作就是"设计资源类". 下面就是三星设计的类, 我把次要的部分剔除了资源类,资源对象是整个驱动运作的核心, 所有的方法需要的资源都是对这个对象的操作, 它的设计是迭代的过程, 但当整个框架搭起来之后, 不应该有大的变化

struct s3c24xx_i2c {wait_queue_head_t    wait;unsigned int            quirks;unsigned int        suspended:1;struct i2c_msg      *msg;//收到的i2c-core.c发送过来的i2c_msg对象数组首地址unsigned int     msg_num;//i2c_msg数组的元素个数unsigned int        msg_idx;//i2c_msg数组元素的索引unsigned int        msg_ptr;unsigned int        tx_setup;unsigned int       irq;//使用的中断号enum s3c24xx_i2c_state  state;//当前控制器的状态, 用枚举量表示STATE_IDLE, STATE_START,STATE_READ,STATE_WRITE,STATE_STOPunsigned long      clkrate;//时钟频率void __iomem      *regs;struct clk        *clk;//时钟struct device      *dev;//属于device, 按照device来管理struct i2c_adapter  adap;//构造并使用的i2c_adapter对象, 和上一篇的框架图对应struct s3c2410_platform_i2c   *pdata;//封装的平台信息, 是一个数组首地址, 每一个元素包括从机地址, 标志位, 总线编号等int          gpios[2];struct pinctrl          *pctrl;
#ifdef CONFIG_CPU_FREQstruct notifier_block freq_transition;
#endif
};
static struct platform_device_id s3c24xx_driver_ids[] = {{.name        = "s3c2410-i2c",.driver_data    = 0,}, {.name        = "s3c2440-i2c",.driver_data    = QUIRK_S3C2440,}, {.name        = "s3c2440-hdmiphy-i2c",.driver_data    = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,}, { },
};
static struct platform_driver s3c24xx_i2c_driver = {.probe     = s3c24xx_i2c_probe,.remove        = s3c24xx_i2c_remove,.id_table = s3c24xx_driver_ids,.driver       = {.owner  = THIS_MODULE,.name    = "s3c-i2c",.pm  = S3C24XX_DEV_PM_OPS,.of_match_table = of_match_ptr(s3c24xx_i2c_match),},
};static int __init i2c_adap_s3c_init(void)
{return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

s3c24xx_i2c_probe:

adapter为控制器实际的初始化执行实体,其具体的调用流程如下:

subsys_initcall(i2c_adap_s3c_init);-i2c_adap_s3c_init()-platform_driver_register()-driver_register()-bus_add_driver()-driver_attach()-bus_for_each_dev()-__driver_attach()-driver_match_device-drv->bus->match//(platform_bus_type.match        = platform_match)-platform_match_id()//如存在多个adpater,则逐一比较列表各成员找到匹配项目(s3c24xx_driver_ids包含3个)-strcmp(pdev->name, drv->name)//如只有1个i2c adpater则直接比较name-driver_probe_device()-really_probe()-dev->bus->probe(dev)//(i2c_bus_type.probe        = i2c_device_probe)-i2c_device_probe()-driver->probe()//(s3c24xx_i2c_driver.probe        = s3c24xx_i2c_probe)-s3c24xx_i2c_probe()

4.2.1 i2c_adap_s3c_init分析

功能:以平台总线方式注册adapter驱动

static int __init i2c_adap_s3c_init(void)
{return platform_driver_register(&s3c24xx_i2c_driver);
}

4.2.2 i2c_adap_s3c_init分析

int platform_driver_register(struct platform_driver *drv)
{drv->driver.bus = &platform_bus_type;//绑定总线类别,主要注意name,dev_attrs,matchif (drv->probe) //当前已经被填充,不会再次填充drv->driver.probe = platform_drv_probe;if (drv->remove)//当前已经被填充,不会再次填充drv->driver.remove = platform_drv_remove;if (drv->shutdown)//需要填充drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}

4.2.3 bus_add_driver分析

int bus_add_driver(struct device_driver *drv)
{if (drv->bus->p->drivers_autoprobe) {error = driver_attach(drv);//加载驱动if (error)goto out_unregister;}      //创建sys子系统下文件目录module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {......}error = driver_add_attrs(bus, drv);if (error) { ......}
}

4.2.4 bus_add_driver分析

int driver_attach(struct device_driver *drv)
{//attach链路上所有驱动return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

4.2.5 bus_add_driver分析

static int __driver_attach(struct device *dev, void *data)
{......//用name来比较看之前是否有此驱动对应的设备添加进来,如发现匹配则进如driver内设备初始化函数即device_driver.probe()if (!driver_match_device(drv, dev))return 0;......if (!dev->driver)driver_probe_device(drv, dev);......return 0;
}

4.2.6 driver_probe_device 分析

int driver_probe_device(struct device_driver *drv, struct device *dev)
{......ret = really_probe(dev, drv);//只是简单的封装really_probe......return ret;
}

4.2.7 really_probe  分析

platform_bus_type

struct bus_type platform_bus_type = {.name      = "platform",.dev_attrs  = platform_dev_attrs,.match        = platform_match,.uevent       = platform_uevent,.pm      = &platform_dev_pm_ops,
};

static int really_probe(struct device *dev, struct device_driver *drv)

{......
     //如挂在总线上,先经总线的probe转一把再进入对应设备的driver->probeif (dev->bus->probe) {ret = dev->bus->probe(dev);//,当前我们挂在plat_form总线上,如上所示其probe为NULLif (ret)goto probe_failed;

else if (drv->probe) {
        //直接进入设备对应driver->proberet = drv->probe(dev);
        if (ret)goto probe_failed;} 
             .....
        return ret;}

4.2.7 s3c24xx_i2c_probe  分析

设备:硬件设备物理信息的封装

驱动:软件层次对硬件物理信息的设置

由此可见,驱动最终是对硬件信息的获取以及对硬件寄存器的设置,分层的主要原因就是解耦合,进入此函数前请仔细回顾devices的添加,此处操作硬件资源与其息息相关

static int s3c24xx_i2c_probe(struct platform_device *pdev)//即为4.1.2中s3c_device_i2c0
{struct s3c24xx_i2c *i2c;struct s3c2410_platform_i2c *pdata = NULL;struct resource *res;int ret;//表示设备是否通过设备树添加方式获得if (!pdev->dev.of_node) {pdata = pdev->dev.platform_data;//查看4.1.3得知编写设备文件时设置为platform_data = default_i2c_dataif (!pdata) {dev_err(&pdev->dev, "no platform data\n");return -EINVAL;}}//pdev->dev是device类型, 以它为的detach为标志分配一个我们自己的对象的空间并将分配的首地址返回给i2c。 这里使用的是devm_kzalloc(),函数 devm_kzalloc()和kzalloc()一样都是内核内存分配函数,但是devm_kzalloc()是跟设备(device)有关的,当设备(device)被detached或者驱动(driver)卸载(unloaded)时,内存会被自动释放。另外,当内存不在使用时,可以使用函数devm_kfree()释放。而kzalloc()则需要手动释放(使用kfree()),但如果工程师检查不仔细,则有可能造成内存泄漏i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);if (!i2c) {dev_err(&pdev->dev, "no memory for state\n");return -ENOMEM;}i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);if (!i2c->pdata) {dev_err(&pdev->dev, "no memory for platform data\n");return -ENOMEM;}//如果在--1079--中获得了相应的s3c2410_platform_i2c对象地址,就将其拷贝到资源对象中的相应的域存起来,否则自己去设备树中找i2c->quirks = s3c24xx_get_device_quirks(pdev);if (pdata)memcpy(i2c->pdata, pdata, sizeof(*pdata));elses3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);//获取slave_addr,frequency,sda_delay信息(详见4.1.2default_i2c_data)//使用赋值的方式直接对一部分资源对象的域进行初始化strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));i2c->adap.owner   = THIS_MODULE;i2c->adap.algo    = &s3c24xx_i2c_algorithm;//控制器的收发送函数i2c->adap.retries = 2;i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;i2c->tx_setup     = 50;//初始化资源对象中的等待队列头wait_queue_head_t waitinit_waitqueue_head(&i2c->wait);/* find the clock and enable it *///初始化资源对象中的device devi2c->dev = &pdev->dev;//初始化资源对象中的struct clki2c->clk = devm_clk_get(&pdev->dev, "i2c");if (IS_ERR(i2c->clk)) {dev_err(&pdev->dev, "cannot get clock\n");return -ENOENT;}dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);/* map the registers *///获取pdev中的地址resource, ioremap之后用于初始化资源对象中的regs域, 使用的是devm_ioremap_resource(), 同样是基于device的资源自动回收APIres = platform_get_resource(pdev, IORESOURCE_MEM, 0);i2c->regs = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(i2c->regs))return PTR_ERR(i2c->regs);dev_dbg(&pdev->dev, "registers %p (%p)\n",i2c->regs, res);/* setup info block for the i2c core *///将自定义资源对象指针藏到algo_data中, 和--1203--的作用一样, 给xfer()接口函数用i2c->adap.algo_data = i2c;//初始化资源对象中的i2c_adapter对象中的部分成员, 指定其父设备是控制器设备的device域i2c->adap.dev.parent = &pdev->dev;//初始化资源对象中的pctrl域, 使用的是devm_pinctrl_get_select_default()i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);/* inititalise the i2c gpio lines *///使用to_platform_device(其实就是container_of)通过i2c->dev找到包含它的platform_device对象, 回调cfg_gpio()函数, 配置GPIO引脚(见4.1.3)if (i2c->pdata->cfg_gpio) {i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));} else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) {return -EINVAL;}/* initialise the i2c controller *///初始化时钟clk_prepare_enable(i2c->clk);//初始化adapter相关寄存器ret = s3c24xx_i2c_init(i2c);clk_disable_unprepare(i2c->clk);if (ret != 0) {dev_err(&pdev->dev, "I2C controller init failed\n");return ret;}/* find the IRQ for this unit (note, this relies on the init call to* ensure no current IRQs pending*///获取中断资源i2c->irq = ret = platform_get_irq(pdev, 0);if (ret <= 0) {dev_err(&pdev->dev, "cannot find IRQ\n");return ret;}//注册中断, devm_request_irqret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0,dev_name(&pdev->dev), i2c);if (ret != 0) {dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);return ret;}ret = s3c24xx_i2c_register_cpufreq(i2c);if (ret < 0) {dev_err(&pdev->dev, "failed to register cpufreq notifier\n");return ret;}/* Note, previous versions of the driver used i2c_add_adapter()* to add the bus at any number. We now pass the bus number via* the platform data, so if unset it will now default to always* being bus 0.*/初始化i2c->adap对象, 总线编号是来自于设备(4.1.3 bus_num)i2c->adap.nr = i2c->pdata->bus_num;i2c->adap.dev.of_node = pdev->dev.of_node;ret = i2c_add_numbered_adapter(&i2c->adap);if (ret < 0) {dev_err(&pdev->dev, "failed to add bus to i2c core\n");s3c24xx_i2c_deregister_cpufreq(i2c);return ret;}//将构造的adapter对象注册到内核of_i2c_register_devices(&i2c->adap);//设置私有数据, pdev->dev->p->driver_data = i2c; 由于i2c->dev==pdev->dev, 所以其实就是将资源对象的首地址赋值给藏到device->device_private->driver_data中, 因为所有的接口都是使用platform_device作为形参的, 这种方法可以方便的找到自定义资源对象, 所以才叫void * driver_dataplatform_set_drvdata(pdev, i2c);//设置dev的电源管理pm_runtime_enable(&pdev->dev);//设置adap的电源管理pm_runtime_enable(&i2c->adap.dev);dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));return 0;
}

Linux I2C总线详解相关推荐

  1. AUTOSAR从入门到精通100讲(一)-SPI、UART、I2C总线详解

    当您将微控制器连接到传感器,显示器或其他模块时,您是否考虑过这两种设备是如何相互通信的?他们到底在说什么? 事实上电子设备之间的通信就像人类之间的交流,双方都需要说相同的语言.在电子产品中,这些语言称 ...

  2. linux驱动之总线详解

    linux驱动 第一章 linux驱动之设备与驱动 第二章 linux驱动之设备树与GPIO子系统 linux驱动之总线详解 linux驱动 一.总线bus 1.bus在linux中文件结构 2.bu ...

  3. SPI、I2C、UART(即串口)三种串行总线详解

    以下内容均来源于网络资源的学习与整理,如有侵权请告知删除. 参考博客 几个串口协议学习整理 UART IIC SPI_mainn的博客-CSDN博客 SPI.I2C.UART三种串行总线的原理.区别及 ...

  4. linux内核I2C子系统详解

    1.I2C通信协议 参考博客:<I2C通信协议详解和通信流程分析>:https://csdnimg.cn/release/blogv2/dist/pc/themesSkin/skin3-t ...

  5. 非常好的Linux编译内核详解 - -

    转载: http://blog.chinaunix.net/uid-263488-id-2138150.html 非常好的Linux编译内核详解 - - 一.内核简介  内核,是一个操作系统的核心.它 ...

  6. Linux /dev目录详解和Linux系统各个目录的作用

    Linux /dev目录详解和Linux系统各个目录的作用 标签: linuxtcpfunctionclassfirefoxtimer 2012-01-11 23:08 45517人阅读 评论(2) ...

  7. FPGA学习之路—接口(2)—I2C协议详解+Verilog源码分析

    FPGA学习之路--I2C协议详解+Verilog源码分析 定义 I2C Bus(Inter-Integrated Circuit Bus) 最早是由Philips半导体(现被NXP收购)开发的两线时 ...

  8. RS485通信总线详解

    RS485 总线详解 RS-485 是美国电子工业协会(EIA)在 1983 年批准了一个新的平衡传输标准(Balanced Transmission Standard)也称作差分,EIA 刚开始将 ...

  9. Linux常用命令详解(最全)

    Linux命令目录 Linux命令集合 系统信息 关机 (系统的关机.重启以及登出 ) 文件和目录 文件搜索 挂载一个文件系统 磁盘空间 用户和群组 文件的权限 - 使用 "+" ...

最新文章

  1. JS高级程序设计拾遗
  2. CentOS 6.7 RPM安装MySQL
  3. Android重写FragmentTabHost来实现状态保存
  4. ApplicationContext||ApplicationContext与BeanFactory的区别||SpringContextUtil类的作用
  5. caffe 中的一些参数介绍
  6. Java 8 Stream示例
  7. AI读懂两千年前文字,登上Nature封面,惊艳历史学家
  8. Vue实例和生命周期 1
  9. 数据挖掘与数据分析的区别是什么
  10. 声艺数字调音台si说明书32路_Soundcraft 声艺 Si Impact 数字调音台 32路数字调音台...
  11. 串口RS232的学习
  12. KNN算法以及R语言的实现
  13. 应广PFS122单片机比较器测供电电源VDD电压带临界点消抖处理
  14. 技嘉1080显卡体质测试软件,技嘉GTX1080 Xtreme Gaming显卡评测:创新的重峦式三风扇...
  15. oj 中G++和C++区别
  16. 将时间戳转换为日期格式:moment、new Date()
  17. Shortest Prefixes(字典树)
  18. @NotNull, @NotEmpty和@NotBlank之间的区别是什么?
  19. iphone控制中心自定义没有计算机,iphone6的控制中心里头没有airplay在哪里可以修改...
  20. Django 中celery的使用

热门文章

  1. 在 MaCA 根目录运行所有 py 文件
  2. 搞笑而富有哲理,看完后一个字——————“爽”
  3. Sizzle选择器揭秘--Sizzle过滤器
  4. 线性关系和非线性关系异或与非线性关系
  5. C语言入门教程之一变量和常量
  6. CLIP(Learning Transferable Visual Models From Natural Language Supervision)
  7. 三人表决器与八人抢答器实验
  8. “人工智能基础”课程笔记
  9. DataGrip连接MySQL报错: Server returns invalid timezone. Go to ‘Advanced‘ tab and set ‘serverTimezon
  10. OpenGL---GLUT教程(五) GLUT键盘控制