知道为什么大部分人喜欢用IO口模拟IIC吗?知道怎么去模拟吗?这里有你要的答案

也许你会说,我直接用硬件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的帖子


文件IIC_driver.c



#include "I2C_Driver.h"

extern void Systick_Delay_1ms(u32 nCount);


void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/[i] Configure I2C1 pins: SCL and SDA [/i]/
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

/[i] Includes ------------------------------------------------------------------[/i]/
#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



本帖程序已经上传到本站网盘,文件名: IIC模拟程序(驱动部分).zip
已邀请:

admin

赞同来自:

有兴趣的可以移植到stm32cube的HAL驱动,同时分享一下,小子在这里先谢啦,到时候移植好发到邮箱:
AD@stm32cube.com

天涯羁旅

赞同来自:

SCL_READ 和SDA_READ没用到
 

____________________

赞同来自: ujewm

配置好,硬件I2C还是好用的,低功耗时候必须用DMA,降低功耗,现在CUBEMX配置IIC更是好用了,究其更本还是不熟悉STM32才会用不好I2C

yunqingabc - 80

赞同来自:

将IIC讲的很透彻了!

大博哥 - 90后,嵌入式软硬件

赞同来自:

 楼主你好,我现在用cubemx生成的hal_i2c_mem_read函数,发设备地址0x6b,寄存器地址0x01。用示波器发现,发设备地址的时候函数把最低位置0了,也就是改为了写的逻辑电平,我把他改回来发出0x6b的波形后,无法产生0x01的波形,请问你产生的波形是什么样子,谢谢!我的邮箱是768839134@qq.com,方便的话是否可以分享一下你成功的代码 


我使用的英飞凌的A2B6芯片,要读取他的指定寄存器需要发送如下波形,我用STM32的硬件I2C发不出这个波形,因为发送设备地址的时候的第八位发送的1,也就是read command,导致无法将register address发送出去,请问你用STM32的I2C能发出相同的波形吗,谢谢。

QQ图片20190116215051.jpg

ujewm - 90后理工

赞同来自:

我的话是把STM32设置成Slave, 用一个Arduino Due当做主机来控制I2C收发信息,但感觉对ST的这个配置理解的不够透彻,还在踩坑中,有熟悉STM32 I2C配置及使用的朋友,求指导一下

molly

赞同来自:

你好我生成的库没有BRR寄存器,这个是要自己加的吗

要回复问题请先登录注册