Lecture Notes for Embedded Controllers

how do embedded controllers work and how does a embedded controller function pdf free download
FrankRoberts Profile Pic
Published Date:11-07-2017
Your Website URL(Optional)
1. Course Introduction Overview This course introduces the C programming language and specifically addresses the issue of embedded programming. It is assumed that you have worked with some other high level language before, such as Python, BASIC, FORTRAN or Pascal. Due to the complexities of embedded systems, we begin with a typical desktop system and examine the structure of the language along with basic examples. Once we have a decent grounding in syntax, structure, and the development cycle, we switch over to an embedded system, namely an Arduino based development system. This course is designed so that you can do considerable work at home with minimal cost, if you choose (entirely optional, but programming these little beasties can be addicting so be fore warned). Along with the main course text (Intro to Embedded Programming by Russell), you will need an Arduino Uno board (about 25) and a USB host cable. A small “wall wart” power adapter for it may also be useful. There’s a lot of free C programming info on the ‘net but if you prefer print books and want more detail, you may also wish to purchase one of the many C programming texts available. Two good titles are Kochan's book Programming in C and the one by Deitel & Deitel C-How to Program. Whichever book you choose, make sure that its focus is C, not C++. You will also need a desktop C compiler. Just about any will do, including Visual C/C++, Borland, Code Warrior, or even GCC. A couple of decent freeware compilers available on the ‘net include Pelles C and Miracle C. Frequently Asked Questions Why learn C language programming? C is perhaps the most widely used development language today. That alone is a good reason to consider it, but there’s more:  It is a modern structured language that has been standardized (ANSI).  It is modular, allowing reuse of code.  It is widely supported, allowing source code to be used for several different platforms by just recompiling for the new target.  Its popularity means that several third-party add-ons (libraries and modules) are available to “stretch” the language.  It has type checking which helps catch errors.  It is very powerful, allowing you to get “close to the metal”.  Generally, it creates very efficient code (small space and fast execution). What’s the difference between C and C++? C++ is a superset of C. First came C, then came C++. In fact, the name C++ is a programmer’s joke because ++ is the increment operator in C. Thus, C++ literally means “increment C”, or perhaps “give me the next C”. C++ does everything C does plus a whole lot more. These extra features don’t come free and embedded applications usually cannot afford the overhead. Consequently, although much desktop work is done in C++ as well as C, most embedded work is done in C. Desktop development systems are usually 8 Lecture Notes for Embedded Controllers referred to as C/C++ systems meaning that they’ll do both. Embedded development systems may be strictly C (as is ours). Where can I buy an Arduino development board? The Arduino Uno board is available from a variety of sources including Digi-Key, Mouser, Parts Express and others. Shop around What’s the difference between desktop PC development and embedded programming? Desktop development focuses on applications for desktop computers. These include things like word processors, graphing utilities, games, CAD programs, etc. These are the things most people think of when they hear the word “computer”. Embedded programming focuses on the myriad nearly invisible applications that surround us every day. Examples include the code that runs your microwave oven, automobile engine management system, cell phone, and many others. In terms of total units, embedded applications far outnumber desktop applications. You may have one or even a few PCs in your house, but you probably use dozens of embedded applications every day. Embedded microcontrollers tend to be much less powerful but also much less expensive than their PC counterparts. The differing programming techniques are an integral part of this course and we shall spend considerable time examining them. How does C compare with Python? If, like many students taking this course, your background is with the Python language, you may find certain aspects of C a little odd at first. Some of it may seem overly complicated. Do not be alarmed though. The core of the language is actually simple. Python tends to hide things from the programmer while C doesn’t. Initially, this seems to make things more complicated, and it does for the most simple of programs. For more complicated tasks C tends to cut to the heart of the matter. Many kinds of data manipulation are much easier and more efficient in C than in other languages. One practical consideration is that C is a compiled language while most versions of Python are essentially interpreted. This means that there is an extra step in the development cycle, but the resulting compiled program is much more efficient. We will examine why this is so a little later. How does C compare with assembly language? Assembly has traditionally been used when code space and speed are of utmost importance. Years ago, virtually all embedded work was done in assembly. As microcontrollers have increased in power and the C compilers have improved, the tables have turned. The downside of assembly now weighs against it. Assembly is processor-specific, unstructured, not standardized, nor particularly easy to read or write. C now offers similar performance characteristics to assembly but with all the advantages of a modern structured language. Lecture Notes for Embedded Controllers 9 2. Hardware Architecture Introduction When programming in C, it helps if you know at least a little about the internal workings of simple computer systems. As C tends to be “close to the metal”, the way in which certain things are performed as well preferred coding techniques will be more apparent. First off, let’s narrow the field a bit by declaring that we will only investigate a fairly simple system, the sort of thing one might see in an embedded application. That means a basic processor and solid state memory. We won’t worry about disk drives, monitors, and so forth. Guts 101 A basic system consists of a control device called a CPU (central processing unit), microprocessor, or microcontroller. There are subtle distinctions between these, but we have little need to go very deep at this point. Microcontrollers tend not to be as powerful as standard microprocessors in terms of processing speed, but they usually have an array of input/output ports and hardware functions (such as analog to digital or digital to analog converters) on chip that typical microprocessors do not. To keep things simple we shall use the term “processor” as a generic. Processors are often connected to external memory (RAM chips). Microcontrollers generally contain sufficient on-board memory to alleviate this requirement, but it is worthwhile to note that we are not talking about large (megabyte) quantities. A microcontroller may only contain a few hundred bytes of memory, but in simple applications that may be sufficient. Remember, a byte of memory consists of 8 bits, each bit being thought of as a 1/0, high/low, yes/no, or true/false pair. In order for a processor to operate on data held in memory, the data must first be copied into a processor’s register (it may have dozens of registers). Only in a register can mathematical or logical operations be carried out. For example, if you desire to add one to variable, the value of the variable must first be copied into a register. The addition is then performed on the register contents yielding the answer. This answer is then copied back to the original memory location of the variable. It seems a little roundabout at first, but don’t worry, the C language compiler takes care of most of those details for you. Memory Maps Every byte of memory in a computer system has an address associated with it. This is a requirement. Without an address, the processor has no way of identifying a specific location in memory. Generally, memory addressing starts at 0 and works its way up, although some addresses may be special or “reserved” in some systems. That is, a specific address might not refer to normal memory, but might refer to a certain input/output port for external communication. Very often it is useful to draw a “memory map”. This is nothing more than a huge array of memory slots. Some people draw them with the lowest 10 Lecture Notes for Embedded Controllers (starting) address at the top and other people draw them with the lowest address at the bottom. Here’s an example with just six bytes of memory: address 0 address 1 address 2 address 3 address 4 address 5 Each address or slot represents a place we can store one byte. If we had to remember specific addresses we would be doing a lot of work. Instead, the C compiler will keep track of this for us. For example, if we declare a char named X, it might be at address 2. If we need to print that value, we don’t have to say “fetch the value at address 2”. Instead we say; “fetch the value of X” and the compiler generates code to make this work out to the proper address (2). This abstraction eases our mental burden considerably. As many variables require more than one byte, we may need to combine addresses to store a single value. For example, if we chose a short int, that needs two bytes. Suppose this variable starts at address 4. It will also require the use of address 5. When we access this variable the compiler automatically generates the code to utilize both addresses because it “knows” we’re using a short int. Our little six byte memory map could hold 6 char, 3 short int, 1 long int with 1 short int, 1 long int with 2 char, or some other similar combination. It cannot hold a double as that requires 8 bytes. Similarly, it could not hold an array of 4 or more short int. Arrays are of special interest as they must be contiguous in memory. For example, suppose a system has 1000 bytes of memory and a 200 element char array was declared. If this array starts at address 500 then all of the slots from 500 through 699 are allocated for the array. It cannot be created in “scattered” fashion with a few bytes here and a few bytes there. This requirement is due to the manner in which arrays are indexed (accessed), as we shall see later. Stacks Many programs need only temporary storage for certain variables. That is, a variable may only be used for a limited time and then “thrown away”. It would be inefficient to allocate permanent space for this sort of variable. In its place, many systems use a stack. Ordinarily, an application is split into two parts, a code section and a data section. The data section contains the “permanent” (global) data. As these two will not consume the entire memory map, the remainder of the memory is often used for temporary storage via a stack. The stack starts at the opposite end of the memory map and grows toward the code and data sections. It is called a First-In-Last-Out stack or FILO stack. It works like a stack of trays in a cafeteria. The first try placed on the stack will be the last one pulled off and vice versa. When temporary variables are needed, this memory area is used. As more items are needed, more memory is taken up. As our code exits from a function, the temporary (auto) variables declared there are no longer needed, and the stack shrinks. If we make many, many function calls with many, many declared variables, it is Lecture Notes for Embedded Controllers 11 possible for the stack to overrun the code and data sections of our program. The system is now corrupt, and proper execution and functioning of the program are unlikely. address 0 area used by code and data area currently unused /\ stack area. stack grows this way address 65,535 10 Above is a memory map example of a system with 64k bytes of memory (k=1024 or 2 ). Individual memory slots are not shown. Only the general areas are shown. It is worthwhile to note that in some systems, code and data are in a common area as shown (Von Neumann architecture) while in others they are physically split (Harvard architecture). Whether split or not, the basic concepts remain. So, why would we want to split the two areas, each accessed via its own 1 memory bus ? Simple, separating the code and data allows the processor to fetch the next instruction (code) using a memory bus that is physically separate from the data bus it is currently accessing. A shared code/data memory bus would require special timing to coordinate this process as only one thing can be on the bus at any given time. Having two separate memory buses will speed execution times. 1 A bus typically refers to a collection of wires or connections upon which multiple data bits (or address bits) are sent as a group. 12 Lecture Notes for Embedded Controllers 3. C Language Basics Introduction C is a terse language. It is designed for professional programmers who need to do a lot with a little code quickly. Unlike BASIC or Python, C is a compiled language. This means that once you have written a program, it needs to be fed into a compiler that turns your C language instructions into machine code that the microprocessor or microcontroller can execute. This is an extra step, but it results in a more efficient program than an interpreter. An interpreter turns your code into machine language while it’s running, essentially a line at a time. This results in slower execution. Also, in order to run your program on another machine, that machine must also have an interpreter on it. You can think of a compiler as doing the translation all at once instead of a line at a time. Unlike many languages, C is not line oriented, but is instead free-flow. A program can be thought of as consisting of three major components: Variables, statements and functions. Variables are just places to hold things, as they are in any other language. They might be integers, floating point (real) numbers, or some other type. Statements include things such as variable operations and assignments (i.e., set x to 5 times y), tests (i.e., is x more than 10?), and so forth. Functions contain statements and may also call other functions. Variable Naming, Types and Declaration Variable naming is fairly simple. Variable names are a combination of letters, numerals, and the underscore. Upper and lower case can be mixed and the length is typically 31 characters max, but the actual limit depends on the C compiler in use. Further, the variable name cannot be a reserved (key) word nor can it contain special characters such as . ; , - and so on. So, legal names include things like x, volts, resistor7, or even I_Wanna_Go_Home_Now. C supports a handful of variable types. These include floating point or real numbers in two basic flavors: float, which is a 32 bit number, and double, which is a higher precision version using 64 bits. There are also a few integer types including char, which is 8 bits, short int, which is 16 bits, and long int, which is 32 bits. As char is 8 bits, it can hold 2 to the 8th combinations, or 256 different values. This is sufficient for a single ASCII character, hence the name. Similarly, a short int (or short, for short) can hold 2 to the 16th combinations, or 65,536 values. chars and ints may be signed or unsigned (signed, allowing negative values, is the default). There is also a plain old int, which might be either 16 or 32 bits, depending on which is most efficient for the compiler (to be on the safe side, never use plain old int if the value might require more than 16 bits). Sometimes you might also come across special double long integers (also called long longs) that take up 8 bytes as well as 80 bit extended precision floats (as defined by the IEEE). 14 Lecture Notes for Embedded Controllers Here is a table to summarize the sizes and ranges of variables: Variable Type Bytes Used Minimum Maximum char 1 -128 127 unsigned char 1 0 255 short int 2 -32768 32767 unsigned short int 2 0 65535 long int 4 ≈ -2 billion ≈ 2 billion unsigned long int 4 0 ≈ 4 billion float 4 ± 1.2 E -38 ± 3.4 E +38 (6 significant digits) double 8 ± 2.3 E -308 ± 1.7 E +308 (15 significant digits) C also supports arrays and compound data types. We shall examine these in a later segment. Variables must be declared before they are used. They cannot be created on a whim, so to speak, as they are in Python. A declaration consists of the variable type followed by the variable name, and optionally, an initial value. Multiple declarations are allowed. Here are some examples: char x; This declares a signed 8 bit integer called x unsigned char y; This declares an unsigned 8 bit integer called y short z, a; This declares two signed 16 bit integers named z and a float b =1.0; This declares a real number named b and sets its initial value to 1.0 Note that each of these declarations is followed with a semi-colon. The semi-colon is the C language way of saying “This statement ends here”. This means that you can be a little sloppy (or unique) in your way of dealing with spaces. The following are all equivalent and legal: float b = 1.0; float b=1.0; float b = 1.0 ; Functions Functions use the same naming rules as variables. All functions use the same template that looks something like this: return_value function_name( function argument list ) …statements… You might think of the function in the mathematical sense. That is, you give it some value(s) and it gives you back a value. For example, your calculator has a sine function. You send it an angle and it gives you back a value. In C, functions may have several arguments, not just one. They might not even have an argument. Also, C functions may return a value, but they don’t have to. The “guts” of the function are Lecture Notes for Embedded Controllers 15 defined within the opening and closing brace pair . So, a function which takes two integers, x and y, as arguments, and returns a floating point value will look something like this: float my_function( int x, int y ) …appropriate statements here… If the function doesn’t take or return values, the word void is used. If a function neither required values nor returned a value, it would look like: void other_function( void ) …appropriate statements here… This may appear to be extra fussy work at first, but the listing of data types makes a lot of sense because C has something called type checking. This means that if you try to send a function the wrong kind of variable, or even the wrong number of variables, the compiler will warn you that you’ve made a mistake Thus if you try to send my_function() above two floats or three integers, the compiler will complain and save you a big headache during testing. All programs must have a place to start, and in C, program execution begins with a function called main. This does not have to be the first function written or listed, but all programs must have a function called main. OK, so here’s our first program: / Our first program / void main( void ) float x = 2.0; float y = 3.0; float z; z = xy/(x+y); There is only one function here, main(). It takes no variables and returns nothing. What’s the other stuff? 2 First, the / / pair denotes a comment . Anything inside of the comment pair is ignored by the compiler. A C comment can stretch for many lines. Once inside the function, three variables are declared with two of them given initial values. Next, the variables x and y are multiplied together, divided by their sum, and assigned to z. As C is free-flow, an equivalent (but ugly) version is: / Our first program / void main(void) float x=2.0;float y=3.0;float z;z=xy/(x+y); This is the complete opposite of Python which has very rigid spacing and formatting rules. Now, suppose that this add, multiply, divide operation is something that you need to do a lot. We could split this off into its own function. Our program now looks like: 2 C also allows // to denote a single line comment without the “backend pairing”. 16 Lecture Notes for Embedded Controllers / Our second program / float add_mult_div( float a, float b ) float answer; answer = ab/(a+b); return( answer ); void main( void ) float x = 2.0; float y = 3.0; float z; z = add_mult_div( x, y ); The new math function takes two floats as arguments and returns a float to the caller. The compiler sees the new function before it is used in main(), thus, it already “knows” that it should be sent two floats and that the return value must be assigned to a float. It is very important to note that the new math function uses different variable names (a and b) from the caller (x and y). The variables in the new math function are really just place-holders. The values from the original call (x and y) are copied to these new variables (a and b) and used within the new function. As they are copies, they can be altered without changing the original values of x and y. In this case, x and y are said to be local to the main() function while a and b are local to the add_mult_div() function. In other words, a isn’t visible from main() so you can’t accidentally alter it Similarly, x isn’t visible from add_mult_div(), so you can’t accidentally alter it either. This is a positive boon when dealing with large programs using many variable names. While it’s not usually preferred, there are times when you want a variable to be known “everywhere”. These are called global items. You can make variables global by simply declaring them at the beginning of the program outside of the functions (i.e., right after that initial comment in our example). Libraries The examples above are rather limited because, although they perform a calculation, we have no way of seeing the result We need some way to print the answer to the computer screen. To do this, we rely on system functions and libraries. There are a series of libraries included with most C development systems to cover a variety of needs. Essentially, someone has already coded, tested and compiled a bunch of functions for you. You add these functions to your program through a process called linking. Linking simply combines your compiled code along with any required library code into a final executable program. For basic printouts, data input, and the like, we use the standard IO (Input/Output) library, or stdio for short. There is a function in this library named printf() for “print formatted”. So that the compiler can do type checking, it must know something about this new function. We tell the compiler to look into a special file called a header file to find this information. Every library will have an associated header file (usually of the same name) and it will normally end with a .h file extension. The compiler directive is called an include statement. Lecture Notes for Embedded Controllers 17 // Our third program, this is an example of a single line comment include stdio.h void main( void ) printf(“Hello world.\n”); This program simply prints the message Hello world. to the screen. The backslash-n combo is a special formatting token that means add a new line (i.e., bring the cursor to the line below). If we did not add the include directive, the compiler wouldn’t know anything about printf(), and would complain when we tried to use it. So, what’s in a header file? Well, among other things they contain function prototypes. The prototypes are nothing more than a template. You can create your own by cutting and pasting your function name with argument list and adding a semicolon to it. Here is the function prototype for our earlier math function: float add_mult_div( float a, float b ); You could make your own library of functions if you want. To use them, all you’d need is an appropriate include statement in your code, and remember to add in your library code with the linker. This will allow you to reuse code and save time. We will look at multiple file projects and more on libraries in a later segment. OK, so if we want to print out the answer to the first program, we’d wind up with something like this: / Our fourth program / include stdio.h void main( void ) float x = 2.0; float y = 3.0; float z; z = xy/(x+y); printf(“The answer is %f\n”, z); The %f in the printf() function serves as a place holder for the variable z. If you need to print several values you can do something like this: printf(“The answer from %f and %f is %f\n”, x, y, z); In this case, the first %f is used for x, the second %f for y, and the final one for z. The result will look like: The answer from 2.0 and 3.0 is 1.2 18 Lecture Notes for Embedded Controllers Some Simple Math C uses the same basic math operators as many other languages. These include +, -, /(divide), and (multiply). Parentheses are used to group elements and force hierarchy of operations. C also includes % for modulo. Modulo is an integer operation that leaves the remainder of a division, thus 5 modulo 7 is 2. The divide behaves a little differently with integers than with floats as there can be no remainder. Thus 9 integer divide 4 is 2, not 2.25 as it would be if you were using floats. C also has a series of bit manipulators that we will look at a little later. For higher math operations, you will want to look at the math library (math.h header file). Some examples are sin(), cos(), tan(), log10() (common log) and pow() for powers and roots. Do not try to use as you do on many calculators. x raised to the y power is not xy but rather pow(x, y). The operator has an entirely different meaning in C Recalling what we said earlier about libraries, if you wanted to use a function like sin() in your code, you’d have to tell the compiler where to find the prototype and similar info. At the top of your program you’d add the line: include math.h A final caution: The examples above are meant to be clear, but not necessarily the most efficient way of doing things. As we shall see, sometimes the way you code something can have a huge impact on its performance. Given the power of C, expert programmers can sometimes create code that is nearly indecipherable for ordinary people. There is a method behind the apparent madness. The program creation/development cycle To create a C program: 1. Do the requisite mental work. This is the most important part. 2. Create the C source code. This can be done using a text editor, but is normally done within the IDE (Integrated Development Environment). C source files are plain text and saved with a “.c” extension. 3. Compile the source code. This creates an assembly output file. Normally, compiling automatically fires up the assembler, which turns the assembly file into a machine language output file. 4. Link the output file with any required libraries using the linker. This creates an executable file. For desktop development, this is ready to test. 5. For embedded development, download the resulting executable to the target hardware (in our case, the Arduino development board). For the Arduino, steps 3, 4, and 5 can be combined by selecting “Build” from the IDE menu. 6. Test the executable. If it doesn’t behave properly, go back to step one. Lecture Notes for Embedded Controllers 19 Summary Here are some things to keep in the back of your mind when learning C:  C is terse. You can do a lot with a little code.  As it allows you to do almost anything, a novice can get into trouble very quickly.  It is a relatively thin language, meaning that most “system functions” are not part of the language per se, but come from link-time libraries.  Function calls, function calls, and more function calls  Source code is free flow, not line oriented. A “line” of code is usually terminated with a semicolon.  Shortcuts allow experts to create code that is almost indecipherable by normal programmers.  All variables must be declared before use (not free flow as in Python).  Variables can be global or local in scope. That is, a local variable can be “known” in one place of the program and not in another. 20 Lecture Notes for Embedded Controllers Lecture Notes for Embedded Controllers 21 4. C Basics II Input and Output We’ve seen the use of printf() to send information to the computer screen. printf() is a very large and complicated function with many possible variants of format specifiers. Format specifiers are the “% things” used as placeholders for values. Some examples are: %f float %lf double (long float) %e float using exponent notation %g float using shorter of e or f style %d decimal integer %ld decimal long integer %x hexadecimal (hex or base 16) integer %o octal (base 8) integer %u unsigned integer %c single character %s character string Suppose that you wanted to print out the value of the variable ans in decimal, hex, and octal. The following instruction would do it all: printf(“The answer is %d, or hex %x, or octal %o.\n”, ans, ans, ans ); Note how the three variables are labeled. This is important. If you printed something in hex without some form of label, you might not know if it was hex or decimal. For example, if you just saw the number “23”, how would you know it’s 23 decimal or 23 hex (35 decimal)? For that matter, how would you set a hex constant in your C code? The compiler would have no way of “knowing” either. To get around this, hex values are prefixed with 0x. Thus, we have 0x23 for hex 23. The printf() function does not automatically add the 0x on output. The reason is because it may prove distracting if you have a table filled only with hex values. It’s easy enough to use 0x%d instead of just %d for the output format. You can also add a field width specifier. For example, %5d means print the integer in decimal with 5 spaces minimum. Similarly, %6.2f means print the floating point value using 6 spaces minimum. The “.2” is a precision specifier, and in this case indicates 2 digits after the decimal point are to be used. As you can see, this is a very powerful and flexible function The mirror input function is scanf(). This is similar to Python's raw_input statement. Although you can ask for several values at once, it is generally best to ask for a single value when using this function. It uses the same sort of format specifiers as printf(). There is one important point to note. The scanf() function needs to know where to place the entered value in computer memory. Simply informing it of the name of the variable is insufficient. You must tell it where in memory the variable is, in other words, you must specify the address of the variable. C uses the & operator to signify “address of”. For example, if you wish to obtain an integer from the user and place it in a variable called voltage, you might see a program fragment like so… 22 Lecture Notes for Embedded Controllers printf(“Please enter the voltage:”); scanf(“%d”, &voltage); It is very common for new programmers to forget the &. Be forewarned Variable Sizes A common question among new programers is “Why are there so many sizes of variables available?” We have two different sizes of reals; float at 32 bits, and double at 64 bits. We also have three different 3 sizes of intgers at 8, 16, and 32 bits each . In many languages, there’s just real and integer with no size variation, so why does C offer so many choices? The reason is that “one size doesn’t fit all”. You have options in order to optimize your code. If you have a variable that ranges from say, 0 to 1000, there’s no need to use more than a short (16 bit) integer. Using a 32 bit integer simply uses more memory. Now, you might consider 2 extra bytes to be no big deal, but remember that we are talking about embedded controllers in some cases, not desktop systems. Some small controllers may have only a few hundred bytes of memory available for data. Even on desktop systems with gigabytes of memory, choosing the wrong size can be disastrous. For example, suppose you have a system with an analog to digital converter for audio. The CD standard sampling rate is 44,100 samples per second. Each sample is a 16 bit value (2 bytes), producing a data rate of 88,100 bytes per second. Now imagine that you need enough memory for a five minute song in stereo. That works out to nearly 53 megabytes of memory. If you had chosen long (32 bit) integers to hold these data, you’d need about 106 megabytes instead. As the values placed on an audio CD never exceed 16 bits, it would be foolish to allocate more than 16 bits each for the values. Data sizes are power-of-2 multiples of a byte though, so you can’t choose to have an integer of say, 22 bits length. It’s 8, 16, or 32 for the most part (some controllers have an upper limit of 16 bits). In the case of float versus double, float is used where space is at a premium. It has a smaller range (size of exponent) and a lower precision (number of significant digits) than double. double is generally preferred and is the norm for most math functions. Plain floats are sometimes referred to as singles (that is, single precision versus double precision). If you don’t know the size of a particular data item (for example an int might be either 16 or 32 bits depending on the hardware and compiler), you can use the sizeof() command. This looks like a function but it’s really built into the language. The argument is the item or expression you’re interested in. It returns the size required in bytes. size = sizeof( int ); size will be either 2 or 4 depending on the system. 3 In some systems, 80 bit doubles and 64 bit integers are also available. Lecture Notes for Embedded Controllers 23 More Math OK, so what happens if you add or multiply two short int together and the result is more than 16 bits long? You wind up with an overrange condition. Note that the compiler cannot warn you of this because whether or not this happens will depend entirely on values entered by the user and subsequently computed within the program. Hopefully, you will always consider maximum value cases and choose appropriate data sizes and this won’t be a problem. But what actually happens? To put it simply, the top most bits will be ignored. Consider an 8 bit unsigned integer. It goes from 0 to 255. 255 is represented as eight 1s. What happens if you add the value 1 to this? You get a 9 bit number: a 1 followed by eight 0s. That ninth bit is thrown away as the variable only has eight bits. Thus, 255 plus 1 equals 0 This can create some serious problems For example, suppose you wanted to use this variable as a loop counter. You want to go through a loop 500 times. The loop will never terminate because an 8 bit integer can’t go up that high. You keep adding one to it, but it keeps flipping back to 0 after it hits 255. This behavior is not all bad; it can, in fact, be put to good use with things like interrupts and timers, as we shall see. What happens if you mix different types of variables? For example, what happens if you divide a double by an int or a float by double? C will promote the lesser size/precision types to the larger type and then do the operation. This can sometimes present a problem if you try to assign the result back to something smaller, even if you know it will always “fit”. The compiler will complain if you divide a long int by another long int and try to assign the result to a short int. You can get around this by using a cast. This is your way of telling the compiler that you know there is a potential problem, but to go ahead anyway (hopefully, because you know it will always work, not just because you want to defeat the compiler warning). Casting in C is similar to type conversion in Python (e.g., the int() function). Here’s an example. short int x, y=20; long int z=3; x=(short int)(y/z); Note how you are directing the compiler to turn the division into a short int. Otherwise, the result is in fact a long int due to the promotion of y to the level of z. What’s the value of x? Why it’s 6 of course Remember, the fractional part is meaningless, and thus lost, on integer divides. Casting is also useful when using math functions. If you have to use float, you can cast them to/from double to make use of functions defined with double. For example, suppose a, b, and c are declared as float but you wish to use the pow() function to raise a to the b power. pow() is defined as taking two double arguments and returning a double answer. c = (float)pow( (double)a, (double)b ); This is a very explicit example. Many times you can rely on a “silent cast” promotion to do your work for you as in the integer example above. Sometimes being explicit is a good practice just as a form of documentation. 24 Lecture Notes for Embedded Controllers Bitwise Operations Sometimes you’d like to perform bitwise operations rather than ordinary math. For example, what if you want to logically AND two variables, bit by bit? Bitwise operations are very common when programming microcontrollers as a means of setting, clearing and testing specific bits in control registers (for example, setting a specific pin on a digital port to read mode instead of write mode). C has a series of bitwise operators. They are: & AND OR XOR One’s Complement Shift Right Shift Left Note the double use of & for “address of” and now AND. The unary operation is always “address of”, and the binary operation is always AND, so a & b would not imply the address of b. If you wanted to AND x with y, shift the result 2 places to the left and assign the result to z, you’d use: z = (x&y)2; Setting, Clearing and Reading Register Bits Bitwise operations may appear to be somewhat arcane to the uninitiated but are in fact commonly used. A prime use is in setting, clearing and testing specific bits in registers. One example involves configuring bidirectional ports for input or output mode via a data direction register, typically abbreviated DDR. Each bit of the DDR represents a specific output pin. A logic high might indicate output mode while a logic low would indicate input mode. Assuming DDR is an 8 bit register, if you wanted to set all bits th 4 except the 0 bit to input mode, you could write : DDR = 0x01; // set bit zero to output mode st nd If sometime later you wanted to also set the 1 and 2 bits to output mode while keeping everything else intact, the easy way to do it is simply to OR the bits you want: DDR = DDR 0x06; or using the more common shorthand: DDR = 0x06; Note that the code above does not affect any of the other bits so they stay in whatever mode they were originally. By the way, a set of specific bits (such as the 0x06 above) is often referred to as a bit pattern or bitmask. 4 In C, bit position counting, like most sequences, starts from position 0 not position 1. Lecture Notes for Embedded Controllers 25 st To see if a specific bit is set, simply AND instead of OR. So, to see if the 1 bit of DDR is set for output mode, you could use something like: if ( DDR & 0x02 ) // true if set Clearing bits requires ANDing with a bitmask that has been complemented. In other words, all 1s and 0s th th have been reversed in the bit pattern. If, for example, we want to clear the 0 and 4 bits, we’d first complement the bit pattern 0x11 yielding 0xee. Then we AND: DDR &= 0xee; Often, it’s easier to just use the logical complement operator on the original bit pattern and then AND it: DDR &= (0x11); If you’re dealing with a single bit, you can use the left shift operator so you don’t even have to bother rd th figuring out the bit pattern in hex. To set the 3 bit and then clear the 4 bit of DDR, you could use the following: DDR = (0x013); DDR &= (0x014); These operations are so common that they are often invoked using an in-line expansion via a define. define Very often it is desirable to use symbolic constants in place of actual values. For example, you’d probably prefer to use a symbol such as PI instead of the number 3.14159. You can do this with the define preprocessor directive. These are normally found in header files (such as stdio.h or math.h) or at the top of a module of C source code. You might see something like: define PI 3.14159 Once the compiler sees this, every time it comes across the token PI it will replace it with the value 3.14159. This directive uses a simple substitution but you can do many more complicated things than this. For example, you can also create something that looks like a function: define parallel((x),(y)) ((x)(y))/((x)+(y)) The x and y serve as placeholders. Thus, the line a = parallel( b, c ); gets expanded to: a = (ab)/(a+b); Why do this? Because it’s an in-line expansion or macro. That means that there’s no function call overhead and the operation runs faster. At the same time, it reads like a function, so it’s easier for a programmer to follow. OK, but why all the extra parentheses? The reason is because x and y are 26 Lecture Notes for Embedded Controllers placeholders, and those items might be expressions, not simple variables. If you did it this way you might get in trouble: define parallel(x,y) xy/(x+y) What if x is an expression, as in the following example? a = parallel(2+b,c); This would expand to: a = 2+bc/(2+b+c); As multiplication is executed before addition, you wind up with 2 being added to the product of b times c after the division, which is not the same as the sum of 2 and b being multiplied by c, and that quantity then being divided. By using the extra parentheses, the order of execution is maintained. Referring back to the bit field operations, here are some useful definitions for what appear to be functions but which are really just bitwise operations expanded in-line: define bitRead(value, bit) (((value) (bit)) & 0x01) define bitSet(value, bit) ((value) = (1UL (bit))) define bitClear(value, bit) ((value) &= (1UL (bit))) The 1UL simply means 1 expressed as an unsigned long. Finally, bit could also be defined as a symbol which leads to some nice looking self-documenting code: define LEDBIT 7 // more code here… bitSet( DDR, LEDBIT ); define expansions can get quite tricky because they can have nested references. This means that one define may contain within it a symbol which is itself a define. Following these can be a little tedious at times but ultimately are worth the effort. We shall look at a few down the road. Remember, these are done to make day-to-day programming easier, not to obfuscate the code. For now, start with simple math constant substitutions. They are extremely useful and easy to use. Just keep in the back of your mind that, with microcontrollers, specific registers and ports are often given symbolic names such as PORTB so that you don't have to remember the precise numeric addresses of them. The norm is to place these symbolic constants in ALL CAPS. Lecture Notes for Embedded Controllers 27

Advise: Why You Wasting Money in Costly SEO Tools, Use World's Best Free SEO Tool Ubersuggest.