You are on page 1of 50

(Revised) Rough Notes on Programming AVR Microcontrollers in C.

Mechanical Engineering Report 2007/04 P. A. Jacobs School of Engineering The University of Queensland. February 21, 2008
Preface These notes follow on from the material that you studied in CSSE1000 Introduction to Computer Systems. There you studied details of logic gates, binary numbers and instruction set architectures using the Atmel AVR microcontroller family as an example. In your present course (METR2800 Team Project I ), you need to get on to designing and building an application which will include such a microcontroller. These notes focus on programming an AVR microcontroller in C and provide a number of example programs to illustrate the use of some of the AVR peripheral devices. Session Contents 1. Introduction to the hardware and software development environment. A small but complete application example is implemented with an ATmega88 microcontroller on the STK500 development board. 2. An example embedded application. Elements of C programming: data types, variables, operators, expressions and statements. C quick reference sheet. 3. A more extensive application the uses serial communication and more C control structures. Decision and control statements. Functions, scope of variables. 4. An application using the analog-to-digital converter. Pointers and addresses. Pass by reference. Arrays and strings. The C preprocessor. Interrupts.

CONTENTS

Contents
1 Embedded computing system based on Atmels AVR 2 Example: ash a LED 2.1 Example peripheral: PORTD bidirectional port 2.2 Development tools software and hardware . . 2.3 Preparing the rmware . . . . . . . . . . . . . . 2.4 C source code . . . . . . . . . . . . . . . . . . . 4 7 . 7 . 8 . 9 . 11

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

3 Example 2: a task loop and a hardware timer 13 3.1 C source code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.2 Your own prototype board with ISP . . . . . . . . . . . . . . . . . . . . . . 15 4 Elements of a C program 17 4.1 Types of data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2 Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.3 Assignment statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5 Example 3: Serial-port communication 23 5.1 C source code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 6 Decision and ow-control statements 6.1 Conditional statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Unconditional control statements . . . . . . . . . . . . . . . . . . . . . . . 7 Functions 8 Pointers-to and addresses-of variables 9 Functions revisited: pass-by-reference 26 26 27 29 30 32 33

10 Arrays 34 10.1 Pointers and arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 10.2 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 11 The C preprocessor 36

12 Example 4: Using the analog-to-digital converter 38 12.1 C source code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 13 Interrupts 42 13.1 Example 5: Using interrupts with a hardware timer. . . . . . . . . . . . . . 44 13.2 Example 6: Using interrupts to count button presses . . . . . . . . . . . . 45 14 Software architectures of embedded systems 46 14.1 Round-Robin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 14.2 Round-Robin with interrupts . . . . . . . . . . . . . . . . . . . . . . . . . 47

CONTENTS A ANSI C Reference Card

3 49

1 EMBEDDED COMPUTING SYSTEM BASED ON ATMELS AVR

Embedded computing system based on Atmels AVR


AVR microcontroller units (MCUs) all have the same core, i.e. same instruction set and memory organisation. avours are based on features and complexity tinyAVR: reduced feature set megaAVR: lots of features and peripheral devices built in AVR or classic AVR: in between range of features selection based on tight budget - choose one with just enough functionality convenience of development - choose one with bells and whistles in CSSE1000/COMP1300, you used the ATmega8515, a digital-only MCU for these demonstrations, we will work with the ATmega88 has a nice selection of features (see following page), including a serial port and an analog-to-digital converter (with several input channels) 28-pin narrow DIL package is convenient for prototyping and there are enough I/O pins to play without needing very careful planning. pinout is shown at the start of the Atmel datasheet (book) on the ATmega88 [1]. You will be reading the pages of this book over and over... internal arrangement is around an 8-bit data bus Harvard architecture with separate paths and storage areas for program instructions and data We wont worry too much about the details of the general-purpose registers, the internal static RAM or the machine instruction set because we will let the C compiler handle most of the details. However, memory layout, especially the I/O memory layout is important for us as C programmers; the peripheral devices are controlled and accessed via special function registers in the I/O memory space. For the ATmega88 data memory, we have: 32 general purpose registers (0x00000x001F) 64 I/O registers (0x00200x005F) 160 extended I/O registers (0x00600x00FF) 1kbytes internal SRAM (0x01000x04FF)

1 EMBEDDED COMPUTING SYSTEM BASED ON ATMELS AVR

1 EMBEDDED COMPUTING SYSTEM BASED ON ATMELS AVR Block diagram for the ATmega88, as scanned from the datasheet [1].

Pin layout for the ATmega88 for the 28-pin Plastic Dual Inline Package (PDIP), as scanned from the datasheet.

2 EXAMPLE: FLASH A LED

Example: ash a LED


The microcontroller version of the Hello, World program. Well look at the hardware, development environment and software required to make this happen.

2.1

Example peripheral: PORTD bidirectional port

Schematic dragram for a digital I/O pin, as scanned from the datasheet.

peripheral device can be used for digital input or output three I/O memory addresses are assigned to this port
I/O address
7 6 5 4

bits
3 2 1 0

name PD2 PD1 PD0 PORTD data register DDRD data direction register PIND input register

0x0B (0x2B)

PD7

PD6

PD5

PD4

PD3

0x0A (0x2A)

DDD7 DDD6 DDD5 DDD4 DDD3 DDD2 DDD1 DDD0

0x00 (0x29)

PIND7 PIND6 PIND5 PIND4 PIND3 PIND2 PIND1 PIND0

The names for the registers and the bits are contained in the header le supplied with the C compiler. On my Linux computer, these denitions may be found in /usr/avr/include/avr/iomx8.h.

2 EXAMPLE: FLASH A LED

Registers DDRD and PORTD are readable and writeable with initial values for all bits being 0. Register PIND is readable only. Writing a 0 to a bit in DDRD sets the corresponding pin to input (and a 1 will set the pin to output. Note that initial value for DDRD bits being 0 implies that all pins are initially congured for input. When set as an input pin, a pull-up resistor can be activated by writing a 1 to the corresponding PORTD bit. Output buers can source or sink an absolute maximum current of 40mA per I/O pin and the whole device can cope with a total of 200mA. See section 27 of the datasheet.

2.2

Development tools software and hardware


Consists of editor, simulator, device programming software. We dont have to remember so much about the individual tools because we can usually select actions and options from the menus. Visit http://www.atmel.com to download a copy for home.

We will be using Atmels AVR Studio 4 as our Integrated Development Environment.

We will use the WinAVR programming tools. Consists of the GNU C compiler and the AVR-libc standard C library and peripherals library. Visit http://winavr.sourceforge.net to download a copy for home. Atmels STK500 development hardware which supports the target MCU by providing power, clock signal and input/output devices (such as switches, LEDs and an RS232 port). To install our ATmega88 MCU in the STK500, use socket SCKT3200A2 (the green one) use the In-System Programming (ISP) header SPROG2 and connect (with the 6-pin cable) to the appropriate header (green) Other jumpers that should be installed: VTARGET, supply voltage to the MCU AREF, analogue reference voltage RESET, allow the master MCU (on the top of the STK500) to control the targets reset pin. XTAL1, use STK500 on-board clock as the main clock to the target MCU.

2 EXAMPLE: FLASH A LED

OSCSEL, jumper 12 selects the on-board software-generated clock. This is handy on the STK500 because you can adjust its frequency but remember that, when you build your own board, you must have a working clock for the MCU to respond to in-system programming. It could be the MCUs internal 8MHz clock if you dont want to build an external clock circuit. make sure that BSEL2 is not jumpered. Finally, connect a 10-pin jumper cable from PORTD header to the LEDs header.

2.3

Preparing the rmware

(a) Start AVR Studio (b) Create a new project and congure settings. project type: AVR GCC project name: ashleds1 create initial le create folder location: C:\avr-work next debug platform: AVR Simulator device: ATmega88 Also, at this time, congure the editor (ToolsOptionsEditor) to set tab width 4 and check replace tabs with spaces. (c) Type in source code (from the following section) Save as ashleds1.c and use Ctrl-s frequently. (d) Build the project. (BuildRebuild All) and check for errors. Behind the GUI, avr-gcc has cross-compiled the program and then linked it with the standard library and start-up code to produce an executable program that can be downloaded to the MCU. Alternatively, this code may be run in the simulation environment. Note the messages in the lower window (labelled Build). Note the creation of an Intel HEX le that we will later download to the MCU. (e) Start the simulator (DebugStart Debugging) Note that the yellow arrow points to the rst line of code to be executed. Stepping options are shown in the toolbar (nongreyed now). Open the IO View entry I/O ATmega88, PORTD. Step into the code a couple of times to see the DDRD bits change. Hover the cursor over the which bit variable name to see details of this variables value and location in memory. This doesnt work for DDRD because that is a macro name, however, one can see its address as well as its content in the I/O View window.

2 EXAMPLE: FLASH A LED (f) Download/program the executable program into the MCU.

10

ToolsProgram AVR...ConnectSelect STK500 or AVRISP and we should get STK500 Window showing the state of the target MCU. Make sure that the power is supplied to the board and that the status LED is a steady green. Press Program to download the executable program and run it. Note messages in the log window telling us how the download process went. Note the Fuse settings. The factory-set default settings on the ATmega88 seem to be Boot ash section size=1024 words, Boot start address=$0c00. Serial program downloading enabled (greyed on). Brown-out detection disabled. Divide clock by 8 internally. Internal RC oscillator 8MHz; Start up time: 6CK/14CL+65ms

We could try setting External clock; Start up time: 6CK/14CL+65ms Unset divide clock by 8 internally then experiment with setting the STK500 Oscillator frequency. Values line 3.686 MHz seem odd but are good for RS232 communications. Close the STK500 connection window. Save project. Exit AVR Studio. The MCU should continue to run independently so long as power is applied.

2 EXAMPLE: FLASH A LED

11

2.4

C source code

// f l a s h l e d s 1 . c // Try out t h e AVR S t u d i o + AVRGCC + STK500 . // PJ , 26Feb 2007 // g e t t h e p r o c e s s o r s p e c i f i c #i n c l u d e <a v r / i o . h> d e f i n i t i o n s f o r t h e IO memory s p a c e

// macro d e f i n i t i o n t o s e l e c t b i t n , assuming 0 <= n <= 7 #d e f i n e BIT ( n ) ( 1 << ( n ) ) v o i d my delay ( u n s i g n e d c h a r c 1 s t a r t ) { u n s i g n e d c h a r c1 , c2 ; u n s i g n e d i n t count = 0 ; f o r ( c1 = c 1 s t a r t ; c1 > 0 ; c1 ) { f o r ( c2 = 2 5 5 ; c2 > 0 ; c2 ) { count += 1 ; } } // we i g n o r e t h e v a l u e o f count return ; } i n t main ( v o i d ) { unsigned char which bit ; DDRD = 0xFF ; // a l l ou t put which bit = 0; w h i l e ( 1 ) { // nevere n d i n g l o o p w h i c h b i t += 1 ; i f ( which bit > 7 ) which bit = 0; // Turn on a LED by w r i t i n g 0 t o i t s c o r r e s p o n d i n g MCU p i n PORTD = BIT ( w h i c h b i t ) ; my delay ( 1 0 ) ; } r e t u r n 0 ; // we s h o u l d n e v e r g e t h e r e . }

Notes on this program: Have used C++-style comments. Simple statements end with ;. Compound statements are delimited by { and }. Execution starts at the main function. Nonzero values are interpreted at true in a Boolean context. Operations on individual bits in GCC. // bit position macro #define BIT(n) (1 << (n)) // turn on bit n, leaving the others unchanged PORTD |= BIT(n);

2 EXAMPLE: FLASH A LED // turn off bit n, leaving the others unchanged PORTD &= ~BIT(n); // toggle bit n PORTD ^= BIT(n); Standard C integer objects (desktop computing) int is at least 16 bits sizeof(char) sizeof(short) sizeof(int) sizeof(long) GCC on AVR char is 8 bits can use shift operators to multiply or divide by powers of 2 there is no hardware divide but newer MCUs have hardware multiply avoid the use of oating-point data and operations LED connection on STK500

12

+5V

R2 150

LED1 VTG

2
C

R1
B

Q1

10K
E

LEDn

3 EXAMPLE 2: A TASK LOOP AND A HARDWARE TIMER

13

Example 2: a task loop and a hardware timer


The simplest arrangement for an embedded program that performs a loop of tasks with a xed period. Flowchart is somewhat trivial but does visualize the task loop clearly.
Start main

initialize hardware setup_timer()

setup tumer

reset timer

wait for timer

reset timer

set register for prescale CK/1024

set initial count in timer register

select bit return return No light up LED timer overflow?

Yes wait for timer return

3 EXAMPLE 2: A TASK LOOP AND A HARDWARE TIMER

14

3.1
// // // // // // // // //

C source code

flashleds2 . c The a p p l i c a t i o n doesn t do much ; i t j u s t l i g h t s one LED a t a time a c r o s s t h e bottom o f t h e STK500 board . I t does , however , d e m o n s t r a t e t h e u s e o f a hardware t i m e r t o p r o v i d e a r e g u l a r p e r i o d f o r t h e main l o o p o f t h e a p p l i c a i o n . PJ , 27Feb 2007 o r i g i n a l f o r AT90S4433 11Feb 2008 update t o ATmega88 d e f i n i t i o n s f o r t h e IO memory s p a c e

// g e t t h e p r o c e s s o r s p e c i f i c #i n c l u d e <a v r / i o . h>

// macro d e f i n i t i o n t o s e l e c t b i t n , assuming 0 <= n <= 7 #d e f i n e BIT ( n ) ( 1 << ( n ) ) void setup timer1 ( void ) // We w i l l u s e t h e 16 b i t t i m e r / c o u n t e r 1 w i t h o u t i n t e r r u p t s // and we w i l l s l o w t h i n g s down by p r e s c a l i n g t h e system c l o c k . { // Normal p o r t o p e r a t i o n , OC1A and OC1B d i s c o n n e c t e d // Leave b i t s COM1A1 t h r o u g h COM1B0 o f TCCR1A a s d e f a u l t z e r o v a l u e s // p r e s c a l e CK/1024 TCCR1B |= BIT ( CS12 ) | BIT ( CS10 ) ; return ; } void r e s e t t i m e r 1 ( unsigned i n t counts ) // With a c l o c k f r e q u e n c y o f 3 . 6 8 6 MHz on t h e STK500 and // a 1024 p r e s c a l i n g f a c t o r , we e x p e c t a count o f 360 t o r e s u l t // i n t i m e r 1 o v e r f l o w a f t e r 100 m i l l i s e c o n d s . { TIFR1 |= BIT (TOV1 ) ; // c l e a r t h e o v e r f l o w b i t by w r i t i n g l o g i c a l 1 TCNT1 = 0xFFFF c o u n t s ; // s e t t h e s t a r t i n g count a s a 16 b i t v a l u e return ; } void w a i t f o r t i m e r 1 ( void ) { w h i l e ( ( TIFR1 & BIT (TOV1) ) == 0 ) / do n o t h i n g / ; return ; } i n t main ( v o i d ) { unsigned char which bit ; DDRD = 0xFF ; // a l l ou t put setup timer1 ( ) ; which bit = 0; w h i l e ( 1 ) { // nevere n d i n g l o o p r e s e t t i m e r 1 ( 3 6 0 ) ; // Now, we have 100ms t o do our work . // The body o f t h i s l o o p doesn t do much work . // I t j u s t makes t h e n e x t LED t u r n on b e f o r e w a s t i n g time // w a i t i n g f o r t i m e r / c o u n t e r 1 t o o v e r f l o w . w h i c h b i t += 1 ; i f ( which bit > 7 ) which bit = 0; // Turn on a LED by w r i t i n g 0 t o i t s c o r r e s p o n d i n g MCU p i n PORTD = BIT ( w h i c h b i t ) ; // H o p e f u l l y , t h e r e i s some time t o w a i t f o r t h e t i m e r t o o v e r f l o w . wait for timer1 (); // we s h o u l d n e v e r g e t h e r e .

} return 0;

3 EXAMPLE 2: A TASK LOOP AND A HARDWARE TIMER

15

3.2

Your own prototype board with ISP

Encourage groups to make a prototype on stripboard. Allows software development well before printed-circuit boards have been manufactured. Soldered joints are more reliable than spring contacts in breadboards. Photograph shows a partly constructed prototype board for a timer. The coloured ribbon cable connects the 6-pin ISP header from the STK500 to a 6-pin inline header on the prototype board. The other pairs of wires are to LEDs that will eventually be mounted on the front panel of the device. There is a voltage regulator on the top part of the board for when the board is not powered through the ISP cable. An external full-swing crystal oscillator is used for the clock signal. Current-limiting resistors are used with the peripheral connectors. One pair of lines (RXD and TXD) can be connected to PCs serial port through the level-shifting chip on the STK500.

3 EXAMPLE 2: A TASK LOOP AND A HARDWARE TIMER

16

Circuit-diagram for the prototype board, including the voltage regulator and the ISP header.
+5V

10K

470 RESET/
1

ISP_CONNECTOR
2 2 1
1 2 3 4 5 6

C? S_RESET
2 +5V

U2 470
1 28

RXD 470 TXD

PCINT14/!RESET/PC6 ATmega88 PCINT16/RXD/PD0 PCINT17/TXD/PD1 PCINT18/INT0/PD2 PCINT19/OC2B/INT1/PD3 PCINT20/XCK/T0/PD4 VCC GND PCINT6/XTAL1/TOCS1/PB6 PCINT7/XTAL2/TOSC2/PB7 PCINT21/OC0B/T1/PD5 PCINT22/OC0A/AIN0/PD6 PCINT23/AIN1/PD7 PCINT0/CLKO/ICP1/PB0

PC5/ADC5/SCL/PCINT13 PC4/ADC4/SDA/PCINT12 PC3/ADC3/PCINT11 PC2/ADC2/PCINT10 PC1/ADC1/PCINT9 PC0/ADC0/PCINT8 GND AREF AVCC PB5/SCK/PCINT5 PB4/MISO/PCINT4 PB3/MOSI/OC2A/PCINT3 PB2/!SS/OC1B/PCINT2 PB1/OC1A/PCINT1

27

26

470 INT0 470 INT1 470 PD4

25

24

23

22

100n 20MHz 15p

21

100n

+5V

20

10

19

100n SCK

11

18

15p
12 17

MISO MOSI
16

13

14

15

TITLE FILE: PAGE OF REVISION: DRAWN BY:

VTG D2 U1 J1_5V
1 IN GND 2 1 1 2 7805 2 OUT 3 1 2 +5V

CONN_PLUG_PACK
1

D1

10u

C1

10u

C2

J2_POWER_LED

MISO VTG SCK MOSI RESET/ GND

470

R1

TITLE FILE: PAGE OF REVISION DRAWN B

4 ELEMENTS OF A C PROGRAM

17

Elements of a C program
We will be dealing with data (variables, register values) and instructions (program code, functions) on what should be done with the data. If you want some further reading, see the book [2]. It has a good tutorial introduction but uses a dierent compiler for the AVRs. Basic program structure // heading comments ... data declarations, function definitions ... int main( void ) // this function is special; execution starts here { ... local difinitions ... ... executable statements ... return 0; } For a small embedded program, it is unlikely that we should never arrive at the return statement. For a desktop computing application, the zero will be returned to the operating system. Comments // C++ style comment /* C style comment */ Be careful to avoid nesting the C-style comments. Statements consist of identiers, keywords, expressions and other statements a single statement ends with a semicolon; b = 10; /* assignment of integer value 10 to variable b */ if ( i > 1 ) b = a - 2; The second statement contains a keyword, expression and another single statement. a compound statement is delimited by braces if ( i > 1 ) { // begin compound statement b = a - 2; j = j + 1; } // end if statement Note that there is no semicolon at the end of the compound statement. Putting one there would introduce a null statement.

4 ELEMENTS OF A C PROGRAM An identier is the name of a variable or function. must start with a letter of the alphabet or an underscore this can be followed by any number of letters, digits or underscores usually the rst 32 characters are signicant case sensitive recommendations

18

identiers starting with underscores are usually reserved for system names dont rely on case sensitivity to distinguish names use descriptive names keywords (or reserved words) are names that have special meaning to the compiler examples: if while and for for a complete list, see the appendix A these cannot be used for variable or function names A variable labels a specic memory location (or register) that is set aside to hold a particular type of data. Each variable is identied by its name. Each must be dened before use in a declaration statement that may also initialize the value of the variable. General form of the variable declaration statement type name ; // comment Examples: int count; // This will accumulate the number of key presses. int count = 0; // On startup, we know that nothing has happened.

4.1

Types of data

Internally, all data is stored and handled as binary numbers. All other types are interpretations of the bit patterns. Characters Examples: char letter; letter = a; // 8-bit quantity on the AVR. // Note the use of single quotes.

Some special characters \b backspace \f form feed \n new line \r carriage return

4 ELEMENTS OF A C PROGRAM \t tab \ apostrophe \ double quote \\ backslash \nnn character number nnn in the ASCII table where nnn is octal

19

In AVR programming, we will use char variables to hold 8-bit data (a byte) without necessarily interpreting the data as a character. strings are arrays of characters char my_name[] = "Peter"; The square brackets signify an array. Note that we use double quotes for string constants. The storage in memory is P,e,t,e,r,\000 where the trailing null character marks the end of the string. int variables are used to store 16-bit data. unsigned int is used to represent a whole number in the range 0 .. 65535 (signed) int has the range -32768 .. 32767 Be careful with large values; the number system wraps around. Usually integers are written in base 10 (or decimal) with digits 0..9. When working with AVR MCUs, it is often convenient to use other bases. base 2 (binary) for single bits base 8 (octal) for groups of 3 bits base 16 (hexadecimal) for groups of 4 bits Examples: 0b1011 binary (0b prex) 11 decimal (no prex or leading zero) 013 octal (leading zero) 0xB hexadecimal (0x prex) oat variables are used to represent numbers with a fractional part Can only represent a subset of real numbers, for example, 5.5, -2.31 and 9.0. Note the decimal point. This is what tells the compiler to use a oating-point representation. oats are represented as 32-bit patters on AVR MCUs. Operations are done in software and are slow. Try to avoid using oat data on AVR, however, its hard to beat the convenience of representing real numbers as oat data. Range for oat numbers -3.4e+38 .. 3.4e+38 Precision is about 6 decimal digits.

4 ELEMENTS OF A C PROGRAM boolean or logical values There is no dedicated type. Use an interpretation of integer data. 0 is equivalent to false. Any nonzero value is interpreted as true. Logical expressions evaluate to 1 (for true) or 0 (for false).

20

program space Data may be stored in the ash memory of the AVR. This is separate to the normal RAM data space. #include <avr/pgmspace.h> ... prog_char c; prog_int16_t j; There are also a number of functions in AVR libc to copy data into and out of program space.

4.2

Expressions

Expressions are used to specify simple computations. An expression is a combination of operands and operators that express a single value. Examples: int a = 1; int b = 2; ... a + 2 // expresses an arithmetic value 3 a >= b // expresses a logical value 0 a = b // expresses an assignment of the value 2 arithmetic + operators addition (binary operator) subtraction multiplication division (note that newer AVR MCUs have / hardware multiplication but not division) % modulus (or remainder) ++, increment and decrement (one operand only)

precedence 1. () 2. negation 3. , / 4. +,

4 ELEMENTS OF A C PROGRAM relational operators == is equal to > is greater than >= is greater than or euqal to < is less than <= is less than or equal to ! = is not equal to logical && ! || operators result in true (1) or false (0) values and not or

21

bitwise operators ones complement << shift bit pattern left (with zero ll) >> shift bit pattern right & and | or exclusive-or More examples: char i = 0b0011; char j = 0xB; ~i i << 2 i & j

// 0b00001011 0b11111100 0b00001100 0b00000011

has the value

The right operand for the shift operators gives the number of bit-places to shift. Each left shift is equivalent to multiplying the left operator by 2. Right shift treats signed and unsigned quantities dierently. unsigned: shifts zero into the most-signicant bit signed: the sign bit will be replicated Rules of thumb for precedence 1. multiply and divide come before add and subtract 2. put parentheses around everything else For arithmetic operations, the operand of smaller type is coerced to the larger type. For example, int is a larger type than char so, if we have an addition operation involving both int and char operands, the char value will be promoted to an int value before the operation is performed.

4 ELEMENTS OF A C PROGRAM

22

4.3

Assignment statements

A declaration sets aside space in memory for the variable. unsigned char z; // 8-bit quantity

An assignment statement is is used to give the variable a value. General form: variable = expression ; Example: z = 22; The assignment operator means: compute the value of the expression on the right and assign that value to the variable. May initialize variables in their declaration statement. int upper limit = 10000; Sometimes we want a value that does not change. const int upper limit = 10000; Interacting with the AVR hardware via the special function registers in I/O memory. #include <avr/io.h> int main( void ) { unsigned char z; DDRD = 0xFF; // sets all port pins as output while (1) { z = PINB; // read the binary value from port B PORTD |= (z & 0xF); // set the least-significant 4 bits // to look like those at port B } return; } The read-modiy-write statement could be written as PORTD = PORTD | (z & 0xF);

5 EXAMPLE 3: SERIAL-PORT COMMUNICATION

23

Example 3: Serial-port communication

This example also demonstrates some of the C ow-control structures. The implicit communication between the blocks is via variables.

Start main

Initialize hardware, ports, timer, RS232 total=0

reset timer

read PORTB switches

VTG

increment or decrement or reset the total


10K

send updated total to PC via RS232 port

SWn

150
1

S1

wait for timer

5 EXAMPLE 3: SERIAL-PORT COMMUNICATION

24

5.1
// // // // // // // // // // // // // // //

C source code

countupdown . c This a p p l i c a t i o n t a k e s i n p u t from t h e b u t t o n s a t t h e bottom o f t h e STK500 board and a c c u m u l a t e s a count o f t h e key p r e s s e s . SW0 == c l e a r t o t a l SW1 == i n c r e m e n t t o t a l SW2 == decrement t o t a l Remember t o c o n n e c t PORTB t o t h e s w i t c h e s . The t o t a l i s o ut pu t t o t h e s p a r e RS232 p o r t a s a h e x a d e c i m a l number . Remember t o c o n n e c t p i n s PD0 , PD1 t o t h e h e a d e r RxD, TxDs p a r e with a 2w i r e jumper c a b l e . PJ , 28Feb 2007 o r i g i n a l f o r AT90S4433 12Feb 2008 r e v i s e d f o r ATmega88

#i n c l u d e <a v r / i o . h> // t i m e r void setup timer1 ( void ) // We w i l l u s e t h e 16 b i t t i m e r / c o u n t e r 1 w i t h o u t i n t e r r u p t s // and we w i l l s l o w t h i n g s down by p r e s c a l i n g t h e system c l o c k . { TCCR1B |= ( 1 << CS12 ) | ( 1 << CS10 ) ; // p r e s c a l e CK/1024 return ; } void r e s e t t i m e r 1 ( unsigned i n t counts ) // With a c l o c k f r e q u e n c y o f 3 . 6 8 6 MHz on t h e STK500 and // a 1024 p r e s c a l i n g f a c t o r , we e x p e c t a count o f 360 t o r e s u l t // i n t i m e r 1 o v e r f l o w a f t e r 100 m i l l i s e c o n d s . { TIFR1 |= ( 1 << TOV1 ) ; // c l e a r t h e o v e r f l o w b i t by w r i t i n g l o g i c a l 1 TCNT1 = 0xFFFF c o u n t s ; // s e t t h e s t a r t i n g count return ; } void w a i t f o r t i m e r 1 ( void ) { w h i l e ( ( TIFR1 & ( 1 << TOV1) ) == 0 ) / do n o t h i n g / ; return ; } // s e r i a l p o r t // Assume t h a t we run t h e STK500 c l o c k a t f u l l s p e e d . #d e f i n e FOSC 3686400 #d e f i n e BAUD 9600 #d e f i n e MYUBRR FOSC/16/BAUD 1 // See t a b l e 181 o f d a t a s h e e t f o r t h e baudr a t e r e g i s t e r f o r m u l a . void setup uart ( void ) { UBRR0 = MYUBRR; // a v a l u e o f 23 f o r 9600 baud with FOSC=3.686 MHz UCSR0B |= ( 1 << TXEN0 ) ; // c o n n e c t t h e t r a n s m i t t o PD1 return ; } void s e n d c h a r a c t e r ( unsigned char c ) { // Note t h e n u l l s t a t e m e n t s i n t h e b o d i e s o f t h e f o l l o w i n g l o o p s . w h i l e ( (UCSR0A & ( 1 << UDRE0) ) == 0 ) / w a i t u n t i l empty / ; UDR0 = c ; // send t h e c h a r a c t e r w h i l e ( (UCSR0A & ( 1 << TXC0) ) == 0 ) / w a i t u n t i l c o m p l e t e / ; } unsigned char hex repr ( unsigned char n)

5 EXAMPLE 3: SERIAL-PORT COMMUNICATION


// Return a h e x a d e c i m a l c h a r a c t e r r e p r e s e n t i n g t h e b i n a r y v a l u e . { n &= 0x0F ; // keep o n l y 4 b i t s , j u s t i n c a s e t h e u s e r has s e n t more i f ( n < 10 ) { return n+ 0 ; } else { r e t u r n ( n10)+ A ; } } v o i d send number ( u n s i g n e d i n t n ) // Send a 4 d i g i t h e x a d e c i m a l r e p r e s e n t a t i o n o f t h e number // t o t h e s e r i a l p o r t , s t a r t i n g with t h e most s i g n i f i c a n t d i g i t . { u n s i g n e d c h a r nybble , i ; f o r ( i = 0 ; i < 4 ; i++ ) { n y b b l e = ( n & 0 xF000 ) >> 1 2 ; // g e t most s i g n i f i c a n t d i g i t s e n d c h a r a c t e r ( h ex r ep r ( nybble ) ) ; n = n << 4 ; // move r e m a i n i n g b i t s l e f t } } // t h e main program / l o o p i n t main ( v o i d ) { unsigned char t o t a l , o l d t o t a l , k e y b i t s ; // PORTB d e f a u l t s t o i n p u t ; t h i s i s where t h e s w i t c h e s a r e c o n n e c t e d . setup timer1 ( ) ; setup uart ( ) ; total = 0; w h i l e ( 1 ) { // nevere n d i n g l o o p r e s e t t i m e r 1 ( 3 6 0 ) ; // Now, we have 100ms t o do our work . // Attend t o a l l o f our c h o r e s . . . old total = total ; k e y b i t s = PINB & 0 b00000111 ; // o n l y l o o k a t SW2, SW1 and SW0 // Note t h a t , when p r e s s e d , t h e s w i t c h e s p u l l t h e MCU p i n s low . i f ( ( k e y b i t s & 0 b001 ) == 0 ) { total = 0; } e l s e i f ( ( k e y b i t s & 0 b010 ) == 0 ) { t o t a l ++; } e l s e i f ( ( k e y b i t s & 0 b100 ) == 0 ) { t o t a l ; } i f ( o l d t o t a l != t o t a l ) { // Only send v a l u e when t h a t has been a change . send number ( t o t a l ) ; s e n d c h a r a c t e r ( \ n ) ; // new l i n e s e n d c h a r a c t e r ( \ r ) ; // c a r r i a g e r e t u r n } // H o p e f u l l y , t h e r e i s some time t o w a i t f o r t h e t i m e r t o o v e r f l o w . wait for timer1 (); // we s h o u l d n e v e r g e t h e r e .

25

} return 0;

6 DECISION AND FLOW-CONTROL STATEMENTS

26

Decision and ow-control statements

Some of these notes were adapted from texts [3] and [4].

6.1

Conditional statements

simplest form

false

true expression?

if ( expression ) statement

statement

The statement may be a single or a compound statement. else clause for a case where the condition is false
false expression? true

if ( expression ) statement-1 else statement-2

statement-2

statement-1

Even if statemenst 1 and 2 are single statements, it is a good idea to use braces and indentation to show the structure unambiguously. We can nest if statements to get more than two execution paths. Unless otherwise indicated by the use of braces, an else clause belongs to the innermost if statement that does not yet have an associated else clause. (page 52, [3]) For many execution paths, use a switch statement. switch ( expression ) { case const-expr-1 : statements-1 case const-expr-2 : statements-2 case const-expr-3 : statements-3 default : statements } The expression is evaluated and is tested for a match with a number of integer-valued

6 DECISION AND FLOW-CONTROL STATEMENTS

27

constants (the const-expr values) If a match is found, execution starts at that case. All of the statements from that point until the end of the switch construct will be executed or until a break statement is encountered. Using a break statement is the normal way to stop fall-through between case clauses. However, making use of fall-through, we may have several cases for one particular set of statements. The optional default case is executed if none of the others are satised.

6.2

Loops

pretested loop

false

while ( expression ) statement

expression?

true

statement

post-tested loop

do statement while ( expression );


statement

true expression?

false

6 DECISION AND FLOW-CONTROL STATEMENTS for statement


init_expr;

28

false cond_expr?

for ( init expr ; cond expr ; nal expr ) statement

true

statement

final_expr;

init expr is usually an assignment expression. The statement is executed if cond expr is nonzero (true). Note that cond expr does not have to be a boolean expression. nal expr is evaluated each time, after the statement is executed. It is usually an increment expression. Note the semicolons separating the expressions. An example of a never-ending loop with a null statement as the body. for (;;) ;

6 DECISION AND FLOW-CONTROL STATEMENTS

29

6.3

Unconditional control statements


Flow of control jumps to the statement immediately following the body of the statement containing the break statement. Use for exceptional situations where a loop must be abandoned. Also used to stop execution falling through from one case to the next in a switch statement.

break;

continue; Execution starts the next iteration of the innermost enclosing do, for or while loop. Useful for skipping the body of a loop in an exceptional situation. goto label ; May be useful in exceptional situations, for example, when breaking out of multiply-nested loops. The label needs to be part of a full statement (which could be a null statement). Execution can only jump to a target within a single function body. Example: Start: i = 0; // this is part of the labelled statement ... if ( i > 200 ) goto Start;

7 FUNCTIONS

30

Functions
Functions are a form of procedural abstraction, so we dont have to deal with too much detail at any one point in time. The idea is to encapsulate an operation or procedure, give it a name, and subsequently use this operation in other parts of the program simply by using its name. There is a special function called main which is entered when your program starts running (possibly after power-on or some other reset condition for the AVR MCUs). All other functions are called directly or indirectly from main except interrupt routines which are called directly by the MCU hardware. (More on that in Section 13.) type of the function Functions return a value of their declared type whenever they are used. The value returned may be ifnored (implicitly discarded). If you wish to always do this, it is probably best to declare the type of the function to be void to indicate that no value is returned. denition of the function declares the name and type of the function and the argument list. provides the body of the function as a compound statement. formal parameters (or arguments) are the names used inside the function to refer to its arguments. actual parameters (or arguments) are the expressions or values used as arguments when the function is actually called. If we wish to refer to the function before dening it, we need to declare it to the compiler by specifying its prototype. In the function prototype, we dont need to specify the formal parameter names, just their type, however, specifying the names improves readability of your code. function body The body of a function is always a compound statement. New variables may be declared within this compound statement (or block). These will be local to the function and not be visible to code outside the function. If any new variable has the same name as any global variable, the local variable will hide the global variable. Formal parameters are eectively local variables.

7 FUNCTIONS passing arguments (pass-by-value mechanism)

31

When a function is called, any arguments that are provided by the caller are simply treated as expressions. The value of each expression is used to initialize the corresponding formal parameter. The formal parameters behave the same way as other local variables within the function. Any changes to them are not seen outside the function. scope of variables The scope of a variable is the area of the program code in which the variable is valid (or is visible). A global variable is one that is declared outside any function and is valid anywhere (within the source code module). A local variable has a scope that is limited to the block in which it is declared and cannot be accessed outside of that block. (A block is a section of code enclosed in curly braces { ... }).

8 POINTERS-TO AND ADDRESSES-OF VARIABLES

32

Pointers-to and addresses-of variables


// defines 16-bit variables // defines a pointer variable

unsigned int i, j; unsigned int *p;

The variable p will contain the address of an unsigned int variable (as opposed to the value of the int). Conceptual model of the data memory.

address ?

int value ?

int value ?

The memory locations will have numerical addresses as seen in the AVR datasheet. The names of the variables are shown to the right of the memory locations that they label. The question marks in the memory locations indicate undened values (presently). // assign some values i = 1; j = 3; p = &i;

&i

p now points to the variable i because p contains the address (numerical location in data memory) of the variable i. & is the address-of operator.

9 FUNCTIONS REVISITED: PASS-BY-REFERENCE

33

To change the value of a variable, given its address, we use the (prex) operator. It is equivalent to what is pointer to and is sometimes called the dereference operator. *p = 4; j = (*p);

&i

The expression p evaluates to 4. Also, we may have more than one pointer variable pointing to any particular memory location.

Functions revisited: pass-by-reference

Sometimes we would like a mechanism to aect the variables used as actual parameters to a function. Even though values only are passed through to the formal arguments of a function, we may pass the address of any variable that we want to aect from within the function. void double_it( int *v ) { (*v) = (*v) * 2; return; } int main( void ) { int i = 4; ... double_it( &i ); ... }

10 ARRAYS

34

10

Arrays

An array is a collection of variables with identical type and a single name. General denition of a single-dimensional array: type variable[size]; Example: int data list[3];

int value ?

data_list[2]

int value ? Increasing address int value ?

data_list[1]

data_list[0]

Although the size may be an expression, it must be evaluated at compile time. Note also that the array elements start at index 0 and are numbered up to n 1 where n is the number of elements. We can consider this index as an oset from the rst element in the array. Initialize each element: int i; for ( i = 0; i < 3; i += 1 ) { data_list[i] = i + 1; }

data_list[2]

2 Increasing address 1

data_list[1]

data_list[0]

10 ARRAYS Use the same notation to access the elements: int sum = 0; for ( i = 0; i < 3; i += 1 ) { sum += data_list[i]; }

35

10.1

Pointers and arrays

The name of the array itself is a pointer to the rst element (at index 0). Example: int data_list[3]; int *p; ... p = data_list; // or p = &data_list[0]; ... *p = 5;
&data_list[0] p

int value ?

data_list[2]

int value ?

data_list[1]

data_list[0]

We could use pointer arithmetic to step through the array. for ( p = data_list; p <= &data_list[2]; ++p ) *p = 0;

11 THE C PREPROCESSOR

36

10.2

Strings

Strings are created out of arrays of characters with the last character being the special null character. Example: char name[10]; ... name[0] = P; name[1] = e; name[2] = t; name[3] = e; name[4] = r; name[5] = \000; Alternatively: #include <string.h> ... strcpy(name, "Peter");

11

The C preprocessor

The preprocessor is essentially a specialized text editor which changes your source code before passing it (the transformed source code) onto the compiler. All preprocessor directives (also known as commands or macros) begin with a sharp # character in the rst column and terminate at the end of the line unless they are continues by having a backslash \ character as the very last character on the line. Be careful that the syntax of the preprocessor is dierent to the C language. You cannot use C constructs as preprocessor directives. include les The include directive allows the program to use source code from another le. Examples #include <avr/io.h> #include "my_defs.h" The angle brackets tell the preprocessor to look in the system area for the include le. The double quotes tell the preprocessor start looking in the current area. We can include any le we like but, usually, the le will contain function prototypes and parameter denitions. replacement directive General form: #define name substitute-text

11 THE C PREPROCESSOR Examples: #define BIT(n) (1 << (n)) ... #define NDIM 10 int x[NDIM]; ... for ( i = 0; i < NDIM; ++i ) x[i] = 0;

37

The convention is to use captial letters for the name of the denition. Also, when parameters are passed to a denition, use parentheses substitute text in order to be safe. The denition may be used in all sorts of places. We can also remove a denition: #undef NDIM Denitions remain in force until they are removed or until the end of the source code le is reached. The AVR libc writers have provided denitions for all of the useful I/O registers, for example: #include <avr/io.h> ... DDRD = 0xFF; conditional compilation We may have multiple versions of the source code in one le. #define DEBUG ... #ifdef DEBUG ... code for the debugging version ... #else ... code for the production version ... #endif

12 EXAMPLE 4: USING THE ANALOG-TO-DIGITAL CONVERTER

38

12

Example 4: Using the analog-to-digital converter

This example demonstrates the construction of a program from several modules. Add source les to the project tree-view in the top-left of the IDE. adc demo.c my adc.c my serial.c my timer.c The header les are automatically put under the heading External Dependencies when you next build the project.

12.1
// // // // // // // // // // //

C source code

main module
adc demo . c This a p p l i c a t i o n t a k e s i n p u t from c h a n n e l 0 o f t h e ADC and s e n d s t h o s e v a l u e s t o t h e s e r i a l p o r t ( when t h e y change ) . PJ , 02Mar2007 v e r s i o n f o r AT90S4433 12Feb 2008 r e v i s e d f o r ATmega88 Also , d i v i d e d t h e code i n t o s e v e r a l s o u r c e f i l e s . The names a r e a b i t u g l y but a r e r e a s o n a b l y c e r t a i n not t o be t h e same a s t h e l i b r a r i e s s u p p l i e d by t h e c o m p i l e r vendor .

#i n c l u d e my timer . h #i n c l u d e m y s e r i a l p o r t . h #i n c l u d e my adc . h i n t main ( v o i d ) { unsigned i n t adc value , o l d v a l u e ; setup timer0 ( ) ; setup uart ( ) ; setup adc ( ) ; adc value = 0; w h i l e ( 1 ) { // nevere n d i n g l o o p r e s e t t i m e r 0 ( 1 8 0 ) ; // Now, we have 50ms t o do our work . // Attend t o a l l o f our c h o r e s . . . old value = adc value ; adc value = get adc value ( 0 ) ; i f ( o l d v a l u e != a d c v a l u e ) { // Only send v a l u e when t h a t has been a change . send number ( a d c v a l u e ) ; s e n d c h a r a c t e r ( \ n ) ; // new l i n e s e n d c h a r a c t e r ( \ r ) ; // c a r r i a g e r e t u r n } // H o p e f u l l y , t h e r e i s some time t o w a i t f o r t h e t i m e r t o o v e r f l o w . wait for timer0 (); // we s h o u l d n e v e r g e t h e r e .

} return 0;

12 EXAMPLE 4: USING THE ANALOG-TO-DIGITAL CONVERTER ADC module


// my adc . c // F u n c t i o n s t o a c c e s s t h e a n a l o g u e tod i g i t a l c o n v e r t e r . // // PJ , 12Feb 2008 #i n c l u d e <a v r / i o . h> void setup adc ( void ) { ADCSRA &= ( 1 << ADATE) ; // s e l e c t s i n g l e sample mode ADCSRA |= ( 1 << ADEN) ; // e n a b l e ADC hardware // S e t p r e s c a l e r d i v i s i o n f a c t o r t o 8 . ADCSRA |= ( 1 << ADPS0 ) ; ADCSRA |= ( 1 << ADPS1 ) ; ADCSRA &= ( 1 << ADPS2 ) ; return ; } unsigned i n t g e t a d c v a l u e ( unsigned char c h a n n e l i d ) { unsigned char low byte , high byte ; i f ( channel id > 5 ) channel id = 5; ADMUX = c h a n n e l i d ; ADCSRA |= ( 1 << ADSC ) ; // s t a r t c o n v e r s i o n w h i l e ( (ADCSRA & ( 1 << ADIF ) ) == 0 ) / w a i t / ; l o w b y t e = ADCL; // D a t a s h e e t s a y s t o r e a d ADCL f i r s t . h i g h b y t e = ADCH; ADCSRA |= ( 1 << ADIF ) ; // c l e a r t h e c o n v e r s i o n c o m p l e t e f l a g r e t u r n ( i n t ) h i g h b y t e 256 + l o w b y t e ; }

39

12 EXAMPLE 4: USING THE ANALOG-TO-DIGITAL CONVERTER Timer module


// my timer . c // F u n c t i o n s t o s e t and w a i t f o r hardware t i m e r s . // // PJ , 12Feb 2008 , updated f o r ATmega88 #i n c l u d e <a v r / i o . h> void setup timer0 ( void ) // We w i l l u s e t h e 8 b i t t i m e r / c o u n t e r 0 w i t h o u t i n t e r r u p t s // and we w i l l s l o w t h i n g s down by p r e s c a l i n g t h e system c l o c k . { TCCR0B |= ( 1 << CS02 ) | ( 1 << CS00 ) ; // p r e s c a l e CK/1024 return ; } void r e s e t t i m e r 0 ( unsigned char counts ) // With a c l o c k f r e q u e n c y o f 3 . 6 8 6 MHz on t h e STK500 and // a 1024 p r e s c a l i n g f a c t o r , we e x p e c t a count o f 36 t o r e s u l t // i n t i m e r 1 o v e r f l o w a f t e r 10 m i l l i s e c o n d s . { TIFR0 |= ( 1 << TOV0 ) ; // c l e a r t h e o v e r f l o w b i t by w r i t i n g l o g i c a l 1 TCNT0 = 0xFF c o u n t s ; // s e t t h e s t a r t i n g count return ; } void w a i t f o r t i m e r 0 ( void ) { w h i l e ( ( TIFR0 & ( 1 << TOV0) ) == 0 ) / do n o t h i n g / ; return ; } // void setup timer1 ( void ) // We w i l l u s e t h e 16 b i t t i m e r / c o u n t e r 1 w i t h o u t i n t e r r u p t s // and we w i l l s l o w t h i n g s down by p r e s c a l i n g t h e system c l o c k . { TCCR1B |= ( 1 << CS12 ) | ( 1 << CS10 ) ; // p r e s c a l e CK/1024 return ; } void r e s e t t i m e r 1 ( unsigned i n t counts ) // With a c l o c k f r e q u e n c y o f 3 . 6 8 6 MHz on t h e STK500 and // a 1024 p r e s c a l i n g f a c t o r , we e x p e c t a count o f 360 t o r e s u l t // i n t i m e r 1 o v e r f l o w a f t e r 100 m i l l i s e c o n d s . { TIFR1 |= ( 1 << TOV1 ) ; // c l e a r t h e o v e r f l o w b i t by w r i t i n g l o g i c a l 1 TCNT1 = 0xFFFF c o u n t s ; // s e t t h e s t a r t i n g count return ; } void w a i t f o r t i m e r 1 ( void ) { w h i l e ( ( TIFR1 & ( 1 << TOV1) ) == 0 ) / do n o t h i n g / ; return ; }

40

12 EXAMPLE 4: USING THE ANALOG-TO-DIGITAL CONVERTER Serial port module


// m y s e r i a l p o r t . c // F u n c t i o n s t o send c h a r a c t e r s v i a t h e hardware USART. // // PJ , 12Feb 2008 #i n c l u d e <a v r / i o . h> // Assume t h a t we run t h e STK500 c l o c k a t f u l l s p e e d . #d e f i n e FOSC 3686400 #d e f i n e BAUD 9600 #d e f i n e MYUBRR FOSC/16/BAUD 1 // See t a b l e 181 o f d a t a s h e e t f o r t h e baudr a t e r e g i s t e r f o r m u l a . void setup uart ( void ) { UBRR0 = MYUBRR; // a v a l u e o f 23 f o r 9600 baud with FOSC=3.686 MHz UCSR0B |= ( 1 << TXEN0 ) ; // c o n n e c t t h e t r a n s m i t t o PD1 return ; } void s e n d c h a r a c t e r ( unsigned char c ) { // Note t h e n u l l s t a t e m e n t s i n t h e b o d i e s o f t h e f o l l o w i n g l o o p s . w h i l e ( (UCSR0A & ( 1 << UDRE0) ) == 0 ) / w a i t u n t i l empty / ; UDR0 = c ; // send t h e c h a r a c t e r w h i l e ( (UCSR0A & ( 1 << TXC0) ) == 0 ) / w a i t u n t i l c o m p l e t e / ; } unsigned char hex repr ( unsigned char n) // Return a h e x a d e c i m a l c h a r a c t e r r e p r e s e n t i n g t h e b i n a r y v a l u e . { n &= 0x0F ; // keep o n l y 4 b i t s , j u s t i n c a s e t h e u s e r has s e n t more i f ( n < 10 ) { return n+ 0 ; } else { r e t u r n ( n10)+ A ; } } v o i d send number ( u n s i g n e d i n t n ) // Send a 4 d i g i t h e x a d e c i m a l r e p r e s e n t a t i o n o f t h e number // t o t h e s e r i a l p o r t , s t a r t i n g with t h e most s i g n i f i c a n t d i g i t . { u n s i g n e d c h a r nybble , i ; f o r ( i = 0 ; i < 4 ; i++ ) { n y b b l e = ( n & 0 xF000 ) >> 1 2 ; // g e t most s i g n i f i c a n t d i g i t s e n d c h a r a c t e r ( h ex r ep r ( nybble ) ) ; n = n << 4 ; // move r e m a i n i n g b i t s l e f t } }

41

13 INTERRUPTS

42

13

Interrupts

The hardware may signal that it needs servicing via an interrupt request 1 . If the processor has its interrupts enabled, it suspends whatever it is doing and executes the corresponding interrupt service routine before resuming work on the task code. AVR MCUs have a number of interrupt sources, each enabled by one enable bit and each with an entry in the interrupt vector table in the lowest part of program memory. Entries in the table are typically jump instructions. When an interrupt occurs, the Global Interrupt Enable bit is cleared and all interrupts are disabled. The program counter is vectored to the actual interrupt vector in order to jump to the interrupt handling routine. Hardware clears the corresponding ag that generated the interrupt. See section 6.26 in the AVR-libc reference manual for details of setting up an interrupt handler. For example: #include <avr/interrupt.h> ... ISR(TIMER1_OVF_vect) { // Our interrupt handling code here // for dealing with the timer overflow signal. } We are provided with a macro to dene the ISR and a table of interrupt vector names for our particular processor. Interrupt service routines (ISRs) must save the context of the task code and restore that context when they are nished. The C compiler usually handles that detail for us by writing suitable code at the start and at the end of our code in the ISR. Usually, it is neither desirable nor possible to do all of the work in an interrupt routine so the routine needs to signal the task code to do follow-up processing. To do this communication, the ISR shares one or more variables. Once one or more variables are shared, we have to be careful because, while in the task code, the interrupt routins may change the data at any instant. This is the classic data-sharing problem. It may be dicult to identify when programming in C because single statements in C may be translated to several assembler instructions and the interrupt event may occur within these assembler statements.
1

Some of these notes from Simons text [5].

13 INTERRUPTS

43

One cure for the data-sharing problem is to disable interrupts in critical sections of task code. (These are sections of code that must not be interrupted for the system to work properly.) Be sure to use the volatile keyword to indicate that the content of a variable may change because of interrupts or other things that the compiler doesnt know about. static volatile int my_flag; Interrupt latency is the amount of time it take a processor to respond to an interrupt. It is determined by: 1 The longest period of time during which the interrupt is disabled. 2 The period of time that it takes to execute routines for higher-priority interrupts. 3 The length of time that it takes the MCU to stop what it is doing (in the task code), do the necessary book-keeping and start executing code in the ISR. Look this up in the AVR datasheet. 4 The length of time it takes the ISR to save the context of its just-left task and do the work that constitutes the response to the interrupt signal. Item 2 leads to the desire to make interrupt routines short.

13 INTERRUPTS

44

13.1
// // // // // // // // //

Example 5: Using interrupts with a hardware timer.

flashleds3 . c The a p p l i c a t i o n doesn t do much ; i t j u s t l i g h t s one LED a t a time a c r o s s t h e bottom o f t h e STK500 board . I t does , however , d e m o n s t r a t e t h e u s e o f a hardware t i m e r with i n t e r r u p t t o p r o v i d e a r e g u l a r p e r i o d f o r t h e d i s p l a y update . PJ , 28Aug2007 21Feb 2008 updated f o r ATmega88 definitions

// g e t t h e p r o c e s s o r s p e c i f i c #i n c l u d e <a v r / i o . h> #i n c l u d e <a v r / i n t e r r u p t . h>

// We w i l l u s e t h e 16 b i t t i m e r / c o u n t e r 1 with o v e r f l o w i n t e r r u p t // and we w i l l s l o w t h i n g s down by p r e s c a l i n g t h e system c l o c k . // With a c l o c k f r e q u e n c y o f 3 . 6 8 6 MHz on t h e STK500 and // a 1024 p r e s c a l i n g f a c t o r , we e x p e c t a count o f 360 t o r e s u l t // i n t i m e r 1 o v e r f l o w a f t e r 100 m i l l i s e c o n d s . unsigned i n t s t a r t c o u n t = 360; // We s l s o need some o t h e r data t h a t i s p r e s e r v e d between u p d a t e s // o f t h e d i s p l a y . unsigned char which bit ; void setup timer1 ( void ) { // Normal p o r t o p e r a t i o n , OC1A and OC1B d i s c o n n e c t e d // Leave b i t s COM1A1 t h r o u g h COM1B0 o f TCCR1A a s d e f a u l t z e r o v a l u e s // p r e s c a l e CK/1024 TCCR1B |= ( 1 << CS12 ) | ( 1 << CS10 ) ; TIFR1 |= ( 1 << TOV1 ) ; // c l e a r t h e o v e r f l o w f l a g TIMSK1 |= ( 1 << TOIE1 ) ; // e n a b l e o v e r f l o w i n t e r r u p t return ; } ISR ( TIMER1 OVF vect ) // This r o u t i n e i s e x e c t u t e d on t h e Timer 1 o v e r f l o w i n t e r r u p t . // Note t h a t i t i s not c a l l e d d i r e c t l y by any o f our o t h e r code . // A l s o n o t e t h a t t h e body o f t h i s s e r v i c e r o u t i n e s h o u l d not // do much work b e c a u s e t h e i n t e r r u p t s a r e c u r r e n t l y t u r n e d o f f . { // o v e r f l o w b i t i s a l r e a d y c l e a r e d by hardware TCNT1 = 0xFFFF s t a r t c o u n t ; // r e s e t t h e c o u n t e r // D e c i d e t h e n e x t LED t o t u r n on then make i t happen and l e a v e w h i c h b i t += 1 ; i f ( which bit > 7 ) which bit = 0; // Turn on a LED by w r i t i n g 0 t o i t s c o r r e s p o n d i n g MCU p i n PORTD = ( 1 << w h i c h b i t ) ; } i n t main ( v o i d ) { DDRD = 0xFF ; // a l l ou tp ut s o t h a t we can t u r n on t h e LEDs setup timer1 ( ) ; TCNT1 = 0xFFFF s t a r t c o u n t ; // r e s e t t h e c o u n t e r which bit = 0; sei (); // At t h i s p o i n t , we can g e t one with whatever work n e e d s t o be done // and t h e Timer1 i n t e r r u p t w i l l r e g u l a r l y be s e r v i c e d by t h e ISR . w h i l e ( 1 ) / do n o t h i n g / ; r e t u r n 0 ; // we s h o u l d n e v e r g e t h e r e .

13 INTERRUPTS

45

13.2

Example 6: Using interrupts to count button presses

// buttoni n t e r r u p t . c // // D emon str atio n o f u s i n g e x t e r n a l i n t e r r u p t s on t h e AVR. // Port B i s o ut pu t with t h e 10 p i n jumper c o n n e c t e d t o LEDs on STK500 . // I t shows t h e c u r r e n t count o f b ut to n p r e s s e s . // Note t h a t o n l y LED0 . . LED5 a r e c o n n e c t e d f o r ATmega88 . // Port D i s i n p u t with PD2=INT0 ( count up ) and PD3=INT1 ( count down ) . // Use 10 p i n jumper t o c o n n e c t t o b u t t o n s on STK500 . // // PJ , August 2007 v e r s i o n f o r AT90S4433 // 21Feb 2008 v e r s i o n f o r ATmega88 // #i n c l u d e <a v r / i o . h> #i n c l u d e <a v r / i n t e r r u p t . h> u n s i g n e d c h a r count = 0 ; ISR ( INT0 vect ) { count++; PORTB = count ; } ISR ( INT1 vect ) { count ; PORTB = count ; } i n t main ( ) { // S e t i n p u t and ou tp ut DDRD &= ( 1 << PD2 ) ; DDRD &= ( 1 << PD3 ) ; DDRB = 0xFF ; PORTB = 0xFF ;

// // // //

INT0 a s i n p u t INT1 a s i n p u t A l l o ut pu t t u r n o f f LEDs on STK500

// Enable s p e c i f i c e x t e r n a l i n t e r r u p t s EIMSK |= ( 1 << INT0 ) ; EIMSK |= ( 1 << INT1 ) ; // S e t up i n t e r r u p t s e n s e c o n t r o l s o t h a t f a l l i n g e d g e s // on INT0 and INT1 g e n e r a t e i n t e r r u p t r e q u e s t s . EICRA |= ( 1 << ISC01 ) ; EICRA |= ( 1 << ISC11 ) ; sei (); // A l l o f t h e i n t e r e s t i n g code i s now i n t h e ISRs s o // we j u s t w a i t around f o r t h e hardware t o make r e q u e s t s . while (1) ; } return 0;

14 SOFTWARE ARCHITECTURES OF EMBEDDED SYSTEMS

46

14

Software architectures of embedded systems

Its all about system response. For the source of these note, see section 5 of Simons text [5]. How we structure our software depends on how much the MCU needs to do and how strict the time requirements are. Options discussed by Simon [5] are: Round-Robin Round-Robin with interrupts Function-queue scheduling Real-time operating system

14.1

Round-Robin

This is the simplest architecture (and was used in the early examples). No interrupts No shared-data issues Structure of task code: int main( void ) { // initialize hardware while ( 1 ) { if ( device A needs service ) { // service device A; handle data transfer to or from device } if ( device B needs service ) { // service device B; handle data transfer to or from device } // ... more devices ... } // end Round-Robin Loop return 0; } // end main Provided that we have slack in our processing needs, we can make the Round-Robin loop regular by adding a wait-for-timer-tick at the end of the loop. If any device needs a response time less than the time needed to do all of the tasks, the system wont work properly. If any task does a large amount of processing, the system response will be poor. We may be able to improve response time for one device by testing the device more than once in the loop: ABACADA...

14 SOFTWARE ARCHITECTURES OF EMBEDDED SYSTEMS

47

The system is fragile. After changing the task sequence to meet response needs, one change or addition can mess it up again.

14.2

Round-Robin with interrupts

Interrupt routines deal with the urgent needs of any devices and set ags. The main (task) loop polls the ags and does follow-up processing requested by the interrupts. Structure of the code: char flag_dev_A = 0; char flag_dev_B = 0; ... void ISR( device_A ) { // service device A flag_dev_A = 1; // to signal follow-up } void ISR( device_B ) { // service device B flag_dev_B = 1; // to signal follow-up } ... int main( void ) { while ( 1 ) { if ( flag_dev_A ) { flag_dev_A = 0; // Do follow-up processing for } if ( flag_dev_B ) { flag_dev_B = 0; // Do follow-up processing for } ... } // end Round-Robin Loop return 0; } // end main

processing

processing

device A.

device B.

All processing that is in the interrupt routines gets higher priority than the task-loop code. This ability to set priority for a piece of code is an advantage but it comes with the cost of having to deal with shared-data.

REFERENCES

48

References
[1] Atmel. Atmega48/88/168. Datasheet doc2545, http://www.atmel.com/. [2] Richard F. Barnett, Larry OCull, and Sarah Cox. Embedded C Programming and the Atmel AVR. Clifton Park, Delmar, New York, 2003. [3] Steven R. Lerman. Problem solving and computation for scientists and engineers : an introduction using C. Prentice-Hall, Englewood Clis, New Jersey, 1993. [4] B. W. Kernighan and D. M. Richie. The C Programming Language. Prentice Hall PTR, Upper Saddle River, New Jersey, 1988. [5] David E. Simon. An embedded software primer. Addison-Wesley, 1999.

C Reference Card (ANSI)


Constants
statement terminator ; block delimiters { } exit from switch, while, do, for break; next iteration of while, do, for continue; go to goto label; label label: statement return value from function return expr Flow Constructions if statement if (expr 1 ) statement 1 else if (expr 2 ) statement 2 else statement 3 while statement while (expr ) statement for statement for (expr 1 ; expr 2 ; expr 3 ) statement do statement do statement while(expr ); switch statement switch (expr ) { case const 1 : statement 1 break; case const 2 : statement 2 break; default: statement }

A ANSI C REFERENCE CARD

Flow of Control

ANSI Standard Libraries


<assert.h> <locale.h> <stddef.h> <ctype.h> <math.h> <stdio.h> <errno.h> <setjmp.h> <stdlib.h>

<float.h> <signal.h> <string.h>

<limits.h> <stdarg.h> <time.h>

ANSI C Reference Card

sux: long, unsigned, oat 65536L, -1U, 3.0F Program Structure/Functions exponential form 4.2e1 prex: octal, hexadecimal 0, 0x or 0X type fnc(type 1 , . . . ); function prototype Example. 031 is 25, 0x31 is 49 decimal type name; variable declaration character constant (char, octal, hex) 'a', '\ooo', '\xhh' int main(void) { main routine \n, \r, \t, \b declarations local variable declarations newline, cr, tab, backspace special characters \\, \?, \', \" statements string constant (ends with '\0') "abc. . . de" } type fnc(arg 1 , . . . ) { function denition declarations local variable declarations Pointers, Arrays & Structures statements declare pointer to type type *name; return value; declare function returning pointer to type type *f(); } declare pointer to function returning type type (*pf)(); /* */ comments generic pointer type void * int main(int argc, char *argv[]) main with args null pointer constant NULL exit(arg); terminate execution object pointed to by pointer *pointer address of object name &name C Preprocessor array name[dim] include library le #include <lename> multi-dim array name[dim 1 ][dim 2 ]. . . include user le #include "lename" Structures replacement text #define name text struct tag { structure template replacement macro #define name(var ) text declarations declaration of members Example. #define max(A,B) ((A)>(B) ? (A) : (B)) }; undene #undef name create structure struct tag name quoted string in replace # member of structure from template name.member Example. #define msg(A) printf("%s = %d", #A, (A)) member of pointed-to structure pointer -> member concatenate args and rescan ## Example. (*p).x and p->x are the same conditional execution #if, #else, #elif, #endif single object, multiple possible types union is name dened, not dened? #ifdef, #ifndef bit eld with b bits unsigned member : b; name dened? defined(name) line continuation char \ Operators (grouped by precedence)

Character Class Tests <ctype.h>

Data Types/Declarations
struct member operator struct member through pointer increment, decrement plus, minus, logical not, bitwise not indirection via pointer, address of object cast expression to type size of an object multiply, divide, modulus (remainder) add, subtract left, right shift [bit ops] relational comparisons equality comparisons and [bit op] exclusive or [bit op] or (inclusive) [bit op] *, /, % +, <<, >> >, >=, <, <= ==, != & ^ | name.member pointer ->member ++, -+, -, !, ~ *pointer , &name (type) expr sizeof

alphanumeric? alphabetic? control character? decimal digit? printing character (not incl space)? lower case letter? printing character (incl space)? printing char except space, letter, digit? space, formfeed, newline, cr, tab, vtab? upper case letter? hexadecimal digit? convert to lower case convert to upper case s is a string; cs, ct are constant strings

isalnum(c) isalpha(c) iscntrl(c) isdigit(c) isgraph(c) islower(c) isprint(c) ispunct(c) isspace(c) isupper(c) isxdigit(c) tolower(c) toupper(c)

character (1 byte) char integer int real number (single, double precision) float, double short (16 bit integer) short long (32 bit integer) long double long (64 bit integer) long long positive or negative signed non-negative modulo 2m unsigned pointer to int, float,. . . int*, float*,. . . enumeration constant enum tag {name 1 =value 1 ,. . . }; constant (read-only) value type const name; declare external variable extern internal to source le static local persistent between calls static no value void structure struct tag {. . . }; create new name for data type typedef type name; size of an object (type is size_t) sizeof object size of a data type (type is size_t) sizeof(type)

String Operations <string.h>

Initialization

initialize variable initialize array initialize char string

type name=value; type name[]={value 1 ,. . . }; char name[]="string";

logical and && logical or || conditional expression expr 1 ? expr 2 : expr 3 assignment operators +=, -=, *=, . . . expression evaluation separator , Unary operators, conditional expression and assignment operators group right to left; all others group left to right.

length of s copy ct to s concatenate ct after s compare cs to ct only rst n chars pointer to rst c in cs pointer to last c in cs copy n chars from ct to s copy n chars from ct to s (may overlap) compare n chars of cs with ct pointer to rst c in rst n chars of cs put c into rst n chars of s

strlen(s) strcpy(s,ct) strcat(s,ct) strcmp(cs,ct) strncmp(cs,ct,n) strchr(cs,c) strrchr(cs,c) memcpy(s,ct,n) memmove(s,ct,n) memcmp(cs,ct,n) memchr(cs,c,n) memset(s,c,n)

c 2007 Joseph H. Silverman Permissions on back. v2.2

49

C Reference Card (ANSI)


Standard Utility Functions <stdlib.h> Mathematical Functions <math.h>
Arguments and returned values are double trig functions inverse trig functions arctan(y/x) hyperbolic trig functions exponentials & logs exponentials & logs (2 power) division & remainder powers rounding sin(x), cos(x), tan(x) asin(x), acos(x), atan(x) atan2(y,x) sinh(x), cosh(x), tanh(x) exp(x), log(x), log10(x) ldexp(x,n), frexp(x,&e) modf(x,ip), fmod(x,y) pow(x,y), sqrt(x) ceil(x), floor(x), fabs(x)

Input/Output <stdio.h>

A ANSI C REFERENCE CARD

absolute value of int n abs(n) absolute value of long n labs(n) quotient and remainder of ints n,d div(n,d) returns structure with div_t.quot and div_t.rem quotient and remainder of longs n,d ldiv(n,d) returns structure with ldiv_t.quot and ldiv_t.rem pseudo-random integer [0,RAND_MAX] rand() set random seed to n srand(n) terminate program execution exit(status) pass string s to system for execution system(s) Conversions convert string s to double atof(s) convert string s to integer atoi(s) convert string s to long atol(s) convert prex of s to double strtod(s,&endp) convert prex of s (base b) to long strtol(s,&endp,b) same, but unsigned long strtoul(s,&endp,b) Storage Allocation allocate storage malloc(size), calloc(nobj,size) change size of storage newptr = realloc(ptr,size); deallocate storage free(ptr); Array Functions search array for key bsearch(key,array,n,size,cmpf) sort array ascending order qsort(array,n,size,cmpf)

Integer Type Limits <limits.h>

Time and Date Functions <time.h>

The numbers given in parentheses are typical values for the constants on a 32-bit Unix system, followed by minimum required values (if signicantly dierent). CHAR_BIT bits in char (8) CHAR_MAX max value of char (SCHAR_MAX or UCHAR_MAX) CHAR_MIN min value of char (SCHAR MIN or 0) SCHAR_MAX max signed char (+127) SCHAR_MIN min signed char (128) SHRT_MAX max value of short (+32,767) SHRT_MIN min value of short (32,768) INT_MAX max value of int (+2,147,483,647) (+32,767) INT_MIN min value of int (2,147,483,648) (32,767) LONG_MAX max value of long (+2,147,483,647) LONG_MIN min value of long (2,147,483,648) UCHAR_MAX max unsigned char (255) USHRT_MAX max unsigned short (65,535) UINT_MAX max unsigned int (4,294,967,295) (65,535) ULONG_MAX max unsigned long (4,294,967,295)

Standard I/O standard input stream stdin standard output stream stdout standard error stream stderr end of le (type is int) EOF get a character getchar() print a character putchar(chr ) print formatted data printf("format",arg 1 ,. . . ) print to string s sprintf(s,"format",arg 1 ,. . . ) read formatted data scanf("format",&name 1 ,. . . ) read from string s sscanf(s,"format",&name 1 ,. . . ) print string s puts(s) File I/O declare le pointer FILE *fp; pointer to named le fopen("name","mode") modes: r (read), w (write), a (append), b (binary) get a character getc(fp) write a character putc(chr ,fp) write to le fprintf(fp,"format",arg 1 ,. . . ) read from le fscanf(fp,"format",arg 1 ,. . . ) read and store n elts to *ptr fread(*ptr,eltsize,n,fp) write n elts from *ptr to le fwrite(*ptr,eltsize,n,fp) close le fclose(fp) non-zero if error ferror(fp) non-zero if already reached EOF feof(fp) read line to string s (< max chars) fgets(s,max,fp) write string s fputs(s,fp) Codes for Formatted I/O: "%-+ 0w.pmc" - left justify + print with sign space print space if no sign 0 pad with leading zeros w min eld width p precision m conversion character: h short, l long, L long double c conversion character: d,i integer u unsigned c single char s char string f double (printf) e,E exponential f oat (scanf) lf double (scanf) o octal x,X hexadecimal p pointer n number of chars written g,G same as f or e,E depending on exponent processor time used by program clock() Example. clock()/CLOCKS_PER_SEC is time in seconds current calendar time time() time2 -time1 in seconds (double) difftime(time2 ,time1 ) arithmetic types representing times clock_t,time_t structure type for calendar time comps struct tm tm_sec seconds after minute tm_min minutes after hour tm_hour hours since midnight tm_mday day of month tm_mon months since January tm_year years since 1900 tm_wday days since Sunday tm_yday days since January 1 tm_isdst Daylight Savings Time ag

Float Type Limits <float.h>

Variable Argument Lists <stdarg.h>

declaration of pointer to arguments va_list ap; initialization of argument pointer va_start(ap,lastarg); lastarg is last named parameter of the function access next unnamed arg, update pointer va_arg(ap,type) call before exiting function va_end(ap);

convert local time to calendar time mktime(tp) convert time in tp to string asctime(tp) convert calendar time in tp to local time ctime(tp) convert calendar time to GMT gmtime(tp) convert calendar time to local time localtime(tp) format date and time info strftime(s,smax,"format",tp) tp is a pointer to a structure of type tm

The numbers given in parentheses are typical values for the constants on a 32-bit Unix system. FLT_RADIX radix of exponent rep (2) FLT_ROUNDS oating point rounding mode FLT_DIG decimal digits of precision (6) FLT_EPSILON smallest x so 1.0f + x = 1.0f (1.1E 7) FLT_MANT_DIG number of digits in mantissa FLT_MAX maximum float number (3.4E38) FLT_MAX_EXP maximum exponent FLT_MIN minimum float number (1.2E 38) FLT_MIN_EXP minimum exponent DBL_DIG decimal digits of precision (15) DBL_EPSILON smallest x so 1.0 + x = 1.0 (2.2E 16) DBL_MANT_DIG number of digits in mantissa DBL_MAX max double number (1.8E308) DBL_MAX_EXP maximum exponent DBL_MIN min double number (2.2E 308) DBL_MIN_EXP minimum exponent

January 2007 v2.2. Copyright c 2007 Joseph H. Silverman Permission is granted to make and distribute copies of this card provided the copyright notice and this permission notice are preserved on all copies. Send comments and corrections to J.H. Silverman, Math. Dept., Brown Univ., Providence, RI 02912 USA. jhs@math.brown.edu

50