gpio模拟I2C,驱动pcf8574T

一、pcf8574T介绍

查看pcf8574T的数据手册,

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

二、I2C介绍

参考:

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

1、起始和停止时序

2、数据位的传输

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

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

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

代码片段:

// 接收应答信号
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。

代码片段:

// 发送应答信号
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\n");
        //return -1;
    }
      i2c_write_byte(val);
      acktmp = i2c_recv_ack();
      if (acktmp == 1) {
        PRK("i2c_recv_ack fail\n");
        //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 <[email protected]>
 *
 * 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\n");
        //return -1;
    }
      i2c_write_byte(val);
      acktmp = i2c_recv_ack();
      if (acktmp == 1) {
        PRK("i2c_recv_ack fail\n");
        //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!\n");
        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!\n",wrdata);
            return 0;
        } else {
            PRK("op_sensorid_write %d fail!\n",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.\n", __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.\n", __LINE__);
        return -1;
    }
    gpio_direction_output(sda_pin, 1);//该管脚设为输出,且输出为高电平

    PRK(KERN_INFO " op_pcf8574_open\n");
    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\n");

    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\n");
        return ret;
    }

    printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" init\n");

    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\n");
        return ;
    }

    printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" exit\n");
}
module_exit(pcf8574_exit);

MODULE_DESCRIPTION(DRV_DESC);//描述
MODULE_VERSION(DRV_VERSION);//版本
MODULE_AUTHOR("tingpan <[email protected]>");//作者
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]\n"      "MODE is r or w\n"      "data is the value want to write when MODE is w\n"

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\n", __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\n",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

时间: 2024-07-30 19:08:22

gpio模拟I2C,驱动pcf8574T的相关文章

gpio模拟i2c驱动

前段时间做项目,需要gpio模拟i2c通信,最后参考了一些资料,然后编写了一个程序.现在发出来,以免以后忘记,也为一些需要的朋友提供参考.不喜勿喷哈. /* 说明:该程序是基于atmel公司的sama5d35 MCU 用其中两个GPIO引脚模拟i2c通信. * 其中两个引脚连接到了hd1650上面.然后检测按键扫描的驱动 * */ 该程序可以作为gpio模拟i2c程序的参考.不同的平台,函数实现不同,但是i2c通信的时序和原理是相同的.希望对一些朋友有帮助. 1 #include<linux/i

S5PV210之GPIO模拟I2c时序之pcf8591与at24xx linux3.0.8驱动

目录:一. 说明 二. 驱动程序说明及问题 三. 案例一       四. 案例二 一. 说明 mini210开发板上带了at24c08, 看了linux内核自带的at24.c的驱动程序,编译下载到看发板,读写都行:通过增加一些调试信息,对linux i2c驱动其中的编写方法之一有了一定了解,在我的另外一篇博文有详细说明.但同时对在linux下GPIO模拟i2c产生了兴趣,于是就写这篇博文来记录驱动编写过程中遇到的问题.如果想了解了i2c时序,请google或百度一下. 本篇博文通过misc驱动

GPIO模拟I2C

之前在学习I2C驱动,想了想应该先深刻理解下I2C协议.个人觉得理解I2C协议最好的方法就是实践,而最好的实践方式就是用GPIO来模拟I2C协议进行试验,直观深刻. 先看下I2C时序图: 根据时序图,用两个gpio分别模拟sda和scl即可.在这之前自然需要先对gpio进行输入输出的配置.这里只从写数据角度来描述这一过程.读数据同理即知. 先配置gpio,包括sda.scl.供电gpio等额外功能gpio. gpio_config(GPIO_SDA, GPIO_OUTPUT); gpio_con

STM32F207 两路ADC连续转换及GPIO模拟I2C给Camera Sensor初始化参数

1.为了更好的方便调试,串口必须要有的,主要打印一些信息,当前时钟.转换后的电压值和I2C读出的数据. 2.通过GPIO 模拟I2C对镁光的MT9V024进行参数初始化.之前用我以前公司SP0A19芯片,是I2C是8位宽的,而镁光的地址是8位,而数据位是16个字节, 其实不管是8位还是16位,每次发送都是8个字节然后一个应答位,所以只要稍微改下代码即可. 3.实现两路ADC连续转换,两路ADC转换:一路是检测锂电池电压:一路是检测压力传感器,其实都是检测电压,当检测到压力为零,并累计多长时间后进

触摸按键--模拟I2C驱动TS12芯片

TS12芯片驱动 使用模拟I2C驱动TS12完成触摸按键检测功能: #include "stm8s.h" #include "global.h" #include "drv_key.h" uint8_t KeyByte; /******************************************************************************* // Function: I2C_Int // Descripti

【转载】GPIO模拟i2c通信

I2C总线的通信过程(见图4-8)主要包含三个主要阶段:起始阶段.数据传输阶段和终止阶段. 1. 起始阶段 在I2C总线不工作的情况下,SDA(数据线)和SCL(时钟线)上的信号均为高电平.如果此时主机需要发起新的通信请求,那么需要首先通过SDA和SCL发出起始标志.当SCL为高电平时,SDA电平从高变低,这一变化表示完成了通信的起始条件. 在起始条件和数据通信之间,通常会有延时要求,具体的指标会在设备厂商的规格说明书中给出. 2. 数据传输阶段 I2C总线的数据通信是以字节(8位)作为基本单位

Linux的i2c驱动详解

目录(?)[-] 简介 架构 设备注册 I2C关键数据结构和详细注册流程 关键数据结构 详细注册流程 使用I2C子系统资源函数操作I2C设备 Gpio模拟i2c总线的通用传输算法 总结 理清i2c中的个结构体关系 i2c驱动的编写建议 1 简介 I2C 总线仅仅使用 SCL . SDA 两根信号线就实现了设备之间的数据交互,极大地简化对硬件资源和 PCB 板布线空间的占用.因此, I2C 总线被非常广泛地应用在 EEPROM .实时钟.小型 LCD 等设备与 CPU 的接口中. Linux I2

Linux内核调用I2C驱动_以MPU6050为例

Linux内核调用I2C驱动_以MPU6050为例 0. 导语 最近一段时间都在恶补数据结构和C++,加上导师的事情比较多,Linux内核驱动的学习进程总是被阻碍.不过,十一假期终于没有人打扰,有这个奢侈的大块时间,可以一个人安安静静的在教研室看看Linux内核驱动的东西.按照Linux嵌入式学习的进程,SPI驱动搞完了之后就进入到I2C驱动的学习当中,十一还算是比较顺利,I2C的Linux驱动完成了. 为了测试I2C是否好用,选择一个常用的I2C传感器,手头有个MPU6050,刚好作为I2C的

GPIO模拟SPI

上次用gpio模拟i2c理解i2c协议,同样的,我用gpio模拟spi来理解spi协议. 我用的是4线spi,四线分别是片选.时钟.命令/数据.数据. 数据在时钟上升沿传递,数据表示的是数据还是命令由命令/数据线决定. 开始条件: void spi_start(void) { gpio_config(GPIO_CS, GPIO_OUTPUT); udelay(SPI_SPEED_DURATION); gpio_set(GPIO_CS, 0);/* start condition */ udela