Linux之IIC驱动

一、IIC接口下的24C02 驱动分析

1、I2C通信介绍

它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式

每个挂接在总线上的器件都有个唯一的地址

位速在标准模式下可达 100kbit/s,在快速模式下可达400kbit/s,在高速模式下可待3.4Mbit/s。

2、I2C总线系统结构,如下所示

I2C总线系统结构

其中SCL时钟线的频率由主机提供,且从机不能主动来引起数据传输,必须等待主机先发信号才行

两个或多个主机同时发起数据传输时,可以通过冲突检测和仲裁来防止数据被破坏。

3、I2C时序介绍

1)空闲状态

当总线上的SDA和SCL两条信号线同时处于高电平,便是空闲状态,如上面的硬件图所示,当我们不传输数据时, SDA和SCL被上拉电阻拉高,即进入空闲状态

2)起始信号

当SCL为高期间,SDA由高到低的跳变;便是总线的启动信号,只能由主机发起,且在空闲状态下才能启动该信号,如下图所示:

起始信号

3)停止信号

当SCL为高期间,SDA由低到高的跳变;便是总线的停止信号,表示数据已传输完成,如下图所示:

停止信号

4)传输数据格式

当发了起始信号后,就开始传输数据,传输的数据格式如下图所示:

当SCL为高电平时,便会获取SDA数据值,其中SDA数据必须是稳定的(若SDA不稳定就会变成起始/停止信号)

当SCL为低电平时,便是SDA的电平变化状态

若主从机在传输数据期间,需要完成其它功能(例如一个中断),可以主动拉低SCL,使I2C进入等待状态,直到处理结束再释放SCL,数据传输会继续

传输数据格式

5)应答信号ACK

I2C总线上的数据都是以8位数据(字节)进行的,当发送了8个数据后,发送方会在第9个时钟脉冲期间释放SDA数据,当接收方接收该字节成功,便会输出一个ACK应答信号,当SDA为高电平,表示为非应答信号NACK,当SDA为低电平,表示为有效应答信号ACK

主机为接收方时,收到最后一个字节后,主机可以不发送ACK,直接发送停止信号来结束传输。
从机为接收方时,没有发送ACK,则表示从机可能在忙其它事、或者不匹配地址信号和不支持多主机发送,主机可以发送停止信号再次发送起始信号启动新的传输

应答信号

并非每传输8位数据之后,都会有ACK信号,有以下3种例外。

  • 当从机不能响应从机地址时(例如它正忙于其他事而无法响应I2C总线的操作,或者这个地址没有对应的从机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。
    这时,主机发出一个P信号终止传输或者重新发出一个S信号开始新的传输。
  • 如果从机接收器在传输过程中不能接收更多的数据时,它也不会发出ACK信号。这样,主机就可以意识到这点,从而发出一个P信号终止传输或者重新发出一个S信号开始新的传输。
  • 主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。

6)完整的数据传输

如下图所示, 发送起始信号后,便发送一个8位的设备地址,其中第8位是对设备的读写标志,后面紧跟着的就是数据了,直到发送停止信号终止

当我们第一次是读操作,然后想换成写操作时,可以再次发送一个起始信号,然后发送读的设备地址,不需要停止信号便能实现不同的地址转换

完整的数据传输

4、AT24C02介绍

AT24C02是通过I2C实现通讯的,是一个存储芯片,能够存储2Kb(256KB*8)数据

4.1、原理图

原理图

其中A2~A0,是这个24C02设备的硬件地址,接GND表示硬件地址都为0

4.2、AT24C02的数据格式

如下所示:

数据格式

4.3、AT24C02的设备地址

打开AT24C02数据手册,它的设备地址如下图所示:
设备地址

其中A2~A1表示硬件地址,P2~P0表示page页地址

bit[0]地址:表示读/写状态,1:读,0:写

所有I2C器件都是这样,最低位表示方向位

4.3.1、为什么需要page页地址

因为I2C的数据位是8位,而AT24CXX的读写地址值最大可以为2048(2^11^),超过了I2C的数据位

而page页地址就是用来解决这个问题的

比如AT24C16:

当发送:0XA2(设备地址P[2:0]=0x01) ,0x00(读地址)时:

表示要读的真正地址=0x01(页地址)*256+0(读地址)=0x100,转化为二进制= 1 0000 0000
当发送:0XA0(设备地址), 0x00(读地址)时:

表示要读的真正地址=0x00*256+0xFF=0xFF,转化为二进制= 0 1111 1111

4.3.2、对于AT24C02
  • 芯片的容量小于等于2^8(256)字节,那么读写地址就用8bit来表示,所以设备地址里没有P2~P0
  • 读操作时,发送的设备地址等于0XA1
  • 写操作时,发送的设备地址等于0XA0

5、AT24C02时序图介绍

5.1、写时序介绍

写时序

当随机写一个字节时,只需要先发送一个起始信号,然后跟上0XA0设备地址,以及要写的起始地址值,后面便是要写入地址的data,如果需要连续写数据,只需要连续写入data,地址会自动加1,直到发送停止信号结束

5.2、读时序介绍

读时序

当随机读一个字节时,先发送第一个起始信号,然后写入0XA0设备地址和要读的地址值,

接着发送第二个起始信号,然后写入0XA1设备地址,接着就是要读的data,如果需要连续读数据,只需要连续读出data,地址会自动加1,直到发送停止信号结束

二、Linux下IIC驱动分析

1、I2C体系结构分析

1.1、driver/i2c目录

进入linux内核的driver/i2c目录下,其中重要的文件介绍如下:

  • 1)algos文件夹(algorithms)

    里面保存I2C的通信方面的算法

  • 2)busses文件夹

    里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。

  • 3) chips文件夹

    里面保存I2C设备驱动相关的文件,比如m41t00.c就是RTC实时钟

  • 4) i2c-core.c
    这个文件实现了I2C核心的功能(I2C总线的初始化、注册和适配器添加和注销等相关工作)以及/proc/bus/i2c*接口。

  • 5) i2c-dev.c
    提供了通用的read() 、write()和ioctl()等接口,实现了I2C适配器设备文件的功能,其中I2C设备的主设备号都为89, 次设备号为0~255。
    应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器, 并控制I2C设备的工作方式

显然,它和前几次驱动类似, I2C也分为总线驱动和设备驱动,总线就是协议相关的,它知道如何收发数据,但不知道数据含义,设备驱动却知道数据含义

1.2、I2C驱动架构

I2C驱动架构

如上图所示,每一条I2C对应一个adapter适配器(可以理解为插槽,就是利用之前的dev-bus-drv模型改的,属于之前的dev设备模型),在kernel中,adapter适配器是通过struct adapter结构体定义,主要是通过i2c core层将i2c设备与i2c adapter关联起来。

在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()i2c_add_numbered_adapter()。由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号。这个总线号和PCI中的总线号不同。它和硬件无关,只是软件上便于区分而已。

对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败。

2、分析I2C总线驱动i2c-s3c2410.c

参考drivers/i2c/busses/i2c-s3c2410.c

先进入init入口函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static struct platform_driver s3c2440_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2440-i2c",//driver名字
},
};

static int __init i2c_adap_s3c_init(void)
{
int ret;

ret = platform_driver_register(&s3c2410_i2c_driver);
if (ret == 0) {
ret = platform_driver_register(&s3c2440_i2c_driver);//注册平台驱动s3c2440_i2c_driver
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
}

return ret;
}

在init函数中,注册了一个 “s3c2440-i2c”的platform_driver平台驱动,当内核中有同名的平台设备时会调用platform_driver结构体的.probe成员函数,我们来看看probe函数做了些什么

3、s3c24xx_i2c_probe()分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct i2c_adapter  adap;

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
... ...

/*获取,使能I2C时钟*/
i2c->clk = clk_get(&pdev->dev, "i2c");//获取i2c时钟
clk_enable(i2c->clk); //使能i2c时钟

... ....
/*获取资源*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = ioremap(res->start, (res->end-res->start)+1);

... ....

/*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/
i2c->adap.algo_data = i2c; //i2c_adapter适配器指向s3c24xx_i2c;
i2c->adap.dev.parent = &pdev->dev;


/* initialise the i2c controller */
/*初始化2440的I2C相关的寄存器*/
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;

... ...
/*注册中断服务函数*/
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);
... ...

/*注册i2c_adapter适配器结构体*/
ret = i2c_add_adapter(&i2c->adap);
... ...
}

其中i2c_adapter结构体是放在s3c24xx_i2c->adap下,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct s3c24xx_i2c {
spinlock_t lock;
wait_queue_head_t wait;

struct i2c_msg *msg;
unsigned int msg_num;
unsigned int msg_idx;
unsigned int msg_ptr;

unsigned int tx_setup;

enum s3c24xx_i2c_state state;

void __iomem *regs;
struct clk *clk;
struct device *dev;
struct resource *irq;
struct resource *ioarea;
struct i2c_adapter adap;//i2c_adapter适配器结构体
};

4、i2c_add_adapter()函数分析

该函数是注册i2c_adapter适配器结构体,这里我们来分析下他是如何进行注册的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;

retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) //调用idr_pre_get()为i2c_adapter预留内存空间
return -ENOMEM;

mutex_lock(&core_lists);

/* "above" here means "above or equal to", sigh */
res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);
//调用idr_get_new_above()将结构插入i2c_adapter_idr中,并将插入的位置赋给id,以后可以通过id在i2c_adapter_idr中找到相应的i2c_adapter结构体

mutex_unlock(&core_lists);

if (res < 0) {
if (res == -EAGAIN)
goto retry;
return res;
}
adapter->nr = id;
return i2c_register_adapter(adapter); //调用i2c_register_adapter()函数进一步来注册.
}

接着分析**i2c_register_adapter()**函数,其代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int i2c_register_adapter(struct i2c_adapter *adap)
{
struct list_head *item; //链表头,用来存放i2c_driver结构体的表头
struct i2c_driver *driver; //i2c_driver,用来描述一个IIC设备驱动
list_add_tail(&adap->list, &adapters); //添加到内核的adapter链表中
... ...
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);
... ...
list_for_each(item,&drivers) { //for循环,从drivers链表里找到i2c_driver结构体的表头
driver = list_entry(item, struct i2c_driver, list);//通过list_head表头,找到i2c_driver结构体
if (driver->attach_adapter)
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap);//调用i2c_driver的attach_adapter函数来看看,这个新注册的设配器是否支持i2c_driver
}
}

在**i2c_register_adapter()**函数里主要执行以下几步:

  • ①将adapter放入i2c_bus_type的adapter链表

  • ②将所有的i2c设备调出来,执行i2c_driver设备的attach_adapter函数来匹配

其中, i2c_driver结构体会在后面讲述到

i2c_adapter适配器结构体的成员结构,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct i2c_adapter {
struct module *owner;        //所属模块
unsigned int id;           //algorithm的类型,定义于i2c-id.h,
unsigned int class;
const struct i2c_algorithm *algo; //总线通信方法结构体指针
void *algo_data;          //algorithm数据
struct rt_mutex bus_lock;     //控制并发访问的自旋锁
int timeout;
int retries;             //重试次数
struct device dev;          //适配器设备
int nr;      //存放在i2c_adapter_idr里的位置号
char name[48];            //适配器名称
struct completion dev_released;  //用于同步
struct list_head userspace_clients;//client链表头
};

i2c_adapter表示物理上的一个i2C设备(适配器), 在i2c-s3c2410.c中,是存放在s3c24xx_i2c结构体下的(struct i2c_adapter adap)成员中

5、s3c24xx_i2c的结构体成员分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer, //主机传输
.functionality = s3c24xx_i2c_func,
};

static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50, //用来延时,等待SCL被释放
.adap = { // i2c_adapter适配器结构体
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm,//存放i2c_algorithm算法结构体
.retries = 2, //重试次数
.class = I2C_CLASS_HWMON,
},
};

显然这里是直接设置了i2c_adapter结构体,所以在s3c24xx_i2c_probe ()函数中没有分配i2c_adapter适配器结构体,

其中, i2c_adapter结构体的名称等于"s3c2410-i2c",它的通信方式等于s3c24xx_i2c_algorithm,重试次数等于2

如果缺少i2c_algorithm的i2c_adapter什么也做不了,就只是个I2C设备,而没有通信方式

s3c24xx_i2c_algorithm中的关键函数**master_xfer()**就是用于产生i2c通信所需要的start、stop、ack等信号

比如,在s3c24xx_i2c_algorithm中的关键函数master_xfer()里,调用了:

s3c24xx_i2c_xfer -> s3c24xx_i2c_doxfer()->s3c24xx_i2c_message_start()

来启动传输message信息, 其中s3c24xx_i2c_message_start()函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg)
{
unsigned int addr = (msg->addr & 0x7f) << 1;//IIC从设备地址的最低位为读写标志位
... ...

stat = 0;
stat |= S3C2410_IICSTAT_TXRXEN; //设置标志位启动IIC收发使能

if (msg->flags & I2C_M_RD) { //判断是读,还是写
stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1; //设置从IIC设备地址为读标志
} else
stat |= S3C2410_IICSTAT_MASTER_TX;

s3c24xx_i2c_enable_ack(i2c); //使能ACK信号

iiccon = readl(i2c->regs + S3C2410_IICCON);//读出IICCON寄存器

writel(stat, i2c->regs + S3C2410_IICSTAT); //写入IICSTAT寄存器,使能IIC的读或写标志

dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);

writeb(addr, i2c->regs + S3C2410_IICDS); //将IIC从设备地址写入IICDS寄存器

/* delay here to ensure the data byte has gotten onto the bus
* before the transaction is started */

ndelay(i2c->tx_setup); //延时,等待SCL被释放,下面便可以发送起始信号+IIC设备地址值


dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
writel(iiccon, i2c->regs + S3C2410_IICCON);

stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT);//设置IICSTAT寄存器的bit5=1,开始发送起始信号+IIC从设备地址值,并回应ACK
}

通过上面的代码和注释,发现主要是写入IIC从设备地址,然后发送起始信号+IIC从设备地址值,并回应ACK

显然IIC总线驱动i2c-s3c2410.c中主要是设置适配器adapter。里面帮我们做好了IIC通信的架构,就是不知道发什么内容

6、分析I2C设备驱动eeprom.c

进入driver/i2c/chips中,看看eeprom设备驱动是如何写的

参考: driver/i2c/chips/eeprom.c

首先来看它的init入口函数:

1
2
3
4
5
6
7
8
9
static int __init eeprom_init(void)
{
return i2c_add_driver(&eeprom_driver);
}

static void __exit eeprom_exit(void)
{
i2c_del_driver(&eeprom_driver);
}

其中struct i2c_driver eeprom_driver的成员如下:

1
2
3
4
5
6
7
8
static struct i2c_driver eeprom_driver = {
.driver = {
.name = "eeprom",//名称
},
.id = I2C_DRIVERID_EEPROM,//IIC设备标识ID
.attach_adapter = eeprom_attach_adapter,//用来与总线驱动的适配器匹配,匹配成功添加到适配器adapter中
.detach_client = eeprom_detach_client,//与总线驱动的适配器解绑,分离这个IIC从设备
};

如下所示, eeprom_driver结构体的ID成员在i2c-id.h中,里面还定义了大部分常用I2C设备驱动的设备ID(事实证明不写也行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
......
/* IDs -- Use DRIVERIDs 1000-1999 for sensors.
These were originally in sensors.h in the lm_sensors package */
#define I2C_DRIVERID_LM78 1002
#define I2C_DRIVERID_LM75 1003
#define I2C_DRIVERID_GL518 1004
#define I2C_DRIVERID_EEPROM 1005//EEPROM的ID
#define I2C_DRIVERID_W83781D 1006
#define I2C_DRIVERID_LM80 1007
#define I2C_DRIVERID_ADM1021 1008
#define I2C_DRIVERID_ADM9240 1009
#define I2C_DRIVERID_LTC1710 1010
#define I2C_DRIVERID_BT869 1013
#define I2C_DRIVERID_MAXILIFE 1014
#define I2C_DRIVERID_MATORB 1015
#define I2C_DRIVERID_GL520 1016
#define I2C_DRIVERID_THMC50 1017
#define I2C_DRIVERID_ADM1025 1020
#define I2C_DRIVERID_LM87 1021
#define I2C_DRIVERID_PCF8574 1022
#define I2C_DRIVERID_MTP008 1023
#define I2C_DRIVERID_DS1621 1024
#define I2C_DRIVERID_ADM1024 1025
#define I2C_DRIVERID_CH700X 1027 /* single driver for CH7003-7009 digital pc to tv encoders */
#define I2C_DRIVERID_FSCPOS 1028
#define I2C_DRIVERID_FSCSCY 1029
#define I2C_DRIVERID_PCF8591 1030
#define I2C_DRIVERID_LM92 1033
#define I2C_DRIVERID_SMARTBATT 1035
#define I2C_DRIVERID_BMCSENSORS 1036
#define I2C_DRIVERID_FS451 1037
#define I2C_DRIVERID_LM85 1039
#define I2C_DRIVERID_LM83 1040
#define I2C_DRIVERID_LM90 1042
#define I2C_DRIVERID_ASB100 1043
#define I2C_DRIVERID_FSCHER 1046
#define I2C_DRIVERID_W83L785TS 1047
#define I2C_DRIVERID_OV7670 1048 /* Omnivision 7670 camera */
......

显然,在init函数中通过i2c_add_driver()注册i2c_driver结构体,然后通过i2c_driver ->attach_adapter来匹配内核中的各个总线驱动的适配器,发送这个设备地址,若有ACK响应,表示匹配成功

7、i2c_add_driver()函数分析

接下来,我们进入i2c_add_driver()来看看是不是像刚刚分析的那样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int i2c_add_driver(struct module *owner, struct i2c_driver *driver)
{
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type; //将i2c_driver放在i2c_bus_type链表中

res = driver_register(&driver->driver);//注册一个i2c_driver
... ...

if (driver->attach_adapter) {
struct i2c_adapter *adapter;//定义一个i2c_adapter适配器
list_for_each_entry(adapter, &adapters, list)//for循环提取出adapters链表中所有的i2c_adapter适配器,放入到adapter结构体中
{
driver->attach_adapter(adapter);//来匹配取出来的i2c_adapter适配器
}
}
... ...
return 0;
}

在i2c_add_driver()函数里主要执行以下几步:

  • ①将i2c_driver放入到i2c_bus_type链表

  • ②取出adapters适配器链表中所有的i2c_adapter适配器,然后执行i2c_driver->attach_adapter()

所以i2c_adapter适配器和i2c_driver设备驱动注册框架如下所示:

i2c_adapter适配器和i2c_driver设备驱动注册框架

这里调用了i2c_driver->attach_adapter(adapter),我们看看里面是不是通过发送IIC设备地址,等待ACK响应来匹配的

8、i2c_driver ->attach_adapter()函数分析

这里以struct i2c_driver eeprom_driver 为例

1
2
3
4
5
6
7
8
9
/* This is the driver that will be inserted */
static struct i2c_driver eeprom_driver = {
.driver = {
.name = "eeprom",
},
.id = I2C_DRIVERID_EEPROM,
.attach_adapter = eeprom_attach_adapter,//attach_adapter函数
.detach_client = eeprom_detach_client,
};

所以这里的i2c_driver ->attach_adapter()函数即为eeprom_attach_adapter()

如下所示,eeprom_attach_adapter()调用了i2c_probe(adapter, &addr_data, eeprom_detect)函数

1
2
3
4
static int eeprom_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, eeprom_detect);
}

上述代码中i2c_probe的参数如下:

  • 第1个参数是i2c_adapter适配器;

  • 第2个参数addr_data变量,里面存放了IIC设备地址的信息;

  • 第3个参数eeprom_detect就是具体的设备探测回调函数i2c_probe()函数,会通过adapter适配器发送IIC设备地址addr_data,如果收到ACK信号,就调用eeprom_detect()回调函数来注册i2c_client结构体,该结构体对应真实的物理从设备,而i2c_driver对应的是设备驱动,也就是说,只有当适配器支持这个设备驱动,才会注册i2c_client从设备,后面会讲这个回调函数如何注册i2c_client

    而在i2c_driver ->detach_client()中,则注销i2c_client结构体

其中addr_data变量是struct i2c_client_address_data结构体,它的成员如下所示:

1
2
3
4
5
6
struct i2c_client_address_data {
unsigned short *normal_i2c; //存放正常的设备高7位地址数据
unsigned short *probe; //存放不受*ignore影响的高7位设备地址数据
unsigned short *ignore; //存放*ignore的高7位设备地址数据
unsigned short **forces; //forces表示适配器匹配不了该设备,也要将其放入适配器中
};

当上面结构体的数组成员以I2C_CLIENT_END结尾,则表示地址已结束,比如AT24C02设备为例,看这个结构体如何定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define  AT24C02_ADDR           (0xA0>>1)           //AT24C02地址

static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { AT24C02_ADDR, I2C_CLIENT_END };
static unsigned short force_addr[] = {ANY_I2C_BUS, AT24C02_ADDR ,2C_CLIENT_END};
static unsigned short* forces[] = {force_addr, NULL};
//ANY_I2C_BUS:表示支持所有适配器总线,若填指定的适配器总线ID,则表示该设备只支持指定的那个适配器

static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, //存放at24c02地址
.probe = ignore, //表示无地址
.ignore = ignore, //表示无地址
. forces = forces, //存放强制的at24c02地址,表示强制支持
};

一般而言,都不会设置.forces成员,这里只是打个比方

8.1、i2c_probe()函数分析

1
2
3
4
5
int i2c_probe(struct i2c_adapter *adapter,struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int))
{
... ...
err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);
}

里面调用了i2c_probe_address()函数,从名称上来看,显然它就是用来发送起始信号+设备地址,来探测IIC设备地址用的

8.2、i2c_probe_address()函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int))
{
/*判断设备地址是否有效,addr里存放的是设备地址前7位,比如AT24C02=0xA0,那么addr=0x50*/
if (addr < 0x03 || addr > 0x77) {
dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);//打印地址无效,并退出
return -EINVAL;
}

/*查找链表中其它IIC设备的设备地址,若这个设备地址已经被使用,则return*/
if (i2c_check_addr(adapter, addr))
return 0;

if (kind < 0) {
if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)//进入I2C传输函数
return 0;
... ...
}

8.3、i2c_smbus_xfer()函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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) { //如果adapter适配器有smbus_xfer这个函数
mutex_lock(&adapter->bus_lock);//加互斥锁
res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);//调用adapter适配器里的传输函数
mutex_unlock(&adapter->bus_lock);//解互斥锁
} else //否则使用默认函数传输设备地址
res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
return res;
}

看了上面代码后,显然我们的s3c2410-i2c适配器没有algo->smbus_xfer函数,而是使用i2c_smbus_xfer_emulated()函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
//缺少smbus_xfer函数
};

static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
.adap = {//s3c2410的i2c_adapter结构体适配器
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm,
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};

通常适配器都是不支持的,都使用默认的i2c_smbus_xfer_emulated()函数

8.4、i2c_smbus_xfer_emulated()函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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)
{
unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];//属于msg[0]的buf成员
unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];//属于msg[1]的buf成员
int num = read_write == I2C_SMBUS_READ?2:1; //如果为读命令,就等于2,表示要执行两次数据传输
struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
{ addr, flags | I2C_M_RD, 0, msgbuf1 }};//定义两个i2c_msg结构体,


msgbuf0[0] = command; //IIC设备地址最低位为读写命令
... ...

if (i2c_transfer(adapter, msg, num) < 0)
return -1;

/*设置i2c_msg结构体成员*/
if (read_write == I2C_SMBUS_READ)
switch(size) {
... ...
case I2C_SMBUS_BYTE_DATA://如果是读字节
if (read_write == I2C_SMBUS_READ)
msg[1].len = 1;
else {
msg[0].len = 2;
msgbuf0[1] = data->byte;
}
break;
... ...
}
... ...

if (i2c_transfer(adapter, msg, num) < 0)//将i2c_msg结构体的内容发送给I2C设备
return -1;
... ...
}

其中i2c_msg结构体的结构,如下所示:

1
2
3
4
5
6
struct i2c_msg {
__u16 addr; //I2C从机的设备地址
__u16 flags; //当flags=0表示写, flags= I2C_M_RD表示读
__u16 len; //传输的数据长度,等于buf数组里的字节数
__u8 *buf; //存放数据的数组
};

上面代码中之所以读操作需要两个i2c_msg,写操作需要一个i2c_msg,是因为读IIC设备是两个流程,在上一节已经分析到了,具体如下所示:

只要发送一个S起始信号则就是一个i2c_msg,如下图所示:

读一个字节

写一个字节

而在i2c_transfer()函数中,最终又是调用了之前分析的i2c_adapter->algo->master_xfer()发送函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
int ret;

if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif

mutex_lock_nested(&adap->bus_lock, adap->level);
ret = adap->algo->master_xfer(adap,msgs,num);//最终还是调用master_xfer()函数
mutex_unlock(&adap->bus_lock);

return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -ENOSYS;
}
}

其中i2c_transfer()的参数adap表示通过哪个适配器传输出去,msgs表示I2C消息,num表示msgs的数目

内核每发送一个Msg都会先发出S开始信号和设备地址,直到所有Msg传输完毕,最后发出P停止信号。

当i2c_transfer()返回值为正数,表示已经传输正数个数据,当返回负数,说明I2C传输出错

8.5、总结

综上,在i2c_driver->attach_adapter(adapter)函数里主要执行以下几步:

  1. 调用i2c_probe(adap适配器, i2c_client_address_data设备地址结构体, 回调函数);

  2. 将要发的设备地址结构体打包成i2c_msg

  3. 然后执行**i2c_transfer()**来调用i2c_adapter->algo->master_xfer()将i2c_msg发出去

  4. 若收到ACK回应(即发现有I2C设备),便进入回调函数,注册i2c_client从设备,使该设备与适配器联系在一起

所以适配器和iic设备驱动最终注册框架图如下所示:

最终注册框架图

9、注册i2c_client从设备

接下来便来分析回调函数如何注册i2c_client从设备,先来看看i2c_client结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct i2c_client {  

unsigned short flags;//标志

unsigned short addr;//该i2c从设备的设备地址,存放地址高7位

char name[I2C_NAME_SIZE];//设备名字

struct i2c_adapter *adapter;//依附的i2c_adapter,表示该IIC设备支持哪个适配器

struct i2c_driver *driver;//依附的i2c_driver,表示该IIC从设备的驱动是哪个

struct device dev;//设备结构体

int irq;//设备所使用的结构体

struct list_head detected;//链表头

};

还是以driver/i2c/chips/eeprom.c为例,如下所示:

1
2
3
4
static int eeprom_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, eeprom_detect);
}

这里的回调函数是eeprom_detect()函数接下来便分析该函数

9.1、回调函数eeprom_detect()分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *new_client; //定义一个i2c_client结构体局部变量

new_client =kzalloc(sizeof(struct i2c_client), GFP_KERNEL);//分配i2c_client结构体为全局变量


/*设置i2c_client结构体*/
new_client->addr = address; //设置设备地址
new_client->adapter = adapter; //设置依附的i2c_adapter
new_client->driver = &eeprom_driver; //设置依附的i2c_driver
new_client->flags = 0; //设置标志位为初始值
strlcpy(new_client->name, "eeprom", I2C_NAME_SIZE);//设置名字


/*注册i2c_client*/
if ((err = i2c_attach_client(new_client)))
goto exit_kfree; //注册失败,便释放i2c_client这个全局变量
... ...

exit_kfree:
kfree(new_client);
exit:
return err;
}

当注册了i2c_client从设备后,便可以使用i2c_transfer()来实现与设备传输数据了

10、24C02驱动及测试程序的编写

参考driver/i2c/chips/eeprom.c驱动,可以得出驱动代码步骤如下:

  1. 定义file_operations结构体 ,设置字符设备的读写函数(实现对24C02的读写操作)
    //构造i2c_msg结构体,使用i2c_transfer()来实现与设备传输数据

  2. 定义i2c_client_address_data结构体,里面保存24C02的设备地址

  3. 定义一个i2c_driver驱动结构体

    3.1 设置i2c_driver-> attach_adapter // i2c_add_driver函数的最后会调用attach_adapter函数,attach_adapter函数最后会对adapters链表中的每个adapter(可理解为设备dev(这里的adapter相当于芯片内的硬件i2c部分,而芯片外接的i2c从机可理解为i2c_client))调用该函数与相应的驱动进行连接,里面直接调用函数i2c_probe(adap适配器, i2c_client_address_data设备地址结构体, 回调函数fn);即可

    attach_adapter函数(实际上是i2c_probe)中的连接过程:

    1. 发出start信号(调用adapter->algo->master_xfer算法函数)
    2. 发出设备地址
    3. 若从设备会用ACK信号则调用回调函数fn

    3.2 设置i2c_driver-> detach_client //卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理,主要负责卸载i2c_client、字符设备

  4. 回调函数fn,里面注册i2c_client,字符设备( 字符设备用来实现读写24C02里的数据)

    4.1 分配并设置i2c_client(adress(高七位,不算读写位)、adapter、driver、name)

    4.2 使用i2c_attach_client()将i2c_client与适配器adapter进行连接

    此步骤后i2c_driver->detach_client函数才能在卸载时被正常调用

    • 上述两步可通过以下函数实现

      1
      2
      3
      struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)//认为设备肯定存在
      //adap:适配器
      //info:单板信息

      或:

      1
      2
      3
      4
      struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,struct i2c_board_info *info,unsigned short const *addr_list)//对于“已经识别出来的设备”,才会创建(最后调用i2c_new_device)
      //adap:适配器
      //info:单板信息
      //addr_list:地址列表,会根据该地址列表依次判断设备是否有回应,即是否存在

    4.3 注册字符设备

  5. 写init入口函数,exit出口函数
    init:使用**i2c_add_driver()注册i2c_driver驱动结构体
    exit:使用
    i2c_del_driver()**卸载i2c_driver驱动结构体

  6. i2c数据传输:

6.1 构造i2c消息i2c_msg结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
struct i2c_msg {
__u16 addr; /*从机地址,一般为i2c_client->addr*/
__u16 flags;/* 0:写 */
#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
#define I2C_M_RD 0x01 /* 读 */
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* 消息长度 */
__u8 *buf; /* 消息数据指针 */
};

6.2 调用函数i2c_transfer()函数传输消息

1
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)

adap:适配器(dev),一般i2c_client->adapter

msgs:传输消息

num:消息条数

返回值:发送成功条数


i2c从机设备i2c_client的4种构建方法(3.4.2内核)

1. 通过总线号声明

定义一个i2c_board_info结构体,用于描述硬件名字、地址addr及相应其他数据,该结构体会在系统初始化时被i2c_register_board_info调用并生成对应的i2c_client

1
2
3
static struct i2c_board_info at24cxx_info = {
I2C_BOARD_INFO("at24c08",0x50),
}

i2c_register_board_info函数最后还是通过调用i2c_new_device生成及注册i2c_client

限制:不适合动态加载

2. 直接创建设备

直接使用i2c_new_devicei2c_new_probed_device函数,具体使用方法见上文

可以通过以下方法取得适配器:

1
2
3
4
5
6
static struct i2c_client *at24cxx_client;
...
struct i2c_adapter *i2c_adap;
i2c_adap = i2c_get_adapter(0);//取得系统中的第0个适配器
at24cxx_client = i2c_new_device(i2c_adap,&at24cxx_info);//得到i2c_client
i2c_put_adapter(i2c_adap);//释放适配器
3. 从用户空间创建设备

new_device写入数据,能够自己创建设备:

1
2
3
4
#eg1:
$ echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-0/new_device
#eg2:
$ echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device

delete_device写入数据,能够自己删除设备

最终仍是调用i2c_new_device

4. 探测某些设备的I2c总线

例子:drivers/hwmon/lm90.c

使用较少,类似于2.6内核中的方式


具体驱动代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
* I2C-24C02
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static struct i2c_client *at24c02_client; //从设备结构体
static struct class *at24c02_class; //类结构体
static unsigned int at24c02_major;

/*1.定义file_operations结构体 ,
* 设置字符设备的读写函数(实现对24C02的读写操作)
*/
static ssize_t at24c02_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
struct i2c_msg msg[2];
u8 addr;
u8 data;
int ret;

if(size!=1)
return -EINVAL;

copy_from_user(&addr,buf,1);

/* 数据传输三要素: 源,目的,长度 */
/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr=at24c02_client->addr;/* 目的 */
msg[0].flags=0; /* 表示写 */
msg[0].len =1; /* 地址=1 byte */
msg[0].buf =&addr; /* 源 */

/* 然后启动读操作 */
msg[1].addr=at24c02_client->addr;/* 源 */
msg[1].flags=I2C_M_RD; /* 表示读 */
msg[1].len =1; /* 数据=1 byte */
msg[1].buf =&data; /* 目的 */

ret=i2c_transfer(at24c02_client->adapter, msg, 2);
if(ret==2) //表示2个msg传输成功
{
copy_to_user(buf,&data,1); //上传数据
return 0;
}
else
return -EAGAIN;
}

static ssize_t at24c02_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
struct i2c_msg msg[1];
u8 val[2];
int ret;

if(size!=2)//地址 数据
return -EINVAL;

copy_from_user(val,buf,2);

/* 数据传输三要素: 源,目的,长度 */
msg[0].addr=at24c02_client->addr;/* 目的 */
msg[0].flags=0; /* 表示写 */
msg[0].len =2; /* 地址+数据=2 byte */
msg[0].buf =val; /* 源 */

ret=i2c_transfer(at24c02_client->adapter, msg, 1);
if(ret==1) //表示1个msg传输成功
{
return 0;
}
else
return -EAGAIN;
}

static struct file_operations at24c02_fops={
.owner = THIS_MODULE,
.read = at24c02_read,
.write = at24c02_write,
};


/*2.定义i2c_client_address_data结构体,保存24C02的设备地址*/
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = {0X50, I2C_CLIENT_END };//这里地址值是7位,左移一位后即为0xA0
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};//这地地址设为0x60,即使不存在该设备也会认为存在这个设备,并调用at24cxx_detect函数
static unsigned short * forces[] = {force_addr, NULL};//指针数组

static struct i2c_client_address_data at24c02_addr={
.normal_i2c=normal_addr,//要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备
.probe=ignore,
.ignore=ignore,
// .forces=forces, // 强制地址,强制认为存在这个设备,一般不使用
};

/*3. 定义一个i2c_driver驱动结构体*/
static int at24c02_attach_adapter(struct i2c_adapter *adapter);
static int at24c02_detach_client(struct i2c_client *client);
static int at24c02_detect(struct i2c_adapter *adap, int addr, int kind);

/* This is the driver that will be inserted */
static struct i2c_driver at24c02_driver = {
.driver = {
.name = "at24c02",
},

.attach_adapter = at24c02_attach_adapter,//绑定回调函数
.detach_client = at24c02_detach_client, //解绑回调函数
};

/*3.1 设置i2c_driver-> attach_adapter*/
static int at24c02_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_probe(adapter,&at24c02_addr, at24c02_detect);
}


/*3.2 设置i2c_driver-> detach_client*/
static int at24c02_detach_client(struct i2c_client *client)
{
printk("at24c02_detach_client\n");
//销毁i2c_client结构体at24c02_client
i2c_detach_client(at24c02_client) ;
kfree(at24c02_client);
//销毁类和对应的设备
class_device_destroy(at24c02_class,MKDEV(at24c02_major, 0));
class_destroy(at24c02_class);

return 0;
}

/*4.写回调函数,里面注册i2c_client,字符设备*/
static int at24c02_detect(struct i2c_adapter *adap, int addr, int kind)
{
printk("at24c02_detect\n");

/* 4.1 分配并设置i2c_client */
at24c02_client= kzalloc(sizeof(struct i2c_client), GFP_KERNEL);

at24c02_client->addr = addr;
at24c02_client->adapter = adap;
at24c02_client->driver = &at24c02_driver;
at24c02_client->flags = 0;
strlcpy(at24c02_client->name, "at24c02", I2C_NAME_SIZE);

/*4.2 使用i2c_attach_client()将i2c_client与适配器进行连接*/
i2c_attach_client(at24c02_client);

/*4.3 注册字符设备*/
at24c02_major= register_chrdev(0, "at24c02", &at24c02_fops);
at24c02_class=class_create(THIS_MODULE, "at24c02");
class_device_create(at24c02_class,0, MKDEV(at24c02_major, 0),0,"at24c02");
return 0;
}


/*5. 写init入口函数,exit出口函数*/
static int at24c02_init(void)
{
i2c_add_driver(&at24c02_driver);
return 0;
}
static void at24c02_exit(void)
{
i2c_del_driver(&at24c02_driver);
}

module_init(at24c02_init);
module_exit(at24c02_exit);
MODULE_LICENSE("GPL");

测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* 测试方法:
* i2c_test r addr
* i2c_test w addr val
*/
void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];

if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}

//打开设备
fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}

//读操作
if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);//将字符串转为数字
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
}
else if (strcmp(argv[1], "w") == 0)//写操作
{
buf[0] = strtoul(argv[2], NULL, 0);//将字符串转为数字
buf[1] = strtoul(argv[3], NULL, 0);//将字符串转为数字
write(fd, buf, 2);
}
else//错误使用
{
print_usage(argv[0]);
return -1;
}

return 0;
}

扩展:

strtoul函数(将字符串转换成无符号长整型数)

头文件

1
>#include<stdlib.h>

定义函数

1
>unsigned long strtoul(const char *nptr,char **endptr,int base);

函数说明

strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当base值为0时会根据情况选择用哪种进制:如果第一个字符是’0’,就判断第二字符如果是‘x’则用16进制,否则用8进制;第一个字符不是‘0’,则用10进制。一开始strtoul()会扫描参数nptr字符串,跳过前面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(‘’)结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。

返回值

返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。

附加说明

ERANGE指定的转换字符串超出合法范围。

范例

将十六进制 0xFF,转换成 10进制,得到 255

1
2
3
4
5
6
7
8
9
10
>#include<stdio.h>
>#include<stdlib.h>
>int main()
>{
int a;
char pNum[]="0xFF";
a=strtoul(pNum,0,0);//最后的0,表示自动识别pNum是几进制
printf("%ul\n",a);
return 0;
>}

输出:

1
>255

11、测试运行

如下所示:

1
2
3
4
5
6
7
# ./19th_test R 0x10
Read address 0x10 data is 0xff
# ./19th_test W 0x10 0x10
Write address 0x10 data is 0x10
# ./19th_test R 0x10
Read address 0x10 data is 0x10
#