友情提示:380元/半年,儿童学编程,就上码丁实验室。
我是潘,曾经是个工程师。这是“Arduino 公开课” 系列的入门教程。本课开始将介绍 I2C 通信协议,利用该协议 Arduino 将获得极大的扩展,我们将深入了解 I2C 各类应用和细节。有任何疑问请在评论区提出,我会逐一回答。
Arduino UNO 扩展接口并不多,但它有一项必杀技:I2C(全称“Inter-Integrated Circuit”,也叫
“IIC” )。这个通信协议已经存在了将近40年,由飞利浦半导体(现在的 NXP)所定义。它将连接数百个设备或传感器的复杂电路,简化为2根数据线(SDA —— 数据 和 SCL —— 时序)。我们可以看看 NXP 的官方介绍:
I2C 的优势
现在已有成千上万使用 I2C 协议的设备或传感器,通过 Arduino 开发板都能控制它们。常见的如。外部精密时钟(DS1307)、数字电位器、温度计、指南针、FM收音模块、外部存储、I/O扩展,液晶显示屏、数字放大器等等。只需一条总线(即两条数据线)就可以连接所有的设备,上限是112个。
Arduino UNO 的I2C端口是 A4(SDA)、A5(SCL):
如果你使用的是 Mega2560,那么SDA 和 SCL 分别是 20、21,其他开发板请参考官方DataSheet。如果使用 DIY系统 则 SDA、SCL 分别为27脚和28脚。I2C 总线接线方式很简单:
如果只连接一个 I2C 设备,那么上拉电阻就不是必须的。如果连接多个设备,那么要接两个 10KΩ 上拉电阻,但也有些设备会要求 4.7KΩ 上拉电阻。没关系,按要求连接即可。I2C 总线可以达到 1 米左右,但要注意导线寄生电容对数据传输的影响。另外,使用 NXP P82B715 等IC,可以将总线长度延长至20米以上,这个以后我们会讲到。
I2C 设备分 master 和 slave 两类定义,Arduino 开发板一般就是 master,总线连接的设备和传感器是 slaves 。问题出现了,总线上设备那么多,读写数据时怎样才能区分它们?我们想对A设备写入数据,但如何保证不会写进了B设备?
实际上,每个 I2C 设备都有一个唯一的地址。利用地址信息就可以区分它们。
首先,I2C 需要使用库 wire.h,同时要用到方法 Wire.begin(),要把它放到 void setup() 中。然后,向I2C设备发送数据必须具备两个元素,设备的唯一地址(16进制)和至少一个字节的数据。
1
|
Wire.beginTransmission(0x2F);// 设备地址为0x2F
|
地址会向 SDA 线发送,提醒对应的设备,数据来了:
1
|
Wire.write(0);//向总线写入0,这个数据会传送到上面的地址中
|
根据地址找到设备后,向其发送数据。这期间,这个设备会张开手臂迎接数据的到来,而其他设备则会无视,因此一次只能操作一个I2C设备。
1
|
Wire.endTransmission();//结束通信
|
此语句表示本次通信结束。否则,无法进行下一个设备的数据传输。
我们需要从设备接受数据,同时也知道了有多少数据将会反馈回来。比如,要求某个设备一次性反馈3个寄存器的信息,每个信息需要一个变量来存储:
1
|
Wire.requestFrom(device_address,3);
|
这告诉设备,向 Arduino 反馈3个寄存器的信息,后面立刻跟上3个指针变量:
1
2
3
|
*a=Wire.read();
*b=Wire.read();
*c=Wire.read();
|
获得设备的数据反馈后,将指针变量 *a、*b、*c 视作普通变量使用即可。实际上,用普通变量接受数据也没有问题,但一些操作中,使用指针变量会带来意想不到的好处,比如利用 sizeof(指针变量类型) 的方法,跳转到前后一个字节。
更多 I2C 的细节可以参考 NXP 的官方文档。
现在利用上面的知识,通过 I2C 控制 MCP4725 (12bits DAC )模块的电压输出。其实,MCP4725是一个能将参考电压分成 4096 份(12bits)的精密电压 IC。在 Arduino 上,可以输出0~5V之间的电压,每级电压为 5/4096V :
在 Arduino UNO 上,模块 SDA 接 A4,SCL 接 A5,VCC 是参考电压,接5V或3.3V都可以,输出范围分别为 0~5V 和 0~3.3V,GND 接 GND。
学习操作一个模块时,首先要做的事情是查阅其 DataSheet,MCP4725 的在这里。第19页有一个表格(FIGURE 6-2: Write Commands for DAC Input Register and EEPROM.)很重要:
表格告诉我们,操作这个模块要给出4个字节:
1st byte 是地址;
2nd byte 为操作字节。前三位C2=0、C1=1、C0=0 时,只对 DAC 操作,如果C0=1,还会对EEPROM写入。
3rd byte 是 12bits DAC 的前8bits;
4th byte 前4位为 DAC 总数据的后4位,最后4位没有定义。
现在写一个程序,输出 0~ 3.3V 任意一个电压值,精度位 3.3/4096 V:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/*
作者:Ardui.Co
效果:MCP4725 输出 0~3.3V 任意电压
版本:1.0
更新时间:2017年3月26日
*/
#include <Wire.h> // I2C 协议必不可少的库
#define MCP4725 0×62 // MCP4725 的地址,如果不清楚可以扫描一下
unsignedintval;
bytebuffer[3];//设置字节变量
voidsetup() {
Wire.begin();// I2C 要开始传输了
}
voidloop(){
buffer[0]=0b01000000;// 对应表格 2nd byte “操作字节”
val=3000;// 写入 0 ~ 4095 的范围
buffer[1]=val>>4;// 将一个整数转化为二进制,并向右移位4位,对应 3rd byte
buffer[2]=val<<4;// 将一个整数转化为二进制,并向左移位4位,最右边空出4位了,对应 4th byte
Wire.beginTransmission(MCP4725);// 传入地址,对应 1st byte
Wire.write(buffer[0]); // 找到地址后,将操作参数传递给 2nd byte
Wire.write(buffer[1]); // 将前面8位传递给 3rd byte
Wire.write(buffer[2]); // 将后面4位传递给 4th byte
Wire.endTransmission();// 结束传输
}
|
此时测量MCP4725 上的 OUT 和 GND 之间的电压:
Vout = VCC * val/4095 = 3.3V * 3000/4095 ≈ 2.418V。
不过,由于仪器的测量误差和 LDO 的输出精度,VCC不一定等于3.3V,此时,先测量一下VCC,再做运算。实际测得 VCC = 3.274V,因此 Vout = 3.274V * 3000/4095 = 2.398V
Arduino 是没有内置 DAC 的,但 DAC 的用途很广,比如结合第13课的旋转编码器,就能制作一个连续可以调的数字电源;也可以利用各种数学函数,输出任意波形的信号。