Atmel Start I2C write and read Async

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

Hi,

I have 2 micro-controllers talking each other via I2C set with Atmel Start.

There is a SAMD11 as master writing one byte and requesting the slave for another byte.

There is a SAMD21 as slave receiving the master byte and transmitting upon request.

 

Apparently i have no prbs on Master write activity but when it comes to master read everything become unstable and unreliable.

I'm under the impression that i'm doing something wrong in the read activity but unfortunately atmel start does not provide example for async read (or at least i haven't found)

 

These are the two codes

 

SAMD11 Master

#include <atmel_start.h>

volatile uint8_t data_out;
volatile uint8_t data_in;
volatile uint8_t reg;
struct io_descriptor *I2C_0_io;


void I2C_0_tx_complete(struct i2c_m_async_desc *const i2c)
{

}

void I2C_0_rx_complete(struct i2c_m_async_desc *const i2c)
{

}

void I2C_init(void)
{
	i2c_m_async_get_io_descriptor(&I2C_0, &I2C_0_io);
	i2c_m_async_enable(&I2C_0);
	i2c_m_async_register_callback(&I2C_0, I2C_M_ASYNC_TX_COMPLETE, (FUNC_PTR)I2C_0_tx_complete);
	i2c_m_async_register_callback(&I2C_0, I2C_M_ASYNC_RX_COMPLETE, (FUNC_PTR)I2C_0_rx_complete);
	i2c_m_async_set_slaveaddr(&I2C_0, 0x12, I2C_M_SEVEN);
}

int main(void)
{
	/* Initializes MCU, drivers and middleware */
	atmel_start_init();
	I2C_init();

	PORT->Group[0].DIRSET.reg = PORT_PA05;
	
	data_out = 0xFF;

	io_write(I2C_0_io, &data_out, 1); // write on byte
	delay_ms(1);

	/* Replace with your application code */
	while (1) 
	{
		io_read(I2C_0_io, &data_in, 1); // read one byte

		if(data_in == 0xFE) // check if the data read is present and correct
		{
			PORT->Group[0].OUTSET.reg = PORT_PA05;
		}
		else
		{
			PORT->Group[0].OUTCLR.reg = PORT_PA05;
		}
	}
}

 

SAMD21 Slave

#include <atmel_start.h>

static struct io_descriptor *io;
volatile uint8_t data_in;
volatile uint8_t data_out;

static void I2C_rx_complete(const struct i2c_s_async_descriptor *const descr)
{
	io_read(io, &data_in, 1); // read one byte
}

static void I2C_tx_pending(const struct i2c_s_async_descriptor *const descr)
{
	io_write(io, &data_out, 1); //answer master request writing one byte
	delay_ms(1);
}

void I2C_init(void)
{
	i2c_s_async_get_io_descriptor(&I2C_0, &io);
	i2c_s_async_register_callback(&I2C_0, I2C_S_RX_COMPLETE, I2C_rx_complete);
	i2c_s_async_register_callback(&I2C_0, I2C_S_TX_PENDING, I2C_tx_pending);
	i2c_s_async_enable(&I2C_0);
}

int main(void)
{
	/* Initializes MCU, drivers and middleware */
	atmel_start_init();
	I2C_init();

	PORT->Group[1].DIRSET.reg = PORT_PB30;
	PORT->Group[1].OUTSET.reg = PORT_PB30;

	data_out = 0xFE;

	/* Replace with your application code */
	while (1) 
	{
		if(data_in == 0xFF) // check if the master write byte is present and correct
		{
			PORT->Group[1].OUTCLR.reg = PORT_PB30;
		}
	}
}

Is the above correct in your opinion?

Is there someone with some kind of example of master async read activity?

 

Thanks a lot in advance,

 

Filo

This topic has a solution.

Last Edited: Sat. Nov 11, 2017 - 03:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am no expert on SAM devices, but I use I2C, and as far as I know I2C cannot operate asynchronously as the slave devices will not take control of the bus unless polled by the master.  When the master wants to read a slave, it asserts the START condition, followed by the appropriate address/command byte, and any added bytes to complete the poll, once this is done the slave responds accordingly.

 

Do you have 2k2 pullup resistors on your data lines by the way?

 

Jim

If you want a career with a known path - become an undertaker. Dead people don't sue! - Kartman

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB user

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

The slave write on I2C MUST follow the master clock. Slave puts the data onto the data line on one  clock edge (don't remember which) generated by the master and the master reads it on the opposite edge of its own clock.

 

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

The master must ack each byte received from the slave except for the last byte, it must be nak'd, this tells the slave the master wants no more data, and releases the buss so the master can send stop!

 

Jim

 

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

We have not heard from the OP so lets not pile on more and more information until needed.  But I would gather it safe to say that I2C is not Asynchronous.

 

Jim

If you want a career with a known path - become an undertaker. Dead people don't sue! - Kartman

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB user

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

Yes I2C is a synchronous bus.  But the drivers from Atmel come in two flavors - synchronous and asynchronous.  The synchronous drivers block until the operation complete.  So a call to io_write() with the synchronous driver does not return until the operation sends the data across the I2C bus.  When using the asynchronous driver, a call to io_write() returns immediately with the driver calling a user callback functions the operation completes.  Using the asynchronous drivers lets the upper level application do other things in parallel to the I2C physical transfer.

 

The same driver model exists for UARTS/USARTs - synchronous drivers blocking on the function call until complete, and asynchronous drivers that return immediately then use callbacks to signal the operation is complete.

 

OP - your master is using the async I2C driver talking to slave address 0x12.  The slave is also using the async drivers but it's not clear what address it's listening too.  First make sure your slave is listening to address 0x12.  I've seen problems with Atmel START/ASF4 where the slave address in START does not get translated to the generated code properly.  Your code snippet above doesn't show the slave driver setup details.  It's probably buried in the details of atmel_start_init() where I2C_0 is defined.

 

Next the callback functions are used to tell you when the operation is complete.  So for master transmitting you could set a global flag bTxComplete=false, call io_write(), and have the callback function set bTxComplete=true.  Then the main loop does not need the delay_ms() call, instead put the app in a polling loop on while (bTxComplete == false) {... do stuff...}.  Same thing on the slave side with the receiver.

 

Another baby step would be getting the synchronous drivers working first to validate the hardware, then move up to the async drivers with it's added parallelism complexity.

 

 

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

ScottMN wrote:
Yes I2C is a synchronous bus.  But the drivers from Atmel come in two flavors - synchronous and asynchronous. 

Pretty sure I made a post to that effect yesterday - but it seems to have disappeared!

 

surprise

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

Andy
I never saw a post from you in this thread....
Maybe you missed hitting POST?
Jim

If you want a career with a known path - become an undertaker. Dead people don't sue! - Kartman

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB user

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

jgmdesign wrote:
Maybe you missed hitting POST?
Presumably.

 

blush

 

Further comment awaiting OP's return ...

 

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

Hi all,

first of all thanks everybody for all the answers.

 

I confirm that when I'm referring to async concept in this thread I mean the IRQ/event handler software approach rather then the I2C protocol characteristic which obviously is synchronous. Sorry if i was not clear enough.

 

The circuit is set with two pull up resistors on SDA and SCL of 4.7 Kohm

 

@ScottMn: I did dig into the code of the slave I2C device and i have found "#define CONF_SERCOM_0_I2CS_ADDRESS 0x12" inside the hpl_sercom_config.h file which apparently is the declaration of the slave address.

Anyway I'll do the following:

1) i'll try to set again the slave address using i2c_s_async_set_addr function inside the init function of the main file

2) if 1. doesn't work I'll set the synch setup first and then move again to asynch when first is proved as working.

 

Thanks again.

 

Filo

 

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

fcampanini wrote:
I have 2 micro-controllers talking each other via I2C set with Atmel Start.

Whenever trying to make two systems communicate, it is best not to try to do both ends at once!

 

The trouble with trying to do both ends at once is that, when it doesn't work, you don't know if that's because the sender isn't sending properly, or the receiver isn't receiving properly, or your connections and/or hardware are faulty - or some combination of all of these!

 

With I2C, the Master is the far more common use-case in a microcontroller - so I would suggest that you start there.

 

Get your I2C master working with some standard I2C chip - such as an IO expander.

 

Instrument your code - eg, with UART trace output - so that you can see & understand what happens when it is working properly.

 

When this is all working, try deliberately introducing some faults - to see how your master behaves/responds in such situations; eg,

  • No slave connected
  • No pullups
  • Wrong slave address

 

Then - and only then - create your Slave to "mimic" the chip you used above.

 

 

EDIT

 

Added other possible failure modes.

 

#DivideAndConquer

 

Last Edited: Mon. Nov 13, 2017 - 10:27 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

after making some test I've managed to make it working.

I put below the working code cause i haven't found examples of slave asynch and it might be useful.

 

Some consideration:

1) in the end the fact of specifying the address in spite of define CONF_SERCOM_0_I2CS_ADDRESS 0x12 looks useful which makes me really say that there must be issues about this aspect on the atmel start

2) I understood that the stacking function was the reading on the master. I took out the delay_ms(1) below the writing on both master and slave and it worked. I guess that the delay was somehow putting the synch communication out of order (error was bus busy)

 

Some further consideration:

1) Atmel Start is really buggy :-( during the A/B testing I have tried to add and take away drivers in particular timer/counter. Taking out the timer driver was crashing the code, I was not able to compile anymore!! Forcing me to restart from scratch

2) @awneil . I'm totally with you, the best is always test one at a time. But what is the advantage of using a framework like the ASF4 if you are forced to test side by side the driver? Before and sometime instead your application code?

3) Atmel start is not good for now surely for production (ok, that's said by Atmel...) and neither for prototyping. I am prototyping and as of now I spent 75% of my time testing Atmel start and 25% my app code. It should be the other way around

 

SAMD11 Master

#include <atmel_start.h>

volatile uint8_t data_out;
volatile uint8_t data_in;
volatile uint8_t reg;
struct io_descriptor *I2C_0_io;
static struct timer_task TIMER_0_task1;

uint8_t test;

void I2C_tx_complete(struct i2c_m_async_desc *const i2c)
{
	test = data_in;
}

void I2C_rx_complete(struct i2c_m_async_desc *const i2c)
{

}

void I2C_init(void)
{
	i2c_m_async_get_io_descriptor(&I2C_0, &I2C_0_io);
	i2c_m_async_register_callback(&I2C_0, I2C_M_ASYNC_TX_COMPLETE, (FUNC_PTR)I2C_tx_complete);
	i2c_m_async_register_callback(&I2C_0, I2C_M_ASYNC_RX_COMPLETE, (FUNC_PTR)I2C_rx_complete);
	i2c_m_async_set_slaveaddr(&I2C_0, 0x12, I2C_M_SEVEN);
	i2c_m_async_enable(&I2C_0);
}

static void TIMER_task1_cb(const struct timer_task *const timer_task)
{
	if(data_out == 0xFf)
	{
		data_out = 0x00;
	}
	else if(data_out == 0x00)
	{
		data_out = 0xFF;
	}
}

void TIMER_init(void)
{
	TIMER_0_task1.interval = 500;
	TIMER_0_task1.cb       = TIMER_task1_cb;
	TIMER_0_task1.mode     = TIMER_TASK_REPEAT;

	timer_add_task(&TIMER_0, &TIMER_0_task1);
	timer_start(&TIMER_0);
}

int main(void)
{
	/* Initializes MCU, drivers and middleware */
	atmel_start_init();
	I2C_init();
	TIMER_init();

	PORT->Group[0].DIRSET.reg = PORT_PA05;

	/* Replace with your application code */
	while (1)
	{
		io_write(I2C_0_io, &data_out, 1); // write on byte
		io_read(I2C_0_io, &data_in, 1); // read one byte

		if(data_in == 0xFE) // check if the data read is present and correct
		{
			PORT->Group[0].OUTSET.reg = PORT_PA05;
		}
		else if(data_in == 0x00)
		{
			PORT->Group[0].OUTCLR.reg = PORT_PA05;
		}
	}
}

 

SAMD21 Slave

#include <atmel_start.h>

#define SLAVE_ADDR 0x12

static struct io_descriptor *io;
static struct timer_task TIMER_0_task1;

volatile uint8_t data_in;
volatile uint8_t data_out;

static void I2C_rx_complete(const struct i2c_s_async_descriptor *const descr)
{
	io_read(io, &data_in, 1); // read one byte
}

static void I2C_tx_pending(const struct i2c_s_async_descriptor *const descr)
{
	io_write(io, &data_out, 1); //answer master request writing one byte
}

void I2C_init(void)
{
	i2c_s_async_get_io_descriptor(&I2C_0, &io);
	i2c_s_async_register_callback(&I2C_0, I2C_S_RX_COMPLETE, I2C_rx_complete);
	i2c_s_async_register_callback(&I2C_0, I2C_S_TX_PENDING, I2C_tx_pending);
	i2c_s_async_set_addr(&I2C_0, SLAVE_ADDR);
	i2c_s_async_enable(&I2C_0);
}

/**
 * Example of using TIMER_0.
 */
static void TIMER_task1_cb(const struct timer_task *const timer_task)
{
	if(data_out == 0xFE)
	{
		data_out = 0x00;
	}
	else if(data_out == 0x00)
	{
		data_out = 0xFE;
	}
	PORT->Group[1].OUTTGL.reg = PORT_PB00;
}

void TIMER_init(void)
{
	TIMER_0_task1.interval = 1000;
	TIMER_0_task1.cb       = TIMER_task1_cb;
	TIMER_0_task1.mode     = TIMER_TASK_REPEAT;

	timer_add_task(&TIMER_0, &TIMER_0_task1);
	timer_start(&TIMER_0);
}

int main(void)
{
	/* Initializes MCU, drivers and middleware */
	atmel_start_init();
	TIMER_init();
	I2C_init();

	PORT->Group[1].DIRSET.reg = PORT_PB30;
	PORT->Group[1].OUTSET.reg = PORT_PB30;

	PORT->Group[1].DIRSET.reg = PORT_PB00;

	//data_out = 0xFE;

	/* Replace with your application code */
	while (1)
	{
		if(data_in == 0xFF) // check if the master write byte is present and correct
		{
			PORT->Group[1].OUTCLR.reg = PORT_PB30;
		}
		else if(data_in == 0x00)
		{
			PORT->Group[1].OUTSET.reg = PORT_PB30;
		}
	}
}

Thanks again everybody

 

Filo

Last Edited: Sat. Nov 11, 2017 - 03:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

fcampanini wrote:
2) @awneil . I'm totally with you, the best is always test one at a time. But what is the advantage of using a framework like the ASF4...

Whether or not you use any framework is irrelevant.

 

The point is that you get one side working & solid first.