[Return to Home Page]

ECE 445 Wiki : Topics : WiredPICToPICCommunication

[Login]


Wired PIC to PIC communications

Introduction

When using multiple microcontrollers it can be useful to allow them to transfer information to each other. Given here are details on how to implement a network in which each PIC can send messages to every other PIC over a single wire bus - which means that only one can "talk" at a time (or else the messages get mixed together). If you can get past this limitation the communication is quick (fast enough to handle sound localization, which was our project in Spring 2006, approximately 2 kb/sec). Not only this, but it only requires 1 pin on each microcontroller, as described below.

NOTE: The protocol used here can be extended to wireless communications, but additional investigation must be done to determine how the transcievers function with your PIC.

Basic Requirements and Idea

First you must decide whether or not (and how) to tell your various microcontrollers apart. Assigning an ID (either by hardcoding or by using pins connected to switches) will allow you to send messages from one specific PIC to another specific PIC. Those messages must be sent one at a time, of course. In our example we will assume that switches were used to tell the microcontrollers apart. As far as transmission of information goes, you must set aside 1 pin per microcontroller for this purpose. Since all communication will be done on this pin, you must include a device ID in the message to indicate a target PIC. Finally, you can either use the internal oscillator provided by your PIC or provide one of your own - but you MUST make sure all PICs in your system are using the same oscillator frequency.

The basic idea of the communication scheme implemented here is that when we want to transmit information, we send a long "No Data" pulse so that the other PICs know to start listening. Then we make a '0' last for a certain amount of time, say T1, and a '1' last for T2. This means that the VALUE of our signal (the actual line voltage, 0V or 5V) isn't important at any particular time. It's the distance between consecutive line voltage transitions (positive to negative edge or vice versa) that's important:

The receiving PICs interpret the incoming signal in the following way: If the comm pin goes high then wait for it to come down again (this means that someone is trying to transmit data on the communications line and the "nodata" sequence has ended). Then the reciever waits to see how long it is before the pulse goes high again - this is measured via a counter (whose operation depends on the oscillator). If the amount of time our pulse took to change from 0 to 1 was over a certain threshold, we count it as a 1. If it was below, it is a zero. And if the pulse never went up to 1 then it is a communications error (time out). The sequence continues until the last data bit is read (the reciever knows how many bits it has to recieve).

In order to simplify the communication scheme, it was decided that each packet would have exactly the following components:
1) long start pulse (at the active line voltage, 5V)
2) N data bits (final version, 32 bits of data)
3) end pulse (inactive line voltage level, 0V)

The interaction between sender and receiver occurs in the following way:

sender receiver
positive edge of start pulse
call RECEIVE function, wait for end of start pulse
neg edge, end of start pulse
begin measuring time to next edge
wait amount of time corresponding to the data bit, then change line voltage (timing...)
wait amount of time corresponding to next data bit, then change line voltage decide, based on measurement, on a '0' or '1'. begin timing measurement for next bit
... ...
wait amount of time corresponding to last data bit, then change line voltage (ideally ending on 0V, inactive line level) decide, based on measurement, on a '0' or '1'. begin timing measurement for next bit
wait out the length of the end pulse (should be considerably longer than data pulse sizes) and return from function. decide, based on measurement, on a '0' or '1'. exit the subroutine returning SUCCESS code.

You might ask yourself, "why not just have a high voltage indicate a 1 and a low voltage indicate a 0?" The answer to this is that using such a scheme allows for very little tolerance in variations of oscillator frequency. We indicated above that the oscillators need to be consistent. Suppose we use a scheme as shown below:

In order for the message to be recieved properly, we must make sure that we poll the data at EXACTLY the same spot for each bit that we expect to recieve. Any discrepancies in oscillator frequency will lead to errors that propagate. Using pulse WIDTH rather than value to convey information allows us just to wait for edges - errors do not propogate. Going through the code given below should clarify this.

So how do we figure out the data rate? If you need your data rate to be at a certain level, the best way to determine the average rate is to send a sequence that counts upwards. Suppose you count from 0 to 1023. Make one of the output pins on your recieving PIC the LSB of the number. Measure its frequency with a multimeter. Since each time the bit goes from 1 to 0 to 1 represents 2 transmissions, but 1 period, you need to multiply this number by 2 to get your average data rate. It should be noted that transmitting 11111111 takes longer than transmitting 00000000 because our delay is longer for a 1.

Code Example

The following assembly code implements the scheme detailed above. While the code was written for a particular microprocessor, instruction sets should not vary too much from PIC to PIC. Only the send and recieve subroutines are provided. Both make use of a subroutine called delay, which is shown below as well.

; ; Written by Andrew J. Bean for Senior Design Spring 2006 using Microchip PIC 16F684

; Subroutine to delay

; input: Load W with a value

; : delay is 10*W cycles

Delay

	GOTO	$+1
	NOP
	MOVWF	Counter1
	DECFSZ	Counter1,F
	GOTO	Delay_Loop
	RETURN

Delay_Loop

	GOTO	$+1
	GOTO	$+1
	GOTO	$+1
	NOP
	DECFSZ	Counter1,F
	GOTO	Delay_Loop
	RETURN

; Subroutine to send 2 bytes

; Data0 is placed in SendByte0 register

; typically will include destination code

; Data1 is placed in SendByte1 register

; 350 cycle start pulse

; 200 cycle zeros

; 250 cycle ones

ZeroDelay equ 10

OneDelay equ 25

Send

	BSF		STATUS,RP0		; bank 1
	BCF		TRISA,1			; make output
	BCF		STATUS,RP0		; bank 0

	BSF		PORTA,1			; set output
	MOVLW	35				; long (non-data) delay
	CALL	Delay

	BCF		PORTA,1			; clear output
	MOVLW	ZeroDelay				; delay for a ZERO
	BTFSC	SendByte0,0		; if ONE, increase delay
	ADDLW	OneDelay-ZeroDelay
	CALL	Delay

	BSF		PORTA,1			; set output
	MOVLW	ZeroDelay				; delay for a ZERO
	BTFSC	SendByte0,1		; if ONE, increase delay
	ADDLW	OneDelay-ZeroDelay
	CALL	Delay

(((ETC ETC ETC.....))

	BCF		PORTA,1			; clear output
	MOVLW	ZeroDelay				; delay for a ZERO
	BTFSC	SendByte1,6		; if ONE, increase delay
	ADDLW	OneDelay-ZeroDelay
	CALL	Delay

	BSF		PORTA,1			; set output
	MOVLW	ZeroDelay				; delay for a ZERO
	BTFSC	SendByte1,7		; if ONE, increase delay
	ADDLW	OneDelay-ZeroDelay
	CALL	Delay

	BCF		PORTA,1			; clear output
	MOVLW	35				; long (non-data) delay
	CALL	Delay

	BSF		STATUS,RP0		; bank 1
	BSF		TRISA,1			; make input
	BCF		STATUS,RP0		; bank 0

	RETURN

; Subroutine to receive 2 bytes

; low byte is placed in GetByte0 register

; typically will include destination code

; high byte is placed in GetByte1 register

; assumes call is during start pulse

; 200 cycle zeros

; 250 cycle ones

TimeBound EQU 33

Receive

	CLRF	GetByte0
	CLRF	GetByte1

	; wait for first neg edge
	CLRF	Counter1

Receive.wait

	btfss	PORTA,1			; test the port input value
	goto	Receive.wait.done	; if clear, get first bit
	incfsz	Counter1,F		; increment TO counter
	goto	Receive.wait	; still wait
	retlw	0				; timed out, indicate no data

Receive.wait.done

	CLRF	Counter1		; about 5 cycles per counter increment

Receive.00 ; less than TimeBound=>0, more=>1

	btfsc	PORTA,1
	goto	Receive.00.decide
	incfsz	Counter1,F
	goto	Receive.00
	retlw	0				; timed out, indicate no data

Receive.00.decide

	bcf		GetByte0,0
	movf	Counter1,W
	sublw	TimeBound		; calculate TimeBound-W
	btfss	STATUS,C		; 1 means TimeBound-W>0 (ie 0)
	bsf		GetByte0,0		; 0 means W>TimeBound (ie 1)

	CLRF	Counter1		; about 5 cycles per counter increment

Receive.01 ; less than TimeBound=>0, more=>1

	btfss	PORTA,1
	goto	Receive.01.decide
	incfsz	Counter1,F
	goto	Receive.01
	retlw	0				; timed out, indicate no data

Receive.01.decide

	bcf		GetByte0,1
	movf	Counter1,W
	sublw	TimeBound		; calculate TimeBound-W
	btfss	STATUS,C		; 1 means TimeBound-W>0 (ie 0)
	bsf		GetByte0,1		; 0 means W>TimeBound (ie 1)

	CLRF	Counter1		; about 5 cycles per counter increment

((ETC ETC ETC ..... ETC ETC))

Receive.16 ; less than TimeBound=>0, more=>1

	btfsc	PORTA,1
	goto	Receive.16.decide
	incfsz	Counter1,F
	goto	Receive.16
	retlw	0				; timed out, indicate no data

Receive.16.decide

	bcf		GetByte1,6
	movf	Counter1,W
	sublw	TimeBound		; calculate TimeBound-W
	btfss	STATUS,C		; 1 means TimeBound-W>0 (ie 0)
	bsf		GetByte1,6		; 0 means W>TimeBound (ie 1)

	CLRF	Counter1		; about 5 cycles per counter increment

Receive.17 ; less than TimeBound=>0, more=>1

	btfss	PORTA,1
	goto	Receive.17.decide
	incfsz	Counter1,F
	goto	Receive.17
	retlw	0				; timed out, indicate no data

Receive.17.decide

	bcf		GetByte1,7
	movf	Counter1,W
	sublw	TimeBound		; calculate TimeBound-W
	btfss	STATUS,C		; 1 means TimeBound-W>0 (ie 0)
	bsf		GetByte1,7		; 0 means W>TimeBound (ie 1)

	RETLW	1

	END

PmWiki can't process your request

Cannot acquire lockfile

We are sorry for any inconvenience.