I took a look at the STM32F20X Reference Manual (RM0033) and the current I2C_hal.c implementation in the Develop branch and have a theory on what the photon I2C issues might be:
Assumptions:
- HAL_I2C_Request_Data() is being called with stop parameter true, as it is extremely unlikely to be doing a repeated read operation.
RM0033 pages 597/598 and Figure 221 describe the Master Receiver transaction. Of interest is what happens when there are two bytes left to be received, or when this is a single byte read.
2 Bytes left:
- When RXNE (Data Register status) is set, the 2nd to last byte is in the DR, it has already been ACKED, and the last byte is being shifted into the shift register. As soon as the DR is emptied by reading out the 2nd last byte, the DR is available to take the last byte. This last byte will be ACKED according to the setting the Acknowledge bit in CR1at the time the transfer to DR occurs. Therefore the Acknowledge Bit must be reset, and the Stop condition set, prior to reading out the 2nd last byte from the DR. Otherwise the sending slave device will receive an ACK for the last byte and assume it still has control of SDA. It is most likely it will hold SDA low, awaiting a Clock pulse it will never get. It would probably take a power cycle of the peripheral to clear this condition.
1 Byte left:
- With only one byte and the DR empty, it will be ACKED as soon as it is received. Therefore the Acknowledge bit must be reset and the Stop condition set immediately after the ADDR bit is cleared (I2C_CheckEvent() will always clear ADDR if set.
Here is the relevant code snippets from I2C_hal.c:
/* Send Slave address for read */
I2C_Send7bitAddress(I2C1, address << 1, I2C_Direction_Receiver);
_millis = HAL_Timer_Get_Milli_Seconds();
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if(EVENT_TIMEOUT < (HAL_Timer_Get_Milli_Seconds() - _millis))
{
/* Send STOP Condition */
I2C_GenerateSTOP(I2C1, ENABLE);
/* SW Reset the I2C Peripheral */
HAL_I2C_SoftwareReset();
return 0;
}
}
/* perform blocking read into buffer */
uint8_t *pBuffer = rxBuffer;
uint8_t numByteToRead = quantity;
/* While there is data to be read */
while(numByteToRead)
{
There needs to be a check for a single byte request immediately after the while(I2C_CheckEvent() loop and if so, reset the acknowledge bit and set stop before anything else happens. At 400 KHz clock, the first data byte will be in the DR and the ack/nack already sent within 20us after ADDR is cleared by I2C_CheckEvent().
Further down in the code, within a while loop controlled by NumbytetoRead being non zero, we check the DR register for having data, store the data in the receive buffer, do some checking and loop:
/* Wait for the byte to be received */
_millis = HAL_Timer_Get_Milli_Seconds();
//while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET)
{
if(EVENT_TIMEOUT < (HAL_Timer_Get_Milli_Seconds() - _millis))
{
/* SW Reset the I2C Peripheral */
HAL_I2C_SoftwareReset();
return 0;
}
}
/* Read the byte from the Slave */
*pBuffer = I2C_ReceiveData(I2C1);
bytesRead++;
/* Point to the next location where the byte read will be saved */
pBuffer++;
/* Decrement the read bytes counter */
numByteToRead--;
We don’t check whether we just processed the 2nd last byte until we get back around to the top of the loop. This is too late, especially with the faster internal clock of the photon. We need to move and refine that test to immediately prior to reading the 2nd last byte:
/* Wait for the byte to be received */
_millis = HAL_Timer_Get_Milli_Seconds();
//while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET)
{
if(EVENT_TIMEOUT < (HAL_Timer_Get_Milli_Seconds() - _millis))
{
/* SW Reset the I2C Peripheral */
HAL_I2C_SoftwareReset();
return 0;
}
}
if(numByteToRead == 2 && stop == true)
{
/* Disable Acknowledgement */
I2C_AcknowledgeConfig(I2C1, DISABLE);
/* Send STOP Condition */
I2C_GenerateSTOP(I2C1, ENABLE);
}
/* Read the byte from the Slave */
*pBuffer = I2C_ReceiveData(I2C1);
bytesRead++;
/* Point to the next location where the byte read will be saved */
pBuffer++;
/* Decrement the read bytes counter */
numByteToRead--;
If these mods are made, the check for the stop condition has to moved outside the while loop as we still have work to do (i.e. storing the last byte of data from the DR after the stop condition was set). It is a somewhat redundant check anyway and probably can be removed.
Hope this helps
Peter