世纪电源网社区logo
社区
Datasheet
标题
返回顶部
原创

IIC,一个坑,还是用MCU模拟吧

[复制链接]
查看: 2304 |回复: 5
1
yzpc05d_x04
  • 积分:6294
  • |
  • 主题:85
  • |
  • 帖子:1034
积分:6294
版主
  • 2020-6-5 08:48:40
硬件I2C上的缺陷,新版英文ErrSheet已经写得很清楚,就不引用了,这里只简单说说要点和一些个人总结。
1、EV7, EV7_1, EV6_1,EV6_3, EV2, EV8 和 EV3 必须在当前字节传输前处理完成,不然,有可能会导致数据出错。
EV5:   SB=1,读SR1然后将地址写入DR寄存器将清除该事件。
EV6:   ADDR=1,读SR1然后读SR2将清除该事件。
EV6_1:  没有对应的事件标志,只适用于接受1个字节的情况。恰好在EV6之后,要清除响应和停止条件的产生位。
EV_7: RxNE=1,读DR寄存器清除该事件。
EV7_1: RxNE=1,读DR寄存器清除该事件。设置ACK=0和STOP请求。
EV8_1: TxE=1,移位寄存器空,数据寄存器空,写DR寄存器。
EV8:   TxE=1,移位寄存器非空,数据寄存器空,写入DR寄存器将清除该事件。
EV8_2: TxE=1,BTF=1,请求设置停止位。TxE,BTF位由硬件在产生停止条件时清除。
EV9: ADDR10=1,读SR1然后写入DR寄存器将清除该事件。
EV_9:ADDR10=1,读SR1然后写入DR寄存器将清除该事件。
注:EV5,EV6,EV9,EV8_1,EV8_2事件拉低SCL低的事件,直到对应的软件序列结束。
EV7,EV8的软件序列必须在当前字节传输结束前完成。
EV6_1      或EV7_1的软件序列必须在当前传输字节的ACK脉冲之前完成。
这几个事件都涉及到DR和DSR,可能是读出或者写入DR的同时DSR被填满或清空,导致数据出错。理想情况下“读出或者写入DR的同时DSR被填满或清空”是不可能发生的,中断一来临的时候,CPU马上处理中断请求,读出或者写入DR数据,这时DSR的数据还是“新鲜”的,可能连一位都没有接收或发送。但是,在实际使用时,可能有别的中断优先级比I2C的事件中断要高,I2C事件没有及时处理而出现了上述的情况。所以,ST建议把I2C的事件中断设置成最高优先级。
2、产生STOP前DSR必须为空,不然,会导致DSR里的数据左移一位。
这个没什么好说的,就是一个硬件的BUG,保证发送STOP前DSR没有数据就可以了。
3、总线上,开始条件(S)后没有进行数据传输就马上设置停止条件(P),或者S后忘记P会导致硬件I2C不能再次产生S,必须软复位I2C。
这个ST解释成是,STM32严格按照了I2C的标准,S之后没有数据传输是不能P的。其实这点可以体谅,但是,这点如果没有处理好,总线上的错误会导致STM32I2C陷入瘫痪。

硬件模拟IIC-EEPROM 24C02

yzpc05d_x04
  • 积分:6294
  • |
  • 主题:85
  • |
  • 帖子:1034
积分:6294
版主
  • 2020-6-5 09:01:16
  • 倒数5
 
#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入
#define SDA_IN(){GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=8<<12;}
#define SDA_OUT(){GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=3<<12;}
#define IIC_SCL    PBout(6)                  //SCL
#define IIC_SDA    PBout(7)                 //SDA         
#define READ_SDA   PBin(7)                         //输入SDA

//初始化 IIC
void IIC_Init(void)
{
    GPIO_InitTypeDefGPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE );//PB 时钟使能
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6|GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP ; //推挽输出
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化 GPIO
    GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);//PB6,PB7 输出高
}
//产生 IIC 起始信号
void IIC_Start(void)
{
    SDA_OUT(); //sda线输出
    IIC_SDA=1;
    IIC_SCL=1;
    delay_us(4);
    IIC_SDA=0;//START:when CLK is high,DATA change form high to low
    delay_us(4);
    IIC_SCL=0; //钳住 I2C 总线,准备发送或接收数据
}
//产生 IIC 停止信号
void IIC_Stop(void)
{
    SDA_OUT(); //sda线输出
    IIC_SCL=0;
    IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
    delay_us(4);
    IIC_SCL=1;
    IIC_SDA=1; //发送 I2C 总线结束信号
    delay_us(4);
}
//等待应答信号到来
//返回值: 1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
    u8 ucErrTime=0;
    SDA_IN(); //SDA 设置为输入
    IIC_SDA=1;delay_us(1);
    IIC_SCL=1;delay_us(1);
    while(READ_SDA)
    {
        ucErrTime++;
        if(ucErrTime>250)
        {
            IIC_Stop();
            return 1;
        }
    }
    IIC_SCL=0; //时钟输出 0
    return 0;
}
//产生 ACK 应答
void IIC_Ack(void)
{
    IIC_SCL=0;
    SDA_OUT();
    IIC_SDA=0;
    delay_us(2);
    IIC_SCL=1;
    delay_us(2);
    IIC_SCL=0;
}
//不产生 ACK 应答
void IIC_NAck(void)
{
    IIC_SCL=0;
    SDA_OUT();
    IIC_SDA=1;
    delay_us(2);
    IIC_SCL=1;
    delay_us(2);
    IIC_SCL=0;
}
//IIC 发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
    u8 t;
    SDA_OUT();
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {
        IIC_SDA=(txd&0x80)>>7;
        txd<<=1;
        delay_us(2); //对 TEA5767 这三个延时都是必须的
        IIC_SCL=1;
        delay_us(2);
        IIC_SCL=0;
        delay_us(2);
    }
}
// 1 个字节, ack=1 时,发送 ACK ack=0,发送 nACK
u8 IIC_Read_Byte(unsigned char ack)
{
    unsigned char i,receive=0;
    SDA_IN(); //SDA 设置为输入
    for(i=0;i<8;i++ )
    {
        IIC_SCL=0;
        delay_us(2);
        IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)
               receive++;
        delay_us(1);
    }
    if (!ack)
        IIC_NAck(); //发送 nACK
    else
        IIC_Ack(); //发送 ACK
    return receive;
}

//初始化 IIC 接口
void AT24CXX_Init(void)
{
    IIC_Init();
}
// AT24CXX 指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
    u8 temp=0;
    IIC_Start();
    if(EE_TYPE>AT24C16)
    {
        IIC_Send_Byte(0XA0); //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(ReadAddr>>8); //发送高地址
    }
    else
        IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));//发送器件地址 0XA0,写数据
    IIC_Wait_Ack();
    IIC_Send_Byte(ReadAddr%256); //发送低地址
    IIC_Wait_Ack();
    IIC_Start();
    IIC_Send_Byte(0XA1); //进入接收模式
    IIC_Wait_Ack();
    temp=IIC_Read_Byte(0);
    IIC_Stop(); //产生一个停止条件
    return temp;
}
// AT24CXX 指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8DataToWrite)
{
    IIC_Start();
    if(EE_TYPE>AT24C16)
    {
        IIC_Send_Byte(0XA0); //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(WriteAddr>>8);//发送高地址
    }
    else
        IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址 0XA0,写数据
    IIC_Wait_Ack();
    IIC_Send_Byte(WriteAddr%256); //发送低地址
    IIC_Wait_Ack();
    IIC_Send_Byte(DataToWrite); //发送字节
    IIC_Wait_Ack();
    IIC_Stop(); //产生一个停止条件
    delay_ms(10);
}
// AT24CXX 里面的指定地址开始写入长度为 Len 的数据
//该函数用于写入 16bit 或者 32bit 的数据.
//WriteAddr :开始写入的地址
//DataToWrite:数据数组首地址
//Len :要写入数据的长度 2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32DataToWrite,u8 Len)
{
    u8 t;
    for(t=0;t<Len;t++)
    {
        AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
    }
}
// AT24CXX 里面的指定地址开始读出长度为 Len 的数据
//该函数用于读出 16bit 或者 32bit 的数据.
//ReadAddr :开始读出的地址
//返回值 :数据
//Len :要读出数据的长度 2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8Len)
{
    u8 t;
    u32 temp=0;
    for(t=0;t<Len;t++)
    {
        temp<<=8;
        temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
    }
    return temp;
}
//检查 AT24CXX 是否正常
//这里用了 24XX 的最后一个地址(255)来存储标志字.
//如果用其他 24C 系列,这个地址要修改
//返回 1:检测失败
//返回 0:检测成功
u8 AT24CXX_Check(void)
{
    u8 temp;
    temp=AT24CXX_ReadOneByte(255); //避免每次开机都写 AT24CXX
    if(temp==0X55)
        return 0;
    else //排除第一次初始化的情况
    {
        AT24CXX_WriteOneByte(255,0X55);
        temp=AT24CXX_ReadOneByte(255);
        if(temp==0X55)return 0;
    }
    return 1;
}
// AT24CXX 里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 24c02 0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8*pBuffer,u16 NumToRead)
{
    while(NumToRead)
    {
        *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
        NumToRead--;
    }
}
// AT24CXX 里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 24c02 0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8*pBuffer,u16 NumToWrite)
{
    while(NumToWrite--)
    {
        AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
        WriteAddr++;
        pBuffer++;
    }
}
ZWC317441532
  • 积分:14333
  • |
  • 主题:55
  • |
  • 帖子:1262
积分:14333
LV10
总工程师
  • 2020-6-6 08:33:57
  • 倒数4
 
原来是STM32的IIC有坑,难怪一些列程都不用它。我有这样的需求,要如何用模似IIC读数据,设备地址:单字节,存储地址是双字节的.
zhangxiaolong
  • 积分:107
  • |
  • 主题:0
  • |
  • 帖子:1
积分:107
LV2
本网技师
  • 2020-6-12 21:03:10
  • 倒数3
 
很少人喜欢用STM32的硬件IIC,都是IO模拟IIC居多
BingSun
  • 积分:10963
  • |
  • 主题:58
  • |
  • 帖子:1997
积分:10963
LV10
总工程师
  • 2020-7-4 00:00:46
  • 倒数2
 
用硬件可以减小占用CPU资源。
top041376
  • 积分:6700
  • |
  • 主题:21
  • |
  • 帖子:101
积分:6700
LV8
副总工程师
最新回复
  • 2020-9-1 14:47:19
  • 倒数1
 
模拟的移植方便,硬件半天不见得能调好      
热门技术、经典电源设计资源推荐

世纪电源网总部

地 址:天津市南开区黄河道大通大厦8层

电 话:400-022-5587

传 真:(022)27690960

邮 编:300110

E-mail:21dy#21dianyuan.com(#换成@)

世纪电源网分部

广 东:(0755)82437996 /(138 2356 2357)

北 京:(010)69525295 /(15901552591)

上 海:(021)24200688 /(13585599008)

香 港:HK(852)92121212

China(86)15220029145

网站简介 | 网站帮助 | 意见反馈 | 联系我们 | 广告服务 | 法律声明 | 友情链接 | 清除Cookie | 小黑屋 | 不良信息举报 | 网站举报

Copyright 2008-2024 21dianyuan.com All Rights Reserved    备案许可证号为:津ICP备10002348号-2   津公网安备 12010402000296号