2024-05-15 01:02 AM
I am studying the SMBUS stack code to understand how it works. My system has a smart battery. The battery periodically emits two messages intended for the charger (charging voltage and charging current). My software is the host. Sending commands to the battery is working just fine. As an exercise I am trying to receive the two charger messages. Following (more or less) the ST code I do this:
- Set own address to the expected charger address
- Call HAL_SMBUS_EnableListen_IT()
- enables ADDR interrupt, I guess
- On ADDR interrupt, AddrCallback is called
- Call HAL_SMBUS_Slave_Receive_IT() to receive one byte (the command ID)
- On completion, SlaveRxCpltCallback() is called
- Call HAL_SMBUS_Slave_Receive_IT() to receive remaining bytes of the command
- On completion, SlaveRxCpltCallback() is called a second time
- Call HAL_SMBUS_EnableListen_IT() to set up for the next command
This apparently works perfectly for the first command. I get the ADDR interrupt, the command byte and the data bytes, and I see STOP on the bus. But then... For the second command, about 1s later, I don't see the ADDR interrupt. I can see that the bus is held low for a bit, stretching the clock, and then I get the error interrupt.
I'll have to add some code to log the register values upon each interrupt, but was hoping for some insight here. I realise this is a bit vague, but what might I be missing? I have looked at the reference manual and found the interaction of some of the control bits a little unclear. I note that the SBC bit has been set when it wasn't previously, but clearing it after the first command is done has no effect.
2024-05-15 02:36 AM
Hello @unicyclebloke
Could you please capture the traffic data on the bus using a logic analyzer and send it to us?
Additionally, could you confirm whether the address is retransmitted by the Master for the second command?
2024-05-15 05:45 AM
Thank you for your response. I can confirm that the address is retransmitted for the second command.
For comparison, this capture shows the two messages when I use the ST SMBUS stack code with own address set to 0x12 (7-bit address 0x09). This is working fine.
This image shows the two messages when I use my own code to intercept the ADDR interrupt callback and so on as described. The first one appear to do the right thing. The second (and subsequent) message fails. The address appears on the bus but ADDR interrupt callback is not called. It seems that the interrupt does not occur at all. So the clock is held low for 25ms (the configured timeout), and then the next byte appears (presumably when the clock is released), but is NAKed.
I'm pretty sure that the ADDRI flag is set to allow the address interrupt, but suspect there is some other settings which I have not got quite right. The initial set up is fine, but perhaps there is a side-effect of receiving a message which I have not dealt with correctly.
Clearly the ST stack works well enough. I would like to understand the internals because, unfortunately, the callback model used is a bit convoluted and inconsistent with the C++ API I have created for the I2C/SMBUS peripheral. In any case, I only need a small subset of SMBUS features. Sadly, I found the code rather complicated and could not easily identify just the parts I need.
Thanks again.
2024-05-15 06:34 AM - edited 2024-08-06 03:35 AM
I see that the address is acknowledged by the slave. Check if the address match flag is set in the SR register. If yes, insert a breakpoint in the SMBUS IRQ Handler to observe the behavior during the address interrupt event.
2024-05-15 07:34 AM
Thanks again for a prompt response. I have found debugging the ISRs very difficult so have just recorded the complete state of the I2C peripheral in each ISR until the error occurs. I recorded the registers before and after the interrupt calls into HAL, so there are before and after blocks shown for each interrupt. Please have a look at the attached spreadsheet.
It looks to me as if I might have done something wrong with the PEC at the end of the first message. Maybe I should not attempt to read the PEC at all (I found the XferOptions flags a bit confusing). My calls to receive the bytes for the first message are:
// To receive the command byte (called from address callback):
HAL_SMBUS_Slave_Receive_IT(&m_hsmbus, &m_rx_buffer[0], 1U, SMBUS_NEXT_FRAME);
// To receive the remaining bytes (called from slave RX complete callback):
2024-05-15 09:25 AM
Hello @unicyclebloke
The value 0x06000000 correspond to the XferOptions SMBUS_LAST_FRAME_WITH_PEC. Could you try with the option SMBUS_LAST_FRAME_NO_PEC.
2024-05-15 09:25 AM
I have finally got something more or less working! The callbacks are represented in the following C++ functions. I also disabled the PEC in the I2C configuration. This appears to receive the two commands without error but there is still something little odd:
- The first command (address + command + 2 data) has its last byte ACKed
- The second command (address + command + 1 data) has its last byte NACKed
- SlaveRxCpltCallback was called in the RxState::Data state for only one of the commands
- ListenCpltCallback was called only for one of the commands (the other one)
The inconsistency in these callbacks is repeatable and might be related to the number of data bytes.
void SMBusDriverBase::AddrCallback(uint8_t direction, uint16_t address_match_code)
{
if ((address_match_code << 1) == m_hsmbus.Init.OwnAddress1)
{
// Receive one byte to identify the command being received. What should XferOptions be?
m_rx_state = RxState::Command;
HAL_SMBUS_Slave_Receive_IT(&m_hsmbus, &m_rx_buffer[0], 1U, SMBUS_NEXT_FRAME);
}
else
{
__HAL_SMBUS_GENERATE_NACK(&m_hsmbus);
}
}
void SMBusDriverBase::SlaveRxCpltCallback()
{
switch (m_rx_state)
{
// The command byte has been received.
case RxState::Command:
switch (m_rx_buffer[0])
{
case 0x14:
// This command has two data bytes to receive. What should XferOptions be?
HAL_SMBUS_Slave_Receive_IT(&m_hsmbus, &m_rx_buffer[1], 2U, SMBUS_NEXT_FRAME);
break;
case 0x15:
// This command has one data byte to receive. What should XferOptions be?
HAL_SMBUS_Slave_Receive_IT(&m_hsmbus, &m_rx_buffer[1], 1U, SMBUS_NEXT_FRAME);
break;
}
m_rx_state = RxState::Data;
break;
// The data byte(s) have been received.
case RxState::Data:
// Should NAK the last byte?
__HAL_SMBUS_GENERATE_NACK(&m_hsmbus);
HAL_SMBUS_EnableListen_IT(&m_hsmbus);
break;
}
}
void SMBusDriverBase::ListenCpltCallback()
{
// Appears to be called on STOP seen on the bus but only called for one of the two commands.
// Should NAK the last byte?
__HAL_SMBUS_GENERATE_NACK(&m_hsmbus);
HAL_SMBUS_EnableListen_IT(&m_hsmbus);
}