This is my work in progress ~50cm Robotic Arm with 6 AXIS Servo motors, an Arduino and a RaspberryPi (for image recognition in phase 2):
While writing the C++ code in Arduino I found I needed a way to send easily and fast commands to Arduino with my iPhone using Bluetooth.
Basically I need to control my robotic arm using an external device instead of running and running again the code on the Arduino board.
For this reason: I’ve attached a BLE board to Arduino and I have created a simple app (completely written in SwiftUI 😍😎) that use BLE connection to connect to Arduino BLE board and send string commands that are parsed and executed.
Commands sended are like:
2350
Where:
2 -> is the command type (in my case is the single movement action)
3 -> is the servo index
50 -> are the degree
and so on…
iOS RoboticArm Controller
Let’s talk about Swift.
Bluetooth pairing
I’ve used on Arduino the SH-HC-08 Board for bluetooth communication.
This board has a service-id and a characteristic-id, named: FFE0 and FFE1.
Read more on Apple reference if you need:
I’ve prepared a simple bluetooth helper that connect automatically to the SH-HC-08 board and helps you to send data:
https://gist.github.com/elpsk/7eaf10e056e516220be847910079cf7e#file-arduinobluetoothhelper-swift
In particular the func sendDataToDevice( _ data: String ) {} that helps you to send a string to Arduino with a ‘#‘ as end-of-line character.
Now in your app you should be able to send commands like:
DataSender.send(command: .power(on: data == "ON"))
DataSender.send(command: .move(servo: 2, position: 45))
DataSender.send(command: .step(steps: stepsArray))
DataSender.send(command: .macro(steps: stepsArray))
[...]
The DataSender class is explained below.
Steps:
Create a Step class with the values needed to move the servo.
struct Step {
var servoIndex: Int
var servoPosition: Int
}
Now write the ArduinoCommands enum that contains the “commands”.
enum ArduinoCommands { // Arduino parser:
case power(on: Bool) // 1
case move(servo: Int, position: Int) // 2
case step(steps: [Step]) // 3
case macro(steps: [Step]) // 4
}
And at last, the DataSender class
class DataSender {
class func send( command: ArduinoCommands ) {
var stringToSend = ""
switch command {
case .power(on: let on):
stringToSend = "1\(on ? "1":"0")"
case .move(servo: let servo, position: let position):
stringToSend = "2\(servo)\(position)"
case .step(steps: let steps):
stringToSend = "3"
steps.forEach {
let currentStep = "\($0.servoIndex)\($0.servoPosition)"
stringToSend = "\(stringToSend)\(currentStep)_"
}
stringToSend.removeLast()
case .macro(steps: let steps):
stringToSend = "4"
steps.forEach {
let currentStep = "\($0.servoIndex)\($0.servoPosition)"
stringToSend = "\(stringToSend)\(currentStep)_"
}
stringToSend.removeLast()
}
BluetoothHelper.sharedBluetooth.sendDataToDevice(stringToSend)
}
}
…and send data!
This is my internal application used to debug the arm (source code will be uploaded soon).
The most interesting section of the app is the “Macro” section, that allow you to “record” a set of movements and loop infinitely.
Best for repetitive tasks, like take an object, move in another place and start again.
Arduino RoboticArm Controller
For Arduino coding, I prefer to use Visual Studio Code for macOS instead of the built-in IDE, that works better.
The plugin used is called “Platform.io“, downloadable from here: https://platformio.org/ .
Configure Bluetooth board
To use the BLE board you need to import the SoftwareSerial.h built-in library:
#include <SoftwareSerial.h>
and initialize the serial port with your pins, like this:
void BluetoothManager::begin(uint8_t txPin, uint8_t rxPin) {
_bleSerial = SoftwareSerial(rxPin, txPin);
_bleSerial.begin(9600);
}
Once configured you can read the commands sended through the iOS App using two methods (depends on your implementation):
String BluetoothManager::readCommandUntil(char separator) {
return _bleSerial.readStringUntil(separator);
}
String BluetoothManager::readCommand() {
return _bleSerial.readString();
}
You can also read a data-stream, of course depends on what you need.
In my case readStringUntil(‘#’) is enough.
Parse strings using Arduino
First of all you need a data model, I’ve created the BLEStep in C++:
struct BLEStep {
byte servoIndex;
uint8_t servoPosition;
};
…that is the corrispective of “Step” on Swift code.
In your loop() function or on-demand like buttons etc… call the readAndParseData() and execute or set data to servos with dedicated functions.
The string that you receive from bluetooth is now something like this:
Power control: 11# or 10#
Single move: 2222# or 2345#, etc…
Steps: 3090_140_220_365_4105_50# (like step screenshot)
Macro:
400_50_2120_3135_10_4112_5180_290_390_180_2150_090_2115_3130_150_50#
And as explained before how to read, and can be parsed using this simple snippet of code:
void BluetoothParser::readAndParseData() {
String data = _bleManager.readCommandUntil(COMMAND_TERMINATOR); // COMMAND_TERMINATOR = '#'
if ( !dataValid(data) ) { // do some checks on the received string
return;
}
byte command = data[0] - '0'; // your command type
data.remove(0, 1); // remove command digit
[...]
if ( command == 1 ) { // status
// sendStatus(data[0] == '1'); // power on/off the servos
Serial.println(data[0]);
}
else if ( command == 2 ) { // single move
byte servo = data[0] - '0';
data.remove(0, 1);
uint8_t position = data.toInt();
sendMove(servo, position); // send values to servos
}
else if ( command == 3 ) { // defined steps
char buffer[30];
char *p = buffer;
char *str;
data.toCharArray(buffer, 30);
while ((str = strtok_r(p, COMMAND_ITEMS_SEPARATOR, &p)) != NULL) { // COMMAND_ITEMS_SEPARATOR = '_'
byte servo = str[0] - '0';
String step(str);
step.remove(0, 1);
uint8_t position = step.toInt();
sendSteps([...])
}
free(p);
free(str);
}
else if ( command == 4 ) { // macro
char buffer[100];
char *p = buffer;
char *str;
data.toCharArray(buffer, 100);
uint8_t idx = 0;
while ((str = strtok_r(p, COMMAND_ITEMS_SEPARATOR, &p)) != NULL) {
byte servo = str[0] - '0';
String step(str);
step.remove(0, 1);
uint8_t position = step.toInt();
sendMacro([...]) // like steps, but more
idx++;
}
free(p);
free(str);
}
}
I prefer to not explain “sendMove“, “sendStatus“, “sendMacro“, etc… because are strictly related to your servo implementation and cannot be useful for this tutorial.
Hint: prefer the use of byte and uint8_t, etc… types instead of int to dramatically reduce memory usage and consumption. On Arduino Uno is very limited.
To check the status and to execute the servo commands you can do something like this:
void loop() {
bleParser.readAndParseData();
if (bleParser.dataStatus.powered) {
executeArmMovements();
}
}
Where executeArmMovements() is your handle to the data struct filled with the parser data.
Now your Robotic Arm is (not) ready. You are (not) able to control your robot using iOS!
Of course you need to write your code better than these snippets…
You need to manage the ranges, the speed, the various delay, all the exceptions, the memory consumption etc.
Stay tuned for the phase 2/3 of the project: take the objects recognized with the camera of the RaspberryPI.
But this is a little bit more complex that moving a servo using recorded step… I’m working on in the spare time.
In the meantime please take a look around this blog for Arduino posts or Swift coding posts. Or if you prefer browse the entire site!
Enjoy moving.