Some time ago, I made the decision to move all of the feedback elements of the prototype Keyglove design to a separate I2C-controlled module, instead of using direct I/O pin connections. I did this because I also added three new touch sensors and better Bluetooth link and mode detection, and I simply didn’t have enough I/O pins to satisfy everything at the same time. The I2C bus was already being used by other modules anyway, so adding an additional slave device didn’t reduce the number of available pins anywhere else. At the same time, I freed up five whole pins for other functionality (Red, Green, Blue, Vibe, and Piezo). Not bad.
Of course, this turned out to be a bit difficult to accomplish, due to my lack of knowledge about many low-level AVR hardware features (including timers—*shudder*). I originally hoped I would be able to find an existing chip that did what I needed: 3-channel PWM LED control, on/off digital I/O for the vibrating motor, and frequency-based PWM output for the piezo buzzer. Alas, I found no such device that combined all three of those things together. I guess I shouldn’t be surprised though; that is a pretty unique combination of functionality to pack into a single dedicated pre-fab IC.
Building an ATTiny44-Powered I2C Slave
So, I set about finding an alternative. My choices were to put two or three separate existing chips on the same board to achieve enough functionality—maybe using a couple different PWM control modules—or to create my own using a fully programmable MCU. The MCU approach seemed a bit overkill until I checked the cost of I2C-based PWM modules and compared them with the cost of small AVR modules such as the ATTiny44A (at the moment, $0.83 in single quantities from Mouser).
To make a long story very short, I gave myself a crash course in the ATTinyX4 line of chips, AVR timers, avr-gcc, and the I2C protocol. The guys over at AVRfreaks.net were immensely helpful with pesky objcopy compiler flags with TWI/USI/timer functionality on ATTinyX4s, and Dean Camera’s Newbie’s Guide to AVR Timers was great as well. The final key that allowed me to fully comprehend timers and interrupts came from reading Atmel’s own datasheet for the ATTiny44A, poring over the timer register descriptions and expected behavior. There was a tremendous “AHA!” moment when I rewrote the timer setup portion of my code from scratch while referencing the datasheet, compiled it, and watched it do exactly what I wanted it to on the first try. Amazing.
The I2C/TWI/USI implementation was made many orders of magnitude simpler by using Donald R. Blake’s usiTwiSlave code with a few modifications. I had to add specific chip support for the ATTinyX4 line (most other ATTiny chips were already supported), and then I added callback functions to START/STOP conditions on the I2C bus (only the START one is used in my code; the STOP callback may not actually work due to USI interrupt vector limitations).
Ladies and gentlemen, we’ve progressed to the stage where it’s hard not to justify throwing together a little custom part using a fully programmable microcontroller, simply because they are so darn cheap and flexible. The future is here. (And yes, I realize that this statement will probably seem horribly outdated in a matter of months thanks to Moore’s Law.)
The Eagle schematic and PCB for this module are both available here (always use the latest version, though the one in this post is v2.0).
Programming the Mounted ATTiny44
The current “finished” code is available on the Keyglove GitHub repository here. It uses almost exactly half of the 4K of memory on the ATTiny44 chip, but since the *44 is cheaper than either the *24 or the *84, I decided to go with the *44. What about programming it through? Obviously these things don’t come preloaded with the feedback module slave code. I did all my testing with a DIP package version of the chip, but on the module itself I used an SOIC-14 chip (to keep the same small form factor I used for the original direct I/O version of the feedback board). To be able to program this, I wired up a 14-pin SOIC test clip with appropriate ISP connections for the ATTiny44:
Incidentally, I ran into two difficulties using this approach, both of which I fixed with a small revision to the PCB. First is that the vibration motor is too close to the MCU, so that when it is mounted, the test clip can’t grab the IC. This was easily solved by moving the chip a little farther away from the motor. The second issue wasn’t a problem, but it makes reprogramming the MCU more difficult for anyone who doesn’t have a test clip—namely, only four of the typical six ISP header pins are broken out. These (GND, VCC, SDA, SCL) are needed for regular communication with the module. So, I also added a couple of extra header pins to break out MOSI and RESET. Note that the ATTinyX4 line of MCUs shares the same pins for the SPI and I2C ports.
I did most of my testing and development without the motor mounted, therefore, and I finally got the code working the way I needed it to. Originally I was trying to pack a whole lot of functionality into it, but after a lot of headache and timer troubleshooting, I decided to just strip it down the exactly the same functionality I had before with direct I/O connections. After all, if I could do it that way before, there shouldn’t be an problem continuing. In reality, even adding PWM fade capability to the LEDs was a step up from what I had before, which was simple on/off only.
The feedback I2C slave module is a write-only device (reading is kind of pointless for this application), and it has the following simple register structure:
|0x00||RED_LEVEL||0-255 brightness, 0 = off, 255 = full|
|0x03||VIB_LEVEL||0 = off, 1 = on (or any non-zero value)|
|0x04||SPK_LEVEL||0 = off, 1 = on (or any non-zero value)|
|0x05||SPK_FREQ_H||16-bit tone frequency value|
The default value of all LEVEL registers is zero (off), and the default FREQ value is 0x06E0, which is 1760 Hz, or a high A note. Yes, I chose that arbitrarily.
The code makes use of soft PWM control for the LEDs, and timer-based frequency control for the frequency generator. It is probably not the most efficient way to do PWM, but due to the combination of pins needed for three independent LED channels plus a separate timer-based control of the tone output, I couldn’t figure out a better way, at least with my limited knowledge. Any suggestions are welcome though, if you have any.
The important thing is that the feedback module is now working great while using zero dedicated pins, and I even learned a whole lot about AVR stuff that I didn’t know before. I call this a success.
This recent document from Atmel is also interesting, and may be useful for others (and me, later): Atmel AVR290: Avoid Clock Stretch with Atmel tinyAVR