Arduino Uno平台上的Shadowplay LED手表

而不是加入




因此,我们的三人团队的任务是:在很短的时间内(最好是在Arduino平台上)组装一个小型硬件项目。值得一提的是,直到那一刻,我们在很大程度上还是从理论上熟悉电路。这意味着-既没有使用烙铁的经验(实际上),也没有特别的Arduino经验。

突然,我们偶然发现了一篇文章致力于Shadowplay Clock项目。这是由维也纳设计师团队设计的挂钟,您可以通过将手指触摸到他们的中心来查看时间。LED以某种方式点亮,以使中心的手指阴影显示时间。决定在家里创建相同(或非常相似)的文件。如您所见,以上文章没有包含该项目的详细描述。因此,我们自己必须弄清楚该设备的工作原理并将其实现。实际上,我们做了什么。

用料


要创建手表,您需要:

  • 纤维板坯
  • 60个LED二极管带
  • Arduino Uno
  • RTC DS1307实时时钟模块
  • 纽扣
  • 面包板
  • 移位寄存器74HC595(x2)
  • 8位寄存器锁存器74HC573N(x8)
  • 解码器4-16 K155ID3
  • 开漏逆变器IN74HC05AN(x10)
  • 动力单元

让我们开始吧


因此,设备的算法为:

  1. 通电时,LED以预定的组合点亮。在最初的Shadowplay中,所有LED都点亮了,但在我们看来,启动某种组合作为屏保会更有趣。
  2. 当按下按钮时(是的,我们也离开了原稿,并在中间插入了一个小按钮),从RTC模块中读取了时间。
  3. 读取时间将转换为二进制代码(掩码),并输入到寄存器中。
  4. 根据面罩,所需的二极管将被点亮。

硬体


当我们最终决定该项目的构想时,我们尝试的第一件事是从精神上勾勒出实施该方案的大概版本。主要问题是如何处理60个LED。实际上,这个问题的答案决定了构建几乎整个方案的方法。

我想到的第一个选择是使用解码器。编译电路是由四个解码器4-16和一个解码器2-4组成的级联,它们都具有解密使能输入。这样的级联允许寻址到64个输出,这足以连接60个LED。

但是,随之而来的问题是如何使多个LED同时工作(地址)(毕竟,我们至少需要为时钟提供分针和时针)。此处显示了该方案的主要缺点-根据定义,解码器一次不能寻址多个输出。

这个缺陷迫使我们放弃使用级联的解码器的想法。另外,现在我们对未来的电路还有另一个要求-支持同时运行不同数量的LED。

为了满足此要求,我们认为可以使每个LED存储其状态。寄存器非常适合于此目的,其中每个单独的放电对应于一个LED的状态。我们决定使用8位寄存器,因为它们更通用,更实用。因此,在我们的电路中,我们将需要8个这样的寄存器来支持60个LED。

接下来,我们考虑了如何通过寄存器使用Arduino控制LED的状态。每个正常操作的寄存器应完整接收所有8位。当然,Arduino Uno提供足够的输出以一次传输多个位,但是这种方法并不合理。此外,该方案仅包含8个寄存器,这意味着您需要以某种方式对其进行寻址。为此,我们添加了一个解码器和两个串联连接到电路的8位移位寄存器。一个移位寄存器存储一个8位状态掩码,该状态掩码将被加载到8个常规寄存器之一中,其数量存储在第二个移位寄存器中。因此,解码器连接到第二移位寄存器。为此,一个3×8的解码器就足够了。

为了从所需数量的输出中消除反相,我们使用了两个KR1533LN1反相器电路。当然,这使方案有些复杂。

另一个任务是LED的工作电压等于12伏,而逻辑电路为5伏。提出的解决方案是使用开漏逆变器。这种微电路起着按键的作用,该按键闭合(逻辑1)或断开(逻辑0)LED的接地触点之一,从而打开或关闭LED。该电路假设根据LED的工作电压在12伏特下工作,因此为了使逻辑电路获得5伏特,在电路中添加了带有两个电容器的KR142EN5A稳定器。

某些微电路的某些输入在该输入处暗示一个恒定值,因此它们已接地或连接到电源。在这种情况下,这些是以下输入:

  • 两个移位寄存器中的MR反向输入通过负载寄存器连接至5伏稳定器输出。
  • 两个移位寄存器中的反向OE输出使能输入直接接地。
  • 反向解码器使能输入E0接地




该电路由四个输入(E1,SH,ST,DS)控制。它们的目的和信号电平将在下面详细讨论:

输入E1设计为启用解码器。在我们的情况下,最初在解码器上有两个控制输入E1,E0,并且它们都是相反的。一个出口将足够,因此第二个(E0)可以放到地面上。在向E1施加高信号电平之前,“默认”解码器状态是可操作的。为了进行相反的操作,我们将此输入连接到逆变器。否则,例如,在更新移位寄存器中的数据时,解码器会向寄存器提供错误的控制信号。如已经提到的,电路中可以使用3 x 8解码器,该解码器可以具有一个非反向控制输入,这将易于解决上述所有问题,而无需额外的导线和烙铁。

当将单个信号电平施加到E1时,解码器解码位于相应移位寄存器中的寄存器地址,并将信号发送到所需的输出。此后,解码器通过向E1施加低电平信号再次关闭。解码器的这种开关在所需的输出处生成信号,该信号的边沿和衰减用作寄存器时钟脉冲,用于自身将存储在总线上的数据捕捉。

接下来的三个输入已经用于控制移位寄存器。值得从最简单的事情开始-DS数据输入。顾名思义,此输入旨在用于数据传输。由于电路中的移位寄存器是级联的,所以DS代表其中之一的相应输出。第二个移位寄存器的输入连接到第一个寄存器的最后一位的输出。结果是一个用于16位的移位寄存器,其中仅使用了12位。

输入SH是时钟输入。方波馈入此输入,该输入分别负责在每个寄存器中加载和移位数据,电路的此触点连接到两个寄存器的SHCP引脚。

最后一个ST引脚用作寄存器输出上的数据锁存器。给该输入一个脉冲,但是,仅当移位寄存器中的数据最终装入并且需要将它们固定在寄存器的输出时才提供脉冲。仅在发出此信号后,在第一行触发器上的寄存器内下载的数据才会进入第二行触发器,并在总线上可用。 ST是连接到两个寄存器的STcp引脚的引脚。

剩下的将解释移位寄存器MR和OE的两个引脚的接线。第一个输入(MR)负责将数据转储到寄存器中。在该电路中,不需要这样的机会,因此通过负载将高信号电平提供给该输出。

第二寄存器输入(OE)负责使移位寄存器内的第二行触发器与总线断开,即使能输入。也不需要此功能,因此,输出将缠绕在地上。

上面未描述的另一种触点设计用于从手表中心的按钮上去除信号电平,该按钮电路是典型电路,代表负载和按键,具体取决于将低信号电平或高信号电平发送到Arduino的位置。根据按钮的状态,手表可以在屏幕保护程序模式或时间显示模式下工作。
与Arduino的连接没有特殊功能,只是SH引脚理想地连接到SCK数字引脚。电路的其余输出可以连接到任何可用的通用数字输入。在我们的例子中,连接具有以下形式:

  • Arduino pin13 (SCK) – SH
  • Arduino pin 11 – ST Arduino pin 8 – DS Arduino pin 5 – E1 Arduino pin 3 – Arduino pin GND – ( )
    , .

    : 36 — ; 36 ( ) \ 26 ( ) — . Shadowplay 72 , 36. , 60 ( + ). . , , , .



    , . , . , . , «» 50 — 60, . , 60 . : — .



    , , , . — . , , . , — .

    .


    -, .

    Shadowplay. , , . , , . , .. . , , 100% . , . . , . , .

    , ( ), , — . , , ! . , - - . , , . , , . , , , . ., - . , , , , , .



    , . . .. , , ( , , ), .


    , (). Arduino. , RTC, — . DS1307, — I2C. 2100 . . RTC Arduino, . RTC.



    , RTC, .

    #include <Time.h>
    #include <DS1307RTC.h>
    
    char REG [8];
    tmElements_t te;
    int c,reset=1;
    
    void setup() {
      pinMode(13, OUTPUT); 
      pinMode(11, OUTPUT); 
      pinMode(8, OUTPUT);  
      pinMode(5, OUTPUT);  
      pinMode(3, INPUT);   
      Serial.begin(57600);
      
      //      RTC  
      //te.Hour = 18;
      //te.Minute = 50;
      //te.Second = 0;
      //te.Day = 20; //
      //te.Month = 4; // 
      //te.Year = CalendarYrToTm(2016); 
      //RTC.write(te);
    }
    
    void loop()
    { 
    if(digitalRead(3))                    // ,     
      {RTC.read(te);
       SetShadowTime(te.Hour,te.Minute,te.Second,2);        //         
       delay(900);
       reset=1;
      }
    
    else                                   //    ,  
      {wait1();
       reset=1;
      }
                                             //  
      for(int j = 0; j<8 ; j++) SetUpLightByMask(j,0); 
       
     
    }
    
    
    //======================================================================= 
    
    void SetUpLightByMask(int RegNum, char LightMask) //         
    {
      
      digitalWrite(5, LOW);
      digitalWrite(11, LOW);
      shiftOut(8, 13, MSBFIRST, LightMask);
      shiftOut(8, 13, LSBFIRST, RegNum);
      digitalWrite(11, HIGH);
      digitalWrite(5, HIGH);
    }
    
    void digitalClockDisplay() {  //    RTC  ,    RTC  
      RTC.read(te);
      Serial.print(te.Hour);
      Serial.print(" : ");
      Serial.print(te.Minute);
      Serial.print(" :");
      Serial.print(te.Second);
      Serial.print(" ");
      Serial.print(te.Day);
      Serial.print(" ");
      Serial.print(te.Month);
      Serial.print(" ");
      Serial.print(tmYearToCalendar(te.Year));
      Serial.println();
    }
    
    //     ,     , ,        :
    //0 -  ,1 -   , 2 -    
    
    void SetShadowTime(int Hours, int Minutes, int Seconds, int param){   
      int h,hshift,m,s;
      for(int j = 0; j<8 ; j++) REG[j] = 0;
    
      if(Hours >= 12) Hours -= 12;
      h = Hours + 6;
      if(h >= 12) h -= 12;
        
      hshift = (int) Minutes / 12;
      
      REG[(int)(((h*5)+hshift)/8)] = REG[(int)(((h*5)+hshift)/8)] | 1<<(((h*5)+hshift)%8);
                 
       
      if(param == 1)
      {m = Minutes + 30;
       if(m >= 60) m -= 60;
       REG[(int)(m/8)] = REG[(int)(m/8)] | 1<<(m%8);
       }
    
      if(param == 2)
      {m = Minutes + 30;
       if(m >= 60) m -= 60;
       REG[(int)(m/8)] = REG[(int)(m/8)] | 1<<(m%8);
         
       s = Seconds + 30;
       if(s >= 60) s -= 60;
       REG[(int)(s/8)] = REG[(int)(s/8)] | 1<<(s%8);
      }
    
      for(int j = 0; j<8 ; j++) SetUpLightByMask(j,REG[j]);
      }
      
    void wait1() //    
    {for(int a = 0; a < 8; a++)
        {c=0;
         for(int b = 0; b < 8; b++)
          {c = c << 1;
           c = c | 1;
           SetUpLightByMask(a, c);  
           delay(10);   
          }
       }
      for(int a = 0; a < 8; a++)
        {c=255;
          for(int b = 0; b < 8; b++)
          {c = c << 1;
           SetUpLightByMask(a, c);
           delay(10);     
          }
        }  
    }
    



    . . (Arduino, RTC) . , . , «- ». — . :

    .



    , «»:



    , , :



    , , , .

    , , . , ? . : ( , , ..). , , , . . , .


    ?

    • , , .
    • .
    • 能够以编程方式轻松更改LED闪烁顺序的能力,从而提供了无限多种组合。

    我们不主张我们的电路和软件解决方案的绝对正确性。如前所述,负面经验也是经验。也许这篇文章可以帮助某人避免我们的错误。

    感谢您的关注!

Source: https://habr.com/ru/post/zh-CN394107/


All Articles