资讯详情

【国货之光】GD32E230F4使用硬件IIC+DMA读写24C04

很久很久以前,我写过GD32E230替换STM32F031帖子主要介绍USART和SPI当时移植开发,当时IIC使用软件i2c,没有介绍的价值。在使用IIC当时,我们大多数人都使用软件,因为软件的方式非常简单,一套组合拳几乎可以得到任何东西MCU去使用。而STM32的硬件IIC也不稳定,经常容易卡住,我在STM32F在031侥幸将硬件IIC调试成功,但后来使用STM32F但是103点不能成功,真是菜狗。但由于项目需要,读写IIC时间很难空出来。我必须腾出时间给其他外设。我的软件IIC只能无效,需要重写硬件IIC需要带代码,需要带代码DMA,尽量减少时间。所以有今天的帖子。(准备在这里添加STM32F031的硬件IIC链接,发现自己没写。以后有时间给大伙带上吧) 这篇文章是上个月写的,但是因为设计PCB不考虑硬件IIC问题,导致IIC的引脚不在硬件GPIO再次打板贴片,耽误了两周。 我的硬件: 这里介绍我的硬件有点浪费感情。之所以介绍这个,是因为我在调试过程中遇到了几个问题,都和我的硬件有关,虽然问题和IIC没关系,但我还是想分享一下,以后遇到朋友可以避免。 MCU使用的是GDF32E230F4.外设如下:

flash只有16KB,封装是LGA20.资源少,包装小。这会导致价格便宜,但我没用过这么小的flash和资源这么少MCU,这导致了我背后的事故。 其实我们的项目只用了一路SPI,一路485,一路IIC和两个外部中断,使用的资源真的不多,但是用在这部电影上就很拥挤了。IO如下所示:

3脚PA0用于后续休眠唤醒,只剩下资源PA1,PA2,PA3。因为考虑PCB我们将直接取消布局布局和布局的外部晶振PF0和PF1作为IIC使用。 第一个问题出现在这里,我们需要收到它RX引脚的第一个下降触发外部中断。为了方便布线,烧录口SWDIO和RX对接,烧录程序后SWDIO会用作普通GPIO,结果打板回来后无法烧录代码,取下485芯片就没问题了,要重新打板贴片。我利用这个努力调试了硬件IIC。 开始调试代码: 首先,初始化使用的外设时钟: 复制 void rcu_config(void)

{

/* enable GPIOA,F clock */

rcu_periph_clock_enable(RCU_GPIOA);

rcu_periph_clock_enable(RCU_GPIOF);

/* enable I2C0 clock */

rcu_periph_clock_enable(RCU_I2C0);

/* enable DMA0 clock */

rcu_periph_clock_enable(RCU_DMA);

} 然后初始化IIC。 复制 void i2c_config(void)

{

/* connect PF1 to I2C0_SCL */

/* connect PF0 to I2C0_SDA */

gpio_af_set(GPIOF, GPIO_AF_1, GPIO_PIN_0);

gpio_af_set(GPIOF, GPIO_AF_1, GPIO_PIN_1);

/* configure GPIO pins of I2C */

gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0);

gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_0);

gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1);

gpio_output_options_set(GPIOF, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_1);

/* configure I2C clock */

i2c_clock_config(I2C0, I2C0_SPEED, I2C_DTCY_2);

/* configure I2C address */

i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C0_SLAVE_ADDRESS7);

/* enable I2C0 */

i2c_enable(I2C0);

/* enable acknowledge */

i2c_ack_config(I2C0, I2C_ACK_ENABLE);

} 上面的GPIO一般没什么好说的,IO设置为泄漏输出模式,因为IIC总线有两个操作:读写。泄漏是最好的。这里说的是最好的。事实上,也可以将其设置为推拉输出。这里有必要提一下,因为我上周在论坛上看到一些兄弟在调试SPI例程,GPIO设置为推拉输出,引起疑惑,设置为如何读取输出SPI的数据。

这里有一个冷知识:只要能够GPIO,无论你设置GPIO在GPIO当数据出现时,GPIO数据寄存器可以将数据存储在寄存器中。此时,您可以通过阅读数据寄存器获取数据。事实就是这样。也许我的描述不是很准确,但一定是可以的。 初始化GPIO之后就是调用库函数对IIC初始化。 i2c_clock_config()函数的三个参数,第一个是选择哪个I2C,二是设置I2C这里定义的速度是宏定义,速度是1万,第三个设置快速模式下的空比,如果速度是100KHz以下是使用参数I2C_DTCY_2,如果是100KHz-1MHz,则使用I2C_DTCY_16_9。最高只支持1M。 IIC使用DMA写24C02: 复制 void eeprom_buffer_write_dma_timeout(uint8_t* p_buffer, uint8_t write_address, uint16_t number_of_byte)

{

uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0;

address = write_address % I2C_PAGE_SIZE;

count = I2C_PAGE_SIZE - address;

number_of_page =number_of_byte / I2C_PAGE_SIZE;

number_of_single = number_of_byte % I2C_PAGE_SIZE;

/* if write_address is I2C_PAGE_SIZE aligned*/

if(0 == address){

while(number_of_page--){

eeprom_page_write_dma_timeout(p_buffer, write_address, I2C_PAGE_SIZE);

eeprom_wait_standby_state_timeout();

write_address =I2C_PAGE_SIZE;

      p_buffer += I2C_PAGE_SIZE;

        }

        if(0 != number_of_single){

            eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_single);

            eeprom_wait_standby_state_timeout();

        }

    }else{

        /* if write_address is not I2C_PAGE_SIZE aligned */

        if(number_of_byte < count){ 

            eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_byte);

            eeprom_wait_standby_state_timeout();

        }else{

            number_of_byte -= count;

            number_of_page =  number_of_byte / I2C_PAGE_SIZE;

            number_of_single = number_of_byte % I2C_PAGE_SIZE;

            

            if(0 != count){

                eeprom_page_write_dma_timeout(p_buffer, write_address, count);

                eeprom_wait_standby_state_timeout();

                write_address += count;

                p_buffer += count;

            } 

            /* write page */

            while(number_of_page--){

                eeprom_page_write_dma_timeout(p_buffer, write_address, I2C_PAGE_SIZE);

                eeprom_wait_standby_state_timeout();

                write_address +=  I2C_PAGE_SIZE;

                p_buffer += I2C_PAGE_SIZE;

            }

            /* write single */

            if(0 != number_of_single){

                eeprom_page_write_dma_timeout(p_buffer, write_address, number_of_single);

                eeprom_wait_standby_state_timeout();

            }

        }

    }  

}

IIC使用DMA读24C02: 复制 uint8_t eeprom_page_write_dma_timeout(uint8_t* p_buffer, uint8_t write_address, uint8_t number_of_byte)

{

    dma_parameter_struct dma_init_struct;

    uint8_t state = I2C_START;

    uint16_t timeout = 0;

    uint8_t i2c_timeout_flag = 0;

    while(!(i2c_timeout_flag)){

        switch(state){

        case I2C_START:

            /* i2c master sends start signal only when the bus is idle */

            while(i2c_flag_get(I2CX, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)){

                timeout++;

            }

            if(timeout < I2C_TIME_OUT){

                i2c_start_on_bus(I2CX);

                timeout = 0;

                state = I2C_SEND_ADDRESS;

            }else{

                i2c_bus_reset();

                timeout = 0;

                state = I2C_START;

                printf("i2c bus is busy in PAGE WRITE!\n");

            }

            break;

        case I2C_SEND_ADDRESS:

            /* i2c master sends START signal successfully */

            while((!i2c_flag_get(I2CX, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)){

                timeout++;

            }

            if(timeout < I2C_TIME_OUT){

                i2c_master_addressing(I2CX, eeprom_address, I2C_TRANSMITTER);

                timeout = 0;

                state = I2C_CLEAR_ADDRESS_FLAG;

            }else{

                timeout = 0;

                state = I2C_START;

                printf("i2c master sends start signal timeout in PAGE WRITE!\n");

            }

            break;

        case I2C_CLEAR_ADDRESS_FLAG:

            /* address flag set means i2c slave sends ACK */

            while((!i2c_flag_get(I2CX, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)){

                timeout++; 

            }

            if(timeout < I2C_TIME_OUT){

                i2c_flag_clear(I2CX, I2C_FLAG_ADDSEND);

                timeout = 0;

                state = I2C_TRANSMIT_DATA;

            }else{

                timeout = 0;

                state = I2C_START;

                printf("i2c master clears address flag timeout in PAGE WRITE!\n");

            }

            break;

        case I2C_TRANSMIT_DATA:

            /* wait until the transmit data buffer is empty */

            while((!i2c_flag_get(I2CX, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)){

                timeout++;

            }

            if(timeout < I2C_TIME_OUT){

                /* send the EEPROM's internal address to write to : only one byte address */

                i2c_data_transmit(I2CX, write_address);

                timeout = 0;

            }else{

                timeout = 0;

                state = I2C_START;

                printf("i2c master sends EEPROM's internal address timeout in PAGE WRITE!\n");

            }

            /* wait until BTC bit is set */

            while(!i2c_flag_get(I2CX, I2C_FLAG_BTC));

            dma_deinit(DMA_CH1);

            dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;

            dma_init_struct.memory_addr = (uint32_t)p_buffer;

            dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;

            dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;

            dma_init_struct.number = number_of_byte;

            dma_init_struct.periph_addr = (uint32_t)&I2C_DATA(I2CX);

            dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;

            dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;

            dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;

            dma_init(DMA_CH1, &dma_init_struct);

            /* enable I2CX DMA */

            i2c_dma_enable(I2CX, I2C_DMA_ON);

            /* enable DMA0 channel1 */

            dma_channel_enable(DMA_CH1);

            /* wait until full transfer finish flag is set */

            while(!dma_flag_get(DMA_CH1, DMA_FLAG_FTF));

            /* wait until BTC bit is set */

            while((!i2c_flag_get(I2CX, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)){

                timeout++;

            }

            if(timeout < I2C_TIME_OUT){

                timeout = 0;

                state = I2C_STOP;

            }else{

                timeout = 0;

                state = I2C_START;

                printf("i2c master sends data timeout in PAGE WRITE!\n");

            }

            break;

        case I2C_STOP:

            /* send a stop condition to I2C bus */

            i2c_stop_on_bus(I2CX);

            /* i2c master sends STOP signal successfully */

            while((I2C_CTL0(I2CX) & 0x0200) && (timeout < I2C_TIME_OUT)){

                timeout++;

            }

            if(timeout < I2C_TIME_OUT){

                timeout = 0;

                state = I2C_END;

                i2c_timeout_flag = I2C_OK;

            }else{

                timeout = 0;

                state = I2C_START;

                printf("i2c master sends stop signal timeout in PAGE WRITE!\n");

            }

            break;

        default:

            state = I2C_START;

            i2c_timeout_flag = I2C_OK;

            timeout = 0;

            printf("i2c master sends start signal in PAGE WRITE!\n");

            break;

        }

    }

    return I2C_END;

} 使用这两个函数只需传入需要操作的数组,页的地址和读写的数据量便可,这里贴一下测试的函数: 复制 uint8_t i2c_24c02_test(void)

{

    uint16_t i;

    printf("\r\nAT24C02 writing...\r\n");

    

    /* initialize i2c_buffer_write */

    for(i = 0; i < BUFFER_SIZE; i++){ 

        i2c_buffer_write[i] = i;

        printf("0x%02X ", i2c_buffer_write[i]);

        if(15 == i%16){

            printf("\r\n");

        }

    }

    /* EEPROM data write */

    eeprom_buffer_write_dma_timeout(i2c_buffer_write, EEP_FIRST_PAGE, BUFFER_SIZE);

    printf("AT24C02 reading...\r\n");

    /* EEPROM data read */

    eeprom_buffer_read_dma_timeout(i2c_buffer_read, EEP_FIRST_PAGE, BUFFER_SIZE);

    /* compare the read buffer and write buffer */

    for(i = 0; i < BUFFER_SIZE; i++){

        if(i2c_buffer_read[i] != i2c_buffer_write[i]){

            printf("0x%02X ", i2c_buffer_read[i]);

            printf("Err:data read and write aren't matching.\n\r");

            return I2C_FAIL;

        }

        printf("0x%02X ", i2c_buffer_read[i]);

        if(15 == i%16){

            printf("\r\n");

        }

    }

    printf("I2C-AT24C02 test passed!\n\r");

    return I2C_OK;

} 参数定义: 复制 #define EEPROM_BLOCK0_ADDRESS    0xA0

#define BUFFER_SIZE              256

uint16_t eeprom_address;

uint8_t i2c_buffer_write[BUFFER_SIZE];

uint8_t i2c_buffer_read[BUFFER_SIZE];

uint8_t i2c_buffer_read1[BUFFER_SIZE];

#define I2C_TIME_OUT   (uint16_t)(5000)

#define I2C_TIME_OUT1  (uint32_t)(200000)

#define EEP_FIRST_PAGE 0x00

#define I2C_OK         1

#define I2C_FAIL       0

#define I2C_END        1

#define I2CX           I2C0

一开始向发送数组中填充256个数据,然后调用写函数将256个数据写进24C02,因为24C02只有一页,所以页数设置为0,写完后再读出数据,校验写入和读出的数据是否一致。 这里我便遇到了第二个坑,写完之后调试不通过,代码卡死在IIC的时钟初始化i2c_clock_config(I2C0, I2C0_SPEED, I2C_DTCY_2);,继续进入这个函数,发下卡死在:

这里仅仅是一个数据的运算,卡死在这里明显不合理。这时候我想到了上周调试报错:\output\Project.axf: Error: L6406E: No space in execution regions with .ANY selector matching usart.o(.text.RS_485_SEND).当时是内存溢出了,于是我看了一下工程的map文件:

果然,已经快要顶不住了,于是果断修改keil的编译优化选项,将优化等级提升为1级,内存缩小很多,问题便可解除,因为之前没用过这么下的MCU,而且这次编译没报错,若不是上次报错让我找到原因,这次不知又要卡多久。  

至此代码都是只能读写24c02,也就是操作一页。如果是更大的EEPROM器件,那么该如何操作。这里稍微吐槽一下:为啥我会用IIC来操作24C04,因为我的硬件板子上焊接的就是24C04,而为啥是24C04,并不是24C02不够用,只是因为04比02便宜,市场都畸形了吗?而我为啥要吐槽,是因为下文遇到的第三个问题。 扩充到24C04: 既然知道如何写一页,那么即使再大的容量,我们也有办法去操作,以24C04为例,只是比24C02多了一页,地址为0x01;那么我们增加一个宏定义:

定义一下第二页的地址,然后定义一个数组去接收我们读到的数据:

最后在测试程序中添加读写第二页的操作:

测试的时候再次翻车,第一次debug没问题,第二次debug的时候程序卡死在第一个写EEPROM的函数,我猜测难道又是数据超了,但是只增加了一个数组啊,代码量应该不会超标啊,于是把添加的代码全部删除,结果还是失败。 解决方法:烧录之前先擦除全部flash,再烧录便可成功。

这个原因是为啥,我还没找到,希望有知道的大佬可以给个答案,如果后续知道答案我再补上。 --------------------- 作者:呐咯密密 链接:https://bbs.21ic.com/icview-3160886-1-1.html 来源:21ic.com 此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。  

标签: 内部传感器贴片ic

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台