Welcome! In this entry, we will learn how to program and control a PrintBot via a terminal with the help of Protocoder. Protocoder is a prototyping tool for mobile applications which allows you to create interfaces and programs quickly. Protocoder was developed by Victor Díaz. You can find more information and download Protocoder here. We will use Protocoder as an interface for communicating with the ZUM BT-328 board via Bluetooth, and we will make the PrintBot Evolution move freely or we will use one of the automatic modes: line follower, light follower or obstacle dodger. We will use two programs to control the PrintBot Evolution:
- Protocoder code: it will store the graphical interface and the functions that send data to the ZUM board via the Bluetooth of the terminal.
- ZUM BT-328 code: it will store the PrintBot Evolution programming, such as the operating modes or the Bluetooth communication.
You can download both codes at the end of this entry. Remember that to run the Protocoder code, you will need to install it on your terminal first (you can download it here).
Protocoder code
To modify the Protocoder code, first access the application via your terminal and enter the IP address that appears at the bottom of the browser screen on your computer.
Download the code at the end of this entry and move the printbot_evolution_protocoder.proto file to your terminal. Using a file explorer on your terminal, go to the folder that you saved the file in (usually the downloads folder), click on the Protocoder code and a dialogue box will appear. Select “open with Protocoder”. Select the install option in the pop-up window. Now you can use and modify the code.
Once you have imported the .proto file, you will see the project in the MY PROJECTS tab of the browser. Click on the the icon and you will see the code. Various parts can be distinguished within the code:
- Creating text buttons: create a button with a text inside it, useful for indicating the purpose of the button. To create them, we will use a code like this one:
123var btnSiguelineas = ui.addButton("Sigue lineas",240, 350, w, h).onClick(function(){ // We create the button. Parameters: ("texto a mostrar",posición X, posicion Y, ancho, alto)send("siguelineas"); // We pass the world siguelíneas (line follower) to the send function as a parameter, which is fully received on Arduino.});
Within the code, we are declaring the button with var btnSiguelinas, and we have to give each button a different name to avoid any conflicts. Then with ui.addButton, we can state that the btnSiguelineas variable will be a button. In brackets and in order, we will write the text that will appear on the button, the position in X and Y, the width and height of the button. Then we will need to indicate the function executed on pressing the button, in this case, sending the word siguelinas (line follower) via Bluetooth. - Creating buttons with images
123var btn180 = ui.addImageButton(x+2*w, y+3*w, w, w, "180.png",false).onClick(function(){ // We create a button with an image. Parameters: (positionX, positionY, width, height,"image file path",true/false)send("180");});
We are declaring the button with var btn180, but this time it´s different as we are using an image instead of text. Then with ui.addImageButton, we can state that the btn180 variable will be an image button. In brackets and in order, we will write the position in X and Y, the width and height of the button and the name of the image that we want to appear on the button. Then we will need to indicate the function executed on pressing the button, in this case, sending the word 180 via Bluetooth. To add an image or any other file, simply drag it from the file explorer on your computer to the box on the bottom right. Bear in mind that the more simple the name is, the easier the process of entering it into the code will be.
- Creating buttons for the Dashboard: To include them, we can do it in the same way as the buttons for the device, only instead of using the ui prefix, we will use the dashboard prefix. By default, the dashboard will not appear, however you can change this by replacing dashboard.show(false); with dashboard.show(true);.
- Functions for connecting and disconnecting the Bluetooth: These are the ones included within the btnConectar and btnDesconectar variables. We will also create a variable with the name btClient, which will be used to “interact” via Bluetooth.
- Connecting: In order to connect, first we need to create a button with the text “Connect Bluetooth” and within the function activated on pressing the button, we will include Bluetooth connection.
12345678910111213var btnConectar = ui.addButton("Conectar bluetooth", 240, 150,300,100).onClick(function() { // We create the connect Bluetooth buttonbtClient = network.bluetooth.connectSerial(function(status) { // We connect btClient -- It will display a list of any linked devicesconsole.log("connected " + status); // Debugif (status === true){ // If it is connectedconsole.log(status); // We save the statusbluetoothOn = 1; // We change the status variable to connected (1)ui.jump(btnConectar); // We make the jump buttonui.alpha(btnConectar, 0); // We make the button totally transparentmedia.playSound("Drago"); // We play the stored sounddevice.vibrate(750); // We make the device vibrate}});});
Within this function, as well as connecting the Bluetooth, we will declare the bluetoothOn variable – which serves as the Bluetooth connect/disconnect “signal”´- it is assigned the value 1 and the transparency of the button is changed to 0, which means totally transparent . We will also include some animations such as jump and we will make the device vibrate when connected. - Disconnecting: To disconnect, we need to create a button with the text “Disconnect” and within the function activated on pressing the button, we will include the Bluetooth disconnection.
123456var btnDesconectar = ui.addButton("Desconectar", 540, 150,200,100).onClick(function() { // We create the connect buttonbtClient.disconnect(); // We disconnect if pressedbluetoothOn=0; // We change the status variable to disconnected (0)ui.alpha(btnConectar, 255); // We change the transparency of the button to opaqueui.jump(btnDesconectar); // We make the disconnect button jump});
Within this function, in addition to disconnecting the Bluetooth, we will declare the bluetoothOn variable – which serves as the Bluetooth connect/disconnect “signal”´- it is assigned the value 0 and the transparency of the button is changed to 255, which is opaque .
- Connecting: In order to connect, first we need to create a button with the text “Connect Bluetooth” and within the function activated on pressing the button, we will include Bluetooth connection.
- Function for sending and receiving data via Bluetooth function: The function for data transmission via Bluetooth is:
123456function send(str) { // The function we use to send a stringif (bluetoothOn == 1) { // If the Bluetooth is connected, the code is executed for the following linesbtClient.send("="+str+"+"); // We send the string between the = and + symbolsconsole.log("="+str+"+"); // We display the string being sent on the console}}
This function doesn´t have a button, as it will be called up every time that a button is pressed, except for connecting and disconnecting via Bluetooth. To send data via Bluetooth, we will use the btClient.send(“=”+str+”+”); command. For example, this function sends the car moving forward: =avanzar+. This is how the coding was done in this instance, but you can try another way of coding. The = y + characters are used as delimiters at the beginning and end of the transmission.- Sending data via Bluetooth: To send data via Bluetooth, we will use the function with a text in quotation marks, for example, to send the word retroceder (go back) we will use: send(“retroceder”);
Arduino code
To view and modify the Arduino code, download the packet at the end of the entry and click on the printbot_evolution_arduino.ino file. If you haven´t installed the Arduino IDE or another IDE for reading this file, go to Programming with Arduino and Protocoder for makers, have a look at the different IDEs and download your favourite. You can also find out more about Arduino and Protocoder. Once the file is open, various parts can be distinguished within the code:
- Declaring variables and including libraries: We will declare the various different variables that we will be using during the program:
1234567891011121314151617181920212223242526272829303132333435// We use the Servo.h library to control the servos#include "Servo.h"// We declare the servosServo servoR, servoL; // Servos of the left and right motorsServo servoBat; // Servo that controls the bat (ultrasound) sensor movement// We declare the variables relating the bat sensor// TP corresponds to the "trigger pin" (the pin that sends the ultrasound signal)// and EP corresponds to the "Echo pin" (the pin that receives the ultrasound signal)// By measuring the time that passes in sending and receiving the ultrasound signal// we can calculate the distanceint TP = 4, EP = 5;// We declare the pins corresponding to the LDR (light-dependent resistor) sensorsint ldrIzq = A2, ldrDrch = A3; // LDR pins// We declare the pins sensores infrared sensorsint irIzq = 2, irDrch = 3; // infrared pins// Default delay times.// A delay is a given period of time during which execution// of the program stops, until this period ends// For example, it is used here to make the PrintBot rotate for a certain duration.int defaultDelay = 200, lineasDelay = 0;// Delays to make the PrintBot rotate at different angles// You might need to adjust these times to your PrintBotint giro_180 = 1028, giro_90 = 514, giro_45 = 257, giro_30 = 200;// We declare the buzzer pinsint pinBuz = 12;// We will receive a string via Protocoder which we will use to// determine the movement or the functioning mode of the PrintBotString inString = "";
We will use some of them to tell Arduino which pins the vitamins are connected to, as well as others such as delays, to change how the program is executed. If the turns are incorrect or if you want to change adjust the angles, you can change the giro_180, giro_90, giro_45 and giro_30 variables. - Setup: In the setup function, one of the compulsory functions in Arduino (in addition to loop), we will start the program and the components it is formed of. The code for the setup function is:
1234567891011121314151617181920212223242526272829void setup() {// We start the serial communication. The Bluetooth of the ZUM BT 328 board// runs at 19200, so if we change this value it will no longer workSerial.begin(19200);Serial.flush(); // We clear the serial portservoL.attach(6); // We tell Arduino that the Left servo is on pin 6servoR.attach(9); // We tell Arduino that the Right servo is on pin 9servoBat.attach(11); // We tell Arduino that the servo that controls the bat (ultrasound) sensor is on pin 11servoR.write(90); // We write the value 90 degrees on the right servo (90 is equal to stopped)servoL.write(90); // We write the value 90 degrees on the left servo (90 is equal to stopped)servoBat.write(90); // We place the bat sensor servo in the centre// We tell Arduino that the pins will be used as intput and output// bat sensor pinspinMode(EP, INPUT);pinMode(TP, OUTPUT);// LDR pinspinMode(ldrDrch, INPUT);pinMode(ldrIzq, INPUT);// Infrared pinspinMode(irDrch, INPUT);pinMode(irIzq, INPUT);}
Within this function, we will start the serial port at a speed of 19200 bauds/s, which is the functioning speed of the ZUM BT-328 board Bluetooth module.
Then we need to indicate where each servo is connected, stop the moving servos and move the servo with the bat ultrasound sensor into central position.
Finally, we will use the pinMode(pin,mode) command to set up the different INPUT and OUTPUT pins as required. - Loop: Within the loop function, another complusory Arduino function, we are dealing with data received:
1234567void loop() {if (Serial.available() > 0) { // If there is data on the serial port (0 indicates that there is)readFromAndroid(); // we call the readFromAndroid function to read the data received via Bluetooth}writeData(); // Once we have read the data received via Bluetooth, we call the// writeData function which will "decide" which mode to enable according to the data receieved}
Within this function, we can see the if (Serial.available() >0) command which would translate into “human” language as: If there is data on the serial port, then carry out the following commands. In this case, the commands are to call up the readFromAndroid(); function. This function reads the data sent via the Protocoder application. The other command appearing within loop is writeData();. This function is used to enable different control modes (both manual and external), once the data from the previous function has been read. - readFromAndroid();:
12345678910111213void readFromAndroid() {char inChar; // We create a character variablewhile (Serial.available() > 0) {// Execute the commands in brackets until there is no data on the serial port (Bluetooth)// with the While commandinChar = (char) Serial.read(); // Store the data read via the serial port in the inChar variableSerial.flush(); // clears the serial portif (inChar == '=') // = the character at the end of sendinginString = ""; // If the character at the end of sending has arrived, it deletes the data stored in the inString variableelse if (inChar != '+') // If the character is neither = nor + then, concatenate the characters read on the inString variableinString += inChar;}}
Instead of this function, we could use the readString(); function but it would be read more slowly, causing the commands sent from the Protocoder program on our terminal to be delayed in reaching the PrintBot evolution. - writeData();: This function controls the flow of the program, reads the data received from the readFromAndroid() function and carries out one command or another in response to that data.
- Movement functions: These are the functions that make the PrintBot Evolution move. These are: avanzar (go forward), retroceder (go back), parar (stop), izquierda (left), derecha (right), derecha, giro_180_grados (turn_180_degrees), izquierdaLineas (leftLines) and derechaLineas (rightLines). We can use these functions at any time, simply by calling them up. For example, to turn 180 degrees we will use this command:
1giro_180_grados(); - Mode functions: These are the functions used to switch the PrintBot Evolution onto the automatic modes.
- Line follower: This auto mode uses infrared sensors to detect whether the sensor is over a line. The special functions izquierdaLineas and derechaLineas are used in this mode to move just one wheel, instead of two in separate directions, to perform the movement.
- Obstacle dodger: This auto mode uses the ultrasound sensor to detect objects and avoid crashing into them. The code for this function is:
123456789101112131415161718192021222324252627282930313233343536373839void obstaculos() {// The following values are used to determine the positions of// the bat sensor bat (using the servoBat)int izq = 140, drch = 50, cent = 90;// Checks if there are obstacles in the 3 positions. If so, it will stopboolean obsIzq = buscaObstaculo(izq); // We call the function which returns true if there is an obstacleif (obsIzq) {parar();}boolean obsCent = buscaObstaculo(cent);if (obsCent) {parar();}boolean obsDrch = buscaObstaculo(drch);if (obsDrch) {parar();}// It carries out a series of checks and moves in the obstacle-free directionif (!obsCent) { // If there is no obstacle in the centreavanzar();} else if (!obsDrch) { // If there is no obstacle on the right and there is in the centrederecha();delay(giro_45);parar();} else if (!obsIzq) { // If there is no obstacle on the left and there is in the centre and on the rightizquierda();delay(giro_45);parar();} else { // If there is an obstacle in the centre, on the right and on the left. In which case it will rotate 180ºretroceder(); // We make the PrintBot go backwars for a certain timedelay(giro_30); // We use the same time delay as for rotating 30ºgiro_180_grados(); // Finally we make the PrintBot turn 180 degrees}}
Within this function, we need to first find the izq, drch and cent variables which determine the position of the mini servo that moves the ultrasound sensor. These positions are left, right and centre respectively, but you can modify them or create new positions. Then we have to check if there are any obstacles in the direction the servo is facing by using the buscaObstaculos() function used for all positions, transmitting the angle that the servo should be pointing as data. The code for this function is:
1234567891011121314151617181920212223boolean buscaObstaculo(int angulo) {// This function is used to detect obstacles.// To use it, you will need to pass the following dataint distancia_max = 25; // Maximum distance to determine if the signal detects any obstaclesint distancia; // We store the distance read hereservoBat.write(angulo); // We move the servo to the reading position which we have passed as an argumentdelay(200); // We pause the execution to give the servo time to arrivedistancia = Distance(); // We save the distance returned by the Distance() function in the distance variableif (distancia != 0 && distancia < distancia_max) {// If the distance is not 0 and is less than the maximum distance// we make the buzzer sound and return true// which indicates that there is an obstacletone(pinBuz, 261, 100);return true;} else {// If the distance is 0 or is greater than the maximum distance// the functions returns false, indicating that there are no obstaclesreturn false;}}
Within this function, you can change the maximum distance that will be used to detect obstacles, or in other words, the distance that determines whether an object is considered to be an obstacle or not. To do this, the auxiliary functions Distance and TP_init are used, which enable the bat sensor, send an ultrasound signal, calculate the time taken to read the signal after bouncing back and then, according to that time, calculate the distance. Once we know whether there is an obstacle in front of each of the three positions, we can move the PrintBot towards the clear area. - Light follower: This auto mode is used to move the PrintBot towards the area with the greatest light intensity. To do this, we need to read the value of each pin connected to the LDR modules (light dependent resistor), compare the values, turn to the side with the greatest light intensity and move the PrintBot in a straight line for 1 second.
You can see this entry put in action in this video:
Remember that you can adjust each of the parameters used to make your PrintBot Evolution function the way you want. We also recommend having a go at these exercises:
- Change the Protocoder application icons to include images on the buttons rather than text.
- Use the infrared sensors to detect the edges. You can see an example of how to do this here.
- You can create a button so that your PrintBot moves its head and makes a sound.
If you want to learn more about Arduino, Protocoder and robotics, you can follow this course: Programming with Arduino and Protocoder for makers