Programming embedded systems in c and c++ pdf download






















This should provide reader with sufficient knowledge to develop and test other, more complicated C programs for small scale embedded systems employing this microcontrollers. The same statement in an embedded system causes the string to be transmitted - there are many factors to consider when selecting languages for via the serial port pin i. In an embedded system the and utilise various features of the microcontroller's internal and instruction causes a character to be read from the serial pin i.

RXD external architecture; this includes: of the microcontroller. P0 the symbol used for port 0 of the processor is assigned its corresponding numeric address 80 in hexadecimal.

This tells the compiler to send the number 0xFF which is the hexadecimal equivalent of or in binary to port 1 which in turn causes the 8 LEDs in port 1 to turn ON. Note that just like the line in the main body of the program, every line of a C program must end with a semicolon i. Note that the first line immediately after the beginning of the main Program to indefinitely flash all LEDs on port 1 at a rate of 1 second.

Note the absence of semicolon at the end of function declaration. Instructions to generate single loop delay using those two loop Program from Examples 2 and 3 is rewritten and modified so that LEDs techniques are given below. False Condition True? Result is 0. Using ; bit-wise AND in this manner is called bit masking. For example if switch easily be accessed by the program at some later stage S0 is pressed, program will turn LED0 ON, if S2 and S3 are pressed, - as any other data in the program array must be declared at the then LED 2 and 3 must turn ON and so on.

Example The timers have three general functions: 0. This is actually a way to 2 Counting the events themselves event counting mode measure time using timers on the microcontroller — by counting the 3 Generating baud rates for the serial port number of timer increments during the measured time period.

All that is The first two uses will be discussed in this handout while the use of now needed is to learn how to control and initialise timers to provide the timers for baud rate generation will be considered in the handout needed information.

This is done through the proper setting of a related to serial ports. When the timer reaches the maximum value and is Two timers are called Timer0 and Timer1 and they share two subsequently incremented, it will reset- or overflow -back to 0.

When a timer is in interval timer mode as opposed to event counter Timer registers names; brief descriptions and addresses are given in mode and correctly configured, it will increment by 1 every machine the table below.

When Timer 0 has the value of for example , TH0 will hold the high byte of the value 3 decimal and TL0 will contain the low byte of the value decimal. The final value When this bit is clear the timer will be incremented every machine cycle. When this bit is clear the timer will run regardless of the state of INT1. The high four bits bits 4 through 7 The modes of operation determined with four bits from the TMOD relate to Timer 1 whereas the low four bits bits 0 through 3 perform register are: the same functions, but for timer 0.

This mode is used to maintain compatability of with the older generation microcontroller, the Generally the bit timer mode is not used in new development. When the timer is in bit mode, TLx will count from 0 to When TLx is incremented from 31, it will "reset" to 0 and increment THx. Thus, effectively, only 13 bits of the two timer bytes are being used: bits of TLx and bits of THx. This also means, in essence, the timer can only contain values.

If you set a bit timer to 0, it will overflow back to zero machine cycles later. In this mode THx holds the "reload value" and TLx is the timer itself. Thus, TLx starts counting up. When TLx reaches and is subsequently incremented, instead of resetting to 0 as in the case of modes 0 and 1 , it will be reset to the value stored in THx.

In this mode microcontroller hardware takes care of checking for the timer overfow and reloading of the timer, thus saving some of the processing time. Timer mode "1" is a bit timer. Compiling, Linking, and Locating I consider that the golden rule requires that if I like a program I must share it with other people who like it.

Software sellers want to divide the users and conquer them, making each user agree not to share with others. I refuse to break solidarity with other users in this way.

I cannot in good conscience sign a nondisclosure agreement or a software license agreement. So that I can continue to use computers without dishonor, I have decided to put together a sufficient body of free software so that I will be able to get along without any software that is not free. We'll also discuss the associated development tools and see how to build the Blinking LED program shown in Chapter 2.

But before we get started, I want to make it clear that embedded systems programming is not substantially different from the programming you've done before. The only thing that has really changed is that each target hardware platform is unique. Unfortunately, that one difference leads to a lot of additional software complexity, and it's also the reason you'll need to be more aware of the software build process than ever before. For example, if all of your programs will be executed on IBM-compatible PCs running DOS, your compiler can automate-and, therefore, hide from your view-certain aspects of the software build process.

Embedded software development tools, on the other hand, can rarely make assumptions about the target platform. Instead, the user must provide some of his own knowledge of the system to the tools by giving them more explicit instructions. If no operating system is present-as is sometimes the case in an embedded system-the target platform is simply the processor on which your program will be run.

The process of converting the source code representation of your embedded software into an executable binary image involves three distinct steps. First, each of the source files must be compiled or assembled into an object file. Second, all of the object files that result from the first step must be linked together to produce a single object file, called the relocatable program. The result of this third step is a file that contains an executable binary image that is ready to be run on the embedded system.

The embedded software development process just described is illustrated in Figure In this figure, the three steps are shown from top to bottom, with the tools that perform them shown in boxes that have rounded corners.

Each of these development tools takes one or more files as input and produces a single output file. More specific information about these tools and the files they produce is provided in the sections that follow. The embedded software development process Each of the steps of the embedded software build process is a transformation performed by software running on a general-purpose computer.

To distinguish this development computer usually a PC or Unix workstation from the target embedded system, it is referred to as the host computer.

In other words, the compiler, assembler, linker, and locator are all pieces of software that run on a host computer, rather than on the embedded system itself. Yet, despite the fact that they run on some other computer platform, these tools combine their efforts to produce an executable binary image that will execute properly only on the target embedded system.

This split of responsibilities is shown in Figure The split between host and target In this chapter and the next I'll be using the GNU tools compiler, assembler, linker, and debugger as examples. These tools are extremely popular with embedded software developers because they are freely available even the source code is free and support many of the most popular embedded processors.

I will use features of these specific tools as illustrations for the general concepts discussed. Once understood, these same basic concepts can be applied to any equivalent development tool. In that sense, an assembler is also a compiler you might call it an "assembly language compiler" but one that performs a much simpler one-to-one translation from one line of human-readable mnemonics to the equivalent opcode. Everything in this section applies equally to compilers and assemblers.

Together these tools make up the first step of the embedded software build process. Of course, each processor has its own unique machine language, so you need to choose a compiler that is capable of producing programs for your specific target processor. In the embedded systems case, this compiler almost always runs on the host computer. It simply doesn't make sense to execute the compiler on the embedded system itself. A compiler such as this-that runs on one computer platform and produces code for another-is called a cross-compiler.

The use of a cross-compiler is one of the defining features of embedded software development. As cross-compilers these tools support an impressive set of host-target combinations. Of course, the selections of host platform and target processor are independent; these tools can be configured for any combination. Table This is a specially formatted binary file that contains the set of instructions and data resulting from the language translation process. Although parts of this file contain executable code, the object file is not intended to be executed directly.

In fact, the internal structure of an object file emphasizes the incompleteness of the larger program. The contents of an object file can be thought of as a very large, flexible data structure. If you'll be using more than one compiler i.

Although many compilers particularly those that run on Unix platforms support standard object file formats like COFF and ELF gcc supports both , there are also some others that produce object files only in proprietary formats. If you're using one of the compilers in the latter group, you might find that you need to buy all of your other development tools from the same vendor. Each of these sections contains one or more blocks of code or data that originated within the original source file.

However, these blocks have been regrouped by the compiler into related sections. For example, all of the code blocks are collected into a section called text, initialized global variables and their initial values into a section called data, and uninitialized global variables into a section called bss.

There is also usually a symbol table somewhere in the object file that contains the names and locations of all the variables and functions referenced within the source file. Parts of this table may be incomplete, however, because not all of the variables and functions are always defined in the same file. These are the symbols that refer to variables and functions defined in other source files.

And it is up to the linker to resolve such unresolved references. The object files themselves are individually incomplete, most notably in that some of the internal variable and function references have not yet been resolved. The job of the linker is to combine these object files and, in the process, to resolve all of the unresolved symbols. The output of the linker is a new object file that contains all of the code and data from the input object files and is in the same object file format.

It does this by merging the text, data, and bss sections of the input files. So, when the linker is finished executing, all of the machine language code from all of the input object files will be in the text section of the new file, and all of the initialized and uninitialized variables will reside in the new data and bss sections, respectively.

While the linker is in the process of merging the section contents, it is also on the lookout for unresolved symbols. For example, if one object file contains an unresolved reference to a variable named foo and a variable with that same name is declared in one of the other object files, the linker will match them up. The unresolved reference will be replaced with a reference to the actual variable.

In other words, if foo is located at offset 14 of the output data section, its entry in the symbol table will now contain that address. It is essentially a command-line tool that takes the names of all the object files to be linked together as arguments. For embedded development, a special object file that contains the compiled startup code must also be included within this list.

See Startup Code later in this chapter. The GNU linker also has a scripting language that can be used to exercise tighter control over the object file that is output. Startup code is a small block of assembly language code that prepares the way for the execution of software written in a high-level language.

Each high-level language has its own set of expectations about the runtime environment. Space for the stack has to be allocated and initialized before software written in either language can be properly executed.

Most cross-compilers for embedded systems include an assembly language file called startup. The location and contents of this file are usually described in the documentation supplied with the compiler. Disable all interrupts.

Zero the uninitialized data area. Allocate space for and initialize the stack. Initialize the processor's stack pointer. Create and initialize the heap. Enable interrupts. Call main. Typically, the startup code will also include a few instructions after the call to main.

These instructions will be executed only in the event that the high-level language program exits i. Depending on the nature of the embedded system, you might want to use these instructions to halt the processor, reset the entire system, or transfer control to a debugging tool. Because the startup code is not inserted automatically, the programmer must usually assemble it himself and include the resulting object file among the list of input files to the linker.

He might even need to give the linker a special command-line option to prevent it from inserting the usual startup code. Working startup code for a variety of target processors can be found in a GNU package called libgloss. If the same symbol is declared in more than one object file, the linker is unable to proceed.

It will likely appeal to the programmer-by displaying an error message-and exit. The reference might be to a function that is part of the standard library, so the linker will open each of the libraries described to it on the command line in the order provided and examine their symbol tables. If it finds a function with that name, the reference will be resolved by including the associated code and data sections within the output object file.

In non-embedded environments, dynamic linking of libraries is very common. In that case, the code and data associated with the library routine are not inserted into the program directly. Unfortunately, the standard library routines often require some changes before they can be used in an embedded program. The problem here is that the standard libraries provided with most software development tool suites arrive only in object form.

So you only rarely have access to the library source code to make the necessary changes yourself. Thankfully, a company called Cygnus has created a freeware version of the standard C library for use in embedded systems. This package is called newlib. You need only download the source code for this library from the Cygnus web site, implement a few target-specific functions, and compile the whole lot.

The library can then be linked with your embedded software to resolve any previously unresolved standard library calls. After merging all of the code and data sections and resolving all of the symbol references, the linker produces a special "relocatable" copy of the program. In other words, the program is complete except for one thing: no memory addresses have yet been assigned to the code and data sections within.

If you weren't working on an embedded system, you'd be finished building your software now. But embedded programmers aren't generally finished with the build process at this point. Even if your embedded system includes an operating system, you'll probably still need an absolutely located binary image. In fact, if there is an operating system, the code and data of which it consists are most likely within the relocatable program too.

The entire embedded application-including the operating system-is almost always statically linked together and executed as a single binary image. It takes responsibility for the easiest step of the three. In fact, you will have to do most of the work in this step yourself, by providing information about the memory on the target board as input to the locator. The locator will use this information to assign physical memory addresses to each of the code and data sections within the relocatable program.

It will then produce an output file that contains a binary memory image that can be loaded into the target ROM. However, in the case of the GNU tools, this functionality is built right into the linker.

Try not to be confused by this one particular implementation. Whether you are writing software for a general-purpose computer or an embedded system, at some point the sections of your relocatable program must have actual addresses assigned to them.

In the first case, the operating system does it for you at load time. In the second, you must perform the step with a special tool. This is true even if the locator is a part of the linker, as it is in the case of ld. The memory information required by the GNU linker can be passed to it in the form of a linker script. Such scripts are sometimes used to control the exact order of the code and data sections within the relocatable program.

But here, we want to do more than just control the order; we also want to establish the location of each section in memory.

The linker will use these symbols to resolve references in the input object files. So, for example, there might be a part of the embedded software usually within the startup code that copies the initial values of the initialized variables from ROM to the data section in RAM.

The result of this final step of the build process is an absolutely located binary image that can be downloaded to the embedded system or programmed into a read-only memory device. In the previous example, this memory image would be exactly 1 MB in size.

However, because the initial values for the initialized data section are stored in ROM, the lower kilobytes of this image will contain only zeros, so only the upper half of this image is significant.

You'll see how to download and execute such memory images in the next chapter. See Appendix A for ordering information. I used version 3. However, any version of the Borland tools that can produce code for the processor will do. However, the inclusion of certain command-line options allows us to specify a particular 80x86 processor-the , for example-and, thus, use this tool as a cross-compiler for embedded systems like the Arcom board.

The first step in the build process is to compile these two files. In other words, you should be in the Chapter2 subdirectory. The result of each of these commands is the creation of an object file that has the same prefix as the.

So if all goes well, there will now be two additional files-led. Although it would appear that there are only these two object files to be linked together in our example, there are actually three.

That's because we must also include some startup code for the C program. See Startup Code earlier in this chapter. Example startup code for the Arcom board is provided in the file startup. The command that's actually used to link the three object files together is shown here. Beware that the order of the object files on the command line does matter in this case: the startup code must be placed first for proper linkage.

The first file contains the relocatable program and the second contains a human-readable program map. If you have never seen such a map file before, be sure to take a look at this one before reading on.

It provides information similar to the contents of the linker script described earlier. However, these are results and, therefore, include the lengths of the sections and the names and locations of the public symbols found in the relocatable program. One more tool must be used to make the Blinking LED program executable: a locator.

The locating tool we'll be using is provided by Arcom, as part of the SourceVIEW development and debugging package included with the board.

Because this tool is designed for this one particular embedded platform, it does not have as many options as a more general locator. ROM image size H bytes 2K The tcrom locator massages the contents of the relocatable input file-assigning base addresses to each section-and outputs the file blink.

This file contains an absolutely located binary image that is ready to be loaded directly into ROM. For this we will use a utility provided by Arcom, called bin2hex. Here is the syntax of the command: bin2hex blink. Downloading and Debugging I can remember the exact instant when I realized that a large part of my life from then on was going to be spent in finding mistakes in my own programs.

The executable binary image is usually loaded into a memory device on the target board and executed from there. And if you have the right tools at your disposal, it will be possible to set breakpoints in the program or to observe its execution in less intrusive ways.

This chapter describes various techniques for downloading, executing, and debugging embedded software. One of the most obvious ways to download your embedded software is to load the binary image into a read-only memory device and insert that chip into a socket on the target board.

Obviously, the contents of a truly read-only memory device could not be overwritten. However, as you'll see in Chapter 6, embedded systems commonly employ special read-only memory devices that can be programmed or reprogrammed with the help of a special piece of equipment called a device programmer. A device programmer is a computer system that has several memory sockets on the top-of varying shapes and sizes-and is capable of programming memory devices of all sorts.

In an ideal development scenario, the device programmer would be connected to the same network as the host computer. That way, files that contain executable binary images could be easily transferred to it for ROM programming. After the binary image has been transferred to the device programmer, the memory chip is placed into the appropriately sized and shaped socket and the device type is selected from an on-screen menu.

The actual device programming process can take anywhere from a few seconds to several minutes, depending on the size of the binary image and the type of memory device you are using. After you program the ROM, it is ready to be inserted into its socket on the board. Of course, this shouldn't be done while the embedded system is still powered on. The power should be turned off and then reapplied only after the chip has been carefully inserted.

As soon as power is applied to it, the processor will begin to fetch and execute the code that is stored inside the ROM. However, beware that each type of processor has its own rules about the location of its first instruction.

For example, when the Intel EB processor is reset, it begins by fetching and executing whatever is stored at physical address FFFF0h. If your program doesn't appear to be working, it could be there is something wrong with your reset code. You must always ensure that the binary image you've loaded into the ROM satisfies the target processor's reset rules. During product development, I often find it useful to turn on one of the board's LEDs just after the reset code has been completed.

That way, I know at a glance that my new ROM either does or doesn't satisfy the processor's most basic requirements. Debugging Tip 1: One of the most primitive debugging techniques available is the use of an LED as indicator of success or failure. The basic idea is to slowly walk the LED enable code through the larger program.

In other words, you first begin with the LED enable code at the reset address. If the LED turns on, then you can edit the program, moving the LED enable code to just after the next execution milestone, rebuild, and test. This works best for very simple, linearly executed programs like the startup code. But if you don't have access to a remote debugger or any of the other debugging tools described later in this chapter, this type of debugging might be your only choice.

The Arcom board includes a special in-circuit programmable memory, called Flash memory, that does not have to be removed from the board to be reprogrammed. In fact, software that can perform the device programming function is already installed in another memory device on the board.

You see, the Arcom board actually has two read-only memory devices-one a true ROM contains a simple program that allows the user to in-circuit program the other a Flash memory device.

All the host computer needs to talk to the monitor program is a serial port and a terminal program. Instructions for loading an Intel Hex Format file, like blink. The biggest disadvantage of this download technique is that there is no easy way to debug software that is executing out of ROM. The processor fetches and executes the instructions at a high rate of speed and provides no way for you to view the internal state of the program.

This might be fine once you know that your software works and you're ready to deploy the system, but it's not very helpful during software development. Of course, you can still examine the state of the LEDs and other externally visible hardware but this will never provide as much information and feedback as a debugger. The frontend of a remote debugger looks just like any other debugger that you might have used. It usually has a text or GUI-based main window and several smaller windows for the source code, register contents, and other relevant information about the executing program.

However, in the case of embedded systems, the debugger and the software being debugged are executing on two different computer systems. A remote debugger actually consists of two pieces of software. The frontend runs on the host computer and provides the human interface just described.

But there is also a hidden backend that runs on the target processor and communicates with the frontend over a communications link of some sort. The backend provides for low-level control of the target processor and is usually called the debug monitor. Figure shows how these two components work together. A remote debugging session The debug monitor resides in ROM-having been placed there in the manner described earlier either by you or at the factory -and is automatically started whenever the target processor is reset.

It monitors the communications link to the host computer and responds to requests from the remote debugger running there. Of course, these requests and the monitor's responses must conform to some predefined communications protocol and are typically of a very low-level nature. Examples of requests the remote debugger can make are "read register x," "modify register y," "read n bytes of memory starting at address," and "modify the data at address.

One such debugger is the GNU debugger gdb. Like the other GNU tools, it was originally designed for use as a native debugger and was later given the ability to perform cross-platform debugging. So you can build a version of the GDB frontend that runs on any supported host and yet understands the opcodes and register names of any supported target.

Source code for a compatible debug monitor is included within the GDB package and must be ported to the target platform. However, beware that this port can be tricky, particularly if you only have LED debugging at your disposal see Debugging Tip 1.

The command format and some of the major commands are shown in Table These commands exemplify the type of interactions that occur between the typical remote debugger frontend and the debug monitor. This is mainly because of their low cost. Embedded software developers already have the requisite host computer. In addition, the price of a remote debugger frontend does not add significantly to the cost of a suite of cross-development tools compiler, linker, locator, etc.

Finally, the suppliers of remote debuggers often desire to give away the source code for their debug monitors, in order to increase the size of their installed user base.

As shipped, the Arcom board includes a free debug monitor in Flash memory. Together with host software provided by Arcom, this debug monitor can be used to download programs directly into target RAM and execute them. To do this, you can use the tload utility. In this case, though, we want to start with the relocatable program. The tload utility will automatically locate the program for us, at the first available location in RAM. For remote debugging purposes, Arcom's debug monitor can be used with Borland's Turbo Debugger as the frontend.

Here's the command you would use to start a debugging session for the Blinking LED program: tdr blink. The first tells the on-board debug monitor which version of Turbo Debugger you will be using, and the second actually invokes it.

Both of these commands need to be issued each time you want to start a remote debugging session with the Arcom board. The tdr. Again we use the relocatable version of the program because we will be downloading the program into RAM and executing it from there. The debugger startup options -rp1 and -rs3 establish the parameters for the communications link to the debug monitor.

These are the parameters required to communicate with Arcom's debug monitor. After establishing a connection to the debug monitor, Turbo Debugger should start running. If it does not, there might be a problem with the serial link. Once you're in Turbo Debugger, you will see a dialog box that says: "Program out of date on remote, send over link?

The debugger will then set an initial breakpoint at main and instruct the debug monitor to execute the program until that point is reached. Using normal Turbo Debugger commands, you can step through the program, set breakpoints, monitor the values stored in variables and registers, and do all of the other things debuggers allow. Or you can simply press the F9 key to immediately execute the rest of the program. If you do this, you should then see the green LED on the front of the board start blinking.

When you are satisfied that the program and the debugger are both working properly, press the reset switch attached to the Arcom board. This will cause the embedded processor to be reset, the LED to stop blinking, and Turbo Debugger to again respond to your commands. In fact, an ICE actually takes the place of-or emulates-the processor on your target board. As a result, in-circuit emulators are usually pretty expensive-often more expensive than the target hardware.

The provision of realtime responsiveness, especially where short response times are demanded, requires support from the underlying operating system. However, most OS does not natively provide such support because the requirements of realtime responsiveness can conflict with the requirements of multiuser timesharing operating systems.

Realtime variants of Linux have been created, and recent Linux kernels are moving toward full native support for realtime applications. An RTOS is an operating system designed to meet strict deadlines which associated with tasks.

In RTOS, therefore, missing the deadline can cause undesired or even catastrophic outcome. Here is the list of sizes of the primitive types:. In the code, to print the individual bits of the integer, we used a standard library bitset :. A bitset is a fixed number of bits. Then, we initialized that bitset with i. That's because the index i is signed integer, but v.

Mixing signed and unsigned could lead to disaster. For instance, the loop variable i might overflow. In other words, v. Then, i would reach the highest value that could represent a positive integer in a signed int.

The loop would never terminate! That can be significant, but it is still gives only a single bit of range. The loop using iterators has no such limitation. The operations are known as shift and mask. Barr Group's Embedded C Coding Standard was developed to minimize bugs in firmware by focusing on practical rules that keep bugs out--while also improving the maintainability and portability of embedded software.

The coding standard details a set of guiding principles as well as specific naming conventions and other rules for the use of data types, functions, preprocessor macros, variables and much more. Individual rules that have been demonstrated to reduce or eliminate certain types of bugs are highlighted. Beware though you are never permitted to post your copy of the PDF on the Internet nor share it with others outside of your company.



0コメント

  • 1000 / 1000