It's from some development code I wrote a while back so it's a little rough around the edges, but it can be used to
test basic functionality. It doesn't do much of anything... just collect up bytes sent.
The code should be used with an I2C master that supports clock stretching, so it may not work well with some bit-banged masters.
Also, I've had mixed results porting it to other chips. It seems that there are a number of different hardware MSSP module implementations
and they each have their own quirks and errata.
i2cslave.bas:
Code: Select all
//
//------------------------------------------------------------------------------
// Name : i2cslave.bas
// Author : Jerry Messina
// Date : 5/1/2012
// Version : 1.0.0
// Notes :
//
// MSSP I2C slave for the 18F26K22
//
// ver 1.0.0
// - initial
//------------------------------------------------------------------------------
//
module i2cslave
// i2c_handler state tracking (for debugging)
#option ENA_I2C_STATE_TRACKING = false
// select MSSP1 or MSSP2
#option _I2C_SLAVE_MSSP = 1
// i2c slave address and mask bits
// note: in address mask mode, addr 0 is reserved (no ACK) unless GCEN is set
// in 7-bit address masking mode (the default configuration with MSSPMSK = 1)
// clearing a bit in the SSPMSK causes the address bit to be masked, while setting
// SSPMSK bit requires a match in that position.
#option I2C_SA = $80 // slave address
#option I2C_SAM = $80 // slave addr mask
const
I2C_SLAVE_ADDR = I2C_SA,
I2C_SLAVE_ADDR_MASK = I2C_SAM
'I2C_SLAVE_ADDR = %10100000, // $A0
'I2C_SLAVE_ADDR_MASK = %11110010 // $A0, $A4, $A8, $AC
'I2C_SLAVE_ADDR = %10100000, // $00
'I2C_SLAVE_ADDR_MASK = %00000000 // $A0-AF
//
//----------------------------------------------------------------
// MSSP definitions
//----------------------------------------------------------------
//
// device-specific register check
#if not(_device in (18F25K22, 18F26K22))
#warning "check device MSSP register assignments"
#endif
#if (_I2C_SLAVE_MSSP = 1)
// MSSP1 pins
dim
SCL as PORTC.3, // MSSP1 I2C scl
SDA as PORTC.4 // MSSP1 I2C sda
// MSSP1 control registers
public dim
SSPCON1 as SSP1CON1,
SSPCON2 as SSP1CON2,
SSPSTAT as SSP1STAT,
SSPADD as SSP1ADD, // addr and mask reg share same reg addr
SSPMSK as SSP1MSK,
SSPBUF as SSP1BUF
// MSSP1 interrupt flag bits
public dim
SSPIF as PIR1.bits(3),
SSPIE as PIE1.bits(3),
SSPIP as IPR1.bits(3),
BCLIF as PIR2.bits(3),
BCLIE as PIE2.bits(3),
BCLIP as IPR2.bits(3)
#elseif (_I2C_SLAVE_MSSP = 2)
// MSSP2 pins
dim
SCL as PORTB.1, // MSSP2 I2C scl
SDA as PORTB.2 // MSSP2 I2C sda
// MSSP2 control registers
public dim
SSPCON1 as SSP2CON1,
SSPCON2 as SSP2CON2,
SSPSTAT as SSP2STAT,
SSPADD as SSP2ADD, // addr and mask reg share same reg addr
SSPMSK as SSP2MSK,
SSPBUF as SSP2BUF
// MSSP2 interrupt flag bits
public dim
SSPIF as PIR3.bits(7),
SSPIE as PIE3.bits(7),
SSPIP as IPR3.bits(7),
BCLIF as PIR3.bits(6),
BCLIE as PIE3.bits(6),
BCLIP as IPR3.bits(6)
#else
#error "invalid _I2C_SLAVE_MSSP selection"
#endif
// MSSPx register bits
// SSPxCON1 bits
const
SSPM0 = 0,
SSPM1 = 1,
SSPM2 = 2,
SSPM3 = 3,
CKP = 4,
SSPEN = 5,
SSPOV = 6,
WCOL = 7
// SSPxCON2 bits
const
SEN = 0,
RSEN = 1,
PEN = 2,
RCEN = 3,
ACKEN = 4,
ACKDT = 5,
ACKSTAT = 6,
GCEN = 7
// SSPxSTAT bits
const
BF = 0,
UA = 1,
R_W = 2,
S = 3,
P = 4,
D_A = 5,
CKE = 6,
SMP = 7
// the RD/WR# bit (lsb of the address byte)
const RD_WRN = 0 // bit(0)
//
//----------------------------------------------------------------
// I2C state definitions (from microchip AN734)
//----------------------------------------------------------------
//
// State 1: MWR_ADDR - I2C write operation, last byte was an address byte
// SSPSTAT bits: S = 1, D_A = 0, R_W = 0, BF = 1
//
// State 2: MWR_DATA - I2C write operation, last byte was a data byte
// SSPSTAT bits: S = 1, D_A = 1, R_W = 0, BF = 1
//
// State 3: MRD_ADDR - I2C read operation, last byte was an address byte
// SSPSTAT bits: S = 1, D_A = 0, R_W = 1, BF = 1 (see note 3)
//
// State 4: MRD_DATA - I2C read operation, last byte was a data byte
// SSPSTAT bits: S = 1, D_A = 1, R_W = 1, BF = 0, CKP = 0
//
// State 5: M_NACK - Slave I2C logic reset by NACK from master
// SSPSTAT bits: S = 1, D_A = 1, (R/W = 1), BF = 0, CKP = 1 (see note 5)
//
// note 3: In PIC16 and older PIC18 devices, the BF flag is not set.
// In newer PIC18 devices, the BF flag is set and needs to be read and cleared
// for State 3
//
// note 5: In PIC16 and older PIC18 devices, the R/W flag is expected
// to be cleared. In newer PIC18 devices, R/W remains set. Instead of testing
// this bit, the state machine tests the CKP bit, expecting it to be set
//
// note: AN734 assumes that these states are qualified with S=1 (START bit), but the NACK state
// can be an issue. It is not necessarily possible to catch the NAK state in an interrupt handler.
// SCL will not be held low because ACK=1, and the stop bit can occur just a few microseconds after
// the falling edge of bit 9's clock (which is what sets SSPIF), causing P to be set before you get
// a chance to see SSPIF. This condition can be seen by testing for SSPCON1.CKP==1 and ignoring S and P
// In general, probably don't really care about this state... just look for the STOP.
//
// addtl note: if using General Call (GC), then the state of R_W is not modified, and so it
// reflects whatever the PREVIOUS packet was! For whatever reason, this is "by design", perhaps
// since GC must always be a write. This means that the state machine would have to deal with
// this as a special case since R_W becomes a don't care.
//
// SSPSTAT I2C states mask... START, D/A, R/W, BF
public const I2C_STATE_MASK as byte = ( (1<<S) + (1<<D_A) + (1<<R_W) + (1<<BF) )
public const
MWR_ADDR as byte = (1<<S) + (1<<BF), // Master Write Address D/A=0, R/W=0, BF=1, (S=1)
MWR_DATA as byte = (1<<S) + (1<<D_A) + (1<<BF), // Master Write Data D/A=1, R/W=0, BF=1, (S=1)
MRD_ADDR as byte = (1<<S) + (1<<R_W) + (1<<BF), // Master Read Address D/A=0, R/W=1, BF=1, (S=1)
MRD_DATA as byte = (1<<S) + (1<<D_A) + (1<<R_W), // Master Read Data D/A=1, R/W=1, BF=0, (S=1)
M_NACK as byte = (1<<D_A) + (1<<R_W) // Master NACK last data byte (SSPCON1.CKP = 1)
//
// variables
//
public dim
ssp_stat as byte,
addr as byte,
rxc as byte,
rxb(64) as byte,
rxix as byte,
txc as byte,
txix as byte
#if (ENA_I2C_STATE_TRACKING)
public dim
stbuf(64) as byte,
stix as byte
#endif
// pic18 nop
inline sub nop()
asm
NOP
end asm
end sub
//
//------------------------------------------------------------------------------
// slave write byte
//------------------------------------------------------------------------------
//
public sub write_byte(b as byte)
// send the data, making sure a write collision did not occur
// if clock stretching, the clock will be released by the i2c_handler
// code (CKP = 1)
repeat
SSPCON1.bits(WCOL) = 0
SSPBUF = b
until (SSPCON1.bits(WCOL) = 0)
end sub
//
//------------------------------------------------------------------------------
// slave read byte
//------------------------------------------------------------------------------
//
public function read_byte() as byte
// check that overflow has not occured. since we're clock-stretching, this should
// never happen but if it does we must clear the bit so reception can continue
if (SSPCON1.bits(SSPOV) = 1) then
SSPCON1.bits(SSPOV) = 0
endif
result = SSPBUF
end function
//
//------------------------------------------------------------------------------
// mssp I2C state handler
// example shell handler (over-ride with your own)
//------------------------------------------------------------------------------
//
public sub handler()
dim state as byte
if (SSPIF = 1) then
SSPIF = 0 // clear intr request
ssp_stat = SSPSTAT // read status reg
#if (ENA_I2C_STATE_TRACKING)
stbuf(stix) = ssp_stat // record state transitions
inc(stix)
#endif
if (ssp_stat.bits(S) = 1) then // most slave states require START to be set
state = ssp_stat and I2C_STATE_MASK
select (state)
case MWR_ADDR // I2C write operation, last byte was an address byte
addr = read_byte() // get the addr byte (clears BF)
rxix = 0 // reset data count
case MWR_DATA // I2C write operation, last byte was a data byte
rxc = read_byte() // get the data byte (clears BF)
rxb(rxix) = rxc
rxix = rxix + 1
case MRD_ADDR // I2C read operation, last byte was an address byte
addr = read_byte() // get the addr byte (clears BF)
write_byte(addr) // send back the matched addr as the first byte
txix = 0
case MRD_DATA // I2C read operation, last byte was a data byte
if (txix >= rxix) then
txc = 0
else
txc = rxb(txix)
txix = txix + 1
endif
write_byte(txc)
case M_NACK // Slave I2C logic reset by NACK from master
nop() // not currently implemented
else // unknown state
nop()
end select
endif
if (ssp_stat.bits(P) = 1) then // STOP bit...
addr = 0 // slave idle
endif
SSPCON1.bits(CKP) = 1 // release SCL clock stretching
endif
end sub
//
//------------------------------------------------------------------------------
// set address mask register
//------------------------------------------------------------------------------
//
public sub set_addrmask(mask as byte)
// in 7-bit address masking mode the SSPMSK register is not directly addressable,
// and must be accessed via the SSPCON1/SSPADD registers
SSPCON1 = %1001 // select SSPMSK access mode
SSPMSK = mask // write the addr mask value
end sub
//
//------------------------------------------------------------------------------
// module initialization
//------------------------------------------------------------------------------
//
public sub init_buf()
clear(rxb)
rxix = 0
end sub
public sub init()
// make sure pins are inputs
input(SCL)
input(SDA)
// use the default 7-bit address masking mode (config MSSPMSK=1)
set_addrmask(I2C_SLAVE_ADDR_MASK)
'SSPCON1 = $0E // I2C slave mode: 7-bit address with start and stop
SSPCON1 = $06 // I2C slave mode: 7-bit address
SSPCON2 = $00
SSPCON2.bits(SEN) = 1 // enable clock stretching. disable general call
SSPADD = I2C_SLAVE_ADDR // 7 bits I2C slave
SSPSTAT.bits(7) = 1 // Slew Rate Control: disabled for standard speed mode (100kHz and 1MHz)
SSPSTAT.bits(6) = 1 // SMBus Select bit: Enable input logic thresholds to SMbus specification
SSPCON1.bits(CKP) = 1 // release clock
SSPCON1.bits(SSPEN) = 1 // enable MSSP
init_buf()
#if (ENA_I2C_STATE_TRACKING)
clear(stbuf)
stix = 0
#endif
// mssp polled mode
BCLIE = 0
SSPIE = 0
BCLIF = 0
SSPIF = 0
end sub
end module
Code: Select all
device = 18F26K22
include "i2cslave.bas"
i2cslave.init()
while (true)
i2cslave.handler()
end while