第30课 初识 SPI 与数字电位器 MCP41xxx

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 公开课” 系列的入门教程。之前介绍了飞利浦的 I2C 协议,从本课将开始介绍 SPI 总线;但 SPI 不能称为协议,因为与 I2C 不同,它并没有一个机构为其制定任何标准,也正因如此,SPI 运用要比 I2C 灵活。有任何疑问请在评论区提出,我会逐一回答。

与 I2C 两根数据线相比,要用到4根数据线的 SPI 好像更复杂了。实际并非如此。首先,SPI 总线没有 I2C 复杂时序设计。其次,I2C 寻址和数据操作都是在两条数据线上的,而 SPI 总线上每个从属设备都有一条物理地址线与主机连接,谁的地址线电位低,谁就是操作对象,不容易出错。

先就此打住,实践中获得真知是 ArduiCo 的理念 ,因此我先从最简单通信方式讲起,直到我们对 SPI 的运用了然于心后,再深入总线内部,与 I2C 来一次大对决。

不过,我们还先得从理论讲起……

SPI 是 “Serial Peripheral Interface” (串行外设接口)的缩写,通信由 3 条 数据线和 1 条寻址线来负责:

MOSI,“Master-out, Slave-in”,主机发送,从机接收
MISO,“Master-in, Slave out”,主机接收,从机发送
SCK,“Serial clock”,时序
SS,“Slave-select”,从机寻址,1 条数据线接 1 个从机,不能连接多个;低电平时,该从机进行读写操作。

发现问题了吗? I2C 两天数据线是 1 根时序(SCL)和1 根数据(SDA),单根 SDA 要负责发送和接收,而 SPI 的接收和发送是分开两根数据线的。这意味着,后者可以工作在“全双工”(同时收发)状态,而 I2C 只能工作在半双工(要么接收、要么发送)状态。另外,I2C 的 SDA 还要负责寻址,会导致切换设备时产生延迟。

在这系列教程中,我们约定 Arduino 是主机,所有传感器等设备都是从机。对 Arduino UNO来说, 已经默认了 4 个 I/O 为 SPI 接口:

MOSI,D11;
MISO,D12;
SCK,D13;
SS,寻址端默认为 D10,但连接多个设备时,可将D9、D8……只要你有足够的数字端口,都可以设置为寻址端。

不同版本的开发板默认接口不一样,Mega 的 MISO 为 50, MOSI 为 51, SCK 为 52 and SS 通常是 53。具体请参考官方文档。 SPI 连接多个设备的方式如下:

一个从属设备需要一根 SS 寻址线,这是SPI的优点,也是缺点,因为 Arduino 的接口有限。另外,请记住,SS 低电平时,对应设备进入操作状态,所以连接多个设备时,SS 尽量设置上拉电阻。

SPI 设备通信以 byte 为单位,每次可以传输一个 8bits 数据,相当于 0~255 值。数据结构如下:

SPI 通信时要先定义从 byte 那一边读起,如图所示,左边为 MSB,右边是 LSB,大部分情况我们都会选择从 MSB 读起。

8bits 二进制数据除了数值,还能代表8位开/关的控制命令,这个参数设定非常多样,具体要看 DataSheet 的图例。我们现在就拿数字电位器 MCP41xxx(单通道) /MCP42xxx(双通道) 来举例:

 

SI 就是 MOSI,Arduino 向设备发送数据。要操作这个电位器,起码要发送,COMMAND Byte 和 Data Byte 两个字节的数据。

首先,发送一个 COMMAND Byte,C1、C0 bits 用于设置电位器的状态,P1、P0 用于选择通道(仅对双通道的 MCP42xxx 适用)。然后,发送 Data Byte,给电位器设置 0~255 值。

程序怎么写呢?Arduino 已经内置了SPI 库:

下一步,void setup() 中声明 SS 寻址线的输出状态,记住: SS 为低电平时,对应设备可进行读写操作:

启动 SPI 总线:

我们需要设置从 MSB 读取,还是 LSB 读取,默认是前者:

设置完成后,要对设备进行读写,操作逻辑是先把SS拉低,然后 transfer 数据:

数据传输完成后,将 SS 拉高:

是否感觉到发送模式很简单?一般来说,要理解 DataSheet 是很费劲(但不困难),但只要通过简单的演示,就变得很容易理解了。

两周前,我专门订购了几块 MCP41010 数字电位器(MCP41xxx,100表示 100KΩ,050表示 50KΩ,010 表示 10KΩ;MCP42xxx系列是双通道的,适合立体声音频控制。另外,同类芯片还有 MCP4162,但市面很少见),含进口税价大概 6.5 元 1 片。为什么要用这块 IC 来讲解,因为它的数据结构是最直观的(DataSheet):

按照 DataSheet 的标识,连接 Arduino UNO 默认的 SPI 端口即可,但 MISO悬空,因为数据电位器没有数据要反馈给 Arduino:

MOSI,D11;
MISO,无,悬空
SCK,D13;
CS(即SS),D10
Vdd,5V
Vss,GND

IC上,剩下的 3个引脚分别是电位器 PB0 端(B)、PW0(Wiper)、PA0 端(A),我们程序要控制的是 Wiper ,相当于旋转电位器的中间引脚,电阻向B滑动,B端和W端的电阻 Rbw 变小,W 端和A端之间的电阻 Rwa 变大,反之亦然。

根据电阻分压原理,我们将 B 端连接到 Arduino 3.3V端口,A 端连接到 GND,然后调整 Wiper,并测量 W端 它与 GND之间的电压:

V = V * (Rwa)/(Rbw + Rwa)

没有看懂?请找本初中物理课本,复习“电阻”那一章。

下面的程序会让Wiper不断向两边滑动,电压不断起伏变化:

我们会看到电压来回变化:

(注意:由于万用表电池电压下降,导致显示电压不正确,实际是在 0 ~ 3.3V ~ 0 ~ 3.3V 之间变化)

Leave a Reply

Your email address will not be published. Required fields are marked *