关于MSP硬件I2C讲解

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代码实现

[cpp] view
plain
 copy

  1. void ucb0_config(void)
  2. {
  3. P3SEL &= ~BIT2;                        // [email protected]
  4. P3DIR |= BIT2;
  5. P3OUT |= BIT2;
  6. // 输出9个时钟以恢复I2C总线状态
  7. for( uint8_t i= 0; i <9 ; i++)
  8. {
  9. P3OUT |= BIT2;
  10. __delay_cycles(8000);
  11. P3OUT &= ~BIT2;
  12. __delay_cycles(8000);
  13. }
  14. P3SEL |= (BIT1 + BIT2);                // [email protected]@UCB0SCL
  15. // [email protected] [email protected]
  16. UCB0CTL1 |= UCSWRST;
  17. UCB0CTL0 = UCMST+ UCMODE_3 + UCSYNC;  // I2C主机模式
  18. UCB0CTL1 |= UCSSEL_2;                  // 选择SMCLK
  19. UCB0BR0 = 40;
  20. UCB0BR1 = 0;
  21. UCB0CTL0 &= ~UCSLA10;                  // 7位地址模式
  22. UCB0I2CSA = EEPROM_ADDRESS;           // EEPROM地址
  23. UCB0CTL1 &= ~UCSWRST;
  24. }

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 代码实现

[cpp] view
plain
 copy

  1. uint8_teeprom_writebyte( uint8_t word_addr, uint8_tword_value )
  2. {
  3. while( UCB0CTL1& UCTXSTP );
  4. UCB0CTL1 |= UCTR;                // 写模式
  5. UCB0CTL1 |= UCTXSTT;             // 发送启动位
  6. UCB0TXBUF = word_addr;           // 发送字节地址
  7. // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可
  8. while(!(UCB0IFG& UCTXIFG))
  9. {
  10. if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1
  11. {
  12. return 1;
  13. }
  14. }
  15. UCB0TXBUF = word_value;          // 发送字节内容
  16. while(!(UCB0IFG& UCTXIFG));     // 等待UCTXIFG=1
  17. UCB0CTL1 |= UCTXSTP;
  18. while(UCB0CTL1& UCTXSTP);       // 等待发送完成
  19. return 0;
  20. }

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参考手册。

[cpp] view
plain
 copy

  1. while( UCB0CTL1& UCTXSTP );
  2. UCB0CTL1 |= UCTR;                // 写模式
  3. UCB0CTL1 |= UCTXSTT;             // 发送启动位
  4. // 等待UCTXSTT=0同时变化,但是很遗憾该变化不会发送
  5. while(UCB0IFG& UCTXSTT);
  6. UCB0TXBUF = word_addr;           // 发送字节地址

3.写多个字节

3.1代码实现

[cpp] view
plain
 copy

  1. uint8_teeprom_writepage( uint8_t word_addr, uint8_t *pword_buf, uint8_t len)
  2. {
  3. while( UCB0CTL1& UCTXSTP );
  4. UCB0CTL1 |= UCTR;                // 写模式
  5. UCB0CTL1 |= UCTXSTT;             // 发送启动位
  6. UCB0TXBUF = word_addr;           // 发送字节地址
  7. // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可
  8. while(!(UCB0IFG& UCTXIFG))
  9. {
  10. if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1
  11. {
  12. return 1;
  13. }
  14. }
  15. for( uint8_t i= 0; i < len; i++)
  16. {
  17. UCB0TXBUF = *pword_buf++;      // 发送寄存器内容
  18. while(!(UCB0IFG& UCTXIFG));   // 等待UCTXIFG=1
  19. }
  20. UCB0CTL1 |= UCTXSTP;
  21. while(UCB0CTL1& UCTXSTP);       // 等待发送完成
  22. return 0;
  23. }

3.2 代码分析

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

4.读单个字节

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

4.1 代码实现

[cpp] view
plain
 copy

  1. uint8_teeprom_readbyte( uint8_t word_addr, uint8_t *pword_value)
  2. {
  3. UCB0CTL1 |= UCTR;                // 写模式
  4. UCB0CTL1 |= UCTXSTT;             // 发送启动位和写控制字节
  5. UCB0TXBUF = word_addr;            //发送字节地址,必须要先填充TXBUF
  6. // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可
  7. while(!(UCB0IFG& UCTXIFG))
  8. {
  9. if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1
  10. {
  11. return 1;
  12. }
  13. }
  14. UCB0CTL1 &= ~UCTR;                //读模式
  15. UCB0CTL1 |= UCTXSTT;             // 发送启动位和读控制字节
  16. while(UCB0CTL1& UCTXSTT);       // 等待UCTXSTT=0
  17. // 若无应答 UCNACKIFG = 1
  18. UCB0CTL1 |= UCTXSTP;             // 先发送停止位
  19. while(!(UCB0IFG& UCRXIFG));     // 读取字节内容
  20. *pword_value = UCB0RXBUF;        // 读取BUF寄存器在发送停止位之后
  21. while( UCB0CTL1& UCTXSTP );
  22. return 0;
  23. }

4.2代码分析

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

[cpp] view
plain
 copy

  1. while(!(UCB0IFG& UCRXIFG));
  2. *pword_value = UCB0RXBUF;        // 读取BUF寄存器在发送停止位之后
  3. UCB0CTL1 |= UCTXSTP;             // 发送停止位

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

5.读多个字节

5.1代码实现

[cpp] view
plain
 copy

  1. uint8_t eeprom_readpage(uint8_t word_addr, uint8_t *pword_buf, uint8_t len )
  2. {
  3. while( UCB0CTL1& UCTXSTP );
  4. UCB0CTL1 |= UCTR;                // 写模式
  5. UCB0CTL1 |= UCTXSTT;             // 发送启动位和写控制字节
  6. UCB0TXBUF = word_addr;           // 发送字节地址
  7. // 等待UCTXIFG=1与UCTXSTT=0 同时变化等待一个标志位即可
  8. while(!(UCB0IFG& UCTXIFG))
  9. {
  10. if( UCB0IFG& UCNACKIFG )      // 若无应答 UCNACKIFG=1
  11. {
  12. return 1;
  13. }
  14. }
  15. UCB0CTL1 &= ~UCTR;               // 读模式
  16. UCB0CTL1 |= UCTXSTT;             // 发送启动位和读控制字节
  17. while(UCB0CTL1& UCTXSTT);       // 等待UCTXSTT=0
  18. // 若无应答 UCNACKIFG = 1
  19. for( uint8_t i= 0; i< len -1 ; i++)
  20. {
  21. while(!(UCB0IFG& UCRXIFG));   // 读取字节内容,不包括最后一个字节内容
  22. *pword_buf++= UCB0RXBUF;
  23. }
  24. UCB0CTL1 |= UCTXSTP;             // 在接收最后一个字节之前发送停止位
  25. while(!(UCB0IFG& UCRXIFG));     // 读取最后一个字节内容
  26. *pword_buf = UCB0RXBUF;
  27. while( UCB0CTL1& UCTXSTP );
  28. return 0;
  29. }

5.2代码分析

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

6 单元测试

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

另外,EEPROM写操作之后需要有10ms的延时,否则将无法进行写操作和读操作。具体请查看AT24C02数据手册。

6.1 测试代码

[cpp] view
plain
 copy

  1. void eeprom_config()
  2. {
  3. #ifDEBUF_EEPROM_I2C
  4. uint8_t test_byte1 =0x0B;
  5. uint8_t test_byte2 =0x01;
  6. /*
  7. step1 向地址0x00写入某个值,例如0x0B
  8. 然后读出地址0x00结果,判断该值是否为0x0B
  9. */
  10. eeprom_writebyte(0x00 , test_byte1);
  11. delay_ms(10);
  12. eeprom_readbyte(0x00 ,&test_byte2 );
  13. assert_param( test_byte1== test_byte2 );
  14. if( test_byte1== test_byte2 )
  15. {
  16. printf( "Byte Read andByte Write Test Pass\r\n" );
  17. }
  18. /*
  19. step2 以地址0x08作为起始地址,连续写入8个字节数据
  20. 再连续从该起始地址读取8字节内容,比较写入和读出字节内容
  21. 成功的条件为写入和读取字节内容相同
  22. */
  23. uint8_t test_buf1[8]= {1,2,3,4,5,6,7,8};
  24. uint8_t test_buf2[8]= {0,0,0,0,0,0,0,0};
  25. eeprom_writepage(0x08 , test_buf1, 8);
  26. delay_ms(10);
  27. eeprom_readpage(0x08 , test_buf2, 8);
  28. assert_param( memcmp( test_buf1, test_buf2 ,8) == 0 );
  29. if(!memcmp( test_buf1, test_buf2 ,8))
  30. {
  31. printf("Page Read andPage Write Test Pass!\r\n");
  32. }
  33. #endif
  34. }

6.2 测试结果

7.工程代码链接

工程代码请转至百度网盘【链接】:

时间: 04-16

关于MSP硬件I2C讲解的相关文章

nRF52832之硬件I2C

这几天一直在折腾nRF52832的硬件I2C,到了今天最终出现了成果,在此也印证了那句话:"耕耘就有收获" 52832的硬件I2C尽管官方提供了demo,可是自己对I2C通信理解的不够深入,再一个52832的代码也封装的太深了.可是对接口函数没有一个明白的解释(也能够说是我英文太渣,别人写了可是我没看懂. . .),这样对于首次接触nRF产品的人就造成了一定的难度 依据我的开发过程,还是先说明一下I2C的一些相关知识,由于我是先调硬件I2C搞了半天不正确头,然后再开发模拟I2C,模拟的

STM32F10x_模拟I2C读写_硬件I2C读写

STM32F10x_模拟I2C读写EEPROM STM32F10x_硬件I2C读写EEPROM(标准外设库版本) STM32F10x_硬件I2C主从通信(轮询发送,中断接收)

MSP430G2553 Launchpad 硬件I2C驱动

一.USCI I2C 驱动介绍 对于MSP430G2553,硬件I2C由外设USCI(Universal Serial Communication Interface)提供.USCI又分为USCI_A和USCI_B,其中USCI_A支持UART/IrDA/LIN/SPI通讯,USCI_B支持I2C/SPI通讯.MSP430G2553带有一个USCI_A和一个USCI_B,硬件I2C对应的管脚为P1.6(UCB0SCL)和P1.7(UCB0SDA). 由于Launchpad上P1.6连接到了LED

I2C驱动详解

I2C讲解: 在JZ2440开发板上,I2C是由两条数据线构成的SCL,SDA:SCL作为时钟总线,SDA作为数据总线:两条线上可挂载I2C设备,如:AT24C08 两条线连接ARM9 I2C控制器,通过控制来控制I2C设备的识别设备地址.读.写操作:如图所示 从中所知:I2C线上可以挂载很多个I2C设备:挂载简单,只需要一根数据线和一根时钟线就可以挂载上去,通过地址来去别每个设备的区别: I2C操作: 对I2C操作主要思想为:1.找到设备  2.进行读写操作 主要原理为: 1.发送开始信号S

STM32F4XX高效驱动篇2 I2C

说到I2C很多用过STMF10X硬件I2C方式的工程师,都感觉有点头痛.大部分还是使用软件模拟的方式,I2C由于一般的工作频率是400,100KHz.所以在平凡读取,或所读数据量大时,使用这模拟的方式,还是比较浪费CPU有效工作时间的. 在之前的使用I2C的经历中,主要是I2C死锁问题让我也困扰了一段时间.不过后来经过多方资料,最后还是把这个问题解决了.以下驱动程序已集成了此功能. 什么是死锁,在I2C主设备进行读写操作的过程中.主设备在开始信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为

[I2C]pca9555应用层测试代码

驱动方面: 首先配置I2C内核驱动,将pca9555的源码built-in进入(这里根据需要可能要配thermal的驱动),然后在devicetree中根据pca9555硬件I2C地址配置节点. 测试源码: // I2C test program for a PCA9555 #include <stdint.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <l

(5)I2C总线的10bit地址以及通用广播地址

其实,10bit地址我没用过,通用广播地址更没用过.通用广播地址应该是在多个mcu之间用i2c进行通信时使用的.虽说没用到,但还是做了翻译,说不定以后有机会用到: 10bit地址 10bit的寻址扩展可能寻址的数目.有7bit地址和10bit地址的设备可以连接到相同的I2C总线上,而且7bit寻址和10bit寻址都可以用在所有的总线速度模式下.不过,10bit寻址用的不多. 10bit的从机地址由开始条件(S)或重复开始条件(Sr)后的两个字节组成.第一个字节的前7位是1111 0XX,XX是1

四轴飞行器1.3 MPU6050(大端)和M4的FPU开启方法

 原创文章,欢迎转载,转载请注明出处      最近时间花在最多的地方就是STM32的I2C上了.之前就知道STM32的I2C并不好用,因为之前用过模拟的I2C,也写过AVR的I2C也就是TWI的硬件驱动,所以想试试写STM32的硬件I2C...为了避免库带来的麻烦,之前和特意将STM32F4的标准库升级到了1.3.0,但是貌似问题依旧.于是在网上找到了ST转为I2C写的CPAL的库,拿着它的英文手册和例子看了下,觉得很不错,功能相当的齐全,按照手册配置用起来应该不错,于是开始加载到自己的项目中

六轴加速度传感器MPU6050官方DMP库到瑞萨RL78/G13的移植

2015年的电赛已经结束了.赛前接到器件清单的时候,看到带防护圈的多旋翼飞行器赫然在列,又给了一个瑞萨RL78/G13的MCU,于是自然联想到13年的电赛,觉得多半是拿RL78/G13做四旋翼的主控,虽然事后证实我的猜测是错的,但是在赛前我还是完成了相关代码的准备,这其中就包括了MPU6050的DMP库移植.在移植前我大概搜了一下,发现网上还没有相关的源代码.一起准备电赛的同学还买过一份RL78/G13的飞控代码,虽然也是使用MPU6050进行姿态获取,但是对MPU6050的读取并不是通过DMP

四轴飞行器1.4 姿态解算和Matlab实时姿态显示

原创文章,欢迎转载,转载请注明出处 MPU6050数据读取出来后,经过一个星期的努力,姿态解算和在matlab上的实时显示姿态终于完成了. 1:完成matlab的串口,并且实时通过波形显示数据 2:添加RTT查看CPU使用率的扩展功能,MPU6050读取数据的优化 3:四元素表示的坐标变化,四元素与欧拉角的关系和Madgwick的IMUupdate算法 4:飞控数据采集线程和数据处理线程的安排,类似于生产者与消费者的关系. 先放个效果视频... 如果看不了视频,请打开视屏网址:http://v.