Arduino, Android e Bluetooth: costruire un’auto telecomandata

Dopo mesi di inattività ho ripreso in mano il mio primo progetto con Arduino e ho deciso di evolverlo in qualcosa di più interattivo, così ho creato un auto telecomanda via Bluetooth da uno smartphone Android.

Questo progetto quindi mi ha richiesto lo studio (in realtà meno approfondito di quando mi aspettassi) della piattaforma mobile di Google.

Per rendere tutto più semplice ho deciso di utilizzare il toolkit Amarino, che permette la comunicazione tra Android e Arduino via Bluetooth, mettendo a disposizione due librerie, una per parte.

L’applicazione Android che funge da telecomando ha due modalità di controllo: tramite pulsanti oppure tramite i sensori di accelerazione dello smartphone (in base all’inclinazione viene inviato un segnale).

Elenco componenti

Il materiale di base necessario per questo progetto è lo stesso del suo predecessore, con l’aggiunta di:

Assemblaggio

Le indicazioni da seguire per il montaggio della macchinina sono quelle presenti nel mio precedente articolo.
L’unico passo aggiuntivo da compiere per realizzare questa evoluzione è il collegamento del modulo Bluetooth HC-05.
L’immagine seguente rappresenta lo schema elettrico del sistema:

VentuRoverBT

E qui si vede dove sono andato a posizionare il modulo:
VentuRoverBT

Codice sorgente

Per semplificare il progetto ho utilizzato la libreria MeetAndroid per la comunicazione.
Ad essa ho apportato una modifica per poterla combinare con la libreria SoftwareSerial in maniera tale da poter mappare il Bluetooth su una seconda porta seriale per tenere libera quella hardware (Pin 0 e 1) di Arduino (vedere sezione Download in fondo).

Di seguito il codice per Arduino:

#include <Servo.h>
#include <MeetAndroid.h>
#include <SoftwareSerial.h>


// On boards other than the Mega, use of the library disables analogWrite() (PWM)
// functionality on pins 9 and 10, whether or not there is a Servo on those pins.
Servo myservo;

const long baud = 38400;

// bluetooth
const int rxPin = 2;
const int txPin = 3;

const int servoPin  = 5;  // PWM

const int redPin    = 6;
const int yellowPin = 7;
const int greenPin  = 8;

const int motorPin1 = 9;  // H-bridge leg 1 (1A)
const int motorPin2 = 10; // H-bridge leg 2 (2A)
const int motorEnab = 11; // H-bridge enable pin (PWM)

const int signalPin = 12; // SRF05

int distance = 0;            // obstacle distance
const int numOfReadings = 3; // number of readings to take items in the array
int readings[numOfReadings]; // stores the distance readings in an array
int arrayIndex = 0;          // arrayIndex of the current item in the array
int total = 0;               // stores the cumlative total
int avgDistance = 0;         // stores the average value

// stop threshold
int thStop;


MeetAndroid meetAndroid(rxPin,txPin,baud);

String pDist;
String pVelo;
String pDire;
String pVers;

String strPrint;

void setup()
{
  Serial.begin(baud); // open serial port to receive data
  myservo.attach(servoPin); // attaches the servo pin to the servo object

  pinMode(redPin,  OUTPUT);
  pinMode(yellowPin, OUTPUT);
  pinMode(greenPin,  OUTPUT);
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorEnab, OUTPUT);

  // turn off motor
  digitalWrite(motorEnab, LOW);

  // steering in central position
  myservo.write(0);
  delay(100);
  straight();

  thStop = 10;

  // create array loop to iterate over every item in the array
  for (int thisReading = 0; thisReading < numOfReadings; thisReading++) {
    readings[thisReading] = 0;
  }

  // register callback function
  meetAndroid.registerFunction(moveCar, 'a');
}

void loop()
{
  distance = getDistance();

  total = total - readings[arrayIndex]; // subtract the last distance
  readings[arrayIndex] = distance;      // add distance reading to array
  total = total + readings[arrayIndex]; // add the reading to the total
  arrayIndex++;                         // go to the next item in the array

  // at the end of the array then start again
  if (arrayIndex >= numOfReadings)  {
    arrayIndex = 0;
  }

  // calculate the average distance
  avgDistance = total / numOfReadings;
  pDist = String(avgDistance);

  // you need to keep this in your loop() to receive events
  meetAndroid.receive();
}

/**
 * Calculate the distance of nearby obstacle
 * @return unsigned long
 */
long getDistance()
{
  unsigned long pulseTime;
  unsigned long distance;

  pinMode(signalPin, OUTPUT);
  digitalWrite(signalPin, LOW);      // set signal pin low
  delayMicroseconds(2);              // for 2 microseconds
  digitalWrite(signalPin, HIGH);     // send a trigger pulse
  delayMicroseconds(10);             // 10 microseconds long
  digitalWrite(signalPin, LOW);      // signal pin low waiting for echo pulse

  pinMode(signalPin, INPUT);
  pulseTime = pulseIn(signalPin, HIGH); // read echo pulse length in microseconds
  distance = pulseTime/58;              // distance in centimeters

  return distance;
}

/**
 * Turn on motor and move forward
 * @param int speed  Percentage 0-100%
 */
void forward(int speed)
{
  // obstacle too close
  if(avgDistance<=thStop)
  {
    speed = 0;
    digitalWrite(greenPin,LOW);
    digitalWrite(redPin,HIGH);
  }
  else
  {
    digitalWrite(greenPin,HIGH);
    digitalWrite(redPin,LOW);
  }

  int vel = map(speed, 0, 100, 0, 255);

  pDire = "Forward";
  pVelo = String(vel);

  if(speed==0)
  {
    pDire = "Stop";
  }

  digitalWrite(motorPin1, LOW);
  digitalWrite(motorPin2, HIGH);

  analogWrite(motorEnab, vel);
}

/**
 * Turn on motor and move backward
 * @param int speed  Percentage 0-100%
 */
void backward(int speed)
{
  int vel = map(speed, 0, 100, 100, 255);

  pDire = "Backward";
  pVelo = String(vel);

  digitalWrite(motorPin1, HIGH);
  digitalWrite(motorPin2, LOW);

  analogWrite(motorEnab, vel);
}

/**
 * Move the steering wheel
 * @param int direction  0=right, 1=left
 */
void steer(int direction)
{
  if(direction == 0)
  {
    // right
    myservo.write(180);
    pVers = "Right";
  }
  else if(direction == 1)
  {
    // left
    myservo.write(0);
    pVers = "Left";
  }
  else
  {
    pVers = "ERROR";
  }
}


/**
 * Move the steering in middle position
 */
void straight()
{
  if(myservo.read()==180)
  {
    // from right to middle
    myservo.write(50);
    pVers = "Straight";
  }
  else if(myservo.read()==0)
  {
    // from left to middle
    myservo.write(110);
    pVers = "Straight";
  }
}

/**
 * Read commands from remote control and make car moves
 */
void moveCar(byte flag, byte numOfValues)
{
  int length = meetAndroid.stringLength();
  char inputString[length];

  meetAndroid.getString(inputString);

  strPrint = String("Len=") + length + " - str=" + inputString;
  Serial.println(strPrint);

  int *lastCmd = split(inputString, ',');

  switch(*lastCmd)
  {
    case 0: // "stop"
      forward(0);
      break;

    case 1: // "forward"
      forward(100);
      break;

    case 2: // "backward"
      backward(100);
      break;
  }

  switch(*(lastCmd+1))
  {
    case 0: // "straight"
      straight();
      break;

    case 1: //"right"
      steer(0);
      break;

    case 2: // "left"
      steer(1);
      break;
  }

  // log on serial port
  strPrint = pDist+","+pVelo+","+pDire+","+pVers+","+(*lastCmd)+","+(*(lastCmd+1))+",#"+inputString+"#";
  Serial.println(strPrint);
}

/**
 * Split command string in parts
 * @param String string  The input string
 * @param char   delim   The boundary string
 */
int * split(String command, char delim)
{
  static int data[2];
  int numArgs = 0;
  int idx;
  String tmp;

  do
  {
    idx = command.indexOf(delim);
    if(idx != -1)
    {
	  tmp = command.substring(0, idx);
	  data[numArgs++] = tmp.toInt();
      command = command.substring(idx+1, command.length());
    }
    else if(command.length() > 0)
    {
      // last iteration
	  data[numArgs++] = command.toInt();
    }
  } while(idx>=0);

  Serial.println(String("uno=")+data[0]+" / due="+data[1]+" / tre="+data[2]);

  return data;
}

Per quanto riguarda il codice per Android riporto solo la porzione più significativa, il progetto completo è nella sezione Download.

VentuRoverBT
VentuRoverBT

public class MainActivity extends Activity implements OnClickListener, SensorEventListener {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		imgConnected    = (View) findViewById(R.id.connected);
		imgDisconnected = (View) findViewById(R.id.disconnected);
		txtDevice   = (TextView) findViewById(R.id.txtDevice);
		mStatusView = (TextView) findViewById(R.id.status_view);
		tv1 = (TextView) findViewById(R.id.tv1);
		tv2 = (TextView) findViewById(R.id.tv2);
		tv3 = (TextView) findViewById(R.id.tv3);

		mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
		mDisplay = mWindowManager.getDefaultDisplay();

		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

		btnForward  = (Button) findViewById(R.id.btnForward);
		btnBackward = (Button) findViewById(R.id.btnBackward);
		btnRight    = (Button) findViewById(R.id.btnRight);
		btnLeft     = (Button) findViewById(R.id.btnLeft);
		btnAccelerometer = (ToggleButton) findViewById(R.id.btnAccelerometer);

		btnAccelerometer.setOnClickListener(this);

		writeLog("onCreate");
		setStatus(DISCONNECTED);
	}

	@Override
	protected void onStart() {
		super.onStart();

		motor = 0;
		direction = 0;

		lastChange = System.currentTimeMillis();

		registerReceiver(arduinoReceiver, new IntentFilter(AmarinoIntent.ACTION_CONNECTED));
		registerReceiver(arduinoReceiver, new IntentFilter(AmarinoIntent.ACTION_DISCONNECTED));
		registerReceiver(arduinoReceiver, new IntentFilter(AmarinoIntent.ACTION_CONNECTION_FAILED));
		registerReceiver(arduinoReceiver, new IntentFilter(AmarinoIntent.ACTION_PAIRING_REQUESTED));
		registerReceiver(arduinoReceiver, new IntentFilter(AmarinoIntent.ACTION_RECEIVED));

		if(device_address!=null)
		{
			Amarino.connect(this, device_address);
		}
	}

	@Override
	protected void onStop() {
		super.onStop();

		Amarino.disconnect(this, device_address);

		unregisterReceiver(arduinoReceiver);
	}

	/**
	 * Read the toggle button and set the command mode (buttons or sensors)
	 */
	private void setMode()
	{
		motor = 0;
		direction = 0;

		if(btnAccelerometer.isChecked())
        {
        	// turn on sensors
        	mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);

        	stopTimer();

    		if (D) Log.d(TAG, "setMode() - SENSOR ON");
        }
        else
        {
        	// turn off sensors
        	mSensorManager.unregisterListener(this);

        	startTimer();

    		if (D) Log.d(TAG, "setMode() - SENSOR OFF");
        }

		motor = 0;
		direction = 0;
	}

	/**
	 * Timer: every 100 milliseconds check buttons state and send commands
	 */
	public void startTimer(){
		t = new Timer();
		task = new TimerTask() {

			@Override
			public void run() {
				runOnUiThread(new Runnable() {

					@Override
					public void run() {

						// check buttons state
						motor = 0;
						direction = 0;

						if(btnForward.isPressed())
						{
							motor = 1;
						}
						else if(btnBackward.isPressed())
						{
							motor = 2;
						}

						if(btnRight.isPressed())
						{
							direction = 1;
						}
						else if(btnLeft.isPressed())
						{
							direction = 2;
						}

						updateState();
					}
				});
			}
		};

		t.scheduleAtFixedRate(task, 0, 100);
	}

	/**
	 * Stop the timer
	 */
	public void stopTimer(){

		t.cancel();
    	t.purge();
	}

	@Override
	public final void onSensorChanged(SensorEvent event)
	{
	    // Many sensors return 3 values, one for each axis.
	    float x = event.values[0];
	    float y = event.values[1];
	    float z = event.values[2];

	    // check phone orientation and adjust sensor values
	    switch (mDisplay.getRotation())
	    {
		    case Surface.ROTATION_0:
		        x = event.values[0];
		        y = event.values[1];
		        break;
		    case Surface.ROTATION_90:
		        x = -event.values[1];
		        y = event.values[0];
		        break;
		    case Surface.ROTATION_180:
		        x = -event.values[0];
		        y = -event.values[1];
		        break;
		    case Surface.ROTATION_270:
		        x = event.values[1];
		        y = -event.values[0];
		        break;
	    }

	    if(x>=-1 && x<=1)
	    {
	    	// straight
	    	direction = 0;
	    }
	    else if(x<-1)
	    {
	    	// right
	    	direction = 1;
	    }
	    else if(x>1)
	    {
	    	// left
	    	direction = 2;
	    }

	    if(y>=-1 && y<=1)
	    {
	    	// stop
	    	motor = 0;
	    }
	    else if(y<-1)
	    {
	    	// forward
	    	motor = 1;
	    }
	    else if(y>1)
	    {
	    	// backward
	    	motor = 2;
	    }

	    updateState();

	    // display values using TextView
	    tv1.setText("X axis\t\t"+x);
	    tv2.setText("Y axis\t\t"+y);
	    tv3.setText("Z axis\t\t" +z);
	}

	/**
     * Send commands to Arduino
     */
	private void updateState()
	{
		if(System.currentTimeMillis()-lastChange > DELAY) {

			String msg = motor+","+direction;
			Amarino.sendDataToArduino(this, device_address, 'a', msg);

			writeLog(motor+", "+direction);
			lastChange = System.currentTimeMillis();
		}
	}

	@Override
    public boolean onOptionsItemSelected(MenuItem item)
	{
		if (D) Log.d(TAG, "onOptionsItemSelected()");

        Intent serverIntent = null;
        switch (item.getItemId())
        {
	        case R.id.connect_scan:
	            // Launch the DeviceListActivity to see devices and do scan
	            serverIntent = new Intent(this, DeviceListActivity.class);
	            startActivityForResult(serverIntent, 0);
	            return true;
        }
        return false;
    }

	public void onActivityResult(int requestCode, int resultCode, Intent data)
	{
		if(resultCode == Activity.RESULT_OK)
		{
            connectDevice(data);
        }

		if (D) Log.d(TAG, "onActivityResult()");
    }

    private void connectDevice(Intent data)
    {
    	device_name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
    	device_address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
    	Amarino.connect(this, device_address);

        if (D) Log.d(TAG, "connectDevice() - "+device_name+" - "+device_address);
    }

    /**
     * Show on the screen connection state, name and address of paired device
     * @param status
     */
	public void setStatus(int status)
	{
		if (D) Log.d(TAG, "setStatus() - BEGIN - "+status);

		switch(status)
		{
			case CONNECTED:
				txtDevice.setText(device_name+" ("+device_address+")");
				imgConnected.setBackgroundColor(getResources().getColor(R.color.connected_on));
				imgDisconnected.setBackgroundColor(getResources().getColor(R.color.disconnected_off));
				break;

			case DISCONNECTED:
				txtDevice.setText(R.string.nodevice);
				imgConnected.setBackgroundColor(getResources().getColor(R.color.connected_off));
				imgDisconnected.setBackgroundColor(getResources().getColor(R.color.disconnected_on));
				break;
		}

		if (D) Log.d(TAG, "setStatus() - END");
	}

	class ArduinoReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			final String action = intent.getAction();

			if (D) Log.d(TAG, "onReceive() - BEGIN");

			// this is how to retrieve the address of the sender
			//final String address = intent.getStringExtra(AmarinoIntent.EXTRA_DEVICE_ADDRESS);

			if (AmarinoIntent.ACTION_CONNECTED.equals(action)){
				// connection has been established
				setStatus(CONNECTED);
			}
			else if (AmarinoIntent.ACTION_DISCONNECTED.equals(action)){
				// disconnected from a device
				setStatus(DISCONNECTED);
			}
			else if (AmarinoIntent.ACTION_CONNECTION_FAILED.equals(action)){
				// connection attempt was not successful
				//setStatus(DISCONNECTED);
			}
			else if (AmarinoIntent.ACTION_PAIRING_REQUESTED.equals(action)){
				// a notification message to pair the device has popped up
				//setStatus(DISCONNECTED);
			}
			else if (AmarinoIntent.ACTION_RECEIVED.equals(intent.getAction())){

				// the device address from which the data was sent,
				// we don't need it here but to demonstrate how you retrieve it
				//final String address = intent.getStringExtra(AmarinoIntent.EXTRA_DEVICE_ADDRESS);

				// the type of data which is added to the intent
				final int dataType = intent.getIntExtra(AmarinoIntent.EXTRA_DATA_TYPE, -1);

				switch (dataType){

					case AmarinoIntent.STRING_EXTRA:
					//String data = intent.getStringExtra(AmarinoIntent.EXTRA_DATA);
					// do something useful with the received data
					break;
				}
			}

			if (D) Log.d(TAG, "onReceive() - END");
		}
	}
}

Download

Datasheets

Reference

47 thoughts to “Arduino, Android e Bluetooth: costruire un’auto telecomandata”

  1. ciao piacere gabriele,, complimenti per il blog… molto esaustivo… una cosa vorrei chiederti… non riesco a trovare il tuo precedente progetto di macchina telecomandata.. mi serve per capire quali materiali di base mi servono… e poi un’aptra domanda.. ma se metto un motore più potente poi devo cambiare anche altri componenti giusto?… GRAZIE

    1. Ciao andrea, potresti mettere un link per il dowload per l’applicazione? Oppure inviarmi l’apk via e-mail?
      Grazie

  2. grazie andrea. Comunque ho visto che usi un motor driver, invece nei modelli che si vendono già assemblati c’è un controller per la velocità(forse perchè in quei casi non usano schede madri)… Mi puoi dare delucidazioni? Io vorrei usare una scheda arduino e mi sono studiato il tuo codice, però non so se devo comprare solo il motore oppure prenderci anche il controller per la velocità.. GRAZIE

  3. un’altra domanda… il modulo bluetooth va agganciato alla breadbord giusto? E nella figura sopra è rappresentato dal rettangolino rosso giusto?

  4. Ciao Gabriele, per quanto riguarda il motor driver ci sono due strade: la prima, ovvero quella che ho scelto io, più semplice ed economica, è quella di utilizzare solo un H-bridge (il SN754410), mentre la seconda è più “professionale” e costosa e consiste nell’utilizzare un motor driver vero e proprio. La scelta dipende da quello che vuoi ottenere, nel mio caso è un semplice progetto a scopo educativo, quindi ho preso la strada più economica.
    Il modulo bluetooth è rappresentato con un rettangolo rosso nello schema e va collegato ai pin di Arduino come indicato.

  5. Salve Andrea. Volevo chiederle come utilizzare la libreria MeetAndroid. Sto cercando di usarla con il codice arduino che hai messo a disposizione, ma il compilatore mi dice che MeetAndroid non esiste, anche se ho copiato il file .h nella mia directory. Potresti spiegarmi come risolvere questo problema?

  6. Salve Andrea,dopo aver installato l’applicazione e aver montato il bluetooth ho riscontrato un problema:il cellulare riesce ad associare il dispositivo bluetooth ma quando lo seleziono dal search devices nella schermata appare ancora la scritta NO DEVICE e il flag è ancora rosso.secondo te quale può essere l’errore?
    grazie dell’attenzione,
    Daniele

  7. salve andrea, avrei solo una domanda,premetto che non sono molto esperto in fatto di Arduino e dell’elettronica in se, però io voglio guidare una macchinina a motore brushless però io non voglio modoficarla piu di tanto xke vorrei utilizzare il suo circuito di pilotaggio usando il tuo programma per arduino quale piedino mi da il segnale che poi il suo circuito di pilotaggio farà andare il motore devo usare?perchè tu utilizzi il 9-10-11 io penso che dovrei usare solo l’11….

  8. salve andrea, sono un principiante in materia e il tuo progetto mi ha affascinato. Io volevo modificare una vecchia macchinina radiocomandata Gig Nikko (di cui si sono persi pacco batteria e radiocomando) e riuscitla a guidarla via bluetooth.Avrei due domande da farti: il servomotore della mia macchina rc ha solo due uscite e non 3,come mai? E in secondo, dove devo collocare l’interruttore generale sulla breadboard?( nella foto è posizionato vicino il power plug 1, devo metterlo lì?).

  9. Ciao Andrea avrei una domanda da farti:
    Vorrei realizzare una macchini: dove ho 4 tasti
    ( avanti,indietro,destra,sinistra)e un 5 di start.
    Dove io do 5impulsi in avanti e 1 a sinistra,quando do lo start lamacchinina fa: 5 passi in avanti e un passo a sinistra come posso realizzare questo????
    Saluti Roberto

  10. Salve Andrea, vorrei avere un informazione se è possibile.
    Perché dopo che ho copiato i tuoi codici nell’arduino e poi verifico mi dice che c’è un errore?
    Anche se ho eseguito i tuoi consigli su meetandroid

  11. Anche a me da il suo stesso errore, ovvero: MeetAndroid meetAndroid(rxPin,txPin,baud); Cosa può essere? Ti prego aiutami

  12. ciao andrea a me compaiono questi errori
    Arduino:1.6.5 (Windows 7), Scheda:”Arduino/Genuino Uno”
    sketch_mar22a:1: error: stray ‘#’ in program
    sketch_mar22a.ino:3:25: fatal error: MeetAndroid.h: No such file or directory
    compilation terminated.
    stray ‘#’ in program
    cosa devo fare?

  13. Ciao Andrea, quando vado a caricare mi dice “exit status 1
    No matching function for call to ‘Me…..”
    sotto a MeetAndroid: :MeetAndroid (rxPin, txPin, baud)

  14. Lo schema elettrico che hai riportato causa un cortocircuito che ha rischiato di bruciare il connettore ,ne potresti caricare uno più completo per favore?

  15. salve, articolo molto ben fatto. Uno dei migliori sul web.
    Quando provo a compilare il codice mi da l’errore:
    Arduino:1.6.12 (Windows 10), Scheda:”Arduino/Genuino Uno”

    definitC1-P8:52: error: no matching function for call to ‘MeetAndroid::MeetAndroid(const int&, const int&, const long int&)’

    MeetAndroid meetAndroid(rxPin,txPin,baud);

    Perchè? Grazie.
    (sto cercando di modificare il codice perché utilizzerò una modalita tipo “cingolato” per ruotare il mezzo, quindi niente “servo”. Spero di riuscirci.)

  16. Ciao e complimenti, una domanda , ma se volessi in maniera semplice, riportare i collegamenti della breadbord su un circuito stampato e stamparlo su una pcb, come si fá? Disegnato per poi stamparlo, sembra molto più difficile che realizzarlo su una breadbord di Arduino.

    1. Ciao, so che ci sono dei siti in cui puoi caricare lo schema e poi ordinare la stampa del PCB, però non sono aggiornato, quindi non saprei darti un consiglio.
      Mi ricordo che il software di progettazione Fritzing ti permette di esportare il progetto per la stampa.

Rispondi a Daniele Annulla risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.