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代码完结
- voiducb0_config(void)
- {
- P3SEL&=~BIT2;//P3.2@UCB0SCL
- P3DIR|=BIT2;
- P3OUT|=BIT2;
- //输出9个时钟以康复I2C总线状况
- for(uint8_ti=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.1P3.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_tword_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
- {
- return1;
- }
- }
- UCB0TXBUF=word_value;//发送字节内容
- while(!(UCB0IFG&UCTXIFG));//等候UCTXIFG=1
- UCB0CTL1|=UCTXSTP;
- while(UCB0CTL1&UCTXSTP);//等候发送完结
- return0;
- }
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_tword_addr,uint8_t*pword_buf,uint8_tlen)
- {
- while(UCB0CTL1&UCTXSTP);
- UCB0CTL1|=UCTR;//写形式
- UCB0CTL1|=UCTXSTT;//发送发动位
- UCB0TXBUF=word_addr;//发送字节地址
- //等候UCTXIFG=1与UCTXSTT=0一起改变等候一个标志位即可
- while(!(UCB0IFG&UCTXIFG))
- {
- if(UCB0IFG&UCNACKIFG)//若无应对UCNACKIFG=1
- {
- return1;
- }
- }
- for(uint8_ti=0;i
- {
- UCB0TXBUF=*pword_buf++;//发送寄存器内容
- while(!(UCB0IFG&UCTXIFG));//等候UCTXIFG=1
- }
- UCB0CTL1|=UCTXSTP;
- while(UCB0CTL1&UCTXSTP);//等候发送完结
- return0;
- }
3.2 代码剖析
多字节写函数和单字节写函数类似,不做过多的解说。
4.读单个字节
单字节读函数是4中读写函数中最为杂乱的,杂乱的原因在于读最终一个字节之前就需求操作UCTXSTP标志位。
4.1 代码完结
- uint8_teeprom_readbyte(uint8_tword_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
- {
- return1;
- }
- }
- UCB0CTL1&=~UCTR;//读形式
- UCB0CTL1|=UCTXSTT;//发送发动位和读操控字节
- while(UCB0CTL1&UCTXSTT);//等候UCTXSTT=0
- //若无应对UCNACKIFG=1
- UCB0CTL1|=UCTXSTP;//先发送中止位
- while(!(UCB0IFG&UCRXIFG));//读取字节内容
- *pword_value=UCB0RXBUF;//读取BUF寄存器在发送中止位之后
- while(UCB0CTL1&UCTXSTP);
- return0;
- }
4.2代码剖析
这段代码给人一个幻觉,MSP430先发送了中止位,然后再读取了一个字节内容。其实实践情况并不是这样的。I2C读操作时,主机读取最终一个字节内容之后,应向从机发送无应对NACK(无应对差异于应对),之后主机发送中止位。MSP430为了完结这一组合动作,要求用户提早操作UCTXSTP标志位,在读取RXBUF之后做动身送NACK和I2C中止位的“组合动作”。
- while(!(UCB0IFG&UCRXIFG));
- *pword_value=UCB0RXBUF;//读取BUF寄存器在发送中止位之后
- UCB0CTL1|=UCTXSTP;//发送中止位
以上代码或许导致后续的I2C操作无法进行。
5.读多个字节
5.1代码完结
- uint8_teeprom_readpage(uint8_tword_addr,uint8_t*pword_buf,uint8_tlen)
- {
- while(UCB0CTL1&UCTXSTP);
- UCB0CTL1|=UCTR;//写形式
- UCB0CTL1|=UCTXSTT;//发送发动位和写操控字节
- UCB0TXBUF=word_addr;//发送字节地址
- //等候UCTXIFG=1与UCTXSTT=0一起改变等候一个标志位即可
- while(!(UCB0IFG&UCTXIFG))
- {
- if(UCB0IFG&UCNACKIFG)//若无应对UCNACKIFG=1
- {
- return1;
- }
- }
- UCB0CTL1&=~UCTR;//读形式
- UCB0CTL1|=UCTXSTT;//发送发动位和读操控字节
- while(UCB0CTL1&UCTXSTT);//等候UCTXSTT=0
- //若无应对UCNACKIFG=1
- for(uint8_ti=0;i
- {
- while(!(UCB0IFG&UCRXIFG));//读取字节内容,不包含最终一个字节内容
- *pword_buf++=UCB0RXBUF;
- }
- UCB0CTL1|=UCTXSTP;//在接纳最终一个字节之前发送中止位
- while(!(UCB0IFG&UCRXIFG));//读取最终一个字节内容
- *pword_buf=UCB0RXBUF;
- while(UCB0CTL1&UCTXSTP);
- return0;
- }
5.2代码剖析
读单个字节和写单个字节类似。仅有需求留意的是,写操作需求先填充TXBUF,而读操作不存在这个问题。试想一下,I2C写操作时必定会向I2C从机写入一个字节内容,所以先填充TXBUF也是入情入理的工作,填充TXBUF之后MSP430会进行一连串的动作——发送I2C开端位、I2C读操控器和写入从机的第一个字节。
6 单元测验
单元测验分为两个部分。单字节写函数和单字节读函数分为一组,先运用单字节凶恶函数向某地址写入某内容,在运用单字节读函数读出某内容,假如写入的参数和读出的内容相同,则测验经过。多字节写函数和多字节度函数分为一组,测验进程类似,不同的是写入的内容从一个变为了接连8个。请留意AT24C02的页巨细为8,若从页首地址开端,最大的写字节个数为8。
别的,EEPROM写操作之后需求有10ms的延时,否则将无法进行写操作和读操作。详细请检查AT24C02数据手册。
6.1 测验代码
- voideeprom_config()
- {
- #ifDEBUF_EEPROM_I2C
- uint8_ttest_byte1=0x0B;
- uint8_ttest_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(“ByteReadandByteWriteTestPass\r\n”);
- }
- /*
- step2以地址0x08作为开端地址,接连写入8个字节数据
- 再接连从该开端地址读取8字节内容,比较写入和读出字节内容
- 成功的条件为写入和读取字节内容相同
- */
- uint8_ttest_buf1[8]={1,2,3,4,5,6,7,8};
- uint8_ttest_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(“PageReadandPageWriteTestPass!\r\n”);
- }
- #endif
- }