zoukankan      html  css  js  c++  java
  • STM32 I2C读写EEPROM(POLLING模式)

    本工程运行于野火MINI开发板,主控型号为STM32F103RC,读写对象为AT24C02 2Kbit容量的EEPROM

    STM32的硬核I2C十分复杂,而且网上有说有缺陷,这就有意思了,值得一探究竟

    I2C通信中各设备有主从之分,那么此处先从简单的主模式下手,做一个简单的读写EEPROM实验

    从AT24C02的规格书中看到,对它的操作有以下几种

    写操作

      BYTE WRITE

      PAGE WRITE

      ACKNOWLEDGE POLLING

    读操作

      CURRENT ADDRESS READ

      RANDOM READ

      SEQUENTIAL READ

    简单梳理一下,POLLING操作适用于写完之后等待存储芯片搬运数据完成。PAGE WRITE和RANDOM READ既是基本的单字节读写。为了提高效率,写操作支持8字节的页写,完整的页写需要地址边缘对齐,不然写进去的内容会从一页的末端回滚到页首,覆盖原来的数据。而顺序读即完成一字节读后回应ACK即可继续读下一个地址的数据,直到所有数据读取完成回应NACK即可,读的回滚是达到器件末地址后从首地址继续

    初始化I2C和GPIO

    void mini_i2c_init(void)
    {
        // structure define
        I2C_InitTypeDef i2c_struct;
        GPIO_InitTypeDef gpio_struct;
        
        // clock enable
        USER_I2C_RCC_CMD(USER_I2C_RCC, ENABLE);
        GPIO_RCC_CMD(USER_I2C_GPIO_RCC, ENABLE);
        
        // GPIO initialization
        gpio_struct.GPIO_Mode = GPIO_Mode_AF_OD;
        gpio_struct.GPIO_Speed = GPIO_Speed_50MHz;
        gpio_struct.GPIO_Pin = USER_I2C_SCL | USER_I2C_SDA;
        GPIO_Init(USER_I2C_GPIO, &gpio_struct);
        
        // I2C initialization
        i2c_struct.I2C_Mode = I2C_Mode_I2C;
        i2c_struct.I2C_DutyCycle = I2C_DutyCycle_2;
        i2c_struct.I2C_OwnAddress1 = USER_I2C_OWN_ADDR;
        i2c_struct.I2C_Ack = I2C_Ack_Enable;
        i2c_struct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
        i2c_struct.I2C_ClockSpeed = USER_I2C_SPEED;
        I2C_Init(USER_I2C, &i2c_struct);
        
        I2C_Cmd(USER_I2C, ENABLE);
    }

    为了方便调试,配置按钮并开启外部中断

    void mini_pb_config(void)
    {
        // struct define
        GPIO_InitTypeDef gpio_struct;
        EXTI_InitTypeDef exti_struct;
        NVIC_InitTypeDef nvic_struct;
        
        // enable clock
        // enable AFIO!!!
        GPIO_RCC_CMD(AFIO_RCC, ENABLE);
        GPIO_RCC_CMD(PB_K1_RCC, ENABLE);
        GPIO_RCC_CMD(PB_K2_RCC, ENABLE);
        
        // GPIO initialization
        gpio_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        gpio_struct.GPIO_Pin = PB_K1;
        GPIO_Init(PB_K1_GPIO, &gpio_struct);
        
        GPIO_EXTILineConfig(PB_K1_PORTSOURCE, PB_K1_PINSOURCE);
        
        gpio_struct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        gpio_struct.GPIO_Pin = PB_K2;
        GPIO_Init(PB_K2_GPIO, &gpio_struct);
        
        GPIO_EXTILineConfig(PB_K2_PORTSOURCE, PB_K2_PINSOURCE);
        
        // EXTI initialization
        exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;
        exti_struct.EXTI_Trigger = EXTI_Trigger_Rising;
        exti_struct.EXTI_Line = PB_K1_EXTILINE;
        exti_struct.EXTI_LineCmd = ENABLE;
        EXTI_Init(&exti_struct);
        
        exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;
        exti_struct.EXTI_Trigger = EXTI_Trigger_Rising;
        exti_struct.EXTI_Line = PB_K2_EXTILINE;
        exti_struct.EXTI_LineCmd = ENABLE;
        EXTI_Init(&exti_struct);
        
        // NVIC initialization
        nvic_struct.NVIC_IRQChannel = PB_K1_IRQ;
        nvic_struct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&nvic_struct);
        
        nvic_struct.NVIC_IRQChannel = PB_K2_IRQ;
        nvic_struct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&nvic_struct);
    }

    然后可以在中断服务函数调用I2C读写操作

    void PB_K1_IRQ_HANDLER(void)
    {
        if(EXTI_GetITStatus(PB_K1_EXTILINE) != RESET)
        {
            EXTI_ClearITPendingBit(PB_K1_EXTILINE);
            
            mini_i2c_sequential_read(0x00, i2c_rx_data, 8);
            LED_D4_TOGGLE;
        }
    }
    
    void PB_K2_IRQ_HANDLER(void)
    {
        if(EXTI_GetITStatus(PB_K2_EXTILINE) != RESET)
        {
            EXTI_ClearITPendingBit(PB_K2_EXTILINE);
            
            mini_i2c_page_write(0x00, page_data);
            LED_D5_TOGGLE;
        }
    }
    
    void DEBUG_UART_IRQ_HANDLER(void)
    {
        if(USART_GetITStatus(DEBUG_UART, USART_IT_RXNE) != RESET)
        {
            USART_ClearITPendingBit(DEBUG_UART, USART_IT_RXNE);
            
            uint16_t data = USART_ReceiveData(DEBUG_UART);
            USART_SendData(DEBUG_UART, data);
        }
    }

    字节写功能的实现最为简单,依次调用开始,设备地址,字地址,数据操作的库函数,并且在每次操作完检查对应的事件或是标志位

    void mini_i2c_byte_write(uint8_t word_addr, uint8_t data)
    {
        // start
        I2C_GenerateSTART(USER_I2C, ENABLE);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
        
        // device address
        I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Transmitter);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET);
        
        // EEPROM address
        I2C_SendData(USER_I2C, word_addr);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET);
        
        I2C_SendData(USER_I2C, data);
        while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
        
        I2C_GenerateSTOP(USER_I2C, ENABLE);
    }

    页写操作即将原来的写一次数据改为写八次数据,并在最后一次写数据之后检查EV8_2

    // AT24C02 accept 8-byte page write
    void mini_i2c_page_write(uint8_t word_addr, uint8_t *page_data)
    {
        // S
        I2C_GenerateSTART(USER_I2C, ENABLE);
        // EV5: SB=1, cleared by reading SR1 register followed by writing DR register with Address
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
        
        // Address
        I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Transmitter);
        // EV6: ADDR=1, cleared by reading SR1 register followed by reading SR2
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET);
        
        // word address
        I2C_SendData(USER_I2C, word_addr);
        
        for(uint8_t i=0;i<8;i++)
        {
            // EV8: TxE=1, shift register not empty, data regiter empty, cleared by writing DR register
            while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING) == RESET);
            I2C_SendData(USER_I2C, page_data[i]);
        }
        
        // EV8_2: TxE=1, BTF=1, Program Stop request. TxE and BTF are cleared by hardware by the Stop condition
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET);
        I2C_GenerateSTOP(USER_I2C, ENABLE);
    }

    相对之下,读操作则复杂的多,且有两种操作方式,这里使用的是方式二,关于每种方式的使用场景,参考手册中有如下说明

    Method 2: This method is for the case when the I2C is used with interrupts that do not have the highest priority in the application or when the I2C is used with polling

    本实验使用的就是POLLING的方式,所用用方式二,麻烦之处是对应读一个字节,两个字节和三个字节及以上还需要不同处理

    void mini_i2c_sequential_read(uint8_t word_addr, uint8_t *data, uint32_t nbyte)
    {
        // start
        I2C_GenerateSTART(USER_I2C, ENABLE);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
        
        // device address
        I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Transmitter);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET);
        
        // register address
        I2C_SendData(USER_I2C, word_addr);
        while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
            
        // restart
        I2C_GenerateSTART(USER_I2C, ENABLE);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
        
        // device address
        I2C_Send7bitAddress(USER_I2C, USER_I2C_DEVICE_ADDR, I2C_Direction_Receiver);
        while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_ADDR) == RESET);
        
        if(nbyte == 1)
        {
            // EV6_3
            I2C_AcknowledgeConfig(USER_I2C, DISABLE);
            USER_I2C->SR2;
            I2C_GenerateSTOP(USER_I2C, ENABLE);
            
            // EV7
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_RXNE) == RESET);
            *data = I2C_ReceiveData(USER_I2C);
            
            I2C_AcknowledgeConfig(USER_I2C, ENABLE);        
        }
        else if(nbyte == 2)
        {
            // set pos flag
            I2C_NACKPositionConfig(USER_I2C, I2C_NACKPosition_Next);
            
            // EV6
            USER_I2C->SR2;
            // EV6_1
            I2C_AcknowledgeConfig(USER_I2C, DISABLE);
            
            // EV7_3
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
            I2C_GenerateSTOP(USER_I2C, ENABLE);
            *data = I2C_ReceiveData(USER_I2C);
            *(data+1) = I2C_ReceiveData(USER_I2C);
            
            I2C_AcknowledgeConfig(USER_I2C, ENABLE);
        }
        else
        {
            // EV6
            USER_I2C->SR2;
            
            for(uint32_t i=0;i<nbyte-3;i++)
            {
                // EV7, using BTF instea of RXNE, refering Discovering STM32 Microcontroller
                while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
                *(data+i) = I2C_ReceiveData(USER_I2C);
            }
            
            // EV7_2
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
            I2C_AcknowledgeConfig(USER_I2C, DISABLE);
            *(data+nbyte-3) = I2C_ReceiveData(USER_I2C);
            I2C_GenerateSTOP(USER_I2C, ENABLE);
            *(data+nbyte-2) = I2C_ReceiveData(USER_I2C);
            
            // EV7
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_RXNE) == RESET);
            *(data+nbyte-1) = I2C_ReceiveData(USER_I2C);
            
            I2C_AcknowledgeConfig(USER_I2C, ENABLE);
        }
    }

    代码中有少量注释,可参照参考手册

    读写变量定义如下

    uint8_t page_data[8] = {0x08, 0x07, 0x01, 0x06, 0x02, 0x05, 0x03, 0x04};
    uint8_t i2c_rx_data[8];

    写入截图

    读取截图

    且可以连续操作,功能验证OK

  • 相关阅读:
    ES6 语法详解(Promise对象(重点))
    ES6 语法详解(形参默认值)
    ES6 语法详解(三点运算符)
    ES6 语法详解(箭头函数(重点))
    ES6 语法详解(简化的对象写法)
    ES6 语法详解(模板字符串)
    ES6 语法详解(变量的解构赋值)
    [刷题] 走迷宫
    [bug] A reference cannot be reassigned, so it must be initialized at the member-initialization-list.
    [Qt] LibreCAD 源码分析
  • 原文地址:https://www.cnblogs.com/qingkai/p/9803070.html
Copyright © 2011-2022 走看看