打印
[应用相关]

知道为什么大部分人喜欢用IO口模拟IIC吗?

[复制链接]
653|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
laocuo1142|  楼主 | 2022-8-10 09:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
也许你会说,我直接用硬件iic怎么没见有过问题!
也许你遇到了,也许没遇到,但是问题就在那,不管你遇不遇到....
也许你不屑于使用模拟iic,对之不屑一顾!
也许你已经在使用模拟iic了,但是我还是要写出来


许多将STM32微控器应用到实际项目中的开发人员发现,I2c接口存在工作不稳定的现象,比如经常出现传输失败或陷入死循环,原因在于:stm32的硬件i2c时序不能被中断!
根据ST所给出的建议对i2c接口惊醒修改使用,确实可以避免这个问题.
但若将i2c总线接口的中断优先级改至最高,那便意味着使用了i2c中断的潜入系统中,其余的中断服务将有可能被i2c中断所嵌套,这种霸道的处理方式很显然无法适用于所有的i2c总线应用场合.
而若使用i2c的DMA模式,则会显著提升应用程序的开发难度,同时i2c接口的灵活性大大降低!
I2c外设:
某些软件事件必须在发送当前字节之前处理
问题描述:
如果没有在传输当前字节之前处理EV7,EV7_1,EV6_1,EV2,EV8和EV3事件,有可能产生问题,如收到一个额外字节,两次读到相同的数据或丢失数据.
暂时解决办法:
当不能再传输当前字节之前和改变ACK控制位送出相应脉冲之前,处理EV7,EV7_1,EV6_1,EV2,EV8和EV3事件时,建议如下操作:
①使用i2c的DMA模式,除非作为主设备时只接受一个字节.
②使用i2c的中断并把它的优先级设置最高,使得他不能被中断.
但是,
使用I/O来模拟i2c总线时序是一种很常见的做法.
但是相对于硬件i2c,在实时性和传输速度上会带来无法避免的下降,但i2c总线本身就不是一种速度很快的总线(最高400khz),同时也不需要具备很高的实时性能.相比之下,使用stm32的I/O口模拟i2c时序完全可以满足大部分场合的需求,并且移植性更佳,因此许多开发人员更倾向于使用模拟形势i2c总线接口.
模拟程序如下:


用的时候发现下面的代码是有问题的.....
问题出在 多页写入上面.......
我移植到AVR时候写入不对
改过的程序,请去看哪个AVR的帖子

使用特权

评论回复
沙发
laocuo1142|  楼主 | 2022-8-10 09:19 | 只看该作者
文件IIC_driver.c






#include "I2C_Driver.h"
         
extern void Systick_Delay_1ms(u32 nCount);




void I2C_GPIO_Config(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;
        / Configure I2C1 pins: SCL and SDA /
        GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  
        GPIO_Init(GPIOB, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_7;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
        GPIO_Init(GPIOB, &GPIO_InitStructure);
}




void I2C_delay(void)
{       
        u8 i=100; //这里可以优化速度,经测试最低到5还能写入
        while(i)
        {
                i--;
        }
}


bool I2C_Start(void)
{
        SDA_H;
        SCL_H;
        I2C_delay();
        if(!SDA_read)return FALSE;        //SDA线为低电平则总线忙,退出
        SDA_L;
        I2C_delay();
        if(SDA_read) return FALSE;        //SDA线为高电平则总线出错,退出
        SDA_L;
        I2C_delay();
        return TRUE;
}


void I2C_Stop(void)
{
        SCL_L;
        I2C_delay();
        SDA_L;
        I2C_delay();
        SCL_H;
        I2C_delay();
        SDA_H;
        I2C_delay();
}


void I2C_Ack(void)
{       
        SCL_L;
        I2C_delay();
        SDA_L;
        I2C_delay();
        SCL_H;
        I2C_delay();
        SCL_L;
        I2C_delay();
}


void I2C_NoAck(void)
{       
        SCL_L;
        I2C_delay();
        SDA_H;
        I2C_delay();
        SCL_H;
        I2C_delay();
        SCL_L;
        I2C_delay();
}


bool I2C_WaitAck(void)          //返回为:=1有ACK,=0无ACK
{
        SCL_L;
        I2C_delay();
        SDA_H;                       
        I2C_delay();
        SCL_H;
        I2C_delay();
        if(SDA_read)
        {
                SCL_L;
                return FALSE;
        }
        SCL_L;
        return TRUE;
}


void I2C_SendByte(u8 SendByte) //数据从高位到低位//
{
    u8 i=8;
    while(i--)
    {
        SCL_L;
        I2C_delay();
                if(SendByte&0x80)
                        SDA_H;  
                else
                        SDA_L;   
        SendByte<<=1;
        I2C_delay();
                SCL_H;
        I2C_delay();
    }
    SCL_L;
}


u8 I2C_ReceiveByte(void)  //数据从高位到低位//
{
    u8 i=8;
    u8 ReceiveByte=0;
       
    SDA_H;                               
    while(i--)
    {
                ReceiveByte<<=1;      
                SCL_L;
                I2C_delay();
                SCL_H;
                I2C_delay();       
                if(SDA_read)
                {
                        ReceiveByte|=0x01;
                }
    }
    SCL_L;
    return ReceiveByte;
}


//写入1字节数据       待写入数据    待写入地址       器件类型(24c16或SD2403)
bool I2C_WriteByte(u8 SendByte, u16 WriteAddress, u8 DeviceAddress)
{               
    if(!I2C_Start())return FALSE;
    I2C_SendByte(((WriteAddress & 0x0700) >>7) | DeviceAddress & 0xFFFE);//设置高起始地址+器件地址
    if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
    I2C_SendByte((u8)(WriteAddress & 0x00FF));   //设置低起始地址      
    I2C_WaitAck();       
    I2C_SendByte(SendByte);
    I2C_WaitAck();   
    I2C_Stop();
        //注意:因为这里要等待EEPROM写完,可以采用查询或延时方式(10ms)
    //Systick_Delay_1ms(10);
    return TRUE;
}


//注意不能跨页写
//写入1串数据      待写入数组地址    待写入长度      待写入地址       器件类型(24c16或SD2403)
bool I2C_BufferWrite(u8* pBuffer, u8 length,     u16 WriteAddress, u8 DeviceAddress)
{
    if(!I2C_Start())return FALSE;
    I2C_SendByte(((WriteAddress & 0x0700) >>7) | DeviceAddress & 0xFFFE);//设置高起始地址+器件地址
    if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
    I2C_SendByte((u8)(WriteAddress & 0x00FF));   //设置低起始地址      
        I2C_WaitAck();       
       
        while(length--)
        {
                I2C_SendByte(* pBuffer);
                I2C_WaitAck();
                pBuffer++;
        }
        I2C_Stop();
        //注意:因为这里要等待EEPROM写完,可以采用查询或延时方式(10ms)
        //Systick_Delay_1ms(10);
        return TRUE;
}




//跨页写入1串数据  待写入数组地址    待写入长度      待写入地址       器件类型(24c16或SD2403)
void I2C_PageWrite(  u8* pBuffer, u8 length,     u16 WriteAddress, u8 DeviceAddress)
{
    u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
    Addr  = WriteAddress % I2C_PageSize;      //写入地址是开始页的第几位
    count = I2C_PageSize - Addr;                                            //在开始页要写入的个数
    NumOfPage   =  length / I2C_PageSize;     //要写入的页数
    NumOfSingle =  length % I2C_PageSize;     //不足一页的个数
       
    if(Addr == 0)         //写入地址是页的开始
    {
                if(NumOfPage == 0)  //数据小于一页
                {
                        I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress);   //写少于一页的数据
                }
                else                            //数据大于等于一页  
                {
                        while(NumOfPage)//要写入的页数
                        {
                                I2C_BufferWrite(pBuffer,I2C_PageSize,WriteAddress,DeviceAddress);//写一页的数据
                                WriteAddress +=  I2C_PageSize;
                                pBuffer      +=  I2C_PageSize;
                                NumOfPage--;
                                Systick_Delay_1ms(10);
                        }
                        if(NumOfSingle!=0)//剩余数据小于一页
                        {
                                I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress); //写少于一页的数据
                                Systick_Delay_1ms(10);
                        }
                }
    }
       
    else                  //写入地址不是页的开始
    {
                if(NumOfPage== 0)   //数据小于一页
                {
                        I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress);   //写少于一页的数据
                }
                else                //数据大于等于一页
                {
                        length       -= count;
                        NumOfPage     = length / I2C_PageSize;  //重新计算要写入的页数
                        NumOfSingle   = length % I2C_PageSize;  //重新计算不足一页的个数       
                       
                        if(count != 0)
                        {  
                                I2C_BufferWrite(pBuffer,count,WriteAddress,DeviceAddress);      //将开始的空间写满一页
                                WriteAddress += count;
                                pBuffer      += count;
                        }
                       
                        while(NumOfPage--)  //要写入的页数
                        {
                                I2C_BufferWrite(pBuffer,I2C_PageSize,WriteAddress,DeviceAddress);//写一页的数据
                                WriteAddress +=  I2C_PageSize;
                                pBuffer      +=  I2C_PageSize;
                        }
                        if(NumOfSingle != 0)//剩余数据小于一页
                        {
                                I2C_BufferWrite(pBuffer,NumOfSingle,WriteAddress,DeviceAddress); //写少于一页的数据
                        }
                }
    }
}


//读出1串数据         存放读出数据  待读出长度      待读出地址       器件类型(24c16或SD2403)       
bool I2C_ReadByte(u8* pBuffer,   u8 length,     u16 ReadAddress,  u8 DeviceAddress)
{               
    if(!I2C_Start())return FALSE;
    I2C_SendByte(((ReadAddress & 0x0700) >>7) | DeviceAddress & 0xFFFE);//设置高起始地址+器件地址
    if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
    I2C_SendByte((u8)(ReadAddress & 0x00FF));   //设置低起始地址      
    I2C_WaitAck();
    I2C_Start();
    I2C_SendByte(((ReadAddress & 0x0700) >>7) | DeviceAddress | 0x0001);
    I2C_WaitAck();
    while(length)
    {
                *pBuffer = I2C_ReceiveByte();
                if(length == 1)I2C_NoAck();
                else I2C_Ack();
                pBuffer++;
                length--;
    }
    I2C_Stop();
    return TRUE;
}




++++++++++++++++++++++++++++++++++++++++
文件IIC_driver.h
++++++++++++++++++++++++++++++++++++++++




#ifndef __I2C_Driver_H
#define __I2C_Driver_H


/ Includes ------------------------------------------------------------------/
#include "stm32f10x_lib.h"


#define SCL_H         GPIOB->BSRR = GPIO_Pin_6
#define SCL_L         GPIOB->BRR  = GPIO_Pin_6
   
#define SDA_H         GPIOB->BSRR = GPIO_Pin_7
#define SDA_L         GPIOB->BRR  = GPIO_Pin_7


#define SCL_read      GPIOB->IDR  & GPIO_Pin_6
#define SDA_read      GPIOB->IDR  & GPIO_Pin_7


#define I2C_PageSize  8  //24C02每页8字节


void I2C_GPIO_Config(void);
bool I2C_WriteByte(u8 SendByte, u16 WriteAddress, u8 DeviceAddress);
bool I2C_BufferWrite(u8* pBuffer, u8 length, u16 WriteAddress, u8 DeviceAddress);
void I2C_PageWrite(u8* pBuffer, u8 length, u16 WriteAddress, u8 DeviceAddress);
bool I2C_ReadByte(u8* pBuffer, u8 length, u16 ReadAddress, u8 DeviceAddress);


#endif

使用特权

评论回复
板凳
两只袜子| | 2022-8-10 15:00 | 只看该作者
配置好,硬件I2C还是好用的,低功耗时候必须用DMA,降低功耗,现在CUBEMX配置IIC更是好用了,究其更本还是不熟悉STM32才会用不好I2C

使用特权

评论回复
地板
jcky001| | 2022-8-11 09:00 | 只看该作者
我生成的库没有BRR寄存器,这个是要自己加的吗

使用特权

评论回复
5
cr315| | 2022-8-12 09:00 | 只看该作者
如果裸机跑的话用硬件还行,但是上操作系统就不行了!所以我都是使用模拟了!

使用特权

评论回复
6
tpgf| | 2022-9-5 15:38 | 只看该作者
因为感觉比本身自带的模块运行起来稳定

使用特权

评论回复
7
qcliu| | 2022-9-5 16:10 | 只看该作者
主要我感觉这样做比较灵活

使用特权

评论回复
8
drer| | 2022-9-5 16:18 | 只看该作者
只是因为硬件模拟的iic好用

使用特权

评论回复
9
coshi| | 2022-9-5 16:26 | 只看该作者
这个库还需要自己生成吗

使用特权

评论回复
10
kxsi| | 2022-9-5 16:33 | 只看该作者
为什么stm32的硬件i2c时序不能被中断呢

使用特权

评论回复
11
wiba| | 2022-9-5 16:42 | 只看该作者
能从根本上解决这个问题吗

使用特权

评论回复
12
Stahan| | 2022-9-11 22:13 | 只看该作者
硬件好用的话也不会用软件模拟了

使用特权

评论回复
13
asmine| | 2022-9-12 09:13 | 只看该作者
就是移植方便

使用特权

评论回复
14
LLGTR| | 2022-9-13 14:16 | 只看该作者
移植方便,换个单片机只要改一下引脚的拉高拉低和输入输出就好了。

使用特权

评论回复
15
Bowclad| | 2022-9-13 19:46 | 只看该作者
方便移植

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1095

主题

4660

帖子

11

粉丝