Category Archives: Arduino 入门教程

第33课 ENC28J60联网 Web 服务器

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 公开课” 系列的入门教程。上节课介绍了如何访问服务器,反过来,本课要让 Arduino 作为一个可以随时随地访问 Web 服务器,在交互控制中更有现实意义。有任何疑问请在评论区提出,我会逐一回答。

Arduino 更适合作为控制节点,而不适合当服务器,在复杂的中央控制系统上,Raspberry Pi 是我的首选。不过,在需要快速、简单、可靠的应用环境中,将一块 Arduino 作为节点的网络界面也未尝不可。但是区区的 32KB ROM 十分考验我们对 HTML 代码的优化能力。

其实,这节课的意义在于理解 Arduino 是如何相应来自网络的请求,作为系统中的节点,这点相当重要。

HTTP 可能是世界上使用最广的通信协议了。访问一个网站,服务器和浏览器之间就是通过 HTTP 协议来互通信息。HTTP 协议的应用层是由 HTML 语言构成的,由于 HTML 属于文本类型的解析性语言,所以即使是主频只有 16MHz 的 Arduino 单片机,也可以轻松驾驭它。

利用 HTTP 通信,不仅通过一般的 PC 浏览器去访问、控制 Arduino,而且可以让它利用局域网,构成一个更大的节点网络。相比 I2C、SPI 的通信连接方式,HTTP 理论上,只要找到路由,是可以无线拓展的。当然,HTTP 更适合人机、节点之间、或者中央与节点间的长距离通信,毕竟在硬件通信层面,串口、I2C、SPI 等协议的延时要低得多。 (HTTP 的总体延时在毫秒级别,而 I2C 在高速模式下 4MHz,延时在微秒级)

Web 服务器完整代码如下:

设置MAC、IP地址等都属于基础部分,核心内容在 loop() 中:

首先,我们将 packetLoop() 的返回值保存在一个变量里 pos。

缓存中,pos 是浏览器请求开始的位置,如果 pos 有效(大于0),则把请求打印出来(方便学习理解,但不是必须的)。

难点在这里,BufferFiller 是一个构造函数(Constructor,参考官方文档),它的对象 bfill,用于存储服务器对浏览器的响应内容。tcpOffset() 指针,指向响应内容的位置。

用 emit_p() 方法将响应内容保存在缓冲区,并用PSTR() 将网页内容(字符串存)储在 Flash 空间中,以节省 SRAM。

position() 方法以获得 bfill 包含的内容长度,然后,httpServerReply() 获取缓冲区的内容,响应浏览器的请求。

为何一次访问会产生两次请求?因为浏览器除了网页内容外,还会请求 favicon.ico 网站的角标:

 

第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” 的设备。