详细讲解RT-Thread I2C设备驱动框架及相关函数
I2C串行总线的组成及工作原理
I2C串行总线的组成及工作原理I2C是一种常用的串行通信协议,用于在电子设备之间进行数据传输。
它的全称是Inter-Integrated Circuit,即片间串行总线。
1. 主设备(Master Device):负责发起通信请求并控制整个传输过程的设备。
主设备通常是微控制器、处理器或其他智能设备。
2. 从设备(Slave Device):被主设备控制的设备。
从设备可以是各种外围设备,如传感器、存储器、显示器等。
3. SDA(Serial Data Line):用于数据传输的双向串行数据线。
主设备和从设备都可以发送和接收数据。
4. SCL(Serial Clock Line):用于同步数据传输的时钟线。
主设备产生时钟信号来同步数据传输。
5. VCC(Supply Voltage):提供电源电压给I2C总线上的设备。
6. GND(Ground):提供共地连接。
I2C总线的工作原理如下:1.初始化:主设备发起一次总线初始化,在I2C总线上产生一个启动信号。
启动信号表示I2C总线上有新的数据传输将开始。
2.寻址:主设备发送一个7位的设备地址到总线上指定要与之通信的从设备。
I2C总线上可以存在多个从设备,每个设备都有唯一的地址。
3.数据传输:主设备发送数据或者命令到从设备,或者从设备向主设备发送数据回复。
数据通过SDA线传输,时钟通过SCL线提供。
4.确认(ACK):数据传输完成后,每个接收设备都会回复一个确认信号,表示它已经成功接收数据。
主设备和从设备都可以发送确认信号。
5.停止:主设备发送一个停止信号来结束一次数据传输过程。
停止信号表示I2C总线上没有更多的数据传输。
I2C总线的工作原理是基于主从结构的,主设备控制数据传输的流程。
主设备通过发送启动信号来开始一个数据传输过程,并通过发送设备地址和数据来与特定的从设备进行通信。
通过SCL线的时钟同步,主设备和从设备可以准确地进行数据传输,避免了数据丢失和冲突。
I2C架构的详细分析
I2c是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而I2C 是基于multi master机制.一同总线上可允许多个master.关于I2C协议的知识,这里不再赘述.可自行下载spec 阅读即可.二:I2C架构概述在linux中,I2C驱动架构如下所示:如上图所示,每一条I2C对应一个adapter.在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作(struct i2c_adapter).再通过i2c core层将i2c设备与i2c adapter关联起来.这个图只是提供了一个大概的框架.在下面的代码分析中,从下至上的来分析这个框架图.以下的代码分析是基于linux 2.6.26.分析的代码基本位于:linux-2.6.26.3/drivers/i2c/位置.I2C-mt6516.c三:adapter注册在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.分别来看一下这两个函数的代码:int i2c_add_adapter(struct i2c_adapter *adapter){int id, res = 0;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lock);/* "above" here means "above or equal to", sigh */res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);mutex_unlock(&core_lock);if (res < 0) {if (res == -EAGAIN)goto retry;return res;}adapter->nr = id;return i2c_register_adapter(adapter);}在这里涉及到一个idr结构.idr结构本来是为了配合page cache中的radix tree而设计的.在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存.避免在内存不够的时候出现问题.所在,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id.以后凭这个id就可以在idr中找到相对应的结构了.对这个数据结构操作不太理解的可以查阅本站<<linux文件系统之文件的读写>>中有关radix tree的分析.注意一下idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的参数的含义,它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于__i2c_first_dynamic_bus_num,然后将对应的id号存放在adapter->nr中.调用i2c_register_adapter(adapter)对这个adapter进行进一步注册.看一下另外一人注册函数: i2c_add_numbered_adapter( ),如下所示:int i2c_add_numbered_adapter(struct i2c_adapter *adap){int id;int status;if (adap->nr & ~MAX_ID_MASK)return -EINVAL;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lock);/* "above" here means "above or equal to", sigh;* we need the "equal to" result to force the result*/status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);if (status == 0 && id != adap->nr) {status = -EBUSY;idr_remove(&i2c_adapter_idr, id);}mutex_unlock(&core_lock);if (status == -EAGAIN)goto retry;if (status == 0)status = i2c_register_adapter(adap);return status;}对比一下就知道差别了,在这里它已经指定好了adapter->nr了.如果分配的id不和指定的相等,便返回错误.过一步跟踪i2c_register_adapter().代码如下:static int i2c_register_adapter(struct i2c_adapter *adap){int res = 0, dummy;mutex_init(&adap->bus_lock);mutex_init(&adap->clist_lock);INIT_LIST_HEAD(&adap->clients);mutex_lock(&core_lock);/* Add the adapter to the driver core.* If the parent pointer is not set up,* we add this adapter to the host bus.*/if (adap->dev.parent == NULL) {adap->dev.parent = &platform_bus;pr_debug("I2C adapter driver [%s] forgot to specify ""physical device\n", adap->name);}sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);adap->dev.release = &i2c_adapter_dev_release;adap->dev.class = &i2c_adapter_class;res = device_register(&adap->dev);if (res)goto out_list;dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);/* create pre-declared device nodes for new-style drivers */if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);/* let legacy drivers scan this bus for matching devices */dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);out_unlock:mutex_unlock(&core_lock);return res;out_list:idr_remove(&i2c_adapter_idr, adap->nr);goto out_unlock;}首先对adapter和adapter中内嵌的struct device结构进行必须的初始化.之后将adapter内嵌的struct device注册. 在这里注意一下adapter->dev的初始化.它的类别为i2c_adapter_class,如果没有父结点,则将其父结点设为platform_bus.adapter->dev的名字为i2c + 总线号.测试一下:[eric@mochow i2c]$ cd /sys/class/i2c-adapter/[eric@mochow i2c-adapter]$ lsi2c-0可以看到,在我的PC上,有一个I2C adapter,看下详细信息:[eric@mochow i2c-adapter]$ tree.`-- i2c-0|-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0|-- name|-- subsystem -> ../../../class/i2c-adapter`-- uevent3 directories, 2 files可以看到,该adapter是一个PCI设备.继续往下看:之后,在注释中看到,有两种类型的driver,一种是new-style drivers,另外一种是legacy driversNew-style drivers是在2.6近版的kernel加入的.它们最主要的区别是在adapter和i2c driver的匹配上.3.1: new-style 形式的adapter注册对于第一种,也就是new-style drivers,将相关代码再次列出如下:if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);如果adap->nr 小于__i2c_first_dynamic_bus_num的话,就会进入到i2c_scan_static_board_info().结合我们之前分析的adapter的两种注册分式: i2c_add_adapter()所分得的总线号肯会不会小于__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()才有可能满足:(adap->nr < __i2c_first_dynamic_bus_num)mt6516_devs.c而且必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册时才会更改__i2c_first_dynamic_bus_num的值.在x86上只没有使用i2c_register_board_info()的.因此,x86平台上的分析可以忽略掉new-style driver的方式.不过,还是详细分析这种情况下.s首先看一下i2c_register_board_info(),如下:int __initi2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len){int status;mutex_lock(&__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);}mutex_unlock(&__i2c_board_lock);return status;}这个函数比较简单,struct i2c_board_info用来表示I2C设备的一些情况,比如所在的总线.名称,地址,中断号等.最后,这些信息会被存放到__i2c_board_list链表.跟踪i2c_scan_static_board_info():代码如下:static void i2c_scan_static_board_info(struct i2c_adapter *adapter){struct i2c_devinfo *devinfo;mutex_lock(&__i2c_board_lock);list_for_each_entry(devinfo, &__i2c_board_list, list) {if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info))printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",i2c_adapter_id(adapter),devinfo->board_info.addr);}mutex_unlock(&__i2c_board_lock);}该函数遍历挂在__i2c_board_list链表上面的i2c设备的信息,也就是我们在启动的时候指出的i2c设备的信息. 如果指定设备是位于adapter所在的I2C总线上,那么,就调用i2c_new_device().代码如下:struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info){struct i2c_client *client;int status;client = kzalloc(sizeof *client, GFP_KERNEL);if (!client)return NULL;client->adapter = adap;client->dev.platform_data = info->platform_data;device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);client->flags = info->flags & ~I2C_CLIENT_WAKE;client->addr = info->addr;client->irq = info->irq;strlcpy(client->name, info->type, sizeof(client->name));/* a new style driver may be bound to this device when we* return from this function, or any later moment (e.g. maybe* hotplugging will load the driver module). and the device* refcount model is the standard driver model one.*/status = i2c_attach_client(client);if (status < 0) {kfree(client);client = NULL;}return client;}我们又遇到了一个新的结构:struct i2c_client,不要被这个结构吓倒了,其实它就是一个嵌入struct device的I2C设备的封装.它和我们之前遇到的struct usb_device结构的作用是一样的.首先,在clinet里保存该设备的相关消息.特别的, client->adapter指向了它所在的adapter.特别的,clinet->name为info->name.也是指定好了的.一切初始化完成之后,便会调用i2c_attach_client( ).看这个函数的字面意思,是将clinet关联起来.到底怎么样关联呢?继续往下看:int i2c_attach_client(struct i2c_client *client){struct i2c_adapter *adapter = client->adapter;int res = 0;//初始化client内嵌的dev结构//父结点为所在的adapter,所在bus为i2c_bus_typeclient->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;//如果client已经指定了driver,将driver和内嵌的dev关联起来if (client->driver)client->dev.driver = &client->driver->driver;//指定了driver, 但不是newstyle的if (client->driver && !is_newstyle_driver(client->driver)) {client->dev.release = i2c_client_release;client->dev.uevent_suppress = 1;} elseclient->dev.release = i2c_client_dev_release;//clinet->dev的名称snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),"%d-%04x", i2c_adapter_id(adapter), client->addr);//将内嵌的dev注册res = device_register(&client->dev);if (res)goto out_err;//将clinet链到adapter->clients中mutex_lock(&adapter->clist_lock);list_add_tail(&client->list, &adapter->clients);mutex_unlock(&adapter->clist_lock);dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",client->name, client->dev.bus_id);//如果adapter->cleinet_reqister存在,就调用它if (adapter->client_register) {if (adapter->client_register(client)) {dev_dbg(&adapter->dev, "client_register ""failed for client [%s] at 0x%02x\n",client->name, client->addr);}}return 0;out_err:dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, res);return res;}参考上面添加的注释,应该很容易理解这段代码了,就不加详细分析了.这个函数的名字不是i2c_attach_client()么?怎么没看到它的关系过程呢?这是因为:在代码中设置了client->dev所在的bus为i2c_bus_type .因为只需要有bus为i2c_bus_type的driver 注册,就会产生probe了.这个过程在后面分析i2c driver的时候再来详细分析.3.2:legacy形式的adapter注册Legacy形式的adapter注册代码片段如下:dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);这段代码遍历挂在i2c_bus_type上的驱动,然后对每一个驱动和adapter调用i2c_do_add_adapter().代码如下:static int i2c_do_add_adapter(struct device_driver *d, void *data){struct i2c_driver *driver = to_i2c_driver(d);struct i2c_adapter *adap = data;if (driver->attach_adapter) {/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}return 0;}该函数很简单,就是调用driver的attach_adapter()接口.到此为止,adapter的注册已经分析完了.四:i2c driver注册在分析i2c driver的时候,有必要先分析一下i2c架构的初始化代码如下:static int __init i2c_init(void){int retval;retval = bus_register(&i2c_bus_type);if (retval)return retval;retval = class_register(&i2c_adapter_class);if (retval)goto bus_err;retval = i2c_add_driver(&dummy_driver);if (retval)goto class_err;return 0;class_err:class_unregister(&i2c_adapter_class);bus_err:bus_unregister(&i2c_bus_type);return retval;}subsys_initcall(i2c_init);很明显,i2c_init()会在系统初始化的时候被调用.在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.I2c_bus_type结构如下:static struct bus_type i2c_bus_type = {.name = "i2c",.dev_attrs = i2c_dev_attrs,.match = i2c_device_match,.uevent = i2c_device_uevent,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,.suspend = i2c_device_suspend,.resume = i2c_device_resume,};这个结构先放在这里吧,以后还会用到里面的信息的.从上面的初始化函数里也看到了,注册i2c driver的接口为i2c_add_driver().代码如下: static inline int i2c_add_driver(struct i2c_driver *driver){return i2c_register_driver(THIS_MODULE, driver);}继续跟踪:int i2c_register_driver(struct module *owner, struct i2c_driver *driver){int res;/* new style driver methods can't mix with legacy ones *///如果是一个newstyle的driver.但又定义了attach_adapter/detach_adapter.非法if (is_newstyle_driver(driver)) {if (driver->attach_adapter || driver->detach_adapter|| driver->detach_client) {printk(KERN_WARNING"i2c-core: driver [%s] is confused\n",driver->);return -EINVAL;}}/* add the driver to the list of i2c drivers in the driver core *///关联到i2c_bus_typesdriver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;/* for new style drivers, when registration returns the driver core* will have called probe() for all matching-but-unbound devices.*///注册内嵌的driverres = driver_register(&driver->driver);if (res)return res;mutex_lock(&core_lock);pr_debug("i2c-core: driver [%s] registered\n", driver->);/* legacy drivers scan i2c busses directly *///遍历所有的adapter,对其都调用driver->attach_adapterif (driver->attach_adapter) {struct i2c_adapter *adapter;down(&i2c_adapter_class.sem);list_for_each_entry(adapter, &i2c_adapter_class.devices,dev.node) {driver->attach_adapter(adapter);}up(&i2c_adapter_class.sem);}mutex_unlock(&core_lock);return 0;}这里也有两种形式的区分,对于第一种,只需要将内嵌的driver注册就可以了,对于legacy的情况,对每一个adapter都调用driver->attach_adapter().现在,我们可以将adapter和i2c driver关联起来考虑一下了:1:如果是news style形式的,在注册adapter的时候,将它上面的i2c 设备转换成了struct client.struct client->dev->bus 又指定了和i2c driver同一个bus.因为,它们可以发生probe.2:如果是legacy形式,就直接找到对应的对象,调用driver->attach_adapter().五:i2c_bus_type的相关操作I2c_bus_type的操作主要存在于new-style形式的驱动中.接下来分析一下对应的probe过程:5.1:match过程分析Match对应的操作函数为i2c_device_match().代码如下static int i2c_device_match(struct device *dev, struct device_driver *drv){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(drv);/* make legacy i2c drivers bypass driver model probing entirely;* such drivers scan each i2c adapter/bus themselves.*/if (!is_newstyle_driver(driver))return 0;/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;return 0;}如果该驱动不是一个new-style形式的.或者driver没有定义匹配的id_table.都会匹配失败.继续跟踪进i2c_match_id():static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client){while (id->name[0]) {if (strcmp(client->name, id->name) == 0)return id;id++;}return NULL;}由此可见.如果client的名字和driver->id_table[]中的名称匹配即为成功.5.2:probe过程分析Probe对应的函数为:i2c_device_probe()static int i2c_device_probe(struct device *dev){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(dev->driver);const struct i2c_device_id *id;int status;if (!driver->probe)return -ENODEV;client->driver = driver;dev_dbg(dev, "probe\n");if (driver->id_table)id = i2c_match_id(driver->id_table, client);elseid = NULL;status = driver->probe(client, id);if (status)client->driver = NULL;return status;}这个函数也很简单,就是将probe流程回溯到i2c driver的probe()六:其它的扩展分析完adapter和i2c driver的注册之后,好像整个架构也差不多了,其它,扩展的东西还有很多. 我们举一个legacy形式的例子,这个例子是在kernel中随便搜索出来的:在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函数为:static int __init ad7418_init(void){return i2c_add_driver(&ad7418_driver);}i2c_driver ad7418_driver结构如下:static struct i2c_driver ad7418_driver = {.driver = {.name = "ad7418",},.attach_adapter = ad7418_attach_adapter,.detach_client = ad7418_detach_client,};该结构中没有probe()函数,可以断定是一个legacy形式的驱动.这类驱动注册的时候,会调用driver的attach_adapter函数.在这里也就是ad7418_attach_adapter.这个函数代码如下:static int ad7418_attach_adapter(struct i2c_adapter *adapter){if (!(adapter->class & I2C_CLASS_HWMON))return 0;return i2c_probe(adapter, &addr_data, ad7418_detect);}在这里我们又遇到了一个i2c-core中的函数,i2c_probe().在分析这个函数之前,先来看下addr_data是什么?#define I2C_CLIENT_MODULE_PARM(var,desc) \static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; \static unsigned int var##_num; \module_param_array(var, short, &var##_num, 0); \MODULE_PARM_DESC(var,desc)#define I2C_CLIENT_MODULE_PARM_FORCE(name) \I2C_CLIENT_MODULE_PARM(force_##name, \"List of adapter,address pairs which are " \"unquestionably assumed to contain a `" \# name "' chip")#define I2C_CLIENT_INSMOD_COMMON \I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan " \"additionally"); \I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to " \"scan"); \static const struct i2c_client_address_data addr_data = { \.normal_i2c = normal_i2c, \.probe = probe, \.ignore = ignore, \.forces = forces, \}#define I2C_CLIENT_FORCE_TEXT \"List of adapter,address pairs to boldly assume to be present"由此可知道,addr_data中的三个成员都是模块参数.在加载模块的时候可以用参数的方式对其赋值.三个模块参数为别为probe,ignore,force.另外需要指出的是normal_i2c不能以模块参数的方式对其赋值,只能在驱动内部静态指定.从模块参数的模述看来, probe是指"List of adapter,address pairs to scan additionally"Ignore是指"List of adapter,address pairs not to scan "Force是指"List of adapter,address pairs to boldly assume to be present"事实上,它们里面的数据都是成对出现的.前面一部份表示所在的总线号,ANY_I2C_BUS表示任一总线.后一部份表示设备的地址.现在可以来跟踪i2c_probe()的代码了.如下:int i2c_probe(struct i2c_adapter *adapter,const struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int)){int i, err;int adap_id = i2c_adapter_id(adapter);/* Force entries are done first, and are not affected by ignoreentries *///先扫描force里面的信息,注意它是一个二级指针.ignore里的信息对它是无效的if (address_data->forces) {const unsigned short * const *forces = address_data->forces;int kind;for (kind = 0; forces[kind]; kind++) {for (i = 0; forces[kind][i] != I2C_CLIENT_END;i += 2) {if (forces[kind][i] == adap_id|| forces[kind][i] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found force ""parameter for adapter %d, ""addr 0x%02x, kind %d\n",adap_id, forces[kind][i + 1],kind);err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);if (err)return err;}}}}/* Stop here if we can't use SMBUS_QUICK *///如果adapter不支持quick.不能够遍历这个adapter上面的设备if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) { if (address_data->probe[0] == I2C_CLIENT_END&& address_data->normal_i2c[0] == I2C_CLIENT_END) return 0;dev_warn(&adapter->dev, "SMBus Quick command not supported, ""can't probe for chips\n");return -1;}/* Probe entries are done second, and are not affected by ignore entries either *///遍历probe上面的信息.ignore上的信息也对它是没有影响的for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) { if (address_data->probe[i] == adap_id|| address_data->probe[i] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found probe parameter for ""adapter %d, addr 0x%02x\n", adap_id,address_data->probe[i + 1]);err = i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);if (err)return err;}}/* Normal entries are done last, unless shadowed by an ignore entry */ //最后遍历normal_i2c上面的信息.它上面的信息不能在ignore中.for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) { int j, ignore;ignore = 0;for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;j += 2) {if ((address_data->ignore[j] == adap_id ||address_data->ignore[j] == ANY_I2C_BUS)&& address_data->ignore[j + 1]== address_data->normal_i2c[i]) {dev_dbg(&adapter->dev, "found ignore ""parameter for adapter %d, ""addr 0x%02x\n", adap_id,address_data->ignore[j + 1]);ignore = 1;break;}}if (ignore)continue;dev_dbg(&adapter->dev, "found normal entry for adapter %d, ""addr 0x%02x\n", adap_id,address_data->normal_i2c[i]);err = i2c_probe_address(adapter, address_data->normal_i2c[i],-1, found_proc);if (err)return err;}return 0;}这段代码很简单,结合代码上面添加的注释应该很好理解.如果匹配成功,则会调用i2c_probe_address ().这个函数代码如下: static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int)){int err;/* Make sure the address is valid *///地址小于0x03或者大于0x77都是不合法的if (addr < 0x03 || addr > 0x77) {dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);return -EINVAL;}/* Skip if already in use *///adapter上已经有这个设备了if (i2c_check_addr(adapter, addr))return 0;/* Make sure there is something at this address, unless forced *///如果kind小于0.检查adapter上是否有这个设备if (kind < 0) {if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)return 0;/* prevent 24RF08 corruption */if ((addr & ~0x0f) == 0x50)i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL);}/* Finally call the custom detection function *///调用回调函数err = found_proc(adapter, addr, kind);/* -ENODEV can be returned if there is a chip at the given addressbut it isn't supported by this chip driver. We catch it here asthis isn't an error. */if (err == -ENODEV)err = 0;if (err)dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)\n",addr, err);return err;}首先,对传入的参数进行一系列的合法性检查.另外,如果该adapter上已经有了这个地址的设备了.也会返回失败.所有adapter 下面的设备都是以adapter->dev为父结点的.因此只需要遍历adapter->dev下面的子设备就可以得到当前地址是不是被占用了.如果kind < 0.还得要adapter检查该总线是否有这个地址的设备.方法是向这个地址发送一个Read的Quick请求.如果该地址有应答,则说明这个地址上有这个设备.另外还有一种情况是在24RF08设备的特例.如果adapter上确实有这个设备,就会调用驱动调用时的回调函数.在上面涉及到了IIC的传输方式,有疑问的可以参考intel ICH5手册的有关smbus部份.跟踪i2c_smbus_xfer().代码如下:s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){s32 res;flags &= I2C_M_TEN | I2C_CLIENT_PEC;if (adapter->algo->smbus_xfer) {mutex_lock(&adapter->bus_lock);res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);mutex_unlock(&adapter->bus_lock);} elseres = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);return res;}如果adapter有smbus_xfer()函数,则直接调用它发送,否则,也就是在adapter不支持smbus协议的情况下,调用i2c_smbus_xfer_emulated()继续处理.跟进i2c_smbus_xfer_emulated().代码如下:static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){/* So we need to generate a series of msgs. In the case of writing, weneed to use only one message; when reading, we need two. We initializemost things with sane defaults, to keep the code below somewhatsimpler. *///写操作只会进行一次交互,而读操作,有时会有两次操作.//因为有时候读操作要先写command,再从总线上读数据//在这里为了代码的简洁.使用了两个缓存区,将两种情况统一起来.unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];//一般来说,读操作要交互两次.例外的情况我们在下面会接着分析int num = read_write == I2C_SMBUS_READ?2:1;//与设备交互的数据,一般在msg[0]存放写入设备的信息,在msb[1]里存放接收到的//信息.不过也有例外的//msg[2]的初始化,默认发送缓存区占一个字节,无接收缓存struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },{ addr, flags | I2C_M_RD, 0, msgbuf1 }};int i;u8 partial_pec = 0;//将要发送的信息copy到发送缓存区的第一字节msgbuf0[0] = command;switch(size) {//quick类型的,其它并不传输有效数据,只是将地址写到总线上,等待应答即可//所以将发送缓存区长度置为0 .再根据读/写操作,调整msg[0]的标志位//这类传输只需要一次总线交互case I2C_SMBUS_QUICK:msg[0].len = 0;/* Special case: The read/write field is used as data */msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;num = 1;break;case I2C_SMBUS_BYTE://BYTE类型指一次写和读只有一个字节.这种情况下,读和写都只会交互一次//这种类型的读有例外,它读取出来的数据不是放在msg[1]中的,而是存放在msg[0]if (read_write == I2C_SMBUS_READ) {/* Special case: only a read! */msg[0].flags = I2C_M_RD | flags;num = 1;}break;case I2C_SMBUS_BYTE_DATA://Byte_Data是指命令+数据的传输形式.在这种情况下,写只需要一次交互,读却要两次//第一次将command写到总线上,第二次要转换方向.要将设备地址和read标志写入总线.//应回答之后再进行read操作//写操作占两字节,分别是command+data.读操作的有效数据只有一个字节//交互次数用初始化值就可以了if (read_write == I2C_SMBUS_READ)msg[1].len = 1;else {msg[0].len = 2;msgbuf0[1] = data->byte;}break;case I2C_SMBUS_WORD_DATA://Word_Data是指命令+双字节的形式.这种情况跟Byte_Data的情况类似//两者相比只是交互的数据大小不同if (read_write == I2C_SMBUS_READ)msg[1].len = 2;else {msg[0].len=3;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;}break;case I2C_SMBUS_PROC_CALL://Proc_Call的方式与write 的Word_Data相似,只不过写完Word_Data之后,要等待它的应答//应该它需要交互两次,一次写一次读num = 2; /* Special case */read_write = I2C_SMBUS_READ;msg[0].len = 3;msg[1].len = 2;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;break;case I2C_SMBUS_BLOCK_DATA://Block_Data:指command+N段数据的情况.//如果是读操作,它首先要写command到总线,然后再读N段数据.要写的command已经//放在msg[0]了.现在只需要将msg[1]的标志置I2C_M_RECV_LEN位,msg[1]有效长度为1字节.因为//adapter驱动会处理好的.现在现在还不知道要传多少段数据.//对于写的情况:msg[1]照例不需要.将要写的数据全部都放到msb[0]中.相应的也要更新//msg[0]中的缓存区长度if (read_write == I2C_SMBUS_READ) {msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */} else {//data->block[0]表示后面有多少段数据.总长度要加2是因为command+count+N段数据msg[0].len = data->block[0] + 2;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {dev_err(&adapter->dev, "smbus_access called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i < msg[0].len; i++)msgbuf0[i] = data->block[i-1];}break;case I2C_SMBUS_BLOCK_PROC_CALL://Proc_Call:表示写完Block_Data之后,要等它的应答消息它和Block_Data相比,只是多了一部份应答而已num = 2; /* Another special case */read_write = I2C_SMBUS_READ;if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {dev_err(&adapter->dev, "%s called with invalid ""block proc call size (%d)\n", __func__,data->block[0]);return -1;}msg[0].len = data->block[0] + 2;for (i = 1; i < msg[0].len; i++)msgbuf0[i] = data->block[i-1];msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */break;case I2C_SMBUS_I2C_BLOCK_DATA://I2c Block_Data与Block_Data相似,只不过read的时候,数据长度是预先定义好了的.另外//与Block_Data相比,中间不需要传输Count字段.(Count表示数据段数目)if (read_write == I2C_SMBUS_READ) {msg[1].len = data->block[0];} else {msg[0].len = data->block[0] + 1;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i <= data->block[0]; i++)msgbuf0[i] = data->block[i];}break;default:dev_err(&adapter->dev, "smbus_access called with invalid size (%d)\n", size);return -1;}//如果启用了PEC.Quick和I2c Block_Data是不支持PEC的i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK&& size != I2C_SMBUS_I2C_BLOCK_DATA);if (i) {/* Compute PEC if first message is a write *///如果第一个操作是写操作if (!(msg[0].flags & I2C_M_RD)) {//如果只是写操作if (num == 1) /* Write only *///如果只有写操作,写缓存区要扩充一个字节,用来存放计算出来的PECi2c_smbus_add_pec(&msg[0]);else /* Write followed by read *///如果后面还有读操作,先计算前面写部份的PEC(注意这种情况下不需要//扩充写缓存区,因为不需要发送PEC.只会接收到PEC)partial_pec = i2c_smbus_msg_pec(0, &msg[0]);}/* Ask for PEC if last message is a read *///如果最后一次是读消息.还要接收到来自slave的PEC.所以接收缓存区要扩充一个字节if (msg[num-1].flags & I2C_M_RD)msg[num-1].len++;}if (i2c_transfer(adapter, msg, num) < 0)return -1;。
RT Thread设备驱动开发指南
2
第22章 ETH设 备驱动开发
3 第23章 AUDIO
MIC设备驱动 开发
4 第24章 AUDIO
SOUND设备驱 动开发
5
第25章 USBD 设备驱动开发
第26章 USBH 设备驱动开发
第27章 CAN设 备驱动开发
21.1 WLAN层级结构 21.2创建WLAN设备 21.3实现WLAN设备的操作方法 21.4注册WLAN设备 21.5驱动配置 21.6驱动验证 21.7本章小结
15.1传感器层级结构 15.2创建传感器设备 15.3实现传感器设备的操作方法 15.4设备注册 15.5驱动配置 15.6驱动验证 15.7本章小结
16.1 MTD NOR层级结构 16.2创建MTD NOR设备 16.3实现MTD NOR设备的操作方法 16.4注册MTD NOR设备 16.5驱动配置 16.6驱动验证 16.7本章小结
3.1 PIN层级结构 3.2实现PIN设备的操作方法 3.3注册PIN设备 3.4驱动配置 3.5驱动验证 3.6本章小结
4.1 I2C层级结构 4.2 I2C总线设备结构 4.3硬件I2C总线设备驱动开发 4.4软件I2C总线设备驱动开发 4.5本章小结
5.1 SPI/QSPI层级结构 5.2 SPI总线设备驱动开发 5.3 QSPI总线设备驱动开发 5.4本章小结
19.1加解密设备层级结构 19.2创建加解密设备 19.3实现加解密设备的操作方法 19.4注册加解密设备 19.5驱动配置 19.6驱动验证 19.7本章小结
20.1 PM层级结构 20.2实现PM设备的操作方法 20.3注册PM设备 20.4驱动配置 20.5驱动验证 20.6本章小结
I2C通信原理及程序详细讲解
I2C通信原理及程序详细讲解I2C(Inter-Integrated Circuit)是一种串行通信协议,常用于连接微控制器、传感器和其他外部设备。
I2C通信协议由荷兰飞利浦公司于1982年开发,它使用两根信号线(SDA和SCL)进行数据传输。
I2C通信协议采用主从结构,一个主设备(如微控制器)可以连接多个从设备(如传感器)。
主从设备之间通过SDA和SCL线进行数据传输。
SDA线是双向数据线,用于传输数据,SCL线是时钟线,用于同步数据传输。
I2C通信协议中,设备的地址是一个重要概念。
每个设备都有一个唯一的地址,通过该地址可以选择和通信特定的设备。
地址由7个位组成,其中最高位是固定的,并取决于设备是主设备还是从设备。
如果最高位为0,则表示该设备是主设备;如果最高位为1,则表示该设备是从设备。
通过以下步骤,让我们详细了解如何在I2C总线上进行通信。
1.初始化I2C总线:在程序开始时,需要初始化I2C总线。
这通常包括初始化SDA和SCL引脚,设置时钟频率等。
具体的初始化步骤取决于使用的硬件和软件环境。
2.发送开始信号:开始信号表示I2C数据传输的开始。
它由主设备发送,并且SDA线从高电平转为低电平时发出。
发送开始信号后,SDA线上的数据将被解释为地址数据。
3.发送设备地址:主设备发送一个包含设备地址和读/写位(R/W)的数据字节。
设备地址是唯一的,并且由主设备选择。
读/写位指示从设备是要读取数据还是写入数据。
4.等待从设备响应:主设备发送设备地址后,会等待从设备的响应。
从设备将响应一个应答位(ACK)来确认地址接收成功。
如果收到ACK位,则继续进行下一步,否则可能是设备未连接或通信错误。
5.发送数据:主设备发送数据给从设备。
数据可以是命令、配置或实际数据,具体取决于应用场景。
发送数据的方式是将每个数据字节传输到SDA线上,并在每个数据字节后发送一个ACK位。
6.接收数据:从设备将数据发送给主设备。
数据可以是传感器读数、存储器数据等。
rt-thread技术指标
rt-thread技术指标RT-Thread技术指标RT-Thread是一款开源的嵌入式实时操作系统(RTOS),它具有高效、灵活、可裁剪等特点,广泛应用于物联网、智能家居、工业控制等领域。
本文将从几个方面介绍RT-Thread的技术指标。
一、内核特性1.1 轻量级RT-Thread的内核非常轻量级,仅占用几KB的ROM和几百字节的RAM。
这使得RT-Thread适用于资源有限的嵌入式系统,可以运行在低端处理器上,如8位单片机。
1.2 实时性RT-Thread具有良好的实时性能,它支持多任务、多线程的并行执行。
任务的调度粒度可以达到微秒级,可满足实时性要求较高的应用场景。
1.3 可裁剪性RT-Thread的内核和组件都是可裁剪的,可以根据实际需求选择所需的功能模块,减少不必要的资源消耗。
这种可裁剪性使得RT-Thread适应各种不同规模的嵌入式系统。
二、任务管理RT-Thread的任务管理是其核心功能之一,它支持多任务并发执行。
每个任务都有自己的优先级,可以通过优先级调度算法进行任务切换。
同时,RT-Thread还提供了任务挂起、恢复、删除等功能,方便对任务进行管理。
三、内存管理RT-Thread提供了灵活高效的内存管理机制,包括动态内存分配和静态内存分配。
动态内存分配使用RT-Thread自带的内存管理器,可以根据实际需要进行内存的申请和释放;静态内存分配则是在编译时确定内存大小,适用于资源有限的系统。
四、设备驱动RT-Thread支持多种设备驱动,包括串口、SPI、I2C、网络等。
它提供了统一的设备驱动框架,可以方便地添加和管理设备驱动。
同时,RT-Thread还支持设备驱动的动态加载和卸载,可以根据需要动态加载所需的驱动模块。
五、文件系统RT-Thread支持多种文件系统,包括FAT文件系统、YAFFS文件系统等。
文件系统可以对外提供统一的文件操作接口,方便应用程序对文件进行读写操作。
同时,RT-Thread还支持文件系统的动态加载和卸载。
RT-Thread嵌入式系统简介
研发部技术交流会
RT-Thread嵌入式系统简介
2014.12.04
主要内容
1、RT-ThBiblioteka ead基础知识2、 RT-Thread软件架构
3、 RT-Thread优点
RTT目录详解
•
•
•
•
•
•
bsp/:board support package,对应于开发板 libcpu/:芯片架构,CPU资源(PMC电源控制器,AIC 先进中断控制器,系统时钟)驱动 src/:RTT内核源码(rt_object, ipc,调度) components/:RTT组件,包含协议栈(lwip)、总线驱 动(SPI I2C SDIO USB等)、Finsh、DFS、libc库 tools/:RT-Thread官方提供的用于所有BSP的共同脚本 控制文件,提供一些强大的功能函数,方便我们编写 SConscript. include/:RTT头文件
行执行时,这种设计能够让系统满足实时系统的性能及时间的
要求。
RTT线程转换图
I/O设备管理
RT-Thread的设备模型
RTT设备继承关系
设备操作接口与驱动程序的映射
RT-Thread 的文件系统
RTT文件系统目录结构
谢谢!
RTT线程调度与管理
一个典型的简单程序会设计成一个串行的系统运行:按 照准确的指令步骤一次一个指令的运行。但是这种方法对于复 杂一些的实时应用是不可行的,因为它们通常需要在固定的时 间内“同时”处理多个输入输出,实时软件应用程序应该设计 成一个并行的系统。 并行设计需要开发人员把一个应用分解成一个个小的, 可调度的,序列化的程序单元。当合理的划分任务,正确的并
I2C设备驱动介绍
I2C设备驱动介绍I2C(Inter-Integrated Circuit)是一种串行通信协议,用于连接并使多个外部设备与主控制器进行通信。
在嵌入式系统中,I2C设备驱动起着至关重要的作用,负责将操作系统与I2C总线上的设备进行通信,促进数据的传输和交互。
1.初始化:驱动程序需要初始化I2C控制器,包括设置时钟频率、地址范围等。
2.设备注册:设备驱动需要在操作系统中注册I2C设备,以便操作系统能够识别和管理设备。
3.读写操作:驱动程序需要实现读写设备寄存器的功能,包括发送开始和停止信号、以及发送、接收数据等。
4.错误处理:驱动程序需要处理I2C通信过程中可能出现的错误,例如传输失败、设备无响应等情况。
5.中断处理:驱动程序需要支持I2C设备的中断机制,以便及时处理设备的状态变化或数据传输完成的中断信号。
6.电源管理:驱动程序需要支持设备的电源管理功能,包括设备的唤醒、睡眠等操作。
7.设备控制:驱动程序需要实现设备特定的控制功能,例如设置传感器的采样率、配置设备的工作模式等。
8. 虚拟文件系统接口:在Linux系统中,驱动程序通常通过虚拟文件系统接口(如/dev)与用户空间进行交互,提供读写设备寄存器的功能。
1.确定设备:首先,开发者应该确定需要驱动的I2C设备。
这可能包括传感器、EEPROM、显示器等。
2.确定硬件连接:确定I2C设备与主控制器之间的硬件连接和电气特性。
这包括设备的I2C地址、I2C总线上的物理接口等。
3.编写驱动程序:在操作系统中,开发者可以根据设备的文档或芯片厂商提供的驱动程序框架,编写自己的I2C设备驱动程序。
驱动程序需要实现上述提到的功能,并且根据设备的特点进行相应的适配和优化。
4.编译和测试:完成驱动程序的编写后,需要将其编译成与操作系统内核匹配的模块或静态链接库。
然后,通过加载驱动模块或重新编译内核来使驱动程序生效。
最后,进行测试,确保驱动程序在各种场景下的正常运行。
RT-Thread学习之UART设备驱动框架
STM32 芯片具有多个USART 外设用于串口通讯,它是Universal Synchronous Asynchronous Receiver and Transmitter 的缩写,即通用同步异步收发器可以灵活地与外部设备进行全双工数据交换。
有别于 USART,它还有具有UART 外设(Universal Asynchronous Receiver and Transmitter),它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。
简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。
UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
其工作原理是将传输数据的每个字符一位接一位地传输。
其传输数据格式如下:UART设备框架学习笔记RT-Thread 提供了一套简单的 I/O 设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层:应用程序访问串口设备的接口:下面我们直接来看个实例:同时使用两个串口(uart1和uart3),uart1作为系统打印调试串口,用来打印一些日志信息,uart3作为我们本次实验的测试串口,实现与串口调试助手的收发测试。
uart3设备启动后,往串口调试助手发送字符串I am uart3。
同时,uart3设备使用中断的方式接收数据,然后再错位输出数据,比如收到ASCII码字符A,则会回复B。
#define SAMPLE_UART_NAME "uart3" /* 串口设备名称*//* uart3应用函数*/static int uart3_app(void){rt_err_t ret = RT_EOK; /* 函数返回值*/ rt_thread_t tid; /* 动态线程句柄*/char uart3_name[RT_NAME_MAX]; /* 保存查找的设备名*/char usart3_tx_str[] = "I am uart3.\r\n"; /* uart3发送的字符串*/rt_strncpy(uart3_name, SAMPLE_UART_NAME, RT_NAME_MAX);/* 查找串口设备*/uart3_dev = rt_device_find(uart3_name);if (!uart3_dev){rt_kprintf("find %s failed!\n", uart3_name);return RT_ERROR;}/* 初始化信号量*/rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);/* 以读写及中断接收方式打开串口设备*/rt_device_open(uart3_dev, RT_DEVICE_OFLAG_RDWR |RT_DEVICE_FLAG_INT_RX);/* 设置接收回调函数*/rt_device_set_rx_indicate(uart3_dev, uart3_rx_callback);/* 发送字符串*/rt_device_write(uart3_dev, 0, usart3_tx_str, (sizeof(usart3_tx_str) - 1));/* 创建动态线程:优先级25 ,时间片5个系统滴答,线程栈512字节*/tid = rt_thread_create("uart3_rx_thread",static_uart3_rx_entry,RT_NULL,STACK_SIZE,THREAD_PRIORITY,TIMESLICE);/* 创建成功则启动动态线程*/if (tid != RT_NULL){rt_thread_startup(tid);}return ret;}我们的应用程序首先根据串口设备名字uart3来查找设备,查找到设备之后则返回串口设备句柄uart3_dev。
i2c_register_driver函数详解
i2c_register_driver函数详解在嵌入式软件开发中,I2C(Inter-Integrated Circuit)总线是一种常用的串行通信接口,用于在微控制器和外部设备之间传输数据。
i2c_register_driver函数是Linux内核中一个重要的函数,用于注册I2C 驱动程序。
本文将详细解析i2c_register_driver函数的功能、参数和应用。
一、i2c_register_driver函数概述i2c_register_driver函数是在Linux内核中注册一个I2C驱动程序的函数。
它的作用是将驱动程序与对应的I2C适配器绑定,使得操作系统能够正确地识别和管理该驱动程序。
在驱动程序注册后,当相应的I2C设备连接到系统时,驱动程序将会自动加载并为该设备提供服务。
二、i2c_register_driver函数参数i2c_register_driver函数包含一个结构体参数,该结构体用于指定驱动程序的相关信息和功能。
1. struct i2c_driverstruct i2c_driver是一个定义I2C驱动程序的结构体,包含了以下重要的成员:- .driver:指向内核的struct device_driver结构体,用于描述驱动程序的信息,如名称、文件操作方法等。
- .probe:指向I2C设备探测函数的指针,用于在设备连接时进行初始化和配置。
- .remove:指向I2C设备移除函数的指针,用于在设备断开连接时进行清理和释放资源。
- .id_table:指向I2C设备ID表的指针,用于匹配设备和驱动程序。
2. I2C设备探测函数(probe函数)I2C设备探测函数是I2C驱动程序的核心功能之一,在I2C设备连接到系统时被调用。
该函数的作用是检测和初始化I2C设备,并将设备与驱动程序进行绑定。
在probe函数中,可以执行一系列必要的操作,如配置寄存器、分配内存、注册字符设备等。
I2C驱动架构
I2C驱动架构i2c概述i2c是philips提出的外设总线.i2c只有两条线,一条以太网数据线:sda,一条就是时钟线scl,采用scl,sda这两根信号线就同时实现了设备之间的数据可视化,它便利了工程师的布线。
因此,i2c总线被非常广泛地应用在eeprom,实时钟,小型lcd等设备与cpu的接口中。
linux下的驱动思路在linux系统下编写i2c驱动,目前主要有两种方法,一种是把i2c设备当作一个普通的字符设备来处理,另一种是利用linux下i2c驱动体系结构来完成。
下面比较下这两种方法:第一种方法:优点:思路比较直接,不需要花很多时间去了解linux中复杂的i2c子系统的操作方法。
缺点:建议工程师不仅必须对i2c设备的操作方式熟识,而且要熟悉i2c的适配器(i2c控制器)操作。
建议工程师对i2c的设备器及i2c的设备操作方法都比较熟识,最重要的就是写下的程序可以移植性高。
对内核的资源无法轻易采用,因为内核提供更多的所有i2c设备器以及设备驱动都就是基于i2c子系统的格式。
第一种方法的优点就是第二种方法的缺点,第一种方法的缺点就是第二种方法的优点。
i2c架构概述linux的i2c体系结构分成3个组成部分:i2c核心:i2c核心提供了i2c总线驱动和设备驱动的注册,注销方法,i2c通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。
i2c总线驱动:i2c总线驱动就是对i2c硬件体系结构中适配器端的同时实现,适配器可以由cpu掌控,甚至可以轻易内置在cpu内部。
i2c设备驱动:i2c设备驱动(也称为客户驱动)是对i2c硬件体系结构中设备端的实现,设备一般挂接在受cpu控制的i2c适配器上,通过i2c适配器与cpu交换数据。
linux驱动中i2c驱动架构上图完整的描述了linuxi2c驱动架构,虽然i2c硬件体系结构比较简单,但是i2c 体系结构在linux中的实现却相当复杂。
I2C设备驱动介绍
I2C设备驱动介绍I2C(Inter-Integrated Circuit)是一种简单的串行通信协议,用于在微控制器和外部设备之间传输数据。
它是由飞利浦公司(现在的恩智浦)于1982年推出的,现在已成为一种广泛应用的通信接口。
总线驱动是与I2C总线硬件相关的组件,它负责控制总线的时钟频率和数据传输速度。
它还提供了与硬件相关的函数,如初始化总线、发送数据和接收数据。
设备驱动是与特定设备相关的组件,它负责控制设备的初始化和配置,并提供与设备相关的功能函数。
设备驱动还负责将数据从总线读取到设备或从设备写入总线。
用户接口是设备驱动和应用程序之间的接口,通常是通过设备文件或命令行界面实现的。
用户接口提供了一组API函数,允许应用程序通过设备驱动与I2C设备进行通信。
1.初始化:驱动程序负责初始化I2C总线和设备,包括设置速率、地址和模式等。
2.读操作:驱动程序负责从设备读取数据,并将其传输到应用程序。
它可以使用主动读取或中断读取的方式来获取数据。
3.写操作:驱动程序负责将数据从应用程序写入设备,并通过I2C总线将其传输。
它可以使用主动写入或中断写入的方式发送数据。
4.错误处理:驱动程序需要能够检测和处理I2C传输中的错误,例如总线冲突、超时和校验错误等。
5.设备控制:驱动程序提供了控制设备状态和功能的功能函数。
6.多设备支持:驱动程序可以支持多个I2C设备,并提供适当的接口来选择和操作特定的设备。
1.可移植性:驱动程序应该是可移植的,适用于不同的硬件平台和操作系统。
2.灵活性:驱动程序应该具有足够的灵活性,以允许根据不同的应用需求进行配置和定制。
3.可靠性:驱动程序应该能够处理各种异常情况,并提供合适的错误处理机制。
4.性能:驱动程序应该能够实现高速数据传输,并尽可能减少处理延迟。
5.易用性:驱动程序应该提供简单易用的接口,以便应用程序能够方便地使用和控制I2C设备。
总之,I2C设备驱动是控制和管理I2C设备的关键组成部分。
i2c_register_driver函数详解 -回复
i2c_register_driver函数详解-回复标题:i2c_register_driver函数详解在嵌入式系统中,I2C(Inter-Integrated Circuit)是一种常用的通信协议,用于设备之间的数据交换。
在Linux内核中,i2c_register_driver函数是用于注册I2C驱动程序的核心函数。
本文将详细解析i2c_register_driver函数的使用方法和工作原理。
一、函数定义首先,我们来看一下i2c_register_driver函数的定义:cstruct i2c_driver *i2c_register_driver(struct i2c_driver *);该函数接受一个指向i2c_driver结构体的指针作为参数,返回一个指向同一结构体的指针。
这个结构体包含了驱动程序的相关信息,如名称、操作函数等。
二、i2c_driver结构体在深入理解i2c_register_driver函数之前,我们需要先了解i2c_driver 结构体。
以下是一个典型的i2c_driver结构体的例子:cstruct i2c_driver {const char *name;struct module *owner;const struct i2c_device_id *id_table;int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);void (*shutdown)(struct i2c_client *);int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);};在这个结构体中,各个字段的含义如下:- name:驱动程序的名称。
- owner:模块的所有者,通常设置为THIS_MODULE。
iic--rtc驱动
这几天主要做I2c驱动和RTC驱动关于I2c其实控制器的核心代码已经写好了,我要做的就是移植到现在的linux3.0内核架构中一.关于I2c总线架构谈到在linux系统下编写I2C驱动,目前主要有两种方式,一种是把I2C设备当作一个普通的字符设备来处理,另一种是利用linux I2C驱动体系结构来完成而最新的内核3.0使用的是最新的Probe方式,I2c裸机程序很简单,按照它的时序收发,但是内核中的I2c架构有点麻烦,但是万变不离其宗,内核都是严格遵循分层思想的I2c子系统中最上面的层次i2c-dev.c,主要处理的是应用层接口中间层是i2c-core.c,主要处理的是通用层和具体设备和算法之间的接口下层是I2c主要适配器的算法(读写方式)我需要移植的就是适配器算法让其和中间层对接起来适配器算法已经由gpio方式实现了,只需要让其挂接在中间层的通用接口上就可以了具体下层和中间层的对接代码我是参考S3c2410的代码架构的最后I2c driver 能和I2c 总线设备match的话,该I2c总线驱动基本完成二.关于DS3231驱动架构接下来是做I2c 设备驱动,已经做好总线驱动了,设备驱动很简单了,我这里参考的DS3232代码驱动架构,两款芯片差不多,因为现在用的DS3231没有用到报警器之类的功能,在DS3232,的代码基础上,做了大量的裁剪,只留下基本的驱动框架,在驱动注册到I2c总线后,会i2c总线上match相应的device设备。
如果匹配成功了,就会自动调用probe了我们就可以在probe中注册普通字符驱动设备(其实在这里字符设备早已经在rtc子系统初始化注册了,在这里只是做些初始化分配资源而已),设置普通字符设备的read,write(其实就是从I2c总线收发数据),我们在这里的read,write中注意要调用I2c中间层的提供的函数,I2c core中有许多供I2c适配器算法使用的通用接口,这里我只挑选了2个,master_i2c_send和master_i2c_recv,其实这么多接口归根结底都是调用i2c_transfert,I2c_trandfer才是转向下层i2c总线适配器算法的真正接口.三.关于温度接口实现问题最后按照需求增加对读RTC温度的接口这个实现比较简单只要在DS3231中增加接口读温度寄存器的值即可,我在这里选用的接口是IOCTL,这个接口,在上册字符驱动还需要增加调用DS3231接口的代码,这里实在上层的IOCTL中添加新的命令。
rtthread 参数
rtthread 参数
RT-Thread是一个开源的实时操作系统,它专门针对嵌入式系
统而设计。
在RT-Thread中,参数可以指代多种不同的概念,以下
我将从多个角度来解释这个术语。
首先,参数可以指代在使用RT-Thread时所需要配置的一些选项,比如线程的优先级、堆栈大小、设备驱动的配置等。
这些参数
的设置可以根据具体的应用需求来进行调整,以达到最佳的系统性
能和资源利用效率。
另外,参数还可以指代在调用函数或方法时传入的数值或对象。
在RT-Thread中,许多API函数都需要传入一些参数来指定操作的
具体细节,比如创建线程时需要指定线程的入口函数和参数等。
这
些参数的设置直接影响了函数的行为和结果,因此在使用RT-
Thread时需要对这些参数有清晰的认识和理解。
此外,参数还可以指代在RT-Thread的配置文件中进行设置的
一些常量或宏定义。
通过修改这些参数,可以灵活地调整RT-
Thread的功能和特性,以满足不同的应用场景和需求。
总的来说,RT-Thread中的参数涵盖了系统配置、函数调用和编译选项等多个方面,对这些参数的合理设置和使用是使用RT-Thread进行嵌入式开发的重要内容之一。
希望这些解释能够帮助你更好地理解RT-Thread中参数的含义和作用。
RT—Thread的I2C总线驱动结构分析、移植及应用
t i o n S y s t e m, RTOS ) 是 国 人 自创 的 开 源 实 时 操 作 系 统 , 借
鉴 了 Vx Wo r k s 、 / , c / o s 、 RTXC( Re a l — Ti me e Xe c u t i v e i n C)
1 I C 总线 驱 动 结 构
串行 C MOS E EP ROM ( 2 4 I C 0 2 B ) 为 例 ] , 介 绍 了 RT —
引 言
RT—Th r e a d嵌 入 式 实 时操 作 系 统 ( R e a l — TC总 线 驱 动 移 植 及 应 用 程 序 设 计 。
d r i v e r mi g r a t i o n p r o c e s s i s i n t r o d u c e d wi t h t h e e x a mp l e o f t h e I C d r i v e mi g r a t i o n f o r S TM 3 2 F 4 0 7 VG , a n d b y t h e r e a d / wr i t e 2 4 1 C 0 2 B
R T—T h r e a d的 I 2 C 总线驱 动 结构 分析 、 移 植及 应 用
高 培
AN0003+RT-Thread应用笔记之I2C设备应用指南
图4.2.1-1 发送数据函数调用关系
drv_mpu6050.c 中的 mpu6050_write_reg() 函数是MCU向mpu6050寄存器写数据。此函数的实现共有2种,分别 调用了I2C设备驱动接口 rt_i2c_transfer() 和 rt_i2c_master_send() 实现。
图3.2-2 修改MCU 图3.2-3 修改调试选项
5. 编译工程后下载程序至开发板运行。在终端PuTTY(打开对应端口,波特率配置为115200)输入 list_device 命令可以看到名为i2c2的设备,设备类型是I2C Bus,说明I2C设备驱动添加成功了。如图所示:
图3.2-4使用list_device命令查看i2c总线
1. 用户可以在msh shell输入 list_device 命令查看已有的I2C设备,确定I2C设备名称。 2. 查找设备使用 rt_i2c_bus_device_find() 或者 rt_device_find() ,传入I2C设备名称获取i2c总线设备句
柄。 3. 使用 rt_i2c_transfer() 即可以发送数据也可以接收数据,如果主机只发送数据可以使用
3.3 运行示例代码
将I2C示例代码里的 main.c 拷贝到\rt-thread\bsp\stm32f4xx-HAL\applications目录,替换原有的 main.c 。 drv_mpu6050.c、drv_mpu6050.h 拷贝到\rt-thread\bsp\stm32f4xx-HAL\drivers目录,并将它们添加到工程中对应 分组。如图所示:
I2C详细介绍及编程
I2C详细介绍及编程I2C(Inter-Integrated Circuit)是一种串行通信协议,常用于连接微控制器、传感器、存储器等设备,以实现数据通信。
本文将详细介绍I2C的原理、特点以及编程。
一、I2C的原理和特点I2C协议由飞利浦(Philips)公司于1982年开发,旨在简化数字电路上周边设备的通信。
I2C使用两条线(SCL和SDA)进行数据传输,其中SCL是时钟线,SDA是数据线。
这种双线式的通信使得I2C可以同时进行数据传输和电源供给,极大地简化了设备之间的连接。
在I2C通信中,主设备(通常是微控制器)发起通信,而从设备被动应答。
主设备通过在SCL线上产生时钟信号来控制通信节奏,并通过SDA 线实现数据传输。
数据的传输可以是单向的(主设备向从设备发送数据)或双向的(主设备与从设备之间的双向数据传输)。
I2C协议中的从设备通过一个唯一的地址来识别和寻址。
主设备可以选择与一个或多个从设备进行通信,只需发送相应的地址即可。
在开始通信前,主设备会发送一个开始信号,然后跟着从设备地址和读写位,然后才是数据或命令。
从设备在收到自己地址后会发出应答信号,主设备接收到应答信号后才会继续发送数据。
通信结束后,主设备会发送停止信号。
I2C的特点包括:1.双向通信:主设备和从设备之间可以进行双向的数据传输,减少通信线的需求和复杂度。
2.主-从结构:I2C通信中有一个主设备控制通信的发起和终止,而从设备被动应答。
3.多从结构:主设备可以与多个从设备进行通信,只需要发送不同的地址。
4.低速传输:I2C通信的时钟频率相对较低,一般在100kHz或400kHz。
二、I2C的编程实现在进行I2C编程之前,需要确保硬件上有I2C接口。
常见的I2C接口引脚包括SCL(时钟线)和SDA(数据线),同时需要进行相应的电源连接。
I2C编程的具体实现会有所差异,根据不同的硬件平台和编程语言而有所不同。
以下是一个基于Arduino平台的简单示例:```cpp#include <Wire.h>#define DEVICE_ADDRESS 0x50void setuWire.begin(;Serial.begin(9600);void loo//发送命令Wire.beginTransmission(DEVICE_ADDRESS);Wire.write(0x00); // 使用写入地址0x00Wire.write(0x01); // 写入数据0x01Wire.endTransmission(;delay(100);//读取数据Wire.requestFrom(DEVICE_ADDRESS, 1);if (Wire.available()int data = Wire.read(;Serial.print("Received: ");Serial.println(data);}delay(1000);```上述示例代码中,我们使用Wire库来实现I2C通信。
I2C设备驱动介绍
盈量而知芯,方行天下
I2C子系统
盈量而知芯,方行天下
I2C子系统
在Linux内核源代码中的drivers目录下包含一个i2c目录,而在i2c目录下又包含如下文件和 文件夹: • i2c-core.c : 这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。 • i2c-dev.c : 实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过 适配器访问设备时的主设备号都为89,次设备号为0~255。应用程序通过 “i2c-%d” (i2c-0, i2c-1, ..., i2c-10, ...)文件名并使用文件操作接口open()、write()、read()、ioctl()和close()等来 访问这个设备。i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write() 和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄 存器并控制I2C设备的工作方式。 • chips文件夹 : 这个目录中包含了一些特定的I2C设备驱动,如Dallas公司的DS1337实时钟 芯片、EPSON公司的RTC8564实时钟芯片和I2C接口的EEPROM驱动等。 • busses文件夹:这个文件中包含了一些I2C总线的驱动,如S3C2410的I2C控制器驱动为i2cs3c2410.c。 • algos文件夹 : 实现了一些I2C总线适配器的algorithm。 此外,内核中的i2c.h这个头文件对i2c_driver、i2c_client、i2c_adapter和i2c_algorithm这 4个数据结构进行了定义。理解这4个结构体的作用十分关键,分别给出了它们的定义。
I2C子系统
struct i2c_client { unsigned short flags; /* div., see below */ unsigned short addr; /* chip address - NOTE: 7bit */ /* addresses are stored in the */ /* _LOWER_ 7 bits */ char name[I2C_NAME_SIZE]; struct i2c_adapter *adapter; /* the adapter we sit on */ struct i2c_driver *driver; /* and our access routines */ struct device dev; /* the device structure */ int irq; /* irq issued by device */ struct list_head detected; };
RT-Thread学习之内核基础
RT-Thread,全称是Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。
事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。
RT-Thread 与其他很多 RTOS 如 FreeRTOS、uC/OS 的主要区别之一是,它不仅仅是一个实时内核,还具备丰富的中间层组件,如下图所示:RT-Thread提供了一些网络组件及软件包,我们可以运用这些软件包很方便地与云端进行通讯。
因此,RT-Thread是一个物联网操作系统(IoT OS)。
RT-Thread的架构由四层组成:硬件层、内核层、组件层、软件包。
其中硬件架构现在最常用的就是ARM架构,但是,同样值得关注的是RISC-V架构,这个势头很猛。
本篇笔记我们着重关注内核层。
分层的思想在我们软件方面来说是个很重要的思想,相邻层之间通过一些API接口进行交互,跨层之间不会互相影响。
此处,硬件层无论用哪一种架构,上面的组件层及软件包都能通用。
RT-Thread内核架构如下:其中,内核库kservice.c是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。
这部分根据编译器的不同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带更多的标准 C 库实现。
kservice.c中的函数如下:C 库:也叫 C 运行库(C Runtime Library),它提供了类似“strcpy”、“memcpy” 等函数,有些也会包括“printf”、“scanf” 函数的实现。
RT-Thread Kernel Service Library 仅提供内核用到的一小部分 C 库函数实现,为了避免与标准 C 库重名,在这些函数前都会添加上 rt_前缀。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
详细讲解RT-Thread I2C设备驱动框架及相关函数
本应用笔记以驱动I2C接口的6轴传感器MPU6050为例,说明了如何使用I2C设备驱动接口开发应用程序,并详细讲解了RT-Thread I2C设备驱动框架及相关函数。
1 本文的目的和结构
1.1 本文的目的和背景
I2C(或写作i2c、IIC、iic)总线是由Philips公司开发的一种简单、双向二线制(时钟SCL、数据SDA)同步串行总线。
它只需要两根线即可在连接于总线上的器件之间传送信息,是半导体芯片使用最为广泛的通信接口之一。
RT-Thread中引入了I2C设备驱动框架,I2C 设备驱动框架提供了基于GPIO模拟和硬件控制器的2种底层硬件接口。
1.2 本文的结构
本文首先描述了RT-Thread I2C设备驱动框架的基本情况,然后详细描述了I2C设备驱动接口,并使用I2C设备驱动接口编写MPU6050的驱动程序,并给出了在正点原子STM32F4探索者开发板上验证的代码示例。
2 I2C设备驱动框架简介
在使用MCU进行项目开发的时候,往往需要用到I2C总线。
一般来说,MCU带有I2C 控制器(硬件I2C),也可以使用MCU的2个GPIO自行编写程序模拟I2C总线协议实现同样的功能。
RT-Thread提供了一套I/O设备管理框架,它把I/O设备分成了三层进行处理:应用层、I/O 设备管理层、底层驱动。
I/O设备管理框架给上层应用提供了统一的设备操作接口和I2C 设备驱动接口,给下层提供的是底层驱动接口。
应用程序通过I/O设备模块提供的标准接口访问底层设备,底层设备的变更不会对上层应用产生影响,这种方式使得应用程序具有很好的可移植性,应用程序可以很方便的从一个MCU移植到另外一个MCU。
本文以6轴惯性传感器MPU6050为例,使用RT-Thread I2C设备驱动框架提供的GPIO模拟I2C控制器的方式,阐述了应用程序如何使用I2C设备驱动接口访问I2C设备。