Eccomi.
Cerco di riassumere il tutto... sarà un post lunghetto... porta pazienza...
Essendo wireless c'è un master con N slave. Il master ovviamente comanda e gli slave poveracci ubbidiscono.
MASTER
L'idea era quella di "allenare" i riflessi e la reattività, quindi ho previsto la possibilità di inserire un tempo massimo di risposta e un tempo di attesa tra gli stimoli. Il master gestisce i tempi, invia i comandi di accensione led allo slave corretto, legge lo stato di risposta.
Ho usato un arduino mega, un modulo hc12, un ldc e 5 pulsanti.
SLAVE
Lo slave si limita ad aspettare i comandi dal master. Quando riceve il comado di accensione accende i led e verifica se il sensore di prossimità rileva qualcosa entro il tempo impostato. Se non si reagisce in tempo o se si reagisce allo slave sbagliato segna l'errore. Quando riceve il comando per leggere lo stato risponde col numero di errori commessi.
Ho usato un arduino nano, un modulo hc12, un ldc, un buzzer e diversi led bicolore.
Il tutto funziona a batteria.
Riguardo al codice, c'è una parte in comune tra master e slave (questi file devono essere nelle cartelle di entrambi gli sketch):
comunicazione.h
/*
* Funzioni per l'invio e la ricezione di dati byte / byte
*
*/
#ifndef CHR_START
#define CHR_START '['
#endif
#ifndef CHR_STOP
#define CHR_STOP ']'
#endif
#ifndef CHR_SEP
#define CHR_SEP ','
#endif
#ifndef TIMEOUT
#define TIMEOUT 1000
#endif
// - 0 = ricezione non completata
// - 1 = dati ricevuti correttamente
// - 2 = errore timeout
// - 3 = errore dati in arrivo oltre la dimensione massima
// - 4 = errore dati incompleti
#define RX_WAIT 0
#define RX_OK 1
#define RX_TIMEOUT 2
#define RX_HIGH 3
#define RX_LOW 4
// funzione che trasmette
// i dati vengono trasmessi un byte alla volta
// parametri:
// - seriale = canale su cui trasmettere (serial / HC12)
// - pacchetto = dati da trasmettere. serve un puntatore per scorrere la struttura dati un byte alla volta
// - dimensione = numero di byte da trasmettere
void Trasmetti(Stream &seriale, unsigned char * pacchetto, size_t dimensione) {
// invio il carattere di start
seriale.print(CHR_START);
// invio i dati
for (byte i=0; i<dimensione; i++) {
seriale.print(*(pacchetto+i), HEX);
seriale.print(CHR_SEP);
}
// invio il carattere di stop
seriale.print(CHR_STOP);
return;
}
// funzion che riceve
// i dati arrivano sotto forma di caratteri che presi a 2 a 2 formano
// un byte rappresentato in HEX
// parametri:
// - seriale = canale da cui ricevere (serial / HC12)
// - pacchetto = contenitore dei dati da ricevere. serve un puntatore per scorrere la struttura dati un byte alla volta
// - dimensione = numero di byte massimo da ricevere
// ritorna:
// - 0 = ricezione non completata
// - 1 = dati ricevuti correttamente
// - 2 = errore timeout
// - 3 = errore dati in arrivo oltre la dimensione massima
// - 4 = errore dati incompleti
byte Ricevi(Stream &seriale, unsigned char * pacchetto, size_t dimensione) {
// dimensione array di appoggio
#define MAX 2
//indice del byte ricevuto all'interno della struttura di destinazione
//inizializzo con un valore fuori range massimo
static uint32_t i = dimensione;
// indice del carattere arrivato dentro l'array di appoggio
static uint8_t j = 0;
// array di appoggio per decodificare la stringa HEX in numero
static char stringa[MAX + 1] = "";
// valore millis per controllo timeout
static uint32_t inizio = millis();
// valore appena ricevuto
byte valore;
while (seriale.available()) {
valore = seriale.read();
// controllo che carattere è
switch (valore) {
case CHR_START:
// è il carattere di inizio trasmissione
// azzero gli indici
i = 0;
j = 0;
// azzero l'array di appoggio;
stringa[0] = '\0';
stringa[1] = '\0';
stringa[2] = '\0';
// faccio partire il tempo per il timeout
inizio = millis();
break;
case CHR_STOP:
// è il carattere di fine trasmissione
if (i == dimensione) {
return RX_OK;
} else {
//il terminatore è arrivato troppo presto
return RX_LOW;
}
break;
case CHR_SEP:
// è il carattere separatore
// salvo il byte
if (i < dimensione) {
*(pacchetto+i) = strtol(stringa, NULL, 16);
} else {
// ho superato la dimensione della struttura
return RX_HIGH;
}
// devo passare al byte successivo
i++;
j = 0;
// azzero l'array di appoggio;
stringa[0] = '\0';
stringa[1] = '\0';
stringa[2] = '\0';
break;
default:
// è un carattere da convertire
if (j < MAX) {
stringa[j] = valore;
j++;
}
break;
}
// controllo il timeout
if ((millis() - inizio) > TIMEOUT) {
// azzero tutto prima di restituire errore
i = 0;
j = 0;
// azzero l'array di appoggio;
stringa[0] = '\0';
stringa[1] = '\0';
stringa[2] = '\0';
return RX_TIMEOUT;
}
}
return RX_WAIT;
}
datastruct.h
// DEFINIZIONE STRUTTURE DATI - deve essere uguale su trasmettitore e ricevitore
typedef struct {
// id messaggio = mi serve un valore univoco per avere crc diversi anche con dati uguali
// è un valore arbitrario deciso dal trasmittente, non viene usato dal ricevente
// uso il valore di millis()
uint32_t idmsg;
// id del destinatario
uint32_t destinatario;
// valori da comunicare al destinatario
uint32_t comando;
uint32_t valore;
} t_dati;
#define DIMDATI (sizeof(t_dati))
typedef struct {
t_dati dati;
uint32_t crc;
} t_pack;
#define DIMPACK (sizeof(t_pack))
define.h
/*************************************************************************************
* Comandi:
* 0 = richiesta stato (errori)
* 1 = accendi luce
* 2 = suona beep
* 3 = accendi + suona
*
* 100 = risposta richiesta stato (errori)
* 101 = slave comunica rilevazione sensore in tempo
* 102 = workout finito
*
* 200 = check presenza slave (ricerca iniziale)
* 201 = risposta presenza ok
*
* 253 = pacchetto corrotto (crc non valido)
* 254 = comando sconosciuto
* 255 = comando ricevuto correttamente
*/
#define CMD_STATUS 0
#define CMD_LIGHT 1
#define CMD_BEEP 2
#define CMD_BOTH 3
#define CMD_REPLY 100
#define CMD_FGHTOK 101
#define CMD_FINISH 102
#define CMD_START 103
#define CMD_CHECK 200
#define CMD_CHKOK 201
#define CMD_ERRCRC 253
#define CMD_ERRCMD 254
#define CMD_TXOK 255
funz-crc32.c
#ifndef __SAM3X8E__
#ifndef __PGMSPACE_H_
#include <avr/pgmspace.h>
#endif
#endif
#ifdef __SAM3X8E__
const unsigned long crctab32[] =
#else
const uint32_t crctab32[] PROGMEM =
#endif
{
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
/*
* Update a CRC check on the given buffer.
*/
unsigned long calc_crc32(unsigned char* buf, unsigned int len)
{
unsigned long crc;
unsigned int i;
crc = 0xffffffff;
for (i = 0; i < len; i++)
#if defined(__SAM3X8E__)
crc = ((crc >> 8) & 0x00ffffff) ^ crctab32[(crc ^ *buf++) & 0xff];
#else
crc = ((crc >> 8) & 0x00ffffff) ^ pgm_read_dword_near(&crctab32[(crc ^ *buf++) & 0xff]);
#endif
return (crc ^ 0xffffffff);
}
funz-crc32.h
#ifdef __cplusplus
extern "C" {
#endif
unsigned long calc_crc32(unsigned char*, int);
#ifdef __cplusplus
}
#endif
Poi c'è la parte specifica del MASTER
sketch principale
/********************************************************************************
PROGETTO LUCI ALLENAMENTO
Trasmissione
- Arduino Mega
- Modulo HC12
Organizzazione:
N 0 = master
N 1..n = slave
Modalità:
- Ciclica 123 123
- Reverse 12321
- Random
Parametri:
- Tempo acceso
- Tempo morto tra uno e l'altro
- Numero ripetizioni
- Tipo avviso (luce / beep)
Elenco pin:
- 0: riservato usb
- 1: riservato usb
- A8: Button Sx
- A9: Button Ok
- A10: Button Giu
- A11: Button Su
- A12: Button Dx
- 7: Buzzer
- 6: Hc12 Set
- 14: Hc12 Rx (Serial3)
- 15: Hc12 Tx (Serial3)
- 20: Lcd sda
- 21: Lcd scl
- 0:
- 0:
********************************************************************************/
// funzione calcolo CRC32 - grazie Guglielmo GPB01
#include "funz-crc32.h"
// ******************************************************************************
// DEFINIZIONE STRUTTURE DATI - deve essere uguale su trasmettitore e ricevitore
#include "datastruct.h"
// ******************************************************************************
// DEFINIZIONE COSTANTI COMANDI - STATI - deve essere uguale su trasmettitore e ricevitore
#include "define.h"
// ******************************************************************************
// FUNZIONI INVIO / RICEZIONE - deve essere uguale su trasmettitore e ricevitore
#include "comunicazione.h"
// librerie
#include <Wire.h>
#include <PCF8574_HD44780_I2C.h>
//#include <SoftwareSerial.h> // su MEGA non serve
#include <EEPROM.h>
// pin modulo HC12
#define HC12RxdPin 14 // Recieve Pin on HC12
#define HC12TxdPin 15 // Transmit Pin on HC12
#define HC12SetPin 6 // Transmit Pin on HC12
HardwareSerial* HC12;
//SoftwareSerial HC12(HC12TxdPin,HC12RxdPin); // Create Software Serial Port
// imposto DISPLAY LCD I2C
//PCF8574_HD44780_I2C lcd(0x27, 20, 4);
PCF8574_HD44780_I2C lcd(0x3F, 20, 4);
// parametri pulsanti
#define BTN_SU A11
#define BTN_GIU A10
#define BTN_OK A9
#define BTN_DX A12
#define BTN_SX A8
bool btn_su_stato, btn_giu_stato, btn_ok_stato, btn_dx_stato, btn_sx_stato = false;
uint32_t btn_pressione_lunga = 1000;
uint32_t btn_pressione_breve = 150;
// parametri buzzer
#define BEEP_PIN 7
#define BEEP_OK 4000
#define BEEP_ERR 400
#define BEEP_DELAY 100
// numero di slave trovati
uint32_t NumTarget = 0;
// struttura per la trasmissione / ricezione dei dati
t_pack tx, rx;
// variabile per il risultato della trasmissione
uint8_t ricevuto;
// variabili per il timeout della trasmissione
#define TX_TIMEOUT 1000
uint32_t tx_inizio;
// parametri allenamento
// fight = tempo per raggiungere il target e spegnere la luce
// rest = intervallo tra un fight e l'altro
// rep_max = ripetizioni
// mode = modalità
// alert = tipo avviso luce / beep
#define MODE_MAX 2 //valore massimo modalità
#define MODE_CLC 0
#define MODE_REV 1
#define MODE_RND 2
#define ALERT_MAX 2
#define ALERT_LIGHT 0
#define ALERT_BEEP 1
#define ALERT_BOTH 2
struct t_workout {
uint32_t fight;
uint32_t rest;
uint32_t rep;
uint32_t mode;
uint32_t alert;
} workout;
uint32_t errori, bug = 0;
uint32_t rep, target = 0;
// parametri EEPROM
#define EEPROM_START 0
//#define EEPROM_DIM (sizeof(uint32_t))
// parametri stato loop
uint8_t stato = 0;
#define STATO_FIGHT_INVIA 0
#define STATO_FIGHT_WAIT 1
#define STATO_REST 2
// parametri temporizzazione workout
uint32_t workout_inizio, workout_ritardo;
// conto alla rovescia iniziale
#define COUNTDOWN 5
// parametri polling (interrogazione ciclica slave)
bool polling = false;
// variabili varie di appoggio
int32_t i, direzione, inizio;
char stringa[20];
bool finito;
void setup() {
// seriale a 9600
Serial.begin(9600);
// HC12 a 9600
HC12 = &Serial3;
(*HC12).begin(9600);
// impostazione HC12
pinMode(HC12SetPin,OUTPUT);
digitalWrite(HC12SetPin,HIGH); // LOW = AT mode - HIGH = modo normale
delay(200);
// pulsanti
pinMode(BTN_SU, INPUT_PULLUP);
pinMode(BTN_GIU, INPUT_PULLUP);
pinMode(BTN_OK, INPUT_PULLUP);
pinMode(BTN_DX, INPUT_PULLUP);
pinMode(BTN_SX, INPUT_PULLUP);
// buzzer
pinMode(BEEP_PIN, OUTPUT);
noTone(BEEP_PIN);
// inizializzo LCD
lcd.init();
lcd.backlight();
// scrivo un messaggio di attesa
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Searching...");
delay(200);
// cerco gli slave
CercaSlave();
//NumTarget = 1;
lcd.clear();
// ricerca slave finita
// verifico quanti ne ho trovati
if (NumTarget == 0) {
// nessuno slave trovato
lcd.setCursor(0,0);
lcd.print("No target found");
lcd.setCursor(0,2);
lcd.print("Restart & try again");
Beep(2,BEEP_ERR);
// mi fermo qua
while (1);
}
// impostazioni allenamento
Settings();
lcd.noCursor();
lcd.noBlink();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Ready to fight?");
lcd.setCursor(2,1);
lcd.print("Press OK to start!");
lcd.setCursor(0,3);
lcd.print(" <OK>");
// aspetto il pulsante ok
while (LeggiPulsante(BTN_OK, &btn_ok_stato) == 0);
// conto alla rovescia
lcd.clear();
for (i=COUNTDOWN; i>0; i--) {
lcd.setCursor(6,1);
lcd.print("Wait ");
dtostrf(i,2,0,stringa);
lcd.print(stringa);
Beep(1,BEEP_OK);
delay(500);
lcd.clear();
delay(500);
}
lcd.setCursor(8,1);
lcd.print("GO!");
Beep(4,BEEP_OK);
// aggiorno il display
lcd.clear();
//AggiornaDisplayRun(false);
// mando a tutti gli slave il comando di START
for (i=1; i<=NumTarget; i++) {
// trasmetto il comando
tx.dati.idmsg = millis();
tx.dati.destinatario = i;
tx.dati.comando = CMD_START;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti((*HC12), (unsigned char *)&tx, DIMPACK);
// non aspetto la risposta
} // for
// si comincia
randomSeed(analogRead(0));
stato = STATO_FIGHT_INVIA;
if (workout.mode == MODE_RND) {
target = random(NumTarget) + 1;
} else {
target = 1;
}
direzione = +1;
rep = 1;
workout_inizio = millis();
workout_ritardo = 0;
}
void loop() {
// verifico lo stato attuale
switch (stato) {
case STATO_FIGHT_INVIA:
if ((millis() - workout_inizio) > workout_ritardo) {
// devo inviare il comando allo slave - DEVO PREVEDERE DEI RETRY SE NON RICEVO RISPOSTA
tx.dati.idmsg = millis();
tx.dati.destinatario = target;
switch (workout.alert) {
case ALERT_BEEP:
tx.dati.comando = CMD_BEEP;
break;
case ALERT_BOTH:
tx.dati.comando = CMD_BOTH;
break;
default:
tx.dati.comando = CMD_LIGHT;
break;
}
tx.dati.valore = workout.fight;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti((*HC12), (unsigned char *)&tx, DIMPACK);
// aggiorno il display
AggiornaDisplayRun(false);
// controllo se lo slave ha ricevuto il comando entro il timeout
finito = false;
tx_inizio = millis();
while ((!finito) && ((millis() - tx_inizio) <= TX_TIMEOUT)) {
ricevuto = Ricevi((*HC12), (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che il comando sia corretto
if (rx.dati.comando == CMD_TXOK) {
// lo slave ha risposto
finito = true;
}
}
}
} // while
// controllo se sono uscito per risposta o timeout
if (finito) {
// faccio partire il tempo
workout_inizio = millis();
workout_ritardo = workout.fight * 1000;
// abilito il polling degli slave
polling = true;
// passo allo stato successivo
stato = STATO_FIGHT_WAIT;
} else {
// lo slave non ha risposto
// aumento il contatore errori trasmissione
bug++;
// rimango nello stato invio per riprovare???
stato = STATO_FIGHT_INVIA;
} // if
}
break;
case STATO_FIGHT_WAIT:
// verifico se lo slave comunica la fine anticipata
if ((millis() - workout_inizio) > workout_ritardo) {
// tempo scaduto, passo in attesa
stato = STATO_REST;
} else {
ricevuto = Ricevi((*HC12), (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che il comando sia corretto
if (rx.dati.comando == CMD_FGHTOK) {
// lo slave ha comunicato che ha rilevato il sensore entro il tempo previsto
// quindi passo subito alla fase successiva
stato = STATO_REST;
// imposto ritardo 0 per entrare subito nella fase successiva
workout_inizio = millis();
workout_ritardo = 0;
}
}
}
} // else
break;
case STATO_REST:
if ((millis() - workout_inizio) > workout_ritardo) {
// ciclicamente interrogo gli slave
if (polling) {
errori = 0;
// chiamo tutti gli slave
for (i=1; i<=NumTarget; i++) {
// trasmetto il comando
tx.dati.idmsg = millis();
tx.dati.destinatario = i;
tx.dati.comando = CMD_STATUS;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti((*HC12), (unsigned char *)&tx, DIMPACK);
// aspetto la risposta
inizio = millis();
while ((millis() - inizio) <= TX_TIMEOUT) {
ricevuto = Ricevi((*HC12), (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che il comando sia corretto
if (rx.dati.comando == CMD_REPLY) {
// lo slave ha risposto
// aggiorno gli errori
errori += rx.dati.valore;
// esco dal while
break;
}
}
}
} // while
// devo verificare se sono uscito perché lo slave ha risposto
// oppure perché è passato troppo tempo
if ((millis() - inizio) > TX_TIMEOUT) {
// devo decidere cosa fare se lo slave non risponde...
;
}
} // for
// disabilito il polling fino al prossimo fight
polling = false;
}
// aggiorno il display
AggiornaDisplayRun(false);
// decido il prossimo slave
switch (workout.mode) {
case MODE_CLC: //123123
target++;
if (target > NumTarget) {
target = 1;
}
break;
case MODE_REV: //12321
target += direzione;
if (target <= 1) {
direzione = +1;
} else if (target >= (NumTarget-1)) {
direzione = -1;
}
break;
case MODE_RND: //random
target = random(NumTarget) + 1;
break;
}
// aumento il round
rep++;
// faccio partire il tempo
workout_inizio = millis();
workout_ritardo = workout.rest * 1000;
// passo allo stato successivo
stato = STATO_FIGHT_INVIA;
}
break;
default: // stato sconosciuto
// devo decidere cosa fare
;
} // switch
// verifico se ho finito
if (rep > workout.rep) {
rep--; // torno indietro di 1 solo per questioni di visualizzazione
AggiornaDisplayRun(true);
// comunico a tutti gli slave che abbiamo finito
for (i=1; i<=NumTarget; i++) {
// trasmetto il comando
tx.dati.idmsg = millis();
tx.dati.destinatario = i;
tx.dati.comando = CMD_FINISH;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti((*HC12), (unsigned char *)&tx, DIMPACK);
// non aspetto la risposta
} // for
// aspetto in eterno...
while (1);
}
}
funzioni messe in un tab separato
// funzione che legge un pulsante
// PIN = pin da leggere
// premuto = puntatore alla variabile premuto riferita al pulsante
// restituisce: 0 = non premuto
// 1 = pressione breve
// 2 = pressione lunga
uint8_t LeggiPulsante(uint8_t PIN, bool *premuto) {
// variabili gestione pressione
static uint32_t old_time, new_time;
if ((digitalRead(PIN) == LOW) && !(*premuto)) {
old_time = millis();
*premuto = true;
}
if ((*premuto) && (digitalRead(PIN) == HIGH)) {
new_time = millis();
if ((new_time - old_time) > btn_pressione_lunga) {
*premuto = false;
return 2;
} else if ((new_time - old_time) > btn_pressione_breve) {
*premuto = false;
return 1;
}
}
if (*premuto) {
if ((millis() - old_time) > btn_pressione_lunga) {
*premuto = false;
return 2;
}
}
return 0;
}
// funzione che cerca gli slave
void CercaSlave() {
bool finito;
uint8_t i, ricevuto;
uint32_t inizio;
finito = false;
i = 1;
while (!finito) {
// provo a contattare uno slave
tx.dati.idmsg = millis();
tx.dati.destinatario = i;
tx.dati.comando = CMD_CHECK;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti((*HC12), (unsigned char *)&tx, DIMPACK);
// aspetto la risposta
inizio = millis();
while ((millis() - inizio) <= TX_TIMEOUT) {
ricevuto = Ricevi((*HC12), (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che il comando sia corretto
if (rx.dati.comando == CMD_CHKOK) {
// lo slave ha risposto
// aumento il progressivo slave
NumTarget++;
i++;
// esco dal while
break;
}
}
}
} // while
// devo verificare se sono uscito perché lo slave ha risposto
// oppure perché è passato troppo tempo
if ((millis() - inizio) > TX_TIMEOUT) {
// l'ultimo slave provato non ha risposto, quindo ho finito
finito = true;
}
}
}
// funzione per impostare i parametri allenamento
void Settings() {
#define MAX_OPZIONI 5
bool finito;
int8_t direzione;
int8_t opzione, btn;
// recupero i parametri in EEPROM
EEPROM.get(EEPROM_START, workout);
// Visualizzo il numero di slave trovati
// e chiedo i parametri
Beep(1,BEEP_OK);
finito = false;
direzione = 0;
opzione = 0;
AggiornaDisplaySettings(opzione);
while (!finito) {
// controllo il pulsante SU
btn = LeggiPulsante(BTN_SU, &btn_su_stato);
if (btn != 0) {
direzione = +1;
}
// controllo il pulsante GIU
btn = LeggiPulsante(BTN_GIU, &btn_giu_stato);
if (btn != 0) {
direzione = -1;
}
// cambio il valore in base alla posizione i
if (direzione != 0) {
switch (opzione) {
case 0: // rep
workout.rep += direzione;
if ((workout.rep < 1) || (workout.rep > 999)) {
workout.rep = 1;
}
break;
case 1: // fight
workout.fight += direzione;
if ((workout.fight < 1) || (workout.fight > 999)) {
workout.fight = 1;
}
break;
case 2: // rest
workout.rest += direzione;
if ((workout.rest < 0) || (workout.rest > 999)) {
workout.rest = 0;
}
break;
case 3: // mode
workout.mode += direzione;
if (workout.mode > MODE_MAX) {
workout.mode = 0;
}
break;
case 4: // alert
workout.alert += direzione;
if (workout.alert > ALERT_MAX) {
workout.alert = 0;
}
break;
}
AggiornaDisplaySettings(opzione);
//Beep(1,BEEP_OK);
direzione = 0;
}
// controllo il pulsante DX
btn = LeggiPulsante(BTN_DX, &btn_dx_stato);
if (btn != 0) {
Beep(1,BEEP_OK);
opzione = ((opzione+1) % MAX_OPZIONI);
AggiornaDisplaySettings(opzione);
}
// controllo il pulsante SX
btn = LeggiPulsante(BTN_SX, &btn_sx_stato);
if (btn != 0) {
Beep(1,BEEP_OK);
opzione--;
if (opzione < 0) {
opzione = MAX_OPZIONI - 1;
}
AggiornaDisplaySettings(opzione);
}
// controllo il pulsante OK
btn = LeggiPulsante(BTN_OK, &btn_ok_stato);
if (btn != 0) {
Beep(2,BEEP_OK);
finito = true;
}
} // while
// salvo i parametri in eeprom
EEPROM.put(EEPROM_START, workout);
}
// funzione che aggiorna il display delle impostazioni
void AggiornaDisplaySettings(uint8_t pos) {
lcd.blink();
// prima riga
lcd.setCursor(0,0);
lcd.print("Target ");
dtostrf(NumTarget,2,0,stringa);
lcd.print(stringa);
lcd.setCursor(11,0);
lcd.print("Round ");
dtostrf(workout.rep,3,0,stringa);
lcd.print(stringa);
// seconda riga
lcd.setCursor(0,1);
lcd.print("Fight ");
dtostrf(workout.fight,3,0,stringa);
lcd.print(stringa);
lcd.setCursor(11,1);
lcd.print("Rest ");
dtostrf(workout.rest,3,0,stringa);
lcd.print(stringa);
// terza riga
lcd.setCursor(0,2);
lcd.print("Mode ");
switch (workout.mode) {
case MODE_CLC:
lcd.print("CLC");
break;
case MODE_REV:
lcd.print("REV");
break;
case MODE_RND:
lcd.print("RND");
break;
}
lcd.setCursor(11,2);
lcd.print("Alert ");
switch (workout.alert) {
case ALERT_LIGHT:
lcd.print("LGH");
break;
case ALERT_BEEP:
lcd.print("BIP");
break;
case ALERT_BOTH:
lcd.print("L+B");
break;
}
// quarta riga
lcd.setCursor(0,3);
switch (pos) {
case 0: // rep
lcd.setCursor(19,0);
break;
case 1: // fight
lcd.setCursor(8,1);
break;
case 2: // rest
lcd.setCursor(19,1);
break;
case 3: // mode
lcd.setCursor(6,2);
break;
case 4: // alert
lcd.setCursor(17,2);
break;
}
}
// funzione che aggiorna il display durante l'allenamento
void AggiornaDisplayRun(bool finito) {
//lcd.noCursor();
lcd.noBlink();
// prima riga
lcd.setCursor(0,0);
lcd.print("Mode ");
switch (workout.mode) {
case MODE_CLC:
lcd.print("Cyclic ");
break;
case MODE_REV:
lcd.print("Reverse");
break;
case MODE_RND:
lcd.print("Random ");
break;
}
lcd.setCursor(15,0);
if ((stato == STATO_FIGHT_INVIA) || (stato == STATO_FIGHT_WAIT)) {
lcd.print("Fight");
} else if (stato == STATO_REST) {
lcd.print("Rest ");
} else {
lcd.print(" ");
}
// seconda riga
lcd.setCursor(0,1);
lcd.print("Round ");
dtostrf(rep,3,0,stringa);
lcd.print(stringa);
lcd.print("/");
dtostrf(workout.rep,3,0,stringa);
lcd.print(stringa);
// terza riga
lcd.setCursor(0,2);
lcd.print("Target ");
dtostrf(target,2,0,stringa);
lcd.print(stringa);
lcd.print("/");
dtostrf(NumTarget,2,0,stringa);
lcd.print(stringa);
// quarta riga
lcd.setCursor(0,3);
lcd.print("Bug ");
dtostrf(bug,3,0,stringa);
lcd.print(stringa);
lcd.setCursor(11,3);
lcd.print("Error ");
dtostrf(errori,3,0,stringa);
lcd.print(stringa);
if (finito) {
lcd.setCursor(14,0);
lcd.print("FINISH");
}
}
// funzione che fa il BEEP
void Beep(uint8_t num, uint32_t tipoSquillo) {
for (int i=0;i<num;i++) {
tone(BEEP_PIN,tipoSquillo,BEEP_DELAY); // tone(pin,frequenza,durata);
delay(BEEP_DELAY*2);
}
}
Infine la parte degli SLAVE
skecth principale
/********************************************************************************
PROGETTO LUCI ALLENAMENTO
Ricezione
- Arduino Nano / Pro Mini
- Modulo HC12
Organizzazione:
N 0 = master
N 1..n = slave
Modalità:
- Ciclica 123 123
- Reverse 12321
- Random
Parametri:
- Tempo acceso
- Tempo morto tra uno e l'altro
- Numero ripetizioni
- Tipo avviso (luce / beep)
Elenco pin:
- 0: riservato usb
- 1: riservato usb
- 2: Tm clk
- 3: Tm dio
- 4: Hc12 Rx
- 5: Hc12 Tx
- 6: Hc12 Set
- 7: Buzzer
- 8: Led verde
- 9: Led rosso
- 10: Dip-switch (pull-up)
- 11: Dip-switch (pull-up)
- 12: Dip-switch (pull-up)
- 13:
- A0:
- A1:
- A2:
- A3: Sensore Sharp
- A4:
- A5:
- A6: n/d su pro mini
- A7: n/d su pro mini
********************************************************************************/
// funzione calcolo CRC32 - grazie Guglielmo GPB01
#include "funz-crc32.h"
// ******************************************************************************
// DEFINIZIONE STRUTTURE DATI - deve essere uguale su trasmettitore e ricevitore
#include "datastruct.h"
// ******************************************************************************
// DEFINIZIONE COSTANTI COMANDI - STATI - deve essere uguale su trasmettitore e ricevitore
#include "define.h"
// ******************************************************************************
// FUNZIONI INVIO / RICEZIONE - deve essere uguale su trasmettitore e ricevitore
#include "comunicazione.h"
// librerie
#include <TM1637Display.h>
#include <SoftwareSerial.h>
//#include <avr/wdt.h>
// software reset per pro-mini
void(* SoftwareReset) (void) = 0;
// pin modulo HC12
#define HC12RxdPin 4 // Recieve Pin on HC12
#define HC12TxdPin 5 // Transmit Pin on HC12
#define HC12SetPin 6 // Transmit Pin on HC12
SoftwareSerial HC12(HC12TxdPin,HC12RxdPin); // Create Software Serial Port
// pin display TM1637
#define TMCLK 2
#define TMDIO 3
TM1637Display lcd(TMCLK, TMDIO);
// intervallo lampeggio
#define BLINK 500UL
// variabile millis per lampeggio
uint32_t blink_inizio;
// variabile per il lampeggio
bool vuoto = false;
// led
#define PINGRN 8
#define PINRED 9
#define COLOFF 0
#define COLGRN 1
#define COLRED 2
#define COLYLW 3
// buzzer
#define BEEP_PIN 7
#define BEEP_OK 4000
#define BEEP_DELAY 100
// DIP switch
#define PINDIP1 12
#define PINDIP2 11
#define PINDIP3 10
// numero di questo slave
uint8_t slave_id = 1;
// struttura per la trasmissione / ricezione dei dati
t_pack tx, rx;
// variabile per il risultato della trasmissione
uint8_t ricevuto;
// variabili allenamento
#define STATO_ATTESA 0
#define STATO_FIGHT 1
uint8_t stato;
uint32_t fight_inizio, fight_timeout;
#define ALERTTIMEOUT 900
bool alert = false;
uint32_t alert_inizio = 0;
uint32_t errori;
// variabili controllo sensore
#define SENSORE A3
#define SENSMEDIA 5
#define SENSSOGLIA 550
// variabili varie di appoggio
bool finito;
void setup() {
// seriale a 9600
Serial.begin(9600);
// HC12 a 9600
HC12.begin(9600);
// impostazione HC12
pinMode(HC12SetPin,OUTPUT);
digitalWrite(HC12SetPin,HIGH); // LOW = AT mode - HIGH = modo normale
delay(200);
// buzzer
pinMode(BEEP_PIN, OUTPUT);
// dip switch
pinMode(PINDIP1, INPUT_PULLUP);
pinMode(PINDIP2, INPUT_PULLUP);
pinMode(PINDIP3, INPUT_PULLUP);
// leggo il valore del dip switch
if (digitalRead(PINDIP1) == LOW) {
slave_id += 1;
}
if (digitalRead(PINDIP2) == LOW) {
slave_id += 2;
}
if (digitalRead(PINDIP3) == LOW) {
slave_id += 4;
}
// led
pinMode(PINGRN,OUTPUT);
pinMode(PINRED,OUTPUT);
AccendiLed(COLOFF);
// imposto il display
lcd.setBrightness(0x0a);
lcd.clear();
lcd.showNumberDecEx(slave_id,0b01000000,true,2,0);
vuoto = false;
blink_inizio = millis();
// aspetto l'abbinamento col master
finito = false;
while (!finito) {
ricevuto = Ricevi(HC12, (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che il comando sia corretto
if (rx.dati.comando == CMD_CHECK) {
// verifico che la richiesta sia per questo slave
if (rx.dati.destinatario == slave_id) {
// devo rispondere per confermare l'abbinamento
tx.dati.idmsg = millis();
tx.dati.destinatario = 0;
tx.dati.comando = CMD_CHKOK;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti(HC12, (unsigned char *)&tx, DIMPACK);
// posso uscire dal while
finito = true;
}
}
}
}
// lampeggio il numero slave
if ((millis() - blink_inizio) > BLINK) {
blink_inizio += BLINK;
if (vuoto) {
lcd.showNumberDecEx(slave_id,0b01000000,true,2,0);
} else {
lcd.clear();
}
vuoto = !vuoto;
}
} // while
// abbinamento master ok
// visualizzo il numero slave
lcd.clear();
lcd.showNumberDecEx(slave_id,0b01000000,true,2,0);
// aspetto lo START dal master
finito = false;
while (!finito) {
ricevuto = Ricevi(HC12, (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che il comando sia corretto
if (rx.dati.comando == CMD_START) {
// verifico che la richiesta sia per questo slave
if (rx.dati.destinatario == slave_id) {
// posso uscire dal while
finito = true;
}
}
}
}
} // while
alert = false;
errori = 0;
stato = STATO_ATTESA;
}
void loop() {
switch (stato) {
case STATO_ATTESA:
// sono in attesa di comandi dal master
ricevuto = Ricevi(HC12, (unsigned char *)&rx, DIMPACK);
if (ricevuto == RX_OK) {
// i dati sono arrivati
// verifico il CRC
if (rx.crc == calc_crc32((unsigned char *)&rx.dati, DIMDATI)) {
// i dati sono validi
// verifico che la richiesta sia per questo slave
if (rx.dati.destinatario == slave_id) {
// verifico il comando
switch (rx.dati.comando) {
case CMD_LIGHT:
case CMD_BEEP:
case CMD_BOTH:
// devo rispondere per confermare
tx.dati.idmsg = millis();
tx.dati.destinatario = 0;
tx.dati.comando = CMD_TXOK;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti(HC12, (unsigned char *)&tx, DIMPACK);
// devo eseguire l'azione
switch (rx.dati.comando) {
case CMD_LIGHT:
AccendiLed(COLYLW);
// disabilito lo spegnimento del led alert
alert = false;
break;
case CMD_BEEP:
Beep();
break;
case CMD_BOTH:
AccendiLed(COLYLW);
Beep();
// disabilito lo spegnimento del led alert
alert = false;
break;
}
// imposto il tempo
fight_inizio = millis();
fight_timeout = rx.dati.valore * 1000;
stato = STATO_FIGHT;
break;
case CMD_STATUS:
// comando per il polling
// trasmetto gli errori
tx.dati.idmsg = millis();
tx.dati.destinatario = 0;
tx.dati.comando = CMD_REPLY;
tx.dati.valore = errori;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti(HC12, (unsigned char *)&tx, DIMPACK);
break;
case CMD_FINISH:
// il workuot è finito
// blocco tutto
AccendiLed(COLOFF);
while (1) {
if (VerificaSensore() > SENSSOGLIA) {
// resetto arduino
SoftwareReset();
}
}
} // switch
} // if
}
} else {
// controllo se devo spegnere il led di avviso
if (alert && ((millis() - alert_inizio) > ALERTTIMEOUT)) {
AccendiLed(COLOFF);
alert = false;
}
// verifico se sono passato vicino al sensore mentre la luce è spenta
if (!alert && (VerificaSensore() > SENSSOGLIA)) {
// segnalo l'errore col led rosso
AccendiLed(COLRED);
// aumento gli errori
errori++;
AggiornaDisplay();
alert = true;
alert_inizio = millis();
}
}
break;
case STATO_FIGHT:
// controllo il sensore entro il timeout previsto
if ((millis() - fight_inizio) <= fight_timeout) {
// controllo il sensore
if (VerificaSensore() > SENSSOGLIA) {
// se arrivo in tempo avviso che è ok col led verde
AccendiLed(COLGRN);
AggiornaDisplay();
alert = true;
alert_inizio = millis();
// devo avvisare il master di non aspettare il resto del tempo
// e di passare subito al round successivo
tx.dati.idmsg = millis();
tx.dati.destinatario = 0;
tx.dati.comando = CMD_FGHTOK;
tx.dati.valore = 0;
tx.crc = calc_crc32((unsigned char *)&tx.dati,DIMDATI);
Trasmetti(HC12, (unsigned char *)&tx, DIMPACK);
// torno nello stato attesa
stato = STATO_ATTESA;
}
} else {
// non sono arrivato in tempo
// segnalo l'errore col led rosso
AccendiLed(COLRED);
// aumento gli errori
errori++;
AggiornaDisplay();
alert = true;
alert_inizio = millis();
// torno nello stato attesa
stato = STATO_ATTESA;
}
break;
} // switch
}
funzioni mese in un tab separato
// funzione che accende il led del colore desiderato
// colore = colore da accendere
// uso un transistor bc327 per accendere i led, quindi logica invertita
void AccendiLed(uint8_t colore) {
switch (colore) {
case COLGRN:
// logica diretta
//digitalWrite(PINGRN, HIGH);
//digitalWrite(PINRED, LOW);
//logica invertita
digitalWrite(PINGRN, LOW);
digitalWrite(PINRED, HIGH);
break;
case COLRED:
// logica diretta
//digitalWrite(PINRED, HIGH);
//digitalWrite(PINGRN, LOW);
//logica invertita
digitalWrite(PINRED, LOW);
digitalWrite(PINGRN, HIGH);
break;
case COLYLW:
// logica diretta
//digitalWrite(PINGRN, HIGH);
//digitalWrite(PINRED, HIGH);
//logica invertita
digitalWrite(PINGRN, LOW);
digitalWrite(PINRED, LOW);
break;
default:
// logica diretta
//digitalWrite(PINGRN, LOW);
//digitalWrite(PINRED, LOW);
//logica invertita
digitalWrite(PINGRN, HIGH);
digitalWrite(PINRED, HIGH);
break;
}
}
// funzione che aggiorna il display
void AggiornaDisplay() {
uint32_t temp;
temp = ((slave_id % 100) * 100) + (errori % 100);
lcd.showNumberDecEx(temp,0b01000000,true,4,0);
}
// funzione che verifica il sensore
uint16_t VerificaSensore() {
uint16_t media = 0;
uint8_t i;
for (i=0; i<SENSMEDIA; i++) {
media += analogRead(SENSORE);
}
//Serial.println(media);
return (media / SENSMEDIA);
}
// funzione che fa il BEEP
void Beep() {
// buzzer passivo
//tone(BEEP_PIN,BEEP_OK,BEEP_DELAY); // tone(pin,frequenza,durata);
// buzzer attivo
digitalWrite(BEEP_PIN, HIGH);
delay(BEEP_DELAY);
digitalWrite(BEEP_PIN, LOW);
}
*/
Dovrei aver messo tutto.
Il codice è più o meno commentato. Se però qualcosa non è chiaro chiedi pure.
Ho fatto diversi test e secondo me funziona tutto... comunque se trovi qualcosa che non va segnalamelo che sistemo.
Se può servire posto anche gli schemi "elettrici"... anche se non sono fatti benissimo...
EDIT: dimenticavo... le funzioni per il crc-32 sono di Guglielmo. Lo ho spudoratamente copiate.