STM32 as I2C SLAVE || PART 3
This is the 3rd tutorial in the STM32 I2C Slave series. In the previous tutorial we saw how to receive data in a circular buffer. The slave was able to receive any amount of data, the master can send.
Although the slave was able to receive the data without triggering the NACK response, but it is very hard to process this data. This is because we ned to keep track of the start and end position of the buffer, where the new data is received.
In this tutorial we will try another approach which will make it easier for us to process this data. There are 3 main points we will cover in this tutorial.
- Whenever the master transmits a new data (using the start condition), the data will always be stored from the beginning of the Rx Buffer.
- The slave will only receive a fixed maximum amount of data at once. Although there is no restriction on the minimum amount of data.
- The processing of data will start in 2 scenarios:
- when the master stops transmitting before the amount of data, the slave was expecting.
- when the master has transmitted the amount of data, the slave was expecting. Irrespective of either the master has stopped or continuing the transmission, the data processing will begin.
We will cover the points mentioned above. The cubeMX setup is still the same that we used in the previous tutorial.
CubeMX Setup
Above shown is the configuration for the I2C1
- The mode is set as standard mode with the clock speed of 100000 Hz
- The
Clock No Stretch Mode
is disabled, that means the Clock stretching is enabled. - The Primary slave address length is 7 bit and the address for the device is set to 0x12 (7 bit)
- The STM32 I2C is capable of acting as 2 different slave devices with 2 different addresses, but it is disabled, and there will be only 1 slave.
- We will cover more about Clock stretching and General call address detection in the upcoming tutorials.
We also need to enable the Event Interrupt and Error Interrupt in the NVIC Tab
The pinout is shown below
The pin PB6 is the SCL (Clock) pin and must be connectde to the SCL pin of the master. The pin PB7 is the SDA (Data) pin and must be connected to the SDA of the master. If you are connecting 2 similar MCUs, you can connect the same pins together. For eg- PB6 -> PB6 and PB7 -> PB7.
Some Insight into the CODE
We created separate file to write the source code for the I2C Slave. We will modify these files again.
The i2c_slave.c is in the src folder and the i2c_slave.h is in the inc folder. The image is shown below.
The main function remains the same. We put the I2C in the Listen mode.
HAL_I2C_EnableListen_IT(&hi2c1);
Address Callback
The changes are going to be made in the slave source file. They are as follows
int is_first_recvd = 0;
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
if (TransferDirection == I2C_DIRECTION_TRANSMIT) // if the master wants to transmit the data
{
rxcount = 0;
countAddr++;
// receive using sequential function.
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, RxData+rxcount, 1, I2C_FIRST_FRAME);
}
}
The Address Callback is called when the address sent by master matches with the slave address.
- Here we will check if the Master wants to Write the data or Read it, using the variable TransferDirection.
- If the Master wants to write (Transmit) the data, we will start receiving the data.
- The variable rxcount keeps track of the buffer position, so we will reset it to 0. This way the new data will start storing from the beginning of the RxData buffer.
- The slave will receive only 1 byte in the interrupt mode, and the Option is set as I2C_FIRST_FRAME.
- The FIRST FRAME option allow to manage a sequence with start condition, and is generally used when the slave receives the fresh new byte.
Once the slave successfully receives 1 byte data, the Rx complete callback will be called.
Receive Callback
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
rxcount++;
if (rxcount < RxSIZE)
{
if (rxcount == RxSIZE-1)
{
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, RxData+rxcount, 1, I2C_LAST_FRAME);
}
else
{
HAL_I2C_Slave_Sequential_Receive_IT(hi2c, RxData+rxcount, 1, I2C_NEXT_FRAME);
}
}
if (rxcount == RxSIZE)
{
process_data();
}
}
- In the Rx complete callback we increment the rxcount variable, so that the new data to be received can be stored at a new position in the buffer.
- Since we don’t want to receive more data than the RxSIZE, the reception will only continue if the rxcount is less than the RxSIZE.
- As long as the buffer has space in it, the slave will continue receiving 1 bye of data using the option I2C_NEXT _FRAME.
- I2C_NEXT_FRAME implies that the slave is receiving this byte and is also ready to receive the next byte.
- If the rxcount has reached a value that is 1 less than the RxSIZE (this means that there is only 1 space available in the buffer), we will receive 1 byte of data with the option set to I2C_LAST_FRAME.
- I2C_LAST_FRAME is used to indicate that the slave does not wants to receive anymore data after this. Now it’s upto the master, whether it wants to continue the transmission or end it. But the slave will start sending a NACK response after this reception is complete.
Basically the slave wants to end the transmission after receiving “RxSIZE” bytes of data. Also remember that even after receiving with the option of I2C_LAST_FRAME, the Rx complete callback will be called. So we need to make sure that the slave does not start the reception again. This is why the entire receving sequence is safeguarded inside the if (rxcount < RxSIZE)
condition.
If the master sends more data than the RxSIZE, the slave will start sending the NACK response.
Finally if the rxcount is equal to RxSIZE (that means the RxData buffer is full), we will start processing the received data.
Error Callback
I mentioned earlier that the processing will begin when the slave has received all the data (RxSIZE) or if the master ends the transmission before that. In the latter case, an error is triggerd in the slave device. This is because it was expecting more data from the master, but the master sent a stop condition instead.
The Acknowledgement Failure error gets triggerd in the above scenario. We will use this error as an indication that the master has stopped the transmission.
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
counterror++;
uint32_t errorcode = HAL_I2C_GetError(hi2c);
if (errorcode == 4) // AF error
{
process_data();
}
HAL_I2C_EnableListen_IT(hi2c);
}
- In the error callback function, we will first check the errorcode.
- If the errorcode is 4 (AF Error), indicating that the master has sent a stop condition, we will start processing the data.
Whether the processing data has been called by the “error callback” or the “Rx complete callback”, the valid data is always between RxData[0] and RxData[rxcount-1].
Result
Watch the video to see the complete working.