-
Thoughts and Plans
01/03/2015 at 04:59 • 0 commentsI'd like to take some time to reflect on what I encountered with this project. I will start with the Trinket Pro side of things then move on to Android.
Hardware (Trinket Pro and components)
Trinket Pro
- Awesome little controller. I will gladly use 2 pins for a USB bootloader. I look forward to using this in further projects
- I wish the bootloader didn't wait as long to start the FW or if the bootloader would only start on hardware reset
OLED Screen and Libs- Awesome resolution
- Can require a bunch of RAM (depending on the Library)
- functions can be stripped to gain back ram
- Would opt for a bigger (same resolution) next time 1.3 inch or so
Hardware Debounce- Very nice not to have to do in code.
- Bulky hardware
- Timing sensitive (but can be fixed)
LiPo Monitor and Charger- Awesome to have all of this built in.
- A bit expensive to buy all of the breakout boards
- Plan on making an all in one at a later date
Bluetooth RN42XV- I Love this little module. A bit overkill. yes. but easy to work with
- Replaceable for a 4 pin UART based module (3.3v)
Total Hardware Time- I always underestimate how long hardware takes me. It took a good few days to get everything in the Altoids tin and working.
Software - Android
Android is a lot more time consuming than I remember. I always run it to odd issues. My saving grace for this project when it comes to android is that I was able to reuse the BluetoothSerialService (slightly modified). I remember this taking days to get right. There are still a ton of things I want to learn about android application development and the resources are out there. Hopefully I will have more time in the future to look into this more.
Future Improvements
I want to make the device more state-ful with various additional menus and a bunch of additional functionality. This has been a fun learning exercise. I was able to cram quite a bit into the Trinket Pro firmware and still have a lot of ram (despite the OLED Libs) and code store space left.
I would also like to make a PCB for inside of the altoids tin to make it look better. I may do this as a Rev 2. It sounds like a fun design project. I would integrated the Trinket Pro ref design and the lipo management and a handful of other components on the same board. Would free up space in the tin.. Say to carry headphones or something.
-
Project Video
01/03/2015 at 04:21 • 0 commentsNot sure if this should be in a project log or in details.. so ill just leave this here.
Forgot a few things. so part 2
-
Android App Walkthrough
01/03/2015 at 04:19 • 0 commentsTo note. This is not my cleanest code. I threw it together as the project progressed. This project will not go into as much detail as the Arduino Firmware Walkthrough, but should give a general idea of what each part of the Android does.
Most of these files were auto-generated by the Android Studio. I am unfamiliar with the format myself (first project using Android Studio and all) but I can point out where my source code is.
To begin navigate to WorkoutAid/Android/app/src/main/java/com/example/mike/workoutaid. You will see four files here and they are all Java Classes. I will detail each Class, but the overview of the classes:
- BackgroundService.java
- Used primarily for handling the automation of the app. Timer events, and processing the Bluetooth communication.
- BluetoothSerialService.java
- Used to manage the connection to the Bluetooth device and handle the buffer communications to the Bluetooth device.
- NOTE: To use a different Bluetooth device, the MAC address variable must be changed in the source.
- Will add modifications to make this configurable later.
- MainActivity.java
- Processes the on-screen buttons used primarily for testing. Is also the main entry point to the app.
- Android Activity Layout for the Main Activity was auto generated by the layout editor in Android Studio
- SpotifyBCR.java
Lets start out with the easier Classes and work our way down the the larger and more complicated ones.MainActivity.java
This is the main entry point into the app and starts all of the Services needed to get this to work. We extend a java base class (ActionBarActivity) and override some of the base classes functions.
The onCreate function is run when a new app is created (or started) or the Activity state became 'started' (due to screen rotation etc.. ). In this function we do some initial house keeping and start the BluetoothSerialService and the BackgroundService. Order is important here. The BluetoothSerialService needs to be started first due to accepting events to provide configuration that could possibly be sent by this Activity or another class (cough cough BackgroundService). After the services are started the GUI thread continues and listens for events (more on this in a bit).
The onDestroy function cleans up when the activity is going to close or some other 'close like' event occurs (possibly screen rotation etc..). This function stops the services in the reverse order in which they were started.
The onCreateOptionsMenu and onOptionsItemSelected functions were auto-generated for the Activity and I didn't do anything with them.
The buttonHandler function is a function that ALL button onClick events call. I did it this way because there is one entry / processing area for all of the on screen buttons. The actions these buttons take are not going to be detailed due to them being used for debugging. If you are interested please dig a bit into the code. It should be a short read.
Testing. Testing. Testing. A lot of the testing and verification I did was behavioral testing.. EG I looked at the Android LogCat output to verify things were happening as I would expect them to. This is not 100% ideal, but worked.
Moving on the buttonEmuHelper is used with some of the debugging buttons to help emulate a Bluetooth message coming from the BluetoothSerialService. Not 100% needed, but allowed me to test button functionality while I didn't have the Trinket Pro device with me.
The setupBluetooth and destroyBluetooth functions are helper functions to get the BluetoothSerialService properly started and destroyed.
SpotifyBCR.java
This class is very simple, but some research is required to get it to 'work'. The class extends an Android BroadcastReceiver. The Broadcast Receiver (BCR) sits on the device and listens for broadcasts. This BCR specifically listens for com.spotify.music.metadatachanged, com.spotify.music.playbackstatechanged, and com.spotify.music.queuechanged. However the Class using this class must register the receiver to actually listen for these events.
The BrodcastTypes static class just holds the broadcast event information.
The onReceive function actually receives the event and processes it. In this class implementation, I actually generate additional broadcasts to send to the BluetoothSerialService. This made it a bit easier and having to pass bundles between classes.. Just call a static function and BAM. It works. Basically I parse the even data and spawn new events. A bit of math was involved to calculate the track length in a more readable format but it was straightforward.
BluetoothSerialService.java
Behold the class that does most of the lower level leg work. This class is responsible for handling all Bluetooth configuration, connection, and data passing. I have used this class in a few other projects but I had to modify the receive side quite a bit to handle data the way I wanted it to. This class extends an Android Service and is run in the background of the starting activity. Perfect fit for handling all Bluetooth tasks in the background.
This is most likely not the best implementation of something like this, but as I have said a lot.. It works for what I need it for. Please don't worry about all of the public static final strings and the lack of enums.
This class has a few threads that it spawns to handle to socket communication to the RF device in the Android OS. If there are enough comments, I may document this later but will have to pass for now.
The primary code I want to focus on in this class is the data communications found in the ConnectedThread. This thread is spawned once the Bluetooth RF Socket has been connected to. The run function in this thread sets up the buffers to receive messages and sits in an infinite loop waiting for messages. The call mmInStream.read(buffer); is (IIRC) a blocking call hence why its in its own thread. This receive functions basically waits until it finds the terminating sequence (0xA, 0xD, and 0x0 in order) before broadcasting the message out to the system. Once the broadcast is sent, the function clears its buffer and waits for the next message. I had some problems getting this to work and I ended up using a two buffer copy to get it to work. This is due to the fact that if I expect to get a 10 byte message, I may get a byte at a time on the socket. I needed to compound the bytes received in order to properly process. Using the terminating sequence, I was able to send and receive data and string messages in the same command. (Aside and out of scope) Take for instance sending a line update to the OLED. I needed to send the command, the line number, and the 21 char message. Most of the commands are set up this way.. Its a good thing I was able to modify this.
One of the nice features of this service is it broadcasts messages to let other components know that something happened to the Bluetooth connection.. eg connection established or connection was lost. This is useful for automating the connection and keeping a connection to the Bluetooth device.
BackgroundService.java
This one is a bit tricky to talk about because it does quite a lot. I will try to break it down into sections. A handful of the class variables are used to keep state or are static variables to store certain information.
- Managing the Bluetooth Connection
- When the service is started the service registers broadcasts from the Bluetooth Service. Both alerts (connection status) and messages (data). Then an Android Handler is used to trigger the BluetoothSerialService to start a connection. The BluetoothSerialService sends connection state alerts to this BackgroundService and this service makes decisions based on that connection state given a timed event on the connection handler. If the state goes from disconnected to connected, this service sends init data to the Trinket Pro.
- Handling Bluetooth Messages Received (Status from the Trinket Pro)
- Basically I listen for the BluetoothSerialService.BLUETOOTH_SERVICE_MESSAGE broadcast (that I registered above) and determine if I sent the message or received the message. I then process the message (pass it off to either the button processing or other).
- Registering the Spotify Receiver
- This is easy because all of the work is done in the SpotifyBCR.java cass. I basically have an instance of the class and on start register that instance as a BroadcastReceiver and let it do the work.
- Updating Time / Delayed polling for status
- For this I used Java Timers. Declare an instance of the timer (clockTimer and muteStatusTimer in the code), and on service start schedule the timer to fire at X interval. Register a function that that timer calls when it runs and do the processing there.
- AudioManager
- Useful for managing volume and mute states. I only mess with(volume up, volume down, and mute) the Audiomanager.STREAM_MUSIC in this service. Grab an instance and go (AudioManager) serviceContext.getSystemService(Context.AUDIO_SERVICE);
- KeyEvents / Runtime Commands
- A Pain in the behind. This is where I spent most of my time and this is where the most improvement can be had. I used Runtime.getRuntime().exec(cmd) to send in a key event. For some reason it liked to hook into other audio players and I saw interesting behavior. This will need to be improved in the future.
- Static Helper Functions
- Each 'public static void' function at the end of the service are used across multiple components to generate intents to send over the Bluetooth connection. These functions handle the formatting of the predefined command structure that is shared (implicitly) between Android and the Trinket Pro.
- Button Processing
- Each time a button event is received (from Bluetooth and processes) I store that state. If that state is not the same as the previous state (YAY XOR) I process the buttons else do nothing. The event also sends a deviceState which is configurable from both the Trinket Pro side and Android side. I did not use this feature, but will be used for future planning to have multiple button maps or different menus to implement additional functionality on the device.
- BackgroundService.java
-
Trinket Pro Firmware Walkthrough
01/03/2015 at 02:12 • 0 commentsTrinket 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) { setup(); 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..)
-
Device construction
01/03/2015 at 01:12 • 0 commentsI just wanted to detail the construction of the portable device.
For all point to point connections, see the schematic. For the most part.. Just build the schematic. Note. The button wiring may be different in the finished device, but this can easily be changed in the firmware.
Please see this Imgur Album for pictures of the construction process and the finished device.
- Dry-fit enclosure and determine part placement.
- Make sure everything will fit inside
- Layout front panel
- I masked the front with Blue tape to aid in layout and cutting
- Cut front panel
- Dremel anyone?
- Create button board
- Place / Solder / Wire 6 buttons with a common pin (ground) and then solder 6 lengths of wire with one connected to each button
- Solder the power switch in place
- Solder 2 lengths of wire to the power switch
- Add a pin header socket to the 2 wire leads (after cutting to length)
- Mount button board
- I used hot glue. Make sure the buttons do not short on the tin
- Wire / Mount OLED Screen
- Solder 7 lengths of wire to the OLED Screen (SPI mode).
- Make hot glue 'risers' for the OLED Screen
- should allow me to easily remove the screen if / when needed
- Mount the screen to the risers
- Connect button panel to MC14490P
- I make a PCB with a socket to add connection points, but this can be avoided by directly soldering to the chip.
- Make connection board for Trinket Pro
- The connection board had the LDO Regulator mounted near the USB jack. This prevented me from having to wire it elsewhere. Having the connection board allowed me to easily connect the various lengths of wire from various components to the Trinket Pro. It also makes the Trinket Pro reusable / removable.
- Power Bar
- I added a common power bar to make routing power easier. The power bar connects to the output of the LDO Regulator and then connects to the various componets. The Trinket Pro has its own 3.3V regluator so this LDO was only powering the MC4490P, OLED, RN42XV, and the MAX17043.
- Add the MAX17043.
- I removed the JST connector to make the board smaller.
- I then cut the power trace to the IC from the battery and powered the IC via the power bar.
- Connect the battery connection points on the Trinket Pro connection board to the IC
- Connect VCC and GND of the IC to the power bar (3.3v)
- Connect the SDA and SCL lines to the Trinket Pro
- Pictures
- Connect the MCP14490P and OLED to Trinket Pro connection board
- Draw 4 wires (TX,RX,VCC,GND) to an Xbee header to easily connect the RN42XV
- Connect TX and RX to RX and TX of the Trinket Pro
- Connect the Power Switch
- Connect the Battery
- Fit all parts into case
- Make sure nothing shorts. If you are unsure, use electrical or other insulating tape.
- Power on
- Connect via USB
- Compile and upload firmware to the Trinket Pro
- Follow Adafruit's Tutorial for more Trinket Pro related Getting Started Items
- Download required libraries and install them to the Arduino IDE
- See GitHub for links to libs
- Enable Spotify Broadcasts in the Android Spotify app
- Navigate to the Spotify App settings and enable the 'Device Broadcast Status'
- Loading the App
- Put your Android device into Developer Mode (Google search.. may be different per device) and enable USB Debugging (You will need drivers). This option is found in Settings->Developer options
- In Setting->Security check the Allow install of apps from unknown sources
- Load the project from GitHub into Android Studio and upload to the device
- Don't for get to change the Bluetooth MAC Address (Background Service?)
- Device auto connects and you should be good to go.
- Dry-fit enclosure and determine part placement.
-
Where a Scope would have been nice...
01/03/2015 at 00:53 • 0 commentsWhen testing some of the firmware on the Trinket Pro side I noticed that my button IO was a bit slow. I was using a 0.1uF cap (cap code 104). This gave me (according to the MC14490P datasheet) a debounce rate of ~15Hz (at 5V). If I use a 0.001uF cap (cap code 102) the debounce rate of ~1.5kHz (at 5V). This is much better. It took me a while to figure this one out. I must have looked over the data sheet at least a half a dozen times before I found the debounce rate calculation. If I had a scope, I would have easily been able to connect input and output sides and determine the debounce time / rate.
-
Start of Trinket Firmware
12/15/2014 at 22:42 • 0 commentsOver the past week I have been working with 'bring-up' of the various hardware components. My biggest fear for this project was running our of memory (RAM) due to the Adafruit OLED + GFX Library taking over 1K of RAM (on a 2K Ram MCU) for a display buffer. Don't get me wrong: I absolutely love the work Adafruit has done with the library and what they offer in terms of support on their forums. The library is fantastic for actually drawing to the screen. The first rev of this project is (for the most part) text only. I was able to find a text only library for the SSD1306 (link). This library reduced RAM usage and I (at the time of only testing the oled) was well under 100 Bytes of RAM usage including some basic structures in the sketch. This was a huge improvement when the other library used over 1K (1024 Bytes). I now feel more comfortable that I will be able to included all of the features I want on this device. I am still leaving the button de-bouncing up to the MC14490P to save some code store and RAM. I should easily be able to fit this dip package into the Altoids Tin. I did a mock fitting prior to rewiring the prototype (indicated in my last project log).
At the time of writing this project log, I have a test firmware that tests the oled, various oled supporting functions, and button input. I have stated working on the command structure to interface to the Android Application but it has be a time scarce week. I still need to account for the AT messages from the RN42.
More later.
-
Bluetooth Modules - Aside
12/15/2014 at 22:32 • 0 commentsI wanted to use Bluetooth 4.0 Low Energy with the project, but given the time constraints I will not be able to figure out why my BLE module is not working. I recently purchased a HM-10 Module from Amazon and was going to direct wire (without an adapter board) to the Trinket Pro 3.3V. This module however is not responding when I connect it to a console through a 3.3V FTDI adapter. It is scanable on my Android Phone, but when I attempt to actually serially communicate with it, I get various messages through Android saying connection failed etc.. I am however able to pair the device (code 000000). So for the time being, I will stick with the RN42. Transitioning to the HM-10 may be a future upgrade.
-
Updated Hardware
12/15/2014 at 22:23 • 0 commentsI have decided that the 5V route was not the way to go. Instead, I have upgraded (downgraded?) my hardware to a 3.3V Trinket Pro @ 12MHz. This should not impact the serial communication to the RN42X. This also means that I'm changing the battery. I will now be using a 3.7V 2500mAh lipo that is much smaller (physically in size) than the USB battery pack. This should allow me to fit all of the components in and on an Altoids Tin. In addition to the battery change, I decided to add the Adafruit Trinket Pro LiPo Backpack. This backpack is a stack-able pack that adds a LiPo charger off of the bus voltage, a LiPo connector (JST?), and a header for a power switch. Why stop here! I'm also adding a Sparkfun LiPo Fuel Gauge based on the MAX17034.
Given the hardware change, I opted to rewire the entire circuit also taking into account that I had previously wired the MC14490 incorrectly in the first image. I plan to upload a schematic and updated photo of the board in the next day or so.
[EDIT 16-DEC-2014] Updated hardware (excluding the LiPo Fuel Gauge MAX17034).
To recap. Previously I was using the Trinket Pro 5V, and a 5V USB Battery pack. Now I will be using a Trinket Pro 3.3V, 3.7V LiPo, LiPo Charger, and a LiPo Fuel Gauge. NEAT!
The components list will reflect the updated hardware.
-
Android IDE + ADB
12/08/2014 at 03:19 • 0 commentsFrom my understanding of Android Development there are two ways to set up a development environment. (Assuming you want to use an IDE)
I have included screen captures of each IDE below. I personally like the look and feel of Android Studio a bit better.I have used Eclipse + the SDK before so I thought it would be interesting to give Android Studio a try (still in beta). Setup was painless (minus download times). Basically install a Java Development Kit (JDK), set JAVA_HOME as an env var in windows, download Android Studio and extract to install. You will have to pick and choose various SDK options if you want to develop with an older SDK. In my use case that would be 4.4.4 (API level 19). I used the built in SDK Manager to download the SDK I was targeting.
Next I needed to install ADB Drivers for my Samsung Galaxy S5. Sending this (ADB drivers galaxy s5) through Google resulted in getting a link to the drivers within the first 5 entries. I ended up using the drivers linked on this page. Don't let the date fool you. This will work for the Samsung Galaxy S5 on Windows 7 Ultimate x64.
[EDIT] The drivers linked above worked on my laptop, however I needed to use updated drivers on my Desktop directly from Samsung.
Eclipse Juno with Android SDK
Android Studio