I am trying to establish a half-duplex I2C communication between two ATSAMD21G18A microcontrollers, using Atmel Start’s I2C functionality. (HAL Synchronised I2C)
The automatically generated example files include only a write example on master, and only a read example on slave. I figured that I can use same function calls to do vice versa: writing from slave and reading at master.
However, while the code works perfectly fine if master writes to slave, if I modify the code so that slave writes to master, it behaves … unpredictably. Occasionally it appears to work, sometimes sends garbage, and mostly 255. After a few seconds, slave MCU freezes completely, no longer drives the bus and remains so until a power cycle. In the mean time, master MCU remains functional.
I am not sure if I am missing something obvious, maybe even in general for I2C. Example codes and my codes are given below. And few key points are as such:
- Actual read and write functions are, of course, different in master and slave devices, in the codes below, the same
io_write
andio_read
functions point to different functionsi2c_m_sync_write
(master) andi2c_s_sync_write
(slave) and so on, at the background. - I cannot really post actual functions that does the work, because there are 6 functions that sequentially call each other, for no apparent good reason. If I were to copy-paste them here, entire question would turn into an unintelligible mess.
- For the life of me I cannot do proper debugging because Microchip’s IDE flips me off and tells me that it failed to load .elf file. I have failed to solve this problem for months now. Therefore I cannot tell if the program gets stuck on an infinite loop somewhere, waits something or gets unrecoverably corrupted.
The fact that it works master to slave as provided on the examples fine, but not in the other way around, makes me hope that there is something trivial and obvious that I missed. In any case, any help or suggestions will be appreciated.
Provided write example on master device:
void I2C_0_example( void )
{
struct io_descriptor *I2C_0_io;
i2c_m_sync_get_io_descriptor( &I2C_0, &I2C_0_io );
i2c_m_sync_enable( &I2C_0 );
i2c_m_sync_set_slaveaddr( &I2C_0, 0x12, I2C_M_SEVEN );
io_write(I2C_0_io, (uint8_t *)"Hello World!", 12);
}
Provided read example on slave device
void I2C_0_example(void)
{
struct io_descriptor *io;
uint8_t c;
i2c_s_sync_get_io_descriptor( &I2C_0, &io );
i2c_s_sync_set_addr( &I2C_0, 0x12 );
i2c_s_sync_enable( &I2C_0 );
io_read( io, &c, 1 );
}
Based on which, I wrote:
For the Master:
uint8_t slavedata[ 64 ] = { 0 };
for ( ;; )
{
// attemps to read from the slave device
memset( slavedata, 0, sizeof slavedata);
if ( io_read( io, slavedata, sizeof slavedata) == sizeof slavedata )
{
usb_serial_puts( slavedata);
usb_serial_puts( "rn" );
}
// yes, io_read returns the passed size argument back if it succeeds,
// not the number of characters it actually read.
}
For the slave:
uint8_t data[ 64 ] = { 0 };
for ( ;; )
{
// populates the `data` array.
// example str: "0.000 0.000 0.000 0.000", null terminated
// format remains same all the time, values change.
memset( data, 0, sizeof data );
read_to_str( data, sizeof data );
// same as the example, only difference is this device is a slave device.
io_write( io, selfdata, sizeof selfdata );
// prints to a terminal over uart.
usb_serial_puts( "Sent: " );
usb_serial_puts( data );
usb_serial_puts( "rn" );
}