Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2C example software #76

Open
gosha-z opened this issue Nov 15, 2022 · 9 comments
Open

I2C example software #76

gosha-z opened this issue Nov 15, 2022 · 9 comments

Comments

@gosha-z
Copy link

gosha-z commented Nov 15, 2022

Hello!

Using Efinix version of SaxonSoC (T120F324 devkit), trying to use i2c as master to communicate with plain slave peripheral. Is there an example to communicate SaxonSoC with plain i2c slave?

Thanks
Igor

@Dolu1990
Copy link
Member

@gosha-z
Copy link
Author

gosha-z commented Nov 16, 2022

Hi,

I saw that, but it doesn't explain how to implement usual ш2с read/write sequence. I've tried the following code to read register reg (unisigned 8bit variable):

i2c_masterStartBlocking(I2C_CTRL);
i2c_txByte(I2C_CTRL,(DEV_ADDR << 1) & 0xFE); // To be sure that LSB=0
i2c_listenAck(I2C_CTRL);
while(!i2c_rxAck(I2C_CTRL));
i2c_txByte(I2C_CTRL,reg);
i2c_listenAck(I2C_CTRL);
while(!i2c_rxAck(I2C_CTRL));
i2c_masterStartBlocking(I2C_CTRL);
i2c_txByte(I2C_CTRL,(DEV_ADDR << 1) | 0x01); // To be sure that LSB=1
i2c_listenAck(I2C_CTRL);
while(!i2c_rxAck(I2C_CTRL));
val = i2c_rxData(I2C_CTRL);
i2c_masterStopBlocking(I2C_CTRL);

Looking into i2c protocol exchange on oscilloscope I didn't see second START condition even I see second successful ACK
Why?

And where I can find detailed timing register description (sample clock divider, Tlow, Thigh, Tsudat, Tbuf)? Not found it somewhere in the repository

Thanks in advance
Igor

@Dolu1990
Copy link
Member

Ahhh,

Instead of
while(!i2c_rxAck(I2C_CTRL));
i2c_masterStartBlocking(I2C_CTRL);
i2c_txByte(I2C_CTRL,(DEV_ADDR << 1) | 0x01); // To be sure that LSB=1

Can you do a :
while(!i2c_rxAck(I2C_CTRL));
i2c_masterStopBlocking(I2C_CTRL);
i2c_masterStartBlocking(I2C_CTRL);
i2c_txByte(I2C_CTRL,(DEV_ADDR << 1) | 0x01); // To be sure that LSB=1

Or you need specificaly a I2C restart sequance ?

sample clock divider, Tlow, Thigh, Tsudat, Tbuf

Hmmm following : https://github.com/SpinalHDL/SaxonSoc/blob/dev-0.3/software/standalone/i2cMcp4725/src/main.c#L26

sample clock divider -1 = number of clocks between each io sample on the i2c bus (to avoid glitch issues)
Tlow = number of cycles the SCL is put keept low by the master (clock generation)
Thigh = number of cycles the SCL is put keept high by the master (clock generation)
Tsudat = number of cycles that the controller will extends SCL low after providing data on SDA
Tbuf = number of cycles between a I2C stop and I2C start (as a master)

let's me know how it goes ^^

@gosha-z
Copy link
Author

gosha-z commented Nov 16, 2022

If I2C Master communicates with slave device that have a registers, exchange sequence usually performed the following way:

  1. Master send START
  2. Master sends slave addr with LSB=0 (write operation)
  3. Master waits for slave Ack
  4. Master sends register address
  5. Master waits for slave Ack
  6. Master send START again without sending STOP
  7. Master send slave addr with LSB=1 (read operation)
  8. Master waits for slave Ack
  9. Master receives register value
  10. master sends Nack
  11. Master send STOP

Most of the devices I've seen follow these steps. What should I do to implement this?

Thanks in advance
Igor

@Dolu1990
Copy link
Member

Hi,

I reproduced the issue in simulation (no restart condition when the master issue a start in a frame)

So, the issue i found was in the C driver, where the i2c_masterStartBlocking wasn't blocking at all in that specific condition.

Here is my fix :

static void i2c_masterStartBlocking(u32 reg){
    i2c_masterStart(reg);
    while(i2c_getMasterStatus(reg) & I2C_MASTER_START);
}

Also, note that the driver i had in hand had some missnamed "gpio_" that should have been "i2c_"

I got a good restart sequance doing that : (as an example)

    bsp_putString("Test 1\n");
    i2c_masterStartBlocking(I2C_CTRL);
    i2c_txByte(I2C_CTRL, 0xAA);
    i2c_txAckBlocking(I2C_CTRL);
    i2c_txByte(I2C_CTRL, 0x02);
    i2c_txAckBlocking(I2C_CTRL);
    i2c_masterStartBlocking(I2C_CTRL); //The driver fix will fix this call
    i2c_txByte(I2C_CTRL, 0x04);
    i2c_txAckBlocking(I2C_CTRL);
    i2c_masterStopBlocking(I2C_CTRL);
    bsp_putString("restart done\n");

Which give me in simulation (seems ok to me, the cursor is on the restart) :
image

I updated the i2c driver with this commit :
a447101#diff-47ab647e0bbd29191e3d86e012c38a0f0d05e43acdfb0b3cad140b5b047077afR102

let's me know if anything isn't right.

@gosha-z
Copy link
Author

gosha-z commented Dec 1, 2022

Why i2c_txAckBlocking after i2c_txByte? I understand it as "Transmitting ACK after sending data byte". But it violates the protocol! Slave sends Ack as a confirmation that the data byte has been successfully received, thus it should be like
i2c_listenAck(reg)
while(!i2c_rxAck(reg)
Do am I right?

@Dolu1990
Copy link
Member

Dolu1990 commented Dec 1, 2022

Hoo, the i2c_txAckBlocking of my example are just there for the sim. In my tests i wanted to check the impact of having the master driving the ACK low before the restart to ensure that the restart force the line release.

You can use the

i2c_listenAck(I2C_CTRL);
while(!i2c_rxAck(I2C_CTRL));

This should not create any issue.

Do am I right?

Yes ^^

@gosha-z
Copy link
Author

gosha-z commented Dec 1, 2022

The following code:
`
#define I2C_CTRL SYSTEM_I2C_0_IO_CTRL
#define I2C_CTRL_HZ SYSTEM_CLINT_HZ

static void efx_masterStartBlocking(u32 reg){
i2c_masterStart(reg);
while(i2c_getMasterStatus(reg) & I2C_MASTER_START);
}

static void efx_waitAck(u32 reg)
{
i2c_listenAck(reg);
while(!i2c_rxAck(reg));
}

void i2c_soc_init()
{
//I2C init
I2c_Config i2c = {
.samplingClockDivider = 3,
.timeout = I2C_CTRL_HZ/1000, //1 ms;
//.timeout = 10000,
//.tsuDat = I2C_CTRL_HZ/2000000, //500 ns
.tsuDat = 10,
//.tLow = I2C_CTRL_HZ/800000, //1.25 us
.tLow = 50,
//.tHigh = I2C_CTRL_HZ/800000, //1.25 us
.tHigh = 50,
.tBuf = I2C_CTRL_HZ/400000, //2.5 us
//.tBuf = 100,
};

i2c_applyConfig(I2C_CTRL, &i2c);

}

u8 i2c_soc_read(u8 reg)
{
u8 val;

val = (DEV_ADDR << 1) & 0xFE; // Addr shifted, LSB=0
efx_masterStartBlocking(I2C_CTRL);
i2c_txByte(I2C_CTRL,val);
//i2c_txAckBlocking(I2C_CTRL);
efx_waitAck(I2C_CTRL);
i2c_txByte(I2C_CTRL,reg);
//i2c_txAckBlocking(I2C_CTRL);
efx_waitAck(I2C_CTRL);
val |= 1;	// LSB=1
efx_masterStartBlocking(I2C_CTRL);
i2c_txByte(I2C_CTRL,val);
//i2c_txAckBlocking(I2C_CTRL);
efx_waitAck(I2C_CTRL);
val = i2c_rxData(I2C_CTRL);
i2c_masterStopBlocking(I2C_CTRL);
return val;

}
`
produces only two start conditions when looking at oscilloscope with real hardware (Efinix T120F324-DK). What can be the source of such behaviour?

Thanks in advance
Igor

@Dolu1990
Copy link
Member

Dolu1990 commented Dec 1, 2022

Hi Igor,

So i just tried in sim, there is a few issue with the code. Here is a version which works :

static void efx_masterStartBlocking(u32 reg){
i2c_masterStart(reg);
while(i2c_getMasterStatus(reg) & I2C_MASTER_START);
}

static void efx_waitAck(u32 reg)
{
    i2c_txNackBlocking(I2C_CTRL); //Sending a Nack will allow the slave to put his ack/nack
    assert(i2c_rxAck(I2C_CTRL));
}



u8 i2c_soc_read(u8 reg)
{
    u8 val;
    val = (DEV_ADDR << 1) & 0xFE; // Addr shifted, LSB=0

    efx_masterStartBlocking(I2C_CTRL);

    i2c_txByte(I2C_CTRL,val);
    efx_waitAck(I2C_CTRL);

    i2c_txByte(I2C_CTRL,reg);
    efx_waitAck(I2C_CTRL);

    val |= 1;	// LSB=1
    efx_masterStartBlocking(I2C_CTRL);
    i2c_txByte(I2C_CTRL,val);
    efx_waitAck(I2C_CTRL);

    i2c_txByte(I2C_CTRL, 0xFF); //Provide 0xFF to release the line (pull up)
    efx_waitAck(I2C_CTRL);
    val = i2c_rxData(I2C_CTRL);

    i2c_masterStopBlocking(I2C_CTRL);

    return val;
}

The main thing was that i2c_txByte(I2C_CTRL, 0xFF) and i2c_txNackBlocking(I2C_CTRL); are required, they kind of say "Send something which will not interfer with SDA (pull up)"
The hardware controller implement in the soc do not look at the first byte read/write information. It is kind of a very unaware controller (so you are not limited by the exact i2c spec)

image

The rxListen stuff isn't required, it is mostly a way to specify : "block the i2c if there is something in that register that i haven't read yet". But as by default, the i2c bus is blocked until i2c_txByte i2c_txNackBlocking, it isn't that usefull.

Let's me know how it goes ^^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants