My project: Controlling 3 stepper motors with bluetooth. I'm using Android Studio for the app and Arduino to receive the bluetooth data and control the Steppers.
My Problem When my Arduino receives the data it lags or misses some of the data.
Here is my ConnectedThread.java class used to communicate to the Arduino.
package com.example.a3axisrig;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.SystemClock;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static com.example.a3axisrig.MainActivityKt.MESSAGE_READ;
public class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final Handler mHandler;
public ConnectedThread(BluetoothSocket socket, Handler handler) {
mmSocket = socket;
mHandler = handler;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
@Override
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.available();
if(bytes != 0) {
buffer = new byte[1024];
SystemClock.sleep(10); //pause and wait for rest of data. Adjust this depending on your sending speed.
bytes = mmInStream.available(); // how many bytes are ready to be read?
bytes = mmInStream.read(buffer, 0, bytes); // record how many bytes we actually read
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget(); // Send the obtained bytes to the UI activity
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(char input) {
//byte[] bytes = input.getBytes(); //converts entered String into bytes
try {
mmOutStream.write(input);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
And here's my Activity class(TimelapseActivity.kt) that inflates the view with a joystick to control two motors and a Left and Right buttons to control a 3rd motor.
package com.example.a3axisrig
import android.R.attr.button
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
private lateinit var setButton: Button
private lateinit var doneButton: Button
private lateinit var backButton: Button
private lateinit var point1Button: Button
private lateinit var point2Button: Button
private lateinit var point3Button: Button
private lateinit var rightButton: Button
private lateinit var leftButton:Button
private var points: Int = 0
class TimelapseActivity : AppCompatActivity(), JoystickView.JoystickListener {
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_timelapse)
val joystick = JoystickView(this)
window.decorView.setBackgroundColor(Color.argb(255, 212, 212, 212));
setButton = findViewById(R.id.set_button)
doneButton = findViewById(R.id.done_button)
backButton = findViewById(R.id.back_button)
rightButton = findViewById(R.id.right_button)
leftButton = findViewById(R.id.left_button)
point1Button = findViewById(R.id.point1Button)
point2Button = findViewById(R.id.point2Button)
point3Button = findViewById(R.id.point3Button)
point1Button.setBackgroundColor(Color.BLUE)
point2Button.setBackgroundColor(Color.BLUE)
point3Button.setBackgroundColor(Color.BLUE)
setButton.setOnClickListener { view: View ->
points++
updatePoints()
if (mConnectedThread != null) { //First check to make sure thread created
if (points==1) mConnectedThread!!.write('i') else if (points ==2)mConnectedThread!!.write('o')
}
}
doneButton.setOnClickListener { view: View ->
val intent = Intent(this, TimelapseSetupActivity::class.java)
startActivity(intent)
if (mConnectedThread != null) { //First check to make sure thread created
mConnectedThread!!.write('r')
}
}
backButton.setOnClickListener { view: View ->
points=0
updatePoints()
}
leftButton.setOnTouchListener(OnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
if (mConnectedThread != null) { //First check to make sure thread created
mConnectedThread!!.write('a')
}
} else if (event.action == MotionEvent.ACTION_UP) {
if (mConnectedThread != null) { //First check to make sure thread created
mConnectedThread!!.write('1')
}
}
true
})
rightButton.setOnTouchListener(OnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
if (mConnectedThread != null) { //First check to make sure thread created
mConnectedThread!!.write('9')
}
} else if (event.action == MotionEvent.ACTION_UP) {
if (mConnectedThread != null) { //First check to make sure thread created
mConnectedThread!!.write('1')
}
}
true
})
}
override fun onJoystickMoved(xPercent: Float, yPercent: Float, id: Int) {
Log.d("Joystick", "X percent: $xPercent Y percent: $yPercent")
if (mConnectedThread != null) { //First check to make sure thread created
if (xPercent > 0.1 && yPercent >0.1) mConnectedThread!!.write('1') //Pan Right & Tilt Up
else if (xPercent > 0.1 && yPercent< -0.1) mConnectedThread!!.write('2') //Pan Right & Tilt Down
else if (xPercent < -0.1 && yPercent > 0.1) mConnectedThread!!.write('3') //Pan Left & Tilt Up
else if (xPercent < -0.1 && yPercent < -0.1) mConnectedThread!!.write('4') //Pan Left & Tilt Down
else if (xPercent > 0.1) mConnectedThread!!.write('5') //Pan Right
else if (xPercent < -0.1) mConnectedThread!!.write('6') //Pan Left
else if (yPercent > 0.1) mConnectedThread!!.write('7') //Tilt Up
else if (yPercent < -0.1) mConnectedThread!!.write('8') //Tilt Down
else mConnectedThread!!.write('9')
}
}
}
private fun updatePoints(){
if(points ==1){
point1Button.setBackgroundColor(Color.GREEN)
}else if (points ==2){
point2Button.setBackgroundColor(Color.GREEN)
}else if (points >=3){
point3Button.setBackgroundColor(Color.GREEN)
}else{
point1Button.setBackgroundColor(Color.BLUE)
point2Button.setBackgroundColor(Color.BLUE)
point3Button.setBackgroundColor(Color.BLUE)
}
}
And here's my arduino code
#include <AccelStepper.h>
#include <MultiStepper.h>
#include <SPI.h>
#include <Wire.h>
// -----------------------------------BLUETOOTH-----------------------------------
#include <SoftwareSerial.h> // use the software uart
SoftwareSerial bluetooth(0 , 1); // RX, TX
// -----------------------------------MENU-----------------------------------
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
//#include <Adafruit_PCD8544.h>
#include <ClickEncoder.h>
#include <TimerOne.h>
int contrast = 60;
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
int menuitem = 1;
int frame = 1;
int page = 0;
int mainMenuItem = 1;
int subMenuItem1 = 1;
int subMenuItem2 = 1;
int subMenuItem3 = 1;
int subMenuItem4 = 1;
String menuItem1 = "Setup";
String menuItem2 = "Free Control";
String menuItem3 = "Saved";
String menuItem4 = "Settings";
String setupItem1 = "Timelapse";
String setupItem2 = "Video";
String setupItem3 = "Back";
boolean up = false;
boolean down = false;
boolean middle = false;
bool sliderLengthSet = false;
// ------------------------------------------------------------------------------------
#define JoyX A0
#define JoyY A1
#define JoySwitch 10 // Joystick switch connected
// Rotary Encoder
#define CLK 8
#define DT 9
#define SW 12
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir = "";
unsigned long lastButtonPress = 0;
ClickEncoder *encoder;
int16_t last, value;
//Opto Trigger
#define opto 13
//limitSwitch
#define limitSwitch 11
AccelStepper tiltStepper(1, 2, 3);
AccelStepper panStepper(1, 4, 5);
AccelStepper sliderStepper(1, 6, 7);
MultiStepper StepperControl;
int JoyXPos = 0;
int JoyYPos = 0;
int tiltSpeed = 60;
int sliderSpeed = 1500;
int panSpeed = 10;
int inOutSpeed = 0;
int panInPoint = 0;
int tiltInPoint = 0;
int sliderInPoint = 0;
int panOutPoint = 0;
int tiltOutPoint = 0;
int sliderOutPoint = 0;
int sliderLength = 43; //in cm (725steps = 1cm)
int sliderLengthInSteps = (sliderLength * 725) - 100; //725steps = 1cm && 100 steps as safety
long gotoposition[3]; // An array to store the In or Out position for each stepper motor
//Rotary Encoder
int clickCount = 0;
bool rotaryEncoderMenuState = true; //true when in Menu mode, false when in slider mode
//Timer
long duration = 3000; //ms
//Intervalometer
int shotDuration = 100; //in ms
int interval = 3000; //in ms
int numberOfShots = 0;
char lastReading;
char bt ;
// ------------------------------------------------------------------ SETUP -------------------------------------------------------------------
void setup() {
//Bluetooth
bluetooth.begin(9600); // start the bluetooth uart at 9600 which is its default
//delay(3000); // wait for settings to take affect.
//Set initial speed values for the steppers
tiltStepper.setMaxSpeed(6000);
tiltStepper.setSpeed(0);
tiltStepper.setAcceleration(10);
panStepper.setMaxSpeed(6000);
panStepper.setSpeed(0);
panStepper.setAcceleration(5);
sliderStepper.setMaxSpeed(6000);
sliderStepper.setSpeed(0);
sliderStepper.setAcceleration(10);
StepperControl.addStepper(panStepper);
StepperControl.addStepper(tiltStepper);
StepperControl.addStepper(sliderStepper);
//limitSwitch
pinMode(limitSwitch, INPUT_PULLUP);
//Opto Trigger
pinMode(opto, OUTPUT);
// //Go to Home
// while (digitalRead(limitSwitch) == 0) {
// sliderStepper.setSpeed(500);
// sliderStepper.runSpeed();
// sliderStepper.setCurrentPosition(0); // When limit switch pressed set position to 0 steps
// }
// delay(20);
// // Move 200 steps back from the limit switch
// while (sliderStepper.currentPosition() != -500) {
// sliderStepper.setSpeed(-1500);
// sliderStepper.run();
// }
}
// ------------------------------------------------------------------ LOOP -------------------------------------------------------------------
void loop() {
//
// if (digitalRead(limitSwitch) != 0 || sliderStepper.currentPosition() < sliderLengthInSteps + 3000) {
// sliderStepper.setSpeed(1500);
// sliderStepper.run();
// }
if (bluetooth.available()) {
bt = bluetooth.read();
}
int inOutSpeed = 500; //steps per second
// -----------------------------------BLUETOOTH-----------------------------------
lastReading = bluetooth.read();
// while (lastReading == bluetooth.read()) {
// lastReading = bluetooth.read();
switch (bt) {
case '1':
sliderStepper.setSpeed(0);
tiltStepper.setSpeed(0);
panStepper.setSpeed(0);
break;
case '2':
panStepper.setSpeed(500);
bluetooth.print("Pan Right");
break;
case '3':
panStepper.setSpeed(-500);
bluetooth.print("Pan Left");
break;
case '4':
tiltStepper.setSpeed(-500);
bluetooth.print("Tilt Down");
break;
case '5':
tiltStepper.setSpeed(500);
bluetooth.print("Tilt Up");
break;
case '6':
//sliderStepper.setSpeed(500);
bluetooth.print("Slider Right");
break;
case '7':
//sliderStepper.setSpeed(-500);
bluetooth.print("Slider Left");
break;
case '9':
sliderStepper.setSpeed(500);
break;
case 'a':
sliderStepper.setSpeed(-500);
break;
case 'i':
panInPoint = panStepper.currentPosition();
tiltInPoint = tiltStepper.currentPosition();
sliderInPoint = sliderStepper.currentPosition();
break;
case 'o':
panOutPoint = panStepper.currentPosition();
tiltOutPoint = tiltStepper.currentPosition();
sliderOutPoint = sliderStepper.currentPosition();
gotoposition[0] = panInPoint;
gotoposition[1] = tiltInPoint;
gotoposition[2] = sliderInPoint;
//inOutSpeed = findSpeed();
panStepper.setMaxSpeed(500);
tiltStepper.setMaxSpeed(300);
sliderStepper.setMaxSpeed(500);
StepperControl.moveTo(gotoposition); // Calculates the required speed for all motors
while (panStepper.distanceToGo() != 0 || tiltStepper.distanceToGo() != 0 || sliderStepper.distanceToGo() != 0) {
StepperControl.run(); // Blocks until all are in position
}
delay(200);
bluetooth.println("o called");
break;
case'r':
bluetooth.println("r called");
gotoposition[0] = panOutPoint;
gotoposition[1] = tiltOutPoint;
gotoposition[2] = sliderOutPoint;
inOutSpeed = findSpeed();
bluetooth.println(inOutSpeed);
panStepper.setMaxSpeed(inOutSpeed);
tiltStepper.setMaxSpeed(inOutSpeed);
sliderStepper.setMaxSpeed(inOutSpeed);
StepperControl.moveTo(gotoposition); // Calculates the required speed for all motors
//StepperControl.runSpeedToPosition(); // Blocks until all are in position
long timePassed = millis();
long lastTrigger = millis();
while (panStepper.distanceToGo() != 0 || tiltStepper.distanceToGo() != 0 || sliderStepper.distanceToGo() != 0) {
StepperControl.run();
if ((millis() - lastTrigger) / 1000 == interval / 1000) {
Serial.println(lastTrigger);
digitalWrite(opto, HIGH);
delay(shotDuration); // wait for a shutter speed
digitalWrite(opto, LOW);
//delay(interval+duration);
Serial.println("triggered");
lastTrigger = millis();
}
}
break;
}
sliderStepper.runSpeed();
tiltStepper.runSpeed();
panStepper.runSpeed();
}
int findSpeed() {
numberOfShots = duration / (interval + shotDuration);
if (abs(panInPoint - panOutPoint) > abs(tiltInPoint - tiltOutPoint) && abs(panInPoint - panOutPoint) > abs(sliderInPoint - sliderOutPoint)) {
return (abs(panInPoint - panOutPoint) / ((duration - (shotDuration * numberOfShots)) / 1000));
} else if (abs(tiltInPoint - tiltOutPoint) > abs(panInPoint - panOutPoint) && abs(tiltInPoint - tiltOutPoint) > abs(sliderInPoint - sliderOutPoint)) {
return (abs(tiltInPoint - tiltOutPoint) / ((duration - (shotDuration * numberOfShots)) / 1000));
} else {
return (abs(sliderInPoint - sliderOutPoint) / ((duration - (shotDuration * numberOfShots)) / 1000));
}
}
//----------------------------------------------------- DRAW MENU -----------------------------------------------------
void drawMenu()
{
//Setup->Timelapse->Next
panInPoint = panStepper.currentPosition();
tiltInPoint = tiltStepper.currentPosition();
sliderInPoint = sliderStepper.currentPosition();
//Save Out Point
int inOutSpeed = 0;
panOutPoint = panStepper.currentPosition();
tiltOutPoint = tiltStepper.currentPosition();
sliderOutPoint = sliderStepper.currentPosition();
Serial.println("out");
Serial.println(panOutPoint);
Serial.println(tiltOutPoint);
Serial.println(sliderOutPoint);
//go to Start Point
gotoposition[0] = panInPoint;
gotoposition[1] = tiltInPoint;
gotoposition[2] = sliderInPoint;
Serial.println("in");
Serial.println(gotoposition[0]);
Serial.println(gotoposition[1]);
Serial.println(gotoposition[2]);
//inOutSpeed = findSpeed();
panStepper.setMaxSpeed(500);
tiltStepper.setMaxSpeed(300);
sliderStepper.setMaxSpeed(1000);
StepperControl.moveTo(gotoposition); // Calculates the required speed for all motors
StepperControl.runSpeedToPosition(); // Blocks until all are in position
delay(200);
//Setup->Timelapse->Next->Next-> Start
// Execute Move
gotoposition[0] = panOutPoint;
gotoposition[1] = tiltOutPoint;
gotoposition[2] = sliderOutPoint;
inOutSpeed = findSpeed();
panStepper.setMaxSpeed(inOutSpeed);
tiltStepper.setMaxSpeed(inOutSpeed);
sliderStepper.setMaxSpeed(inOutSpeed);
StepperControl.moveTo(gotoposition); // Calculates the required speed for all motors
//StepperControl.runSpeedToPosition(); // Blocks until all are in position
long timePassed = millis();
long lastTrigger = millis();
while (panStepper.distanceToGo() != 0 || tiltStepper.distanceToGo() != 0 || sliderStepper.distanceToGo() != 0) {
StepperControl.run();
if ((millis() - lastTrigger) / 1000 == interval / 1000) {
Serial.println(lastTrigger);
digitalWrite(opto, HIGH);
delay(shotDuration); // wait for a shutter speed
digitalWrite(opto, LOW);
//delay(interval+duration);
Serial.println("triggered");
lastTrigger = millis();
}
}
}
I'm receiving bluetooth data to my Arduino and the motors are turning, it's just not very accurate and misses some button presses.
Please could you help me! :)