• STM32 I2C读写EEPROM(中断模式)


    上一篇博客是使用I2C读写EERPOM的初级版本,这次在上次的基础上添加中断,使用DMA传输控制,新添加两个读写函数

    采用中断方式和DMA可以充分利用单片机强大的硬件外设,提高整体运行效率,而且,在编程上也更为便捷

    新的页写函数如下

    // AT24C02 accept 8-byte page write
    void mini_i2c_page_write(uint8_t *page_data, uint8_t word_addr, uint8_t *data_num_pointer)
    {
        global_data_num_pointer = data_num_pointer;
        
        // 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, EEPROM_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);
    
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET);
        
        mini_i2c_dma_config((uint32_t)page_data, *data_num_pointer, USER_I2C_DMA_DIR_TX);
        
        DMA_Cmd(USER_I2C_DMA_CH_TX, ENABLE);
    }

    代码中注释掉的部分是原来的设计,在它下面是新的设计,首先配置好DMA,然后再开启它

    void mini_i2c_dma_config(uint32_t mem_addr, uint32_t buffer_size, uint32_t direction)
    {
        dma_struct.DMA_MemoryBaseAddr = mem_addr;
        dma_struct.DMA_DIR = direction;
        dma_struct.DMA_BufferSize = buffer_size;
        if(direction == USER_I2C_DMA_DIR_TX)
        {
            DMA_Init(USER_I2C_DMA_CH_TX, &dma_struct);
        }
        else
        {
            DMA_Init(USER_I2C_DMA_CH_RX, &dma_struct);
        }
    }

    DMA初始化结构体为全局变量, 在I2C初始化时已经初始化了该结构体的其他成员变量,此处需要配置一个读写的存储地址,数据量和方向

    void mini_i2c_init(void)
    {
        // structure define
        I2C_InitTypeDef i2c_struct;
        GPIO_InitTypeDef gpio_struct;
        NVIC_InitTypeDef nvic_struct;
        
        // clock enable
        USER_I2C_RCC_CMD(USER_I2C_RCC, ENABLE);
        GPIO_RCC_CMD(AFIO_RCC, ENABLE);
        GPIO_RCC_CMD(USER_I2C_GPIO_RCC, ENABLE);
        USER_I2C_DMA_RCC_CMD(USER_I2C_DMA_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 DMA NVIC initialization
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        nvic_struct.NVIC_IRQChannel = USER_I2C_DMA_TX_IRQ;
        nvic_struct.NVIC_IRQChannelPreemptionPriority = USER_I2C_DMA_PREPRIO;
        nvic_struct.NVIC_IRQChannelSubPriority = USER_I2C_DMA_SUBPRIO;
        nvic_struct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&nvic_struct);
        
        nvic_struct.NVIC_IRQChannel = USER_I2C_DMA_RX_IRQ;
        NVIC_Init(&nvic_struct);
        
        // I2C DMA
        DMA_DeInit(USER_I2C_DMA_CH_TX);
        dma_struct.DMA_PeripheralBaseAddr = USER_I2C_DR_ADDR;
        dma_struct.DMA_MemoryBaseAddr = 0;            // will be reconfigure
        dma_struct.DMA_DIR = DMA_DIR_PeripheralDST;    // will be reconfigure
        dma_struct.DMA_BufferSize = 0xFFFF;            // will be reconfigure
        dma_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        dma_struct.DMA_MemoryInc = DMA_MemoryInc_Enable;
        dma_struct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
        dma_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
        dma_struct.DMA_Mode = DMA_Mode_Normal;
        dma_struct.DMA_Priority = DMA_Priority_VeryHigh;
        dma_struct.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(USER_I2C_DMA_CH_TX, &dma_struct);
        
        DMA_DeInit(USER_I2C_DMA_CH_RX);
        DMA_Init(USER_I2C_DMA_CH_RX, &dma_struct);
        
        DMA_ITConfig(USER_I2C_DMA_CH_TX, DMA_IT_TC, ENABLE);
        DMA_ITConfig(USER_I2C_DMA_CH_RX, DMA_IT_TC, ENABLE);
        
        // 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);
        
        I2C_DMACmd(USER_I2C, ENABLE);
    }

    这段I2C初始化代码添加了中断和DMA部分,且如上所说,DMA初始化结构体为全局变量,其中有三个成员变量需要在具体的读写函数中配置

    下面的I2C写函数移植的官方给的示例工程

    void mini_i2c_write(uint8_t *data, uint8_t word_addr, uint16_t data_num)
    {
        uint8_t num_page = 0, num_single = 0, count = 0, addr = 0;
        
        addr = word_addr % EEPROM_PAGESIZE;
        count = EEPROM_PAGESIZE - addr;
        num_page = data_num / EEPROM_PAGESIZE;
        num_single = data_num % EEPROM_PAGESIZE;
        
        // if word_addr is EEPROM_PAGESIZE aligned
        if(addr == 0)
        {
            // if data_num < EEPROM_PAGESIZE
            if(num_page == 0)
            {
                global_data_num = num_single;            
                mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num));            
                while(global_data_num > 0);            
                mini_i2c_wait_standby();
            }
            // if data_num > EEPROM_PAGESIZE
            else
            {
                while(num_page--)
                {
                    global_data_num = EEPROM_PAGESIZE;                
                    mini_i2c_page_write(data, word_addr, &global_data_num);                
                    while(global_data_num > 0);                
                    mini_i2c_wait_standby();
                    
                    data += EEPROM_PAGESIZE;
                    word_addr += EEPROM_PAGESIZE;
                }
                if(num_single != 0)
                {
                    global_data_num = num_single;                
                    mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num));                
                    while(global_data_num > 0);                
                    mini_i2c_wait_standby();
                }
            }
        }
        else
        {
            // if data_num < EEPROM_PAGESIZE
            if(num_page == 0)
            {
                // if the number of data to be written is more than 
                // the remaining space in the current page
                if(data_num > count)
                {
                    // first part
                    global_data_num = count;                
                    mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num));                
                    while(global_data_num > 0);                
                    mini_i2c_wait_standby();
                    
                    // second part
                    global_data_num = data_num - count;                
                    mini_i2c_page_write(data+count, word_addr+count, (uint8_t*)(&global_data_num));                
                    while(global_data_num > 0);                
                    mini_i2c_wait_standby();
                }
                else
                {
                    global_data_num = num_single;                
                    mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num));                
                    while(global_data_num > 0);                
                    mini_i2c_wait_standby();
                }
            }
            // data_num > EEPROM_PAGESIZE
            else
            {
                data_num -= count;
                num_page = data_num / EEPROM_PAGESIZE;
                num_single = data_num % EEPROM_PAGESIZE;
                
                if(count != 0)
                {
                    global_data_num = count;                
                    mini_i2c_page_write(data, word_addr, &global_data_num);
                    while(global_data_num > 0);
                    mini_i2c_wait_standby();
                    
                    data += count;
                    word_addr += count;
                }
                while(num_page--)
                {
                    global_data_num = EEPROM_PAGESIZE;
                    mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num));
                    while(global_data_num > 0);
                    mini_i2c_wait_standby();
                    
                    data += EEPROM_PAGESIZE;
                    word_addr += EEPROM_PAGESIZE;
                }
                if(num_single != 0)
                {
                    global_data_num = num_single;
                    mini_i2c_page_write(data, word_addr, (uint8_t*)(&global_data_num));
                    while(global_data_num > 0);
                    mini_i2c_wait_standby();
                }
            }
        }
    }

    I2C读函数也同样移植的官方示例工程

    // method 1
    void mini_i2c_read(uint8_t *data, uint8_t word_addr, uint8_t data_num)
    {
        I2C_GenerateSTART(USER_I2C, ENABLE);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
        
        I2C_Send7bitAddress(USER_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET);
        
        I2C_SendData(USER_I2C, word_addr);
        while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
        
        I2C_GenerateSTART(USER_I2C, ENABLE);
        while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
        
        I2C_Send7bitAddress(USER_I2C, EEPROM_ADDR, I2C_Direction_Receiver);
        
        if(data_num == 1)
        {
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_ADDR) == RESET);
            
            I2C_AcknowledgeConfig(USER_I2C, DISABLE);
            USER_I2C->SR2;
            I2C_GenerateSTOP(USER_I2C, ENABLE);
            
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_RXNE) == RESET);
            
            *data = I2C_ReceiveData(USER_I2C);
            
            while(USER_I2C->CR1 & I2C_CR1_STOP);
            
            I2C_AcknowledgeConfig(USER_I2C, ENABLE);
        }
        else
        {
            while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == RESET);
            
            mini_i2c_dma_config((uint32_t)data, data_num, USER_I2C_DMA_DIR_RX);
            
            // Inform the DMA that the next End Of Transfer Signal will be the last one
            I2C_DMALastTransferCmd(USER_I2C, ENABLE);
            
            DMA_Cmd(USER_I2C_DMA_CH_RX, ENABLE);
        }
    }

    I2C写函数中调用的时页写函数,每次写完一页后需要等待EERPOM操作完成,等待函数如下

    // after write operation, EEPROM need some time
    void mini_i2c_wait_standby(void)
    {
        uint8_t busy = 1;
        uint16_t tmp_sr;
        
        while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BUSY));
        
        while(busy)
        {
            I2C_GenerateSTART(USER_I2C, ENABLE);
            while(I2C_CheckEvent(USER_I2C, I2C_EVENT_MASTER_MODE_SELECT) == RESET);
            
            I2C_Send7bitAddress(USER_I2C, EEPROM_ADDR, I2C_Direction_Transmitter);
            tmp_sr = USER_I2C->SR1;
            
            // check if the EEPROM responded
            do
            {
                tmp_sr = USER_I2C->SR1;
            }
            while((tmp_sr & (I2C_SR1_ADDR | I2C_SR1_AF)) == 0);
            
            if(tmp_sr & I2C_SR1_ADDR)
            {
                USER_I2C->SR2;
                
                I2C_GenerateSTOP(USER_I2C, ENABLE);
                
                busy = 0;
            }
            else
            {
                I2C_ClearFlag(USER_I2C, I2C_FLAG_AF);
            }
        }
    }

    在读写函数中开启DMA后,就等待DMA传输完成开启中断,然后在中断服务函数中关闭DMA且调用I2C结束函数

    void USER_I2C_DMA_TX_IRQ_HANDLER(void)
    {
        if(DMA_GetITStatus(USER_I2C_DMA_IT_TX_TC) != RESET)
        {
            // GL is used to clear all flag
            DMA_ClearITPendingBit(USER_I2C_DMA_IT_TX_GL);
            
            DMA_Cmd(USER_I2C_DMA_CH_TX, DISABLE);
            while(I2C_GetFlagStatus(USER_I2C, I2C_FLAG_BTF) == RESET);
            
            I2C_GenerateSTOP(USER_I2C, ENABLE);
            
            USER_I2C->SR1;
            USER_I2C->SR2;
            
            *global_data_num_pointer = 0;
        }
    }
    
    void USER_I2C_DMA_RX_IRQ_HANDLER(void)
    {
        if(DMA_GetITStatus(USER_I2C_DMA_IT_RX_TC) != RESET)
        {
            DMA_ClearITPendingBit(USER_I2C_DMA_IT_RX_GL);
            
            I2C_GenerateSTOP(USER_I2C, ENABLE);
            
            DMA_Cmd(USER_I2C_DMA_CH_RX, DISABLE);
        }
    }

    如上一个实验,I2C读写函数调用在按键触发的外部中断的服务函数中调用,此处如果不注意对中断分组和中断优先级的配置的话,会出错。比如我在调试中碰到的问题,未将按键中断的抢断优先级和I2C DMA的抢断优先级区分开,这样在按键中断中调用I2C写函数时,就会导致DMA传输完成后无法进入中断而导致出错

  • 相关阅读:
    ZOJ
    Clock(数学题)
    The Lucky Week(规律)
    POJ 3233 Matrix Power Series
    POJ 1061 青蛙的约会(扩展欧几里德算法)
    2266: number
    2263: neighbor
    2269: minval(优先队列)
    HDU
    Problem 2150 Fire Game (广搜+枚举)
  • 原文地址:https://www.cnblogs.com/qingkai/p/9815542.html
Copyright © 2020-2023  润新知