View previous topic :: View next topic |
Author |
Message |
jmann
Joined: 27 Dec 2004 Posts: 21
|
[partly solved] 2C slave example. Read consecutive bytes. |
Posted: Fri Mar 22, 2013 9:50 pm |
|
|
In the example code for I2C slave, only one byte is read per I2C frame.
ex [start][ChipAddr write][write address][start][ChipAddr read][read byte 1][stop]
Code: | #INT_SSP
void ssp_interupt ()
{
BYTE incoming, state;
state = i2c_isr_state();
if(state <= 0x80) //Master is sending data
{
incoming = i2c_read();
if(state == 1) //First received byte is address
address = incoming;
if(state == 2) //Second received byte is data
buffer[address] = incoming;
}
if(state == 0x80) //Master is requesting data
{
i2c_write(buffer[address]);
}
} |
This appears to contradict the example in the documentation where for a 0x80, we do nothing.
Code: | #INT_SSP
void i2c_isr()
{
state = i2c_isr_state();
if((state== 0 ) || (state== 0x80))
i@c_read();
if(state >= 0x80)
i2c_write(send_buffer[state - 0x80]);
else if(state > 0)
rcv_buffer[state - 1] = i2c_read();
} |
How do you read more than one byte?
ex [start][ChipAddr write][write address][start][ChipAddr read][read byte 1][read byte 2].....[stop]
It is my understanding that i2c_isr_state() will return a number beginning at 0x80 on writes and increment each byte.
Will this work?
Code: | #INT_SSP
void ssp_interupt ()
{
BYTE incoming, state;
state = i2c_isr_state();
if(state ==0) //address byte
{ // we do nothing
}
else if (state == 1) //First received byte is address
{
address = i2c_read();
}
else if (state < 0x80) //Second or more received byte is data we write to the buffer
buffer[address] = i2c_read();
}
else if (state == 80) // match the address for a write
{ // do nothing
}
else if (state>80) //a value above 0x80 is request for a read.
{
i2c_write(buffer[address++]);
}
} |
Also, is there a way to be notified or interrupted once we receive a STOP?[/code]
Last edited by jmann on Mon Apr 01, 2013 9:28 pm; edited 1 time in total |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Sat Mar 23, 2013 1:38 am |
|
|
There is a slight misunderstanding here. There is not an 'I2C_frame' as far as the interrupt is concerned. The interrupt occurs for every I2C _byte_ transaction.
It can also occur for start/stop (this is a programmable option normally left off).
Then it next occurs on the start byte.
Then it occurs for every byte in the transaction, one after the other.
The thing keeping track of the 'frame', is i2c_isr_state, and 'address'. Now in I2C, there are two 'addresses'. The first is the device address, then you have the 'register address'. The ISR, responds to the device address, with isr_state==0, The next transaction normally sends the register address, and this is 'state==1', and is put into the 'address' variable.
Then the write transaction puts the next byte(s) into the location defined by this. The only change needed to handle more bytes, is to have the i2c_write increment the 'register address'.
Code: |
if(state >= 0x80) //Master is requesting data
{
i2c_write(buffer[address++]);
}
|
Then on each call the next byte will be sent.
This is the so called 'auto-increment' mode for an I2C slave.
This is what the example PCM points to, does.
Best Wishes |
|
|
jmann
Joined: 27 Dec 2004 Posts: 21
|
|
Posted: Sat Mar 23, 2013 6:56 am |
|
|
Ttelmah wrote: |
Code: |
if(state >= 0x80) //Master is requesting data
{
i2c_write(buffer[address++]);
}
|
|
picture of a frame: http://www.8051projects.net/i2c-twi-tutorial/read-i2c.png
So, I see, this is how we're handling it with the built-in functions.
Code: | [start]
[write ChipAddr writeFlag] <<-- interrupt with i2c_isr_state == 0x00
do nothing
[write RegisterAddress] <<-- interrupt with i2c_isr_state == 0x01
i2c_read(RegisterAddress)
[start] <<-- this is the second start for a i2c frame with master writing data
[write ChipAddr readflag] <<-- wouldn't the i2c_isr_state read in this interrupt be 0x80?
i2c_write(register[RegisterAddress] <<-- we need to pre-load the data to be sent, Byte 1
this byte will be read out of the i2c register and then we'll get another interrupt when it is empty.
[read byte 1] <<-- this is read out of the I2C buffer, and then we get an interrupt because the buffer is empty with i2c_isr_state == 0x81
i2c_write(register[RegisterAddress+1] <<-- we need to pre-load the data to be sent, byte 2
[read byte 2] <<-- this is read out of the I2C buffer, and then we get an interrupt because the buffer is empty with i2c_isr_state == 0x82
i2c_write(register[RegisterAddress+2] <<-- we need to pre-load the data to be sent
.....
[read byte N] <<-- this is read out of the I2C buffer, but there isn't an ACK, so the interrupt doesn't get called to load the next byte
[stop] |
That last byte is read by the master without sending an ack. Then we get a stop.
This brings me to the next question: Let's say I was waiting for the frame to be completed before processing the data sent to me. The point is that I want to wait for a STOP. Are there any built-in functions that will let me get an interrupt when there is a STOP so I can can know when I am done getting data so I can process it? |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1349
|
|
Posted: Sat Mar 23, 2013 7:36 am |
|
|
I don't think there is a built in to see the stop bit. Most I2C devices don't need it as the protocol is either defined as a specific message or there is a length field as part of the message (only really needed for master -> slave data as the master can ack/nack slave->master data).
However, if you absolutely need to see it, then you can use #bit to access it directly and see what the value is.
As a note since you are using a protocol defined with N bytes of data. Be careful using the isr state when dealing with N >= 127 as the isr state was only designed for the slave to read 127 bytes and send 128 bytes (or less). If more is used, then the state will roll over the boundaries stated in the help file/manual and you will get states of 0x00 and 0x80 that aren't address but just the 128th or 129th bytes to read or be written by the slave, respectively. In cases of data this large, you need to monitor the D/A bit in the I2C data register in addition to the state so that if the A bit is set and the state is 0x00 or 0x80 it is indeed an address, but if the D is set, then it is just rollover data. And in the case of rollover, you have to account for that in your determination of if that byte is read or a request to write as well. |
|
|
jmann
Joined: 27 Dec 2004 Posts: 21
|
|
Posted: Sat Mar 23, 2013 7:46 am |
|
|
Simplifying this previous example, I believe this is how we do start and stop interrupts.
SSP1CON1<3:0> would ordinarily be set to the option "0110 = I2C Slave mode, 7-bit address "
we want to set it to the option "1110 = I2C Slave mode, 7-bit address with Start and Stop bit interrupts enabled"
Code: | #use i2c(slave,sda=PIN_C4,scl=PIN_C3, address=0x30, FORCE_HW)
// create bits for the status items for I2C
#byte SSPSTAT = 0x214 // register address specific to processor
#bit SMP = SSPSTAT.7
#bit CKE = SSPSTAT.6
#bit D_A = SSPSTAT.5
#bit P = SSPSTAT.4
#bit S = SSPSTAT.3
#bit R_W = SSPSTAT.2
#bit UA = SSPSTAT.1
#bit BF = SSPSTAT.0
#byte SSPCON1 = 0x215 // register address specific to processor
#INT_SSP
void ssp_isr(){
SSPIF = 0;
if(S){ // Checking for the Start Condition or Restart Condition
// Do nothing because we got a start
return;
}
if(P){ // Checking for the STOP Condition
ProcessI2cData(); // Do something because we got a stop
return;
}
//Do the usual slave stuff here
}
void main(){
SSPCON1 = (SSPCON1 & 0xF0) | 0x0E ; //Modifythe interrupt type set by #USE_I2C to be 7 bit addr with start and stop interrupts
enable_interrupts(INT_SSP);
enable_interrupts(global);
while(1)
{
}
} |
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Sat Mar 23, 2013 8:08 am |
|
|
Seriously, depends what chip you are using.
Several of these operations do not work correctly in many of the chips....
However the operation is basically simple:
Code: |
#bit SSPM3=getenv("BIT:SSPM3")
#bit P=getenv("BIT:P")
#bit S=getenv("BIT:S")
SSPM3=TRUE; //enable interrupts on start/stop
SSPM3=FALSE; //or to disable these
//Then at the start of the interrupt handler
if (S==TRUE)
//code here for start
if (P==TRUE)
//code here for stop
//obviously these must exit and not call the normal I2C code
|
Have fun.
It is almost an 'unused' feature, and as I say on many chips won't work properly. Remember you will get start/stop interrupts for every packet on the bus.
Normally on I2C, handshaking is done by writing a trigger bit to a specific register, or reading a handshaking bit (or using a separate line). Transactions are not considered important, since you can access registers in any order and at any time.
Best Wishes |
|
|
jmann
Joined: 27 Dec 2004 Posts: 21
|
|
Posted: Mon Apr 01, 2013 9:45 pm |
|
|
This forum became worthless over the past ten years. I'm removing my useful code example.
Last edited by jmann on Tue Apr 02, 2013 3:01 pm; edited 1 time in total |
|
|
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Tue Apr 02, 2013 10:46 am |
|
|
The moderator believes your new question to be still on this topic and duly locked the new thread.
I did not understand your 'new' question at all.
Can you kindly re-phrase and post again.
Mike |
|
|
jmann
Joined: 27 Dec 2004 Posts: 21
|
|
Posted: Wed Apr 03, 2013 9:01 am |
|
|
Mike Walne wrote: | The moderator believes your new question to be still on this topic and duly locked the new thread.
I did not understand your 'new' question at all.
Can you kindly re-phrase and post again.
Mike |
It was a competely differen topic. This topic was about using the built-in I2C functions properly with isr_state. that topic was about interrupt behavior.
These forums become worthless. They use to be fabulous in 2005. I'm done with them and am probobly switching to the compiler Microchip discributes now. |
|
|
|