|
|
View previous topic :: View next topic |
Author |
Message |
csptol
Joined: 28 Nov 2012 Posts: 3 Location: The Netherlands
|
I2C problem |
Posted: Wed Nov 28, 2012 5:52 am |
|
|
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 . 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: 9232 Location: Greensville,Ontario
|
|
Posted: Wed Nov 28, 2012 6:17 am |
|
|
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
|
|
Posted: Wed Nov 28, 2012 7:32 am |
|
|
Hi Jay,
Thank you for the tips!
Regards,
Chris |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
|
Posted: Wed Nov 28, 2012 8:03 am |
|
|
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
|
|
Posted: Fri Nov 30, 2012 7:42 am |
|
|
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 |
|
|
|
|
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
|