Arduino+Nunchuck+Nunchucky

Ciao a tutti, sto iniziando a cimentarmi nella sperimentazione del nunchuck e del suo collegamento ad arduino, io ho acquistato il nunchucky, ovvero questo:

e stavo provando il tutto...le cose che non mi sono chiare sono:

1)Perchè quando sta tutto fermo, in particolare quando è fermo il joystick al centro, mi restituisce dei valori del joystick= 10 e -2 (x e y rispettivamente) ?

  1. Mi sapete indicare a che servono istruzioni del genere:

Serial.print(buf[5]&1 ? "\t- " : "\t[z] ");

Conosco il loro effetto ma vorrei capire meglio il funzionamento in generale di un'istruzione del genere (proprio dal punto di vista della programmazione)

  1. Perchè quando appoggio il nunchuck sul tavolo le accelerazioni percepite su x y e z sono diverse da 0 ? non dovrebbero essere = 0 ?

Vi ringrazio e vi terrò aggiornati su qualunque progresso!!!

Il codice che ho utilizzato è questo:

//  nunchuck.pde
//

#include <Wire.h>

uint8_t buf[6];     // buffer for the six-bytes packet coming from Nunchuck
int cnt=0;          // count of bytes in the buffer
int readcnt=0;      // number of times we read a packet from Nunchuck
int ledPin=13;      // which pin will host the blinking LED


uint8_t nunchuk_decode(uint8_t x)   // decode nunchuck data
{
  return (x^0x17)+0x17;             // not that an hard encryption...
}


void nunchuck_ack()                 // acknowledge a Nunchuck packet
{
  Wire.beginTransmission(0x52);     // send a zero to device 0x52
  Wire.send(0);
  Wire.endTransmission();
}


void setup()                        // initialization
{
  int n;
  digitalWrite(ledPin, HIGH);       // turn on LED
  
  for(n=0; n<6; n++) buf[n]=0;      // fill buffer with zero values
  
  Serial.begin(115200);              // console init

  Wire.begin();                     // TWI init
  
  Wire.beginTransmission(0x52);     // nunchuck init
  Wire.send(0x40);
  Wire.send(0);
  Wire.endTransmission();

  digitalWrite(ledPin, LOW);        // turn off LED
  
  Serial.print("!-- start\n");
}


void loop()                         // main loop
{
  Wire.requestFrom(0x52, 6);        // request data from nunchuck
  while(Wire.available())           // read data and light the LED
  {
    buf[cnt++] = nunchuk_decode(Wire.receive());
    digitalWrite(ledPin, HIGH);
  }

  if(cnt>=6)                        // an entire Nunchuck packet was read?
  {
    printdata();                    // yes, print it
    cnt=0;                          // clear buffer counter
    nunchuck_ack();                 // acknowledge received packet
    digitalWrite(ledPin, LOW);      // turn off the LED
    delay(800);                     // wait 800msec before next loop
  }
}


void printdata()                    // print out the values
{
  int n;   // note: the 123,131,524,597... depend on my Nunchuck calibration
 
  Serial.print(++readcnt, DEC);

  Serial.print("\t");               // joystick x/y values
  Serial.print((int)buf[0]-123);
  Serial.print("\t");  
  Serial.print((int)buf[1]-131);
  
  n=(buf[2]<<2)+((buf[5]>>2)&3)-524;  // accel X
  Serial.print("\t");  
  Serial.print(n);

  n=(buf[3]<<2)+((buf[5]>>4)&3)-597;  // accel Y
  Serial.print("\t");  
  Serial.print(n);

  n=(buf[4]<<2)+((buf[5]>>6)&3)-668;  // accel Z
  Serial.print("\t");  
  Serial.print(n);

  Serial.print(buf[5]&1 ? "\t- " : "\t[z] ");
  Serial.print(buf[5]&2 ? "-" : "[c]");

  Serial.print ("\r\n");
}

f.schiano:
1)Perchè quando sta tutto fermo, in particolare quando è fermo il joystick al centro, mi restituisce dei valori del joystick= 10 e -2 (x e y rispettivamente) ?

è normale, basta fare qualche lettura nel setup e trovare il proprio "zero" (-2 e 10 nel tuo caso) e con un poco di matematica risolvi il problema. Si chiama Calibrazione :slight_smile:
se noti, muovendo il joystic al massimo anche i valori massimi saranno diversi

f.schiano:
2) Mi sapete indicare a che servono istruzioni del genere:

Serial.print(buf[5]&1 ? "\t- " : "\t[z] ");

buf[5] & 1 algebra booleana: prende il dato contenuto nella cella 5 dell'array e ogni suo bit fa una and con 1; in pratica ritorna zero se e solo se buf[5] era 0 (mi pare che il contenuto di buf[5] sia modificato dall'operazione!).
poi c'è un if: condizione?vera:falsa che è come l'if if (condizione){vera}else{falsa}. \t è il carattere di escape che vuol dire "disegna un tab", esattamente come \n è "vai a capo" e \0 è "fine stringa" ecc....

f.schiano:
3) Perchè quando appoggio il nunchuck sul tavolo le accelerazioni percepite su x y e z sono diverse da 0 ? non dovrebbero essere = 0 ?

NO! prima di tutto il nunchuck legge le accelerazione, quindi se lo stai muovendo (salvo moto rettilineo uniforme) vedi l'accelerazione (o decellerazione)
Ma comunque prima di usare i valori, devi fare esattamenet come il bottone: Calibrare i valori letti! metti il nunchuck sul tavolo e leggi lo 0 di X e Y, poi lo metti "di lato" e leggi la Z.
Per avere valori migliori sarebbe da leggere il valore massimo e minimo per ogni asse in stato di quiete, ovvero da fermo, e poi cambiare i valori letti prima in modo che:
sqrt(xx+yy*+z*z) in stato di quite dia sempre lo stesso numero (più o meno, la perfezione non esiste) in qualsiasi posizione del nunchuck.

kiss :slight_smile:

lesto:
buf[5] & 1 algebra booleana: prende il dato contenuto nella cella 5 dell'array e ogni suo bit fa una and con 1; in pratica ritorna zero se e solo se buf[5] era 0 (mi pare che il contenuto di buf[5] sia modificato dall'operazione!).

Non sarebbe corretto. L'AND a livello di bit confronta bit per bit fra i 2 operandi, non fa and AND di ogni bit del primo operando con 1. E' leggermente differente.
In pratica una operazione del tipo OPER1 AND 1 controlla solo il 1° bit di OPER1 dato che il secondo operando è il decimale 1, ovvero in rappresentazione binaria 00000001. Restituisce 1, cioè TRUE, solo nel caso in cui anche il 1° bit di OPER1 sia ad 1, ma gli altri suoi bit saranno confrontati con gli "0" del 2° operando.
Questo genere di confronti si usa(va) per controllare 1 bit di stato: quel codice controlla cioè se il valore del 1° bit (probabilmente avrà un qualche significato) sia 1.

Successivamente vedi un confronto differente: Serial.print(buf[5]&2 ? "-" : "

");
Questo confronta il 2° bit di quell'elemento dell'array. Difatti 2 in binario ad 8 bit è 00000010, ed il confronto darà esito positivo solo se anche il 2° bit del 1° operando sarà a 1. Gli altri bit di buf[5] non intervengono nella determinazione del risultato perché il 2° operando, che si può intendere come una specie di "maschera", li filtra tutti: essendo tutti i bit meno il 2° di valore 0, con un AND qualunque valore assumeranno i corrispondenti bit del 1° operando il confronto darà sempre 0. Quindi in questo caso è solo il 2° bit ad influire sul risultato.

leo72:
Non sarebbe corretto. L'AND a livello di bit confronta bit per bit fra i 2 operandi, non fa and AND di ogni bit del primo operando con 1. E' leggermente differente.
In pratica una operazione del tipo OPER1 AND 1 controlla solo il 1° bit di OPER1 dato che il secondo operando è il decimale 1, ovvero in rappresentazione binaria 00000001. Restituisce 1, cioè TRUE, solo nel caso in cui anche il 1° bit di OPER1 sia ad 1, ma gli altri suoi bit saranno confrontati con gli "0" del 2° operando.
Questo genere di confronti si usa(va) per controllare 1 bit di stato: quel codice controlla cioè se il valore del 1° bit (probabilmente avrà un qualche significato) sia 1.

confermo, mea culpa :slight_smile:

Ciao a tutti sono di nuovo qui...

ho calibrato la posizione del joystick, ora come faccio a calibrare gli accelerometri?

GRAZIE

ciao,
è abbastanza semplice.
appoggi l'accelerometro in piano, e leggi x, y, e z. toterai che 2 dei valori (teoricamente x e y) sono pressochè uguali: quel valore è lo zero dell'accelerometro (zero offset). Da tutte le letture dovresti sottrarre qual valore, se ottieni numeri negativi è perchè hai un'accelerazione contraria al verso dell'asse considerato.
tra l'altro hai un asse che legge G, quindi diverso dagli altri due. se da quel valore (asse Z in teoria) sottrai il valore di zero, otterrai il valore di G.

Per migliorare la tua calibrazione, sai che ad accelerometro fermo, qualsiasi sua la sua posizione/inclinazione, il vettore gravità deve avere modulo = G, quindi xx+yy+zz=GG
o se vuoi la più classica sqrt(xx+yy+z*z)=G (che è il teorema di pitagora in 3d)

Se i tuoi risultati sfasano molto è perchè probabilmente il valore dello zero offset è settato male. Potrebbe addirittura essere necessario avere valori di zero offset diversi per ogni asse: questo dipende oltre da fattori costruttivi, anche dal fatto che l'asse Z è un accelerometro in un chip a parte rispetto a X e Y, e quindi può essere tarato diversamente.

Non aspettarti valori perfetti, ma vicini al valore atteso: questo dipende dal fatto che per valori precisi dovresti usare accelerometri molto costosi, oppure il cui output sia rielaborato da qualche filtro che ti fa' solo credere di avere valori perfetti tramite arrotondamenti

Grazie lesto, sei gentilissimo...
Nei prossimi giorni aggiusto un pò la calibrazione, per ora sono arrivato a questo:

CALIBRATO

ESEMPIO MOVIMENTO SERVO

Non so come incorporare un video direttamente sul topic, se qualcuno me lo spiega lo faccio.

Ciao

non vorrei dire baggianate, ma pare che dall'asse Z sottrai anche G. questo è un grosso errore con cui ti troverai a litigare..

io uso questo codice

#include <Wire.h>

void setup()
{
  Serial.begin(19200);
  Serial.print ("Begin of setup\n");
  nunchuck_setpowerpins(); // use analog pins 2&3 as fake gnd & pwr
  nunchuck_init(); // send the initilization handshake
  Serial.print ("End of setup\n");
}

void loop()
{
  nunchuck_get_data();
  nunchuck_print_data();
  delay(100);
}


//
// Nunchuck functions
//

static uint8_t nunchuck_buf[6];   // array to store nunchuck data,
uint8_t ctrlr_type[6];


// Uses port C (analog in) pins as power & ground for Nunchuck
static void nunchuck_setpowerpins()
{
#define pwrpin PORTC3
#define gndpin PORTC2
    DDRC |= _BV(pwrpin) | _BV(gndpin);
    PORTC &=~ _BV(gndpin);
    PORTC |=  _BV(pwrpin);
    delay(100);  // wait for things to stabilize
}

// initialize the I2C system, join the I2C bus,
// and tell the nunchuck we're talking to it
void nunchuck_init()
{
  /*Wire.begin();			    // join i2c bus as master
  Wire.beginTransmission(0x52);	// transmit to device 0x52
  Wire.send(0x40);		// sends memory address
  Wire.send(0x00);		// sends sent a zero.
  Wire.endTransmission();	// stop transmitting*/

  Serial.print ("Begin nunchuck init\n");
  byte cnt;
  Wire.begin();

  Serial.print ("Step 1\n");
  // init controller
  delay(1);
  Wire.beginTransmission(0x52);	// device address
  Wire.send(0xF0);			  // 1st initialisation register
  Wire.send(0x55);			  // 1st initialisation value
  Wire.endTransmission();
  delay(1);
  Wire.beginTransmission(0x52);
  Wire.send(0xFB);			  // 2nd initialisation register
  Wire.send(0x00);			  // 2nd initialisation value
  Wire.endTransmission();
  delay(1);

  Serial.print ("Step 2\n");
  // read the extension type from the register block
  Wire.beginTransmission(0x52);
  Wire.send(0xFA);			  // extension type register
  Wire.endTransmission();
  Wire.beginTransmission(0x52);
  Wire.requestFrom(0x52, 6); 		  // request data from controller
  for (cnt = 0; cnt < 6; cnt++) {
    if (Wire.available()) {
	ctrlr_type[cnt] = Wire.receive(); // Should be 0x0000 A420 0101 for Classic Controller, 0x0000 A420 0000 for nunchuck
    }
  }
  Wire.endTransmission();
  delay(1);

  Serial.print ("Step 3\n");
  // send the crypto key (zeros), in 3 blocks of 6, 6 & 4.
  Wire.beginTransmission(0x52);
  Wire.send(0xF0);			  // crypto key command register
  Wire.send(0xAA);			  // sends crypto enable notice
  Wire.endTransmission();
  delay(1);
  Wire.beginTransmission(0x52);
  Wire.send(0x40);			  // crypto key data address
  for (cnt = 0; cnt < 6; cnt++) {
	Wire.send(0x00);			  // sends 1st key block (zeros)
  }
  Wire.endTransmission();
  Wire.beginTransmission(0x52);
  Wire.send(0x40);			  // sends memory address
  for (cnt = 6; cnt < 12; cnt++) {
	Wire.send(0x00);			  // sends 2nd key block (zeros)
  }
  Wire.endTransmission();
  Wire.beginTransmission(0x52);
  Wire.send(0x40);			  // sends memory address
  for (cnt = 12; cnt < 16; cnt++) {
    Wire.send(0x00);			  // sends 3rd key block (zeros)
  }
  Wire.endTransmission();
  delay(1);
  // end device init
}

// Send a request for data to the nunchuck
// was "send_zero()"
void nunchuck_send_request()
{
  Wire.beginTransmission(0x52);	// transmit to device 0x52
  Wire.send(0x00);		// sends one byte
  Wire.endTransmission();	// stop transmitting
}

// Receive data back from the nunchuck,
int nunchuck_get_data()
{
    int cnt=0;
    Wire.requestFrom (0x52, 6);	// request data from nunchuck
    while (Wire.available ()) {
	// receive byte as an integer
	nunchuck_buf[cnt] = nunchuk_decode_byte(Wire.receive());
	cnt++;
    }
    nunchuck_send_request();  // send request for next data payload
    // If we recieved the 6 bytes, then go print them
    if (cnt >= 5) {
     return 1;   // success
    }
    return 0; //failure
}

// Print the input data we have recieved
// accel data is 10 bits long
// so we read 8 bits, then we have to add
// on the last 2 bits.  That is why I
// multiply them by 2 * 2
void nunchuck_print_data()
{
  static int i=0;
  int joy_x_axis = nunchuck_buf[0];
  int joy_y_axis = nunchuck_buf[1];
  int accel_x_axis = nunchuck_buf[2]; // * 2 * 2;
  int accel_y_axis = nunchuck_buf[3]; // * 2 * 2;
  int accel_z_axis = nunchuck_buf[4]; // * 2 * 2;

  int z_button = 0;
  int c_button = 0;

  // byte nunchuck_buf[5] contains bits for z and c buttons
  // it also contains the least significant bits for the accelerometer data
  // so we have to check each bit of byte outbuf[5]
  if ((nunchuck_buf[5] >> 0) & 1)
    z_button = 1;
  if ((nunchuck_buf[5] >> 1) & 1)
    c_button = 1;

  if ((nunchuck_buf[5] >> 2) & 1)
    accel_x_axis += 2;
  if ((nunchuck_buf[5] >> 3) & 1)
    accel_x_axis += 1;

  if ((nunchuck_buf[5] >> 4) & 1)
    accel_y_axis += 2;
  if ((nunchuck_buf[5] >> 5) & 1)
    accel_y_axis += 1;

  if ((nunchuck_buf[5] >> 6) & 1)
    accel_z_axis += 2;
  if ((nunchuck_buf[5] >> 7) & 1)
    accel_z_axis += 1;

  Serial.print(i,DEC);
  Serial.print("\t");

  Serial.print("joy:");
  Serial.print(joy_x_axis,DEC);
  Serial.print(",");
  Serial.print(joy_y_axis, DEC);
  Serial.print("  \t");

  Serial.print("acc:");
  Serial.print(accel_x_axis, DEC);
  Serial.print(",");
  Serial.print(accel_y_axis, DEC);
  Serial.print(",");
  Serial.print(accel_z_axis, DEC);
  Serial.print("\t");

  Serial.print("but:");
  Serial.print(z_button, DEC);
  Serial.print(",");
  Serial.print(c_button, DEC);

  Serial.print("\r\n");  // newline
  i++;
}

// Encode data to format that most wiimote drivers except
// only needed if you use one of the regular wiimote drivers
char nunchuk_decode_byte (char x)
{
  x = (x ^ 0x17) + 0x17;
  return x;
}

cosa ne dite?? per calibrarlo cosa devo fare??? se volessi usare un servo devo usare un comando tipo

if (accel_x_axis >= numero && accel_y_axis >= numero && accel_z_axis >= numero )
{
servo.write(90) 
}

sarebbe giusto??? se si dove lo devo mettere questa parte di codice??

dipende cosa vuoi far fare al tuo servo. se seguire l'inclinazione del nunckuk avanti-indietro(pitch) destra-sinistra(roll). in tondo (yaw) non puoi, perchè sfrutti la G (e vedi che inizia a saltare fuori?)

vorrei comandare un robot se sposto in avanti il nunchuck fa andare avanti il robot se lo sposto a destra mandare il robot a destra e cosi via

allora secondo me la cosa più semplice è:
sapendo che il nunckuck stazionario da lettura G, se lungo l'asse X leggi + G o - G devi girare al massimo a destra o sinistra, per gli angoli intermedi usi una proporzione.
Lo stesso vale per l'asse Y.

Ciò vale solo se a nunchud dritto leggi G solo sull'asse 0, altrimenti ci vole qualche sbatti con le coordinate polari sferiche, niente più di un paio di seni, coseni e wikipedia.

Per controllare se stai facendo i calcoli giusti, ti consiglio di interfaciare arduino col PC e via seriale inviare gli angoli e i dati in modo (magari con processing) da avere un riscontro visivo.

lesto:
non vorrei dire baggianate, ma pare che dall'asse Z sottrai anche G. questo è un grosso errore con cui ti troverai a litigare..

Cosa significa questa cosa che hai detto??

Ora provo a rifare la calibrazione!!! Come mi hai indicato tu!!!

Con il codice postato da crono precedentemente (che mi sembra funzioni meglio del mio) mi esce:

918 joy:134,129 acc:82,134,145 but:1,1
919 joy:134,129 acc:80,134,145 but:1,1
920 joy:133,129 acc:80,134,145 but:1,1
921 joy:133,129 acc:80,136,147 but:1,1
922 joy:133,129 acc:80,134,145 but:1,1
923 joy:134,129 acc:80,136,145 but:1,1
924 joy:134,129 acc:80,134,145 but:1,1
925 joy:133,129 acc:80,134,145 but:1,1
926 joy:134,129 acc:80,136,145 but:1,1
927 joy:134,129 acc:80,136,145 but:1,1
928 joy:134,129 acc:80,134,145 but:1,1
929 joy:133,129 acc:80,136,145 but:1,1
930 joy:134,129 acc:80,136,145 but:1,1
931 joy:134,129 acc:82,134,145 but:1,1
932 joy:134,129 acc:80,134,145 but:1,1
933 joy:134,129 acc:80,134,147 but:1,1
934 joy:133,129 acc:80,136,145 but:1,1
935 joy:133,129 acc:80,134,145 but:1,1
936 joy:134,129 acc:80,136,147 but:1,1
937 joy:133,129 acc:80,136,145 but:1,1
938 joy:134,129 acc:80,136,145 but:1,1
939 joy:133,129 acc:80,134,145 but:1,1

con il joystick fermo e il nunchuck posato di lato su un tavolo!!!

Che faccio???

nota che se appoggiato è comunque inclinato, poichè non hai 2 valori simili.
prova ad appoggiarlo su un lato, dovresti vedere 2 valori uguali. segnali tutti e 3.
poi lo metti preciso orizzontale, e leggi altri 2 valori uguali. segni
poi lo metti preciso verticale e leggi altri 2 valori uguali. segna.
Ora tutti i valori uguali dovrebbero essere simili, l'errore è dato dallo strumento e dalle professionalità del test.

Per verticale e orizzontale intendo come punto di riferimento il sensore accelerometro, che è in piano però non quando il nunkuk è appoggiato, ma rispetto alla parte finale, cmq mentre fai i test te ne accorgerai

ah ricordati di rimanere fermo qualche secondo prima di leggere i valori o rischi di leggere l'accelerazione

Ma se vedi nel mio messaggio precedente tra i valori di acc non ce ne sono 2 uguali !!!

I valori sopra riportati sono usciti con nunchuck appoggiato di lato a sinistra su un piano. (diciamo come in figura)

y e z sono molto simili, devi trovare l'inclinazione giusta. prova a mettere qualche spessore e vedrai che trovi i 2 valori quasi uguali

Quindi una volta trovata l'inclinazione che mi fa avere 2 valori uguali di y e z quelli sono gli zeri del mio accelerometro?

e poi la x?

si, poi inclini l'accelerometro in modo da avere la X uguale alla Y o Z

un altro metodo è vedere il valore minimo e massimo da fermo, e lo zero è in mezzo ai due valori, dovrebbe essere simile al valore trovato prima

Ok.

Però scusami perchè forse non sto capendo niente...ma i valori di x y e z che leggo cosa rappresentano? Quando il nunchuck sta fermo? Il nunchuck ha un giroscopio giusto? Quindi sarebbe l'inclinazione dei vari assi rispetto alla terna fissa del pavimento, o sbaglio completamente??

GRAZIE