52905.fb2 Introduction to Microprocessors and Microcontrollers - читать онлайн бесплатно полную версию книги . Страница 17

Introduction to Microprocessors and Microcontrollers - читать онлайн бесплатно полную версию книги . Страница 17

16. Using a PIC microcontroller for a real project

PIC microcontrollers are a very convenient choice to get started in this field although, as we have seen, there are enormous areas of overlap between microprocessors and other microcontroller designs.

One convenience is that Microchip Technology has taken the RISC concept seriously. There are only 35 instructions and, of these, only a few are required to write quite usable programs.

All data movements are based around just a single register called ‘W’ for ‘working’. This performs much the same function as the accumulator in the earlier processors like the 6502.

Getting started

To program the PIC we need a copy of software instructions and more details of the register layout.

There is no getting away from the fact that when we first meet a microcontroller, the information appears overwhelming – not only in the quantity but in the apparent complexity. This is despite the efforts of the designers to make it as simple as possible. It is very much like seeing a new foreign language, which it is really – and tackle it in much the same way, a bit at a time.

There are four golden rules which may help:

1 Don’t get scared! From time to time, it will seem tempting just to throw it all away and find something easier to do.

2 Start really simple – one step at a time.

3 Be prepared for it to take a long time in the early stages.

4 When you are feeling low and despondent, remember that everyone else has felt the same. It will get better. Honest.

The hardware

Let’s keep it really simple. We have to buy a microcontroller and connect it up and then program it.

The device that we are going to use is a PIC16F84A-04/P. The pinout is shown in Figure 16.1. The number PIC16F84A is the type of microcontroller. The 04 tells us that the maximum frequency is 4 MHz and the /P means that it is the standard plastic package. There is no minimum operating frequency and the slower it runs, the less power it consumes.

Figure 16.1

Figure 16.2 shows the most basic circuit. It includes only the chip supplies and a positive supply to prevent the MCLR (master clear) from for a real project accidentally resetting the PIC during the program. Unless they are tied to a definite value all disconnected inputs will float up and down and can cause random switching.

Figure 16.2

We need a clock signal and the simplest and cheapest is just an RC combination. The resistor should be between 5 kΩ and 100 kΩ and the capacitor should be greater than 20 pF. The values chosen for this circuit were not selected carefully and are not critical. In practice, it is difficult to predict the operating frequency of an RC combination. If we need a particular frequency it is better to use a variable resistor and adjust it to give the desired frequency.

Later on, when we have written the program we must tell the assembler what clock signal we are going to use. This programs the PIC to expect an RC oscillator. This information can, as an alternative, be included into the program. It’s our choice but it is slightly easier to do it at the assembly stage but we must remember to do it.

The software

There are three layers of information that we need to use the registers. Firstly, we need the names and addresses of the files as we met in the register file map in Figure 15.8. The second step is the function of each bit in each register, and this is shown in Appendix A, and finally, an explanation of these functions as we see in Appendix B.

All this data looks daunting but the thing to remember is that we only use one bit at a time and we can ignore the rest.

All PICs and indeed all microprocessors and microcontrollers use binary code and nothing else. To make life easier for us, we use assembly language or a high level language. To use these languages, we need a program which is able to convert them to binary code. For assembly language, the required program is called an assembler and for higher level languages a similar job is done by a compiler.

An assembler is part of the PICSTART PLUS development system but there are other assemblers available that will handle code for all the PICs. The code that we write as the input to the assembler is called ‘source code’ and the code that is supplied by the assembler is called ‘object code’. The object code is really in binary but to make it easier for us, it is usually displayed on the screen in hex.

Important note

As we type in the program we must not leave any spaces in instructions or in the data.

The programming steps

The purpose of our first program

We want to do something really simple but not something that may have happened by accident. We are going to configure all of PortB as outputs and then make the output signal to be off, on, off, on, off, on, off, on. We can connect a series of LEDs to the output pins so alternate lights will be on.

1 We will start by selecting Bank 1 of the register file map. To do this we need to talk to the status register, so by referring to Figure 15.8, we can see that the status register is file 03. From our look at the Status register in Chapter 15, we can see that bit 5 is the RP0 which controls the selection of Bank0 or Bank1. The first line of the program must set bit 5 of register 3.

Program starts:

      BSF 3,5; Sets bit 5 of register 3 to select Bank1

It is worth mentioning at this stage, that anything written after the semicolon is just a note for us and is ignored by the assembly program. This is called the comment field. It is always worth using this area to explain the program, and this often saves hours trying to remember what we were attempting to do when we first wrote the program. This can be an even larger problem if someone else writes a program and is then off sick and we are left to find out why the program doesn’t work.

2 We now want to arrange for PortB to be an output. This involves loading a 0 into each of the controlling bits in the data direction register which we can see from Figure 15.8 is called TRISB and is register number 86. To do this, we are going to clear the W register using the CLRW instruction and then copy this zero into register 86. This involves two extra instructions so, at this stage, our program will read:

      BSF 3,5 ; Sets bit 5 of register 3 to select Bank1

      CLRW    ; puts a zero into register W

      MOVWF 86; copies the zero into register 86 which is

              ; the PortB data direction register

3 Now we have sorted out the direction of the data flow and we can input the actual data. This time we are going to clear bit 5 of register 3 to allow us to access Bank0. We can now load our data into the W register. But what is the data? If we assume the LED is going to be connected between the port output and the zero volt connection, the voltages corresponding to the light sequence off, on, off, on, off, on, off, on will be 0V, +5 V, 0 V, +5 V, 0 V, +5 V, 0 V, +5 V and the data will be 0,1,0,1, 0,1,0,1. We could enter this as a binary number written as B’01010101’ or as the hex number 55 which is a little easier to read.

Once the 55H is in the W register, we can use the code MOVWF to copy it into register 06 which in Figure 15.8, we can see is the PortB data register.

The program is now:

      BSF 3,5 ; Sets bit 5 of register 3 to select Bank1

      CLRW    ; puts a zero into register W

      MOVWF 86; copies the zero into register 86 which is

              ; the PortB data direction register

      BCF 3,5 ; clears bit 5 of register 3

      MOVLW 55; this is the output data to give the on, off

              ; sequence

      MOVWF 06; this copies the data into PortB

4 As it stands, the micro will perform each of these steps once and then stop. We have a problem here because it will take only a few microseconds to complete these instructions – certainly too fast for us to see if the correct sequence of LEDs are illuminated. We need to give the micro something to do which will keep the LEDs operational and our choice here is to reload the output port continuously. Have a look at our new program:

      BSF 3,5   ; Sets bit 5 of register 3 to select Bank1

      CLRW      ; puts a zero into register W

      MOVWF 86  ; copies the zero into register 86 which

                ; is the PortB data direction register

      BCF 3,5   ; clears bit 5 of register 3

      MOVLW 55  ; this is the output data to give the on,

                ; off sequence

again MOVWF 06  ; this copies the data into PortB

      GOTO again; this line forces the micro to return to

                ; the previous line

The words ‘again’ are called labels and the assembler program notices that the two are identical and replaces them by the correct address. The fact that we have used ‘again’, a word that makes sense in the context is just to help us to understand the program, the assembler would accept ‘asdf’ or anything else just as happily. Some assemblers put restrictions on the names chosen. It may, for example, not allow it to start with a number, or use certain words or symbols.

5 At the end of the program, we have to put the instruction END to tell the assembler to stop. It is called an ‘assembler directive’ and is there to tell the assembler program that it has reached the end of our program. Directives are not instructions to the microcontroller and are not converted to machine code.

6 At the start of the program we can use the directive ORG which means ‘origin’ and gives the starting address for the assembled program. This has been added to our final program. If we had not done this, the assembler will assume the address to be zero so, in this case it would make no difference whether we added this directive or left it out. When the PIC is reset, it always goes to address 000 so if we wanted the program to start elsewhere we would have to leave a GOTO instruction at address 000 to tell the microcontroller where it is to start. Remember that the ORG is only an instruction to the assembler telling it where to start loading the program – the PIC doesn’t know anything about this because directives are not converted to the program code.

So our final program is:

      ORG 000   ; Program starts at address 000

      BSF 3,5   ; Sets bit 5 of register 3 to select Bank1

      CLRW      ; puts a zero into register W

      MOVWF 86  ; copies the zero into register 86 which

                ; is the PortB data direction register

      BCF 3,5   ; clears bit 5 of register 3 to select

                ; Bank0

      MOVLW 55  ; this is the output data to give the on,

                ; off sequence

again MOVWF 06  ; this copies the data into PortB

      GOTO again; this line forces the micro to return to

                ; the previous line

      END       ; the end of our code to be assembled

Notice how the program is written in columns or ‘fields’. It is necessary to use the correct fields as this tells the assembler what the items are. Remember to use the semicolon to start notes that we wish the assembler to ignore.

Connecting the LEDs

For clarity, only one LED is shown but an LED and resistor should be joined to all the pins 7–13 to show the full output.

LEDs come in different colours and sizes and the cathode must be connected to a less positive voltage than the anode. The cathode is generally recognized either by a shorter connector wire or a flat moulded onto the body.

Component values

Looking at the data for a standard red LED, the typical voltage (Vf) across them when lit is 2 volts with a maximum current of (If) 20 mA. The small ‘f’ stands for ‘forward’. The light lost by reducing the current for a real project below its maximum value is not very great and it would be quite reasonable to operate the LED on, say, 10 mA.

To limit the current flow, a resistor is connected in series. Now, if the supply voltage for the microcontroller is 5 volts and about 0.7 volts are ‘lost’ inside the PIC and the LED is using 2 volts, the series resistor must be ‘using up’ the other 2.3 volts. The value of the resistor is given by R=V/I=2.3/(10×10–3)=230 Ω. If in doubt start with 470 ohms and see how it goes – this is a generally safe value for all situations.

More labels

The use of labels not only makes the program more readable but it allows modifications to be accommodated. For example, if we put in the actual address instead of the label and then modified the program by adding an extra instruction, the actual addresses would all shuffle along a bit to make room for the new instruction, making our old address inappropriate. The program would not work and it might take us hours before we see what we have done whereas a label would be sorted out by running it through the assembler with the new instruction added.

There is another useful assembler directive, EQU, which is an abbreviation for equates or ‘is equal to’. This can be used to make programs more readable by replacing some of the numbers with words. For example, register 86 is the PortB Data Direction register but the program would be easier to read if we replaced the number by the name. This would be done adding the line: PortBDDR EQU 86 before the program listing so as soon as the assembler spots the name PortBDDR it would replace it with 86. This has no affect on the final program but it makes life easier for us – which has got to be a ‘good thing’.

If we add some other labels, the final program can now be written as:

;EQUATES

PortBDDR EQU 86        ; PortB data direction reg.

                       ; is register 86

PortB    EQU 06        ; PortB data register is

                       ; register 06

Status   EQU 03        ; Status register is register

                       ; 03

RP0      EQU 05        ; Bank1 is selected by bit 5

Data     EQU 55        ; Data used is 55H

         BSF Status,RP0; Sets bit 5 of register 3 to

                       ; select Bank1

         CLRW          ; puts a zero into register

                       ; W

         MOVWF PortBDDR; copies the zero into

                       ; register 86 which is the

                       ; PortB data direction

                       ; register

         BCF Status,RP0; clears bit 5 of register 3

         MOVLW Data    ; output data to give the

                       ; on, off sequence

again    MOVWF PortB   ; this copies the data into

                       ; PortB

         GOTO again    ; this line forces the micro

                       ; to return to the previous

                       ; line

         END           ; the end of our code to be

                       ; assembled

By making full use of labels, we have rewritten our program without any numbers at all. This is just a matter of choice – all labels, some labels or no labels, whatever we like.

Using a crystal

This gives a more accurate clock speed so that programs that involve real can be written. It may be that we want a display sequence to run at a particular rate.

To change to a crystal we need to set up the configuration bits in the PIC so that it knows that it is being controlled by a crystal instead of the RC method. This is most easily handled during the assembly process by clicking on ‘configuration bits’ and selecting the clock source from the options offered.

The two capacitors shown in Figure 16.3 in the clock circuit always have equal values and the range shown is suitable for 200 kHz and all clocks of 2 MHz and over. Other recommended values are: 32 kHz — 68/100 pF and 100 kHz — 100/150 pF. The higher values in each range results in higher stability but slower startup times.

Figure 16.3 A Crystal Controlled Clock

A ceramic resonator can be used as a plug-in replacement for the crystal.

A modification to the program

In the last program we controlled the voltages to each of the PortB outputs. With slight modifications we would be able to apply any combinations of voltages to control any external circuits. Even this first circuit has significant control capabilities but now we are going to extend the capability by applying a counting sequence to the output signals.

All programs are built on the backs of other programs that we have used before so we can save considerable time by keeping copies of our successful programs to be recycled whenever possible. This is well demonstrated in this example.

The program consists of three steps, two of which we have already designed and tested, so we know it works. If the new program refuses to work, we don’t have to start from scratch, we know two–thirds of it is OK. This is a very powerful method of designing programs and whole libraries of programs are available so new developments can be reduced to slotting together ready-made program segments.

When we make changes to a previously program, it is important to save the new version under a new name so that, in the event of a disaster, we can retreat and start again.

Here is the section that we have ‘borrowed’ from our previous work:

     ORG 000

     BSF 3,5

     CLRW

     MOVWF 86; PortB data direction = output

     BCF 3,5

     MOVLW 55

     MOVWF 06; PortB data set to a start value

At this stage we can, of course, set the start value for the output to any value between 00H to FFH which is binary B’00000000’ to B’11111111’.

We have only one new instruction to worry about: INCF f,d. It increments or increases the value of a selected file ‘f’ by 1, and where the new value goes to is determined by the value of the ‘d’ term. If ‘d’ is 0 the new value is put into the W register but if it is 1, the new value is put back into the register in use.

PortB data register is register 06 so the code INCF 06,1 will take the current value of PortB data, increase it by 1 and put the answer back into PortB data so our starting value of 55 will change to 56 and the output voltages on the pins will change from 01010101 to 01010110.

This was just a single count, but for a continuous count we could use a label to make the program jump back and do the INCF trick again and again. When it reaches its maximum value, it will roll over to zero and start again so the count process can be continuous. Our program would now be:

      ORG 000

      BSF 3,5

      CLRW

      MOVWF 86  ; PortB data direction = output

      BCF 3,5

      MOVLW 55

      MOVWF 06  ; PortB data set to a start value

again INCF 06,1

      goto again; go back and INCF again

      end       ; end of source code

One more step

The speed at which the count continues is determined by the rate at which instructions are being followed.

Slowing things down

If we wish to slow things down, we can give the microcontroller something to do just to keep it busy. We have a NOP instruction which does absolutely nothing but takes one instruction cycle to do it. Since it doesn’t do anything, it doesn’t matter how many we include in a program, or where we use them. For a significant delay we made need hundreds, which is not an elegant way of solving a problem.

In the last modification to the problem, we made it count up on the PortB register. Now this takes time, so we could use this counting trick as a time waster. The PIC has 68 general purpose registers that can be made to count for us. Just choose any one of them and have it count for a set number of counts and then we can go back and count once on the PortB register, then go back to the time waste count. In Figure 16.4, we have loaded a register with a number, say 30H (48 in decimal). The next instruction decreases it by 1 to give 2FH (not 29!!!) and since the answer is not zero, we go around the loop and decrement it again to 2EH and so on until it gets to zero whereupon it leaves the loop to carry out ‘instruction 2’ shown in the figure.

Figure 16.4 Using a timing loop

The instruction we are going to use this time is INCFSZ f,d. This is designed just for this type of counting job. It decrements the chosen register and if d=0, the result goes into the W register but if it is 1, it will go back into the same register. For our purposes we would load the code as DECFSZ 20,1. This would decrement register 20 and put the answer back into register 20. When this register reaches zero, it will miss out the next instruction to stop it going around the loop again and will move on to the next instruction.

A slower count

Once again, this uses some of our previous programs.

      ORG 000

      BSF 3,5

      CLRW

      MOVWF 86   ; PortB data direction = output

      BCF 3,5

      MOVLW 55   ; PortB data set to a start value

      MOVWF 06

again INCF 06,1

      MOVLW 30   ; Loads W with 30H

      MOVWF 20   ; puts the number 30 into file 20

count DECFSZ 20,1; decrements register 20

      goto count ; keeps decrements until it gets to zero

      goto again ; returns to increment PortB

      end

For the slowest count on PortB, we would have to increase the count number in register 20 to its maximum number which, using this microcontroller is 7FH or 127 in decimal.

Calculating the delay

Starting from the moment that PortB is incremented:

MOVLW takes 1 count.

MOVWF takes 1 count.

DECFSZ takes 1 count normally but 2 when it leaves the loop.

As the register was loaded with the hex number 30, which is 48 in decimal, it will go around the ‘count’ loop 47 times at 1 instruction clock each and 2 clocks as it leaves the loop. This gives a total of 49 cycles.

goto will be used 48 times at 2 clocks each giving a total of 96 clocks.

goto will also be used once to return to the PortB, this is another 2 cycles.

Finally, INCF takes 1 count to increment the value on PortB.

The total is: 1 + 1 + 49 + 48 + 2 + 1 = 102 cycles

Assuming a crystal frequency of 32 kHz, we can divide it by 4 to give the instruction clock frequency and then by the delay of 102 cycles to give the rate at which the PortB is incremented of about 78 counts per second. PortB counts in binary from 0000 0000 to 1111 1111 and will finish its count after 256 counts so it will start recounting after 256/78 or roughly 3.3 seconds. We could reasonably double this time delay by a liberal sprinkling of NOPs or using a longer loop.

Longer delays

We have three alternatives.

1 For small changes, we could add some NOPs inside of the counting loop to boost the number of counts.

2 Our delay was built into the main program but we could have used it as a subroutine. A subroutine is any block of code that we may want to use more than once. In the main program we insert an instruction CALL followed by a label to identify the block of code so for our delay loop which we called ‘count’ we would insert the instruction ‘CALL count’ at any time we want to use our program to cause a delay. When the delay loop ‘count’ has been completed, we insert the instruction RETURN at the end of this block of code and the microcontroller will return to the main program.

The benefit of using a subroutine is that we can run the ‘count’ delay twice just by inserting the instruction CALL count twice in the main program and we don’t have to enter the delay loop again with the fear that we will mistype something and it will all collapse. We can make a subroutine as long as we want and use it as often as we want just by adding the CALL and RETURN instructions.

Here is our previous program but reorganized to use the delay loop ‘count’ as a subroutine.

count DECFSZ 20,1; decrements register 20

      goto count ; keeps decrements until it gets to zero

      RETURN

      ORG 000

      BSF 3,5

      CLRW

      MOVWF 86  ; PortB data direction = output

      BCF 3,5

      MOVLW 55

      MOVWF 06  ; PortB data set to a start value

again INCF 06,1

      MOVLW 30  ; Loads W with 30H

      MOVWF 20  ; puts the number 30 into file 20

      CALL count

      goto again; returns to increment PortB

      end

The subroutine is called count and has the instruction RETURN at the end.

The main program has the instruction CALL count which means ‘go and get a subroutine and use the one called count’. We can then put:

CALL count

CALL count

CALL count

In the main program which would be an easy way to treble the length of a delay. We could design a subroutine called ‘1second’ and another for ‘0.1second’.

Then if we needed to insert a delay of 2.3 seconds, we could just add:

CALL 1second

CALL 1second

CALL 0.1second

CALL 0.1second

CALL 0.1second

All subroutines would end with the same code RETURN, so how do they know where they have to go back to?

The answer is a series of memory locations called a stack. Each return address is stored in the stack in order that each CALL occurs, the relevant address is sent to the stack and as each RETURN will occur in sequence, the addresses will be unloaded from the stack in the order required. This is a first-in last-out (FILO) organization. See Chapter 8 for more on the stack.

A subroutine can include a CALL to another subroutine. These are called nested subroutines – the PIC16F84A has room in its stack for eight return addresses – which is pretty small by microprocessor standards.

3 In the PIC, most instructions are completed in a single instruction cycle which is 14 of the clock speed. To change the delay, we could always change the clock speed. There are two benefits, a slower clock speed reduces the power consumed, there is no low-speed limit for the PIC, unlike some devices. Generally subroutines are preferred as there are often other constraints on the clock speed.

Quiz time 16

In each case, choose the best option.

1 Return is:

(a) only used as part of a delay loop.

(b) a ticket to take you home again.

(c) an assembly directive.

(d) an instruction found at the end of a subroutine.

2 ORG is:

(a) never needed since the PIC always starts at address 0000.

(b) an assembler directive.

(c) short for orgasm.

(d) an instruction code.

3 An assembler converts:

(a) decimals into hexadecimals.

(b) main codes into subroutines.

(c) source code to object code.

(d) object code into binary code.

4 In choosing a clock circuit:

(a) a ceramic resonator is not as accurate nor so robust as a crystal.

(b) an RC runs at four times the frequency of a crystal.

(c) a crystal gives the most accurate and stable frequency.

(d) use an RC circuit and a crystal to get accuracy and robustness.

5 The normal execution time for when using 4 MHz crystal is:

(a) 0.25 microseconds.

(b) 1 millisecond.

(c) 4 milliseconds.

(d) 1 microsecond.