Chinaunix首页 | 论坛 | 博客
  • 博客访问: 831512
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: LINUX

2017-02-21 21:08:36

4. 用户态 I2C 编程
    I2C 总线的设备文件通常为/dev/i2c-n( n=0、 1、 2...),每个设备文件对应一组 I2C 总
    线。应用程序通过这些设备文件可以操作 I2C 总线上的任何从机器件。 EasyARM-i.MX283A
    提供了一路 I2C 接口,设备文件为/dev/i2c-0。
    root@EasyARM-iMX283 /# ls /dev/i2c*
    /dev/i2c-0  /dev/i2c-1
    
4.1 I2C 编程接口
    1. 打开设备
        在操作 I2C 总线时,先调用 open()函数打开 I2C 设备获得文件描述符,代码如程序清单4.1 所示。
        程序清单 4.1 打开 I2C 设备文件
            int fd;
            fd = open("/dev/i2c-0", O_RDWR);
            if (fd < 0) {
                perror("open i2c-1 \n");
            }
    
    2. 关闭设备
        当操作完成后,调用 close()函数关闭设备:
        close(fd);
        
    3. 配置设备
        当应用程序操作 I2C 总线上的从机器件时,必须先调用 ioctl()函数设置从机地址和从机地址的长度。
        
        3.1 设置从机地址
            设置从机地址是使用 I2C_SLAVE 命令,其定义为:
            #define I2C_SLAVE 0x0703
            
            该命令的参数为从机地址右移一位。设置从机地址为 0xA0 的示例代码为:
            if (ioctl(fd, I2C_SLAVE, 0xA0 >> 1) < 0) {
                perror("set slave address failed \n");
            }
            注意:地址需要右移一位,是因为地址的 Bit0 是读写控制位,在驱动中会将从机地址
            命令参数左移一位,并补上读写控制位。
        
        3.2 设置地址长度
            设置从机地址的长度是使用 I2C_TENBIT 命令,其定义为:
            #define I2C_TENBIT 0x0704
            该命令的参数可选择为: 1 表示设置从机地址长度为 10 位; 0 表示设置从机地址长度为8 位。
            设置从机地址长度为 10 位的示例代码为:
            ioctl(fd, I2C_TENBIT, 1);
            该命令是不会返回错误的。
            如果不设置地址长度,则默认为 8 位地址。
            
    4. 发送数据
        应用程序调用 write()函数可以向 I2C 总线发送数据。例如在 I2C 总线发送“ hello”字符
        串的代码如程序清单 4.2所示。
        程序清单 4.2 在 I2C 总线发送数据
            int len;
            char buf[] = "hello";
            len = write(fd, buf, sizeof(buf));
            if (len < 0) {
                printf("send data failed");
                exit(-1);
            }
        write()函数调用成功后,返回成功发送数据的长度。在 write()函数调用时,数据发送过
        程如下:
        (1) 主机在 I2C 总线发送始起信号( S),然后发送从机地址( slave addr) ;
        (2) 从机成功接收到属于自己的从机地址后,返回应答信号( ACK);
        (3) 主机接收到应答信号后,把 buf 缓冲区中的数据逐个在 I2C 总线发送;
        (4) 从机每成功接收到一个从主机发来的数据都返回应答信号;
        (5) 当主机的数据发送完毕后,在 I2C 上发送结束信号( P)。

    5. 接收数据
        应用程序调用 read()函数可以在 I2C 总线接收数据。例如在 I2C 总线接收 10 个字节的代
        码如程序清单 4.3 所示。
        程序清单 4.3 在 I2C 总线读取数据
            char buf[10];
            int len;
            len = read(fd, buf, 10);
            if (len < 0){
                printf("read i2c data failed");
                exit(-1);
            }
        read()调用成功后,返回接收数据的长度。
        
        read()函数调用时,数据接收过程如下:
        (1) 主机在 I2C 总线发送始起信号( S),然后发送从机地址( slave addr) ;
        (2) 从机成功接收到属于自己的从机地址后,返回应答信号( ACK);
        (3) 主机接收到应答信号后,准备接收从机发来的数据;
        (4) 从机把数据逐个向主机发送;
        (5) 主机每成功接收到一个在从机发来的数据都返回应答信号;
        (6) 当主机接收到最后一个数据时并不返回应答信号,而是在 I2C 总线上发送结束信号( P)。

4.2 编程范例
    AP-283Demo 板上的 FM24C02A 是 I2C 接口的 EEPROM 芯片。 FM24C02A 是 2Kb( 256
    字节)大小的 EEPROM,分为 32 个页,每页 8 字节。
    这里通过演示读/写 I2C 接口的 EEPROM 来进一步说明应用程序如何使用 I2C 编程接口。
    
    1. FM24C02A 的操作
        1.1 从机寻址
            当接收到起始信号后, FM24C02A 需要一个 8 位的从机地址来启动一次读/写操作,其
            从机地址构成:
                1010  A2 A1 A0 R/W
                
            从机地址前 4 位的值固定不变,第 2、 3、 4 位的值分别由 FM24C02A 的 A0、 A1、 A2
            引脚的输入电平决定(高电平为 1,低电平为 0)。从机地址的第 0 位为读/写启动选择位( R/W):
            1 为启动读操作; 0 为启动写操作。
            
        1.2 字节写
            字节写操作为每次在 FM24C02A 内部储存器的指定地址写入 1 个字节的数据。主机先
            发送起始信号和从机地址( R/W 位为 0)。在接收到 FM24C02A 返回的应答信号后,主机发
            送需要写入的数据地址( 1 个字节),然后发送需要写入的数据。在收到 FM24C02A 返回的
            应答信号后,主机发送结束信号。
        
        1.3 页写
            FM24C02A 支持在一次写操作中连续写入一页的数据( 8 个字节)。页写操作的启动
            方式和字节写操作类似,只是主机发送了第 1 个字节的数据后并不是马上停止,而是继
            续发送剩余的 7 个字节的数据。 FM24C02A 在每接收到主机发来的 1 个数据都返回 1
            个应答信号。当主机的所有数据都发送完毕后,主机发送结束信号。每当 FM24C02A
            接收到主机发来的 1 个数据时,数据地址的低三位加 1,而高五位不会变化,保持存储
            器的页地址不变。当内部产生的数据地址达到页边界时,数据地址将会翻转,接下来的
            数据的写入地址将置为同一页的最小地址。所以若有超过 8 个字节数据写入 FM24C02A,
            数据地址将回到最先写入的地址,先前写入的数据将被覆盖。
        
        1.4 当前地址读
            FM24C02A 的内部数据地址计数器保留最后一次访问的地址,并自动加 1。只要
            FM24C02A 处于上电状态,这个地址在操作运行期间始终有效。在读操作中,如果存
            储器的最后一页的最后一个字节开始读,则读下一个字节时地址将会翻转到整个储存器
            的最小地址。
            主机发送起始信号和从机地址( R/W 位为 1)后, FM24C02A 返回答应信号,然后
            向主机发送数据。这时主机接收到数据后,并不返回答应信号,而发送结束信号。
            
        1.5 自由读
            自由读需要通过假的字节写操作来获得数据地址。主机首先发送起始信号、从机地址(写操作)和
            数据地址来定位需要读取的地址。当 FM24C02A 返回数据地址的应答信号之后,主机马上
            重新发送起始信号和从机地址(读操作)。这时 FM24C02A 返回应答信号,然后发送数据。主机接收
            到数据后,并不返回应答信号,而发送结束信号。
        
        1.6 连续读
            在自由读操作中,若主机在接收了 FM24C02A 发来的数据后,并不发送结束信号,而
            是立即返回应答信号,那么 FM24C02A 则自动把数据地址加 1,并将新数据地址的数据发
            送给主机。当储存器的数据地址达到最大时,数据地址将翻转到最小地址,并且继续进行连
            续读操作。当主机不再返回应答信号,而是发送停止信号时, FM24C02A 停止发送数据。
            
    2. 电路原理
        AP-283Demo 板上的 FM24C02A 是连接到 I2C1 总线,见电路图。
        在该电路图中, FM24C02A 的 A0、 A1、 A2 引脚电平被拉低,所以 FM24C02A 的从机
        地址为 0xA0。    
            1010  A2 A1 A0 R/W         = 1010  000 R/W = 0xA0 + R/W
                
    3. 示例程序
        在程序清单 4.4 所示的代码中,通过 I2C 总线在 FM24C02A 内部储存器的 0x00 ~ 0x07
        地址连续写入 8 个字节的数据,然后在这些地址中把数据读出来,最后把写入数据和读出数
        据进行对比,以检验程序的正确性。
        
        程序清单 4.4 连续写/读程序代码    
        

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #include <termios.h>
  8. #include <errno.h>

  9. #define I2C_SLAVE         0x0703
  10. #define I2C_TENBIT         0x0704
  11. #define I2C_ADDR         0xA0
  12. #define DATA_LEN         8
  13. #define I2C_DEV_NAME     "/dev/i2c-1"

  14. int main(int arg,char*args[])
  15. {
  16.     unsigned int ret,len;
  17.     int i,flag=0;
  18.     int fd;
  19.     char tx_buf[DATA_LEN + 1];     /* 用于储存数据地址和发送数据 */
  20.     char rx_buf[DATA_LEN];         /* 用于储存接收数据 */
  21.     char addr[1];                 /* 用于储存读/写的数据地址 */
  22.     addr[0] = 0;                 /* 数据地址设置为 0 */
  23.     
  24.     fd = open(I2C_DEV_NAME, O_RDWR); /* 打开 I2C 总线设备 */
  25.     if(fd < 0) {
  26.         printf("open %s failed\n", I2C_DEV_NAME);
  27.         return -1;
  28.     }
  29.     
  30.     ret = ioctl(fd, I2C_SLAVE, I2C_ADDR >> 1);     /* 设置从机地址 */
  31.     if (ret < 0) {
  32.         printf("setenv address failed ret: %x \n", ret);
  33.         return -1;
  34.     }
  35.     
  36.     /* 由于没有设置从机地址长度,所以使用默认的地址长度为 8 */
  37.     tx_buf[0] = addr[0];                     /* 发数据时,第一个发送是数据地址 */
  38.     for (i = 1; i < (DATA_LEN + 1); i++)     /* 初始化要写入的数据:0, 1, ..., 7 */
  39.         tx_buf[i] = i - 1; /* 总共为8个数据: 0 - 7 */
  40.     
  41.     len = write(fd, tx_buf, DATA_LEN + 1); /* 把数据写入到 FM24C02A, */
  42.     if (len < 0) {
  43.         printf("write data failed \n");
  44.         return -1;
  45.     }
  46.     
  47.     usleep(1000*100);             /* 需要延迟一段时间才能完成写入 EEPROM */

  48.     /* 读取操作 */
  49.     len = write(fd, addr, 1);     /* 设置要读取的数据地址 */
  50.     if (len < 0) {
  51.         printf("write data addr failed \n");
  52.         return -1;
  53.     }
  54.     
  55.     len = read(fd, rx_buf, DATA_LEN); /* 在设置的数据地址连续读入数据 */
  56.     if (len < 0) {
  57.         printf("read data faile \n");
  58.         return -1;
  59.     }
  60.     
  61.     printf("read from eeprom:");
  62.     for(i = 0; i < DATA_LEN; i++) { /* 对比写入数据和读取的数据 */
  63.         printf(" %x", rx_buf[i]);
  64.         if (rx_buf[i] != tx_buf[i+1])
  65.             flag = 1;
  66.     }
  67.     
  68.     printf("\n");

  69.     if (!flag) { /* 如果写入/读取数据一致,打印测试成功 */
  70.         printf("eeprom write and read test sussecced!\r\n");
  71.     }
  72.     else { /* 如果写入/读取数据不一致,打印测试失败 */
  73.         printf("eeprom write and read test failed!\r\n");
  74.     }
  75.     return 0;
  76. }


        该代码可以交叉编译为 i2c_eeprom_test 程序文件,测试方法:
        (1) 把 i2c_eeprom_test 上传到 EasyARM-i.MX283A 的任何目录;
        (2) 执行 i2c_eeprom_test 程序。
        若 i2c_eeprom_test 程序执行无误,将打印信息
        
        注意:这里刚好是一整页的读写,如果错开了,就会覆盖低地址的数据。

阅读(2336) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~