搜索
bottom↓
回复: 38

MSP430F5438 I2C学习笔记 硬件I2C等待法+AT24C02

[复制链接]
(240787900)

出0入0汤圆

发表于 2013-8-24 23:12:16 | 显示全部楼层 |阅读模式
0.   前言
对于大多数单片机来说,I2C成了一个老大难问题。从51时代开始,软件模拟I2C成了主流,甚至到ARMCortex M3大行其道的今天,软件模拟I2C依然是使用最广的方法。虽然软件模拟可以解决所有的问题,但是总感觉没有充分发挥MCU内部的硬件资源。查阅了所有关于MSP430F5系列的图书,没有关于硬件I2C的应用代码,自己通过调试摸索,把经验总结之后和大家分享,希望大家喜欢。同时,I2C的使用可以分为等待法和中断法,从理解的角度来说等待法思路清晰易于上手,从功耗的角度出发,中断法可以灵活的进入低功耗模式,但是不易理解。本文先从等待法入手。
MSP430F5系列的硬件I2C使用大致会有以下问题:
n I2C地址设定。一般情况下I2C的7位地址被写成了8位长度,最低位无效。例如AT24C02的I2C地址为0xA0,其实真正的7位地址为0x50。而MSP430正是需要填入这7位地址0x50。
n I2C停止位发送。在I2C读操作过程中,读取最后一个字节之后MCU应向从机发送无应答,MSP430F5系列的MCU发送无应答的操作将自动完成,这就以为在读取最后一个字节内容时,应先操作停止位相关寄存器。
n I2C起始位发送。如果仔细分析MSP430F5参考手册,将会发现读操作和写操作发送I2C起始位时略有不同。写操作时需要先向TXBUF中写入数据,之后才可以等待TXSTT标志位变为0,而读操作和写操作稍有不同。
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
  
  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。
另外,EEPROM写操作之后需要有10ms的延时,否则将无法进行写操作和读操作。具体请查看AT24C02数据手册。
6.1 测试代码
void eeprom_config()
{
#ifDEBUF_EEPROM_I2C
  
  uint8_t test_byte1 = 0x0B;
  uint8_t test_byte2 = 0x01;
  
  /*
    step1 向地址0x00写入某个值,例如0x0B
          然后读出地址0x00结果,判断该值是否为0x0B
  */
  eeprom_writebyte( 0x00 , test_byte1 );  
  delay_ms(10);
  eeprom_readbyte( 0x00 , &test_byte2 );
  assert_param( test_byte1 == test_byte2 );
  
  if( test_byte1 == test_byte2 )
  {
    printf( "Byte Read andByte Write Test Pass\r\n" );   
  }
  /*
    step2 以地址0x08作为起始地址,连续写入8个字节数据
          再连续从该起始地址读取8字节内容,比较写入和读出字节内容
          成功的条件为写入和读取字节内容相同
  */
  uint8_t test_buf1[8] = {1,2,3,4,5,6,7,8};
  uint8_t test_buf2[8] = {0,0,0,0,0,0,0,0};
  
  eeprom_writepage( 0x08 , test_buf1 , 8);
  delay_ms(10);
  eeprom_readpage( 0x08 , test_buf2 , 8);
  assert_param( memcmp( test_buf1 , test_buf2 , 8) == 0 );
  
  if( !memcmp( test_buf1 , test_buf2 , 8))
  {
    printf("Page Read andPage Write Test Pass!\r\n");   
  }
#endif
}
6.2 测试结果
                              
7 工程代码链接工程代码请转至百度网盘,链接如下:
http://pan.baidu.com/share/link?shareid=341219718&uk=3707837393

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
(240784234)

出0入0汤圆

发表于 2013-8-25 00:13:22 | 显示全部楼层
这字体,这颜色,楼主绝对辛苦了!

佩服像这样的工程师,有认真的态度。
(240748067)

出0入0汤圆

发表于 2013-8-25 10:16:09 | 显示全部楼层
顶起来,谢谢楼主的分享...
(240747420)

出0入0汤圆

 楼主| 发表于 2013-8-25 10:26:56 | 显示全部楼层
renpeng009 发表于 2013-8-25 00:13
这字体,这颜色,楼主绝对辛苦了!

佩服像这样的工程师,有认真的态度。 ...

您的STM8最小系统貌似很吸引人吗!
(240746402)

出0入0汤圆

发表于 2013-8-25 10:43:54 | 显示全部楼层
前段时间搞IIC,经常会出错,9个时钟信号真心很有用,感谢大神点睛~
(240745897)

出0入0汤圆

 楼主| 发表于 2013-8-25 10:52:19 | 显示全部楼层
活力的小冰 发表于 2013-8-25 10:43
前段时间搞IIC,经常会出错,9个时钟信号真心很有用,感谢大神点睛~

所有的芯片都可以这样用,包括STM32 AVR。
(240698008)

出0入0汤圆

发表于 2013-8-26 00:10:28 | 显示全部楼层
谢谢楼主,就是关于地址还是不明白
(240663709)

出0入0汤圆

发表于 2013-8-26 09:42:07 | 显示全部楼层
谢谢楼主分享,收下了
(240655040)

出0入0汤圆

 楼主| 发表于 2013-8-26 12:06:36 | 显示全部楼层
深海烟花 发表于 2013-8-26 00:10
谢谢楼主,就是关于地址还是不明白

如果软件模拟I2C AT24C02的地址为0xA0的话,那么使用MSP430硬件I2C,填入寄存器的地址为0xA0>>1 = 0x50.

0x50才是真正的I2C 7位地址
(240109540)

出0入0汤圆

发表于 2013-9-1 19:38:16 | 显示全部楼层
xukai871105 发表于 2013-8-26 12:06
如果软件模拟I2C AT24C02的地址为0xA0的话,那么使用MSP430硬件I2C,填入寄存器的地址为0xA0>>1 = 0x50.
...

谢谢楼主,我们的硬件I2c不知道什么原因一直调不通。。。。。。。。。。。。。。。。
(240108748)

出0入0汤圆

 楼主| 发表于 2013-9-1 19:51:28 | 显示全部楼层
深海烟花 发表于 2013-9-1 19:38
谢谢楼主,我们的硬件I2c不知道什么原因一直调不通。。。。。。。。。。。。。。。。
...

要不拿我的代码试一下!

STM32的硬件I2C我也一直没有调通,使用软件I2C的为主
MSP430的硬件I2C用的还是比较顺手的
EFM32的硬件I2C非常好用,简单方便,中断模式容易进入低功耗模式
(240031526)

出0入0汤圆

发表于 2013-9-2 17:18:30 | 显示全部楼层
很感谢楼主,调通了我们的I2c.........
(239607540)

出0入0汤圆

发表于 2013-9-7 15:04:56 | 显示全部楼层
不错,调430的硬件I2c接收部分有点累
(239363536)

出0入0汤圆

发表于 2013-9-10 10:51:40 | 显示全部楼层
楼主的代码分析的很详细!谢谢楼主分享!
(239343309)

出0入0汤圆

发表于 2013-9-10 16:28:47 | 显示全部楼层
确实给力。正需要IIC呢。楼主感谢
(236082772)

出0入0汤圆

发表于 2013-10-18 10:11:04 | 显示全部楼层
楼主的分析非常清楚到位,辛苦了!
(236071017)

出0入0汤圆

 楼主| 发表于 2013-10-18 13:26:59 | 显示全部楼层
divineliu 发表于 2013-10-18 10:11
楼主的分析非常清楚到位,辛苦了!

不客气!继续努力!
(235645004)

出0入0汤圆

发表于 2013-10-23 11:47:12 | 显示全部楼层
太给力了,爱死楼主
(235440424)

出0入0汤圆

发表于 2013-10-25 20:36:52 来自手机 | 显示全部楼层
我之前也打算硬件i2c操作的,发现官方提供的太模糊,自己折腾了一下,花时间,直接上软件模拟了,主要花不起时间专门研究硬件i2c,最好还是官方提供详细的应用说明
(232440159)

出0入0汤圆

发表于 2013-11-29 14:01:17 来自手机 | 显示全部楼层
楼主说得很详细,学习了
(210826275)

出0入0汤圆

发表于 2014-8-6 17:52:41 | 显示全部楼层
多谢LZ的强大教程,今天试了下,成功了! 前人栽树,后人乘凉!
(210764952)

出0入0汤圆

 楼主| 发表于 2014-8-7 10:54:44 | 显示全部楼层
xueshengke1993 发表于 2014-8-6 17:52
多谢LZ的强大教程,今天试了下,成功了! 前人栽树,后人乘凉!

如果你可以用的话这就太好了,看来我的努力值了!
(203466764)

出0入0汤圆

发表于 2014-10-30 22:11:12 | 显示全部楼层
作为一个只会软件的计算机妹子,由于老师要求,不得不画了板子,学起MSP430。
最近项目要求I2C实现,问了很多人,都直截了当说相当简单。
可是,作为一个新手,好迷茫!

好在找到了楼主的帖子 ,受益匪浅,也缓解了最近烦躁的心情!!楼主辛苦了!!!
(203414416)

出0入0汤圆

 楼主| 发表于 2014-10-31 12:43:40 | 显示全部楼层
472666316GAQ 发表于 2014-10-30 22:11
作为一个只会软件的计算机妹子,由于老师要求,不得不画了板子,学起MSP430。
最近项目要求I2C实现,问了很 ...

不客气,刚开会总有这么一段时间。
有的时候别人问我问题,我也会不经意回答“这个东西很简单”,但是回头来想想其实很多细节没有弄清楚。
有时间可以看看我签名里面的博客链接,或许有更多收获。
(200983024)

出0入0汤圆

发表于 2014-11-28 16:06:52 | 显示全部楼层
我想麻烦问一下,我用的是IAR,写I2C程序的过程中应该怎么调试啊?用SIMULATOR看寄存器的值么?还是直接下载到板子上啊?我下载到板子上试过,一点反应也没有,看寄存器的值也很奇怪,我也不知道到底通信成功没有,还用过示波器看SCL的值,结果什么都没有一样~好纠结,望楼主有空回答一下啊!!!大谢!!!

不懂硬件编程好难过。。
(200398937)

出0入0汤圆

发表于 2014-12-5 10:21:39 | 显示全部楼层
我擦,酷毙了。。。

         代码、文字分段,颜色标注,楼主辛苦了!
(198480791)

出0入0汤圆

发表于 2014-12-27 15:10:45 | 显示全部楼层
顶楼主, MB刚刚搜了半天,这个TI的 I2C真心难用,一移植楼主的代码马上OK/
(186638184)

出0入0汤圆

发表于 2015-5-13 16:47:32 | 显示全部楼层
我又来你这里学习了
(186563166)

出0入0汤圆

 楼主| 发表于 2015-5-14 13:37:50 | 显示全部楼层
skyli 发表于 2015-5-13 16:47
我又来你这里学习了

可关注我的博客!在回复的签名中!
(181435726)

出0入0汤圆

发表于 2015-7-12 21:55:10 | 显示全部楼层
标记一下
(180829020)

出0入0汤圆

发表于 2015-7-19 22:26:56 | 显示全部楼层
很棒,正在调cc2541的IIC,可以参考.
(176191546)

出0入0汤圆

发表于 2015-9-11 14:38:10 | 显示全部楼层
Mark                                             
(175155414)

出0入0汤圆

发表于 2015-9-23 14:27:02 | 显示全部楼层
MARK                             
(172149889)

出0入0汤圆

发表于 2015-10-28 09:19:07 | 显示全部楼层
很好的东西,楼主辛苦了
(155776404)

出0入0汤圆

发表于 2016-5-4 21:30:32 | 显示全部楼层
收藏了,准备学习
(147166882)

出0入0汤圆

发表于 2016-8-12 13:02:34 | 显示全部楼层
收藏了,准备学习
(137883658)

出0入0汤圆

发表于 2016-11-27 23:42:58 | 显示全部楼层
楼主辛苦,正好在学习i2c相关,多谢多谢
(113824913)

出0入0汤圆

发表于 2017-9-2 10:42:03 | 显示全部楼层
好东西,学习下。。
(60385807)

出0入0汤圆

发表于 2019-5-13 22:53:49 | 显示全部楼层
mark                  
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子论坛 ( 公安交互式论坛备案:44190002001997 粤ICP备09047143号 )

GMT+8, 2021-4-11 20:43

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表