EDN首页   博客首页 用户登陆  |  注册
aaa
发表于 2008/8/31 10:03:16

2

关于投票

单片机模拟I2C从机的构思

在论坛里看到有人想用单片机模拟I2C从机。

原帖:http://bbs.ednchina.com/ShowTopic.aspx?id=68892

#1 楼主:有关I2C的问题,请求帮助。 精华帖

贴子发表于:2008-8-25 18:06:20

我最近用单片机模拟I2C做slave,现在做出来了,但是误码率很高,检查了一下原因,是地址经常读错,还有的是地址读到之后没有给主机应答。我检测时钟信号是用while语句写的,下面是我写的接收一个字节数据的程序:

uchar RcvB()
{

。。。。。。
}

首先很少人有这个想法,但模拟主机很普遍。

模拟I2C主机是可行的,因为时钟SCL由主机提供,MCU很容易掌控。

但是模拟I2C从机的难度要大很多。

首先SCL由主机提供,有效地捕捉和跟随它要看主机的速率而定。

所以选择模拟从机应该是个错误,但又带来了可行性的讨论。

一般IO模拟有2种方法:中断或查询。

由于中断有着固定的响应时间,再加上到中断的有效处理处的时间,可能信号早已翻转,此时有不如简单的IO查询了。

所以要根据主机速率来选择IO模拟的方式。

真正满足I2C协议的主机有个很重要的特性:

允许从机拉低时钟降速。

这就告诉我们模拟从机是可行的,虽然并不合理。

考虑到中断嵌套和中断响应时间等问题,我认为查询比中断会好些。(当主机速率低是中断是很好的选择)

但这又带来了CPU占有率的问题。

因为while(SCL)或while(!SCL)会使其他任务阻塞,故裸奔时其他程序

应该由定时中断及其他中断激活,OS时由高优先级任务激活。

这样做问题又来了---其他任务打断了I2C协议的次序。

所以程序应该采用协作式,即在主机SCL=0时:

首先从机拉低SCL=0!!!

若需从机发送数据,此时从机可以发送SDA.

然后开中断在其他任务中断时把控制权让出。

当从机程序重新获取控制权后,再关中断后释放SCL=1.

再用while(!SCL)捕捉SCL的上升沿后来读取SDA数据。

总之模拟从机很困难,成功的前提时:

必须捕捉到SCL=0而不丢失一次。

要做到这个可以选择外部中断为最高级中断。但中断响应时间

必须小于SCL=0的脉宽,否则只能采用查询方法。

系统分类: 嵌入式  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(1440) | 回复(2)

发表于 2008/7/14 1:51:00

1

关于投票

菜农三剑客之HotBus裸照(I2C Toolkit)

点击开大图

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(781) | 回复(0)

发表于 2007/6/29 7:31:44

0

关于投票

关于串口直接与I2C通讯讨论的结果

祥见:http://bbs.21ic.com/club/bbs/ShowAnnounce.asp?id=2581604

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(2126) | 回复(0)

发表于 2007/6/27 21:48:13

1

关于投票

这个电路该如何编程为好???确实有些不解

网友TestCode给了个老外的串口直接与I2C通讯的电路图,如下:

点击看大图

其中:

DTR控制串口窃电.

TX作为I2C的时钟信号SCL.

RTS作为PC侧SDA的数据输出.

DSR作为PC侧SDA的数据输入.

我有些愚笨,有些不明白...主要是TX的问题.

如果用Output()向串口发数据,实际是给I2C发时钟脉冲.

如发字符"U"(0x55),即在8位无校验1个停止位时,刚好是个方波信号.

即:S10101010P.其中S为起始位'0',P为停止为'1'.

也就是0101010101.即5个I2C的时钟周期.

但是在常规的编程时,TX和RX信号都是测试不了的,他们只能用函数

Output()和Input()函数操作.

这样虽然可以利用数据来发送I2C的时钟信号,那么怎样和在什么时候

发送或接收SDA上的数据呢???

难道在线程中根据给定的波特率即时间来捕捉TX信号上的跳变???

想了一天都没能想明白~~~脑子确实进水了...可是怎么也想不出来.

没办法,在TestCode给出图的瞬间,总觉得这样可能更合理些.

点击看大图

我的想法如下:

1.最关键的是如何测试TX的边沿.

我连了2个信号,CTS(可测试上升或下降的跳变)和RI(只能测出下降的跳变).

2.对TX的捕捉能力即时间是否跟上???

为此在不想搭上面的电路的情况下,做了下面电路的实验:

"自环"程序大概是这样的:

用Output("UU");发送I2C需要的10个SCL时钟信号.

在首个"U"的起始位"0"发出时,这是在CTS或RI上的RS232电平为10V左右.

CTS将会捕捉到comEvCTS事件.(但不能捕捉到comEvRing事件,画出它想在正规的电路上用)

在comEvCTS事件中,由于CTS为"1",所以这时控制RTS="1",导致RX线上为RS232电平10V左右

即RX收到RTS间接发来的起始位.依此类推,发完10个SCL时钟信号.

这时用Input()函数确实也收到了"数据"---"UU".即完成了软件的自环链接.

3.如何直接在RX上收到I2C的数据???

虽然发送"UU"可以得到很高的速率,但内部其中包含了2个停止位.

在真正的I2C数据通讯时,在SCL即TX的低电平(经过非门电路)是发送方放入数据的.

在SCL的高电平不允许SDA信号改变是告诉你接收方要取出数据的.

那么在硬件RS232电路内部是不会在那2个停止位上采样数据的,故"UU"组合不成立.

即2分频是不成立的.

故可采用:

8位无校验1个停止位的0x92,即

S0 1 00 1 00 1P S0 1 00 1 00 1P S0 1 00 1 00 1P

即发送3个0x92来产生9个I2C时钟(8位数据+ACK)

这样,起始位可在发送前拉低SDA简单的得到,

停止位和重复位可随便做到,如0x0f等.

也可用6位无校验1个停止位的0x26+0x3E,即

S0 11 00 1P S0 11 00 1P S0 11 00 1P S0 11 00 1P S0 11 00 1P...

这样写确实晕,不过0x26为字符"&",0x3e为字符">"

I2C时钟信号为"&&&&>"表示写7位地址+R/W+ACK的时钟序列.

若地址后带命令的停止位的完整的I2C时钟序列符为:

&&&&>&&&&&

从以上2个例子可以看出,要想用RX硬件读出数据,时钟序列中

都有"1P",即把停止位藏在一个高电平时钟后,这样就可以

"在停止位上从RX上采样到的数据"了~~~晕!!!

4.求助帮忙想想老外是如何"思想"的???

谢谢各位!!!

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(1088) | 回复(2)

发表于 2007/6/18 13:49:54

0

关于投票

菜农用LabWindows写的I2C程序,望白沙烟酒同志笑纳

HotPower 发表于 2007-6-18 13:29 侃单片机 ←返回版面 按此察看该网友的资料 按此把文章加入收藏夹 按此编辑本帖

楼主: 菜农用LabWindows写的I2C程序,望白沙烟酒同志笑纳

HRESULT CVIFUNC I2C_Start(void)
{
    HRESULT __result = S_FALSE;
    int i;
    unsigned char ack;
    for (i = 0; i < I2C_waitStartDelay;i ++) {
        ack = I2C_GetSDA();//释放数据总线读数据总线
        I2C_SetSCL();//首先拉高SCL, 保证SDA在SCL=1时跳变,拉高时钟(必须在SDA=1时)
        I2C_ClrSDA();//首先拉低SDA.必须按次序!!!
        I2C_ClrSCL();//然后拉低SCL,构成I2C从机内部中断并唤醒
        if (ack) {//测试成功!!!总线已释放
            __result = S_OK;//
            break;
        }
    }
    if (__result == S_OK)
    {
        Hot_I2CErrorState = I2C_START;//已发送起始条件
    }
    else
    {
        Hot_I2CErrorState = -1;//未发送起始条件
        I2C_Stop ();
    }
    return __result;
}

HRESULT CVIFUNC I2C_ReStart(unsigned char addrss)
{
    HRESULT __result;
//入口:SDA=SCL=0
    I2C_SetSDA();//首先拉高SDA, 这样才能保证在SCL=1时SDA下跳构成再次复位条件即复位
    I2C_SetSCL();//然后拉高SCL, 保证SDA在SCL=1时跳变,拉高时钟(必须在SDA=1时)
    I2C_ClrSDA();//首先拉低SDA.必须按次序!!!
    I2C_ClrSCL();//然后拉低SCL,构成I2C从机内部中断并唤醒
    I2C_ReadWriteByte (addrss | 1);//强行发送I2C读地址,SDA=SCL=0
    __result =I2C_GetAck();//取从机ACK信号
    if (__result == S_OK)
    {
        Hot_I2CErrorState = I2C_MT_SLA_ACK;//已发送SLA+W,且已接收ACK
    }
    else
    {
        Hot_I2CErrorState = I2C_MT_SLA_NACK;//已发送SLA+W,且未接收ACK
        I2C_Stop ();
    }
    return __result;
}

void CVIFUNC I2C_Stop (void)
{
    I2C_ClrSCL();
    I2C_ClrSDA();
      I2C_SetSCL();//在SCL为高时准备Stop信号,拉高时钟(必须在SDA=1时,故本例形成Stop())
      I2C_SetSDA();//STOP信号结束
}

void CVIFUNC I2C_WriteAck(unsigned char ack)
{
    if (ack) {
        I2C_ClrSDA();//给从机ACK信号
    }
    else
    {
        I2C_SetSDA();//给从机nACK信号
    }
    I2C_SetSCL();//拉高时钟信号,拉高时钟(必须在SDA=1时)
    I2C_ClrSCL();//拉低时钟信号,防止START信号产生
    I2C_ClrSDA();//给从机ACK信号
}


HRESULT CVIFUNC I2C_GetAck(void)
{
    HRESULT __result = S_FALSE;
    int i;
    for (i = 0; i < I2C_waitAckDelay; i ++)
    {
        if (I2C_GetSDA() == 0) //释放数据总线读数据总线
        {
            __result = S_OK;
            break;
        }
    }
    I2C_SetSCL();//拉高时钟(必须在SDA=1时)???
    I2C_ClrSCL();//拉低时钟
    I2C_ClrSDA();//保证SDA=SCL=0///pwy(拉低SDA便于发送停止喜欢)
    return __result;//SDA=1,SCL=0
}

/*-----------------------------------------------------------
    I2C一体化读写程序
-------------------------------------------------------------*/
unsigned char CVIFUNC I2C_ReadWriteByte(unsigned char value)
{
    int i;
    for (i = 0; i < 8; i ++)//每个字节8位
    {
        if (value >= 0x80) //D7位=1
        {
             value <<= 1;//I2C协议先出D7位
            I2C_SetSDA();
            if (I2C_GetSDA())//GetSDA()内带释放数据总线SetSDA()
            {
                value ++;//读回1位I2C数据1
            }
        }
        else//D7=0
        {
             value <<= 1;//I2C协议先出D7位
            I2C_ClrSDA();//写入1位I2C数据0
        }
        I2C_SetSCL();//拉高I2C时钟
        I2C_ClrSCL();//拉低I2C时钟
    }
    I2C_ClrSDA();//保证出口SDA=SCL=0
    return value;//返回数据
}

unsigned char CVIFUNC I2C_ReadByte (unsigned char ack)
{
    unsigned char value;
    value = I2C_ReadWriteByte (0xff);//取数据
    I2C_WriteAck(ack);//向从机发送ACK/nACK信号  
    return value;//返回接收数据
}

unsigned short CVIFUNC I2C_ReadWord (unsigned char ack)
{
    unsigned short value;
    unsigned char ch, cl;
    cl = I2C_ReadByte (I2C_ACK);
    if (ack) ch = I2C_ReadByte (I2C_ACK);
    else ch = I2C_ReadByte (I2C_nACK);
    value = (ch << 8) | cl;
    return value;
}

void CVIFUNC I2C_ReadBlock (unsigned char *str, int size)
{
    int i;
    for (i = 0; i < size; i ++)
    {
        if (i == size - 1)
        {
            str[i] = I2C_ReadByte(I2C_nACK);
        }
        else
        {
            str[i] = I2C_ReadByte(I2C_ACK);
        }
    }
}

HRESULT CVIFUNC I2C_WriteByte (unsigned char value)
{
    HRESULT __result = S_FALSE;
    I2C_ReadWriteByte (value);//写数据,SDA=SCL=0
    __result = I2C_GetAck();//取从机ACK信号
    if (__result == S_OK)
    {
        Hot_I2CErrorState = I2C_MT_DATA_ACK;//已发送I2DAT 中的数据字节,且已接收ACK
    }
    else
    {
        Hot_I2CErrorState = I2C_MT_DATA_NACK;//已发送I2DAT 中的数据字节,且未接收ACK
        I2C_Stop();
    }
    return __result;
}

HRESULT CVIFUNC I2C_WriteWord (unsigned short value)
{
    HRESULT __result = S_FALSE;
    unsigned char ch, cl;
    ch = value  >> 8;
    cl = value & 0xff;
    __result = I2C_WriteByte (cl);
    if (__result == S_OK)
    {
        __result = I2C_WriteByte (ch);
    }
    return __result;
}

HRESULT CVIFUNC I2C_WriteBlock (unsigned char *str, int size)
{
    HRESULT __result = S_FALSE;
    int i;
    for (i = 0; i < size; i ++)
    {
        __result = I2C_WriteByte(str[i]);
        if (__result != S_OK) break;
    }
    return __result;
}

附该程序效果图:
1.读出EEPROM数据文件
点击看大图

2.写入EEPROM数据文件
点击看大图

3.单/双字节EEPROM读出
点击看大图

4.单/双字节EEPROM写入
点击看大图

5.整片EEPROM读出
点击看大图

6.整片EEPROM写入
点击看大图

签名:

●█〓██▄▄▄▄▄▄ ●●●●●●→ ''''╭WWWW╮
▄▅██████▅▄▃▂ 灌水入坛传播非典 ( ●_●)
██████████████ '''',,,;,;,;'''/▇\''
◥⊙▲⊙▲⊙▲⊙▲⊙▲⊙▲◤ 东戳西顶一片天/MMMM\
点击遨游水上蔬菜批发市场


打造21IC晕汁晕味晕菜的BLOG

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(1224) | 回复(0)

发表于 2007/4/15 22:31:21

1

关于投票

求助2060的unsealEEPROM问题

http://www.dianyuan.com/bbs/d/52/156434.html

求助2060的unseal EEPROM问题在2060中Pack Configuration的第6位即SEAL位.
2060命令有0x062b SEAL Command,但我如何UNSEAL,即向SEAL位写0???

2060的SMBus总线和I2C总线是分离的,而且外部只能操作SMBus总线.
故发SMBus命令很方便,但我想改写某些特殊值时,必须访问I2C.
而且只能是SEAL=0时.
如果SEAL=1时如何操作SMBus命令使SEAL=0????

谢谢各位用过2060的朋友.我一直想不明白这个问题.

菜农在这里有礼了~~~衷心感谢!!!

谢谢!!!HotPower@126.com

系统分类: 自由话题  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(1038) | 回复(0)

发表于 2007/3/13 20:07:40

1

关于投票

LPCARM之I2C中断读写CAT1025C++程序祥解

/*--------------------------------------------------------------------------
    LPCARM之I2C中断读写CAT1025 C++程序祥解

本例程可以做为通用串行EEPROM读写或SMBus总线操作基础参考(祥见SMBus程序祥解)。

菜地公告:引用本文必须注明出处!!!
菜农HotPower 2007.3.11 于西安大雁塔菜地 http://HotPower.21ic.org/
---------------------------------------------------------------------------*/

点击下载I2C.RAR rar

点击看大图

系统分类: ARM  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 无分类  | 

点击查看原文

发表评论 阅读全文(2907) | 回复(4)

发表于 2007/1/12 0:37:00

0

关于投票

讨论一下24C01中数据出错(丢失)的问题吧(HOTPOWER请进!!)

http://bbs.21ic.com/club/bbs/ShowAnnounce.asp?v=&ID=2326529

讨论一下24C01中数据出错(丢失)的问题吧(HOTPOWER请进!!)
程序匠人 发表于 2006-11-8 23:38 侃单片机 ←返回版面 按此察看该网友的资料 按此把文章加入收藏夹 按此编辑本帖举报该贴

24C01读写,大多数情况下正常,但是在极偶然的时候,发生数据出错的情况。

由于出现的概率太小,很难再现故障,所以给查找原因分析问题带来困难。

虽然出错的概率很小,但仍然是不可接受的,所以请大家一起,讨论一下可能的原因。

匠人猜想过的几中可能原因:

1、上拉电阻是采用CPU内部上拉电阻,是否太大了?
2、快速频繁地断电/上电,是否会打乱读写的时序?并凑巧拼接出一个错误的读写命令?
3、中断的打搅?(可能性不大吧?)


HOTPOWER是此中忽悠高手了,快来忽悠一下吧。忽悠的深刻的话,匠人将赠送“尿童牌”开档裤子一条,呵呵!

菜农就为匠人忽悠几句吧~~~
hotpower 发表于 2006-11-9 18:33 侃单片机 ←返回版面 按此察看该网友的资料 按此把文章加入收藏夹 按此编辑本帖举报该贴

匠人猜想过的几中可能原因:

1、上拉电阻是采用CPU内部上拉电阻,是否太大了?
2、快速频繁地断电/上电,是否会打乱读写的时序?并凑巧拼接出一个错误的读写命令?
3、中断的打搅?(可能性不大吧?)

菜农认为1:
    最好再外加上拉电阻,5V时在4.7K左右,3.3V在3.3K左右.这样可加大驱动能力和加速边沿的翻转.
菜农认为2:
    这个是最关键的原因所在.
    首先应该提供电源的保护机制,即能及时知晓外部电源是否具备写入EEPROM数据的能量.
    如果有掉电中断,那么在系统完全失去能量前会有一端时间的.这个时间与系统的电容存储的电能等有关.在此时间里只能写入一部分的EEPROM数据,若写入的数据很小,可能全部写入成功.所以在外部EEPROM的环境下,最好加大电容甚至要加法拉电容,以便增大写入数据块的长度.
    在无掉电保护机制的环境下,可以采用迂回战术来弥补无保护机制的问题.主要应该做到以下考虑:
    1.上电后不应该立即对EEPROM进行写操作!
    2.在EEPROM中找个空闲位置,写入EEPROM正常标志,一般为0x55aa.
      这个标志有很多的意义:
      当读出不为0x55aa时,可认为是掉电或EEPROM为空白片或EEPROM为盗版片.我们可以采取任何的手段进行处理.
      在其他外围也不正常时,我们可认为是真掉电而进入休眠.否则,我们认为是EEPROM空白即可对其初始化.
      注意:必须对EEPROM初始化即写入全部的默认配置数据后,才能写入0x55aa标志!!!因为可能在没初始化后又再次掉电!!!
      这样做后才能保证EEPROM初始化数据的完整及可靠(可信).
    3.每次写入前最好先读出数据,若与写入数据相同就不要招惹EEPROM!!!
      这样在提高寿命的同时,也变相地把EEPROM当RAM使用了.
      最好在此再读0x55aa标志(可能老头很烦人),以变相地确定电源没掉电,至少能短期地保证后面数据写入的安全.
    4.写入数据后应该再次读出,若写入失败就应该再次重试3次以上,否则通知老板该EEPROM坏了.
    5.在写入前应该设置一个全局的写入成功标志为假.在写入和校验成功后,再设置其为真.
      如果在写入期间掉电,那么在启动程序时就应该处理其标志,这样可以"断点续写".
菜农认为3:
    此点并不是太重要,因为一般的I2C总线的SDA/SCL不会再被其他功能复用的,除非他是匠人~~~
    菜农主张必须在一处操作硬件,数据都应该放入缓冲区内处理,各模块无权操作I2C总线.必须由"专人负责"!!!
    这在菜农的设计中一直是这样坚持的,因为婆婆多了肯定坏事.


就忽悠到这里吧,菜农要喂肚子了~~~但认为最重要的是:
0x55aa,法拉电容,上拉电阻,数据校验,容错处理....

 

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(1575) | 回复(0)

发表于 2007/1/6 0:29:35

1

关于投票

I2C总线忽悠记

http://bbs.21ic.com/club/bbs/bbsview.asp?essenceID=7642

I2C总线忽悠记

hotpower 发表于 2006-3-16 1:57:46 侃单片机 ←返回版面 按此给该网友发送邮件 按此察看该网友的资料 按此把文章加入收藏夹

一般串行数据通讯都有时钟和数据之分,有异步和同步之别.
有单线,双线和三线等.

I2C肯定是2线的(不算地线).

I2C协议确实很科学,比3/4线的SPI要好,当然线多通讯速率相对就快了.

I2C的原则是:

在SCL=1(高电平)时,SDA千万别忽悠!!!

否则,SDA下跳则"判罚"为"起始信号S",SDA上跳则"判罚"为"停止信号P".

在SCL=0(低电平)时,SDA随便忽悠!!!(可别忽悠过火到SCL跳高)

每个字节后应该由对方回送一个应答信号ACK做为对方在线的标志.

非应答信号一般在所有字节的最后一个字节后.一般要由双方协议签定.

SCL必须由主机发送,否则天下大乱.

首字节是"片选信号",即7位从机地址加1位方向(读写)控制.

从机收到(听到)自己的地址才能发送应答信号(必须应答!!!)表示自己在线.

其他地址的从机不允许忽悠!!!(当然群呼可以忽悠但只能听不许说话)

读写是站在主机的立场上定义的.

"读"是主机接收从机数据,"写"是主机发送数据给从机.

重复位主要用于主机从发送模式到接收模式的转换"信号",由于只有2线,

所以收发转换肯定要比SPI复杂,因为SPI可用不同的边沿来收发数据,而I2C不行.

在硬件I2C模块,特别是MCU/ARM/DSP等每个阶段都会得到一个准确的状态码,

根据这个状态码可以很容易知道现在在什么状态和什么出错信息.

7位I2C总线可以挂接127个不同地址的I2C设备,0号"设备"作为群呼地址.

10位I2C总线可以挂接更多的10位I2C设备.

总之,只要掌握I2C的忽悠记,一般很容易掌控...

I2C总线忽悠记(全集)

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(1750) | 回复(1)

发表于 2006/12/28 22:48:00

1

关于投票

关于USI接口密文传送增加I2C模块拦截难度的问题

吓了我一跳~~~以为您无闯了红灯区~~~
hotpower 发表于 2006-11-10 01:18 AVR 单片机 ←返回版面 按此察看该网友的资料 按此把文章加入收藏夹 按此编辑本帖举报该贴

今天我又倒塌地优化了代码,加入了类似滚动密码~~~
方法实际很简陋,因为代码长度的原因使我不能自由发挥...
由于USI可以进行I2C操作,但地址不像I2C模块必须事先约定从机地址.
从机的USI也就有机会任意胡作非为,由主机随机地发送0x00~0xff的地址.
而且每次都不同.这样就使正规的I2C模块拦截密文数据流成为难题~~~
并且每次的读地址和写地址也不相同,这就使重复位后的I2C陷入迷茫之中~~~
http://bbs.21ic.com/club/bbs/ShowAnnounce.asp?v=&ID=2328286

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(1454) | 回复(0)

发表于 2006/12/28 22:44:50

0

关于投票

TWI一主多从实战程序片段

晕菜~~~搞个铁电玩玩...(是TWI???)
hotpower 发表于 2006-4-8 03:37 侃单片机 ←返回版面 按此察看该网友的资料 按此把文章加入收藏夹 按此编辑本帖举报该贴

若是AVR的TWI,要注意每次通信都要先
  DDRC &= ~((1 << SCL) | (1 << SDA));//SCL、SDA 引脚内部上拉电阻
  TWCR &= ~(1 << TWEN);
  PORTC |= (1 << SCL) | (1 << SDA);//SCL、SDA 引脚内部上拉电阻
再在TWStart()内TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN) | (1 << TWIE);

我反复做过实验...把我害苦了...助好友TestCode成功!!!

哈哈...以下程序在ouravr那里都没舍得发...(非常好用)

inline
void TwiObj::WorkExec(void)
{
static unsigned char Count = 0;
unsigned char worknum;
  DDRC &= ~((1 << SCL) | (1 << SDA));//SCL、SDA 引脚内部上拉电阻
  TWCR &= ~(1 << TWEN);
  PORTC |= (1 << SCL) | (1 << SDA);//SCL、SDA 引脚内部上拉电阻
  if (Busy) {//主机忙
    TWStop();
  }
  else {
    MainCount = 0;//发送0个数据
    SubComm = 0;//不允许发送数据
    SubCount = 4;//接收4个数据
    Count &= 0x0f;//16个为一轮
    if ((Count & 1) == 0) {//偶数为1通道
      worknum = 0;
      SubAddr = UsiSlaveAddrRd1;//从机地址
    }
    else {//奇数为2通道
      worknum = 1;
      SubAddr = UsiSlaveAddrRd2;//从机地址
    }
//..................






inline
void TwiObj::TWStart(void)
{
  Busy = true;
  Status = 0;//主机准备发送启始位
  Count = 0;//发送数据个数
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN) | (1 << TWIE);
}


inline
void TwiObj::TWREStart(void)
{
  Busy = true;
  Status = 0x55;//主机准备发送启始位
  Count = 0;//接收数据个数
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN) | (1 << TWIE);
}

inline
void TwiObj::TWStop(void)
{
  Busy = false;
//  Status = 0xaa;//pwy
  Count = 0;
  TWCR = (1 << TWINT) | (1 << TWSTO);//关闭TWIE//pwy
}

//inline(不敢加)
void TwiObj::Exec(void)
{
//  TW_Error = TW_STATUS;
//  if (!Busy) TWStop();//pwy
//  else {
  switch(TW_STATUS) {
    case TW_START://主机收到自己发送的开始信号
          if (Status == 0) {//本次中断应该接收TW_START信号//pwy
            TWDR = SubAddr & 0xfe;//发送子机地址(写)
            Status = 1;//Status下次主发为1,主收为2
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE);//发送出去
//::Uart.puts("TW_START");
//::Uart.putstr("Status=");
//::Uart.puthex(Status);
//::Uart.puts("");
          }
          else TWStop();
          break;
    case TW_REP_START://主机收到自己发送的重新开始信号
          if ((Status == 0x55) && (SubAddr & 0xfe)) {//本次中断应该接收TW_START信号//pwy
            TWDR = SubAddr;//发送子机地址(读)
            Status = 2;//Status下次主发为1,主收为2
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE);//发送出去
          }
          else TWStop();
          break;
    case TW_MT_SLA_ACK://主发机接收到从机的地址应答信号后发送命令
          if (Status == 1) {//本次中断应该接收TW_MT_SLA_ACK信号
            Status = 3;//Status下次应该收TW_MT_DATA_ACK
            TWDR = SubComm;//发送子机命令
/*-----------------------------------------------------------------------------
            以后可以复杂些
-----------------------------------------------------------------------------*/
            TxBuffer[0] = SubComm;//简单命令校验
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE);//发送出去
//::Uart.puts("TW_MT_SLA_ACK");
//::Uart.putstr("Status=");
//::Uart.puthex(Status);
//::Uart.puts("");
          }
          else  TWStop();
          break;
    case TW_MR_SLA_ACK://主收机接收到从机的地址应答信号
          if (SubCount && (Status == 2)) {//本次中断应该接收TW_MR_SLA_ACK信号
            Status = 4;//Status下次应该收TW_MR_DATA_ACK
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE) | (1 << TWEA);//主机转入接收状态
//::Uart.puts("TW_MT_SLA_ACK");
//::Uart.putstr("Status=");
//::Uart.puthex(Status);
//::Uart.puts("");
          }
          else TWStop();
          break;
    case TW_MT_DATA_ACK:
          if ((Count < MainCount) && (Status == 3) && ((SubAddr & 1) == 0)) {//本次中断应该接收TW_MT_DATA_ACK信号
            TWDR = TxBuffer[Count ++];//发送子机数据
            TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE);//发送出去
          }
          else
            if ((Count == MainCount) && (Status == 3) && (SubAddr & 1)) {//本次中断应该接收TW_MT_DATA_ACK信号
              TWREStart();//
            }
//            else if ((Count == MainCount) && (Status == 3) && ((SubAddr & 1) == 0)) {
//              TW_Error = 0xfe;//数据发送成功
//              TWStop();
//            }
            else TWStop();
          break;
//    case TW_MT_DATA_NACK://数据发送结束
//          if ((Count == MainCount) && (Status == 4)) {
//            TW_Error = 0xf0;//数据发送失败
//          }
//          TWStop();
//          break;
    case TW_MR_DATA_ACK:
          if ((Count < SubCount) && (Status == 4)) {
            RxBuffer[Count ++] = TWDR;//接收子机数据
            if (Count < SubCount)
              TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE) | (1 << TWEA);//主机转入接收状态
            else
              TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE);//主机转入接收状态
          }
          else TWStop();
          break;
    case TW_MR_DATA_NACK://数据接收结束
//::Uart.puts("TWI_GOOD");
          if ((Count == SubCount) && (Status == 4)) {
            if (((RxBuffer[0] ^ RxBuffer[2]) == 0xff) && ((RxBuffer[1] ^ RxBuffer[3]) == 0xff)) {
              if ((RxBuffer[0] & 0xf0) == (SubComm & 0xf0)){
                SetTwiDataBuff();
//Uart.puts("TWI_GOOD");
              }
            }
//::Uart.puts("TWI_GOOD");
//            TW_Error = 0xff;//数据接收成功
          }
//          TWStop();
//          break;
    default:
          TWStop();
//::Uart.puts("TWI_BAD");
//::Uart.putstr("TW_STATUS=");
//::Uart.puthex((unsigned int)TW_STATUS);
//::Uart.puts("");
          break;
    }
//  }
}
http://bbs.21ic.com/club/bbs/ShowAnnounce.asp?v=&ID=2046816

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(1300) | 回复(0)

发表于 2006/12/27 21:27:49

1

关于投票

菜农I2C从机锁死的处理方法

第1次看见所长给农校学生耐心"讲习"~~~实际我也遇到此事

hotpower 发表于 2006-12-27 21:04 侃单片机 ←返回版面   举报该贴 

哈哈,最近一直在论战什么"狗论",故一直没空关心此帖~~~

这个问题我实际遇到过,当时也很郁闷,发现主机时常被从机叼死...
后来编写了从机I2C程序后才明白了不少~~~(这对搞清I2C器件的内部状况很有帮助)


菜农I2C从机锁死的处理方法:

从机方:(USI实现的TWI即I2C)
  while (tmp = (PINB & (1 << SCL)));//等待SCL=0主机处理结束
  PORTB &= ~(1 << SCL);//保持低电平
  DDRB |= (1 << SCL);//占用SCL总线,以便长期处理
  switch(Status) {
.......
  }
  DDRB &= ~(1 << SCL);//释放SCL总线
  USISR |= (1 << USIOIF);//清除计数器溢出中断标志

这是因为从机要等待主机释放SCL为高电平后,再反向拉低SCL电平
以适应于不同的通讯速率,这不像异步通讯协议需要约定相同的通讯速率.
在从机处理完后,再释放SCL信号.
当然此时从机处理过程肯定要操作SDA总线的.
如果主机在这时读从机肯定会失败!!!

主机方:(硬件I2C,本例用的是AVR,也用ARM测试过)
//不过三七二十一,主机在每次发送时都先放弃I2C功能,再在启动时开启I2C功能
  DDRC &= ~((1 << SCL) | (1 << SDA));//SCL、SDA设置为输入方式
  TWCR &= ~(1 << TWEN);//放弃I2C功能!!!
  PORTC |= (1 << SCL) | (1 << SDA);//SCL、SDA 引脚内部上拉电阻
  if (Twi.Busy) {//I2C忙(因为用的是收发中断,有类FIFO)
    Twi.TWStop();//强行停止操作,本次操作失败,下次再运行
  }
  else {
.......................
    Twi.TWStart();//注意里面的TWCR = ...(1 << TWEN);//选择I2C功能
  }

..............

inline 
void TwiObj::TWStart(void)
{
  Busy = true;
  Status = 0;//主机准备发送启始位
  Count = 0;//发送数据个数
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN) | (1 << TWIE);
}


通过实战片段可以看出:
主机要模拟I2C时,要多考虑从机的接续机制,在主机发送时钟信号后,
即SCL的上跳沿时,从机肯定要拉低SCL以匹配速率.
主机要再次主机发送时钟信号前,
即SCL的下跳沿前,一定要等到SCL总线为高电平时才能发送低电平信号!!!

特别注意从机程序的:
  while (tmp = (PINB & (1 << SCL)));//等待SCL=0主机处理结束
  PORTB &= ~(1 << SCL);//保持低电平
  DDRB |= (1 << SCL);//占用SCL总线,以便长期处理


总而结之,拿我们村里的一句粗话:"没病不死人".

可能话不好听,但确实是句大实话~~~

今天就忽悠到这里吧~~~

我最近还要将"狗论"进行到底呢~~~也欢迎所长多拍砖~~~ 
 

 
__________________________
●█〓██▄▄▄▄▄▄ ●●●●●●→ '''',,;;
▄▅██████▅▄▃▂ 灌水入坛,传播非典!; 
██████████████ '''',,,;,.,,,;,;;
◥⊙▲⊙▲⊙▲⊙▲⊙▲⊙▲◤ '''',,,;,.,,,;,;;
','''',''',,,;,.',''',,',,,'.╭⌒╮⌒╮,,',,;;
⌒╮'╭⌒╮⌒╮.╭WWWW╮'''',,',,',,.,.,,.,.;;
╱◥██◣'';',( ●_●),,,,东戳一下西顶一下;;
︱田︱田田| ⌒╮''/▇\ '','''',,,;,.,,,;,.,,;;
╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬╬

系统分类: 单片机  |  用户分类: I2C接口  |  标签: 无标签  |  来源: 原创  | 

点击查看原文

发表评论 阅读全文(2202) | 回复(2)

Total , Page /