专家
公告
财富商城
电子网
旗下网站
首页
问题库
专栏
标签库
话题
专家
NEW
门户
发布
提问题
发文章
DSP
学习:GPIO口模拟I2C
2019-07-13 17:46
发布
生成海报
站内文章
/
DSP
12785
0
1741
什么是 GPIO口模拟I2C?
就是用一个GPIO pin(CLK pin)的高低电位切换来模拟出始终的high/low,用另外一根GPIO pin(DATA pin)的高低电位,传递数据。
IIC本来是一个硬件模块之间的通信协议,一般芯片都有专门的电路逻辑块来处理协议,并通过两根线路(时钟SCk、数据SDA)来跟其余同样有IIC模块的器件通信。
由于其通信速率(400K)和通信方式(串行)跟其他串并口通信方式不同,所以一般都用于主机跟从机传递控制参数、参考数据、或者少量的其他数据。
IIC协议主要在乎的时序的准确性,所以在没有专门的IIC模块的单片机中,你也可以用两个IO口来模拟输出IIC协议的波形,这个波形不管是IIC硬件模块生成的,还是软件模拟出来的,只要符合IIC协议标准的时序和电平,都是一样的,接受端也不会挑剔。就像你要喝水,主要是喝 H2O,它究竟是从山涧里面流出来的泉水,还是工厂里面处理的蒸馏水,你可能并不在意,只要符合饮用标准就行。
还有一些系统中,CPU原有的IIC总线可能用于控制几个比较重要的几个芯片,为了避免争用、或者为了硬件上相互影响,对于不太重要的芯片,有时候会再用几个GPIO脚来做模拟IIC跟它通信。软件实现非常简单,就是根据IIC协议,先拉高SCK、SDA线,然后延时Nms后,将SDA拉低,然后再延时Nms,一个IIC Start工作就完成了。传输数据过程也差不多,但是根据不同的芯片,可能时序定义会有差异,所以要常常调整IIC时钟速率,调整各个延时时长,提高驱动电流等。
另外gpio口模拟IIC是占系统CPU资源的,而硬件IIC不占cpu资源!所以在有OS的应用中,如果用gpio模拟IIC的话,一定要进入临界区!
怎么实现?
ARM编程:ARM普通GPIO口线模拟I2C
请教个问题:
因为需要很多EEPROM进行点对点控制,所以我现在要用ARM的GPIO模拟I2C,管脚方向我设
置的是向外的。我用网上的RW24C08的万能程序修改了一下,先进行两根线的模拟,SDA6,
SCL6,但是读出来的数不对。我做了一个简单的实验,模拟SDA6,SCL6输出方波,在示波
器上看到正确方波,也就是说,我的输出控制是没问题的。
哪位大哥能指点一下,是否在接收时管脚方向要设为向内?(不过IOPIN不管什么方向都可
以读出当前状态值的阿)
附修改的RW24C08()程序:
#define
SomeNOP() delay(300);
/**/
/*
********************************* RW24C08
****************************************
*/
/**/
/*
-----------------------------------------------------------------------------
---
调用方式:void I2CInit(void)
函数说明:私有函数,I2C专用
-------------------------------------------------------------------------------
--
*/
void
I2CInit(
void
)
...
{
IO0CLR
=
SCL6;
//
初始状态关闭总线
SomeNOP();
//
延时
I2CStop();
//
确保初始化,此时数据线是高电平
}
/**/
/*
----------------------------------------------------------------------------
----
调用方式:void I2CStart(void)
函数说明:私有函数,I2C专用
-------------------------------------------------------------------------------
--
*/
void
I2CStart(
void
)
...
{
SomeNOP();
IO0SET
=
SCL6;
SomeNOP();
//
INI
IO2CLR
=
SDA6;
SomeNOP();
//
START
IO0CLR
=
SCL6;
SomeNOP();
}
/**/
/*
-----------------------------------------------------------------------------
---
调用方式:void I2CStop(void)
函数说明:私有函数,I2C专用
------------------------------------------------------------------------------
---
*/
void
I2CStop(
void
)
...
{
IO2CLR
=
SDA6;
SomeNOP();
//
INI
IO0SET
=
SCL6;
SomeNOP();
IO2SET
=
SDA6;
SomeNOP();
IO0CLR
=
SCL6;
}
/**/
/*
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
I2CClock: 发送总线时钟信号,并返回时钟电平为高期间SDA上的状态,低为 ACK,高失败
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
INT8U I2CClock(
void
)
...
{
INT8U sample;
SomeNOP();
IO0SET
=
SCL6;
delay(
180
);
sample
=
(INT8U)((IO2PIN
&
0x2000
)
>>
13
);
delay(
120
);
IO0CLR
=
SCL6;
SomeNOP();
//
return (sample);
if
(sample
==
1
)
return
(
1
);
else
return
(
0
);
}
/**/
/*
-----------------------------------------------------------------------------
---
调用方式:void SendAck(void)
函数说明:私有函数,I2C专用,主器件为接收方,从器件为发送方时,应答信号。
------------------------------------------------------------------------------
---
*/
void
SendAck(
void
)
...
{
IO2CLR
=
SDA6;
I2CClock();
IO2SET
=
SDA6;
}
/**/
/*
----------------------------------------------------------------------------
----
调用方式:void SendAck(void)
函数说明:私有函数,I2C专用,主器件为接收方,从器件为发送方时,非应答信号。
**-----------------------------------------------------------------------------
---
*/
void
SendNotAck(
void
)
...
{
IO2SET
=
SDA6;
I2CClock();
}
/**/
/*
----------------------------------------------------------------------------
----
调用方式:void I2CSend(uchar ch)
函数说明:私有函数,I2C专用
------------------------------------------------------------------------------
---
*/
INT8U I2CSendByte(INT8U ch)
...
{
register INT8U i;
INT8U statue;
for
(i
=
0
; i
<
8
; i
++
)
...
{
statue
=
(INT8U) ((ch
&
0x80
)
>>
7
);
if
(statue
==
1
)
IO2SET
=
SDA6;
else
IO2CLR
=
SDA6;
ch
<<=
1
;
I2CClock();
}
IO2SET
=
SDA6;
return
(
~
I2CClock());
}
/**/
/*
----------------------------------------------------------------------------
----
调用方式:uchar I2CReceive(void)
函数说明:私有函数,I2C专用
------------------------------------------------------------------------------
---
*/
INT8U I2CReceiveByte(
void
)
...
{
register INT8U i;
INT8U ddata
=
0
;
for
(i
=
0
;i
<
8
;i
++
)
...
{
ddata
<<=
1
;
if
(I2CClock()) ddata
++
;
}
return
(ddata);
}
/**/
/*
------------------------------------------------
RW2408 Function
------------------------------------------------
*/
//
DataBuff 为读写数据输入/输出缓冲区的首址
//
Length 为要读写数据的字节数量
//
Address 为EEPROM的片内地址
//
ControlByte 为EEPROM的控制字节,具体形式为(1)(0)(1)(0)(A2)(A1)(A0)(R/W),
//
其中R/W=1,表示读操作,R/W=0为写操作,A2,A1,A0为EEPROM的页选或片选
地址;
void
RW2408(INT8U
*
DataBuff,INT8U Length,INT32U Addr,INT8U Wr)
...
{
INT8U j;
//
发送字节索引
//
EA=0;
//
发送期间禁止中断,防止
干扰
I2CInit();
I2CStart();
//
启动总线
I2CSendByte(
0xa0
);
//
向IIC总线发送2408写地址
I2CSendByte(Addr);
//
向IIC总线发送要操作的RAM
地址
if
(Wr
==
0
)
//
如果是写操作
...
{
for
(j
=
0
;j
<
Length;j
++
)
...
{
I2CSendByte(
*
DataBuff
++
);
//
每个循环送1个数据
}
}
/**/
/*
---------------------------如果是读操作 ----------------------
*/
else
if
(Wr
==
1
)
...
{
I2CStart();
//
启动总线
I2CSendByte(
0xa1
);
//
向IIC总线发送2408读地址
for
(j
=
0
;j
<
Length
-
1
;j
++
)
...
{
IO2SET
=
SDA6;
//
写之前把数
据线置高
*
DataBuff
++=
I2CReceiveByte();
//
每次读1个字节
SendAck();
//
发送应答信号
}
*
DataBuff
=
I2CReceiveByte();
//
读最后一个数据
SendNotAck();
//
发送非应答信号
}
I2CStop();
//
停止IIC总线
//
EA=1;
}
//
*************回答*************
你一定要改变GPIO的方向,ARM我还不太熟,不过我用过类似的MCU,要不读到的不是GPIO的外部电平,好像是别外一个REGISTER的东西,你好好看看吧,你在
读写的时候改变GPIO的方向就应该没有错了
发送时SDA设为输出,接收时将SDA设为输入
我也是碰到了这个情况啊,也是不能读写数据啊,我也是用8位机的修改的
那意思就是我sendbyte的时候GPIO是向外,receivebyte的时候GPIO是向内,也就是我要写
两个I2CCLOCK函数,一个用在发送,一个用在接收,分别对应不同的GPIO方向设置?
读IOPIN的时候是不管方向的,这是书上这么写的
没有错啊,可是你的SDA是用什么定义的 如果是用IO0PIN吗,还是用IO0SET或是IO0CLR?后面的两个只能反应你上次对这两个寄存器操作的值,不能反应其IO口的状态的,只能有用IO0PIN吗,那么好象不用定义方向,请指教!谢谢
----------------------------------------------
转自:
http://www.c51bbs.com/c51bbs/topic/c51bbs781049.htm
时序问题参考:
http://www.seeddsp.com/service/bbs/viewthread.php?tid=8481
Ta的文章
更多
>>
学习:GPIO口模拟I2C
0 个评论
阿里电面的题目
0 个评论
STM32 usb_pwr.c文件分析
0 个评论
热门文章
×
关闭
举报内容
检举类型
检举内容
检举用户
检举原因
广告推广
恶意灌水
回答内容与提问无关
抄袭答案
其他
检举说明(必填)
提交
关闭
×
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮