S3c2440 I2C驱动与测试程序追踪交叉分析

发布者:SerendipityLove最新更新时间:2016-12-06 来源: eefocus关键字:S3c2440  I2C驱动  测试程序  追踪交叉 手机看文章 扫描二维码
随时随地手机看文章

VMware虚拟机+Fedora10, 硬件平台TQ2440, 内核2.6.30.4
最近学习linux I2C驱动, 用刘洪涛老师的测试程序测试内核自带的驱动, 打开调试语句dev_dbg后(具体参考我的另一篇博客),发现应用程序
对应的驱动程序豁然开朗, 然后自己添加了一些dev_dbg后, 对于不理解的地方也有了一定的参考提示, 记录下来与大家分享.
测试程序如下:
-----------------------------------------------------------------------------
  /*i2c_test.c
        * hongtao_liu
        */
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #define I2C_RETRIES 0x0701
        #define I2C_TIMEOUT 0x0702
        #define I2C_RDWR 0x0707
        /*********定义struct i2c_rdwr_ioctl_data和struct i2c_msg,要和内核一致*******/

struct i2c_msg
        {
                unsigned short addr;
                unsigned short flags;
        #define I2C_M_TEN 0x0010
        #define I2C_M_RD 0x0001
                unsigned short len;
                unsigned char *buf;
        };

struct i2c_rdwr_ioctl_data
        {
                struct i2c_msg *msgs;
                int nmsgs;
        /* nmsgs这个数量决定了有多少开始信号,对于“单开始时序”,取1*/
        };

/***********主程序***********/
        int main()
        {
                int fd,ret;
                struct i2c_rdwr_ioctl_data e2prom_data;

                fd=open("/dev/i2c-0",O_RDWR);
        /*
        *dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c
        *的方式,就没有,也不需要这个节点。
        */
                if(fd<0)
                {
                        perror("open error");
                }
                e2prom_data.nmsgs=2;
        /*
        *因为操作时序中,最多是用到2个开始信号(字节读操作中),所以此将
        *e2prom_data.nmsgs配置为2
        */
                e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
                if(!e2prom_data.msgs)
                {
                        perror("malloc error");
                        exit(1);
                }
                ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
                ioctl(fd,I2C_RETRIES,2);/*重复次数*/

                /***write data to e2prom**/
                e2prom_data.nmsgs=1;
                (e2prom_data.msgs[0]).len=2; //1个 e2prom 写入目标的地址和1个数据
                (e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
                (e2prom_data.msgs[0]).flags=0; //write
                (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
                (e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址
                (e2prom_data.msgs[0]).buf[1]=0x58;//the data to write

        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
                if(ret<0)
                {
                        perror("ioctl error1");
                }
                sleep(1);
        /******read data from e2prom*******/
                e2prom_data.nmsgs=2;
                (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
                (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
                (e2prom_data.msgs[0]).flags=0;//write
                (e2prom_data.msgs[0]).buf[0]=0x10;//e2prom数据地址
                (e2prom_data.msgs[1]).len=1;//读出的数据
                (e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址
                (e2prom_data.msgs[1]).flags=I2C_M_RD;//read
                (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
                (e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲

        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
                if(ret<0)
                {
                        perror("ioctl error2");
                }
                printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);
        /***打印读出的值,没错的话,就应该是前面写的0x58了***/
                close(fd);
                return 0;
        }
------------------------------------------------------------------------------------------------------
从UART口打印的调试信息如下:

i2c-adapter i2c-0: ioctl, cmd=0x702, arg=0x01

i2c-adapter i2c-0: ioctl, cmd=0x701, arg=0x02

i2c-adapter i2c-0: ioctl, cmd=0x707, arg=0xbeb95d28

i2c-adapter i2c-0: master_xfer[0] W, addr=0x50, len=2

s3c2440-i2c s3c2440-i2c: START: 000000d0 to IICSTAT, a0 to DS

s3c2440-i2c s3c2440-i2c: iiccon, 000000e0

s3c2440-i2c s3c2440-i2c: STOP

s3c2440-i2c s3c2440-i2c: master_complete 0

i2c-adapter i2c-0: ioctl, cmd=0x707, arg=0xbeb95d28

i2c-adapter i2c-0: master_xfer[0] W, addr=0x50, len=1

i2c-adapter i2c-0: master_xfer[1] R, addr=0x50, len=1

s3c2440-i2c s3c2440-i2c: START: 000000d0 to IICSTAT, a0 to DS

s3c2440-i2c s3c2440-i2c: iiccon, 000000e0

s3c2440-i2c s3c2440-i2c: WRITE: Next Message

s3c2440-i2c s3c2440-i2c: START: 00000090 to IICSTAT, a1 to DS

s3c2440-i2c s3c2440-i2c: iiccon, 000000f0

s3c2440-i2c s3c2440-i2c: READ: Send Stop

s3c2440-i2c s3c2440-i2c: STOP

s3c2440-i2c s3c2440-i2c: master_complete 0

buff[0]=58

现在,针对测试程序, 内核驱动和调试信息,分析测试程序对应内核的自行流程:

调试信息:i2c-adapter i2c-0: ioctl, cmd=0x702, arg=0x01

是应用程序: ioctl(fd,I2C_TIMEOUT,1) 

执行系统调用内核中i2c-dev.c中的i2cdev_ioctl函数中的dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
        cmd, arg);语句打印的, 而且经case语句设置client->adapter->timeout的滴答数.

同理, 调试信息i2c-adapter i2c-0: ioctl, cmd=0x701, arg=0x02

由测试程序ioctl(fd,I2C_RETRIES,2)调用相同的系统调用打印,应用层的ioctl向内核的ioctl传递参数. 

调试信息: i2c-adapter i2c-0: ioctl, cmd=0x707, arg=0xbed7cd28 也是由测试程序
ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data) 调用i2c-dev.c中的i2cdev_ioctl函数打印,
cmd=0x707代表I2c_RDWR命令, arg=0xbed7cd28是e2prom_data数据结构在用户空间的虚地址.
switch语句判断cmd是i2c_RDWR后,执行i2cdev_ioctl_rdrw函数.

在i2cdev_ioctl_rdrw函数中, 在 i2c_transfer前的代码, 
将测试程序ioctl传入的数据arg, 通过kmalloc申请内核空间, 然后通过copy_from_user拷贝到
申请的内核空间后, 调用i2c_transfer进行处理.

在i2c_transfer函数中, 由于我们打开了debug功能,所以会由以下代码
#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
打印调试信息, 如下:
i2c-adapter i2c-0: master_xfer[0] W, addr=0x50, len=2
可知, 设备地址为0x50, 由于本次写操作, len=2, 表示buf[0]=0x10;buf[1]=0x58.
代码ret = adap->algo->master_xfer(adap,msgs,num); 调用了
内核代码i2c-s3c2410.c 中的s3c24xx_i2c_xfer (why? to be study for this point)

在s3c24xx_i2c_xfer中自然就调用了s3c24xx_i2c_doxfer, 
然后调用s3c24xx_i2c_message_start, 
调试信息: s3c2440-i2c s3c2440-i2c: START: 000000d0 to IICSTAT, a0 to DS
就是由s3c24xx_i2c_message_start函数中
dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr) 语句打印的.
然后, dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon); 打印调试信息:
s3c2440-i2c s3c2440-i2c: iiccon, 000000e0;
在地址信息(0x50, 左移一位, 最低位设0,表示写,最终0xa0, 如上调试信息)写入IICDS寄存器,
最后一行, 写1到IICSTAT bit5,Start 产生后,在IICDS里的数据会自动传输, 这时触发中断.
后续的操作交由中断处理. 时序图如下图标注:


中断函数 s3c24xx_i2c_irq, 调用i2s_s3c_irq_nextbyte, switch(i2c->state)后,
进入case STATE_STAR, 判断msg->flags后, 进入case(STATE_WRITE),
注意, 此时的msg_ptr=0, i2c->msg->len=2, i2c->msg_idx=0, i2c->msg_num=1
我是怎么知道的? 分析程序上下文吗? No, 我只是在这里加了调试语句 :)
进入retry_write后, (!is_msgend(i2c))为真(通过上面自加的调试语句也知道).
然后向设备写入要写入数据的地址0x10, 即是
应用程序的(e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址.
然后触发中断


OK, bla bla.
又回来啦. 这次进入i2s_s3c_irq_nextbyte后, 直接进入case STATE_WRITE, 
此时msg_ptr=1, i2c->msg->len=2 i2c->msg_idx=0, i2c->msg_num=1.
(!is_msgend(i2c))还是为真, 向设备写入数据0x58, 即是应用程序的
(e2prom_data.msgs[0]).buf[1]=0x58;//the data to write.

然后触发中断.
第三次进入, 此时msg_ptr=2, i2c->msg->len=2, i2c->msg_idx=0, i2c->msg_num=1,
这种情况下, (!is_msgend(i2c))和(!is_lastmsg(i2c))都不满足, 所以执行会后的哪个else语句,
在调用s3c24xx_i2c_stop时, 调试语句 s3c2440-i2c s3c2440-i2c: STOP.由dev_dbg(i2c->dev, "STOP\n")打印.
产生stop信号.
然后调用s3c24xx_i2c_master_complete, 调试语句s3c2440-i2c s3c2440-i2c: master_complete 0由
其中的dev_dbg(i2c->dev, "master_complete %d\n", ret)打印, 并唤醒iic的等待队列wake_up(&i2c->wait) (如果有,继续传输??)
然后s3c24xx_i2c_disable_irq(i2c), disable 中断, 等待下一个start传输触发中断.
Write部分的分析到此为止, 下面分析Read部分的.

Read对应的应用程序里, e2prom_data.nmsgs=2, 传到内核里面rdwr_arg.nmsgs也=2,
所以在i2c_transfer函数里面会打印2次调试信息如下:
i2c-adapter i2c-0: master_xfer[0] W, addr=0x50, len=1
i2c-adapter i2c-0: master_xfer[1] R, addr=0x50, len=1
......   ......
发送设备地址后, 触发中断, ......, 进入i2s_s3c_irq_nextbyte的case STATE_START:
此时msg_ptr=0, i2c->msg->len=1, i2c->msg_idx=0, i2c->msg_num=2.
由于应用程序传入的 (e2prom_data.msgs[0]).flags=0, 是写动作, 所以进入case STATE_WRITE:
由于(!is_msgend(i2c))为真, 所以写数据地址. 写完后, 触发中断.

再进入case STATE_WRITE, 此时In write, msg_ptr=1, i2c->msg->len=1, i2c->msg_idx=0, i2c->msg_num=2
(!is_lastmsg(i2c))为真, 且(i2c->msg->flags & I2C_M_NOSTART)为0, 所以, 发送新的start,
即是执行s3c24xx_i2c_message_start(i2c, i2c->msg), 故而, 我们可以看到调试信息:
s3c2440-i2c s3c2440-i2c: START: 00000090 to IICSTAT, a1 to DS, 和s3c2440-i2c s3c2440-i2c: iiccon, 000000f0
意思就是:开始,发送设备地址,末位1, 读命令.如下红色所示.

发送完后触发中断, 进入case STATE_START, 然后进入prepare_read:
调试信息: msg_ptr=0, i2c->msg->len=1, i2c->msg_idx=1, i2c->msg_num=2
(is_lastmsg(i2c))为真, 执行s3c24xx_i2c_disable_ack(i2c), 因为如上图, DATAn传输完毕后, 是没有ack的, 所以
在此之前, 把ack关掉. 触发中断, 又进入case STATE_READ, 读取IICDS里的值, 然后存到msg->buf[0].
此时的msg_ptr=1, i2c->msg->len=1, i2c->msg_idx=1, i2c->msg_num=2. (is_lastmsg(i2c))为真, 调试消息READ: Send Stop STOP
READ: Send Stop.


关键字:S3c2440  I2C驱动  测试程序  追踪交叉 引用地址:S3c2440 I2C驱动与测试程序追踪交叉分析

上一篇:S3C2440 UART串口驱动1
下一篇:S3C2440下linux按键驱动编写及测试程序

推荐阅读最新更新时间:2024-03-16 15:23

s3c2440裸机-时钟编程(二、配置时钟寄存器)
1.2440时钟时序 下图是2440时钟配置时序: 1.上电后,nRESET复位信号拉低,此时cpu还无法取指令工作。 2.nRESET复位信号结束后变为高电平,此时cpu开始工作。此时cpu主频FCLK=osc。 3.此时可以配置PLL,经过lock time后,FCLK倍频成新的时钟。 2.如何配置时钟 在参考手册的特性里介绍了S3C2440的工作频率,Fclk最高400MHz,Hclk最高136MHz,Pclk最高68MHz。那么 我们干脆配置FCLK:HCLK:PCLK= 400:100:50 (MHz). 1,先配置lock time 我们取芯片手册上的推荐值。 /* LOCKTIME(0x4C000000)
[单片机]
<font color='red'>s3c2440</font>裸机-时钟编程(二、配置时钟寄存器)
S3C2440存储器SDRAM控制笔记
最近一年多的时间一直在从事任意轮系机器人运动控制及惯性导航的研究实践,买的开发板闲置了一年多了,决定继续延续一年多前的学习,继续做笔记。 SDRAM读写操作流程 Cpu发出nGCS6信号,选中bank7,对应开发板nSCS引脚,引脚标号19 SDRAM芯片行地址使用13根地址线,列地址复用行地址9根地址线,同时还有两根地址线用于芯片内部逻辑块选择,每个芯片输出16bit数据,由两个芯片并联输出32位到cpu数据总线上,所以内存大小为2^(13+9+2)*4 = 64Mbyte 由于cpu始终以32位的宽度读取SDRAM,所以cpu读SDRAM地址低2位始终为0,所以原理图cpu的地址线addr0和addr1并未接到SDRAM上
[单片机]
3.4.2内核下I2C驱动之24CXX实例
at24cxx_dev.c部分: #include linux/kernel.h #include linux/module.h #include linux/platform_device.h #include linux/i2c.h #include linux/err.h #include linux/regmap.h #include linux/slab.h static struct i2c_board_info at24cxx_info = { I2C_BOARD_INFO( at24c08 , 0x50), }; static struct i2c_client *at2
[单片机]
S3C2440学习二(基础资源的使用)
虽然有种流水账的感觉,但是这是学习的必经之路,等我渗透ARM三次后,我再全面处理问题,给出最简洁最直接的答案吧。 ①ARM有多少个通用寄存器,有什么用?(让我们一个一个记录) 通用寄存器 通用寄存器可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果。除此之外,它们还各自具有一些特殊功能。通用寄存器的长度取决于 机器字长 ,汇编语言程序员必须熟悉每个寄存器的一般用途和特殊用途,只有这样,才能在程序中做到正确、合理地使用它们。   16位cpu通用寄存器共有 8 个:AX,BX,CX,DX,BP,SP,SI,DI.   八个寄存器都可以作为普通的 数据寄存器 使用。   但有的有特殊的用途:AX为 累加器 ,CX
[单片机]
S3C2440裸机之用查询方式按键控制LED
前言 本文基于JZ2440开发板 一、思维导图 二、代码 1.key_led.c 代码如下(示例): /************************************************************************ * * 文件名:key_led.c * * 功能: 用EINT0(GPF0)按键控制D12(GPF6), 用EINT2(GPF2)按键控制D11(GPF5), 用EINT11(GPG3)按键控制D10(GPF4) * * 创建人:LiZhenhao * * 时间:2021年10月9日16:49:52 * * 版本号:1.0 * * 修改记录:无 * ********
[单片机]
<font color='red'>S3C2440</font>裸机之用查询方式按键控制LED
24LC65 I2C EEPROM字节读写驱动程序
/* 〖说明〗24LC65 I2C EEPROM字节读写驱动程序,芯片A0-A1-A2要接VCC。 现缺页写、页读,和CRC校验程序。以下程序经过50台验证,批量的效果有待考 察。 为了安全起见,程序中很多NOP是冗余的,希望读者能进一步精简,但必须经过验 证。 51晶振为11.0592MHz 〖文件〗24LC65.c ﹫2001/03/23 〖作者〗龙啸九天 c51@yeah.net http://mcs51.yeah.net 〖修改〗修改建议请到论坛公布 http://c51bbs.yeah.net 〖版本〗V1.00A Build 0323 */ #define SDA P0_0
[单片机]
s3c2440 省电模式开发详解
1、源码包 Kernel:linux-2.6.18.2 Uboot:u-boot-1.1.4 Gcc:arm-linux-gcc-3.4.1.tar.bz2 开发流程及详细步骤 1、休眠部分 1.电源管理守护进程 省略 2.内核接口文件(arch/arm/kernel/apm.c) 电源守护进程通过apm.c的ioctl函数来使内核开始进入sleep模式。 case APM_IOC_SUSPEND: as- suspend_result = -EINTR; if (as- suspend_state == SUSPEND_READ) { as- suspend_state = SUSPEND_
[单片机]
ST9+系列单片机I2C总线驱动程序实现
    摘要: 串行扩展总线技术是新一代单片机技术发展的一个显著特点,其中Philips公司推出的I2C总线最为著名。ST9+系列是意法半导体公司的单片机产品,能够很好地支持I2C总线协议。本文以ST9+单片机为例阐述I2C总线协议,并给出在ST9+单片机上实现I2C总线驱动程序的流程和方法。     关键词: I2C总线 串行扩展总线 ST9+ 总线驱动 串行扩展总线技术是新一代单片机技术发展的一个显著特点,其中Philips公司推出的 I2C总线最为著名。I2C总线最显著的特点是规范的完整性、结构的独立性和用户使用时的简单化。 I2C总线有严格的规范,如接口的电气特性、信号时序、信号传输的定
[应用]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved