diff --git a/os/hal/platforms/STM32/I2Cv1/i2c_lld.c b/os/hal/platforms/STM32/I2Cv1/i2c_lld.c index 5957266d4..1c325b9b0 100644 --- a/os/hal/platforms/STM32/I2Cv1/i2c_lld.c +++ b/os/hal/platforms/STM32/I2Cv1/i2c_lld.c @@ -290,6 +290,9 @@ static void i2c_lld_set_opmode(I2CDriver *i2cp) { * @details Important but implicit destination of this function is * clearing interrupts flags. * @note Internal use only. + * @note Function reads SR2 before SR1. This is workaround, not a bug. + * ADDR was not cleared and it was possible to clear ADDR after + * transfer is properly setup (enabled DMA stream). * * @param[in] i2cp pointer to the @p I2CDriver object * @@ -297,8 +300,8 @@ static void i2c_lld_set_opmode(I2CDriver *i2cp) { */ static uint32_t i2c_get_event(I2CDriver *i2cp) { I2C_TypeDef *dp = i2cp->i2c; - uint16_t regSR1 = dp->SR1; uint16_t regSR2 = dp->SR2; + uint16_t regSR1 = dp->SR1; #if CH_DBG_ENABLE_ASSERTS dbgSR1 = regSR1; @@ -319,15 +322,21 @@ static uint32_t i2c_get_event(I2CDriver *i2cp) { */ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { I2C_TypeDef *dp = i2cp->i2c; + uint32_t event = i2c_get_event(i2cp); + uint32_t rxlen = dmaStreamGetTransactionSize(i2cp->dmarx); /* Interrupts are disabled just before dmaStreamEnable() because there is no need of interrupts until next transaction begin. All the work is done by the DMA.*/ - switch (i2c_get_event(i2cp)) { + switch (event) { case I2C_EV5_MASTER_MODE_SELECT: dp->DR = i2cp->addr; break; case I2C_EV6_MASTER_REC_MODE_SELECTED: + if (rxlen <= 2) + dp->CR1 &= ~I2C_CR1_ACK; + if (rxlen == 2) + dp->CR1 |= I2C_CR1_POS; dp->CR2 &= ~I2C_CR2_ITEVTEN; dmaStreamEnable(i2cp->dmarx); dp->CR2 |= I2C_CR2_LAST; /* Needed in receiver mode. */ @@ -338,7 +347,7 @@ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { break; case I2C_EV8_2_MASTER_BYTE_TRANSMITTED: /* Catches BTF event after the end of transmission.*/ - if (dmaStreamGetTransactionSize(i2cp->dmarx) > 0) { + if (rxlen > 0) { /* Starts "read after write" operation, LSB = 1 -> receive.*/ i2cp->addr |= 0x01; dp->CR1 |= I2C_CR1_START | I2C_CR1_ACK; @@ -351,6 +360,10 @@ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { default: break; } + /* Clear ADDR flag. Last part of workaround + (see comments to i2c_get_event() function). */ + if (event & (I2C_SR1_ADDR | I2C_SR1_ADD10)) + if (dp->SR2){;} } /**