小熊派gd32f303学习之旅(8)— 使用软件模拟I2C读写EEPROM

    科技2026-01-14  9

    小熊派gd32f303学习之旅(8)— 使用软件模拟I2C读写EEPROM

    一、前言

    IIC是一种非常常用的低速通信总线,广泛被用于连接微控制器及其外围设备。 关于IIC的介绍可以参考:IIC通信协议详解 这里我们用软件模拟 IIC来读写AT24C02这个EEPROM。芯片AT24C02的总容量是 256 个字节,该芯片通过 IIC 总线与外部连接。 如下所示,是E53_SC1拓展板上AT24C02的硬件连接图,不过这里有一个很大的问题,就是通过其地址设置引脚的接线我们得到其7位地址为0x51,可实际上,三个地址设置引脚都接到了地,即其7位地址为0x50;不知道是不是因为改版的原因,所以在使用的时候最好从实际硬件去确认一下其器件地址。 接下来我们查看小熊派的原理图,可以看到其使用的IIC引脚

    二、编写软件模拟I2C驱动程序

    接下来我们就要编写软件模拟I2C的驱动程序了,首先,将PB6和PB7引脚初始化,

    /* 描述:软件模拟IIC引脚初始化 * 参数:无 * 返回值:无*/ void Soft_I2C_Init(void) { rcu_periph_clock_enable(RCU_GPIOB); /* 使能GPIOB时钟 */ /* 配置 IIC_SCL --> PB6 引脚为推挽输出 */ gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); /* 配置 IIC_SDA --> PB7 引脚为推挽输出 */ gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7); gpio_bit_set(GPIOB, GPIO_PIN_6); gpio_bit_set(GPIOB, GPIO_PIN_7); }

    因为模拟IIC通信里需要用到us延时,所以,编写一个us延时函数

    /* 描述:us级延时函数 * 参数nus:需要延时的us数 * 返回值:无*/ void delay_us(uint32_t nus) { uint32_t ticks; uint32_t told, tnow, tcnt = 0; uint32_t reload = SysTick->LOAD; /* 滴答定时器的重装载值 */ ticks = nus * 120; /* 需要的节拍数 */ told = SysTick->VAL; /* 刚进入时的计数器值 */ while(1) { tnow = SysTick->VAL; if(tnow != told) { if(tnow < told)tcnt += told - tnow; else tcnt += reload - tnow + told; if(tcnt >= ticks)break; /* 时间超过/等于要延迟的时间,则退出. */ told = tnow; } } }

    然后就是模拟IIC通信的函数

    /* 描述:启动I2C总线,即发送I2C起始条件. * 参数: 无 * 返回值:无 */ void IIC_Start(void) { SDA_OUT(); IIC_SDA(1); IIC_SCL(1); delay_us(4); IIC_SDA(0); delay_us(4); IIC_SCL(0); } /* 描述:结束I2C总线,即发送I2C结束条件. * 参数: 无 * 返回值:无 */ void IIC_Stop(void) { SDA_OUT(); IIC_SCL(0); IIC_SDA(0); delay_us(4); IIC_SCL(1); delay_us(4); IIC_SDA(1); delay_us(4); } /* 描述:发送应答 ACK * 参数: 无 * 返回值:无 */ void IIC_ACK(void) { SDA_OUT(); IIC_SCL(0); delay_us(2); IIC_SDA(0); delay_us(2); IIC_SCL(1); delay_us(2); IIC_SCL(0); delay_us(1); } /* 描述:发送非应答 NACK * 参数: 无 * 返回值:无 */ void IIC_NACK(void) { SDA_OUT(); IIC_SCL(0); delay_us(2); IIC_SDA(1); delay_us(2); IIC_SCL(1); delay_us(2); IIC_SCL(0); delay_us(1); } /* 描述:等待ACK * 参数: 无 * 返回值:等待应答返回0,没有等待到应答返回1 */ uint8_t IIC_wait_ACK(void) { uint8_t t = 200; SDA_OUT(); IIC_SDA(1); delay_us(1); IIC_SCL(0); delay_us(1); SDA_IN(); /* 数据发送完后释放数据线,准备接收应答位 */ delay_us(1); while(READ_SDA) /* 等待IIC应答*/ { t--; delay_us(1); if(t==0) { IIC_SCL(0); return 1; } delay_us(1); } delay_us(1); IIC_SCL(1); delay_us(1); IIC_SCL(0); delay_us(1); return 0; } /* 描述:一个字节数据发送函数 * 参数: 无 * 返回值:无 */ void IIC_SendByte(uint8_t byte) { uint8_t BitCnt; SDA_OUT(); IIC_SCL(0); for(BitCnt=0;BitCnt<8;BitCnt++) /* 要传送的数据长度为8位 */ { if(byte&0x80) IIC_SDA(1); /* 判断发送位 */ else IIC_SDA(0); byte<<=1; delay_us(2); IIC_SCL(1); delay_us(2); IIC_SCL(0); delay_us(2); } } /* 描述:一个字节数据接收函数 * 参数: 无 * 返回值:接收到的字节数据 */ uint8_t IIC_RcvByte(void) { uint8_t retc; uint8_t BitCnt; retc=0; SDA_IN(); /* 设置数据线为输入方式 */ delay_us(1); for(BitCnt=0;BitCnt<8;BitCnt++) { IIC_SCL(0); /* 设置时钟线为低,准备接收数据位 */ delay_us(2); IIC_SCL(1); /* 设置时钟线为高使数据线上数据有效 */ retc=retc<<1; if(READ_SDA) retc |=1; /* 读数据位,接收的数据位放入retc中 */ delay_us(1); } IIC_SCL(0); return(retc); }

    三、编写AT24C02控制函数

    通过查看AT24C02的数据手册可以看到其读写时序如下 然后编写AT24C02的读取和写入函数

    /* 描述:AT24C02初始化 * 参数:无 * 返回值:无*/ void at24c02_init(void) { Soft_I2C_Init(); } /* 描述:在AT24C02指定地址读出一个字节的数据 * 参数:ReadAddr: 需要读出数据的地址 * ReadByte: 读出的数据值的存放指针 * 返回值:0:读取成功 其他:读取错误*/ uint8_t AT24C02_Read_Byte(uint16_t ReadAddr, uint8_t *ReadByte) { uint8_t err = 0; IIC_Start(); IIC_SendByte((AT24C02_Addr<<1) | WRITE_BIT); /* 发送器件地址,写数据 */ err |= IIC_wait_ACK(); IIC_SendByte(ReadAddr); /* 发送要读出数据的地址 */ err |= (IIC_wait_ACK() << 1); IIC_Start(); IIC_SendByte((AT24C02_Addr<<1) | READ_BIT); /* 进入接收模式 */ err |= (IIC_wait_ACK() << 2); *ReadByte = IIC_RcvByte(); IIC_NACK(); /* 发送非应答 NACK */ IIC_Stop(); /*产生一个停止条件*/ return err; } /* 描述:在AT24C02指定地址写入一个字节的数据 * 参数:WriteAddr: 需要写入数据的地址 * WriteByte: 要写入的数据 * 返回值:0:写入成功 其他:写入错误*/ uint8_t AT24C02_Write_Byte(uint16_t WriteAddr,uint16_t WriteByte) { uint8_t err = 0; IIC_Start(); IIC_SendByte((AT24C02_Addr<<1) | WRITE_BIT); /* 发送器件地址,写数据 */ err |= IIC_wait_ACK(); IIC_SendByte(WriteAddr); /* 发送要写入数据的地址 */ err |= (IIC_wait_ACK() << 1); IIC_SendByte(WriteByte); /* 写入数据 */ err |= (IIC_wait_ACK() << 2); IIC_Stop();//产生一个停止条件 return err; }

    四、编写主函数

    int main(void) { uint8_t buff; uint8_t err; /* 配置系统时钟 */ systick_config(); /* 初始化LED */ // led_init(); /* 初始化USART0 */ uart0_init(115200); /* 初始化AT24C02 */ at24c02_init(); /* 通过串口打印 Hello world! */ u0_printf("Hello world! "); u0_printf("I am William. \r\n"); err = AT24C02_Write_Byte(0x0a, 0xa5); if(err == 0) printf("Write 0xa5 to addr 0x0a ok \r\n"); else { printf("Write 0xa5 to addr 0x0a err \r\n"); printf("err num : 0x%x \r\n",err); } if(AT24C02_Read_Byte(0x0a, &buff) == 0) printf("Read data: 0x%x from addr 0x0a ok \r\n", buff); else printf("Read data from addr 0x0a err \r\n"); while(1) { if(UART0_RX_STAT > 0) { UART0_RX_STAT = 0; u0_printf("RECEIVE %d data:%s \r\n", UART0_RX_NUM, UART0_RX_BUF); } delay_1ms(10); } }

    五、功能验证

    编译链接烧录到小熊派开发板,然后观察串口输出情况,可以看到读取和写入都成功了

    六、附录

    完整代码我存放在码云,可以查看:https://gitee.com/william_william/BearPi-GD32F303RGT6.git 上一篇:小熊派gd32f303学习之旅(7)—使用PWM实现LED呼吸灯 下一篇:小熊派gd32f303学习之旅(9)— 使用硬件I2C读写EEPROM

    Processed: 0.014, SQL: 9