rsta2 / circle

A C++ bare metal environment for Raspberry Pi with USB (32 and 64 bit)

Home Page:https://circle-rpi.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.