Bi-Directional wireless communication can be quite complex, so we’re going to demystify it using the NRF24L01+ and by implementing an OLED screen to display the data received.
Our resident Somewhat Autonomous Machine (SAM) has come along way over the past few months, with the most recent addition of the NRF24L01+ providing us with the ability to send commands to control it remotely. While this is great, we now need to create a feedback mechanism to allow precise remote control. With an extended range we could send SAM into another room, a separate building, or maybe even Mars!
THE BROAD OVERVIEW
SAM comprises of several subsystems but we can separate these into two distinct parts; while it’s not exclusive, we have divided them into the transmitter (TX) and receiver (RX). The transmitter contains two joysticks that give us directional control over SAM, four push buttons and small 0.96” OLED screen. There are many similar units out there but this unit is a monochrome unit where the pixel colours are split with a small yellow bar at the top. Finally, we have the small NRF24L01+ 2.4GHz transceiver. At the centre of this is an Arduino UNO.
The receiver unit is SAM itself. This contains an Arduino Uno as the microcontroller, and two SG90 servos: one for the gripper and one to control the pan. This is attached to the ultrasonic sensor that is used to measure the distance that objects are from SAM. There is a two-channel motor driver board that drives the four motors, and of course the NRF24L01+ for communications.
TX
The transmitter works by looping through each button and joystick input to package it all up into an array. This array is sent to the NRF24L01+ for transmission. Once the transmission has taken place, the radio then pauses for five milliseconds before listening for an incoming data transmission. This transmission also includes an array, but only contains three elements. The loop continues on and performs all of our graphic functions. It then loops and repeats the process.
RX
Our receiver unit is SAM, what started as a Somewhat Antonomous Machine has evolved into an entirely different machine. It now can send us back information to our transmitter. In this case, we will send back the current state of the gripper, if it opened or closed, the current distance in centimetres that an object is in front of SAM, and the current position of the scanning servo. When in Antonomous mode, SAM uses the servo and the ultrasonic sensor to scan the area in front for obstacles, and then avoids them. Clicking down on the joystick will put the unit into remote mode, which allows us to drive SAM around; the ultrasonic scanner will still work and send back data to the transmission unit. You can also control the servo to scan the areas around SAM. SAM also has an LED matrix display to give SAM a little bit of personality.
HOW IT WORKS
For this build, we have employed a small organic light emitting diode (OLED) display to give us visual feedback from SAM. OLED displays offer sharp contrast and bright pixels. They are rapidly becoming low cost in the market and depending on your unit incredibly easy to use. For this unit, we have chosen a two-wire i2c design, which means it will only take up two of our GPIO pins on our Arduino. It is a master/slave protocol that allows for low-speed communication for microprocessors but it uses a bus model. This means that you can use multiple units on a single two-pin interface. Each device has a unique address, and we address our data packets to make sure it goes to the right person.
JOYSTICKS & BUTTONS
The joysticks being used are comprised of two potentiometers to measure the X and the Y movement. This gives us two values, which allow us to plot a point in space. The values are read independently of each other, so you do not have to read both the X and Y, nor do you have to implement them together. In this case, we have one joystick that controls forward movement on the X-axis but pans the servo on the Y. These controllers allow us to specify a precision level of accuracy and then feed to SAM. The alternative is to use a button, and this could easily be allocated to “go forward” or “turn right”. The problem with this is that it’s either on or off. SAM will only drive at one speed, really fast or slow depending on what we set in the code.
BUTTONS
Both joysticks have built-in buttons by pushing them down. These buttons read high and will float unless you specify the use of the internal pull-up resistor. We have also added four single buttons in the classic game configuration, and have included them in the circuit diagrams and the transmission code. The receiver will receive all the codes assigned to the packet, but are currently unassigned.
ADAC
To get a precise location of where the joystick has been moved to the Arduino, measure the given voltage on the desired pin. It allocates a reference point and gives you the value as a number on that scale. For example, this standard implementation the scale is 0V-5V. The digital version of this scale is 0-1023, so 0V = the number 0, 5V = 1023. If the joystick is halfway and reading 2.5V, then the value is 512. Arduino will give you the 0-1023 scale, as that is the resolution of that port. However, if your required scale is different, say 0-255̊, rather than 0-1023, we can “map” our original value and use a new scale. We are using a map of 0-255 as the motor drive speed is controlled via a scale of 0-255.
RECEIVER (RX)
ULTRASONIC
Our input device for this build is the HC-SR04 Ultrasonic module. This is one of the most simple and cost-effective methods for measuring distance. The unit works by sending out a timed soundwave, well beyond what the human ear can hear. If there is an object in front of the soundwave, it is reflected off the object. This bounce back is received by the unit and the time taken for this to occur calculated using the speed of sound to give you the distance the object is away. The speed of sound is 0.034cm/µs or 340m/s, so if the object is 10cm away it will take 294u seconds. However what you get back from the echo pin will be double that number, due to the time it takes for the sound wave to return to the unit. To get the distance in centimetres, we need to multiply the received travel time value from the echo pin by 0.034 and divide it by 2.
LED MATRIX
An 8x8 LED matrix array is here to give SAM the injection of personality that it needs. We have programmed the matrix with a series of expressions: happy, sad, annoyed and indifferent. Each expression is triggered in the various drive functions. The matrix can be thought of as a grid of LEDs. Now, if we were to connect all of them directly to the Uno it would take up 64 IO lines, which is more than the UNO has available. The matrix module also contains the matrix driver. The MAX7219 (part of the 72XX series) is a serial interfaced, 8-digit encoder, which makes the setup and execution of 8x8 icons easy.
The LED matrix can be thought of as a series of eight rows of eight LEDs. We feed the data to the matrix one byte at a time. Each byte represents eight bits, each one being an LED on or off.
To declare our icons or emotions, we use a byte array of eight bytes, each byte containing eight bits. Each byte here is represented by a hexadecimal (base 16) value. If you converted them to a decimal (base 10), you would get a normal number; but if you convert them to base 2 or binary, then you till get a series of bits. Each one of these bytes represents a row in the matrix.
NRF BI-DIRECTIONAL COMMUNICATION
To do this will be using the 2.4GHz spectrum using the NRF24L01+ (2.4GHz) transceiver module. These are low-cost, short-range data transmitters. We need to use two for this project: one for transmission (TX) and one for receiving (RX) the data. We will get the information required to build our packet of data from the various buttons and controls. The unit has baud rates from 250kps to 2mb if used in open space, and at the lower baud rate you can get 100m. Do be mindful that other devices on the 2.4GHz spectrum may interfere and reduce the stated range. Note: this module MUST be run at 3.3V. Running it at 5V is likely to result in the unit becoming damaged.
Compared to the previous iteration of SAM we are faced with the requirement to enable bi-directional communication. That is, we need SAM and the transmitter unit to send information back and forth to each other. This is more complex than it would appear to be. In traditional serial communications such as USB, we have a send and receive line. These are referred to as “pipes”. Each pipe allows us to send the data we need and provided each end knows what speed and error checking to do, we can communicate to our heart’s content. However, when we use a wireless system, we end up with packets of data flying around in the ether. Which ones are ours? Where are they going? In a simple transmit and receive situation the transmitter talks, and the receiver listens. It’s a wonderfully simple relationship, but to have data flowing both ways we need to take turns in talking and listening. Therefore, we need to set up our unit, slightly different to the previous iteration.
const byte address[6] = "00001";
radio.openReadingPipe(0, address);
becomes this:
const byte addresses[][6] = {"00001", "00002"};
radio.openWritingPipe(addresses[1]);
radio.openReadingPipe(1, addresses[0]); /
This gives us two known pipes for our data, but while we now know where our data is located, we still need to make sure that transmissions and receipts are getting read and written at the right time.
In the previous iteration, one unit just transmitted, one received; but now we need to take turns. The TX unit starts out by transmitting its control data. Once completed it calls the start listening command to the radio. It waits until it finds the radio and transmission, gets a packet of data, then it executes the stop listening command and returns to transmitting the data. This pattern is repeated on SAM. The downside however, is that we start to experience some lag in our system. Only slightly, about five milliseconds at times, but it can make the odd button press feel laggy.
L298N
For this project, we will be using the L298N Dual H Bridge package. It operates as a two-channel device that can let us operate the left and right pairs of motors. You can easily use two or four motors. We will use two channels and enable skid steer or tank track style direction changes. The L298N works by sending a PWM signal (from 0-255) to the enable pin of the channel you are using. This specifies the direction required by two other pins. This is repeated for the second channel, thus requiring six outputs from the Uno to control the motors.
This configuration is unchanged from previous iterations of SAM. The old saying "if it ain't broke, don't fix it" applies here. We're foused on exploring SAMs capabilities in various ways, using different technologies. While a change to Stepper Motors or something may allow some better control of the motors, they don't have huge advantages with what we're trying to achieve. Feel free to experiment yourself though!
The Build
Original SAM Parts LISTs for v1.0-1.3:
Parts Required: | Jaycar | Altronics | |||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 x Arduino UNO R3 or Mega 2560 | XC4410 | Z6240 | |||||||||||||||||||||||||||||||||||||||||||
1 x Ultrasonic Module | XC4442 | Z6322 | |||||||||||||||||||||||||||||||||||||||||||
1 x L298n Motor Control Module | XC4492 | Z6343 | |||||||||||||||||||||||||||||||||||||||||||
1 x 4WD Chassis & Motor Kit | KR3162 | K1092 | |||||||||||||||||||||||||||||||||||||||||||
1 x SG90 Servo with Screws and Horns | YM2758 | Z6392 | |||||||||||||||||||||||||||||||||||||||||||
1 x 8x8 LED Matrix Module | XC4499 | Z6362 | |||||||||||||||||||||||||||||||||||||||||||
2 x Joystick Modules | XC4422 | Z6363 | |||||||||||||||||||||||||||||||||||||||||||
2 x NRF24L01+ | XC4508 | Z6361 | |||||||||||||||||||||||||||||||||||||||||||
1 x 8AA Battery Holder | PH9209 | S5034 | |||||||||||||||||||||||||||||||||||||||||||
8 x AA Batteries | SB2333 | S4955B | |||||||||||||||||||||||||||||||||||||||||||
2 x 2.5mm Screws | |||||||||||||||||||||||||||||||||||||||||||||
2 x 20mm M3 Bolts | |||||||||||||||||||||||||||||||||||||||||||||
4 x M3 Nuts | |||||||||||||||||||||||||||||||||||||||||||||
1 x 3D Printed Radar Head | |||||||||||||||||||||||||||||||||||||||||||||
1 x 3D Printed Body (for UNO R3 or Mega 2560) |
Optional: | |||
---|---|---|---|
4 x Push Button Momentary Switches | SP0724 | S1098 | |
4 x 10K Resistors | RR0596 | R7249 |
PARTS LIST FOR v1.4:
Parts Required: | Jaycar | Altronics | |
---|---|---|---|
1 x OLED 128*64 Screen (I2C) (SS1306) Chipset | XC4384 | - | |
1 x SG90 Servo+1 | YM2758 | Z6392 | |
1 x 3D Printed Gripper (L+R) | |||
1 x 3D Printed Left Arm | |||
1 x 3D Printed Right Arm | |||
1 x 3D Printed Lower Support | |||
2 x 3D Printed Connection Rods | |||
9 x M3 Screws & Nuts (Gripper Arm) |
You'll also require jumper wires and standard prototyping hardware.
Sam Unit
TRANSMITTER (TX)
To build our transmitter, we start by connecting the Arduino Uno to the breadboard and connect the power up. We then connect the OLED screen to power, then connect SDA to A4 and SCL to A5. We need to check the i2c address of our OLED, and we do that by running an i2c scanner. You will find this in the provided files.
Upload and run the scanner and get the address from the serial monitor. This will look something like 0x3C. Once you have the address, we change line 50 in the TX code to the relevant address. Next, we connect the NRF directly to the Uno, remember that this requires 3.3V and putting 5V into it will likely damage it. Next, we connect the joysticks and the joystick buttons. We then connect the push buttons, remembering the 10k resistors. Open the Arduino transmitter code and upload to the Uno.
We have included an i2c scanner Arduino code in the digital resources for this purpose.
RECEIVER (RX) SAM
To assemble the SAM unit, we need to make sure that we have printed all the 3D parts.
Start by fitting both the Arduino Uno and the motor driver in place. We have provisioned a place for an SG90 servo and have made a small rectangle to allow the three-wire cable to pass through.
Once you have the main body piece, please take your SG90 servo and run the lead through the rectangular hole on the right-hand side (when looking down). This should pass easily. Now depending on your plastic, you may or may not have enough flex to push the servo into its slot. We tried two different PLAs from the same company, and one was fine but the other would not budge. If it’s the latter for you then take a Dremel, rotary tool or small cutter, and cut out the back edge of the rectangle. This will easily allow the servo to slide in and move the cable into place. Once the servo is in place, affix it using the supplied screws.
On the motor driver take note to pull the 5V jumper off on the right-hand side of the motor board, to avoid SAM springing into action when you are not ready. Take care to then wire up the two pairs of drive motor cables. Leave the main power off for the moment, as we need to power up the Arduino. Wire up the SG90 servo, the orange wire goes to pin 2, with the red to positive and the brown to negative.
MATRIX
Due to the position and rotation of the servo we will just use two points to mount it on. This is not an issue as this particular part bears no load other than itself. To offset the unit and make sure his “mouth” is in the right spot, we have pushed the bulkhead across to the right. The matrix itself is not able to sit flush against the bulkhead due to the underside solder points. To address this, take your two M3 bolts and place them on the right-hand side (when looking from above). Once through, place an M3 nut on and do it up firmly, but not too tight. Then place the matrix on SAM, pushing the two bolts through the holes in the bulkhead. Take another two bolts and tighten up; just enough so they do not come loose. Now wire up the matrix interface to the Arduino as per the diagram.
Control Unit
HEAD UNIT
Take your 3D printed ultrasonic head unit and take your time to fit the ultrasonic sensor. You will probably need to take the time to Dremel or file the holes to get a good fit. Turn the unit over and take the long double-ended control horn and 2x2.5mm screws. We have several guide holes in the unit, but found the first to be adequate. Take your time to get these in properly, as they will create a squarer mounting angle for the head unit. Once complete, connect your Arduino to your machine and upload the following code:
_007_Robot_001_Setup_Scanner
Once the servo is at 90̊, place the head unit on top of the servo – perpendicular to the body – and gently screw the horn into the base. Get it as close to 90 as possible, being mindful that the stop of the servo control has a spline on it. Once you have firmly screwed it down, you can connect the ultrasonic as per the diagram.
Connect up the NRF24L01+, being mindful of the 3.3V power input, as 5V is quite likely to damage the unit, as we previously mentioned.
THE GRIPPER
We start with the gripper. Be mindful that this is meant to be fun and an experiment. To get truly perfect travel and motion, we would use shims around all the bolts or bearings in a larger installation that had to bear loads. For now though, we will adjust to get a functioning unit. Start by attaching the servo to the base unit. Install the control horn and then connect the servo to power, ground and pin 2. Add the two arms into each of the gripper claws. Next, place the two arms with the cogs attached. Note that these are left and right. The left one has a place for the servo control horn. Load the file _007_Robot_001_Setup_Gripper code onto the Arduino and run. Similarly to the head unit, this will set the correct position. Place the control horn as pictured:
Put the control horn screw in place. Next, we place the three long screws into the base unit, then place each assembled arm section of the gripper in place. This will take some fiddling and patience. Once in place, put the gripper top over the three screws and tighten them. We now have our assembled gripper. Once everything is tight, it will be difficult to move, so just back off each nut slightly. You could add some silicon lubricant if you like. As discussed before, this is meant to be experimental, and we wanted to reduce the part count. In a production environment, you would be using shims or bearings to ensuring the moment was smooth.
Finally, double check your connections and then load the software included. You should start to get some data on your OLED screen within a few seconds.
THE CODE
From a code perspective, the SAM unit is very similar to previous versions. We have added the addition of the pipes for transmission of data, and added a few lines in the chkRadio function to package up some data into a packet, to send back to the transmitter.
radio.begin();
radio.openWritingPipe(addresses[1]); // 00001
radio.openReadingPipe(1, addresses[0]); // 00002
radio.setPayloadSize(32); //change our packet size
radio.setPALevel(RF24_PA_MAX);
radio.setDataRate(RF24_250KBPS);
The transmitter has received quite a few changes, the addition of the pipes and the reading of the returned data from SAM but the biggest additional lines, with the OLED screen and the code required to draw on the screen.
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
We have included some drawing libraries, which make it much easier to work with. Once the display object has been declared, there are two simple ways we can draw on the screen: one is the print method. Like Serial.println(), display.Println() will simply write characters to the screen as if you were writing to the serial port. It supports both print and println so you will have a new line after.
The second way is to draw some basic geometry. We can easily draw lines, rectangles, triangles and circles. We can also call on a function to fill them. In the case of our project, we have used the five bar graphs to show the distance that an object is away from SAM, while also moving the bar graph in question, relative to the angle of the head servo.
You can see how they're sent to the OLED from the code below:
//draw circles
if (packet[6] == 1) {
display.fillCircle(5, 12, 2, WHITE);
} else {
display.drawCircle(5, 12, 2, WHITE);
}
Or perhaps our rectangles for object detection:
// right 1
display.drawRect(90+12,1,10,45,1); //center top(yelloew is close)
//right 2
display.drawRect(90+24,1,10,45,1); //center top(yelloew is close)
Text is quite simply sent, including dynamic variables:
display.print("CM: ");
display.println(packetTX[2]);
display.println();
display.println("Throttle");
Once you dive in to the code, you'll find it fairly easy to modify and update the OLED with your own requirements, including the feedback from SAM itself.
WHERE TO FROM HERE?
The sky is the limit for SAM. Perhaps some touch-sensing on the gripper to avoid undue stress on the gripper's servo?
Though it's worth noting we're running short of I/O on the UNOs. To expand things further, a larger Arduino board would probably be required if you had more I/O requirements.
The control pad is a little difficult to use as part of the breadboard. We were more concerned with getting our bi-directional communication and a nice display running. Once we've finished developing SAM we'll likely develop a controller PCB that makes this a whole lot easier.
Part 2: SAM v1.1: Upgraded Senses