arduino iot
最近,我想为制造商创建基于Arduino的低功耗物联网(IoT)设备,其内置传感器可用于将传感器数据从任何位置传输到云,并可能控制恒温器等已连接的设备,灯,门锁和其他家庭自动化产品。 在整个过程中,我了解到创建一个新的IoT设备(从构思到原型再到最终产品)并不像我想象的那么简单,并且没有“准备就绪”的开发设备。 但是,通过弄清楚如何做到这一点,我创建了一种名为Siguino的新产品,这是一种开放源代码的 IoT电路板,我希望它将使其他人可以更轻松,更快地创建自己的IoT产品。
Siguino基于Arduino Pro Mini的低功耗版本,该版本具有板载传感器和天线,并用一块电池供电。 它还利用Sigfox (一种旨在将IoT设备连接到云的低功耗广域网)。
本文介绍了从非常杂乱的电路板(但可以正常工作)的原型到最终的,定制设计的印刷电路板(PCB)的过渡过程,其他人将有望使用该电路板。
原型1与最终原型相比
在所有优秀制造商项目开始之初 ,我首先准备了一个概念性电路。 这涉及确定您希望设备具有的功能以及将使用的组件。 我希望我的设备能够:
结果是一组相当混乱(但实用!)的组件:
一切正常后,我花了一些时间使用面包板跳线整理出更整齐的版本:
下一步是编写基本代码,使面包板设备执行我想要的操作。 其中一些是标准的,并包含在每个组件的现有示例代码中。 例如,使用DS18B20 测量温度的代码如下所示:
#include
#include
// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature temp_sensor(&oneWire);
void setup(){
Serial.begin(9600);
temp_sensor.begin();
Serial.println("DS18B20 Temperature Test\n\n");
delay(300);//Let system settle
}//end "setup()"
void loop(){
Serial.print("Requesting temperatures...");
temp_sensor.requestTemperatures(); // Send the command to get temperatures
Serial.print("Temperature is: ");
float temp_reading = temp_sensor.getTempCByIndex(0);
Serial.println(temp_reading);
delay(1000);
}// end loop()
有许多第三方库提供了用于Arduino Pro Mini的低功耗使用的选项。 我选择了可在GitHub上找到的Rocket Scream库。 家庭自动化社区和Andreas Rohner提供了有关修改Arduino Pro Mini以降低功耗的良好信息。 该项目的样本用法为:
// **** INCLUDES *****
#include "LowPower.h"
void setup()
{
// No setup is required for this library
}
void loop()
{
// Enter power down state for 8 s with ADC and BOD module disabled
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
// Do something here
// Example: Read sensor, data logging, data transmission.
}
可以使用标准AT命令与WiSOL Sigfox芯片进行通信(产品数据表中包含基本示例)。 对于这个项目,我只需要两个功能:
String send_at_command(String command, int wait_time){
altSerial.println(command);
delay(wait_time);
return recv_from_sigfox();
}
void test_sigfox_chip(){
Serial.println("Sigfox Comms Test\n\n");
altSerial.begin(9600);
delay(300);//Let system settle
Serial.println("Check awake with AT Command...");
chip_response = send_at_command("AT", 50);
Serial.println("Got reponse from sigfox module: " + chip_response);
Serial.println("Sending comms test...");
chip_response = send_at_command("AT", 50);
Serial.println("Comms test reponse from sigfox module: " + chip_response);
chip_response = send_at_command("AT$I=10", 50);
Serial.println("Dev ID reponse from sigfox module: " + chip_response);
chip_response = send_at_command("AT$I=11", 50);
Serial.println("PAC Code reponse from sigfox module: " + chip_response);
}
//message send
chip_response = send_at_command("AT$SF=" + hex_bits, 10000);
Serial.println("Reponse from sigfox module: " + chip_response);
//Sigfox sleep mode enabled via AT$P=1 command
// to wake need to set UART port low (see AX-SIGFOX-MODS-D.PDF for further details)
void set_sigfox_sleep(bool go_sleep){
String chip_response;
if (go_sleep){
//send go sleep AT command
chip_response = send_at_command("AT$P=1", 100);
Serial.println("Set sleep response: " + chip_response);
}else{
//wake up sigfox chip
altSerial.end();
pinMode(TX_PIN, OUTPUT);
digitalWrite(TX_PIN, LOW);
delay(100);
altSerial.begin(9600);
}
}
我决定对Sigfox消息使用位打包 ; 由于Sigfox消息的最大长度为12个字节,因此最好将尽可能多的数据压缩到每个消息中。 例如,假设温度传感器返回的温度将在-40到+80摄氏度之间浮动。 C ++中的浮点数使用4个字节的内存,但是如果不需要,您不想用12个字节的消息中的4个字节来发送数字。 通常,您只需要知道温度值的一半精度,就可以将整个温度范围压缩为8位(1字节),因为将-40到+80的范围限制为一半度增量,只有240个可能的值,如下所示:
0b00000000 [0] = -40
0b00000001 [1] = -39.5
0b00000010 [2] = -39
…
0b11101111 [239] = 79.5
0b11110000 [240] = 80
为了节省更多空间,我将范围限制在-10至+50 C(半度精度),这需要7位的温度,加上5位的光水平(从0到1,000),1位用于打开/关闭或设备移动,以及4位消息序列号,这样我就可以发现任何丢失的消息。 因此,我的基本传感器只需要使用我的12个字节的可用消息空间中的18位,如下所示:
我修改了一组位打包功能 ,该功能将获取所有传感器数据以及我想为每个传感器使用的位数,然后将它们打包为一个12字节的值:
#ifndef BITPACKER_H_INCLUDED
#define BITPACKER_H_INCLUDED
#include
#define BIT(n) ( 1UL<<(n) ) //UL = unsigned long, forces chip to use 32bit int not 16
#define BIT_SET(y, mask) ( y |= (mask) )
#define BIT_CLEAR(y, mask) ( y &= ~(mask) )
#define BIT_FLIP(y, mask) ( y ^= (mask) )
/*
Set bits Clear bits Flip bits
y 0x0011 0x0011 0x0011
mask 0x0101 | 0x0101 &~ 0x0101 ^
--------- ---------- ---------
result 0x0111 0x0010 0x0110
*/
//! Create a bitmask of length \a len.
#define BIT_MASK(len) ( BIT(len)-1 )
//! Create a bitfield mask of length \a starting at bit \a start.
#define BF_MASK(start, len) ( BIT_MASK(len)<<(start) )
//! Prepare a bitmask for insertion or combining.
#define BF_PREP(x, start, len) ( ((x)&BIT_MASK(len)) << (start) )
//! Extract a bitfield of length \a len starting at bit \a start from \a y.
#define BF_GET(y, start, len) ( ((y)>>(start)) & BIT_MASK(len) )
//! Insert a new bitfield value \a x into \a y.
#define BF_SET(y, x, start, len) \
( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) )
namespace BitPacker {
static uint32_t get_packed_message_32(unsigned int values[], unsigned int bits_used[], int num_vals){
uint32_t retval = 0x0;
int j = 0;
for (int i=0;i
BF_SET(retval, values[i], j, j + bits_used[i]);
j += bits_used[i];
}
return retval;
}
static uint64_t get_packed_message_64(unsigned int values[], unsigned int bits_used[], int num_vals){
uint64_t retval = 0x0;
int j = 0;
for (int i=0;i
BF_SET(retval, values[i], j, j + bits_used[i]);
j += bits_used[i];
}
return retval;
}
}
#endif // BITPACKER_H_INCLUDED
在为您的设备定制设计PCB电路之前,值得确定一个更小,更整洁的原型电路。 我选择了该电路的配电盘版本。 最终结果应该是电路更整洁,更紧凑,这对于帮助调整最终的PCB设计非常有用。 (这很重要,因为根据经验,PCB越大,其成本就越高。)它还可以很好地了解最终产品可能需要哪种外壳。
我还使用了Fritzing ,这是一款用于布置Stripboard或Veroboard电路的软件。 它允许您设计一个虚拟电路,您可以在剥离板上复制它。 我的原型电路在Fritzing中看起来像这样:
导致此实际(工作)电路的原因:
为了设计PCB,我使用了Autodesk Eagle ,这是一款出色的软件,可以免费用于小板(<80cm),并且具有许多组件库(包括良好的第三方库,例如,所有SparkFun和AdaFruit组件)。
我从这些SparkFun教程中学到了有关Eagle的所有知识:
根据经验,我会建议一些技巧:
换句话说,不要这样做:
而是这样做:
我最终的,完全布线的电路板布局如下所示:
在项目开始之初,我的一个大未知数是如何构建包括表面安装组件(SMC)的原型。 使用电镀通Kong(PTH)组件进行原型制作(例如,面包板)要容易得多,但是由于SMC体积更小且更整洁,因此您不会为最终产品选择PTH组件。
当您使用理想的SMC组件设计PCB布局,进行印刷并想要将它们放在一起进行测试时会发生什么,但是您没有任何表面贴装机械(例如拾取和放置机器或回流焊炉)? 您可以构建自己的回流焊炉 ,但是如果您要构建自己的电路,我认为偏离重点的工作有点耗时。 而且,几乎没有必要,因为您可以通过足够的实践手工焊接几乎所有的SMC,并且可以使用价格相对便宜的焊锡气枪来简化工作。
我使用EEVBlog YouTube频道学习了SMC焊接的基础知识,最后我将所有部件手工焊接到0402组件(如果呼吸太小,您将失去它们!)。 有关上下文,请参见以下组件尺寸比较表:
我不建议在电路中使用0402组件。 (我别无选择,因为它们是天线下面的射频网的一部分,并且较大的组件可能会影响天线的性能。)实际上,0602组件也非常小,很难焊接,但是有点练习这是非常可行的。 我建议第一批订购额外的PCB,纯粹是为了进行焊接,因为您很可能会把第一次尝试弄得一团糟。
所需的工具包括:
功耗测量是过程中非常困难但非常重要的一部分。 我希望我的设备具有超低功耗,因此可以使用小电池(例如900mAh CR2)使用一年。 这意味着要确保静态电流(恒定电流消耗)尽可能小,降低到低µA范围,同时考虑到消息发送过程中偶尔会有较高的电流消耗。 尽管有许多方法可以评估电路的电流需求,但大多数方法对于极低端的分辨率都很差。 手动机制(例如跨电源线连接的电流表)使用起来很麻烦,而且只能提供在特定时间使用多少电流的快照(在某些情况下,React速度不足以进行任何可靠的测量)。
在我尝试过的各种选择中,最后唯一起作用的是Nordic Semiconductor的Power Profiler Kit (PPK)。 它不是太贵(它和底板的价格都在100美元左右),而且效果很好。 (我的一个抱怨是,即使它是一个Python程序,也无法使它在Linux上可靠地工作,因此我必须启动Windows才能使用它。)
PPK不仅可以产生低至极低分辨率(<1µA)的恒定功耗视图,而且还可以产生一个时间窗口的运行平均值(正是我计算电池寿命所需的时间):
您可能已经焊接到PCB上的原始ATmega芯片可能没有使用正确的保险丝设置(请参见下文)或编程的引导程序进行硬编码,因此您可能需要对其进行配置以使电路板正确运行。 对于首次使用PCB的设计师/建造者来说,这令人惊讶地令人困惑!
设置从芯片供应商处收到的原始ATmega芯片时,要解决三个主要任务。 (注意:有关详细信息,请参阅ATmega328P,但其中许多内容也适用于ATmega系列的其他部件):
保险丝是非易失性位,定义了芯片行为方式的许多可编程方面。 一共有三个熔丝字节,每个熔丝字节有8位:低字节,高字节和扩展字节。 例如,它们控制什么类型的时钟驱动芯片或欠压检测器(BOD)触发的电压。 BOD会在设定电压下停止执行代码,以免功率太低时操作不可靠。
默认值是在工厂提供的芯片中设置的。 这些可能适合芯片的预期用途。 但是,如果没有,则需要进行更改。 这可以通过SPI总线使用合适的接口(例如Ardiuno Uno板)来完成。 这里和这里都有一些有关此过程的良好指南。
需要将运行项目应用程序所需的代码加载到芯片中。 通常,使用FTDI头设备通过USB将芯片连接到编程PC。 在这种情况下,芯片需要安装引导加载程序以促进此操作。 实际上,这是一个加载程序的程序,但是它是使用合适的接口通过SPI总线加载的。
对于此项目,我使用了一个单独的Arduino UNO来按如下方式引导ATmega芯片:
宇野 | 目标 |
---|---|
D10 | 重启 |
D11 | 摩西 |
D12 | 味噌 |
D13 | SCK |
Gnd | nd |
+5V | Vcc |
一旦芯片上安装了引导加载程序,就可以通过FTDI接口加载程序代码。 在开发人员PC上运行的Arduino IDE可以通过此接口将应用程序代码直接加载到芯片上。
要从面包板转移到批量生产,您将需要各种资源:
因此,您已经构建了设备,并且该设备在Sigfox网络上(主要是向Sigfox服务器)发送了消息……现在!!! 您将如何处理这些消息以及如何处理它们?
要做的第一件事是让Sigfox服务器将设备收到的所有消息转发到您控制的Web服务器或服务。 Sigfox系统上有很多选择方法,但是我认为最简单的方法是构建自己的RESTful Web服务(如下所述),并让Sigfox服务器通过以下方式向您的新服务发出HTTP(S)请求:消息数据。 这可以在Sigfox后端中通过使用设备的回调机制来完成,在该机制中,您可以根据需要从包括原始消息数据在内的可用变量列表中指定发布的变量或URL参数:
RESTful Web服务是现代的API,在Web上无处不在。 创建它们的方法有很多,但是我决定使用Go编程语言,首先,因为它是我想学习的语言,其次,因为它很容易通过Docker进行部署。 Go中的Web服务(保存到MongoDB数据库)的基本结构如下所示:
// Handler for HTTP Post - "/sensordata"
// Register new sensor data
func NewSensorData(w http.ResponseWriter, r *http.Request) {
var dataResource SensorDataResource
// Decode the incoming Task json
err := json.NewDecoder(r.Body).Decode(&dataResource)
if err != nil {
common.DisplayAppError(
w,
err,
"Invalid Sensor Data format",
500,
)
return
}
sensorData := &dataResource.Data
context := NewContext()
defer context.Close()
c := context.DbCollection("SensorData")
repo := &db.SensorDataRepository{c}
// Insert a sensor data document
repo.Create(sensorData)
if j, err := json.Marshal(SensorDataResource{Data: *sensorData}); err != nil {
common.DisplayAppError(
w,
err,
"An unexpected error has occurred",
500,
)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
w.Write(j)
}
}
您可能构建的用于对Sigfox服务器中的原始数据进行基本处理的大多数简单Web服务都具有类似的结构。
我发现对于Sigfox消息解析特别有用的东西是位解包(因为我之前在Arduino代码中使用位打包将尽可能多的数据压缩到Sigfox消息中)。 用于解压缩数据的相应Go代码如下所示:
func bit(n uint64) uint64 {
return 1<
}
func bit_set(y uint64, mask uint64) uint64 {
return y | mask
}
func bit_clear(y uint64, mask uint64) uint64 {
return y & ^mask
}
func bit_flip(y uint64, mask uint64) uint64 {
return y ^ mask
}
func bit_mask(len uint64) uint64 {
return bit(len) - 1
}
func Bf_mask(start uint64, len uint64) uint64 {
return bit_mask(len) << start
}
func Bf_prep(x uint64, start uint64, len uint64) uint64 {
return (x & bit_mask(len)) << start
}
func Bf_get(y uint64, start uint64, len uint64) uint64 {
return (y>>start) & bit_mask(len)
}
func Bf_set(y uint64, x uint64, start uint64, len uint64) uint64 {
return (y & ^Bf_mask(start, len)) | Bf_prep(x, start, len)
}
最后,就使您的设备完成除数据记录之外的其他工作而言,将其与其他设备或生态系统集成的最简单方法可能是通过If This Then That(IFTTT) ,这是许多不同的API和系统的结合。 将设备连接到IFTTT后 ,就可以访问现有的后续操作。 例如,“如果[您的设备发送X],然后[向Y发送电子邮件],或[使Alexa说Y],或[在Y房间打开Philips Hue灯],”或其他多种选择。
我在Siguino项目中的下一步工作是为其开发3D外壳,通过Sigfox设备认证计划,调整天线以充分利用它,以及资助和组织该设备的首次生产。
由于该项目的主要目的是学习技术,因此我已在GitHub上将所有软件代码和硬件开源。 如果您有任何疑问或发现此有价值的信息,请在评论中让我知道。
翻译自: https://opensource.com/article/17/12/how-build-custom-iot-hardware-arduino
arduino iot