I2C feature request: add support for repeated start
u77345 opened this issue · comments
To add support for reading from I2C devices with repeated start.
One such device that needs repeated start is MAX31334 to use it effectively. The repeated start ( bcm2835_i2c_write_read_rs() ) is demonstrated at http://www.airspayce.com/mikem/bcm2835/group__i2c.html#gabb58ad603bf1ebb4eac2d439820809be or https://github.com/Tinkerforge/generators/blob/aaf854e9d0b2020aae544b185bcf5383f9eb3ffa/uc/hal_raspberry_pi/bcm2835.c#L1593
Here is a patch for the develop branch, which tries to add the method CI2CMaster::WriteReadRepeatedStart()
. It is not tested, because I do not have an I2C device, which needs repeated start. Please let me know, if it works.
diff --git a/include/circle/i2cmaster.h b/include/circle/i2cmaster.h
index 831e5ea..f882cec 100644
--- a/include/circle/i2cmaster.h
+++ b/include/circle/i2cmaster.h
@@ -2,7 +2,7 @@
/// \file i2cmaster.h
//
// Circle - A C++ bare metal environment for Raspberry Pi
-// Copyright (C) 2014-2022 R. Stange <rsta2@o2online.de>
+// Copyright (C) 2014-2023 R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -74,6 +74,17 @@ public:
/// \return Number of written bytes or < 0 on failure
int Write (u8 ucAddress, const void *pBuffer, unsigned nCount);
+ /// \brief Consecutive write and read operation with repeated start
+ /// \param ucAddress I2C slave address of target device
+ /// \param pWriteBuffer Write data for will be taken from here
+ /// \param nWriteCount Number of bytes to be written (max. 16)
+ /// \param pReadBuffer Read data will be stored here
+ /// \param nReadCount Number of bytes to be read
+ /// \return Number of read bytes or < 0 on failure
+ int WriteReadRepeatedStart (u8 ucAddress,
+ const void *pWriteBuffer, unsigned nWriteCount,
+ void *pReadBuffer, unsigned nReadCount);
+
private:
unsigned m_nDevice;
uintptr m_nBaseAddress;
diff --git a/lib/i2cmaster.cpp b/lib/i2cmaster.cpp
index 7c7f927..5ca5172 100644
--- a/lib/i2cmaster.cpp
+++ b/lib/i2cmaster.cpp
@@ -2,7 +2,7 @@
// i2cmaster.cpp
//
// Circle - A C++ bare metal environment for Raspberry Pi
-// Copyright (C) 2014-2022 R. Stange <rsta2@o2online.de>
+// Copyright (C) 2014-2023 R. Stange <rsta2@o2online.de>
//
// Large portions are:
// Copyright (C) 2011-2013 Mike McCauley
@@ -331,3 +331,115 @@ int CI2CMaster::Write (u8 ucAddress, const void *pBuffer, unsigned nCount)
return nResult;
}
+
+int CI2CMaster::WriteReadRepeatedStart (u8 ucAddress,
+ const void *pWriteBuffer, unsigned nWriteCount,
+ void *pReadBuffer, unsigned nReadCount)
+{
+ assert (m_bValid);
+
+ if (ucAddress >= 0x80)
+ {
+ return -I2C_MASTER_INALID_PARM;
+ }
+
+ if ( nWriteCount == 0 || nWriteCount > FIFO_SIZE || pWriteBuffer == 0
+ || nReadCount == 0 || pReadBuffer == 0
+ )
+ {
+ return -I2C_MASTER_INALID_PARM;
+ }
+
+ m_SpinLock.Acquire ();
+
+ u8 *pWriteData = (u8 *) pWriteBuffer;
+
+ PeripheralEntry ();
+
+ // setup transfer
+ write32 (m_nBaseAddress + ARM_BSC_A__OFFSET, ucAddress);
+
+ write32 (m_nBaseAddress + ARM_BSC_C__OFFSET, C_CLEAR);
+ write32 (m_nBaseAddress + ARM_BSC_S__OFFSET, S_CLKT | S_ERR | S_DONE);
+
+ write32 (m_nBaseAddress + ARM_BSC_DLEN__OFFSET, nWriteCount);
+
+ // fill FIFO
+ while (nWriteCount-- > 0)
+ {
+ write32 (m_nBaseAddress + ARM_BSC_FIFO__OFFSET, *pWriteData++);
+ }
+
+ // start transfer
+ write32 (m_nBaseAddress + ARM_BSC_C__OFFSET, C_I2CEN | C_ST);
+
+ // poll for transfer has started
+ while (!(read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_TA))
+ {
+ if (read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_DONE)
+ {
+ break;
+ }
+ }
+
+ // transfer active
+ while (!(read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_DONE))
+ {
+ // just wait
+ }
+
+ u8 *pReadData = (u8 *) pReadBuffer;
+
+ int nResult = 0;
+
+ // setup transfer
+ write32 (m_nBaseAddress + ARM_BSC_DLEN__OFFSET, nReadCount);
+
+ write32 (m_nBaseAddress + ARM_BSC_C__OFFSET, C_I2CEN | C_ST | C_READ);
+
+ // transfer active
+ while (!(read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_DONE))
+ {
+ while (read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_RXD)
+ {
+ *pReadData++ = read32 (m_nBaseAddress + ARM_BSC_FIFO__OFFSET) & FIFO__MASK;
+
+ nReadCount--;
+ nResult++;
+ }
+ }
+
+ // transfer has finished, grab any remaining stuff from FIFO
+ while ( nReadCount > 0
+ && (read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_RXD))
+ {
+ *pReadData++ = read32 (m_nBaseAddress + ARM_BSC_FIFO__OFFSET) & FIFO__MASK;
+
+ nReadCount--;
+ nResult++;
+ }
+
+ u32 nStatus = read32 (m_nBaseAddress + ARM_BSC_S__OFFSET);
+ if (nStatus & S_ERR)
+ {
+ write32 (m_nBaseAddress + ARM_BSC_S__OFFSET, S_ERR);
+
+ nResult = -I2C_MASTER_ERROR_NACK;
+ }
+ else if (nStatus & S_CLKT)
+ {
+ nResult = -I2C_MASTER_ERROR_CLKT;
+ }
+ else if (nReadCount > 0)
+ {
+ nResult = -I2C_MASTER_DATA_LEFT;
+ }
+
+ write32 (m_nBaseAddress + ARM_BSC_S__OFFSET, S_DONE);
+
+ PeripheralExit ();
+
+ m_SpinLock.Release ();
+
+ return nResult;
+}
Thanks for the patch; did not work. The call returned with -4 (I2C_MASTER_DATA_LEFT). On the wire only the device address was written out on 8 bit, the command byte was not. The address was acknowladged. Target was MAX31334, however any other device, working by addressing a register before read should pass the repeat start test.
u8 Cmd[] = {0x09};
u8 Reg[7];
int nResult = m_pI2CMaster->WriteReadRepeatedStart (m_ucSlaveAddress, Cmd, sizeof Cmd, Reg, sizeof Reg);
if (nResult != sizeof Reg)
{
CLogger::Get ()->Write (FromMCP7941X, LogError, "I2C read/write failed (err %d)", nResult);
return FALSE;
}
Thanks for testing! Here comes another patch. It now contains the delay, which I was trying to eliminate before, which was obviously not working. The previous patch is included in this one.
diff --git a/include/circle/i2cmaster.h b/include/circle/i2cmaster.h
index 831e5ea..4b13f25 100644
--- a/include/circle/i2cmaster.h
+++ b/include/circle/i2cmaster.h
@@ -2,7 +2,7 @@
/// \file i2cmaster.h
//
// Circle - A C++ bare metal environment for Raspberry Pi
-// Copyright (C) 2014-2022 R. Stange <rsta2@o2online.de>
+// Copyright (C) 2014-2023 R. Stange <rsta2@o2online.de>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -74,6 +74,17 @@ public:
/// \return Number of written bytes or < 0 on failure
int Write (u8 ucAddress, const void *pBuffer, unsigned nCount);
+ /// \brief Consecutive write and read operation with repeated start
+ /// \param ucAddress I2C slave address of target device
+ /// \param pWriteBuffer Write data for will be taken from here
+ /// \param nWriteCount Number of bytes to be written (max. 16)
+ /// \param pReadBuffer Read data will be stored here
+ /// \param nReadCount Number of bytes to be read
+ /// \return Number of read bytes or < 0 on failure
+ int WriteReadRepeatedStart (u8 ucAddress,
+ const void *pWriteBuffer, unsigned nWriteCount,
+ void *pReadBuffer, unsigned nReadCount);
+
private:
unsigned m_nDevice;
uintptr m_nBaseAddress;
@@ -85,6 +96,7 @@ private:
CGPIOPin m_SCL;
unsigned m_nCoreClockRate;
+ unsigned m_nClockSpeed;
CSpinLock m_SpinLock;
};
diff --git a/lib/i2cmaster.cpp b/lib/i2cmaster.cpp
index 7c7f927..537a44b 100644
--- a/lib/i2cmaster.cpp
+++ b/lib/i2cmaster.cpp
@@ -2,7 +2,7 @@
// i2cmaster.cpp
//
// Circle - A C++ bare metal environment for Raspberry Pi
-// Copyright (C) 2014-2022 R. Stange <rsta2@o2online.de>
+// Copyright (C) 2014-2023 R. Stange <rsta2@o2online.de>
//
// Large portions are:
// Copyright (C) 2011-2013 Mike McCauley
@@ -26,6 +26,7 @@
#include <circle/bcm2835.h>
#include <circle/machineinfo.h>
#include <circle/synchronize.h>
+#include <circle/timer.h>
#include <assert.h>
#if RASPPI < 4
@@ -108,6 +109,7 @@ CI2CMaster::CI2CMaster (unsigned nDevice, boolean bFastMode, unsigned nConfig)
m_nConfig (nConfig),
m_bValid (FALSE),
m_nCoreClockRate (CMachineInfo::Get ()->GetClockRate (CLOCK_ID_CORE)),
+ m_nClockSpeed (0),
m_SpinLock (TASK_LEVEL)
{
if ( m_nDevice >= DEVICES
@@ -165,6 +167,8 @@ void CI2CMaster::SetClock (unsigned nClockSpeed)
PeripheralEntry ();
assert (nClockSpeed > 0);
+ m_nClockSpeed = nClockSpeed;
+
u16 nDivider = (u16) (m_nCoreClockRate / nClockSpeed);
write32 (m_nBaseAddress + ARM_BSC_DIV__OFFSET, nDivider);
@@ -331,3 +335,112 @@ int CI2CMaster::Write (u8 ucAddress, const void *pBuffer, unsigned nCount)
return nResult;
}
+
+int CI2CMaster::WriteReadRepeatedStart (u8 ucAddress,
+ const void *pWriteBuffer, unsigned nWriteCount,
+ void *pReadBuffer, unsigned nReadCount)
+{
+ assert (m_bValid);
+
+ if (ucAddress >= 0x80)
+ {
+ return -I2C_MASTER_INALID_PARM;
+ }
+
+ if ( nWriteCount == 0 || nWriteCount > FIFO_SIZE || pWriteBuffer == 0
+ || nReadCount == 0 || pReadBuffer == 0
+ )
+ {
+ return -I2C_MASTER_INALID_PARM;
+ }
+
+ m_SpinLock.Acquire ();
+
+ u8 *pWriteData = (u8 *) pWriteBuffer;
+
+ PeripheralEntry ();
+
+ // setup transfer
+ write32 (m_nBaseAddress + ARM_BSC_A__OFFSET, ucAddress);
+
+ write32 (m_nBaseAddress + ARM_BSC_C__OFFSET, C_CLEAR);
+ write32 (m_nBaseAddress + ARM_BSC_S__OFFSET, S_CLKT | S_ERR | S_DONE);
+
+ write32 (m_nBaseAddress + ARM_BSC_DLEN__OFFSET, nWriteCount);
+
+ // fill FIFO
+ for (unsigned i = 0; i < nWriteCount; i++)
+ {
+ write32 (m_nBaseAddress + ARM_BSC_FIFO__OFFSET, *pWriteData++);
+ }
+
+ // start transfer
+ write32 (m_nBaseAddress + ARM_BSC_C__OFFSET, C_I2CEN | C_ST);
+
+ // poll for transfer has started
+ while (!(read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_TA))
+ {
+ if (read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_DONE)
+ {
+ break;
+ }
+ }
+
+ u8 *pReadData = (u8 *) pReadBuffer;
+
+ int nResult = 0;
+
+ // setup transfer
+ write32 (m_nBaseAddress + ARM_BSC_DLEN__OFFSET, nReadCount);
+
+ write32 (m_nBaseAddress + ARM_BSC_C__OFFSET, C_I2CEN | C_ST | C_READ);
+
+ assert (m_nClockSpeed > 0);
+ CTimer::SimpleusDelay ((nWriteCount + 1) * 9 * 1000000 / m_nClockSpeed);
+
+ // transfer active
+ while (!(read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_DONE))
+ {
+ while (read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_RXD)
+ {
+ *pReadData++ = read32 (m_nBaseAddress + ARM_BSC_FIFO__OFFSET) & FIFO__MASK;
+
+ nReadCount--;
+ nResult++;
+ }
+ }
+
+ // transfer has finished, grab any remaining stuff from FIFO
+ while ( nReadCount > 0
+ && (read32 (m_nBaseAddress + ARM_BSC_S__OFFSET) & S_RXD))
+ {
+ *pReadData++ = read32 (m_nBaseAddress + ARM_BSC_FIFO__OFFSET) & FIFO__MASK;
+
+ nReadCount--;
+ nResult++;
+ }
+
+ u32 nStatus = read32 (m_nBaseAddress + ARM_BSC_S__OFFSET);
+ if (nStatus & S_ERR)
+ {
+ write32 (m_nBaseAddress + ARM_BSC_S__OFFSET, S_ERR);
+
+ nResult = -I2C_MASTER_ERROR_NACK;
+ }
+ else if (nStatus & S_CLKT)
+ {
+ nResult = -I2C_MASTER_ERROR_CLKT;
+ }
+ else if (nReadCount > 0)
+ {
+ nResult = -I2C_MASTER_DATA_LEFT;
+ }
+
+ write32 (m_nBaseAddress + ARM_BSC_S__OFFSET, S_DONE);
+
+ PeripheralExit ();
+
+ m_SpinLock.Release ();
+
+ return nResult;
+}
Thanks again, it worked wonderfully! Tested on BCM2837/Pi 3 A+.
Great! The patch has been added on the develop branch now. Thanks again for testing!
Support for I2C repeated start is in Circle 45.3.