GD32E230F4使用硬件IIC+DMA读写24C04

news2024/12/24 21:09:42

前言

在很久很久以前,我就写过GD32E230替换STM32F031的帖子,主要介绍了USART和SPI的外设移植开发,当时IIC使用的是软件i2c,没有介绍的价值。在使用IIC时,大多数我们都是采用软件的方式,因为软件的方式及其简单,一套组合拳几乎可以拿到任意MCU去使用。而STM32的硬件IIC也不稳定,经常容易卡死,我在STM32F031时侥幸将硬件IIC调试成功,但是后来使用STM32F103时却无法成功,真是个菜狗。但是由于项目需求,读写IIC的时间很难空出来,必须将时间腾出来给其他外设,我的软件IIC只能作废,需要重新编写硬件IIC的代码,并且需要带上DMA,将时间缩减到最小。于是就有了今天帖子。
STM32F031硬件IIC传送门:STM32F031硬件读写24C02

软件代码

1.外设初始化

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);
}

2.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的数据寄存器都能将数据存进寄存器内部,此时去读数据寄存器就能获取到数据,道理就是这么个道理,可能我描述的不是很准确,但是一定是可以的。

3.IIC操作函数

初始化GPIO之后就是调用库函数对IIC进行初始化。
i2c_clock_config()函数的三个参数,第一个就是选定哪个I2C,第二个是设置I2C的速度,这里是以宏定义的方式定义的,速度为100000,第三个设置快速模式下的占空比,如果是速度在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;那么我们增加一个宏定义:
在这里插入图片描述
定义一下第二页的地址,然后定义一个数组去接收我们读到的数据:
在这里插入图片描述
最后在测试程序中添加读写第二页的操作:
在这里插入图片描述
源码下载链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/623804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

护网是什么?怎么参加

一、什么是护网行动&#xff1f; 护网行动是以公安部牵头的&#xff0c;用以评估企事业单位的网络安全的活动。 具体实践中。公安部会组织攻防两方&#xff0c;进攻方会在一个月内对防守方发动网络攻击&#xff0c;检测出防守方&#xff08;企事业单位&#xff09;存在的安全…

对象进阶、原型-原型链

工厂方法创建对象 我们之前已经学习了如何创建一个对象&#xff0c;那我们要是想要创建多个对象又该怎么办&#xff1f;聪明的同学可能会说&#xff0c;直接在写几个对象不就好了吗&#xff1f;比如下边的代码&#xff1a; var person1 {name: "孙悟空",age: 18,s…

2核2G3M轻量服务器腾讯云和阿里云怎么选择?

2核2G3M轻量应用服务器选阿里云还是腾讯云&#xff1f;腾讯云2核2G3M轻量应用服务器95元一年&#xff0c;阿里云2核2G3M配置优惠价108元一年&#xff0c;如何选择&#xff1f;阿腾云详细对比阿里云和腾讯云轻量应用服务器2核2G3M配置CPU性能及选择方法&#xff1a; 目录 轻量…

基于STM32F103+思岚A1激光雷达的扫描仪

前言 一个朋友在做服务机器人项目&#xff0c;用到思岚的激光雷达&#xff0c;于是便把淘汰的A1M8雷达送我一个&#xff0c;本着拿到啥就玩啥的态度&#xff0c;必须整一波。其实激光雷达还是搭配ROS才能发挥最大的作用&#xff0c;奈何资源有限&#xff0c;实力不足&#xff…

STM32 USB CDC VPC

STM32 USB CDC VPC 关键字 STM32,STM32CubeMX,HAL库,USB,虚拟串口,串口不定长接收 1.简介 通过使用stm32cubemx&#xff0c;实现USB CDC虚拟串口&#xff0c;并与硬件串口进行数据传输&#xff0c;实现了硬件串口数据的不定长接收&#xff0c;以及USB虚拟串口超过64字节的数…

钉钉H5微应用基础学习

钉钉开发文档 一、使用调试工具——IDE&#xff1a; 1、先下载调试工具&#xff0c;并且新建一个企业内部应用。 如果需要管理员权限&#xff0c;可以自己创建一个企业。 &#xff08;tips&#xff1a;一定要屏蔽自己创建的企业的消息&#xff0c;不然消息很多&#xff09; 2…

Burpsuite超详细安装教程

概述 Burp Suite 是用于攻击web 应用程序的集成平台&#xff0c;包含了许多工具。Burp Suite为这些工具设计了许多接口&#xff0c;以加快攻击应用程序的过程。所有工具都共享一个请求&#xff0c;并能处理对应的HTTP 消息、持久性、认证、代理、日志、警报。 接下来我来给大…

Linux4.8Nginx Rewrite

文章目录 计算机系统5G云计算第六章 LINUX Nginx Rewrite一、Nginx Rewrite 概述1.常用的Nginx 正则表达式2.rewrite和location3.location4.实际网站使用中&#xff0c;至少有三个匹配规则定义5.rewrite6.rewrite 示例 计算机系统 5G云计算 第六章 LINUX Nginx Rewrite 一、…

看完一位毕业的拼多多“P9”级别员工以及他的四页半简历,我悟了

前几天在脉脉上看到一个热帖&#xff0c;是刚从PDD毕业的P9级别员工吴可发的&#xff0c;同时附上了他的简历&#xff0c;这个简历很有意思&#xff0c;基本上和国内互联网这十多年来的发展步骤重叠&#xff0c;能够反映出&#xff0c;在这样一个跌宕起伏的时代里&#xff0c;个…

一次有关 DNS 解析导致 APP 慢的问题探究

一、业务背景 HTTTPDNS AWS Router53 APP 使用 HTTPDNS&#xff0c; 为解决 DNS 解析生效慢&#xff0c; DNS 劫持等问题。 我们 IOS 和安卓都是使用了 HTTPDNS。 域名托管在 AWS Router53。 域名有多个解析(基于延迟)&#xff0c;为了解决就近接入。 示例配置 ai.baidu.c…

网易Java后端面经(一面)

这是网易的Java一面&#xff0c;问的都很基础。 1.session过期怎么处理&#xff1f; session过期通常指用户在一段时间内没有进行任何操作而导致session失效。针对这种情况&#xff0c;可以采取以下措施&#xff1a; 1. 前端提示用户session即将过期&#xff0c;提醒其重新登录…

JavaScript对象的增强知识

Object.defineProperty ◼ 在前面我们的属性都是直接定义在对象内部&#xff0c;或者直接添加到对象内部的&#xff1a;  但是这样来做的时候我们就不能对这个属性进行一些限制&#xff1a;比如这个属性是否是可以通过delete删除的&#xff1f;这个属性是否在for-in遍历的时候…

微信能取代对讲机吗?区别在哪?

对讲机和微信的区别在哪&#xff1f;为什么大家在通讯方面选择对讲机而不是微信&#xff1f; 微信作为社交软件在多个领域都有着广泛的应用&#xff0c;不过在对讲机行业也在讨论一个话题&#xff1a;微信能否取代对讲机&#xff1f;下面河南宝蓝小编就和大家聊聊这个话题。 …

基于redis实现秒杀并防止超卖

基于redis实现秒杀并防止超卖 为什么基于redis针对秒杀商品库存为一个的情况setnx代码实现测试 针对有多个库存的商品实现测试 为什么基于redis 因为所有redis的操作&#xff08;这里指的是key的操作&#xff0c;像备份落盘之类的另算&#xff09;都是单线程的&#xff0c;所以…

一文读懂:LoRA实现大模型LLM微调

LoRA大模型LLM微调 为什么要进行微调&#xff1f;LoRA思路提高权重更新效率选择低的秩 实现LoRALoRA在LLaMA实现 为什么要进行微调&#xff1f; 在快速发展的人工智能领域中&#xff0c;以高效和有效的方式使用大型语言模型变得越来越重要。 预训练的大型语言模型通常被称为优…

02-启动 Vue 项目

一. 学习目标 掌握 Vue 项目的启动 二. 学习内容 掌握 Vue 项目的启动 三. 学习过程 项目的启动也有两种方式&#xff0c;一种是通过图形界面启动&#xff0c;另一种是通过命令行启动。 1.图形界面 打开vscode编辑器&#xff0c;点击 1.文件 ——>打开文件夹&#xff0c…

springboot实现支付宝支付(沙箱环境)

springboot实现支付宝支付 1. 获取应用id,应用私钥和支付宝公钥2. 开始开发3. 内网穿透4. 测试支付功能 1. 获取应用id,应用私钥和支付宝公钥 进入支付宝控制台:https://open.alipay.com/develop/manage 找到沙箱 这里可以看到应用id 可以看到应用私钥和支付宝公钥,获取这…

Zoho:集成ChatGPT、开发大型语言模型,加紧布局AI+SaaS

在企业的数字化转型进程中&#xff0c;管理层和员工的数字化意识会随着建设的推进而不断提高&#xff0c;对于办公场景的数字化应用需求也不断产生。传统的办公系统建设中&#xff0c;系统的应用能力需要支撑越来越丰富的场景需求。 《今日人工智能》采访到Zoho中国VP兼SaaS事业…

【编程语言 · C语言 · for语句】

for 语句 C语言中&#xff0c;使用for语句也可以控制一个循环&#xff0c;并且在每一次循环时修改循环变量。在循环语句中&#xff0c;for语句的应用最为灵活&#xff0c;不仅可以用循环次数已经确定的情况&#xff0c;而且可以用于循环次数不确定而只给出循环结束条件的情况。…