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