New Year Countdown

Un element important al oricărui revelion este, bineînțeles, numărătoarea inversă până la trecerea în noul an – cronometrul care ne arată cât timp mai este până la deschiderea sticlei de șampanie și până la pornirea focurilor de artificii. În cadrul proiectului de față ne propunem să implementăm un sistem care să ne arate cu precizie cât timp mai este până la un anume moment de timp, poate să fie trecerea în noul an, o aniversare sau orice alt moment important.

Pentru a putea calcula cu exactitate timpul rămas trebuie să știm în primul rând data și ora la momentul actual. Pentru acest lucru vom utiliza o placă cu conectivitate WiFi ce ne va permite sincronizare de timp NTP și anume placa de dezvoltare Adafruit Feather M0 WiFi. Această placă este similară cu placa Arduino MKR1000, montajul propus poate fi realizat cu oricare dintre ele. Pe lângă conectivitatea WiFi, ambele plăci de dezvoltare permit alimentarea de la un acumulator LiPo de 3.7V permițând astfel realizarea simplă a unui montaj portabil. Pentru afișare vom utiliza un modul cu afișaj pe 7 segmente cu 4 caractere și circuit de comandă TM1637. Schema de interconectare dintre placa de dezvoltare și modulul de afișare este:

2

Modulul de afișare se va alimenta la 3.3V (pinul VCC se va conecta la pinul de 3V al plăcii de dezvoltare iar pinul GND la pinul de GND). Pinul CLK se va conecta la pinul 5 al plăcii de dezvoltare și pinul DIO la pinul 6. Comunicația între placa de dezvoltare și modulul de afișare se face serial.

Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.5 având instalate bibliotecile TM1637Display, Time 1.5.0, WiFi101 0.14.5 și extensia Adafruit SAMD Boards 1.0.21.

#include <TM1637Display.h>

#define CLK 5

#define DIO 6

TM1637Display display(CLK, DIO);

#include <SPI.h>

#include <WiFi101.h>

#include <WiFiUdp.h>

În cadrul programului trebuie personalizate informațiile de conectare WiFi (ssid[] și pass[]). Constanta timeZone conține decalajul de fus orar specific zonei grografice în care ne aflăm (2 pentru ora de iarnă în România).

int status = WL_IDLE_STATUS;

char ssid[] = “…”;       

char pass[] = “…”;   

unsigned int localPort = 2390;

IPAddress timeServer(129, 6, 15, 28);

const int NTP_PACKET_SIZE = 48;

byte packetBuffer[ NTP_PACKET_SIZE];

WiFiUDP Udp;

#include <TimeLib.h>

const int timeZone = 2;

tmElements_t TheDay;

Structurile SEG_DONE și dy conțin configurația de segmente pentru mesajul done și pentru literele d și Y. Mesajul done va fi afișat după ce numărătoare inversă se va încheia. Litele d și Y vor fi utilizate când se vor afișa anii și zilele rămase pâna la evenimentul programat.

const uint8_t SEG_DONE[] = {

  SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,           // d

  SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,   // O

  SEG_C | SEG_E | SEG_G,                           // n

  SEG_A | SEG_D | SEG_E | SEG_F | SEG_G            // E

  };

const uint8_t dy[] = {

  SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,           // d

  SEG_B | SEG_E | SEG_F | SEG_G ,                  // Y

  };

În cadrul secțiunii setup se realizează conectarea la rețeaua WiFi și programarea evenimentului dorit (în exemplul nostru 1 ianuarie 2018 ora 0:00, anul este înregistrat ca diferență față de anul 1970). Instrucțiunea WiFi.setPins este necesară pentru placa Adafruit Feather M0, se va șterge în cazul plăcii Arduino MKR1000.

void setup() {

  WiFi.setPins(8,7,4,2);

  if (WiFi.status() == WL_NO_SHIELD) {

    while (true);

  }

  while ( status != WL_CONNECTED) {

    status = WiFi.begin(ssid, pass);

    delay(10000);

  }

  Udp.begin(localPort);

  setSyncProvider(getNtpTime);

  setSyncInterval(3600);

  TheDay.Second = 0;

  TheDay.Minute = 0;

  TheDay.Hour = 0;

  TheDay.Day = 1;

  TheDay.Month = 1;

  TheDay.Year = 48;

  }

Secțiunea loop implementează partea de verificare a diferenței de timp între momentul actual și data evenimentului (diferența se calculează în secunde și este stocată în variabila dif) și partea de afișare. Dacă diferența este negativă înseamnă că evenimentul a trecut și se afișează done. Dacă diferența este mai mare de 31556926 secunde (adică un an) pe ecran se va afișa diferența în ani întregi, pe trei caractere urmate de litera Y. Dacă diferența este mai mare de 86400 secunde (adică o zi) pe ecran se va afișa diferența în zile, pe trei caractere urmate de litera d. Dacă diferența este mai mică de o zi dar mai mare de o oră (3600 secunde) pe ecran se va afiș diferența în ore:min. În final, când avem un interval mai mic de o oră, pe ecran se va afișa diferența în format min:sec.

void loop() {

  time_t dif;

  dif = makeTime(TheDay) – now();

  display.setBrightness(0x0f);

  if (dif < 0) display.setSegments(SEG_DONE);

  else if (dif>31556926L) {

    display.showNumberDec(((1970+TheDay.Year) –

(year()+1)),true,3,0);

display.setSegments(dy+1, 1, 3); }

  else if (dif>86400L) {

display.showNumberDec((dif/86400L),true,3,0); display.setSegments(dy, 1, 3); }

  else if (dif>3600) {

display.showNumberDecEx((dif/3600L),0xFF,true,2,0); display.showNumberDec(((dif%3600L)/60),true,2,2); }

  else {

display.showNumberDecEx((dif/60),0xFF,true,2,0); display.showNumberDec((dif%60L),true,2,2); }

  delay(1000);

}

Funcții getNtpTime și sendNTPpacket sunt utilizate de biblioteca Time pentru sincronizarea de timp.

time_t getNtpTime() {

   sendNTPpacket(timeServer);

  delay(1000);

  if ( Udp.parsePacket() ) {

    Udp.read(packetBuffer, NTP_PACKET_SIZE);

    unsigned long highWord = word(packetBuffer[40],

packetBuffer[41]);

    unsigned long lowWord = word(packetBuffer[42],

packetBuffer[43]);

    unsigned long secsSince1900 = highWord << 16 | lowWord;

    const unsigned long seventyYears = 2208988800UL;

    unsigned long epoch = secsSince1900 – seventyYears;

    return (time_t) (secsSince1900 – 2208988800UL +

timeZone * SECS_PER_HOUR);

  }

  else return 0;

}

unsigned long sendNTPpacket(IPAddress& address) {

  memset(packetBuffer, 0, NTP_PACKET_SIZE);

  packetBuffer[0] = 0b11100011;  

  packetBuffer[1] = 0;    

  packetBuffer[2] = 6;    

  packetBuffer[3] = 0xEC;

  packetBuffer[12]  = 49;

  packetBuffer[13]  = 0x4E;

  packetBuffer[14]  = 49;

  packetBuffer[15]  = 52;

  Udp.beginPacket(address, 123);

  Udp.write(packetBuffer, NTP_PACKET_SIZE);

  Udp.endPacket();

}

Dacă se dorește implementarea unui afișaj pe 7 segmente utilizând o componentă proprie (un afișaj de mari dimensiuni de exemplu) se poate utiliza un circuit de tip shift register cu o schemă de interconectare de genul:

3

Pentru circuitul 74HC595 se poate avea în vedere utilizarea bibliotecii SparkFun 74HC595 Arduino Library pentru implementarea comenzii către caracterele pe 7 segmente.

My Heart Will Go IoT

Măsurarea pulsului, a numărului de bătăi ale inimii pe minut, este un instrument de evaluare atât a stării de sănătate dar și a stării emoționale sau a efortului fizic depus. Apariția de senzori optici performanți fac această evaluare foarte simplă permițând măsurarea pulsului cu telefonul mobil sau cu diverse dispozitive de tip brățară. În cadrul proiectului de față vom explica construirea unui astfel de dispozitiv dar, în plus față de  dispozitivele clasice de măsurare a pulsului, vom înregistra datele obținute în cloud.

Atenție!!! Dispozitivul prezentat nu este un dispozitiv medical! Dispozitivul din acest proiect nu poate fi folosit pentru evaluarea stării de sănătate! Proiectul de față prezintă un principiu de măsurare și înregistrare nu și o metodă exactă de calibrare a datelor măsurate.

Pentru implementarea dispozitivului vom utiliza o placă de dezvoltarea Adafruit Feather M0 WiFi și un senzor de particule și puls MAX30105. Schema de interconectare dintre cele două componente este următoarea:

2

Senzorul comunică cu placa de dezvoltare prin intermediul magistralei I2C și atunci conexiunile dintre cele două componente sunt evidente: pinul SCL al senzorului la pinul 4 (SCL) al plăcii de dezvoltare și pinul SDA al senzorului la pinul 3 (SDA) al plăcii de dezvoltare. Pinul /INT al senzorului nu este folosit. Alimentarea senzorului se va face la 5V – pinul 5V se va conecta la pinul USB al plăcii de dezvoltare atâta timp cât placa se alimentează prin intermediul cablului USB. Dacă doriți să transformați sistemul într-un sistem portabil, alimentând placa de la un acumulator LiPo, atunci pinul de 5V al senzorului se va conecta la pinul BAT al plăcii de dezvoltare – senzorul se va alimenta la 3.7V (producătorul specifică posibilitatea de a alimenta senzorul la tensiuni între 3.3V și 5V dar explică că o tensiune de peste 3.5V va asigura o funcționare mai bună a senzorului).

Pentru punerea în funcțiune și pentru integrarea plăcii de dezvoltare cu mediul Arduino IDE este necesară parcurgerea materialului „Adafruit Feather M0 WiFi with ATWINC1500 – ARM Cortex M0+ with fast & fun wireless built in”. Pentru înțelegerea mai bună a modului de funcționare a senzorului de puls se poate parcurge materialul „MAX30105 Particle and Pulse Ox Sensor Hookup Guide”.

Programul sistemului se bazează pe exemplul Example5_HeartRate al bibliotecii software Sparkfun MAX3010x Pulse and Proximity Sensor Library. Programul a fost testat utilizând Arduino IDE 1.8.3 cu extensia Adafruit SAMD Boards 1.0.17 instalată și bibliotecile WiFi101 0.14.3 și Sparkfun MAX3010x 1.0.0. Atenție!!! Pentru a compila programul pentru placa Feather M0 este necesară adăugarea unei linii (#define BUFFER_LENGTH 32) în fișierul MAX30105.cpp al bibliotecii Sparkfun MAX3010x.

#include <SPI.h>

#include <WiFi101.h>

#define WINC_CS   8

#define WINC_IRQ  7

#define WINC_RST  4

#define WINC_EN   2 

Datele de conectare WiFi trebuie personalizate în program. Programul va înregistra datele măsurate în cloud utilizând serviciul Robofun IoT.

char ssid[] = ““;

char pass[] = ““; 

WiFiClient client;

int status = WL_IDLE_STATUS;

char server[] = “iot.robofun.ro”;

Constanta postingInterval va dicta intervalul (în milisecunde) la care se face înregistrarea datelor în cloud.

unsigned long lastConnectionTime = 0;

const unsigned long postingInterval = 30L * 1000L;

#include <Wire.h>

#include “MAX30105.h”

#include “heartRate.h”

MAX30105 particleSensor;

const byte RATE_SIZE = 4;

byte rates[RATE_SIZE];

byte rateSpot = 0;

long lastBeat = 0;

float beatsPerMinute;

int beatAvg;

Decomentarea directivei debug va permite vizualizarea datelor măsurate în concola serială.

//#define debug

3

În cadrul secțiunii setup() se va inițializa conexiunea WiFi și comunicația cu senzorul de puls. Mesajele de eroare vor apărea în consola serială doar dacă este activată directiva debug.

void setup() {

  #ifdef debug

    Serial.begin(115200);

    while (!Serial) { ; }

    Serial.println(“Initializing…”);

  #endif

  WiFi.setPins( 8,7,4,2);

  if (WiFi.status() == WL_NO_SHIELD) {

    #ifdef debug

      Serial.println(“WiFi shield not present”);

    #endif

    while (true);

  }

  while (status != WL_CONNECTED) {

    #ifdef debug

      Serial.print(“Attempting to connect to SSID: “);

      Serial.println(ssid);

    #endif

    status = WiFi.begin(ssid, pass);

    delay(10000);

  }

  #ifdef debug

    Serial.println(“Connected to wifi”);

  #endif

  if (!particleSensor.begin(Wire, I2C_SPEED_FAST))

  {

    #ifdef debug

      Serial.println(“MAX30105 was not found. Please check wiring/power. “);

    #endif

    while (1);

  }

  #ifdef debug

    Serial.println(“Place your index finger on the sensor with steady pressure.”);

  #endif

  particleSensor.setup();

  particleSensor.setPulseAmplitudeRed(0x0A);

  particleSensor.setPulseAmplitudeGreen(0);

}

În cadrul secțiunii loop() se va efectua atât citirea senzorului de puls (BPM – bătăi pe minut, medie – Avg BPM) cât și postarea către serviciul Robofun IoT. În cadrul codului trebuie personalizată cheia de autentificare (TOKEN) primită la înregistrarea senzorului în cadrul serviciului IoT. Intervalul de postare se contorizează începând cu detecția degetului pe senzor, dacă se ia degetul de pe senzor nu se mai efectuează înregistrarea valorilor în cloud. Se recomandă utilizarea unui elastic pentru a asigura o presiune constantă pe senzor.

4

void loop() {

  long irValue = particleSensor.getIR();

  if (checkForBeat(irValue) == true)

  {

    long delta = millis() – lastBeat;

    lastBeat = millis();

    beatsPerMinute = 60 / (delta / 1000.0);

    if (beatsPerMinute < 255 && beatsPerMinute > 20) {

      rates[rateSpot++] = (byte)beatsPerMinute;

      rateSpot %= RATE_SIZE;

      beatAvg = 0;

      for (byte x = 0 ; x < RATE_SIZE ; x++)

        beatAvg += rates[x];

      beatAvg /= RATE_SIZE; }

  }

  #ifdef debug

    Serial.print(“IR=”);

    Serial.print(irValue);

    Serial.print(“, BPM=”);

    Serial.print(beatsPerMinute);

    Serial.print(“, Avg BPM=”);

    Serial.print(beatAvg);

  #endif

  if (irValue < 50000) {

    #ifdef debug

      Serial.print(” No finger?”);

    #endif

    lastConnectionTime = millis();

  }

  #ifdef debug

    Serial.println();

  #endif

  if ((millis() – lastConnectionTime > postingInterval) && (beatAvg > 20))

  {   String temp = “GET /api/v1/senzor/TOKEN/input?value=” + String(beatAvg) + ” HTTP/1.1″;

      char param[100];

      temp.toCharArray(param,temp.length()+1);

      if (client.connect(server, 80)) {

        client.println(param);

        client.println(“Host: iot.robofun.ro”);

        client.println(“Connection: close”);

        client.println();

      }

      client.stop();

      lastConnectionTime = millis();

      lastBeat = 0;

  }

}

Pentru a utiliza serviciul Robofun IoT este necesară înregistrarea gratuită.

4

După înregistrare și conectare este necesară definirea unui noi senzor (Adauga senzor).

5

După definirea senzorului este necesar să copiem cheia de autentificare (Token) pentru a o utiliza în program.

3

După configurarea serviciului IoT și încărcarea programului pe placa de dezvoltare putem începe utilizarea dispozitivului și putem observa datele înregistrate on-line:

5

O facilitate foarte interesantă a serviciului Robofun IoT este posibilitatea de partajare a graficelor (Share senzor) sau de integrare a acestora în alte pagini web. Putem astfel să adăugăm paginii web personale sau blog-ului personal un grafic live cu numărul de bătăi ale propriei inimi.

6