Codevision AVR Microcontroller TIPS by AbdulMalik54

VIEWS: 219 PAGES: 25

Codevision AVR Microcontroller TIPS

More Info
									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.
                                                                                               2



Tools

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

       If you have the Atmel AVRDB, you will also have received or can download
through the Web, the two main tools; AvrProg.exe for programming the AVRDB and
AvrDebug.exe for simulating and debugging assembled code. If you have some other
development 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 on
your computer.

      You use the Tools | Configure pull-down window to add tools to the
CodeVisionAVR C Compiler window. For example, let’s assume that you have the
AVRDB and want to add the Atmel programming program, AvrProg.exe, to your tool
box. Pulling down the Tools menu and clicking on Configure gives you the following
window. You click on the Add button and a window opens up to allow you to point to
                                                          the desired file which you
                                                          wish to add to the tool box:
                                                          AvrProg.exe. Once you
                                                          have selected it, you can
                                                          change the settings as you
                                                          like. If you intend to go
                                                          through the programs
                                                          described in this tutorial,
                                                          you will want to change the
                                                          settings so that the initial
                                                          directory which
                                                          AvrProg.exe goes to is the
                                                          directory containing all the
                                                          tutorial programs;
                                                          C:\cvavr\examples\Tutorial.

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




                                                                                               2
                                                                                        3




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

         Finally, the 90S series of microcontrollers were designed to be programmed via
an SPI port. This is called ISP or ‘In System Programming’. The programming is done
while holding the RESET- line of the microcontroller low and sending data to and from
the microcontroller serially. Using ISP means that the target microcontroller mounted on
its 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 a
latch mounted on a ‘dongle’ which plugs into the computer’s parallel port. They have a
cable terminating in a ten-pin IDC (insulation displacement connector) which plugs into
the header on the target board. More details on ISP programming are available in Atmel
application notes and at http://www.avr-forum.com/avrsource.html. A circuit schematic
for this dongle is available at
http://members.xoom.com/_XOOM/volkeroth/dongle_e.htm.

                            The CodeVisionAVR C Compiler system provides ISP using
                    the STK200/300 dongle. It is accessed through the ‘Chip
                    Programmer’ button shown to the left. NOTE: you must first add the
                    isp.exe program to the Tools menu in a way similar to that described
                    previously to add the Atmel prog.exe program. After doing so and
                    with the STK200/300 dongle in place, you just need to click on this
                    button in order to program the microcontroller. Doing so will bring
                    in a window allowing one to access the microcontroller via the ISP
                                                  interface. This window is shown here.
                                                  Using the facilities of this window, one
                                                  may read the existing program in the
                                                  microcontroller, erase it and/or
                                                  reprogram it from a file. One may also
                                                  read and/or reprogram the contents of
                                                  the EEPROM on board the
                                                  microcontroller. The flash memory
                                                  may be programmed from Intel format
                                                  HEX files, from Atmel format .ROM
                                                  files or from binary files. The
                                                  EEPROM may be programmed from
                                                  .EEP files, from HEX files or from
                                                  binary files.



                                                                                        3
                                                                                       4



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




                                                                                       4
                                                                                               5




Simple Program

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

                                                                      After starting the
                                                              Codevision compiler, you
                                                              will see this window.

                                                              Using File | Open
                                                              commands, ‘open’ the
                                                              ‘Tutor1.prj’ project in the
                                                              ‘C:\cvavr\examples\Tutorial’
                                                              directory. You will then see
                                                              a window with the C code
                                                              for this first project. It will
                                                              contain the code shown in
                                                              the next window. The file
                                                              name for this code is
                                                              tutor1.c.




                                                                                               5
                                                                                              6




        Before going any further, let’s look at this code. The first four lines after the
opening comment are extensions to C which tell the compiler about the ports of the AVR
series. From the viewpoint of the programmer, they assign a variable name to a
microcontroller 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 microcontroller
address of (hex) 10.

        In the main procedure, we first declare an unsigned character variable which we
name ‘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 want
port D to be all inputs and to store the hex byte $FF into the data direction register of port
B since it is to be an output port. In assembly language, we would accomplish this by
some code like:

       clr     r17             ; clear all data bits
       out     DDRD,r17        ; put this into port D DDR to make these all inputs
       ser     r17             ; set all data bits
       out     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 address
corresponds to the variable names, DDRB and DDRD.

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

       data = PIND;

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

        When a push-button switch is pressed, the pin is grounded – an unpressed switch
is 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 for
pressed switches.

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



                                                                                              6
                                                                                            7



       PORTB = data;

just stores this value into the output port. The output pins are connected to LED’s which
go to the +5V supply through 680 Ohm resistors. When an output pin is low, the
corresponding 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 LED
corresponding to any particular switch to be lit as long as the switch is pushed. You can
press any number of switches simultaneously and the corresponding LED’s will be lit.

                                     Now, we need to compile this program. A section of
                            the tool bar is shown to the left. The two buttons shown are
                            the ‘Compile’ button and the ‘Make’ button. The ‘Compile’
                            button does just that – it compiles the source file. Click on
                            this to see what happens. You should get a notice, shown
                            next, telling you that the program has compiled successfully
                            with no errors and no warnings. The notice also tells you
                            how much RAM memory has been used, the size of the
stacks and how much space has been allocated for global variables. It also causes the
assembly language file to be generated and stored on disk. In this case, since the C
program was named tutor1.c, the assembly language file which is generated is named
tutor1.asm.

                                                If you clicked the ‘Make’ button instead of
                                      the ‘Compile’ button, you would again cause the
                                      compiler to operate and to generate the assembly
                                      language file but you would also subsequently
                                      invoke the assembler which then generates the
                                      object code either as an Intel hex file or some other
                                      format. We’ll discuss selecting these options a
                                      little later. For now, for this project, the option has
                                      been set to generate an Intel hex file which will be
                                      named tutor1.hex. This may now be loaded into
                                      the target 90S2313 microcontroller with the AVR
                                      programming tool named AVRProg.exe; or
                                      whichever other programmer you use.

                                               The assembly language .asm file is
                                        generated in a format compatible with the AVR
assembler. This is a very simple assembler with limited macro capabilities but
sophistication is not required here – the compiler does all the hard work. After
programming is complete, the program will run by itself and you can now light any LED
by pressing the corresponding switch on the AVRDB. You can change the target
processor from a 90S2313 to a 90S4414 or a 90S8515 without changing any of the code
and the program will still work correctly because the addresses assigned to the various



                                                                                            7
                                                                                                 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 LED
corresponding to port B, bit 7 will not always be lit. Can you think why it is always lit
when the microcontroller is the 90S2313? Hint: the 90S2313 only has 15 I/O pins and
there 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 overall
structure which is very common in microcontroller programs. There is first a section
defining 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 its
work. Inside the body of the ‘main’ procedure, we have the code itself which is really in
two sections. The first two lines are the ‘initialization’ portion of the code which sets up
the microcontroller to do its task. Inside the endless loop, is the action which the
microcontroller is supposed to do. This basic structure, an initialization sequence
followed by an endless loop, is the quintessential microcontroller program.

        How can we improve this simple program? Firstly, we shouldn’t have to look up
the 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 with
the Codevision compiler and the author of the compiler has thoughtfully provided
‘include’ files for all the common AVR microcontrollers. To see how this works, just
edit the code in the tutor1.c file by removing the four ‘srfb’ statements and replacing
them with the single statement:

       #include <90s2313.h>


Remember that C is case sensitive so type the line exactly as shown. 90s2313.h is a file
in the ‘inc’ folder of the cvavr directory. Try compiling the program again. It should
give you exactly the same result. You will notice that the number of lines which were
compiled is greater because this include file contains many more register definitions than
just the four we had in the original program. However, the compiled code will be exactly
the 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 refresh
your memory about what registers are available, etc. Note that, using the convention
common to AVR programmers, all the registers use upper-case letters exclusively. You
must be careful not to accidentally reuse some of these names for variables in your
program.

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

       data=PIN;




                                                                                                 8
                                                                                            9

Now, when you click the ‘Compile’ button, you will see that there is an error and a
warning. At the bottom of the compiler window, there is a white area which will now
contain two lines. The first of these will tell us what the error is and the second will
describe the warning. The warning is simple; we defined PIND with an ‘sfrb’ statement
but never used it. The error statement is a bit more obscure but still understandable – we
haven’t defined the name PIN but we’ve used it in a statement. If we click on this error
statement, the offending line will be highlighted in the edit window. Sure enough, it is
the 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. When
the ‘compile’ button is clicked, the source file is saved automatically. This is terribly
convenient but also can cause problems. If you have a source file which works and you
want to make extensive changes in it, you really should save it somewhere first with a
different 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 modify
the program to turn out that annoying bit 7 LED which is always on. If you haven’t
figured 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 last
bit 7 LED is always off. Finally, to make an even shorter program, we can replace the
two 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 can
eliminate the declaration of the unsigned char, data.

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




                                                                                            9
                                                                                            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 a
project and the exact configuration for that project. We’ll start a new project in this
exercise. During the course of working with this project, we’ll examine how the
compiler 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. At
this point, it is useful to add something to the file while it is open so I usually just write
an opening comment describing the project, giving the date, etc. Then save the file using
                                                                Save As and give it any
                                                                appropriate name.

                                                                       Now, we will create a
                                                              project using this source file.
                                                              Use File | New again but this
                                                              time, select the ‘Project’ radio
                                                              button because you wish to
                                                              start a new project. You will
                                                              be asked to give this project a
                                                              name – I opted to call this
                                                              tutor2. You will get a new
                                                              window like the one on the
                                                              left. Click the Add button and
                                                              select the source file which
                                                              you’ve just created as the one
                                                             to add to this project.

                                                                      Clicking the compiler
                                                             tab gives you access to the
                                                             properties of the
                                                             microcontroller you will use
                                                             for this particular project. For
                                                             the example which we will do,
                                                             select the AT90S2313. The
                                                             default options for this
                                                             processor are 128 bytes of
                                                             RAM (this is determined by the
                                                             microcontroller itself) and a
                                                             stack size of 32. The top of the
                                                             hardware stack is normally


                                                                                            10
                                                                                           11

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

                                                                      Clicking the
                                                              Assembler tab gives you
                                                              access to the properties of the
                                                              assembler output. If you’re
                                                              using AvrProg.exe as your
                                                              programming tool, it is most
                                                              convenient to choose the Intel
                                                              HEX format for the assembler
                                                              output. This is shown to the
                                                              left.

                                                                       Finally, click on the
                                                               OK button to close this
                                                               window. You have now
                                                               created a project with a
source file. In any real life project, one normally will want to make extensive notes
associated with various aspects of the project and have them attached in some way to the
programming itself. These may contain details of the hardware development, problems
encountered, etc. Along with the source file in a project, the IDE automatically creates a
text file with the project name and the file extension of .txt. This file is useful for making
these notes in the course of developing a project.

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

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


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



                                                                                           11
                                                                                                        12

                                                                  the previous one, the value saved as the
/*
Revised version of tutor1.c in which the LEDs stay on after       global variable ‘data’ is changed and it
release of the switch                                             is written to the LED port. When this
*/
                                                                  program is compiled, you get the
#include <90s2313.h>                                              following compilation summary:
//
// 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();
                                                                  You can see that global variables are
 while (1)                                                        allocated space at the bottom of the
  {
                                                                  hardware stack and therefore reduce
     ch = read_switch_bank();                                     hardware stack space. The data stack is
     if ((ch != data) && (ch != 0xff)) // see if it has changed
       {                                                          reserved for local variables like the
       data = ch;                                                 unsigned char variable, ch, located in
       write_to_LEDs(data);
       }                                                          the ‘main’ procedure. When compiling
     }                                                            large programs, you should use this
 }
                                                                  compilation summary to keep an eye on
//                                                                how much room you have for the
// procedure and function definitions
//                                                                hardware stack. You can alter this at
                                                                  any time by using the Projects |
void initialize(void)
 {                                                                Configure pull-down menu to change
                                                                  the size of the data stack for the
 data = 0xff;           // starting value
 DDRD = 0x00;           // all inputs                             particular project.
 DDRB = 0xff;           // all outputs
 PORTB = 0xff;          // start by turning all LEDs off
 }                                                                         We have seen how the compiler
                                                                  allocates variables in RAM. How does
unsigned char read_switch_bank(void)
 {                                                                it allocate variables in the flash memory
 unsigned char ch;                                                (ROM)? Since flash memory cannot be
 ch = PIND | 0x80; // the $80 makes bit 7 a logical 1             altered in the course of a running
 return (ch);                                                     program, flash variable storage is only
 }
                                                                  useful for constants. A common
void write_to_LEDs(unsigned char ch)                              example might be the strings which are
 {
 PORTB = ch;                                                      to be displayed by a running program on
 }                                                                an LCD display. We can invoke this


                                                                                                        12
                                                                                              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 flash
memory space.

         As an example of this, let’s modify the preceding program so that the
microcontroller, when turned on, displays alternate LED’s lit. We may do this by adding
a 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 turned
on and then subsequently operate as before.

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

         EEPROM can be accessed in exactly the same way by writing the keyword
eeprom before the otherwise normal declaration of a variable. The compiler produces all
the code necessary for the storage and retrieval of variables from eeprom. This can be
illustrated 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 by
changing 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 will
generate an EEPROM *.eep file with the initial value stored in it. You may declare
arrays or any type of variable to be of type eeprom. If you do not assign initial values,
the compiler will generate a warning.



                                                                                              13
                                                                                             14



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

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

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

        Data hiding is considered to be one aspect of good programming and we can do
that 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 new
file and save them there. This can then be given a name (such at “tutorial_stuff.h”) and
saved in the \inc directory. In the main program, then, we somewhere just need to write a
statement:

        #include <tutorial_stuff.h>

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




                                                                                             14
                                                                                           15




Assembly Interface

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

       The CodeVisionAVR C Compiler, like other compilers meant for microcontroller
development, has an easy interface to assembly language. Assembler code may be
imbedded 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; a
total of 17 registers are thus available for assembly language use. The other 15 registers
are used by the compiler and using of some of them in the inline assembler code might
compromise the rest of the program. The use of assembly language in a C program is
described in the CodeVisionAVR C Compiler help files. We will start with a very simple
example code here. Let’s suppose we want to reproduce the very first program used in
the project, tutor1 but we want to execute the read switches, write to LED loop as quickly
as 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 call
tutor3 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 assembly
language 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’t
   help to use #include <90s2313> because those sfrb statements are instructions to the
   compiler, 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 addresses
that it uses when it is creating an assembly language file. However compiler labels



                                                                                           15
                                                                                                                     16

                                                                        always start with the underline character.
  /*
                                                                        For this reason, it behooves one not to use
  Tutor3 program - reproduces the tutor1 program                        the underline as a first character in a label.
  but does it as quickly as possible. The program is very
  simple:
                                                                                In this example, we have let the
  - an initialization section
  - an infinite loop of read switch, transfer to LED                    compiler create all the code necessary for
  -                                                                     setting up the hardware and software
  */
                                                                        stacks. It will also fill the RAM data space
  void main(void)                                                       with zeroes before jumping to the
   {
                                                                        beginning of the main() procedure. None
  #asm                                                                  of these are necessary for this simple
  ;
  ;                                                                     program but might be in a more
  ; first,we have to give the assembler the addresses of the ports      complicated program. Finally, it goes
  ; then, the initialization section
  ;                                                                     without saying that one has to conform to
  ; we'll use register r16 throughout                                   the syntax for the AVR Assembler – a very
  ;
  ;                                                                     simple assembler.
  .equ DDRB= $17
  .equ DDRD= $11
  .equ PORTB= $18 ; the output LED port                   Let’s do our next project as one
  .equ PIND= $10 ; the input switch port         where we want to use assembly language
  ;
  ; initialize                                   because it allows us to do something which
  ;                                              is difficult to do in a higher level language
       ldi      r16,0
       out      DDRD,r16                         and that is to generate a well defined time
                                      ; set PORTD to all inputs
       ldi     r16,$ff                           delay. In C, we can always generate a time
       out      DDRB,r16              ; set PORT B to all outputs
  ;                                              delay by simple for (i=0; i< n; i++) loops
  ; now, for the infinite loop                   but we are never sure of how long it will
  ;
  forever_loop:                                  take. In a low level language like
       in      r16,PIND                          assembly, we can calculate exactly how
                                      ; read the switches
       ori     r16,$80                ; set bit 7
       out      PORTB,r16                        long a sequence of code will take.
                                      ; store result into LED port
       rjmp      forever_loop                    Consider the following problem. Suppose
  #endasm
    }                                            we want to write a program in which there
                                                 are a series of delays each of which is some
 integral number of milliseconds long (this is better done by using interrupts as we shall
 see in the next section). For now, we’ll do it this way as an illustration of how to
                                                                 interface to assembly
                                                                 language. Let’s start by
          Instruction       Number of Cycles
                                                                 writing a procedure which
          ldi         r16,n           1                          will take precisely one
waitLoop:
          ld          r17,x           2      \                   millisecond to execute.
           ld         r17,x                   2          |
           ....                                          |
           ....                                            m lines = 2m cycles                    Consider the code in
           ld         r17,x                   2          |                                the adjacent box. Here we
           ld         r17,x                   2          |
           ld         r17,x                   2          /                                have a tight loop which
           dec        r16                     1                                           contains a number of
           brne       waitloop                2 except for the last time when it is one
                                                                                          statements which load r17
Total time = 1 + n(2m + 3) -1 = 2nm + 3n cycles                                           with whatever is located
                                                                                          where the X register is



                                                                                                                     16
                                                                                                            17

pointing. We have chosen this instead of a string of NOP’s because a NOP only takes
one cycle while the load r17 statements take two cycles each. As this will be written as a
subroutine, there will be a RET instruction at the end (4 cycles) and, of course, the calling
program will have to execute a ‘rcall’ statement which takes 3 cycles. The total time, in
cycles, for this procedure is thus n(2m+3) + 7 cycles. For the AVRDB, the crystal clock
runs at 4 MHz so each cycle is 0.25 microseconds. To generate a total delay of 1
  /*
                                                    millisecond, we will therefore need the
  Tutor4 - a program to blink the LED array at one  total number of cycles to be 4000.
  second intervals
                                                    Therefore, we want n(2m+3) = 3993.
  */                                                In order to conserve space, we also
                                                    want m to be as few as possible. The
  #include <90s2313.h>                              other constraint is that n must be less
  void WaitAMilliSec(void);
                                                    than 256 because it is a single byte.
                                                    Suppose m were 8, and n were 210,
  void main(void)
    {
                                                    then the total number of cycles would
                                                    be 3990 – just 3 short of what we
    int i; // our counter
                                                    want. We can add 3 NOP’s after the
  //                                                loop and before the RET in order to
  // initialize PORT B to all outputs
  //
                                                    make the delay exactly equal to 4000
    DDRB = 0xff;                                    cycles.
  //
  // light every other LED
  //                                                                           So … let’s start a new project
    PORTB = 0xaa;
                                                                       and write a program which will blink
   while (1)                                                           the LED’s once per second using our
     {
     for (i=0; i < 1000; i++) WaitAMilliSec(); // call it 1000 times
                                                                       accurate one millisecond procedure.
     PORTB = ~PORTB;          // invert the LED display                My version is shown in the adjacent
     }
   }
                                                                       box. When this is compiled and
                                                                       loaded into the ‘2313, you will see the
  void WaitAMilliSec(void)
    {
                                                                       lights blink alternatively at
  //                                                                   approximately once per second. I’m
  // use r16 and r17 since they are not used in the compiler
  //
                                                                       sure you can guess why I said
                                                                       approximately. Even though we’ve
  #asm
      ldi      r16, 210
                                                                       gone to a lot of trouble to make the
  wait_loop:                                                           subroutine take exactly one
      ld r17,x ; do this eight times
      ld r17,x
                                                                       millisecond, the problem is that there
      ld r17,x                                                         is some overhead in the for (……..)
      ld r17,x
      ld r17,x
                                                                       loop and that is going to mean that the
      ld r17,x                                                         lights will blink a little more slowly
      ld r17,x
      ld r17,x
                                                                       than once per second. If you examine
      dec r16                                                          the .asm or .lst files which the
      brne wait_loop
      nop
                                                                       compiler produces, you will see that
      nop                                                              the overhead is only a few instructions
      nop
  ; no RET needed
                                                                       but those few instructions will add
  #endasm                                                              several microseconds to each
    }
                                                                       millisecond and we will not have the
                                                                       accuracy we’ve gone to so much



                                                                                                            17
                                                                                                             18

trouble to obtain.

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

void WaitMilliSecs(int number);

#pragma warn-                                                            With this procedure, we can delay
void WaitMilliSecs(int n)                                                an accurate second by calling it with
  {
// n is passed on the y stack. The MSB will be located at y+1            a parameter of 1000, or half a second
// and the LSB will be located at y                                      with 500, and so on. To write this
// 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            procedure, we will need to know how
// up to zero                                                            variables are passed to functions or
#asm
    clr r18                                                              procedures. There is a file in the
    clr r19                                                              CodeVisionAVR C Compiler help
    ld r17,y           ; get LSB from stack
    sub r19,r17        ; put negative value in r19                       system which describes this in some
    ldd r17,y+1        ; get MSB from stack                              detail. Parameters are passed to
    sbc r18,r17       ; put negative into r18
inner_loop:                                                              functions and procedures on the data
    rcall     wait1msec               ; takes precisely 1 mS to return   stack. The pointer to this stack is the
    inc r19       ; least sig byte - one cycle
    brne inner_loop ; two cycles except the last time                    Y index register pair and the stack
    inc r18        ; most sig byte                                       builds downward. Parameters are
    brne inner_loop
#endasm                                                                  stacked in order of declaration within
  }                                                                      the brackets and are stacked MSB
#pragma warn+
                                                                         first, then LSB. Procedures by
#asm                                                                     definition have no return value but
;
; subroutine _wait1msec takes exactly one millisec                       functions do. Functions are returned
; use r16 and r17 since they are not used in the compiler                in specific registers: char variables
;
wait1msec:                                                               are returned inr30, int and unsigned
   ldi      r16, 210                                                     int variables are returned in the r30,
wait1msec_loop:
   ld r17,x ; do this eight times                                        r31 register pair with r30 having the
   ld r17,x                                                              LSB and r31 the MSB. Long signed
   ld r17,x
   ld r17,x                                                              and unsigned int’s are returned in
   ld r17,x                                                              four registers as follows: LSB – r30,
   ld r17,x
   ld r17,x                                                              next most – r31, next most – r22,
   ld r17,x                                                              MSB - r23. In the procedure we will
   dec r16
   brne wait1msec_loop                                                   write, we will not return any values
   nop                                                                   so we do not need to use these. The
   nop
   nop                                                                   code in the adjacent box (just the
  ret                                                                    procedure) shows how we recover
#endasm
                                                                         the passed parameter in this case.




                                                                                                             18
                                                                                                         19

         Strictly speaking, this solution is also not quite exact. Firstly, there is some
overhead in calling the WaitMilliSecs procedure from the calling program. Secondly,
there is some overhead inside this procedure – both in recovering the variables from the
stack and in the loop which calls the very precise 1 mS delay subroutine. However, these
latter two have delays which can be calculated and compensated for – we won’t bother to
go through that as it is tedious; it is possible, though. Then, we just have the overhead
from 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 part
in 105, a few µS in a delay of several tens or hundred of mS is not significant. It might be
a problem if we needed accurate delays of just a few mS.

  /*
                                                                 We will leave this problem not quite
  Test program for unsigned long integer increment       solved. It was intended as an exercise in passing
  routine done in assembly language.                     variables to an assembly language procedures
  */
  unsigned long int number;                              and functions rather than a discussion of timing
  unsigned long int IncLong(unsigned long int n);        accuracy.
  void main(void)
   {                                                            As a semi-final exercise, let’s look at
   number = 1234567;
   number = IncLong(number);                             passing variables back to the calling program.
   while (1);                                            Imagine a program where we want to increment
   }
                                                         an unsigned long integer variable as quickly as
  #pragma warn-                                          possible - perhaps because it is often called in a
  unsigned long int IncLong(unsigned long int i)
    {                                                    program. Incrementing a four-byte variable in C
  #asm                                                   involves a four byte addition and so takes some
  ;
  ;                                                      time; we will be able to do it much faster in
  ; the stack will look like this:                       assembly language. The prototype function will
  ;
  ;     MSB                                              be:
  ;     2nd MSB
  ;     3rd MSB
  ;     LSB                <- Y                          unsigned long int IncLong(unsigned long int i);
  ;
  ; with Y pointing to the least significant byte
  ; of the variable                                      The code for this function and its calling program
  ;                                                      is shown in the adjacent box. Because we’ll be
        ldd r23,y+3 ; get MSB into r23
        ldd r22,y+2        ; next into r22               returning the function value in registers r22, r23,
        ldd r31,y+1        ; next into r31               r30 and r31, we can use these registers in the
        ld    r30,y        ; least into r30
        inc r30            ; increment LSB               body of the code without worrying about the fact
        brne done_IncLong                                that the compiler also uses them – the compiler
        inc r31
        brne done_IncLong                                will be expect them to be changed. We use the
        inc r22                                          #pragma warn- before the function and #pragma
        brne done_IncLong
        inc r23                                          warn+ after so that the compiler will not generate
  done_IncLong:                                          a warning. It would normally do so since it does
  ;
  ; the results are already in the proper registers so   not see a ‘return(i);’ statement anywhere in the
  ; we can just return                                   function.
  ;
  ; NOTE - we do NOT alter the data stack point, Y
  ;                                                        Finally, there is one other topic worth
  #endasm
    }                                                    considering and that is accessing global variables
  #pragma warn+                                          in assembly language sections. The


                                                                                                         19
                                                                                          20

CodeVisionAVR C Compiler assigns memory locations in RAM to global variables. The
compiler uses the same name you give to the variable except that an underline character
is prefixed to the name. For example, if you have a list of global variables in a program
such 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 stored
with the least significant byte at the lowest address and successively more significant
bytes at successive addresses. Because these global variables have been assigned storage
space by name, we may access them in assembly language directly. For example, given
the 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 most
significant 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 internal
variables inside a procedure or function, they are stored on a stack and hence not
accessible by name.




                                                                                          20
                                                                                           21

Interrupts

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

       rjmp reset ; where it goes on RESET
       rjmp 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 the
number of possible vectors depends on the processor; there are 11 for the 90S2313 and
13 for the 90S8515, for example.

        To make use of these interrupt vectors, the compiler allows one to write special
interrupt procedures which are designated by writing the reserved word, interrupt, before
the 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 start
with 1; vector 1 is the RESET vector. For example, vector number 3 is for the external
interrupt 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 right
one for the particular microcontroller you are using. Because the interrupt service routine
is never called by anything, don’t waste too much time trying to think of a neat name for
it; any old name will do.

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

        In programs which use interrupts, the basic structure of the main() procedure is
the 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 of
interrupts procedures in a project in the Examples\led directory. You should examine this



                                                                                           21
                                                                                         22

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

/*                                                In the next exercise, we will change the
                                                  program above to explore the use of
Tutor 7 project - interrupt procedures
                                                  interrupts in this manner. The project,
*/                                                tutor7.prj, is a rewrite of led.prj in
// I/O register definitions                       which all the initialization is gathered
#include <90s2313.h>                              together into one procedure. The
#define fmove 2
#define xtal 4000000                              tutor7.c code is shown at the left. I have
                                                  removed all the comment statements to
unsigned char led_status=0xfe;
                                                  make the code a little smaller – refer to
void initialize(void);                            Example\led.c to see what each
void main(void)                                   statement does. In tutor7.c, the global
 {                                                variable, led_status, is altered by the
 initialize();                                    interrupt service routine but nothing else
                                                  is done. In the main program, the value
 while (1)PORTB=led_status;
 }                                                of led_status is written to the LED bank.
                                                  Compile and ‘make’ this file and
interrupt [6] void timer1_overflow(void)
 {                                                download it into the AVRDB to verify
 TCNT1=0x10000-(xtal/1024/fmove);                 that it works in exactly the same way as
 led_status+=led_status;
 led_status|=1;                                   led.prj does.
 if (led_status==0xff) led_status=0xfe;
 }
                                                          Now, let’s add another interrupt
void initialize(void)                             routine to change the pattern of the
 {
 DDRB=0xff;                                       display if one of the external interrupts
 PORTB=led_status;                                is activated. The push-button switches
 DDRD=0xff;
 PORTD=0;                                         of the AVRDB are connected to PORT
                                                  D and two of the pins in PORT D can be
  TCCR1A=0;
  TCCR1B=5;                                       set to be external interrupts. Let’s
  TCNT1=0x10000-(xtal/1024/fmove);                choose the INT 0 interrupt which is
  TIFR=0;
  TIMSK=0x80;                                     activated by bit 2 of PORT D. We will
  GIMSK=0;                                        define a new global variable, called pb2,
 #asm                                             which has an initial value of zero and
    sei                                           which is inverted every time the
 #endasm
                                                  interrupt occurs. We will also choose to
  }                                               activate the interrupt on the falling edge
                                                  of the signal on bit 2 of PORT D. The
code of this interrupt routine and the modified program are given in tutor8.prj; this is
shown below.




                                                                                         22
                                                                                                      23


/*                                                                        The program is very similar to
                                                                 tutor7 except that another global
Tutor 8 project - interrupt procedures
                                                                 variable has been added as described
*/                                                               before. In the initialization procedure,
// I/O register definitions                                      we also have to set up the INT0
#include <90s2313.h>                                             interrupt characteristics. When this
#define fmove 2
#define xtal 4000000                                             program is running, generating a
                                                                 general interrupt by pressing the push-
unsigned char led_status=0xfe;
unsigned char pb2 = 0;                                           button switch (bit 2 of PORT D – this
                                                                 is the third switch from the low end)
void initialize(void);
                                                                 will cause the moving LED pattern to
void main(void)                                                  invert. This (i.e., the INT0 service
 {
                                                                 routine) is a very simple interrupt
 initialize();                                                   routine and, in real life, we would
 while (1)if (pb2)PORTB=~led_status; else PORTB=led_status;      want to debounce the switches or
 }                                                               otherwise ensure that the interrupts
interrupt [2] void INT0(void)                                    occurred controllably. In this simple
 {                                                               routine, because the switch is not
 pb2 = ~pb2;          // just invert it
 }                                                               debounced, multiple interrupts can and
                                                                 do occur and so the state of the
interrupt [6] void timer1_overflow(void)
 {                                                               variable, pb2, is somewhat
 TCNT1=0x10000-(xtal/1024/fmove);                                indeterminate.
 led_status+=led_status;
 led_status|=1;
 if (led_status==0xff) led_status=0xfe;                                  For some microprocessors, you
 }
                                                                 have to disable the interrupts inside an
void initialize(void)                                            interrupt routine to make sure that
  {
  DDRB=0xff;                                                     another interrupt cannot occur while
  PORTB=led_status;                                              you’re servicing an earlier one. In the
  DDRD=0;          // set PORT D to inputs
// timer 1 stuff                                                 case of the 90S series of
  TCCR1A=0;                                                      microcontrollers, the internal interrupt
  TCCR1B=5;
  TCNT1=0x10000-(xtal/1024/fmove);                               enable flags are cleared when an
  TIFR=0;                                                        interrupt is serviced thus preventing
  TIMSK=0x80;
// INT0 stuff                                                    another interrupt from occurring
  MCUCR=0x02; // set bit 1 to enable interrupt on falling edge   unless you deliberately enable it in the
  GIMSK=0x40; // enable interrupt on INT0
                                                                 interrupt service routine code. The
#asm                                                             flags are reset when the RETI is
  sei
#endasm                                                          executed at the end of the routine thus
                                                                 re-enabling further interrupts.
 }




                                                                                                      23
                                                                                         24



Bit-wise I/O

         Setting and clearing I/O bits in a microcontroller is not as simple as it may seem
at first glance. Consider the following case. Let’s imagine we have a controller with a
lot of I/O being handled by interrupt routines. Somewhere in the program, we want to
activate a relay which has been connected to bit 2 of PORTB. Let’s assume PORTB is a
general output port and the other pins go to other devices and let’s also assume that the
relay 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 are
doing, we have to be sure that we don’t affect them. Therefore, we have to read the pins
of 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 something
like:

       data = PORTB;
       PORTB = data & 0xfb;


These two actions are shown here as separate statements to emphasize that it requires a
read of a register followed by a write of the register after some internal operations. What
happens if some of the other bits in the latch get changed in the time interval between
these two statements by an interrupt routine? The answer is that these pins will get put
back to what they were before the interrupt routine took place – it is as if the interrupt
never 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 to
PORTB at a subsequent time. To be safe, we’d have to turn the interrupts off before this
operation and then turn them back on after. This is awkward and may cause time-critical
interrupt routines to fail.

        The 90S series of microcontrollers have machine code instructions for setting and
clearing individual bits in the I/O registers. This capability gets around the difficulty
described above but, unfortunately, standard C doesn’t have statements which allow the
use of these direct instructions. The CodeVisionAVR C Compiler has an extension to
standard 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;



                                                                                         24
                                                                                            25

sets bit 2 of PORTB and doesn’t affect the other pins. We can further clarify the code by
writing 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 the
statement:

        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 to
writing clear, simple and easily understandable code.

       We may also use single bit operations in reading ports. For example, in polling
the UART receive register to see if bit 7 were set indicating that a character has been
received 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 the
same 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 the
I/O registers – these are register 0 through 31 inclusive. You cannot use this kind of
single bit operation, for example, to read from or write to the general interrupt mask
register, GIMSK, which is register number 59 (0x3b).




                                                                                            25

								
To top