Assembly language, assemblers and compilers.
PIC 16F690 family Instruction Set:-
Exact details of how to interpret this table can be found in the datasheet.
w
Edited by wakibaki - 12/14/12 at 6:55pm
Assembly language, assemblers and compilers.
PIC 16F690 family Instruction Set:-
Exact details of how to interpret this table can be found in the datasheet.
w
Here's a simple TH layout.
This file has the gerbers:
PIC_voltmeter_th - CADCAM.ZIP 11k .ZIP file
Or for SMT, I'd modify the schematic a bit to make it easier to route:
Giving us this:
... which would be more convenient if, for example, you wanted to panel mount it, as the side facing has only the display and a few SMT res. on it. The display would need to be surface mounted, but this is not a big problem as it has long legs, I've used them like this.
I've retained a few thru-hole components, it's quite common to do this.
PIC_voltmeter_SMT - CADCAM.ZIP 8k .ZIP file
Or you can simply build it on a breadboard for purposes of the tutorial.
w
How to read a datasheet.
;This code block configures the ADC ;for polling, Vdd reference, Frc clock ;and AN0 input. ;Conversion start & polling for completion ;are included. BANKSEL ADCON1 MOVLW B’01110000’ ;ADC Frc clock MOVWF ADCON1 BANKSEL TRISA BSF TRISA,0 ;Set RA0 to input BANKSEL ANSEL BSF ANSEL,0 ;Set RA0 to analog BANKSEL ADCON0 MOVLW B’10000001’ ;Right justify, MOVWF ADCON0 ;Vdd Vref, AN0, On CALL SampleTime ;Acquisiton delay BSF ADCON0,GO ;Start conversion BTFSC ADCON0,GO ;Is conversion done? GOTO $-1 ;No, test again BANKSEL ADRESH MOVF ADRESH,W ;Read upper 2 bits MOVWF RESULTHI ;store in GPR space BANKSEL ADRESL MOVF ADRESL,W ;Read lower 8 bits MOVWF RESULTLO ;Store in GPR space

This is all really good stuff so far.
I intend to dive right in in the new year.
Thank you again!
So.
#include <p16F887.inc> __CONFIG _CONFIG1, _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT __CONFIG _CONFIG2, _WRT_OFF & _BOR21V
If we search the Microchip directory tree (under Program Files) for p16F690.inc, we will soon discover the file in \MPASM Suite (depending on exact version). We can just click on the file, which should cause it to open in Notepad, or drop it into any text editor of our choice.
;========================================================================== ; The following is an assignment of address values for all of the ; configuration registers for the purpose of table reads _CONFIG EQU H'2007' ;----- CONFIG Options -------------------------------------------------- _FOSC_LP EQU H'3FF8' ; LP oscillator: Low-power crystal on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN _LP_OSC EQU H'3FF8' ; LP oscillator: Low-power crystal on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN _FOSC_XT EQU H'3FF9' ; XT oscillator: Crystal/resonator on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN _XT_OSC EQU H'3FF9' ; XT oscillator: Crystal/resonator on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN _FOSC_HS EQU H'3FFA' ; HS oscillator: High-speed crystal/resonator on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN _HS_OSC EQU H'3FFA' ; HS oscillator: High-speed crystal/resonator on RA4/OSC2/CLKOUT and RA5/OSC1/CLKIN _FOSC_EC EQU H'3FFB' ; EC: I/O function on RA4/OSC2/CLKOUT pin, CLKIN on RA5/OSC1/CLKIN _EC_OSC EQU H'3FFB' ; EC: I/O function on RA4/OSC2/CLKOUT pin, CLKIN on RA5/OSC1/CLKIN _FOSC_INTRCIO EQU H'3FFC' ; INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN _INTRC_OSC_NOCLKOUT EQU H'3FFC' ; INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN _INTOSCIO EQU H'3FFC' ; INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN _FOSC_INTRCCLK EQU H'3FFD' ; INTOSC oscillator: CLKOUT function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN _INTRC_OSC_CLKOUT EQU H'3FFD' ; INTOSC oscillator: CLKOUT function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN _INTOSC EQU H'3FFD' ; INTOSC oscillator: CLKOUT function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN _FOSC_EXTRCIO EQU H'3FFE' ; RCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, RC on RA5/OSC1/CLKIN _EXTRC_OSC_NOCLKOUT EQU H'3FFE' ; RCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, RC on RA5/OSC1/CLKIN _EXTRCIO EQU H'3FFE' ; RCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, RC on RA5/OSC1/CLKIN _FOSC_EXTRCCLK EQU H'3FFF' ; RC oscillator: CLKOUT function on RA4/OSC2/CLKOUT pin, RC on RA5/OSC1/CLKIN _EXTRC_OSC_CLKOUT EQU H'3FFF' ; RC oscillator: CLKOUT function on RA4/OSC2/CLKOUT pin, RC on RA5/OSC1/CLKIN _EXTRC EQU H'3FFF' ; RC oscillator: CLKOUT function on RA4/OSC2/CLKOUT pin, RC on RA5/OSC1/CLKIN _WDTE_OFF EQU H'3FF7' ; WDT disabled and can be enabled by SWDTEN bit of the WDTCON register _WDT_OFF EQU H'3FF7' ; WDT disabled and can be enabled by SWDTEN bit of the WDTCON register _WDTE_ON EQU H'3FFF' ; WDT enabled _WDT_ON EQU H'3FFF' ; WDT enabled _PWRTE_ON EQU H'3FEF' ; PWRT enabled _PWRTE_OFF EQU H'3FFF' ; PWRT disabled _MCLRE_OFF EQU H'3FDF' ; MCLR pin function is digital input, MCLR internally tied to VDD _MCLRE_ON EQU H'3FFF' ; MCLR pin function is MCLR _CP_ON EQU H'3FBF' ; Program memory code protection is enabled _CP_OFF EQU H'3FFF' ; Program memory code protection is disabled _CPD_ON EQU H'3F7F' ; Data memory code protection is enabled _CPD_OFF EQU H'3FFF' ; Data memory code protection is disabled _BOREN_OFF EQU H'3CFF' ; BOR disabled _BOD_OFF EQU H'3CFF' ; BOR disabled _BOR_OFF EQU H'3CFF' ; BOR disabled _BOREN_SBODEN EQU H'3DFF' ; BOR controlled by SBOREN bit of the PCON register _BOD_SBODEN EQU H'3DFF' ; BOR controlled by SBOREN bit of the PCON register _BOR_SBODEN EQU H'3DFF' ; BOR controlled by SBOREN bit of the PCON register _BOREN_NSLEEP EQU H'3EFF' ; BOR enabled during operation and disabled in Sleep _BOD_NSLEEP EQU H'3EFF' ; BOR enabled during operation and disabled in Sleep _BOR_NSLEEP EQU H'3EFF' ; BOR enabled during operation and disabled in Sleep _BOREN_ON EQU H'3FFF' ; BOR enabled _BOD_ON EQU H'3FFF' ; BOR enabled _BOR_ON EQU H'3FFF' ; BOR enabled _IESO_OFF EQU H'3BFF' ; Internal External Switchover mode is disabled _IESO_ON EQU H'3FFF' ; Internal External Switchover mode is enabled _FCMEN_OFF EQU H'37FF' ; Fail-Safe Clock Monitor is disabled _FCMEN_ON EQU H'3FFF' ; Fail-Safe Clock Monitor is enabled
_CONFIG EQU H'2007'
__CONFIG _CONFIG1, _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT
Bytes, data types and ASCII
Before we move on to writing a program, there is another topic that I want to touch on.
This is related to the format for data, the byte, 8 bits.
The byte is very important as a fundamental unit for storage and processing, and an appreciation of why this is the case is important in programming of all kinds, so although we are not going to use the ASCII code here, I think it is worth a mention, because I'm trying to give an introduction to programming here that will have wider application than just in PIC micros.
I touched on assemblers, compilers and text files earlier. These are the tools that make it possible for us to program a computer or control system using a computer, rather than using paper tables of instructions and a pen and paper.
In order to store text in a computer memory it is necessary to turn the alphabet into numbers. We need to do this in a standard way if information is to be transferred from one system to another and still remain comprehensible.
The basic (worldwide) system for doing this is called the American Standard Code for Information Interchange, ASCII for short.
There are several more complex ways of encoding text, to include the large number of characters, accents, Cyrillic alphabet, ideograms (Japanese, Chinese), Hebrew, Farsi and other written forms of communication, but the fundamental basis for computer text is the ASCII code.
It takes the 256 values available in a byte and assigns the digits, 0-9 and the alphabet A-Z and a-z (called alphanumerics) an individual number.
You can see here A is 65, B is 66 and so on. Don't ask me why these particular numbers were chosen to represent these particular letters, I don't know, but these are used, almost without exception, in all the computers all over the world, other than when data is encrypted.
The 256 values available in a byte mean that it is possible to encode any number, all the upper and lower case alphabet and quite a few punctuation marks and other formatting commands such as return and newline, which date back to the days of teletype (teleprinter, TTY) machines most of which used a 5-bit code called BAUDOT.
In fact the code shown below only requires 128 values, but you can look up the extended ASCII code, which includes graphics primitives which enable the drawing of onscreen boxes and other useful features which will be familiar to anybody who used the previous generation of IBM PCs which ran DOS, the predecessor of Windows.
Hence when talking about bytes we sometimes call them characters and compilers call character variables char.
A one dimensional array of char is called a string.
A variable declaration for a chunk of memory (called mystring) to hold 30 characters might be declared like this
unsigned char[31] mystring;
31 bytes are used because, by convention, character strings in memory are terminated with a zero byte (B'00000000'), so that the system knows where the string ends.
unsigned char, because char variables can still be used to store numbers. We can reuse these variables for multiple purposes as we choose if memory is short, but where memory is not severely limited, we normally assign variables to a single purpose, as then we don't have to do as much work keeping track of what use the memory is being put to at any given time.
signed and unsigned, when used to refer to variables, denotes whether they are used to contain numbers with a positive or negative value (signed) or a value alone (unsigned).
When a variable is used to store signed values, the MSB (Most Significant Bit) is given over to indicating positive (zero, 0) or negative (one, 1). Hence a signed char can contain numeric values between -128 and +127.
An integer is normally a 16-bit (2-byte) value, and a long integer is a 32-bit value, although these definitions are flexible to a degree, depending on the system in question. Values with a decimal point are called float (floating point) and have special compiler libraries or header files (math.h in C) to deal with processing them which take the burden of manipulating these values off the programmer, and may make use of a hardware maths co-processor.
w
Good stuff Fred; a clear and concise cover of the topic. Since this is my 'wheelhouse' so to speak, I'm anticipating the application. Hope you had a good New Years and again - thanks for your time and efforts!
Back again.
I said I would show a couple of applications using 7-segment displays, and I will, but in the meantime I needed to develop something for myself, so I'm going to show it first.
A friend of mine recently suggested using a stepper motor to drive a pot or stepped attenuator for a remote, powered volume control.
As it happened I had just learned a new technique for PCB layout.
Some components are circular, such as tubes. Previously, when developing footprints (pad layouts) for circular components, I placed the pads on a grid to within a thousandth of an inch using trigonometry to calculate the positions. The layout tool draws circles no problem, so the component outlines were no problem.
Recently I started to use the layout tool to rotate components and tracks as a block. The menu feature which permits this is different from the rotate facility for standalone components which works in 90 degree increments. The block rotate facility works in degrees and fractions of a degree. Thus it's possible to use it to make circular component footprints by placing one pad, rotating the drawing and placing another pad, and so on...
So making circular layouts of all kinds has become a lot simpler for me. You can use a tool for years without discovering all its features.
The next obvious thing to do was to make a circular layout with SMT resistors, make a custom mechanism (a wiper) to fit a stepper motor, and bolt the stepper + wiper directly to the PCB. Hey presto, a custom stepped attenuator.
So I looked on ebay, and quickly found these, stepper motors + gearboxes:-
You can see my test logic-level board with 16F690 and a 5V regulator. It has 3 sets of header pins. One to connect a PicKit2 programmer, one 4-output header to drive the motor coils A, B, C and D and a 2-pin carrying +5V and GND. Each stepper came with a driver board with a ULN2003 transistor array chip, with indicator LEDs for the stepper coils and header pins for connections..
The driver board means that the motor can be driven using logic levels if DC power is supplied to the driver board, in this case 5-12V DC.
A stepper motor has a rotor which resembles a toothed gearwheel. The coils are arranged so that the rotor moves a fraction of a whole rotation when one coil after another is driven. 200 steps per revolution is common. To get half steps sometimes adjacent coils are energised simultaneously: A, A+B, B, B+C, C, C+D, D, D+A, A, A+B and so on. This gives a smoother rotation. It's possible to drive a stepper motor with orthogonal sinewaves to get even smoother motion, but the using the cogged motion it's possible to reposition the motor accurately very successfully. These motors have a built-in gearbox, permitting even finer positioning.
A peculiarity of stepper motors is that they have a maximum speed of rotation when starting up, otherwise the magnetic fields can move ahead of the rotor motion, causing the motor to stall. Once the motor is started the stepping speed can be ramped up, if so desired.
Here's the ebay ad.
...and the stepping sequence.
The first objective here is to determine a reasonable starting (stepping) speed using the PICs internal clock. To do that we create a simple program to output the stepping sequence on one of the PICs ports, PORTC in this case. We can determine the switching rate empirically by putting a delay in between steps and changing its duration.
That's probably enough for one post...
w
Lest anybody get impatient to see some code, here's the first, basic program in its entirety:-
list p=16F690
#include <p16F690.inc>
__CONFIG _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT
cblock h'20'
rflag
sflag
dflag
endc
ORG 0x000
bcf STATUS,RP1
bcf STATUS,RP0 ; 00, BANK 0
movlw b'11111111'
movwf PORTA ; PORTA all ones
movwf PORTB ; PORTB all ones
clrf PORTC ; PORTC all zero
bsf STATUS,RP1
bcf STATUS,RP0 ; 00, BANK 2
clrf ANSELH ; make PORTB 4,5 inputs digital
bcf STATUS,RP1
bsf STATUS,RP0 ; 01, BANK 1
movwf TRISB ; PORTB inputs
movwf TRISA ; PORTA inputs
clrf TRISC ; PORTC outputs
bcf STATUS,RP1
bcf STATUS,RP0 ;00, BANK 0
movlw b'00000001'
movwf rflag
movwf PORTC
clrf dflag
mainloop:
btfsc PORTB,4
goto main_anti
call clockwize
call do_your_thing
call delay
call delay
call delay
call delay
call delay
call delay
call delay
call delay
call delay
call delay
goto mainloop
main_anti:
btfsc PORTB,5
goto mainloop
call aclockwize
call do_your_thing
call delay
call delay
call delay
call delay
call delay
call delay
call delay
call delay
call delay
call delay
goto mainloop
clockwize:
rrf rflag,f
btfss STATUS,C
goto clk
bsf rflag,7
clk:
bcf STATUS,C
return
aclockwize: ;6
rlf rflag,f
btfss STATUS,C
goto aclk
bsf rflag,0
aclk:
bcf STATUS,C
return
do_your_thing:
movfw rflag
movwf sflag
rrf sflag,f
btfsc STATUS,C
goto s0
rrf sflag,f
btfsc STATUS,C
goto s1
rrf sflag,f
btfsc STATUS,C
goto s2
rrf sflag,f
btfsc STATUS,C
goto s3
rrf sflag,f
btfsc STATUS,C
goto s4
rrf sflag,f
btfsc STATUS,C
goto s5
rrf sflag,f
btfsc STATUS,C
goto s6
rrf sflag,f
btfsc STATUS,C
goto s7
s0:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
bcf STATUS,C
movlw b'00000001'
movwf PORTC
return
s1:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
bcf STATUS,C
movlw b'00000011'
movwf PORTC
return
s2:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
bcf STATUS,C
movlw b'00000010'
movwf PORTC
return
s3:
nop
nop
nop
nop
nop
nop
nop
nop
bcf STATUS,C
movlw b'00000110'
movwf PORTC
return
s4:
nop
nop
nop
nop
nop
nop
bcf STATUS,C
movlw b'00000100'
movwf PORTC
return
s5:
nop
nop
nop
nop
bcf STATUS,C
movlw b'00001100'
movwf PORTC
return
s6:
nop
nop
bcf STATUS,C
movlw b'00001000'
movwf PORTC
return
s7:
bcf STATUS,C
movlw b'00001001'
movwf PORTC
return
delay: decf dflag,f
btfss STATUS,Z
goto delay
return
END
Here's the program reduced to a template for PIC 16F690:
list p=16F690
#include <p16F690.inc>
__CONFIG _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT
cblock h'20'
endc
ORG 0x000
mainloop:
goto mainloop
END
list p=16F690
#include <p16F690.inc>
__CONFIG _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT
cblock h'20' var1... var2... var3... endc
You can see that this diagram is titled 'PIC 16F690 SPECIAL FUNCTION REGISTERS'. This is because the data memory is dual-purpose. There are 4 byte-wide banks. The lower 32 (0-31) registers (bytes) of each bank are used for controlling special functions of the PIC which are accessible at runtime from your program.
The bytes from 32-128 are called General Purpose Registers and are available for temporary storage of data for the use in programs. If you use a different processor you have to look at the memory map, because the GP registers in some start at h'40' or elsewhere.
So now we can see that the constant h'20' which we called var1 can be used to address the memory at address h'20', so that when we write an assembler instruction to perform an operation on a file, which is what the assembler calls an 8-bit register, and we use the name var1 in that line, the assembler knows that we mean that the register on which to perform the operation is the one at h'20'.
If this all seems a bit long-winded, it will become clearer as we examine the program. It's just a way of making the program easier to understand when reading it, because we could call the variable 'kilograms', or 'miles' and we don't have to remember that register h'20' contains the number of miles or kilograms or volts or whatever.
ORG 0x000
mainloop:
goto mainloop
main()
{
}
END
In the cblock section of the stepper program I create (name) three variables.
cblock h'20' rflag sflag dflag endc
sflag because I need a shadow register to move rflag into so I can do operations on it to discover where the one is without affecting the state of rflag, and s comes after r.
dflag because I need a register to use for counting, and it was less typing to copy rflag and change the r to d for delay.
Now let's look at the first section of the program, the initialisation:-
bcf STATUS,RP1
bcf STATUS,RP0 ; 00, BANK 0
movlw b'11111111'
movwf PORTA ; PORTA all ones
movwf PORTB ; PORTB all ones
clrf PORTC ; PORTC all zero
bsf STATUS,RP1
bcf STATUS,RP0 ; 00, BANK 2
clrf ANSELH ; make PORTB 4,5 inputs digital
bcf STATUS,RP1
bsf STATUS,RP0 ; 01, BANK 1
movwf TRISB ; PORTB inputs
movwf TRISA ; PORTA inputs
clrf TRISC ; PORTC outputs
bcf STATUS,RP1
bcf STATUS,RP0 ;00, BANK 0
movlw b'00000001'
movwf rflag
movwf PORTC
clrf dflag
bsf STATUS, RP0
bcf STATUS, RP1
The first thing we want to do is initialise the port values, because when we assign them as inputs or outputs they immediately output their current value (if outputs), and the set value can influence what is read if they are inputs.
bcf STATUS,RP1
bcf STATUS,RP0 ; 00, BANK 0
movlw b'11111111'
movwf PORTA ; PORTA all ones
movwf PORTB ; PORTB all ones
clrf PORTC ; PORTC all zero
clrf ANSELH
Now let's take a look at the program's main loop.
This is the section between the label, 'mainloop:' and the last 'goto mainloop'.
mainloop:
btfsc PORTB,4
goto main_anti
call clockwize
call do_your_thing
call delay
goto mainloop
main_anti:
btfsc PORTB,5
goto mainloop
call aclockwize
call do_your_thing
call delay
goto mainloop
btfsc PORTB,4
OK, here are the 2 subroutines, clockwize and anticlockwize, they're practically identical, so there's no reason not to just describe one.
clockwize:
rrf rflag,f
btfss STATUS,C
goto clk
bsf rflag,7
clk:
bcf STATUS,C
return
aclockwize:
rlf rflag,f
btfss STATUS,C
goto aclk
bsf rflag,0
aclk:
bcf STATUS,C
return
delay:
decf dflag,f
btfss STATUS,Z
goto delay
return
do_your_thing:
movfw rflag
movwf sflag
rrf sflag,f
btfsc STATUS,C
goto s0
rrf sflag,f
btfsc STATUS,C
goto s1
rrf sflag,f
btfsc STATUS,C
goto s2
rrf sflag,f
btfsc STATUS,C
goto s3
rrf sflag,f
btfsc STATUS,C
goto s4
rrf sflag,f
btfsc STATUS,C
goto s5
rrf sflag,f
btfsc STATUS,C
goto s6
rrf sflag,f
btfsc STATUS,C
goto s7
s0:
nop
bcf STATUS,C
movlw b'00000001'
movwf PORTC
return
s1:
nop
bcf STATUS,C
movlw b'00000011'
movwf PORTC
return
s2:
nop
bcf STATUS,C
movlw b'00000010'
movwf PORTC
return
s3:
nop
bcf STATUS,C
movlw b'00000110'
movwf PORTC
return
s4:
nop
bcf STATUS,C
movlw b'00000100'
movwf PORTC
return
s5:
nop
bcf STATUS,C
movlw b'00001100'
movwf PORTC
return
s6:
nop
bcf STATUS,C
movlw b'00001000'
movwf PORTC
return
s7:
bcf STATUS,C
movlw b'00001001'
movwf PORTC
return
Sorry I haven't written anything here for a while, I'm having some trouble with the 16F690 voltmeter.
I've got the meter working with a 16F887 with a non-multiplexed display, i.e the A/D code works, I've got the display working multiplexed with the 16F690, but I can't get the A/D to work with the multiplexed display. It's occupied several days now without significant progress. The minute I enable the A/D the display goes haywire. Something in the interrupt service routine.
I may simply move on to implementing the PGA2310 volume control, I have this working with a 16F887 with a multiplexed display, it should transfer to the 16F690 no problem, but that's what I thought about the meter. Anyway, having got my teeth into this particular problem, I'm reluctant to let it go, so I'm going to put a few more hours into it...
w.






