codevisionavr c compiler2

25
Introduction This tutorial provides an introduction to the capabilities of the DHP Technology CodeVisionAVR C Compiler and Integrated Development Environment. It does not pretend to show all the features of this development system – it is just what it says in the first line; an introduction. This is not a tutorial for C - we assume that you know it – nor is it a tutorial for assembly language for the AVR series of microcontrollers. We assume that you are at least somewhat familiar with the operation of the 90S series of AVR microcontrollers. In this tutorial, we will just give you some examples of using the CodeVisionAVR C compiler and all its tools for the easy compilation of programs for the AVR series. For microcontrollers, the best way to learn is to actually program some devices to do some simple functions. There are many development boards available; several companies make them. We have designed this tutorial around Atmel Microcontroller Starter Kit Development Board (we’ll just abbreviate this to AVRDB) because we think it is the board you are most likely to have. This small unit and inexpensive unit is readily available and it may be used to program and run programs for all the Atmel 90S series of microcontroller chips. The exercises are designed around a 90S2313 microcontroller plugged into this board but will work equally well with any of its larger brothers – the 90S4414 or the 90S8515. The 90S2313 has 2K bytes (1K instructions) of flash memory, 128 bytes of EEPROM, 128 bytes of RAM and 15 free I/O pins. The other larger microcontrollers have larger flash memories, greater EEPROM and RAM and more I/O pins. It is also assumed that you have installed the ‘demo’ version of the CodeVisionAVR C compiler in the C:\cvavr directory and that you have installed the Atmel tools for programming (AvrProg.exe) and debugging (AvrDebug.exe) somewhere on your computer. It goes without saying that you have to know how to access these programs and that you are familiar with the Windows 95, Windows 98 or Windows NT operating system that you are using.

Upload: letanbaospkt06

Post on 10-Apr-2015

1.562 views

Category:

Documents


8 download

TRANSCRIPT

Page 1: CodeVisionAVR C Compiler2

Introduction

This tutorial provides an introduction to the capabilities of the DHP TechnologyCodeVisionAVR C Compiler and Integrated Development Environment. It does notpretend to show all the features of this development system – it is just what it says in thefirst line; an introduction.

This is not a tutorial for C - we assume that you know it – nor is it a tutorial forassembly language for the AVR series of microcontrollers. We assume that you are atleast somewhat familiar with the operation of the 90S series of AVR microcontrollers. Inthis tutorial, we will just give you some examples of using the CodeVisionAVR Ccompiler and all its tools for the easy compilation of programs for the AVR series.

For microcontrollers, the best way to learn is to actually program some devices todo some simple functions. There are many development boards available; severalcompanies make them. We have designed this tutorial around Atmel MicrocontrollerStarter Kit Development Board (we’ll just abbreviate this to AVRDB) because we thinkit is the board you are most likely to have. This small unit and inexpensive unit is readilyavailable and it may be used to program and run programs for all the Atmel 90S series ofmicrocontroller chips. The exercises are designed around a 90S2313 microcontrollerplugged into this board but will work equally well with any of its larger brothers – the90S4414 or the 90S8515. The 90S2313 has 2K bytes (1K instructions) of flash memory,128 bytes of EEPROM, 128 bytes of RAM and 15 free I/O pins. The other largermicrocontrollers have larger flash memories, greater EEPROM and RAM and more I/Opins.

It is also assumed that you have installed the ‘demo’ version of theCodeVisionAVR C compiler in the C:\cvavr directory and that you have installed theAtmel tools for programming (AvrProg.exe) and debugging (AvrDebug.exe) somewhereon your computer. It goes without saying that you have to know how to access theseprograms and that you are familiar with the Windows 95, Windows 98 or Windows NToperating system that you are using.

Page 2: CodeVisionAVR C Compiler2

2

2

Tools

Before starting with some programming, we first need to set up the IDE to makelife easier for ourselves later by configuring several ‘tools’. These tools will allow us toprogram the target microcontroller and to debug it using a simulator all from within theCodeVisionAVR C Compiler window. It is beyond the scope of this tutorial to describethe detailed use of these tools. We will merely describe how to set them up so that theycan be accessed from within the CodeVisionAVR C Compiler window.

If you have the Atmel AVRDB, you will also have received or can downloadthrough the Web, the two main tools; AvrProg.exe for programming the AVRDB andAvrDebug.exe for simulating and debugging assembled code. If you have some otherdevelopment system, it will have a program for programming the target microcontroller.We need to tell the CodeVisionAVR C Compiler system where these files are located onyour computer.

You use the Tools | Configure pull-down window to add tools to theCodeVisionAVR C Compiler window. For example, let’s assume that you have theAVRDB and want to add the Atmel programming program, AvrProg.exe, to your toolbox. Pulling down the Tools menu and clicking on Configure gives you the followingwindow. You click on the Add button and a window opens up to allow you to point to

the desired file which youwish to add to the tool box:AvrProg.exe. Once youhave selected it, you canchange the settings as youlike. If you intend to gothrough the programsdescribed in this tutorial,you will want to change thesettings so that the initialdirectory whichAvrProg.exe goes to is thedirectory containing all thetutorial programs;C:\cvavr\examples\Tutorial.

Later, in the course of this tutorial, when you want to actually program a targetmicrocontroller on the AVRDB, you just pull down the Tools menu and click on theAvrProg.exe selection to run this program. You may add other programming tools orremove them at a later time.

Page 3: CodeVisionAVR C Compiler2

3

3

The CodeVisionAVR C Compiler produces files in a formatwhich is compatible with the Atmel simulator/debugger programwhich they call Studio and whose running program is AvrDebug.exe.You can add this tool to the environment by clicking on the Settingspull-down menu and selecting Debugger. You will then be promptedfor the location of AvrDebug.exe. Once this has been set, you can runthe Studio program from within the CodeVisionAVR C Compilerwindow by clicking on the ‘debug’ button shown to the left.

Finally, the 90S series of microcontrollers were designed to be programmed viaan SPI port. This is called ISP or ‘In System Programming’. The programming is donewhile holding the RESET- line of the microcontroller low and sending data to and fromthe microcontroller serially. Using ISP means that the target microcontroller mounted onits target board may be programmed in place via a 10 pin header mounted on the board.The Kanda STK200/300 series of ISP programmers are simple devices which consist of alatch mounted on a ‘dongle’ which plugs into the computer’s parallel port. They have acable terminating in a ten-pin IDC (insulation displacement connector) which plugs intothe header on the target board. More details on ISP programming are available in Atmelapplication notes and at http://www.avr-forum.com/avrsource.html. A circuit schematicfor this dongle is available athttp://members.xoom.com/_XOOM/volkeroth/dongle_e.htm.

The CodeVisionAVR C Compiler system provides ISP usingthe STK200/300 dongle. It is accessed through the ‘ChipProgrammer’ button shown to the left. NOTE: you must first add theisp.exe program to the Tools menu in a way similar to that describedpreviously to add the Atmel prog.exe program. After doing so andwith the STK200/300 dongle in place, you just need to click on thisbutton in order to program the microcontroller. Doing so will bringin a window allowing one to access the microcontroller via the ISP

interface. This window is shown here.Using the facilities of this window, onemay read the existing program in themicrocontroller, erase it and/orreprogram it from a file. One may alsoread and/or reprogram the contents ofthe EEPROM on board themicrocontroller. The flash memorymay be programmed from Intel formatHEX files, from Atmel format .ROMfiles or from binary files. TheEEPROM may be programmed from.EEP files, from HEX files or frombinary files.

Page 4: CodeVisionAVR C Compiler2

4

4

One can also access the signature bytes in the microcontroller and lock the flash memoryto prevent unauthorized access to the program. Once locked, the microcontroller mayonly be reprogrammed by completely erasing it first.

Page 5: CodeVisionAVR C Compiler2

5

5

Simple Program

It is customary to write, as a first program, one that puts “Hello world!” on someoutput device. Since your first device will be just the AVRDB with a single 90S2313 init, this is not really realistic. Instead, we will write a first program in which LED’s arecontrolled by push-button switches; all these devices are already on the board.

After starting theCodevision compiler, youwill see this window.

Using File | Opencommands, ‘open’ the‘Tutor1.prj’ project in the‘C:\cvavr\examples\Tutorial’directory. You will then seea window with the C codefor this first project. It willcontain the code shown inthe next window. The filename for this code istutor1.c.

Page 6: CodeVisionAVR C Compiler2

6

6

Before going any further, let’s look at this code. The first four lines after theopening comment are extensions to C which tell the compiler about the ports of the AVRseries. From the viewpoint of the programmer, they assign a variable name to amicrocontroller I/O register. The port D input register, for example, is given the name‘PIND’ in the first of these four lines and is given the appropriate internal microcontrolleraddress of (hex) 10.

In the main procedure, we first declare an unsigned character variable which wename ‘data’. This will be used as our temporary storage when we ‘read’ the switch port.The next two lines of code show how easy it is to access I/O registers in this compiler.We want to store the hex byte $00 into the data direction port of port D since we wantport D to be all inputs and to store the hex byte $FF into the data direction register of portB since it is to be an output port. In assembly language, we would accomplish this bysome code like:

clr r17 ; clear all data bitsout DDRD,r17 ; put this into port D DDR to make these all inputsser r17 ; set all data bitsout DDRB,r17 ; put this into port B DDR to make these all outputs

Here, we just state:

DDRD=0x00;DDRB=0xff;

because we have already defined, with the ‘sfrb’ statements what I/O register addresscorresponds to the variable names, DDRB and DDRD.

The next part of the program is a ‘while’ loop which will run forever since thecondition is always true (non-zero). Inside the loop, in the first instruction, we read thestatus of the switches:

data = PIND;

The ‘data’ byte will just contain the value at the input to the port D pins. This simplestatement shows how we ‘read’ an I/O register and transfer its value to a variable in the Ccode. Easy, isn’t it?

When a push-button switch is pressed, the pin is grounded – an unpressed switchis pulled high by pull-up resistors on the AVRDB. So, the unsigned char variable, data,will have bits which are logical ones for unpressed switches and logical zeroes forpressed switches.

Again, to show how easy it is to access I/O registers in the micro, the next line:

Page 7: CodeVisionAVR C Compiler2

7

7

PORTB = data;

just stores this value into the output port. The output pins are connected to LED’s whichgo to the +5V supply through 680 Ohm resistors. When an output pin is low, thecorresponding LED will be lit; when the output pin is high, the LED is not lit.

These two lines of code, inside the endless loop, will cause the LEDcorresponding to any particular switch to be lit as long as the switch is pushed. You canpress any number of switches simultaneously and the corresponding LED’s will be lit.

Now, we need to compile this program. A section ofthe tool bar is shown to the left. The two buttons shown arethe ‘Compile’ button and the ‘Make’ button. The ‘Compile’button does just that – it compiles the source file. Click onthis to see what happens. You should get a notice, shownnext, telling you that the program has compiled successfullywith no errors and no warnings. The notice also tells youhow much RAM memory has been used, the size of the

stacks and how much space has been allocated for global variables. It also causes theassembly language file to be generated and stored on disk. In this case, since the Cprogram was named tutor1.c, the assembly language file which is generated is namedtutor1.asm.

If you clicked the ‘Make’ button instead ofthe ‘Compile’ button, you would again cause thecompiler to operate and to generate the assemblylanguage file but you would also subsequentlyinvoke the assembler which then generates theobject code either as an Intel hex file or some otherformat. We’ll discuss selecting these options alittle later. For now, for this project, the option hasbeen set to generate an Intel hex file which will benamed tutor1.hex. This may now be loaded intothe target 90S2313 microcontroller with the AVRprogramming tool named AVRProg.exe; orwhichever other programmer you use.

The assembly language .asm file isgenerated in a format compatible with the AVR

assembler. This is a very simple assembler with limited macro capabilities butsophistication is not required here – the compiler does all the hard work. Afterprogramming is complete, the program will run by itself and you can now light any LEDby pressing the corresponding switch on the AVRDB. You can change the targetprocessor from a 90S2313 to a 90S4414 or a 90S8515 without changing any of the codeand the program will still work correctly because the addresses assigned to the various

Page 8: CodeVisionAVR C Compiler2

8

8

registers are the same for all these microcontrollers. If you have one of these, try it.There will be one difference in the operation of the program and that is that the LEDcorresponding to port B, bit 7 will not always be lit. Can you think why it is always litwhen the microcontroller is the 90S2313? Hint: the 90S2313 only has 15 I/O pins andthere are eight switches and eight LED’s – what difference does this make?

If you look at the structure of this very simple program, you will see an overallstructure which is very common in microcontroller programs. There is first a sectiondefining the attributes of the microcontroller being used; in this case, it is the four ‘srfb’statements. This does not generate any code but instead allows the compiler to do itswork. Inside the body of the ‘main’ procedure, we have the code itself which is really intwo sections. The first two lines are the ‘initialization’ portion of the code which sets upthe microcontroller to do its task. Inside the endless loop, is the action which themicrocontroller is supposed to do. This basic structure, an initialization sequencefollowed by an endless loop, is the quintessential microcontroller program.

How can we improve this simple program? Firstly, we shouldn’t have to look upthe addresses of the particular registers in order to write the ‘srfb’ statements. Instead,we’d normally like to do this sort of definition with an ‘include’ file. We can do this withthe Codevision compiler and the author of the compiler has thoughtfully provided‘include’ files for all the common AVR microcontrollers. To see how this works, justedit the code in the tutor1.c file by removing the four ‘srfb’ statements and replacingthem with the single statement:

#include <90s2313.h>

Remember that C is case sensitive so type the line exactly as shown. 90s2313.h is a filein the ‘inc’ folder of the cvavr directory. Try compiling the program again. It shouldgive you exactly the same result. You will notice that the number of lines which werecompiled is greater because this include file contains many more register definitions thanjust the four we had in the original program. However, the compiled code will be exactlythe same length because the definitions generate no code.

You can open the ‘include’ file and look at it with the editor if you want to refreshyour memory about what registers are available, etc. Note that, using the conventioncommon to AVR programmers, all the registers use upper-case letters exclusively. Youmust be careful not to accidentally reuse some of these names for variables in yourprogram.

Finally, let’s see what happens when we have errors in the code. For the purposesof this exercise, let’s delete the last D of the name, PIND, in the first of the two linesinside the endless loop. This line will become:

data=PIN;

Page 9: CodeVisionAVR C Compiler2

9

9

Now, when you click the ‘Compile’ button, you will see that there is an error and awarning. At the bottom of the compiler window, there is a white area which will nowcontain two lines. The first of these will tell us what the error is and the second willdescribe the warning. The warning is simple; we defined PIND with an ‘sfrb’ statementbut never used it. The error statement is a bit more obscure but still understandable – wehaven’t defined the name PIN but we’ve used it in a statement. If we click on this errorstatement, the offending line will be highlighted in the edit window. Sure enough, it isthe line that we altered. If we alter it back again, the program will compile correctly.

Note that we didn’t have to save the source file anywhere along the way. Whenthe ‘compile’ button is clicked, the source file is saved automatically. This is terriblyconvenient but also can cause problems. If you have a source file which works and youwant to make extensive changes in it, you really should save it somewhere first with adifferent name. That way, your changes won’t overwrite an already working program.

Finally, let’s do one more thing before closing this project and that is to modifythe program to turn out that annoying bit 7 LED which is always on. If you haven’tfigured it out, let me just give you the solution which is to replace the line:

data=PIND;

with

data=PIND | 0x80;

Then, remake the program and reprogram it into the AVRDB and you’ll see that the lastbit 7 LED is always off. Finally, to make an even shorter program, we can replace thetwo line program inside the while loop with just a single statement:

PORTB = PIND | 0x80;

Doing this, we have a program which now needs no declared variables so we caneliminate the declaration of the unsigned char, data.

Now, close the project in the editor giving you a blank window for the nextexercise.

Page 10: CodeVisionAVR C Compiler2

10

10

Projects, variables

A ‘project’ is just a convenient way of keeping together a group of files for a real,physical project. It also provides a convenient way of specifying the tools needed for aproject and the exact configuration for that project. We’ll start a new project in thisexercise. During the course of working with this project, we’ll examine how thecompiler handles the three types of memory in a 90S series microcontroller: RAM,EEPROM and flash (ROM).

First, you will want to create a new source file so, using the File | New pull down,select Source file as the type you want to create and a text window will now open up. Atthis point, it is useful to add something to the file while it is open so I usually just writean opening comment describing the project, giving the date, etc. Then save the file using

Save As and give it anyappropriate name.

Now, we will create aproject using this source file.Use File | New again but thistime, select the ‘Project’ radiobutton because you wish tostart a new project. You willbe asked to give this project aname – I opted to call thistutor2. You will get a newwindow like the one on theleft. Click the Add button andselect the source file whichyou’ve just created as the one

to add to this project.

Clicking the compilertab gives you access to theproperties of themicrocontroller you will usefor this particular project. Forthe example which we will do,select the AT90S2313. Thedefault options for thisprocessor are 128 bytes ofRAM (this is determined by themicrocontroller itself) and astack size of 32. The top of thehardware stack is normally

Page 11: CodeVisionAVR C Compiler2

11

11

selected to be the top of RAM and that is the case for this compiler. The hardware stackis the stack used by the microcontroller during subroutine calls, pushes and pops. Thedata stack is used by the compiler for storing variables and you will want to set it to somevalue less than the total ram size – in the example shown, I have set it to 96 bytes leavinga hardware stack of 32 bytes. The quantities can be modified later if necessary. When aprogram is compiled, the little box showing the compilation summary shows how thevariables are allocated in the data stack.

Clicking theAssembler tab gives youaccess to the properties of theassembler output. If you’reusing AvrProg.exe as yourprogramming tool, it is mostconvenient to choose the IntelHEX format for the assembleroutput. This is shown to theleft.

Finally, click on theOK button to close thiswindow. You have nowcreated a project with a

source file. In any real life project, one normally will want to make extensive notesassociated with various aspects of the project and have them attached in some way to theprogramming itself. These may contain details of the hardware development, problemsencountered, etc. Along with the source file in a project, the IDE automatically creates atext file with the project name and the file extension of .txt. This file is useful for makingthese notes in the course of developing a project.

For this project, we will expand on the very simple program used previously inorder to introduce some different concepts. The aim of this project is to make a systemvery similar to the previous one except that, after pressing a switch, the LED stays lituntil another switch is pressed. There are probably a hundred ways to do this and it iseven possible that a competent (??) C programmer could do it in one very convolutedstatement. For the purposes of illustration, it is coded in a very formal way as shown inthe next text box.

In this simple example, we will not run out of memory space and speed will notmatter so we can indulge ourselves in the matter of style even though it is overkill forsuch a simple program!

Here, the program is written in the form of an initialization procedure followed bythe endless while loop. Inside the loop, the program reads the switch port. If the byte isnot $FF (i.e., if a switch has been pressed) AND if the switch pressed is different from

Page 12: CodeVisionAVR C Compiler2

12

12

/*Revised version of tutor1.c in which the LEDs stay on afterrelease of the switch*/

#include <90s2313.h>

//// global character declarations//

unsigned char data; // global byte giving last switch press

//// Prototype declarations//

void initialize(void);unsigned char read_switch_bank(void);void write_to_LEDs(unsigned char ch);

//// main program//

void main(void) { unsigned char ch;

initialize();

while (1) {

ch = read_switch_bank(); if ((ch != data) && (ch != 0xff)) // see if it has changed { data = ch; write_to_LEDs(data); } } }

//// procedure and function definitions//

void initialize(void) {

data = 0xff; // starting value DDRD = 0x00; // all inputs DDRB = 0xff; // all outputs PORTB = 0xff; // start by turning all LEDs off }

unsigned char read_switch_bank(void) { unsigned char ch;

ch = PIND | 0x80; // the $80 makes bit 7 a logical 1 return (ch); }

void write_to_LEDs(unsigned char ch) { PORTB = ch; }

the previous one, the value saved as theglobal variable ‘data’ is changed and itis written to the LED port. When thisprogram is compiled, you get thefollowing compilation summary:

You can see that global variables areallocated space at the bottom of thehardware stack and therefore reducehardware stack space. The data stack isreserved for local variables like theunsigned char variable, ch, located inthe ‘main’ procedure. When compilinglarge programs, you should use thiscompilation summary to keep an eye onhow much room you have for thehardware stack. You can alter this atany time by using the Projects |Configure pull-down menu to changethe size of the data stack for theparticular project.

We have seen how the compilerallocates variables in RAM. How doesit allocate variables in the flash memory(ROM)? Since flash memory cannot bealtered in the course of a runningprogram, flash variable storage is onlyuseful for constants. A commonexample might be the strings which areto be displayed by a running program onan LCD display. We can invoke this

Page 13: CodeVisionAVR C Compiler2

13

13

kind of variable by using the keyword flash in the declaration as follows:

flash char SignOnMsg[] = “Greetings”;flash char ErrorMsg[] = “Error # “;flash int OneThousandPi = 3142;

and so on. The keyword flash ensures that the compiler will put the constant into flashmemory space.

As an example of this, let’s modify the preceding program so that themicrocontroller, when turned on, displays alternate LED’s lit. We may do this by addinga line just after the global declaration as follows:

flash unsigned char TurnOn = 0xaa;

And, in the initialization procedure, initialize(), we change the first line to read:

data = TurnOn;

and the last line to read:

PORTB = data;

Now, the program will cause the byte $AA to be stored into the LED’s when first turnedon and then subsequently operate as before.

Of course, this is a trivial use of the flash keyword since we could have just aseasily just loaded the variable, data, directly with the value of 0xaa in the first line ofinitialize(). Nevertheless, it does illustrate how one may use flash memory to storeconstants. In a complex program, it makes sense to declare constants at the front of theprogram and not write them into the program itself; it is easier to make changes laterwithout having to search through the whole text.

EEPROM can be accessed in exactly the same way by writing the keywordeeprom before the otherwise normal declaration of a variable. The compiler produces allthe code necessary for the storage and retrieval of variables from eeprom. This can beillustrated by modifying the program one more time so that, when turned on, it‘remembers’ the last switch reading and comes up showing that LED lit. We’ll start bychanging the declaration for the byte, data, to:

eeprom unsigned char data = 0xaa;

The compiler will then allocate memory space in the EEPROM for this byte and willgenerate an EEPROM *.eep file with the initial value stored in it. You may declarearrays or any type of variable to be of type eeprom. If you do not assign initial values,the compiler will generate a warning.

Page 14: CodeVisionAVR C Compiler2

14

14

In the initialization procedure, we will not need to initialize the byte, data, as ithas the initial value given to it by the .eep file. It will retain its value when the power isoff so the next time the power is turned on, it will have the last stored value. Tosummarize, we just need to remove the first statement in the initialize() procedureleaving:

DDRD = 0x00;DDRB = 0xff;PORTB = data;

Now, when we program the microcontroller, we will also need to program the EEPROMwith the generated .eep file. When running, the program will now store the value of datainto EEPROM and, when turned on, load the initial value from the EEPROM. Acautionary note: Atmel warns that the first byte in the EEPROM array is occasionallycorrupted during a power-off/power-on sequence. Since the compiler allocates EEPROMvariables in the order that they are declared, it is prudent to first declare an EEPROMvariable which is never used in the program to occupy the first byte. This will result in acompiler warning which can be ignored.

Data hiding is considered to be one aspect of good programming and we can dothat by saving well-designed subroutines and functions in a library somewhere and just‘INCLUDEing’ them in the source code. This compiler always looks for files given in a#INCLUDE statement in the \inc sub-directory. In the program we have here, we can‘cut’ the procedures and functions declared after the main() procedure and open a newfile and save them there. This can then be given a name (such at “tutorial_stuff.h”) andsaved in the \inc directory. In the main program, then, we somewhere just need to write astatement:

#include <tutorial_stuff.h>

to give the compiler access to it. The program will compile normally and generateexactly the same code as before.

Page 15: CodeVisionAVR C Compiler2

15

15

Assembly Interface

Assembly code is used for one or more of three reasons: speed, compactness orbecause some functions are easier to do in assembler than in a higher level language. It iswell known that using a high level language always results in the faster programdevelopment but there are times when, for the reasons stated above, one wants to useassembly language.

The CodeVisionAVR C Compiler, like other compilers meant for microcontrollerdevelopment, has an easy interface to assembly language. Assembler code may beimbedded anywhere in a C program by using the following ‘inline’ construct:

#asm….….Assembly language code….….#endasm

The only precaution one must observe is to use only registers r4 through r20 inclusive; atotal of 17 registers are thus available for assembly language use. The other 15 registersare used by the compiler and using of some of them in the inline assembler code mightcompromise the rest of the program. The use of assembly language in a C program isdescribed in the CodeVisionAVR C Compiler help files. We will start with a very simpleexample code here. Let’s suppose we want to reproduce the very first program used inthe project, tutor1 but we want to execute the read switches, write to LED loop as quicklyas possible. We will get this speed writing the program mostly in assembly language.So, start a new project (use the default settings for data stack, etc.) which we will calltutor3 and create a source file, also named tutor3.c which contains the code shown next.Here, the skeleton structure is in C but the whole main() procedure is written in assemblylanguage in order to execute it as rapidly as possible.

This is a very simple program but it does illustrate the main features:

1. you have to give the assembler any used addresses with .equ statements. It doesn’thelp to use #include <90s2313> because those sfrb statements are instructions to thecompiler, not the assembler.

2. do not use r0 through r3 and r21 through r31 – any other registers are OK

Note the label used in the infinite loop here. The compiler creates labels for addressesthat it uses when it is creating an assembly language file. However compiler labels

Page 16: CodeVisionAVR C Compiler2

16

16

/*

Tutor3 program - reproduces the tutor1 programbut does it as quickly as possible. The program is verysimple:

- an initialization section- an infinite loop of read switch, transfer to LED- */

void main(void) {

#asm;;; first,we have to give the assembler the addresses of the ports; then, the initialization section;; we'll use register r16 throughout;;.equ DDRB= $17.equ DDRD= $11.equ PORTB= $18 ; the output LED port.equ PIND= $10 ; the input switch port;; initialize; ldi r16,0 out DDRD,r16 ; set PORTD to all inputs ldi r16,$ff out DDRB,r16 ; set PORT B to all outputs;; now, for the infinite loop;forever_loop: in r16,PIND ; read the switches ori r16,$80 ; set bit 7 out PORTB,r16 ; store result into LED port rjmp forever_loop #endasm }

Instruction Number of Cycles

ldi r16,n 1waitLoop:

ld r17,x 2 \ld r17,x 2 |.... |.... m lines = 2m cyclesld r17,x 2 |ld r17,x 2 |ld r17,x 2 /dec r16 1brne waitloop 2 except for the last time when it is one

Total time = 1 + n(2m + 3) -1 = 2nm + 3n cycles

always start with the underline character.For this reason, it behooves one not to usethe underline as a first character in a label.

In this example, we have let thecompiler create all the code necessary forsetting up the hardware and softwarestacks. It will also fill the RAM data spacewith zeroes before jumping to thebeginning of the main() procedure. Noneof these are necessary for this simpleprogram but might be in a morecomplicated program. Finally, it goeswithout saying that one has to conform tothe syntax for the AVR Assembler – a verysimple assembler.

Let’s do our next project as onewhere we want to use assembly languagebecause it allows us to do something whichis difficult to do in a higher level languageand that is to generate a well defined timedelay. In C, we can always generate a timedelay by simple for (i=0; i< n; i++) loopsbut we are never sure of how long it willtake. In a low level language likeassembly, we can calculate exactly howlong a sequence of code will take.Consider the following problem. Supposewe want to write a program in which thereare a series of delays each of which is some

integral number of milliseconds long (this is better done by using interrupts as we shallsee in the next section). For now, we’ll do it this way as an illustration of how to

interface to assemblylanguage. Let’s start bywriting a procedure whichwill take precisely onemillisecond to execute.

Consider the code inthe adjacent box. Here wehave a tight loop whichcontains a number ofstatements which load r17with whatever is locatedwhere the X register is

Page 17: CodeVisionAVR C Compiler2

17

17

/*Tutor4 - a program to blink the LED array at onesecond intervals

*/

#include <90s2313.h>

void WaitAMilliSec(void);

void main(void) {

int i; // our counter

//// initialize PORT B to all outputs// DDRB = 0xff;//// light every other LED// PORTB = 0xaa;

while (1) { for (i=0; i < 1000; i++) WaitAMilliSec(); // call it 1000 times PORTB = ~PORTB; // invert the LED display } }

void WaitAMilliSec(void) {//// use r16 and r17 since they are not used in the compiler//

#asm ldi r16, 210wait_loop: ld r17,x ; do this eight times ld r17,x ld r17,x ld r17,x ld r17,x ld r17,x ld r17,x ld r17,x dec r16 brne wait_loop nop nop nop; no RET needed#endasm }

pointing. We have chosen this instead of a string of NOP’s because a NOP only takesone cycle while the load r17 statements take two cycles each. As this will be written as asubroutine, there will be a RET instruction at the end (4 cycles) and, of course, the callingprogram will have to execute a ‘rcall’ statement which takes 3 cycles. The total time, incycles, for this procedure is thus n(2m+3) + 7 cycles. For the AVRDB, the crystal clockruns at 4 MHz so each cycle is 0.25 microseconds. To generate a total delay of 1

millisecond, we will therefore need thetotal number of cycles to be 4000.Therefore, we want n(2m+3) = 3993.In order to conserve space, we alsowant m to be as few as possible. Theother constraint is that n must be lessthan 256 because it is a single byte.Suppose m were 8, and n were 210,then the total number of cycles wouldbe 3990 – just 3 short of what wewant. We can add 3 NOP’s after theloop and before the RET in order tomake the delay exactly equal to 4000cycles.

So … let’s start a new projectand write a program which will blinkthe LED’s once per second using ouraccurate one millisecond procedure.My version is shown in the adjacentbox. When this is compiled andloaded into the ‘2313, you will see thelights blink alternatively atapproximately once per second. I’msure you can guess why I saidapproximately. Even though we’vegone to a lot of trouble to make thesubroutine take exactly onemillisecond, the problem is that thereis some overhead in the for (……..)loop and that is going to mean that thelights will blink a little more slowlythan once per second. If you examinethe .asm or .lst files which thecompiler produces, you will see thatthe overhead is only a few instructionsbut those few instructions will addseveral microseconds to eachmillisecond and we will not have theaccuracy we’ve gone to so much

Page 18: CodeVisionAVR C Compiler2

18

18

#pragma warn-void WaitMilliSecs(int n) {// n is passed on the y stack. The MSB will be located at y+1// and the LSB will be located at y// we'll use the r18, r19 pair as counters, r18 = MSB, r19 = LSB// we'll set them to negative of the count and then increment// up to zero#asm clr r18 clr r19 ld r17,y ; get LSB from stack sub r19,r17 ; put negative value in r19 ldd r17,y+1 ; get MSB from stack sbc r18,r17 ; put negative into r18inner_loop: rcall wait1msec ; takes precisely 1 mS to return inc r19 ; least sig byte - one cycle brne inner_loop ; two cycles except the last time inc r18 ; most sig byte brne inner_loop#endasm }#pragma warn+

#asm;; subroutine _wait1msec takes exactly one millisec; use r16 and r17 since they are not used in the compiler;wait1msec: ldi r16, 210wait1msec_loop: ld r17,x ; do this eight times ld r17,x ld r17,x ld r17,x ld r17,x ld r17,x ld r17,x ld r17,x dec r16 brne wait1msec_loop nop nop nop ret#endasm

trouble to obtain.

The solution is to write a procedure, in assembly language, to which we pass thenumber of milliseconds we want to delay. The prototype declaration will be:

void WaitMilliSecs(int number);

With this procedure, we can delayan accurate second by calling it witha parameter of 1000, or half a secondwith 500, and so on. To write thisprocedure, we will need to know howvariables are passed to functions orprocedures. There is a file in theCodeVisionAVR C Compiler helpsystem which describes this in somedetail. Parameters are passed tofunctions and procedures on the datastack. The pointer to this stack is theY index register pair and the stackbuilds downward. Parameters arestacked in order of declaration withinthe brackets and are stacked MSBfirst, then LSB. Procedures bydefinition have no return value butfunctions do. Functions are returnedin specific registers: char variablesare returned inr30, int and unsignedint variables are returned in the r30,r31 register pair with r30 having theLSB and r31 the MSB. Long signedand unsigned int’s are returned infour registers as follows: LSB – r30,next most – r31, next most – r22,MSB - r23. In the procedure we willwrite, we will not return any valuesso we do not need to use these. Thecode in the adjacent box (just theprocedure) shows how we recoverthe passed parameter in this case.

Page 19: CodeVisionAVR C Compiler2

19

19

/*Test program for unsigned long integer incrementroutine done in assembly language.*/unsigned long int number;unsigned long int IncLong(unsigned long int n);

void main(void) { number = 1234567; number = IncLong(number); while (1); }

#pragma warn-unsigned long int IncLong(unsigned long int i) {#asm;;; the stack will look like this:;; MSB; 2nd MSB; 3rd MSB; LSB <- Y;; with Y pointing to the least significant byte; of the variable; ldd r23,y+3 ; get MSB into r23 ldd r22,y+2 ; next into r22 ldd r31,y+1 ; next into r31 ld r30,y ; least into r30 inc r30 ; increment LSB brne done_IncLong inc r31 brne done_IncLong inc r22 brne done_IncLong inc r23done_IncLong:;; the results are already in the proper registers so; we can just return;; NOTE - we do NOT alter the data stack point, Y;#endasm }#pragma warn+

Strictly speaking, this solution is also not quite exact. Firstly, there is someoverhead in calling the WaitMilliSecs procedure from the calling program. Secondly,there is some overhead inside this procedure – both in recovering the variables from thestack and in the loop which calls the very precise 1 mS delay subroutine. However, theselatter two have delays which can be calculated and compensated for – we won’t bother togo through that as it is tedious; it is possible, though. Then, we just have the overheadfrom the calling program to contend with and this will just be a few cycles – a few µS.Since the crystal clock in a typical microcontroller is rarely more accurate than one partin 105, a few µS in a delay of several tens or hundred of mS is not significant. It might bea problem if we needed accurate delays of just a few mS.

We will leave this problem not quitesolved. It was intended as an exercise in passingvariables to an assembly language proceduresand functions rather than a discussion of timingaccuracy.

As a semi-final exercise, let’s look atpassing variables back to the calling program.Imagine a program where we want to incrementan unsigned long integer variable as quickly aspossible - perhaps because it is often called in aprogram. Incrementing a four-byte variable in Cinvolves a four byte addition and so takes sometime; we will be able to do it much faster inassembly language. The prototype function willbe:

unsigned long int IncLong(unsigned long int i);

The code for this function and its calling programis shown in the adjacent box. Because we’ll bereturning the function value in registers r22, r23,r30 and r31, we can use these registers in thebody of the code without worrying about the factthat the compiler also uses them – the compilerwill be expect them to be changed. We use the#pragma warn- before the function and #pragmawarn+ after so that the compiler will not generatea warning. It would normally do so since it doesnot see a ‘return(i);’ statement anywhere in thefunction.

Finally, there is one other topic worthconsidering and that is accessing global variablesin assembly language sections. The

Page 20: CodeVisionAVR C Compiler2

20

20

CodeVisionAVR C Compiler assigns memory locations in RAM to global variables. Thecompiler uses the same name you give to the variable except that an underline characteris prefixed to the name. For example, if you have a list of global variables in a programsuch as:

char ch, temp;unsigned int value;

the compiler will generate assembly code which reserves storage in RAM with the names_ch, _temp, and _value. Multi-byte variables such as ‘value’ in the list above, are storedwith the least significant byte at the lowest address and successively more significantbytes at successive addresses. Because these global variables have been assigned storagespace by name, we may access them in assembly language directly. For example, giventhe previous declarations, if we wanted to load a register, say r17, with the character, ch,we could do it with an assembly language statement:

lds r17, _ch

Similarly, if we wanted to store a register, for example, again, r17, into the mostsignificant byte of the integer variable, value, we could do it with a statement like:

sts _value+1, r17

Please note that this technique only works with global variables. If you have internalvariables inside a procedure or function, they are stored on a stack and hence notaccessible by name.

Page 21: CodeVisionAVR C Compiler2

21

21

Interrupts

The interrupt structure of the AVR 90S series of microcontrollers is completelyvectored. The first few locations in the flash memory are reserved for vectors to thevarious interrupt service routines. In assembler, a program normally starts with thesequence:

rjmp reset ; where it goes on RESETrjmp INT0 ; where it goes if external interrrupt 0 line is activated…….…….

and so on. Each of these instructions takes a single word in flash memory and thenumber of possible vectors depends on the processor; there are 11 for the 90S2313 and13 for the 90S8515, for example.

To make use of these interrupt vectors, the compiler allows one to write specialinterrupt procedures which are designated by writing the reserved word, interrupt, beforethe procedure definition. The formal syntax is:

interrupt [vector number] void procedure_name(void)

where the vector number is the one given in the Atmel data sheets. These numbers startwith 1; vector 1 is the RESET vector. For example, vector number 3 is for the externalinterrupt request 1, INT1. In general, the numbers differ for each microcontroller so,when writing an interrupt service procedure, you need to be sure you’ve chosen the rightone for the particular microcontroller you are using. Because the interrupt service routineis never called by anything, don’t waste too much time trying to think of a neat name forit; any old name will do.

At the beginning of an interrupt procedure, the compiler automatically insertscode to push all the registers it uses before starting the code and automatically pops thembefore returning via an RETI instruction. This means that you do not have to worryabout saving anything.

In programs which use interrupts, the basic structure of the main() procedure isthe following:

- initialize the microcontroller normally- set up the registers which control the interrupt(s) being used- enable interrupts- do the infinite loop which is the main program

The author of the compiler has written an example of code illustrating the use ofinterrupts procedures in a project in the Examples\led directory. You should examine this

Page 22: CodeVisionAVR C Compiler2

22

22

/*

Tutor 7 project - interrupt procedures

*/

// I/O register definitions#include <90s2313.h>#define fmove 2#define xtal 4000000

unsigned char led_status=0xfe;

void initialize(void);

void main(void) {

initialize();

while (1)PORTB=led_status; }

interrupt [6] void timer1_overflow(void) { TCNT1=0x10000-(xtal/1024/fmove); led_status+=led_status; led_status|=1; if (led_status==0xff) led_status=0xfe; }

void initialize(void) { DDRB=0xff; PORTB=led_status; DDRD=0xff; PORTD=0;

TCCR1A=0; TCCR1B=5; TCNT1=0x10000-(xtal/1024/fmove); TIFR=0; TIMSK=0x80; GIMSK=0;

#asm sei#endasm

}

code. In the example, led.c, program, there is really no main program but just an emptywhile() loop – all the work being done is done inside the interrupt service routine. Amore common way of handling interrupts is to have the interrupt service routinescommunicate with the main program through global variables or ‘semaphores’. In thismode, the main program runs in the ‘foreground’ and doesn’t know anything about theinterrupts. The interrupt service routines just change the value of some global variablesto indicate that an interrupt has occurred.

In the next exercise, we will change theprogram above to explore the use ofinterrupts in this manner. The project,tutor7.prj, is a rewrite of led.prj inwhich all the initialization is gatheredtogether into one procedure. Thetutor7.c code is shown at the left. I haveremoved all the comment statements tomake the code a little smaller – refer toExample\led.c to see what eachstatement does. In tutor7.c, the globalvariable, led_status, is altered by theinterrupt service routine but nothing elseis done. In the main program, the valueof led_status is written to the LED bank.Compile and ‘make’ this file anddownload it into the AVRDB to verifythat it works in exactly the same way asled.prj does.

Now, let’s add another interruptroutine to change the pattern of thedisplay if one of the external interruptsis activated. The push-button switchesof the AVRDB are connected to PORTD and two of the pins in PORT D can beset to be external interrupts. Let’schoose the INT 0 interrupt which isactivated by bit 2 of PORT D. We willdefine a new global variable, called pb2,which has an initial value of zero andwhich is inverted every time theinterrupt occurs. We will also choose toactivate the interrupt on the falling edgeof the signal on bit 2 of PORT D. The

code of this interrupt routine and the modified program are given in tutor8.prj; this isshown below.

Page 23: CodeVisionAVR C Compiler2

23

23

/*

Tutor 8 project - interrupt procedures

*/

// I/O register definitions#include <90s2313.h>#define fmove 2#define xtal 4000000

unsigned char led_status=0xfe;unsigned char pb2 = 0;

void initialize(void);

void main(void) {

initialize();

while (1)if (pb2)PORTB=~led_status; else PORTB=led_status; }

interrupt [2] void INT0(void) { pb2 = ~pb2; // just invert it }

interrupt [6] void timer1_overflow(void) { TCNT1=0x10000-(xtal/1024/fmove); led_status+=led_status; led_status|=1; if (led_status==0xff) led_status=0xfe; }

void initialize(void) { DDRB=0xff; PORTB=led_status; DDRD=0; // set PORT D to inputs// timer 1 stuff TCCR1A=0; TCCR1B=5; TCNT1=0x10000-(xtal/1024/fmove); TIFR=0; TIMSK=0x80;// INT0 stuff MCUCR=0x02; // set bit 1 to enable interrupt on falling edge GIMSK=0x40; // enable interrupt on INT0

#asm sei#endasm

}

The program is very similar totutor7 except that another globalvariable has been added as describedbefore. In the initialization procedure,we also have to set up the INT0interrupt characteristics. When thisprogram is running, generating ageneral interrupt by pressing the push-button switch (bit 2 of PORT D – thisis the third switch from the low end)will cause the moving LED pattern toinvert. This (i.e., the INT0 serviceroutine) is a very simple interruptroutine and, in real life, we wouldwant to debounce the switches orotherwise ensure that the interruptsoccurred controllably. In this simpleroutine, because the switch is notdebounced, multiple interrupts can anddo occur and so the state of thevariable, pb2, is somewhatindeterminate.

For some microprocessors, youhave to disable the interrupts inside aninterrupt routine to make sure thatanother interrupt cannot occur whileyou’re servicing an earlier one. In thecase of the 90S series ofmicrocontrollers, the internal interruptenable flags are cleared when aninterrupt is serviced thus preventinganother interrupt from occurringunless you deliberately enable it in theinterrupt service routine code. Theflags are reset when the RETI isexecuted at the end of the routine thusre-enabling further interrupts.

Page 24: CodeVisionAVR C Compiler2

24

24

Bit-wise I/O

Setting and clearing I/O bits in a microcontroller is not as simple as it may seemat first glance. Consider the following case. Let’s imagine we have a controller with alot of I/O being handled by interrupt routines. Somewhere in the program, we want toactivate a relay which has been connected to bit 2 of PORTB. Let’s assume PORTB is ageneral output port and the other pins go to other devices and let’s also assume that therelay is activated by a logical zero and released by a logical 1. How do we do it?

Because we don’t know (or may not know) what the other pins on PORTB aredoing, we have to be sure that we don’t affect them. Therefore, we have to read the pinsof the port latch (PORTB) and then rewrite that same word back to the output latch,PORTB, after making bit 2 a zero. The code to activate the relay would be somethinglike:

data = PORTB;PORTB = data & 0xfb;

These two actions are shown here as separate statements to emphasize that it requires aread of a register followed by a write of the register after some internal operations. Whathappens if some of the other bits in the latch get changed in the time interval betweenthese two statements by an interrupt routine? The answer is that these pins will get putback to what they were before the interrupt routine took place – it is as if the interruptnever occurred. Note that changing the statement to:

PORTB = PORTB & 0xfb;

doesn’t help – the compiled code will still have a read of PORTB followed by a write toPORTB at a subsequent time. To be safe, we’d have to turn the interrupts off before thisoperation and then turn them back on after. This is awkward and may cause time-criticalinterrupt routines to fail.

The 90S series of microcontrollers have machine code instructions for setting andclearing individual bits in the I/O registers. This capability gets around the difficultydescribed above but, unfortunately, standard C doesn’t have statements which allow theuse of these direct instructions. The CodeVisionAVR C Compiler has an extension tostandard C to make use of this capability. The instruction:

PORTB.2 = 0;

clears bit 2 of PORTB and doesn’t affect the other pins. The instruction:

PORTB.2 = 1;

Page 25: CodeVisionAVR C Compiler2

25

25

sets bit 2 of PORTB and doesn’t affect the other pins. We can further clarify the code bywriting several ‘define’ statements such as:

#define relay_on PORTB.2=0#define relay_off PORTB.2=1

Then, in our code for the program, to turn the relay on, we just need to write thestatement:

relay_on;

and, to turn it off, the statement:

relay_off;

This not only gets around the problem with changing a single bit, it also contributes towriting clear, simple and easily understandable code.

We may also use single bit operations in reading ports. For example, in pollingthe UART receive register to see if bit 7 were set indicating that a character has beenreceived by the UART. In a normal C program, we would have a statement like:

if (USR & 0x80) ………etc There’s no problem with this but the function of the statement is not really very self-evident. The CodeVisionAVR C Compiler allows a more concise code for doing thesame thing:

if (USR.7) …..etc

and, to be even clearer, we can define the RXC bit being set in a statement like:

#define character_received USR.7

and, the ‘if’ statement can be written:

if (character_received) ……..etc

NOTE: this syntax, for both single bit write and single bit read, is only allowed for theI/O registers – these are register 0 through 31 inclusive. You cannot use this kind ofsingle bit operation, for example, to read from or write to the general interrupt maskregister, GIMSK, which is register number 59 (0x3b).