Sunday, October 30, 2011

My MSP430 based RGB light

The Design

For a long time I thought it would be nice to have a customizable colour light, but I didn't get around to building it. This year, inspired by the MSP430 LaunchPad and my purchase of a TLC5940 with SparkFun Free Day funds I finally built it.

For the LED, I chose the 10W 500-Lumen Multi-Color RGB LED Emitter Metal Plate (140 degree) from DealExtreme. It's a bright LED at a good price. Similar LEDs are available from multiple Chinese sites. I chose DealExtreme because I like the way their site is organized, they have a good reputation and good prices.

Initially, I was disappointed with the TLC5940. Yes, it is a "16 channel PWM unit with 12 bit duty cycle control", as described on SparkFun. However, it requires an external clock and PWM cycle start signal, with some very specific timings when loading new PWM data and starting a new cycle. Generating all that precisely would require a lot of resources from the microcontroller.

My first circuit clocked the TLC5940 using a 555 timer, and restarted the PWM cycle using a simple R-C network. The PWM data was bit-banged from my computer, and not in any way synchronized with the PWM cycle. This worked, with the main disadvantage being that the method was slow and not suited to colour changing effects.

I spent quite a bit of time wondering how to satisfy the timings in the datasheet while having low CPU utilization and fast PWM speed. My first design used a 7474 dual D-type flip-flop. A flip-flop in toggle configuration generated SMCLK/2, and I used the CLR input to extend the GSCLK cycle at the end of the PWM cycle.

This worked well, but I ended up choosing a simpler design, connecting SMCLK to GSCLK without glue logic. BLANK was generated from TACCR0, with the timer in up mode, and output mode 3. This creates a 1 cycle pulse, using only one compare register. This does not guarantee the proper timing between the rising edges of GSCLK and BLANK, but it works perfectly. I feel it's okay because this is just a personal project, and because that GSCLK edge is after the 4096th edge which ends the PWM cycle.

For XLAT, the nicest solution would be to use the multiple TA0.0 outputs, and enable XLAT via P1SEL. However, I chose to use USI in SPI mode to load PWM data more quickly, and so the other TA0.0 output wasn't available. I instead connected another pin to XLAT, with a 1 kΩ resistor between BLANK and XLAT. When the XLAT pin outputs a low, XLAT is inhibited, and when it is an input, XLAT is pulsed when BLANK is pulsed. It's unfortunate that even the 20-pin MSP430 Value Line chips cram most special functions into the 14-pin footprint.

I wanted the light to have both computer control and a user interface. I chose a serial port for computer control. A capture/compare register can be used to build a nice software UART which is not affected by interrupt jitter. I based my code on msp430g2xx1_ta_uart9600.c from the TI sample code. I wasn't too happy with the DCO tolerances however. They are sufficient for serial communication if the other side is precise, but I wanted something that would use up well under half the error budget. (Maxim AN2141 (PDF) provides a nice explanation on the subject.) The MSP430 Value Line chips don't support high frequency crystals, and according to the datasheet, they can't even accept a high frequency external clock input. It is possible to provide a high frequency external clock, but I didn't want to rely on this undocumented feature, so I used a watch crystal. The crystal triggers the watchdog timer interrupt 4 times a second, and code calculates the length of one bit in SMCLK cycles, based on the crystal. To avoid PWM jitter, I don't actually change DCO settings like an FLL, and I set up the DCO without modulation. I chose a high frequency, just under 16 MHz, so the PWM rate is high and calculations are finished quickly. Serial communication is at 9600 baud, which allows colour changes up to about 192 times a second, with the three 12 bit values packed into 5 bytes.

With all the pins needed, it became difficult to use a 14-pin chip. Some tricks and compromises could have allowed it, but I didn't really like those ideas. I got an MSP430G2252 in a 20-pin package.

For the user interface, I used two switches and three potentiometers. One switch selects between off, serial control and local control, while the other selects between RGB, effects and HSV during local control. I had thoughts of using the comparator to measure pots, but I went for the easy solution, using the ADC10. I was disappointed at the noise, even with proper bypass capacitors. To mitigate the issue, code performs smoothing. Rotary encoders would be a better alternative, but I have plenty of pots, and quadrature encoders would need more pins. With a rotary encoder, it would be possible to avoid colour changes when changing between RGB and HSV input modes, and instead just allow further tweaking in the new mode.

The key software component of the local user interface is a multiplication routine, which multiplies two 16-bit values as fixed point numbers between 0 and 1. That same routine is used for gamma correction, HSV to RGB conversion and fading. For gamma correction, values are simply squared. A power of 2.2 might be more accurate, but squaring is close enough. HSV to RGB conversion is done via a highly optimized assembler routine, partly just because I had fun writing it. Fading via Bresenham's line algorithm would have been more efficient, but the multiplication based version was fast enough, and code size matters when only 2 KB of flash is available.

After all that was done, one pin and some flash space remained. I used the pin as a serial output, so the computer could read the current colour and potentiometer positions. Due to the special purpose pins being crammed into the 14 pin footprint, I wasn't able to directly output from TA0.2, and so the output is done from the interrupt handler. It's not ideal, but it works. I consider it to be a bonus feature.

The gamma correction in the local interface is necessary, but it created a problem. Fading requires linearly changing the value before gamma correction, but the original serial interface only allowed setting the raw PWM value, which is the result of gamma correction. For proper fading, the code would require the corresponding value before gamma correction. The serial output can help here, by allowing the current setting to be read and initial fading to be done on the computer. The serial input also allows input of values before gamma correction. For a proper fade when switching away from serial mode, code can either use that all the time or just use it once before quitting.

Originally, I had various ideas for colour changing effects. Due to the limited code size, I ended up only implementing hue spinning, with the ability to set brightness, saturation and speed. The speed selection allows a wide range, from rotations taking several minutes to such rapid rotation that the light seems steady but fast movement leaves coloured trails.

The circuit is relatively uninteresting. Mostly, it's a matter of point to point connections between chips. I used an LM317 to supply power to the MSP430 and TLC5940. At the inputs, single transistor inverters perform level shifting and provide some protection for the MSP430.

The biggest difficulty with the circuit was the TLC5940's power dissipation. With a 10W LED and 2456mW maximum power dissipation, I had to be careful to avoid overheating the chip. The light requires a 12V regulated wall wart, and I added resistors to limit the voltage drop at the chip to about 1.4V. I also mounted the chip on the underside of the circuit board and connected it to the metal bottom panel using thermally conductive putty.

According to calculations, the chip isn't close to its limit, but it's nice to have a big safety margin. The microcontroller also monitors the XERR pin via an interrupt and turns off the light if the TLC5940 overheats. I'm satisfied with the power losses due to the linear current regulation. However, considering the power losses and resultant heat dissipation, if driving a higher power LED or array I would choose three switch-mode LED drivers instead of the TLC5940.

Usage experiences

In the local interface, I mostly use the HSV input mode. It's far more convenient than the RGB mode. HSV is kind of stupid, because it ignores many perceptual factors. (The three primaries have different apparent brightness. When multiple primaries produce a colour, there is an increase in brightness and decrease in saturation. Colour does not seem to vary at a fixed speed as H is changed.) However, more complex colour spaces such as CIE LCH have many colours which cannot be reproduced via the three primaries. If the three potentiometers set L, C and H, there would be settings that are out of range. I tried it via the serial interface, and it was quite confusing. HSV to RGB conversion is also a much simpler algorithm and more suited to small microcontrollers. I have no regrets about choosing HSV.

So far, my favourite computer-based effect is a Winamp plugin which divides the spectrum into 3 zones, and sets red, green and blue values based on an exponential moving average of sound intensity in the corresponding zone. I used red for the lowest band and blue for the highest band. It's very nice with some types of music.

The Code

I'm releasing the firmware under the GNU General Public License (GPL) version 3, because I like how the GPL encourages creation of free software. I developed and compiled the code using IAR Embedded Workbench KickStart, because it offers a nice IDE for developing and debugging. Pin assignments are listed in the header file. If you want to change them, consider that many of the port 1 connections depend on the special functionality available on certain pins. You can download MSPRGB source code from Dropbox.

I'm separately releasing some code which uses the RGB lamp via a serial connection. The zip you can download from Dropbox contains librgb, a library for interfacing to the lamp, and vis_rgb, the Winamp plugin I described. I just cleaned up librgb and improved portability. I'm not protecting librgb and vis_rgb via the GPL because I don't feel it is especially worthy and because I don't want to restrict its usage.

Synaesthesia 2.4 for Windows

It seems like a lot of "music visualization" plugins just use music as a random seed for interesting visual effects. There is a link between changes in music and changes on screen, but there's no real collection between individual elements of music and individual elements on screen. The best software I know of for visualizing music is Synaesthesia. It presents an image with the horizontal axis being stereo and the vertical axis being frequency. Images can directly correspond to individual sounds, and so I can see sounds on the screen as I'm hearing them. The program truly deserves the name Synaesthesia.

Until now, the only publicly released Windows version was an old port based on Synaesthesia 1.2. The current version of Synaesthesia is 2.4, and there are many new features since 1.2. A long time ago I started working on a port of Synaesthesia as a Winamp plugin. However, I learned that GPL licensed code (Synaesthesia) can't be used in a plugin for an application that has an incompatible license (Winamp). I just created a new port based on Synaesthesia 2.4, the 1.2 Windows port and some of my code. You can download it from Dropbox. Further information is available within "Synaesthesia 2.4 Windows port readme.txt" in that zip file.

This is just a quick port I created today. There may be bugs. Please don't bother the original authors of Synaesthesia and the 1.2 port. If you run into problems, leave a comment here.

Friday, October 28, 2011

Winamp plugin which enables UAC virtualization

Some old Winamp plugins attempt to store settings in the plugin directory. This fails in Vista and Windows 7 because directories under Program Files aren't writable for ordinary users. Windows has a feature, UAC Virtualization, which redirects these writes to a folder in the user's profile (%USERPROFILE%\AppData\Local\VirtualStore). However, the manifest in winamp.exe disables this feature.

It's possible to grant write access to the directory, toggle UAC state via Task Manager, and edit the manifest in winamp.exe. However, all of these workarounds have disadvantages. Because of that, I just created a simple plugin which enables UAC. It may not execute early enough to help code which runs when Winamp starts, but it's perfect for visualization plugins which run on demand later. You can download it from Dropbox. I'm not releasing the full source because most of it is just sample code from the Winamp SDK. Enabling UAC Virtualization is easy:

HANDLE token;

if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, &token)) {
    DWORD v = 1;
    SetTokenInformation(token, TokenVirtualizationEnabled, &v, sizeof(v));
    CloseHandle(token);

}

Thursday, October 27, 2011

The PL-2303 code 10 error

Some cheap PL-2303 based USB to serial adapters don't work with new drivers from Prolific. In Windows, Device Manager gives a "This device cannot start. (Code 10)" error. Prolific knows about this error and has a FAQ question about it. To me, the answer there seems to imply that these PL-2303 chips may be counterfeit.

When I ended up with such a bad PL-2303 based  adapter, I investigated the issue using Snoopy Pro. It is a free open source (GPL) program which logs USB communication between the driver and device. SnoopyPro allowed me to see the requests sent by the Prolific driver, and compare responses.

Here is the request sent from Prolific v1417 driver in 32 bit Windows 7 SP1, with version 3.3.17.203 ser2pl.sys:

URB Header (length: 80)
SequenceNumber: 10
Function: 0008 (CONTROL_TRANSFER)
PipeHandle: 00000000

SetupPacket:
0000: c0 01 86 86 00 00 02 00 
bmRequestType: c0
  DIR: Device-To-Host
  TYPE: Vendor
  RECIPIENT: Device
bRequest: 01  

No TransferBuffer

Here is the response from a Dynex DX-UBDB9 adapter, which works perfectly:

URB Header (length: 80)
SequenceNumber: 10
Function: 0008 (CONTROL_TRANSFER)
PipeHandle: a24f2724

SetupPacket:
0000: c0 01 86 86 00 00 02 00 
bmRequestType: c0
  DIR: Device-To-Host
  TYPE: Vendor
  RECIPIENT: Device
bRequest: 01 

TransferBuffer: 0x00000001 (1) length
0000: aa

Finally, here's the response from the BAFO USB to RS232 Converter Adapter that I got from DinoDirect:

URB Header (length: 80)
SequenceNumber: 10
Function: 0008 (CONTROL_TRANSFER)
PipeHandle: 856e8594

SetupPacket:
0000: c0 01 86 86 00 00 02 00 
bmRequestType: c0
  DIR: Device-To-Host
  TYPE: Vendor
  RECIPIENT: Device
bRequest: 01

TransferBuffer: 0x00000001 (1) length
0000: 00

There's one difference: the good adapter responds with 0xAA and the bad adapter responds with zero. The information could be used to find the check in ser2pl.sys and make the driver accept the zero. However, the EULA doesn't allow that. In any case, the adapter from DinoDirect has other problems which cannot be fixed.

Why you need to be careful when buying a USB serial adapter

I got a BAFO USB to RS232 Converter Adapter from DinoDirect. I thought I was getting a good deal. It was more expensive than the no-name adapters available elsewhere, but BAFO is an actual brand with support and drivers. I noticed that they were distributing a relatively recent Prolific PL-2303 drivers, which should mean they used PL-2303 chips which didn't trigger the code 10 error. What I got was a piece of junk:
  • Recent drivers fail to work with the adapter, giving a code 10 error. Even the most recent drivers downloaded from BAFO fail. (I wonder if it is really a BAFO product.)
  • The output is 0-5V. The adapter cannot output negative voltages like DinoDirect claims. (They write "The output voltage of our USB to RS232 converters (Pin 3: TX line) is about -9VDC".)
  • The plug is loose, and the USB connection is disrupted if the adapter is moved.
  • In Linux or in OS X with the open source drivers, the adapter starts endlessly repeating the last character that is received. (Prolific's drivers don't work in OS X either.)
  • The adapter cannot detect RS-232 break.
  • Timing may be inaccurate at higher baud rates
The adapter is usable for some applications in Windows with older drivers. However, watch out: some old drivers from Prolific cause bluescreens.

When I complained to DinoDirect, I was told I'd get a $5 giftcard with which I could "pay as cash" , but I was given a $5 off $20 coupon. When I tried to post a review listing the problems the review didn't get approved. A positive review for another item was approved immediately, and I got DinoPoints. When a second attempt to post the review didn't get approved either, I decided to write this blog post.

I should have done some research before dealing with DinoDirect. It seems like a lot of people dislike them. Some even feel that DinoDirect is a scam. I think it's pretty clear they're not a scam, but I can't trust the information on their website, or that they will respond to my problems in a satisfying way. (DealExtreme would probably give store credit for a product like this.) After browsing the DinoDirect web site more, I found a lot of questionable reviews and even questionable product information. For example, some macro diopters have information and reviews as if they're polarizers. Overall, many reviews seem questionable. I'm not saying that DinoDirect is posting fake reviews. Maybe some users post lots of reviews so they can get DinoPoints and free stuff. DealExtreme also offers points for reviews, but their reviews seem genuine. Go ahead and compare DealExtreme and DinoDirect reviews. Also, note that DealExtreme has a lot of activity in the per-product forums.

DinoDirect isn't all bad. They gave me a $1 gift card, I guess for creating my account. Then, I got 100 DinoPoints, corresponding to $1, for a short survey. When the flashlight I ordered wasn't in stock, live support they gave me a $3 gift card. Finally, I ordered the RICHONG RC-7001 LED Flashlight (1*AAA Battery) flashlight and received it. It's a decent little LED flashlight. It seems pretty rugged, and I like how it uses one AAA battery instead of several coin cells. Getting that for free partly makes up for the USB to serial junk I paid for.

Finally, if you really want to order from DinoDirect, two suggestions: Try to find some better information on the product elsewhere. Check for coupon codes, because DinoDirect is always running promotions. RetailMeNot has a good list.

Monday, October 24, 2011

The Unix serial port output buffer

Unix serial ports have associated input and output buffers in the kernel. When writing data to the port, if sufficient space is available in the output buffer, the write call returns after copying the data into the buffer. The hardware slowly outputs data from the buffer, freeing space for more data. A program can easily fill up the buffer, because the CPU works much faster than the serial port. When insufficient space is available in the buffer, a normal blocking write will wait. It will not simply wait for sufficient space; instead, it will wait for the buffer to empty to a specific low limit.

When simply sending large amounts of data, this behaviour is appropriate and efficient. Programs don't need to constantly wake to feed more data to the hardware. Instead, they can put a lot of data into the buffer and then sleep until the buffer gets to that low limit. However, for some applications the buffer is a problem.

Bytes that are already in the buffer form a delay between when a new byte is written and when it is actually output from the port. (All those bytes need to be output first.) As a result, serial responses and actions from serially controlled hardware may be delayed. If a program performs checks to see whether to continue sending data, that will also be delayed when a write call waits for the output buffer to empty.

In Linux the output buffer is one page (typically 4KB), and the process is woken when only 256 bytes (set via WAKEUP_CHARS) remain. That may seem like a small amount of memory, but at low baud rates, it corresponds to a long time. There is no standard way to change the buffer, and in Linux, it could only be changed by editing kernel source code and recompiling the kernel.

It is simplest to wait for data to be output after writes. This can be done by waiting for responses after writs or via tcdrain. (Other methods such as fsync and O_SYNC cannot be relied on for serial ports.) Unfortunately, this defeats the efficiency benefits of buffering.

When the issue is a need to stop output quickly, instead of a need for constant low latency, data in the buffer can be discarded via tcflush. However, this gets tricky. Since writes can wait for a long time, some form of asynchronous I/O is needed. Once the buffer is flushed, serial controlled hardware may be in an unknown state, which complicates things.

As an added benefit, tcflush and tcdrain also deal with the hardware buffer. In most built in serial ports, these buffers are tiny. However, USB to serial adapters can have large buffers.

Friday, October 14, 2011

In Linux, use the Realtek r8168 driver for RTL8168

I have a Gigabyte GA-P35-DS3R motherboard with a RTL8168 Ethernet chip.
When using the r8169 driver that's part of the Linux kernel, I often lose the connection after S3 suspend. Usually, taking the interface down and back up with ifconfig fixes it, but unloading and reloading the module is sometimes necessary. This problem has been around for a while, and it still exists in the current Linux Mint Debian Edition 3.0.0-1-amd64 kernel. Even adding r8168 to SUSPEND_MODULES doesn't help.

The best solution seems to be using the r8168 driver from Realtek instead. It solved the problems after suspend, and seems to have also sped up suspend a bit. It may have also fixed some hangs I used to get occasionally.

At first, version 8.025.00 refused to build because the src/Makefile didn't recognize that Linux 3 should be treated like Linux 2.6. I fixed this by changing the lines setting KEXT and KFLAG to:
KEXT := ko
KFLAG := 2x
After this, I had no further problems and autorun.sh correctly built and installed the driver. It renames r8169.ko to r8169.bak to deactivate that driver. After confirming that r8168 works properly, it's a good idea to blacklist r8169. It's also necessary to update the initial ramdisk, because r8169 may be loaded from there.