Optimizarea programelor în mediul Arduino IDE

Optimizarea procesului de compilare

Mediul Arduino IDE utilizează compilatorul GCC. Pe lângă procesul de compilare propriu-zis, mediul de dezvoltare efectuează o serie de operații (pre-procesare) ce permit implementarea funcțiilor specifice Arduino (de exemplu includerea automată a definițiilor din Arduino.h). Pentru detalierea procesului de compilare se poate parcurge materialul „Arduino Build Process”.

Procesul de compilare este efectuat, invizibil pentru programator, la un anumit nivel de optimizare (-Os sau -O2 în funcție de versiunea mediului de dezvoltare). Pentru detalierea nivelurilor de optimizare recomandăm parcurgerea materialului „Using the GNU Compiler Collection (GCC): Optimize Options”. Modificarea nivelului de optimizare la compilarea programului se poate face la nivel de fișiere de configurare a mediului de dezvoltare sau chiar la nivel de program.

Prin introducerea unor directive de compilare putem modica nivelul de optimizare pentru întregul program:

#pragma GCC optimize („-O0”)

#pragma GCC push_options

void setup() { }

void loop() { }

#pragma GCC pop_options

sau doar pentru o anumită secțiune, funcție sau procedură:

void loop() __attribute__((optimize(„-O0”)));

void loop() { }

 

Optimizarea instrucțiunilor specifice mediului Arduino IDE

Unul din obiectivele principale ale mediului Arduino IDE este înlesnirea accesului programatorului la diversele mecanisme interne ale microcontrolerului. Din aceste motiv s-a efectuat rebotezarea pinilor și tot din același motiv au fost introduse instrucțiuni specifice mediului Arduino IDE de tipul pinMode, digitalRead sau digitalWrite. Din punct de vedere al procesului de învățare și dezvoltare aceste facilități accelerează foarte mult timpul de lucru dar din punctul de vedere al eficienței execuției aduc penalizări destul de mari. Cel mai bun exemplu, prezentat și în materialul „Arduino Is Slow”, este înlocuirea instrucțiunii digitalWrite cu o instrucțiune de modificare a registrului intern ce stochează starea pinului pe care dorim să-l modificăm. Se poate testa diferența dintre timpul de execuție a celor două variante rulând următorul exemplu de program:

void setup()

{

 Serial.begin(9600);

}

void loop()

{

 int initial = 0;

 int final = 0;

 initial = micros();

 for(int i = 0; i < 500; i++)

 {

    digitalWrite(13,HIGH);

    digitalWrite(13,LOW);

 }

 final = micros();

 Serial.print(„Time for digitalWrite(): „);

 Serial.print(final-initial);

 Serial.println(„”);

 initial = micros();

 for(int i = 0; i < 500; i++)

 {

    PORTB |= _BV(PB5);

    PORTB &= ~_BV(PB5);

 

 }

 final = micros();

 Serial.print(„Time for true c command: „);

 Serial.print(final-initial);

 while(1);

}

Programul înlocuiește instrucțiunea digitalWrite cu o scriere în registrul intern al microcontrolerului (pinul 13 este pinul PB5). Rezultatul este destul de convingător:

2

Instrucțiunea digitalWrite nu este singura care poate fi înlocuită în cadrul programelor Arduino pentru a obține performanțe mai bune. O altă instrucțiune ”delicată” ce poate afecta funcționarea unor montaje mai complicate este instrucțiunea shiftOut.  Dacă examinăm fișierul wiring_shift.c din directorul Arduino putem vedea că această funcție se bazează pe instrucțiunea digitalWrite deci poate fi accelerată prin înlocuirea acestei instrucțiuni.

void shiftOut(uint8_t dataPin, uint8_t clockPin,

uint8_t bitOrder, uint8_t val)

{

    uint8_t i;

    for (i = 0; i < 8; i++)  {

         if (bitOrder == LSBFIRST)

             digitalWrite(dataPin, !!(val & (1 << i)));

         else

             digitalWrite(dataPin, !!(val & (1 <<

(7 – i))));

         digitalWrite(clockPin, HIGH);

         digitalWrite(clockPin, LOW);     

    }

}

ATENȚIE!!! Optimizările prezentate conduc la pierderea portabilității programelor între diverse plăci Arduino. Toate indicațiile se aplică direct doar pentru plăcile ce sunt echipate cu un microcontroler ATmega328P (Arduino Uno de exemplu).

Pentru exemplificarea optimizării instrucțiunii shiftOut vom considera următorul montaj bazat pe o placă de dezvoltare Arduino Uno, un registru de deplasare 74HC595 și 8 leduri:

3

Programul propus va implementa un mini joc de lumini ce constă în aprinderea succesivă a câte un led din cele 8. Programul obișnuit (ce utilizează instrucțiunea shiftOut) este:

const int latchPin = 4;

const int clockPin = 3;

const int dataPin = 2;

void setup() {               

  pinMode(latchPin, OUTPUT);

  pinMode(dataPin, OUTPUT); 

  pinMode(clockPin, OUTPUT);

  digitalWrite(latchPin, LOW);

  shiftOut(dataPin, clockPin, MSBFIRST, 0); 

  digitalWrite(latchPin, HIGH);

}

void loop() {

  int afisare = 1;

  while (1) {

   for (int i=0;i<8;i++) {

      digitalWrite(latchPin, LOW);

      shiftOut(dataPin, clockPin, MSBFIRST, afisare << i); 

      digitalWrite(latchPin, HIGH);

      delay(500);

     }

   }

}

Varianta nouă ce nu folosește instrucțiunile shiftOut și digitalWrite este:

void loop() {

  int afisare = 1;

  while (1) {

   for (int i=0;i<8;i++) {

    PORTD |= _BV(PD4);

    for (uint8_t b = 0; b < 8; b++)  {

      if(!!((afisare << i) & (1 << (7 – b)))) PORTD |=

_BV(PD2);

      else PORTD &= ~_BV(PD2);

      PORTD |= _BV(PD3);

      PORTD &= ~_BV(PD3);

    }

    PORTD &= ~_BV(PD4);

    delay(500);

   }

  }

}

Chiar dacă la prima vedere exemplul funcționează la fel, se poate justifica necesitatea modificării codului prin imaginarea situației în care procesul de transmitere serială se repetă de un număr suficient de mare de ori încât produce o întârziere inacceptabilă (de exemplu încascadarea unui număr mare de registre de deplasare).

Utilizarea limbajului de asamblare în Arduino IDE

Ce este ”Inline Assembler”?

Facilitatea de ”Inline Assembler” permite inserarea de cod în limbaj de asamblare în cadrul programelor de C/C++ compilate cu ajutorul GCC (compilatorul utilizat de mediul Arduino IDE). Utilizarea de cod în limbaj de asamblare permite optimizarea unor porțiuni de cod și obținerea unor programe mai mici ca dimensiune (în format binar). Pentru mai multe informații se recomandă consultarea materialului „Inline Assembler Cookbook”.

Inserarea de cod în limbaj de asamblare se face utilizând directiva asm (sau ___asm___) direct în program. De exemplu (instrucțiunea NOP în limbaj de asamblare nu are nici un efect):

asm ( „nop \n”);

Bineînțeles, în cadrul secțiunii de cod în limbaj de asamblare nu vom beneficia de aceleași avantaje și înlesniri ca într-un program obișnuit în limbaj de nivel înalt. Pentru a înțelege mai bine vom reface exemplul clasic din Arduino IDE – programul Blink:

void setup() {

  pinMode(13, OUTPUT);

}

void loop() {

  digitalWrite(13, HIGH);

  delay(1000);

  digitalWrite(13, LOW);  

  delay(1000);     

}

O variantă a acestui program utilizând directiva asm este:

void setup() {

 asm(„sbi  0x04, 0x5 \n”);

}

void loop() {

 asm(„cbi  0x05, 0x5 \n”);  

 delay(1000);                

 asm(„sbi  0x05, 0x5 \n”); 

 delay(1000);           

}

După cum se poate observa instrucțiunile pinMode și digitalWrite, specifice mediului Arduino IDE, au fost înlocuite cu instrucțiuni în limbaj de asamblare: sbi și cbi ce permit setarea sau ștergerea unui bit de la o anumită adresă din memorie. Mai mult decât atâta, nu am mai folosit referința la pinul plăcii Arduino așa cum suntem obișnuiți (pinul 13) ci adrese de memorie la care se află registrele interne de configurare ale pinului (registrul de sens DDRB – adresa 0x04 și registrul de ieșire PORTB – adresa 0x05, în ambele registre am manipulat bitul 5 corespondent pinului PB5 adică pinul 13 al plăcii Arduino). Comparați memoria program ocupată de exemplul original și cel care utilizează directiva asm.

2

Registre interne și echivalarea pinilor între mediul Arduino și arhitectura microcontrolerului ATmega328P

Pentru a ușura lucrul cu pinii I/O mediul Arduino IDE are propria modalitate de identificare a acestora (D0-D13, A0-A5) dar în realitate aceștia sunt organizați în trei porturi a câte 8 pini (PB0-PB7, PC0-PC7, PD0-PD7), echivalența între cele două organizări este reprezentată în diagrama următoare (nu toți pinii sunt prezenți la varianta THT a circuitului ATmega328P):

3

Pentru a putea manipula pinii microcontrolerului (la nivel de limbaj de asamblare) este nevoie să cunoaștem adresele registrelor DDRx (registrul de sens) și PORTx (registrul de ieșire) al portului din care face parte pinul. Pentru mai multe informații despre organizarea internă a registrelor interne este utilă consultarea manualului circuitului ATmega328P.

4 

Exemplu de program: joc de lumini

Presupunem următoarea schemă de interconectare a 8 leduri cu placa de dezvoltare Arduino Uno în mod individual – fiecare led este comandat în mod direct de câte un pin al plăcii de dezvoltare (led 1 – pin 2, led 2 – pin 3…. led 8 – pin 9):

5

Funcționalitatea sistemului va consta în realizarea a două jocuri de lumini. Secțiunea setup a programului va trebui să configureze toți cei opt pini utilizați ca fiind pini de ieșire. Varianta inițială ce utilizează instrucțiunea pinMode este:

void setup() {               

  pinMode(2, OUTPUT);

  pinMode(3, OUTPUT);  

  pinMode(4, OUTPUT); 

  pinMode(5, OUTPUT);   

  pinMode(6, OUTPUT); 

  pinMode(7, OUTPUT); 

  pinMode(8, OUTPUT); 

  pinMode(9, OUTPUT);

  Serial.begin(9600);

}

Prima variantă propusă utilizează instrucțiunea sbi ca și în exemplul precedent (adresa 0x0a este adresa registrului DDRD iar adresa 0x04 adresa registrului DDRB):

asm (

  „sbi 0x0a, 2 \n”

  „sbi 0x0a, 3 \n”

  „sbi 0x0a, 4 \n”

  „sbi 0x0a, 5 \n”

  „sbi 0x0a, 6 \n”

  „sbi 0x0a, 7 \n”

  „sbi 0x04, 0 \n”

  „sbi 0x04, 1 \n”

 );

O variantă mai scurtă este configurarea biților din cele două registre (DDRD și DDRB) simultan utilizând registrul de uz general R26 ca intermediar pentru transmiterea valorii către cele două registre:

asm (

    „ldi r26, 0b11111100 \n”

    „out 0x0a, r26 \n”

    „ldi r26, 0b00000011 \n”

    „out 0x04, r26 \n”

    : : : „r26”

    );

Utilizarea unui registru de uz general trebuie semnalizată compilatorului pentru a nu apărea suprapuneri în utilizarea registrelor – ultima linie din cod, a se vedea și materialul „Arduino Inline Assembly Tutorial #3 (Clobbers)”.

Prima variantă de joc de lumini va aprinde alternativ la un interval de 1 secundă ledurile de rang impar și ledurile de rang par (led 1, led 3, led 5, led 7 – led 2, led 4, led 6, led 8). Secțiunea loop (utilizând cod Arduino) este:

void loop() {

  for (int i=2; i<10; i++) {

      if ((i%2)==0) digitalWrite(i,HIGH);

      else digitalWrite(i,LOW);

   }

   delay(1000); 

   for (int i=2; i<10; i++) {

      if ((i%2)==0) digitalWrite(i,LOW);

      else digitalWrite(i,HIGH);

   }

   delay(1000);

}

Transpunerea în limbaj de asamblare este:

void loop() {

asm (

      „sbi 0x0b,2 \n”

      „cbi 0x0b,3 \n”

      „sbi 0x0b,4 \n”

      „cbi 0x0b,5 \n”     

      „sbi 0x0b,6 \n”

      „cbi 0x0b,7 \n”

      „sbi 0x05,0 \n”

      „cbi 0x05,1 \n”

      „ldi r25, 0x7F \n”

      „wait1: ldi r26, 0xFF \n”

      „wait2: ldi r27, 0xFF \n”

      „wait3: dec r27 \n”

      „nop \n”

      „brne wait3 \n”

      „dec r26 \n”

      „brne wait2 \n”

      „dec r25 \n”

      „brne wait1 \n”     

      „cbi 0x0b,2 \n”

      „sbi 0x0b,3 \n”

      „cbi 0x0b,4 \n”

      „sbi 0x0b,5 \n”     

      „cbi 0x0b,6 \n”

      „sbi 0x0b,7 \n”

      „cbi 0x05,0 \n”

      „sbi 0x05,1 \n”

      „ldi r25, 0x7F \n”

      „wait1b: ldi r26, 0xFF \n”

      „wait2b: ldi r27, 0xFF \n”

      „wait3b: dec r27 \n”

      „nop \n”

      „brne wait3b \n”

      „dec r26 \n”

      „brne wait2b \n”

      „dec r25 \n”

      „brne wait1b \n”

      : : : „r25” , „r26”, „r27”

  ); 

Cea de a doua variantă de joc de lumini va aprinde unul câte unul (pornind de la led-ul 1 până la led-ul 8) toate led-urile și apoi le va stinge în mod similar (în ordine inversă). Operația de aprindere sau stingere a unui led se va efectua la un interval de 500 milisecunde. Secțiunea loop (utilizând cod Arduino) este:

void loop() {

   for (int i=2;i<10;i++) {

     digitalWrite(i,HIGH);

     delay(500); }

   for (int i=9;i>1;i–) {

     digitalWrite(i,LOW);

     delay(500);

   }

  }

Transpunerea în limbaj de asamblare este:

void loop() {

asm (

      „start: sbi 0x0b,2 \n”

      „rcall wait \n”

      „sbi 0x0b,3 \n”

      „rcall wait \n”

      „sbi 0x0b,4 \n”

      „rcall wait \n”

      „sbi 0x0b,5 \n”

      „rcall wait \n”     

      „sbi 0x0b,6 \n”

      „rcall wait \n”

      „sbi 0x0b,7 \n”

      „rcall wait \n”

      „sbi 0x05,0 \n”

      „rcall wait \n”

      „sbi 0x05,1 \n”

      „rcall wait \n”     

      „cbi 0x0b,2 \n”

      „rcall wait \n”

      „cbi 0x0b,3 \n”

      „rcall wait \n”

      „cbi 0x0b,4 \n”

      „rcall wait \n”

      „cbi 0x0b,5 \n”

      „rcall wait \n”     

      „cbi 0x0b,6 \n”

      „rcall wait \n”

      „cbi 0x0b,7 \n”

      „rcall wait \n”

      „cbi 0x05,0 \n”

      „rcall wait \n”

      „cbi 0x05,1 \n”

      „rcall wait \n”     

      „rjmp start \n”

      „wait: ldi r25, 0x3F \n”

      „wait12: ldi r26, 0xFF \n”

      „wait22: ldi r27, 0xFF \n”

      „wait32: dec r27 \n”

      „nop \n”

      „brne wait32 \n”

      „dec r26 \n”

      „brne wait22 \n”

      „dec r25 \n”

      „brne wait12 \n”

      „ret \n”

      : : : „r25” , „r26”, „r27”

  );

}

Ambele jocuri de lumini utilizează instrucțiunile sbi și cbi pentru manipularea pinilor I/0 (pentru o listă completă a instrucțiunilor în limbaj de asamblare se recomandă parcurgerea materialului „AVR Instruction Set Manual”). Funcția de temporizare delay din mediul Arduino IDE este înlocuită cu o succesiune de bucle ce realizează o întârziere aproximativă (în a doua variantă această succesiune este implementată ca o subrutină).

Cum să realizăm un gateway LoRaWAN

Acoperirea rețelelor LoRaWAN la noi în țară este destul de scăzută (atât a rețelelor comerciale cât și a rețelei TTN). Din acest motiv, pentru a testa un sistem IoT LoRaWAN (ca cel descris în „Cum să realiză un sistem IoT LoRaWAN”) uneori este necesară realizarea unui sistem gateway LoRaWAN propriu. Sistemele profesionale de acest tip sunt destul de scumpe reprezentând o variantă de lux, a se vedea studiul comparativ a celor de la LorIoT.

O altă variantă este construirea unui sistem gateway propriu utilizând o placă de dezvoltare de genul Raspberry Pi. Problema în acest caz este generată de complexitatea modulației radio LoRa – sistemele gateway fiind sisteme care ascultă frecvențe radio multiple simultan (sunt denumite și concentratoare). Din acest motiv un modul radio LoRa obișnuit nu poate echipa un sistem gateway LoRaWAN fiind necesar un modul de tip concetrator, de exemplu: iC880A – LoRaWAN Concentrator 868MHz – modul cel mai adesea folosit în sisteme gateway LoRaWAN bazate pe Raspberry Pi.

2

Pentru mai multe detalii despre cum puteți construi un sistem gateway LoRaWAN bazat pe un modul de tip concentrator puteți consulta și materialele:

Chiar dacă prețul unui modul concentrator este mai mic decât a unui gateway profesional construirea unui astfel de sistem implică totuși un buget destul de mare.

Singura alternativă, accesibilă ca buget, este realizarea unui sistem gateway LoRaWAN de tipul One Channel (sau Single Channel). Adică vom un utiliza un modul radio LoRa obișnuit împreună cu o placă de tipul Raspberry Pi pentru realizarea unui sistem gateway. Dezavantajul unui astfel de gateway este faptul că ascultă pe o singură frecvență radio neputând comunica simultan cu mai multe sisteme IoT LoRaWAN. Acest tip de sisteme sunt considerate sisteme de tip ”forwarder” (Single Channel Forwarder) neavând o funcționalitate gateway LoRaWAN completă. Totuși, un astfel de sistem poate fi utilizat în locații izolate (fără acoperire LoRaWAN) pentru a testa comunicația LoRaWAN. Rețeaua TTN permite accesul acestor sisteme în rețea dar nu încurajează și nu asigură suport pentru ele fiind considerate compatibile dar neconforme cu specificațiile LoRaWAN.

Pentru implementare vom utiliza o placă de dezvoltare Raspberry Pi 3 și un hat LoRa/GPS. Testele au fost realizate sub Raspbian 9 (stretch) Lite, kernel 4.9.41-v7+.

Placa Raspberry Pi trebuie să aibă protocolul SPI activat (cu ajutorul utilitarului raspi-config) și pachetul wiringpi instalat.

$sudo raspi-config

4

$sudo apt-get update

$sudo apt-get install wiringpi

Pentru a implementa funcționalitatea de redirecționare a comunicației LoRa către platforma TTN vom utiliza software-ul single_chan_pkt_fwd.

$ wget https://github.com/tftelkamp/single_chan_pkt_fwd/archive/master.zip

$ unzip master.zip

$ cd single_chan_pkt_fwd-master

$ nano main.cpp

În fișierul main.cpp vom personaliza următoarele linii:

int ssPin = 6;

int dio0  = 7;

int RST   = 0;

sf_t sf = SF7;

uint32_t  freq = 868100000;

// opțional, dacă dorim să declarăm

// poziția și altitudinea sistemului

float lat=…;

float lon=…;

int   alt=…;

static char platform[24] = „Single Channel Gateway”;

static char email[40] = „…”;

static char description[64] = „…”;

#define SERVER1 „52.169.76.203”

#define PORT 1700

Salvăm (CTRL+O, CTRL+X), compilăm programul și îl lansăm în execuție:

$ make

$ sudo ./single_chan_pkt_fwd

5

Următorul pas necesită înregistrarea sistemului gateway în cadrul platformei TTN. În momentul înregistrării sistemului gateway este foarte important să bifăm opțiunea ”I’m using the legacy packet forwarder” (nu se poate modifica ulterior) și să copiem Gateway ID din consola ssh în consola de înregistrare.

6

După terminare înregistrării vom putea observa în consola TTN conexiunea dintre sistem și platforma TTN (Gateway Overview):

7

Pentru a face ca programul single_chan_pkt_fwd să ruleze automat la repornirea sistemului de operare adăugăm următoarea linie în fișierul /etc/rc.local (înainte de linia cu exit 0):

sudo /home/pi/single_chan_pkt_fwd-master/single_chan_pkt_fwd &

presupunând că am salvat și realizat compilarea în directorul utilizatorului pi.

Pentru mai multe informații legate de realizarea unui gateway LoRaWAN TTN Single Channel se pot consulta și următoarele materiale:

Cum să realizăm un sistem IoT LoRaWAN

În cadrul proiectului ”LoRa meets Robofun IoT” am văzut cum putem realiza un sistem IoT utilizând comunicația radio LoRa. Utilizând module radio LoRa putem transmite date la mare distanță dar pentru implementarea unui sistem IoT este necesară implementarea atât a modulului de achiziție (sau acționare) cât și a sistemului de tip gateway ce face legătura cu rețeaua Internet și cu serviciile cloud IoT. Specificațiile LoRaWAN permit implementare unor rețele radio LoRa standardizate astfel încât sistemele gateway să permită conectarea dispozitivelor IoT după un set de reguli larg acceptate. Realizarea unui sistem IoT LoRaWAN presupune realizare unui sistem de achiziție / acționare care respectă acest set de reguli și se conectează la o infrastructură de gateway-uri deja existentă (nu mai este nevoie să realizăm și să operăm sistemul gateway). Există mai multe rețele de gateway-uri LoRaWAN dar în cadrul acestui proiect vom arăta cum putem realiza un sistem ce folosește rețeaua TTN (The Things Network). Accesul în rețeaua TTN este gratuit deoarece se bazează pe gateway-uri particulare partajate între utilizatorii rețelei. Tot ce trebuie să faceți este să verificați dacă vă aflați în aria de acoperire a unui sistem gateway TTN.

Pentru sistemul IoT vom utiliza o placă de dezvoltare Arduino Uno și un shield Dragino LoRa echipat cu un modul radio LoRa în frecvență de 868MHz. Pentru partea de achiziție vom exemplifica măsurarea temperaturii utilizând un senzor brick conectat la pinul analogic A0 al plăcii de dezvoltare.

3

Pentru implementarea comunicației LoRaWAN vom utiliza biblioteca Arduino-LMIC. Testele au fost realizate utilizând Arduino IDE 1.8.3 și versiunea 1.5.0+arduino-1 a bibliotecii. Programul pleacă de la exemplul ttn-abp al bibliotecii în care vom efectua o serie de mici modificări. În primul rând trebuie să înregistrăm sistemul pe platforma TTN pentru a obține datele de autentificare în rețea:

static const PROGMEM u1_t NWKSKEY[16] = { … };

static const u1_t PROGMEM APPSKEY[16] = { … };

static const u4_t DEVADDR = … ;

Înregistrarea presupune crearea unui cont de utilizator, definirea unei aplicații (Applications) și, în cadrul aplicației, definirea unui dispozitiv (Device). În secțiunea se setări (Settings) a noului dispozitiv trebuie aleasă metoda ABP de activare și debifată opțiunea Frame Counter Checks. Tot în cadrul acestei secțiuni se regăsesc datele de autentificare în rețeua TTN. Pentru mai multe detalii legate de definirea aplicației și dispozitivului în rețeua TTN se poate consulta și materialul „LoRaWAN IoT with Arduino Uno, Dragino v1.3 & TheThingsNetwork”.

4

Tot în secțiunea de inițializare a exemplului se va șterge declarația mesajului mydata (se va defini din nou în program sub o altă formă) și se va modifica intervalul de postare a mesajelor (postarea la 1 minut este destul de agresivă pentru politica de utilizare a rețelei TTN).

const unsigned TX_INTERVAL = 3600;

Shield-ul Dragino LoRa necesită următoarea modificare în structura de definire a pinilor utilizați:

const lmic_pinmap lmic_pins = {

    .nss = 10,

    .rxtx = LMIC_UNUSED_PIN,

    .rst = 9,

    .dio = {2, 6, 7},

};

Ultima modificare adusă exemplului ttn-abp este rescrierea procedurii do_send pentru a trasmite valoare achiziționată de la brick-ul de temperatură în locul mesajului text predefinit. După cum se poate observa se va transmite valoarea returnată de funcția analogRead, prelucrarea numerică pentru a obține valoarea temperaturii se va face în sistemul cloud TTN.

void do_send(osjob_t* j){

    static uint8_t mydata[2];

    int reading = analogRead(A0);

    mydata[0] = highByte(reading);

    mydata[1] = lowByte(reading);

    if (LMIC.opmode & OP_TXRXPEND) {

        Serial.println(F(„OP_TXRXPEND, not sending”));

    } else {

        LMIC_setTxData2(1, mydata, sizeof(mydata), 0);

        Serial.println(F(„Packet queued”));

    }

}

După punerea în funcțiune a sistemului, și dacă vă aflați în aria de acoperire a unui gateway TTN, în consola TTN vor începe să apară valorile transmise de acesta (secțiunea Application Data). După cum se poate observa datele transmise sunt sub forma unui șir de valori în hexazecimal (2 octeți – 16 biți).

5

Pentru a transforma datele primite într-o formă mai ușor de înțeles se va scrie o funcție de decodare (în secțiunea Payload Formats / decoder). Această funcție va avea și rolul de a calcula temperatura echivalentă valorii achiziționate. După implementarea acestei funcții vom putea vedea în secțiunea de Application Data valoarea efectivă a temperaturii.

function Decoder(bytes, port) {

  var decoded = (((((bytes[0]<<8)|bytes[1])*5.0)/1024.0)-0.5)*100;

  return { value:decoded };

}

6

Atenție!!! Platforma TTN nu este o platformă IoT – nu stochează datele preluate de la sistemele LoRaWAN. Datele se pot observa în consolă doar dacă sunt transmise atunci când consola este deschisă. Platforma TTN permite în schimb transmiterea datelor primite prin rețeaua LoRaWAN către alte platforme online inclusiv platforme IoT. În secțiunea Integrations se pot defini diverse mecanisme automate de redirectare a datelor către sisteme precum Cayenne, OpenSensors sau IFTTT. Vom explica în cele ce urmează cum putem transmite datele către serviciul IFTTT care ne va trimite apoi valoarea temperaturii prin email. Bineînțeles, multitudinea de opțiuni oferite de platforma IFTTT permite redirectarea datelor către un serviciu IoT, postarea pe o rețea de socializare sau interacțiunea directă cu alte dispozitive IoT.

Definirea mecanismului automat de trimitere a datelor către serviciului IFTTT presupune adăugarea unui integrator de tipul IFTTT Maker (add integration / secțiunea Integrations). Conexiunea între cele două servicii (TTN și IFTTT) se realizează pe baza Event Name (trebuie să fie identic cu numele declanșatorului IFTTT) și Key (cheie de autentificare oferită de obiectul IFTTT Webhooks).

7

În cadrul platformei IFTTT se va realiza o regulă ce va avea declanșator serviciul Webhooks (Event Name trebuie să fie identic cu cel definit în platforma TTN) și ca efect transmiterea unui email.

8

La fiecare valoare a temperaturii transmisă de sistemul nostru vom primi un email de forma:

9

Pentru mai multe variante de realizare a unui sistem IoT LoRaWAN se pot consulta și următoarele materiale:

LoRa meets Robofun IoT

Modulele radio LoRa oferă posibilitatea de a transmite date la distanță mare (sute de metri sau chiar kilometri) utilizând module electronice de cost redus și cu un consum de energie scăzut. Acest lucru constituie o metodă eficientă pentru a extinde aria de acoperire pentru rețelele IoT fără fir. Chiar dacă semnalul WiFi are o acoperire limitată fiind influențat de puterea dispozitivelor de tip AP și de mediul în care operează (câmp deschis, locuințe sau birouri) există posibilitatea să extindem aria de acoperire radio a unei rețele IoT prin intermediul comunicațiilor ISM iar soluțiile LoRa oferă o variantă foarte bună cost / arie de acoperire.

Pentru a implementa o soluție LoRa în vederea extinderii ariei de acoperire IoT vom implementa un sistem gateway ce va realiza transferul datelor provenite de la modulele IoT către un sistem specific IoT și anume Robofun IoT. Modulul gateway propus se bazează pe placa de dezvoltare NodeMCU ce oferă conectivitate WiFi și un modul LoRa RFM96W în bandă de 433MHz. Conexiuniile între placa de dezvoltare și modulul radio sunt prezentate în diagrama următoare:

2.png

Modulul radio RFM96W se interconectează cu placa de dezvoltare prin intermediul magistralei SPI:

  • Pinul SCK al modulului se conectează la pinul D5 (GPIO14 – HSCLK);
  • Pinul MISO se conectează la pinul D6 (GPIO12 – HMISO);
  • Pinul MOSI se conectează la pinul D7 (GPIO13 – HMOSI);
  • Pinul CS se conectează la pinul D2;
  • Pinul RST se conectează la pinul D3;
  • Pinul G0 (INT) se conectează la pinul D1;
  • Vin și GND la pinii 3.3V și GND ai plăcii de dezvoltare.

Pentru ca placa să poată transmite prin Internet datele către serviciul Robofun IoT este necesară înregistrarea gratuită pe platformă:

3

După înregistrare și conectare este necesară definirea (Adauga senzor) a trei senzori: Temperatura, Umiditate și Nivel baterie, pentru a putea transmite datele primite de la sistemul IoT către platforma online. După definirea fiecărui senzor este necesar să copiem cheia de autentificare (Token) pentru a fi utilizată în program.

4

5

Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.3 (pentru instalarea plăcii NodeMCU sub Arduino IDE se poate vedea materialul „Quick Start to Nodemcu (ESP8266) on Arduino IDE”), extensia esp8266 versiunea 2.3.0 și biblioteca RadioHead 1.7.9. În cadrul programului trebuie personalizate datele de conectare la rețeaua WiFi (ssid și password) precum și cheile de autentificare pentru cele trei canale IoT (SENSOR_TOKEN1, SENSOR_TOKEN2 și SENSOR_TOKEN3).

#include <SPI.h>

#include <RH_RF95.h>

#include <ESP8266WiFi.h>

#include <ESP8266HTTPClient.h>

const char* ssid = „…”;

const char* password = „…”;

#define RFM95_CS D2

#define RFM95_RST D3

#define RFM95_INT D1

#define RF95_FREQ 434.0

RH_RF95 rf95(RFM95_CS, RFM95_INT);

#define LED D0

void setup() {

  pinMode(LED, OUTPUT);

  digitalWrite(LED,HIGH);    

  pinMode(RFM95_RST, OUTPUT);

  digitalWrite(RFM95_RST, HIGH);

  Serial.begin(9600);

  delay(100);

  Serial.println();

  Serial.println();

  Serial.println(„Gateway Module starting…”);

  Serial.print(„Connecting to „);

  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

      delay(500);

      Serial.print(„.”);

    }

    Serial.println(„”);

    Serial.println(„WiFi connected”);

  digitalWrite(RFM95_RST, LOW);

  delay(10);

  digitalWrite(RFM95_RST, HIGH);

  delay(10);

  while (!rf95.init()) {

    Serial.println(„LoRa radio init failed”);

    while (1);

  }

  Serial.println(„LoRa radio init OK!”);

  if (!rf95.setFrequency(RF95_FREQ)) {

    Serial.println(„setFrequency failed”);

    while (1);

  }

  Serial.print(„Set Freq to: „);

  Serial.println(RF95_FREQ);

}

typedef struct {float temperature; float humidity;

float bat_voltage;} ParametriiRX;

ParametriiRX parametrii;

Ledul de pe placa de dezvoltare (conectat pe pinul D0) se va aprinde pe perioada recepției de mesaje LoRa. Toate datele primite prin intermediul LoRa vor fi postate pe platforma Robofun IoT. Sistemul va raporta și în consola serială toate mesajele recepționate.

6

void loop() {

  if (rf95.available())  {

    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];

    uint8_t len = sizeof(buf);

    if (rf95.recv(buf, &len)){

      digitalWrite(LED, LOW);

      RH_RF95::printBuffer(„Received: „, buf, len);

      parametrii = *(ParametriiRX*)buf;

      Serial.print(„Temperature: „);

      Serial.println(parametrii.temperature);

      Serial.print(„Humidity: „);

      Serial.println(parametrii.humidity);

      Serial.print(„Battery Voltage: „);

      Serial.println(parametrii.bat_voltage);

      Serial.print(„RSSI: „);

      Serial.println(rf95.lastRssi(), DEC);

      digitalWrite(LED, HIGH);

      String SENSOR_TOKEN1=”…”;

      String SENSOR_TOKEN2=”…”;

      String SENSOR_TOKEN3=”…”;

      HTTPClient http;

      String data = String(„http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN1 + „/input?value=” + String(parametrii.temperature, DEC);

      http.begin(data);

      int httpCode = http.GET();

      http.end();

      data = String(„http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN2 + „/input?value=” + String(parametrii.humidity, DEC);

      http.begin(data);

      httpCode = http.GET();

      http.end();

      data = String(„http://iot.robofun.ro/api/v1/senzor/&#8221;) + SENSOR_TOKEN3 +  „/input?value=” + String(parametrii.bat_voltage, DEC);

      http.begin(data);

      httpCode = http.GET();

      http.end();    }

    else    {

      Serial.println(„Receive failed”);    }

  }

}

Modulul LoRa ce va beneficia de extinderea ariei de comunicație (se poate afla la sute de metri, sau chiar kilometri în spațiu deschis, de aria de acoperire WiFi) se bazează pe placa de dezvoltare Feather M0 RFM95 433MHz LoRa – placă ce combină puterea unui microcontroler ARM Cortex-M0+ ATSAMD21 (la fel ca și plăcile Arduino M0/Zero) cu conectivitatea unui modul radio LoRa. Placa de dezvoltare va transmite către modulul gateway date preluate de la un senzor digital I2C de temperatură și umiditate Si7021 precum și nivelul bateriei proprii (unul dintre avantajele majore ale plăcilor Feather este posibilitatea de alimentare mobilă de la un acumulator LiPo de 3.7V).

Pentru mai multe informații legate de utilizarea plăcii Feather M0 RFM95 puteți consulta și materialul: „Adafruit Feather M0 Radio with LoRa Radio Module”.

Schema de interconectare între senzor și placa de dezvoltare este specifică unei magistrale I2C (pinii SDA și SCL conectați între senzor și placa de dezvoltare, alimentarea senzorului se va face la 3.3V):

7

Programul a fost dezvoltat și testat utilizând Arduino IDE 1.8.3, extensia Adafruit SAMD Boards 1.0.19 și bibliotecile RadioHead 1.7.9, Adafruit Si7021.

Decomentarea directivei debug va permite observarea funcționării modulului în consola serială.

//#define debug

7.5

#include <SPI.h>

#include <RH_RF95.h>

#define VBATPIN A7

#include <Adafruit_Si7021.h>

Adafruit_Si7021 sensor = Adafruit_Si7021();

#define RFM95_CS 8

#define RFM95_RST 4

#define RFM95_INT 3

#define RF95_FREQ 434.0

RH_RF95 rf95(RFM95_CS, RFM95_INT);

void setup() {

  pinMode(RFM95_RST, OUTPUT);

  digitalWrite(RFM95_RST, HIGH);

  sensor.begin();

  #ifdef debug

    while (!Serial);

    Serial.begin(9600);

    delay(100);

    Serial.println(„LoRa Sensor Module starting…”);

  #endif

  digitalWrite(RFM95_RST, LOW);

  delay(10);

  digitalWrite(RFM95_RST, HIGH);

  delay(10);

  while (!rf95.init()) {

    #ifdef debug

      Serial.println(„LoRa radio init failed”);

    #endif

    while (1);

  }

  #ifdef debug

    Serial.println(„LoRa radio init OK!”);

  #endif

  if (!rf95.setFrequency(RF95_FREQ)) {

    #ifdef debug

      Serial.println(„setFrequency failed”);

    #endif

    while (1);

  }

  #ifdef debug

    Serial.print(„Set Freq to: „);

    Serial.println(RF95_FREQ);

  #endif

  rf95.setTxPower(23, false);

}

typedef struct {float temperature; float humidity;

float bat_voltage;} ParametriiTX;

ParametriiTX parametrii;

void loop() {

  float voltage = analogRead(VBATPIN);

  voltage *= 2;   

  voltage *= 3.3;

  voltage /= 1024;

  parametrii.bat_voltage = voltage;

  parametrii.temperature = sensor.readTemperature();

  parametrii.humidity = sensor.readHumidity();

  #ifdef debug

    Serial.print(„Sample OK: „);

    Serial.print(parametrii.temperature);

   Serial.print(” *C, „);

    Serial.print(parametrii.humidity);

    Serial.println(” %”);

    Serial.print(„VBat: ” ); Serial.println(voltage);

    Serial.println(„Sending to Gateway Module”);

    Serial.println(„Sending…”); delay(10);

  #endif

  RH_RF95::printBuffer(„Sending: „, (uint8_t*)&parametrii, sizeof parametrii);

  #ifdef debug

    Serial.println(„Sending…”); delay(10);

  #endif

  rf95.send((uint8_t *)&parametrii, sizeof parametrii);

  #ifdef debug

    Serial.print(„Waiting for packet to complete…”);

delay(10);

  #endif

  rf95.waitPacketSent();

  #ifdef debug

    Serial.println(„done.”); delay(10);

  #endif

  delay(100);

  rf95.sleep();

  delay(60000);

}

După programarea și punerea în funcțiune a celor două sisteme se pot observa datele înregistrate în cadrul platformei Robofun IoT (capturile de ecran de mai jos reprezintă date înregistrate de sistemul de test pentru temperatură și umiditate).

8

9

Bineînțeles, sistemul LoRa poate deservi atât sisteme de achiziție (temperatură, umiditate, presiunea în diverse conducte, consumul de energie electrică, nivelul radiației solare etc.) dar și sisteme de acționare (chiar dacă nu a fost exemplificată această parte este posibil să comandăm de la distanță diverse mecanisme de închidere / deschidere, motoare etc).

Utilizarea bibliotecii LUFA

LUFA (Lightweight USB Framework for AVRs) este o bibliotecă ce implementează stiva USB pentru microcontrolerele Atmel AVR ce dețin un port hardware USB (AVR USB Controller) permițând implementarea de dispozitive USB variate: Android Accessory Host, Audio In Device, MIDI Device, Generic HID Device, Joystick Device, Keyboard Device, Printer Host, Virtual Serial Device etc. Biblioteca LUFA este componenta software care stă la baza funcționării majorității plăcilor Arduino: în cazul Arduino Uno / Arduino Mega biblioteca este utilizată de firmware-ul ce permite microcontrolerelor ATmega8U2/16U2 să realizeze comunicația USB-to-serial și să încarce programul în memoria microcontrolerului central; în cazul Arduino Leonardo / Arduino Micro, plăci echipate cu un microcontroler ATmega32U4, biblioteca este utilizată direct de bootloader-ul plăcii permițând atât comunicația USB-to-serial cât și încărcarea programului.

Plăcile de dezvoltare Arduino nu sunt singurele aplicații embedded ce utilizează biblioteca LUFA, mai jos sunt trecute în revistă câteva dintre multele proiecte bazate pe această bibliotecă:

The Smart Card Detective este un sistem ce permite interceptarea, înregistrarea și modificarea comunicației dintre un smartcard și un cititor de carduri (inclusiv de tip EMV).

1

RFM12B USB light – Stick un dispozitiv de comunicație pentru PC-uri bazat pe microcontrolerul ATmega32U4 și modulul radio FSK RFM12B.

The Finch este un robot educațional gândit pentru a fi folosit în explicarea timpurie a noțiunilor de programare, robotică, sisteme de automatizare.

AD9833-based USB Function Generator este un sistem programabil ce permite generarea unor semnale analogice.

FlySight este un sistem ce permite înregistrarea pe un card de memorie a coordonatelor GPS corelate cu viteza și accelerația pe 3 axe.

2

În cadrul materialului de față vă propunem detalierea a două sisteme bazate pe biblioteca LUFA: (1) transformarea unei plăci Arduino Uno într-un dispozitiv MIDI prin reprogramarea microcontrolerului 16U2 și (2) un cititor de carduri bazat pe microcontrolerul ATmega32U4.

Realizarea unui dispozitiv de tip MIDI

Dispozitivele MIDI sunt dispozitive digitale capabile să genereze sunete sau să transmită comenzi de generare a acelor sunete către alte dispozitive digitale (putem vorbi de un sintetizator sau de o claviatură conectată la un sistem de calcul ce rulează un software de sinteză a notelor). În cadrul proiectului nostru vom transforma o placă de dezvoltare Arduino Uno într-un dispozitiv ce se va conecta la PC prin intermediul unei conexiuni USB și va transmite comenzi specifice MIDI către un software specializat. Pentru a putea modifica comportamentul USB al plăcii (tipul de periferic USB văzut de sistemul de operare) este necesar să modificăm firmware-ul microcrontrolerului 16U2 (prezent pe Uno R3, versiunile anterioare se bazează pe microcontrolerul 8U2). Pentru a reprograma microcontrolerul 16U2 există două posibilități:

3

  • prin intermediul conexiunii USB utilizând un software de programare DFU;
  • utilizând portul ICSP al microcontrolerului 16U2/8U2 (imaginea de mai sus surprinde poziția portului ICSP la diverse variante de plăci Arduino Uno) utilizând un programator ISP (USBtiny – modalitate utilizată în explicațiile următoare – sau USBasp) sau o altă placă Arduino Uno pe post de programator ISP.

Firmware-ul microcontrolerului 16U2/8U2 va fi rescris cu Moco for LUFA, software ce va transforma placa Arduino Uno într-un dispozitiv MIDI – se va download-a arhiva MocoLUFA-110123.tgz și se va utiliza binarul MIDI.hex din directorul HEX. Binarul este compilat pentru microcontrolerul 8U2 dar poate fi utilizat și pentru 16U2 (va funcționa pe orice variantă de placă Arduino Uno). Utilizând utilitarul avrdude se va încărca binarul utilizând comanda (programator USBtinyISP, placă de dezvoltare Arduino Uno R3 echipată cu varianta 16U2):

avrdude -c usbtiny -p m16u2 -U flash:w:MIDI.hex

După încărcare noului firmware, la conectarea plăcii sistemul de operare o va identifica ca dispozitiv MIDI (captură de ecran pe pagina următoare) și va instala driverul implicit (nu este necesar un driver special).

4

Atenție!!! După modificarea firmware-urului microcontrolerului 16U2/8U2 placa Arduino Uno va funcționa doar ca dispozitiv MIDI, nu va mai fi posibilă utilizarea ca o placă de dezvoltare obișnuită – nu va mai fi posibilă reprogramarea acesteia. Pentru revenirea la funcționalitatea inițială se va reîncărca firmware-ul original al plăcii pe microcontrolerul 16U2/8U2 utilizând comanda (firmware-ul original se găsește în directorul de instalare al mediului Arduino IDE în subdirectorul hardware\arduino\avr\firmware\atmegaxxu2):

avrdude -c usbtiny -p m16u2 \

-U flash:w:Arduino-COMBINED-dfu-usbserial-atmega16u2-Uno-Rev3.hex

Din aceste motiv încărcarea programului pe placa Arduino trebuie făcută înainte de modificarea firmware-ului pentru microcontrolerul 16U2/8U2. Programul utilizat va trimite pe serială diverse note conform protocolului MIDI – exemplu preluat din [*]:

void setup() {

Serial.begin(31250);

}

void loop() {

for (int note = 0x1E; note < 0x5A; note ++) {

noteOn(0x90, note, 0x45);

delay(100);

noteOn(0x90, note, 0x00);

delay(100); }

}

void noteOn(int cmd, int pitch, int velocity) {

Serial.write(cmd);

Serial.write(pitch);

Serial.write(velocity);}

Pentru a verifica funcționarea dispozitivului MIDI se poate utiliza orice software specializat, de exemplu: MIDI-OX – utilitar MIDI gratuit. Captura de ecran următoare prezintă modul de configurare a dispozitivului MIDI și captura comenzilor emise de dispozitivul MIDI Arduino utilizând MIDI-OX.

5

 

Plăci de dezvoltare bazate pe microcontrolerul ATmega32U4

Următoarea generație de plăci de dezvoltare Arduino înlocuiește combinația de microcontroler AVR (ATmega328P – Arduino Uno/Ethernet/Mini/Nano sau ATmega2560 – Arduino Mega) și microcontroler AVR cu suport USB (ATmega16U2 sau ATmega8U2), utilizat pentru conversia USB-to-serial necesară programării, cu un singur microcontroler ce include atât performanțele necesare funcționării plăcii de dezvoltare cât și conectivitatea necesară conexiunii USB. Noua generație de plăci de dezvoltare Arduino utilizează microcontrolerul ATmega32U4 pentru realizarea ambelor sarcini, un exemplu de astfel de placă de dezvoltare este Arduino Leonardo – placă de dezvoltare cu formă și performanțe similare cu placa Arduino Uno dar care este echipată cu un singur microcontroler ATmega32U4 ce rulează atât bootloader-ul de încărcare USB cât și programul scris de utilizator. La fel ca și în cazul microcontrolerului ATmega16U2/8U2 (ce echipează Arduino Uno) firmware-ul ce rulează pe microcontrolerul ATmega32U4 se bazează pe biblioteca LUFA. Din acest motiv placa de dezvoltare Arduino Leonardo permite utilizatorului, spre deosebire de placa Arduino Uno, să scrie programe utilizând mediul Arduino IDE care să transforme placa de dezvoltare într-un dispozitiv de tip tastatură sau mouse fără a suprascrie firmware-ul original al plăcii (facilitatea de a se comporta ca un dispozitiv HID USB este deja inclusă în firmware).

Placa Arduino Leonardo nu este singura placă din familia Arduino care este echipată cu microcontrolerul ATmega32U4, alte exemple de astfel de plăci sunt: Arduino Micro, Arduino Yun, Arduino Robot, Arduino Esplora. O placă dezvoltare ce trebuie de asemenea menționată este Teensy 2.0 deoarece este una dintre cele mai mici plăci de dezvoltare bazate pe ATmega32U4 fiind alegerea cea mai bună pentru proiectele ce au constrângeri legate de dimensiune și din care placa de dezvoltare nu se mai recuperează. Un alt avantaj major al acestei plăci de dezvoltare este dat de componentele software suplimentare față de o placă de dezvoltare Arduino Leonardo dar compatibile cu mediul Arduino IDE. Pe baza acestei plăci vom prezenta realizarea dispozitivul următor.

7

 

Realizarea unui dispozitiv de tip Cititor de Carduri

Dispozitivul prezentat este bazat pe exemplul de la adresa. Plecând de la exemplul Mass Storage conținut în biblioteca LUFA, sistemul implementează un dispozitiv USB de citire a unui card de memorie SD (un card reader) combinând exemplul cu biblioteca de lucru cu un card de memorie. Schema de interconectare a componentelor este prezentată în imaginea alăturată. Pentru compilarea / recompilarea proiectului este necesară instalarea mediului WinAVR și se realizează prin comanda make all în consolă de comandă în directorul LowLevelMassStorage+SD al proiectului. Fișierul MassStorage.hex rezultat în urma compilării proiectului se încarcă în memoria microcontrolerului ATmega32U4 utilizând un programator ICSP (inclusiv o placă Arduino Uno pe post de programator ISP). După încărcarea noului firmware, placa de dezvoltare va fi văzută de sistemul de operare ca un dispozitiv de stocare și se vor putea efectua operațiile de bază cu cardul de memorie (citire, scriere, formatare).

8

Un proiect similar (o placă Teensy 2.0 programată să se comporte ca un dispozitiv de stocare) se găsește la adresa. Acest proiect utilizează exact același program ca și exemplul precedent dar explică în mod detaliat modul de conectare a unui adaptor SD (Teensy SD adaptor) și modul în care tot ansamblul de componente poate fi integrat într-o carcasă USB astfel încât să rezulte o formă finală elegantă (imagine alăturată). O idee interesantă lansată de acest proiect este posibilitatea de a realiza dispozitive mixte (HID+MassStorage) ce pot fi utilizate în lansarea unor atacuri informatice (un mouse sau o tastatură ce conține un virus în memoria internă – un troian hardware).

9

Utilizarea bibliotecii V-USB

V-USB este o componentă software ce implementează comunicația USB (low-speed USB / USB 1.1) pentru orice microcontroler din familia Atmel AVR – cu alte cuvinte permite comunicația USB pentru microcontrolerele din această familie chiar dacă nu au un port USB hardware (similar cu biblioteca SoftwareSerial ce permite utilizarea oricăror doi pini ai plăcii Arduino pentru comunicația UART). Prin intermediul acestei biblioteci putem utiliza orice microcontroler Atmel AVR pentru o conexiune directă USB fără a fi nevoie de un circuit suplimentar de conversie UART – USB (FTDI sau microcontroler cu port hardware USB – 8U2 sau 16U2). Singurele restricții sunt ca circuitul microcontroler să aibă cel puțin 2kB memorie flash, 128 bytes memorie RAM și să funcționeze la cel puțin 12MHz – condiții îndeplinite fără probleme de microcontrolerul ATmega328P ce echipează placa de dezvoltare Arduino Uno. O aplicație imediată a acestei biblioteci o reprezintă plăcile de dezvoltare compatibile Arduino ce nu dețin circuite suplimentare față de microcontroler (nu au circuit de conversie UART – USB și implicit au un cost mai mic), de exemplu placa de dezvoltare Adafruit Pro Trinket. Placa Adafruit Pro Trinket este echipată cu un microcontroler ATmega328P, la fel ca și placa Arduino Uno, are dimensiuni și cost apropiate de o placă de dezvoltare Arduino Pro Mini dar nu necesită programator extern FTDI pentru încărcarea programelor. La fel ca și celelalte plăci de dezvoltare Arduino, placa Pro Trinket funcționează pe baza unui bootloader rezident în memoria flash a microcontrolerului ce permite transferarea programului de pe USB în memoria program internă. Bootloader-ul plăcii Pro Trinket se bazează pe biblioteca V-USB și funcționează în mod similar cu bootloader-ele altor plăci Arduino. Singurul dezavantaj al unei astfel de soluții este procedura mai greoaie de upload (de încărcare a programului). Circuitele suplimentare prezente pe plăcile Arduino se ocupau cu transferul programului USB – memorie internă program lansând în execuție bootloader-ul intern printr-o operație de reset a microcontrolerului. Din cauza absenței circuitului care să declanșeze operația de reset această operație trebuie efectuată manual – înainte de operația de upload (încărcare a programului) trebuie apăsat butonul de reset și operația de încărcare trebuie efectuată într-un interval de 10 secunde (atâta timp cât placa se află în Bootloader Mode).

V-USB for Arduino

Aplicabilitatea bibliotecii V-USB nu se rezumă doar la dezvoltarea de bootloadere ce permit încărcarea programului în memoria internă a microcontrolerului. Prin intermediul bibliotecii V-USB putem dezvolta periferice USB bazate pe microcontrolere Atmel AVR de tip HID (Human Interface Device – tastatură, mouse), CDC (Communications Device Class – modemuri, mass storage) sau chiar MIDI (Musical Instrument Digital Interface) fără a fi limitați la clasa de dispozitive USB de tip USB-to-serial (USB Serial Converter) oferite de driverul obișnuit al plăcilor de dezvoltare Arduino. Site-ul V-USB oferă o colecție semnificativă de exemple de proiecte diverse de astfel de dispozitive.

Chiar dacă biblioteca V-USB este scrisă în limbajul C nu poate fi folosită direct în mediul Arduino IDE fiind dezvoltată sub mediul WinAVR. Din fericire există o portare parțială a bibliotecii V-USB sub forma unei biblioteci specifice Arduino IDE și anume V-USB for Arduino. Prin intermediul acestei biblioteci o placă Arduino Uno se poate transforma într-un dispozitiv USB personalizat. Biblioteca este format din trei componente independente: UsbDevice (permite implementarea unui dispozitiv USB generic), UsbKeyboard (permite implementarea unui dispozitiv USB HID de tip tastatură), UsbStream (permite implementarea unui dispozitiv de comunicație / transfer de date).

Biblioteca V-USB for Arduino poate fi utilizată fără nici un fel de problemă sub versiunile Arduino IDE 1.0.x dar necesită o mică corecție sub versiunile mai recente (1.5.x, 1.6.x): declarațiile realizare în memoria program (PROGMEM) trebuie să fie de tip constantă (const) – în toate fișierele .c și .h declarațiile de tip PROGMEM trebuie modificate în PROGMEM const.

Realizarea unui dispozitiv de tip „Carte de vizită”

În cadrul acestei secțiuni vom realiza un periferic de tip HID (mai exact tastatură) ce va completa în mod automat datele de contact ale deținătorului (proiect inspirat de (*)). Sistemul va fi format dintr-o placă de dezvoltare Arduino Uno, un buton (plus o rezistență de 10Kohm) și partea de interfațare electrică cu conexiunea USB (2 diode Zener 3.6V maxim 0.5W, 2 rezistențe 68ohm și 1 rezistență 1.5Kohm). La apăsarea butonului sistemul va trimite pe USB textul de semnătură (carte de vizită) ca și cum ar fi tastat.

Butonul este conectat la pinul digital 10, acesta este ținut la Vcc (+5V) prin intermediul unei rezistențe de 10Kohm. Apăsarea butonului trage pinul digital în ”0” logic (0V). Conexiunea electrică USB presupune ca liniile D+, D- să aibă o rezistență de 68ohm în serie și câte o diodă Zener de 3.6V la masă (pentru adaptarea nivelului logic de 5V la 3.6V). Linia D+ necesită și o rezistență de pull-up de 1.5Kohm comandată de pinul digital 5. Linia D- se conectează la pinul digital 4 și linia D+ la pinul digital 2. Liniile conexiunii USB Vcc și GND se interconectează la pinii 5V și GND ai plăcii Arduino asigurând alimentarea sistemului în modul de funcționare periferic USB de tip HID.  Schema de interconectare este următoarea:

2

Programul ce va rula pe placa de dezvoltare va fi încărcat utilizând Arduino IDE și conexiunea USB a plăcii.

#include „UsbKeyboard.h”

#include <util/delay.h>

#define BUTTON_PIN 10

String mesaj;

void setup() {

  pinMode(BUTTON_PIN, INPUT);

  TIMSK0&=!(1<<TOIE0);

}

void loop() {

  UsbKeyboard.update();

  if (digitalRead(BUTTON_PIN) == 0) {

    mesaj = „ING. POPESCU ION”;

    KeyStrokeAlpNum(mesaj);

    mesaj = „DIRECTOR TEHNIC EMBEDDED DEVICES SRL”;

    KeyStrokeAlpNum(mesaj);

    mesaj = „MOBIL 0789.000.233”;

    KeyStrokeAlpNum(mesaj);

    mesaj = „FIX 021.456.30.03”;

    KeyStrokeAlpNum(mesaj);

    UsbKeyboard.sendKeyStroke(KEY_ENTER);

    _delay_ms(200);   }

}

După încărcarea programului conexiunea USB cu placa de dezvoltare se va deconecta și se va utiliza conexiunea USB prin intermediul mufei de pe breadboard.

În cadrul programului este necesară includerea bibliotecii UsbKeyboard.h. În secțiunea setup se va configura pinul digital 10 ca intrare (pin utilizat pentru buton) și se va dezactiva timerul 0 (utilizat de mediul Arduino IDE pentru funcțiile de temporizare) pentru a nu interfera cu temporizarea utilizată în biblioteca V-USB. Secțiunea loop va conține un apel periodic al procedurii UsbKeyboard.update() – apel necesar pentru a efectua operații obligatorii în cadrul comunicației USB, operații invocate de procedura usbpool() a bibliotecii V-USB; trimiterea de caractere prin intermediul conexiunii USB este condiționată de apăsarea butonului (digitalRead(BUTTON_PIN) == 0) – trimiterea efectivă a codului unei taste se realizează prin intermediul funcțiilor UsbKeyboard.sendKeyStroke(cod_tasta) pentru o singură tastă și KeyStrokeAlpNum(mesaj) pentru un șir de taste.

void KeyStrokeAlpNum(String Sc){

    int sPoint = Sc.length();         

    for (int x = 0; x < sPoint; x++){

      int y = x + 1;

      if (Sc.substring(x, y) == „H”){ 

          UsbKeyboard.sendKeyStroke(KEY_H, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „L”){

          UsbKeyboard.sendKeyStroke(KEY_L, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == ” „){

          UsbKeyboard.sendKeyStroke(KEY_SPACE);

          }

       else if (Sc.substring(x, y) == „1”){

          UsbKeyboard.sendKeyStroke(KEY_1);

          }

       else if (Sc.substring(x, y) == „2”){

          UsbKeyboard.sendKeyStroke(KEY_2);

          }

       else if (Sc.substring(x, y) == „3”){

          UsbKeyboard.sendKeyStroke(KEY_3);

          }

       else if (Sc.substring(x, y) == „4”){

          UsbKeyboard.sendKeyStroke(KEY_4);

          }

       else if (Sc.substring(x, y) == „5”){

          UsbKeyboard.sendKeyStroke(KEY_5);

          }

       else if (Sc.substring(x, y) == „6”){

          UsbKeyboard.sendKeyStroke(KEY_6);

          }

       else if (Sc.substring(x, y) == „7”){

          UsbKeyboard.sendKeyStroke(KEY_7);

          }

       else if (Sc.substring(x, y) == „8”){

          UsbKeyboard.sendKeyStroke(KEY_8);

          }

       else if (Sc.substring(x, y) == „9”){

          UsbKeyboard.sendKeyStroke(KEY_9);

          }

       else if (Sc.substring(x, y) == „0”){

          UsbKeyboard.sendKeyStroke(KEY_0);

          }

       else if (Sc.substring(x, y) == „.”){

          UsbKeyboard.sendKeyStroke(55);

          }

       else if (Sc.substring(x, y) == „-„){

          UsbKeyboard.sendKeyStroke(45);

          }

       else if (Sc.substring(x, y) == „A”){

          UsbKeyboard.sendKeyStroke(KEY_A, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „B”){

          UsbKeyboard.sendKeyStroke(KEY_B, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „C”){

          UsbKeyboard.sendKeyStroke(KEY_C, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „D”){

          UsbKeyboard.sendKeyStroke(KEY_D, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „E”){

          UsbKeyboard.sendKeyStroke(KEY_E, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „F”){

          UsbKeyboard.sendKeyStroke(KEY_F, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „G”){

          UsbKeyboard.sendKeyStroke(KEY_G, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „I”){

          UsbKeyboard.sendKeyStroke(KEY_I, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „J”){

          UsbKeyboard.sendKeyStroke(KEY_J, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „K”){

          UsbKeyboard.sendKeyStroke(KEY_K, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „M”){

          UsbKeyboard.sendKeyStroke(KEY_M, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „N”){

          UsbKeyboard.sendKeyStroke(KEY_N, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „O”){

          UsbKeyboard.sendKeyStroke(KEY_O, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „P”){

          UsbKeyboard.sendKeyStroke(KEY_P, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „Q”){

          UsbKeyboard.sendKeyStroke(KEY_Q, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „R”){

          UsbKeyboard.sendKeyStroke(KEY_R, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „S”){

          UsbKeyboard.sendKeyStroke(KEY_S, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „T”){

          UsbKeyboard.sendKeyStroke(KEY_T, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „U”){

          UsbKeyboard.sendKeyStroke(KEY_U, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „V”){

          UsbKeyboard.sendKeyStroke(KEY_V, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „W”){

          UsbKeyboard.sendKeyStroke(KEY_W, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „X”){

          UsbKeyboard.sendKeyStroke(KEY_X, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „Y”){

          UsbKeyboard.sendKeyStroke(KEY_Y, MOD_SHIFT_LEFT);

          }

       else if (Sc.substring(x, y) == „Z”){

          UsbKeyboard.sendKeyStroke(KEY_Z, MOD_SHIFT_LEFT);

          }

      }

      UsbKeyboard.sendKeyStroke(KEY_ENTER);

}

Realizarea unui dispozitiv de tip mouse

De această dată vom realiza un sistem bazat pe placa de dezvoltare Arduino Pro Mini 5V  (pentru a reduce costurile și dimensiunea sistemului) plecând de la exemplul hid-mouse pus la dispoziție de biblioteca V-USB. Compilarea exemplului necesită instalarea mediului de dezvoltare WinAVR iar încărcarea lui pe placa de dezvoltare necesită un programator ISP (de exemplu Arduino ISP sau Pocket AVR). Schema de interconectare a componentelor (placă de dezvoltare și componente de interfațare USB – similare cu proiectul precedent) este:

3

Pentru încărcarea programului se urmează următorii pași:

  • Se instalează mediul de dezvoltare WinAVR.
  • Se actualizează utilitarul avrdude (avrdude.exe și avrdude.conf) din directorul de instalare al mediului WinAVR (uzual C:\WinAVR-20100110\bin) pentru a putea lucra cu microcontrolerul ATmega328P.
  • Se conectează programatorul ISP pe pinii RST, MOSI (pinul 11), MISO (pinul 12), SCK (pinul 13), GND, 5V ai plăcii de dezvoltare Arduino Pro Mini.
  • Se deschide o fereastră de comandă de tip consolă (click-dreapta cu tasta shift apăsată) pentru directorul hid-mouse (vusb-20121206\examples).
  • În fereastra de comandă se tastează make hex pentru a compila exemplul și a obține fișierul binar hex ce urmează a fi încărcat în memoria microcontrolerului.
  • În fereastra de comandă se tastează comanda

avrdude -c usbtiny -p m328p -U hfuse:0xDE:m -U lfuze:w:0xFF:m

pentru a inițializa biții de fuse ai microcontrolerului; se consideră utilizarea

unui programator USBtinyISP (precum Pocket AVR).

  • În fereastra de comandă se tastează comanda

avrdude -c usbtiny -p m328p –U flash:w:main.hex:i

pentru a încărca programul în memoria internă a microcontrolerului.

  • Se deconectează programatorul ISP și se conectează sistemul prin intermediul conexiunii USB.

Sistemul odată conectat se va comporta ca un dispozitiv de tip mouse (descriptor dispozitiv în imaginea următoare) – cursorul de mouse se va mișca pe ecran pe o traiectorie de tip cerc. Proiectul poate constitui un bun punct de plecare pentru dezvoltarea unor dispozitive de tip mouse – de exemplu se poate încerca dezvoltarea unui sistem care să miște cursorul de mouse cu ajutorul a patru butoane (sus, jos, dreapta, stânga).

4