65.9K
CodeProject 正在变化。 阅读更多。
Home

构建物联网设备:从创客原型到定制 PCB 制造

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2017 年 12 月 6 日

GPL3

17分钟阅读

viewsIcon

23481

从零开始为物联网(IoT)“互联”设备构建定制设计的 PCB

引言

这个项目是关于从零开始构建一个物联网(IoT)、“互联”设备。我基本上构建了一个定制设计的 SigFox enabled Arduino(基于低功耗版本的 Arduino ProMini)。我将带您从概念到“面包板”测试、Arduino 编程、Fritzing 图、万用板、PCB 设计、PCB 打印、焊接和表面贴装元件的考虑,直至完成产品。我构建的所有软件和硬件都是开源的,并且我提供了指向 GitHub 源代码文件的链接。

最终的定制 PCB 原型

Final Prototype Custom PCB Siguino

背景

这个项目的出现主要源于我希望接触并了解更多低级硬件和物联网设备。多年来我一直专注于软件开发,最近更多地担任管理角色,所以当有机会做一些新的事情时,我想尝试走出我的舒适区(自从很多年前完成大学学位以来,我就没有直接的硬件经验了!)。随着物联网在近些年变得越来越普及,并且越来越有趣,我觉得是时候开始构建一些这方面的东西了。我所在的爱尔兰部署 SigFox 网络,使得构建某种 SigFox 传感设备成为一个显而易见的选择,但我越深入研究,就越发觉得并没有一个“即用型”的开发设备供我开始使用。于是,SigFox 版 Arduino(或称“Siguino”)的概念应运而生。

1. 面包板测试

正如所有优秀的创客项目一样,我从 面包板概念电路开始。这涉及到确定你想要设备具备的功能,并从中选择要使用的组件。对我来说,我希望我的设备能够

  • 基于 Arduino 且低功耗(所以我选用了 Pro Mini

  • 能够通过 SigFox 网络发送消息:为此,你需要一个 SigFox 芯片,有很多选项。我选择了 Wisol 的 SFMR10,原因有二

    • 它是一个仅发送芯片,而不是收发器,我没有双向通信的需求

    • 有现成的开发板(对面包板和原型制作非常有用,这款来自 Yadom 的 开发板效果很好)

  • 配备四个基本传感器

    • 温度(最初,我使用了 DS18B20,但它在低于 3V 的电压下效果不佳,所以我换成了 AT30TS750A

    • 光照强度(标准光敏电阻)

    • 磁感应“霍尔效应”用于门窗开关检测:AH9246-W-7

    • 运动检测:我尝试过行程开关、水银开关等,但考虑到我主要为创客设计,加速度计最终是更好的选择,因为它能提供更多原生可能性。我选择了 LIS3DH注意:AdaFruit 的该组件的 breakout 板*不*是低功耗的,尽管芯片本身是。详细信息 在此

最终的结果是一堆相当杂乱(但功能齐全!)的组件

Siguino Prototype 1

然而,我建议花点额外的时间,使用特殊的面包板“跳线”(见下文)来组装一个更整洁的版本。

Siguino Prototype 2

2. Arduino 代码

接下来是编写基本代码,让你的面包板设备按照你的意愿工作。其中一些非常标准,包含在许多现有组件的示例代码中,例如,从 DS18B20 获取温度,这里有很好的介绍 在此,看起来是这样的

#include <DallasTemperature.h>
#include <OneWire.h>

// 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 的低功耗使用,有多种第三方库可供选择。我选择了 RocketScream 在 GitHub 上的开源低功耗库, RocketScream这里。关于使用此库,有很好的文章 在此在此,本项目的示例用法是

// **** 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.

}

SigFox 消息

我为本项目选择的 Wisol 芯片可以使用标准的 AT 命令进行通信(产品数据手册中包含基本示例)。对于本项目,我只需要两个功能

发送消息

我为低级 AT 命令编写了一个包装器,允许更方便地发送命令,例如测试设备和发送消息。

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 response from sigfox module: " + chip_response);
  
  Serial.println("Sending comms test...");
  chip_response = send_at_command("AT", 50);  
  Serial.println("Comms test response from sigfox module: " + chip_response);

  chip_response = send_at_command("AT$I=10", 50);  
  Serial.println("Dev ID response from sigfox module: " + chip_response);

  chip_response = send_at_command("AT$I=11", 50);  
  Serial.println("PAC Code response 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);

进入低功耗(睡眠)模式

为此,我选择了基本的睡眠模式,尽管该芯片也支持“深度睡眠”选项。从约 1.5uA 降至低于 1uA 似乎不值得,因为 1.5uA 的静态电流消耗对于我们的目的来说已经完全可以接受了。睡眠/唤醒循环代码看起来是这样的

// 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 摄氏度的浮点数,例如 22.46 或 -4.67 等。C++ 中的浮点数占用 4 字节内存,但如果你不需要,你不想在 12 字节的消息中占用 4 字节来发送这样的数字。在大多数情况下,你只需要知道温度值到半度的精度,所以如果你的可能温度范围是 -40 到 +80,并且你只需要半度的精度,那么你只有 240 个可能的值需要发送,所以你把它们都压缩到 8 位(1 字节)中,本质上就是
0b00000000 [0] = -40
0b00000001 [1] = -39.5
0b00000010 [2] = -39

0b11101111 [239] = 79.5
0b11110000 [240] = 80

事实上,我选择了 7 位用于温度(-10 到 +50,半度精度),5 位用于光照强度(从 0 到 1000),1 位用于开/关或设备移动检测,以及 4 位用于消息序列号,这样我就可以发现任何丢失的消息。所以对于我的基本传感器,我只需要使用 18 位中的 12 字节可用消息空间,打包方式如下

我改编了一组位打包函数(原始代码 在此),这些函数接受所有传感器数据以及我想要的每种数据使用的位数,并将它们打包成一个 12 字节的值。

#ifndef BITPACKER_H_INCLUDED
#define BITPACKER_H_INCLUDED

#include <stdint.h>

#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<num_vals;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<num_vals;i++){
            BF_SET(retval, values[i], j, j + bits_used[i]);
            j += bits_used[i];
        }
        return retval;
    }

}
#endif // BITPACKER_H_INCLUDED

3. Fritzing 和万用板

在开始为你的设备定制设计 PCB 电路之前,尝试缩小并制作一个更整洁的原型电路是非常值得的。我选择了该电路的 Stripboard 版本,尽管万用板也同样可用。最终结果应该是电路的一个更整洁、更紧凑的版本,这对于帮助缩减最终 PCB 设计非常有帮助(记住一个经验法则,PCB 越大,成本越高)。它还能让你对产品可能需要的某种外壳有一个很好的了解。

Fritzing 是一个很棒的软件,用于布局 Stripboard 或万用板电路,允许你完全制作一个虚拟电路,然后你可以直接在你的 stripboard 上复制。有一些很好的 Fritzing 教程,例如,参见 这里。我的原型电路在 Fritzing 中看起来是这样的

这导致了实际的(工作)电路

Siguino Prototype 3

4. PCB 设计和打印

现在你开始 PCB 设计。我使用了 Autodesk Eagle,这是一个很棒的软件,对于小型电路板(<80cm)是免费使用的。它有大量的组件库,包括很好的第三方库(例如,所有 SparkFun 和 AdaFruit 组件)。

关于如何使用这个产品的教程本身就可以成为一篇 CodeProject 文章,但实际上,在这方面已经有一些绝佳的资源。我强烈推荐 SparkFun 的教程,我从这三个教程中学到了我需要知道的一切

  1. 安装和设置
  2. 创建原理图
  3. 电路板布局和布线

完成所有这三项工作需要一些时间,但它们是值得的。根据我的经验,我建议一些技巧

  • 经常保存!

  • 每次更改后,无论多小,都要重新检查 DRC 规则。铺铜后也要重新检查,即使更改*不应该*影响铺铜。

  • 在使用非常小的组件(例如,FPGA 表面贴装组件)进行布线时,尽量不要在组件下方有孔。虽然这是允许的并且应该工作良好,但在没有专业工具(例如,回流焊炉、贴片机等)的情况下手工焊接/表面贴装组件进行原型测试时,会成为一个问题。在手工施加焊锡/焊膏时,很难确保它不会落在组件下方并流入下方的布线孔(你看不见的地方),并且在布线时很容易忘记这些组件有多小。

所以不要这样做

而是这样做

  • 与上面类似,但对于较大的组件,出于同样的原因,尽量不要让布线孔离组件引脚/焊盘太近。

最终完全布线好的电路板布局看起来是这样的

5. 焊接和表面贴装元件 (SMC)

这是我在此项目开始时的一个大未知数:如何构建包含表面贴装元件的原型?在原型制作(例如,面包板测试)中使用插件式元件(PTH)要容易得多,但你不会为最终产品选择 PTH 元件,因为 SMC 更小、更整洁,那么当你设计好 PCB 布局并使用你理想的 SMC 元件进行打印、组装和测试时,但你又没有贴片机或回流焊炉等表面贴装设备,该怎么办?有关于如何自己构建回流焊炉的教程和 YouTube 视频 [例如,在此],但如果你自己制造电路,我个人认为这种偏离重点的事情有点耗时。而且事实证明,只要有适当的练习,几乎所有表面贴装元件都可以手工焊接。此外,你还可以使用相对便宜的热风枪使工作更容易。

我使用了优秀的 YouTube 频道 EEVBlog 来自学如何进行 SMC 焊接 的基础知识,最终,我能够手工焊接所有元件,包括 0402 尺寸的元件(它们太小了,你一不小心就会弄丢!)。请看元件尺寸比较图

SMD Component relative sizes

我不建议在你的电路中使用 0402 尺寸的元件(我的电路中别无选择,因为它们作为天线下的 RF 网络的一部分使用,较大的元件可能会影响天线性能)。实际上,0602 尺寸的元件也相当小且难以焊接,但经过一些练习,这些都可以完成。我建议在订购 PCB 时,在第一批中多订购几块板子用于焊接练习,因为你很可能会搞砸第一次尝试!

最后,关于所需的工具

  • 焊锡烙铁:我强烈建议购买一个好的焊锡烙铁。多花点钱购买一个高质量的烙铁绝对值得,因为便宜的烙铁根本不够好(根据我的经验——我在亚马逊上买了一个便宜的,几周后就把它扔了,换了一个更好的,一切都变得容易多了)。我选择了 这款 来自 circuit specialists 的烙铁,它效果非常好。

  • 热风枪:我还买了一个 热风枪,虽然使用起来比我希望的要棘手一些(调整气压以避免吹走小元件是一门艺术!),但它确实使焊接 LIS3DH 等较小的 VFLGA 封装 IC 变得容易得多(我甚至不确定仅用焊锡烙铁如何做到这一点,尽管我读过它是可能的)。它还使得在出错时移除元件变得非常容易。

  • 镊子:一套优质、尖细的镊子是必不可少的(我选择了一套,类似 这个)。你将需要拾取非常小的元件,所以这些是必备工具。

  • 眼放大镜/放大镜:你需要放大你的焊接工作来检查不良焊点、焊桥、焊球、漏焊等。我发现珠宝放大镜,最好带有内置灯,非常有帮助。

6. 功耗测量

这是过程中一个出乎意料地困难但非常重要的部分。我希望这个设备是超低功耗的,以便它能用一个小电池(最终选择了 900mAh CR2 电池)工作近 1 年。这意味着要确保静态电流(恒定电流消耗)尽可能小,降至低 uA 范围,同时还要考虑偶尔发送消息时的高电流消耗。虽然有许多电路和方法可以评估电路的电流需求,但大多数在非常低的范围内分辨率不高,并且手动机制,如连接到电源线的电流表,使用起来很麻烦,也只能提供给定时间的电流使用快照(并且在某些情况下反应不够快,无法进行可靠测量)。

在我尝试解决这个问题的各种方案中,最终有效的方法是 Nordic Semiconductor 的 Power Profiling Kit。它并不太贵(PPK 和基板一起约 100 美元左右),而且效果非常好(尽管我唯一抱怨的是它只支持 Windows——我试图在 Linux 上运行它,因为它是一个 Python 程序,但遗憾的是我只能在 Windows 上可靠地运行它。这通常不是大问题,只是我所有的其他软件都在 Linux 上运行,所以需要重新启动到 Windows 才能运行此软件……我没有尝试在 Wine 上运行)。

它能以非常高的分辨率(<1uA)提供持续的功耗视图,并能提供一段时间窗口内的运行平均值(这正是我们计算电池寿命所需的)。

7. Atmega Bootloader 编程

一旦你收到了裸的 Atmega 芯片(即,不是已经装在 Arduino 上的芯片,而是芯片本身),它将没有编程的 bootloader,所以你无法通过 Arduino IDE 与它通信和加载程序,直到你对其进行 bootloader 编程。最好使用单独的 Arduino Uno 来完成此操作(也可以使用其他板子,但这些说明假定使用标准的 Uno)。

  1. Bootloader:使用 Nick Gammon 的 Atmega 芯片编程器
  2. 在此处找到:https://github.com/nickgammon/arduino_sketches
  3. 下载 ZIP 文件
  4. 解压 `Atmega_Board_Programmer` 文件夹(例如,解压到 Arduino IDE 库目录)
  5. 打开 `Atmeaga_Board_Programmer` sketch
  6. 将标准 Arduino Uno 连接到 PC
  7. 将板设置为“Arduino/Genuino Uno”,并选择正确的端口。
  8. 上传 sketch
  9. 断开 Uno,并按如下方式连接目标芯片
    • Uno 目标
    • D10 -> Reset
    • D11 -> MOSI
    • D12 -> MISO
    • D13 -> SCK
    • Gnd -> Gnd
    • +5V -> Vcc
  10. 给 Uno 上电 / 选择端口 / 以 115200 波特率运行串口监视器。
  11. Bootloader 应该会立即运行,并在串口监视器窗口显示结果。按照串口窗口中的说明操作(例如,按“L”加载 Bootloader)。
  12. 注意:Bootloader 会将芯片设置为使用内部 8MHz 时钟(如果你有外部晶振,可以修改此设置,请参阅 sketch 中的注释)。

8. PCB 打印、组件购买、制造和组装

在各个阶段,从面包板测试到批量制造,你需要利用一些资源。

硬件组件

为了进行电路的面包板测试,你需要各种电阻、电容、传感器、集成电路等组件。你可以在亚马逊等主流网站上找到其中一些,但我建议选择一些硬件专用网站作为更好的选择。我主要使用了 DigiKey,发现他们非常好,但 MouserFarnell 也很不错。

PCB 打印

一旦你设计好 PCB 并生成了 Gerber 文件,你就需要有人为你打印。我使用了 Multi-CB,发现他们做得非常好,而且速度很快,价格也很具竞争力。唯一的缺点是他们没有在线支付处理选项,因为他们只与企业打交道,需要银行转账。尽管如此,服务很好,生产的电路板质量也很高,而且还有很多选项。根据你所在的地区,可能还有其他 PCB 制造商可供选择,此页面下的“选择 PCB 制造商”部分可能值得一看。

PCB 制造

好的,你的 PCB 完全设计好了,你已经购买了所有组件并手工焊接并测试了最后一个原型,现在你想批量制造它,该怎么办?我找到了 PCB Cart 并获得了非常合理的报价,其中包括组装和使用我的默认程序对 Atmega 芯片进行编程,但目前为止,我还没有进行过板子的制造,所以除了报价阶段之外,我无法评论他们的质量/交付情况。

9. 后端开发

你已经构建了设备,并且它能在 SigFox 网络上发送消息(本质上是发送到 SigFox 服务器)……然后呢!?你将如何处理这些消息,你将如何处理它们?

SigFox 回调

首先要做的是让 SigFox 服务器将收到的任何消息转发到你控制的某个 Web 服务器/Web 服务。SigFox 系统上有很多方法可以做到这一点,但我认为最简单的方法是构建自己的 RESTful Web 服务(见下一节),并让 SigFox 服务器向你的新服务发出 HTTP(S) 请求,其中包含消息数据。这可以在 SigFox 后端中通过为你的设备使用回调机制来实现,你可以在其中根据可用变量列表指定发布的变量或 URL 参数,包括原始消息数据。

RESTFUL Web 服务

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 消息解析特别有用的一点是位解包(因为 SigFox 消息最多只有 12 字节,你真的需要尽可能地将数据压缩到消息中,因此你很可能会像我在本项目中那样进行位打包)。与之前的 Arduino 代码一起使用的位打包数据的相应 Go 代码如下所示。

func bit(n uint64) uint64 {
    return 1<<n
} 

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)
}

IFTTT 集成

最后,就让你的设备完成数据记录之外的其他功能而言,可能最简单的集成方法是利用现有的集成基础设施,并使用 If This Then That (IFTTT),这是一个集成了许多不同 API 和系统的平台。一旦你将设备连接到这个系统,所有现有的后续操作都将可用。例如,“如果 [你的设备发送 x],那么 [发送电子邮件到 y] 或 [让 Alexa 说 Y] 或 [在 y 房间打开飞利浦灯]”或者无数其他选项。有一些很好的文章介绍了如何最好地连接到 IFTTT 系统,在此

下一步 & 继续前进

目前,我正在研究为该设备创建 3D 外壳,以及通过 SigFox 设备认证计划,调整天线以充分发挥其性能,并尝试组织我的设备的首批生产(如果有人对设备本身而非其制造过程感兴趣,可以通过 Kickstarter 了解)。但我开始这个项目的初衷始终是学习硬件和物联网技术,因此所有 Arduino 代码和硬件都是开源的,并且在 Github 上。我希望在这里详细介绍我所经历的过程,能对其他有兴趣进入这个领域的人有所帮助。谢谢阅读!

历史

  • 2017 年 9 月 14 日:创建初始版本
© . All rights reserved.