STM32F103使用GPIO模拟IIC操作AT24C02

STM32F103使用GPIO模拟I2C操作AT24C02

最近找到一块0.96寸OLED屏幕,使用的是I2C通信协议。
但是自己使用GPIO模拟I2C通信总是不成功,猜测应该是自己写的I2C协议代码的问题。
最后移植了别人用STM32硬件I2C操作OLED屏幕的代码,完成后发现用指头触碰OLED引脚的时候会导致STM32卡死,我相信这是之前总提到STM32硬件I2C会跑飞的现象(之后用GPIO模拟I2C通信时不会出现)。
触碰这里时,STM32会卡住

使用GPIO模拟I2C

阅读了之前学习AT24C02写的模拟I2C代码,发现当时写的这破玩意根本不能看(当然现在这个代码可能过几个月也觉得是一坨poop),因此重新写了使用GPIO模拟I2C的代码,使用AT24C02进行测试,并且使用时只需要修改iic_gpio.h中相关宏定义即可切换I2C时钟、端口和引脚。

#define	IIC3_SCL		GPIO_Pin_2
#define	IIC3_SDA		GPIO_Pin_3
#define IIC3_CLK		RCC_APB2Periph_GPIOA
#define IIC3_PORT		GPIOA

由于我的单片机有两个硬件I2C,因此命名为了IIC3。

为了提高模拟IIC的速度,使用直接操作寄存器的方式代替库函数。

#define IIC3_SCL_SET		(GPIOA->ODR |= GPIO_Pin_2)
#define IIC3_SCL_CLR		(GPIOA->ODR &= ~(GPIO_Pin_2))
#define IIC3_SDA_SET		(GPIOA->ODR |= GPIO_Pin_3)
#define IIC3_SDA_CLR		(GPIOA->ODR &= ~(GPIO_Pin_3))
#define IIC3_SDA_GET		(GPIOA->IDR &= GPIO_Pin_3)

使用时需初始化IIC3,函数为IIC3_Init();

void IIC3_Init(void)
{
	IIC3_RCC_Config(); // 配置GPIO时钟
	IIC3_GPIO_Config(); // 配置GPIO的模式,I2C需要配置为开漏输出
}

其他函数为I2C的相关操作。

void IIC3_Start(void);
void IIC3_Stop(void);
void IIC3_SendAck(void);
void IIC3_SendNAck(void);
unsigned char IIC3_GetAck(void); //返回值为 ACK or NACK 在头文件中已经定义
void IIC3_WriteByte(unsigned char byte);
unsigned char IIC3_ReadByte(void);

简化延时操作,没有使用systick或者tim定时,而是使用软件延时,系统时钟为72MHz。

void IIC3_Delay(uint16_t ntime);

使用I2C通讯的器件时,只需要在其BSP中包含iic_gpio.h,我这里使用的是绝对路径,没在Options中配置头文件路径。

#include "./Board Support Package/eeprom/iic_gpio.h"

通过I2C协议操作AT24C02

重新写了AT24C02的驱动(应该是叫驱动吧?),将Atmel公司手册中相关读写操作都实现了一下,并且实现了对整数,浮点数,双精度浮点数的操作(这一部分代码的很没有艺术感,之后抽空要优化一下),自己测试了一下感觉没什么问题,但是我觉得还是有点不可靠。

此外 ,如果有幸哪位技术大佬看到的话,还恳请大佬批评指正,自己C语言确实一般,刚刚看完C Peimer Plus,实在感觉刚刚入门,尤其是指针方面的操作,如果有好的建议还请赐教。
在这里插入图片描述

使用前可以检查一下AT24C02是否能够通信,其实就是写器件地址,看有没有ACK应答信号。
我使用的板子AT24C02地址引脚全接地了,因此按照手册中的描述地址应该是(0x50 << 1)。

/* EEPROM(AT24C02) Address Define */
#define EEPROM_ADDR			(0x50 << 1)
#define EEPROM_WRITEMASK	0xFE
#define EEPROM_READMASK		0x01

AT24C02手册中给出的相关读写操作。

void EEPROM_WriteByte(uint8_t addr, uint8_t data);
void EEPROM_WritePage(uint8_t addr, const uint8_t arr[]);
uint8_t EEPROM_CurrentReadByte(void);
uint8_t EEPROM_RandomReadByte(uint8_t addr);
void EEPROM_SequentialReadByte(uint8_t addr, uint8_t arr[]);

int float double 类型的读写操作。

void EEPROM_intWrite(uint8_t addr, int intdata);
void EEPROM_floatWrite(uint8_t addr, float floatdata);
void EEPROM_doubleWrite(uint8_t addr, double doubledata);
int EEPROM_intRead(uint8_t addr);
float EEPROM_floatRead(uint8_t addr);
double EEPROM_doubleRead(uint8_t addr);

向AT24C02中全部的地址写入同一个数据,可以用来擦除AT24C02。

void EEPROM_FillWrite(uint8_t data);

AT24C02写入时需要延时一定时间,等待AT24C02将数据写入单元,这个延时很重要,必须添加。

void EEPROM_WriteDelay(uint16_t ntime);

测试效果

通过串口打印到串口调试助手。测试代码在task.c中。

void EEPROM_Test(void)
{
	uint8_t arr[8] = {
		00, 11, 22, 33, 44, 55, 66, 77
	};
	uint8_t copy[8];
	uint8_t i, j;
	
	EEPROM_FillWrite(0x00);
	printf( "EEPROM Checking...\n");
	if(EEPROM_CHECK() == EEPROM_READY)
		printf("EEPROM Ready\n");
	else
		printf("EEPROM NOT Ready, Please Check It\n\n");
	printf("*******************************************\n");
	printf("EEPROM Ranrom Write\\Read Test...");
	EEPROM_WriteByte(0x54,5);
	EEPROM_WriteByte(0x67,2);
	printf("\nEEPROM Random Addr[0x54] = %d", EEPROM_RandomReadByte(0x54));
	printf("\nEEPROM Random Addr[0x67] = %d", EEPROM_RandomReadByte(0x67));
	printf("\n*******************************************\n");
	printf("EEPROM Current Read Test...");
	printf("\nEEPROM Current Addr[0x68] = %d", EEPROM_CurrentReadByte());
	printf("\nEEPROM Random Addr[0x68] = %d", EEPROM_RandomReadByte(0x68));
	printf("\n*******************************************\n");
	printf("EEPROM intWrite Test...\n");
	EEPROM_intWrite(0xB0, 0x11223344);
	printf("intdata = %X(%d)", EEPROM_intRead(0xB0), EEPROM_intRead(0xB0));
	printf("\n*******************************************\n");
	printf("EEPROM floatWrite Test...");
	EEPROM_floatWrite(0xC0, -254.987654321);
	printf("\n floatdata = %f", EEPROM_floatRead(0xC0));
	printf("\n*******************************************\n");
	printf("EEPROM doubleWrite Test...");
	EEPROM_doubleWrite(0xD0, -123456789.987654321);
	printf("\n doubledata = %f", EEPROM_doubleRead(0xD0));
	printf("\n*******************************************\n");
	printf("EEPROM Pages Write\\Read Test...");
	EEPROM_WritePage(0x00, arr);
	EEPROM_WritePage(0x08, arr);
	for(i = 0; i < 32; i++)
	{
		EEPROM_SequentialReadByte(i*8, copy);
		for(j = 0; j < 8; j++)
		{
			printf("\n%d:Addr[0x%02x] = %X(%d)", i*8 + j, i*8 + j, copy[j], copy[j]);
		}
	}
	printf("\n*******************************************");
}

效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

学习中遇到的一些问题和经验

## IIC通信
 - 连接总线的设备要有独立地址,这个地址可以是 7 位或者 10 位,8位地址时加入了 R\W| 位,为读地址和写地址,才能正确返回数据。
 - 总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
 - 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多I2C设备尚不支持高速模式。
 - 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
 - 仲裁机制:逻辑 0 比逻辑 1 优先。(?- 值得注意的是:推挽输出对应->需要切换输入输出模式;开漏输出对应->不需要切换输入输出模式。输入部分施密特触发器是否打开(更像是一个三态门)决定。
   ### EEPROM 
   - Byte Write 时序:起始信号,先发送设备地址+0(LSB),接收应答,再发送写入单元格地址,接收应答,发送写入的数据,接收应答,停止信号。
   - Page Write 时序:起始信号,先发送设备地址+0(LSB),接收应答,再发送写入起始单元格地址,接收应答,发送写入的第一个数据,接收应答.....发送写入的第8\16个数据,接收应答,停止信号。
     -  2kB 大小一页为 8 个字节,当写入数据大于 8 时,会覆盖最后一个地址的数据。
   - Current Address Read:起始信号,先发送设备地址+1(LSB),接收应答,接收读取的数据,发送非应答,停止信号。
   - Random Read:[起始信号,先发送设备地址+0(LSB),接收应答,再发送读取单元格地址,接收应答,]发送起始信号,发送设备地址+1(LSB),接收应答,接收读取的单元格的数据,发送非应答,停止信号。
     - [Dummy_Write] + Current Address Read
   - Sequential Read:[起始信号,先发送设备地址+0(LSB),接收应答,再发送读取单元格地址,接收应答,]发送起始信号,发送设备地址+1(LSB),接收应答,接收读取的单元格的数据,[发送应答]......接收读取的单元格的数据,发送非应答,停止信号。
     - sequential:按次序的。随机读取结束发送应答信号,而不是非应答信号。
   - 
 - 注意:操作 SDA 后要释放总线
 - 注意:EEPROM 写入时速度缓慢,必须延时等待写入完成。

最后把相关的工程和代码放一下

STM32F103使用GPIO模拟I2C操作AT24C02 - keil工程文件

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是stm32f103c8t6模拟iIC驱动at24c02模拟spi驱动的w25q64的代码示例: 模拟iIC驱动at24c02代码: ``` #include "stm32f10x.h" #define ADDR_AT24C02 0xA0 void I2C1_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); } void I2C1_Mode_Config(void) { I2C_InitTypeDef I2C_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); I2C_DeInit(I2C1); I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 100000; I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); } uint8_t I2C1_WriteByte(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) { I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, dev_addr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg_addr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, data); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); return 0; } uint8_t I2C1_ReadByte(uint8_t dev_addr, uint8_t reg_addr) { uint8_t data; I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, dev_addr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg_addr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, dev_addr, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2C1); return data; } void AT24C02_Write(uint8_t addr, uint8_t data) { I2C1_WriteByte(ADDR_AT24C02, addr, data); } uint8_t AT24C02_Read(uint8_t addr) { return I2C1_ReadByte(ADDR_AT24C02, addr); } int main() { I2C1_GPIO_Config(); I2C1_Mode_Config(); uint8_t data = 0x55; AT24C02_Write(0x00, data); data = AT24C02_Read(0x00); while (1); } ``` 模拟spi驱动的w25q64代码: ``` #include "stm32f10x.h" #include "spi.h" #include "w25q64.h" void W25Q64_Init(void) { SPI2_Init(); GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_12); } uint8_t W25Q64_ReadSR1(void) { uint8_t cmd = W25Q64_CMD_READ_SR1; uint8_t sr1; GPIO_ResetBits(GPIOB, GPIO_Pin_12); SPI2_WriteByte(cmd); sr1 = SPI2_ReadByte(); GPIO_SetBits(GPIOB, GPIO_Pin_12); return sr1; } void W25Q64_WriteEnable(void) { uint8_t cmd = W25Q64_CMD_WRITE_ENABLE; GPIO_ResetBits(GPIOB, GPIO_Pin_12); SPI2_WriteByte(cmd); GPIO_SetBits(GPIOB, GPIO_Pin_12); } void W25Q64_EraseChip(void) { uint8_t cmd = W25Q64_CMD_ERASE_CHIP; W25Q64_WriteEnable(); GPIO_ResetBits(GPIOB, GPIO_Pin_12); SPI2_WriteByte(cmd); GPIO_SetBits(GPIOB, GPIO_Pin_12); while (W25Q64_ReadSR1() & 0x01); } void W25Q64_WritePage(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; uint32_t i; cmd[0] = W25Q64_CMD_PAGE_PROGRAM; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; W25Q64_WriteEnable(); GPIO_ResetBits(GPIOB, GPIO_Pin_12); for (i = 0; i < 4; i++) { SPI2_WriteByte(cmd[i]); } for (i = 0; i < len; i++) { SPI2_WriteByte(data[i]); } GPIO_SetBits(GPIOB, GPIO_Pin_12); while (W25Q64_ReadSR1() & 0x01); } void W25Q64_ReadData(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; uint32_t i; cmd[0] = W25Q64_CMD_READ_DATA; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; GPIO_ResetBits(GPIOB, GPIO_Pin_12); for (i = 0; i < 4; i++) { SPI2_WriteByte(cmd[i]); } for (i = 0; i < len; i++) { data[i] = SPI2_ReadByte(); } GPIO_SetBits(GPIOB, GPIO_Pin_12); } int main() { W25Q64_Init(); uint8_t data[256]; uint32_t i; for (i = 0; i < 256; i++) { data[i] = i; } W25Q64_EraseChip(); for (i = 0; i < 65536; i += 256) { W25Q64_WritePage(i, data, 256); } for (i = 0; i < 65536; i += 256) { W25Q64_ReadData(i, data, 256); /* do something with data */ } while (1); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值