IIC是一种非常常用的低速通信总线,广泛被用于连接微控制器及其外围设备。 关于IIC的介绍可以参考:IIC通信协议详解 这里我们用软件模拟 IIC来读写AT24C02这个EEPROM。芯片AT24C02的总容量是 256 个字节,该芯片通过 IIC 总线与外部连接。 如下所示,是E53_SC1拓展板上AT24C02的硬件连接图,不过这里有一个很大的问题,就是通过其地址设置引脚的接线我们得到其7位地址为0x51,可实际上,三个地址设置引脚都接到了地,即其7位地址为0x50;不知道是不是因为改版的原因,所以在使用的时候最好从实际硬件去确认一下其器件地址。 接下来我们查看小熊派的原理图,可以看到其使用的IIC引脚
接下来我们就要编写软件模拟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初始化 * 参数:无 * 返回值:无*/ 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; }编译链接烧录到小熊派开发板,然后观察串口输出情况,可以看到读取和写入都成功了
完整代码我存放在码云,可以查看:https://gitee.com/william_william/BearPi-GD32F303RGT6.git 上一篇:小熊派gd32f303学习之旅(7)—使用PWM实现LED呼吸灯 下一篇:小熊派gd32f303学习之旅(9)— 使用硬件I2C读写EEPROM
