cstm32bare-metal

Why does I2C send wrong data?


I am trying to communicate with the MCP4728 DAC chip using STM32G474RE's I2C. The bytes I want to send to the chip are {0xFF, 0x58, 0x04, 0x56} and the debug agrees that the TX register receives the above bytes. However, when I actually transmit the bytes, what shows up on the oscilloscope are {0xFF, 0x58, 0xFF, 0xFF} and whenever I modify the first byte the third and fourth would also change.

int main(void){
    SystemClock_Config();   //16MHz HSI16 Clock
    I2C2_Init();
    while (1){
        MCP4728_Transmit(SINGLE_WRITE, CHANNEL_A, 0x456);
    }
}

void MCP4728_Transmit(uint8_t tx_mode, uint8_t channel, uint16_t data){
    /*If data larger than 12 bits (0xFFF) return*/
    if (data > 0xFFF)
        return;
    
    int tx_buffer_index = 0;
    uint8_t tx_buffer[4];
    tx_buffer[0] = 0xFF;
    tx_buffer[1] = tx_mode | (channel << 1); 
    tx_buffer[2] = (uint8_t) (data >> 8) & 0x0F;
    tx_buffer[3] = (uint8_t) data & 0xFF;
    
    I2C2->CR2 |= 0x04 << I2C_CR2_NBYTES_Pos;
    I2C2->CR2 &= ~I2C_CR2_AUTOEND;
    I2C2->CR2 |= ((0x60<<1)<< I2C_CR2_SADD_Pos);
    I2C2->CR2 |= I2C_CR2_START;
    
    
    
    while(!(I2C2->ISR & I2C_ISR_TC)){
        if (I2C2->ISR & I2C_ISR_NACKF)
            return;
        if (!(I2C2->ISR & I2C_ISR_TXIS))
            return;
        else{
            I2C2->TXDR = tx_buffer[tx_buffer_index];
            tx_buffer_index++;
        }
    }
    I2C2->CR2 |= I2C_CR2_STOP;
}

void I2C2_Init(void){
    /*Enable I2C and GPIOA clock*/
    RCC->APB1ENR1 |= RCC_APB1ENR1_I2C2EN;
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
    
    
    /*Config PA8 and PA9*/
    //PA8 and PA9 Alternate Function (AF4)
    GPIOA->MODER &= ~(GPIO_MODER_MODE8 | GPIO_MODER_MODE9);
    GPIOA->MODER |= (0x02 << GPIO_MODER_MODE8_Pos | 0x02 << GPIO_MODER_MODE9_Pos);
    GPIOA->AFR[1] |= (0x04 << GPIO_AFRH_AFSEL8_Pos | 0x04 <<GPIO_AFRH_AFSEL9_Pos);
    //Output type Open Drain
    GPIOA->OTYPER |= (GPIO_OTYPER_OT8 | GPIO_OTYPER_OT9);
    //High Speed
    GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED8 | GPIO_OSPEEDR_OSPEED9);
    GPIOA->OSPEEDR |= (0x02 << GPIO_OSPEEDR_OSPEED8_Pos | 0x02 << GPIO_OSPEEDR_OSPEED9_Pos);
    //Enable Pull Up
    GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD8 | GPIO_PUPDR_PUPD9);
    GPIOA->PUPDR |= (0x01 << GPIO_PUPDR_PUPD8_Pos | 0x01 << GPIO_PUPDR_PUPD9_Pos);
    
    
    /*Configure I2C2*/
    //Disable I2C
    I2C2->CR1 &= ~I2C_CR1_PE;
    //Default filter
    I2C2->CR1 &= ~I2C_CR1_ANFOFF;
    I2C2->CR1 &= ~I2C_CR1_DNF;
    //TIMINGR value for 16MHz taken from table in Reference Manual (page xxx)
    I2C2->TIMINGR |= (0x03 << I2C_TIMINGR_PRESC_Pos  | 0x04 << I2C_TIMINGR_SCLDEL_Pos | \
                      0x02 << I2C_TIMINGR_SDADEL_Pos | 0x0F << I2C_TIMINGR_SCLH_Pos   | \
                      0x13 << I2C_TIMINGR_SCLL_Pos);
    //Enable stretching
    I2C2->CR1 &= ~I2C_CR1_NOSTRETCH;
    //Enable I2C
    I2C2->CR1 |= I2C_CR1_PE;
}

Solution

  • I2C_ISR.TXIS indicates, that transmit buffer is empty, i.e. that data have been moved to shift register. This may happen almost immediately for the first byte, when shift register is still empty, but after you write the second byte, it takes transmitting the whole previous byte for TXIS to become 1 again. However, you don't wait:

    if (!(I2C2->ISR & I2C_ISR_TXIS))
                return;
    

    which means, that immediately after second byte is written and third can't be, you return. As in main loop you call this function repeatedly unconditionally, once the first byte is transmitted, second is transmitted to shift register and TXIS becomes 1 again, this function again stores the first byte, and does not succeed to store the second, etc. That's why you see the pattern first byte - second byte - first byte - first byte.

    Wait for I2C_ISR.TXIS in a loop.