Rover controllato con Arduino + Nunchuck (wired)

Ciao a tutti
è un po’ che ero fermo, ma si sa, le vacanze servono proprio a quello… a prendersi una meritata pausa.

Come al solito avevo ordinato un pezzo interessante ma di cui non sapevo cosa fare.

Si tratta di un display OLED con connessione I2C, che meriterebbe una trattazione a parte, e me ne occuperò di sicuro…
Avevo anche da parte un telaio con due motori per una automobile a due ruote motrici.

Nunchuck Wii

Nunchuck Wii

L’idea è stata istantanea.
Il solito Nunchuck utilizza, guarda caso, la stessa interfaccia I2C per la comunicazione con Arduino. Proprio per questo ho deciso di utilizzarlo per comandare la macchina e farla andare un po’ dove voglio…

L’interfaccia I2C è molto potente. Si tratta di una interfaccia seriale che con solo 4 pin vi permette di collegare parecchi dispositivi in parallelo.

Quindi ricapitolando la mia auto avrebbe avuto alcuni sistemi indipendenti e correlati tra loro tramite Arduino.

  • il Nunchuck collegato tramite I2C
  • un chip L293D come interfaccia verso i due motori
  • i due motori
  • un display OLED da 0.96″ con interfaccia I2C, con funzioni di debug

Di seguito un video di presentazione del funzionamento del progetto:


 

Il Nunchuck e il bus I2C

Per delucidazioni sul Nunchuck e sul bus I2C fate riferimento a questo articolo.


Il chip L293D e i due motori

Il chip L293D è un integrato molto utile per controllare motori e separare le alimentazioni usate per la logica di controllo da quella utilizzata per i motori.

L293  pinout

L293 pinout

Il funzionamento di questo chip è molto semplice, a prescidere da quelle che potrebbe apparire a prima vista.

Ho collegato uno dei due motori al lato sinistro del chip (pin 3 e 6) e l’altro al lato destro (pin 11 e 12).

Arduino controlla lo stato di quei pin in due modi.

  • Può abilitare o disabilitare uno dei due lati fornendo un ingresso alto o basso al pin 1 (lato sinistro) o al pin 2 (lato destro)
  • fa passare la corrente ai pin 3, 6, 11, 14 a seconda dello stato rispettivamente dei pin 2, 7, 10, 15.

L’alimentazione Vcc1 è il riferimento per i motori, Vcc2 è il riferimento per la logica di controllo.

La messa a terra è comune ai due circuiti.

La tabella della verità per ciascuno dei due lati è quella qui di seguito

Tabella della verità per ciascuno dei due motori

Tabella della verità per ciascuno dei due motori.

Nella tabella le colonne A e B rappresentano rispettivamente il valore logico sui pin 2 e 7 (motore 1, lato sinistro) o sui pin 10 e 15 (motore 2, lato destro). In questo modo si può quindi definire direzione e azionamento dei motori e di conseguenza se il rover si debba muovere o meno e in che direzione.

Per decidere invece la velocità dei motori, si deve agire sui pin di ENABLE (pin 1 per il lato sinistro e pin 9 per il lato destro) con un ingresso PWM. In questo modo i motori non andranno a velocità massima, ma avranno una velocità proporzionale al duty-cycle della PWM.


Il display OLED

Il display OLED mi ha procurato parecchi mal di testa, almeno fino a che non ho capito di che display si trattava (sí, si trattava di un acquisto compulsivo! 😉 ). È più o meno come quello in foto.

OLED 0.96" bicolor yellow-blue

Il display ha queste caratteristiche:

  • 0.96″
  • bicolore (giallo nella parte superiore, blu in quella inferiore). Sì proprio bicolore, con due zone di colori diversi.
  • risoluzione 128×64 (di cui 16 righe nella zona gialla e le restanti 48 nella zona blu)
  • Interfaccia I2C

Quando ho capito che era basato sul controller SSD1306, ho capito anche come comandarlo.

Ho scoperto la libreria U8G, una libreria molto potente e che supporta un sacco di display. La potete trovare a questo link.

Questa libreria consente di utilizzare delle funzioni grafiche primitive molto potenti con estrema semplicità. Alcuni esempi di comandi: drawBox disegna un rettangolo, drawCircle disegna un cerchio, drawLine disegna una linea, setFont imposta il font, ecc.).


Lo schema elettrico

Di seguito riporto lo schema del progetto se per caso voleste cimentarvi nel costruirlo.

Nunchuck Car OLED schematics

Il codice

Di seguito incollo il codice che ho scritto per il funzionamento del rover.

/* This code is based on the following code:
 - test code for the http://code.google.com/p/u8glib/ library
 */
// include the library code:
#include <U8glib.h>
#include <Wire.h>
#include <string.h>
#include <stdio.h>

// initialize the library with the numbers of the interface pins
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NO_ACK); // Display which does not send ACK
int disp_w = 128;
int disp_h = 64;

/*
Connessione L293D
 ----
 pin3 ----------------- ENABLE1 -| |- VSS ------- L293D power supply
 pin4 ----------------- INPUT1  -| |- INPUT4 ------------------ pin8
 motor 1 + ------------ OUTPUT1 -| |- OUTPUT4 ------------- motor2 +
 GND  ----------------- GND     -| |- GND ---------------------- GND
 GND  ----------------- GND     -| |- GND ---------------------- GND
 motor 1 - ------------ OUTPUT2 -| |- OUTPUT3 ------------- motor2 -
 pin2 ----------------- INPUT2  -| |- INPUT3 ------------------ pin7
 motor pwr supply ----- VS      -| |- ENABLE2 ----------------- pin6
 ----
 */
// Porte Motori (L293D)
int en1Pin=3; // pin 1 on L293
int in1Pin=4; // pin 2 on L293
int in2Pin=2; // pin 7 on L293
int en2Pin=6; // pin 9 on L293
int in3Pin=7; // pin 10 on L293
int in4Pin=8; // pin 15 on L293

// this comes from nunchuck init
uint8_t outbuf[6];
int joy_x_axis;
int joy_y_axis;
int accel_x_axis;
int accel_y_axis;
int accel_z_axis;
int z_button = 0;
int c_button = 0;

int t = 0;
int i = 0;
int cnt = 0;
int dtime = 50;

// Prepare U8G library regular font
void u8g_prepare(void) {
 u8g.setFont(u8g_font_6x10);
 u8g.setFontRefHeightExtendedText();
 u8g.setDefaultForegroundColor();
 u8g.setFontPosTop();
}

// Prepare U8G library Micro font
void u8g_prepare_micro(void) {
 u8g.setFont(u8g_font_micro);
 u8g.setFontRefHeightExtendedText();
 u8g.setDefaultForegroundColor();
 u8g.setFontPosTop();
}

void setup(){
 // init serial port
 Serial.begin(9600);
 Serial.println( "Nunchuck + OLED + L293D + Car");
 // init nunchuck interface and hardware
 Wire.begin ();
 nunchuck_init ();
 // init pins
 pinMode(en1Pin, OUTPUT);
 pinMode(in1Pin, OUTPUT);
 pinMode(in2Pin, OUTPUT);
 pinMode(en2Pin, OUTPUT);
 pinMode(in3Pin, OUTPUT);
 pinMode(in4Pin, OUTPUT);

 u8g_prepare();
 u8g.firstPage(); 
 do {
 u8g.setColorIndex(1);
 u8g.drawBox(0, 0, disp_w-1, (disp_h/4)-1); 
 u8g.setColorIndex(0);
 u8g.drawStr( int((disp_w - u8g.getStrWidth("Nunchuck + OLED")) / 2), 4, "Nunchuck + OLED");
 } 
 while( u8g.nextPage() );

 Serial.print ("Finished setup\n");

 delay (1000);
}
/*
void drawStrMid(int y, char words, int disp_w){
 u8g.drawStr( int((disp_w - u8g.getStrWidth(words)) / 2), y, words);
 }
 */
void nunchuck_init(){
 Wire.beginTransmission (0x52);
 Wire.write (0x40);
 Wire.write (0x00); 
 Wire.endTransmission ();
}

void send_zero(){
 Wire.beginTransmission (0x52);
 Wire.write (0x00);
 Wire.endTransmission ();
}

void decodeNunchuckData() {
 joy_x_axis = outbuf[0];
 joy_y_axis = outbuf[1];
 accel_x_axis = outbuf[2] * 2 * 2; 
 accel_y_axis = outbuf[3] * 2 * 2;
 accel_z_axis = outbuf[4] * 2 * 2;

 z_button = 0;
 c_button = 0;

 if ((outbuf[5] >> 0) & 1) 
 z_button = 1;
 if ((outbuf[5] >> 1) & 1)
 c_button = 1;
 if ((outbuf[5] >> 2) & 1) 
 accel_x_axis += 2;
 if ((outbuf[5] >> 3) & 1)
 accel_x_axis += 1;

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

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

void loop(){
 t++;
 long last = millis();

 if( t == 1) {
 t = 0;
 Wire.requestFrom (0x52, 6);
 while (Wire.available ()) {
 outbuf[cnt] = nunchuk_decode_byte (Wire.read ());
 cnt++;
 }
 if (cnt >= 5) {
 int z_button = 0;
 int c_button = 0;
 if ((outbuf[5] >> 0) & 1) 
 z_button = 1;
 if ((outbuf[5] >> 1) & 1)
 c_button = 1;
 }
 cnt = 0;
 send_zero();
 } // if(t==)

 decodeNunchuckData();
 drawNunchuckData();
 runMyCar();
 delay(dtime);
}

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

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

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

 Serial.print ("AccX: ");
 Serial.print (accel_x_axis, DEC);
 Serial.print ("\t");

 Serial.print ("AccY: ");
 Serial.print (accel_y_axis, DEC);
 Serial.print ("\t");

 Serial.print ("AccZ: ");
 Serial.print (accel_z_axis, DEC);
 Serial.print ("\t");

 Serial.print (z_button, DEC);
 Serial.print (" ");
 Serial.print (c_button, DEC);
 Serial.print ("\r\n");
 i++;
}

void drawNunchuckData() {
 int jx = map (joy_x_axis, 0, 256, 2, (disp_w/2)-3);
 int jy = map (joy_y_axis, 0, 256, disp_h-3, (disp_h/4)+2);
 int ax = map (accel_x_axis, 0, 1024, (disp_w/2)+1, disp_w-2);
 int ay = map (accel_y_axis, 0, 1024, disp_h-3, (disp_h/4)+2);
 int az = map (accel_z_axis, 0, 1024, disp_w/4, 3*(disp_w/4));

 u8g.firstPage(); 
 do {
 // draw debug area
 u8g_prepare_micro();
 drawLabelValue(13, 1, "jX: ", joy_x_axis);
 drawLabelValue(49, 1, "jY: ", joy_y_axis);
 drawLabelValue(86, 1, "set: ", i);
 drawLabelValue(13, 7, "aX: ", accel_x_axis);
 drawLabelValue(49, 7, "aY: ", accel_y_axis);
 drawLabelValue(86, 7, "aZ: ", accel_z_axis);
 u8g.drawBox (0,0,10,14);
 u8g_prepare();
 u8g.setColorIndex(0);
 u8g.drawStr(2, 3, "Z");
 if(z_button) {
 u8g.drawBox(1, 1, 8, 12); 
 }
 u8g.setColorIndex(1);
 u8g.drawBox (118,0,10,14);
 u8g.setColorIndex(0);
 u8g.drawStr(120, 3, "C");
 if(c_button) {
 u8g.drawBox(119, 1, 8, 12); 
 }

 //draw graphic area
 u8g.setColorIndex(1);
 u8g.drawVLine(31,16,46);
 u8g.drawHLine(1,40,62);
 u8g.drawDisc(jx,jy,2);

 u8g.drawVLine(95,16,46);
 u8g.drawHLine(65,40,62);
 u8g.drawDisc(ax,ay,2);

 if (az > 63){ 
 u8g.drawBox(63,16,az-63,3);
 } 
 else {
 u8g.drawBox(az,16,63-az,3); 
 }

 } 
 while( u8g.nextPage() );
 i++;
}

char nunchuk_decode_byte (char x) {
 x = (x ^ 0x17) + 0x17;
 return x;
}

void drawLabelValue(int x, int y, char *label, int val) {
 u8g.drawStr (x, y, label);
 u8g.setPrintPos ( x + u8g.getStrWidth(label), y);
 u8g.print (val);
}


void runMyCar() {
 int motor;
 int brake;
 int fwd;
 int spd;

 int jx = map (joy_x_axis, 29, 225, 0, 10);
 int jy = (32 * map (joy_y_axis, 29, 225, -7, 7)) ;

 // remember that runMotor ( motor, brake, fwd, spd )
 if (jx == 5 && jy == 0) {
 runMotor (1, HIGH, HIGH, 0);
 runMotor (2, HIGH, HIGH, 0);
 } 
 else {
 if (jx == 5) {
 if (jy > 0) {
 runMotor (1, LOW, HIGH, abs(jy));
 runMotor (2, LOW, HIGH, abs(jy));
 } 
 else {
 runMotor (1, LOW, LOW, abs(jy));
 runMotor (2, LOW, LOW, abs(jy));
 }
 } 
 else {
 if (jy == 0) {
 if (jx < 5){
 runMotor (1, LOW, HIGH, int((255*abs(5-jx))/5));
 runMotor (2, LOW, LOW, int((255*abs(5-jx))/5));
 } 
 else {
 runMotor (1, LOW, LOW, int((255*abs(5-jx))/5));
 runMotor (2, LOW, HIGH, int((255*abs(5-jx))/5));
 }
 } 
 else if (jy > 0) {
 if (jx < 5){
 runMotor (1, LOW, HIGH, abs(jy));
 runMotor (2, LOW, HIGH, int((jy*abs(5-jx))/5));
 } 
 else {
 runMotor (1, LOW, HIGH, int((jy*abs(5-jx))/5));
 runMotor (2, LOW, HIGH, abs(jy));
 }
 } 
 else { // jy < 0
 if (jx < 5){
 runMotor (1, LOW, LOW, abs(jy));
 runMotor (2, LOW, LOW, int((jy*abs(5-jx))/5));
 } 
 else {
 runMotor (1, LOW, LOW, int((jy*abs(5-jx))/5));
 runMotor (2, LOW, LOW, abs(jy));
 }
 }
 }
 }
}

void runMotor ( int motor, int brake, int fwd, int spd ) {
 int enPin = en1Pin;
 int fwdPin = in2Pin;
 int revPin = in1Pin;
 // motor 1 = motor A; motor 2 = motor B
 if ( motor == 2 ) {
 enPin = en2Pin;
 fwdPin = in3Pin;
 revPin = in4Pin;
 }

 if (brake == HIGH) {
 digitalWrite (fwdPin, HIGH);
 digitalWrite (revPin, HIGH);
 analogWrite (enPin, HIGH);
 } 
 else if (fwd == LOW) {
 int tmp = fwdPin;
 fwdPin = revPin;
 revPin = tmp;
 }
 digitalWrite (fwdPin, HIGH);
 digitalWrite (revPin, LOW);
 analogWrite (enPin, spd);
}

Arrivederci!!!

Pierluigi

4 comments

  1. Posso chiederti dove hai trovato lo schermo OLED per fritzing?
    Io trovo soltanto le librerie di Adafruit, ma lo schermo ha 8 pin…

    "Mi piace"

Lascia un commento

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.