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@UCB0SCLP3DIR |= 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.5UCB0CTL1 |= UCSWRST;UCB0CTL0 = UCMST+ UCMODE_3 + UCSYNC;  // I2C主机模式UCB0CTL1 |= UCSSEL_2;                  // 选择SMCLKUCB0BR0 = 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=1UCB0CTL1 |= 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 = 1UCB0CTL1 |= 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 = 1for( 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_I2Cuint8_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.工程代码链接

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

MSP430F5438 I2C学习笔记——AT24C02相关推荐

  1. I2C学习笔记——I2C协议学习

    1.I2C简介:一种简单.双线双向的同步串行总线,利用串行时钟线(SCL)和串行数据线(SDA)在连接总线的两个器件之间进行信息传递:                        数据传输是通过对S ...

  2. LibMPSSE I2C学习笔记

    Visual Studio环境下无法运行,建议参考:编译连接 获取当前主机已连接I2C通道数目 函数:FTDI_API FT_STATUS I2C_GetNumChannels(uint32 *num ...

  3. linux at24c 前几个字节错误,I2C操作笔记——以 AT24C04为例

    Centos7系统下修改主机名操作笔记 习惯了在Centos6系统下修改主机名的操作,但是Centos7下修改主机名的操作却大不相同!操作笔记如下: 在CentOS中,有三种定义的主机名:静态的(st ...

  4. STM32CubeMX学习笔记(9)——I2C接口使用(读写EEPROM AT24C02)

    一.I2C简介 I2C(Inter-Integrated Circuit ,内部集成电路) 总线是一种由飞利浦 Philip 公司开发的串行总线.是两条串行的总线,它由一根数据线(SDA)和一根 时钟 ...

  5. 【C51单片机学习笔记--DS1302时钟芯片蜂鸣器I2C总线AT24C02存储器】

    C51单片机学习笔记–DS1302时钟芯片&&蜂鸣器&&I2C总线&&AT24C02存储器 文章目录 一.DS1302时钟芯片介绍 二.DS1302时钟 ...

  6. AutoLeaders控制组——51单片机学习笔记(蜂鸣器、AT24C02芯片)

    本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整. 以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅) 一些内容涉及 ...

  7. STM32学习笔记(9)——(I2C续)读写EEPROM

    STM32学习笔记(9)--(I2C续)读写EEPROM 一.概述 1. 背景介绍 2. EEPROM简介 二.AT24C02--常用的EEPROM 1. 电路原理图 2. 写操作 (1)按字节写操作 ...

  8. 基于I2C总线的MPU6050学习笔记

    MPU6050学习笔记 1. 简述 一直想自己做个四轴飞行器,却无从下手,终于狠下决心,拿出尘封已久的MPU6050模块,开始摸索着数据手册分析,一步一步地实现了MPU6050模块的功能,从MPU60 ...

  9. OpenHarmony学习笔记——I2C驱动0.96OLED屏幕

    文章目录 前言 I2C简介 硬件连接 编程实现 创建代码框架 初始化并复用GPIO 初始化I2C0 初始化OLED 从机地址 OLED初始化配置 功能代码 总结 目录 前言 前面介绍了一些关于在Hi3 ...

最新文章

  1. 大盘点|无人驾驶领域的综述汇总
  2. oracle eco 开放接口,问题:关于ECO,ECN的API或者INTERFACE
  3. Py之pytest-shutil:Python库之pytest-shutil简介、安装、使用方法之详细攻略
  4. 像素/厘米与像素/英寸区别_像素/体素艺术入门指南
  5. MySQL 企业监控器 2.3.10 正式版发布
  6. linux 网络dma驱动,S3C2410的Linux下DMA驱动程序开发
  7. shell 文件路径有空格_Python学习第57课-shell入门之基本简单命令(一)
  8. python生成器 图片分类_Python内置类型(6)——生成器
  9. CentOS下使用rpm-build制作nginx的RPM包
  10. java server faces
  11. Css学习总结(3)——CSS布局解决方案 - 水平、垂直居中、多列布局、全屏布局
  12. loginservlet.java_求助HTTP Status 404 - /Book/servlet/cn.servlet.LoginServlet
  13. 论文阅读(XiangBai——【CVPR2017】Detecting Oriented Text in Natural Images by Linking Segments)...
  14. AIX双机调整DB2配置
  15. python画线段代码_python画线代码
  16. Angular学习总结-入门篇
  17. C#射击类小游戏简单思路及代码
  18. oracle中private同义词和public同义词
  19. python将视频转为图片
  20. Only a type can be imported. com.xxx.xxx.XXX resolves to a package 解决方法

热门文章

  1. ID|IC|CPU卡|国密卡|二代证|防复制门禁一体机门禁读卡器带触摸键盘(不带二维码)选型必备
  2. JSON数据平铺和对象化
  3. Appium培训文档
  4. (9.3)【数据隐藏】取证方法:缓存审计、缩略图痕迹、隐藏的系统文件
  5. 关于导数、偏导数的理解
  6. 5年前, 以太坊大脑送给V神一份神秘大礼; 今天, V神将它给了你...
  7. matlab 植物生长算法,基于模拟植物生长的BP神经网络学习算法研究
  8. 微信h5网页点击链接跳转到默认浏览器是怎么弄的?gdtool一键实现该方案
  9. 现代控制理论1——前期理论体系
  10. 再来一篇,看jdk源码大师亲自操刀编写的集合源码