zoukankan      html  css  js  c++  java
  • gpio模拟I2C,驱动pcf8574T

    一、pcf8574T介绍

    查看pcf8574T的数据手册,

    clip_image001

    A表示读或写,当A为1的时候表示读,当A为0的时候表示写。现把地址控制线,即A2、A1、A0全部接地,可以得到读控制指令为0x41,写控制指令为0x40。

    二、I2C介绍

    参考:

    http://blog.csdn.net/ce123_zhouwei/article/details/6882221

    1、起始和停止时序

    clip_image002

    2、数据位的传输

    clip_image002[4]

    也就是在SCL的下降沿将数据位传出。

    3、主控制器为写的时候,接收应答

    当传输完数据的第8位,第9位要发送一个接收应答信号,将SDA拉高,设为输入模式,在SCL为低电平之前将总线上的数据读取过来,如果为1表示从设备接收数据失败,如果为0表示从设备接收数据成功,可以继续发送下一个字节。

    clip_image001[5]

    代码片段:

    // 接收应答信号 
    static int i2c_recv_ack(void)
    {
        int tmp;
        //SDA=1;
        gpio_direction_output(sda_pin, 1);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //F0=SDA;
        gpio_direction_input(sda_pin);
        tmp = gpio_get_value(sda_pin);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_direction_output(scl_pin, 0);
        //delay_us(5);
        udelay(5);
        //if(F0==1) return 1;
        if (tmp == 1) return 1;
    
        return 0;
    }

    4、主控制器为读的时候,发送应答

    第9位发送0,表示接收成功,发送1表示接收失败。如果到最后一个字节后,发送一个NACK信号(1),以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

    clip_image002[6]

    代码片段:

    // 发送应答信号
    static void i2c_send_ack()
    {
        //SDA=0;
        gpio_direction_output(sda_pin, 0);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SDA=0;
        gpio_set_value(sda_pin, 0);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    static void i2c_send_noack()
    {    
        //SDA=1;
        gpio_direction_output(sda_pin, 1);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SDA=1;
        gpio_set_value(sda_pin, 1);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }

    5、写过程完整数据传输

    代码片段:

    // 控制PCF8574引脚电平
    static int pcf8574_write(u8 val)
    {
          int acktmp = 1; 
          i2c_start();
          i2c_write_byte(0x40);//写控制指令 0x20<<1 R/W
          acktmp = i2c_recv_ack();
          if (acktmp == 1) {
            PRK("i2c_recv_ack fail
    ");
            //return -1;
        }
          i2c_write_byte(val);                   
          acktmp = i2c_recv_ack();
          if (acktmp == 1) {
            PRK("i2c_recv_ack fail
    ");
            //return -1;
        }
          i2c_stop();
          if (acktmp == 1) return -1;
          return 0;
    }

    6、读过程完整数据传输

    代码片段:

    // 读出PCF8574引脚电平
    static u8 pcf8574_read()
    {    
        int acktemp = 1;
        u8 rddata = 0;
        i2c_start();
        i2c_write_byte(0x41);//读控制指令
        i2c_send_ack();
        rddata = i2c_read_byte();
        i2c_send_noack();
        i2c_stop();
        return rddata;
    }

    三、示例代码:

    1、驱动

    /*
     * Copyright (c) 2015 tingpan
     * Copyright 2012-2015 Senscom.cn
     *    tingpan <smbx-ztbz@cnblog.com>
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License version 2 as
     * published by the Free Software Foundation.
     *
     */
    
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h>  //混杂设备
    #include <linux/fs.h>
    #include <linux/module.h>
    #include <linux/delay.h>  //mdelay
    #include <linux/device.h>
    #include <linux/gpio.h>
    #include <linux/spi/spi.h>
    #include <linux/spi/spi_gpio.h>
    
    #include <linux/kfifo.h>
    #include <linux/interrupt.h>
    #include <linux/irq.h>
    
    #include <linux/types.h>  //u8
    
    #include <linux/ioctl.h>
    
    
    #define PCF8574_DEBUG    1
    
    #if (PCF8574_DEBUG == 1)
    #define PRK(...) printk(__VA_ARGS__)
    #else 
    #define PRK(...) 
    #endif
    
    #define DRV_NAME    "pcf8574"
    #define DRV_DESC    "use i2c to extend i/o" 
    #define DRV_VERSION    "0.1.0"
    
    //#define PCF8574_NODE_NAME DRV_NAME
    
    #define scl_pin 17
    #define sda_pin 14
    
    static struct mutex pcf8574_lock;
    
    static DEFINE_MUTEX(pcf8574_lock);
    
    //pcf8574
    // 发送I2C启动位 
    static void i2c_start(void)
    {
        //sda_pin=1; 
        gpio_direction_output(sda_pin, 1);
        //scl_pin=1; 
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SDA=0; 
        gpio_set_value(sda_pin, 0);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    // 发送I2C停止位 
    static void i2c_stop(void)
    {
        //SDA=0;
        gpio_direction_output(sda_pin, 0);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SDA=1;
        gpio_set_value(sda_pin, 1);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    // 发送BIT0  
    static void i2c_send_bit_0(void)
    {
        //SDA=0;
        gpio_direction_output(sda_pin, 0);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    // 发送BIT1 
    static void i2c_send_bit_1(void)
    {
        //SDA=1;
        gpio_direction_output(sda_pin, 1);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    // 接收应答信号 
    static int i2c_recv_ack(void)
    {
        int tmp;
        //SDA=1;
        gpio_direction_output(sda_pin, 1);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //F0=SDA;
        gpio_direction_input(sda_pin);
        tmp = gpio_get_value(sda_pin);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_direction_output(scl_pin, 0);
        //delay_us(5);
        udelay(5);
        //if(F0==1) return 1;
        if (tmp == 1) return 1;
    
        return 0;
    }
    
    // 发送应答信号
    static void i2c_send_ack()
    {
        //SDA=0;
        gpio_direction_output(sda_pin, 0);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SDA=0;
        gpio_set_value(sda_pin, 0);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    static void i2c_send_noack()
    {    
        //SDA=1;
        gpio_direction_output(sda_pin, 1);
        //SCL=1;
        gpio_direction_output(scl_pin, 1);
        //delay_us(5);
        udelay(5);
        //SDA=1;
        gpio_set_value(sda_pin, 1);
        //delay_us(5);
        udelay(5);
        //SCL=0;
        gpio_set_value(scl_pin, 0);
        //delay_us(5);
        udelay(5);
    }
    
    // 写一个字节
    static void i2c_write_byte(u8 data)
    {
        u8 i;
        for (i=0; i<8; i++) {
            if ((data<<i) & 0x80)
                    i2c_send_bit_1();
                else
                    i2c_send_bit_0();
        }
    }
    
    // 接收一个字节
    static u8 i2c_read_byte(void)
    {
        u8 data = 0;
        u8 i;
        int tmp;
        for (i=0; i<8; i++) {
            //SDA=1;  
            gpio_direction_output(sda_pin, 1);  
            //SCL=1;   
            gpio_direction_output(scl_pin, 1); 
            //delay_us(5);
            udelay(5);
            //F0=SDA;
            gpio_direction_input(sda_pin);
            tmp = gpio_get_value(sda_pin);
            //delay_us(5);
            udelay(5);
            //SCL=0;
            gpio_set_value(scl_pin, 0);
            if (tmp == 1) {
                data = data<<1;
                data = data | 0x01;
            } else
                data = data<<1;
        }
        return data;
    }
    
    // 控制PCF8574引脚电平
    static int pcf8574_write(u8 val)
    {
          int acktmp = 1; 
          i2c_start();
          i2c_write_byte(0x40);//写控制指令 0x20<<1 R/W
          acktmp = i2c_recv_ack();
          if (acktmp == 1) {
            PRK("i2c_recv_ack fail
    ");
            //return -1;
        }
          i2c_write_byte(val);                   
          acktmp = i2c_recv_ack();
          if (acktmp == 1) {
            PRK("i2c_recv_ack fail
    ");
            //return -1;
        }
          i2c_stop();
          if (acktmp == 1) return -1;
          return 0;
    }
    
    // 读出PCF8574引脚电平
    static u8 pcf8574_read()
    {    
        int acktemp = 1;
        u8 rddata = 0;
        i2c_start();
        i2c_write_byte(0x41);//读控制指令
        i2c_send_ack();
        rddata = i2c_read_byte();
        i2c_send_noack();
        i2c_stop();
        return rddata;
    }
    
    static ssize_t op_pcf8574_read(struct file *file, char __user * dat, 
                                        size_t len, loff_t *loff)
    {
        int err = 0;
        u8 result;
        err = !access_ok(VERIFY_WRITE, (void __user *)dat, _IOC_SIZE(len));//用到len,分配指定大小空间
        if (err) {
            PRK(KERN_INFO " access not allowed!
    ");
            return -EFAULT;
        }    
        result = pcf8574_read();
        __copy_to_user(dat, &result, 1);
        return 0;
    }
    
    static ssize_t op_pcf8574_write(struct file *file, char __user * dat, 
                                        size_t len, loff_t *loff)
    {
        u8 wrdata = 0;
        if(!copy_from_user(&wrdata, dat, len))
        {
            if (!pcf8574_write(wrdata)) {
                PRK("op_sensorid_write %d success!
    ",wrdata);
                return 0;
            } else {
                PRK("op_sensorid_write %d fail!
    ",wrdata);
                return -1;
            }
            
        }  
        else
             return -1;
    }
    
    static int op_pcf8574_open(struct inode *inode, struct file *file)
    {
        int err;
    
        err = gpio_request(scl_pin, "scl_pin");//管脚申请
        if (err) {
            PRK("[%d]gpio_request scl_pin failed.
    ", __LINE__);
            return -1;
        }
        gpio_direction_output(scl_pin, 1);//该管脚设为输出,且输出为高电平
    
        err = gpio_request(sda_pin, "sda_pin");//管脚申请
        if (err) {
            PRK("[%d]gpio_request sda_pin failed.
    ", __LINE__);
            return -1;
        }
        gpio_direction_output(sda_pin, 1);//该管脚设为输出,且输出为高电平
        
        PRK(KERN_INFO " op_pcf8574_open
    ");
        return 0;
    }
    
    static int op_pcf8574_release(struct inode *inode, struct file *file)
    {
        gpio_free(sda_pin);//释放IO口
        gpio_free(scl_pin);//释放IO口
        
        PRK(KERN_INFO " op_pcf8574_release
    ");
    
        return 0;
    }
    
    static const struct file_operations pcf8574_fops =
    {
        .owner   = THIS_MODULE,
        
        .open    = op_pcf8574_open,
        .read    = op_pcf8574_read,
        .write    = op_pcf8574_write,
        .release = op_pcf8574_release,
    };
    
    static struct miscdevice pcf8574_miscdev =
    {
        //次设备号,驱动注册时,如果次号指定MISC_DYNAMIC_MINOR,则进行动态分配。
        .minor = MISC_DYNAMIC_MINOR,
        .name = DRV_NAME,//设备名称,将在/dev文件夹下显示
        .fops = &pcf8574_fops,
    };
    static int __init pcf8574_init(void)//放后面,因为 misc_register 要包含前面的内容
    {
        int ret;
    
        ret = misc_register(&pcf8574_miscdev);
        if (ret) {
            printk("misc_register error
    ");
            return ret;
        }
        
        printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" init
    ");
    
        return 0;
    }
    
    module_init(pcf8574_init);
    
    static void __exit pcf8574_exit(void)
    {
        int ret;
    
        ret = misc_deregister(&pcf8574_miscdev);//注销
        if (ret) {
            printk("misc_deregister error
    ");
            return ;
        }
    
        printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" exit
    ");
    }
    module_exit(pcf8574_exit);
    
    MODULE_DESCRIPTION(DRV_DESC);//描述
    MODULE_VERSION(DRV_VERSION);//版本
    MODULE_AUTHOR("tingpan <smbx-ztbz@cnblogs.com>");//作者
    MODULE_LICENSE("GPL v2");//协议

    2、应用程序

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <string.h>
    #include <errno.h>
    
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <sys/time.h>
    
    #include <linux/types.h>
    
    #include <syslog.h>
    
    #include <uci.h>
    
    #include <time.h>
    #include <math.h>
    
    #include <sys/select.h>
    
    #include <pthread.h>
    
    #define PCF8574_DTEST 1
    
    #if (PCF8574_DTEST == 1)
    #define PRT(...) printf(__VA_ARGS__)
    #else 
    #define PRT(...) 
    #endif
    
    #define USAGE_MESSAGE 
        "Usage: pcf8574-dtest MODE [data]
    "  
        "MODE is r or w
    "  
        "data is the value want to write when MODE is w
    "
        
    int main(int argc, char **argv)
    {
        int fd,ret;
        unsigned char data = 0;
        //if (argc<2 || (argv[1]=='r')) {
            //PRT(USAGE_MESSAGE);
            //return -1;
        //}
        //memset(data, 0, sizeof(data));
        //PRT("Compile Time %s %s
    ", __DATE__, __TIME__);//打印出最后的编译日期
        fd = open("/dev/pcf8574", O_RDWR);              //打开设备文件   
        if (fd < 0) {  
            perror("Open device file err:");  
            close(fd);  
            return -1;  
        }  
    
        if(argc > 2 && !strcmp(argv[1], "w")) {
            data  = atoi(argv[2]); 
            ret = write(fd, &data, 1); //第三个参数没用上,先固定为1
            if (ret) perror("write"); 
        } else if (argc > 1 && !strcmp(argv[1], "r")) {
            ret = read(fd, &data, 1);
            if (ret) perror("write"); 
            PRT("read data is %d
    ",data);
        } else {
            PRT(USAGE_MESSAGE);
            return -1;
        }
        return 0;
    }

    四、问题

    试过用pcf8574读取ds2431的序列号,但初始化就失败了,这是因为这样做就类似于用I2C通讯每控制一个IO空的电平,即约每传输20位就要把单总线通讯的管脚电平拉高或拉低,这样的话就要求I2C的通讯速度要大于单总线通讯的二十多倍(可能更多),才能正常控制ds2431。

    参考:

    http://blog.csdn.net/xukai871105/article/details/18273653

    源码下载:

    http://pan.baidu.com/s/1qXyQJ3Q

  • 相关阅读:
    GRYZ20211029模拟赛解题报告
    LG 题解 CF1545B AquaMoon and Chess
    GRYZ10.27模拟赛解题报告
    开博客通告
    民科吧编程赛 题解
    民科吧编程赛 试题
    四则运算开平方——对民科吧编程大赛题目的再探究
    The First
    机械手相机9点坐标标定-基于C#+EmguCV
    Emgucv图像处理工具
  • 原文地址:https://www.cnblogs.com/smbx-ztbz/p/5040984.html
Copyright © 2011-2022 走看看