Trinket Pro Firmware Walkthrough

A project log for WorkoutAid

A Trinket powered wireless user interface for multimedia and workout tracking.

mikeneiderhausermikeneiderhauser 01/03/2015 at 02:120 Comments

Trinket Pro Firmware on GitHub

To note. This is not my cleanest code. I threw it together as the project progressed. I stuck with the Arduino IDE and Arduino libs to make it more hacker friendly.

I designed the firmware to act in more of a slave mode to the android device. It has the basic functionality to manage all components, but the data is not very uses (minus the battery charge) unless there is something controlling it. I guess it can be seen as a generic bluetooth controller.

First lets look at def_types.h. You can see the DEF_TYPES_H include guard at the top and bottom of the file. I then define the pin mappings for the buttons. On the physical button panel button 0 is the top left, button 1 is top middle, button 2 is top right, button 3 is bottom left, button 4 is bottom middle, and finally button 5 is bottom right. I then define the pins for the SSD1306 SPI interface. These are the same pins that are used in the Adafruit Tutorial for the OLED Screen.

SERIAL_BUF_SIZE is the buffer I use for incoming Bluetooth messages (from the Android device).

FONT_TYPE_1 is the font used by the OLED Library. I also define the maximum chars per line, max lines, and max chars per button text. I then have some display formatting offsets (STATUS_LINE_NUM, MUTE_CHAR_OFFSET, PLAYER_CHAR_OFFSET, and BATTERY_CHAR_OFFSET). These are used to help layout the top 'notification bar' on the oled. BAT_READ_SEC is how often the firmware will poll the MAX17043 Lipo Sensor.

I then typedef some states and command sets.

Finally, I defined the freeRam function in the header. This function was used for debugging only. It calculates how much ram is left for the MCU to use. Using different OLED Libs caused this metric to drastically improve (more ram).

On to HaD_Trinket.ino.

I first include the def_types.h then include the other libraries needed for the firmware (SSD1306ASCII, Wire, and MAX17043). I then declare the variables that are used in the firmware. First you can see instances of the battery monitor and the oled libraries. Then you see various variables that are used to store player states, device states, time, battery levels, and a serial buffer. The do_process_cmd is used to indicate that there is a command in the serial buffer to process.

Next is the setup function. For those who are not familiar with Arduino this function is the default run once configuration function. In this function I do all of the hardware setup (Serial, buttons, OLED, and I2C / battery monitor), I then write defaults to the OLED screen and flush the serial buffer. This function executes every time the device is powered on or reset.

Next is the main program loop function. For those who are not familiar with Arduino this function is essentially a while(true) loop in C. This loop is very basic but does a lot of work. It processes the serial buffer commands (and actually operates / sets value to the hardware), reads the hardware serial port for new commands, checks the battery, and checks the button IO.

Below is my interpretation of the setup and loop functions in the Arduino IDE.

void setup() {
    // Arduino setup function code

int main(void) {

    for(;;) {
        // Arduino loop function code
Side Note: One nice (but occasionally inconvenient) note about the Arduino IDE. All functions have their function prototypes automatically generated. This is useful for throwing code together, but makes the code a bit harder to maintain in multiple files. Hence why the frimware (minus the defines file) is coded in a singe file.

Next there is the oled_display_time function. This function can use some improvement, but works for now. It handles printing the time represented by hour and minute to the OLED. It should be able to handle both 12 and 24 hour formats.

Next there is the oled_display_mute function. This function will display the mute_state_t on the OLED screen.

Next there is the oled_display_player function. This function will display the player state on the OLED screen.

Next there is the oled_display_battery_function. This function will display the battery percentage (casted to the nearest int) on the OLED screen.

The next two functions (both named) oled_display_line will display a line of text (up until a null terminator) on a specified line. The line is always cleared prior to printing the new line.

Next there is the oled_clear_line function. This function will clear a line on the OLED screen.

Now for the big one. The function that does most of the heavy lifting. the process_command function. This function will process the serial_buffer and process command strings. The command formats are documented in the code. Please feel free to take a look. Each command is always terminated with a sequence of the following characters (0xA, 0xD, 0x0). I did this so I can send string based messages and data is the same serial command. There may be better ways to do this, but it works for me.

Next is the read_serial function. This function will read a single byte determine the command and then read the remaining predefined static number of bytes for that command. The do_process_cmd is set so on the next run of the loop the buffer will be processed. Again there are other and possibly better ways to do this (both cmd indicator and reading a static count of bytes) but it works for me.

Next is the process_battery function that is used in the loop function. This handles the polling of the battery and Serial message updates if the battery stats change. Again all serial messages end in (0xA, 0xD, 0x0).

Next is the process_buttons command that is used in the loop function. This handles reading the buttons and determining if the overall button state changed. If the state changed, it will send a serial message. All buttons are compressed into a single byte in the message. It follows a 1-hot encoding where 1 stands for button press and 0 for button relaxed (even though it is different in hardware).

The last and final function is the read_buttons function. This function reads all 6 buttons. NOTE (and this one threw me for a little spin). The buttons connected to A6 and A7 MUST be read with an analogRead. This is due to the layout in the 328P and the register layout. DigitalRead WILL NOT WORK. I debugged this for at least a day thinking it was a hardware issue with the MC14490P. A quick google search will give you the answer, but i initially thought it was a hardware issue. I used the bitSet and bitClear functions to make the code more readable to fellow hackers (so you won't see the bit shifts, masks, etc..)