Tuesday, September 15, 2009

Using Cyclical Redundancy Codes (CRC-16, CRC-32 etc.) on microcontrollers

Oftentimes it is useful to get a checksum or hash of the data, either for verifying there are no errors, or for determining whether something has changed. A Cyclical Redundancy Code (CRC-16, CRC-32 etc.) is commonly used. It is not a cryptographically secure hash (like SHA-1 or the now-obsolete MD-5) but it is useful for checksums and is a little better than a simple checksum or parity bit.

There are two ways of implementing a CRC algorithm - one that uses a table, one that does not. The table is stored in the nonvolatile memory of the microcontroller and makes computing the CRC a little faster. First I'll show the table method, then the table-less method.

3. CRC-16 With Table
To begin with, you need the table:
unsigned int const crc16table[256] = { 
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040, 
};

Next, you simply run the following function over the data you want to check.
unsigned int computeCrc16(unsigned char* start, unsigned int length)
{
  unsigned int crc = 0;
  unsigned char* bytePtr;
  bytePtr = start;
  for (unsigned int i =0; i> 8) ^ crc16table[(crc ^ *bytePtr) & 0xff];
    bytePtr++;
  }
  return crc;
}
Finally, a simple example:
unsigned char test[] = {0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39};
printf("CRC-16=%02X\r\n", computeCrc16(test, 9));
This should give a result of 0xBB3D.

3. CRC-32 With Table
This is very similar for CRC-32 but the numbers just get a little larger:

unsigned long const crc32table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, };

Now, the function:
unsigned long computeCrc32(unsigned char* start, unsigned int length)

{ unsigned long crc = 0xffffffff; unsigned char* bytePtr; bytePtr = start; for (unsigned int i =0; i { crc = (crc >> 8) ^ crc32table[(crc ^ *bytePtr) & 0xff]; bytePtr++; } crc = crc ^ 0xffffffff; //flip bits return crc; }
Finally, a simple example:

unsigned char test[] = {0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39}; printf("CRC-32=%lX \r\n", computeCrc32(test, 9));
This should give a result of 0xCBF43926.

3. CRC-16 (CRC-CCITT) Without Table
The table-less version is very similar, but crunches through the math without the benefit of the table. Running this on {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39} results in 0x31C3.
Here's the code (remember, there is no table):
unsigned int crcCCITT;

void initCrcCcitt(void)
{
  crcCCITT = 0x0000;
}

void processCrcCcitt(unsigned char x)
{
unsigned crc_new = (unsigned char)(crcCCITT >> 8) | (crcCCITT << 8);
crc_new ^= x;
crc_new ^= (unsigned char)(crc_new & 0xff) >> 4;
crc_new ^= crc_new << 12;
crc_new ^= (crc_new & 0xff) << 5;
crcCCITT = crc_new;
}

unsigned int getCrcCcitt(void)
{
return crcCCITT;
}

And here's a simple example:

  initCrcCcitt();
  for (int i=0; i<9; i++)
      processCrcCcitt(test[i]);
  printf("%04X\r\n", getCrcCcitt());
This should output 0x31C3.

Using Quicksort (Qsort) on microcontrollers

Need to sort an array of numbers? I needed to do this on a microcontroller and Quicksort did the trick, very easily. Here's how:

1. Need to include stdlib:
#include //for qsort
2. Need to make a comparison function. For 2-byte integers it's pretty easy:
int comp(const void * a, const void * b)
{
int* aa = (int*) a;
int* bb = (int*) b;
if (*aa==*bb)
return 0;
else
if (*aa < *bb)
return -1;
else
return 1;
}
3. The arguments for qsort are:
a) the array to sort
b) The number of elements to sort (starting from the element at index==0 in the array)
c) The size (in bytes) of what you are sorting
d) the comparison function you wish to use.
See the following example:
void testSort()
{
int numbers[]={1892,45,200,-98,-4,5,-123,107,88,-1000};
printf("Before sorting: ");
for (int i=0;i<9;i++)
printf(" %d ",numbers[ i ]) ;
qsort(numbers,10,sizeof(int),comp) ;
printf("\r\nAfter sorting: ");
for (int i=0;i<9;i++)
printf(" %d ",numbers[ i ]) ;
printf("\r\n");
}
For more information, see http://cplus.about.com/od/learningc/ss/pointers2_8.htm

Saturday, June 13, 2009

Version Control for Dummies

Version Control is one of those things that once you have it you wondered how you ever lived without it. A version control system (like CVS, Subversion, Perforce, etc.) runs on a server and holds all the versions of the files you put into it.

Daily Development with version control works like this:
First, you do an update of the files you're working on. This will get the latest version of each file from the server and copy it onto your local hard drive. Obviously, if you're the only developer you probably won't need to do it very often.
Then, when you come to a good stopping point (usually when you get something working or make a substantial change) you check-in the files to the server.
Now, let's say you're working on something and you screw something up, can't get the code to compile, etc. With a version control system in place, you can just revert the file back to the last good version.

I put all my firmware and software into the version control system and many of my PCB design files too. Oftentimes I'll put in other documents too if I'm working with multiple developers.

I use a hosted SVN solution from http://svnrepository.com/ - they have plans starting at $3.95 per month, and they're very good. For client side, I use RapidSVN on the mac which isn't very good but I haven't found anything better yet, or TortoiseSVN in Windows which is a very nice shell extension.

Electrical vs. Mechanical

This week I delivered five electrical prototypes to a world renowned industrial designer. Here's a few lessons I learned:

Get the mechanicals as soon as you can. I have yet to see a product where the first mechanical and electrical prototypes fit together perfectly. Have the mechanical guy there when you first assemble them together to discuss any problems right away.

Mechanical guys like datasheets but they love datasheets plus samples. Give them samples of everything, particularly switches, connectors, displays, light pipes, etc.

Better yet, have the mechanical guy pick out the switches, connectors, displays, etc. based on the requirements you give them.

Good things to have on-hand for modifications to the enclosures: Dremel, hot-glue gun, and strong double-sided tape.

For tight designs, some of the higher-end PCB layout programs (Altium, for example) will produce a 3D model of the PCB from the layout. This is very useful, and then creates a 3D space contract between electrical and mechanical. But be sure that your heights are all correct! Even better, have someone else double-check your design.

When determining how much space you'll need, don't forget to take into account space for wiring etc.

For a prototype, try to get as much space as you can. There will always be surprises.

For components that are very tall, try to find lower height components, even just as a backup. One of the prototypes I just delivered had a speaker that was about 2" in diameter and 1" tall. The mechanical guy screwed up and the space for it was nowhere near large enough, a good 0.2" too short. But, I found a couple other speakers that were similar but thinner, and I swapped one of those in and looked like the hero.

Speaking of Heroes and Villains, at some point both you (the electrical guy) and the mechanical guy will screw up something, so don't be too hard on each other. Be nice when they screw up and chances are they'll be nice to you when you screw up.

Spare, Spares, Spares: if you're working on a product for a demo, get extra enclosures and make extras of everything. On one of my prototypes a stupid little $0.20 battery holder prevented the a system of 3 devices from working. Of course it worked fine in the lab and failed when I got over to the client. Unfortunately I didn't have any spares so I had to troubleshoot it and fix it there on the spot. Doh!

Tuesday, June 9, 2009

Bringing up Baby (or at least a new PCB)

A few words of advice when bringing up a new board.

Idiot Check - I first take a blank PCB and do continuity between Vcc and Gnd as a simple idiot-check. You never know if DRC or ERC missed something, or the PCB vendor screwed up somewhere.

Start with the micro - Next, I put down the bare minimum required to get the microcontroller up and running - usually just the micro, crystal, and JTAG port.

Work outward - Then work your way outward from there, adding peripherals, testing them as you go along. When you power up the board, keep an eye on current consumption - if it inexplicably spikes then you may have a problem.

This being said, if I'm working on a new circuit where I'm not sure if part of the circuit is correct, I'll just populate that part and leave the rest of the PCB unpopulated. For example, a recent project had a DAC feeding an audio output to a speaker; something that I haven't done much before. I populated the audio filter and PA and tested that before I put down anything else.

The art of the prototype

Here's a few things I've learned to make prototype hardware. This mainly applies to low-speed (<100mhz)>
  • Use a processor with more Flash and RAM than you think you need. It's always easier to downgrade later than upgrade now.
  • Give yourself a debug serial port. It doesn't have to be much, and you could bit-bang it if need be, but this will make your life much easier. For complicated designs where I know I'll need to rely on it I'll go ahead and put an RS-232 level converter IC and DB-9 connector to make life easier. If I'm not sure whether or not I'll need it then I'll just leave it as a header with Tx,Rx,Gnd, and Vcc.
  • Don't route traces under ICs, if you can. I learned that the hard way recently. Why? because if you need to cut/jump that trace you now need to remove the IC first. I use both, but through-hole parts (in particular resistors and caps) tend to be more flexible than surface-mount. I like to use through-hole parts for power resistors or large (>100uF) decoupling caps because it makes it much easier to swap parts.
  • Use zero-ohm resistors liberally to give yourself options. On one board I made, I wasn't sure if I would need to power the audio amp from 3V or 6V. So, I put a zero-ohm resistor tying the audio power rail to both 3V and 6V rails. Of course I only populated one of them, but it allows you to change things easily.
  • Give yourself space. If possible, don't bunch components tightly together. The one that's in the middle of them all will be the one you need to replace. Also, I try to use the larger SMT components (0805+) to make it easier to rework.
  • Use "full-size" JTAG connectors, preferably polarized. Make life easy on yourself and prevent mistakes. Nothing's worse than having to replace your processor IC because the firmware guy reversed the JTAG connector, frying it.
  • Use the silkscreen to label liberally. Every connector/switch, etc should be labeled. A little thinking now can save a lot of time later on.
  • Bring out unused pins to headers. I like to use 0.100" single-row headers because it makes it easy to route. On each header be sure to put Vcc & Gnd just in case.
  • Idiot Lights - give yourself a power LED and also connect one LED to an output pin on your micro. Makes life a lot easier when troubleshooting.
  • DIP Switches - if you have space, put on 2 or 4 DIP switches. Very handy for when you have customers who can't make up their mind. I write the firmware to do a couple different things and then I can change things on the fly easily.
  • If you are working on a power-critical project, then run each subsection through a zer0-ohm resistor so you can do a good power analysis. There's nothing worse than populating a board, putting the micro to sleep, and then finding out that the PCB is still consuming a huge amount of current.
  • Use decoupling capacitors liberally. I use a ton of 0.1uF caps, putting them near Vcc pins on ICs. A few designs I've worked on recently controlled very power-hungry devices (high brightness LEDs, speakers) and I used a few large electrolytics.
  • Get extra PCBs made. They're very handy to test out different parts of the circuit, or even just to look at as a visual reference.
  • Speaking of PCBs, if your design is very simple and you're only making 1-2 then you might be able to use a development board and just connect everything point-to-point by soldering wires around. If your design is not trivial or you have to make more than 1-2 of them then by all means make a PCB. They're really inexpensive nowadays.
  • And finally, connectorize. I don't like to have lots of cables soldered directly to the PCB; if possible use connectors instead. They don't have to be fancy; I use a lot of simple 0.100" headers and mating connectors because they're easy to work with.

Thursday, June 4, 2009

The Joy of Bit-Banging, part 1: TX

Need a good bit-bang?

Sometimes you need one more UART than what your microcontroller has. In that case you need to bit-bang it out. "Bit-banging" sounds much more vulgar than it actually is - it just means that instead of using the built-in shift peripheral, you are manually turning on/off the bits. For this example I could implement it using a blocking method, so I just delay the microcontroller inbetween bits. A better implementation would be to use a timer to generate the bit timings.

The most common UART data format is 8N1: 8 start bits, no parity bits, one stop bit. This only tells part of the story. Actually there are a total of 10 bits: (see wikipedia)
  • One start bit (a "1")
  • Eight data bits
  • One stop bit (a "0")
For 9600 baud, bit spacing is 104uSec. To perfect the bit spacing I first just twiddled a bit on and off with the delay loop inbetween until I got exactly 104uSec.

On the hardware side, I ran the output of this through a simple level converter and then into an RS-232 port on a PC to verify that everything was working ok. One level shifter I like is available from sparkfun.com here.

/**
* Bit-Bang UART transmit
* Transmits one byte out the specified pin
* Baud rate is 9600 (104uSec bit timing)
* 8-N-1: 8 data bits, no parity, 1 stop bit
*
* Line is nominally at '1' (high)
* 1 Start bit = '0'
* Data, MSB first, LSB last. '1' = line high, '0' = line low
* 1 Stop bit = '1' (idle, or high)
*
* PRECONDITION: LINE IS high (idle) and port/bit configured for output!
*/

#define BIT_BANG_TX_PORT P4OUT
#define BIT_BANG_TX_BIT BIT6
#define BIT_LENGTH_9600 160
void bitBangOutput(unsigned char byte)
{
//start bit - pull line down
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT;
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

//LSB
if (byte & BIT0)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

if (byte & BIT1)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

if (byte & BIT2)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

if (byte & BIT3)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

if (byte & BIT4)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

if (byte & BIT5)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

if (byte & BIT6)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

//MSB
if (byte & BIT7)
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //'1' = line high
else
BIT_BANG_TX_PORT &= ~BIT_BANG_TX_BIT; //'0' = line low
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay

//Stop bit
BIT_BANG_TX_PORT |= BIT_BANG_TX_BIT; //let line go high
for (unsigned int i = 0; i < BIT_LENGTH_9600; i++) ; //104uSec delay
}

For receive, see The Joy of Bit-Banging, part 2: RX

Wednesday, June 3, 2009

PWM on a microcontroller, the Hard Way

On a project I had to implement nine PWM outputs for an LED dimming application. Up to eight is easy on an MSP430 - use TimerB outputs TB 1..6 and TimerA outputs TA1,2. But I needed nine. So, I had to do this the hard way, in code. Here's how I did it.

This example "fades up" an LED:
unsigned int onTime = 0; //periodsPerStep;
unsigned int offTime = 0; //PWM_PERIOD - onTime;

#define PWM_PERIOD 0xFF //period for PWM wavelength - 0xFF = 8kHz or so
#define CROSSFADE_STEPS 0xFF //how many intervals of intensity. 16 looks choppy
#define PERIODS_PER_STEP 0x3F//0x7F //how much time to wait at each intensity interval: 0xFF = 6 secs

unsigned int perStepInterval = PWM_PERIOD / CROSSFADE_STEPS;

for (int step = 0; step < CROSSFADE_STEPS; step++)
{
onTime = perStepInterval * step;
offTime = PWM_PERIOD - onTime;

//PWM periods:
for (int periodCounter = 0; periodCounter < PERIODS_PER_STEP; periodCounter++)
{
P1OUT = 0x01;
for (unsigned int counter = 0; counter < onTime; counter++) ; //on interval

P1OUT = 0x00;
for (unsigned int counter = 0; counter < offTime; counter++) ; //off interval
}
}

White Noise on a Microcontroller

I recently finished a project where I had to make a white noise generator using a microcontroller. It was a lot harder than I thought it would be. What didn't work: wavetable with 100-1000 random numbers. The human ear can detect aural patterns, and at an 8kHz sample rate, the 1000 number pattern repeats itself about 8 times per second which is noticeable. What does work: a Linear Feedback Shift Register. (Wikipedia) Using a 16 bit LFSR you can get a 16*16 = 65535 pseudorandom number sequence. In my tests the pattern could not be detected, which is perfect.

I implemented this on an MSP430F2618 using its internal DAC and the internal timer; though an MSP430F169 could work too. It uses TimerA0 to generate a constant period. When the timer interrupt occurs, the LFSR generates a new value and sends it out via the DAC.

Setting up the DAC:
void setupDac()
{
ADC12CTL0 = REF2_5V + REFON; // Internal 2.5V ref on
// Delay is 17mSec for caps to charge, [from code] 13600 at 1MHz, or 0x1A900 at 8MHz
// At 8000 cycles/Msec, need 0x21340 clock cycles to get 17mSec
// for safety, we'll delay a little longer, or 0x22000 clock cycles
#define SEVENTEEN_MS_AT_8MHZ 0x22000
for (long j = SEVENTEEN_MS_AT_8MHZ; j; j--); // Delay for needed ref start-up.
DAC12_1CTL = DAC12IR + DAC12AMP_5 + DAC12ENC + DAC12OPS; // Int ref gain 1, DAC12OPS = output select for DAC12_1 on P6.5
}
Setting up TimerA0
void setupTimerA()
{
TACCTL0 = CCIE; // TACCR0 interrupt enabled
TACCR0 = 200; //starting interval, gets changed at first interrupt
TACTL = TASSEL_2 + MC_2; // SMCLK, up mode
}
And finally, the TimerA0 interrupt service routine (ISR):
/*
* White Noise Generator
* Galois Linear Feedback Shift Register implementation
*/
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A (void)
{
TACCR0 += 0x3FF; // Timer interval, Add Offset to TACCR0
lsb = lfsr & 1; //Get lsb (i.e., the output bit).
lfsr >>= 1; //Shift register
if(lsb == 1) //Only apply toggle mask if output bit is 1.
lfsr ^= 0xB400u; //apply toggle mask, value has 1 at bits corresponding to taps, 0 else where.
DAC12_1DAT = lfsr;
}
That's it. In your main code, be sure that you enable interrupts, or else it won't work.

Saturday, May 30, 2009

Sound on the MSP430

Started playing with sound on the MSP430. I'm using an MSP430F2618 because it has an internal DAC, although a '169 or using an external DAC would work too. A couple of notes:
  • Output of DAC feeds a Sallen-Key filter which then drives a TI TPA721 300mW amplifier which drives a little toy speaker
  • White noise is harder than it sounds. After several tries, I ended up using a Linear Feedback Shift Register, specifically a Galois LFSR. See WikiPedia.
  • For sine waves, I made a wave table with 72 points (every 5 degrees from 0 to 355 degrees)
  • To get volume levels I ended up making ten wave tables, one for each 10% volume level. You could of course compute this on the fly but it took too many clock cycles for my implementation.
  • To prevent "zippering" at the start of the code I compute intermediate wave tables (5%, 15%, etc)
  • Learned the hard way that the "rail to rail" op amp ain't. Needed to recompute the wave tables with an offset to keep the bottom of the DAC output above the ~100mV floor of the op-amp.