STM32软件模拟iic写eeprom过程中遇到的问题

IIC通信中遇到的问题

最近用IIC往EEPROM中读写数据,遇到了一些问题:

主控:STM32F411U6
从机:GT24C64A
SDA:PB13
SCL:PB14

1、首先遇到的第一个问题就是IIC的SDA无法拉低,其低电平也有2V左右,如下图所示:

一般这个问题就是用了单片机的默认JTAG脚,但是没有禁用 。而我这里PB13并不是默认的JTAG脚。
回去看了看原理图:
在这里插入图片描述
我所用的SDA即PB14不近作为GT24C64A的IIC通信接口,还是与MA730通信的MISO接口。
虽然我这里没有配置MA730的相关代码,但是MA730是低电平片选。上电默认片选,所以MA730的引脚对其产生了影响。
所以在上电初始化的时候,将MA730的片选引脚拉低即可。

	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	GPIO_Init(GPIO_PORT_I2C,&GPIO_InitStruct);
	GPIO_SetBits(GPIOB, GPIO_Pin_12);            //直接拉高,失能 MA730

这里还需要注意一点,本来我是把该引脚和IIC的引脚一起初始化的,然后发现SDA还是无法拉低,原因是IIC的两个引脚上外接了上拉电阻,所以初始化的配置为了开漏输出, 而PB12上面没有外接上拉电阻,配置为开漏,无法拉高,所以PB12应该配置为推挽。

2、解决了这个问题之后SDA ,SCL电平正常,但是没有ACK信号。

回头看了一遍发现不小心用错了函数:在接收应答信号的时候读取SDA上的电平状态读的是:GPIOx_IDR寄存器所以可以调用函数:GPIO_ReadInputDataBit(GPIO_PORT_I2C, I2C_SDA_PIN)
当时错用:GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

3、然后用示波器测波形,有ack信号但是无法完全拉低,波形如下:

SDA第九位明显从机发生了应答,但是并未完全拉低所以通信失败。

本来我输出模式 用的都是推挽输出,然后就出现了上述情况,无论输入模式怎么改变SDA输出波形均如上图所示。将SDA的输出改为开漏后SDA输出正常,应答位也正常。波形如下所示:

(中间的那个毛刺:我发送完一个字节之后将SDA=1 释放总线,之后被slave立刻拉低,slave发送应答,第九个时钟信号结束后,从机释放总线。有些时候第九个时钟之后也会出现一个波动信号,是由于在第九个时钟结束之后slave会释放总线,这个时候在下个时钟信号来到之前,主机还未操作SDA,所以在这个瞬间就会出现主机和从机都没操作SDA的状态。我的板子上的SDA上加了上拉,所以应处于高电平。不同的板子可能出现不同的波动,和sda的setup time与hold time都有关系。

虽然最后数据正常但是我还是不明白SDA的输出模式为什么会影响输入????

代码如下

iic.h

#ifndef __IIC_H_
#define __IIC_H_
#include"stm32f4xx.h"

#define GPIO_PORT_I2C	GPIOB			               //IIC所用端口
#define RCC_I2C_PORT 	RCC_APB1Periph_GPIOB		 //IIC时钟总线的位置
#define I2C_SCL_PIN		GPIO_Pin_13			        
#define I2C_SDA_PIN		GPIO_Pin_14			      
#define GET_SDA_DATA    GPIO_ReadInputDataBit(GPIO_PORT_I2C,  I2C_SDA_PIN)  //读取输入SDA的值

#define I2C_SCL_0   GPIO_ResetBits(GPIOB,I2C_SCL_PIN)
#define I2C_SCL_1		GPIO_SetBits(GPIOB,I2C_SCL_PIN)
#define I2C_SDA_0		GPIO_ResetBits(GPIOB,I2C_SDA_PIN)
#define I2C_SDA_1		GPIO_SetBits(GPIOB,I2C_SDA_PIN)
#define GT24C64_ADDR	0xa0                     //EEPROM的地址
#define GT24C64_READ	0x1											 
#define GT24C64_WRITE	0x0

u8 i2c_read_byte(u8 ack);
void i2c_gpio_init(void);
u8 GT24C64_i2c_write_byte(u16 addr, u8 dat);
u8 GT24C64_i2c_read_byte(u16 addr);
u8 GT24C64_i2c_write_page(u8 *array,u16 addr,u16 num);
u8 GT24C64_i2c_read_page(u8 *array,u16 addr,u16 num);
u8 GT24C64_i2c_read_string(u8 *array,u16 addr,u16 num);
u8 GT24C64_i2c_write_string(u8 *array,u16 addr,u16 num);
#endif

iic.c

/*
 *	F4和EEPROM的IIC通信接口
 *	SCL:PB13 	 SDA:PB14
 */
#include"i2c.h"
#include"delay.h"
#include"stm32f4xx.h"
#include"stm32f4xx_gpio.h"
u8 ack_flag = 0;

/*初始化i2c  SCL和SDA*/
void i2c_gpio_init()
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
	GPIO_DeInit(GPIOB);
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN|I2C_SDA_PIN;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	GPIO_Init(GPIO_PORT_I2C,&GPIO_InitStruct);

	/*PB12拿出来单独配置 因为PB12上面没有外接上拉电阻,配置为开漏即使设置为1也无法输出高电平*/
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
	GPIO_Init(GPIO_PORT_I2C,&GPIO_InitStruct);
	GPIO_SetBits(GPIOB, GPIO_Pin_12);
}

//SDA设置为输出模式
static void i2c_sda_out(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStruct.GPIO_Pin =  I2C_SDA_PIN;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	GPIO_Init(GPIO_PORT_I2C,&GPIO_InitStruct);
}
 
//SDA设置为输入模式
static void i2c_sda_in(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
	//GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_Pin =  I2C_SDA_PIN;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
	GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
	GPIO_Init(GPIO_PORT_I2C,&GPIO_InitStruct);
	GPIO_SetBits(GPIO_PORT_I2C,I2C_SDA_PIN);
}
//iic起始信号
static void i2c_start()
{
	i2c_sda_out();//配置一下引脚,引脚设置为输出
	I2C_SDA_1;
	I2C_SCL_1;
	delay_n_us(4); //start condition setup time
	I2C_SDA_0; 
	delay_n_us(4); //start condition hold time
}

//i2c结束信号
static void i2c_stop()
{
	i2c_sda_out();//配置一下引脚,引脚设置为输出
	I2C_SCL_0;
	I2C_SDA_0;
	delay_n_us(4);
	I2C_SCL_1;
	delay_n_us(4);
	I2C_SDA_1;
	delay_n_us(4);
}
//发送一个字节
void i2c_send_byte(u8 dat)
{
	u8 t;    
	i2c_sda_out();
	for(t=0;t<8;t++)   //时钟线一拉低就开始改变sda上的数据
    {  
	  I2C_SCL_0;			
		delay_n_us(5); 
		if(dat&0x80)
			I2C_SDA_1; 
		else I2C_SDA_0;
        dat<<=1; 	
		I2C_SCL_1;        
		delay_n_us(5);  
    }
	I2C_SDA_1;     	//释放数据总线准备等待应答    
	I2C_SCL_0;        
	delay_n_us(5); 
}
u8 i2c_wait_ack()
{	
	u8 ack;
	i2c_sda_in();
	I2C_SCL_1;
	delay_n_us(5);
	ack = GET_SDA_DATA;
	delay_n_us(5);
	I2C_SCL_0;
	return ack;
}
//读数据的时候是否发送应答 ack = 1:发送应答 ack = 0 :不发送应答
void i2c_send_ack(u8 ack)
{
	i2c_sda_out();
	I2C_SCL_0;           
	if(ack)
	{
		I2C_SDA_0;
	}
	else
	{
		I2C_SDA_1;
	}
	delay_n_us(5);  
	I2C_SCL_1;
	delay_n_us(5);
}

//读一个字节 
u8 i2c_read_byte(u8 ack) 
{  
    u8 i,k;  
		i2c_sda_in();  
    for(i=0;i<8;i++)  
    {  
		I2C_SCL_0;
		delay_n_us(5); 
		I2C_SCL_1; 
		k<<=1;
		if(GET_SDA_DATA)k++;
		//k=((k<<i)|GET_SDA_DATA);  
		delay_n_us(5);
    }
	i2c_send_ack(ack);  
	I2C_SDA_1;        	//释放数据总线         
	I2C_SCL_0;
	delay_n_us(5);
  return k;  
} 

/*从指定的地址写入一字节*/
//u8 GT24C64_i2c_write_byte(u16 addr, u8 dat)
//{
//	i2c_start();
//	i2c_send_byte(GT24C64_ADDR+GT24C64_WRITE);
//	if(i2c_wait_ack()!=0)                              //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}
//	
//	i2c_send_byte(addr>>8);                             //发送要写入的地址
//	if(i2c_wait_ack()!=0)                               //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}

//	i2c_send_byte(addr%256);                            //发送数据地址
//  if(i2c_wait_ack()!=0)                               //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}
//	i2c_send_byte(dat);
//	if(i2c_wait_ack()!=0)                              //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}
//	i2c_stop();
//	
//	return 0;                                          //完成一字节
//}


/*从指定的地址读出一字节*/
//u8 GT24C64_i2c_read_byte(u16 addr)	
//{
//	u8 rcv;
//	i2c_start();
//	i2c_send_byte(GT24C64_ADDR+GT24C64_WRITE);          //先发送器件地
//	if(i2c_wait_ack()!=0)                              //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}
//	//发送
//	i2c_send_byte(addr>>8);                           
//	if(i2c_wait_ack()!=0)                            
//	{
//		//没有拉低,应答失败 直接退出
//		i2c_stop();
//		return 0;
//	}
//	
//	i2c_send_byte(addr%256);
//	if(i2c_wait_ack()!=0)                          //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}

//	i2c_start();
//	i2c_send_byte(GT24C64_ADDR+GT24C64_READ);  
//	if(i2c_wait_ack()!=0)                           //没有拉低,应答失败 直接退出
//	{
//		i2c_stop();
//		return 0;
//	}

//	rcv = i2c_read_byte(0);
//	i2c_send_ack(0);
//	i2c_stop();
//	
//	return rcv;
//}


/*
 *从eeprom读出<=一页的数据
 *array :要写入的属于首地址
 *addr  :写入EEPROM的目的地址
 *num   :写入的字节数
*/              
u8 GT24C64_i2c_read_page(u8 *array,u16 addr,u16 num) 
{
	u16 i = 0;
	i2c_start();
	//第一步发送器件地址
	i2c_send_byte(GT24C64_ADDR+GT24C64_WRITE);          //先发送器件地
	if(i2c_wait_ack()!=0)                              //没有拉低,应答失败 直接退出
	{
		i2c_stop();
		return 0;
	}
	//发送EEPROMM地址高八位
	i2c_send_byte(addr>>8);                          
	if(i2c_wait_ack()!=0)                           
	{
		i2c_stop();
		return 0;
	}
	//发送EEPROM地址低八位
	i2c_send_byte(addr%256);
	if(i2c_wait_ack()!=0)                          
	{
		i2c_stop();
		return 0;
	}
	//发送读出指令
	i2c_start();
	i2c_send_byte(GT24C64_ADDR+GT24C64_READ);  
	if(i2c_wait_ack()!=0)                           
	{
		i2c_stop();
		return 0;
	}
	//开始接受数据
    for(;num>0;num--)
    {
	 if(num == 1)  *(array+i)=i2c_read_byte(0);     //不发送应答信号
   
	 else
		 *(array+i)=i2c_read_byte(1);
	 i++;
    }
	i2c_stop();
	delay_n_ms(500);
	delay_n_ms(500);
  return 0 ;
}

/*
 *往eeprom写入<=一页的数据
 *array :要写入的属于首地址
 *addr  :写入EEPROM的目的地址
 *num   :写入的字节数
*/    

u8 GT24C64_i2c_write_page(u8 *array,u16 addr,u16 num)  
{
	u8 i = 0;
	i2c_start();
	//发送器件地址
	i2c_send_byte(GT24C64_ADDR+GT24C64_WRITE);
	if(i2c_wait_ack()!=0)                              
	{
		i2c_stop();
		return 0;
	}
	//发送内存高地址
	i2c_send_byte(addr>>8);                             
	if(i2c_wait_ack()!=0)                             
	{
		i2c_stop();
		return 0;
	}
	//发送内存低地址
	i2c_send_byte(addr%256);                         
 	if(i2c_wait_ack()!=0)                          
	{
		i2c_stop();
		return 0;
	}
	for(;num>0;num--)
    {
		i2c_send_byte(*(array+i));
		if(i2c_wait_ack()!=0)                             
		{
			i2c_stop();
			return 0;
	    }
        i++;
    }
	i2c_stop();
	delay_n_us(500);
	delay_n_us(500);
	return 1;
}

/*
 *往EEPROM中写入任意字节的数据
 *array :要写入的属于首地址
 *addr  :写入EEPROM的目的地址
 *num   :写入的字节数
*/
u8 GT24C64_i2c_write_string(u8 *array,u16 addr,u16 num)
{
	u8 i =0 ;
	//page_num:以32字节为单位需要循环的次数 (GT24C64一次最长32byte)
	u8 page_num = num/32;
	u8 *num_addr = array;
	u8 least_num = 32;
	if((num%32)!=0) page_num++;
	for(i=0;i<page_num;i++)
	{
		//最后一次循环可能不够32byte ,但是同时要考虑是32整数倍的情况
		if(((page_num-1)==i)&&((num%32)!=0))             
		{
			least_num = num%32;
		}
		else
		{
			least_num = 32;
		}	
		GT24C64_i2c_write_page(num_addr,addr,least_num);
		num_addr = num_addr+32;
		addr = addr +32;
		//这里的延时是必要的:数据从I2CRSR数据接收缓冲寄存器(多字节)复制到I2CCDRR数据接收寄存器
		delay_n_ms(500);
		delay_n_ms(500);
		delay_n_ms(500);
	}
	return 1;
}
/*
 *从EEPROM中读出任意字节的数据
 *array :要写入的属于首地址
 *addr  :写入EEPROM的目的地址
 *num   :写入的字节数
*/
u8 GT24C64_i2c_read_string(u8 *array,u16 addr,u16 num)
{
	u8 i =0 ;
	u8 page_num = num/32;
	u8 *num_addr = array;
	u8 least_num = 0;
	if((num%32)!=0) page_num++;
	
	for(i=0;i<page_num;i++)
	{
		if(((page_num -1)==i)&&((num%32)!=0))              
	    {
			least_num =num%32;
	 	}
		else
		{
			least_num = 32;
		}
	    GT24C64_i2c_read_page(num_addr,addr,least_num);
	 
		num_addr = num_addr+32;
		addr = addr+32;
		delay_n_ms(500);
	}
	return 1;
}

delay.h :

#ifndef __SYSTICK_H_
#define __SYSTICK_H_

#include "stdint.h"

void delay_init(uint8_t SYSCLK);

void delay_n_ms(uint16_t nms);
void delay_n_us(uint32_t nus);

#endif

delay.c

#include "stm32f4xx.h"
#include "delay.h"

static uint8_t fac_us = 0;
static uint16_t fac_ms = 0;

void delay_init(uint8_t SYSCLK)
{
    SysTick->CTRL &= 0xfffffffb;

    fac_us = SYSCLK / 8;
    fac_ms = (uint16_t)fac_us * 1000;
}

void delay_n_ms(uint16_t nms)
{
    uint32_t temp;
    SysTick->LOAD = (uint32_t)nms * fac_ms;
    SysTick->VAL = 0x00;
    SysTick->CTRL = 0x01;
    do
    {
      temp = SysTick->CTRL;
    } while (temp & 0x01 && !(temp & (1 << 16)));

    SysTick->CTRL = 0x00;
    SysTick->VAL = 0X00;
}

void delay_n_us(uint32_t nus)
{
    uint32_t temp;
    SysTick->LOAD = nus * fac_us;
    SysTick->VAL = 0x00;
    SysTick->CTRL = 0x01;

    do
    {
      temp = SysTick->CTRL;
    } while (temp & 0x01 && !(temp & (1 << 16)));

    SysTick->CTRL = 0x00;
    SysTick->VAL = 0X00;
}


参考文章:
[1]:https://blog.csdn.net/luckywang1103/article/details/17549739

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值