This week, I wanted to create an interface for the "T Map" board I made in Week 8. The stretch goal is to pull train locations from the MBTA API and use those to animate the map, and also perhaps let people select their station and see the nearest train. To start, though, I'll just try to get an interface set up where you can use a GUI to select which LED lights up. The first step was to allow for serial communication with the board. I had not originally planned to do serial communication with this board, so I soldered wires to two free pins to use as my Tx and Rx lines and manually fed these jumper wires into the FTDI cable header. Next, I adapted Neil's serial FTDI communication code to run on this board. This was a little trickier than I expected. I had to make a few adjustments, including:
bit_delay_time
to 103 (instead of 8.5). Because this circuit didn't have the external 20Hz crystal, it couldn't run fast enough to operate at 115200 baud.serial_pin_in
and serial_pin_out
macros to the appropriate pins on my board.serial_port
and serial_pin
macros so that they wouldn't conflict with the registers used for the LED array functionalityMany thanks to Palash for helping me figure out some serial communication issues!
With serial communication in place, I wanted to start by building an interface where a user could select which "station" they wanted to light up. I needed to start by creating a mapping between the LED ID (e.g. 1, 2, 3...29, 30) and the pins on each LED's cathode. After spending a bit of time trying to figure out how to convert between characters (e.g. 'F', 'E'...) and macros (e.g. F, E...), I ended up using the following snippets of code to cycle through the LEDs in order. With this setup, each LED can be specified by a single integer: it's ID. The ID integer is used to grab a certain index of the anode
and cathode
string arrays, which is then converted into the appropriate macro and used with the flash
function. I'm not sure the macro_conv
routine is the most efficient way to do this, but it works for the time being.
static char anode[] = "FFFFFEEEEEDDDDDCCCCCBBBBBAAAAA"; static char cathode[] = "ABCDEABCDFABCEFABDEFACDEFBCDEF";
uint8_t macro_conv(char c) { if (c == 'F') return F; else if (c == 'E') return E; else if (c == 'D') return D; else if (c == 'C') return C; else if (c == 'B') return B; else if (c == 'A') return A; return A; }
void led_cycle2(char* anode, char* cathode, uint8_t delay) { uint8_t i; for (i=0; i < num_leds; i++) { flash(macro_conv(anode[i]), macro_conv(cathode[i]), delay); } }
Next, I built a simple Tkinter GUI that took advantage of these functions to allow the user to send a specific LED ID and then have the board light up that LED. The GUI is not pretty, but it works. Here's the gist of that GUI:
def show_entry_fields(): txt = e.get() print("LED ID: %s" % (txt)) try: val = int(txt) if (val > 0 and val <= 30): print("valid number, sending!") ser.write(txt[0]) if (val >= 10): #only send second char if there are 2 digits root.after(bit_delay, ser.write, txt[1]) root.after(2*bit_delay, ser.write, 'x') else: print("Not a valid input , please try again!") except ValueError: print("Not a numerical input, please try again!")
I'm doing some input validation in the GUI: checking that the entered value is actually a number, and that it's in the correct input range. This validation is also done on the embedded side; I probably don't need to duplicate it. On the one hand, it's nice to have validation baked into the board since it needs to be validated based on its physical properties. It probably makes less sense to have numerical validation on the GUI side because hard-coded validation parameters make it less portable...but it also makes the set of possible values to send much smaller, which allows me to better validate how information is sent across the serial interface. Anyway, thoughts for some other time. I'm sending the number character by character to match the get_char
function in the embedded code. I use a delay so that each character is actually read. The bit_delay
used here is 103. I am using 'x' as my string delimiter on the embedded side. Here's the gist of the embedded code that's receiving characters through serial:
while (1) { get_char(&serial_pins, serial_pin_in, &chr); if (chr == 'x') { val = atoi(buffer); if (val <= 30) { flash_by_id(anode, cathode, val-1, long_delay); } else { put_string(&serial_port, serial_pin_out, "That is not a valid LED ID\n\r"); } memset(buffer, 0, sizeof(buffer)); put_string(&serial_port, serial_pin_out, "You cleared your string\n\r"); index = 0; } else { put_string(&serial_port, serial_pin_out, "mbta.interface.c: you typed \""); buffer[index++] = chr; if (index == (max_buffer-1)) index = 0; put_string(&serial_port, serial_pin_out, buffer); put_char(&serial_port, serial_pin_out, '\"'); put_string(&serial_port, serial_pin_out, "\n\r"); } }
N.B. Since I'm not actually using the serial interface to control the board, all the output text in the code above is unnecessary. I left it in there for clarity and because I used it extensively while debugging.
Here's a video of the GUI in action. I'm typing in specific LED IDs and the corresponding LED is lighting up on the board. The LEDs are numbered 1-30, starting with the "top" of the red line, then the "bottom" of the blue line, then the "east" of the green line.
As usual, I was hoping to get more done this week, but I also encountered a number of unanticipated difficulties that made progress more difficult than anticipated, so I'm happy that I got this working. I would like to work on this further. Next steps would be: writing a web interface and using the node server/ websocket setup described in class, and pulling data from the MBTA API to create a more useful/ interesting animation.
Code