Pagine

Visualizzazione post con etichetta arduino. Mostra tutti i post
Visualizzazione post con etichetta arduino. Mostra tutti i post

lunedì 11 luglio 2011

bikeComputer con Arduino

Premessa

Finalmente è arrivata l’estate e con essa il caldo; la voglia di uscire e stare all’aria aperta si fa sentire ogni giorno con maggiore forza e, talvolta, nei pigri pomeriggi cittadini, nelle assolate domeniche fuori porta o sulla passeggiata del lungo mare decidiamo di abbandonare la tanto amata automobile per tornare a mezzi di locomozione più tradizionali: le nostre gambe ma, soprattutto, la bicicletta.


I più curiosi, in queste occasioni, vorrebbero sapere quanti chilometri riescono a percorrere, quale sia la velocità di punta toccata e quante pedalate riescono a fare in un minuto; magari per vantarsi con gli amici :-). E’ per far fronte a queste esigenze che io ed Enkel abbiamo pensato di progettare il bikeComputer con Arduino. In questo articolo vi racconterò come abbiamo fatto.

L'immagine che vedete qui sopra è il prototipo funzionante che abbiamo avvolto nello scotch sul manubrio della bici di Enkel per fare il test "su strada" :-)
Potete trovare tutte le immagini del progetto in questa pagina.


L'idea

L’idea è semplice: tramite arduino si visualizza la velocità di punta e la velocità media su un display 16X2.
Per contare i giri della ruota abbiamo utilizzato un reed switch. Questo sensore si comporta come un interruttore: esso rimane normalmente aperto (e quindi non passa corrente); arduino lo vede come un segnale digitale LOW.
Quando passa un magnete vicino (posizionato sui raggi della ruota), il sensore si chiude facendo passare corrente; in questo modo restituisce un HIGH digitale ad arduino riconosce il passaggio del magnete; il tempo trascorso tra un passaggio e l’altro ci permette di calcolare la velocità istantanea (e successivamente quella media) e, conoscendo il raggio della ruota, si può calcolare il numero di metri percorsi.
Analogo discorso si può fare per contare le pedalate piazzando il magnete sulla corona della bicicletta ed un secondo reed switch sul telaio della bicicletta.
Per ragioni di compattezza abbiamo utilizzato uno schermo LCD 16x2 ma abbiamo meditato l’uso di un 20x4 in modo da vedere tutte le informazioni contemporaneamente.
Ciò che ci interessava molto, però, era anche la possibilità di poter fruire dei dati dopo aver terminato la scampagnata (si pensi ad una gita: vado in un luogo in bici, mangio, gioco a freesbie e riprendo la bici fino a casa dove voglio analizzare le mie performance); per questo motivo abbiamo usato uno shield microSD con memoria da 1Gb.
La memoria ha un duplice scopo: l'utente dovrà inserire un file da denominare config.txt (qualsiasi operativo può creare un file di testo semplice) dove inserisce un numero di 4 cifre che esprime il diametro della ruota della propria bicicletta in mm (serve per il calcolo della distanza percorsa); dal canto suo arduino aggiorna il file log.txt.
Tale file sarà creato ed alimentato da arduino con tante righe quanti sono i passaggi del magnete; ogni riga contiene il numero di millisecondi passati dall’accensione di arduino (tramite la funzione millis()) nel momento in cui si rileva il passaggio del magnete vicino al reed (da questi è poi semplice ricavarsi tutto il resto magari effettuando un grafico con Processing).
L’alimentazione del sistema è stata affidata ad un pacco batterie composto da 4 pile in serie. Conteggiando circa 1.2-1.5V per ciascuna si arriva ad una tensione di alimentazione di 4.8-6V che abbiamo inserito direttamente nel pin Vin per evitare inutili dispersioni di energia.
Per quanto riguarda il “case” ci siamo appoggiati al FabLab di Torino (nelle immagini non è ancora presente ma è "coming soon") grazie all’aiuto di Davide G. ed Enrico C. che ci hanno aiutato con il laser-cutter a progettare un adeguato alloggiamento per il sistema.
Una esigenza con la quale ci siamo scontrati, inoltre, era il desiderio di rendere indipendente il reed sensor e il “case” con arduino dentro: l’idea è quella di arrivare a destinazione (magari davanti un supermarket per fare la spesa) e poter staccare tutto il sistema (per non lasciarlo in balia di occhi indiscreti e mani disoneste); a questo proposito è stata ingegnosa l’intuizione di Enkel che ha pensato di riutilizzare delle vecchie cuffie stereo per walkman: abbiamo tagliato gli auricolari dai quali prelevare due fili (massa e segnale); dall’altra estremità il jack da 3.5mm era già pronto per essere inserito durante l’utilizzo ed estratto a destinazione.

Schema

Seguono lo schema di connessione sulla breadboard e lo schema elettrico. Entrambi realizzati con fritzing.
Schema sulla breadboard:
Schema elettrico:

Codice

Di seguito il codice utilizzato
/*
////////////////////////////////////////////////

[ Arduino Cycle Computer ] Version A0.2

>> Original written by Adam O'Hern
>> Modified by Alexdlp for Instructables 2011
>> Modified by Enkel Bici
>> and Vittorio Zuccala' for Wired.it 2011

////////////////////////////////////////////////
*/

#include <LiquidCrystal.h>
//#include <LcdBarGraph.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd( 7,6, 5, 4, 3, 2);

#include <SD.h>
File myFile;


//byte lcdNumCols = 15; // -- number of columns in the LCD
//LcdBarGraph lbg(&lcd, lcdNumCols); // -- creating

int LED = 13; //pin for the LED
int SensorPin = 15; //input for REED SensorPin (analog 0 as digital)
int rim = 2035; //circumference of wheel in mm (28" rim)

int val = 0; // used to store input value
int previousVal = 0; // lets not be too repetitious

int debounce = 10; // the debounce time, increase if the output flickers
int cycles = 1; // total number of revolutions
float currentSpeed = 0; // current speed in MPH
float averageSpeed = 0; // average speed since "newRide" was true

unsigned long revTimer; // create a timer that tells us how long we go between pulses,
unsigned long serialTimer; // and one for how long it's been since we sent anything over serial
unsigned long rideTimer; // total time since "newRide" was true

boolean activeRiding = false; // is the bike moving?
boolean activityChange = true; // just a way of keeping track of how long we're inactive
long inactivityTimer; // millis() on which we began inactivity
long activeRidingOffset = 0; // time subtracted from total riding time when calculating average speed
boolean newRide = true; // true if we haven't moved in half an hour or more
float rideDistance = 0; // total distance traveled since "newRide" was true

void setup() {
lcd.begin(16, 2);
pinMode(LED, OUTPUT); // tell arduino LED port is an output,
pinMode(SensorPin, INPUT); // and SensorPin port is input

lcd.setCursor(0,0);
lcd.print("Init SD...");
lcd.setCursor(0,1);
if (!SD.begin(8)) {
lcd.print("Init failed!");
delay(20000);
return;
}
else{
delay(2000);
lcd.print("Init OK!!");
delay(10000);
lcd.clear();
myFile = SD.open("setup.txt");
if (myFile) {
lcd.setCursor(0,0);
lcd.print("wheel:");
lcd.setCursor(0,1);
char c[5];
int count=0;
// read from the file until there's nothing else in it:
while (myFile.available()) {
c[count]=myFile.read();
count++;
}
// close the file:
myFile.close();
delay(2000);
rim=0; //reset the wheel diameter
for(count=0;count<4;count++){ //it supposes the diameter is expressed in mm and composed of 4 characters written in a text file on the sd card
rim=rim+(c[count]-48)*pow(10,(3-count)); //each character reappresents a number. 0 is equal 48 in ascii code. From then on you add 1 to get the next numbers.
}
rim=rim+1; //magic number
lcd.print(rim);
delay(2000);
}
else {
// if the file didn't open, print an error:
lcd.print("error reading...");
delay(20000);
}
}

Serial.begin(9600); // start a serial session
revTimer = millis(); // start pulse timer
serialTimer = millis(); // start serial timer
rideTimer = millis(); // start ride timer
}

void loop(){

if(!activeRiding) {
if(activityChange) {
inactivityTimer = millis();
activityChange = false;
}
}
else {
if(activityChange) {
activeRidingOffset += millis() - inactivityTimer;
activityChange = false;
}
}


val = digitalRead(SensorPin);
if (val==LOW) { // check whether input is LOW (magnet is NOT in range of reed SensorPin)
digitalWrite(LED, LOW); // turn LED off
previousVal = LOW; // allow the next "pulse" to be counted
}
else{
digitalWrite(LED, HIGH); // turn LED off
lcd.setCursor(1, 0); // Set cursor to the top right of LCD
lcd.print("*"); // Print a blank character or 'space'
//delay(100); // debounce timer
lcd.setCursor(1, 0); // Set cursor to the top right of LCD
lcd.print(" "); // Print a blank character or 'space'
if (previousVal != HIGH && millis() - revTimer > debounce) { // we've got a pulse!
pulse();
}
previousVal = HIGH; // (in case the magnet is in range of the SensorPin while sitting still)
}

// if it's been too long since the last pulse, assume we're not moving.
if(millis()-revTimer > 2000) {
currentSpeed = 0;
sendStats();
if(activeRiding) {
activityChange = true;
activeRiding = false;
}
}

// if it's been more than fifteen minutes...
if (millis() - revTimer > 15*60*1000) {
// we'll assume it's a new riding session & zero everything out at next pulse.
newRide = true;
}
}


void pulse() {
if(newRide) {
lcd.clear();
cycles = 0;
averageSpeed = 0;
rideTimer = millis();
rideDistance = 0;

//log data to file
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("log.txt", FILE_WRITE);

// if the file is available, write to it:
if (dataFile) {
dataFile.print("newride");
dataFile.println(",");
dataFile.close();
// print to the serial port too:
//Serial.println("newride");
}
// if the file isn't open, pop up an error:
else {
lcd.setCursor(1, 0);
lcd.println("can't write");
}
}

cycles++; // The wheel has obviously turned another revolution

rideDistance = (float) rim*cycles/1000; // distance in meters,
//rideDistance = rideDistance * 0.000621371192; // converted to miles

currentSpeed = (float) (millis() - revTimer)*0.001; // Convert time elapsed to milliseconds to seconds
currentSpeed = rim/currentSpeed; // S = d/t: Rim circumference divided by time elapsed
//currentSpeed = currentSpeed*0.002237; // MPH Conversion: 1 mm/s = 0.001 m/s * 3600 s/hr * 1 mile / 1609 m = 0.002237
i/hr
currentSpeed = currentSpeed*0.0036; // 1Km/hr Conversion: 1 mm/s = 0.001 m/s * 3600 s/hr * 1Km/1000m = 0,0036 Km/hr

// time ridden since "newRide", in hours, not including inactive time
unsigned long activeRidingMillis = millis() - rideTimer - activeRidingOffset;
//float activeRidingHours = (float) activeRidingMillis/1000/60/60; // convert to hours
float activeRidingSeconds = (float) activeRidingMillis*0.001; // convert to hours
averageSpeed = rideDistance / activeRidingSeconds;
averageSpeed = averageSpeed*3,6;

revTimer = millis(); // remember the current moment for speed calculations next time around
sendStats(); // Tell Processing what's going on
newRide = false;



if(!activeRiding) { //if you start from standin still
activityChange = true;
activeRiding = true;
}

//log data to file
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("log.txt", FILE_WRITE);

// if the file is available, write to it:
if (dataFile) {
dataFile.print(revTimer);
dataFile.println(",");
dataFile.close();
// print to the serial port too:
Serial.println(revTimer);
}
// if the file isn't open, pop up an error:
else {
lcd.setCursor(1, 0);
lcd.println("can't write");
}
}

void sendStats() {
/*
Serial.print("rideDistance=");
Serial.print(rideDistance,2);
Serial.print("&currentSpeed=");
Serial.print(currentSpeed,1);
*/
lcd.setCursor(0, 1);
lcd.print("Speed = ");
lcd.setCursor(9, 1);
lcd.print(currentSpeed,1);
lcd.setCursor(13, 1);
lcd.print("kmh");

//lbg.drawValue(currentSpeed, 35);
/*
Serial.print("&averageSpeed=");
Serial.print(averageSpeed,2);

// send a linefeed character, telling Processing that we're done transmitting.
Serial.print(10,BYTE);
*/
serialTimer = millis();
}

mercoledì 24 marzo 2010

Arduino e seriale python

Premessa

Quando si effettua uno sketch con arduino che invia/riceve dati verso/dalla seriale è necessario un programma che recepisca o mi permetta di scrivere dati sulla porta seriale.
Per effettuare i test solitamente si utilizza il monitor dell'IDE ma se si vuole automatizzare qualche passaggio o se si vuole rendere indipendente lo sketch è necessario "captare" le informazioni che transitano sulla seriale ed eventualmente rispondere.
Molti programmi hanno una libreria che permette di dialogare con la seriale.
In questo articolo vi parlerò con pyserial la libreria per python che comunica con la seriale.

pyserial

Si presuppone che sulla vostra macchina sia installato python.
Personalmente mi trovo bene con la versione 2.5
La pagina principale del progetto è http://pyserial.sourceforge.net/
Potete scaricare la libreria da questa pagina: http://sourceforge.net/projects/pyserial/files/ e diversi esempi qui http://pyserial.sourceforge.net/shortintro.html

Pyserial e Arduino

A questo punto vi voglio presentare una piccola parte di uno programma che ho fatto ultimamente in python.
L'idea è quella di recepire delle stringhe inviate da Arduino e in base a ciò che c'è scritto agire in diversi modi con il sistema operativo.
A questo scopo, dopo aver importato la libreria:
import serial

dopo aver inizializzato la seriale con una velocità di 9600 baud:
s=serial.Serial(1)
Su windows sarebbe COM1 mentre su Linux la /dev/ttyUSB1
Si può passare ad un ciclo che legge dalla seriale:
line=s.readline()


A questo punto il programma completo anche se depurato delle parti necessarie a me per interagire con il sistema operativo risulta molto semplicemente come segue:
import serial
s=serial.Serial(1)

while(1):

line=s.readline()
# interazioni con la variabile s

Ne consegue che è molto semplice fa interagire il PC con Arduino e questo rende questa tecnologia ancora più affascinante ai miei occhi...
Che ne pensate?

martedì 9 febbraio 2010

Arduino e programmare stati finiti

Premessa

Dopo aver visto come simulare il multitasking con Arduino grazie alla funzione millis() nella prima e seconda parte dell'articolo, andiamo a vedere la programmazione a stati finiti.
Il problema fondamentale che iniziava a presentarsi nella seconda parte è che il programma tende a diventare piuttosto mastodontico mano a mano che si aggiungono nuovi elementi che ineragiscono tra loro.
Dopo poco tempo l'utente inizia a sentire l'esigenza di dividere il codice in funzioni: si tende, quindi, a raccogliere le istruzioni in "gruppi" organizzati per rendere lo sketch più facile da leggere e gestire --i romani dicevano "Divide et impera"--.
Non è scopo di questo articolo vedere cosa sia una funzione e come si passino argomenti ad essa; l'autore presuppone che il lettore sappia creare una funzione sull'IDE di Arduino.
Fatta questa doverosa premessa andiamo a vedere come iniziare la programmazione a stati finiti su Arduino ed alleggerire la lettura del codice dei nostri sketch.

La programmazione a stati finiti

La programmazione a stati finiti consiste nel dividere il codice in blocchi diversi.
Tali blocchi sono racchiusi in una funzione che rappresenta un singolo evento e raccoglie porzioni di codice appartenenti al medesimo "livello logico" e nel quale si aggiorna, alla fine, la variabile-tempo (rif. prima parte e seconda parte dell'articolo) associata riassegnandogli il millis().
Si riprenda l'esempio visto in precedenza (rif. fondo pagina della seconda parte) e vediamo nella pratica in cosa consista.
Lo riporto qui di seguito per comodità del lettore.
#include <Servo.h>
#define SENSORELUCE1 0
#define SENSORELUCE2 5
int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1;
Servo servo2;
int posservo1;
int posservo2;

int k;

unsigned long time;
unsigned long servotime1;
unsigned long servotime2;
unsigned long letturadati;
unsigned long letturaluce_time;

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9);
servo2.attach(10);
Serial.begin(9600);

time=millis();
servotime1=millis();
servotime2=millis();
letturadati=millis();
letturaluce_time=millis();

k=0;
luce1=0;
luce2=0;
luce1temp=0;
luce2temp=0;
}

void loop(){
time=millis();

if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
luce2=analogRead(SENSORELUCE2);
k=k+1;
luce1temp=luce1temp+luce1;
luce2temp=luce2temp+luce2;
}
if(k=20){
k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}

if(time>servotime1+15){
posservo1= analogRead(letturaluce1);
posservo1=map(posservo1,0,1023,0,179);
servo1.write(posservo1);
servotime1=millis();
}

if(time>servotime2+15){
posservo2= analogRead(letturaluce2);
posservo2=map(posservo2,0,1023,0,179);
servo2.write(posservo2);
servotime2=millis();
}

if(time>letturadati+3000){
Serial.print("Sensore 1 : ");
Serial.print(letturaluce1,DEC);
Serial.print(" Posizione motore 1 : ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2 : ");
Serial.print(letturaluce2,DEC);
Serial.print(" Posizione motore 2 : ");
Serial.println(posservo2,DEC);
Serial.print(" Differenza : ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}
Il codice già ampiamente spiegato permette di far muovere due servo motori al variare dell'intensità di luce che insiste su due sensori di luce perpendicolari fra loro.
Questo codice si "divide" in porzioni ben definite:
  • La sezione in cui si leggono i sensori
  • La sezione in cui si fanno muovere i motori
  • La sezione nella quale si invia alla seriale un output.
Queste sezioni si possono raccogliere in apposite funzioni e si sfrutta una peculiarità dell'IDE di arduino: i tab.
Ogni funzione infatti si aggiunge ad un tab differente: per fare questo si clicchi la freccina verso destra nell'angolo in alto a destra dell'IDE e poi su "New Tab".
Si dia un nome alla tab che DEVE essere uguale al nome della funzione che si scriverà all'interno.
Come si vede in figura quindi, si aggiungeranno tre Tab; una per ciascuna funzione:
  • leggiLuce
  • muoviServoMotori
  • scriviSuOutput
Riporto di seguito ciò che si scriverà nei tre tab.

Leggi Luce

Questa funzione racchiude le istruzioni che arduino deve svolgere per leggere i sensori di luce.
In particolare si notino le 20 letture necessarie per "ammortizzare" gli sbalzi del sensore analogico e il reset della variabile tempo alla fine della funzione.
void leggiLuce(){
if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
luce2=analogRead(SENSORELUCE2);
k=k+1;
luce1temp=luce1temp+luce1;
luce2temp=luce2temp+luce2;
}
if(k=20){
k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}
letturaluce_time=millis();
}

Muovi servo motori

Questa funzione, come si intuisce, raccoglie le istruzioni sul movimento dei Servi.
Anche in questo caso si noti l'azzeramento della variabile-tempo al fondo della funzione.

/* Muove i SERVI*/

void muoviServoMotori(){
if(time>servotime1+15){
posservo1= analogRead(letturaluce1);
posservo1=map(posservo1,0,1023,0,179);
servo1.write(posservo1);
servotime1=millis();
}

if(time>servotime2+15){
posservo2= analogRead(letturaluce2);
posservo2=map(posservo2,0,1023,0,179);
servo2.write(posservo2);
servotime2=millis();
}
}

Scrivi su Outout

Infine lo scrivi output si comporta "a livello logico" come le funzioni viste in precedenza. Esso scrive sulla seriale le informazioni da visualizzare all'utente ogni 3 secondi.
void scriviSuOutput(){
if(time>letturadati+3000){
Serial.print("Sensore 1: ");
Serial.println(letturaluce1,DEC);
Serial.print("Posizione motore 1: ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2: ");
Serial.println(letturaluce2,DEC);
Serial.print("Posizione motore 2: ");
Serial.println(posservo2,DEC);
Serial.print("Differenza : ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}

Sketch principale

A questo punto lo sketck principale si riduce sensibilmente.
Ogni tab racchiude le porzioni di codice e ci possiamo soffermare sulla dinamica del programma.
#include <Servo.h>
#define SENSORELUCE1 0
#define SENSORELUCE2 5
int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1;
Servo servo2;
int posservo1;
int posservo2;

int k;


unsigned long time;
unsigned long servotime1;
unsigned long servotime2;
unsigned long letturadati;
unsigned long letturaluce_time;

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9);
servo2.attach(10);
Serial.begin(9600);

time=millis();
servotime1=millis();
servotime2=millis();
letturadati=millis();
letturaluce_time=millis();
k=0;
luce1=0;
luce2=0;
luce1temp=0;
luce2temp=0;
}

void loop(){
time=millis();

leggiLuce();
muoviServoMotori();
scriviSuOutput();

}

Come si può vedere la funzione loop() viene ridotta veramente all'osso.
Questo sketck si riduce ad un banale programmino.
Nel loop non è stata aggiunta alcuna "logica" ma è facile immaginare degli if che determinano l'attivazione di una o più funzioni a seconda di determinati eventi...

Conclusione

Frequentando il forum di Arduino, noto che le persone spesso entrano in crisi nel momento in cui gli sketch aumentano di dimensioni.
Spesso si tende a desistere nei propri progetti per questo motivo.
Trovo che l'applicazione dei consigli dati in questi tre articoli (multitasking + programmazione a stati finiti) risolva la maggior parte delle problematiche a livello di programmazione dovuta all'aggiunta di più componenti all'arduino.
Personalmente incontro maggiori difficoltà nelle saldature ma questo è un problema della mia mancanza di manualità :-)

Argomento precedente: "Multitasking Arduino: millis() -- PARTE 2"

venerdì 5 febbraio 2010

Multitasking Arduino: millis() -- PARTE 2

Un esempio pratico: inseguitore di luce

Nell'articolo precedente abbiamo visto la funzione millis() ed il suo utilizzo.
In questo post si vuole evidenziare con un esempio i passaggi da seguire per passare da uno sketch con il delay() ad uno con il millis().

Lo scenario

Per cercare di spiegare meglio il tutto partiamo con un esempio pratico.
Si immagini di avere due sensori di luce a distanza di pochi centimetri e posizionati a 90 gradi.
In mezzo a loro due servo motori che ruotano rispetto ciascuno di essi a seconda che la luce sia puntata verso un sensore o verso l'altro.
Questo sistema simula, in piccolo, il funzionamento di un inseguitore solare di pannelli fotovoltaici...



Primo sketch con delay...

Il primo esperimento è stato effettuato con il metodo classico usando il delay().
Prima di rappresentare il codice ricapitoliamo le esigenze:
  1. Leggere il sensore 1 e il sensore 2
  2. Muovere a seconda della luce i due motori in direzione della luce stessa
  3. Stampare a video le posizioni dei motori e l'intensità di luce rilevata
Alcune precisazioni sono d'obbligo per comprendere appieno il codice:
  1. I motore, per come è progettato, tra un comando e l'altro, desidera circa 15ms di ritardo
  2. Una persona, per leggere correttamente, deve avere un output circa ogni 500-1000ms
  3. Nell'articolo si da per scontato che il lettore conosca arduino, i fondamenti della sua programmazione e il collegamento di sensori analogici e componenti di base
Procediamo quindi con lo sketch:
#include  

#define SENSORELUCE1 0

#define
SENSORELUCE2 5

int letturaluce1=0;
int letturaluce2=0;
Servo servo1; //crea l'oggetto per il controllo del primo servo
Servo servo2; //crea l'oggetto per il controllo del secondo servo
int posservo1; //immagazzina la posizione del primo servo
int posservo2; //immagazzina la posizione del secondo servo

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9); //attaches the first servo on pin 9 to the servo object
servo2.attach(10); //attaches the second servo on pin 10 to the servo object
Serial.begin(9600);
}

void loop(){
letturaluce1=analogRead(SENSORELUCE1);
posservo1= analogRead(letturaluce1); // reads the value of the light sensor 1 (value between 0 and 1023)
posservo1=map(posservo1,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo1.write(posservo1); // sets the servo position according to the scaled value
delay(15); // waits for the servo to get there

letturaluce2=analogRead(SENSORELUCE2);
posservo2= analogRead(letturaluce2); // reads the value of the light sensor 2 (value between 0 and 1023)
posservo2=map(posservo2,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo2.write(posservo2); // sets the servo position according to the scaled value
delay(15); // waits for the servo to get there

Serial.print("Sensore 1: ");
Serial.println(letturaluce1,DEC);
Serial.print("Posizione motore 1: ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2: ");
Serial.println(letturaluce2,DEC);
Serial.print("Posizione motore 2: ");
Serial.println(posservo2,DEC);
Serial.print("Differenza: ");
Serial.println(letturaluce1-letturaluce2);
delay(1000); //solo per facilitare la lattura dei dati
}


Lo sketch funziona correttamente ma a causa dei continui delay() inserit che, abbiamo visto nelle precisazioni precedenti sono indispensabili, rende il programma macchinoso.
In particolare si riscontrano i seguenti problemi:

  • Se la luce cambia posizione velocemente, i servomotori non riescono a seguirla. Questo avviene perchè non aspettano solo 15ms tra un comando e il successivo bensì 1030ms (1000ms per la lettura e 15+15 per ciascun motore); si pensi se si inserissero altri elementi... il tempo si dilaterebbe aumentando questo probelma in maniera esponenziale.
  • Il movimento dei motori risulta a scatti ed estremamente impreciso. Ciò avviene perchè tra un comando e il successivo non passano 15ms (necessari al corretto funzionamento del motore ed impercettibili per l'occhio umano) bensì, come abbiamo visto al punto prima 1030ms (che per l'occhio umano risultano ben identificabili)
  • Un altro problema importante è che la lettura del sensore non è sempre molto accurata e può variare anche sensibilmente da una rilevazione alla successiva. In questo modo si genera un movimento "confuso" dei motori che rilevano valori anche molto discordanti da una volta all'altra

Il sistema risulta, nel suo complesso, lento a reagire ed estremamente impreciso oltre a dare una sensazione di scatto continuo.
Provate per credere :-)

Da queste "anomalie" nasce la necessità di modificare lo sketch e rendere il sistema più preciso e reattivo.
In particolare i primi due problemi verranno eliminati utilizzando la funzione millis() al posto del delay().
Il terzo problema al contrario con una media di rilevazioni come spiegato di seguito.

Vediamo quindi il codice seguente:


#include  

#define
SENSORELUCE1 0
#define SENSORELUCE2 5

int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1; //crea l'oggetto per il controllo del primo servo
Servo servo2; //crea l'oggetto per il controllo del secondo servo
int posservo1;
int posservo2;

Fino a questo punto il programma non subisce alcuna modifica rispetto la versione precedente.
Le variabili prima dichiarate continuano a presentarsi anche in questo caso.
Successivamente, però, si dichiarano le variabili-tempo presentate nel precedente articolo.
Ad ogni variabile-tempo viene associato un evento.

int k;
unsigned long time;
unsigned long servotime1;
unsigned long servotime2;
unsigned long letturadati;
unsigned long letturaluce_time;

time è la variabile che contiene "lo scorrere" del tempo; servotime1 e 2 rappresentano gli eventi associati al movimento dei motori; letturadati e lettura_luce rappresentano gli eventi associati alla lettura dei due sensori di luce.
La variabile k servirà come contatore per evitare le letture dei sensori "sballate".
Questo problema è stato anticipato prima; l'algoritmo che ci viene in aiuto, infatti, sarà semplicemente fare "x" letture consecutive e farne la media.
Considerando che arduino fa 16 milioni di operazioni al secondo, anche facendo 20 letture a distanza di 5ms, ogni frazione di secondo avremmo una rilevazione molto accurata del valore di luce circostante ed il calcolo della media smorza eventuali picchi anomali nei rilevamenti.

void setup(){

pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9);
servo2.attach(10);
Serial.begin(9600);

time=millis();
servotime1=millis();
servotime2=millis();
letturadati=millis();
letturaluce_time=millis();
k=0;
luce1=0;
luce2=0;
luce1temp=0;
luce2temp=0;
}


void loop(){
time=millis();
if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
luce2=analogRead(SENSORELUCE2);
k=k+1;
luce1temp=luce1temp+luce1;
luce2temp=luce2temp+luce2;
}

Come spiegato nell'articolo precedente, si associa l'evento ad un if.
Ogni 5ms si effettua una lettura di entrambi i sensori ma nel frattempo il programma continua a "girare" senza nessun impedimento.

   if(k=20){

k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}

Questo if risolve il problema della poca accuratezza nella misura.
Come detto due paragrafi fa, si effettuano k letture (in tal caso 20 ma nessuno vieta di aumentare o diminuire questo valore) al termine delle quali si effettua una media e si azzera il valore di k.
I valori di riferimento saranno sempre letturaluce1 e letturaluce2 che varieranno il proprio valore ogni 20 misure.

   if(time>servotime1+15){

posservo1= letturaluce1;
posservo1=map(posservo1,0,1023,0,179); //scala il valore da usare tra 0 e 180.
servo1.write(posservo1); //imposta la posizione del servo1
servotime1=millis();
}


if(time>servotime2+15){
posservo2= letturaluce2;
posservo2=map(posservo2,0,1023,0,179);//scala il valore da 0 a 180
servo2.write(posservo2); // imposta la posizione del servo2.
servotime2=millis();
}

Queste istruzioni dovrebbero essere chiare dopo la lettura del precedente articolo.
In entrambi i casi si rileva la intensità della luce, si posiziona il servo corrispondente rimappando il valore con la funzione map e si "azzerano" le variabile-tempo servotime1 e servotime2 assegnando loro il numero di millisecondi correnti.

   if(time>letturadati+3000){

Serial.print("Sensore 1: ");
Serial.println(letturaluce1,DEC);
Serial.print("Posizione motore 1: ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2: ");
Serial.print(letturaluce2,DEC);
Serial.print("Posizione motore 2 : ");
Serial.println(posservo2,DEC);
Serial.print("Differenza: ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}

Anche queste istruzioni seguono lo stesso ragionamento precedente.
In questo caso, addirittura, possiamo permetterci il lusso di "aspettare" 3000ms e non solo 1000ms tra una scrittura e la successiva (rendendo il tutto più leggibile all'utente finale) perchè il programma non si blocca ma continua a girare ed eseguire le azioni.

Tutto questo rende il movimento dei servo motori estremamente fluido.
L'osservatore non vedrà alcuno scatto e noterà anche una dinamica del sistema estremamente veloce con un "inseguimento" della luce decisamente reattivo.
Il programma nel suo complesso è decisamente utilizzabile.
Tuttavia presenta ancora un piccolo problemino: è lungo...
La lunghezza del codice proposto si può risolvere, come vedremo nel prossimo articolo con la programmazione a stati finiti.

Argomento precedente: "Multitasking Arduino: millis() -- PARTE 1"

Riporto il codice dell'intero sketch visto in questo articolo per facilitare il lettore che volesse testarlo:

#include  

#define SENSORELUCE1 0
#define SENSORELUCE2 5
int letturaluce1=0;
int letturaluce2=0;
int luce1;
int luce2;
int luce1temp;
int luce2temp;
Servo servo1;
Servo servo2;
int posservo1;
int posservo2;

int k;


unsigned long time;
unsigned long servotime1;
unsigned long servotime2;
unsigned long letturadati;
unsigned long letturaluce_time;

void setup(){
pinMode (SENSORELUCE1, INPUT);
pinMode (SENSORELUCE2, INPUT);
servo1.attach(9);
servo2.attach(10);
Serial.begin(9600);

time=millis();
servotime1=millis();
servotime2=millis();
letturadati=millis();
letturaluce_time=millis();
k=0;
luce1=0;
luce2=0;
luce1temp=0;
luce2temp=0;
}

void loop(){
time=millis();
if(time>letturaluce_time+5){
luce1=analogRead(SENSORELUCE1);
luce2=analogRead(SENSORELUCE2);
k=k+1;
luce1temp=luce1temp+luce1;
luce2temp=luce2temp+luce2;
letturaluce_time=millis();
}
if(k=20){
k=0;
letturaluce1=luce1temp/20;
letturaluce2=luce2temp/20;
luce1temp=0;
luce2temp=0;
}

if(time>servotime1+15){
posservo1= analogRead(letturaluce1); // reads the value of the light sensor 1 (value between 0 and 1023)
posservo1=map(posservo1,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo1.write(posservo1); // sets the servo position according to the scaled value
servotime1=millis();
}

if(time>servotime2+15){
posservo2= analogRead(letturaluce2); // reads the value of the light sensor 2 (value between 0 and 1023)
posservo2=map(posservo2,0,1023,0,179); // scale it to use it with the servo (value between 0 and 180)
servo2.write(posservo2); // sets the servo position according to the scaled value
servotime2=millis();
}

if(time>letturadati+3000){
Serial.print("Sensore 1: ");
Serial.println(letturaluce1,DEC);
Serial.print("Posizione motore 1: ");
Serial.println(posservo1,DEC);
Serial.print("Sensore 2: ");
Serial.println(letturaluce2,DEC);
Serial.print("Posizione motore 2: ");
Serial.println(posservo2,DEC);
Serial.print("Differenza: ");
Serial.println(letturaluce1-letturaluce2);
letturadati=millis();
}
}


Argomento precedente: "Multitasking Arduino: millis() -- PARTE 1"

Argomento successivo: "Ardino e programmare a stati finiti"