Showing posts with label MSP430. Show all posts
Showing posts with label MSP430. Show all posts

Friday, September 14, 2012

The Joy of Bit-Banging, part 2: RX

I previously discussed how to implement the Tx side of a bit-bang serial port, in The Joy of Bit-Banging, part 1: TX. This time I'll describe how to implement the receive side. This implementation uses blocking waits, as a result it should not be used unless you can tolerate delays.

In this implementation the receive pin is P2.5

Required definitions, etc:
#include "msp430x24x.h"
#define BIT_BANG_RX_PORT P2IN
#define BIT_BANG_RX_BIT BIT5
#define DISABLE_BIT_BANG_RX_INTERRUPT() (P2IE &= ~BIT5)
#define ENABLE_BIT_BANG_RX_INTERRUPT() (P2IE |= BIT5)
//Timings calculated using DCO_8_MHZ - modify if different oscillator speed or different hardware
#define BIT_LENGTH_9600 160
#define HALF_BIT_LENGTH_9600 76
#define BIT_LENGTH_19200 75
#define HALF_BIT_LENGTH_19200 27

/** Simple delay for bit width. Used in transmit and receive. */
void delayFullBit() {for (unsigned int i = 0; i < BIT_LENGTH_19200; i++) ; } //104uSec delay

/** Simple delay for 1/2 of bit width. Used in receive. */
void delayHalfBit() {for (unsigned int i = 0; i < HALF_BIT_LENGTH_19200; i++) ; } //104uSec delay

#define NUMBER_OF_DATA_BITS 8 
#define TOGGLE_DEBUG_PIN() (P4OUT ^= BIT5)  //debugging only

Output function:
/**
* Blocking UART receiver. Called by BIT_BANG_RX_PORT Interrupt Service Routine upon detection of start bit (high to low transition).
* BIT_BANG_RX_BIT should be configured as high-low edge triggered interrupt.
* Receives a byte and then processes it.
* @note BIT_BANG_RX_PORT, BIT_BANG_RX_BIT, DISABLE_BIT_BANG_RX_INTERRUPT(), and ENABLE_BIT_BANG_RX_INTERRUPT() must be #defined. Modify accordingly for other hardware.
* @pre Oscillator configured for 8MHz
* @pre A high-to-low transition has occurred on BIT_BANG_RX_BIT
* @return the byte received
*/
char bitBangInput()
{
    unsigned char receivedBitIndex = 0;
    unsigned char receivedByte = 0;
   
    DISABLE_BIT_BANG_RX_INTERRUPT();
    delayHalfBit();     //Since this method was called on detection of an edge, need to wait until we're in the middle of the bit width.
   
    TOGGLE_DEBUG_PIN();
    //Read this start bit - should be zero
    if (BIT_BANG_RX_PORT & BIT_BANG_RX_BIT)
    {
        //error - re-enable interrupt and return  
    }

    for (int i =0; i    {
        delayFullBit();
        TOGGLE_DEBUG_PIN();
        receivedByte |= (BIT_BANG_RX_PORT & BIT_BANG_RX_BIT) ? (1 << receivedBitIndex) : 0;     //Sample the bit, and if '1' then increase receivedByte accordingly
        receivedBitIndex++;
    }
        delayFullBit();   
        TOGGLE_DEBUG_PIN();
    //THIS IS STOP BIT (line should be back high)
    if (!(BIT_BANG_RX_PORT & BIT_BANG_RX_BIT))
    {
     //error - line is low; should be high because this is stop bit  
    }
    //all bits have been received!
    ENABLE_BIT_BANG_RX_INTERRUPT();
    return receivedByte;
}



Thursday, September 13, 2012

Implementing I2C

Lately I've been doing a lot of I2C development. In just about every way you can do it. Over the course of a month I've had to write code to communicate with an EEPROM and two sensors via:
  • MSP430 using USCI
  • MSP430 using bit-bang
  • Stellaris using StellarisWare
  • Stellaris using bit-bang
Over the course of that experience I've gotten to know the I2C protocol very well. I won't go into the details of the protocol; there are plenty of sources for that.

When you're first getting started, the best way to verify the i2c interface is configured correctly is to not connect the peripheral, but just watch the i2c signals to see that they are being toggled correctly. By definition I2C is not a push-pull interface but uses open drain I/O. It's really quite elegant, as it prevents a bus conflict if one IC outputs a '1' and another outputs a '0'. As a result if you want to see anything you will need external pull-up resistors. I like to first develop code first without the peripheral ICs, just the pull-up resistors. This way you can connect a logic analyzer to the two signals and verify that your code is working correctly. This is especially important if you are implementing a bit-bang solution and need to check that the GPIOs are configured correctly.

If you are using a hardware based solution then it is fairly straightforward; configure the baud rate, etc. and the hardware does all the signaling. The hard part is determining how to use the various hardware registers to get the desired output. This is much easier if you're using StellarisWare, as it handles all the register settings for you.

If you're implementing a bit-bang solution, there are two ways of doing this. The first is with simple delays; the second is using a timer interrupt. I've done both. There both take about the same amount of time to implement. The first way is simpler but requires hand-tuning the amount of delay between bits to ensure the correct baud rate and a 50% duty cycle. Using a timer is a bit more elegant as you're not waiting the processor but requires a state machine to iterate through the various steps. If you're using a StellarisWare it includes a nifty little softI2c implementation that can be used decently easily.

The first test you should do is to just do a simple write of 4 bytes or so, and observe it on the logic analyzer. Of course the ACK bit will not be pulled down since there's no peripheral IC but you'll at least be able to observe proper timing and framing behavior. After you get that working also verify that reads work too.

After you get basic interfacing working, the next step is to write a simple address tester. This just writes to an address and checks to see if the write is acknowledged. This function is handy for verifying that the I2C peripheral is attached properly. This function can also be used for acknowledgment polling if you're implementing an EEPROM or FLASH interface. These types of memories take a few milliseconds to write and you must check that they are no longer in their write cycle before trying to access them again.

Once basic functionality is working you can implement the basic read/write routines. Write is easier since it's a single step operation. Reading typically requires two steps: First writing an address (e.g. register or memory address), then doing a repeat start, and then doing a read.

Since I2C has a fairly low bit rate (100kHz or 400 kHz under normal conditions) it can take awhile to write or read a lot of bytes (6mSec to read a full 64B page from an EEPROM at 100kHz). If you're using the I2C interface in a simple sensor it's fine to wait while communicating since you're not doing anything else until you receive the result. However, that would waste quite a few clock cycles; 160k if using a 25MHz clock. If you're using an RTOS or would otherwise like to minimize wait states then you'll want to implement it differently. In this case you should use DMA or at least an interrupt driven approach.

A few miscellaneous I2C hints:

Logic Analyzer
I2C is much, much easier to troubleshoot with a Logic Analyzer since it will parse the serial data stream and show you the I2C start, stops, and data. I really like one from Saleae, they're USB based and great for microcontroller use.

Mission Critical
I've had occasions whereby one of the peripheral ICs would get into a funky state and cause the I2C bus to lock up. If you're dealing with mission critical application or you just have an extra GPIO pin then I recommend controlling the power of each peripheral IC from the GPIO. That way you can "reboot" a peripheral IC if there is an issue.

Repeat Start
Several peripherals require you to implement a repeat start condition. This isn't well documented in the processor's documentation and you may need to do a bit of research to find out how to do it.

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 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

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.