You are on page 1of 73

HomC

Programming and the ATmega16 Microcontroller.


C by Example.
A Directed Project by Jeffrey J. Richardson

Welcome to the home page of C Programming and the Atmel ATmega16 Microcontroller. This site was created to be a supplemental software resource for EET 209, Introduction to Microcontrollers, at Purdue University, West Lafayette, IN. This site begins with background information related to software development with special emphasis directed towards microcontrollers. The site then proceeds through the C programming language as it pertains to microcontrollers. The scope of this site is to provide software programming information to students with NO programming background. Questions or comments can be directed towards the author of this site (see contact information). This site was created with Microsoft FrontPage 2002 and designed for a minimum screen area of 1024 by 768. Click on the Navigation link to start the Introduction.

Introduction: The development or writing of software is only one piece of a puzzle. It is important for a programmer to understand and work through the entire problem solving sequence before attempting to write software. This site will outline the steps necessary to solve a problem and implement a solution. After covering the basic problem solving sequence, this site will continue with the other tools necessary to develop a solution. The site will cover the development of algorithms to solve given problems. Flowcharting will be used to aid in the visualization of the algorithm(s) and as a tool to convert the algorithm into working C software. The C programming language will be covered as a tool to implement a solution to the given task. The C programming language has several advantages associated with it when compared to assembly or other high level languages. These advantages make the C language well suited for use with microcontrollers. Once the C software has been written, it must go through a process to create the actual software that is loaded into the microcontroller. The creation of software should also follow a set of standards. Standards aid in the readability and maintainability of software. This site will utilize examples to illustrate the topics being discussed. In addition, this site offers a number of references to aid in the development of microcontroller software. These references include sections from the Atmel data sheets, information on interrupts, number conversions, the ASCII character table, lab hardware, and options for programming the microcontroller with the desired source code. This site has been specifically developed to lead the student through the entire process. However, the individual sections can be used as standalone references. The navigation window on the left has a listing of the topics under the C Programming heading. The intended sequence starts with the introduction (this page) and proceeds down the list starting with Problem Solving. The process continues until the bottom of the list is reached. The information located under the References section is not intended to be part of the lessons covered under this site. They are merely intended to provide a quick reference to additional information that the student may require. In addition to these links, the top banner also has a set of links to other web sites that the student may desire to access.

Embedded Microcontroller Software: Embedded microcontroller software poses a unique challenge. Embedded systems are expected to operate without fail, 24 hours a day, 7 days a week, 365 days a year, year after year. To this end, microcontroller software requires good coding practices, good documentation, and thorough testing. The C programming language is a general purpose, high-level programming language. The C language was not specifically designed to be used for microcontrollers. What makes the C language popular for microcontrollers is that it allows software to be developed in a structured method and compiles into efficient machine level code. The use of any high-level language keeps the programmer at a distance from the actual microcontroller hardware. The C compiler takes care of the details of the software like register allocation, memory access, and the like. This allows the programmer to concentrate on the details of the software and not the intricate details of the specific microcontroller. This separation from the hardware also allows the software to be portable (movable to other microcontrollers) with a minimal amount of effort. C for microcontrollers has several unique features not found in standard C implementations, so even experienced C programmers have to spend some time on the learning curve. The major difference between a program written for a PC and a program written for an embedded microcontroller is that the PC program will most likely end at some point and turn control back to an underlying operating system. Embedded systems do not have an operating system and therefore, embedded applications never end. Embedded applications are not allowed to run out of things to do.

Problem Solving: The key to creating a successful embedded application is understanding and following a sequence of designed steps to solve the given task. The problem solving steps shown will be used throughout this site. The first step in the process is to understand what is being asked. If the programmer does not understand the question or what is being asked of them, then the programmer must seek out additional information. In the "real world", this may involve asking the "customer" in depth questions to arrive at the answers. In the context of this class, the customer will be the instructor (lab or lecture). The student should ask the instructor for additional information to clarify the question or problem statement. If the programmer does not have a clear picture or destination in mind, then arriving at a solution is going to be difficult at best. Once the problem is understood, the programmer must next develop a plan that will solve the problem. It is important for the programmer to understand why each task must be done. The development of algorithms will be covered more in depth in the next section, but for now, it's safe to state that if the programmer can't solve the problem on paper, then the programmer has little if no chance at solving the problem by blindly writing software. Once an algorithm has been developed, the programmer must get down to the nuts and bolts of things, so to speak. An algorithm may require the temperature of a process to be read or monitored. The temperature monitoring device may require certain control signals to manipulate its functionality. The programmer must know what is necessary and in what order the control signals must be operated. This may require the programmer to spend some time reading the datasheets and researching the components used. This additional information is then used to tailor the algorithm to the specific target microcontroller. Some systems may not require external components, but the algorithm must still be tailored to the microcontroller.

With the algorithm tailored to the microcontroller, the next step is to create an action plan to implement the solution. To aid in this process, flowcharts will be used. A flowchart graphically shows the flow through the algorithm. The graphical symbols and structures can then be transformed in software. In addition to aiding or making the software easier to write, developing flowcharts also aid in identifying steps or sequences of tasks that are performed multiple times through the software. Once these tasks are identified, they can be implemented as software functions or code segments that are only typed once, yet called or executed multiple times throughout the program. This is a key concept in structured programming and will be explored more in depth later. Flowcharting of an algorithm also provides documentation for the customer. With all the preliminary development complete, the programmer is now ready to actually write the software and thus, solve the problem at hand. Many programmers, both new and experienced, want to jump to this phase without working through the previous steps. As a programmer gains experience, less and less time can be spent on the prior steps and still achieve good results. However, the less experienced programmers should spend as much time as needed to complete the steps. Completing the preliminary development steps will make the actual software writing much, much easier. The focus of this site is on the use of the C programming language to implement the solution. It should be noted that the C programming language is not the only choice for writing software, but it is the only language used in this particular class. The later portions of this site will cover the actual C language. The final phase in the development cycle is testing and verification of the software. This step will require the software to actually be tested. The testing of software can be accomplished in several different ways. Simulators and emulators can be used to simulate the software while watching the variables and such. This technique can aid the programmer in finding minor, or sometimes major, bugs within the software. EET 209 does utilize a simulator but the primary testing will be performed by programming the actual microcontroller and testing the software with the lab hardware available. This will allow the student to literally see the successful results. Complex software and sometimes simple programs do not perform the desired results on the first attempt. In some cases, the programmer may have to go back and repeat prior phases of the problem solving sequence to ensure the desired results are eventually obtained. One thing is for certain when it comes to software development, software development is an iterative process. Like most problems, problems that involve writing software are easier to solve when they are broken into smaller or more manageable pieces. Breaking the software into more manageable sections not only makes the software easier to write, but it also makes the software easier to test.

Last Updated on March 19, 2003

main

contact

EET 209

Algorithms:
One of the most important topics associated with software development is algorithm development. An algorithm is defined as the steps necessary to provide a solution to a given task. If the programmer is unable to solve the problem at hand on paper, then the programmer has little if no chance at all of solving the problem with a microcontroller through the use of software. The software and microcontroller are simply tools to implement a solution. Some problems must be solved by a specific order of steps while other

problems may be solved through a variety of different combinations of steps. For example, in order to add a series of numbers to find the total sum, the order in which the numbers are added does not matter or make a difference. On the other hand, some problems do require a specific order. For instance, converting a temperature from degrees Fahrenheit to degrees Celsius requires the number 32 to be subtracted from the Fahrenheit value before the value is multiplied by 5/9. Multiplying by 5/9 first and then subtracting 32 will produce the wrong equivalent temperature. This section is dedicated to understanding how to develop a solution. An algorithm is a step-by-step method for solving a problem. Enough detail is required so that a person unfamiliar with the problem could follow the steps and arrive at the desired solution. Computers are extremely powerful tools used to solve problems. However, before a computer can solve a problem, a programmer must specify the order and the actual instructions to follow to solve the problem. The items listed below will be used to describe an algorithm. The steps of the algorithm should be stated clearly and precisely. Anyone following the algorithm must obtain a unique value for each of the steps along the way to the solution. For example, anyone adding two numbers together should arrive at the same answer. In addition, the algorithm can only rely on information already stated in the algorithm or information that has been derived during the algorithm. An algorithm cannot use information that it does not have. The algorithm must have a definite starting and stopping point, providing a finite number of instructions to be executed. The algorithm must list the input information for the system as well as the specific range or ranges for which the algorithm covers. If an algorithm holds true for only positive integers, then the algorithm must clearly state this fact. The algorithm must state the outputs that are produced. When possible, algorithms should be kept general to allow re-use for different sets of data. For example, an algorithm designed to average a set of numbers should work for any number of terms.
Algorithms:

1. Precision: The steps are clearly stated. 2. Uniqueness: The intermediate results of each step of execution are uniquely defined and depend only on the input and results of the preceding steps. 3. Finiteness: The algorithm stops after a finite number of instructions have been executed. 4. Input: The algorithm receives input. 5. Output: The algorithm produces output. 6. Generality: The algorithm applies to a set of inputs.

Example 1 - Adding a series of numbers. Example 2 - Averaging a set of numbers. Example 3 - Calculate the slope of a line.

Last Updated on March 19, 2003

main

contact

EET 209

Flowcharting:
A flowchart is a graphical representation of an algorithm. Flowcharts are often restricted to a one page limit to keep things simple. The basic shapes that make up a flowchart are shown to the right. These will be the only actual symbols used for this site. In addition to these symbols, flow lines and arrows are used to represent the program flow through the steps. The Terminal symbol is used as both the entry and exit point of a flowchart. The Process symbol is used to represent a process that needs to be completed. This process could be as simple as adding two numbers together to as complicated as calibrating a piece of machinery. The Decision symbol is used to represent a "fork in the road". This is the place in the algorithm where a decision must be made. Structured flowcharts, by definition, have a single entry point and a single exit point. Structured programming forces this property. A flowchart can be used as a powerful tool to make the transformation from an algorithm to software. This transformation will become more clear when dealing with the C programming structures for program control which will be covered later. Flowcharts are also a good source of documentation. This author has to generate flowcharts, for documentation purposes, more than once, for software that was written by other programmers.

Flowcharts are not only used to document software. Flowcharts can also be used to document any process flow. For example, the process for problem solving can be shown graphically with a flowchart:

Program Structure:
A basic embedded C program for the ATmega16 is shown below: /* Simple C program to illustrate the basic components of a program. Written by: Jeffrey J. Richardson, Purdue University, February 18, 2003 This program actually performs no tasks, calls no functions, and uses no variables */ #include<mega16.h> void main(void) { while(1); } // stay here forever...never ending

(Click here for a "Marked" version of the above program in PDF format)

Most C programs are written in lower case letters. C is case sensitive meaning that the compiler DOES make a distinction between a capital letter and its matching lower case letter. Capital letters are generally used for constants found commonly in #define statements (discussed later). ALL C statements end with a semicolon.

The Preprocessor Directives C programs are constructed of statements and directives. Directives are instructions to the compiler. The preprocessor is a program that performs these compiler instructions before the actual

C code is compiled. The two most commonly used preprocessor directives are the #include and #define directives. The #include directive tells the compiler to read in the entire contents of another file. The #include preprocessor directive is generally used with header files. Header files have an extension of h and are used to add information about the actual microcontroller or library information. The < > construct of the statement tell the compiler to look in a standard location or directory. This standard location is usually an INC directory or folder found in the compiler main directory structure. An alternate location can be specified by using double quotes ( " " ) instead of the < > symbols. When using the double quote method, the compiler will start in the current directory. To specify a different directory or even drive, the programmer must type in the appropriate path information. The following examples shows the use of double quotes to specify a file located in the current directory. #include "my_header_file.h"

The #define preprocessor is used to define a constant. A constant is exactly that, a value that never changes. A constant may be used to define the maximum size of something in the program or simply used to make the code more readable. The preprocessor simply replaces the text used in the #define statement with the information following it. Regardless of the use, it is common to use all capital letters. For example: #define MAX_SIZE 256

value = MAX_SIZE; load the variable

// use a Constant to

In the above example, the line of code value = MAX_SIZE is transformer by the preprocessor to value = 256. The word or constant MAX_SIZE is replaced with the number 256. Constants can also be used in control statements. Control statements will be discussed in depth later, but the following example demonstrates the basic principle. #define MAX_SIZE 256

if ( current_value > MAX_SIZE) // Test the value against a known maximum limit { // code omitted for clarity } The Main( ) Function All C programs contain at least one function, namely the main function. A function can be defined as a section of a program that performs a well-defined service. However, the main function is slightly different than normal functions. Functions will be discussed in greater detail in a later section, but for now, the major difference between the main function and a normal function is that the main function is called or executed automatically when the program starts and any other function must be called either directly or indirectly from the main function. For this reason, the main function has been described as the lowest level task, because it is the first thing executed when the program starts.

Code Blocks Program structure is part of the format used when creating a program. Structured programming is a technique used to organize the program code. Use of a structured technique can aid in debugging (finding errors in the code) and improves the readability of the source code. A code block can be defined as a group of statements that are joined or surrounded by curly braces to form a compound statement and are the basic building blocks of structured programs. A code block begins with an opening brace { and ends with a closing brace } . The braces define the body or block of code. Braces are used to bound functions and various control statements. Indenting the code block, one tab space for instance, will make the block of code standout from the surrounding code segments. This makes the program easier to read and thus easier to debug. A convention should be set and followed throughout the

program to facilitate reading of the source code. The standard used for all programs associated with this site will be the above mentioned one tab space. The braces do not have to be placed on their own line, but doing so will also aid in the readability of the source code. These braces should match the indentation of the code block. The C programming language ignores so called white spaces. White spaces are generally referred to as any character that does not have a readable symbol associated with it. For example, a space simply leaves a gap between letters. A carriage return places a vertical gap between characters. The compiler ignores both of these types of characters. To the compiler, a curly brace at the beginning or ending of a line of code is the same as if the curly brace was occupying its own line. If the compiler interprets the different cases as the same thing, then the difference only benefits the person reading the code. The underlying goal is to write code that is easy to read. Therefore, this author suggests placing the braces on their own line. On occasion, curly braces may be placed at the beginning or ending of a line of code to potentially save paper when printing large programs. void main(void) { while(1); ending } // stay here forever...never

The above code is taken from the example program at the beginning of this section. Note that the braes are both indented and located on their own line of the code. This helps the programmer and anyone else reading the code recognize the presence of a block of code. Comments Comments are a tool used by programmers to aid in the readability of software. The successful use of comments is critical to the readability of source code. Comments should allow the software to be read easily by someone who may not be familiar with the software (or even the original programmer at a later time). One of the hallmarks of a "good" program is that the program can be read

and understood by anyone, even if the person reading the code does not know how to program. Properly commented code will make this possible. Barnett, Cox, and O'Cull state that every line of code should be commented (except the truly obvious). They continue by stating that the comment should not just echo the instructions but should explain the purpose of the instructions. The comments do not cause any code to be generated. Thus, comments do not cause the target microcontroller to execute any instructions. They only assist someone attempting to read the software. Comment blocks are essential at the beginning of any function (including the main function). In the case of the main function, the comment block is located at the very top of the source code. The remainder of the functions should have a code block immediately preceding the function declaration. This block should contain the following information: name of the function, operation performed, name of the programmer, date the function was written, names of other functions that may be called, the names of any global variables used, and explanations of the local variables. The above listing is just a sample of the type of information that should be included. This list is not set in stone, but should provide a baseline for the type of information that should be present. The following is an example of a comment block located prior to a function: /* Routine: init_lcd By: Jeffrey J. Richardson Date: Jan. 17, 2003 Description: Routine that will initialize a two line LCD. The cursor is on, non-blink, cursor style is an underscore. The display is configured to shift the cursor right after each character. No local or global variables are used. Functions Called: delay_ms putcmd_lcd */

Comments come in two styles. The first type is the above mentioned block comment. Block comments are bounded by the opening comment marker, a forward slash followed by an asterisks /*, and the closing comment mark, an asterisks followed by a forward slash */. The compiler ignores all the information between the opening and closing markers. Most compilers nowadays change the color of the actual text between the markers to help denote that the information is part of a comment. The second type of comment is a single line comment which is denoted by two forward slashes //. The comment ends at the end of the line of source code usually denoted by a carriage return and linefeed. A word of caution...a backslash at the end of a line of C code, tells the compiler to append the next line of code to the end of the current line of code. This has significances if the programmer leaves a backslash at the end of a single line comment. A potential line of C code following the comment will become part of the comment and not be compiled as part of the software. If the GUI or graphical user interface does indicate comments through a different color, it may not indicate this condition and thus not call this condition to the attention of the programmer. y = mx + b; system \ // solve for the y value in this

printf("The y intercept is %d\n\r.", y);

Don't get caught up in the details of the above example. It is only to illustrate that the backslash that appears at the end of the comment line will tell the compiler that the print statement is part of the comment. Therefore, the print statement will never be executed (because it is not really part of the code) and the program will not operate as desired.

Last Updated on March 22, 2003

main

contact

EET 209

In the above flowchart, the process of generating a solution is shown as a single block. In reality, the process of generating a software solution is more complex. As mentioned earlier, processes are typically shown as a single block to simplify the flowchart. There are several rules that can be applied to structured flowcharting. The first rule is to start with the simplest flowchart (like the one above). The second rule states that any process rectangle can be replaced by a sequence of processes. The third rule states that any process can be replaced by any control structure (which contain decisions and actions). These rules can be applied in any order and repeated as many times as necessary. The following diagram shows some of the above rules being applied to a flowchart:

The flowchart below shows a more detailed flowchart for the "Generate a Solution" process shown above in the Problem Solving

Flowchart. This Software Development Flowchart utilizes some of the rules from above.

Both of the above flowcharts have decisions at the bottom of the flowchart. These decisions are questions about the operation of the software or the overall solution. In either case, if the software or solution contains errors, the flow is diverted back to a previous step. The previous steps are then repeated to refine or rework them in an attempt to reach a desirable result. Again, the results are tested and the process repeats until the desired results are reached. The above flowcharts deal with the processes involved in problem solving and software generation. These are definitely good examples of using a flowchart to illustrate a process or algorithm, but they are not processes that one would implement with a microcontroller. Therefore, the algorithms developed earlier during the section on Algorithm Development will be revisited. These processes are more like a "real world" application that a programmer may have to solve with a microcontroller.

Example 1 - Adding a series of numbers. Example 2 - Averaging a set of numbers. Example 3 - Calculate the slope of a line.

Last Updated on March 19, 2003

main

contact

EET 209

Variables and Constants Variables Embedded microcontroller programs are generally written to input information, perform some type of manipulation, and produce an output based on the input information and manipulation. Variables are used to store information in the memory of a microcontroller and they allow the programmer to refer to them by a meaningful name. Before a variable can be used, it must first be declared by specifying the data type and name of the variable. C is case sensitive meaning that it recognized a lower case letter and its upper case equivalent as being different. It is conventional to avoid using capital letters in variable names. Capital letters are generally reserved for constants (more on constants in a moment). Using meaningful and descriptive names for all variables is critical in writing readable software. Descriptive names make the software easier to understand. When selecting the names for variables, the names should be as long as necessary to indicate how the variable is used. The length of the variable name should however, be limited to a maximum of 31 characters. The variable name must start with a letter, but the remainder of the name can be made up of letters, numbers, and the underscore character. Variables can be declared at the start of any block of code, but they are traditionally only declared at the beginning of functions (for local variables). The declarations must be placed after the opening (left) brace and before any executable statement. Multiple variables of the same data type can be declared on the same line by separating the variable names with a comma. It is recommended that any separating comma be followed by a space to aid in the readability of the various variable names. void main(void) { unsigned char number_of_pulses; variable to count pulses unsigned int loop_cntr, count_value; variables for loop control

//

//

// omitted code for clarity } Variables can be initialized when they are declared. To initialize a variable, place an equals sign after the variable name followed by the initial value for the variable. The CodeVisionAVR compiler will initialize variables to zero if not explicitly specified. unsigned char number_of_pulses = 100; variable to count pulses //

Multiple variables declared on the same line can also be initialized to whatever value the programmer desires. However, doing so may create code that is harder to read. Therefore, this author offers the following suggestion: When initializing variables at the time of declaration, only place one variable on each line of code. For example: unsigned int loop_cntr = 25, count_value = 100; // variables for loop control may be easier to read as: unsigned int loop_cntr = 25; for the control loop unsigned int count_value = 100; for counting // variable // variable

Global vs. Local Variables: There are two basic types of variables used in C code. A local variable is declared inside the body of a function and is only available within the confines of that function. Most local variables are created when the function is called and are destroyed when the function terminates. If it is necessary to maintain the value stored in a local variable after the function ends, then the static variable type can be used. Static variables are not destroyed when the function terminates. Instead, the value is preserved. Static variables can be initialized like any other variable, however, the

initialization is only performed the first time that the function is called. The above example shows the creation of local variables inside the main function of the software. These variables only exist or are only valid inside the main function.

The second type of variable used in C code is the global variable. Global variables are created outside of any of the functions (usually at the top of the source code after any preprocessor directives) and are available to any and all functions. Using global variables increases the complexity of the software and can complicate debugging of the code. A general rule is to only use global variables when absolutely necessary. The following is an example of declaring global variables: /* Simple C program to illustrate Global variables Written by: Jeffrey J. Richardson, Purdue University, March 22, 2003 This program actually performs no tasks, calls no functions, and uses no variables */ #include<mega16.h>

unsigned char global_ch; // global character variable for storing serial information unsigned int int_counter = 0; // global counter for use in the interrupt routine

void main(void) { // code omitted for clarity }

Variable Types and Selection Type bit char signed char unsigned char int signed int unsigned int long signed long unsigned long float # of bytes 1 1 1 2 2 2 4 4 4 4 # of bits 1 8 8 8 16 16 16 32 32 32 32 Minimum 0 -128 -128 0 -32,768 -32,768 0 Maximum 1 127 127 255 32,767 32,767 65,535

2,147,483,647 2,147,483,648 2,147,483,647 2,147,483,648 0 4,294,967,295 1.28E-38 3.4E38

(Click here to download the above table in PDF format)

The Atmel AVR Family of microcontrollers are eight bit microcontrollers. Therefore, the default data type for all variables should be the single byte character, unless the size requirements of the variable dictate a larger value. Choosing the proper size of variables can be critical to the proper operation of the microcontroller software. Improper selection of the data type can cause inaccurate or undesired results as well as slow down the execution of the code. Variables should be of the unsigned data type unless they are actually used for signed operations. Looking back at Example 1 from the section on algorithm development: The programmer must know how big any result will be in order to select the proper data type for the total value. The problem stated that the five numbers: 17, 20, 1, 22, and 18 are to be added together. Doing so will produce the decimal value 78. In this case, a character sized variable is adequate to store the resulting value. However, if the problem statement stated that the input range of the five numbers was from 0 to 100, then the total

value could be as high as 500. An unsigned character is only capable of storing a value from 0 to 255. Therefore, the variable to store this "new" total would have to be of the data type integer. Again, since the program is not designed to do signed operations, an unsigned integer variable should be selected. The architecture of the microcontroller is most efficient when dealing with character sized variable ( 8-bits). For this reason, a character sized variable should be used whenever possible to allow the program to operate as quickly as possible. Using a 16-bit integer type variable can require the microcontroller to perform approximately twice the number of instructions to handle the variable when compared to an 8-bit value. However, if a 16-bit value is required, then the programmer has no choice except to use an integer. It is also important for the programmer to keep track of intermediate data sizes. Perhaps this is best illustrated by using the second example from algorithm development. This example requires the average of six numbers to be calculated. In order to calculate the average, the sum of the numbers must be calculated at some point. Changing the problem statement to state that the input numbers can range from 0 to 50 will produce an average value also in this range. On the surface, unsigned character variables appear to be more than adequate to store the results since the values are all below 50. However, it is quite possible for the total of the six numbers to exceed the maximum limit of an unsigned character. For example, given 48, 49, 47, 49, 48, and 50, the intermediate sum of the data set is 291. This value is too large to be stored in a character sized variable. Attempting to do so will produce an inaccurate result. The inaccuracy happens because the value 291 is "stuffed" into an 8-bit variable. In actuality, the decimal number 291 requires 9-bits of data to be stored. Forcing the value into an 8-bit number causes the most significant bit of the data to be lost because there is no place for it to be stored. Therefore, using an 8-bit value to store the result of the above six numbers produces the value 35. Clearly, 35 is not the total of the above numbers. Dividing 35 by 6 would then produce an average value between 5 and 6 instead of the proper result which is between 48 and 49. Looking inside the numbers:

The above example also brings to light a major element of mathematical operations and microcontroller data types. Specifically, the issue of integer math. Integer math has no decimal point which is associated with floating point math. When performing integer math, the results are truncated (no rounding) to a whole number result. Looking back at the above example, 291 divided by 6 is 48.5 assuming floating point math. Since the values above assume character and integer variables (not floating point), the result would be truncated to 48 instead of 48.5 if the proper sized variables were used. Assuming that only character sized variables were used, dividing 35 by 6 would produce 5.833 which would be truncated to 5. Integer math is a key concept that must be taken into account when selecting data types for a program. When an application requires the accuracy associated with floating point math, the float data type can and should be used. Utilizing floating point variables will force the microcontroller to performing floating point math. Since the Atmel microcontrollers are not designed to perform floating point math, this will slow down their operation as they are forced to perform a significant amount of extra instructions to produce the results. The programmer needs to be aware of the accuracy requirements of the software and utilize floating point variables only when they are absolutely necessary. From time to time, the programmer needs to only keep track of a single bit. This bit is sometimes referred to as a flag and is declared in the same manner as any other variable. The data type for this declaration is the bit and can be assigned the value of either 1 or 0.
(Click here to see an example of a basic program with variable declarations)

Constants

Constants are just the written version of a number. Constants can be in any format (decimal, hexadecimal, etc). Constants are typically created by using the #define preprocessor directive which was discussed during the section on Program Structure. When long constants are used, it is common to place a trailing L at the end. Constants allow the software to be somewhat configurable and easier to read. #define XTAL 6000000L

For example, the preprocessor directive #define XTAL 6000000L allows the programmer to refer to the value of the crystal oscillator by the name XTAL. If the crystal frequency is changed after the software has been written, changing the preprocessor directive will change the value of the clock frequency throughout the program.
(Click here to see an example)

Last Updated on March 22, 2003

main

contact

EET 209

I/O Operations As mentioned during the discussion of variables, microcontroller programs are generally written to input information, perform some type of data manipulation, and produce an output. According to the previous statement, there are three key elements necessary to accomplish this task. This section of the site is devoted to the first and last: the reading of the input information and the controlling of the outputs of the microcontroller. The data manipulation portion of this site will start with the next section, Expressions. Sensory inputs are one of the key elements in any embedded system. Any device must "perceive" that there is a need to take action before deciding what action or actions to take and finally acting out those actions. An embedded microcontroller may be used to monitor and control the temperature of a process. The system would require the microcontroller to have the ability to "read" and control the process temperature.

Input Operations In order for an embedded microcontroller to make appropriate decisions, it must be able to see the world around it or at least the key elements necessary to make the appropriate decisions. Looking back at the example from above, the microcontroller needs to know the temperature of the process. It may also be helpful to know the position of any control valves used in the process. The details of how these sensor work or would work are not our area of concern (for the moment), instead we are interested in how the information would get inside the microcontroller so that the microcontroller can take the appropriate or desired actions. The exact assignment of the ports to measure the temperature and valve positions are not important (for this discussion). However a decision must be made and therefore the temperature will be monitored on Port A and the valve positions will monitored on Port B and Port D. The Atmel AVR Family of microcontrollers requires that the ports be configured for either input or output. The microcontroller has a data direction register for each port that controls the direction (input or

output) of the desired port. A more detailed description of configuring port for input and output can be found by clicking on the Atmel Resources link on the navigation window and then selecting the Configuring Port for Input or Output link. To open the detailed description in a separate window at this time, click here. The main register of concern for configuring the Port's Data Direction is the DDRn register. The lower case n symbolizes that multiple registers have the same general name. Replacing the lower case n with the capital letter of the desired port will access the desired data port direction register. For exampe, replacing the lower case n with the letter A allows the programmer to control the data direction register associated with PORTA. To set the entire port for input, the data direction register must be set to zero. The software command to do this is simply: DDRA = 0; . This line of code must be repeated for PortB and PortD as well producing the following three lines of code: DDRA = 0; DDRB = 0; DDRD = 0; // set PortA for Input // set PortB for Input // set PortD for Input

*The data registers must be configured before the port can be used to read information.

With the data ports configured for inputs, the actual sensor information can be read by the microcontroller. In order to save or store the information for later use, three variables should be created to store the sensor values. (Variables were discussed in a previous section. If necessary go back and review the information). Since the microcontroller ports are 8-bits wide, a character sized variable is appropriate to store the values. Assuming that we are not going to be measuring any negative numbers for temperatures, an unsigned character type variable will be chosen. The variable declarations are listed below: unsigned char water_temp; temperature unsigned char hot_position; the hot position unsigned char cold_position; the cold position // 8-bits for the actual // 8-bit variable to store // 8-bit variable to store

With the data direction registers setup as inputs and with a place to store the information from the sensors, the values are now ready to be read. Perhaps the biggest or most important thing to remember when reading inputs is the actual register name that must be read. The actual input information or data is read from the PIN register associated with each port. To read information from the input pins on PortA, the following instruction is used: water_temp = PINA; . This basic structure is repeated for each value that needs to be read. Continuing with this example, the following three lines of code are used to read the actual sensor values: water_temp = PINA; temperature hot_position = PINB; cold_position = PIND; // get the actual faucet water

// get the hot water position // get the cold water position

Output Operations Once the microcontroller has the required information to and has made a decision, the appropriate action or actions must be taken. This will require some type of output action to be taken. Microcontrollers, at least in this class, provide low current TTL compatible output signals. These signals can be amplified to different voltages and currents if required, but that is covered in a different course. Looking back at the example from above, the assumption will be made that turning on, or setting an output to a logic level, will be sufficient to control the outputs. Before a port can be used as an output, the port's corresponding data direction register must be set as an output. This procedure is the same as setting a port for input with the only exception being the value written to the data direction register. A zero was written to the data direction register to configure the port for input, whereas a logic one must be written to the data direction register to configure the port as an output. Setting the data direction register for PortC, DDRC, to all ones, will configure the 8-bits of the port for output. The actual value to be written to the direction register is the binary number 11111111 which can be written as the hex number 0xFF. The C statement to assign this value to the data direction register is as follows: DDRC = 0xFF; . This line of code should be added with the previous lines of code for configuring the input ports

and should also contain a comment as to the function of the statement. The four statements are grouped together below: DDRA = 0; DDRB = 0; DDRD = 0; DDRC = 0xFF; // set PortA for Input // set PortB for Input // set PortD for Input // set PortC as an Output

A device can now be controller by placing either a logic value of one or zero on the appropriate or corresponding port pin. The values used to control the output pins must be written to the PORT register associated with the corresponding port. In this example, output information must be written to PORTC. The value written to the port can come from a variable or from a constant value located inside the software. To place information on the output pins of the port, the following statement is used: PORTC = 0x00; . This example shows the constant value 0 being written to the output port. The example shown below shows an instance in which the output value comes from a variable, which must first be declared. unsigned char water_temp; temperature unsigned char hot_position; the hot position unsigned char cold_position; the cold position unsigned char output_value; control // 8-bits for actual // 8-bit variable to store // 8-bit variable to store // 8-bits for output

PORTC = output_value; variable on PortC

// place the value stored in the

Advanced Topics

Single Bit Manipulations One of the key characteristics of a microcontroller is the ability to manipulate a single port pin without influences the other pins on the port. The CodeVisionAVR C compiler allows the programmer to refer to the port pins symbolically. In other words, the port pins can be referred to by their respective port name and bit location. Assuming that an output is located on PortC bit 0, the output can be set to a logic high by using the following statement: PORTC.0 = 1;. The prior statement assumes that the port has been configured as an output. A similar technique can be used to monitor a single input pin. Assuming that an input of interest is located on PortA bit 5, the following would be used to monitor the input: value = PINA.5 .

Using the #define Preprocessor Directive The #define preprocessor directive is used to make software easier to read and maintain. To this end, it is often used in conjunction with input and output functions. The earlier example had the water temperature sensor connected to PortA. The exact statement to read this input from above was: water_temp = PINA;. Using a #define can potentially make this statement easier to read and maintain. Starting with the following preprocessor directive: #define WATER_TEMP_SENSOR PINA. The previous statement that read the water temperature can be re-written as: water_temp = WATER_TEMP_SENSOR; actual water faucet temperature // get the

The same technique can be used to control an output port or port pin. In the case of an individual port pin, the #define directive must include the bit number. Assuming a status LED is connected to PortC bit 0, then the status led could be referred to by name. For example: #define then STATUS_LED PORTC.0

STATUS_LED = 1;

// turn on the status LED

Last Updated on March 22, 2003

main

contact

EET 209

C Operators C operators are how the mathematical functions in a program are performed. C operators are symbols that tell the compiler to perform certain types of data manipulations or evaluations. The evaluations are things like checking for equality or determining if one particular value is greater or less than another value. Data manipulations can include mathematical expressions like adding, subtracting, multiplying, or dividing of numbers. Data manipulations also can include Boolean operations such as the logical ANDing or ORing of data bits. The C operators fall into one of five categories. These categories are as follows: Relational, Arithmetic, Assignment, Logical, and Bitwise Operators.
(Click here to download a table of the C operators)

Assignment Operators Programming languages need a mechanism to place a constant values into variables and registers. The C language is no different. The assignment operator performs this exact task. The basic assignment operator is the equal sign. The assignment operator is also used to copy values from one variable to another. Operator = Description Assignment

Given: Assign the value 25 to the variable num. Assign the value in the variable num to the variable old_num.

Results: num = 25; old_num = num;

Relational Operators As the name implies, the Relational Operators are used to determine how one value or variable relates to another. The relational operators allow the testing of equality and for the testing of greater than or less than operations. The operators also allow for a subset or combination of these conditions. Relational operators are typically used in control structures. The relational operators allow the program to test two values to see if they are equal or not equal. The relational operators also allow two values to be compared to determine if one value is greater than or less than the other value. A subset of these commands allow the program to combine the equality and magnitude operations together to determine if a value is equal to or greater than a value as well as determine if the value is less than or equal to the value of interest. The result of any Relational operation is a True or a False indication. In the C programming language, a false condition is define as a value of zero. A true condition is indicated or defined as Not false. Therefore, a true condition is defined as a non-zero value. Operator == != < > >= <= Description Equal to Not equal to Less than Greater than Greater than or equal to Less than or equal to

Given: x = 9, y = 16

Evaluate: x == y x != y x>y x<y x >= y x <= y

Results: False True False True False True

Arithmetic Operators The Arithmetic Operators allow the program to perform the basic mathematical operations. These include adding, subtracting, multiplication, and division. The arithmetic operators also include a modulus, an increment, and a decrement instruction. Adding, subtracting, multiplying, and dividing numbers in a computer program follow all the basic mathematical rules that most people learned in elementary school. The modulus operator is related to division in that the result of the operation is the integer value of the remainder of the division of the two numbers. The increment and decrement operations are related to adding or subtracting one from the value. The increment and decrement operations are simply a shortcut method of adding or subtracting one. Operator + * / % ++ -Description Addition Subtraction Multiplication Division Modulus Increment Decrement

Given: x = 12, y = 3

Evaluate: x+y x-y x*y

Results: 15 9 36

x = 10, y = 3

x/y x%y x++ y--

4 1 11 2

Logical Operators The result of a logical operation is either true or false. As defined earlier, false is defined as a value of zero and true is defined as not false. The logical operators treat the multiple-bit values or variables as a single value. The logical operators are the logical AND and the logical OR. The logical operators are frequently combined with the relational operators to create compound statements. In the case of the logical AND, all the the relational operations must be true for the result to be true. If just one of the components of a logical AND operation is false, the entire statement is false. The logical OR only requires one of the conditional or relational operations to be true in order for the entire statement to be true. Operator && || Description Logical AND Logical OR

Given: x=8 x = 12

Evaluate: ( ( x >= 8 ) && ( x <= 15) ) ( ( x == 10 ) || ( x == 18 ) )

Results: True False

Bitwise Operators The bitwise operators deal with the individual bit positions within the values of interest. The result of any single bit position is entirely independent of the other bit positions. The bitwise operators are

used to test, set, and clear individual bits. These operators are closely related to the operations performed by discrete digital systems. These operations include the bitwise AND, OR, Exclusive OR, and Compliment. The bitwise operators also include a shift right and shift left command. Bit testing is accomplished by using the AND operation with an associated "mask". A mask is a value that has a "1" in the bit position of interest. The result of the operation will be non-zero if the bit of interest is set or equal to one. The result of the bit test will be zero if the bit of interest is zero. The clearing of a bit is also accomplished through the use of the bitwise AND. To clear a bit, place a "0" in the bit position of interest in the mask. Performing the bitwise AND will force the corresponding bit to be cleared or set to zero. Any bits that do not need to be cleared, must have ones written to their respective bit positions to prevent the clearing of them as well. A bit is set by placing a logic one in the bit position of interest in the mask and then performing a bitwise OR with the value or variable that needs a bit set to a high value. To prevent the inadvertent setting of bits, any bit that is not intentionally being set in the mask must be zero. The shifting of bits either left or right is extremely useful for manipulating data. These operations allow the data to be broken into smaller and sometimes more manageable pieces. The shift operations are also used to take smaller pieces or portions of information and create one large or bigger value from the individual parts. When using any of the Bitwise operators, it is highly recommended that parenthesis be placed around the operation. Operator & | ^ << >> ~ Description AND OR Exclusive OR Shift left Shift right Complement

Given:

(values in binary)

Evaluate: ( value & mask ) ( value & mask ) ( value | output ) ( value | output ) ( value ^ mask ) ( value << 3 ) ( value >> 4 ) ( ~value )

Results: 10000000 00000000 01010010 00000011 11111100 01100000 00000110 10011010

mask = 10000000, value = 10101010 mask = 10000000, value = 01000000 output = 00000010, value = 01010000 output = 00000011, value = 00000010 mask = 01010011, value = 10101111 value = 00001100 value = 01100010 value = 01100101

Last Updated on March 23, 2003

main

contact

EET 209

Control Statements Introduction: Microcontrollers are usually used to perform tasks that have or display intelligence. The key to intelligence lies in the ability to make decisions and then act on these decisions. Control statements are used in the C language to make decisions and control the flow of the program based on the results of these decisions.

Program Blocks and Loops: A program block is a group of statements that have the following two characteristics: They have a single entry point and a single exit point. A loop has a program block at its heart. A loop is used to repeatedly perform an operation or a block of code through the use of a conditional expression. (Code blocks were covered in more detailed
during the discussion of Program Structure. Click here to re-visit that discussion.)

The conditional expressions were covered in depth under the C operators section. The important concept to remember is that a false condition is define as a value of zero and a true condition is defined as NOT false. (Click here to re-visit the discussion on relational
operators)

The proper use of braces and indentations are important to maintain readable code.

Control Structures: The C language control statements include the if/else, the do/while, the while, the for loop, and the switch/case. The nature of the loop or control structure used determines the number of times the statements (or code block) are executed, if executed at all. This section will introduce and apply the control statements to perform useful tasks that add "intelligence" to the software. With the above listed control statements, a block of code can be executed once, executed repeatedly, executed a certain number of times, or skipped over completely. These control statements are key in

writing structured software. Nesting of control structures also allows for more detailed and complex software. Structured programming is a technique for organizing and coding programs that reduces complexity, improves clarity, and facilitates debugging and modifying. The benefits are listed below: 1. Operation is simple to trace 2. Structures are self-documenting 3. Structures are easy to define in flowcharts 4. Structured programming increases programmer productivity 5. Structures lead to functions Number 3 above refers to flowcharts. Flowcharts are graphical representations of algorithms. A properly drawn flowchart is an invaluable tool for identifying the proper control structure.

IF / Else:

The IF/ELSE statement can be referred to as a "fork in the road". This statement directs the flow of the software according to the result of the conditional expression. The conditional expression in an if statement is a comparison between two values. If the conditional expression is true, then the "true" block of code is executed. If the conditional expression is evaluated as false, then the else branch or block of code is executed. It should be noted that not having the microcontroller perform any task in the event that the conditional is false is quite common. Therefore, the else condition may be omitted altogether. The flowchart for both an IF and an IF / ELSE structure are shown below. The distinguishing characteristic that identifies an IF statement over the other control structures is the flow of the program after the decision and resulting process. In either an IF or IF / ELSE structure, the program flow continues to go down the page.

if (the conditional expression is true) { perform these statements } else { perform these statements if the conditional is false }

While Loop: In a While Loop, the expression is evaluated when the program enters the top of the loop. If the expression is true, the statements within the loop are executed. When the bottom of the code block is reached, program flow is directed back to the top of the loop and the conditional is re-evaluated. As long as the conditional is evaluated as true, the process repeats. IF the conditional is

evaluated as false, then the code block is skipped. Again, the flowchart for a While Loop is shown below.

while (some conditional expression is true) { perform the statements located between the braces }

DO / While: The Do/While loop is very similar to the while loop. The major difference is that the expression or conditional is tested at the bottom of the loop. This means that the body of the loop is executed at least one time regardless of the results of the conditional. If the result of the conditional is true, then the loop body is repeated. This process is repeated until the conditional is evaluated as false.

do { perform the statements located between the braces } while (this conditional expression is true)

The while and do/while loops are frequently used to monitor a particular input or register in control type applications.

For Loop: The For Loop is traditionally used to perform a task or repetitive event for a known number of iterations or in other words, it is used to implement count-controlled loops. The for loop is made up of an initial condition, a conditional expression, a modifier, and the body or the code block. When the for loop is encountered, the initial condition is executed. The conditional expression is then evaluated

as either true or false. If the conditional is determined to be true, then the body of the loop is executed. Upon reaching the bottom of the loop, the modifier is executed. Program control is then returned to the conditional. The conditional expression is re-evaluated, and if true, the body of the loop is executed again. Each time the bottom of the code block is reached, the modifier is executed. The loop continues to be executed until the conditional is evaluated as no longer true. The flowchart for a For Loop is shown below. The basic shape of the flowchart resembles that of a While Loop. The difference or characteristic that determines that it is a For Loop is that the initial condition, conditional, and modifier all refer to the same variable.

for ( variable initialized ; conditional expression ; variable modifier ) { perform these statements while the conditional expression is true

The resemblance or similarities between the For Loop and While Loop go beyond the fact that the flowcharts look similar. In actuality, any For Loop can be replaced or implemented with a While Loop. However, not all While Loops can be replaced by a For Loop.

Switch / Case: The Switch Case structure is similar to a host of if statements. The switch case structure may have a default condition that is executed if none of the statements match. A switch/case structure is used when one statement must be chosen from many.

switch ( variable of interest ) { case case case value1: code to be executed. break; value2: code to be executed. break; value3:

code to be executed. break; . . . default: code to be executed. break; }

Choosing the proper control statements: Picking the appropriate control structure will ensure the proper operation of the software and aid in the readability of the software. The use of a properly developed flowchart can aid in the selection of the control structure. Each control structure has its own unique program flow which is evident in a flowchart. Flowcharts can now be used as a tool to translate an algorithm into C code. Control structures have the common element of a decision located at some point in the flowchart. The location of the processes associated with the decision aid in the identification of the structure. In other words, the relationship of the processes and decision determine the type of control statement. Another key element in the identification of the control structure is the flow through the processes. The While, DO / While, and For Loops all have the flow of the algorithm going back towards the top of the flowchart. The IF / Else and Case / Switch structures both have their program flow going towards the bottom of the flowchart.
( Click here to download a PDF file illustrating the various control structure flowcharts )

Last Updated on March 23, 2003

main

contact

EET 209

Functions: One of the most important concepts underlying any high-level language is the notion of functions. In other languages, they may be called subroutines or procedures, but the idea is the same. A C function is a collection of C language operations that usually performs an operation that is more complex than any of the operations built into the C language. In other words, a function performs multiple operations or statements that cannot be accomplished through a single line of C code.

Modular Software Through Functions Functions can be thought of as abbreviations for long, possibly complicated sets of commands. Functions can, and frequently do, call other functions. Despite being complex, a function should not be so complex that it is difficult to understand. Functions allow the programmer to modularize a program or break the program into smaller more manageable portions. Any set of operations that are performed more than once is a candidate for becoming a function. Packaging code as a function allows the code to be executed from many different locations in a program simply by calling the function.

Function Usage There are several motivations for functionalizing a program. Using functions properly can make the software much easier to read and thus understand. Another motivation is software reusability, which is using existing functions as building blocks to create new programs. An additional motivation is to avoid repeating code in a program which produces several benefits. Software located inside a function can be easier to update and maintain. The divide-and-conquer approach makes program development more manageable. The proper use of functions improves the readability of a program. Using meaningful names for the functions that represent the true

operation of the function is a must. This allows the programmer to refer to a sequence of multiple operations with a single function call. Functions allow the main code of the software to remain uncluttered and easy to maintain. The proper use of functions almost documents the code for the programmer without the use of comments (ALMOST). Any process block in a simplified flowchart is a candidate for a function. In other words, any process in a simplified flowchart that is or can be replaced by a series of processes or control structures is a possible candidate for a function. The ability to reuse software blocks or functions is powerful for several reasons. The first benefit is in program development. Using a function that has been previously created and tested saves the programmer time. The programmer can add the function to the program and be reasonably certain that the software will work without the need to debug the function. The reuse of functions also reduces the overall size of the software. Creating functions with repeated code segments shrinks the overall size of the software and can free up additional code space inside the microcontroller. Embedded microcontrollers usually have a limited amount of program memory that must be conserved whenever possible. Functions allow the programmer to execute the same set of instructions from multiple places within the program. The function only needs to be defined once, but it can be called any number of times. If the need arises to make changes to the software, having the code segmented into functions can expedite the process. Functions allow the programmer to make the required changes in one place and have the change appear to take place in multiple places through the software. In other words, if the function is called five times in the software, changing the function once, influences all five of the processes that call the function. When programs get longer or when several people are writing pieces of the overall program, modular programming is better. Modular programming involves separating the development of the software into pieces and then linking or combining the modules together to make up the final program. This allows the program to be broken down into functions and distributed among the developers.

Creating Functions The first step in creating a function is choosing a meaningful function name that will be used to identify the function. The same rules apply to naming functions and variables. The function name should represent the actual function that is going to be performed. The name can consist of letters, number, and symbols but MUST NOT start with a number. Once an appropriate name has been selected for the function, the parameters of the function must be determined. Functions have the ability to communicate with other functions through parameters. The parameters of the function identify the type of information that is either passed to the function or the information that is returned from the function. The parameters are also similar to the variables used through the program. In fact, the parameters are usually variables that are used inside the function. The parameters have data types that identify the type of information being used. The common data types are the unsigned char and unsigned int. (A more complete listing of data type can be found here) In the event that no parameters are passed to the function, or a parameter is not returned from the function, the data type of void will be used. This tells the compiler that no data will be exchanged in this case. Functions have the following basic characteristics when it comes to parameters: Functions may receive data during the function call, they may return information upon completion, or they may not receive or return any information, or a combination of the above. For example, a function may not require any outside information to perform the desired task, therefore no information is passed to the function. The function may or may not need to send information back to the function that called it. For example, a function may place a value on a port to control an operation and have no feedback information to supply. Another function may actually supply feedback information about a process and therefore need to return a value at the completion of the function. As programs increase in size and complexity, it is quite common to find functions that do not return values, functions that do not except values, and functions that do both. The C language, as it pertains to embedded microcontrollers, has limits on the parameters that can be passed to a function or received from a function. At most, a function is only capable of returning one value. The value being returned can be any of the following data types: character, integer, long, or float. Passing

variables to a function is a different story. The CodeVision compiler uses the stack pointer to pass variables to the functions. This allows multiple variables of different types to be passed to a function. The limit on the number of parameters and data types of these parameters is only limited by the available memory of the microcontroller.

Once all the necessary information has been determined, a function prototype is created. Functions must be defined before they can be used in a program. Prototypes inform the compiler about data being passed to and returned by functions. Function prototypes have the general form of: return data type function name ( passed data size(s) ); The following is an example of a function prototype: unsigned char current_temperature (void); /* function prototype for acquiring the current temperature */ In this example, the name of the function is current_temperature. The function does not receive any parameters and returns an unsigned character. The next example illustrates a function prototype that sends multiple values to a function: void heater_control (unsigned char, unsigned int); /* function that sends two parameters to the heater control routine */ In this example, the function receives two parameters. The first parameter is an unsigned char and the second parameter is an unsigned integer. Note that the function prototype only defines the type of variables being passed and not the actual name(s) of the variables. Once program flow enters the function, local variables hold the values of the parameters passed to the function. The variables receiver their names and data types from the function declaration.

Using the second function prototype from above, the following function declaration can be formed: void heater_control (unsigned char set_point, unsigned int time) { (actual function statements are omitted for clarity) } The statements of the function have been omitted. The function has two local variables that come directly from the function declaration. The first local variable is set_point and is of the data type unsigned character. The second variable is time and is of the data type unsigned integer. Since this particular function does not return a value, the function ends when the closing brace is encountered. The following example illustrates a function that returns a value: unsigned char current_temperature (void) { unsigned char temp; temp = PINA; the temp sensor return temp; back to the calling routine } // get value from // send temp

The above example is over simplified, but still illustrates the returning of a value from a function. A function that passes information back from it, ends with a return statement followed by the information that is being returned.

Perhaps the most important thing to remember about functions is that the variable types must match between the prototype, the declaration, and the values being passed. Calling a function is accomplished by simply referring to the

function name. If a function does not receive any parameters and does not return a value, the function is executed by using the function name, followed by an opening and closing parenthesis, followed by a semicolon. For example, given a function named heater_on that has no parameters, the following statement will execute the function: heater_on(); function called heater on // execute the

When executing a function that returns a value, the value being return needs a place to be stored or used. Using the example from above, current_temperature, the return value could be stored in a variable or used in a conditional operation. The following two examples illustrate the above concepts: temperature = current_temperature(); /* store the return value in a variable for later use */ or if ( current_temperature() < 100 ) returned less than 100 ?? // is

Sending information into a function is accomplished by placing the information inside the parenthesis. Multiple values are separated by commas. The values being passed to functions can come from variables or from constants within the software. The following illustrates these concepts: heater_control( desired_temp, set_time ); /* call the function passing the values stored in these variables */ or passing constants: heater_control( 100, 3000 ); /* set the temperature to 100 degrees in 3000 seconds */

Last Updated on March 23, 2003

main

contact

EET 209

Arrays: An array can be described as a simple collection of values that are closely related. In reality, an array is a group of variables that share the same common reference name and the same data type.

Arrays Defined An array is a group of variables of the same data type that share a common name. Arrays are stored in sequential memory locations inside the memory of the microcontroller. Arrays can be stored in the volatile RAM portion of memory or stored in the non-volatile Flash of the microcontroller.

An array must be declared, like any other variable, before it can be used. An array can be declared with any of the data type used in the C programming language (click here to revisit the discussion of data types). Arrays require an additional piece of information when compared to normal variables. The additional piece of information

required is the length of the array or in other words, the number of elements that will be stored in the array. unsigned char array[ 10 ]; array with 10 elements // character

The individual elements of the array may be initialized when they are declared (just like other variables). The initial values for the array elements are placed between an opening and closing brace with commas separating the values. unsigned char ch_array [ 3 ] = { 0x32, 0x39, 0x41 }; // init the array elements

Arrays are used when groups of information are closely linked. Some examples of arrays include serial queues, data stacks, and control words. Data arrays are commonly used for lookup tables.

Array usage An array is referenced through the use of an index. The index for the array is located inside a set of square brackets. ALL arrays start with an index of zero. The last index number of the array is determined by taking the length or number of elements in the array and subtracting one.

value = ch_array[ 0 ]; 0x32 into value

// places the number

The above array is accessed with the hard-coded number 0. Arrays can be (and often are) accessed through a variable that is used as the index value. For example: unsigned char index = 2; variable // array index

value = ch_array[ index ]; // places the value 0x41 into the variable value

Arrays can be filled with information by using the index. To access a particular value stored in an array, the name of the array is used along with the index position for the desired information. The position index may be an integer or an integer expression that is evaluated to determine the index. ch_array[ 1 ] = 0x55; array // place 0x55 into the

The C language does not automatically check the index used to access an array with the size of the array. If the index is assigned to access a location outside of the actual array, other memory locations will be accessed. This can lead to unpredictable results in operation. ch_array[ 3 ] = 0xAA; // attempts to place information into the array

Last Updated on March 25, 2003

main

contact

EET 209

Pointers: Pointers are variables that contain an address location and therefore naturally point to a memory location. Usually, the value of interest is then located in the particular address that the pointer is pointing to. Pointers are commonly used to index, access, and move data in the microcontroller. Pointer can be very confusing to beginning and even experienced programmers. The main purpose of a pointer is to access memory. C pointers are extremely powerful and effective for accessing the memory of a microcontroller. A pointer can be made to point to ANY data item or object in a C program. Pointers can be consider unsigned integers because they contain a 16-bit address (in Atmel Embedded Microcontroller environment). Pointers allow indirect access to important information.

Pointer Usage: Pointers deal with locations and data stored in those locations. There is not a pointer data type per say. Instead, a pointer is declared by placing an asterisks (*) in front of the identifier or variable name in the declaration statement. For example: unsigned char *ptr; an unsigned character // ptr is a pointer to

Like other variables, a pointer can be initialized. The catch is that the value must be an address. It cannot be overstressed that a pointer holds an address of a variable, not the actual variable data. The pointer is indicating which memory location the program is interested in. unsigned char *ptr = 0x0160; pointer to the hex address 160 // set the

A pointer can also be assigned a value or address by using the & operator. The & tells the compiler to use the address of the variable, not the value stored in the variable. The following example sets the pointer to the address of the variable x. ptr = &x; // set the pointer to the address of the variable x Once a pointer has been initialized, the data that it points to can be accessed through a de-reference operation. The asterisk is also used to de-reference a pointer or in other words, to get to the value stored in the address pointed to by the pointer. The following example illustrates using a pointer to copy a value stored in RAM into a variable. x = *ptr; // take the value stored in the memory location and place it in x

Pointer and Character Strings: Pointers are commonly used to access data strings or sets of characters that represent text messages. For example, the following declaration can be used to access a lists of ASCII characters that make up a text message: unsigned char *str = "Hello World."; store the message in memory //

Remember that the pointer only points to the first memory location of the string which contains the capital letter 'H'. To access the subsequent characters, the pointer must be incremented. Pointer math usually involves incrementing the pointer with the Increment Operator ( ++ ). Incrementing the pointer will point to the next value stored in the string. The data type that the pointer is referencing determines the new value of the pointer. In other words, incrementing a character pointer results in the pointer pointing to the next memory location because a character only occupies one memory location in an 8-bit microcontroller. If the pointer is of the type integer, incrementing the pointer results in the pointer moving two address locations because an integer requires two 8-bit locations in memory. Looking back at the string from above,

incrementing the pointer the first time results in the pointer pointing to the letter 'e'. unsigned char *str = "Hello World."; store the message in memory unsigned char ch; ch = *str; the variable ch //

// assigns the letter 'H' to

str++; // increments the pointer to the next letter in the string ch = *str; // assigns the next letter to the variable ch, in this case the value is 'e' Accessing the individual characters in a string is quite common when sending messages serially or when displaying messages on a liquid crystal display (LCD). Assuming multiple strings, if the length of the character strings are constant, a for loop can be used to access each of the individual characters in the strings. In the event that the length of a string is unknown or may vary, the end of the character string can be detected through a conditional expression. The C language adds a NULL terminator at the end of a string of characters. A Null terminator is simply a zero value placed at the end of the string of characters. Since printable ASCII characters fall above 20 hex, a value of zero is therefore nonprintable and thus a good indicator of the end-of-string. unsigned char *str = "Hello World."; message stored in memory unsigned char ch; unsigned char i; //

for( i = 0; i < 12; i++ ) // access each of the characters in the string { ch = *str; // store the character pointed to by the pointer, in the variable str++; next character } // point to the

The above example uses a for loop to access the twelve characters in the string. In this example, the values are simply stored in a character sized variable. In a real example, a more useful operation should be performed. The above example works for a fixed number of characters in the string. The following example uses a while loop to access each character of the string. This example will work for variable length character strings. The loop is terminated upon reaching the Null terminator at the end of the character string. unsigned char *str = "Hello World."; unsigned char ch; while ( *str != 0 ) not the NULL terminator { ch = *str; character str++; next character } // if the value is // store the // index to the

Again, the above example does not perform a useful function, but does illustrate the use of a while loop to access the characters in the string.

RAM vs. FLASH: Pointers can point to the internal RAM of the microcontroller, the Flash, or External data for example. Since the ATmega16 does not support external memory access, this section will only focus on the internal RAM and Flash of the microcontroller. Pointers that point to the Flash portion of the microcontroller must be declared as such. Declaring a pointer to point to the Flash area of the microcontroller places the string in permanent or non-volatile memory. These values can be read but not written. Placing a character string in flash is accomplished by using or adding the flash keyword in the declaration statement. Below is an example:

unsigned char flash *f_ptr = "Hello World."; // character string stored in Flash The above pointer identifier has an f_ included to help clarify that it is used to point to the Flash portion of the memory. The compiler handles the access to RAM differently than accessing Flash. Therefore, a flash pointer cannot be used to access data stored in RAM and vice versa. Therefore, it may be necessary to maintain or have two areas of the code to handle each type of pointer.

Last Updated on March 23, 2003

main

contact

EET 209

Pointers: Pointers are variables that contain an address location and therefore naturally point to a memory location. Usually, the value of interest is then located in the particular address that the pointer is pointing to. Pointers are commonly used to index, access, and move data in the microcontroller. Pointer can be very confusing to beginning and even experienced programmers. The main purpose of a pointer is to access memory. C pointers are extremely powerful and effective for accessing the memory of a microcontroller. A pointer can be made to point to ANY data item or object in a C program. Pointers can be consider unsigned integers because they contain a 16-bit address (in Atmel Embedded Microcontroller environment). Pointers allow indirect access to important information.

Pointer Usage: Pointers deal with locations and data stored in those locations. There is not a pointer data type per say. Instead, a pointer is declared by placing an asterisks (*) in front of the identifier or variable name in the declaration statement. For example: unsigned char *ptr; an unsigned character // ptr is a pointer to

Like other variables, a pointer can be initialized. The catch is that the value must be an address. It cannot be overstressed that a pointer holds an address of a variable, not the actual variable data. The pointer is indicating which memory location the program is interested in. unsigned char *ptr = 0x0160; pointer to the hex address 160 // set the

A pointer can also be assigned a value or address by using the & operator. The & tells the compiler to use the address of the variable, not the value stored in the variable. The following example sets the pointer to the address of the variable x. ptr = &x; // set the pointer to the address of the variable x Once a pointer has been initialized, the data that it points to can be accessed through a de-reference operation. The asterisk is also used to de-reference a pointer or in other words, to get to the value stored in the address pointed to by the pointer. The following example illustrates using a pointer to copy a value stored in RAM into a variable. x = *ptr; // take the value stored in the memory location and place it in x

Pointer and Character Strings: Pointers are commonly used to access data strings or sets of characters that represent text messages. For example, the following declaration can be used to access a lists of ASCII characters that make up a text message: unsigned char *str = "Hello World."; store the message in memory //

Remember that the pointer only points to the first memory location of the string which contains the capital letter 'H'. To access the subsequent characters, the pointer must be incremented. Pointer math usually involves incrementing the pointer with the Increment Operator ( ++ ). Incrementing the pointer will point to the next value stored in the string. The data type that the pointer is referencing determines the new value of the pointer. In other words, incrementing a character pointer results in the pointer pointing to the next memory location because a character only occupies one memory location in an 8-bit microcontroller. If the pointer is of the type integer, incrementing the pointer results in the pointer moving two address locations because an integer requires two 8-bit locations in memory. Looking back at the string from above,

incrementing the pointer the first time results in the pointer pointing to the letter 'e'. unsigned char *str = "Hello World."; store the message in memory unsigned char ch; ch = *str; the variable ch //

// assigns the letter 'H' to

str++; // increments the pointer to the next letter in the string ch = *str; // assigns the next letter to the variable ch, in this case the value is 'e' Accessing the individual characters in a string is quite common when sending messages serially or when displaying messages on a liquid crystal display (LCD). Assuming multiple strings, if the length of the character strings are constant, a for loop can be used to access each of the individual characters in the strings. In the event that the length of a string is unknown or may vary, the end of the character string can be detected through a conditional expression. The C language adds a NULL terminator at the end of a string of characters. A Null terminator is simply a zero value placed at the end of the string of characters. Since printable ASCII characters fall above 20 hex, a value of zero is therefore nonprintable and thus a good indicator of the end-of-string. unsigned char *str = "Hello World."; message stored in memory unsigned char ch; unsigned char i; //

for( i = 0; i < 12; i++ ) // access each of the characters in the string { ch = *str; // store the character pointed to by the pointer, in the variable str++; next character } // point to the

The above example uses a for loop to access the twelve characters in the string. In this example, the values are simply stored in a character sized variable. In a real example, a more useful operation should be performed. The above example works for a fixed number of characters in the string. The following example uses a while loop to access each character of the string. This example will work for variable length character strings. The loop is terminated upon reaching the Null terminator at the end of the character string. unsigned char *str = "Hello World."; unsigned char ch; while ( *str != 0 ) not the NULL terminator { ch = *str; character str++; next character } // if the value is // store the // index to the

Again, the above example does not perform a useful function, but does illustrate the use of a while loop to access the characters in the string.

RAM vs. FLASH: Pointers can point to the internal RAM of the microcontroller, the Flash, or External data for example. Since the ATmega16 does not support external memory access, this section will only focus on the internal RAM and Flash of the microcontroller. Pointers that point to the Flash portion of the microcontroller must be declared as such. Declaring a pointer to point to the Flash area of the microcontroller places the string in permanent or non-volatile memory. These values can be read but not written. Placing a character string in flash is accomplished by using or adding the flash keyword in the declaration statement. Below is an example:

unsigned char flash *f_ptr = "Hello World."; // character string stored in Flash The above pointer identifier has an f_ included to help clarify that it is used to point to the Flash portion of the memory. The compiler handles the access to RAM differently than accessing Flash. Therefore, a flash pointer cannot be used to access data stored in RAM and vice versa. Therefore, it may be necessary to maintain or have two areas of the code to handle each type of pointer.

Last Updated on March 23, 2003

main

contact

EET 209

AVR Programming Cable: A simple programming cable can be constructed by the student to program the target AVR microcontroller through the ISP port. The actual schematic for the programmer is shown on the right. The programming cable consists of a DB25 male connector, a section of ribbon cable, and a 10 pin ribbon cable connector. To utilize the programming cable, the cable must be connected to the parallel port on a PC running the CodeVision AVR C Compiler. CodeVision supports a number of built in programmers. Set the programming option for the Vogel Elektronik VTEC-ISP programmer with the desired LPT setting. More information is available on the G drive under the EET 309 class folder and the "Atmel Stuff" sub-directory.

main

contact

EET 209

The AVR Boot Loader: The AVR Bootloader allows the programming or re-programming of the target AVR microcontroller using the PC serial port instead of a traditional programmer. Once the AVR Bootloader is programmed into the microcontroller, it

remains until the chip is erased. The Bootloader allows a simple Windows application to download an Intel hex file into the microcontroller from the PC serial port through the microcontrollers UART. This is possibly the most economical method for programming the Atmel AVR family of microcontrollers in the ECET department. To view a detailed user manual for the AVR Bootloader, click on the link to the PDF file: The AVR Bootloader.

More information can be found at the following web address:


http://www.prllc.com/prllc_homemainAVRBL-16-32.htm

main

contact

EET 209

The JTAG ICE and AVR Studio: The AVR JTAG ICE from Atmel is a powerful

development tool for On-chip Debugging of all AVR 8-bit RISC microcontrollers with IEEE 1149.1 compliant JTAG interface. The JTAG ICE and the AVR Studio user interface give the user complete control of the internal resources of the microcontroller. In addition to reducing development time by making debugging easier, the JTAG ICE also allows programming of AVR devices. To view more detailed information, click on the link to the PDF file: The AVR JTAG
ICE

main

contact

EET 209

Lights & Switches

Keypad & LCD

7-Segment Display

Analog & Digital Converters

Stepper

Motor

You might also like