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传输完成后无法进入中断而导致出错

posted @ 2018-10-19 12:37  自由的青  阅读(3904)  评论(0编辑  收藏  举报