Sei sulla pagina 1di 53

Advanced C

Programming
Real Time Programming
Like the Pros

About Us
Chris Hibner
BSME University of Michigan, Ann Arbor (1996) Dynamics
and Control
MSME University of Michigan, Ann Arbor (2002) Control
Systems and Computational Mechanics
TRW Automotive; Algorithm Development and Applications
Group, 1996 present.
chiefdelphi.com user name: Chris Hibner
Michael Shaul
BSEE Rose-Hulman Institute of Technology (2002) Computer
Engineering
MSEE University of Michigan, Dearborn (2005) Control
Systems
TRW Automotive; Product Development Group, Software
Validation Group, and Core Components Group
chiefdelphi.com user name: Mike Shaul

Contents
typedefs
Fixed-Point

Math
Overflow Protection
Switch Debouncing
State Machines
Filtering
Oversampling

Good Coding Practices

Signed (good) vs Unsigned (bad) Math


for physical calculations

Use Braces
{
Always
}
Simple Readable Code

Concept of Self Documenting Code


Code as if your grandmother is reading it

Never use Recursion


(watch your stack)

Treat Warnings as Errors

*Disclaimer: Not all code in this presentation follows these


practices due to space limitations

Typedefs
Using Naturally Named
Data Types

Why Typedef?
You

use variable with logical names,


why not use data types with logical
names?
Is an int 8-bits or 16-bits? Whats
a long? Better question: why
memorize it?
Most integer data types are platform
dependent!!!
typedefs make your code more
portable.

How to use typedefs


1)

2)

3)

4)

Create a logical data type scheme.


For example, a signed 8-bit number
could be s8.
Create a typedef.h file for each
microcontroller platform you use.
#include typedef.h in each of your
files.
Use your new data type names.

typedef.h Example
typedef
typedef
typedef
typedef
typedef
typedef

unsigned char
signed char
unsigned short
signed short
unsigned long
signed long

u8;
s8;
u16;
s16;
u32;
s32;

In your code:
unsigned char variable;
Is replaced with:
u8 variable;

Fixed-Point Math
Fractional Numbers Using
Integer Data Types

Creating Fractions
Fractions are created by using extra bits
below your whole numbers.
The programmer is responsible for
knowing where the decimal place is.
Move the decimal place by using the shift
operator (<< or >>).
Shifting is multiplying by powers of 2.
Ex.: x<<5 = x*2^5; x>>5 = x*2^-5

Fixed Point Fraction Example


A/D Sample
(10-bit)
Shift left by 6 (i.e. A2D << 6;):

Whole part

Fractional part

Fractional Example, continued


We

know 5/2 = 2.5


If we used pure integers, 5/2 = 2
(i.e. the number is rounded toward
negative infinity)
Using a fixed-point fractional portion
can recover the lost decimal portion.

Fractional Example, continued


0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1

=5

A/D Sample
(10-bit)
Shift left by 6 (i.e. A2D << 6;):
0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
Whole part

Fractional part

= 5.0

Fractional Example, continued


0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
Whole part

Fractional part

Divide by 2 (i.e. A2D / 2;):


0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0
Whole part

Fractional part

The whole part: 0000000010(binary) = 2(decimal)


The fractional part: 100000(binary) = 32

(huh???)

Fractional Example, continued


Divide by 2 (i.e. A2D / 2;):
0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0
Whole part

Fractional part

The fractional part: 100000(binary) = 32

(huh???)

How many different values can the fractional part be?


Answer: we have 6 bits => 2^6 values = 64 values
(i.e.) 111111 + 1(binary) = 64(decimal)
Therefore:
Fractional part is actually 32/64 = 0.5

Fractional Example, conclusion


Divide by 2 (i.e. A2D / 2;):
0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0
Whole part

Fractional part

-By using a fixed-point fractional part, we can have 5/2 = 2.5


-The more bits you use in your fractional part, the more
accuracy you will have.
-Accuracy is 2^-(fraction bits).
-For example, if we have 6 bits in our fractional part (like the
above example), our accuracy is 2^-6 = 0.015625. In other
words, every bit is equal to 0.015625

Fractional Example, example


If we look diving and adding multiple values using this method
we can see the benefit of fixed point math. This example
assumes we are adding two 5/2 operations as shown.
0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0
+

Adding 2.5 + 2.5

0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0
0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0

Once our math operations are complete, we right shift our


data to regain our original resolution and data position.
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1

=5

Without using the fixed point math the result of the addition
would have been 4 due to the truncation of the integer division.

Overflow Protection
Making Sure Your Code Is
Predictable

What is Overflow? Why is it Bad?


Overflow

is when you try to store a


number that is too large for its data
type.
For example, what happens to the
following code?
s8 test = 100;
test = test + 50;
answer: -105
(thats not good!!!)

Integer Data Types: Valid Ranges

u8
s8
u16
s16
u32
s32

:
0 - 255
:
-128 - 127
:
0 - 65535
: -32768 - 32767
:
0 - 4.294967295e9
: -2.147483648e9 - 2.147483647e9

Note: ranges given are decimal.

Overflow Protection Methods


1.

2.

3.

Create a new temporary variable using a


data type with a larger range to do the
calculation.
Compare the sign of the variable before
and after the calculation. Did the sign
change when it shouldnt have? (for
signed variables)
Compare the variable after the
calculation to the value before. Did the
value decrease when it should have
increased?

Overflow Protection, Example 1


s16 add16(s16 adder1, s16 adder2); // prototype
S16 add16(s16 adder1, s16 adder2)
{
s32 temp = (s32)adder1 + (s32)adder2;
if (temp > 32767) // overflow will occur
return 32767;
else if (temp < -32768) // underflow
return -32768;
else
return (s16)temp;

}
*This example uses a s32 (larger) data value for overflow checking

Overflow Protection, Example 2


// prototype
s16 addTo16bit(s16 start, s16 adder);
S16 addTo16bit(s16 start, s16 adder)
{
s16 temp = start;
start += adder;
if ((start > 0) && (adder > 0) && (temp <= 0))
return 32767;
// Overflow occurred
else if ((start < 0) && (adder < 0) && (temp >= 0))
return -32768; // Underflow occurred
else
return start;

}
*This example uses 16 bit values only to check for overflow on signed
values this provides improved efficiency on 16 bit platforms.

Overflow Protection, Example 3


// prototype
u16 addToUnsigned16bit(u16 start, s16 adder);
S16 addToUnsigned16bit(u16 start, s16 adder)
{
u16 temp = start;
start += adder;
if ((adder > 0) && (start < temp))
return 65536; // Overflow occurred
else if ((adder < 0) && (start > temp))
return 0; // underflow occurred
else
return start;

}
*This example checks for overflow on unsigned values

Switch Debouncing
Having Confidence in
Switch Inputs

Why Debounce? When to Use It?


Debouncing

a switch input reduces


erroneous inputs.
Use debouncing when pressing a
switch starts a sequence or changes
the state of something.

When to Debounce Examples


Debounce

when a single push of the


switch changes a state. Examples:
- pneumatic gripper
- motorized gripper where a single
push causes the motor to go until a
limit switch is reached
Do not debounce if constant driver
input is needed.

What is Debouncing?
Basically, debouncing means to require a
certain number of samples before you
confirm the switch input.
Various debounce schemes can be used:
- require N consecutive samples (i.e. reset
the counter if one sample fails)
- count up / count down (i.e., if one
sample fails, decrement the counter by 1
rather than resetting to zero.

Debounce Example
// debounce opening gripper
if (!pneumaticGripperOpenState)
{
if (gripperOpenSwitch == ON)
gripperOpenDebCount++;
else
gripperOpenDebCount = 0;
if (gripperOpenDebCount >= DEBOUNCE_LIMIT)
{
gripperOpenDebCount = 0;
pneumaticGripperOpenState = TRUE;
}
}

State Machines
Systematic Approach to
Complicated Logic

What is a state machine?


A

state machine is a method for


organizing complicated logic.
A state machine is broken up into
discrete states. Logic is defined to
switch from one state to another.
Only one state can be active at any
time.
It takes one sample (i.e. one time
through the code) to switch from one
state to another.

When should I use a state machine?


When

performing an automated
routine which has many steps.
When the logic is very large and/or
difficult to grasp. (Like C
programming: break up a large
program into smaller functions)
When the problem has physical
states (like a light it has two
states: ON and OFF).

What does it look like?

Light

bulb example:

Automated Process Example

Automated Process Code


/* single shot state machine states */
#define START_SINGLE_SHOT 0
#define BALL_NOT_AT_SENSOR 1
#define BALL_AT_SENSOR
2
#define END_SINGLE_SHOT
3
static u8 singleShotState = END_SINGLE_SHOT;
// single shot state machine
switch (singleShotState)
{
case START_SINGLE_SHOT:
singleShotTimer = 0;
if (upperBallDebounced)
{
singleShotState = BALL_AT_SENSOR;
}
else
{
singleShotState = BALL_NOT_AT_SENSOR;
}
break;

Automated Process Code, cont.


case BALL_NOT_AT_SENSOR:
chutePWM = CHUTE_FORWARD;
if (upperBallDebounced)
{
singleShotState = BALL_AT_SENSOR;
}
break;
case BALL_AT_SENSOR:
chutePWM = CHUTE_FORWARD;
if (singleShotTimer > SINGLE_SHOT_TIMER_LIMIT)
{
singleShotState = END_SINGLE_SHOT;
chutePWM = PWM_OFF;
}
singleShotTimer++;
break;
case END_SINGLE_SHOT:
// this state does nothing
break;

}
// end single shot state machine

Filtering
Smoothing Your Signals

Filter Types

Filters are classified by what they allow to


pass through (NOT what they filter out).

For example, a low pass filter (abv. LPF)


allows low frequencies to pass through it
therefore removes high frequencies.

The most common filters are: high pass


filters, low pass filters, and band pass
filters.

We will only cover low pass filters.

Low Pass Filters


Low

Pass Filters (LPFs) are used to


smooth out the signal.

Common

applications:

Removing sensor noise


Removing unwanted signal frequencies
Signal averaging

Low Pass Filters, continued


There

are two basic types of filters:

Infinite Impulse Response (IIR)


Finite Impulse Response (FIR)
FIR
IIR

filters are moving averages

filters act just like electrical


resistor-capacitor filters. IIR filters
allow the output of the filter to move
a fixed fraction of the way toward the
input.

Moving Average (FIR) Filter Example


#define WINDOW_SIZE 16
s16 inputArray[WINDOW_SIZE];
u8 windowPtr;
s32 filter;
s16 temp;
s16 oldestValue = inputArray[windowPtr];
filter += input - oldestValue;
inputArray[windowPtr] = input;
if (++windowPtr >= WINDOW_SIZE)
{
windowPtr = 0;
}

Moving Average Filter Considerations


For

more filtering effect, use more


data points in the average.

Since

you are adding a lot of


numbers, there is a high chance of
overflow take precautions

IIR Filter Example (Floating Point)


#define FILTER_CONST 0.8
static float filtOut;
static float filtOut_z;
float input;
// filter code
filtOut_z = filtOut;
filtOut = input + FILTER_CONST * (filtOut_z input);
// optimized filter code (filtOut_z not needed)
filtOut = input + FILTER_CONST * (filtOut input);

IIR Filter Example (Fixed Point)


// filter constant will be 0.75. Get this by
// (1 2^-2). Remember X * 2^-2 = X >> 2
#define FILT_SHIFT 2
static s16 filtOut;
s16 input;
// filter code
filtOut += (input - filtOut) >> FILT_SHIFT;

IIR Filter Example (Fixed Point)


Whoa! How did we get from
filtOut = input + FILTER_CONST * (filtOut input);
To
filtOut += (input - filtOut) >> FILT_SHIFT;
Math:
filtOut = input + (1 - 2^-2) * (filtOut input)
= input + filtOut input 2^-2*filtOut + 2^-2*input
= filtOut + 2^-2 * (input filtOut)
filtOut += (input filtOut) >> 2

IIR Filter Considerations (Fixed Point)


For

more filtering effect, make the


shift factor bigger.

Take

precautions for overflow.

You

can get more resolution by using


more shift factors. For example,
have your filter constant be

(1 2^-SHIFT1 2^-2SHIFT2)
(youll have to work out the math!)

Oversampling
Gain resolution and make
your data more reliable.

Oversampling Basics
Simple

oversampling: sample more


data (i.e. faster sample rate) than
you need and average the samples.
Even if you dont sample faster,
averaging (or filtering the data) can
be beneficial.

Oversampling Effects
Helps

to smooth the data.


Helps to hide a bad or unreliable
sample.
Increases A/D resolution if noise is
present.

Oversampling Effects

0.3!!!

Optimizations
Make your code run faster.

Optimizations

Use fixed point math, not floating point.


If possible, do not use data types larger than the
microcontrollers native data type.
When possible, use bit-shifts instead of
multiplying or dividing.
WARNING: some micro/compiler combinations do not
properly handle signed shifts (the FRC controller is a
good example).

For high speed interrupts: use as little code as


possible; try and move any processing to the
slower code outside of the interrupt.

Use overflow to your advantage.

Example (Using Overflow)


Very efficient 8-sample FIR filter:
static u8 filtIndex = 0;
filtOut += (a2dInput - filtArray[(filtIndex & 0x07)]);
filtArray[(filtIndex & 0x07)] = a2dInput;
filtIndex++;

Compare the above filter to the FIR filter example given


in the Filtering portion of the presentation. Note
that it eliminates the need for if statements. The
overflow automatically handles wrapping the array
index.
This concept will work as long as the size of the filter is
a power of 2.

Potrebbero piacerti anche