0

Busy Indicator

Posted by Keith on Apr 14, 2010 in Uncategorized

It is an interesting aspect of human nature that we can’t stand to be ignored, particularly by a machine.  I doubt we as a species are particularly sensitive to a snub by our own creations, rather I think we just don’t want to look stupid waiting on a machine that has gone off into the weeds.  For what ever reason, our response as designers has been to create a Busy Indicator.  Whether it is an animation of an hour class, a completion bar graphs, or just a blinking cursor, this indicator tells the user to ‘hold on I will be finished in just a moment.’

Of course it also serves the equally important function of telling the user when the system has ‘hung’ as well.  The challenge in simple user interfaces is how to implement the busy indicator using the limited bandwidth at hand.

One option for 7-segment displays is to blink the display, or pulse the intensity.  This is relatively easy to implement in a scanned system, just add in some non-driven scans into the display scanning routine.  Holding non-driven scans for 0.5 to 1 second will blank the display, non-driven scans every other scan will drop the intensity of the display in half.  Then it is just a matter of turning the blank scan function on/off at a 1-2 Hz rate, resulting in a variable intensity display or a flashing one.  Both are relatively simple in that it only requires a few lines of code, but they also effectively demonstrating that the system is still running.

Another option is to use an animation to demonstrate that the system is busy.  One that I implemented recently uses 7 segment displays to indicate that the finish order for cars in a PineWood Derby race.  While the cars are moving, and the display has nothing to display, the displays simulate the wheels on the car by spinning around 1 lit segment.  See below for a visual of the sequence.

4-14-2010 2-25-17 PM

Another variation on the system uses the same single lit segment, but it drives the pattern in a figure 8.

4-14-2010 3-16-27 PM

Both are relatively easy to implement, you just create a few extra segment combinations in your 7 segment decoder, and then cycle through the decoder at a 5-6 Hz rate.  In my implementation, I put a counter on the system that increments the display value very 10-15 scans.  The result was actually very easy on the eyes, and tied in well with the race theme of the system.  It also let the spectators know that ‘Yes, the system is running’, and yes it correctly detected the start of the race.

 
0

Non-Linear functions

Posted by Keith on Jan 20, 2010 in Uncategorized

One of the great ironies of engineering is that we try to control a non-linear world with a linear circuitry.  Entire text books have been written just on the subject of linearization of sensors, and until recently, this linearization usually entailed complex op-amp circuits and tedious/expensive calibration techniques in manufacturing.

However, now that we are firmly entrenched in the age of software; this traditionally hardware based problem has become a new headache for programmers.  So, how do we handle non-linearity in software?  Well, there are two common solutions; piecewise linear approximations, and curve fitting.

Piecewise linear approximati ons take the non-linear curve and break it into multiple segments that can be represented by a straight line.  For example, you can approximate a sine function with a set of equations like the following;

for-blog

Now the approximation is not very good, but it does fit the general outline of a sine wave.    In addition, their can be significant discontinuities where the segments meet, which is all dependant upon the number of segments, or pieces, and how closely the linear segments approximates the function over their range.

Curve fitting is the process of finding a equation that fits the non-linear function over a restricted range of values, for example, an approximation of the same sine wave, for the range 0-180 degrees, is the following function;

Y = -0.00808+(0.0217*X)-(0.000121*X2)

The new function is closer, but even with all the floating point math, it still has an error of 3-4% over the range of the function, and the more cyclic the function, the harder it will become to find one function that fits the entire range.

Usually, for periodic functions, the function is approximated over its period, or some segment of its period and the result is applied like the linear segments in piece wise linear approximations.  That is, for 0-180, the function is used as is; for 180-360, we subtract 180 from the input, and multiply the output by -1.

Regardless of the method used, it is important to determine the overall system error, over the range that the system will be used.  Spread sheets are particularly handy for this, just enter a column of reasonable data in the first column, copy your function into the second column, and calculate the error in the third column.  If the error is more than you can live with, then increase the number of pieces in the approximation, or increase the order of the curve fit. 

Input

Ideal

Curve Fit

Error

 

PWL

Error

0

0.0000

-0.00808

1%

 

0

0%

30

0.5000

0.53402

-3%

 

0.333333

17%

60

0.8660

0.85832

1%

 

0.666667

20%

90

1.0000

0.96482

4%

 

1

0%

120

0.8660

0.85352

1%

 

0.666667

20%

150

0.5000

0.52442

-2%

 

0.333333

17%

180

0.0000

-0.02248

2%

 

0

0%

 
0

Inductive Touch Demonstration

Posted by admin on Jan 14, 2010 in Uncategorized

Check out this new video!

Inductive Touch Demonstration

Tags: ,

 
0

K.I.S.S.

Posted by admin on Dec 1, 2009 in Uncategorized

The other day I was playing with the idea of building an iambic keyer, using the new PIC16F1827 from Microchip. My dad (W7GP) enjoys making code keys and I have a friend up in Colorado that still uses Morse on the air. Besides, I had promised myself that I would relearn the code and actually use it on the air (N7KGC). In addition, the PIC16F1827 was a perfect choice for the project due to a wealth of CCPs and a new modulation peripheral.

I was completely caught up in the wild enthusiasm of a new project; I had figured out the timing, allocated the necessary pins and resources for the tone generation, and was considering what kind of user interface when I hit a brick wall over the question of how to handle code speed, dit-to-dah weighting, and tone volume.

All three functions had typically been handled by a potentiometers, they gave the user a simple interface that was intuitive and simple to handle while actually using the keyer. In fact, all three functions were typically adjusted while generating dits and dahs until the rate, volume, and weighting “felt right.”

The problem was, in the back of my mind, I was stuck on the misconception that because the keyer was digital, the user interface also had to be digital. And, try as I might, I couldn’t come up with a simple system that would work as well as the old fashioned pots.

A menu driven user interface with UP and DOWN buttons was going to be too slow, and would require the user to look at the display. Touch button sliders would work, but if I accidentally touched one, I could mess up their settings and I would need some kind of Braille guide to keep my fingers aligned on the slider. Encoders would also work, but the cost of both the encoders and the code space required to decode them would not be worth it.

It was about this time, that I suddenly saw the light, and promptly gave my self a swift kick in the cerebellum for being dense. The answer was the KISS principal, Keep-It-Simple-Stupid. The potentiometers had the right feel, they retained their setting when the power was removed, and they provided a simple visual feedback and they were simple to interface. All I needed to do was digitize the wiper voltage with an ADC channel, and I would have my simple, cheep, and intuitive user interface to my digital keyer.

So, I setup a Timer0 interrupt for 4.096mS, and dropped a simple statemachine into the ISR to periodically sample the three ADC inputs. The results were stuffed into the appropriate variables, and whiz-bang, I had the perfect interface. I could even adjust the offset and slope in code to provide convenient values.

No complex commands to decode, no expensive graphics LCD module to show a virtual knob, and I even saved pins. Keeping It simple not only saved resources, it also saved about 20 pages worth of user’s manual explaining how to set the rate, weighting, and volume of the keyer.

Of course their was one annoying intermittent problem. Every once in a while I would get a glitch on the pot I was adjusting. I the value would momentarily jump by ¼ or ½ of full scale. After several hours of hair pulling and 2 attempts to digitally filter the values, it finally dawned on me, I was using the values out of the variables at exactly the same time that the ISR was updating the values. The result was a 01FF when the values rolled over from 0FF to 100, or a 000 output when I rolled over 100 to 0FF. The solution was to simple; turn off the interrupts, copy the values into a separate set of variables, and then turn the interrupts back on. Basically, I created the software equivalent of double buffering.

While the final result did take some extra storage space and required a small increase in code size, it was still far smaller than any of the other alternatives and a lot more convenient to use.

 
0

Sensor Degradation

Posted by Cassie on Nov 3, 2009 in Uncategorized

It’s a snowy night, and you’re about to use the ATM outside your local grocery store that’s been there for years. You enter in your access code, and notice only 2 of the 4 entries are reflected on the display.

You clear the entry, and strike each number on the monitor again. Again the entry seems off.

Now your card is eaten up in that darned machine.

The touch screen doesn’t work.

What is really happening is that the touch screen isn’t working as well as it used to. Several factors can lead to sensor degradation. This sensor degradation, when not accounted for, can push the operating specifications of your touch controller and sensor pair out of functioning range.

Resistive touch panels are made of two sheets of conductive material. This conductive material needs to be uniformly coated on both sides. When you press a resistive panel, one side smushes into the other side. The bending of the flexible layer, referred to as the “flex” layer which is often made of polyester, into the glass or plastic layer, the “stable” layer, is what makes the resistive touch solution work and move your cursor. The conductive coating afixed to the flex layer is bent and stretched when the polyester layer comes in contact with a user’s finger or stylus. The metallic conductive coating does not have the same flexible properties as this polyester layer, and can stretch and crack with repeated use.

The humidity and the temperature of the environment also affects the wear and tear seen by the resistive panel. The air trapped inside the resistive panel is maintained by adding a layer, often called spacer dots, between the flex and the stable. Typically, this is maintained when the environment remains static. When the temperature rises or falls dramatically, the air within the sensor will expand or contract, stretching the polyester layer again. Thusly, the uniform coating becomes less uniform, and the sensor’s electrical properties are again compromised. The resistance of the panel will increase as the coating cracks, and the capacitance between the two conductive layers will increase as the polyester relaxes.”

While there are coatings and heaters and special manufacturing precautions to add to the longevity of the sensor, the sensor will eventually succumb to wear and tear and stop functioning. It’s up to the touch controller to be designed with a wide window of operation so that it will continue to function for as long as possible.

Tags:

 
0

Parsing serial text based commands

Posted by Carol on Oct 27, 2009 in Uncategorized

One of the fun tasks I have at work is creating fun lab projects for teaching classes.  In this case, the class was on basic embedded programming and the object was to show how serial input command strings are parsed into their basic components.  My pseudo project was a coffee maker, and the command set was relatively simple;

 

T 12:00                        sets the real time clock to 12:00

A 1:00                         sets the start time for the coffee maker to 1:00

M                                             turns on the timer

m                                             turns off the timer

?                                                          returns the time, start time, and current temperature of the coffee

 

Now, while the command set looks relatively simple, I also had to make it reasonably fool proof to use as well.  So;

1.                  I had to be able to accept both upper and lower case for the time and alarm commands.

2.                  I had to accept both single and double digit input for the hours.

3.                  I had to accept both a ‘ ‘ or a ‘:’ as a data delimiter.

4.                  And, I had to accept a cr as a delimiter and the termination of the command string.

 

So, I started out by creating a set a of variables to hold the parsed data;

 

A CHAR to hold the command character.  Note: mt_char is a default character.

      cmd_char   = mt_char;

 

And a 4 data variables for the individual nibbles of the hours and minutes

      data_var1a = 0;

      data_var2a = 0;

      data_var1b = 0;

      data_var2b = 0;

 

I then created an init routine to preset the variables to their default values.  Note, I use this both at startup, and after I decode the commands, to preset the variables for the next command string.

 

/******************************************************************************

* Function: void pars_init(void)

*

* Overview: This function configures the parser for operation

*

* Input:    none

*

* Output:   none

*

******************************************************************************/

void pars_init(void)

{

       cmd_char   = mt_char;

       data_var1a = 0;

       data_var2a = 0;

       data_var1b = 0;

       data_var2b = 0;

       mode       = cmd;

       pars_done  = 0;

       pars_falt  = 0;

}     

 

Note: resetting the variables to zero also provides any leading zeros required for single digit entries.

 

Next, I created my parser routine in two halves; the first which accepts the individual characters from the serial input and parses the data into blocks, and a second which executes the actual command.

 

The first section is implemented as a statemachine with 4 states, CMD for command, VAR1 for the first data variables, VAR2 for the second data variable, and DEFAULT which is a general error handler. 

 

In the CMD state, any character that is not a number or a delimiter is stored in the cmd_char variable.  If the character is a ‘ ‘, then the statemachine is advanced to the VAR1 state.  If the character is a cr, then the parser is done and the command can be decoded by the second half of the routine.  If any other character is detected than a pars_falt is set indicating an invalid command.

 

In the VAR1 state, any number is stored in the data_var1a, after the contents of data_var1a has been shifted into data_var1b.  This shift register arrangement allows us to either enter 1 or 2 values because both variables were cleared prior to the start of the command, leaving a leading zero for any single value entry.  If the character is a ‘:’, then the statemachine advances to the state VAR2 for a second value entry.  If the character is a cr, then the parser is done and the command can be decoded by the second half of the routine.  If any other character is detected than a pars_falt is set indicating an invalid command.

 

The VAR2 state operates nearly identically to VAR1, with the exception that the only delimiter accepted is the cr to terminate the command.

 

The only function of the default state is to set the fault flag so the second half of the routine can reset the statemachine to its default condition.

 

When the first half of the function is complete, and the  pars_done variable is set, then the 5 data variables (            cmd_char, data_var1a, data_var2a, data_var1b, & data_var2b) should have legal values in them, either from data received from the serial port, or default data loaded during the initialization function.

 

/******************************************************************************

* Function: void parser(void)

*

* Overview: This function disassembles serial command strings

*

* Input:    none

*

* Output:   values

*

******************************************************************************/

void parser(void)

{

       switch(mode)

       {

              Case cmd:     if (((hold >= ‘a’)&(hold <= ‘z’)) | ((hold >= ‘A’)&(hold <= ‘Z’)) | (hold == ‘?’))

                                         {

                                                cmd_char = hold;                         // a-z or A-Z or ? detected store as command

                                          }     

                     else if (hold == ‘ ‘)

                     {

                           mode = var1;                             // ‘ ‘ delimiter detected goto next field of data

                     }

                     else if (hold == CR)

                     {

                           pars_done  = 1;                          // CR detected terminate parser and decode command

                     }

                     else

                     {     

                           pars_falt  = 1;                          // no clue send a ? and restart

                     }

                     break;

              case var1:    if ((hold >= ‘0′) && (hold <= ‘9′))

                                         {

                                                data_var1b = data_var1a;   // 0-9 detected store as first data

                                                data_var1a = hold – ‘0′;

                                         }     

                                         else if (hold == ‘:’)

                                          {

                                                 mode = var2;                             // ‘:’ delimiter detected goto next field of data

                                          }

                                          else if (hold == CR)

                                          {

                                                pars_done  = 1;                                 // CR detected terminate parser and decode command

                                          }

                                         else

                                          {     

                                                pars_falt  = 1;                                 // no clue send a ? and restart

                                         }     

                                         break;

              case var2:    if ((hold >= ‘0′) && (hold <= ‘9′))

                                         {     

                                                data_var2b = data_var2a;          // 0-9 detected store as second data

                                                data_var2a = hold – ‘0′;

                                         }

                                         else if (hold == CR)

                                         {

                                                pars_done  = 1;                                 // CR detected terminate parser and decode command

                                         }

                                         else

                                          {     

                                                pars_falt  = 1;                                 // no clue send a ? and restart

                                          }     

                                          break;

              default:             pars_falt = 1;                                                // definitely lost, send a ?

                                         break;

       }                         

 

Once the data is parsed into its separate elements, it is a simple matter to decode the command and routine the new data into the appropriate variables.  In this case; rtc_hours, rtc_mins, alrm_hours, alrm_min, & alrm_on.  Based on the value in cmd_char, a simple switch statement is all that is needed to routine the incoming data to the appropriate variables.  Once the command decode is complete, then the routine is reset by calling the initialization routine to clear the variables and set them to their default values.  In the event that their was a communications fault during the parse or decode operation, then the data is discarded and the system is reset using the initialization routine.

 

       if (pars_done == 1)                                                                // if CR detected, then decode the command

       {

              switch(cmd_char)

              {

                     case ‘T’:                                                                          // time command uses both data blocks

                     case ‘t’:     rtc_hours = (data_var1b * 10) + data_var1a;

                                                rtc_mins  = (data_var2b * 10) + data_var2a;

                                                break;

                     case ‘A’:                                                                          // alarm set command uses both data blocks

                     case ‘a’:     alrm_hours = (data_var1b * 10) + data_var1a;

                                                alrm_mins  = (data_var2b * 10) + data_var2a;

                                                break;

                     case ‘M’:     alarm_on = 1;

                                                break;

                     case ‘m’:     alarm_on = 0;

                                                break;

                     case ‘?’:     send_all_data();                                // ? sends the time, alarm, and temperature

                                                break;

              }     

              pars_init();

       }     

       if (pars_falt == 1)

       {

              Tx_send(’?');                                                                     // send ‘huh?’ message to user

              Tx_save(CR);

              Tx_save(LF);

              pars_init();

       }     

}

Tags: ,

 
0

Converting between binary and decimal in assembly

Posted by Keith on Oct 22, 2009 in Uncategorized

One of the interesting challenges in working in assembly, versus C, is converting between BCD and binary. We all know that processors work in binary and users work in C, right? The problem is, even thought some processors include BCD math capabilities, things like multiplication, division, and all the system timers, work in binary.

So, one of the long standing problems, is how do we efficiently convert between base 2 and base 10. Well, a couple of years ago, I was faced with this problem, and I came up with a fairly efficient conversion algorithm that works well in both directions.

The routines are based on the same basic principal as a multiply routine. For example, let’s convert an 8-bit binary number to BCD.

8-bit binary value 10110101

First, we build up a constant table for each bit location;

0 0 1

0 0 2

0 0 4

0 0 8

0 1 6

0 3 2

0 6 4

1 2 8

Then we create a set of BCD output variables and clear them

BCD100 BCD10 BCD1

Next, we shift through the binary value, and for every 1, we add the corresponding value from the table, using BCD addition;

8-bit binary value 10110101

0 0 1

0 0 4

0 1 6

0 3 2

+ 1 2 8

—————————————

1 8 1

To go in the other direction, we just use a binary array of constants, and add using binary addition

BCD value 181

Binary constants

00000001 1

00000010 2

00000100 4

00001000 8

00001010 10

00010100 20

00101000 40

01010000 80

01100100 100

11001000 200

And the conversion looks like this;

BCD value 181

01100100 100

01010000 80

+ 00000001 1

———————————-

10110101

Of course, this technique can be expanded to any number of bits; all you need to do is expand the constant table to include all the bit values. Attached to the end of this blog are listings for both binary to decimal and decimal to binary for the PIC16FXXX family of parts;

;************************************************************************

; Converts a 5 digit BCD to 16-bit number in H_byte:L_byte

; Limitation: 5-digit bcd must be less than 2^17 (= 65536) in order

; to fit into the 16-bit result. Else result is remainder.

;

; R2H R2L R1H R1L R0H R0L

; - dig5 dig4 dig3 dig2 dig1

BCD5BIN16:

clrf H_byte

movf R2, W

andlw 0×0F

movwf L_byte

call mpy10a ; result = 10a+b

swapf R1, W

call mpy10b ; result = 10[10a+b]

movf R1, W

call mpy10b ; result = 10[10[10a+b]+c]

swapf R0, W

call mpy10b ; result = 10[10[10[10a+b]+c]+d]

movf R0, W

andlw 0×0F

addwf L_byte, F

btfsc STATUS, C

incf H_byte, F ; result = 10[10[10[10a+b]+c]+d]+e

return ; BCD to binary conversion done

mpy10b:

andlw 0×0F

addwf L_byte, F

btfsc STATUS, C

incf H_byte, F

mpy10a:

bcf STATUS, C ; multiply by 2

rlf L_byte, W

movwf L_temp

rlf H_byte, W ; (H_temp,L_H_temp) = 2*N

movwf H_temp

bcf STATUS, C ; multiply by 2

rlf L_byte, F

rlf H_byte, F

bcf STATUS, C ; multiply by 2

rlf L_byte, F

rlf H_byte, F

bcf STATUS, C ; multiply by 2

rlf L_byte, F

rlf H_byte, F ; (H_byte,L_byte) = 8*N

movf L_temp, W

addwf L_byte, F

btfsc STATUS, C

incf H_byte, F

movf H_temp, W

addwf H_byte, F

return ; (H_byte,L_byte) = 10*N

;*********************************************************************

;———————————————————————

; Bin16BCD

; Converts 16-bit binary number (H_byte:L_byte) into a 5 digit

; BCD number in 5 nibbles of registers R2, R1, R0 as shown.

;

; R2H R2L R1H R1L R0H R0L

; - dig5 dig4 dig3 dig2 dig1

;———————————————————————

Bin16BCD:

bcf STATUS,C ; clear the carry bit in STATUS

movlw .16

movwf temp+1

clrf R0

clrf R1

clrf R2

b16b_loop16:

rlf L_byte, F

rlf H_byte, F

rlf R0, F

rlf R1, F

rlf R2, F

decfsz temp+1, F

goto adjDEC

return

adjDEC:

movlw R0

movwf FSR

call adjBCD

movlw R1

movwf FSR

call adjBCD

movlw R2

movwf FSR

call adjBCD

goto b16b_loop16

adjBCD:

movlw 0×03

addwf INDF,W

movwf temp

btfsc temp,3 ; test if result > 7

movwf INDF

movlw 0×30

addwf INDF,W

movwf temp

btfsc temp,7 ; test if result > 7

movwf INDF ; save as MSD

return

Tags: ,

 
0

24-hr Microchip Technology giveaway for Halloween

Posted by Carol on Oct 8, 2009 in Uncategorized

Makezine

 
1

The Difference Between Period Versus Frequency Measurement In Cap Touch

Posted by admin on Aug 31, 2009 in Uncategorized

In a relaxation oscillator based capacitive touch system, the capacitance shift due to the user’s touch results in a frequency shift in the output of the relaxation oscillator. The shift is converted into a digital value is one of two ways; either the period of the oscillation is measured, or the frequency of the oscillation is measured. Both use on-chip timer peripherals, and both can generate equivalent resolutions, but there are some significant differences.
Let’s start by examining both systems;
Period measurement uses the output of the relaxation oscillator as a clock source for the first timer in the system. A second 16-bit timer is clocked by a high speed clock source, and gated by the output from the first timer. The first timer counts off a preset number of cycles from the relaxation oscillator, and the second timer counts the number of high frequency clock cycles that occur during the first counters timeout.
For example, let’s assume that the relaxation oscillator is running around 200kHz. Further, let’s assume that the second 16-bit timer is running from a 1MHz clock source. So, to get .1% resolution on the period, the second timer will have to count at least 1000 cycles, and that means that the second timer will have to count down 200 counts of the 200 kHz clock source. 200 counts from a 200 kHz source takes 1 milli-second, and during 1 milli-second, the second counter running from 1 MHz will count 1000 counts.
The frequency based system uses the output of the relaxation oscillator as a clock source for the second 16-bit timer and a fixed clock source for the first timer. In this case, .1% resolution requires a count of 1000 clock cycles by the second timer. That requires 5 milli-seconds and the first timer will have to be configured to provide a timeout for the 5 milli-seconds sample period.
As you can see, the period measurement system has some pretty significant advantages for moderate to low frequency oscillators, 1 milli-second versus 5 milli-seconds to determine a value of the same resolution. While this may seem like a clear cut advantage, there are some tradeoffs involved; One, the period measurement system requires a high frequency clock to drive the second 16-bit timer, while the frequency measurement system only requires a 5 milli-second gating signal to make its measurement. Two, the size of the timer driven by the relaxation oscillator output will affect the amount of noise energy passed through the timer to the final digital output value.
The clock speed required is an important issue, if the current consumption of the system is to be minimized. The period measurement system requires a 1 MHz, or higher, clock to provide a good resolution at the higher conversion rate. The frequency measurement system only requires a 200Hz clock (5 milli-second). If the 200 Hz signal is generated by the system Watch Dog Timer (WDT), then the current consumption, for just the first timer, will be 2-3 orders a magnitude lower just because the clock is significantly lower. In addition, using the WDT allows the system to perform frequency measurements while the system is in sleep mode.
The noise attenuation of a timer is due to the averaging nature of the timer. If a clock has edge jitter, dividing the clock by 2 will retain the same absolute jitter, but the period of the clock is 2x the original, so the overall percentage phase noise, is ½. In the period measurement system the devisor is 200 and in the frequency measurement system the devisor is 1000, so the frequency measurement system has 5 times more averaging which will attenuate phase noise by 5:1 over the period measurement system.
This gives us the basic trade off between the two systems;
1. Period measurement is faster, for the same resolution.
2. Frequency measurement has better noise filtering characteristics, and it is significantly lower power.
Choosing between the two methods is then determined by the requirements of the system, either low power/noise or higher scan rated for more responsive buttons.

 
0

Push button inputs on low power.

Posted by Keith on May 7, 2009 in Uncategorized

In today’s power lean designs, the priority is to minimize current consumption without sacrificing performance, especially in the user interface where it would be most noticeable.

OK, so how do we minimize current while remaining responsive to the keyboard?
1. Reduce the clock frequency.
2. Put the part to sleep, basically set the clock to zero Hz.
3. And minimize, or hopefully eliminate, any external current sinks.

Let’s start with the reducing the clock, if we reduce the clock speed of the microcontroller when the system is idle, then we can see a significant reduction in the current consumption. Cutting the clock frequency in half will basically reduce the current of the microcontroller in half (barring current draw for reference voltage generators and any GPIO drive for external circuitry). Further more, when the system becomes active, we can return the clock to its normal frequency and restore all the processing power normally available to the system.

Alright, that reduces the current while maintaining the operation of the processor to monitor the buttons. However, we will need to speed up the button scanning rate to maintain the same responsiveness of the buttons.

However, what if we put the processor to sleep? That produces a very dramatic reduction in current, but it also shuts down the processor so we won’t be able to pole the button inputs. True, but there is a way around the limitation, we can use the interrupt on change or IOC function typically built into one or more of the GPIO ports.

The IOC latches the state of the inputs every time the microcontroller reads the port. If the input state of the GPIO ever changes, the miss-match triggers an interrupt, which wakes the microcontroller. So, if we connect our buttons such that they pull one of the pins on the port with IOC either high or low, we can wake up every time the user presses a button. The only caveat is that we have to make sure the button is completely released before we go back to sleep, or any bounce will wake the microcontroller again.

OK, that works for up to 8 buttons, but what if we have a larger group of keys. The current crop of microcontrollers typically has only one 8 bit port with IOC so we can only do a wake on IOC on the 8 buttons tied to the IOC port.

Well, that’s not entirely true, there is a way to do a wake on more buttons, we just have to setup the buttons in a matrix. Yes, that’s right, a matrix. We connect either the rows or columns to the port with IOC, and drive the other with a separate port configured as outputs. Normally, we scan the matrix of buttons by pulling each output low, one at a time, and look for a corresponding low on the inputs. But if the microcontroller is asleep, there is nothing to pull each line low individually.

So, what we do is pull all of the outputs low, read the IOC port, and put the microcontroller to sleep. Now if any of the buttons are pressed, the attached low output pint will pull the corresponding input on the IOC port low, which wakes the microcontroller, which then scans the matrix normally, and identifies which button was actually pressed. After the button is released, and the appropriate action is taken, the microcontroller can be put back to sleep and the current goes back into the uA range once more.

OK, so we can wake on individual button presses, and we can wake if the buttons are arrayed in a matrix, but what about inputs whice are toggle or slide switches? They can remain closed for a very long time, and if we use a pull up resistor on the input pin of the GPIO port, that will burn current continuously.

Fortunately, there is another new feature, programmable Weak Pull Ups, or WPUs. The WPUs are basically pull up resistors that can be turned on and off under software control. Now if an input is low, we can turn off the WPU and save the current draw. Then periodically turn the WPU back on and check the actual state of the pin, before turning it back off. The only problem is that we can’t generate an IOC, so we will have to have a periodic wake up call from a timer or the watch dog timer so the microcontroller can poll the state of the pin.

So, there you have it, a means of exploiting the low current consumption of the sleep mode, without loosing the ability to quickly respond to a button press. All you need is the IOC function on a GPIO port and you can wakeup and read the state of the button, or wake up and scan a matrix of keys. Further, if we have more permanent low inputs, we can even turn on/off the WPUs on the inputs so we don’t burn excess current on a steady state input.

Copyright © 2010 Notes from the Lab All rights reserved. Theme by Laptop Geek.