树莓派学习笔记——wiringPi I2C设备使用详解

原文地址::http://blog.csdn.net/xukai871105/article/details/18033945


相关文章

1、树莓派学习笔记——I2C PCF8574 BCM2835 Library----http://blog.csdn.net/xukai871105/article/details/14109211

2、树莓派学习笔记——I2C Tools 学习笔记----http://blog.csdn.net/xukai871105/article/details/15029843


1.前言

    最近认真学习了树莓派,从浅到深认真分析了wiringPi实现代码,借助树莓派学习linux收获颇丰。深入学习linux一段时间后发现它非常有魅力,一个简单的IO口输出操作尽有那么多的“玩法”。wiringPi是一个简单易用的函数库,通过wiringPi可以扩展SPI和I2C等芯片,关于wiringPi的介绍和安装请参考我的另一篇【 博文】。
    本篇博文将通过一个简单的例子呈现wiringPi的使用,虽然例子简单但会深入分析wiringPi内部实现代码。

2.若干简述

2.1 MCP23107简述

     a) 硬件基本信息
    MCP23017的采用7位I2C地址,7位I2C地址中的低3位从高到低分别为A2 A1和A0,该3位为地址选择位,若地址选择全部接GND,那么MCP23017的I2C从机地址为 0x20。下图为MCP23017的硬件基本信息,左侧为MCP23017引脚图,右侧为I2C从机地址组成示意图, 从机地址的高4位为0100,高4位数字固定不变

图1 MCP23017硬件基本信息
    MCP23017具有GPA和GPB共16个扩展端口,另外INTA和INTB为IO中断管脚,可反映GPA或GPB的电平变化情况。请注意,RESET为复位管脚低电平有效, 可直接上拉到VDD或接一个1K到10K的电阻上拉至VDD。(经过两天的调试,我发现MCP23017运行几秒便不能运行,后来发现RESET管脚悬空,之后RESET和VDD相连问题解决。)
     b) 字节模式
    MCP23X17 系列能够工作于 字节模式或顺序模式 ( IOCON.SEQOP)。 
     字节模式禁止地址指针自动递增。工作于字节模式时,MCP23X17系列在数据传送期间,传送每个字节后不会递增其内部地址计数器。 顺序模式可使地址指针自动递增。工作于顺序模式时,MCP23X17系列在数据传送期间,传送每个字节后会递增其地址计数器。访问最后一个寄存器后,地址指针自动返回到地址00h。(SEQOP位默认值为0)
    
     c) 寄存器映射关系
     BANK位用于更改寄存器的映射方式
    如果BANK = 1,与每个端口关联的寄存器将相互独立。与PORTA关联的寄存器从地址00h - 0Ah 映射,与PORTB关联的寄存器从10h - 1Ah映射;如果BANK = 0,A/B寄存器将配对组合。例如,IODIRA将映射到地址00h,而IODIRB将映射到 下一地址 (地址01h)。所有寄存器的映射是从00H到15H。(BANK默认值为0)

图2 寄存器地址映射关系
【其他重要寄存器】
【IODIR I/O方向寄存器。
    写1为输入状态,写0为输出状态。请注意在大多数MCU中,写1为输出状态,而MCP23017这样类似的设置完全是microchip的特色。
【GPPU】GPPU上拉电阻寄存器
     写1为使能上拉,写入0为禁止上拉。使能芯片内部的上拉电阻往往有很有用。        
【GPIO】通用I/O端口寄存器
     写1可以使相应的端口输出高电平,写0可以使响应的端口输出低电平。读取该寄存器可以获得相应端口的输入值。

2.2 用户空间开发I2C设备

    通常情况下I2C设备由内核控制。但是在用户空间也可以通过 /dev设备接口访问I2C设备。每一个在内核注册的I2C设备都具有一个编号,该编号从0开始计数,可以查看 /sys/class/i2c-dev/查看具体编号,也可以通过i2ctool工具中的i2cdetect查看。【 i2cdetect工具的安装和使用】【 I2C操作时序介绍

图3 查看 sys/class/i2c-dev
    在linux系统中所有的设备都可以采用文件的形式访问,树莓派的i2c设备位于/dev/i2c-x(对应树莓派版本2为/dev/i2c-1),可通过open,close,wirte,read和ioclt控制i2c设备。例如open为打开I2C设备,close为关闭I2C设备,wirte为向I2C设备写入字节内容,read为从I2C设备获得字节内容,ioctl可控制I2C设备的运行参数,例如从机地址或I2C速度等参数。更多内容请参考【 linux内核 I2C驱动说明】。
    默认情况下,I2C和SPI设备被拉入了“黑名单”,换句话说在树莓派初始化时SPI和I2C设备并没有载入内核。在使用I2C设备之前需要载入SPI和I2C设备,具体参开请参考博文【树莓派I2C和SPI设备配置】

3.简单测试代码

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <wiringPi.h>  
  3. #include <mcp23017.h>  
  4.   
  5. // 扩展MCP23017 起始PIN地址  
  6. // MCP23017占用 100-115   
  7. #define EXTEND_BASE 100  
  8.   
  9. int main (void)  
  10. {  
  11.     // wiringPi初始化  
  12.     wiringPiSetup( );  
  13.   
  14.     // mcp23017初始化,此时MCP23017的I2C地址为0x20  
  15.     mcp23017Setup( EXTEND_BAS E, 0x20 );  
  16.       
  17.     int i;  
  18.     for ( i = 0 ; i < 16 ; i++ )  
  19.     {  
  20.         pinMode( EXTEND_BASE + i, OUTPUT );  
  21.     }  
  22.   
  23.     for (;;)  
  24.     {  
  25.         for( i = 0 ; i < 16; i++)  
  26.         {  
  27.             digitalWrite ( EXTEND_BASE + i, HIGH);  
  28.             delay (500);  
  29.             digitalWrite ( EXTEND_BASE + i, LOW);  
  30.             delay (500);  
  31.         }  
  32.     }  
  33.     return 0 ;  
  34. }  

4.代码详解

4.1 wiringPiSetup

    【 见另一篇博文

4.2 mcp23017Setup

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int mcp23017Setup (const int pinBase, const int i2cAddress)  
  2. {  
  3.     int fd ;  
  4.     struct wiringPiNodeStruct *node ;  
  5.     // 初始化I2C设备  
  6.     if ((fd = wiringPiI2CSetup (i2cAddress)) < 0)  
  7.     return fd ;  
  8.   
  9.     // 附加操作,设置MCP23017设备 I2C操作地址不自动递增  
  10.     wiringPiI2CWriteReg8 (fd, MCP23x17_IOCON, IOCON_INIT) ;  
  11.   
  12.     // MCP23017加入链表  
  13.     node = wiringPiNewNode (pinBase, 16) ;  
  14.   
  15.     // 赋值相应的操作函数 my开头的函数均位于mcp23017.c文件中  
  16.     node->fd = fd ;  
  17.     node->pinMode = myPinMode ;  
  18.     node->pullUpDnControl = myPullUpDnControl ;  
  19.     node->digitalRead = myDigitalRead ;  
  20.     node->digitalWrite = myDigitalWrite ;  
  21.     node->data2 = wiringPiI2CReadReg8 (fd, MCP23x17_OLATA) ;  
  22.     node->data3 = wiringPiI2CReadReg8 (fd, MCP23x17_OLATB) ;  
  23.     return 0 ;  
  24. }  

    mcp23017Setup是一个较为复杂的函数,主要实现三部分功能—— 第一,初始化树莓派I2C设备,通过打开 /dev/i2c-1实现(相对于树莓派2版本),通过open可返回一个 文件fd (可理解为设备的标记或编号,就是一个数字,好比你的身份证号)第二,加入新节点,wiringPi通过一个链表维护所有的pin,在树莓派中pin是一个比较有趣的概念,例如一个MCP23017具有16个输入或输出PIN,这使得wiringPi增加额外的16个PIN,该16个PIN起始地址可以设定,但必须大于64,16个PIN的地址连续; 第三,增加控制函数,例如digitalRead,digitalWrite和pullUpDnControl等。

4.2.1 I2C初始化 wiringPiI2CSetup

初始化的最终目标为   fd = open (device, O_RDWR)  ioctl (fd, I2C_SLAVE, devId) 
【调用关系】 【wiringPiI2CSetup】 --> 【wiringPiI2CSetupInterface】
                                                          【wiringPiI2CSetupInterface】 --> 【  fd = open (device, O_RDWR) 
                                                                                                      --> 【  ioctl (fd, I2C_SLAVE, devId)   】 

4.2.2 加入新节点 wiringPiNewNode

wiringPi的外部设备有一个链表构成,相应的数字量读写和模拟量读写均根据该链表完成。该链表是wiringPi的"核心",链表结构体见以下代码。在该 结构体中记录了节点编号(设备的起始编号 pinBase),以及各种操作函数【操作函数的说明请看4.2.3】。
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct wiringPiNodeStruct  
  2. {  
  3.     int pinBase ;  
  4.     int pinMax ;  
  5.     int fd ; // Node specific  
  6.     unsigned int data0 ; // ditto  
  7.     unsigned int data1 ; // ditto  
  8.     unsigned int data2 ; // ditto  
  9.     unsigned int data3 ; // ditto  
  10.     void (*pinMode) (struct wiringPiNodeStruct *node, int pin, int mode) ;  
  11.     void (*pullUpDnControl) (struct wiringPiNodeStruct *node, int pin, int mode) ;  
  12.     int (*digitalRead) (struct wiringPiNodeStruct *node, int pin) ;  
  13.     void (*digitalWrite) (struct wiringPiNodeStruct *node, int pin, int value) ;  
  14.     void (*pwmWrite) (struct wiringPiNodeStruct *node, int pin, int value) ;  
  15.     int (*analogRead) (struct wiringPiNodeStruct *node, int pin) ;  
  16.     void (*analogWrite) (struct wiringPiNodeStruct *node, int pin, int value) ;  
  17.     struct wiringPiNodeStruct *next ;  
  18. } ;  

wiringPiNewNode相关操作可分为3步
【1】查找MCP23017的pin是否被占用。
【2】在内存中开辟一个wiringPiNewNode。
【3】给该结构体变量设置默认值,并添加到控制链表中。

4.2.3 编写控制函数 

【pinMode】IO口功能设置函数,mode参数—— INPUT为输入状态OUTPUT为输出状态
【pullUpDnControl】IO口输入功能设置,mode参数
【digitalRead】读取IO口管脚电平状态
【digitalWrite】设置IO口电平状态
【analogRead】 若该IO为模拟量输入端口,读取该端口的模拟输入
【analogWrite】 若该IO为模拟量输出端口,设置该端口的模拟输出

4.3 pinMode

      MCP23017的扩展pin必须大于64,在MCP23017Setup函数中注册了相应的pin,此处通过wiringPiFindNode在链表中查找MCP23017的node信息, node信息包括pin编号、设置IO方向、读取和设置IO管脚等。请注意  node->pinMode (node, pin, mode) 指向 MCP23017中的myPinMode函数,该注册工作在mcp23017Setup发生。
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 位于wiringPi.c  
  2. void pinMode (int pin, int mode)  
  3. {  
  4.     int fSel, shift, alt ;  
  5.     struct wiringPiNodeStruct *node = wiringPiNodes ;  
  6.      
  7.     // PIN编号小于64,内部设备  
  8.     if ((pin & PI_GPIO_MASK) == 0)  
  9.     {  
  10.        // 代码省略  
  11.     }  
  12.     else  
  13.     {  
  14.         // 查找是否存在该设备  
  15.         if ((node = wiringPiFindNode (pin)) != NULL)  
  16.         node->pinMode (node, pin, mode) ;  
  17.         return ;  
  18.     }  
  19. }  
  20. // 位于mcp23017.c     
  21. static void myPinMode (struct wiringPiNodeStruct *node, int pin, int mode)  
  22. {  
  23.     int mask, old, reg ;  
  24.     pin -= node->pinBase ;  
  25.     // 操作GPA 或 GPB  
  26.     if (pin < 8)  
  27.     reg = MCP23x17_IODIRA ;  
  28.     else  
  29.     {  
  30.         reg = MCP23x17_IODIRB ;  
  31.         pin &= 0x07 ;  
  32.     }  
  33.      
  34.     // 获得IODIRx原始值  
  35.     mask = 1 << pin ;  
  36.     old = wiringPiI2CReadReg8 (node->fd, reg) ;  
  37.     // 设置新值  
  38.     if (mode == OUTPUT)  
  39.     old &= (~mask) ;  
  40.     else  
  41.     old |= mask ;  
  42.     wiringPiI2CWriteReg8 (node->fd, reg, old) ;  
  43. }  

4.4 digitalWrite

    digitalWrite函数和pinMode函数操作思路相同,先检测该pin位于板载区域还是扩展区域,如果是扩展区域该pin是否有效,如果有效则调用digitalRead函数。
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 位于wiringPi.c  
  2. int digitalWrite (int pin)      
  3. {  
  4.     char c ;  
  5.     struct wiringPiNodeStruct *node = wiringPiNodes ;  
  6.     // PIN编号小于64,内部设备  
  7.     if ((pin & PI_GPIO_MASK) == 0)  
  8.     {  
  9.        // 代码省略  
  10.     }  
  11.     else  
  12.     {  
  13.         // 查找是否存在该设备  
  14.         if ((node = wiringPiFindNode (pin)) == NULL)  
  15.         return LOW ;  
  16.         return node->digitalWrite (node, pin) ;  
  17.     }  
  18. }  
  19. // 位于mcp23017.c  
  20. static void myDigitalWrite (struct wiringPiNodeStruct *node, int pin, int value)  
  21. {  
  22.     int bit, old ;  
  23.   
  24.     pin -= node->pinBase ;   
  25.     bit = 1 << (pin & 7) ;  
  26.   
  27.     // 操作GPA  
  28.     if (pin < 8)  
  29.     {  
  30.         old = node->data2 ;  
  31.   
  32.         if (value == LOW)  
  33.         old &= (~bit) ;  
  34.         else  
  35.         old |= bit ;  
  36.   
  37.         wiringPiI2CWriteReg8 (node->fd, MCP23x17_GPIOA, old) ;  
  38.         node->data2 = old ;  
  39.     }  
  40.     // 操作GPB  
  41.     else      
  42.     {  
  43.         old = node->data3 ;  
  44.   
  45.         if (value == LOW)  
  46.         old &= (~bit) ;  
  47.         else  
  48.         old |= bit ;  
  49.   
  50.         wiringPiI2CWriteReg8 (node->fd, MCP23x17_GPIOB, old) ;  
  51.         node->data3 = old ;  
  52.     }  
  53. }  

4.6 wiringPiI2CReadReg8 和 wiringPiI2CWriteReg8

    这两个函数涉及到linux驱动方面的内容,均通过ioctl (fd, I2C_SMBUS, &args)实现,具体请看以下代码,更多底层的内容请参考【 linux内核 I2C驱动说明
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. struct i2c_smbus_ioctl_data  
  2. {  
  3.     char read_write ;  
  4.     uint8_t command ;  
  5.     int size ;  
  6.     union i2c_smbus_data *data ;  
  7. } ;  
  8. static inline int i2c_smbus_access (int fd, char rw, uint8_t command, int size, union i2c_smbus_data *data)  
  9. {  
  10.     struct i2c_smbus_ioctl_data args ;  
  11.     args.read_write = rw ;  
  12.     args.command = command ;  
  13.     args.size = size ;  
  14.     args.data = data ;  
  15.     return ioctl (fd, I2C_SMBUS, &args) ;  
  16. }  
  17. int wiringPiI2CReadReg8 (int fd, int reg)  
  18. {  
  19.     union i2c_smbus_data data;  
  20.     if (i2c_smbus_access (fd, I2C_SMBUS_READ, reg, I2C_SMBUS_BYTE_DATA, &data))  
  21.     return -1 ;  
  22.     else  
  23.     return data.byte & 0xFF ;  
  24. }  
  25. int wiringPiI2CWriteReg8 (int fd, int reg, int value)  
  26. {  
  27.     union i2c_smbus_data data ;  
  28.     data.byte = value ;  
  29.     return i2c_smbus_access (fd, I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, &data) ;  
  30. }  

5.总结与提高

    wiringPi提供了一个很好的封装,但是也是一把双刃剑。对于初学而言可以很好的利用树莓派实现应用,绕开linux驱动方面的知识,但是慢慢熟悉之后还是要补充linux驱动方面的内容个,使用open、wirte和ioctl实现I2C设备的控制。
    国外有很多博文指出,BCM2835的I2C存在问题,该问题发生在I2C Stop时序保持的时间太短导致I2C从设备无法正常体质。参考资料中的第1项包含对I2C硬件问题的讨论和解决方法,请各位参考。

6.参考资料和精彩博文


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值