Jump to content
nrheckman

I2c Transfer Confusion

Recommended Posts

I have been going through everything I can find (Data Sheet, i2c_driver.h, MicroChip AN735 pdf) and I think I'm missing something important.

 

I "think" i have everything set up correctly but I'm not seeing any transfer happening. If somebody knows another resource I should look at with some examples in C, or if my mistake is blatantly obvious from the code below... Please help me out? :lol:

 

The following is just a basic program that writes some data from the master and reads it on the slave. Not trying to address the slave directly, just using the general call address (I'm just trying to test this out and wrap my head around i2c)

 

The master and the slave share the following:

 

16f882_i2c.h:

// Mask for I2C status bits
// bit 0 BF: Buffer Full Status bit
// bit 2 R/W: Read/Write bit Information (I2C mode only)
// bit 3 S: START bit
// bit 5 D/A: Data/Address bit (I2C mode only)
#define SSPSTAT_BIT_MASK  0b00101101

// init i2c_master mode
void i2c_master_init() {
// SSPADD: Bit Rate Control
// ============================================================================
// SSPADD = ((FOSC / Bit Rate) / 4) - 1
// ((20Mhz / 1Mhz) / 4) - 1 = 4
// ((20Mhz / 100Khz) / 4) - 1 = 40
sspadd = 00101000b;

// set trisc pins
set_bit(trisc, 3);
set_bit(trisc, 4);

// SSPSTAT: SSP STATUS REGISTER
// ============================================================================
// 1 = Slew rate control disabled for standard speed mode (100 kHz and 1 MHz)
// 0 = Slew rate control enabled for high speed mode (400 kHz)
clear_bit(sspstat, SMP);

// CKP = 0:
// 1 = Data transmitted on rising edge of SCK
// 0 = Data transmitted on falling edge of SCK
// CKP = 1:
// 1 = Data transmitted on falling edge of SCK
// 0 = Data transmitted on rising edge of SCK
clear_bit(sspstat, CKP);

// SSPCON: SSP CONTROL REGISTER 1
// ============================================================================
// WCOL: Write Collision Detect bit
// Master mode:
// 1 = A write to the SSPBUF register was attempted while the I2C conditions were not valid for a transmission to be started
// 0 = No collision
clear_bit(sspcon, WCOL);

// SSPOV: Receive Overflow Indicator bit
// 1 = A byte is received while the SSPBUF register is still holding the previous byte. SSPOV is a “don’t care” in Transmit
// mode (must be cleared in software).
// 0 = No overflow
clear_bit(sspcon, SSPOV);

// SSPEN: Synchronous Serial Port Enable bit
// 1 = Enables the serial port and configures the SDA and SCL pins as the source of the serial port pins
// 0 = Disables serial port and configures these pins as I/O port pins
set_bit(sspcon, SSPEN);

// CKP: Clock Polarity Select bit
// Unused in this mode
clear_bit(sspcon, CKP);

// SSPM<3:0>: Synchronous Serial Port Mode Select bits
// 1000 = I2C Master mode, clock = FOSC / (4 * (SSPADD+1))
// 1011 = I2C firmware controlled Master mode (Slave idle)
set_bit(sspcon, SSPM3);
clear_bit(sspcon, SSPM2);
clear_bit(sspcon, SSPM1);
clear_bit(sspcon, SSPM0);

// SSPCON2: SSP CONTROL REGISTER 2
// ============================================================================
// GCEN: General Call Enable bit (in I2C Slave mode only)
clear_bit(sspcon2, GCEN);

// ACKDT: Acknowledge Data bit (in I2C Master mode only)
// In Master Receive mode:
// Value transmitted when the user initiates an Acknowledge sequence at the end of a receive
// 1 = Not Acknowledge
// 0 = Acknowledge
clear_bit(sspcon2, ACKDT);

// ACKEN: Acknowledge Sequence Enable bit (in I2C Master mode only)
// In Master Receive mode:
// 1 = Initiate Acknowledge sequence on SDA and SCL pins, and transmit ACKDT data bit.
// Automatically cleared by hardware.
// 0 = Acknowledge sequence idle
clear_bit(sspcon2, ACKEN);

// RCEN: Receive Enable bit (in I2C Master mode only)
// 1 = Enables Receive mode for I2C
// 0 = Receive idle
clear_bit(sspcon2, RCEN);

// PEN: Stop Condition Enable bit (in I2C Master mode only)
// SCK Release Control:
// 1 = Initiate Stop condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Stop condition Idle
clear_bit(sspcon2, PEN);

// RSEN: Repeated Start Condition Enabled bit (in I2C Master mode only)
// 1 = Initiate Repeated Start condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Repeated Start condition Idle
clear_bit(sspcon2, RSEN);

// SEN: Start Condition Enabled bit (in I2C Master mode only)
// In Master mode:
// 1 = Initiate Start condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Start condition Idle
clear_bit(sspcon2, SEN);

}

// init i2c_slave init
void i2c_slave_init(char addr) {
sspadd = addr;

// set trisc pins
set_bit(trisc, 3);
set_bit(trisc, 4);

// SSPSTAT: SSP STATUS REGISTER
// ============================================================================
// 1 = Slew rate control disabled for standard speed mode (100 kHz and 1 MHz)
// 0 = Slew rate control enabled for high speed mode (400 kHz)
set_bit(sspstat, SMP);

// CKP = 0:
// 1 = Data transmitted on rising edge of SCK
// 0 = Data transmitted on falling edge of SCK
// CKP = 1:
// 1 = Data transmitted on falling edge of SCK
// 0 = Data transmitted on rising edge of SCK
clear_bit(sspstat, CKP);

// SSPCON: SSP CONTROL REGISTER 1
// ============================================================================
// WCOL: Write Collision Detect bit
// Master mode:
// 1 = A write to the SSPBUF register was attempted while the I2C conditions were not valid for a transmission to be started
// 0 = No collision
clear_bit(sspcon, WCOL);

// SSPOV: Receive Overflow Indicator bit
// 1 = A byte is received while the SSPBUF register is still holding the previous byte. SSPOV is a “don’t care” in Transmit
// mode (must be cleared in software).
// 0 = No overflow
clear_bit(sspcon, SSPOV);

// SSPEN: Synchronous Serial Port Enable bit
// 1 = Enables the serial port and configures the SDA and SCL pins as the source of the serial port pins
// 0 = Disables serial port and configures these pins as I/O port pins
set_bit(sspcon, SSPEN);

// CKP: Clock Polarity Select bit
// Unused in this mode
clear_bit(sspcon, CKP);

// SSPM<3:0>: Synchronous Serial Port Mode Select bits
// 0110 = I2C Slave mode, 7-bit address
// 0111 = I2C Slave mode, 10-bit address
// 1110 = I2C Slave mode, 7-bit address with Start and Stop bit interrupts enabled
// 1111 = I2C Slave mode, 10-bit address with Start and Stop bit interrupts enabled
clear_bit(sspcon, SSPM3);
set_bit(sspcon, SSPM2);
set_bit(sspcon, SSPM1);
clear_bit(sspcon, SSPM0);

// SSPCON2: SSP CONTROL REGISTER 2
// ============================================================================
// GCEN: General Call Enable bit (in I2C Slave mode only)
// 1 = Enable interrupt when a general call address (0000h) is received in the SSPSR
// 0 = General call address disabled
set_bit(sspcon2, GCEN);

// ACKDT: Acknowledge Data bit (in I2C Master mode only)
// In Master Receive mode:
// Value transmitted when the user initiates an Acknowledge sequence at the end of a receive
// 1 = Not Acknowledge
// 0 = Acknowledge
clear_bit(sspcon2, ACKDT);

// ACKEN: Acknowledge Sequence Enable bit (in I2C Master mode only)
// In Master Receive mode:
// 1 = Initiate Acknowledge sequence on SDA and SCL pins, and transmit ACKDT data bit.
// Automatically cleared by hardware.
// 0 = Acknowledge sequence idle
clear_bit(sspcon2, ACKEN);

// RCEN: Receive Enable bit (in I2C Master mode only)
// 1 = Enables Receive mode for I2C
// 0 = Receive idle
clear_bit(sspcon2, RCEN);

// PEN: Stop Condition Enable bit (in I2C Master mode only)
// SCK Release Control:
// 1 = Initiate Stop condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Stop condition Idle
clear_bit(sspcon2, PEN);

// RSEN: Repeated Start Condition Enabled bit (in I2C Master mode only)
// 1 = Initiate Repeated Start condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Repeated Start condition Idle
clear_bit(sspcon2, RSEN);

// SEN: Start Condition Enabled bit (in I2C Master mode only)
// In Master mode:
// 1 = Initiate Start condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Start condition Idle
clear_bit(sspcon2, SEN);

}

void i2c_start() {

// SEN: Start Condition Enabled bit (in I2C Master mode only)
// In Master mode:
// 1 = Initiate Start condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Start condition Idle
set_bit(sspcon2, SEN);

// wait for start condition to complete
while (test_bit(sspcon2, SEN)) {}
}

void i2c_stop() {

// PEN: Stop Condition Enable bit (in I2C Master mode only)
// SCK Release Control:
// 1 = Initiate Stop condition on SDA and SCL pins. Automatically cleared by hardware.
// 0 = Stop condition Idle
set_bit(sspcon2, PEN);

// wait for stop condition to complete
while (test_bit(sspcon2, PEN)) {}
}


void i2c_write_data(char c) {

sspbuf = c;

// SSPSTAT: SSP STATUS REGISTER
// BF: Buffer Full Status bit
// 1 = Data transmit in progress (does not include the ACK and Stop bits), SSPBUF is full
// 0 = Data transmit complete (does not include the ACK and Stop bits), SSPBUF is empty
while (test_bit(sspstat, BF)) {}

}

void i2c_write_addr(char c) {

// set write bit on address
set_bit(c, 7);

sspbuf = c;

// SSPSTAT: SSP STATUS REGISTER
// BF: Buffer Full Status bit
// 1 = Data transmit in progress (does not include the ACK and Stop bits), SSPBUF is full
// 0 = Data transmit complete (does not include the ACK and Stop bits), SSPBUF is empty
while (test_bit(sspstat, BF)) {}

}

char i2c_read() {

return sspbuf;
// Reset the SSPIF interrupt flag
clear_bit( pir1, SSPIF );

}

bool i2c_is_data_ready() {
// is there an SSP interupt?
if (test_bit(pir1, SSPIF)) {

	char i2c_data = 0;

	// Test if we have an overflow condition and clear it
	if (test_bit( sspcon, SSPOV )) {
		// Do a dummy read of the SSPBUF
		i2c_data = sspbuf;
		// Clear the overflow flag
		clear_bit( sspcon, SSPOV );

	} else {
		// determin the ssp status
		char i2c_status = 0;
		// Mask the status bits out from the other unimportant register bits
		i2c_status = ( sspstat & SSPSTAT_BIT_MASK );

		// SSPSTAT bits: S = 1, D_A = 0, R_W = 0, BF = 1
		// Master Write, Last Byte was an Address
		if ((i2c_status ^ 0b00001001 ) == 0) {
			// do a dummy read
			i2c_data = sspbuf;
			// Reset the SSPIF interrupt flag
			clear_bit( pir1, SSPIF );

		// Master Write, Last Byte was Data
		} else if ((i2c_status ^ 0b00101001 ) == 0) {
			return true;

		// Master Read, Last Byte was an Address
		} else if ((i2c_status ^ 0b00001100 ) == 0) {
			// do a dummy read
			i2c_data = sspbuf;

			// TODO: implement me

			// Reset the SSPIF interrupt flag
			clear_bit( pir1, SSPIF );

		// Master Read, Last Byte was Data
		} else if ((i2c_status ^ 0b00101100 ) == 0) {
			// do a dummy read
			i2c_data = sspbuf;

			// TODO: implement me

			// Reset the SSPIF interrupt flag
			clear_bit( pir1, SSPIF );

		// Master NACK
		} else if ((i2c_status ^ 0b00101000 ) == 0) {
			// do a dummy read
			i2c_data = sspbuf;

			// TODO: implement me

			// Reset the SSPIF interrupt flag
			clear_bit( pir1, SSPIF );
		}

	}
}

}

 

This is the master main program.

 

i2cmaster_test.c:

##include <system.h>
#include "../16f882_i2c.h"
#include "../serial.h"

#pragma CLOCK_FREQ 20000000
#pragma DATA _CONFIG1, _DEBUG_OFF & _LVP_OFF & _FCMEN_OFF & _IESO_OFF & \
				_BOR_ON & _CPD_OFF & _CP_OFF & _PWRTE_OFF \
				& _MCLRE_OFF & _HS_OSC
#pragma DATA _CONFIG2, _WRT_OFF & _BOR40V

void main() {
// disable analog on porta
ansel = 0;

// char buffers
char porta_buf = 0;

// outputs
trisa = porta_buf;

// serial
serial_init();

// I2C Communications
i2c_master_init();

delay_ms(1000);
puts("Ready");
puts_newline();
puts_newline();

while(1) {

	set_bit(porta_buf, 0);
	porta = porta_buf;

	puts("SSPSTAT: ");
	puts_byte(sspstat);
	puts('\t');
	puts("SSPCON: ");
	puts_byte(sspcon);
	puts('\t');
	puts("SSPCON2: ");
	puts_byte(sspcon2);
	puts('\t');
	puts("SSPADD: ");
	puts_byte(sspadd);
	puts('\t');
	puts("PIR1: ");
	puts_byte(pir1);
	puts_newline();

	// start
	i2c_start();
	// write addr
	i2c_write_addr(00000000b);
	// write data
	i2c_write_data(11111111b);
	// stop
	i2c_stop();

	clear_bit(porta_buf, 0);
	porta = porta_buf;

	delay_ms(1000);
}
}

 

This is the slave main program

 

i2cslave_test.c:

#include <system.h>
#include "../16f882_i2c.h"
#include "../serial.h"

#pragma CLOCK_FREQ 20000000
#pragma DATA _CONFIG1, _DEBUG_OFF & _LVP_OFF & _FCMEN_OFF & _IESO_OFF & \
				_BOR_ON & _CPD_OFF & _CP_OFF & _PWRTE_OFF & _WDT_OFF & _MCLRE_OFF & _HS_OSC
#pragma DATA _CONFIG2, _WRT_OFF & _BOR40V

void main() {

char data = 0;

serial_init();

i2c_slave_init(00000010b);

delay_ms(500);

puts("Ready");
puts_newline();

while (1) {


	puts("SSPSTAT: ");
	puts_byte(sspstat);
	puts('\t');
	puts("SSPCON: ");
	puts_byte(sspcon);
	puts('\t');
	puts("SSPCON2: ");
	puts_byte(sspcon2);
	puts('\t');
	puts("SSPADD: ");
	puts_byte(sspadd);
	puts('\t');
	puts("PIR1: ");
	puts_byte(pir1);
	puts_newline();

	if (i2c_is_data_ready()) {
		data = i2c_read();
		puts_newline();
		puts_newline();
		puts("got data: ");
		puts_byte(data);
		puts_newline();
		puts_newline();
	}

	delay_ms(10);
}
}

 

Incase it's helpful, this is my serial library

 

serial.h:

void serial_init() {
//	reg		7		6			5		4		3		2		1		0
//	BAUDCTL	ABDOVF	RCIDL		?		SCKP	BRG16	?		WUE		ABDEN
//	INTCON	GIE		PEIE		T0IE	INTE	RBIE	T0IF	INTF	RBIF
//	PIE1	?		ADIE		RCIE	TXIE	SSPIE	CCP1IE	TMR2IE	TMR1I

//	PIR1	?		ADIF		RCIF	TXIF	SSPIF	CCP1IF	TMR2IF	TMR1IF
//	RCREG	EUSART	Receive		Data	Register							
//	RCSTA	SPEN	RX9			SREN	CREN	ADDEN	FERR	OERR	RX9D
//	SPBRG	BRG7	BRG6		BRG5	BRG4	BRG3	BRG2	BRG1	BRG0
//	SPBRGH	BRG15	BRG14		BRG13	BRG12	BRG11	BRG10	BRG9	BRG8
//	TRISC	TRISC7	TRISC6		TRISC5	TRISC4	TRISC3	TRISC2	TRISC1	TRISC0
//	TXREG	EUSART	Transmit	Data	Register							
//	TXSTA	CSRC	TX9			TXEN	SYNC	SENDB	BRGH	TRMT	TX9D

//enable the transmiter & receiver for async
txsta.TXEN = 1;
rcsta.CREN = 1;

txsta.SYNC = 0;
rcsta.SPEN = 1;

//	FOSC = 20.000 MHz
//	SYNC = 0, BRGH = 1, BRG16 = 1 or SYNC = 1, BRG16 = 1
//	rate	actual	error	SPBRG
//	9600	9597	-0.03	520
//	19.2k	19.23k	0.16	259
//	57.6k	57.47k	-0.22	86
//	115.2k	116.3k	0.94	42

txsta.BRGH = 1;
baudctl.BRG16 = 1;

spbrg = 42;
spbrgh = 0;
}

void serial_write(char c) {
while (!pir1.TXIF);
	txreg = c;
}

char serial_read () {
while (!pir1.RCIF);
	return rcreg;
}

void gets(char *destination)
{
while ((*destination++ = serial_read()) != 0x0d); // wait until tx register is empty
	*--destination = 0;
}


void puts(char *source)
{
while (*source != 0) // wait until tx register is empty
	serial_write(*source++);
}	

void puts(char *sourcea, char *sourceb) {
while (*sourcea != 0) // wait until tx register is empty
	serial_write(*sourcea++);
while (*sourceb != 0) // wait until tx register is empty
	serial_write(*sourceb++);
}

void puts(char source) {
serial_write(source);
}

void puts_byte(char source) {
if (test_bit(source,7)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,6)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,5)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,4)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,3)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,2)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,1)) {
	puts("1");
} else {
	puts("0");
}
if (test_bit(source,0)) {
	puts("1");
} else {
	puts("0");
}
}

void puts_newline() {
serial_write(0x0d);
serial_write(0x0a);
}

Share this post


Link to post
Share on other sites

Rather a lot of code to wade through and I don't have the time so just a few comments:

 

I notice that in your i2c_is_data_ready() function you are clearing the SSPIF interrupt flag several times but have missed it after 'Master Write, Last Byte was Data'.

You only need to clear this flag once so re-arrange the code and do it at the beginning and remove all the other instances.

 

Secondly I would recommend that the slave receive function is carried out under interrupt with suitable data and address buffering rather than being polled.

I haven't used I2C with the 16F88* family but when using it with an 18F4520 I found that using set_bit(sspcon1, CKP); at the end of the interrupt

ensured that the clock line was always released thereby preventing lockups.

 

Regards

 

davidb

Share this post


Link to post
Share on other sites

Your right... I should have trimmed it down. That post is way out of hand... Honestly it's about 90% comments which nobody but me really cares about!

 

Thanks for your thoughtful observations by the way. I made some changes and reworked it a couple times since I posted all that above but eventually gave up. I'm just using a serial bus now, which was extremely easy to set up and works for my needs.

Share this post


Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...