I2C intermittently working on SAML21J18B (SAML21 XPLAINED PRO)

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello. I'm a SAM noob learning to code them in C without ASF.

 

I'm trying to communicate my SAML21J18B with a I2C slave. My problem is that the I2C communication only starts working 1/5 of the times. It seems that the port starts floating and only sometimes does it pull up at start-up, even though the sensor has 1k pull-up external resistors. I've tried some tweaking on the GCLK, MCLK, PCHCTRL and PM registers to no avail.

 

Here's my code:

#define SW0				PORT_PA02
#define LED0				PORT_PB10
#define I2C_SDA				PINMUX_PA08C_SERCOM0_PAD0
#define I2C_SCL				PINMUX_PA09C_SERCOM0_PAD1
#define addr_SensDist		0xAE

#include "sam.h"
#include <string.h>

void i2cread(unsigned char *mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension);
void i2cwrite(unsigned char mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension);
void delaynops(uint32_t a);

int main(void) {
	//SystemInit();

	REG_PORT_DIRCLR0 = SW0;									//SW0 as input
	REG_PORT_DIRSET1 = LED0;								//LED0 as output
	PORT->Group[0].PMUX[4].reg = 0x22;						//C multiplexing on PA08 and PA09 for SERCOM0
	PORT->Group[0].PINCFG[2].reg = PORT_PINCFG_PULLEN | PORT_PINCFG_INEN ;		//for SW0: pull enable, input read enable
	PORT->Group[0].PINCFG[8].reg = PORT_PINCFG_PMUXEN;		//enable PMUX settings on PA08
	PORT->Group[0].PINCFG[9].reg = PORT_PINCFG_PMUXEN;		//enable PMUX settings on PA09
	REG_PORT_OUTSET0 = SW0 /* | I2C_SCL | I2C_SDA */ ;
	REG_PM_PLCFG = PM_PLCFG_PLSEL(0x2);

	//using main oscillator (4MHz)
	GCLK->GENCTRL[2].reg =		GCLK_GENCTRL_DIV(1)			| GCLK_GENCTRL_RUNSTDBY * 0 |
					GCLK_GENCTRL_DIVSEL * 0			| GCLK_GENCTRL_OE * 0 |
					GCLK_GENCTRL_OOV * 0			| GCLK_GENCTRL_IDC |
					GCLK_GENCTRL_GENEN			| GCLK_GENCTRL_SRC(0x06);

	REG_MCLK_APBCMASK =		MCLK_APBCMASK_SERCOM0;
	REG_MCLK_APBAMASK =		MCLK_APBAMASK_GCLK			| MCLK_APBAMASK_PM |
					MCLK_APBAMASK_MCLK ;

	//from GCLK implementation:   #define GCLK   ((Gclk     *)0x40001800UL) /**< \brief (GCLK) APB Base Address */
	GCLK->PCHCTRL[18].reg =		GCLK_PCHCTRL_WRTLOCK * 0		| GCLK_PCHCTRL_CHEN |
					GCLK_PCHCTRL_GEN_GCLK2;

	while ( REG_GCLK_PCHCTRL2 * GCLK_PCHCTRL_CHEN );

	REG_SERCOM0_I2CM_CTRLA =	SERCOM_I2CM_CTRLA_LOWTOUTEN		| SERCOM_I2CM_CTRLA_INACTOUT(0) |
					SERCOM_I2CM_CTRLA_SCLSM	* 0		| SERCOM_I2CM_CTRLA_SPEED(0) |
					SERCOM_I2CM_CTRLA_SEXTTOEN		| SERCOM_I2CM_CTRLA_MEXTTOEN |
					SERCOM_I2CM_CTRLA_SDAHOLD(0x3)	| SERCOM_I2CM_CTRLA_PINOUT * 0 |
					SERCOM_I2CM_CTRLA_RUNSTDBY * 0	| SERCOM_I2CM_CTRLA_MODE(0x5) |
					SERCOM_I2CM_CTRLA_SWRST * 0;

	REG_SERCOM0_I2CM_CTRLB =	SERCOM_I2CM_CTRLB_ACKACT * 0	| SERCOM_I2CM_CTRLB_CMD(0) |
					SERCOM_I2CM_CTRLB_QCEN			| SERCOM_I2CM_CTRLB_SMEN;

	REG_SERCOM0_I2CM_ADDR =		SERCOM_I2CM_ADDR_LEN(0)			| SERCOM_I2CM_ADDR_TENBITEN * 0 |
					SERCOM_I2CM_ADDR_HS * 0			| SERCOM_I2CM_ADDR_LENEN * 0;

	//f_ref = 4MHz ; f_baud = 333.333kHz
	REG_SERCOM0_I2CM_BAUD = SERCOM_I2CM_BAUD_BAUD(1);
	REG_SERCOM0_I2CM_DBGCTRL = SERCOM_I2CM_DBGCTRL_DBGSTOP * 0;
	REG_SERCOM0_I2CM_CTRLA |= SERCOM_I2CM_CTRLA_ENABLE;
	REG_SERCOM0_I2CM_STATUS |= SERCOM_I2CM_STATUS_BUSSTATE(0x1);		//set IDLE state
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE(0x1) ));

	uint8_t input_packets[2][7] = {	{ 0    , 0 , 0 , 0 , 0 , 0 , 0 } ,
					{ 0xD1 , 0 , 0 , 0 , 0 , 0 , 0 } };

	const uint8_t calibration_packets[2][7] = {	{ 0x49 , 0xA , 0xB , 0xC , 0xD , 0xE , 0xF } ,
							{ 0xB0 , 0x3 , 0x4 , 0x5 , 0x6 , 0x7 , 0x8 } };

	uint8_t len = strlen ( calibration_packets[0] );

	for ( uint8_t i = 0 ; i < len ; i++ ){
		i2cwrite ( calibration_packets[0][i] , addr_SensDist , calibration_packets[1][i] , 1 );
		asm("nop");
	}

	static uint16_t distance = 0;

	while (1) {
		i2cread ( input_packets , addr_SensDist , 0xD1 , 1 );
		distance = ((uint16_t) input_packets[0] << 8) | ((uint16_t) input_packets[1] );
		delaynops(30000);
	}
}

void i2cwrite(unsigned char mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension){
	uint8_t i = 0;

	//check idle port
	REG_SERCOM0_I2CM_STATUS |= SERCOM_I2CM_STATUS_BUSSTATE(0x1);		//set IDLE state
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE(0x1) ));

	//send start, send slave's address and write bit, await acknowledge
	REG_SERCOM0_I2CM_ADDR = slave_address;								//send start; send slave address; send write bit
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD) );	//await SCL held low
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//send addressed slave's location (0x00«00»), await acknowledge
	REG_SERCOM0_I2CM_DATA = memory_address;								//send memory_address' low byte
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD) );	//await SCL held low
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//write bytes
	uint8_t j = memory_extension - 1;
	do {
		REG_SERCOM0_I2CM_DATA = mensaje;								//prepare data to be sent
		while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));//await byte sent
		while ( !( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD ));	//await SCL held low
		while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );	//await acknowledge from slave
		i++;
	} while ( i <= j );
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(0x3);	//send NAK; send STOP
}

void i2cread(unsigned char *mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension){
	uint8_t i = 0;

	//check idle port
	REG_SERCOM0_I2CM_STATUS |= SERCOM_I2CM_STATUS_BUSSTATE(0x1);		//mandar estado a IDLE
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE(0x1) ));

	//send start, send slave's address and write bit, await acknowledge
	REG_SERCOM0_I2CM_ADDR = slave_address;								//send start; send slave address; send write bit
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//send addressed slave's location (0x00«00»), await acknowledge
	REG_SERCOM0_I2CM_DATA = memory_address;								//send memory_address' low byte
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//send restart, send slave's address and read bit, await acknowledge
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x1);				//send repeated start
	REG_SERCOM0_I2CM_ADDR |= slave_address | 0x01;						//send slave address; send read bit
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await byte sent
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT * 0;				//prepare to send ACKs (in smart mode)

	//read bytes
	uint8_t j = memory_extension - 1;
	for ( i = 0 ; i < j ; i++ ) {
		while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_SB ));//await byte received
		mensaje[i] = REG_SERCOM0_I2CM_DATA & 0xFF;						//save read byte
		//sending of ACK not needed, since when in smart mode, reading I2CM_DATA automatically sends ACK and clears INTFLAG_SB
		//REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x2);			//send ACK and read another byte
	}
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_SB ));	//await byte received
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(0x3);	//send NAK; send STOP
	mensaje[i] = REG_SERCOM0_I2CM_DATA & 0xFF;							//save read byte
}

void delaynops(uint32_t a){
	for(a = a ; a > 0 ; a--){
		a--;
	}
}

 

I know it's in no way the most organized, optimized, nor correctly type-casted code. All critique is appreciated, mostly about the main issue.

 

 

When the I2C doesn't work, the code stays in the following line in the start sequence in the i2cwrite function:

while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD) ); //await SCL held low

 

And here's how the I2C lines look like when it fails. It seems the SDA line doesn't initialize(?) correctly and stays low. That pulse is just a 1.5us long pulse.

 

 

 

And sometimes it just works:

 

 

 

 

I don't know what I'm missing, and I've been stuck on this for a week. Any advice is greatly appreciated.

 

Thanks in advance.

Last Edited: Wed. Jul 4, 2018 - 03:40 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is one problem - the arrays are not NUL terminated strings (0x00) so the strlen() function call will return a value much larger than you expect.  It will keep iterating through memory until it finds a NUL character.  Put a breakpoint on that line, step over the function, then examine the return value.

const uint8_t calibration_packets[2][7] = {	{ 0x49 , 0xA , 0xB , 0xC , 0xD , 0xE , 0xF } ,
							{ 0xB0 , 0x3 , 0x4 , 0x5 , 0x6 , 0x7 , 0x8 } };
	
	uint8_t len = strlen ( calibration_packets[0] );

 

Use sizeof() instead.  The length value is calculated at compile time.  Like this:

 

	for ( uint8_t i = 0 ; i < sizeof(calibration_packets[0]) ; i++ ){
	    
	    ...
	    
	    
	}

 

 

2.  The problem you are describing sounds like there is noise on the clock line that shows up as short pulses with the Saleae.  Put an oscilloscope on SCL and look for noise.  If present the slave could be misinterpreting the pulses as real clock signals and advancing its state machine prematurely then locking up.  First eliminate the noise source.  Then add software to recover from a stuck slave by clocking through the problem (sending a series of clock pulses to clear the slave).  See Analog Devices App Note AN-686 "Implementing an I2C Reset" for details.  Excerpt below:

 

The procedure is as follows:
1) Master tries to assert a Logic 1 on the SDA line
2) Master still sees a Logic 0 and then generates a clockpulse on SCL (1-0-1 transition)
3) Master examines SDA. If SDA = 0, go to Step 2; if SDA = 1, go to Step 4
4) Generate a STOP condition

 

A closer look at your failure screenshots show that same pulse on both SCL and SDA.  That's the source of the problem locking up the slave.  Why it is occurring is the mystery to be solved.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ScottMN, thanks for the reply! I'm looking into your advices.

 

1. Doesn't the compiler automatically add the NULL terminators at the end of the strings? I ask, because when I add them, the following warning shows: "excess elements in array initializer".

 

I didn't know about the sizeof() operator. Thanks; it seems very handy!

 

 

2. Just to clarify, the second image of the Saleae Logic Software is of when the I2C write routine does work. Those "pulses", both on SDA and SCL, are in reality what the next zoomed-in picture shows of the first i2cwrite() function in the code (I put a breakpoint on the asm("nop"); line just to show that the i2cwrite() function works).

 

I understand the solution you propose. I guess it's possible that the I2C slave doesn't timeout once noise on the reset makes it advance some machine state, and it retains the SDA line down. I'll look into the datasheet to see how to manually send SCL signals and check status / SDA port.

 

Could it be that the test jig connection is not the most streamline? (see picture below)

 

 

Thanks again! Working on it.

Last Edited: Tue. Jul 3, 2018 - 09:12 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

update:

- I tried cleaning the original I2C slave's PCB with a toothbrush and alcohol, and it stopped working altogether. I inspected it and it seems some pins were feebly making contact with the traces, and prone to be easily disconnected on the cleanup.

- I got another I2C slave's PCB soldered. I'm now sure this one is well soldered. It now acts as the original one at first. The problem persists. It must be software related.

 

I've been messing some more with the code to no avail. Mostly about its oscillators and generic clock controllers. I'm guessing I'm missing something about the SAM's initialization, maybe something on its power saving's registers. Here's the new code that still only works intermittently:

 

#define SW0				PORT_PA02
#define LED0				PORT_PB10
#define I2C_SDA				PINMUX_PA08C_SERCOM0_PAD0
#define I2C_SCL				PINMUX_PA09C_SERCOM0_PAD1
#define addr_SensDist		0xAE

#include "sam.h"
#include <string.h>

void i2cread(unsigned char *mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension);
void i2cwrite(unsigned char mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension);
void delaynops(uint32_t a);

int main(void) {
// 	SystemInit();

	REG_PORT_DIRCLR0 = SW0;						//SW0 as input
	REG_PORT_DIRSET1 = LED0;					//LED0 as output
	PORT->Group[0].PMUX[4].reg = 0x22;				//C multiplexing on PA08 and PA09 for SERCOM0
	PORT->Group[0].PINCFG[2].reg = PORT_PINCFG_PULLEN | PORT_PINCFG_INEN ;		//for SW0: pull enable, input read enable
	PORT->Group[0].PINCFG[8].reg = PORT_PINCFG_PMUXEN;		//enable PMUX settings on PA08
	PORT->Group[0].PINCFG[9].reg = PORT_PINCFG_PMUXEN;		//enable PMUX settings on PA09
	REG_PORT_OUTSET0 = SW0 /* | I2C_SCL | I2C_SDA */ ;
 	REG_PM_PLCFG = PM_PLCFG_PLSEL(0x2);

	//using main oscillator (16MHz) on 4MHz
	REG_OSCCTRL_OSC16MCTRL =	OSCCTRL_OSC16MCTRL_ENABLE		| OSCCTRL_OSC16MCTRL_ONDEMAND |
					OSCCTRL_OSC16MCTRL_FSEL(0x00);

	REG_GCLK_GENCTRL0 =		GCLK_GENCTRL_DIV(1)			| GCLK_GENCTRL_RUNSTDBY * 0 |
					GCLK_GENCTRL_DIVSEL * 0			| GCLK_GENCTRL_OE * 0 |
					GCLK_GENCTRL_OOV * 0			| GCLK_GENCTRL_IDC * 0 |
					GCLK_GENCTRL_GENEN			| GCLK_GENCTRL_SRC(0x06);

	REG_GCLK_GENCTRL1 =		GCLK_GENCTRL_DIV(11)			| GCLK_GENCTRL_RUNSTDBY * 0 |
					GCLK_GENCTRL_DIVSEL * 0			| GCLK_GENCTRL_OE * 0 |
					GCLK_GENCTRL_OOV * 0			| GCLK_GENCTRL_IDC * 0 |
					GCLK_GENCTRL_GENEN			| GCLK_GENCTRL_SRC(0x06);

// 	GCLK->GENCTRL[2].reg =		GCLK_GENCTRL_DIV(1)			| GCLK_GENCTRL_RUNSTDBY * 0 |
// 					GCLK_GENCTRL_DIVSEL * 0			| GCLK_GENCTRL_OE * 0 |
// 					GCLK_GENCTRL_OOV * 0			| GCLK_GENCTRL_IDC |
// 					GCLK_GENCTRL_GENEN			| GCLK_GENCTRL_SRC(0x06);

	REG_MCLK_CPUDIV = 0x01;
	REG_MCLK_LPDIV = 0x04;
	REG_MCLK_BUPDIV = 0x01;
// 	REG_MCLK_INTFLAG = 0x01;

// 	REG_MCLK_APBCMASK =		MCLK_APBCMASK_SERCOM0;
// 	REG_MCLK_APBAMASK =		MCLK_APBAMASK_GCLK			| MCLK_APBAMASK_PM |
// 					MCLK_APBAMASK_MCLK ;

	//from GCLK implementation:   #define GCLK   ((Gclk     *)0x40001800UL) /**< \brief (GCLK) APB Base Address */
	GCLK->PCHCTRL[18].reg =		GCLK_PCHCTRL_CHEN			| GCLK_PCHCTRL_GEN(0x1);
	GCLK->PCHCTRL[17].reg =		GCLK_PCHCTRL_CHEN			| GCLK_PCHCTRL_GEN(0x1);

	while ( REG_GCLK_PCHCTRL2 * GCLK_PCHCTRL_CHEN );

	REG_SERCOM0_I2CM_CTRLA =	SERCOM_I2CM_CTRLA_SCLSM*0		| SERCOM_I2CM_CTRLA_SPEED(0) |
					SERCOM_I2CM_CTRLA_PINOUT * 0 |
					SERCOM_I2CM_CTRLA_MODE(0x5) |
					SERCOM_I2CM_CTRLA_SWRST * 0;

	REG_SERCOM0_I2CM_CTRLB =	SERCOM_I2CM_CTRLB_ACKACT * 0	| SERCOM_I2CM_CTRLB_CMD(0) |
					SERCOM_I2CM_CTRLB_QCEN			| SERCOM_I2CM_CTRLB_SMEN;

	REG_SERCOM0_I2CM_ADDR =		SERCOM_I2CM_ADDR_LEN(0)			| SERCOM_I2CM_ADDR_TENBITEN * 0 |
					SERCOM_I2CM_ADDR_HS * 0			| SERCOM_I2CM_ADDR_LENEN * 0;

	//f_ref = 4MHz ; f_baud = 333.333kHz
	REG_SERCOM0_I2CM_BAUD = SERCOM_I2CM_BAUD_BAUD(0x00000011);
// 	REG_SERCOM0_I2CM_DBGCTRL = SERCOM_I2CM_DBGCTRL_DBGSTOP * 0;
	REG_SERCOM0_I2CM_CTRLA |= SERCOM_I2CM_CTRLA_ENABLE;
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x3);
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x3);
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x3);
	REG_SERCOM0_I2CM_INTFLAG = SERCOM_I2CM_INTFLAG_ERROR;
	REG_SERCOM0_I2CM_STATUS |= SERCOM_I2CM_STATUS_BUSSTATE(0x1);		//set IDLE state
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE(0x1) ));

	uint8_t input_packets[2][7] = {	{ 0x00 , 0 , 0 , 0 , 0 , 0 , 0 } ,
					{ 0xD1 , 0 , 0 , 0 , 0 , 0 , 0 } };

	const uint8_t calibration_packets[2][8] = {	{ 0x49 , 0xA , 0xB , 0xC , 0xD , 0xE , 0xF , 0x00 } ,
							{ 0xB0 , 0x3 , 0x4 , 0x5 , 0x6 , 0x7 , 0x8 , 0x00 } };

	for ( uint8_t i = 0 ; i < sizeof(calibration_packets[0]) ; i++ ){
		i2cwrite ( calibration_packets[0][i] , addr_SensDist , calibration_packets[1][i] , 1 );
		asm("nop");
	}

	static uint16_t distance = 0;

	while (1) {
		for ( uint8_t i = 0 ; i < sizeof(input_packets[0]) ; i++ ){
			i2cread ( input_packets[0][i] , addr_SensDist , input_packets[1][i] , 1 );
		}
		distance = ((uint16_t) input_packets[0] << 8) | ((uint16_t) input_packets[1] );
		if ( distance > 10000 )
			REG_PORT_OUTSET1 = LED0;
		else
			REG_PORT_OUTCLR1 = LED0;
		delaynops(30000);
	}
}

void i2cwrite(unsigned char mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension){
	uint8_t i = 0;

	//check idle port
	REG_SERCOM0_I2CM_INTFLAG = SERCOM_I2CM_INTFLAG_ERROR;
	REG_SERCOM0_I2CM_STATUS |= SERCOM_I2CM_STATUS_BUSSTATE(0x1);		//set IDLE state
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE(0x1) ));

	//send start, send slave's address and write bit, await acknowledge
	REG_SERCOM0_I2CM_ADDR = slave_address;					//send start; send slave address; send write bit
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD) );	//await SCL held low
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//send addressed slave's location (0x00«00»), await acknowledge
	REG_SERCOM0_I2CM_DATA = memory_address;					//send memory_address' low byte
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD) );	//await SCL held low
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//write bytes
	uint8_t j = memory_extension - 1;
	do {
		REG_SERCOM0_I2CM_DATA = mensaje;				//prepare data to be sent
		while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));//await byte sent
		while ( !( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_CLKHOLD ));	//await SCL held low
		while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );	//await acknowledge from slave
		i++;
	} while ( i <= j );
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(0x3);	//send NAK; send STOP
}

void i2cread(unsigned char *mensaje, unsigned char slave_address, unsigned short memory_address, unsigned int memory_extension){
	uint8_t i = 0;

	//check idle port
	REG_SERCOM0_I2CM_STATUS |= SERCOM_I2CM_STATUS_BUSSTATE(0x1);		//set IDLE status
	while ( !(REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_BUSSTATE(0x1) ));

	//send start, send slave's address and write bit, await acknowledge
	REG_SERCOM0_I2CM_ADDR = slave_address;					//send start; send slave address; send write bit
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//send addressed slave's location (0x00«00»), await acknowledge
	REG_SERCOM0_I2CM_DATA = memory_address;					//send memory_address' low byte
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await Master on Bus flag
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave

	//send restart, send slave's address and read bit, await acknowledge
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x1);			//send repeated start
	REG_SERCOM0_I2CM_ADDR |= slave_address | 0x01;				//send slave address; send read bit
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_MB ));	//await byte sent
	while ( REG_SERCOM0_I2CM_STATUS & SERCOM_I2CM_STATUS_RXNACK );		//await acknowledge from slave
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT * 0;			//prepare to send ACKs (in smart mode)

	//read bytes
	uint8_t j = memory_extension - 1;
	for ( i = 0 ; i < j ; i++ ) {
		while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_SB ));//await byte received
		mensaje[i] = REG_SERCOM0_I2CM_DATA & 0xFF;			//save read byte
		//sending of ACK not needed, since when in smart mode, reading I2CM_DATA automatically sends ACK and clears INTFLAG_SB
		//REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_CMD(0x2);		//send ACK and read another byte
	}
	while ( !( REG_SERCOM0_I2CM_INTFLAG & SERCOM_I2CM_INTFLAG_SB ));	//await byte received
	REG_SERCOM0_I2CM_CTRLB |= SERCOM_I2CM_CTRLB_ACKACT | SERCOM_I2CM_CTRLB_CMD(0x3);	//send NAK; send STOP
	mensaje[i] = REG_SERCOM0_I2CM_DATA & 0xFF;						//save read byte
}

void delaynops(uint32_t a){
	for(a = a ; a > 0 ; a--){
		a--;
	}
}

 

Any hint would be greatly appreciated.

Last Edited: Mon. Jul 9, 2018 - 02:45 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Did you put the oscilloscope on the SDA and SCL lines?  Setup triggering for positive or negative pulses less than 1.25us (less than the 1/2 wave clock speed for your application).  Any glitches on SCL will cause the slave to improperly advance its state machine causing the master and slave to be out of sync.  The glitches will not show up on the Saleae, you need an oscilloscope to see them.

 

Also check the signal levels with the scope.  All signals must be full logic zero and full logic one.  Anything in between is a problem.  It's probable that during a failure both the master and slave are driving the lines at the same time which should never happen.  That's likely why the SDA line is not going high when you expect it - the slave is holding it low.  Why it's being held low needs investigation.  Clock glitches is a common failure leading to bus contention - two or more devices driving the lines at the same time.  Start here.

 

The other less likely scenario is "clock stretching" by the slave.  When the slave needs more time to process a command it will hold SCL low to signal to the master that it's not ready for more commands.  If the slave locks up in this state the only way to recover is power cycling.  This is why there are "wait for SCL to be idle" while() loops throughout the code.  At this point though I'm betting the problem is not clock stretching - it's not that common of a problem and rarely if ever shows up at the low speeds of your SCL.

 

Final thoughts after rereading the first post - your slave device is holding lines low (driving the lines) when you are not expecting it.  On power up the reason the lines "seems that the port starts floating and only sometimes does it pull up at start-up" is the slave driving the lines low.  If the slave is actively pulling SDA or SCL low, the pull-ups do nothing.  Why is the slave holding lines low?  Are you waiting long enough after power up for the slave to reset before talking to it?  Check the slave datasheet, it should list the hard reset max period?  Are you monitoring SDA and SCL to go high after a hard reset?  Try adding something like this prior to the for() loop and the first write calls to the slave:

 

while ( <SCL is low> && <SDA is low> ) ;               // after power up, wait for both SCL and SDA to be idle (high) before continuing

 

Alternately, temporarily put a long delay after initializing the master but prior to talking to any slaves, on the order of a second or two.

Last Edited: Mon. Jul 16, 2018 - 05:26 PM