2024-05-10 07:14 AM
I am using SMBus to communicate with a smart battery. After looking at the code, I decided against using the SMBUS middleware but to directly using the HAL_SMBUS_xxx calls. My STM32G491 is the host and sending read-word commands to the battery works just fine. The bit I'm confused about how to receive commands emitted by the battery itself.
I can see that the battery periodically emits a byte. The value is 0x12 and is by default NACKed. If I treat this value as the host's node address (i.e. 7-bit address 0x09) and set OwnAddress1 to 0x12, the byte is ACKed and another byte (0x14) appears on the bus about 25ms later, which is NACKed. There is another similar exchange about a second later. The error callback is called, apparently because of a timeout. The bus is now stuck in the state HAL_SMBUS_STATE_MASTER_BUSY_TX and I don't know how I'm supposed to clear this. Now sending further read-words commands fail because the bus is "busy".
I'm still looking into this but would appreciate any insight into what is going on. How do I correctly set up the SMBUS driver to receive the incoming command? What callback is supposed to be called? How do I reset the bus after an error is detected. I guess I'll have another look at the ST SMBUS "stack" now that I have the master-slave comms working properly, but this prospect does not fill me with joy.
Solved! Go to Solution.
2024-05-15 02:14 AM
Hello @unicyclebloke
Is the I2C global interrupt enabled in your application? If not, you can enable it using the following instruction:
HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
HAL_NVIC_SetPriority(I2C1_ER_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
2024-05-10 08:44 AM
Hello @unicyclebloke
How do I reset the bus after an error is detected
In the HAL_SMBUS_ErrorCallback, you should handle any errors that occur, such as timeouts or NACKs. To recover the bus from an error state, you may need to reset the SMBus peripheral and reinitialize it.
void HAL_SMBUS_ErrorCallback(SMBUS_HandleTypeDef *hsmbus)
{
if (hsmbus->ErrorCode != HAL_SMBUS_ERROR_NONE)
{
// Reset the SMBus to clear the error
HAL_SMBUS_DeInit(&hsmbus);
HAL_SMBus_Init(&hsmbus);
}
}
The error callback is called, apparently because of a timeout. The bus is now stuck in the state HAL_SMBUS_STATE_MASTER_BUSY_TX and I don't know how I'm supposed to clear this. Now sending further read-words commands fail because the bus is "busy".
If the bus is stuck in the HAL_SMBUS_STATE_MASTER_BUSY_TX state, you may need to manually clear the busy state.
hsmbus.State = HAL_SMBUS_STATE_READY;
How do I correctly set up the SMBUS driver to receive the incoming command? What callback is supposed to be called?
- First, ensure that you have configured the Own Address correctly in the SMBus initialization code. The Own Address should match the address that the battery expects to communicate with.
- To receive data from the battery, your STM32G491 must be configured to operate in SMBus slave mode. This involves setting up the appropriate callbacks for receiving data. please refer to the code snippet bellow:
HAL_SMBUS_EnableListen_IT(&SmbusHandle);
while(SlaveAddrMatchCode != 1);
SlaveAddrMatchCode = 0;
HAL_SMBUS_Slave_Receive_IT(&SmbusHandle, RxBuffer, NUMDATA, SMBUS_FIRST_AND_LAST_FRAME_WITH_PEC);
while (SmbusHandle.State != HAL_SMBUS_STATE_READY);
#if (USE_HAL_SMBUS_REGISTER_CALLBACKS == 1)
void User_SlaveRxCpltCallback(SMBUS_HandleTypeDef *hsmbus)
#else /* USE_HAL_SMBUS_REGISTER_CALLBACKS == 0*/
void HAL_SMBUS_SlaveRxCpltCallback(SMBUS_HandleTypeDef *hsmbus)
#endif
{
// Place your processing here.
}
#if (USE_HAL_SMBUS_REGISTER_CALLBACKS == 1)
void User_AddrCallback(SMBUS_HandleTypeDef *hsmbus,uint8_t TransferDirection, uint16_t AddrMatchCode)
#else /* USE_HAL_SMBUS_REGISTER_CALLBACKS == 0*/
void HAL_SMBUS_AddrCallback(SMBUS_HandleTypeDef *hsmbus,uint8_t TransferDirection, uint16_t AddrMatchCode)
#endif
{
SlaveAddrMatchCode = 1;
}
2024-05-10 09:36 AM
In smart battery systems, address 0z12 is the battery charger address and 0x14 is the smart battery selector (aka smart battery system manager) address. The battery is trying to talk to those two devices. You should probably be playing the "smart battery selector" role and have your address set to 0x14. As @Saket_Om mentioned, you should normally be in slave mode so you can receive messages from the battery. When you want to poll the battery (or otherwise talk to it), you need to exit slave mode and enter master mode. When done, go back to slave mode.
2024-05-13 09:57 AM
Thank you both for the responses. I have learned quite a lot more about smart batteries since my first post.
The remaining feature I need to understand is how to receive a "host notify protocol" message. I place the device into listen mode and wait for the AddrCallback to be called. I can see that the ADDRIE bit is set in CR1, but there is no interrupt unless OwnAddress matches the byte received from the battery. I saw the same behaviour when I tried a Cube-generated example which uses the STACK_SMBUS middleware. This seems strange.
I have examined the STACK_SMBUS_xxx() functions and it appears that the AddrCallback override function checks to see if the received byte matches the default SMBUS_HOST_ADDR as well as OwnAddress. My guess is that some other setting is needed to make the I2C peripheral generate the interrupt. Do you have a suggestion for what might be missing?
2024-05-15 02:14 AM
Hello @unicyclebloke
Is the I2C global interrupt enabled in your application? If not, you can enable it using the following instruction:
HAL_NVIC_SetPriority(I2C1_EV_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_EV_IRQn);
HAL_NVIC_SetPriority(I2C1_ER_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(I2C1_ER_IRQn);
2024-05-15 05:07 AM
Those are both enabled. I opened a newer query to pursue this (to which you have kindly responded). Shall we call this one closed?
Thanks.