Friday, August 5, 2011

Reading the supply voltage using the MSP430's internal Analog to Digital Converter

Many times you need to measure the supply voltage (Vdd) of the MSP430. The MSP430 has an ADC input channel which is connected to Vdd/2. The divide by two is required because the MSP430's internal reference is 2.5V, so Vdd must be divided by two for full resolution.

Two simple routines for reading the supply voltage of the MSP430 family are shown below. Some MSP430 devices use the 10-bit ADC, e.g. the MSP430F2274, and some use the 12-bit ADC, e.g. the MSP430F248. Refer to the datasheet for your part to determine which is used. 

Using the ADC10:
/** Reads the MSP430 supply voltage using the Analog to Digital Converter (ADC).
On ez430 boards, this is approx. 3600mV
@return Vcc supply voltage, in millivolts
*/
unsigned int getVcc3()
{
ADC10CTL0 = SREF_1 + REFON + REF2_5V + ADC10ON + ADC10SHT_3;  // use internal ref, turn on 2.5V ref, set samp time = 64 cycles
ADC10CTL1 = INCH_11;                        
delayMs(1);                                     // Allow internal reference to stabilize
ADC10CTL0 |= ENC + ADC10SC;                     // Enable conversions
while (!(ADC10CTL0 & ADC10IFG));                // Conversion done?
unsigned long temp = (ADC10MEM * 5000l);        // Convert raw ADC value to millivolts
return ((unsigned int) (temp / 1024l));
}

Using the ADC12:
/** Private helper method to setup ADC for one-shot conversion and read out value according to registers.
Inserts a delay before beginning conversion if REFON
@return the raw ADC value with the specified commands.
@todo move the VREF warmup to startup and leave on to avoid 17mSec blocking delay each time?
*/
unsigned int getAnalogInput(unsigned int adc12ctl0, unsigned int adc12ctl1, unsigned char adc12mctl0)
{
#define ADC_VREF_DELAY_MS 17   
ADC12CTL0 = adc12ctl0;
ADC12CTL1 = adc12ctl1;
ADC12MCTL0 = adc12mctl0;
if (adc12ctl0 & REFON)                    // if internal reference is used...
delayMs(ADC_VREF_DELAY_MS);           // 17mSec delay required to Vref capacitors
ADC12CTL0 |= ENC;                         // Enable conversions
ADC12CTL0 |= ADC12SC;                     // Start conversions
while (!(ADC12IFG & 0x01));               // Conversion done?
return ADC12MEM0;    // Read out 1st ADC value
}


/** Measures Vcc to the MSP430, nominally 3.3V
- ADC measures VCC/2 compared to 2.5V reference
- If Vcc = 3.3V, ADC output should be (1.65/2.5)*4095 = 2703
- Therefore (halfVcc/2.5)*4095 = ADC reading and (Vcc/2.5)*4095 = 2*ADC
- So Vcc*4096 = 5*ADC and VCC=5*ADC/4095

@return Vcc in millivolts
*/
unsigned int getVcc3()
{
unsigned int ctl0 = REFON + REF2_5V + ADC12ON + SHT0_15;  // turn on 2.5V ref, set samp time=1024 cycles
unsigned int ctl1 = SHP;                                  // Use sampling timer, internal ADC12OSC
unsigned char mctl0 = SREF_1 + INCH_11;                   // Channel A10, Vcc/2
unsigned long vcc = (unsigned long) getAnalogInput(ctl0, ctl1, mctl0);
unsigned long mult = vcc * 5000l;
return ((unsigned int)(mult / 4096l));
}

Saturday, March 12, 2011

Making Development Boards That Don't Suck

Lately I've had to use a few microcontroller development kits from manufacturers that, well, suck. From unlabeled connectors/components to not bringing out the unused pins, I'm convinced that many designers of development boards never actually use them to design anything.

When I'm trying out a new design, I frequently make my own development board. By "development board" I mean a schematic and layout that is the same circuit as the end product but optimized for firmware development and debugging. This allows a few things:
  • Firmware development can start sooner - this is probably the most important since FW typically takes an order of magnitude longer than HW design
  • Verify PCB footprints for components - you can spend a month checking and double checking a PCB footprint, or just build it and verify it on a real PCB in a week.
  • Power Measurement
  • Fast layout. Since the point of a development board is to optimize for development not layout, you can make things a little larger and not worry about squeezing everything in.
  • Related to the previous, a development board helps you get a feel for how small you can make the design once everything is working.

Here are the a few rules for how to design development boards that don't suck:
  • Expose all the pins of the components on test points or at least SMT pads. Soldering wires to fine pitch QFNs is a pain in the ass, and forget trying to do anything with BGAs. I recently helped out a major IC manufacturer and soldered some of their dev kits together because they didn't expose the unused pins.
  • Include a serial port (RS-232 is my preference).
  • Related to the above, bring out all signals that you can on easy 0.100" headers. This makes development much easier since you can attach logic analyzers, scopes, etc. Label them all with their signal name (more on this below).
  • Label liberally. Silkscreen is free, so on a development board label all components, as well as the signals on 0.100 headers, etc. Taking 5 minutes to label the signals on the PCB is faster than taking 5 minutes every time to look it up in documentation.
  • If it is a RF design with an onboard antenna, make an option for a connector. Can be SMA, U.FL or whatever, but something that you can connect to directly. I like to do this with a component in the signal chain that can be populated one way for the onboard antenna, or rotated 90 degrees to go to the connector instead.
  • Include a power connector and LDO to allow powering from a wall-wart. Makes it much easier to hand over to the firmware engineer and reduces unnecessary thought.
  • Enable power measurement: add a 0.100" header between LDO output and power input. This will have a shunt that can be removed and an ammeter attached instead. For extra credit, do this for the major power subsystems of the circuit.
  • Use full-sized JTAG connectors, and used shrouded (polarized) headers to prevent having to think about it.

Also, if this will be a commercially available development board (not just an intermediate step towards a final design) then there are a few more recommendations:
  • Use larger packages to make it easy to assemble and modify. I like to use 0805s for all the non-RF passives, and use the largest package I can get for the micro. And label everything!
  • If the board has onboard peripherals (LEDs, buttons, etc.) then make them able to be disabled (disableable?). My favorite way to do this is with "Cuttable Jumpers", basically an 0805 footprint with a trace connecting the two pads. That way a user can cut the trace to disable the peripheral but can also re-enable it by soldering on a zero-ohm resistor across the pads.
  • Be sure that you include firmware drivers for all the peripherals. You have to develop them to test the board anyway, might as well provide them.
The best development boards I've seen (besides the ones that I've designed of course) are the Stellaris development boards from Texas Instruments. Sadly, that company also makes some of the worst development boards too.

Thursday, June 3, 2010

Calibrating the MSP430 Very Low Power Oscillator

The MSP430F2xxx contains an internal 12kHz very low power low frequency oscillator (VLO). The frequency varies by part, temperature, and supply voltage. On an MSP430F248 the VLO runs at approx. 9.4kHz and on an MSP430F2274 it's approx. 12kHz.
On the MSP430F2274 datasheet (page 38) the base frequency varies between 4kHz-20kHz and has a temp drift of 0.5%/degC and a supply voltage drift of 4%/V.

So in order to make it useful we need to calibrate it. The method below will calibrate the VLO against the main oscillator. The accuracy of this calibration routine will only be as accurate as the main oscillator. If you're using one of the calibrated DCO frequencies (+/- 1%) then the end result will be the VLO within about 2% of actual. This will be more accurate if the main oscillator is sourced from a crystal.

This calibration routine uses Timer A in the capture mode to capture the number of main clock cycles between subsequent ACLK cycles. This routine only counts one pulse, for more consistency you may want to count multiple ACLK cycles and average them.


/** Calibrate VLO. Once this is done, the VLO can be used semi-accurately for timers etc.
Once calibrated, VLO is within ~2% of actual when using a 1% calibrated DCO frequency and temperature and supply voltage remain unchanged.
@return VLO frequency (number of VLO counts in 1sec)
@pre SMCLK is 4MHz
@pre MCLK is 8MHz
@pre ACLK sourced by VLO (BCSCTL3 = LFXT1S_2; in MSP430F2xxx)
@note Calibration is only as good as MCLK source. Obviously, if using the internal DCO (+/- 1%) then this value will only be as good as +/- 1%. YMMV.
@note On MSP430F248 or MSP430F22x2 or MSP430F22x4, must use TACCR2. On MSP430F20x2, must use TACCR0.
Check device-specific datasheet to see which module block has ACLK as a compare input.
For example, see page 23 of the MSP430F24x datasheet or page 17 of the MSP430F20x2 datasheet, or page 18 of the MSP430F22x4 datasheet.
@note If application will require accuracy over change in temperature or supply voltage, recommend calibrating VLO more often.
@post Timer A settings changed
@post ACLK divide by 8 bit cleared
*/
unsigned int calibrateVlo()
{
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
delayMs(1000);

BCSCTL1 |= DIVA_3; // Divide ACLK by 8
TACCTL2 = CM_1 + CCIS_1 + CAP; // Capture on ACLK
TACTL = TASSEL_2 + MC_2 + TACLR; // Start TA, SMCLK(DCO), Continuous
while ((TACCTL0 & CCIFG) == 0); // Wait until capture

TACCR2 = 0; // Ignore first capture
TACCTL2 &= ~CCIFG; // Clear CCIFG

while ((TACCTL2 & CCIFG) == 0); // Wait for next capture
unsigned int firstCapture = TACCR2; // Save first capture
TACCTL2 &= ~CCIFG; // Clear CCIFG

while ((TACCTL2 & CCIFG) ==0); // Wait for next capture

unsigned long counts = (TACCR2 - firstCapture); // # of VLO clocks in 8Mhz
BCSCTL1 &= ~DIVA_3; // Clear ACLK/8 settings

vloFrequency = ((unsigned int) (32000000l / counts));
return vloFrequency;
}

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.