Serial Port Powered PIC to monitor Office Air conditioning Plant with my desk top PC and log it with hyperterminal.

Here is a way of driving the PIC off your PC's serial port.

The TX line is normally low on a the Serial port, so use this to power the PIC16F84.

Now the serial line does not drive the Serial Port on the PC, so use a simple bootstrap to raise the level to drive the Serial Port's RX pin.

 

When the PC Serial Port TX is sending stops, Tx is negative. D2 and R1 charge C4 to provide the power for the chip. D2 prevents the power being sucked out when the Serial ports sends characters to the Chip.

I have used a number of chips powered with any thing from 2 AA cells to 5 volts. It amazes me that the serial port on the 2 two or 3 PCs that I have used have always managed to pick up the character being sent to it. However, not in this case. I need to level shift the pin output. When the PIC is outputing stops, D1 charges C1, Whiles bits are being sent, then C1 level shifts the serial output enough for the PC.

 

Achieving 1200 baud with a 32K clock

The code to drive this is found in code.zip

I have clocked the PIC at 32kHz, as that normally provides enough cycles for what I wanted.

Its useful to have a serial output for debugging and output. When clocked at 32Khz, the PIC is running 8192 instructions per second, so 6.8266 instructions per bit. This is now an intellectual challenge akin to those simple wooden puzzles, relatively pointless, but enjoyable.

Serial output

Its useful to have a serial output for debugging and output. When clocked at 32Khz, the PIC only has 6.83 instructions per bit.

You need 7 instructions to do bit tests and then to get the timing right you need quite a lot of code. It works for inline code, but looped code needs too many instructions.

This routine uses XOR. Test to see if the bit needs testing, This is done by xoring the current output with the new bit. This leaves W with 1 or 0. Now xor this with the output pin, and it toggles if required.

	movfw	SRtxTemp
	xorwf	PORTA,W
	andlw	0x1
	xorwf	PORTA,f	;t+6,19
	RRF	SRtxTemp,f
	movfw	SRtxTemp
	xorwf	PORTA,W
	andlw	0x1
	xorwf	PORTA,f	;t+6,19
	RRF	SRtxTemp,f

If inline this routine uses 5 cycles per bit, It also uses a very small amount of code.

;SRtxChar
SRtxCharLp
	cblock 	
SRtxTemp
SRtxCnt

	endc

	BTFSC   SRinEvents,0	; If serial input, then crash through output
	RETURN

	;
	;Output Start Bit
	;

	movwf	SRtxTemp
	comf	SRtxTemp,f
	BCF	PORTA,0
	MOVLW	~( 1 << 0 )
;	TRIS	PORTA
	BSF	STATUS, RP0
	MOVWF	TRISA		; Set output high to start Measurement
	BCF	STATUS, RP0

	; 1200 baud output
	; move the char into SRtxtemp, it is destroyed.
	;Start Bit
	BSF	PORTA,0
	movlw	4
	movwf	SRtxCnt
	GOTO 	$+1
SRtxCharLp1
	; This outputs two bits at a time 
	; this takes 6.5 cycles each
	
	; After start bit, which is 1, then test each bit
	; I need to set PA:0 to same as LSB SRtxTemp
	; this compares PA:0 with SRtxTemp so see if it needs toggling
	; The xorwf PORTA,f causes the bit to be toggled if required.
	movfw	SRtxTemp
	xorwf	PORTA,W
	andlw	0x1
	xorwf	PORTA,f		;t+6,19
	RRF	SRtxTemp,f

	movfw	SRtxTemp
	xorwf	PORTA,W
	andlw	0x1
	RRF	SRtxTemp,f
	xorwf	PORTA,f		;t+11,24


	decfsz	SRtxCnt
	goto	SRtxCharLp1
	GOTO	$+1	
	GOTO	$+1	
	BCF	PORTA,0
	GOTO	$+1	
	GOTO	$+1	
	RETURN

SRwrtHexNibble	; currently only displays 0..9, A..F
	ANDLW	0x0F
	ADDLW	0x06	; is it A..F, if so trigger a digit overflow
	SKPNDC
	ADDLW	7; subtract 10, then add 'A'-'0'
	ADDLW	0x30-6	; Subtract extra 6 added to cause DC
	MOVWF	SRout
	GOTO	SRtxChar


The timings for these routines can be compared to the required.

Serial Input

With only 6.83 instructions per bit, this is tight. Use the RB0 interrupts to crash into the routine.

Leave SRin set, and SRinEvents bit 0 is set to indicate that a character has been received.

If you are outputing a character, you cannont let is finish. From the raising edge of the Start bit to the sampling of the first bit, you have 1.5 bits or 10.24 instructions.

;--------------------------------------------------------------------------
; Sec 3. Program Code
;--------------------------------------------------------------------------

                ORG     0       
                GOTO    Start

		ORG 	4
		GOTO 	Intrtn

Start
		MOVLW	8
		MOVWF	SRcnt
		; Change Prescaler
		CLRWDT
		; Configure Tmr 0
		BSF	STATUS,RP0
		; Set up INT edge and prescaller for 8192 / 16 = 512 ticks perseconds.
		MOVLW	1 << INTEDG | 0x0 + 2 	; For Tmr0 0=/2, 1=/4, 2=/8,3=/16
		MOVWF	OPTION_REG
		BCF	STATUS,RP0
		; Change Prescaler
		CLRWDT
		; Configure Tmr 0
		BSF	STATUS,RP0
		; Set up prescaller for 8192 / 16 = 512 ticks perseconds.
		MOVLW	1 << INTEDG | 0x0 + 2 	; For Tmr0 0=/2, 1=/4, 2=/8,3=/16
		MOVWF	OPTION_REG
		BCF	STATUS,RP0
maincode
		; your code
		GOTO maincode		
;--------------------------------------------------------------------------
; Sec 5.0	Interrupt Routines
;--------------------------------------------------------------------------

Intrtn	

		movwf   w_temp            ; save off current W register contents
		movf	STATUS,w          ; move status register into W register
		movwf	status_temp       ; save off contents of STATUS register
		; Which Interupt ?
		; No Context Save in these routines.

		; RS232 1200 baud serial code 
		nop
		
TurboSRinA
		; for the 1200 baud serial input, each bit takes 6.83 cycles(8192/1200)
		; We need to sample 1.5 bits (  10.245 cycles in )
		;RS232 input pin must be RB0 ( or RB7 with mods )

TurboSRinA1
		RRF	PORTB,W	; shift LSB into Carry
		RRF	SRin,f
		goto	$+1	; wants 2 cycle delay
		DECFSZ	SRcnt,f
		GOTO	TurboSRinA1
		; now set up the counter for next time
		MOVLW	8
		MOVWF	SRcnt

		; The SRin needs to be inverted
		COMF	SRin,f

IntrtnEnding
		BSF	SRinEvents,0	; Set this bit to indicate a serial byte.

		movf    status_temp,w     ; retrieve copy of STATUS register
		movwf	STATUS            ; restore pre-isr STATUS register contents
		swapf   w_temp,f
		swapf   w_temp,w          ; restore pre-isr W register contents

		BCF 	INTCON,INTF
		retfie                    ; return from interrupt
;		return


		BSF	SRinEvents,0	; Set this bit to indicate a serial byte.

CodeEnd		goto	CodeEnd


The timings for these routines can be compared to the required.

 

Using AWK and DeltaCad to plot timings

With only 6.83 instructions per bit, this is tight. These routines need testing. The timing diagrams were produces using AWK and DeltaCad.

The full code is in the diectory Serial Test in code.zip.

The following AWK script extracts the information from the MPLAB trace saved tracein.txt.

####################################################
#
#	T.awk - A simple awk script to plot test timing
#
####################################################

BEGIN {

	FS	= ","
	lastX 	= 0
	lastY 	= 0
	scale = 0.5

	of[ "00" ] = 3
	of[ "01" ] = 5


	# Start the DXF file
	F_header()
}

/\|/{

	split($0,lr,"|")
	split(lr[2],reg,":")
	ts= substr(lr[1],length(lr[1])-20)
	split(ts,tss," " )
	if ( tss[1] != ";" ) {
		T = tss[2]

 		# print tss[2] "," reg[1]  "," reg[2] > "reg.txt"
		if ( reg[1] =="05" ) {
		  v = substr( reg[2],1,2 )
		 
		  V = of[ v ]

		  #plot line
		  line( lastT * scale  ,lastV,T * scale, lastV , 1 )
		  line( T * scale,3,T * scale, 5 , 1 )
	  	  text( T * scale,6, 90, T )

		  lastT = T
		  lastV = V
		  print T "," V > "reg.txt"
		}	
	}
}

/RRF     PORTB,W/ {
	split($0,lr,"|")
	split(lr[2],reg,":")
	ts= substr(lr[1],length(lr[1])-20)
	split(ts,tss," " )
	if ( tss[1] != ";" ) {
 		# print tss[2] "," reg[1]  "," reg[2] > "reg.txt"
		#if ( reg[1] =="05" ) {
		  v = substr( reg[2],1,2 )
		 
		  T = tss[2]
		  V = of[ v ]
		  lastV  = 3
		  #plot line
		  #line( lastT * scale  ,lastV,T * scale, lastV , 2 )
		  line( T * scale,2,T * scale, 6 , 1 )
	  	  text( T * scale,6, 90, T )

		  lastT = T
		  lastV = V
		  print T "," V > "reg.txt"
		#}	
	
	}
}

/BSF     PORTA,0/ {
	# found start of start bit 
	dt = 0

	line( (T+dt) * scale,1,(T+dt) * scale, 7 , 0 )
	text( (T+dt+1) * scale, 4 , 0, "start" )

	dt = dt+1.5*8192/1200

	for ( cnt = 0 ; cnt < 8 ; cnt ++ ) {
		line( (T+dt) * scale, 2,(T+dt) * scale, 6 , 4 )
		text( (T+dt) * scale, 6 , 90, " " T+dt )
		text( (T+dt+0.25 ) * scale, 4 ,  0, cnt )

		dt = dt+8192/1200
	}
	line( (T) * scale,2,(T+dt) * scale, 2 , 4 )
	line( (T) * scale,6,(T+dt) * scale, 6 , 4 )

}

/call    Intrtn;/ {

	line( (T-4)    * scale,3,(T-4+0.25) 	* scale, 5 , 4 )
	line( (T-3.25) * scale,3,(T-3.25+0.25) 	* scale, 5 , 4 )
	line( (T-3.5)  * scale,3,(T-3.5+0.25) 	* scale, 5 , 4 )
	line( (T-3.75) * scale,3,(T-3.75+0.25) 	* scale, 5 , 4 )


	text( (T-4) * scale, 1 , 0, " INT pin 0->1" )
	#text( (T-3) * scale, 3 , 0, " START" )

	# found start of start bit 
	dt = -3
	line( (T+dt)      * scale,3,(T+dt+0.25) * scale, 5 , 4 )
	line( (T+dt+0.25) * scale,3,(T+dt) * scale, 5 , 4 )
	text( (T+dt+1)    * scale,4, 0, "start" )

	dt = dt+1*8192/1200

	for ( cnt = 0 ; cnt < 8 ; cnt ++ ) {
		line( (T+dt) * scale,3,(T+dt+0.25) * scale, 5 , 4 )
		line( (T+dt+0.25) * scale,3,(T+dt) * scale, 5 , 4 )
		text( (T+dt) * scale, 6 , 90, " " T+dt )
		text( (T+dt+1) * scale, 4 ,  0, " bit " cnt )

		dt = dt+8192/1200
	}

	line( (T-4+0.25) * scale,3,(T+dt) * scale, 3 , 4 )
	line( (T-4) * scale,5,(T+dt) * scale, 5 , 4 )

}

/BCF     PORTA,0/ {
	# found start of start bit 
	dt = 0
	line( (T+dt) * scale,2,(T+dt) * scale, 6 , 5 )
	text( (T+dt) * scale,6, 90, T+dt )
	text( (T+dt) * scale, 1 , 0, "stop" )

}

END {
	# And finally tidy up the DXF file
	F_end()

}

 

If you use these functions and find them useful, please email me or sign my guestbook. This is postcard ware.

Doug Rice,

Copyright, 22nd December 2001