PDA

View Full Version : PIC as I2C Master/Slaves



Adamey
- 13th February 2011, 03:40
Hello, new member here and first post. :)

I got into PIC's by learning on PICAXE. One thing that makes PICAXE great for beginners is the built-in functions that let you develop programs quickly without worrying about all the code running underneath. The drawback is that there are limitations (like program size and speed). My needs have grown beyond what I can do with a PICAXE, so I picked up PBP and a programmer and am starting to learn more about PICs and how they work.

I have a project that I got working using four PICAXE chips, one acting as the master and three as slaves. All communicate using I2C. The built-in functions for I2C on the PICAXE I found extremely useful. The master can read and write data to the slaves at will, without affecting the operation of any program running on the slave PICAXE. In essence, the slave PICAXE behaves just like an I2C EEPROM. When data is written to the slave PICAXE, an interrupt is set so the slave knows it has data "waiting".

I used this in a project where the slaves are gathering data from various sources, performing math on the data and placing the results in scratchpad memory for the master to read (polled at specific intervals). I also have the master send "commands" to the slaves by placing data into their scratchpad memory. The slaves then go and perform tasks on their own while the master continues on with its own tasks (mainly user input and data display).


I did search the forums for I2C communication and PIC to PIC, but haven't yet seen anything similar to this. I'm wondering if anyone has done something like this, and what methods they used (I2C, HSERIN/OUT or something else). I have a feeling this might be a significant programming challenge. :)

ScaleRobotics
- 13th February 2011, 04:57
Hi, and welcome!

You might find this example useful: http://www.picbasic.co.uk/forum/content.php?r=224 I have not used I2C yet, so I can't give you much advice, but there are plenty of folks here that have.

Walter

Charles Linquis
- 13th February 2011, 18:12
This may help. Here is a fairly full-featured interrupt-driven I2C slave. The master part is easy.
This slave relies on the work of many others, but I have added some bug fixes and extra features. For example, you can write
0xAA,0X55,0x00,**your new address here**,0x00
and you can change the address of the slave.

Like my other stuff, it ONLY works on 18F chips.



ASM
ifndef __18F2321
error "18F2321 not selected"
endif
ENDASM

ASM
movlw 0x62 ; %0110 0010 = 4 Mhz, internal osc block
movwf OSCCON
movlw 0x80 ; %1000 0000 = PLL disabled
movwf OSCTUNE
ENDASM

DATA @0,$38 ; Default I2C address if not determined in HW

DEFINE OSC 4
Define USE_LFSR 1

;---------------- Alias the bits ------------------------------------

SSPIF VAR PIR1.3 ' SSP (I2C) interrupt flag
SSPIE VAR PIE1.3 ' Int Enable

SSPUA VAR SSPSTAT.1
SSPS VAR SSPSTAT.3
SSPP VAR SSPSTAT.4
SSPSMP VAR SSPSTAT.7

BF VAR SSPSTAT.0 ' SSP (I2C) Buffer Full
R_W VAR SSPSTAT.2 ' SSP (I2C) Read/Write
D_A VAR SSPSTAT.5 ' SSP (I2C) Data/Address
CKP VAR SSPCON1.4 ' SSP (I2C) SCK Release Control
SSPEN VAR SSPCON1.5 ' SSP (I2C) Enable
SSPOV VAR SSPCON1.6 ' SSP (I2C) Receive Overflow Indicator
WCOL VAR SSPCON1.7 ' SSP (I2C) Write Collision Detect



MasterClock VAR WORD BANKA SYSTEM


'------------------- Buffer defintion --------------------
RxBufferLEN con 10
RxBuffer var BYTE[Rxbufferlen + 1]
TxBufferLEN CON 10
TXBuffer VAR BYTE[TxBufferLEN + 1]

GeneralCall VAR BIT
RXBufferOverFlow VAR BIT
TxBufferUnderFlow VAR BIT
HWAddress VAR BIT
RXBufferPointer VAR Byte
TXBufferPointer VAR Byte
address VAR BYTE
x VAR BYTE
dummy var byte
I2CAddress VAR BYTE
WCOLCounter VAR BYTE
I2CDeviceAddress VAR BYTE
Holdoff VAR WORD ; retry timer

HWAddress = 0 ; Get address from EEPROM, not pins

TestArray VAR BYTE [200]

IF !HWAddress THEN
;----------- Get our Address from EEPROM --------------
READ 0,I2CAddress
;-----------------------------------------------------
ELSE
;----------------OR Get our address from PORT B0..2-----------
I2CDeviceAddress = PORTB & %00000111
I2CMajorAddress CON $3 ; upper 4 bits
I2CAddress = (I2CMajorAddress <<4) | I2CDeviceAddress <<1 ; bit 0 is R/W
;---------------------------------------------------------------
Endif

SSPADD = I2Caddress ' Move our address into the proper reg
SSPCON2.0 = 1 ; Allow clock stretching
SSPCON2.1 = 0 ; Set to 1 to enable masking of address 1 ; 0 is R/W bit
SSPCON2.2 = 0 ; Set to 1 to enable masking of address bit 2
SSPCON2.3 = 0 ; Set to 1 to enable masking of address bit 3
SSPCON2.4 = 0 ; Set to 1 to enable masking of address bit 4
SSPCON2.5 = 0 ; Set to 1 to enable masking of address bit 5
SSPCON2.6 = 0 ; Not used in slave
SSPCON2.7 = 1 ; General Call enabled
SSPSTAT = 0
SSPEN = 1
SSPIE = 1
SSPIF = 0
SSPCON1 = $36 ' Set to I2C slave with 7-bit address

;---------------------------------------------------------------
TRISA = %11111111
TRISB = %11111111
TRISC = %11111111

' -------------- Turn on Ints ------------------------
INTCON.7 = 1 ; global ints
INTCON.6 = 1 ; peripheral ints
IPR1.3 = 1 ; high priority

;------------------ Clear parameters --------------
RXBufferPointer = 0
TxBufferPointer = 0
GeneralCall = 0
RxBufferOverFlow = 0
TXBufferUnderFlow = 0
WCOLCounter = 0
;----------------Timer Setup-----------------------------------
T0CON = %10000011 ; /16 prescaler
;-------- -------------------------------------------
WDTCON = 1 ; Turn on the WDT
;--------------------------------------------------------------


INCLUDE "DT_INTS-18.bas" ' Thanks Darrel!
INCLUDE "ReEnterPBP-18.bas" '

ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler SSP_INT, _I2C_Int, PBP, yes
INT_Handler TMR0_INT, MainTimer, ASM, yes
endm
INT_CREATE ; Creates the interrupt processor
endasm

''----------------- Initialization Done! -----------------------------

Goto OverInt ; jump over ISR, don't execute it.
;----------------------- Timer INt ------------------------------------
ASM
MainTimer
movlw 0x00 ; 10 mSec
movwf TMR0H
movlw 0x63
movwf TMR0L

infsnz MasterClock
incf MasterClock + 1

; This is a good place to pick up pin changes

INT_ENABLE TMR0_INT
INT_RETURN

ENDASM
;---------------------------------------------------------------------
'------------------------- I2C subroutine --------------------------------
I2C_Int:
; We don't get here uless someone has matched our address register.

If R_W = 0 then 'This is a WRITE by the master to us
IF BF = 0 Then goto i2CEXIT ; Nothing for us!
if D_A = 0 then 'We have an address
address = SSPBUF 'Need to READ Address from buffer to clear it.

IF Address = 0 THEN
GeneralCall = 1 ; Set flag if we got a general call address
ELSE
GeneralCall = 0
ENDIF


else
IF WCOL OR SSPOV THEN I2CExit
IF RXBufferPointer < RxBufferLen - 1 THEN
RXBuffer [RxBufferPointer] = SSPBuf ;
RxBufferPointer = RXBufferPointer + 1
ELSE
Dummy = SSPBuf ; Grab the data but don't overwrite old
RXBufferOverFlow = 1
ENDIF

ENDIF


else ' R_W = 1 Master wants to read FROM us

IF !D_A Then
TxBufferPointer = 0 ' Mark as first read
RXBufferPointer = 0
EndIF

IF CKP THEN I2CExit

dummy = SSPBUF ;NECESSARY!

SENDI2C:
WCOL = 0
SSPBUF = TXBuffer[TXBufferPointer] ' Get data from array
IF WCOL THEN
WCOLCounter = WCOLCounter + 1 ; Don't hang the bus with infinite WRITES!
IF WColCounter >= 7 Then goto I2CExit ; 7
RANDOM Holdoff
Pauseus Holdoff >> 3 ; wait 0-8 msec
Goto SendI2C ; Retry
ELSE
WColCounter = 0
ENDIF
TXBufferPointer = TXBufferPointer + 1

If TXBufferPointer = TXBufferLen THEN
TXBufferUnderflow = 1
TXBufferPointer = TXBufferLen - 1 ; Repeat last if req made again, but set flag now
ENDIF
ENDIF

I2CEXIT:
SSPOV = 0
BF = 0
sspif = 0
CKP = 1
@ INT_ENABLE SSP_INT ; probably don't need this, but I like to be certain it gets restarted
@ INT_RETURN


' -------------------------end I2C subroutine --------------------------------
'--------------------[End ISRs]---------------------------------------
OverInt:

@ INT_ENABLE SSP_INT
@ INT_ENABLE TMR0_INT
'--------------------[ main program loop]---------------------------------------
Main:
IF RXBuffer[0] = $AA THEN
IF RXBuffer[1] = $55 and RXBuffer[2] = 0 And RxBuffer[4] = 0 THEN
Dummy = RXBuffer[3]
WRITE 0,Dummy ; Write a new address
Pause 10
@ RESET ; and pick up the new address
ENDIF
ENDIF
;------------------- Load the input buffer to the output buffer for test -----------
For X = 0 to TXBufferLen - 1
TxBuffer[X] = RXBuffer[X]
next x
;----------------------------------------------------------------------
goto main

end

ASM
ORG 0x0FF0 ; Keep track of pgm size in case we want to use it on a smaller chip
NOP
ENDASM

ScaleRobotics
- 13th February 2011, 18:45
Thanks Charles! When I saw you mention it earlier on the other thread, I was tempted to ask you for it. Very nice work.

Charles Linquis
- 14th February 2011, 03:38
It should be "plug and play" in virtually any 18F chip. I have a full ASM version working, but it hasn't been tested to the degree this code has.