Arduino车库开启器和通用无线电接口           这篇文章来源于DevicePlus.com英语网站的翻译稿。
目录
1.简介
1.1创客实用电台
1.1.1频率
1.1.2天线
1.1.3电源
1.1.4 空间
1.2您将会学到什么
2. Arduino车库开启器和通用无线电接口
2.1 Arduino车库开启器,基站
2.1.1 BOM
2.1.2启动!
2.2 Arduino车库开启器,手持单元
2.2.1 BOM
2.2.2启动!
1.简介

无线电波无处不在。来自大爆炸的深空回波在微波频谱中最为明显,而来自家庭内部电线和附近地铁系统的局域波在50-60Hz频段内最为显著。当您触摸3.5mm TRS插孔并听到令人讨厌的嗡嗡声时,您自身就已经成为了接收偶发模拟无线电波的巨型可听电容器。
在本文中,我们将介绍一个非常适合创客的实用且抗干扰的无线电系统。
它适用于模拟和数字的传输与接收,且具有CRC校验和。该系统在ISM/SRD频段(美国常用433和902-928MHz)上的位置非常灵活,并且允许对其进行编程,所以您可以选择适合自己的以及符合国家要求的频率。有关合法性的问题,请参考 低功率、未经许可的发射机FCC规则 文件中的第15.231部分,间歇控制信号。
就范围而言,它相比于WiFi有很大的进步,但还远不及LoRa。在两个站点上都有定向天线的视距(LOS)场景中,范围大约为400米。
在CC1101数据表中每当提到kBaud(千波特)时,可将其认为0.1250 kByte(千字节)。实际上,只需要把波特率视为位即可(每字节8位)。它们之间存在差异,尤其是曼彻斯特编码和4-FSK编码,但这与我们所要讲的Arduino车库开启器和通用无线电接口没什么关系。

1.1创客实用电台
无线电应用中最重要的因素是频率(以赫兹为单位,Hz)、功率(以瓦特为单位,W)天线和空间。
现在我们来简单介绍一下。
1.1.1 Frequency频率
频率,赫兹(千赫、兆赫、千兆赫)描述了特定事物的周期性间隔或每秒的周期数。如果摆钟在一秒钟内完成了一个完整的摆动,它的频率为1 Hz。
如果需要两秒钟,那么频率为0.5Hz。而如果每秒摆动两次,频率则为2Hz。我们这里使用的是电磁辐射,该理论也适用于电磁波。
光速“c”是299.792.458m/s,为了简单起见,光速被近似为300.000km/s。在知道光速和频率的情况下,我们直接用光速除以频率“f”(以兆赫为单位),就可以推导出波长。如下所示:
c / f = l
  • 300 / 433 = 0.692
  • 复制代码
    将结果“I”乘以100,得到以厘米为单位的波长,乘以(100/2.54)就可以得到以英寸为单位的波长。
    如果知道光速和波长,我们将“c”除以“I”,就可以得到“f”的值。因此:
    c / l = f
  • 300 / 0.6928 = 433.02
  • 复制代码
    1.1.2天线
    这就是我们所需要的所有数学知识了。这很容易记住,接下来您只需要使用各种频率和DIY天线就可以了。
    我们的CC1101模块将设置为在433MHz运行,并带有板载SMA连接器。请注意,CC1101可以在300-348 MHz、387-464 MHz和779-928 MHz范围内工作。奇怪的是,附带的天线只有4cm/1.57 in长。虽然这可能是绝缘层包裹的线圈天线,但是我还是称这些天线为十六分之一波长天线,并且对它们没有太多期望。
    幸运的是,使用上面的公式,我们可以构造出四分之一波长鞭形天线。对于SMA这样的天线,可以去除橡胶绝缘层,并焊接四分之一波长线圈天线。实际上,与433MHz信号适配良好的天线长度为:

    300/433/4 = 0.173
    数值为17.3cm/6.18in。四分之一波长天线的长度同样容易计算,只需要将波长的结果除以4即可。对于天线,使用绞合线或实芯线都可以,只要是绝缘的就行,但是实芯线更容易做成线圈。而且电气设备长度也很重要,在一个小小的手持车库开启器上装配17.3cm/6.18in的天线会很不便利。
    1.1.3电源
    信号强度也很重要,它被描述为每米dB毫伏(dBmV/m),或每米dB-微伏(dBuV/m),或超过一毫瓦的分贝(dBm)。您需要将频率计数器直接放置在天线旁边才能准确读取—我通常只关心mW/W值,看看我能与多远的节点之间进行ping-pong信号传递,就像潜艇那样!
    我们的CC1101无线电模块在我的频率计数器上读数约为30mV,这很不错(可以推测它们为3.3×0.03=100mW)。将发射器设置为连续传输,并在室外进行范围测试,我在~60m/197ft处才开始丢失信号,这很棒!
    如果您有兴趣了解关于dBm(这是得到无线电信号强度的正确方式)的更多信息,请参阅 有关dBm的维基百科。这会很有帮助。

    1.1.4空间
    用外行的话来说,空间是距离,以及两点之间空间中的任何物质。
    在空间中,可能有一棵树、一片森林、一所房子,或者知识微小的大气颗粒,比如水分子。如果空间里什么都没有,那么无线电波就会从A电直线传播到B点。如果空间里到处都是金属碎片,那么电波就会被破坏和分散。
    可以预料到,我们的无线电信号仅仅在穿越距离的过程中就会消散和散射,而如果碰到障碍物(即使只是树上的树叶也会成为远程WiFi PtP连接无法克服的障碍)只会使ping-pong转换更加困难。
    令人高兴的是,CC1101无线电解决了这些问题。如今,433MHz频段很拥挤,因此对于充满干扰和障碍物的现代城市环境来说,可以在~60m/197ft的范围内实现非常可靠的数字传输已经很出色了。

    1.2您将会学到什么
    阅读本文后,您将会知道如何同时运行Arduino。
    CC1101无线电通过SPI连接。
    从Arduino 5V到3V3逻辑电平的SPI需要进行电平转换,如果您不学会这一操作,硬件将会被烧毁。我打赌您一定会去学习的。
    您还将需要学会一些实用的无线电公式,例如用于计算波长(用于天线)和波长频率的相关公式。这使您可以轻松制作出所需的任何类型的天线。生活中普遍使用的天线是价格很高的铜线,这对您来说并不适合。
    您还需要学习如何通过适度的置信度来消除输入信号(来自触觉开关/按钮)的抖动。然而,这样写出来的代码可能会非常复杂,一个非常好的替代方案是在一个ADC引脚上使用10K电位器。这非常快速(每10 uS读取一次),而且也不需要用代码去执行—如果您之前没有了解过,现在就应该学会了。
    当您的构建(有两个)完成后,您将能够在比WiFi所能覆盖区域更大的范围内传输和接收控制/遥测数据,以及对字节流执行相关操作。“ArduinoGarageOpener_CC1101.ino”中的“magic_token”变量可以被安全地增加到32或64字节,这似乎并没有真正影响到传输错误率。
    当您在代码中扩展车库基站段时,您将会学习如何非常高效使用内存。
    如果您的代码因为定义的内容而停止运行,就说明您使用了太多的SRAM(atmega328p只有2kB),只需要通过Arduino串行监视器发送一个“foo!”就可以查看所剩内存。如果没有得到任何信息,请撤销您最近的更改。
    之后还将会提到H桥(用于改变直流电机的极性/方向),如果您想要自己构建一个,就可以学习更多的相关内容去完成。为了激发您的这一想法,我将会在这里简单地展示并介绍一个H桥。它一直在我家里的某个地方,但是我从来都没关注过它。不过为了你,我会把它找出来。
    如果您喜欢对Arduino端口寄存器进行快速、直接的操作,H桥将会非常有用。更多内容请参看第2部分!

    2. Arduino车库开启器和通用无线电接口

    车库开启器通过一个大且重的电机进行操作。它们有各种形状和尺寸,从很小的12V到近乎高压的48V,甚至可以达到更高电压。有趣的是,您打算如何与之交互?这取决于您自己。您需要打开盒子,看一下里面的东西。
    可以在两个方向上运行的双极电机很容易获取。只需卸下驱动电路,直到只剩下电机、电源和盒子。
    Arduino电机驱动板(Rev3)不能用于这项任务(事实上应避免使用所有L298N系列)—您需要更强大的电机驱动器,例如额定电压为7-30V、10A的SHIELD-MD10。
    或者构建您自己的H桥,并直接通过Arduino端口操作进行驱动。看起来可能会如下图示意图所示:
    HBridge_NPN_PNP_diagram.png
    请参阅下面的端口操作说明。
    Atmega328p
  •   Ports register reference:
  •   Port D
  •   1   2   3   4   5   6   7   8
  •   --  --  --  --  --  --  --  --
  •   D7  D6  D5  D4  D3  D2  D1  D0
  •   Port B
  •   1   2   3   4   5   6   7   8
  •   --- --- --- --- --- --- --- ---
  •           D13 D12 D11 D10 D9  D8
  •   Port C
  •   1   2   3   4   5   6   7   8
  •   --  --  --  --  --  --  --  --
  •           A5  A4  A3  A2  A1  A0
  • 以及我的一些示例代码:
  • // setup()
  •   // Set D8+D9+D10+D11+D13 as OUTPUTs
  •   DDRB = 0b00101111 ;
  •   // Set all bits in register LOW
  •   PORTB = 0b00000000 ;
  • // ...
  • // Functions that belong elsewhere
  • void bridgeOFF( void ) {
  •   // Set all output pins LOW
  •   PORTB = 0b00000000 ;
  •   // Motor Spin-down grace period
  •   delay( 300 ) ;
  • }
  • void bridgeForward( void ) {
  •   bridgeOFF() ;
  •   // Forward, D8+D9+D13 HIGH, D13 is the onboard LED.
  •   PORTB = 0b00100011 ;
  • }
  • void bridgeReverse( void ) {
  •   bridgeOFF() ;
  •   // Reverse, D10+D10 HIGH, D13 is set LOW here.
  •   PORTB = 0b00001100 ;
  • }
  • 复制代码
    这完全可行,而且非常便宜。但是,其中有一点搞错了,纯*oomph*的单次击穿将会烧毁一条或两条电气路径。H桥必需精确计时。
    图中的H乔如下图所示,尽管它使用过时的BJT,但是性能完美无缺。本来已经弄丢了,但是我又找到了它。
    NPN_PNP_HBridge.png
    电机驱动器可以由您自己来选择(提示:SHIELD-MD10!),而且电机的选择会因车库设置而异。现在来完成Arduino车库开启器!

    2.1 Arduino车库开启器,基站
    首先,如果您不熟悉Arduino IDE,请先阅读这篇文章
    Arduino官网下载Arduino IDE,并点击此处了解如何安装该库。
    我们需要的库包含在这个zip文件中:ArduinoGarageOpener_CC1101
    您也可以在Github上浏览/下载该库,或者直接使用“git”将其直接安装到您的Arduino库文件夹中,如下所示:
    Git_Clone_Into_Arduino_libraries.png
    继续!
    我们的库Arduino_CC1101包含两个用于发送和接收指令的示例。
    Arduino_CC1101_examples.png
    如果您发现主程序过于复杂,请参考这些示例,这些例子只是简单地放在了一起。
    我们有一个程序文件,ArduinoGarageOpener_CC1101.ino, 它包含车库开启器和车库基站的代码。这些行决定了您编译的Arduino固件的功能。如果您希望为手持设备编译,将常数“IS_GARAGE_OPENE”设置为1,“IS_GARAGE_STATION”设置为0。如果编译车库基站,将“IS_GARAGE_STATION”设置为1,“IS_GARAGE_OPENER”设置为0。
    按下CTRL+R t进行编译,按CTRL+U进行(编译和)上传。程序和相关库都可以在zip文件“ArduinoGarageOpener_CC1101”中找到。
    // Handheld unit?
  • #define IS_GARAGE_OPENER         0
  • // Garage base station?
  • #define IS_GARAGE_STATION        1
  • 复制代码

    2.1.1 BOM
    Arduino Nanohttps://www.newark.com/arduino/a000005/dev-board-atmega328-arduino-nano/dp/13T9275
    CC1101 RF 模块https://www.elecrow.com/433mhz-rf-transceiver-cc1101-module-p-374.html
    ROHM SLR343BC4TT32 3mm LEDhttps://www.digikey.com/product-detail/en/rohm-semiconductor/SLR343BC4TT32/SLR343BC4TT32-ND/2337159
    330ohm 电阻https://www.newark.com/multicomp-pro/mccfr0w4j0331a50/carbon-film-resistor-330-ohm-250mw/dp/58K5042
    杜邦电线https://www.newark.com/multicomp-pro/mccfr0w4j0331a50/carbon-film-resistor-330-ohm-250mw/dp/58K5042
    面包板https://www.newark.com/multicomp-pro/mccfr0w4j0331a50/carbon-film-resistor-330-ohm-250mw/dp/58K5042
    3.3+5V 电源https://www.newark.com/bud-industries/bbp-32701/breadboard-power-supply-5v-3-3v/dp/56AC7832
    电平转换器https://www.newark.com/adafruit/395/logic-level-converter-8ch-arm/dp/53W5916

    2.1.2启动!
    按照下图进行接线。
    arduino_garage_opener_basestation_diagram.png
    可以看出Fritzing图标软件的强大功能!虽然CC1101 8P接头画得很差(至少从图像上看如此),但您还是一定要进行电平转换。如果不这样做,CC1101还是可以正常运行,但只能运行很短的时间。
    代码:
    /*
  • Constants from Arduino_CC1101/ELECHOUSE_CC1101.h:
  • Name           Pin  Comment
  • SCK_PIN        13   MUST LEVELSHIFT FROM Arduino 5V to CC1101 SPI 3V3!
  • MISO_PIN   12
  • MOSI_PIN   11   MUST LEVELSHIFT FROM Arduino 5V to CC1101 SPI 3V3!
  • SS_PIN         10   CSN/SS; MUST LEVELSHIFT FROM Arduino 5V to CC1101 SPI 3V3!
  • GDO0           2
  • GDO2           9   
  • */
  • // Handheld unit?
  • #define IS_GARAGE_OPENER         0
  • // Garage base station
  • #define IS_GARAGE_STATION        1
  • #if IS_GARAGE_OPENER
  • // Interrupt/sleep related libraries, not used.
  • //#include <avr/wdt.h>                
  • //#include <avr/interrupt.h>          
  • //#include <avr/sleep.h>              
  • //#include <avr/power.h>              
  • //const byte wakePin = 3 ; // INT1
  • #elif IS_GARAGE_STATION
  • // Motor open/close drive times usually differ,
  • // time both separately with a stopwatch app.
  • #define  motor_open_drive_time 100 // ms, change this
  • #define  motor_close_drive_time 100 // ms, change this
  • #endif
  • #include
  • const byte buttonPin = 6 ;
  • bool buttonState = LOW ;                 
  • bool previousButtonState = LOW;   
  • unsigned long previousDebounceTime = 0 ;  
  • //unsigned long debounceDelay = 50;   
  • unsigned long debounceDelay = 100 ;   
  • const int magic_token_len = 6 ;
  • // PING LIKE A SUBMARINE!
  • byte magic_token[ magic_token_len ] = { "PING!" } ;
  • const int input_buffer_len = 6 ;
  • const byte ledPin = 4 ;
  • bool is_garage_open = false ;
  • // 0: No Serial.printing, except in serialEvent()
  • // 1: Lots of useful information, such as received data, events etc.
  • const bool debug = 1 ;
  • void setup( void ) {
  •   Serial.begin( 115200 ) ;
  •   pinMode( ledPin, OUTPUT ) ;
  •   ELECHOUSE_cc1101.Init( F_433 ); // Frequency: 433MHz
  •   //ELECHOUSE_cc1101.Init( F_868 ) ; // Frequency: 868MHZ
  •   //ELECHOUSE_cc1101.Init( F_915 ) ; // Frequency: 915MHz
  •   if ( IS_GARAGE_STATION )
  •         ELECHOUSE_cc1101.SetReceive() ; // Do listen
  •   else if ( IS_GARAGE_OPENER ) {
  •       // 10K pull-down from D6->GND, then D6->SW1->5V
  •         pinMode( buttonPin, INPUT ) ;
  •   }
  • }
  • void loop( void ) {
  •   // Begin Garage Opener
  •   if ( IS_GARAGE_OPENER ) {
  •         int read = digitalRead( buttonPin ) ;
  •         if( read != previousButtonState ) {
  •           // Reset the debouncing timer
  •           previousDebounceTime = millis() ;
  •         }
  •         if ( (millis() - previousDebounceTime ) > debounceDelay ) {
  •           if ( read != buttonState ) {
  •     buttonState = read ;
  •     // Still HIGH?
  •     if (buttonState == HIGH) {
  •       digitalWrite( ledPin, HIGH ) ;
  •       Serial.println( "[!] Sending magic_token three times ..." ) ;
  •       // Now send magic_token with moderate confidence
  •       for ( int it = 0 ; it < 3 ; it++ ) { ELECHOUSE_cc1101.SendData( magic_token, magic_token_len ) ; } delay( 50 ) ; digitalWrite( ledPin, LOW ) ; } } } previousButtonState = read ; } // End Garage Opener // Begin Garage Station else if ( IS_GARAGE_STATION ) { byte input_buffer[ input_buffer_len ] = { 0 } ; bool token_matched = false ; if ( ELECHOUSE_cc1101.CheckReceiveFlag() ) { int len = 0 ; len = ELECHOUSE_cc1101.ReceiveData( input_buffer ) ; if ( debug ) { Serial.print( F( "[!] RX data => " ) ) ;
  •     Serial.println( (const char *)input_buffer ) ;
  •     Serial.print( F( "[!] RX data length => " ) ) ;
  •     Serial.println( len, DEC ) ;
  •           }
  •           delay( 100 ) ;
  •           // Are magic_token and input_buffer identical?
  •           if( strncmp( (const char *)magic_token, (const char*)input_buffer, 2 ) == 0 ) {
  •     token_matched = true ;
  •     // If open, close
  •     // If closed, open
  •     // set is_garage_open accordingly
  •           }
  •           else token_matched = false ;
  •           // End token_match
  •           // Begin open/close actions
  •           if ( token_matched ) {
  •     if ( is_garage_open ) {
  •       if( debug ) Serial.println( F( "[!] Garage door CLOSE action!" ) ) ;
  •       // Drive closed ...
  •       is_garage_open = false ;
  •       digitalWrite( ledPin, LOW ) ;
  •       delay( motor_close_drive_time ) ;
  •     }
  •     else if ( ! is_garage_open ) {
  •       if ( debug ) Serial.println( F( "[!] Garage door OPEN action!" ) ) ;
  •       // Drive open ...
  •       is_garage_open = true ;
  •       digitalWrite( ledPin, HIGH ) ;
  •       delay( motor_open_drive_time ) ;
  •     }
  •           }
  •           // End open/close actions
  •         }
  •         ELECHOUSE_cc1101.SetReceive() ; // Do continue listening
  •   }
  • }
  • // Called if Arduino receives data over serial link
  • void serialEvent( void ) {
  •   printFreeRAM() ;
  •   while ( Serial.available() > 0 )
  •         Serial.read() ;
  • }
  • // Print free SRAM in bytes
  • void printFreeRAM( void ) {
  •   extern int __heap_start, *__brkval ;
  •   int v ;
  •   v = (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval ) ;
  •   Serial.print( F( "[!] Free RAM => " ) ) ;
  •   Serial.println( v, DEC ) ;
  • }
  • 复制代码
    通电后,基站将开始监听“magic_token”,即“PING!”。这可以是任何内容,这由您来决定。
    当它接收到正确的字节序列(目前由“P”、“I”、“N”、“G”、“!”的ASCII码组成,由ASK调制传输)时,会触发您所设定任何事件的“OPEN(打开)”/ “CLOSE(关闭)”,如SHIELD-MD10,一个H桥,或者继电器。
    “OPEN”和“CLOSE”操作存在延迟。可以假设打开和关闭车库门分别需要两个不同的时间段,而这就是造成延迟的原因。
    改变常量“motor_open_drive_time”和“motor_close_drive_time”,直到找到合适的值为止。另一种方法是使用霍尔效应传感器,但是本文没有涉及。
    #define  motor_open_drive_time 5000 // ms
  • #define  motor_close_drive_time 5000 // ms
  • 复制代码
    我们使用变量“is_garage_open”追踪当前状态,因此可以在车库门打开时对其关闭,反之亦然。它仅存储在SRAM中,而不存储在EEPROM中。
    对于基站来说特别重要的一点是,当您添加电机驱动代码时,一定要通过发送“foo!”到Arduino来关注串行监视器中的剩余SRAM(CTRL+SHIFT+M)信息。当前设置中有1823个字节可用,但是如果您引用了一些功能比较强大的库,这可能会变化得很快。
    当触发“OPEN”动作时,ROHM 3mm LED将会被点亮。如果变量“debug”为真,那么您就可以通过Arduino串行监视器追动当前状态,如下所示。
    Arduino_Serial_Monitor_Garage_Basestation.png
    2.2 Arduino车库开启器,手持单元
    按下按钮,发送“magic_token”
    2.2.1 BOM
    Arduino Nanohttps://www.newark.com/arduino/a000005/dev-board-atmega328-arduino-nano/dp/13T9275
    CC1101 RF 模块https://www.elecrow.com/433mhz-rf-transceiver-cc1101-module-p-374.html
    ROHM SLR343BC4TT32 3mm LEDhttps://www.digikey.com/product-detail/en/rohm-semiconductor/SLR343BC4TT32/SLR343BC4TT32-ND/2337159
    330ohm 电阻https://www.newark.com/multicomp-pro/mccfr0w4j0331a50/carbon-film-resistor-330-ohm-250mw/dp/58K5042
    杜邦电线https://www.newark.com/multicomp-pro/mccfr0w4j0331a50/carbon-film-resistor-330-ohm-250mw/dp/58K5042
    面包板https://www.newark.com/multicomp-pro/mccfr0w4j0331a50/carbon-film-resistor-330-ohm-250mw/dp/58K5042
    3.3+5V 电源https://www.newark.com/bud-industries/bbp-32701/breadboard-power-supply-5v-3-3v/dp/56AC7832
    电平转换器https://www.newark.com/adafruit/395/logic-level-converter-8ch-arm/dp/53W5916
    10kOhm 电阻https://www.newark.com/arcol/mra0207-10k-b-15ppm-ta/res-10k-0-10-250mw-axial/dp/79Y4556
    按钮https://www.newark.com/adafruit/1119/tactile-switch-pcb-breadboard/dp/84X1201

    2.2.2启动!
    我们使用与基站相同的代码,只是将常量“IS_GARAGE_OPENER”更改为1,将“IS_GARAGE_STATION”更改为0.
    再一次,按照下图接线。
    arduino_garage_opener_handheld_diagram.png
    瞬时开关需要消除抖动。这在一定程度上取决于您的所处环境:如果电源放在一个经常振动的办公桌上,那么需要的就不仅仅是手持式浮动电池供电单元。尝试将变量“debounceDelay”设置为40-50ms。我们连接引脚D6 -> 按钮 -> 5V,但在D6附近需要一个下拉电阻,像这样:D6 -> 10kOhm -> GND。
    将手持单元作为通用车库开启器(虽然它的功能更加强大)。如果使用Arduino Nano,您选择的电池组可以很方便地作为USB移动电源。
    如果您使用了Arduino Pro Mini 3V3(atmega168),那么可以使用TP4056(+DW01)电池模块和3.7V锂电池。如此一来就不需要用电平转换器了。为了延长电池寿命,最好将Arduino睡眠模式设置为“LEEP_MODE_PWR_DOWN”。这取决于您。
    当按下按钮并且去抖延迟已经确认了按钮在非抖动影响下确实被按下时,它将继续通过CC1101无线电发送“magic_token”变量的内容。
    如果您的基站处在范围内,并已通电,它将捕获传输信息,并执行“OPEN”/“CLOSE”操作。
    最后,您的桌面上会有这些东西。
    arduino_cc1101_birdsnest_breadboard.jpg
    不要感到绝望,缩短跳线或者将面包板分开会看起来好很多。但重点是测试其是否有效,以及效果是否非常好。
    当然,范围测试是使用一大串电线、手上的Arduino和CC1101以及由3V3导轨降压的USB电池组供电来完成的,闪烁两次,确认收到了“PING!”。
    请记住,这两个版本可以用作模板来通过无线电控制任何类型的电子设备——它不仅限于操作车库门。如果您需要一种简单的方法来发送遥测数据而不会弄乱您的Raspbian装置,则可以在任一端使用其他硬件,如Raspberry Pis。该程序易于使用到任何类型的应用中。
    现在去界面操作吧!

    来源:techclass.rohm