历史上的今天
返回首页

历史上的今天

今天是:2025年01月21日(星期二)

正在发生

2020年01月21日 | 关于MSP硬件I2C讲解

2020-01-21 来源:eefocus

0.前言

对于大多数单片机来说,I2C成了一个老大难问题。从51时代开始,软件模拟I2C成了主流,甚至到ARMCortex M3大行其道的今天,软件模拟I2C依然是使用最广的方法。虽然软件模拟可以解决所有的问题,但是总感觉没有充分发挥MCU内部的硬件资源。查阅了所有关于MSP430F5系列的图书,没有关于硬件I2C的应用代码,自己通过调试摸索,把经验总结之后和大家分享,希望大家喜欢。同时,I2C的使用可以分为等待法和中断法,从理解的角度来说等待法思路清晰易于上手,从功耗的角度出发,中断法可以灵活的进入低功耗模式,但是不易理解。本文先从等待法入手。


MSP430F5系列的硬件I2C使用大致会有以下问题:


【I2C地址设定】一般情况下I2C的7位地址被写成了8位长度,最低位无效。例如AT24C02的I2C地址为0xA0,其实真正的7位地址为0x50。而MSP430正是需要填入这7位地址0x50。


【I2C停止位发送】在I2C读操作过程中,读取最后一个字节之后MCU应向从机发送无应答,MSP430F5系列的MCU发送无应答的操作将自动完成,这就以为在读取最后一个字节内容时,应先操作停止位相关寄存器。


【 I2C起始位发送】如果仔细分析MSP430F5参考手册,将会发现读操作和写操作发送I2C起始位时略有不同。写操作时需要先向TXBUF中写入数据,之后才可以等待TXSTT标志位变为0,而读操作和写操作稍有不同。


【AT24C02操作时序图】

1.初始化设置

1.1代码实现 

void ucb0_config(void)  

{  

  P3SEL &= ~BIT2;                        // P3.2@UCB0SCL  

  P3DIR |= BIT2;  

  P3OUT |= BIT2;  

  // 输出9个时钟以恢复I2C总线状态  

  for( uint8_t i= 0; i <9 ; i++)  

  {  

    P3OUT |= BIT2;  

    __delay_cycles(8000);  

    P3OUT &= ~BIT2;  

    __delay_cycles(8000);  

  }  

   

  P3SEL |= (BIT1 + BIT2);                // P3.1@UCB0SDAP3.2@UCB0SCL  

  // P3.1@ISP.1 P3.2@ISP.5  

   

  UCB0CTL1 |= UCSWRST;  

  UCB0CTL0 = UCMST+ UCMODE_3 + UCSYNC;  // I2C主机模式  

  UCB0CTL1 |= UCSSEL_2;                  // 选择SMCLK  

  UCB0BR0 = 40;  

  UCB0BR1 = 0;  

  UCB0CTL0 &= ~UCSLA10;                  // 7位地址模式  

  UCB0I2CSA = EEPROM_ADDRESS;           // EEPROM地址  

  UCB0CTL1 &= ~UCSWRST;  

}  


1.2代码分析

I2C从设备的地址一般有以下通俗说法——7位地址,写地址(写控制字)和读地址(读控制字)。1个I2C通信的控制字节(I2C启动之后传送的第一个字节)由7位I2C地址和1位读写标志位组成,7位I2C地址即7位地址,若读写标志位为读标志(读写标志位置位)加上7位I2C地址便组成了读地址(读控制字),若读写标志位为写标志(读写标志位清零)加上7位地址便组成了写地址(写控制字)。例如AT24C02的I2C7位地址为0x50,读地址(读控制字)为0xA1,写地址(写控制字)为0xA1。


在MSP430F5系列中,I2CSA地址寄存器应写入7位地址,参照上面的例子应写入0X50。至于I2C读写位的控制由CTL1寄存器完成,用户无需干预。


在I2C设置开始之前,可以先通过SCL端口发送9个时钟信号,该时钟信号可以是I2C从机芯片从一种错误的通信状态恢复,虽然这9个时钟信号不起眼但是对于调试过程来说非常有用。例如在调试过程中,错误的发送了停止位,若再次启动调试则I2C从设备仍处于一种错误的状态,这9个时钟信号可以把I2C从设备从错误的状态“拉”回来。


2.写单个字节

向I2C从设备写入单个字节应该是最为简单的一个操作,因为所有的控制权都在主机手中。写单个字节实际包括了2个重要部分,一个便是写寄存器地址,另一个便是写寄存器内容。对于AT24C02而言,存储内容的字节长度为一个字节,而对于容量更大的EEPROM而言,存储地址可为两个字节。


2.1 代码实现

uint8_teeprom_writebyte( uint8_t word_addr, uint8_tword_value )  

{  

  while( UCB0CTL1& UCTXSTP );  

  UCB0CTL1 |= UCTR;                // 写模式  

  UCB0CTL1 |= UCTXSTT;             // 发送启动位  

   

  UCB0TXBUF = word_addr;           // 发送字节地址  

  // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可  

  while(!(UCB0IFG& UCTXIFG))  

  {  

    if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1  

    {  

      return 1;  

    }  

  }    

   

  UCB0TXBUF = word_value;          // 发送字节内容  

  while(!(UCB0IFG& UCTXIFG));     // 等待UCTXIFG=1  

   

  UCB0CTL1 |= UCTXSTP;  

  while(UCB0CTL1& UCTXSTP);       // 等待发送完成  

   

  return 0;  

}  


2.2 代码分析

关于代码出口的说明,关于I2C的读写函数,若返回值为0说明所有的操作正常,若返回值为非0说明操作有误,例如1代表从机无应答。这种组合方式可能与各位的编程习惯有出入,一般认为返回1表示操作成功,而返回0表示操作失败。这种方式的问题便是无法有效的表达错误原因,因为“0”只有一个,而非“0”却有很多。


写单个字节可以划分为——从机写地址发送、寄存器地址发送、寄存器内容发送。寄存器地址的发送由MSP430自动完成,这和软件模拟的操作有所区别。请勿发送I2C从机地址,若操作AT24C02发送需要写入的存储字节的首地址即可。


在单字节和多字节写操作过程中,尤其要注意UCTXSTT标志位的变化位置。UCTXSTT标志位会在从机接收完写控制字节或读控制字节之后变化,但是在写控制字节发送之后,必须先填充TXBUF,再尝试等待STT标志位复位,此时STT标志位和TXIFG标志位会同时变化。若从机没有应答,那么NACK标志位也会发生变化。再次强调需要先填充TXBUF,在等待STT标志位复位。以下代码将导致程序一直停留在while(UCB0IFG & UCTXSTT)处,具体的原因可查看MSP430参考手册。

 

while( UCB0CTL1& UCTXSTP );  

UCB0CTL1 |= UCTR;                // 写模式  

UCB0CTL1 |= UCTXSTT;             // 发送启动位  

// 等待UCTXSTT=0同时变化,但是很遗憾该变化不会发送  

while(UCB0IFG& UCTXSTT);  

UCB0TXBUF = word_addr;           // 发送字节地址  


3.写多个字节

3.1代码实现

uint8_teeprom_writepage( uint8_t word_addr, uint8_t *pword_buf, uint8_t len)  

{  

  while( UCB0CTL1& UCTXSTP );  

  UCB0CTL1 |= UCTR;                // 写模式  

  UCB0CTL1 |= UCTXSTT;             // 发送启动位  

   

  UCB0TXBUF = word_addr;           // 发送字节地址  

  // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可  

  while(!(UCB0IFG& UCTXIFG))  

  {  

    if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1  

    {  

      return 1;  

    }  

  }    

   

  for( uint8_t i= 0; i < len; i++)  

  {  

    UCB0TXBUF = *pword_buf++;      // 发送寄存器内容  

    while(!(UCB0IFG& UCTXIFG));   // 等待UCTXIFG=1     

  }  

   

  UCB0CTL1 |= UCTXSTP;  

  while(UCB0CTL1& UCTXSTP);       // 等待发送完成  

   

  return 0;  

}  


3.2 代码分析

多字节写函数和单字节写函数相似,不做过多的解释。


4.读单个字节

单字节读函数是4中读写函数中最为复杂的,复杂的原因在于读最后一个字节之前就需要操作UCTXSTP标志位。


4.1 代码实现

uint8_teeprom_readbyte( uint8_t word_addr, uint8_t *pword_value)  

{  

  UCB0CTL1 |= UCTR;                // 写模式  

  UCB0CTL1 |= UCTXSTT;             // 发送启动位和写控制字节  

   

  UCB0TXBUF = word_addr;            //发送字节地址,必须要先填充TXBUF  

  // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可  

  while(!(UCB0IFG& UCTXIFG))  

  {  

    if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1  

    {  

      return 1;  

    }  

  }                         

   

  UCB0CTL1 &= ~UCTR;                //读模式  

  UCB0CTL1 |= UCTXSTT;             // 发送启动位和读控制字节  

   

  while(UCB0CTL1& UCTXSTT);       // 等待UCTXSTT=0  

  // 若无应答 UCNACKIFG = 1  

  UCB0CTL1 |= UCTXSTP;             // 先发送停止位  

   

  while(!(UCB0IFG& UCRXIFG));     // 读取字节内容  

  *pword_value = UCB0RXBUF;        // 读取BUF寄存器在发送停止位之后  

   

  while( UCB0CTL1& UCTXSTP );  

   

  return 0;  

}  


4.2代码分析

这段代码给人一个错觉,MSP430先发送了停止位,然后再读取了一个字节内容。其实实际情况并不是这样的。I2C读操作时,主机读取最后一个字节内容之后,应向从机发送无应答NACK(无应答区别于应答),之后主机发送停止位。MSP430为了完成这一组合动作,要求用户提前操作UCTXSTP标志位,在读取RXBUF之后做出发送NACK和I2C停止位的“组合动作”。

 

while(!(UCB0IFG& UCRXIFG));  

*pword_value = UCB0RXBUF;        // 读取BUF寄存器在发送停止位之后  

UCB0CTL1 |= UCTXSTP;             // 发送停止位  


以上代码可能导致后续的I2C操作无法进行。


5.读多个字节

5.1代码实现  

uint8_t eeprom_readpage(uint8_t word_addr, uint8_t *pword_buf, uint8_t len )  

{  

  while( UCB0CTL1& UCTXSTP );  

  UCB0CTL1 |= UCTR;                // 写模式  

  UCB0CTL1 |= UCTXSTT;             // 发送启动位和写控制字节  

   

  UCB0TXBUF = word_addr;           // 发送字节地址  

  // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可  

  while(!(UCB0IFG& UCTXIFG))  

  {  

    if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1  

    {  

      return 1;  

    }  

  }    

   

  UCB0CTL1 &= ~UCTR;               // 读模式  

  UCB0CTL1 |= UCTXSTT;             // 发送启动位和读控制字节  

   

  while(UCB0CTL1& UCTXSTT);       // 等待UCTXSTT=0  

  // 若无应答 UCNACKIFG = 1  

   

  for( uint8_t i= 0; i< len -1 ; i++)  

  {  

    while(!(UCB0IFG& UCRXIFG));   // 读取字节内容,不包括最后一个字节内容  

    *pword_buf++= UCB0RXBUF;  

  }  

   

  UCB0CTL1 |= UCTXSTP;             // 在接收最后一个字节之前发送停止位  

   

  while(!(UCB0IFG& UCRXIFG));     // 读取最后一个字节内容  

  *pword_buf = UCB0RXBUF;  

   

  while( UCB0CTL1& UCTXSTP );  

   

  return 0;  

}  


5.2代码分析

读单个字节和写单个字节相似。唯一需要注意的是,写操作需要先填充TXBUF,而读操作不存在这个问题。试想一下,I2C写操作时必定会向I2C从机写入一个字节内容,所以先填充TXBUF也是合情合理的事情,填充TXBUF之后MSP430会进行一连串的动作——发送I2C起始位、I2C读控制器和写入从机的第一个字节。


6 单元测试

单元测试分为两个部分。单字节写函数和单字节读函数分为一组,先使用单字节邪恶函数向某地址写入某内容,在使用单字节读函数读出某内容,如果写入的参数和读出的内容相同,则测试通过。多字节写函数和多字节度函数分为一组,测试过程相似,不同的是写入的内容从一个变为了连续8个。请注意AT24C02的页大小为8,若从页首地址开始,最大的写字节个数为8。

上一篇:MSP430晶振配置详解

下一篇:MSP430+DMA

推荐阅读

史海拾趣

福建国光公司的发展小趣事

对于具有锁定功能的物体检测报警电路,网友可能会有多种问题,以下是一些常见问题及其回答:

一、电路工作原理相关问题

  1. 问题:具有锁定功能的物体检测报警电路是如何工作的?
    回答:该电路通常包括光电检测部分、信号处理部分和报警输出部分。当有人或物体入侵到光电检测的空间时,光被遮挡,导致光敏晶体管截止,其集电极电压上升。这一变化被信号处理部分检测并转换为控制信号,使报警电路启动,发出声音或光等报警信号。同时,电路具有锁定功能,即一旦报警触发,需要特定操作(如断开开关)才能解除报警状态。

  2. 问题:锁定功能是如何实现的?
    回答:锁定功能通常通过记忆电路或状态保持电路实现。当报警信号被触发后,这部分电路会保持一个高电平或低电平状态,使报警信号持续输出,直到接收到解除报警的外部信号(如断开开关)。这种设计可以防止短暂的遮挡或误触发导致的短暂报警,提高系统的稳定性和可靠性。

二、电路设计与实现相关问题

  1. 问题:在设计具有锁定功能的物体检测报警电路时,需要考虑哪些因素?
    回答:设计时需要考虑的因素包括检测灵敏度、报警响应时间、误报率、功耗、成本以及与其他系统的兼容性等。此外,还需要考虑电路的稳定性、可靠性和安全性,确保在各种环境条件下都能正常工作。

  2. 问题:如何降低电路的误报率?
    回答:降低误报率的方法包括优化光电检测器的布局和参数设置,以减少外界光线的干扰;采用信号处理算法对检测信号进行滤波和去噪处理;以及设置合理的报警阈值等。此外,还可以通过增加确认机制(如二次检测)来进一步降低误报率。

三、电路应用与维护相关问题

  1. 问题:这种电路在哪些领域有应用?
    回答:具有锁定功能的物体检测报警电路在多个领域都有广泛应用,如安防监控、工业自动化、智能家居等。在安防监控领域,它可以用于入侵检测、门窗防护等场景;在工业自动化领域,可以用于生产线上的物料检测、机器人避障等场景;在智能家居领域,则可以用于门窗传感器、人体存在检测等场景。

  2. 问题:如何对电路进行日常维护和故障排查?
    回答:日常维护包括定期检查电路的连接是否牢固、光电检测器是否清洁无遮挡等。故障排查时,可以使用万用表等工具检测电路各部分的电压和电流是否正常,定位故障点。同时,也可以结合报警记录和监控视频等信息进行综合分析,以快速准确地判断故障原因并采取相应的解决措施。

以上是针对具有锁定功能的物体检测报警电路可能提出的问题及其回答。需要注意的是,由于电路的具体设计和实现方式可能因应用场景和需求的不同而有所差异,因此在实际应用中还需要根据具体情况进行调整和优化。

Gould Ami公司的发展小趣事
检查水泵电机是否损坏,轴承是否磨损。如有必要,可拆卸水泵进行检修或更换损坏部件。
全鹏(CHAMPION)公司的发展小趣事

随着全鹏在巴西市场的稳固地位,公司决定在2006年创立C3 TECH品牌,以进一步提升其品牌影响力。C3 TECH品牌的创立不仅增强了全鹏在巴西市场的竞争力,还为公司带来了更多的商机。通过精心策划的市场营销活动和优质的产品质量,C3 TECH品牌迅速获得了当地消费者的认可,并在市场上树立了良好的口碑。

FORMOSA公司的发展小趣事

在半导体技术日新月异的时代,一家名为“FORMOSA半导体科技”的公司凭借其在先进制程技术上的突破,迅速在行业内崭露头角。该公司专注于研发和生产高性能的处理器和存储器芯片,为智能手机、数据中心等高端应用提供核心动力。通过持续的研发投入和与全球顶尖科技公司的合作,FORMOSA半导体科技成功打破了多项技术壁垒,其产品在市场上赢得了广泛赞誉。公司还积极响应绿色环保的号召,推出了一系列低功耗、高效率的半导体解决方案,为可持续发展贡献力量。

BOOKLY公司的发展小趣事

在电子行业的激烈竞争中,BOOKLY公司以其卓越的技术创新能力脱颖而出。公司初创时,仅是一个由几位电子工程师组成的小团队,专注于研发高效能、低功耗的芯片技术。经过数年的艰苦努力,BOOKLY成功推出了一款颠覆性的芯片产品,被广泛应用于智能手机和电脑领域,迅速赢得了市场份额。此后,公司不断投入研发,拓展产品线,逐渐在电子行业建立了自己的品牌地位。

CSR plc(剑桥硅晶无线电)公司的发展小趣事

除了自主研发和收购外,CSR plc还积极与其他企业进行技术合作与转让。例如,2012年7月,三星电子以3.1亿美元的价格收购了CSR Plc的无线技术部门,包括该部门的310名员工和相关的21项技术专利。这次技术合作不仅为CSR plc带来了可观的收益,也推动了整个行业的技术进步和发展。

通过以上五个故事,我们可以看到CSR plc在电子行业中的发展历程充满了创新与挑战。从初创时期的音频技术突破,到后来的多元化发展、重要收购和技术合作,CSR plc不断适应市场变化,拓展业务领域,最终在电子行业中取得了显著的地位和成就。

问答坊 | AI 解惑

Xilinx通过ISO/TS 16949汽车行业标准认证

赛灵思公司(Xilinx)今天宣布获得汽车行业质量标准ISO/TS 16949认证。ISO/TS 16949标准使赛灵思公司能够为整个汽车供应链提供质量和可靠性最高的电子元器件。赛灵思公司在此之前已经满足了其它世界级质量标准的严格要求,包括ISO 9 ...…

查看全部问答>

单片机C51典型应用设计代码

所有代码均在Keil C51 7.0以上版本编译通过。只需要能够运行Windows 98 以上版本的操作系统、并能够安装Keil C51 7.0以上版本的软件即可。…

查看全部问答>

搞通信的:硬件和软件哪个发展前景更大?

请大家谈谈自己相关领域的状况。…

查看全部问答>

请教!请教!

请问一下,三星笔记本管理员密码忘记了,开不了机怎么办?…

查看全部问答>

那里能找到高通qsd8x50片子的资料

我们用到高通这样一个片子,可是没有芯片资料,据高通说还没有发布,不知各位大爷有没有找得到的。 给个网址、 …

查看全部问答>

关于 飞思卡尔(freescale)i.MX51

1、阅读了一下i.MX51  ARM Cortex A8的datasheet,对于IOMUX还是不清楚, 2、还有一些缩写如:PAD、ALTn(n=0, 1, 2...)等等很多,配置一个引脚做很多工作。 3、感觉freescale的比较难理解,以前做三星的一看寄存器就知道是干什么的。 ...…

查看全部问答>

定制系统时加入了微软拼音中文输入法

定制系统时加入了微软拼音中文输入法,结果运行时,只出声母,不出韵母,打不出中文来,请问有人遇到过没,怎么解决? 1、设置了环境变量SET LOCALE=0804; 2.在platform settings中选择了中文(中国),英文(美国),默认语言设为中文; 3.在P ...…

查看全部问答>

关于驱动的调试release和debug

不知道大家对于调试的看法怎么样。一般你在调试的时候是release还是debug的。我每次都基本上时release的。因为是经理教的。只有在调试一些流层性,我们不知道架构的东西我才用debug。反正基本上不用。。用过几次也很懊恼。。点个屏漫天的打印信息。 ...…

查看全部问答>

求助:如何测量白石灰料位?

求助:料仓里的白石灰粉,当放料后,形成如图(四周高,中间低的料位情况)。 这时候测量仪表无法准确测了料位高度。 求助解决方法:如何能让料仓里的料位处于相对水平状态;或者采用什么样的仪表才能够做到精确测量?…

查看全部问答>