STM32F103RC hardware I2C from the pit to the pit

Publisher:纯真年代Latest update time:2019-04-08 Source: eefocusKeywords:STM32F103RC Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

first day

What resources are available?

1. An MPU6050

2. A stm32f103rc minimum system version with ucos3 ported to it, running several tasks, including an LCD task (lowest priority)

3.st firmware library

4.ALIENTEK's MPU6050 routine

5. Various data manuals and documents of stm32, various materials of ucos


Okay, let’s get started.


First read the stm32 data sheet to understand the principle and architecture of the I2C module. I don't understand it very well, but I will stop reading it if I know anything about it.


Then take a look at the MPU6050 example code of ALIENTEK. Huh? Why is I2C simulated by software? STM32 has an integrated I2C controller but it is not used. What a waste. I won't read it. . . Let's take a look at how MPU6050 works. It probably has a device address of 0x68 and many registers. The data communication is the data of the registers. Each time data is transmitted, the device address 0x68 is sent first, then the register address is sent, and finally the data is received/sent.


Then I copied the code for operating MPU6050 in the ALIENTEK example, rewrote all the functions in it, and used the library function to operate the STM32 hardware I2C to recompile the I2C communication process. After coding, I tested it and it didn't work. I used fprintf to print information to the serial port (I didn't have a debugger, and I was so poor that I only had a serial port board). Then I started the arduous exploration (10,000 words omitted). . . . . . .


After printing information on the serial port, consulting materials, and searching online, I finally successfully rewrote the MPU_Write_Byte(INT08U reg, INT08U data) function in the ALIENTEK routine as follows:


INT08U MPU_Write_Byte(INT08U reg, INT08U data)  

{

    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));


    I2C_GenerateSTART(I2C1, ENABLE);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));


    I2C_Send7bitAddress(I2C1, (MPU6050_ADDR<<1), I2C_Direction_Transmitter);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));


    I2C_SendData(I2C1, reg);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));


    I2C_SendData(I2C1, data);

    I2C_GenerateSTOP(I2C1, ENABLE);


    return 1;

}


The main problems encountered are:

1. The device address 0x68 needs to be shifted left by one bit, and then the read/write bit is added, read is 1, write is 0. Note that it is not 0x68+1 (or +0), but (0x68<<1)|0x01 (or 0x00), that is, 0xd1 is to read data from MPU6050, and 0xd0 is to write data to MPU6050.

2. **Strictly follow the communication process given in the routine of st to code. **There are various reports on the Internet that stm32 hardware I2C has problems. Well, there may be some problems, but for people like me, we will never encounter them. If there are any problems, they are our own problems. Strictly follow the routine of st, there will be absolutely no problem. Don't code by yourself, and don't think that the code you write is equivalent to the code in the routine of st, and believe that you are right. I refer to the routine of st's EEPROM.


Then rewrote the MPU_Read_Byte(INT08U reg) function, OK.

The only missing function is MPU_Read_Len(INT08U addr, INT08U reg, INT08U len, INT08U *buf) to read the data of MPU6050. After modification, I tested it and it didn't work. I inserted an fprintf after each line of code. I tested it and the computer got a blue screen. I restarted and tested it and the computer got a blue screen... OMG!!! It's 12 o'clock now, so I lie down in bed, eat PUBG and go to sleep.


the next day

I turned on the computer and tested it. The computer blue screen... What's the point of doing this? Give up... I removed fprintf and saw that it didn't blue screen. But how can I debug the program? (I don't have a debugger, so I can only rely on the serial port board to print information) I searched online... I pondered... (10,000 words omitted)

Finally, it's fixed!!! The code is as follows,


INT08U MPU_Read_Len(INT08U addr, INT08U reg, INT08U len, INT08U *buf)

{

    INT08U rlen = 0;

    CPU_SR_ALLOC();


    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));


    I2C_GenerateSTART(I2C1, ENABLE);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));


    I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Transmitter);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));


    I2C_SendData(I2C1, reg);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_GenerateSTOP(I2C1, ENABLE);

    

    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));


    I2C_GenerateSTART(I2C1, ENABLE);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));


    I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Receiver);

    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));


    I2C_AcknowledgeConfig(I2C1, ENABLE);

    GPIO_SetBits(GPIOB,GPIO_Pin_7);


    OS_CRITICAL_ENTER();

    for(rlen = 0; rlen < len; rlen++)

    {

        if(len == 1+rlen)

        {

            I2C_AcknowledgeConfig(I2C1, DISABLE);

            (void)I2C1->SR2;

            I2C_GenerateSTOP(I2C1, ENABLE);

        }

        while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));

        *buf = I2C_ReceiveData(I2C1);

        buf++;

    }

    OS_CRITICAL_EXIT();


    return rlen;

}


The main problems encountered are:

1. When receiving data, SDA must output a high level! ! ! (3 exclamation marks represent important things to say 3 times) The SDA pin of stm32 has selected GPIO_Mode_AF_OD analog open-drain output, and the SDA of the MPU6050 module has an external pull-up resistor. If the SDA of stm32 outputs a low level, then SDA cannot be pulled high.

2. I saw a method on the Internet that

I2C_AcknowledgeConfig(I2C1, DISABLE);

(void)I2C1->SR2;

I2C_GenerateSTOP(I2C1, ENABLE);

These three sentences must be written in succession. First, the SR2 register must be read to complete certain operations of clearing register flags (see the data sheet). This is indeed a pitfall of stm32, but it cannot be said that there is a problem with it. Anyway, the correct process is what it is, and it should be encoded according to the correct process.

3. During the data receiving phase, I use OS_CRITICAL_ENTER() to disable interrupts to ensure the correct I2C timing and be safer. (Actually, this understanding is not correct. The entire I2C communication timing has the risk of failure due to interruption. The key is where it can be interrupted (such as when waiting for certain flags to be set) and where it must not be interrupted (such as when reading and writing registers after a certain event))

Test it out, the serial port prints out acceleration, gyroscope, temperature and other data, OK, we're done.

Huh? Why does the LCD display intermittently? It seems that I2C reading data is too time-consuming and also prevents task switching. Rewrite the MPU_Read_Len(INT08U addr, INT08U reg, INT08U len, INT08U *buf) function. Change the while loops in it that wait for the flag to be set to pend a semaphore, and then post a semaphore after the flag is set to trigger an interrupt, and wait for the interrupt to do it, and give the CPU to other tasks during this period.

Coding, coding, coding... It's 12 o'clock again, so I lie in bed, eat PUBG and go to sleep.


Day 3

I slept until lunch, and continued to lie in bed in the afternoon to eat chicken and sleep. The coding was completed after dinner. The code is as follows:


INT08U MPU6050_Read(INT08U addr, INT08U reg, INT08U len, INT08U *buf)

{

    OS_ERR err;

    CPU_TS ts;

    CPU_SR_ALLOC();

    INT08U rlen = 0;

    INT16U I2CTimeout;

    I2CReadState = I2C_CHECK_BUSY;

    I2C_ITConfig(I2C1, I2C_IT_ERR, ENABLE);

    while(I2C_READ_END > I2CReadState)

    {

        switch(I2CReadState)

        {

            case I2C_CHECK_BUSY:

                I2CTimeout = I2C_TIMEOUT*((INT16U)(SystemCoreClock/1000000));

                while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&&(I2CTimeout--));

                if(0 == I2CTimeout) delay_ms(fac_ms);

                else

                {

                    CPU_CRITICAL_ENTER();

                    if(I2C_CHECK_BUSY == I2CReadState) I2CReadState = I2C_SEND_START;//I2C bus is not busy

                    else I2CReadState = I2C_READ_END;

                    CPU_CRITICAL_EXIT();

                }

                break;

            case I2C_SEND_START:

                I2C_GenerateSTART(I2C1, ENABLE);

                I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);

                OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

                CPU_CRITICAL_ENTER();

                if((OS_ERR_NONE == err) && (I2C_SEND_START == I2CReadState)) I2CReadState = I2C_SEND_ADDR_SEND;

                else I2CReadState = I2C_READ_END;

                CPU_CRITICAL_EXIT();

                break;

            case I2C_SEND_ADDR_SEND:

                I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Transmitter);

                I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);

                OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

                CPU_CRITICAL_ENTER();

                if((OS_ERR_NONE == err) && (I2C_SEND_ADDR_SEND == I2CReadState)) I2CReadState = I2C_SELECT_REG;

                else I2CReadState = I2C_READ_END;

                CPU_CRITICAL_EXIT();

                break;

            case I2C_SELECT_REG:

                I2C_SendData(I2C1, reg);

                I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);

                OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

                CPU_CRITICAL_ENTER();

                if((OS_ERR_NONE == err) && (I2C_SELECT_REG == I2CReadState)) I2CReadState = I2C_CHECK_BUSY_AGAIN;

                else I2CReadState = I2C_READ_END;

                CPU_CRITICAL_EXIT();

                I2C_GenerateSTOP(I2C1, ENABLE);

                break;

            case I2C_CHECK_BUSY_AGAIN:

                I2CTimeout = I2C_TIMEOUT*((INT16U)(SystemCoreClock/1000000));

                while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&&(I2CTimeout--));

                if(0 == I2CTimeout) delay_ms(fac_ms);

                else

                {

                    CPU_CRITICAL_ENTER();

                    if(I2C_CHECK_BUSY_AGAIN == I2CReadState) I2CReadState = I2C_SEND_START_AGAIN;//I2C bus is not busy

                    else I2CReadState = I2C_READ_END;

                    CPU_CRITICAL_EXIT();

                }

                break;

            case I2C_SEND_START_AGAIN:

                I2C_GenerateSTART(I2C1, ENABLE);

                I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);

                OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

                CPU_CRITICAL_ENTER();

                if((OS_ERR_NONE == err) && (I2C_SEND_START_AGAIN == I2CReadState)) I2CReadState = I2C_SEND_ADDR_RECV;

                else I2CReadState = I2C_READ_END;

                CPU_CRITICAL_EXIT();

                break;

            case I2C_SEND_ADDR_RECV:

                I2C_Send7bitAddress(I2C1, (addr<<1), I2C_Direction_Receiver);

                I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);

                OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

                CPU_CRITICAL_ENTER();

                if((OS_ERR_NONE == err) && (I2C_SEND_ADDR_RECV == I2CReadState)) I2CReadState = I2C_RECV_DATA;

                else I2CReadState = I2C_READ_END;

                CPU_CRITICAL_EXIT();

                I2C_AcknowledgeConfig(I2C1, ENABLE);

                GPIO_SetBits(GPIOB,GPIO_Pin_7);

                break;

            case I2C_RECV_DATA:

                if(len == 1+rlen)

                {

                    I2C_AcknowledgeConfig(I2C1, DISABLE);

                    (void)I2C1->SR2;

                    I2C_GenerateSTOP(I2C1, ENABLE);

                }

                I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);

                OSSemPend(&MPU6050_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

                CPU_CRITICAL_ENTER();

                if((OS_ERR_NONE != err) || (I2C_RECV_DATA != I2CReadState)) I2CReadState = I2C_READ_END;

                CPU_CRITICAL_EXIT();

                *buf = I2C_ReceiveData(I2C1);

                buf++;

                rlen++;

                if(len == rlen) I2CReadState = I2C_READ_END;

                break;

            default:

                break;

        }

    }

    I2C_ITConfig(I2C1, I2C_IT_ERR, DISABLE);

    return rlen;

}


The delay_ms function inside can cause task switching when the delay time exceeds the OS time slice time.

Test it, OK, the serial port outputs data, and you're done.

Why is the LCD completely stuck? Let's investigate.

My task code for getting MPU6050 data is as follows:

while(1)

{

while(0 < sendmpudata)

{

sendmpudata–;

MPU_Get_Accelerometer(&aacx,&aacy,&aacz);

fprintf(UART_OUT, "ax:%5d, ay:%5d, az:%5d ", aacx, aacy, aacz);

res=MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);

fprintf(UART_OUT, "gx:%5d, gy:%5d, gz:%5d ", gyrox, gyroy, gyroz);

temp=MPU_Get_Temperature();

fprintf(UART_OUT, “temp:%5dnn”, temp);

}

}

sendmpudata is incremented in the OS timer, which is used to receive data from MPU6050 and send it to the serial port after a certain period of time. The problem is that when sendmpudata=0, that is, it is not time to send or receive data from MPU6050, the current task is still running in while(1), and the CPU is not given to the low-priority LCD task. So I inserted a pend to wait for a semaphore before while(0 < sendmpudata). Test it, LCD is not stuck, OK, and it's done.

Isn't it said that the I2C communication process should strictly follow the routine of ST and not be interrupted? In fact, when you understand the whole process, it will be easy to grasp where to interrupt and switch to other tasks, so that I2C can be truly integrated into the multi-tasking system.


Suddenly I realized, why do we have to make it so complicated!!! The time-consuming steps are all done by interrupts, and the remaining steps to be completed at the task level are just reading and writing the DR register? It is also possible to leave the remaining extremely simple steps to the interrupt handling function. Then the task only needs to wait for a semaphore with pend, and when the interrupt handling function handles the I2C communication process and post releases a semaphore, the task-level read and write I2C data function can return. Change the code again... It's 12 o'clock again, lie in bed, eat PUBG and go to sleep.


Day 4

I cleaned up the mess on the 228th and continued to modify the code at night. The modification is as follows:


INT08U I2C_Process(INT08U direction, INT08U addr, INT08U len, INT08U *pbuf)

{

    OS_ERR err;

    CPU_TS ts;

    INT08U res;

    INT16U I2CTimeout;


    I2CDirection = direction;

    I2CStage = I2C_DETECT_START;

    I2CError = I2C_SUCCESS;


    I2CAddress = addr;

    I2CLength = len;

    pI2CBuffer = pbuf;


    I2CIndex = 0;


    I2CTimeout = I2C_TIMEOUT*((INT16U)(SystemCoreClock/1000000));

    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&&(I2CTimeout--));

    if(0 < I2CTimeout)

    {

        I2C_GenerateSTART(I2C1, ENABLE);

        I2C_ITConfig(I2C1, I2C_IT_EVT|I2C_IT_ERR, ENABLE);

        OSSemPend(&I2C_SEM, I2C_TIMEOUT*fac_us, OS_OPT_PEND_BLOCKING, &ts, &err);

        I2C_ITConfig(I2C1, I2C_IT_EVT|I2C_IT_ERR, DISABLE);

        if(OS_ERR_NONE == err) res = I2C_SUCCESS;

        else if(OS_ERR_TIMEOUT == err)

        {

            if(OS_ERR_NONE == I2CError) res = I2C_OS_TIMEOUT;

            else res = I2CError;

        }

        else res = I2C_OS_ERROR;

    }

    else res = I2C_HW_BUSY;

    return res;

}



static INT08U MPU6050_Write(INT08U addr, INT08U reg, INT08U len, INT08U *pbuf)

{

    INT08U buf[MPU6050_WRITE_MAX], i, res;

    buf[0] = reg;

    for(i = 0; i < len; i++) buf[i+1] = pbuf[i];

    res = I2C_Process(I2C_SEND, addr, len+1, buf);

    return res;

}


static INT08U MPU6050_Read(INT08U addr, INT08U reg, INT08U len, INT08U *pbuf)

{

    INT08U res;

    res = I2C_Process(I2C_SEND, addr, 1, ®);

    if(I2C_SUCCESS == res) res = I2C_Process(I2C_RECV, addr, len, pbuf);

    return res;

}


void I2C1_EV_IRQHandler(void)

{

    OS_ERR err;

    CPU_SR_ALLOC();

    CPU_CRITICAL_ENTER();

    OSIntEnter();

    CPU_CRITICAL_EXIT();

    switch(I2CStage)

    {

        case I2C_DETECT_START:

            if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))

            {

                if(I2C_SEND == I2CDirection) I2C_Send7bitAddress(I2C1, (I2CAddress<<1), I2C_Direction_Transmitter);

                else I2C_Send7bitAddress(I2C1, (I2CAddress<<1), I2C_Direction_Receiver);

                I2CStage = I2C_DETECT_ADDR;

            }

            break;

        case I2C_DETECT_ADDR:

            if(I2C_SEND == I2CDirection)

            {

                if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))

                {

                    I2C_SendData(I2C1, *pI2CBuffer);

                    pI2CBuffer++;

                    I2CIndex++;

                    if(I2CLength > I2CIndex) I2CStage = I2C_DETECT_DATA;

                    else

                    {

                        I2C_GenerateSTOP(I2C1, ENABLE);

                        OSSemPost(&I2C_SEM, OS_OPT_POST_1, &err);

                    }

                }

            }

            else

            {

                if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))

                {

                    if(I2CLength > I2CIndex+1) I2C_AcknowledgeConfig(I2C1, ENABLE);

                    else

                    {

                        I2C_AcknowledgeConfig(I2C1, DISABLE);

                        (void)I2C1->SR2;

                        I2C_GenerateSTOP(I2C1, ENABLE);

                    }

                    GPIO_SetBits(GPIOB,GPIO_Pin_7);

                    I2CStage = I2C_DETECT_DATA;

                }

            }

            break;

        case I2C_DETECT_DATA:

            if(I2C_SEND == I2CDirection)

            {

                if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))

                {

                    I2C_SendData(I2C1, *pI2CBuffer);

                    pI2CBuffer++;

                    I2CIndex++;

                    if(I2CLength <= I2CIndex)

                    {

                        I2C_GenerateSTOP(I2C1, ENABLE);

                        OSSemPost(&I2C_SEM, OS_OPT_POST_1, &err);

                    }

                }

            }

            else

            {

                if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))

                {

                    *pI2CBuffer = I2C_ReceiveData(I2C1);

                    pI2CBuffer++;

                    I2CIndex++;

                    if(I2CLength == I2CIndex+1)

                    {

                        I2C_AcknowledgeConfig(I2C1, DISABLE);

                        (void)I2C1->SR2;

                        I2C_GenerateSTOP(I2C1, ENABLE);

                    }

                    else if(I2CLength <= I2CIndex) OSSemPost(&I2C_SEM, OS_OPT_POST_1, &err);

                    else{}

                }

            }

            break;

        default:

            break;

    }

    OSIntExit();

}


void I2C1_ER_IRQHandler(void)

{

    if(SET == I2C_GetITStatus(I2C1, I2C_IT_BERR)) I2CError = I2C_HW_BERR;

    else if(SET == I2C_GetITStatus(I2C1, I2C_IT_ARLO)) I2CError = I2C_HW_ARLO;

    else if(SET == I2C_GetITStatus(I2C1, I2C_IT_AF)) I2CError = I2C_HW_AF;

    else if(SET == I2C_GetITStatus(I2C1, I2C_IT_OVR)) I2CError = I2C_HW_OVR;

    else if(SET == I2C_GetITStatus(I2C1, I2C_IT_PECERR)) I2CError = I2C_HW_PECERR;

    else if(SET == I2C_GetITStatus(I2C1, I2C_IT_TIMEOUT)) I2CError = I2C_HW_TIMEOUT;

    else if(SET == I2C_GetITStatus(I2C1, I2C_IT_SMBALERT)) I2CError = I2C_HW_SMBALERT;

    else{}

    I2C_ClearITPendingBit(I2C1, I2C_IT_SMBALERT|I2C_IT_TIMEOUT|I2C_IT_PECERR|I2C_IT_OVR|I2C_IT_AF|I2C_IT_ARLO|I2C_IT_BERR);

    I2C_ITConfig(I2C1, I2C_IT_ERR, DISABLE);

}


The above code is the final version. The main idea is that the read and write data processes of STM32 hardware I2C are roughly the same, encapsulated into a function, and the reading and writing are distinguished by passing in parameters. This is the I2C layer. Writing data to MPU6050 is to write the register address and write data. Reading data from MPU6050 is to write the register address and read data, which are encapsulated into two functions of MPU6050 read and write respectively, for other functions to operate MPU6050 to call, this is the MPU6050 module layer. The task level calls the function of operating MPU6050 to obtain the desired data, this is the APP layer.

The effect is as follows:

insert image description here


Keywords:STM32F103RC Reference address:STM32F103RC hardware I2C from the pit to the pit

Previous article:STM32 SPI receiving stuck problem
Next article:STM32F103 Slave I2C Configuration

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号