2024-09-20 02:16 AM - edited 2024-09-20 02:23 AM
Hello, I am using an STM32F103CBT as an I2C slave.
A memory buffer is to be transferred via the interface. Although hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; is set, I cannot measure clock stretching and the first byte transferred is always 0xff.
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c){ HAL_I2C_EnableListen_IT(hi2c); } void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode){ HAL_StatusTypeDef hal_i2c_status = HAL_ERROR; if(TransferDirection == I2C_DIRECTION_TRANSMIT){ hal_i2c_status = HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2c_rx_buffer, 1, I2C_NEXT_FRAME); if(hal_i2c_status != HAL_OK)printf("AC Receive IT error\r\n"); }else if(TransferDirection == I2C_DIRECTION_RECEIVE){ i2c_tx_tf_cnt = 0; hal_i2c_status = HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &i2c_tx_buffer[i2c_rx_offset], 1, I2C_NEXT_FRAME);// << this always sends 0xff if(hal_i2c_status != HAL_OK)printf("AC Transmit IT error\r\n"); } } void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c){ i2c_rx_offset = i2c_rx_buffer[0]; } void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c){ i2c_tx_tf_cnt++; HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &i2c_tx_buffer[(i2c_rx_offset+i2c_tx_tf_cnt)], 1, I2C_NEXT_FRAME); } void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c){ uint32_t errorcode = HAL_I2C_GetError(hi2c); printf("HAL_I2C_ErrorCallback: %lu\r\n",errorcode); SET_BIT(hi2c1.Instance->CR1, I2C_CR1_SWRST); HAL_I2C_DeInit(hi2c); HAL_I2C_Init(hi2c); HAL_I2C_EnableListen_IT(hi2c); } void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c){ printf("aborted\n" ); // never seen... }
I2C Init:
static void MX_I2C1_Init(void) { /* USER CODE BEGIN I2C1_Init 0 */ /* USER CODE END I2C1_Init 0 */ /* USER CODE BEGIN I2C1_Init 1 */ /* USER CODE END I2C1_Init 1 */ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 16; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN I2C1_Init 2 */ /* USER CODE END I2C1_Init 2 */ }
Did I miss something?
Screenshot from Logic Analyzer. After Address read there should be clock streching until the data register ist ready and first byte should be 30 instead of FF.
2024-09-20 06:39 AM
> I cannot measure clock stretching and the first byte transferred is always 0xff.
I don't think the two are related.
Why do you think the first value should be different? Perhaps explicitly set it to something like 0x42.
Your plot shows some minor clock stretching. I2C is slow enough that you shouldn't need to wait for long for the code to catch up. Put some HAL_Delay(1) statements if you want to see it more explicitly.
2024-09-23 06:01 AM
Thank you for your answer. I have explicitly filled the buffer memory in the address callback with values. Unfortunately, the problem still exists.
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode){
HAL_StatusTypeDef hal_i2c_status = HAL_ERROR;
i2c_tx_buffer[0] = 0x42;
i2c_tx_buffer[1] = 0x11;
i2c_tx_buffer[2] = 0x22;
i2c_tx_buffer[3] = 0x33;
i2c_tx_buffer[4] = 0x44;
i2c_tx_buffer[5] = 0x55;
i2c_rx_offset = 0;
if(TransferDirection == I2C_DIRECTION_TRANSMIT){
hal_i2c_status = HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2c_rx_buffer, 1, I2C_NEXT_FRAME);
if(hal_i2c_status != HAL_OK)printf("AC Receive IT error\r\n");
}else if(TransferDirection == I2C_DIRECTION_RECEIVE){
i2c_tx_tf_cnt = 0;
hal_i2c_status = HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &i2c_tx_buffer[i2c_rx_offset], 1, I2C_NEXT_FRAME);// << this always sends 0xff
if(hal_i2c_status != HAL_OK)printf("AC Transmit IT error\r\n");
}
}
In another thread, I read that the first value transmitted is always 0xff if clock stretching is not activated, because the buffer of the TX unit is not ready yet. According to RM, SCL is stretched until ADDR in SR1 is cleared and TxE is cleared by writing to the shift register.
In the function "I2C_Slave_ADDR" [stm32f1xx_hal_i2c.c] the address flag is cleared by "__HAL_I2C_CLEAR_ADDRFLAG(hi2c);" after the "HAL_I2C_AddrCallback" has been executed.
The flag is also cleared beforehand by calling "HAL_I2C_Slave_Seq_Transmit_IT".
The TxE register is probably also cleared by setting the hi2c->pBuffPtr within "HAL_I2C_Slave_Seq_Transmit_IT".
Thus, SCL should remain at low until at least "HAL_I2C_Slave_Seq_Transmit_IT" has been processed. However, if I delay the function, I can see that this is not the case.
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode){
HAL_StatusTypeDef hal_i2c_status = HAL_ERROR;
i2c_tx_buffer[0] = 0x42;
i2c_tx_buffer[1] = 0x11;
i2c_tx_buffer[2] = 0x22;
i2c_tx_buffer[3] = 0x33;
i2c_tx_buffer[4] = 0x44;
i2c_tx_buffer[5] = 0x55;
i2c_rx_offset = 0;
if(TransferDirection == I2C_DIRECTION_TRANSMIT){
hal_i2c_status = HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2c_rx_buffer, 1, I2C_NEXT_FRAME);
if(hal_i2c_status != HAL_OK)printf("AC Receive IT error\r\n");
}else if(TransferDirection == I2C_DIRECTION_RECEIVE){
i2c_tx_tf_cnt = 0;
//DELAY -> SCL should remain lo for at least 10ms
HAL_Delay(10);
hal_i2c_status = HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &i2c_tx_buffer[i2c_rx_offset], 1, I2C_NEXT_FRAME);// << this always sends 0xff
if(hal_i2c_status != HAL_OK)printf("AC Transmit IT error\r\n");
}
}
I would expect SCL to be held at lo for at least 10 milliseconds. However, invalid data is sent instead. As you can see on the analyzer, 0xff is always sent.
Is there any way I can clear the shift register and the data register to make sure that the TxE flag is only set after I have set a valid data buffer with "HAL_I2C_Slave_Seq_Transmit_IT"?
Are there any other approaches to finding out why the chip behaves like this?
2024-09-23 06:08 AM
Yep, seems like you're doing everything correct. Feels like it's a bug in HAL, possibly STM32F1-specific. Not sure what else you could do differently (other than not using HAL).
2024-09-23 06:40 AM
Hi,
just check (in debug) whats in I2C registers:
+ look in errata, for i2c known "problems" ...
2024-09-30 07:12 AM
Sorry for the late reply. Another customer project came up. I've finally got around to debugging in more depth. The errata does indeed have an entry for I2C. But I'm not sure if that applies to me. To be on the safe side, I used workaround 2 and assigned the I2C interrupts the highest priority. Unfortunately, that didn't lead to the desired result.
Then I looked at the registers in the debugger. For an overview, I inserted the read values into the source code.
//i2c_tx_buffer = {0x30, 0x31, 0x30, 0x34, ........ };
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode){
HAL_StatusTypeDef hal_i2c_status = HAL_ERROR;
// CR1 0x0401 -> |SWRST|Res.|ALERT|PEC|POS|ACK|STOP|START|NO STRETCH|ENGC|ENPEC|ENARP|SMB TYPE|Res.|SMBUS|PE|
// | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1|
// CR2 0x0318 -> |Reserved[3]|LAST|DMAEN|ITBUFEN|ITEVTEN|ITERREN|Reserved[2]|FREQ[5]|
// | 0 0 0 | 0 | 0 | 0 | 1 | 1 | 0 0 | 011000|
// SR1 0x0002 -> |SMB ALERT|TIMEOUT|Res.|PECERR|OVR|AF|ARLO|BERR|TxE|RxNE|Res.|STOPF|ADD10|BTF|ADDR|SB|
// | 0 | 0 | 0 | 0 | 0 | 0| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0|
// SR2 0x0002 -> | PEC[7] |DUALF|SMBHOST|SMBDEFAULT|GENCAL|Res.|TRA|BUSY|MSL|
// | 0000000| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
// Data register = 0x00
// hi2c->pBuffPtr[0] = 0x00
if(TransferDirection == I2C_DIRECTION_TRANSMIT){
hal_i2c_status = HAL_I2C_Slave_Sequential_Receive_IT(hi2c, i2c_rx_buffer, 1, I2C_NEXT_FRAME);
if(hal_i2c_status != HAL_OK)printf("AC Receive IT error\r\n");
}else if(TransferDirection == I2C_DIRECTION_RECEIVE){
i2c_tx_tf_cnt = 0;
hal_i2c_status = HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &i2c_tx_buffer[i2c_rx_offset], 1, I2C_NEXT_FRAME);// << this always sends 0xff
//hal_i2c_status = HAL_I2C_Slave_Seq_Transmit_DMA(hi2c, i2c_tx_buffer, 40, I2C_NEXT_FRAME);
if(hal_i2c_status != HAL_OK)printf("AC Transmit IT error\r\n");
}
// CR1 0x0401 -> |SWRST|Res.|ALERT|PEC|POS|ACK|STOP|START|NO STRETCH|ENGC|ENPEC|ENARP|SMB TYPE|Res.|SMBUS|PE|
// | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1|
// CR2 0x0718 -> |Reserved[3]|LAST|DMAEN|ITBUFEN|ITEVTEN|ITERREN|Reserved[2]|FREQ[5]|
// | 0 0 0 | 0 | 0 | 1 | 1 | 1 | 0 0 | 011000|
// SR1 0x0000 -> |SMB ALERT|TIMEOUT|Res.|PECERR|OVR|AF|ARLO|BERR|TxE|RxNE|Res.|STOPF|ADD10|BTF|ADDR|SB|
// | 0 | 0 | 0 | 0 | 0 | 0| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0|
// SR2 0x0002 -> | PEC[7] |DUALF|SMBHOST|SMBDEFAULT|GENCAL|Res.|TRA|BUSY|MSL|
// | 0000000| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
// Data register = 0x00 <--- still 0x00
// hi2c->pBuffPtr[0] = 0x00 <--- still 0x00
}
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c){
i2c_tx_tf_cnt++;
// CR1 0x0401 -> |SWRST|Res.|ALERT|PEC|POS|ACK|STOP|START|NO STRETCH|ENGC|ENPEC|ENARP|SMB TYPE|Res.|SMBUS|PE|
// | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1|
// CR2 0x0318 -> |Reserved[3]|LAST|DMAEN|ITBUFEN|ITEVTEN|ITERREN|Reserved[2]|FREQ[5]|
// | 0 0 0 | 0 | 0 | 0 | 1 | 1 | 0 0 | 011000|
// SR1 0x0000 -> |SMB ALERT|TIMEOUT|Res.|PECERR|OVR|AF|ARLO|BERR|TxE|RxNE|Res.|STOPF|ADD10|BTF|ADDR|SB|
// | 0 | 0 | 0 | 0 | 0 | 0| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0|
// SR2 0x0006 -> | PEC[7] |DUALF|SMBHOST|SMBDEFAULT|GENCAL|Res.|TRA|BUSY|MSL|
// | 0000000| 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
// Data register = 0x30 <---- this should already have been transmitted
// hi2c->pBuffPtr[0] = 0x31 <---- valid
HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &i2c_tx_buffer[(i2c_rx_offset+i2c_tx_tf_cnt)], 1, I2C_NEXT_FRAME);
// CR1 0x0401 -> |SWRST|Res.|ALERT|PEC|POS|ACK|STOP|START|NO STRETCH|ENGC|ENPEC|ENARP|SMB TYPE|Res.|SMBUS|PE|
// | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1|
// CR2 0x0718 -> |Reserved[3]|LAST|DMAEN|ITBUFEN|ITEVTEN|ITERREN|Reserved[2]|FREQ[5]|
// | 0 0 0 | 0 | 0 | 1 | 1 | 1 | 0 0 | 011000|
// SR1 0x0000 -> |SMB ALERT|TIMEOUT|Res.|PECERR|OVR|AF|ARLO|BERR|TxE|RxNE|Res.|STOPF|ADD10|BTF|ADDR|SB|
// | 0 | 0 | 0 | 0 | 0 | 0| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0|
// SR2 0x0002 -> | PEC[7] |DUALF|SMBHOST|SMBDEFAULT|GENCAL|Res.|TRA|BUSY|MSL|
// | 0000000| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
// Data register = 0x30 <---- did not update (should be 0x31)
// hi2c->pBuffPtr[0] = 0x31 <---- did not update (should be 0x30)
}
It seems that pBuffer and thus also the data register are not set correctly. I have set a breakpoint within HAL_I2C_Slave_Seq_Transmit_IT at the position __HAL_I2C_CLEAR_ADDRFLAG(hi2c). I would expect that hi2c->pBuffPtr was assigned pData at this point. That does not seem to be the case.
2024-09-30 08:28 AM - edited 2024-09-30 08:38 AM
So...
1. clock stretching is working - at least sometimes.
2. I cannot tell you much about this: i never tried on an (old) F103 , i work almost only with H7xx and its I2C master.
3. As master it works (usually) fine, also with an clock stretching slave.
4. Did you read : https://community.st.com/t5/stm32-mcus-products/i2c-slave-clock-stretching-to-allow-time-to-prepare-response/td-p/570801
- seems, he got it working, but on more recent F4 .
+
https://controllerstech.com/stm32-as-i2c-slave-part-4/