Author Archives: panft

第32课 ENC28J60联网 读取服务器信息

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 公开课” 系列的入门教程。上一节课,我们已成功让 Arduino 连接到网络,现在要让 Arduino 做的第一件事情:访问网络,并读取服务器上的信息。有任何疑问请在评论区提出,我会逐一回答。

IoT 中,指令一般由中枢(服务器)向节点发送,但在一些特殊环境中,比如,节点没有固定的IP地址(尤其在内陆,DDNS 动态域名服务非常不稳定,从互联网访问家庭宽带IP很难实现)。换种思维,让 Arduino 主动去抓取服务器上的指令,或者其他信息?这很容易实现,与家里的电脑访问web一样,让 Arduino 访问服务器即可。

下面是访问 ardui.co 的完整程序:

我们要用到 IDE 内置的 AVR库(后面的课程介绍) “pgmspace.h”,以启用”虚拟内存“功能,节省SRAM。变量 website[],用到 ”PROGMEM“,作用就是将变量信息存储在 FLASH,使用时再调用。FLASH 速度比 SRAM 慢,此方法会影响运行速度。

不过,谁叫 UNO 只有 2KB SRAM 呢?在资源消耗极高的程序中,必须精打细算:》如果是 Arduino Mege 2560,SRAM 容量大很多,且可以扩展,但是后话了。

第二个要点是,确认目标服务器的地址,比如 “ ardui.co ”。这个地址通过 DNS 服务器转换为真实的 IP。上节课使用了静态设置 staticSetup()  或者 dhcpSetup()  两种方式来设置 Arduino 的 IP、DNS 等信息,现在通过 dnsLookup() 方法来检查 DNS 服务器是否正常:

确认连接到 DNS 服务器后,就可以开始 Web 之旅了。

EtherCard 提供了一个便捷的方法 browseUrl() 访问服务器,其数据结构如下:

看起来很复杂,第1个、第2个形参(urlbuf、urlbuf_varpart)分别是地址固定部分,后者变量;第3个形参 hoststr 很好理解,即网址;第4个形参 callback,为回调函数名称,当连接结束时调用的函数。举个例子:

PSTR() 属于AVR 编程的知识,简单理解为,将地址固定的部分存储在 FLASH 中,以达到节省 Arduino 内存(SRAM)的目的。“test.php” 为我们要访问的文件,可以是html,也可以是txt,只要编码一致,后缀不限。“website” 即我们的网址 “ardui.co” 。难点在于其中的回调函数 :“response_callback” 。

先复习一下,上节课用到的缓冲区声明:

该声明的作用为设置缓冲区的大小,这个缓冲区用于储存外来数据包,当调用 browseUrl() 方法后,服务器反馈的数据包都在里面。这里设计的回调函数 response_callback() 作用是读取缓冲区的信息:

通过这个函数,串口会打印出缓冲区里面所有的信息:

红色框框是头文件信息,网站的服务器、缓存控制、编码等基本信息会呈现在这里,紧接着才是网页的内容。头文件信息没有太大用处,需要过滤掉。这里不需要特别的方法或者函数。利用 C 语言的指针,指向头文件结束的位置即可:

数值 “265” 是头文件的大小,将头文件拷贝到文本编辑器,就可以查到:

这是一个指针,指向缓存开始加上265个byte的位置。

所有知识难点都已经介绍完了,要注意的是:

响应函数必须保留,否则程序是不工作的。另外,millis() > timer 的设计是为了延时,如果不需要频繁抓取,可以增加 timer。

上面并非整个网页的内容,而是缓冲区(Ethernet::buffer)所有的信息,一开始设置为 700 bytes,(尝试设置为1000 bytes 呢?)。另外,由于 Arduino 与中文网站的编码不一致,因此显示乱码。

 

第31课 ENC28J60联网 Ping通你的Arduino

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 公开课” 系列的入门教程。ENC28J60是一款非常便宜的网络模块,本课我们介绍该模块的使用,Ping 通你的Arduino。有任何疑问请在评论区提出,我会逐一回答。

接入网络后Arduino 就成为了 IoT(物联网,Internet of Things)的一部分。这片小小的 MCU 不再是一个个体。通过它,我们与被控设备就能进行实时的数据交互。

比如,通过网络可以让控制中心,监控温室里面的温度、湿度、土壤盐度、气压等数据,然后根据这些信息,实时对温室调控。

当然,Arduino 自身也能根据数据,对温室的设备进行反馈,只要设定好相应的程序即可。不过,大规模有成千上百万个温室时,就无法做到统一调度。因此 Arduino 在小型设备中,可以当作一个具有简单收集和反馈能力的大脑,而在大规模应用中,它更适合,扮演节点的角色。

然而,上面的都是题外话了,在后面设计人工智能的可能,我们将详细展开来讨论。

Arduino 官方推荐 W5100 模块,这个模块虽然功能强大,同时,内置了SD模块方便数据存储,但其价格也相当高。所以我们还是选择性能稳定、库同样丰富完善的 ENC28J60 模块,价格 10~20 元之间。

官方Ethernet 库是为W5100开发的,所以我们要下载针对 ENC28J60 的库,建议使用 JeeLabs Café 的 Ethercard 库,JeeLabs Café 已将它在 Github 上开源,国内的朋友可以在这里下载另外,库函数的官方文档在这里。安装方式跟之前一样,解压放到库文件夹中即可。按照一般的 SPI 接线方式来连接即可,但这里必须接 RESET :

MOSI,D11;
MISO,D12;
SCK,D13;
SS/CS,寻址端默认为 D10;
RESET,RESET,重启;
VCC,3V3;
GND,GND;

我们第一个程序就是从PC上,Ping 通我们Arduino。即从计算机发送一个ping的请求,经过网络到达Arduino 设备,然后设备反馈一个“收到了”的信息:

各种方法似乎很复杂,但理解了其含义,用起来就很简单:

注释已经讲的很清楚了,但如果路由/服务器开启了DHCP的话,还可以通过让 Arduino 自动获得 IP 地址,其实,在现实的大部分应用中,我们只需声明 MAC 地址和缓冲区的大小。

声明了一个函数,作用是在串口打印请求设备的 IP 地址,byte* 是一个指针,指向缓冲区存储 IP 地址的地址。注意这个函数要在 void setup() 中,通过 registerPingCallback(function) 方法来调用。

显而易见,Ping Callback 的含义是,当收到 Ping 请求时,调用另外一个函数;这个方法在寄存器中,所以不用放在 void loop(),它也会一直监听。

begin() 作用为初始化网络接口,设置缓冲区和MAC地址,如果反馈“0”,表明硬件连接故障,“1”说明链接成功。staticSetup() 数据结构如下:

如果网关、DNS、mask 不设置,则默认为0。

作用是监听收到的数据包,并将数据包赋值给 len。

对 Ping 等数据包信息作出响应。

将程序载入 Arduino 发送 Ping 请求时,IDE 会显示发送请求主机的 IP 地址:

现在我们已经成功将 Arduino 接入网络了!不过,在智能家居应用中,不可能每次都手动设置设备的IP 地址,更希望它能够自动设置。其实,方式很简单,使用 dhcpSetup() 方法,自动获取DHCP服务器的信息即可:

登陆路由器界面,从 DHCP 查看主机连接列表, 会发现一个 “Arduino-ENC28j6” 的设备。

第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 之间变化)