ARMlinux 内核gpio模拟I2C

    技术2022-05-18  11

    gpio模拟I2C

    I2C是由Philips公司发明的一种串行数据通信协议,仅使用两根信号线:SerialClock(简称SCL)和SerialData(简称SDA)。I2C是总线结构,1个Master,1个或多个Slave,各Slave设备以7位地址区分,地址后面再跟1位读写位,表示读(=1)或者写(=0),所以我们有时也可看到8位形式的设备地址,此时每个设备有读、写两个地址,高7位地址其实是相同的。

      I2C数据格式如下:

          无数据:SCL=1,SDA=1;       开始位(Start):当SCL=1时,SDA由1向0跳变;       停止位(Stop):当SCL=1时,SDA由0向1跳变;       数据位:当SCL由0向1跳变时,由发送方控制SDA,此时SDA为有效数据,不可随意改变SDA;       当SCL保持为0时,SDA上的数据可随意改变;       地址位:定义同数据位,但只由Master发给Slave;       应答位(ACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=0;       否应答位(NACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=1。

    当数据为单字节传送时,格式为:

          开始位,8位地址位(含1位读写位),应答,8位数据,应答,停止位。 当数据为一串字节传送时,格式为:

          开始位,8位地址位(含1位读写位),应答,8位数据,应答,8位数据,应答,……,8位数据,应答,停止位。

    需要注意的是:

          1,SCL一直由Master控制,SDA依照数据传送的方向,读数据时由Slave控制SDA,写数据时由Master控制SDA。当8位数据传送完毕之后,应答位或者否应答位的SDA控制权与数据位传送时相反。      2,开始位“Start”和停止位“Stop”,只能由Master来发出。      3,地址的8位传送完毕后,成功配置地址的Slave设备必须发送“ACK”。否则否则一定时间之后Master视为超时,将放弃数据传送,发送“Stop”。      4,当写数据的时候,Master每发送完8个数据位,Slave设备如果还有空间接受下一个字节应该回答“ACK”,Slave设备如果没有空间接受更多的字节应该回答“NACK”,Master当收到“NACK”或者一定时间之后没收到任何数据将视为超时,此时Master放弃数据传送,发送“Stop”。      5,当读数据的时候,Slave设备每发送完8个数据位,如果Master希望继续读下一个字节,Master应该回答“ACK”以提示Slave准备下一个数据,如果Master不希望读取更多字节,Master应该回答“NACK”以提示Slave设备准备接收Stop信号。      6,当Master速度过快Slave端来不及处理时,Slave设备可以拉低SCL不放(SCL=0将发生“线与”)以阻止Master发送更多的数据。此时Master将视情况减慢或结束数据传送。

         在实际应用中,并没有强制规定数据接收方必须对于发送的8位数据做出回应,尤其是在Master和Slave端都是用GPIO软件模拟的方法来实现的情况下,编程者可以事先约定数据传送的长度,不发送ACK,有时可以起到减少系统开销的效果。开发的过程当中,开发板上的I2C总线有限,如果I2C设备太多的话,就需要用GPIO模拟I2C来解决了。

     

     例子1

    1,kernel/arch/arm/mach-pxa/board-test.c 增加

    设置gpio为普通gpio,为输出口

    static mfp_cfg_t littleton_sw_i2c_cfg[] __initdata = {  /* gpio output*/  GPIO37,   /*_SW_I2C_CLK,   */  GPIO38,  /*_SW_I2C_SDA,  */ };  

    pxa3xx_mfp_config(ARRAY_AND_SIZE(littleton_sw_i2c_cfg));

      gpio_direction_output(GPIO37, 1);

      gpio_direction_output(GPIO38, 1);

     

    /* i2c */ static struct i2c_gpio_platform_data i2c_bus_data = {  .sda_pin = VIPER_RTC_I2C_SDA_GPIO,  .scl_pin = VIPER_RTC_I2C_SCL_GPIO,  .udelay  = 10,  .timeout = 100, };

     

    static struct platform_device i2c_bus_device = {  .name  = "i2c-gpio",  .id  = 1, /* pxa2xx-i2c is bus 0, so start at 1 */  .dev = {   .platform_data = &i2c_bus_data,  } };

     

    static struct i2c_board_info __initdata viper_i2c_devices[] = {  {   I2C_BOARD_INFO("ds1338", 0x68),  }, };

     

    第一个结构体中sda_pin和scl_pin是开发板上对应的gpio口(data线和clock线),udelay是与具体芯片时钟相关的参数,需要参考具体的datasheet。下面的两个open_drain是表明两个管脚是否是开漏电路,如果是则填1,否则填0。下面一个机构体中需要注意name应该填写i2c-gpio,另外id要注意设定为2,因为系统当中已经有两个I2C设备了。

     

     

    添加上需要的头文件:#include <linux/i2c-gpio.h>。在头文件devices.h中添加上设备结构体的声明,extern struct platform_device gpio_device_i2c;

    然后将gpio_device_i2c放在board-XXXX.c的数组devices中,形式请参考该数组中其他的设备。   

    static struct platform_device *viper_devs[] __initdata = {  &smc91x_device,  &i2c_bus_device,    ///  &serial_device,  &isp116x_device,  &viper_mtd_devices[0],  &viper_mtd_devices[1],  &viper_backlight_device, };

     

     static void __init viper_init(void)

     i2c_register_board_info(1, ARRAY_AND_SIZE(viper_i2c_devices));

    ;

    }

     

    然后再用i2c_register_board_info对其进行注册:   

    i2c_register_board_info(2, i2c_gpio_devices, ARRAY_SIZE(i2c_gpio_devices));   

    这样就完成了模拟步骤,可以直接用系统的I2C相关的注册等方法对设备进行注册和读写操作。 

     

     例子2

    设置sda和scl的gpio为普通gpio输出口。

     err = gpio_request(EP93XX_GPIO_LINE_EEDAT,    dev_name(&pdev->dev));  err = gpio_request(EP93XX_GPIO_LINE_EECLK,    dev_name(&pdev->dev));   err = gpio_direction_output(EP93XX_GPIO_LINE_EEDAT,1);   err = gpio_direction_output(EP93XX_GPIO_LINE_EECLK,1);

     

    static struct i2c_gpio_platform_data ep93xx_i2c_data = {  .sda_pin  = EP93XX_GPIO_LINE_EEDAT,  .sda_is_open_drain = 0,  .scl_pin  = EP93XX_GPIO_LINE_EECLK,  .scl_is_open_drain = 0,  .udelay   = 2, };

    static struct platform_device ep93xx_i2c_device = {  .name   = "i2c-gpio",  .id   = 0,  .dev.platform_data = &ep93xx_i2c_data, };

    void __init ep93xx_register_i2c(struct i2c_board_info *devices, int num) {  i2c_register_board_info(0, devices, num);  platform_device_register(&ep93xx_i2c_device); }


    最新回复(0)