|
|
View previous topic :: View next topic |
Author |
Message |
amit78
Joined: 17 Jul 2009 Posts: 22 Location: Kolkata, India
|
Problem in running two i2c lines simultaneously |
Posted: Mon Oct 14, 2024 9:59 am |
|
|
I am having 4 MCUs in my board. I am running a Robotic machine which got 5 Servos and lot of pneumatic controls. There are two separate I2C lines in between these 4 MCUs. Just to make you understand let me explain all the MCUs.
1. MCU0 – PIC24FJ512GL406, 64pin TQFP
a. Works as an HMI Communicator through Modbus, usually to configure, run Production, other three MCUs log, Diagnosis and Alarm Monitoring etc
b. It is also a i2c Master for Data collection line, with a variable packet size. Collect machine health data from other three MCUs
c. Works as a i2c Slave for main Control Line, where MCU2 is the Master
d. Configuration of MCU0:
Code: |
#define SCL_PIN PIN_G2
#define SDA_PIN PIN_G3
#use fast_io(G)
#use I2C(MASTER,scl=SCL_PIN, sda=SDA_PIN,stream = I2C_COM, fast=100000,NOFLOAT_HIGH, restart_wdt)//, RESTART_WDT)
#use I2C(SLAVE, I2C2, address=0xA0, stream = I2C_CTRL, FORCE_HW,NOFLOAT_HIGH, restart_wdt)
|
2. MCU1 - PIC24FJ512GU410, 100pin TQFP
a. Handles two 1KW Servo, few Pneumatic cylinders, few sensors etc
b. Works as a Slave for MCU0 (I2C2) and for MCU2 (I2C1)
c. Configuration of MCU1:
Code: |
// ------------------ For I2C Data Command Slave: Start -------------------
#use fast_io(A)
#use I2C(SLAVE, I2C2, address= 0xB0, stream=I2C_COM, NOFLOAT_HIGH, restart_wdt)
// ------------------ For I2C Data Command Slave: End ---------------------
// --------------------- For I2C Control Command Slave: Start -----------------
#use I2C(SLAVE, I2C1, address=0x80, stream=I2C_CTRL, NOFLOAT_HIGH, restart_wdt)
// --------------------- For I2C Control Command Slave: End -------------------
|
3. MCU2 - PIC24FJ512GU410, 100pin TQFP
a. Handles one 1KW Servo, few Pneumatic cylinders, few sensors etc
b. Works as a Slave for MCU0 for i2c Data Line (I2C2)
c. Works as a Master for MCU0, MCU1 and MCU3 for I2C (I2C1) Control Line, to control all the robotics activities
d. Configuration of MCU2:
Code: |
#use I2C(SLAVE, I2C2, address=0xC0, stream=I2C_COM, NOFLOAT_HIGH, restart_wdt)
// -------------------- For I2C Data Command Slave: End ---------------------
// ------------------ For I2C Control Command Master: Start -------------------
#define SCL_PIN PIN_A14
#define SDA_PIN PIN_A15
#use I2C(MASTER,scl=SCL_PIN, sda=SDA_PIN, stream = I2C_CTRL, fast=100000, NOFLOAT_HIGH, restart_wdt)
// ------------------ For I2C Control Command Master: End ---------------------
|
4. MCU3 - PIC24FJ512GU410, 100pin TQFP
a. Handles two 750W Servo, few Pneumatic cylinders, few sensors etc
b. Works as a Slave for MCU0 (I2C2) and for MCU2 (I2C1)
c. Configuration of MCU3:
Code: |
#use I2C(SLAVE, I2C2, address=0xD0, stream=I2C_COM, NOFLOAT_HIGH, restart_wdt)
// -------------------- For I2C Data Command Slave: End -----------------------
// ------------------ For I2C Control Command Slave: Start --------------------
#use I2C(SLAVE, I2C1, address=0x90, stream = I2C_CTRL, NOFLOAT_HIGH, restart_wdt)
// ------------------ For I2C Control Command Slave: Start --------------------
|
Quote: |
Problem Statement: The system is already deployed in a 24x7 production line, working fine with no stoppages using only the i2c Control Line (I2C1). Data line is only working before control line starts to configure some parameters from HMI to all the other MCUs. Data line got other activities to collect periodic data from other MCUs while the machine is running in production. But, while we are running the machine health check up routine that time it one of the Slave is getting restarted may be after 15 mins to max 40 mins.
But, independently they can run smoothly. Even we have seen data line is collecting data from other MCUs (while the control line is not running, or the machine is not in production mode) without any CPU reset for 72hrs. Similarly, the control line is running without the periodic health data collection for months.
|
5. Code snippet for i2c Control Line Master
Code: |
// Note: We are expecting a quick response from Slaves. In the Below Send Receive Function
// We are holding the Main() control and executing Master Command Sending and Receive in one go
// which is very risky, as there are i2c_read() like blocking calls. We might write this function
// later with more security, but for the time being, lets do it in insecure mode, keeping in mind
// there will be less Slave Clock Stretching, as, slave is not going to calculate anything, only
// responding like a registry reading
void SendReceive_I2C_CTRL_CMD(ST_CTRL_MSTR_CMD_NODE* pstCtrlCmdNode)
{
UINT8 ui8ByteCount = 0;
UINT8 uiDataByteCount = 0;
UINT8 ui8I2C_Response = 0;
// ---------------- I2C Data Sending Section : Start -----------------------
// Start I2C
i2c_start(I2C_CTRL);
// Send the Device Address
i2c_write(I2C_CTRL, pstCtrlCmdNode->m_ui8SlaveID);
// Send the Command Type
i2c_write(I2C_CTRL, pstCtrlCmdNode->m_ui8CMDType);
// For Some Cases we might need to send the Data
if (pstCtrlCmdNode->m_ui8SendByte > 1)
{
// Extra Byte needs to send for Data
uiDataByteCount = pstCtrlCmdNode->m_ui8SendByte - 1;
for (ui8ByteCount = 0; ui8ByteCount < uiDataByteCount; ui8ByteCount++)
{
i2c_write(I2C_CTRL, pstCtrlCmdNode->m_ui8ArrData[ui8ByteCount]);
}
}
i2c_stop(I2C_CTRL);
delay_us(10);
// ---------------- I2C Data Sending Section : End -------------------------
// ---------------- I2C Data Receive Section : Start -----------------------
// Now Start Reading Response from Slave
// So far we are expecting Single Byte Response, if we need more than we need to write the stub
// accordingly.
i2c_start(I2C_CTRL);
i2c_write(I2C_CTRL, pstCtrlCmdNode->m_ui8SlaveID | 0x01);
ui8I2C_Response = i2c_read(I2C_CTRL, 0); // No need of acknowledgment
i2c_stop(I2C_CTRL);
// Call Response Callback for this Control Command
pstCtrlCmdNode->m_pfnSlaveResponseCallBack(ui8I2C_Response);
// ---------------- I2C Data Receive Section : End -------------------------
return;
}
|
6. Code Snippet for Control Line Slave (which is same for all other MCUs)
Code: |
#ifdef MCU0
#INT_SI2C2
void I2C_isr()
{
// Initialize Temp Variable.
g_ui8CtrlIICisrState = 0;
// Return Status of ISR state SSPxIF.
g_ui8CtrlIICisrState = i2c_isr_state(I2C_CTRL);
// Check if Master is Trying To Write Data
if(g_ui8CtrlIICisrState < 0x80)
{
if (0 == g_ui8CtrlIICisrState)
{
// Slave ID Matched
// Master wants to Write Data, So, Slave Buffer Index should be initialized
g_ui8I2cCtrlRxByteCount = 0;
g_ui8i2cCtrlRcvByte = i2c_read(I2C_CTRL, 1);
}
else // Master is writing data.
{
if (1 == g_ui8CtrlIICisrState)
{
g_ui8CtrlMasterCMD = 0;
// This is Actually the Length of upcoming Master Write Statement
g_ui8CtrlMasterCMD = i2c_read(I2C_CTRL, 1);
iT++;
}
else
{
// We are expecting only one byte from Master, if it is encountering more bytes
// than, there might be some problem
int i = 10;
}
}
}
if(g_ui8CtrlIICisrState >= 0x80)
{
// Master is Trying to Read Data.
if (g_ui8CtrlIICisrState == 0x80)
{
// Slave ID Matched
// Get the Slave ID and Hold The SCL line Low (Hold the Bus)
g_ui8i2cCtrlRcvByte = i2c_read(I2C_CTRL, 2);//i2c_read();
if (g_ui8CtrlMasterCMD >= CTRL_CMD_REG_START_OFFSET)
{
g_ui8ResRegIdx = g_ui8CtrlMasterCMD - CTRL_CMD_REG_START_OFFSET;
if (g_ui8ResRegIdx < SIZE_RSP_REGISTRY)
{
g_ui8SlvResponse = CheckSlvResponse(g_ui8CtrlMasterCMD);
i2c_write_slave(I2C_CTRL, g_ui8SlvResponse);
//i2c_write_slave(I2C_CTRL, g_ui8ArrCtrlRespRegistry[g_ui8ResRegIdx]);
}
else
{
i2c_write_slave(I2C_CTRL, NULL);
}
}
else
{
i2c_write_slave(I2C_CTRL, NULL);
}
}
else
{
i2c_write_slave(I2C_CTRL, NULL);
}
}
// Clear Interrupt.
//clear_interrupt(INT_SI2C2);
}
#endif
|
7. Code for Data Line Master:
Code: |
UINT8 SendMsgFrom_I2CDataSendQueue()
{
UINT8 ui8LoopCnt = 0;
UINT8 ui8StreamLen = 0;
UINT16 ui16crcValue = 0;
UINT8 ui8SLV_AckStatus = 255;
UINT8 ui8tempSendRcvDataQStatus = RETURN_FAIL;
UINT8 ui8ExpectedByteLen = 0;
g_ui8I2C_RW_Err = I2C_ERR_NOTHING;
// Assumption: This function will get called if and only if, there is a definite
// message in the Queue. So, no need to check the Queue
do // Single Exit Do While
{
// Get a message from send Queue.
//if (RETURN_SUCCESS == Delete_I2CDataSendListQ(&g_stDCmdNodeSndQTemp))
if (RETURN_SUCCESS == GetHead_I2CDataSendListQ(&g_stDCmdNodeSndQTemp))
{
// Parse Config Message Type to generate Send Data String.
switch(g_stDCmdNodeSndQTemp.m_ui8DataCmdType)
{
case I2C_DATA_CMD_NOTHING:
break;
case I2C_DATA_CMD_HMI_COMMANDS:
ST_I2C_HMICMD_DATA_NODE* pstHMICmd = 0;
//TODO: can be shifted else were used here for testing.
g_ui8ArrSendBuff[0] = 7; // As per Protocol it is fixed for HMI Commands 0-4 Data 5-6 CRC
// Insert Command sequence number.
g_ui8ArrSendBuff[1] = g_stDCmdNodeSndQTemp.m_ui8CmdSeqNo;
// Insert 1st level information in the data frame.
g_ui8ArrSendBuff[2] = g_stDCmdNodeSndQTemp.m_ui8DataCmdType;
// Insert data..
g_ui8ArrSendBuff[3] = g_stDCmdNodeSndQTemp.m_ui8Data;
pstHMICmd = (ST_I2C_HMICMD_DATA_NODE*)g_stDCmdNodeSndQTemp.m_pui16Message;
g_ui8ArrSendBuff[4] = pstHMICmd->m_ui8HMICMDType;
// Lets get prepare Last Send Data Command info for Response
g_ui8LastI2C_1stLevelCMDSent = I2C_DATA_CMD_HMI_COMMANDS;
g_ui8LastI2C_2ndLevelCMDSent = g_ui8ArrSendBuff[4];
break;
case I2C_DATA_CMD_CONFIG:
// Insert Command sequence number.
g_ui8ArrSendBuff[1] = g_stDCmdNodeSndQTemp.m_ui8CmdSeqNo;
// Insert 1st level information in the data frame.
g_ui8ArrSendBuff[2] = g_stDCmdNodeSndQTemp.m_ui8DataCmdType;
g_ui8ArrSendBuff[3] = 0; // Not Used
// generate data string.
GetI2CDataConfigSendString(g_stDCmdNodeSndQTemp.m_pui16Message);
// Lets get prepare Last Send Data Command info for Response
g_ui8LastI2C_1stLevelCMDSent = I2C_DATA_CMD_CONFIG;
g_ui8LastI2C_2ndLevelCMDSent = g_ui8ArrSendBuff[4];
break;
case I2C_DATA_CMD_LDE_MONITOR:
break;
case I2C_DATA_CMD_STATE_LOG:
ST_I2C_STATE_LOG_CMD_DATA_NODE* pstStateLogCmd = 0;
//TODO: can be shifted else were used here for testing.
g_ui8ArrSendBuff[0] = 7; // As per Protocol it is fixed for State Log Commands 0-4 Data 5-6 CRC
// Insert Command sequence number.
g_ui8ArrSendBuff[1] = g_stDCmdNodeSndQTemp.m_ui8CmdSeqNo;
// Insert 1st level information in the data frame.
g_ui8ArrSendBuff[2] = g_stDCmdNodeSndQTemp.m_ui8DataCmdType;
g_ui8ArrSendBuff[3] = 0; // Not Used
pstStateLogCmd = (ST_I2C_STATE_LOG_CMD_DATA_NODE*)g_stDCmdNodeSndQTemp.m_pui16Message;
g_ui8ArrSendBuff[4] = pstStateLogCmd->m_ui8StateLogCMDType;
// Lets get prepare Last Send Data Command info for Response
g_ui8LastI2C_1stLevelCMDSent = I2C_DATA_CMD_STATE_LOG;
g_ui8LastI2C_2ndLevelCMDSent = g_ui8ArrSendBuff[4];
break;
case I2C_DATA_CMD_DI_STATUS:
ST_I2C_DI_STATUS* pstDI_status = 0;
//TODO: can be shifted else were used here for testing.
g_ui8ArrSendBuff[0] = 7; // As per Protocol it is fixed for HMI Commands 0-4 Data 5-6 CRC
// Insert Command sequence number.
g_ui8ArrSendBuff[1] = g_stDCmdNodeSndQTemp.m_ui8CmdSeqNo;
// Insert 1st level information in the data frame.
g_ui8ArrSendBuff[2] = g_stDCmdNodeSndQTemp.m_ui8DataCmdType;
g_ui8ArrSendBuff[3] = 0; // Not Used
pstDI_status = (ST_I2C_DI_STATUS*)g_stDCmdNodeSndQTemp.m_pui16Message;
g_ui8ArrSendBuff[4] = pstDI_status->m_ui8_MCU_DI_STATUS;
// Lets get prepare Last Send Data Command info for Response
g_ui8LastI2C_1stLevelCMDSent = I2C_DATA_CMD_DI_STATUS;
g_ui8LastI2C_2ndLevelCMDSent = g_ui8ArrSendBuff[4];
break;
default:
break;
}
// Insert CRC at end
ui8StreamLen = g_ui8ArrSendBuff[0];
//ui16crcValue = ModbusCRC16(g_ui8ArrSendBuff, (ui8StreamLen - 2));
//ui16crcValue = ModbusCRC16((UINT8*)g_ui8ArrSendBuff, (ui8StreamLen - 2));
g_ui8ArrSendBuff[ui8StreamLen - 2] = (UINT8)((ui16crcValue >> 0) & 0xFF);
g_ui8ArrSendBuff[ui8StreamLen - 1] = (UINT8)((ui16crcValue >> 8) & 0xFF);
}
else
{
g_ui8I2CMasterSendLockState = I2C_MASTER_SEND_LOCK_RELEASE;
return RETURN_FAIL;
}
// Step 6: Hold the I2C Master Send Lock
//g_ui8I2CMasterSendLockState = I2C_MASTER_SEND_LOCK_HOLD;
// Start I2C Port
i2c_start(I2C_COM);
// Send the Device Address for Write
g_ui8I2C_RW_Return = i2c_write(I2C_COM, g_stDCmdNodeSndQTemp.m_uiSlaveID); // 0b1010 0000 as first bit denotes R/W. 1 -> R, 0 -> W.
// Lets Check Slave Response
// If Slave is Ready To Receive Data It Will Response With ACK/0
// otherwise It Will Respond With NACK/1
// If Bus Collision Occur It Will Respond With 2
if(g_ui8I2C_RW_Return)
{
g_ui8I2CMasterSendLockState = I2C_MASTER_SEND_LOCK_RELEASE;
i2c_stop(I2C_COM);
return RETURN_FAIL;
}
for (ui8LoopCnt = 0; ui8LoopCnt < g_ui8ArrSendBuff[0]; ui8LoopCnt++)
{
//g_ui8I2C_RW_Return = i2c_write(I2C_COM, g_ui8ArrSendBuff[ui8LoopCnt]); // Data to device.
i2c_write(I2C_COM, g_ui8ArrSendBuff[ui8LoopCnt]);
}
} while (0);
// Write done, Lets start Read operation
i2c_start(I2C_COM);
// Lets open the Slave in Read Mode, Last bit should be 1 for that
ui8SLV_AckStatus = i2c_write(I2C_COM, g_stDCmdNodeSndQTemp.m_uiSlaveID | 0x01);
// At the beginning Slave will return NULL (0) until it completes the Process with the
// Data sent from Master, till then it will send 0 to Master
// The very next non zero value will be considered as the Length of the Message Slave wants
// to send
ui8LoopCnt = 0;
do
{
g_ui8SlaveResponse = i2c_read(I2C_COM, 1);
if (0 == g_ui8SlaveResponse) // || 0xff == g_ui8SlaveResponse)
{
ui8LoopCnt++;
}
else
{
g_ui8ArrI2CReadBuffer[0] = g_ui8SlaveResponse;
break;
}
} while (ui8LoopCnt < 100); // 100 is the limit to get the 0, otherwise it will hang the system
// Read the First Byte to get the Expected Read Message Length
ui8StreamLen = g_ui8ArrI2CReadBuffer[0] - 1;
// Read remaining bytes.
for (ui8LoopCnt = 1; ui8LoopCnt < ui8StreamLen; ui8LoopCnt++)
{
g_ui8ArrI2CReadBuffer[ui8LoopCnt] = i2c_read(I2C_COM, 1);
}
g_ui8ArrI2CReadBuffer[ui8LoopCnt] = i2c_read(I2C_COM, 0);
// We are done, lets Stop i2c port
i2c_stop(I2C_COM);
// Lets Clear Transmit Buffer Of All Slaves By Sending Null
i2c_start(I2C_COM);
i2c_write(I2C_COM, 0);
i2c_stop(I2C_COM);
// We are done
g_ui8I2CMstrProcessDataState = I2C_DATA_PROCESS_START;
return RETURN_SUCCESS;
}
|
8. Code for Data line Slave (same for all other MCUs)
Code: |
#INT_SI2C2
void I2C_Data_isr()
{
// Initialize Temp Variable.
g_ui8IICisrState = 0;
// Return Status of ISR state SSPxIF.
g_ui8IICisrState = i2c_isr_state(I2C_COM);
// Check if Master is Trying To Write Data
if(g_ui8IICisrState < 0x80)
{
if (0 == g_ui8IICisrState)
{
// Slave ID Matched
// Master wants to Write Data, So, Slave Buffer Index should be initialized
g_ui8I2CRcvSlaveAddrs = i2c_read(I2C_COM, 1);
g_ui8I2cRxByteCount = 0;
}
else // Master is writing data.
{
//Checking slave address..
//g_ui8SlaveAddress = (g_ui8I2CRcvSlaveAddrs & 0xFE);
// Master is Writing Data and Slave didn't received full message
if (1 == g_ui8IICisrState)
{
// This is Actually the Length of upcoming Master Write Statement
g_ui8I2CDataCmdRDBuff[g_ui8I2cRxByteCount] = i2c_read(I2C_COM, 1);
g_ui8MasterWriteLen = g_ui8I2CDataCmdRDBuff[g_ui8I2cRxByteCount];
g_ui8I2cRxByteCount++;
}
else
{
// Read the incoming byte from SSPxBUF
g_ui8I2CDataCmdRDBuff[g_ui8I2cRxByteCount] = i2c_read(I2C_COM, 1);
g_ui8I2cRxByteCount++;
}
if (g_ui8I2cRxByteCount == g_ui8MasterWriteLen && 0 != g_ui8MasterWriteLen)
{
// Desired byte received, so, Raise a Flag to tell Process to Execute the Read Buffer
// Assuming Master Has written data to slave, We need to start processing the Data
g_ui8MasterWriteState = I2C_MASTER_WRITE_DONE;
g_ui8RespBufferIdx = 0;
}
}
}
if(g_ui8IICisrState >= 0x80)
{
// Master is Trying to Read Data.
if (g_ui8IICisrState == 0x80)
{
// Slave ID Matched
// Get the Slave ID and Hold The SCL line Low (Hold the Bus)
switch(g_ui8I2CRcvSlaveAddrs)
{
case I2C_SLAVE0_ADDRESS:
case I2C_SLAVE1_ADDRESS:
case I2C_SLAVE2_ADDRESS:
g_ui8I2CRcvSlaveAddrs = i2c_read(I2C_COM, 2);
i2c_write_slave(I2C_COM, NULL);
break;
default:
// Do nothing..
break;
}
}
else
{
if (I2C_SLAVE_RESPONSE_READY == g_ui8SlaveResponseStatus)
{
if (g_ui8RespBufferIdx < g_ui8I2CDataCmdRespBuff[0])
{
i2c_write_slave(I2C_COM, g_ui8I2CDataCmdRespBuff[g_ui8RespBufferIdx]);
g_ui8RespBufferIdx++;
}
// It Means Slave Bus Not Stuck
//Till Now Slave is Communicate With Master
g_ui16DataBusStuckTimerCnt = 0;
}
else
{
// Response is not ready yet
// Send NULL
i2c_write_slave(I2C_COM, NULL);
}
}
}
// Clear Interrupt.
clear_interrupt(INT_SI2C2);
}
|
It would be a great help if you could suggest me what I am doing wrong. Also, is there any way to avoid the blocking i2c_read() or i2c_write() call? actually, i am getting watch dog reset (setup_wdt(WDT_4S) ) while the bus gets stuck.
using CCS C V-5.101
Thanks in advance
Amit |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19495
|
|
Posted: Mon Oct 14, 2024 10:50 am |
|
|
What are the pull-ups on the I2C lines????.
It sounds as if you have an EMI problem. Either with the ground rail between
the devices or with the actual I2C lines. For these lines at 3.3v, in a noisy
environment, with multiple devices on the busses (you don't tell us how
long the busses are?), you should be using pull-ups like 1KR. You can go even lower since the PIC supports SMBUS and high speed drive currents, so you
could go down to something like 200R.
Now on recovery, this has been discussed here many times. You need to
test the SDA line and if this is low when a transaction is not happening,
disable the I2C peripheral, and manually clock the SCL line until SDA
releases. Then re-enable the I2C peripheral.
This releases a device that has hung because a master starts a read to a slave
which does not correctly complete. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|