Showing posts with label microcontroller. Show all posts
Showing posts with label microcontroller. Show all posts

Thursday, May 8, 2014

The Main Event - A Simple Event Handler for Microcontrollers

Every application has some type of event handler. Typically, an interrupt will occur, and the interrupt service routine (ISR) will then set a flag, and then the ISR will exit to return to the normal application. This approach is used to minimize the time spent in the ISR. Next, in the main application, the event flags are read, and if any were set then the application processes each event.

I've been looking through the Anaren Bluetooth Low Energy software lately, and I saw this rather cool event handler. It uses a table of function pointers that are the functions to be called when the event occurs.

The Event Handler Functions

We will create a typedef for our function pointer. All functions in our Event Handler Table need to be this type.
/* Typedef for the event handler functions that will go into our table */
typedef void (*Hal_Handler)(void);

Next, for convenience we also create a type for the event flag bitmask. For speed, make this the same as the processor's data size.
/* The type of integer that we will use for our event bitflag. uint32 = 32 possible events */
typedef uint32_t EventFlags;

We also create the global variable that will be the bitflag of any events that get set.
/* Bitflag of the events that need to be processed */
static volatile EventFlags handlerEvents = 0;

To complete the initialization, we define a few events, and create our event handler table.
#define NUM_HANDLERS 3
#define BUTTON_HANDLER_ID      0
#define TICK_HANDLER_ID        1
#define DISPATCH_HANDLER_ID    2

#define KNOB_INCREMENT_HANDLER_ID  3
#define KNOB_DECREMENT_HANDLER_ID  4

/* Table of handler function pointers */
static Event_Handler handlerTable[NUM_HANDLERS];

This table will contain function pointers for the function that gets called when the corresponding event gets called. For example, when the knob gets rotated clockwise, its ISR will set bit #3 in the handlerEvents function. Then the main loop will call the function that is located at handlerTable[3].

We initialize our list by adding event handlers to the event handler table using the method below:
 int addEventHandler(uint8_t index, Event_Handler handler)
 {
     if (index > NUM_HANDLERS)
     {
         return -1;
     }
     handlerTable[index] = handler;
     return 0;
 }

We then call this in our main() function, which we'll explain in a bit.

Set a flag when an event occurs

When an event happens, in the ISR we need to set the bitflag that corresponds to that event. This is done in the postEvent() method as shown below. This bitflag will be read later in our main event handling loop. For safety, we disable interrupts while we are modifying the handlerEvents variable.
/** Set the corresponding bitflag for the eventId */
void postEvent(uint8_t handlerId)
{
    //Note: you should check that handlerId < NUM_HANDLERS here
    IntMasterDisable();
    handlerEvents |= 1 << handlerId;
    IntMasterEnable();
}

In the ISRs, you will need to add a call to postEvent. For example, in a button ISR, add:
postEvent(BUTTON_HANDLER_ID);

Configuring The Actions

When the bitflag for an event gets set, we want the event handler to do something. It does so by calling a function pointer that points to the function that we want to get called when that event happens. For example, when a button is pressed we just want to tell the user. Same for knob, as shown below:
void buttonHandler(void)
{
    printf("Button Pressed!\r\n");
}
void knobIncrementHandler(void)
{
    printf("Knob Up!\r\n");
}
void knobDecrementHandler(void)
{
    printf("Knob Down!\r\n");
}

Now, we need these in the Event Table so in main() we add them with the function we created earlier:

    addEventHandler(BUTTON_HANDLER_ID, buttonHandler);
    addEventHandler(KNOB_INCREMENT_HANDLER_ID, knobIncrementHandler);
    addEventHandler(KNOB_DECREMENT_HANDLER_ID, knobDecrementHandler);

Now we've configured the event setting, and the event processing, so now we can implement the main event handler.

The Main Event (handler)

The Main Event Handler is fairly simple. It continually loops, and processes any events that were set by postEvent(). If a bitflag was set, then it calls the corresponding function pointer.
void eventHandler(void)
{
    IntMasterEnable();
    for (;;)
    {
        /* First, disable interrupts so we don't get messed up while we are reading events */
        IntMasterDisable();

        /* While interrupts are disabled, copy the current list of events that need to be processed */
        EventFlags events = handlerEvents;

        /* ... and clear the master bitflag of events */
        handlerEvents = 0;

        /* Now, process any events that need to be processed */
        if (events)
        {   // dispatch all current events
            IntMasterEnable();     // Enable interrupts - note that we do this before calling each handler function
            /* mask will be the bitflag that we use to check the events bitflag. Starts at 0x1, then 0x2, 0x4, 0x8 etc. */
            uint16_t mask;
            /* The numeric index of the event - 0 through 31 */
            uint8_t id;
            /** Iterate through all possible events */
            for (id = 0, mask = 0x1; id < NUM_HANDLERS; id++, mask <<= 1)
            {
                if ((events & mask) && handlerTable[id]) // If we need to process the event
                {                                      // AND there is an event handler for it
                    handlerTable[id]();                  // ... Then process the event
                }
            }
        } else {          // No events to handle, so wait for more
            /* If using a battery powered device, you could go back to sleep here. Just be sure to enable interrupts! */
            IntMasterEnable();        // Enable Interrupts
        }
    }
}





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

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