Search code examples
cstm32accelerometeri2cbare-metal

Am I setting I2C CCR and TRISE wrong? STM32 [bare-metal]


Following @wovano's suggestion, I'll be boiling down the question. For the old versions of the question, see the editing history.

Overview

I'm trying to include I2C on my project (STM32F407ZGT6, MMA8452Q accelerometer) and I'm setting everything bare-metal. I'm using gschorcht library for the accelerometer functions. I currently do not have access to an osciloscope nor to an FT232H.

Problem

I am unsure if my I2C is working properly: when I try to read my Device ID, I get 0x22 instead of 0x2A*. I'm also not being able to read data in a loop. If I use if (mma845x_new_data (sensor) && mma845x_get_float_data (sensor, &data)), I never get a positive condition, and adjusting as below forces me into the Default_Handler: Infinite_Loop for debugging:

void read_data(void)
{
    mma845x_float_data_t  data;
    if (mma845x_new_data (sensor))
    {
        if(mma845x_get_float_data (sensor, &data))
        {
        // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits
        printf("[MMA845X (xyz)[g]] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", data.ax, data.ay, data.az);
        return;
        }
    }
    printf("[not new MMA845X (xyz)[g]] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", data.ax, data.ay, data.az);
}

The Debug tab shows this trace/breakpoint trap thread: Debug trace/breakpoint trap

I'm wondering if I did something wrong with my CCR or TRISE setting (I included below the calculations I'm using). I have my SYSCLK at 168MHz and APB1 at 42MHz (APB1PRESC = 4), and I'm currently testing the Standard Mode.

Things I have right

  1. the board has all pull-up resistors properly placed
  2. MODER and AFR are properly set
  3. the SA0 is set to high- or low-logical level to match the device address
  4. STOP is being sent after reading data from DR

Code

void init_gpio(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;    //Port B

    GPIOB->MODER  |= (2U << 12U);   //MMA845_SCL || PB6
    GPIOB->AFR[0] |= (4U << 24U);   //AF4
    GPIOB->OTYPER |= (1U << 6U);    //Output Type to Open Drain
    GPIOB->PUPDR  |= (1U << 12U);   //Set to Pull-Up

    GPIOB->MODER  |= (2U << 14U);   //MMA845_SDA || PB7
    GPIOB->AFR[0] |= (4U << 28U);   //AF4
    GPIOB->OTYPER |= (1U << 7U);    //Output Type to Open Drain
    GPIOB->PUPDR  |= (1U << 14U);   //Set to Pull-Up
}

/*i2c_init*/
void i2c_init(I2C_TypeDef *I2Cx)
{
    /*Enable clock access to I2Cs*/
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //enable I2C1
//  RCC->APB1ENR |= RCC_APB1ENR_I2C2EN; //enable I2C2
//  RCC->APB1ENR |= RCC_APB1ENR_I2C3EN; //enable I2C3
    RCC->APB1ENR;

    /*Reset I2C*/
    I2Cx->CR1 |= I2C_CR1_SWRST;    /*!BUSY ON */
    I2Cx->CR1 &= ~I2C_CR1_SWRST;   /*!BUSY OFF*/

    int CCR_VAL;
    int TRISE_VAL;
    int MAX_FRQ;

    /*Set I2C frequency same as APB*/
    MAX_FRQ = (SYSCLK/APB1PRESC)/1000000;   //= 42 @ max clock speed
    I2Cx->CR2 &= ~msk(6,0);        /*!BUSY ON*/
    I2Cx->CR2 |= MAX_FRQ;

    if(!MASTER_MODE){
        /* Standard Mode = 100kb/s*/
        /* CCR calculated by half-period of 100k
         * divided by the period of the PCLK
         * CCR = ((1/100'000)/2)/(1/42'000'000)
         * CCR = 5000ns/~24ns = 210
         */
        CCR_VAL = 210;
        /* TRISE calculated by PCLK (in MHz) + 1
         * TRISE = ((42'000'000/1'000'000) + 1)
         * TRISE = 42 + 1 = 43
         */
        TRISE_VAL = 43;
    }
    else
    {
        /*Fast Mode = 400kb/s*/
        if(DUTY_MODE)
        {
            /*16:9 ratio*/
            /* CCR calculated by half-period of 100k
             * divided by the sum of the duty ratios
             * divided by the period of the PCLK
             * t_high = 9 * CCR * TPCLK
             * t_low  = 16 * CCR * TPCLK
             * t_high + t_low = 25 * CCR * TPCLK
             * CCR = ((1/400'000)/2) / (25/42'000'000)
             * CCR = 1250ns/~595ns = 2.1 ~ 2
             */
            CCR_VAL = 2;
        }
        else
        {
            //2:1 ratio
            /* CCR calculated by half-period of 100k
             * divided by the sum of the duty ratios
             * divided by the period of the PCLK
             * t_high = CCR * TPCLK
             * t_low  = 2 * CCR * TPCLK
             * t_high + t_low = 3 * CCR * TPCLK
             * CCR = ((1/400'000)/2) / (3/42'000'000)
             * CCR = 1250ns/~71ns = 17.5 ~ 17
             */
            CCR_VAL = 17;
        }
        /* TRISE calculated by PCLK (in MHz) * 0.3ms + 1
         * TRISE = ((42'000'000/1'000'000)*(300/1000) + 1)
         * TRISE = 42 * 0.3 + 1 = 13.6 ~ 14
         */
        TRISE_VAL = 14;
    }

    /*Set Clock Control value*/
    I2Cx->CCR |= CCR_VAL;   //assuming APB1 = 42MHz, SM = True
    I2Cx->CCR |= (set(DUTY_MODE,I2C_CCR_DUTY_Pos) | set(MASTER_MODE,I2C_CCR_FS_Pos));

    I2Cx->TRISE |= TRISE_VAL;   //assuming APB1 = 42MHz, SM = True
    /*Enable I2C*/
    I2Cx->CR1 |= I2C_CR1_PE;
}

void set_addr(I2C_TypeDef *I2Cx, uint8_t sAddr, const uint8_t *mAddr, int read)
{
    volatile int temp;
    /*Check I2C is not busy*/
    while(I2Cx->SR2 & I2C_SR2_BUSY);

    /*(1) Generate a START, wait for start flag*/
    I2Cx->CR1 |= I2C_CR1_START;
    while (!(I2Cx->SR1 & I2C_SR1_SB));

    /*Transmit slave address + 'write'*/
    I2Cx->DR = sAddr << 1;

    /*Wait for the address flag*/
    while (!(I2Cx->SR1 & I2C_SR1_ADDR));
    temp = I2Cx->SR2; //Clear address flag

    /*Send memory address*/
    //Make sure TXE is emtpy
    while (!(I2Cx->SR1 & I2C_SR1_TXE));
    I2Cx->DR = mAddr;
    //Wait until transmitter is emtpy
    while (!(I2Cx->SR1 & I2C_SR1_TXE));

    if(read)
    {
        /*(2) Generate a START, wait for start flag*/
        I2Cx->CR1 |= I2C_CR1_START;
        while (!(I2Cx->SR1 & I2C_SR1_SB));

        /*Transmit slave address + 'read'*/
        I2Cx->DR = (sAddr << 1) | 1;

        /*Wait for the address flag*/
        while (!(I2Cx->SR1 & I2C_SR1_ADDR));
        temp = I2Cx->SR2; //Clear address flag
    }
}

int i2c_burst_read(I2C_TypeDef *I2Cx, uint8_t sAddr, const uint8_t *mAddr, uint8_t *string, uint32_t size)
{
    if(size == 0)
    {
        return 0;
    }
    uint8_t *pData = string;    //points at the first byte (char) of string
    set_addr(I2Cx, sAddr, mAddr, 1);

    /*Enable ACK*/
    I2Cx->CR1 |= I2C_CR1_ACK;

    while(size > 0U)
    {
        if(size-- == 1)
        {
            /*Disable ACK*/
            I2Cx->CR1 &= ~I2C_CR1_ACK;

            /*Wait until the receive flag is set*/
            while(!(I2Cx->SR1 & I2C_SR1_RXNE));

            //"Generate STOP" was here before

            /*Read data from the DR*/
            *pData++ = I2Cx->DR;
            break;
        }
        else
        {
            /*Wait until the receive flag is set*/
            while(!(I2Cx->SR1 & I2C_SR1_RXNE));

            /*Read data from the DR*/
            *pData++ = I2Cx->DR;
        }
    }
    /*Generate a STOP after receiving*/
    I2Cx->CR1 |= I2C_CR1_STOP;
    return 1;
}

int i2c_burst_write(I2C_TypeDef *I2Cx, uint8_t sAddr, const uint8_t *mAddr, uint8_t *string, uint32_t size)
{
    uint8_t *pData = string;    //points at the first byte (char) of string
    set_addr(I2Cx, sAddr, mAddr, 0);

    while(size > 0U)
    {
        /*Wait for transmitter to be empty*/
        while(!(I2Cx->SR1 & I2C_SR1_TXE));

        I2Cx->DR = *pData++;
        size--;
    }

    /*Wait for byte transfer finished flag*/
    while(!(I2Cx->SR1 & I2C_SR1_BTF));

    /*Generate a STOP after writing*/
    I2Cx->CR1 |= I2C_CR1_STOP;
    return 1;
}

*This might be due to it being a "different brand". Although it raises some suspicion that it isn't reading all the bits (0x2A in binary is 0b0010 1010 and 0x22 is 0b0010 0010), testing it with another accelerometer gives me the value of 0x05. There's a chance bits are being lost but I'm not sure.


Solution

  • I figured my I2C communication was actually working! So the answer is 'Yes, my CCR and TRISE' settings were correct (at least for the 100kHz mode). To check this, I borrowed an Arduino and I pastade a simple Wire code to read and write from the line. With it, I discovered that:

    1. I could read and write to the I2C bus (without register address)
    2. I had problems when using the register address mAddr
    3. The chip's ID is actually 0x2A

    I was mistakenly sending to the function the address of the variable containing the memory address instead of its content (instead of mAddr = 0x1D, I was passing &mAddr as argument).

    Also, @wovano guess about the HardFault was correct too. The printf was causing the crash, either because of the floating-point linkage or because it was reading an empty variable (since it wasn't getting any data).

    I'm still having issues reading data from the MMA since my STATUS->ZYXDR flag never goes high (mma845x_new_data(sensor) always returns 0). My initialization is as follows, in case anyone has experience with Freescale semiconductors (library link):

    #include "mma845x.h"
    
    #define MMA845X_I2C_ADDRESS_1           0x1c  // SDO pin is low
    #define MMA845X_I2C_ADDRESS_2           0x1d  // SDO pin is high
    
    static mma845x_sensor_t* sensor;
    
    void init_MMA_sensor(void)
    {
        sensor = mma845x_init_sensor(I2C1, MMA845X_I2C_ADDRESS_2);
        mma845x_config_int_signals(sensor, mma845x_high_active, mma845x_push_pull);
        mma845x_config_hpf(sensor, 0, true);
        mma845x_set_scale(sensor, mma845x_scale_2_g);
        mma845x_set_mode(sensor, mma845x_high_res, mma845x_odr_50, true, false); //make it active!
    }
    
    void read_data(void)
    {
         mma845x_float_data_t  data;
         if (mma845x_new_data(sensor))
         {
             if(mma845x_get_float_data(sensor, &data))
            {
                // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits
                printf("[MMA845X (xyz)[g]] ax=%+7.3f ay=%+7.3f az=%+7.3f\r\n",
                        data.ax, data.ay, data.az);
                return;
             }
         }
         printf("No new data available\r\n");
    }
    
    int main(void)
    {
        /*Setup*/
        clock_init_168();
        init_systick_MS(SYSTICK_LOAD_VAL_MS);
        gpio_init();
        uart_init();
        i2c_init(I2C1);
        init_MMA_sensor();
    
        for(;;)
        {
            read_data();
            delayMS(500);
        }
    }
    

    But the intial question is resolved. Thanks for all your insight.