ENGR 172 Intermediate Microcontrollers Lesson 4 Name: _______________________ 8 extra credit points of 110 total points Lesson 4 is a brief introduction to assembly language programming. Assembly language takes us several steps closer to understanding and controlling exactly how the microcontroller operates (it’s a great language if you’re a control freak). Be warned: Assembly language is often described as arcane and cryptic, and I suspect you’ll agree with that assessment. Activity 1 (3 pt. I. I. ________) In the first activity we will learn how to turn on an LED using the PIC assembly language, then how to flash the LED. 1. Start by connecting an LED and a 470W resistor to PortB, pin 0 (pin 6 of the 16F84A), as shown in Figure 4.1. Figure 4.1. LED connection for assembly language exercises 2. Create a folder for your assembly code programming project, giving it a distinctive name like TurnOnLED. 3. Open the MPLab IDE (IDE stands for Integrated Development Environment). a. An IDE is a suite of programs that usually include an editor, a linker, an assembler. These programs take your program from what you originally type in, called the source code, to the final file you put into the microcontroller, called the hex file. 4. Start a new project by selecting Project/Project Wizard … a. When it asks you to select a device, choose PIC16F84A. b. The active toolsuite should be left as MicroChip MPASM toolsuite c. When it asks for a project name, use the project name and directory you set up in step 2 above. d. Click through until you’re done. If you look in your directory now, you’ll see you have several files listed. 5. Create the assembly language source code. a. Select File/New b. This will bring up an untitled window. Type in your source code, as follows. You may skip typing in the comments. Those are for your understanding, and to help the discussion that follows. ;***********EQUATES SECTION*********** status equ 3 ;Line 2. Status register is data memory location 3 portb equ 6 ;Ln 3. Port B (I/O) is data memory location 6 trisb equ h'86' ;Ln 4. Port B's tri-state enable register is ;data memory location 86 in hex (=134 in base 10) ;***********CONFIGURATION************ list p=16f84 ;Ln 8. Tells the compiler you have a 16F84 micro __config h'3ff1' ;Ln 9. Too complicated to explain for now. Use this line in all ;assembly language programs you write today. Note that ;the underscore in front of config is actually a double underscore org 0 ;Ln 12. Program will start at program memory location 0 start bsf status, 5 ;Ln. 14. Set bit 5 (make it equal to 1) in the status register. movlw b'00000000' movwf trisb bcf status, 5 ; ;This permits the microcontroller to access Bank 1 of the data registers ;(locations 80 to FF (hex) , see page 78 of your text) ;Ln 17. Move binary 00000000 into the working register ;Ln 18. Move binary 00000000 from working reg to tristate B ;register. This sets all bits of B to output Ln 20. Clear bit 5 (making it equal to 0) of status register. ;This permits the microcontroller to access Bank 0 of the register ;files (locations 0 to 7F). These include most of the usual registers ;we will work with, in particular port B and the status register ;***********Main Routine************* begin movlw b'00000001' ;Ln 26. Move binary 00000001 into the working register movwf portb ;Ln 27. Move binary 00000001 from working reg to port B, making pin ; 0 go high (and therefore turning on the LED connected to pin 0) wait goto wait ;Ln 29. Stay here forever - LED stays on end ;Ln 30. ALWAYS use the end statement at the end of all your code. It ;tells the compiler to stop compiling ;************************************ c. Save this file within your project folder using a distinctive name. You have now written your first assembly language program. 6. Add the file to your project. a. Select Project/Add File to Project b. Select your source file. 7. Compile the program. a. Select Project/Build All. b. Watch the output window. It lists what happens in your attempt to compile your file. If all went well, the final line will be BUILD SUCCEEDED. i. Look in your directory now. There are several files, all with the same name, but various suffixes. You have succeeded when there is one with a .hex suffix. This is the file you will load onto the microcontroller. c. If the output window shows errors, it will tell you on which line number they occur. Fix these errors and try again. i. One common error is to have too long a path name. If it has more than 62 characters, the compiler will choke. Here is the message you will receive: Source file path exceeds 62 characters (F:\CUYAMACAFALL08\ENGR172FALL2008\LESSON4\FIRSTPROGTUR NONLED\TURNONLED.ASM) If you get this error, simply move your entire folder up (for example to your C: directory). 8. Load the hex file onto the microcontroller. Do this using the meLabs programmer, exactly as you would a hex file produced by PICBASIC. Put the chip into the circuit. Did the LED turn on? 9. Let’s look at how assembly language works. If you haven’t yet read the handout from Chapter 19 of PIC In Practice, by D.W. Smith, this would be a good time to do it. Much of what a microcontroller (indeed any computer) does at the lowest level is to manipulate data in memory locations. These memory locations are called more-or-less equivalently files, registers, or file registers. Certain file registers are reserved for special functions. For example, memory location 6 is I/O Port B. Other memory locations (0C to 4F) are used more generally to store variables. See the register file map on p 78 of your text. In addition to these locations, there is something called the working register, or W register. The W register plays the role of a central clearing 2 house. Data are first loaded into the W register, then moved to other registers. Lines 17 and 18 in the source code are examples of this. Line 17: movlw b’00000000’ tells the processor to “move the binary number 00000000 into the W register” (the l in movlw stands for “literal” A plain ol’ number, as opposed to a memory location, is considered a literal). Line 18: movwf trisb means “move what is in the W register to file trisb”, which was defined previously in line 4 as being memory location 86. So putting a 00000000 into the tristate enable B file takes 2 operations, each of which requires exactly 1 s (more on this later). Note that the W register does not have a numbered memory location in the same way that other registers do. 10. movwf trisb is an example of an assembly language instruction. It shows how fundamental assembly language really is. You’re down in the guts of your computer telling it exactly how to move a piece of data. This is why I say assembly language programming is great for control freaks. There are 35 assembly language instructions for all PIC microcontrollers, and together these form the PIC’s instruction set. movwf trisb is also an example of a byte instruction, because it works on a byte of data at a time. bsf status, 5 (line 14 of the source code) is an example of a bit instruction, because it operates on a single bit of data at a time. bsf status, 5 “sets” bit 5 in the status register. 11. The status register is another important register, almost as important as the W register. It records the outcomes of math operations and also tells what memory bank is in use. Each bit of the status register contains valuable information (see the Smith handout, pp 290 – 291), but we will focus on bits 2, 5, and 6. a. Bit 2 is the zero bit, which is set when an operation results in a zero. We’ll see its use in the next program, when we build timers using counters that count down to zero. b. Bits 5 and 6 together identify which register file bank the processor is accessing. Some explanation is needed here. PIC processors are 8-bit processors, which means they can access up to 256 memory locations. If there are more than 256 locations, memory is organized into file banks, sometimes called pages of memory. Bits 5 and 6 help select which of 4 file banks to use: If bits 5 and 6 are 00, then bank 0 is selected. 01 gives bank 1, 10 gives bank 2, and 11 gives bank 3. Actually the 16F84A only has 2 memory banks (text page 78), and you will do most of your work in bank 0. However, the tristate registers (85h and 86h for ports A and B) are located in bank 1. To address them, you must first set bit 5 in the status register (line 14). Then later, to go back to addressing the lower memory locations, you must clear bit 5 (line 20). 12. Look at line 12 which reads org 0. This instruction is interesting in that it points out how PIC microcontrollers handle programs and data. In general for computers, programs and data are stored in RAM (random access memory). There are 2 main competing approaches to this, called Von Neumann architecture and Harvard architecture. With Von Neumann architecture, programs and data share the same memory area, so that if you’re not careful where you POKE data, you can corrupt your program. With Harvard architecture data and program are stored separately, so there is no such danger. Von Neumann architecture is by far the more commonly used, because it is generally more efficient in terms of RAM. Small programs working with large amounts of data can run on the same computer as large programs working with small amounts of data. PIC micros in contrast use Harvard architecture. org 0 tells the computer to start reading the program at program memory location 0. File register 0 does not contain the program (actually, it is used in something called indirect addressing, which we will not do today). 13. Look finally at the end instruction, line 30. In contrast to PICBASIC programs, the end instruction is NOT OPTIONAL in assembly language. It tells the assembler to stop compiling. Also unlike with PICBASIC, you do not put it at the end of your main routine if you have subroutines that follow. Instead, you put it after all your routines, both main and sub-. If you put the end instruction before the subroutines, you will never compile them. I spent hours tearing my hair out (and I don’t have much to tear) until I figured this out. 3 14. For comparison purposes, the following code in PICBASIC does the same thing as the assembly language program you’ve just written. Note the similarity, but also note how PICBASIC takes care of many of the overhead items, like deciding where in program memory to start the program, switching file banks, and so forth. SYMBOL portb = 6 SYMBOL trisb = $86 POKE trisb, %00000000 POKE portb, %00000001 wait: GOTO wait END 15. In this next program we will flash an LED once per second. In doing so, we will learn about timing, conditionals, and subroutines. Type in the following program, without the comments if you wish. ;***********EQUATES SECTION*********** status equ 3 portb equ 6 trisb equ h'86' count1ms equ h'0c' ;Ln 5. counter for 1 ms pause pausecount equ h'0d' ;Ln 6. counter for pause ;***********CONFIGURATION************ list p=16f84 ;all this is the same as previous __config h'3ff1' org 0 start bsf status, 5 movlw b'00000000' movwf trisb bcf status, 5 clrf portb ;Ln 17. clears port B (sets all bits to zero) ;***********Main Routine************* ;Turns on LED, pauses twice for 250 ms each, then turns off ;the LED, pauses twice for 250 ms each ;Repeat forever begin bsf portb, 0 ; Ln 23. sets bit 0 of port b to 1 (LED on) movlw d'250' ;Ln 24. moves decimal 250 into working reg movwf pausecount ;Ln 25. moves 250 from W to counter for pause call pause ;Ln 26. pause for 250 ms movlw d'250' ;repeat pause sequence movwf pausecount call pause bcf movlw movwf call movlw movwf call goto portb, 0 ; d'250' pausecount pause d'250' pausecount pause begin Ln 31. sets bit Port B, bit 0 to 1 (LED off) ;do the whole pause thing again ; ;***********Subroutines************** ;subroutine pause pauses the number of ms given by number in register pausecount ;(in this case, 250) 4 pause call decfsz pause1ms pausecount goto return pause ;Ln 43. pause for 1 ms ;Ln 44. decrement the number in register pausecount ;by 1. When number reaches 0, skip past the next instruction ;to the return ;Ln 48. ;subroutine pause1ms pauses for 1 ms pause1ms movlw d'200' movwf count1ms ;counter to loop 200 times dec1 nop ;Ln 53. means no operation. Does nothing except nop ;take 1 microsecond to execute. Use for timing decfsz count1ms ;decrement count1ms. Skip the goto instruction ;when count1ms reaches 0 goto dec1 return end 16. Note the new way of declaring variables. Line 5 defines a counter, count1ms, by defining its location as register file 0C. Anytime you refer to the variable count1ms, you are in fact referring to the number stored in that register file. 17. Notice how subroutine pause is invoked using the call instruction (e.g. Line 26). When pause finishes executing, control will return to the next line of the main routine (e.g. Line 27). Contrast this with GOSUB and RETURN in BASIC. 18. Let’s examine how the pause subroutine works. When it is called, it immediately calls another subroutine called pause1ms which, not surprisingly, causes a 1 ms pause. The next instruction is decfsz pausecount. This is a very interesting instruction because it does several things. First, it decrements pausecount by 1. If the number in pausecount is 250 before decfsz, it is 249 afterwards. The next thing decfsz does is to determine whether the new number is zero. It does this by looking at the Z bit in the status register (bit 2, according to Smith), which gets set to 1 when the result of decfsz is zero. Under this condition only, the program skips the next instruction (goto pause) and executes the one that follows (return). The overall operation of the pause subroutine is to pause 1 ms, decrement the counter, look to see if the counter is 0. If not, the subroutine jumps back to pause another millisecond, decrement the counter, and so forth. When the counter finally reaches 0, the subroutine returns to the main routine. 19. PIC assembly language has no IF-THEN structure, as such. Instead the language handles conditional situations using the approach we just saw with decfsz. That is, it tests a bit (in the case of decfsz, the Z bit in the status register) and if the bit is set, the program skips the following instruction and executes the next one after that. If the bit is not set, the program executes the instruction immediately after the test. Almost always, this instruction is a goto or call. There are 4 instructions which operate much like decfsz. The 4 are decfsz, incfsz, btfss (which tests a bit in a file and skips the next instruction if the bit is set), and btfsc (which skips if the bit is clear). We will use btfss in the next exercise. 20. Timing in the PIC microcontroller. Consider now the pause1ms subroutine. Here timing is critical, and here is where assembly language really shines. Watch how it works: First the number 200 is moved into the counter count1ms. Then the subroutine goes 200 times through a loop. Look at the instructions inside the loop. Two of them are nop. nop means “no operation”. What kind of useless instruction is that? The answer is nop is used to soak up time. a. PIC processors execute instructions at ¼ the oscillator speed. Since the oscillator runs at 4 MHz, this means the PIC executes 1 million instructions per second. Said differently, 5 b. each instruction requires 1 s to execute. Actually most instructions require 1 s. However, goto and call each require s. The total time for each loop in the pause1ms subroutine is therefore5 s, as computed below. Since there are 200 loops, the total time is 200 x 5 s = 1000 s = 1 ms. If you include the extra time for the movlw, movwf, and return instructions, the actual total is 1004 s, but it’s pretty close to 1 ms. You could get closer by looping only 199 times instead of 200. Then your total would be 999 s. pause1ms movwf dec1 nop nop decfsz goto return movlw d'200' count1ms count1ms dec1 ;1 s ;1 s ;1 s ;2 s 5 s = total time in loop 21. In PICBASIC, the instruction PAUSE 250 will result in a 250 ms pause. In the assembly program we’ve been using, the equivalent requires 3 lines of code: movlw d'250' movwf pausecount call pause This approach may seem unwieldy, but it does allow us to pass the value of 250 to the subroutine. This makes the pause command flexible. If we want to pause a different time, say for example 100 ms, we could simply replace 250 with 100. To get a 500 ms pause, we invoke 2 250 ms pauses. Why not simply movlw d’500’? The answer is we can’t. Remember that all memory locations are 1 byte in size. The biggest number you can move into a register is 255. 22. Activity 2 (5 pt. I. I. ________) Write an assembly language program that flashes an LED once a second when a pushbutton is pressed. Hint: you can use most of the previous program as subroutines, and you may want to check the status of the pushbutton using the btfss instruction. Demonstrate operation, print your subroutine (well commented of course, though you do not need to include as much detail as I gave in the examples – just explain the functions of the main parts of the program, what each variable means, etc.), and include a schematic. Final Comments Even among programmers there aren’t many who can write assembly language code. You now belong to that select group, although I’m sure you’d agree that you have a ways to go before you’d want to put it on your resume. Keep assembly programming in mind when you need to control the timing exactly in a project. References www.microchip.com. Microchip makes the PIC microcontrollers, and also provides the MPLab IDE for free. Benson, D., Easy Microcontrol’n – A Beginner’s Guide to Using PIC Microcontrollers, Square 1 Electronics, 2002. Smith, D. W., PIC In Practice, A Project-Based Approach, 2nd Edition. ISBN 0-75-066826-1. Elsevier, 2006. 6