CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

I2C problem

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
csptol



Joined: 28 Nov 2012
Posts: 3
Location: The Netherlands

View user's profile Send private message

I2C problem
PostPosted: Wed Nov 28, 2012 5:52 am     Reply with quote

I'm trying to display a text message from the I2C master, but it somehow halts after reading the address word. I use the flexible LCD driver from this forum posted by the PCM programmer. I also use an assembler file for the master which works with an assembler file for the slave. I have tried to program the slave in C language but i cannot get it to work Sad . The reason for programming it in C is that i want to develop my C programming skills. Here is the slave Code:

Code:

//*******************************************************************
//dbt_kypd_echo_slave
//Displays any character sent on I2C from Master.
//CSP 23.11.12               tested & working ##.11.12
//Initialisation revised   July 06
//*******************************************************************
//Clock is 1MHz approx
//                        
//      Port A         Port B         Port C
//      ------         ------          ------
//0         -         LCD test led   Display RS
//1         -            -         Display E
//2      lcd bus 4         -         Interrupt op
//3      lcd bus 5         -         SCL
//4      lcd bus 6         -         SDA
//5      lcd bus 7         -         Display R/W
//6         -            -             -
//7          -            -             -

   #include <16F873A.h>
   #use delay(clock=1000000)
   #include <lcd_controller.h>  // call for lcd_controller.c

//Configuration Word: RC oscillator, WDT off, power-up timer on,
//brown-out off, LVP off, all code protect on.
   #fuses RC,PUT,NOBROWNOUT,PROTECT,NOWDT,NOLVP

//Define 16F873A SF registers
   #byte PORTA       = 0x05
   #byte PORTB       = 0x06
   #byte PORTC       = 0x07
   #byte TRISA       = 0x85
   #byte TRISB       = 0x86
   #byte TRISC       = 0x87
   #byte INTCON       = 0x0B
   #byte STATUS       = 0x03
   #byte ADCON1       = 0x9F
   #byte PIE1          = 0x8C
   #byte PIR1          = 0x0C
//I2C SF registers
   #byte SSPCON       = 0x14
   #byte SSPADD       = 0x93
   #byte SSPBUF       = 0x13
   #byte SSPSTAT       = 0x94
//I2C bits
   #bit CKP = SSPCON.4
   #bit SSPIF = PIR1.3
   #bit SSPIE = PIE1.3
   #bit BF = SSPSTAT.0
   #bit R_W = SSPSTAT.2
   #bit D_A = SSPSTAT.5
   #bit PEIE = INTCON.6
   #bit GIE = INTCON.7
   #bit Z = STATUS.2
//Output bits
   #bit led = PORTB.0         //ouput
   #bit int_op = PORTC.2      //interrupt trigger for I2C master

//Function Prototypes (Library prototypes are in the Header files)
   void dig_pntr_set(void);

//***********************************************************
//INITIALISATION & DIAGNOSTICS
//***********************************************************
   void initialisation(void){
//LCD initialisation
      lcd_init();                    // Initialize the LCD

      ADCON1 = 0b00000110;         //set port A for digital function
      TRISA = 0b00000000;            //PORTA bits all initially op's
      TRISB = 0b00000000;            //PORTB bits 7-4 inputs
      TRISC = 0b00011000;            //PORTC bits 3 and 4 are ip's

//I2C bus SFR registers
      SSPCON = 0b00110110;         //SSPCON1 : SSPCON1:MSSP on, I2C Slave, 7 bit address, interrupts off, no clock stretch on receive
      SSPADD = 0b10100100;         //our address to be 52H = 0b01010010 (it's shifted by one in SSPADD)

//Interrupt set-up I2C interrupt
      SSPIE = 1;                  //enable I2C interrupt
//further initialisation, and interrupt enabling
      SSPIF = 0;                  //clear pending interrupts
      PEIE = 1;                  //enable Peripheral Interrupt (INTCON.6)
      GIE = 1;                  //enable Global Interrupt (INTCON.7)
   }

   void diagnostic(void){
      delay_ms(1000);               //delay for settling of power source
      led = 1;
      delay_ms(500);
      led = 0;
      delay_ms(500);
   }

//Global variables
   char kpad_add = 0;
   char display_pntr = 0;
   char I2C_RX_word;                     //holds most recent IC2 word recorded

//***********************************************************
//MAIN PROGRAM
//***********************************************************

void main(void){
   initialisation();
   diagnostic();   
   PORTB = 0b00000001;            //initialise keypad value and hand controller led
   for(;;);                  //repeat for ever and wait for SSP interrupts
}

//***********************************************************
//Interrupt Service Routines.
//***********************************************************
//Interrupt caused by I2C address match (Ack sent automatically)
//Does not context save, as all action is in ISRs.
//OR further received byte has been detected.
//check whether this byte was address or data
#INT_SSP
   void interrupt_SSP(void){
      char dummy;
      led = 1;                           //set diagnostic led
      if(!D_A){
         dummy = SSPBUF;                     //dummy read of the address byte, to clear flag
//for echo purposes send to master; kpad_add not used for test purposes
         if (R_W){
            SSPIF = 0;
            SSPBUF = kpad_add;                  //move character to sspbuf
            CKP = 1;                        //release clock
            while(SSPIF);                     //wait for completion of transfer
         }            
      }
//recieve byte and print on LCD
      else{
         dig_pntr_set();
         I2C_RX_word = SSPBUF;
         lcd_putc(I2C_RX_word);
      }
      led = 0;                           //clear diagnostic led
      SSPIF = 0;                           //clear Synchronous Serial Port interrupt flag
   }

//***********************************************************
//SUBROUTINES
//***********************************************************
//adjusts the digit pointer on the display, so that sent digits don't fall off!
   void dig_pntr_set(void){
            display_pntr = display_pntr + 1;
            if (display_pntr == 8){            
               lcd_putc("\n");         //second row address command
            }
            if (display_pntr == 16){   
               lcd_init();                  //clear lcd display and start over
               display_pntr = 0;
            }


The master Assembler code which works with the assembler slave code is as follows:

Code:

;***************************************************************************
;Dbt_kybd_echo_mstr
;This program exercises I2C bus in several ways:
;  sends an opening message to the hand controller
;  on interrupt receives a digit from the Hand Controller,
;  stores it, and echoes it back.
;Routines can be embedded into any program to provide user control of AGV.
;TJW 20.7.05               Tested and working 21.7.05
;***************************************************************************
;Clock is 4MHz
;
   list    p=16F873A
   #include p16f873A.inc
;
;Set Configuration Word: crystal oscillator HS, WDT off,
;     power-up timer on, code protect off, LV Program off.
   __config _hs_osc & _wdt_off & _pwrte_on & _lvp_off
;
;Specify RAM
delcntr1    equ   20   ;used in delay5 & delayADC SRs
delcntr2    equ   21
temp       equ   22     ;a temp location, to be used only in consecutive instructions
I2C_RX_word    equ   23   ;holds most recent I2C word recd
I2C_add       equ   24     ;holds address used in I2C message
I2C_TX_word    equ   25   ;holds word to be transmitted on I2C
pointer      equ    26     ;table pointer for strings to lcd
   
;Specify some port bits
;For Port A
mot_en_rt    equ   2
mot_en_left   equ   5

;For Port B
sounder      equ   1   ;piezo electric sounder
us_rt      equ    6   ;right microswitch
us_left      equ    7   ;left microswitch

;For PortC
mot_left    equ    1   ;left motor direction bit, 1=forward
mot_rt       equ    2   ;right motor direction bit
led_rt      equ    5   ;diagnostic led
led_left   equ    6   ;diagnostic led
mode      equ   7   ;mode switch

   org    00
   goto    start
;
   org    04
   goto Interrupt_SR
;Initialise SFRs in Bank 1
start   bcf   status,rp1
   bsf   status,rp0    ;select memory bank 1
   movlw   B'00001011'   ;set port A bits according to their function
   movwf   trisa      
   movlw   B'11001001'   ;also port B bits
   movwf   trisb      
   movlw   B'10011000'   ;set port C bits, I2C bits are both set as ip
   movwf   trisc      
   movlw   B'01011100'
   movwf   adcon1      ;select port A bits 0,1,3 for analog input
   movlw   07       ;set up 125kHz baud rate
   movwf   sspadd
;Initialise SFRs in Bank 0
   bcf   status,rp0
   movlw   B'00101000'   ;SSPCON1:MSSP on, I2C Master
   movwf   sspcon
;
;Switch all outputs off
   movlw    00
   movwf    porta
   movwf    portb
   movwf    portc
;diagnostic, flash leds
   bsf    portc,6
   bsf    portc,5
   call    delay500
   bcf    portc,6
   bcf    portc,5
   call    delay500   ;extra delay
;Send opening string
   clrf   pointer
   movlw   0a4      ;send slave address, R/W is write
   movwf   I2C_add
   call    I2C_send_add
loop_str1 movf   pointer,0
   call   Table1
   movwf   I2C_TX_word
   sublw   0ff      ;test and move on if end marker reached
   btfsc   status,z
   goto   string_end
   call   I2C_send_word
   incf   pointer,1
   call    delay1      ;give LCD time to write
   call    delay1
   call    delay1
   goto   loop_str1
string_end call I2C_send_stop
;Enable interrupts
   bcf   intcon,intf   ;clear pending interrupts
   bsf   intcon,inte   ;enable external interrupt
   bsf   intcon,gie
;Wait for interrupts from Hand Controller
loop    call blink
goto   loop
;
;*******************************************************************
;ISR. On external interrupt, SSP reads byte from Hand Controller,
;and echoes it back, ie two I2C messages.
;Received Byte stored in I2C_word for further action.
;*******************************************************************
Interrupt_SR
   bsf    portc,6      ;diagnostic
;Start new I2C message, requesting word from slave.
   movlw   0a5      ;this is slave address, R/W is read
   movwf   I2C_add
   call    I2C_send_add
;now wait for byte to come in
   call    I2C_rec_word
   call    I2C_send_stop
   bcf   status,rp0
   call    delay20u
;Now echo byte - start new message
   movlw   0a4      ;this is slave address, R/W is write
   movwf   I2C_add
   call    I2C_send_add
;send the echoed character   
   movf    I2C_RX_word,0 ;move received word to transmit store
   movwf    I2C_TX_word
   call   I2C_send_word
   call    I2C_send_stop
   bcf   status,rp0
   bcf    portc,6      ;clear diag led
   bcf   intcon,intf
   retfie
   
;*******************************************************************
;SUBROUTINES
;*******************************************************************
;initiates I2C message, by sending the word found in I2C_add, which
;must include R/W bit. Waits for all acknowledgement and completion
;states. Leaves RAM in Bank 0.
blink   bsf    portc,6
   bsf    portc,5
   call    delay500
   bcf    portc,6
   bcf    portc,5
   call    delay500   ;extra delay
goto blink

I2C_send_add
   bsf   status,rp0
   bsf   sspcon2,sen   ;force start bit
   btfsc   sspcon2,sen   ;check for its completion
   goto   $-1
   bcf   status,rp0
   movf   I2C_add,0   ;load address and data dirn bit
   movwf   sspbuf      ;and send
   bcf   pir1,sspif   ;will test this soon
   bsf   status,rp0
   btfsc    sspstat,bf ;test for write complete
   goto   $-1
   btfsc    sspcon2,ackstat   ;wait for 0 acknowledge bit
   goto    $-1
   bcf   status,rp0
   btfss   pir1,sspif ;test for int flag to show completion
   goto   $-1
   bcf   pir1,sspif
   return
;
;Receives (single) word from I2C bus, and stores in I2C_RX_word. Returns Ack of 1,
;signalling this is last byte. Leaves RAM in Bank 0
I2C_rec_word bsf status,rp0
   bsf   sspcon2,rcen   ;set receive enable bit
   btfss   sspstat,bf   ;wait for buffer full
   goto    $-1
   bcf   status,rp0   ;read the data
   movf   sspbuf,0
   movwf   I2C_RX_word   ;store it for use somewhere
   bcf   pir1,sspif   ;preclear int flag, as we are about to use it
   bsf   status,rp0
   bsf   sspcon2,ackdt    ;set required acknowledge state, 1 as it's last byte
   bsf   sspcon2,acken   ;and enable it
   bcf   status,rp0
   btfss   pir1,sspif   ;use interrupt flag to test for end of ack
   goto   $-1
   bcf   status,rp0   
   return
;
;Sends word on I2C bus, and awaits acknowledgement. Leaves RAM in Bank 0.
I2C_send_word bcf status,rp0
   movf   I2C_TX_word,0   ;get the word   
   movwf   sspbuf      ;this starts the transfer
   bsf   status,rp0
   btfsc    sspstat,r_w    ;test for write complete
   goto   $-1
   btfsc    sspcon2,ackstat   ;wait for 0 acknowledge bit
   goto    $-1
   bcf   status,rp0
   return
;
;Sends I2C stop bit, and awaits completion. Leaves RAM in Bank 0.
I2C_send_stop bsf status,rp0
   bsf   sspcon2,pen   ;force stop bit.
   btfss   sspstat,p    ;test for stop bit completion
   goto   $-1
   bcf    status,rp0
   return
;
;introduces delay of 20us approx
delay20u  movlw D'5'      ;5 cycles called,
   ;each taking 3us, plus call, return (2 ea), and 2 move insts
   ;less one cycle lost when last goto is hopped
        movwf     delcntr1
dela    decfsz    delcntr1,1    ;3 inst cycles in this loop, ie 3us
        goto    dela
        return
;
;introduces delay of 1ms approx
delay1  movlw    D'250'      ;250 cycles called,
;         each taking 4us
        movwf     delcntr1
del1    nop         ;4 inst cycles in this loop, ie 4us
        decfsz    delcntr1,1
        goto    del1
        return
;
;10ms delay (approx)      ;10 calls to delay1
delay10  movlw D'10'
        movwf     delcntr2
del10    call    delay1
        decfsz  delcntr2,1
        goto    del10
        return 
;200ms delay (approx)      ;200 calls to delay1
delay200  movlw D'200'
        movwf     delcntr2
del2    call    delay1
        decfsz  delcntr2,1
        goto    del2
        return 
;
;500ms delay (approx)      ;500 calls to delay1
delay500  movlw D'250'
        movwf     delcntr2
del5    call    delay1
   call    delay1
        decfsz  delcntr2,1
        goto    del5
        return 
;
;Character String Tables
Table1 addwf pcl,1
   retlw ' '
   retlw 'D'
   retlw 'e'
   retlw 'r'
   retlw 'b'
   retlw 'o'
   retlw 't'
   retlw 0ff
;
   end


Help with this problem would be very appreciated.
temtronic



Joined: 01 Jul 2010
Posts: 9174
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Wed Nov 28, 2012 6:17 am     Reply with quote

Reading the programs that CCS supplies in the 'examples' folder and 'searching' this fourm will be two of your best friends!

The answers are here and there but you need to get familiar with 'search'.As well, the 'code library' is full of working code as well.

You're not the first to migrate from assembler to CCS C ( I did 15+ years ago...) so what you seek is here.

From a hardware point, be sure to use the correct pullups for the I2C bus ! Usually 4k7 to 2k2 will work. Also, there's a nice I2C Diagnostic program here,from PCM programmer I think, that you should copy and use. It's a GREAT tool to confirm what's going on !

hth
jay
csptol



Joined: 28 Nov 2012
Posts: 3
Location: The Netherlands

View user's profile Send private message

PostPosted: Wed Nov 28, 2012 7:32 am     Reply with quote

Hi Jay,

Thank you for the tips!

Regards,
Chris
RF_Developer



Joined: 07 Feb 2011
Posts: 839

View user's profile Send private message

PostPosted: Wed Nov 28, 2012 8:03 am     Reply with quote

Please take time out to really read the example code, even stuff that doesn't look relevant to what you're trying to do. Take note of how it looks and how it does things. Notice that looks very different to the assembler code you're ued to writing.

Your C code looks like, and clearly is assembler code that you've tried to re-write in C. That is not a good way to learn C. You really need to start again and write C, not convert assembler into C.

CCS C provides a rich "library" of built-in handlers for many PIC hardware functions. It does so in a reasonably PIC non-specific way, so that you can easily port CCS C code from one PIC to another, even from one range of PIC to another. It doesn't always work, and newer functions don't always work, especially on the more complex, but comparitively infrequently used PICs in the 24, dsPIC and 32 series.

CCS C provides almost all the functionality of your code in just a handful of function calls, or code that looks like function calls. For the vast majority of CCS C applications you should never need to define any SFRs and bits within them. All of that is done for you in a largely device independant way. You never need to know port addresses - the compiler handles that too - nor generally do you need to know about TRIS settings (though time and time again folk post code with it to this forum: clearly unaware that its not needed and causes trouble).

You've retained the assembler comments that show me you've not really thought through what's happening in C, instead you're assumung its just like it was in assembler. It's not. For example your comment for your INT_SSP ISR says: "Interrupt ... Does not context save, as all action is in ISRs." CCS C provides a lot of behind the scenes interrupt handling. It DOES save the context for you, whether you like it or not (you can't turn it off). It also handles the clearing of interrupt flag bits and enabling and disabling of interrupts, so you shouldn't. Compare what you wrote to the I2C slave ISR in EX_SLAVE.C. I'll risk posting it here, just the ISR - we should not generally post CCS example code to this forum - to illustrate just how different the C version of the ISR looks to yours. Note there's no SFRs, indeed almost no obvious reference to the hardware at all: that's done through calls to the CCS "library" code. Also it uses buffers on input and output, which is advisable and arguably much easier handled in C than it is in assembler.
Code:

#INT_SSP
void ssp_interrupt ()
{
   BYTE incoming, state;
   
   state = i2c_isr_state();
   
   if(state <= 0x80)                     //Master is sending data
   {
      if(state == 0x80)
         incoming =i2c_read(2);           //Passing 2 as parameter, causes the function to read the SSPBUF without releasing the clock
      else
         incoming = i2c_read();
     
      if(state == 1)                      //First received byte is address
         address = incoming;
      else if(state >=2 && state !=0x80)  //Received byte is data
         buffer[address++] = incoming;
   }
   
   if(state >= 0x80)                      //Master is requesting data
   {
      i2c_write(buffer[address++]);
   }
}

This is the code on which you should base your ISR. Indeed you may even be able to use this code directly. You see quite a few people have difficulties with implementing I2C slaves, and if they all use this CCS example code as a basis then they'd probably have fewer problems. We *should* see this code posted over and over as part of user code in threads on I2C slave problems. We don't. We nearly always see a half-baked version of it....

So please take the time to get used to writing C and exploiting the power of CCS C. Don't think of it as assembler with a twist. Its a whole new world. And yes, if you are wondering, you'll have to do the same paradigm shift again to go to C++ or C#.

RF Developer
csptol



Joined: 28 Nov 2012
Posts: 3
Location: The Netherlands

View user's profile Send private message

PostPosted: Fri Nov 30, 2012 7:42 am     Reply with quote

Thank you RF developer for your feedback!

I have changed my code and removed as much as assembler habits as possible:

Code:

//*******************************************************************
//dbt_kypd_echo_slave
//Displays any character sent on I2C from Master.
//CSP 23.11.12               tested & working ##.11.12
//Initialisation revised   July 06
//*******************************************************************
//Clock is 1MHz approx
//                        
//      Port A         Port B         Port C
//      ------         ------          ------
//0         -         LCD test led   Display RS
//1         -            -         Display E
//2      lcd bus 4         -         Interrupt op
//3      lcd bus 5         -         SCL
//4      lcd bus 6         -         SDA
//5      lcd bus 7         -         Display R/W
//6         -            -             -
//7          -            -             -

   #include <16F873A.h>
   #use delay(clock=1000000)
   #include <lcd_controller.h>  // call for lcd_controller.c
/* Setup I2C */
   #use i2c(SLAVE, SDA=PIN_C4, SCL=PIN_C3, address=0xA4, SLOW, FORCE_HW)

//Configuration Word: RC oscillator, WDT off, power-up timer on,
//brown-out off, LVP off, all code protect on.
   #fuses RC,PUT,NOBROWNOUT,PROTECT,NOWDT,NOLVP

//Define 16F873A SF registers
   #byte TRISB       = 0x86
   #byte PORTB       = 0x06
   #byte INTCON       = 0x0B
   #byte PIE1          = 0x8C
   #byte PIR1          = 0x0C
//I2C SF registers
   #byte SSPCON       = 0x14
   #byte SSPADD       = 0x93
   #byte SSPBUF       = 0x13
   #byte SSPSTAT       = 0x94
//I2C bits
   #bit SSPIE = PIE1.3
   #bit PEIE = INTCON.6
   #bit GIE = INTCON.7
//Output bits
   #bit led = PORTB.0         //ouput

//***********************************************************
//INITIALISATION & DIAGNOSTICS
//***********************************************************
   void initialisation(void){
//LCD initialisation
      delay_ms(2000);               //delay for settling of power source
      lcd_init();                   // Initialize the LCD
      TRISB = 0b00000000;            //PORTB bits 7-4 inputs

//Interrupt set-up I2C interrupt
      SSPIE = 1;                  //enable I2C interrupt
//further initialisation, and interrupt enabling
      PEIE = 1;                  //enable Peripheral Interrupt (INTCON.6)
      GIE = 1;                  //enable Global Interrupt (INTCON.7)
   }

//Global variables
   int8 buffer[0x10];                  //buffer size 16
   char count = 0;
   char flag = 0;

//***********************************************************
//MAIN PROGRAM
//***********************************************************
void main(void){
   char j;
   initialisation();
   delay_ms(100);
   j = count;
   
   for(;;){            //repeat for ever and wait for SSP interrupts      
lcd_putc(count+0b00110000);
   }
}

//***********************************************************
//Interrupt Service Routines.
//***********************************************************
//Interrupt caused by I2C address match (Ack sent automatically)
//OR further received byte has been detected.
//check whether this byte was address or data
#INT_SSP
   void interrupt_SSP(void){
      BYTE incoming, state, address;

      count = count + 1;
      state = i2c_isr_state();
      if(state <= 0x80){                      //Master is sending data
         if(state == 0x80)
            incoming =i2c_read(2);           //Passing 2 as parameter, causes the function to read the SSPBUF without releasing the clock
         else{
            while(!i2c_poll());
            incoming = i2c_read();
         }
         if(state == 1){                     //First received byte is address
            address = incoming;
            led = 1;
         }
         else if(state >= 2 && state !=0x80){  //Received byte is data
            buffer[address++] = incoming;
         }
      }
        if(state >= 0x80){                      //Master is requesting data
         i2c_write(buffer[address++]);
      }
   }


I still have trouble making it work. In the code above I implemented some debug statements. A diagnostic led and a counter to count the number of ISR calls and identify which if statements are entered. I only get one ISR call and the led is lit, this means that the address is read and stored in the address variable, after that the ISR is not called a second time. I have no idea why this is the case.

I have tried so many different things that at the moment I'm kind of empty on ideas. Suggestions anyone?? Again help would be appreciated very much.

ps the pull up resistors seem to be right, because it works when master and slave code are in assembler
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group