CHAPTER 4. Starting to program – an introduction to Assembler
Embedded system design is made up of two main aspects, the hardware and the software. In the early days of microprocessors, systems were built up laboriously using a large number of integrated circuits (ICs). Memory was very limited, so only small programs could be written. Slowly the available ICs became more and more sophisticated, and the designer had to do less to get a working hardware system. Meanwhile memory was growing, so longer programs could be written. Now we are in a situation where memory is plentiful and cheap and the hardware is sophisticated and readily available. Complex hardware systems can be built up with comparative ease, and in many projects software development is now the main creative activity. In this chapter we start down the long but exciting road to developing good programs. We start that road using the Assembler programming language, but later in the book continue it using the high-level language C.
We have one problem if we are to start programming. What will the program run on? Ultimately of course embedded systems programs are written to run on the target system hardware. You may be working with an educational PIC hardware system, or you may have the electronic ping-pong hardware. In many cases, however, we don't want to be dependent on hardware to try out a programming idea. What can really cause a study of programming to spring to life is a simulator – a program running on a desktop computer that will run the program we have developed. Therefore we make it a priority in this chapter to introduce the Microchip MPLAB Integrated Development Environment, and the simulator in it. Once you have the skills to use this, most program ideas can be tried out very quickly, and you should be able to make rapid progress in the noble but tricky art of microcontroller programming!
In this chapter you will learn about:
• Some aspects of the underlying issues of computer programming.
• The essentials of Assembler programming, and how to write simple Assembler programs.
• Development environments for programming, and the Microchip MPLAB Integrated Development Environment.
• The use of certain PIC 16 Series instructions.
• Simulating software, and the MPLAB software simulator MPSIM.
• Program download to a target system.
You will also, if you wish, be able to learn about:
• The details of how the PIC 16 Series instruction word is constructed.
4.1. The main idea – what programs do and how we develop them
The four main ideas of computer programming, according to this author, are listed here:
• A computer has an ‘instruction set’; it can recognise each instruction and ‘execute’ it.
• The program that the computer executes is a list of instructions drawn from its instruction set; it reads these in binary from its program memory. The program in this form is called ‘machine code’.
• To execute, the computer works relentlessly through the instructions of the program from the beginning, doing exactly what each instruction tells it to do – nothing more, nothing less – except when temporarily diverted by an interrupt.
So far this is simple, but here is the difficult one:
• The programmer must find a means of breaking down and translating his/her ideas into steps that the computer can undertake, where each step ultimately must be an instruction from its instruction set.
4.1.1. The problem of programming and the Assembler compromise
The problem of programming is summarised in Figure 4.1. We as humans express our ideas in complex and often loosely defined linguistic forms. A computer reads and `understands' binary, and responds in a precise way to precise instructions. It is ruthlessly logical and does exactly what it is told.
Given this linguistic divide, how can a programmer write programs for a computer? Three ways of bridging the gap present themselves.
1. The human learns machine code. This is what programmers used to do sometimes in the very early days, laboriously writing each instruction in the binary code of the computer, directly as the computer then read it. This is incredibly slow, tedious and error-prone, but at least the programmer relates directly to the needs and capabilities of the computer.
2. Use a high-level language (HLL). This is as if we go some way to asking the computer to learn our language. In a HLL instructions are written in a form that relates in a recognisable way to our own language, in the case of people reading this book probably English. Another computer program, either a compiler or an interpreter, then converts that program into the machine code that the computer can comprehend. The programmer now has a much easier time and can write very sophisticated programs. He/she is now, however, separated from the resources of the computer and the program may be comparatively inefficient in terms of its use of memory and in its execution speed.
3. Use Assembler. This is a compromise position. Every instruction is given a ‘mnemonic’. This is usually a three- or four-letter word that can be used to represent directly one instruction from the instruction set. The programmer then writes the program using the instruction mnemonics. The programmer has to think at the level of the computer, as he/she is working directly with its instructions, but at least the programmer has the mnemonics to use, rather than actually working with the computer machine code. A special computer program called a ‘Cross-Assembler’, usually these days run on a PC, converts the code written in mnemonics to the machine code that the computer will see. Because there is a computer doing the conversion from the Assembler code to machine code, a number of other benefits can be built into the process. For example the Cross-Assembler can look after most of the business of allocating memory space in program memory and it can accept labels for numbers and memory locations, greatly easing the programmer's task.
In the early days of computing, programmers used Assembler to program almost any type of computer. These days, however, it is pretty much the preserve of embedded designers, particularly when using smaller 8-bit devices. For the embedded designer Assembler offers the huge advantage that it allows him/her to work directly with the resources of the computer, and leads to efficient code which executes quickly. Because it is so directly linked to the computer structure, working in Assembler helps the user to learn the structure of the computer. Programming in Assembler has the disadvantage that it is rather slow and error-prone, and does not always produce well-structured programs. This conundrum we will aim to resolve in later chapters. For now, in order to write simple programs and understand the microcontroller more, we will learn Assembler.
4.1.2. The process of writing in Assembler
The actual process of writing in Assembler is illustrated in Figure 4.2. The programmer writes in the microprocessor or microcontroller Assembly language. This can be done using nothing more than a text editor. We will soon recognise the two lines of Assembler program in Figure 4.2 as being from the PIC 16 Series instruction set. The computer that is being used for writing runs the Cross-Assembler. The terminology Cross-Assembler implies that one computer is assembling code for a computer of another type, not for itself. Usually, and somewhat confusingly, Cross-Assembler is shortened simply to Assembler. The Cross-Assembler ‘assembles’ the program, i.e. it converts it from Assembler mnemonics into machine code ready for the microcontroller. In Figure 4.2 the Cross-Assembler is seen converting the two lines of assembler code into the 14-bit machine code words of the PIC 16 Series. For most microcontrollers there are then special programming tools which can download the program in machine code from the main computer and program it into the microcontroller program memory.
4.1.3. The program development cycle
The process of writing in Assembler needs to be placed in the broader context of project development. The possible stages in the development process for the program of a simple embedded system project are shown in Figure 4.3. The programmer writes the program, called the ‘Source Code’, in Assembler language. This is then assembled by the Cross-Assembler running on the host computer. If the programmer has access to a simulator then he/she may choose to test the program by simulation. This is likely to lead to program errors being discovered, which will require alteration to the original source code. When satisfied with the program, the developer will then download it to the program memory of the microcontroller itself, using either a stand-alone `programmer' linked to the host computer or a programming facility designed into the embedded system itself. He/She will then test the program running in the actual hardware. Again, this may lead to changes being required in the source code.
Clearly, to develop even a simple project, a selection of different software tools is beneficial. These are usually bundled together into what is called an ‘Integrated Development Environment’ (IDE).
4.2. The PIC 16 Series instruction set, with a little more on the ALU
Before looking at the 16 Series instruction set, it is worth taking a more detailed look at its ALU (Arithmetic Logic Unit). Understanding this will help in understanding the instruction set.
4.2.1. More on the PIC 16 Series ALU
Looking at the ALU, shown in Figure 4.4, we see that it can operate on data from two sources. One is the W (or `Working') register. The other is either a literal value, or a value from a data memory (whose memory locations Microchip call `register files'). A literal value is a byte of data that the programmer writes in the program and is associated with a particular instruction. Thus we can expect to see some instructions that call on data memory, and others that require literal data to be specified whenever they are used. Examples of all will follow! The data that the instruction operates on, or uses, is called the ‘operand’. Operands can be data or addresses. We will see that some types of instruction always need an operand to be specified with them; others do not.
Once an instruction has been executed, where is the result stored? For many instructions Microchip offer a choice, whereby the result can either be held in the W register or stored back in data memory. The one that is used is fixed by certain instructions; in others it is determined by the state of a special d bit, which is specified within the instruction.
4.2.2. The PIC 16 Series instruction set – an introduction
Turn now to the PIC 16 Series instruction set, which can be found in Appendix 1. Take a long hard look at it – we are aiming to get to know it extremely well! You can see that the table is divided into six columns, and each of the 35 instructions gets one line. The first column gives the actual mnemonic, together with the code specifying the type of operand it acts on. There are four such operand codes:
f for file (i.e. memory location in RAM), a 7-bit number;
b for bit, to be found within a file also specified, a single bit;
d for destination, as described above, a single bit;
k for literal, an 8-bit number if data or 11-bit if an address.
The second column summarises what the instruction does. In some cases this gives adequate information. A much fuller description of how each instruction works can also be found in the full microcontroller data [Ref. 2.1]. The third column shows how many cycles the instruction takes to execute. With a RISC processor, we expect this to be a single cycle. This turns out to be the case, apart from those instructions that cause a branch in the program. We discuss their use in Chapter 5. The fourth column gives the actual 14-bit opcode of each instruction. This is the code that the Cross-Assembler produces as it converts the original program in Assembler language to machine code. It is interesting to see here how the operand codes, listed above, become embedded within the opcode. The fifth column shows which bits in the Status register (Figure 2.3) are affected by each instruction.
Let us immediately look at six representative example instructions, to see how the information is presented. As an aside, let us note now that Assembler programming does not have to be case-sensitive, and that all the examples in this book are not case-sensitive. Therefore do not worry if you see instruction mnemonics and operands appearing in either upper or lower case in different references. In this book, for stylistic reasons, we choose to write Assembler programs in lower case. Find now each of the instructions below in the Instruction Set table in the appendix.
clrwThis clears the value in the W register to zero. There are no operands to specify. Column 5 tells us that the Status register Z bit is affected by the instruction. As the result of this instruction is always zero, the bit is always set to 1. No other Status register bits are affected.
clrf fThis clears the value of a memory location, symbolised as f. It is up to the programmer to specify a value for f, which will need to be a valid memory address. Again, because the result is zero, the Status register Z bit is affected.
addwf f,dThis adds the contents of the W register to the contents of a memory location symbolised by f. It is up to the programmer to specify a value for f. There is a choice of where the result is placed, as discussed above. This is determined by the value of the operand bit d. Because of the different values that the result can take, all three condition code bits, i.e. Z, the Carry bit C, and the Digit Carry bit DC are affected by the instruction.
addlw kThis instruction adds a ‘literal’, i.e. an 8-bit number written into the program and represented by k, to the value held in the W register. Like the addwf instruction, The Z, C and DC Status register bits can all be affected by the instruction.
bcf f,bThis instruction clears a single bit in a memory location. Both the bit and the location must be specified by the programmer. The memory location is symbolised by f. The bit number b will take a value from 0 to 7 to identify any one of the 8 bits in a memory location. No Status register flags are affected, even though it is possible to imagine that the result of the instruction could be to set a memory location to zero.
goto kThis instruction causes the program execution to jump to another point in the program, whose address is given by the constant k. It is up to the programmer to give a value for k. No Status bits are affected.
4.3. Assemblers and Assembler format
4.3.1. Introducing Assemblers and the Microchip MPASM Assembler
For any microprocessor or microcontroller, there are a large number of (Cross-)Assemblers available. Some are distributed free by the makers of the processors to encourage people to buy their products. Others, usually more sophisticated, are written by specialist software houses and sold commercially. Many these days come as part of an IDE, as mentioned in Section 4.1.3. This book uses MPASM, the Assembler offered by Microchip. It is usually used as part of the MPLAB IDE, and both MPASM and MPLAB are introduced in some detail later in this chapter and the next.
While many aspects of Assembler programming are common across all Cross-Assemblers, some are specific to the particular Assembler that is in use.
4.3.2. Assembler format
Having taken a first look at the instruction set, we need now to understand how we can build these instructions into a line of code and then into a program. Assembler programs have a simple format, which must be understood and followed. This is shown in Program Example 4.1.
|Program Example 4.1.|
There are four possible elements to an Assembler line of code:
• Label. A label for a line is optional. When it is first specified, the label must start in the left-most space of the line. The Assembler will interpret anything starting in this space as a label. Once defined in this way, a label can be used as an operand. Labels must start with an alphabetic character or underscore, but not a number. Labels can stand alone on a line, in which case the label is adopted by the next line that contains an instruction.
• Operand. These must conform exactly to the format specified in the instruction set. For better intelligibility, labels are often used rather than numbers. If there is more than one operand they are separated by a comma.
• Comment. This is optional, and is used to add information to the program and improve its intelligibility to the human reader. A comment must always start with a semi-colon. The Cross-Assembler ignores everything that follows a semi-colon in any line. Comments can follow instructions on a line; alternatively a whole line can be used just for commenting.
A line of an assembler program can contain an instruction, properly formatted as above. Alternatively it can be a comment only, or it can be left completely blank – this sometimes helps to improve layout and readability.
4.3.3. Assembler directives
While the Assembler program is written for the target microcontroller, it has to be processed by the Assembler first. To aid this process and make it more powerful and flexible, a way is needed of passing information and instructions to the Assembler such that it recognises them as being for its attention only. These instructions are called ‘Assembler directives’. They are used for very diverse applications, for example defining the target processor or specifying where the program must be placed in memory. A few MPASM examples are shown in Table 4.1. These are written in the code, and appear almost like mnemonics from the instruction set. Their very distinct role must, however, be recognised.
|∗Listing options include setting of radix and microcontroller type.|
|Assembler directive||Summary of action|
|list||Implement a listing option∗|
|#include||Include another file within the source file; the included file is embedded within the source file|
|org||Set program origin; this defines the start address where code which follows is placed in memory|
|equ||Define an assembly constant; this allows us to assign a value to a label|
|end||End program block|
4.3.4. Number representation
One of the things about working close to the inner operations of a microcontroller is that sometimes one is thinking in binary, sometimes in decimal, and sometimes in hexadecimal or even octal. Therefore it is helpful for the Assembler program to be able to recognise and respond to different number bases. MPASM does this first by allowing a default to be set. Thus for example if one wants to work only (or mainly) in hexadecimal, then all numbers can be interpreted as such. Any number that the programmer wants to represent in an alternative radix must be prefixed, as shown in Table 4.2. In Program Example 4.1, for example, the programmer is writing for a default radix of hexadecimal. In the second line of the example, however, he wishes to specify a number in binary, so therefore uses the appropriate format from Table 4.2. In the fourth line he is using the hexadecimal number 53 as an operand. As hexadecimal is the default radix, its number base does not need to be explicitly specified.
|Hexadecimal||H‘8d’ or 0x8d|
|ASCII||‘G’ or A‘G’|
Note that a hexadecimal number must not start with an alphabetic character; otherwise, it might be interpreted as a label. Therefore any hexadecimal number starting with a, b, c, d, e or f must be preceded with a zero. Thus for example the number b2H must be entered as 0b2, or 0xb2.
4.3.5. A very first program
Let's now look at Program Example 4.2. This is a simple but complete program and contains the key Assembler features which have just been described. It uses only instructions from those introduced in Section 4.2.2 and directives from Table 4.1.
|Program Example 4.2.|
|A first program|
The program starts with a header made up of five comment lines, each starting with a semi-colon. These give the program title, briefly describe what the program does, and give the date it was written and its author. They are not essential, but become increasingly important as program length grows.
Before the actual program starts the org directive must be used to define the start address. We have no choice over this – it must be the Reset Vector address, as seen in Figure 2.4. If the address is fixed, you may ask why it can't be stored within MPLAB to save the trouble of having to state it. In longer programs we may need to write program blocks at different locations; hence the control over start address is left with the programmer.
The program which follows uses only three instructions. It first clears the W register. The following instruction has been given the label loop. It adds the number 8, embedded into the instruction as a `literal' value, to the W register. The following goto instruction, using the label loop as its operand, causes the program to return to the add instruction, which it does repeatedly. The W register therefore repeatedly increments by the value 8. The end of the program is defined with an end directive.
This is a complete program and we will soon try assembling and simulating it. It has little practical use of course, mainly because there is neither input nor output of data. That is what we explore later in the chapter.
4.4. Adopting a development environment
4.4.1. Introducing MPLAB
MPLAB is an IDE that can be downloaded free from Microchip's web site [Ref. 1.2]. There is also a copy on the book's companion website. It contains all the software tools necessary to write a program in Assembler, assemble it, simulate it, and then download it to a programmer. The latter must be built, bought or designed into the target system. Further software tools can be bought and then integrated with MPLAB, both from Microchip and from other suppliers. This includes alternatives to what MPLAB already offers – e.g. assemblers or simulators, as well as tools which offer much greater development power, like C compilers or emulator drivers.
MPLAB is a continuously evolving package, with its own manuals [4.1. and 4.2.] and online help facility. Therefore this book does not aim to act as a full MPLAB manual. It will, however, aim to give a clear introduction to its use so that you can begin to apply it with confidence. Screen images from MPLAB Version 8.10 are used in this chapter and the next.
4.4.2. The elements of MPLAB
MPLAB is made up of a number of distinct elements which work together to give the overall development environment. These are:
• Project Manager. The preferred way of developing programs in MPLAB is by creating a project. An MPLAB project groups all the files together that relate to the project and ensures that they interact with each other in an appropriate way and are updated as needed.
• Text Editor. This allows entry of the source code. It behaves to some extent like a simple text editor such as Notepad, but it can recognise the main elements of the programming language that is being used. Thus in Assembler it codes instructions in one colour, labels in another and comments in a third. In this way the programmer can immediately see if there is a misconception in his placing or use of text within the Assembler line.
• Assembler and Linker. The function of the Assembler has already been discussed. So far we have assumed that there is a single source file. In advanced projects, however, the code may be created from a number of different files. The role of the Linker is to put these together, give each its correct location in memory, and ensure that branches and calls from one file to the other are correctly established.
• Software Simulator and Debugger. A software simulator allows a program to be tested by running it on a simulated CPU in the host computer. Inputs can also be simulated and outputs and memory values can be observed. The debugger contains the tools which allow program execution to be fully examined, for example by single stepping through the program, running at slow speed, or halting at a particular location.
4.4.3. The MPLAB file structure
Even with simple projects, a significant number of files are rapidly generated in MPLAB. Each type is designated by the file extension used, of which examples are given in Table 4.3. Whenever a project is set up, files of type .mcp and .mcw are created. When using Assembler, the original source code is written in a file with the .asm extension. The source code may include an .inc file, to be described in Chapter 5. When the source code is assembled successfully, the output appears in .lst, and .hex files. If there is an error, is placed in an .err file.
|.asm||Assembly language source file|
|.hex||Machine code in hex format file|
|.inc||Assembly language Include File|
|.lst||Absolute listing file|
|.mcp||Project information file|
|.mcw||Workspace information file|
4.5. An introductory MPLAB tutorial
This tutorial takes you through the stages of creating a project, writing simple source code and assembling it to create output files. To follow the tutorial, you should download and install the current version of MPLAB if it is not available in your place of work or study.
Open the MPLAB IDE, which should appear as Figure 4.5. If a blank Output window also opens, close it. The main screen is blank, apart from the Workspace window at the top left. It is a good idea to leave this permanently open, as it will give essential information about the project you are working on. If it does not appear, or if you close it, you can display it again by clicking View > Project.
4.5.1. Creating a project
Click the Project button on the toolbar to access the pull-down menu, as shown in Figure 4.6.
There are two ways to create a project, both accessible from this menu. One is by using the Project Wizard, and the other is by selecting New… . Try following the Project Wizard route, making the following selections as you work through the dialogue boxes:
When you click Finish, the workspace window should be updated to show the filename you have selected, as seen in Figure 4.7 (a) for a project called fred. If the window does not display, click View > Project.
4.5.2. Entering source code
Now open a new file by clicking File > New and start to enter into it the program of Program Example 4.2. After a few lines save this using File > Save As…. Select file type Assembly Source File and save as <your project name>.asm. Continue entering the code, and notice now that MPLAB has identified this file as an Assembler Source File. It applies colour-coding to labels, instruction mnemonics, numerical data, assembler directives and comments. When complete, go to the Project menu again, click Add Files to Project… and select the one you have just saved. Your workspace window should now appear as Figure 4.7 (b), with of course your own file names. You will now begin to appreciate how valuable this window is to become, as it shows a complete picture of the files associated with your project.
4.5.3. Selecting the microcontroller and setting the Configuration Word
There are two important settings to be checked or made, which both appear under the Configure tag on the toolbar at the top of the MPLAB screen. The first of these is device selection. Click Configure > Select Device to get the screen shown in Figure 4.8. If you set up the project by using the Project Wizard, you will have already selected the microcontroller, and at this point it should show the 16F84A. Also shown on the screen are the development tools which are available to work with that device, indicated by green `LEDs'. If you didn't select the device through the Project Wizard, ensure that the correct microcontroller is selected. It is always worth checking this setting, even if defined in the project set-up, as it is possible for it to be changed accidentally. If the wrong device is selected there may be problems with the assembly process.
Under the Configure pull-down menu you can also select Configuration Bits, as shown in Figure 4.9. These are very important when you actually download to a microcontroller; they are less so when just simulating. For our early program simulations, just ensure that the Watchdog Timer is turned off, as shown in the figure.
4.5.4. Assembling the project
Now comes one of the testing moments in the development of any project. You have entered new source code and you need to know if it assembles correctly. The Assembler subjects your code to a series of checks. It returns errors if it finds incorrect use of Assembler format, instruction mnemonics, labels, or a range of other things. Remember, however, that the Assembler can effectively only check that your program is correct grammatically; it cannot assure you that it is a viable program. Above all else, it has no knowledge of the target hardware, beyond the fact that the microcontroller has been specified. Correct assembly does not guarantee correct program operation!
Check that the default radix is correctly set by clicking Project > Build Options > Project > MPASM Assembler, and ensuring that Hexadecimal is selected in the dialog box. In the same dialog box you can enable or disable case sensitivity for all the source codes. This is not necessary if you have directly copied Program Example 4.2. You may need to use it in future, however.
Invoke the MPASM Assembler by selecting Project > Build All. This also ensures all files are updated as needed. The Output window will open, reporting on the progress of the build. In the output window you will either get a BUILD SUCCEEDED or BUILD FAILED, message together with a fleeting box showing a green bar (for success) or red (for errors).
If you get neither green nor red bar and the message in the Output window suggests that the process has failed to start properly, then it is worth checking that the software tools are properly selected and located. Select Project > Select Language Toolsuite, and ensure Microchip MPASM Toolsuite is selected. If there are still problems, select Project > Set Language Tool Locations, and ensure that the different elements of the MPASM Toolsuite are shown as being in the locations where they are installed on your computer.
4.5.5. The list file and identifying errors
Whether your build has initially succeeded or failed, open the file <your project name>.lst. This should be in the directory you specified for the project. Use File > Open and ensure you select All Files in the dialog box against Files of Type. The .lst file is very informative. Part of the list file for Program Example 4.2 is shown as Program Example 4.3. The file gives first the original source code; each line is numbered, beginning with number 00001. To the left of this, once the actual instruction mnemonics appear, is the assembled machine code and the memory location in which it is placed. Thus the clrw instruction is assembled to code 0103 and placed in memory location 0000. Because of the quantity of information displayed, there is some line overflow in the figure. The number of errors and warnings that may have been generated is shown below.
Programming Exercise 4.1
|Program Example 4.3.|
|Part of the introductory program list file|
Programming Exercise 4.1
Insert a deliberate error in this file by changing the goto loop line to goto loop1. Build the project and look at the list file again. You will see that Error 113 has been invoked. Click the MPLAB Help and follow Topics > MPASM Assembler > Index. Then select Errors > Assembler. You will see all error numbers and their descriptions displayed. Return to your program and correct the error.
In many cases errors are simply typographical and can easily be fixed by correcting the source code and building again. A few may require careful study of the error description, and exploration of the underlying cause.
In general, once you have a source file which builds correctly, you are in a position either to download to microcontroller memory or to simulate. For now we continue on the simulation path. At the end of your development session, close the current project using Project > Close.
4.6. An introduction to simulation
The following section introduces the MPLAB simulator, MPSIM, by means of a tutorial, simulating Program Example 4.2.
4.6.1. Getting started
In MPLAB, open your project for Program Example 4.2 by clicking Project > Open. Then select the Simulator by invoking Debugger > Select Tool > MPLAB SIM. The simulator menu, as seen in Figure 4.10, will now appear under Debugger.
4.6.2. Viewing microcontroller features
You can observe a number of microcontroller features during simulation, including program memory, SFRs, data memory, and so on. It is possible to open a window for each of these, using the View menu. If you do this, however, you will find that the screen very quickly becomes cluttered.
A Watch window allows you to make selections of only those variables you want to see, while leaving out the others. Open a Watch window by clicking View > Watch. Items for the window are then selected by using the pull-down menus at the top of the window. Using the Add SFR menu, select WREG, PCL, and STATUS. The Watch window should appear as seen in Figure 4.11, although you are likely to have arranged the windows differently. In this image we see the source file in the centre, with the Watch window below it.
4.6.3. Resetting and running a program
When a microcontroller is powered up, it first enters a Reset state, as explained in Section 2.6. This ensures that when the program starts running, it does so from the Reset Vector. Similarly, when using the simulator you need to start from Reset. You can reset the simulated CPU either using the F6 button or by clicking Debugger > Reset. Using the latter, four Reset categories are offered, reflecting the Reset capabilities of the PIC microcontroller (Section 2.7). Alternatively (and more simply) you can use the Reset button of the Debugger toolbar (Figure 4.12). If this is not displayed, invoke it by selecting View > Toolbars > Debug. The simulated CPU of Figure 4.11 has just been reset, so the arrow representing the program counter is pointing to the first instruction. This can be confirmed by checking the PCL value in the Watch window.
There are three ways to run the program. Each can be selected via the Debugger pull-down menu or by clicking the buttons on the tool-bar. These are:
• Single step. This allows you to step through the program one instruction at a time. This version of MPLAB uses the terminology Step Into for this mode.
• Animate. This is like an automated single step. The program runs slowly but continuously, with the screen being updated after each instruction.
• Run. This runs the program, but does not update on-screen windows as it runs. It does, however, accept stimulus input.
It is also possible to Step Over a subroutine, or Step Out of one. Each of these also has a button on the toolbar. These are especially useful for delay routines, which on a simulator may take an unacceptably long time to simulate.
Now, by repeated pressing of the Step Into button, single-step through the program. As you do this, you will see the PCL value being incremented in the Watch window, and the other register values changing. Program execution then circles around the loop formed by the last two instructions. Try now pressing the Animate button. Notice that the program runs continuously, updating the W register as it does so. Stop the animation with the Halt button. Try changing the animation speed by clicking Debugger > Settings > Animation/Realtime Updates, and modifying the Animate Step Time.
This is a useful moment to take a first look at the operation of the Status register (Figure 2.3), which we have already placed in the Watch window. It is easier to read this if the value is presented in binary, so right click on the Value button in the Watch window and then click on Binary. Binary equivalents to the Hex data already displayed should now show in a further column. Reset the program and start stepping through it using Step Into. Watch the least significant three bits of the Status register, which are the Zero, Digit Carry and Carry bits. Notice that when the W register value goes from 8 to 16D, and then on every alternate addition, the Digit Carry bit is set. This is because there is a carry from the less significant digit (i.e. the least significant 4 bits), to the more significant. Keep looping until the W register holds 248 D. Make a further addition of 8, which should lead to a value of 256D. However, this is a 9-bit number, and we have now overflowed the 8-bit range. Therefore the Carry bit (effectively a ninth bit when adding) is set. The Zero and Digit Carry bits are also set, as the W register now holds zero, and because there was a carry between digits.
4.7. A larger program – using data memory and moving data
The next example, Program Example 4.4, makes use of data memory, simple addition and data move instructions. It generates a Fibonacci series, as described in the header.
To use data memory, the first thing to check is the memory map, as seen for the 16F84A in Figure 2.5. In this program four memory locations are needed, three to hold the most recent numbers in the series and one to hold temporary data. While there are a number of ways of allocating data memory, a simple and effective one is to reserve certain locations permanently for certain variables. This is what is done here. The memory map shows that memory locations in the address range 0CH to 4FH are available. In this program the locations from 20H to 23H have been arbitrarily chosen. Labels corresponding to memory location addresses have been defined using the equ directive, for example:
fib0 equ 20 ;lowest number
The action of this line of code is only this: wherever the word fib0 is used after this line, it will be replaced by the number 20H. It is worth observing here that we have now seen label values being assigned in two different ways. Some, like porta or fib0, are assigned a specific value by the programmer, using the equ directive as we have just seen. Others, like the label forward, are inserted into the program, and the Assembler itself allocates them a value.
Aside from the instructions introduced in Section 4.2.2, this program makes use of these move instructions:
movwf fThis moves the contents of the W register to the memory location f.
movf f,dThis instruction moves the contents of the memory location f to the W register, if the d bit is set to 0; if it is set to 1 then the contents of f are just returned to f (but the Z bit may still change).
movlw kThis instruction moves the literal value k, an 8-bit number which accompanies the instruction, into the W register.
The program starts by preloading the three first numbers in the series, 0,1,1, into the reserved memory locations. Location fib0 is simply cleared using a clrf instruction. The value 1 is loaded into fib1 and fib2. To do this we need to specify a number and load it into a memory location. There is, however, no way of doing this in just one instruction. The number must first be moved into the W register with a movlw instruction, before being transferred to the memory location with a movwf instruction. We will often see these two instructions working together to make this sort of data transfer.
Starting at the label forward, the program starts calculating the next value in the series by adding the two most recent numbers. The instruction set does not allow the direct addition of two memory locations. One location therefore, fib1, is moved first to the W register. This is done using a movf instruction, with the d bit set to 0. The W register is then added to fib2. Because the d bit is set to 0 again, the result is saved in the W register. The next instruction moves it to fibtemp. The program then shuffles the numbers held in the memory locations, retaining the three most recent values and discarding the oldest. Using a goto instruction, the program then loops back to forward, and starts to calculate a new member of the series.
Programming Exercise 4.2
|Program Example 4.4.|
|Generating a Fibonacci series|
Programming Exercise 4.2
Create a project around this program, copying the source code from the book's companion website. Simulate it in a manner similar to that already described. In the Watch window observe the W register, and fib0, fib1, fib2 and fibtemp. These can be selected using the Add Symbol menu.
This is a useful program to illustrate certain instructions and also to simulate. Without input and output it still does not have practical value. This we come to in the section that follows.
4.8. Programming for a target piece of hardware – a simple data transfer program
While the first two example programs we have seen are just written for simulation, in reality we will be writing for a target piece of hardware. A number of things become important. The configuration bits (see Figure 2.6) will need to be set. This can be done within the MPLAB IDE, or actually in the program itself. In addition to this, as we will now be moving data in and out of the microcontroller, we will inevitably need to use one or more peripherals; these will need to be set up in the program.
We now look at a program written for a target piece of hardware, in this case the electronic ping-pong game (Appendix 2). The program appears as Program Example 4.5.
|Program Example 4.5.|
|A simple data transfer program|
The program starts with a header made up of six comment lines, similar to the earlier programs. Information on the configuration setting, fundamental to the running of the microcontroller, is then given. Following this approach, it is up to the programmer to set the Configuration Word correctly in MPLAB, as described in Section 4.5.3.
A section follows which uses the equ directive to define the memory locations of the SFRs that will be used. It comes as some surprise to many people that it is necessary to do this. Don't we `tell' the IDE what the processor is, so shouldn't it `know'? The answer is that it doesn't, so we must supply this information. This program just uses the Status register, Ports A and B, and their control registers TRISA and TRISB. Labels for these are therefore defined, taking memory addresses directly from the memory map of Figure 2.5. Remember (from Section 2.4.2) that the Bank Select bit is held in the Status register. Once this is removed from the SFR addresses shown in Figure 2.5, the labels porta and trisa, and portb and trisb, have the same values. In the program it would make some sense to use just one label for each of these pairs, instead of the two. We choose not to do this in this program example for better clarity when the different locations are used.
The actual program, following the org line, makes use of seven instructions. The two which have not so far been used are:
bcf f,bThis clears (i.e. sets to logic 0) the bit b in memory location f.
bsf f,bThis sets to logic 1 the bit b in memory location f.
The program starts with an initialisation section – we will see this as a pattern in all future programs. This sets up the direction of each bit in the two ports that are used; it requires access to the port control registers TRISA and TRISB. As these are placed in RAM memory bank 1, it is necessary first of all to set bit 5 of the Status register to 1 (as explained in Section 2.4.2). This is done in the first program line, labelled start, using the bsf instruction. The label status can be used because it was defined earlier in the program. If this had not been done, then it would have been necessary to write:
start bsf 3,5;select memory bank 1
which would have an identical effect but would have been somewhat less intelligible.
The port pin directions needed are derived from the circuit diagram, Figure A2.1. From this we can see that the two push buttons connect to bits 3 and 4 of Port A, which must accordingly be set up as inputs. The three other bits of Port A are all connected to LEDs, so must be set up as outputs. As described in Section 3.4, to be an output a port pin must have a 0 in its corresponding TRIS register bit. It must have a 1 for the bit to be an input. Therefore we must send the word 00011000 to TRISA. Note that TRISA is an 8-bit location, even though Port A only has 5 bits. It is therefore necessary to specify a complete 8-bit word to be sent, even for those three bits that are not implemented. The binary radix is used (Table 4.2) instead of the default hexadecimal. A similar process is followed for setting up Port B. A quick look at the circuit diagram shows that all Port B pins are connected to LEDs, so all must be set as output. Therefore the word sent to trisb is 00H. From here on the ports will be accessed; their locations are in bank 0. The initialisation section therefore ends with memory bank 0 being selected in the Status register.
Finally we reach the effective program itself, all four lines of it! The program continuously reads the value of Port A and transfers it to Port B. By reading the value of the Port SFR, we are actually directly reading the input state of the port pins for all pins set as inputs. If either push button is pressed, this should be seen on the LEDs connected to bits 3 and 4 of Port B. When Port A is read, all of its 5 bits are read, even though three are set as outputs. For these, the values of the internal data latches (Figure 3.11) are read. All Port A bits are therefore initially cleared to 0 in the program, using a clrf instruction.
The actual data transfer part of the program uses a movf instruction to move the value of Port A to the W register, followed by a movwf instruction to move the W register value to Port B. A goto instruction creates a continuous loop, making use of the earlier defined label loop. As before, the program must end with an end directive.
4.8.1. Tutorial: simulating Program Example 4.5
Create a new project for this example, copying code from the book's companion website. Assemble the project and open the simulator, as described for Program Example 4.2.
This program applies the ping-pong hardware, so to simulate we will need to create simulated inputs for the two ping-pong paddles on Port A pins 3 and 4. Select Debugger > Stimulus > New Workbook. The dialog box that appears is shown as part of Figure 4.13. Select Asynch – this allows you to set up different types of inputs at the port pins, which are initiated by pressing the Fire button at the appropriate moment. Under Pin/SFR, select RA3 and then RA4, with Toggle under Action for each. When you close the project you are invited to save your stimulus settings as an.sbs file. Open a Watch window and select PCL, TRISA, PORTA, TRISB, and PORTB.
You should now have a computer screen similar to that shown in Figure 4.13, although you are likely to have arranged the windows differently. In the middle of this image we see the source file, with the Watch window below it and the Stimulus controller top right. The arrow representing the program counter shows that the program has already been stepped through to the movlw 00 instruction. This can be confirmed by checking the PCL value in the Watch window.
Now, by repeated pressing of the Step Into button, single-step through the program. As the program moves through the initialisation, you will see the SFR values being changed in the Watch window, and the PCL value being incremented. Program execution then circles around the loop formed by the last three instructions. Now try `firing' RA3 or RA4. Display windows are not updated with the new value until the next instruction execution. Observe Port A and Port B being updated as you continue to execute the program. Try now pressing the Animate button. Notice that the program runs continuously but still responds to stimulus inputs.
4.9. Downloading to a microcontroller
Almost all microcontrollers these days have on-chip program memory, using Flash technology. The process of programming requires data to be transferred into the chip in a precisely timed way, applying certain programming voltages, usually higher than the normal supply voltage. Certain microcontroller pins therefore have a secondary function, being used in programming mode to transfer the program data into the chip, and transmit the programming voltages.
In times past, the process of programming always required the IC carrying the memory (whether a stand-alone device or memory in a microcontroller) to be placed in a ‘programmer’. This was linked to a desktop computer for the programming process to be carried out. As memory technology has improved, however, the process has become simpler, and it has become increasingly easy to design the necessary programming circuitry into the target system. This means that most microcontrollers can now be programmed in situ, i.e. within the target system. We will expand on these techniques in later chapters. In this chapter we will introduce one traditional programmer, the PICSTART Plus, and one in-circuit programmer, the PICkit 2.
4.9.1. Conventional programming – using the PICSTART Plus
A popular and low-cost programmer, supplied by Microchip, is the PICSTART Plus, shown in Figure 4.14. There are many alternatives to this, including many designs intended for home-build, which are available on the web. The PICSTART programmer is connected to the host computer by a serial cable, and MPLAB has the software to communicate with it. To use it the microcontroller must be removed from the target circuit and placed into the programmer. The programmer can accept a wide range of dual-in-line microcontroller packages, from 8 to 40 pins. With adaptors it can program other package types.
The following steps take you through actually downloading code to the microcontroller, using the PICSTART Plus programmer. If you have a programmer and the ping-pong hardware, you can immediately download the program you have just created in the preceding tutorial.
You will need to power your PICSTART programmer and connect it to the serial port of your computer (note that this therefore excludes most laptops). From within MPLAB IDE, select Programmer > Select Programmer > PICSTART Plus. Then enable the programmer with Programmer > Enable Programmer. A positive response should be given via the Output window. If there is a problem, you may need to check Programmer > Settings > Communications. Ensure the programmer toolbar, Figure 4.15, is displayed. If not, find it with View > Toolbars > Picstart.
Put the Zero Insertion Force (ZIF) socket on the PICSTART programmer in the Open position. Place a 16F84A into it, ensuring from the legend that the chip is in the right place and is the right way round. Then close the ZIF with the lever. With the project you want open on MPLAB, you should now be able to apply the features available to you, as summarised in Figure 4.15. The Output window in MPLAB will give you feedback on the success or otherwise of all PICSTART actions undertaken, with a useful summary provided in the Program Statistics of Figure 4.15.
4.9.2. In-circuit programming – using the PICkit 2
The PICkit 2 is a very simple and low-cost programmer and debugger, which exploits the In Circuit Serial Programming (ICSP) capability of many PIC microcontrollers. It is pictured in Figure 4.16, alongside a ping-pong unit. The PICkit 2 connects to the target board using a 6-way connector. It also connects to the USB port of a host computer. The PICkit 2 can be driven from MPLAB itself or it can be driven from its own software control package.
The 16F84A and 16LF84A both have ICSP capability, and recent versions of the ping-pong hardware design have interconnections to allow ICSP connection to the microcontroller.
The following description takes you through downloading a program to a target device using the ping-pong, loaded with a 16LF84A microcontroller, as an example. It assumes that you have already successfully assembled the code for a program suitable for download, such as Program Example 4.5. The standalone PICkit 2 user interface, version 2.11, is used. For use in other modes, and further details, consult Ref. 4.3.
Connect the PICkit pod to the ping-pong board and to your computer USB port. The PICkit 2 allows programming power to be supplied from the target board or from the USB via the programmer pod. In this case the user can set a chosen voltage value. The ping-pong board only has a 3 V supply, which is not adequate for some of the programming and erase processes. Therefore leave the ping-pong supply switched off, as we will use power supplied from the PICkit 2.
Start up the application software. You should see a screen similar to Figure 4.17. Notice that the PICkit has identified the microcontroller. It cannot distinguish between 16F84A and 16LF84A, so the former is shown. Notice also that the functionality on the push buttons is very similar to that of Figure 4.15. In the case of Figure 4.17, a read has just been completed from a microcontroller which has already been programmed.
To program the ping-pong hardware, you need to `import' the correct hex file to the PICkit 2. Depending on how the configuration bits have been set, however, it may be necessary first to `export' the file from MPLAB. Do this in the MPLAB screen by selecting File > Export Hex. Accept the defaults you are offered, and then select the file you wish to export (i.e. the one you want to download to the ping-pong board). Return to the PICkit 2 window, select File > Import Hex from the top left menu and select the Hex file you wish to import. You should get a message to say that the file has been successfully imported. All that remains to be done now is to click the Write button and watch the fleeting messages on the screen. These should lead to a `Programming Successful' message. The file import and programming can be run automatically by clicking the Auto Import Hex + Write Device button.
4.10. Taking things further: the 16 Series instruction set format
It is interesting to take a little time here to understand further the way the instruction code is made up of different component parts, as first discussed with the 12F508/9 in Chapter 1. The PIC 16 Series has four possible instruction word formats, as shown in Figure 4.18. The instruction word, which is transferred down the Program Bus (Figure 2.2), is made up of 14 bits. These appear as bits 0 to 13 in the figure. The opcode, the actual instruction part of the instruction word, always occupies the highest bits of the instruction word. This is the part of the instruction word that ends up in the `Instruction Decode and Control' unit of Figure 2.2, but it is not always the same length.
If the instruction is the type that contains a file address, then it is of the first format shown. The most significant 6 bits hold the opcode, while the least significant 7 bits are used to hold the address. These bits are transferred onto the `Direct Addr' bus of Figure 2.2. In fact, because the ’F84A only has a small memory, only the least significant 5 bits are used, as can be seen from the `Direct Addr' bus size indicated. Bit 7 holds the d bit. Different instruction word patterns are used for the other instruction categories. These can be seen and understood by reading the information in the figure.
If you have followed the material of this chapter then you have taken an enormous step forward – you are on your way to becoming a programmer of embedded systems! The key points are:
• Assembler is a programming language that is part of the toolset used in embedded systems programming. It comes with its own distinct set of rules and techniques.
• It is essential to adopt and learn an IDE when developing programs. The MPLAB IDE is an excellent tool for PIC microcontrollers, both for learners and professionals. And it can't be beaten on price!
• While some people are eager to get programs into the hardware immediately, it is extremely useful to learn the features of a simulator. The simulator in MPLAB allows the user to test program features with great speed and is an invaluable learning tool.
4.1. MPASM Assembler, MPLINK Object Linker, MPLIB Object Librarian User's Guide (2009). Microchip Technology Inc., Document no. DS33014K.
4.2. MPLAB User's Guide (2006). Microchip Technology Inc., Document no. DS51519B.
4.3. PICkit 2 Programmer/Debugger User's Guide (2008). Microchip Technology Inc., Document no. DS51553E.