Much of this complication can be avoided by using two instructions that Microchip indicates it might not support in future products. The TRIS instruction can be used to set the port direction registers and OPTION can be used to set the OPTION register which deals mainly with timer operations. If you port your code to future Microchip processors that don't support these instructions, you will probably want to rewrite the code for some other reason anyway.
MOVF PATTERN, W ; copy the contents of PATTERN into the working ; register MOVWF PORTB ; copy the contents of W into Port BThe first instruction is of the form MOV f,d which moves the register 'f' to the destination 'd', ('W' in this case). The second instruction simply moves whatever is in 'W' into the register 'f', ( MOVWF, f ). PATTERN remains unchanged in the first instruction and W remains unchanged in the second. Maybe it is more like a copy than a move.
You might think you could get away with one instruction with literals. Literals ,(k), are 8 bits (0-255). Instructions with literals have no room to specify a register, you must use 'W'. Loading a register with a literal also takes two instructions:
MOVLW $AA ; put the pattern 10101010 into W MOVWF PATTERN ; put W into the register PATTERNThe same applies when literals are used in addition, subtraction and the logical functions AND, IOR ,(inclusive OR) and XOR ,(exclusive OR); all involve two instructions:
MOVLW k ; move literal into 'W' SUBWF f,d ; put the result of subtracting W from f into d ; d could be either W or f ; if it's W then f is not changed ; if it's f then W is unaffectedSuppose we wanted to zero out the lower nibble of PATTERN:
MOVLW $F0 ; set up mask ANDWF PATTERN, f ; result of PATTERN AND $F0 is placed in PATTERN ; note that the destination could be W if we ; want the changed pattern to end up thereIF the value to be changed is already in W and the destination is W, a single instruction will work: ADDLW k, SUBLW k, ANDLW k, IORLW k and XORLW k.
Single operand instructions are easy to understand:
CLRF f ; set all bits in register f to zero, (clear register f) CLRW ; set all bits in register W to zero, (clear working register) BCF f,b ; set bit b in register f to zero, (bit clear bit b in f) BSF f,b ; set bit b in register f to one, (bit set bit b in f)
Many instructions in a program are MOV instructions and involve 'W'. It is very easy to confuse loading a register with W and loading W with a register.
MOVWF f ; W IS MOVED TO THE REGISTER f, ( f is changed ) MOVF f, W ; THE REGISTER f IS MOVED TO W, ( W is changed ) MOVF f, f ; THE REGISTER f IS MOVED TO ITSELF ; f is not changed but flags may be setNote that MOVWF is the only 'WF' instruction that doesn't have a choice of destination. It's always 'f'. The other 'WF' instructions are ADDWF, SUBWF, ANDWF, SWAPWF, IORWF & XORWF. In all of these cases one of 'W' or 'f' will be changed and the other not according to the destination. Also remember in SUBWF that 'W' is subtracted from 'f'. Other instructions where the destination is changed include:
INC f,d ; put the value of register f + 1 in either W or f DEC f,d ; put the value of register f - 1 in either W or f COMP f,d ; put the result of toggling all bits of f in destination d SWAP f,d ; put the result of swapping nibbles in f into d RLF f,d ; the result of rotating f left thru carry goes into d RRF f,d ; the result of rotating f right thru carry goes into dIf the destination is 'W' then only 'W' is affected; the original register remains the same.
IN SUBLW k, 'W' is subtracted from the literal 'k'.
It is easy to code GOTO when you meant to code CALL and vice-versa. You might think that this would cause your program to lock up but many times it just makes it act strangely instead.
Beware of using the same registers in two different routines, especially if one calls the other. For example if you use TEMP in a timing loop and then use TEMP in a subroutine that calls the timing loop, you might overlook the fact that the timing loop is changing TEMP.
The rotate instructions, ( RLF,RRF ), are rotates through carry so carry has to be set up prior to the instruction and rotates into the low or high order bit. Likewise the hi or low order bit rotates into carry.
RETURN - return from call, location of next instruction is popped from the stack and put into the program counter RETLW k - as RETURN but literal k is also placed into W RETIE - as RETURN but interrupts are also enabledThe stack consists of eight words and is circular, after the eighth word it rolls over to the first. Calls can be nested only eight deep. There is no push or pop instruction to access the stack directly.
There are four other flow instructions ,( beside CALL and GOTO ), which might be called 'skip it' instructions:
INCFSZ f,d - put f + 1 into either W or f. skips over the next instruction if the result of the increment is zero DECFSZ f,d - put f - 1 into either W or f. skips over the next instruction if the result of the decrement is zero BTFSC f,b - tests bit b of register f, skip next instruction if the bit is zero (bit test skip clear). doesn't change bit BTFSS f,b - tests bit b of register f, skip next instruction if the bit is one (bit test skip set). doesn't change bit
MOVLW $AA ; port pattern '10101010' TRIS TRISB ; W is placed into register TRISBCertain port pins are also hooked to other functions of the processor. The high 4 bits of PORTB can be used as interrupt pins when they are programmed as inputs. The high bit of PORTA is also used as an external clock input for the counter/timer. Bit 0 of PORTB (RB0/INT) can be used for an external interrupt.
MSEC1 MOVLW $F9 ; allow for 4 microsec overhead.. NOP ; (2 for CALL) MICRO4 ADDLW $FF ; subtract 1 from W BTFSS STATUS,Z ; skip when you reach zero GOTO MICRO4 ; more loops RETURNSome comments about the code:
NMSEC MOVWF CNTMSEC ; W to msec count register MSECLOOP MOVLW $F8 ; allow for 8 microsec overhead CALL MICRO4 ; 248 * 4 + 2 = 994 here NOP ; make rest of loop ... NOP ; add up to 6 microseconds DECFSZ CNTMSEC, f ; decrement count, skip when zero GOTO MSECLOOP ; more loops RETURN
In counter mode, input comes from pulses applied externally at the high bit of Port A. The prescaler can be inserted to count every second, forth, eighth etc. pulse. The input pin can also be set to count on either rising or falling transitions.
Various bits in the OPTION register are used to set up the counter/timer. The low three bits, (0-2), set the prescaler ratio. Bit 3 determines whether the prescaler is assigned to timer 0 or the watchdog timer. Only one can use the prescaler at any one time. Bit 5 decides if TMR0 register is used as a timer or is used as a counter of pulses from Port A bit 4, (RA4).
The interrupt routine does whatever you want to happen each time the interrupt occurs. Further interrupts are disabled when the interrupt starts. You are responsible for clearing the flag and re-enabling interrupts. RETIE pulls the saved instruction address off the stack and enables interrupts.
Three other situations can be set up to cause interrupts:
MSGTXT ADDWF PCL, f ; offset added to PCL RETLW $48 ; 'H' RETLW $65 ; 'e' RETLW $6C ; 'l' RETLW $6C ; 'l' RETLW $6F ; 'o' RETLW $20 ; ' ' RETLW $57 ; 'W' RETLW $6F ; 'o' RETLW $72 ; 'r' RETLW $6C ; 'l' RETLW $64 ; 'd' RETLW $21 ; '!' RETLW $0D ; carriage return RETLW $0A ; line feed RETLW $00 ; indicates endTo output a character string you set up a register to point to the initial character, (MSGPTR), and repeatedly call MSGTXT, incrementing the pointer each time. We have reached the end of the string when a zero is returned. The routine is entered with the offset of the first character in 'W':
OUTMSG MOVWF MSGPTR ; put 'W' into message pointer MSGLOOP MOVF MSGPTR, W ; put the offset in 'W' CALL MSGTXT ; returns ASCII character in 'W' ADDLW 0 ; sets the zero flag if W = 0 BTFSC STATUS, Z ; skip if zero bit not set RETURN ; finished if W = 0 CALL OUTCH ; output the character INCF MSGPTR, f ; point at next GOTO MSGLOOP ; more characters
RS232 high is -3V or lower, low is +3V or greater. You can actually get away with +5V for the low and 0V for the high if you keep the line short. Notice that this is flipped from what you might expect. We can use MICRO4 and 52 X 4 microsec loops for 1 bit time at 4800 baud. Actually 12 microseconds are used in overhead so we use 49 as the count. The subroutine is entered with the character to be output in 'W'. Port A bit 2, (pin 1), is use as output:
OUTCH MOVWF TXREG ; put W into transmit register MOVLW 8 ; eight bits of data MOVWF BITS ; a counter for bits BSF PORTA, 2 ; start bit (flipped remember), RA2 TXLOOP MOVLW $31 ; 49 decimal, delay time CALL MICRO4 ; wait 49 x 4 = 196 microseconds RRF TXREG, f ; roll rightmost bit into carry BTFSC STATUS, C ; if carry 0 want to set bit, ( a low ) GOTO CLRBIT ; else clear bit, ( a high ) BSF PORTA, 2 ; +5V on pin 1 ( RA2 ) GOTO TESTDONE ; are we finished? CLRBIT BCF PORTA, 2 ; 0V on pin 1 ( RA2 ) NOP ; to make both options 12 micosec TESTDONE DECFSZ BITS, f ; 1 less data bit, skip when zero GOTO TXLOOP ; more bits left, delay for this one MOVLW $34 ; full 208 microsec this time CALL MICRO4 ; delay for last data bit BCF PORTA, 2 ; 0V, ( a high ) for stop bits MOVLW $68 ; decimal 104 delay for 2 stop bits CALL MICRO4 RETURN
MAIN MOVLW 0 ; all port bit outputs TRIS TRISA ; on port A TRIS TRISB ; and port B CLRF PORTA ; RA2 is 0 ( RS232 high ) MOVLW $32 ; delay for 50 msec CALL NMSEC ; so no glitches interfere MOVLW 0 ; this is offset of message CALL OUTMSG ; output the message ENDLESS GOTO ENDLESS ; go into an endless loopSome comments about the code:
Various bits in the configuration word tell things like what type of oscillator is being used and whether the watchdog timer is enabled. Some programming software allows you to input this information as command line switches when you run the program. Others expect the information to already be in the .HEX file. In the case the PIC transmitter program, the oscillator type should be set to XT.
CLS OPEN "COM2:4800,N,8,2,CD0,CS0,DS0,OP0" FOR INPUT AS #1 DO PRINT INPUT$(1, #1); LOOPA program that prints out a single message over and over may be instructive but it isn't really very useful. There are many input devices that could be connected to the PIC. Serial devices are especially convenient. One such device is the Dallas Semiconductor DS1820 1 wire Digital Thermometer http://www.dalsemi.com/DocControl/PDFs/1820.pdf. It is available from Newark http://www.newark.com for about $6.
The bus is pulled high to +5V through a 4.7K resistor. Either the PIC or the 1820 can pull this line low. The PIC I/O pin is set to output and the port bit is set to 0 to pull the bus low. Changing the pin to input lets the resistor pull the line high. All timing originates with the PIC. Data bits are transferred in time slots initiated when the PIC pulls the bus down. If the bus is released,(I/O pin made input), and allowed to go high for the rest of the time slot, a 1 bit is sent. If the bus remains low it is a zero bit transfer, PIC to 1820. In either case, the bus has to be high at the end of the time slot and remain high for at least 1 microsecond before the next low. The slot must be at least 60 microseconds long. Bit timing is not critical as it is with RS232. Time slots can be any reasonable length over the minimum.
What about transfers the other way, 1820 to PIC? First, you have to send the 1820 a command to read it's scratchpad memory. The PIC must then generate read time slots to get the data bits out. The PIC pulls the bus low as before to initiate the time slot and quickly releases it. Data from the 1820 will be valid only for the first 15 microseconds. The read should take place toward the end of this interval. If the 1820 is pulling the bus low you receive a 0 bit, if not it is a 1 bit.
Any sequence of commands given to the 1820 by the PIC has to be preceded by a reset pulse and the address protocol, (64 bits). The reset pulse is a low from the PIC lasting about 600 microseconds followed by roughly a 400 microsecond period where the PIC has released the bus and can read a response 'presence pulse' from the 1820, (low), indicating everything is O.K. We will ignore this return pulse an assume everything is O.K.
Commands given the 1820 include:
An additional complication is negative numbers. The unit goes to -55 degrees C, but negative numbers are expressed in twos compliment form. You have to change these by taking the compliment, (flipping all the bits), and adding one to get the magnitude in normal form.
M1HI BSF STATUS, RP0 ; go to page 1 BSF TRISA, 3 ; make RA3 an input BCF STATUS, RP0 ; back to page 0 RETURN ; master 1 wire HI M1LO BCF PORTA, 3 ; port A, bit 3 low BSF STATUS, RP0 ; go to page 1 BCF TRISA, 3 ; make port A, bit 3 an output BCF STATUS, RP0 ; back to page 0 RETURN ; master 1 wire LO SLOTLO CALL M1LO ; begin by taking RA3 low GOTO DLY80 ; and remain low SLOTHI CALL M1LO ; a quick low on RA3 CALL M1HI ; return high right away DLY80 MOVLW $14 ; decimal 20 x 4 = 80 microsec CALL MICRO4 ; delay CALL M1HI ; always end on high RETURNThe routine for transmission of characters looks much like the routine used for RS232. In fact, we can use the same registers:
TXBYTE1 MOVWF TXREG ; put W into transmit register MOVLW 8 ; eight bits of data MOVWF BITS ; a counter for bits TX1LOOP RRF TXREG, f ; roll rightmost bit into carry BTFSC STATUS, C ; if carry 0 want to send 0 GOTO HIBIT1 ; else send 1 bit CALL SLOTLO ; output low bit ( RA3 ) GOTO DONE1 ; are we finished? HIBIT1 CALL SLOTHI ; output a high bit ( RA3 ) DONE1 DECFSZ BITS, f ; 1 less data bit, skip when zero GOTO TXBYTE1 ; more bits left RETURNThe receive routine includes the read time slot generated by the PIC:
RXBYTE1 MOVLW 8 ; eight bits of data MOVWF BITS ; a counter for bits RX1LOOP CALL M1LO ; a quick low pulse CALL M1HI ; and back high NOP ; waiting for about ... NOP ; 14 microseconds ... NOP ; to elapse ... NOP ; before reading RA3 BSF STATUS, C ; make carry a 1 BTFSS PORTA, 3 ; read RA3 skip if received a 1 BCF STATUS, C ; make carry a 0 RLF RXREG, f ; roll carry into RXREG LSB DECFSZ BITS, f ; 1 less data bit, skip when zero GOTO RXLOOP1 ; more bits left RETURNThe reset pulse is straightforward:
RESET CALL M1LO ; make I/O line low MOVLW $96 ; 150 x 4 = 600 microseconds CALL MICRO4 ; delay CALL M1HI ; and back high MOVLW $64 ; 100 x 4 = 400 microseconds CALL MICRO4 ; delay RETURNExcept for the binary to decimal conversion routine, we now have almost all the code necessary to write a program to use the DS1820 to read temperature and send it to the PC COM port. The files 'DEGREES.HEX' and 'DEGREES.F86' can be entered into PicBuild. A LED on RB1 is use to give a quick blink at each reading indicating the unit is working. A normally open push button is connected between RB2 and ground to set up the delay between readings.
If the time interval between points is not correct, you turn the unit off and hold the button down when you power up. Hold the button until the proper number of flashes has occurred for the desired interval. Release the button, turn the unit off and power up again to start collecting data.
Here is a QuickBasic program to store readings in a file. The file is a text file with one reading per line. You should be able to read the file directly into a spreadsheet and graph the data: The program 'GrafData' will give you a quick graph of the data on the screen.
' *** Take N readings, change to Deg F, and store in file *** CLS INPUT "How many readings? ", readings% INPUT "Filename for readings? ", filename$ OPEN "COM2:4800,N,8,2,CD0,CS0,DS0,OP0" FOR INPUT AS #1 IF filename$ <> "" THEN OPEN filename$ FOR OUTPUT AS #2 count% = 1 DO INPUT #1, temp! GOSUB ShowTemp LOOP UNTIL count% > readings% IF filename$ <> "" THEN CLOSE #2 END ShowTemp: temp! = 9 * temp! / 5 + 32 PRINT count%; " - "; PRINT USING "###.# Deg F"; temp! IF filename$ <> "" THEN PRINT #2, USING "###.#"; temp! count% = count% + 1 RETURNI've also included a Pascal program to take data, (NTEMP.EXE) if you don't want to go to the trouble of loading QBasic. Notice how much extra code is necessary just to set up the COM port. COM2 is used by the program but can be changed to COM1 by making the changes noted and recompiling.
Now we have a useful device, but not as useful as it could be. After all, you don't want to lug around a computer just to measure temperature. We could use the EEPROM data memory in the PIC to collect data and carry the unit to the PC and dump them, but data memory is pretty small. The answer lies in adding more memory to the PIC and serial memory is definately the way to go. Adding a 8 pin 24C65 serial EEPROM from Microchip gives us 8K bytes of additional memory. These cost about $3 from Digi-Key. Using serial memory we can construct a reasonable temperature data logger.
Information transfer occurs only when the clock is high:
START CALL LOW_SCL ; bring SCL low before a change to SDA CALL HIGH_SDA ; SDA will start high CALL HIGH_SCL ; now set up for information transfer CALL LOW_SDA ; start: SDA goes low while SCL high CALL LOW_SCL ; clock back to where SDA can change RETURNNotice that the clock is left in a condition where changes to SDA won't be seen as start or stop conditions. The code for a stop condition would be exactly the same except with the calls that change SDA reversed. The stop condition leaves SDA high and the bus released.
Once the slave sees the start condition it expects data bits to come from the PIC. The PIC transfers data by raising the clock for 5 microseconds or more and lowering it again. If SDA is high during this period a 1 is transferred if it is low, a 0. The PIC must also put out this clock pulse to receive data. It releases the bus (HIGH_SDA), brings SCL high and examines SDA. A high SDA means a 1 bit from memory, a low means a 0 bit. Clock lows should also last 5 microseconds or longer. This gives a maximum bit transfer rate of 100Kbits/s. Bit timing isn't important as long as it isn't too fast. The critical factor is what takes place on the SDA line when the SCL line is high .
The basic unit of transfer is the byte with an acknowledgement required after each byte is transferred. The PIC sends 8 bits, releases the bus and sends out a 9th clock pulse. If memory responds by pulling SDA low it means everything is O.K. The acknowledgement doesn't have to be acted upon but the clock pulse must be sent. We call this response from memory NACK. Likewise when the PIC is receiving bytes from memory, memory expects an acknowledgement pulse, (SDA high & clock pulse), back after each byte. We call this pulse from the PIC ACK.
Unlike the case with the DS1820, there is no way to get around sending out a slave address after each start condition. The device address is contained in the high 7 bits of a control byte. The lowest bit of the byte tells if the operation is to be a read, (1), of the slave or a write to the slave, (0).
Transmission and reception of bytes of data over the 2 wire I2C is done by the routines 'TXBYTE2' and 'RXBYTE2' in the program 'TLOGGER'. Notice that RLF is used to transfer bytes into carry because the most significant bit comes first in this case.
Writing a byte to a random address in memory requires sending a control byte and address before the actual byte to be saved. It goes like this:
Reading a byte from memory also requires sending a start condition, control byte and address bytes to memory. The first 7 steps would be the same as for a write. Then a new start condition is created and a control byte with LSB =1 is sent. Only then can the PIC receive a byte by sending out clock pulses and testing SDA while the clock is high. The PIC must then switch into output mode and send an acknowledge pulse indicating the byte has been received, (ACK). Fortunately a sequential read can follow by continuing to read another byte after each ACK. When you are finished you produce a NACK instead of ACK and then produce a stop condition.
We either have to transfer a fixed amount of data or somehow remember how much data was saved. The latter approach is taken by requiring that a button be pushed before the power is turned off. At the button press, the program jumps to the code at FINISH which saves the current memory address to data EEPROM locations $0E and $0F. These two locations provide the first two bytes sent to the PC COM port when the 24C65 memory is dumped.
After collecting data and before you turn the unit off, be sure to press and hold the button to save the stopping address. Hold the button down for at least a second or two. Put the jumper on to prevent loss of data if the unit is accidently turned on again.
While the jumper is on, connect to a PC COM2 port. Run the program 'READUMP' and turn the power on. Press the button to start data transfer. The datalogger doesn't convert the binary data to decimal like the 'DEGREES' program. This is done in the 'READUMP' program. Two other collected bytes are used to break the reading down to tenths of a degree. This is done more easily in the higher level language. It makes the graphs less 'blocky'. The file created by 'READUMP' can be loaded into a spreadsheet program or graphed with the program 'GRAFDATA'.
One thing I've thought of adding but haven't worked out yet is multiple collections of data. The idea would be to hold starting locations in data EEPROM and cycle through them, indicating which collection you want to dump. It would probably mean adding a line to transmit data to the PIC from the PC. A 22k resistor would be used to limit current from the RS232 TX line.
It would be nice to reduce the amount of current drawn by the unit to extend battery life. One way to do this would be to use SLEEP mode, but you would have to supply a low current external clock to do this because the PIC clock shuts down during SLEEP. A simpler way to save current is to put a switch in series with the LED to take it out of the circuit when not needed. The PIC itself seems to draw only a milliamp or so. It is also possible to run the circuit at 3 volts which reduces the current. A lower clock rate would help too but you might have to reduce the RS232 transmit baud rate.
A readout of temperature on the unit itself would be nice. The problem is that we only have 6 port bits left, (7 if you count RB1). It would probably mean quite a bit of extra hardware for miminimal advantage.
The conversion to tenths of a degree and decimal output as in 'DEGREES' could have been done in the PIC. I just thought it would be easier in a higher level language.
Written by Stan Ockers.