# Writing SSPBUF from variable in I2C slave protocol in PIC18

I am migrating this question from StackOverflow to this forum because I believe it is more adequate.

I am writing an I2C slave routine for PIC18F25K80 and I am stuck on a weird problem.

This is my routine: (EDITED WITH THE LATEST VERSION BASED ON THE COMMENTS - STILL SAME PROBLEM)

void interrupt interruption_handler() {
INTCON1bits.GIE = 0; // Disable Master Synchronous Serial Port Interrupt

//This is a A/D interruption
INTCON1bits.GIE = 1; // Enable Master Synchronous Serial Port Interrupt
return;
} else
if (PIR1bits.SSPIF == 1) {
//This is a I2C interruption
PIR1bits.SSPIF = 0;
//Treat overflow
if ((SSPCON1bits.SSPOV) || (SSPCON1bits.WCOL)) {
dummy = SSPBUF; // Read the previous value to clear the buffer
SSPCON1bits.SSPOV = 0; // Clear the overflow flag
SSPCON1bits.WCOL = 0; // Clear the collision bit
SSPCON1bits.CKP = 1;
board_state = BOARD_STATE_ERROR;
} else {
if (!SSPSTATbits.D_NOT_A) {
debug(0, ON);
while (BF); //Wait until completion
if (SSPSTATbits.R_NOT_W) {
SSPCON1bits.WCOL = 0;
unsigned char a = 0x01;
SSPBUF =  a; //0x01; //a+1; //Deliver first byte
}
} else {
if (SSPSTATbits.BF) {
dummy = SSPBUF; // Clear BF (just in case)
while (BF);
}
if (SSPSTATbits.R_NOT_W) {
debug(1, ON);
SSPCON1bits.WCOL = 0;
SSPBUF = 0x02; //Deliver second byte
} else {
//WRITE
debug(2, ON);
}
}
transmitted = TRUE;
SSPCON1bits.CKP = 1;
PIR1bits.SSPIF = 0; //Clear again just in case

INTCON1bits.GIE = 1; // Enable Master Synchronous Serial Port Interrupt
}
} else
PIR1 = 0x00; //Just in case
}


It works like a charm if I set constant values on SSPBUF. For example, if you do:

SSPBUF = 0x01;
(...)
SSPBUF = 0x02;


I get the two bytes on the master. I can even see the wave forms of the bytes being transmitted on the oscilloscope. Quite fun!

But when I try to set SSPBUF using a variable like:

unsigned char a = 0x01;
SSPBUF = a;


I get zero on the master.

It is driving me crazy.

1. Watchdog timer is messing up interrupting in the middle of the protocol: It is not. It is disabled and the problem happens in both SSPBUF assignments
2. I need to wait until BF goes low to continue: I don't. AFAIK, you setup the SSPBUF, clear SSPIF, set CKP and return from interruption to take care of life in 4Mhz while the hardware send data in few Khz. It will interrupt you again when it finishes.

It makes no sense to me. How good it is if you cannot define an arbitrary value using a variable?

Please gurus out there, enlighten this poor programmer.

• Not a solution, but an unrelated hint: When verifying that the interrupt was caused by I2C (if (PIR1bits.SSPIF != 1)), you don't clear any interrupt flags. If the interrupt was, in fact, caused by some other interrupt source, then the ISR will be called again immediately, forever... Aug 10 '16 at 23:41
• Good point. This is a hidden bug. It is not interfering now because on my setup there is no other interruption possible. I will fix it. Tks! Let me know if you have any other comment that help me to sleep at night. I've been working on this for days. Aug 10 '16 at 23:44
• I don't see anything obvious, but I hope that someone else will! You should certainly be able to use a variable to feed SSPBUF. As you say, it wouldn't be very useful otherwise. Aug 10 '16 at 23:57
• Try volatile unsigned char a = 0x01; Aug 11 '16 at 18:47
• rdtsc, I did. Does not work. I've edited the answer and included option 4. I still do not understand and do not consider my workaround as a definitive solution. Aug 12 '16 at 11:26

Turn the extended instruction set off in your codes config bit settings ie:

#pragma config XINST = OFF   // Extended Instruction Set (Disabled)


It makes all sorts of weird problems, just remember to turn it off from the start.

I2C stuff also gets itself in a tizzy sometimes if you don't leave a small delay between initializing I2C, calling functions etc.

• The Extended Instruction Set isn't even supported by the compiler the OP's using, so it's disabled by default.. Jan 23 '17 at 22:41
• Hi M, hey it's not supported by mine either. If you leave the above line out of your config then sometimes it defaults to ON & leads you on a merry chase around the walls after miss behaving variables. Last time for me was 18f45k80 after writing a small test code for i2c which turned out a wasted 1/2 day on the above issue. Maybe will help someone...
– Ken
Jan 23 '17 at 23:34
• It solved the problem! Jan 24 '17 at 1:14

I found a workaround. It is absolutely mind blowing. If I change:

SSPBUF = a;


to

SSPBUF = a+1;


it works. The data+1 goes to the I2C server. I guess it has something to do with the generated assembler code.

Let's analyze the options:

### Option 1 - assigning a constant (it works)

The code

SSPBUF = 0x23;


results in:

1548                           ;naplaca.c: 320: SSPBUF = 0x2;
1549  00008A  0E02                  movlw   2
1550  00008C  D00F                  goto    L3
(...)
1661  0000BA                     L3:
1662  0000BA  6EC9                  movwf   4041,c  ;volatile


### Option 2 - assigning a variable (does not work)

The code:

unsigned char a = 0x2;
SSPBUF = a;//0x01; //Deliver first byte


results in:

1544                           ;naplaca.c: 318: unsigned char a = 0x2;
1545  000086  0E02                  movlw   2
1546  000088  6E11                  movwf   interruption_handler@a,c
1547
1548                           ;naplaca.c: 319: SSPBUF = a;
1549  00008A  C011  FFC9            movff   interruption_handler@a,4041 ;volatile


### Option 3 - assigning the result of an expression (works but send data +1)

The code:

unsigned char a = 0x2;
SSPBUF = a+1;//0x01; //Deliver first byte


results in:

1544                           ;naplaca.c: 318: unsigned char a = 0x2;
1545  000086  0E02                  movlw   2
1546  000088  6E11                  movwf   interruption_handler@a,c
1547
1548                           ;naplaca.c: 319: SSPBUF = a+1;
1549  00008A  2811                  incf    interruption_handler@a,w,c
1550  00008C  D00F                  goto    L3
1551  00
(...)
1661  0000BA                     L3:
1662  0000BA  6EC9                  movwf   4041,c  ;volatile


I tried to sum 0, but the compiler optimizes it and make it equals to option 2. The same happens for a+1-1 and a+2-1-1.

Thus the workaround so far is to send data+1.

### Option 4 (volatile variable - does not work) -- edit

The code:

volatile unsigned char a = 0x09;
SSPBUF =  a;// byte1_0 + 1; //0x01; //Deliver first byte


results in:

1634                           ;naplaca.c: 350: volatile unsigned char a = 0x09;
1635  000098  0E09                  movlw   9
1636  00009A  6E11                  movwf   interruption_handler@a,c    ;volatile
1637
1638                           ;naplaca.c: 351: SSPBUF = a;
1639  00009C  C011  FFC9            movff   interruption_handler@a,4041 ;volatile
1640


### Conclusion

It seems to work when the compiler assign value to SSPBUF with movwf. When it uses move, it does not work.

I am posting this as an answer but I will not mark it as the right answer. I hope someone bring some light to this problem.