During the first phase of Project 1, we were able to program an infrared transmitter and servo motor to be controlled by a joystick. The joystick was able to rotate the servo motor and fire the infrared transmitter, which would send a data byte, when pressed down. For details on how this was implemented, please refer to the previous milestone report. Our code is located on github.
For phase two of Project 1, an nRF24L01 wireless radio was incorporated into the project with the objective of sending commands to a Roomba mobile platform via its serial command interface. Instead of controlling a servo motor and IR transmitter directly, the joystick output is now used to send two different types of packets to the roomba. The position of the joystick sends command packets to the robot which tell the robot what velocity and radius it should move and/or turn at. When the joystick is clicked down, an IR command packet is sent, which triggers an IR transmitter that is mounted on the robot. During the course of this project, we also implemented a time-triggered scheduler in order to more efficiently dispatch commands and to help prepare for future projects. The steps taken to create the system described will be explored further in the following sections.
Time Triggered Architecture
The first major design decision for this phase was to use a time-triggered architecture (TTA) as the the method of controlling our input (stick commands) and output (radio packets). We made this decision on the recommendation of the TAs and the instructor, and with the knowledge that we would need to use TTA in future projects. Although we had not originally seen the benefit of using TTA, as opposed to using interrupts, it turned out to be helpful in streamlining our code.
The TTA scheduler we used was based on the code created by Neil MacMillan and hosted on the Mechatronics Lab Guide section of his blog. We simply downloaded the scheduler files from Neil’s blog and imported them as a library into our Arduino Sketch. We were able to successfully use this code to schedule periodic events with no troubles. We specified two periodic tasks, one to poll the joystick for it’s current state and another to form and send a packet to the Roomba based on that state. These tasks are described in more detail in the following sections.
Gathering Joystick Input
In many third-person console games, such as Grand Theft Auto V, the character’s movement and rotation is controlled entirely with the left analog stick. When you point the stick, the character rotates to face that direction and moves that way. The camera then rotates to realign with the character, meaning that if the stick is held in the upper-right corner, the character will “arc” to the right. We wanted to approximate this to control the Roomba, as it would feel natural to control.
The Roomba is controlled by specifying signed velocity and radius values. At minimal radius and nonzero velocity, the Roomba spins in place clockwise or counterclockwise. As the radius increases or decreases from minimal value, the Roomba will “arc” clockwise or counterclockwise. At maximum radius, the Roomba moves in a straight line. The ideal system to approximate the “third-person” control of the Roomba is as follows:
- The distance from the joystick’s center determines the Roomba’s velocity value. Farther away moves the Roomba faster. If in the top half, the velocity is positive; otherwise, it is negative.
- The angle the stick is held at determines the Roomba’s radius value. If the stick is directly up or down, the Roomba will have maximum radius and move straight. As the stick approaches the side, the Roomba arcs at a smaller and smaller radius. When the stick is pointed directly to the side, it spins in place with zero radius.
A third-person system uses more complicated math and potentially requires much care and time to implement. Many things could go wrong, from coding errors to joystick sensitivity issues, and debugging could be difficult. Additionally, since we only need to point at an IR receiver, and not perform complicated tasks as in a videogame, this level of detail is not necessary. Therefore, we wanted to implement something simpler.
The simplest joystick control scheme is four quadrants – the top and bottom quadrants move the Roomba forwards and backwards at a constant speed, and the left and right ones turn the Roomba in place at a constant speed. We considered using this scheme, but were wary of oversimplifying the controls and didn’t want it to be jerky and difficult to aim.
We decided on using a multi-zone mapping for each of the velocity and radius values, which can be seen visually in the following images. This is simple to implement and gives finer control without sacrificing time and ease of debugging, striking a balance between the extremely fine third-person control and very basic four-quadrant control. If you were to overlay the velocity and radius graphics, you would see something resembling a checkerboard, where each square has different velocity and radius values.
The Roomba’s velocity is determined by the position of the stick within four square concentric zones of control radiating outward from the sticks neutral position. The zone farthest from the neutral position results in the highest velocity whereas the zone at the neutral position results in no velocity. The turning radius is determined by the position of the stick within several vertical zones of control. The zone farthest from the from the neutral position results in the tightest turning radius whereas the one surrounding the neutral position yields no turning radius. To be able to turn the Roomba in place easier – to aim it at the receiver, for example – we added the following the special case: When the y value of the stick is near the neutral position, the turning radius will be zero no matter what the x value of the stick is.
The “state” of the joystick (i.e. what zone of control the stick is in for both radius and velocity) after each poll is maintained in a global variable. We defined a new library (stick.h) that contains enums representing each zone of control for velocity and radius. When the analog values are read from the joystick they are compared to a set of predefined thresholds that represent each zone of control. Appropriate values from the control zone enums are selected and saved to a global variable representing the overall state.
We initially encountered problems when reading the analog values from the stick. Values were unexpected and changed irrationally with stick movement. This turned out to be a grounding problem and was solved by powering and grounding all devices using the Arduino board instead of the power supply.
In Phase 1 we simply polled the joystick in the main Arduino loop. However, as Dr. Cheng mentioned in the lecture, this is a wasteful way of polling the joystick. For Phase 2 we used a periodic task dispatched by the TTA scheduler to poll the joystick instead. This ensures the stick is polled at more reasonable rate (every 100 ms). The periodic event uses the stick library to update a global variable containing the current stick state.
Wireless Radio Setup
In order to transmit packets to the Roomba we were provided a nRF24L01 radio transmitter and receiver. We used the software libraries provided by the TAs Daniel and Neil in order to get the radio working. The wiring was done by copying the the wiring of the base station in the lab, as it is an easy baseline to compare against for troubleshooting purposes.
There were number of troubleshooting steps we had to take before the radio was able to successfully send packets. The first problem was compiling the provided libraries. Unfortunately, this turned into a lengthy delay, but was finally overcome by changing the extension of the files from .c to .cpp. After getting the libraries to compile and attempting to send a message, the test station was printing out extra garbage characters. We were able to fix this by changing the datatype of several structure members in the radio library. As it turned out, the original data types were causing the size of the structure to be too large, which caused the bytes in the radio packet to be misaligned.
Additionally, we encountered problems later on with the radio due to faulty wires. This was overcome with help of Daniel, who could not get his working radio code to work on our system, alerting us to the fact that the wiring was the problem. After manually replacing each wire, the radio started working properly. In order to prevent this in the future, we plan on labelling wires so that we know which wires work properly and where each one is supposed to go.
After completing the basic setup and troubleshooting process, we were able to to follow the example on Neil’s blog to successfully send messages to the radio test station.
To decide when to send an IR command, we simply added another parameter to the stick state mentioned above. The state now specifies if the stick is pressed down or not. The stick library was changed to update this value every time the stick is polled. If the stick has been pressed down when the packet sending task is dispatched, an IR command packet will also be sent along with a regular command packet to tell the Roomba to fire the IR transmitter mounted on it.
Putting It All Together
Once we were able to send messages to the radio test station, and had created a way to translate the joystick input into a defined state, we attempted to integrate these features using the time-triggered scheduler that we had prepared. We initially decided to poll the joystick and send packets containing commands to the Roomba once each every 300 milliseconds, with the packet transmission task offset slightly from the polling. To send a command to the Roomba we simply translated our stick state global variable into a command packet containing the correct SCI commands.
A major obstacle that we faced was the unreliability of the radio receiver and transmitters when sending packets to the Roomba. While setting up the Roomba with the radio receiver, Daniel the TA indicated that only about four of every five packets would make it from the transmitters to the receivers. While testing our transmitter with the Roomba receiver, this ratio appeared to be even lower. The GIF below shows the yellow LED on the Roomba, which flashes when a packet is received. As can be seen, when a packet was sent from the transmitter every 300 milliseconds, the LED was not flashing nearly that often. When attempting to control the robot at this transfer rate, it was extremely sluggish to respond and nearly impossible to control accurately.
We modified the time-triggered scheduler to send message packets to the test station to confirm that we were transmitting packets with the expected timing. It appeared that the test station received a packet every 300 milliseconds or so, which meant that the problem was seemingly only between our radio and the Roomba. It was decided, with advice from Daniel and Neil, to simply flood the Roomba with command packets to overcome this problem.
The desired state the Roomba should be in based on the joystick position is now broadcast every 50 milliseconds instead of 300 milliseconds. With this implementation, we judged that even though many packets may still fail to get through to the receiver, many more would be received. This was confirmed by testing it out and observing that the Roomba was much more responsive with this 50 millisecond period. The following GIF shows the LED on the Roomba while packets are transmitted every 50 milliseconds. While we are satisfied with the level of control that this setup provides, we are still looking into why so many packets are being lost when transmitted to the Roomba.
We were able to use a time-triggered scheduler based on code found in the Mechatronics Lab Guide to dispatch two tasks in order to fulfill the requirements for the final phase of Project 1. The first task polls the joystick and encapsulates the joystick values in a global state variable. The second task sends a command or IR packet to the Roomba. When sending a command packet, the joystick state is sent to the Roomba in the form of a drive command. When sending an IR packet, the IR transmitter mounted on the Roomba is told to fire. The following GIF shows a clip of the Roomba being controlled by our system. Clicking here or on the image will lead to a longer video.
We were able to loosely approximate the control scheme found in many third-person video games in order to control the robot in a way that feels familiar. There were some early troubles, which we are still looking into, with lost packets when trying to send the commands to the Roomba. The wireless radio sends packets every 50 milliseconds to the Roomba, which gets around the packet loss problems by sending many more packets than we initially were sending. Additionally, we suffered from some issues with faulty wires and ran into problems with the external libraries we used, but once these problems were discovered they were fixed in a timely fashion.
If there is time, it would be an interesting exercise to attempt a more detailed control system for the joystick which does not use thresholds. We would also like to figure out the source of packet loss, and if it is something that can be fixed. Additionally, our wiring has ended up costing us some valuable time, so it will be important for future projects to take care with the wiring, and make sure we know which wires work.
Finally, because we tend to swap out and re-wire parts, and transport them to the from the lab fairly often, it would be very beneficial to have some “sanity test” programs that we can plug some initial values into that will then test to make sure a part is working properly. For example, a program that can send a simple test message from a radio wired to certain pins and check that it is received, and a program that can verify that the joystick input is coming in correctly, as this stumped us for some time during this project.