Hi,
I got the i2c slave working, based on the explanations from the application
note AN734.
My code emulates a 128 bytes RAM.
You can read and write registers.
To write, use:
START ADDR+W REG_ADDRESS_BASE DATA DATA DATA... STOP
ADDR+W is the slave address with the LSB cleared (write)
REG_ADDR_BASE is the address of the first register you want to write
DATA are the bytes you want to write from this address.
Read is a bit longer. You have to start a write transaction with no data
(just the reg addr), then restart and go to read mode.
START ADDR+W REG_ADDRESS_BASE STOP
START ADDR+R <data+ACK>....<data+NACK> STOP
=================
ALL THE CODE THAT FOLLOWS IS PUBLIC DOMAIN.
=================
I defined the following useful constants
// DA S RW BF
#define I2C_SLAVE_STATE_MASK 0x2D // - - X - X X - X
#define I2C_SLAVE_STATE_1 0x09 // - - 0 - 1 0 - 1 DA=0 S=1 RW=0 BF=1
#define I2C_SLAVE_STATE_2 0x29 // - - 1 - 1 0 - 1 DA=1 S=1 RW=0 BF=1
#define I2C_SLAVE_STATE_3_MASK 0x2C // - - X - X X - - da,s,rw
#define I2C_SLAVE_STATE_3 0x0C // - - 0 - 1 1 - - DA=0 S=1 RW=1
#define I2C_SLAVE_STATE_4 0x2C // - - 1 - 1 1 - 0 DA=1 S=1 RW=1 BF=0 CKP=0
#define I2C_SLAVE_STATE_5_MASK 0x28 // - - X - X - - - da,s
#define I2C_SLAVE_STATE_5 0x28 // - - 1 - 1 0 - 0 DA=1 S=1 CKP=1
I used the following variables
#define I2C_REG_SIZE 128
static unsigned char i2c_regs[I2C_REG_SIZE]; //actual memory storage
static unsigned char i2c_reg_addr; //current memory address, where the
operation will take place
static unsigned char i2c_first_data; //true when we are about to get the
first data byte, ie register address
static unsigned char i2c_status; //masked SSPSTAT value
static unsigned char i2c_data; //temporary storage for data
#ifdef I2C_DEBUG
static unsigned char flag;
#endif
The initialization code defines the MSSP macrocell configuration. The slave
address is defined here.
the code from fabien lementec initializes the slave with 0x26 instead of
0x36, which I don't like. I prefer initializing CKP to 1 to be sure the
clock line is released. Apart from that, the initialization is very similar.
//-------------------------------------------------------------------------------------------
//Setup the pic peripheral
void i2c_slave_setup(unsigned char addr)
{
TRISCbits.TRISC3 = 1;
TRISCbits.TRISC4 = 1;
SSPCON1 = 0x36; //7-bit I2C slave, no IRQ on start/stop
//SSPCON2bits.SEN = 1; //enable clock stretching
SSPADD = (addr<<1);
PIE1bits.SSPIE = 1;
PIR1bits.SSPIF = 0;
//init vars
i2c_reg_addr = 0;
i2c_first_data = 0;
}
The slave is interrupt driven.
Algorithm:
There are 5 states, defined by the value in the SSPSTAT register
State 1 is triggered when the slave receives the address byte with the RW
flag cleared, ie we start a write transaction.
State 2 is triggered when more bytes are sent by the master. Here, we want
to discriminate the first one which is the register address, but a PCF8574
is even simpler than that
State 3 is triggered when we just got an address byte with the RW flag set,
ie when we are READY to send the first byte of the read transaction. Now you
understand that we must first write the register address byte before reading
anything.
State 4 is triggered when we are READY to send the next bytes.
State 5 is triggered after the master sent a NACK in a read transaction.
There are two MSSP macrocells in PIC devices: PIC16 and devices listed in
AN734 page 17 have the OLD state machine, all other PIC18 have the NEW state
machine.
With the NEW machine, in state 3, we MUST read SSPBUF or the read operation
will fail. This is what prevented me from posting this code earlier! There
is no need to read SSPBUF in state 4.
So with new PICs, you have to
#define I2C_SLAVE_NEW
This does not follow the state machine from fabien, I didn't try his code,
but I guess mine is more straightforward, with less if() statements.
Here is the code:
//-------------------------------------------------------------------------------------------
//Manage I2C slave events
SIGHANDLER(i2c_slave_isr)
{
//no local variables here for faster response times
i2c_status = SSPSTAT & I2C_SLAVE_STATE_MASK; //Mask useless bits
#ifdef I2C_DEBUG
ser_putchar('[');
ser_puthb(i2c_status);
ser_putchar(']');
#endif
//--------------------
// state 1: just got an address byte for a write transaction.
//--------------------
if(i2c_status == I2C_SLAVE_STATE_1) {
i2c_first_data = 1; //next byte will be the register address
//dummy read the buffer to clear BF
i2c_data = SSPBUF;
#ifdef I2C_DEBUG
ser_putchar('1');
#endif
goto i2c_slave_exit;
}
//--------------------
// state 2: just got a data byte in a write transaction.
//--------------------
if(i2c_status == I2C_SLAVE_STATE_2) {
i2c_data = SSPBUF; //read data to clear BF
if(i2c_first_data) { //this is the first data byte, store into the register
address
i2c_first_data = 0;
i2c_reg_addr = i2c_data;
#ifdef I2C_DEBUG
flag=0;
#endif
} else { //next bytes are really data
if(i2c_reg_addr<I2C_REG_SIZE) {
i2c_regs[i2c_reg_addr] = i2c_data;
i2c_reg_addr++; //next write will go to next register
#ifdef I2C_DEBUG
flag=1;
#endif
}
}
#ifdef I2C_DEBUG
ser_putchar('2');
ser_putchar('<');
if(flag==0) {
ser_putchar('@');
ser_puthb(i2c_reg_addr);
}else {
ser_puthb(i2c_reg_addr-1);
ser_putchar(':');
ser_puthb(i2c_data);
}
ser_putchar('>');
#endif
goto i2c_slave_exit;
}
//--------------------
// state 3: just got an address byte for a read transaction. ready to fill
buffer for next read.
//--------------------
if( (i2c_status & I2C_SLAVE_STATE_3_MASK) == I2C_SLAVE_STATE_3) {
#ifdef I2C_SLAVE_NEW
//SSPBUF must be read
i2c_data = SSPBUF;
#endif
if(i2c_reg_addr<I2C_REG_SIZE) {
i2c_data = i2c_regs[i2c_reg_addr];
i2c_reg_addr++;
} else {
i2c_data = 0xFF;
}
while(SSPSTATbits.BF==1); //wait for previous byte to be read
SSPCON1bits.WCOL=0;
redowrite3:
SSPBUF = i2c_data;
if(SSPCON1bits.WCOL) goto redowrite3;
//Release clock
SSPCON1bits.CKP = 1;
#ifdef I2C_DEBUG
ser_putchar('3');
ser_putchar('<');
ser_puthb(i2c_reg_addr-1);
ser_putchar(':');
ser_puthb(i2c_data);
ser_putchar('>');
#endif
goto i2c_slave_exit;
}
//--------------------
//state 4: a byte has just been read by the master. ready to fill buffer for
next read.
//--------------------
if(i2c_status == I2C_SLAVE_STATE_4) {
if(SSPCON1bits.CKP) {
//CKP=1 means state 5!
#ifdef I2C_DEBUG
ser_putchar('!');
#endif
goto i2c_slave_state_5;
}
if(i2c_reg_addr<I2C_REG_SIZE) {
i2c_data = i2c_regs[i2c_reg_addr];
i2c_reg_addr++;
} else {
i2c_data = 0xFF;
}
while(SSPSTATbits.BF==1); //wait for previous byte to be read
SSPCON1bits.WCOL=0;
redowrite4:
SSPBUF = i2c_data;
if(SSPCON1bits.WCOL) goto redowrite4;
//Release clock
SSPCON1bits.CKP = 1;
#ifdef I2C_DEBUG
ser_putchar('4');
ser_putchar('<');
ser_puthb(i2c_reg_addr-1);
ser_putchar(':');
ser_puthb(i2c_data);
ser_putchar('>');
#endif
goto i2c_slave_exit;
}
//--------------------
//state 5: just got a NACK from the master, this is the end of a read
transaction
//--------------------
i2c_slave_state_5:
if( (i2c_status & I2C_SLAVE_STATE_5_MASK) == I2C_SLAVE_STATE_5) {
#ifdef I2C_DEBUG
ser_putchar('5');
#endif
goto i2c_slave_exit;
}
//error... just reset!
#ifdef I2C_DEBUG
ser_putchar('X');
#endif
Reset();
i2c_slave_exit:
PIR1bits.SSPIF = 0; //ACK interrupt or the next one will to be triggered!
}
Here is a sample of state sequences with SSPSTAT values for a write
transaction:
I did not trigger any interruption on start/stop bit.
When I send START ADDRW 00 50 51 52 STOP, I get:
[09]1
[29]2<@00>
[29]2<00:50>
[29]2<01:51>
[29]2<02:52>
For a read transaction:
START ADDRW 00 RESTART READACK READACK READACK READNACK STOP
[09]1
[29]2<@00>
[0D]3<00:50>
[2C]4<01:51>
[2C]4<02:52>
[2C]4<03:00>
[2C]!5
A read transaction for a single byte at register address 0 with no ack
gives:
[09]1
[29]2<@00>
[0D]3<00:50>
[2C]!5
I release this code with the purpose of helping anyone who need an i2c
slave. There is no restriction on it.
Enjoy and report problems you found!
Please ask questions!
Sebastien