In today´s lesson, the latest entry of the Programming with Arduino and Protocoder for makers course, we will be learning how to use LED matrices to create animations for our robots. In this case, we will use the matrix to create an eye that we will control remotely using Protocoder.
List of materials
- ZUM BT-328 board
- LED matrix: This time we are using a board based on the MAX72xx chip.
- Comodoro: Download the files for our robots at the end of the post.
Arduino code
The Arduino code receives data from the Protocoder program and moves the eye to a specified position, depending on the data received. The eye will also blink randomly for a certain time. To control the LED matrix we will use the LedControl library, that you can download here or together with the rest of the code at the end of the entry. If you want to know all the possibilities that this library offers, more information can found on the Arduino Playground website. To start programming with Arduino, we will need to include the library, define the pins and create an LedControl object, which we have named LM in this case:
1 2 3 4 5 6 7 8 9 10 11 |
//Led Display #include "LedControl.h" #define MAX_DEVICES 1 #define CLK_PIN 13 #define DATA_PIN 11 #define CS_PIN 12 LedControl LM = LedControl(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); |
With the last line of the previous code, we created the LM object which we will use to control the LED matrix. We will also create the inString variable that will store the data received via Bluetooth. The readFromAndroid and writeData, functions will store the data in inString and “choose” which way the eye “looks”, respectively. If you want to know more about these functions, you can visit this website.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
String inString = ""; // String for reading from Protocoder void readFromAndroid() { char inChar; while (Serial.available() > 0) { inChar = (char) Serial.read(); Serial.flush(); if (inChar == '='){ inString = ""; LM.clearDisplay(0); }else if (inChar != '+'){ inString += inChar; } } } void writeData(){ if (inString == "up") { lookUp(); } if (inString == "down") { lookDown(); } if (inString == "center") { lookCenter(); } if (inString == "left") { lookLeft(); } if (inString == "right") { lookRight(); } } |
In this case, once the message as been fully read (after receiving the ‘=’ character), we clear the screen using the LM.clearDisplay(0); command to make sure it is correctly positioned. In the “setup”, we start the serial port, we activate the LED matrix (which is set on power-saving mode by default), set the brightness at maximum level, “delete” the LED matrix and, finally, we set the eye so that it looks to the centre.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void setup() { Serial.begin(19200); /* The MAX72XX is in power-saving mode on startup, we have to do a wakeup call // Function to go to power-save mode: LM.shutdown(0,true); */ LM.shutdown(0, false); /* Set the brightness to max values value={0,15} */ LM.setIntensity(0, 15); /* and clear the display */ LM.clearDisplay(0); lookCenter(); // test routines //testPos(250); //test(); } |
Two test routines have been included, testPos(), which changes between the positions of the eye and test(), which lights up each LED one by one. To generate the different blink positions, we can use a matrix from a file available in the download section, where we will mark which LEDs we want to light up and “translate” them to code. We will use this function to light up an LED:
1 |
LM.setLed(0,row, column, status) |
The status can be true or false, to switch the LED on or off, respectively. Here is the matrix diagram for the lookCenter() function which moves the eye to face forward: Here is the corresponding Arduino code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void lookCenter() { LM.setLed(0, 3, 3, false); LM.setLed(0, 3, 4, false); LM.setLed(0, 4, 3, false); LM.setLed(0, 4, 4, false); LM.setLed(0, 1, 3, true); LM.setLed(0, 1, 4, true); LM.setLed(0, 2, 2, true); LM.setLed(0, 2, 3, true); LM.setLed(0, 2, 4, true); LM.setLed(0, 2, 5, true); LM.setLed(0, 3, 1, true); LM.setLed(0, 3, 2, true); LM.setLed(0, 3, 5, true); LM.setLed(0, 3, 6, true); LM.setLed(0, 4, 1, true); LM.setLed(0, 4, 2, true); LM.setLed(0, 4, 5, true); LM.setLed(0, 4, 6, true); LM.setLed(0, 5, 2, true); LM.setLed(0, 5, 3, true); LM.setLed(0, 5, 4, true); LM.setLed(0, 5, 5, true); LM.setLed(0, 6, 3, true); LM.setLed(0, 6, 4, true); } |
This enables us to generate different patterns with the LEDs. In our case, functions have been generated that change the position of the pupil (lookLeft, lookRight, lookUp, lookDown and lookCenter), as well as functions to simulate blinking (blinkClose, blinkOpen and fullBlink). The delayBlink variable changes the transition speed between closing and opening of eye. We will use it to increase or reduce the blink speed. The fullblink function takes the time that passes between the open eye and the closed eye as a parameter. We will use it to increase or reduce the frequency of the blinking.
1 2 3 4 5 6 7 |
int delayBlink = 10; void fullBlink(int timeClosed){ blinkClose(); delay(timeClosed); blinkOpen(); } |
The blinkOpen and blinkClose functions open and close the eye respectively, and use the delayBlink variable to determine the blink speed. Lastly, the loop function controls the blink routine and the receiving of data via Bluetooth by calling the readFromAndroid and writeData functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void loop() { if (millis()>nextTimeDouble){ fullBlink(60); delay(100); fullBlink(60); nextTimeDouble = millis() + random(30000, 60000); writeData(); }else if (millis() > nextTime){ fullBlink(60); nextTime = millis() + random(1500, 4000); writeData(); } if (Serial.available() > 0) { readFromAndroid(); } writeData(); } |
To control the blinking of the eye, we will use the first part of the code:
1 2 3 4 5 6 7 8 9 10 11 |
if (millis()>nextTimeDouble){ fullBlink(60); delay(100); fullBlink(60); nextTimeDouble = millis() + random(30000, 60000); writeData(); }else if (millis() > nextTime){ fullBlink(60); nextTime = millis() + random(1500, 4000); writeData(); } |
To make the blinking different every time the routine is executed, two types have been included (simple and double), which are controlled by a similar routine. First we need to check if the current execution time for the program is greater than the time set for the next blinking. If the condition is true, it will execute the blink routine and set the next time, by adding a variable quantity, using the random function, to the variable that determines the next time. To read the text sent from Protocoder via Bluetooth, the following code is used:
1 2 3 4 |
if (Serial.available() > 0) { readFromAndroid(); } writeData(); |
Protocoder code
The program we will generate on Protocoder will send data to Arduino via Bluetooth. To start programming, we will use the using heading habitual for our programs, as well as managing the for Bluetooth connection/disconnection. If you want to know more about how this code works, take a look at this course entry.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
ui.toolbar.title("Robot-Eyes"); ui.toolbar.bgColor(55, 155, 155, 255); ui.toolbar.show(true); //ui.screenMode("fullscreen"); ui.screenOrientation("portrait"); ui.allowScroll(false); device.screenAlwaysOn(true); var margin = 10; var w = ui.screenWidth - 2*margin; var h = 150 + margin; var y = h + margin; var btStatus=0; // Variable that stores the status (connected/not connected or 1.0) of the Bluetooth var btClient; // "object" that we will use to connect, disconnect and work with the Bluetooth var btnConectar = ui.addButton("Connect Bluetooth", margin, margin,w,h).onClick(function() { // We create the Bluetooth button btClient = network.bluetooth.connectSerial(function(status) { console.log("connected " + status); if (status){ ui.toast("Connected"); btStatus = status; console.log("btStatus = " +btStatus); } }); }); var btnDesconectar = ui.addButton("Disconnect", margin, y,w,h).onClick(function() { // We create the connect button btClient.disconnect(); // We disconnect once it has been pressed btStatus= false; // We will change the status of the variable disconnected (0) }); |
To send data to the ZUM board, we will use the send function which takes string to be sent as a parameter. For example, to make the eye look to the right, the function must send the word “right”, hence send(“right”);. The code for the send function is:
1 2 3 4 5 6 |
function send(str) { // The function we use to send a string (chain of characters) if (btStatus) { // If the Bluetooth is connected, the code from these lines is executed btClient.send("="+str+"+"); // We send the string between the symbols = y + console.log("="+str+"+"); // We display the string being sent on the console. } } |
Next we will create the buttons that determine the position of the eye. To do this, we will upload any files we want to use to Protocoder and then we will create the buttons in the following order:
1 2 3 |
ui.addImageButton(positionX,positionY,width,height,"file name",false).onClick(function(){ }); |
To adjust the size and position of the buttons, we will use two variables:
- WidthImg, used to assign the height and width to the image.
- posX, used to determine position X of the buttons.
In our case, we include the send(); function within the function carried out when the button is pressed, sending the order corresponding to the position:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
ui.addImageButton(posX,3*y,widthImg,widthImg,"up.png",false).onClick(function(){ send("up"); }); ui.addImageButton(10*margin,3*y + widthImg,widthImg,widthImg,"left.png",false).onClick(function(){ send("left"); }); ui.addImageButton(posX,3*y + widthImg,widthImg,widthImg,"center.png",false).onClick(function(){ send("center"); }); ui.addImageButton(posX + widthImg,3*y + widthImg,widthImg,widthImg,"right.png",false).onClick(function(){ send("right"); }); ui.addImageButton(posX,3*y + 2*widthImg,widthImg,widthImg,"down.png",false).onClick(function(){ send("down"); }); |
3D design: Comodoro
To create the Comodoro 3D design, we have used the program Blender. To print the design, we will use Cura with the following settings to generate the support:
This support is very easy to delete and brings stability to the print job. Within Cura, the scale has been modified to 40.79 to adjust the design to the size of our LED matrix, but the scale could vary according to the size of the matrix being used.