The Arduino Hardware
I'm using an Arduino Uno, but this fairly basic setup should be doable on many models. I have four simple pushbuttons for input of the four states that I'm tracking and a single common-anode RGB LED for providing visual feedback on the current state.
The Circuit
The actual wired circuit, upside down:
The buttons look too tight to use at this angle, but it's actually quite comfortable for quick use:
The Buttons
The simple pushbutton switch can be used to provide a digital input in the Arduino - digital means discreet states, and in this case each pushbutton can be either pressed or not. The most confusing aspect is that depending on how things are wired, "on" could mean that current is flowing or that current is not flowing. The version being used is dictated by whether a pull-up or pull-down resistor is used.
Pull-up and pull-down resistors basically ensure that the button state can be determined when the circuit is open (the button is unpressed) by connecting the input into the Arduino to either a voltage source (say 5V for a pull-up) or to ground (0V for a pull-down). Typical values for pull-up/down resistors are in the 10s of thousands of Ohms.
To add to the confusion, Arduino Uno calls a 5V input HIGH, and a 0V input LOW. So, using a pull-up resistor, the unpressed state is connected to 5V, and the Arduino sees this as HIGH. (Or vice-versa if you're using a pull-down). Picking which version you use is generally a matter of personal preference, but it is worth noting that your hardware wiring can be nicely simplified by using the pull-up resistors built into the input pins of the Arduino (20kOhms).
Long story short, I've opted for simple wiring and fewer components by using the Arduino's internal pull-up resistors. One side of each pushbutton is connected to input pins 2, 3, 4, and 5, and the other side of each pushbutton to ground.
The LED
Once again, for the sake of simplifying the circuit, I've opted for a single common-anode RGB LED in order to provide feedback for each of several states. An RGB LED actually has three separate LEDs built in (one red, one green, one blue), but they are all connected to a power source by a single pin (thus, common-anode). Note that common-anode inverts the logic on Arduino - if you want to light up the LED, you need to set the output pin to the cathode to 0V (LOW) leaving a 5V potential across the LED (because the anode is connected to a 5V line): LOW = on. If you want the LED off, send 5V to the output pin to the cathode leaving a 5V potential across the LED: HIGH = off.
LEDs generally require a resistor placed in the circuit to limit the current through the LED - otherwise you might burn it out. The resistor value depends on the LED (you can check the datasheet), but when it comes to using them with an Arduino, resistors in the range of 200-400Ohms will do for most LEDs. Lower values will allow more current to flow through the LED and give a brighter light. The resistors can also go on the anode or cathode side of the LED - it makes no practical difference for this kind of project.
After some trial and error, I've landed on connecting the LED anode to a 220Ohm resistor. On the cathode side of the red LED I've added an additional 100Ohm resistor, and for the green LED a 220Ohm resistor. These values were selected in part due to the fact the lower current is required for these LEDs, but also, with a little trial and error, to make the light nice a white when all three LEDs are lit up at once.
For this project, I've connected the LED anode to 3.3V rather than 5V - I just don't need that much current to get a good bright light with the LED I'm using. Using 5V, it was actually difficult to see what I was doing on small breadboard because the light was so bright. The cathode pins are connected to Arduino output pins 8, 9, and 10 (red, green, and blue, respectively).
The Arduino Code
Fortunately, the code for handling a few input and outputs with some very basic serial communication on the Arduino is fairly straight forward - less than a third as long as the Python side of this project.
int ledPins[] = {8, 9, 10};Pins 8, 9, and 10 control the RGB LED output. Pins 2, 3, 4, and 5 control the pushbutton inputs. Not really necessary, but I set a constant numPins for controlling the inputs/outputs. Set the current state to 4, which translates in this system to "NA". The val[] array works with the state variable to control the current state. More on that below.
int inputPins[] = {2, 3, 4, 5};
int numPins = 4;
int state = 4;
int val[] = {0, 0, 0, 0};
void setup() {
for (int thisPin = 0; thisPin < numPins; thisPin++) {
pinMode(ledPins[thisPin], OUTPUT);
pinMode(inputPins[thisPin], INPUT);
digitalWrite(inputPins[thisPin], HIGH);
}
Serial.begin(9600);
}
The setup() method is run one time when loading the program to Arduino. Here, I declare the ledPins as output, the inputPins as inputs, and turn on Arduino's internal pull-up resistors for the inputs using digitalWrite. (NOTE: The method for setting up the pull-up resistor here is out-dated, but still works.) Serial communication is how the Arduino talks with the Python side, so the communication is initialized and the baud rate is set to 9600. This baud rate has to be the same as that set on the Python side in order to communicate.
void loop(){
if (Serial.available() == 2) {
int byteOne = Serial.read();
int byteTwo = Serial.read();
if (byteOne == 82 && byteTwo != 87) { // Read is R (ASCII 82)
Serial.print(state);
} else if (byteOne == 87) { // Write is W (ASCII 87)
if (byteTwo < 48 || byteTwo > 52) {
Serial.print('E');
} else {
state = byteTwo - 48; // Converting ASCII to number (ASCII 48 is 0)
lightState(state);
}
} else {
Serial.print('E');
}
} else {
for (int thisPin = 0; thisPin < numPins; thisPin++) {
val[thisPin] = digitalRead(inputPins[thisPin]);
}
state = readState();
lightState(state);
}
}
The main loop on the Arduino continuously runs at the rate at which it takes to complete the loop. In this case, it's fairly simple. There's a main if statement that checks to see if there is an incoming 2-bit message from the Python side. If there is an incoming serial message:
- Read those 2 bits
- If the first bit is an "R" (for read request), send the current state of the Arduino to the serial port
- Else if the first bit is a "W" (for write data), check to see that the second bit is a valid value (between 0 and 4 or 48 to 52 in ASCII)
- If that second bit is not valid, send back an error, "E", to the serial port. If it is valid, set the state and light up the LED appropriately - see lightState method below.
- If the first bit is not an "R" or a "W", send back an error, "E".
If there is not a message from the serial port:
- Read the current state of each of the 4 buttons, in order
- Identify the current state using the readState method, see below
- Light up the appropriate LED with thPutting it Togethere lightState method, see below.
int readState() {
if (val[state]!=LOW) {
for (int thisPin = 0; thisPin < numPins; thisPin++) {
if (val[thisPin]==LOW) {
Serial.print(thisPin);
return(thisPin);
}
}
}
return(state);
}
The readState method checks to see if the current state is consistent with the way the state variable is set. If not, it identifies which button was last pressed* and sets that as the current state.
void lightState(int state) {
for (int thisPin = 0; thisPin < numPins-1; thisPin++) {
if (ledPins[thisPin]!=state) {
digitalWrite(ledPins[thisPin], HIGH);
}
}
switch (state) {
case 0:
digitalWrite(ledPins[1], LOW);
break;
case 1:
digitalWrite(ledPins[2], LOW);
break;
case 2:
for (int thisPin = 0; thisPin < numPins-1; thisPin++) {
digitalWrite(ledPins[thisPin], LOW);
}
break;
case 3:
digitalWrite(ledPins[0], LOW);
break;
}
}
The lightState method very quickly turns off all the LEDs, the turns on those appropriate based on the current state: button one turns on the green LED, button two turns on the blue LED, button three turns on all 3 LEDs for white, and button four turns on the red LED. If the current state is 4, or NA, all the LEDs are off. Remember, that the LED is common-anode, so LOW will turn the LED on, and HIGH will turn the LED off. I use switch/case syntax here because it's cleaner and easier to follow than a series of if/else statements.
*Handling Multiple Button Presses
This simple event tracker is intended to handle mutually exclusive events - we don't expect there to be more than one button pressed at a time. Since this is the case, the logic in the code above implements a "last-released" method when holding down more than one button. That is, when more than one button is pressed at any given time, the last button to be released will dictate the next state. In the very unlikely event that more than one button is pressed and they are released simultaneously (actually simultaneous - no way you're going to be able to reliably do this physically using your fingers), the first button pressed will dictate the state. In the even more unlikely case that they are pressed and released at exactly the same times, the button in lower order will take precedence - button 0 will take priority over buttons 1, 2, and 3, and so on.
Putting It All Together
Now with the Python side and the Arduino side set up, I can put them together and get a functional, minimally invasive, event tracker. The Python side by itself is functional, but more invasive. But note that the Arduino side itself will not track events over time. It requires the timed reset signal, audio tone, and file saving abilities from Python to make it actually functional. However, if you just want a way to light up an RGB LED using some buttons and an Arduino (with a little interactive Serial communication), this will do.
The next step for me is to actually put it into action - start using the setup, collect data, think about revisions and refinements, and start analyzing data. All the code, and some extra is freely available on GitHub (there's a branch for the stand-alone Python side, as well).