AVR单片机TWI读写读写范例讲解
- 格式:doc
- 大小:78.50 KB
- 文档页数:24
ATMEGA16 读写iic(24c02) C 语言程序测试通过nclude <iom16v.h>#i nclude "I2C.h"#in clude "1602.h"#in clude "delay.h"/*通过AVR往I IC写数据,并通过串口把数据读出显示出来*///===============================================================void UAR_i nit(void) //UART 初始化{ DDRD = 0x02;PORTD = 0x00;UCSRA = 0x02; /* 无倍速*/UCSRB = 0x18; /*允许接收和发送*/U(SRC= 0x06; 1*8位数据,1位停止位,无校验*/UBRRH = 0x00;UBRRL = 12; /*9600*/}//===============================================================void USART_TXD(float data) // 发送采用查询方式{while( !(UCSRA & BIT(UDRE)));UDR=data;while( !(UCSRA & BIT(TXC )));UCSRA|=BIT(TXC);}void mai n(void){un sig ned char i;// LCDinit();uart_i ni t();//TART 初始化SEI(); // 全局中断使能while(1){/*I2C_Write('n',0x00); I2C_Write('c',0x01); I2C_Write('e',0x02);I2C_Write('p',0x03); I2C_Write('u',0x04);*/ i=I2C_Read(0x00); //LCD_write_char(0,0,i); USART_TXD(i); i=I2C_Read(0x01);//LCD_write_data(i); USART_TXD(i); i=I2C_Read(0x02); //LCD_write_data(i); USART_TXD(i);i=I2C_Read(0x03); //LCD_write_data(i); USART_TXD(i); i=I2C_Read(0x04); //LCD_write_data(i); USART_TXD(i);}}/* 上面上主函数部分*/#include <macros.h>#include "delay.h"//I2C 状态定义//MT 主方式传输MR 主方式接受#define START 0x08#define RE_START 0x10#define MT_SLA_ACK 0x18 #define MT_SLA_NOACK 0x20 #define MT_DATA_ACK 0x28 #defineMT_DATA_NOACK 0x30 #define MR_SLA_ACK 0x40 #define MR_SLA_NOACK 0x48 #define MR_DATA_ACK 0x50 #define MR_DATA_NOACK 0x58#define RD_DEVICE_ADDR 0xA1 // 前4位器件固定,后三位看连线,最后1位是读写指令位#define WD_DEVICE_ADDR 0xA0//常用TWI操作(主模式写和读)#define Start() (TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN)) // 启动I2C #define Stop()(TWCR=(1<<TWINT)|(1<<TWSTO)|(1<<TWEN)) // 停止I2C #define Wait() {while(!(TWCR&(1<<TWINT)));} // 等待中断发生#define TestAck() (TWSR&0xf8) // 观察返回状态#define SetAck (TWCR|=(1<<TWEA)) // 做出ACK应答#define SetNoAck (TWCR&=~(1<<TWEA)) // 做出Not Ack 应答#define Twi() (TWCR=(1<<TWINT)|(1<<TWEN)) // 启动I2C#define Write8Bit(x) {TWDR=(x);TWCR=(1<<TWINT)|(1<<TWEN);} // 写数据到TWDRunsigned char I2C_Write(unsigned char Wdata,unsigned char RegAddress); unsigned charI2C_Read(unsigned RegAddress);/*********************************************I2C 总线写一个字节返回0: 写成功返回1: 写失败**********************************************/unsigned char I2C_Write(unsigned char Wdata,unsigned char RegAddress){Start(); //I2C 启动Wait();if(TestAck()!=START)return 1; //ACKWrite8Bit(WD_DEVICE_ADDR); // 写I2C 从器件地址和写方式Wait(); if(TestAck()!=MT_SLA_ACK) return 1; //ACKWrite8Bit(RegAddress); // 写器件相应寄存器地址Wait();if(TestAck()!=MT_DATA_ACK)return 1; //ACKWrite8Bit(Wdata); // 写数据到器件相应寄存器Wait();if(TestAck()!=MT_DATA_ACK)return 1; //ACKStop(); //I2C 停止delay_nms(10); // 延时return 0;}/*********************************************I2C 总线读一个字节返回0: 读成功返回1: 读失败**********************************************/unsigned char I2C_Read(unsigned RegAddress){ unsigned char temp;Start();//I2C 启动Wait();if (TestAck()!=START) return 1; //ACKWrite8Bit(WD_DEVICE_ADDR); // 写I2C 从器件地址和写方式Wait(); if (TestAck()!=MT_SLA_ACK) return 1; //ACKWrite8Bit(RegAddress); // 写器件相应寄存器地址Wait();if (TestAck()!=MT_DATA_ACK) return 1;Start(); //I2C 重新启动Wait();if (TestAck()!=RE_START) return 1;Write8Bit(RD_DEVICE_ADDR); // 写I2C 从器件地址和读方式Wait(); if(TestAck()!=MR_SLA_ACK)return 1; //ACKTwi(); // 启动主I2C 读方式Wait(); if(TestAck()!=MR_DATA_NOACK) return 1; //ACKtemp=TWDR;// 读取I2C 接收数据Stop();//l2C 停止return temp;}/*以上是IlC.h头文件部分,需要对照技术文档好好研究*/延时函数编译器:I CC-AVR V6.31A 日期:2005-11-24 20:29:57 目标芯片:M16 时钟:8.0000M Hz作者:arche ng504------------------------------------------------- */#ifndef __delay_h#defi ne __delay_hvoid delay_ nus(un sig ned int n);void delay_ nms(un sig ned int n);void delay_1us(void);void delay_1ms(void);void delay_1us(void) //1us 延时函数{asm( "n op");}void delay_ nus(un sig ned int n) 〃N us 延时函数{un sig ned in t i=0;for (i=0;i <n ;i++)delay_1us();}void delay_1ms(void) //1ms 延时函数{un sig ned in t i;for (i=0;i<1140;i++);}void delay_ nms(un sig ned int n) //N ms 延时函数{un sig ned in t i=0;for (i=0;i <n ;i++)delay_1ms();}#en dif/*以上是delay.h 部分,再加上IIC中自带的iom16v.h和macros.h就可以编译通过*//*注意点:本程序在实验板ATMEGA1上测试通过,在示波器把SCL, SDA信号线有数据,移值到自己电路上可以放心使用,在ATMEGA3上一样使用,本人24C02的A2, A1, A0都是接地,若地址不一样,在程序相应位置改一下就可以,串口上调试单片机的基础,所以它一定要会用*//*本程序调试软件环境是ICC6.31*/。
第9章TWI|IIC注:HJ-2G V3.0版没有集成24C02芯片,如果需要做本实验的同学,请自己购一片24C02芯片,用杜邦线接到开发板上的IO口就可以了。
9.1概述TWI或者IIC是两线的总线。
这原本是前几章的笔记,但是一开始因为觉得IIC太复杂了所以延迟到最后在练习。
其实不是这样的,AVR的IIC硬件,从手册上看是很复杂。
但是只要慢慢的分类再分类就觉得很容易了。
当然一开始确实要下一点功夫,但是原理理解了,程式跑起来后。
你就会觉得,IIC很有趣。
这一篇笔记AT24C02A作为我们的主角。
9.2IIC总线概念IIC的总线是由SDA(串行数据)和SCL(串行时钟)组成,有从机和主机之分。
总线空闲时为高电平,所以SDA和SCL都必须使用上拉电阻将电平拉上高电平。
IIC总线又有起始信号(START),终止信号(STOP),重复起始信号(REPEAT START)。
起始信号:SCL持续高电平的时候,SDA从高电平变成低电平。
终止信号:SCL持续高电平的时候,SDA从低电平变成高电平。
重复起始信号:这个比较难明白,在访问一个设备为了改变访问方向而不写入终止信号,第二次的起始信号称为重复起始信号9.2IIC总线仲裁(寻址)当主机要寻找从机时,会发送寻址,我习惯称为硬件地址。
该地址包如下:最高七位为从机地址,而最低位为硬件地址的行为。
最低位逻辑1为读从机设备,逻辑0为写从机设备。
不同的从机设备都有不同的硬件地址,硬件地址是由硬件厂商设定定的。
如AT24C02A,ATMEL公司的串行EEPROM。
AT24C02A硬件地址的区分HJ-2G的AT24C02A的A0~2编程AT24C02A硬件地址是这样区分的:高四位(BIT4~7)为不可编程,AT24C02A是1010。
低四位(BIT0~3)中的BIT1~3,亦即A0~A2为可编程地址。
HJ-2G将A0~A2引脚连地,所以该AT24C02的硬件地址为1010000x。
利用Code Vision AVR C中断程序实现AVR单片机的TWI读写在AVR单片机的开发过程中,TWI(Two-Wire Interface)总线通信协议是较为常用的一种方式。
我们可以利用CodeVision AVR C中断程序来实现AVR单片机的TWI读写。
首先,我们需要了解一下TWI通信协议的基本原理。
TWI总线上有两条线路,一条为SCL时钟线,另一条为SDA数据线。
两条线路上都有上拉电阻,数据传输时由主机(如AVR单片机)控制SCL时钟线和SDA数据线的电平变化来进行数据传输。
在Code Vision AVR C中,我们可以利用TWI库函数进行TWI通信的操作。
在进行TWI读写时,我们需要先发送起始信号,然后发送设备地址和读写指令,接着进行数据传输,最后发送停止信号。
这一系列操作可以使用TWI库函数中的相应函数来完成。
在使用中断程序时,我们需要使TWI模块开启中断功能,并定义好中断服务程序。
在TWI传输过程中,当一个操作完成后,TWI模块会触发中断,执行相应的中断服务程序。
在中断服务程序中,我们可以处理TWI传输过程中出现的各种情况,如传输完成、传输错误等。
下面以TWI读取一个EEPROM芯片中的数据为例,说明如何利用Code Vision AVR C中断程序实现TWI读写。
假设EEPROM芯片的I2C地址为0xA0,我们需要读取从地址0x00开始的8个字节的数据。
首先,我们需要在程序的头文件中引用Code Vision AVR C提供的TWI库。
#include <mega328p.h>#include <delay.h>#include <twi.h>接着,定义TWI芯片地址和读写指令。
#define EEPROM_WRITE 0xA0#define EEPROM_READ 0xA1定义存储数据的缓冲区。
unsigned char EEPROM_Buffer[8];在主函数中初始化TWI模块,并开启TWI中断。
基于TWI接口实现A VR单片机主从机通信【摘要】Atmel公司的TWI(Two-wire Serial Interface)接口具有硬件实现简单、软件设计方便、运行可靠和成本低廉的优点。
提出了一种以A VR单片机ATmega8为平台基于TWI接口的主从机通信的实现方法,给出了该系统主从机进行通信的硬件结构和软件设计。
该方法对于简化硬件电路、提高系统可靠性、缩短开发周期、降低成本是可行的。
【关键词】A VR;TWI;主机模式;从机模式Master-slave Communication of A VR Microcontroller Based on TWI Interface ZHOU Yong-gang(The 41st Institute of China Electronics Technology Group Corporation,Qingdao Shandong,266555)【Abstract】The TWI interface of ATMEL Corporation has simple hardware,software design has the advantages of convenient,reliable operation and low cost. This article presents the design of master-slave communication system for A VR microcontroller based on TWI interface on the platform of ATmega8,give the structure of the system’s hardware and software desi gn. The method is feasible to simplify the hardware circuit,improve system reliability,shorten development cycles,reduce costs.【Key words】A VR;TWI;Master mode;Slave mode0 引言A VR ATmega系列单片机片内集成两线串行接口TWI模块。
TWI接口和TWI接口器件使用A VR单片机的很多型号也具有两线制接口,即TWI接口。
实际上TWI接口时序和常I2总线是兼容的。
我们这本书结合讲的单片机Atmega16就有这种接口。
这种接口的见的C使用也十分广泛。
比如本文会结合介绍的EEPROM A T24C64;MAXIM公司的温度传感器(查出型号);有的A/D转换器;菲利普还有专门的用这种总线的I/O扩展芯片。
TWI电路接线简单,占用I/O,并且可以很多期间共享一个总线,使用比较方便,系统也很简洁。
A VR单片机用硬件实现了这种总线的时序,省去了很多编程工作。
同时支持一条总线多个主设备的通讯。
我们只需要控制相关寄存器就能实现通过TWI传输数据。
很大程度I2的基本知上减少了我们的工作量,从而使代码更简洁,开发更容易。
下面我们会介绍CI2接口的EEPROM的例识,A VR的TWI接口的功能和使用,给出一个用TWI接口读写C子,最后给出适用于A VR-GCC编译器的示例程序。
I2总线的基本知识一、CI2总线的信号线有两条,一条是时钟线SCL,另一条是数据线SDA。
总线连接起来的时候,C需要两个上拉电阻,器件内部这两个信号引脚是集电极开路(或者是漏极开路)的。
这样总线上的器件只要有一个输出低电平总线就会被拉低(实际上就是所谓线与的逻辑),这主要用于总线仲裁。
I2总线上,有几个状态表示特殊的总线信号。
1.在C开始和停止信号时序如下图所示:图上可以看出,在SCL位高电平时SDA的变化将产生总线开始和停止信号。
SDA从高电平跳变到低电平表示开始,从低电平跳变到高电平表示停止。
数据的建立和有效:上图表示在传输数据时,SCL高电平的时候,SDA上的数据不能变化,因为前面已经说明,这是数据的变化将会认为是开始或者结束的信号。
在SCL低电平时数据可以改变。
2.主器件和从器件总线上可以有很多设备但是同时只能有一个主设备进行传输,从设备都有设备地址,当总线上的地址和从设备设置的地址一致时,传输在主设备和被寻址的从设备之间进行,其他设备相当于和总线分离。
1.单片机(AVR)EEPROM的读写分析由于AVR的EEPROM写周期比较长(一般为毫秒级),因此在编程使用过程中要特别注意.对于读EEPROM没什么好说的,读一个字节的数据要耗费4个时钟周期,可以忍受,写就比较麻烦了,虽然放在EEPROM的数据都不是频繁访问的;虽然可以用读-比较-写的机制降低EEPROM的写操作频度,但在写入过程中,过长的写入周期还是会造成一些问题,下面就分析一下几种方式的EEPROM写操作.1.1.循环查询式将地址和数据写入EEPROM相关的寄存器,置写标志后就循环不断查询写完成标志,直到写完成,退出循环,顺序执行其他程序.在置写入标志到写完成的这段时间,程序除了不断查询写完成标志和响应硬件中断之外什么也不干,这段时间就这么浪费了,如果是个实时性要求比较高的应用,浪费的就不仅是时间了,很可能在这段时间里I/O状态的变化不能得到及时的响应,如果一下子要进行多个字节的EEPROM的写入操作,那情况会更糟.针对这种情况,有种解决办法就是,这个写完成查询放在软件的死循环中(无操作系统的情况下,系统启动后都要进入一个死循环),每循环一次查询一次写完成标志,这样就不必一直等待写操作完成而可以干别的事情了,但是这样会带来两个问题.一是,如果死循环周期无法保证,则每一次的EEPROM写操作的完成标志查询也得不到保证,从而进行一次EEPROM写操作的周期也无法保证(最长延迟时间就是一个死循环周期的最长时间);二是,在执行一次EEPROM写入操作到写完成这段时间里死循环里其它的子程序不能进行EEPROM读写操作.第一个问题如果能保证最大循环周期在延迟允许范围内就不是问题了,否则就得采取下面讲到的方式二了;第二个问题的解决方法是每次EEPROM读写操作都要在其中加入对EEPROM写完成标志寄存器的判断,如果有数据正在写入,则等待或退出,但等待和退出又会造成等待延迟和写入操作不成功的问题,解决办法是先放入一个数据缓冲区,待上一次写入操作完成,再从缓冲区里拽出一个字节进行下一次写入周期,但这又会涉及到选择合适的缓冲区大小以及的问题,这个问题在下面中断式操作里继续讨论.1.2.定时查询式在写入EEPROM地址和数据寄存器置写标志后启动定时器,定时查询写完成标志,这种方法继承了方式1的大部分优缺点,唯一的进步就是能够确定写操作延迟时间为定时周期.1.3.中断式编写EEPROM写完成中断子程序,设置一个FIFO缓冲区,要写入的数据先放入这个FIFO,如果EEPROM操作空闲,则从FIFO中揪一字节数据出来写EEPROM,置完写完成标志就返回,然后去干别的事情,EEPROM写完成产生一个中断,在中断服务程序中再从FIFO中揪一个字节去写EEPROM,如此循环直到FIFO空,这种方式虽然能在第一时间完成一次写操作,但是还是有个和和方式1中提到一样的问题,怎么选择这个FIFO的大小.小了,待写入数据可能溢出丢失,大了,多大算大,1K够不够,10K够不够,硬件上有这么多RAM空间么,这就需要根据实际资源和系统需求来定了.如果EEPROM写操作频率很低,比一次EEPROM写操作间隔还要长得多,那么几个单位的FIFO就足够了;如果EEPROM写操作频率可能很高或一次大批量连续写入数据就要求很大的FIFO,达到能够保证数据不丢失,这就要根据批量数据大小和写频率来定了,另外还要考虑可用RAM空间的限制来找出最佳值,同时有FIFO溢出一定要制定相应的应对措施.最后注意两点:1.读写之前一定要检测是否已经有EEPROM写操作;在写完成之后一定要再读出写入的数据校验写入操作是否成功,不成功则EEPROM损坏,要做好应对措施.2.如果要保存一个16位的数据,一定要保证在这16位数据分成的两字节都成功写入了EEPROM再去读该数据,否则在刚写完一个字节就去读该数据,得到的数据显然不是你想要的,如果该数据是一重要参数,后果可想而知.。
TWI:是一种全双工的串行通讯协议,与I2C工作方式相同,由一条数据传输线SDL,一条时钟线SCL组成,对应单片机的外部引脚PC1,PC0。
由于只有两条总线,简化了系统设计。
特点:• 简单,但是强大而灵活的通讯接口,只需要两根线• 支持主机和从机操作• 器件可以工作于发送器模式或接收器模式• 7 位地址空间允许有128 个从机• 支持多主机仲裁• 高达400 kHz 的数据传输率• 斜率受控的输出驱动器• 可以抑制总线尖峰的噪声抑制器• 完全可编程的从机地址以及公共地址• 睡眠时地址匹配可以唤醒AVR主机:控制启动和停止传输的设备。
主机同时要产生SCL 时钟从机:被主机寻址的设备发送器:将数据放到总线上的设备接收器:从总线读取数据的设备TWI工作模式: 主机发送模式(MT)主机接收模式(MR)从机发送模式(ST)从机接收器模式(SR)例如,TWI 可用MT 模式给TWIEEPROM 写入数据,用MR 模式从EEPROM 读取数据。
如果系统中有其它主机存在,它们可能给TWI 发送数据,此时就可以用SR 模式。
应用程序决定采用何种模式。
模式状态缩写:S:START 状态Rs:REPEATED START 状态R:读一个比特(SDA 为高电平)W:写一个比特(SDA 为低电平)A:应答位(SDA 为低电平)A:无应答位(SDA 为高电平)Data:8 位数据P:STOP 状态SLA:从机地址一、主机模式:可以向从机发送数据,进入主机模式,首先发送一个START信号,接着的从机地址决定了进入MT还是MR模式主机发送(MT): S+SLA+W, 主机接收(MR): S+SLA+R1、波特率设置:TWBR=100;2、分频设置:TWSR|=(1<<TWPS1)|(1<<TWPS0);写过程:1)、发送启动信号:TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTA);2)、等待应答信号:while ((TWCR&(1<<TWINT))==0);3)、进入主机写模式:TWDR=0Xa0;// TWDR=SLA+W; //通过在TWDR中写入SLA+W进入主机写模式,SLA+R进入主机读模式TWCR=(1<<TWINT)|(1<<TWEN);while ((TWCR&(1<<TWINT))==0);//如果TWINT为0就原地等待4)、发送要写入的地址:TWDR=adress;TWCR=(1<<TWINT)|(1<<TWEN); //启动发送while(!(TWCR&(1<<TWINT)));5)、发送要写入的数据:TWDR=data;TWCR=(1<<TWINT)|(1<<TWEN); //启动发送while(!(TWCR&(1<<TWINT)));6)、发送停止信号:TWC(1<<TWINT)|(1<<TWEN)|(1<<TWST0); //写过程结束读过程:1)、启动信号:TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTA)while(!(TWCR&(1<<TWINT)));2)、进入主机写模式:TWDR=0Xa0;//TWDR=SLA+W;TWCR=(1<<TWINT)|(1<<TWEN);while(!(TWCR&(1<<TWINT)));3)、写入要读的地址:TWDR=address;TWCR=(1<<TWINT)|(1<<TWEN);while(!(TWCR&(1<<TWINT)));4)、发送启动信号:TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTA)while(!(TWCR&(1<<TWINT)));5)、进入主机读模式:TWDR=0Xa1 ;//TWDR=SLA+R;TWCR=(1<<TWINT)|(1<<TWEN);while(!(TWCR&(1<<TWINT)));6)、开始读数据:TWCR=(1<<TWINT)|(1<<TWEN);while(!(TWCR&(1<<TWINT)));temp=TWDR; //读取的数据送到变量temp中。
发送:1,设定数据传输波特率2,发送START信号,等待应答==》《== 应答信号3,发送芯片地址,等待应答==》《==应答信号4,发送数据的绝对地址,等待应答==》《==应答信号5,发送要写入的数据,等待应答==》《==应答信号6,发送STOP信号,释放总线==》数据写入成功接收:1,设定数据传输波特率2,发送START信号,等待应答==》《== 应答信号3,发送芯片地址,等待应答==》《==应答信号4,发送数据的绝对地址,等待应答 ==》《==应答信号5,发送RESTART信号,等待应答==》《==应答信号6,发送芯片地址并注明读操作,等待应答==》《==应答信号7,读取数据,等待应答==》《==应答信号8,发送STOP信号,释放总线==》数据读操作成功#include <iom16.h>#define uchar unsigned char#define uint unsigned int//########################################################### /*串口初始化函数*/void Uart_Init(void){UCSRB=(1<<RXEN)|(1<<TXEN)|(1<<RXCIE);//允许发送和接收UCSRC=(1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);//8位数据位+1位停止位UBRRH=0x00;//设置波特率寄存器低位字节UBRRL=47;//9600//设置波特率寄存器高位字节DDRD_Bit1=1;//配置TX为输出(很重要)}//########################################################### /*发送一个字符数据,查询方式*/void Uart_Transmit(uchar data){while(!(UCSRA&(1<<UDRE)));//while(UCSRA_UDRE==0); /* 等待发送缓冲器为空*/UDR = data; /* 发送数据*/}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#include <iom16.h>#include "IAR_DELAY.H"#define uchar unsigned char#define uint unsigned intvoid Uart_Init(void);void Uart_Transmit(uchar data);//变量声明#define EEPROM_BUS_ADDRESS 0xA0//器件地址/*从器件地址位定义:______________________________________-------------*//*AT24C02| 1 | 0 | 1 | 0 | A2 | A1 | A0 | R/~W |------------*///主机发送模式时各状态字的后续动作#define TW_START 0x08 //开始信号已发出#define TW_REP_START 0x10 //重复开始信号已发出#define TW_MT_SLA_ACK 0x18 //写字节已发出并受到ACK信号#define TW_MT_SLA_NACK 0x20 //写字节已发出并受到NACK信号#define TW_MT_DATA_ACK 0x28 //数据已发出并受到ACK 信号#define TW_MT_DATA_NACK 0x30 //数据已发出并受到NACK 信号#define TW_MT_ARB_LOST 0x38 //丢失总线控制权//主机接收模式时各状态字的后续动作#define TW_MR_ARB_LOST 0x38//丢失总线控制权,未收到应答信号#define TW_MR_SLA_ACK 0x40//读命令已发出并受到ACK#define TW_MR_SLA_NACK 0x48 //读命令已发出并受到NACK#define TW_MR_DATA_ACK 0x50 //数据已收到,ACK已发出#define TW_MR_DATA_NACK 0x58 //数据已收到,NACK已发出#define IIC_Start() TWCR =(1<<TWINT)|(1<<TWSTA)|(1<<TWEN)// TWINT位通过写1进行清零,一旦清零则TWI开始工作,当相应硬件工作完成后 TWINT位会重新置位为1//TWSTA位会让硬件在总线上产生一个START的信号,声明自己希望成为主机//TWEN位使能TWI功能,将PC0和PC1管脚切换到第二功能上来,如果清零则为中断TWI的传输#define IIC_Stop() TWCR =(1<<TWINT)|(1<<TWSTO)|(1<<TWEN) // TWSTO位在主机模式下,会让硬件在总线上产生一个STOP得信号,并且SCL和SDA两个引脚位高阻态#define IIC_Wait() while(!(TWCR&(1<<TWINT))) // TWINT位经过一次置位使硬件TWI开始工作,然后再检测TWCR寄存器的TWINT位是不是被置位,如果置位为1则表示工作完成可以向下进行//################################################################### ###########/*I2C总线单字节写入*/unsigned char twi_write(unsigned char addr, unsigned char dd){TWBR =10;//设定波特率/*start 启动*/IIC_Start();//硬件发送START信号,并且清零TWINT位,使能硬件TWI,使TWI 开始工作IIC_Wait();//等待发送START完成 TWINT位置位if((TWSR&0xF8)!=0x08) return 0;//检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回 0/*SLA_W 芯片地址*/TWDR=EEPROM_BUS_ADDRESS;//芯片地址0xA0,赋值给数据寄存器TWDR,等待发送TWCR=(1<<TWINT)|(1<<TWEN);//对控制寄存器TWCR的TWINT位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送TWDR寄存器中的数据IIC_Wait();//等待数据发送完毕TWINT重新置位if((TWSR&0xF8)!=0x18) return 0;//检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回 0/*addr 操作地址*/TWDR=addr;//将写入数据的绝对地址,赋值给数据寄存器TWDR,等待发送TWCR=(1<<TWINT)|(1<<TWEN);//对控制寄存器TWCR的TWINT位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送TWDR寄存器中的数据IIC_Wait();//等待数据发送完毕TWINT重新置位if((TWSR&0xF8)!=0x28) return 0;//检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回0/*dd 写入数据*/TWDR=dd;//将要写入的数据,赋值给数据寄存器 TWDR,等待发送TWCR=(1<<TWINT)|(1<<TWEN);//对控制寄存器TWCR的TWINT位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送TWDR寄存器中的数据IIC_Wait();//等待数据发送完毕 TWINT重新置位if ((TWSR & 0xF8) != 0x28) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回 0/*stop 停止*/IIC_Stop();//数据传输完成,发送STOP信号,释放对总线的控制return 1;//写入数据成功,返回1 ,用来判断是否成功写入数据}//################################################################### /*I2C总线单字节读取*/unsigned char twi_read(unsigned char addr){unsigned char Receive_Byte;TWBR=2;//设定波特率/*start 启动*/IIC_Start(); //硬件发送START信号,并且清零TWINT位,使能硬件TWI,使TWI开始工作IIC_Wait(); //等待发送START完成 TWINT位置位if ((TWSR & 0xF8) != 0x08) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回 0/*SLA_W 芯片地址*/TWDR = EEPROM_BUS_ADDRESS; //芯片地址 0xA0 ,赋值给数据寄存器 TWDR ,等待发送TWCR=(1<<TWINT)|(1<<TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送 TWDR寄存器中的数据IIC_Wait(); //等待数据发送完毕 TWINT重新置位if ((TWSR & 0xF8) != 0x18) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回 0/*addr 操作地址*/TWDR=addr;//将写入数据的绝对地址,赋值给数据寄存器 TWDR ,等待发送TWCR=(1<<TWINT)|(1<<TWEN);//对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送 TWDR寄存器中的数据IIC_Wait();//等待数据发送完毕 TWINT重新置位if ((TWSR & 0xF8) != 0x28) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回0/*restart 重启动*/IIC_Start();//硬件发送 RESTART 信号,并且清零TWINT位,使能硬件TWI,使TWI开始工作IIC_Wait();//等待数据发送完毕 TWINT重新置位if((TWSR&0xF8)!=0x10) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回0/*SLA_R 芯片地址*/TWDR = 0xA1; //芯片地址 0xA0 并注明是读取操作(最后一位为1),赋值给数据寄存器 TWDR ,等待发送TWCR=(1<<TWINT)|(1<< TWEN);//对控制寄存器TWCR的TWINT位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送TWDR寄存器中的数据IIC_Wait();//等待数据发送完毕TWINT重新置位if((TWSR&0xF8)!=0x40) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回0/*读取数据*/TWCR=(1<<TWINT)|(1<<TWEN);//对控制寄存器TWCR的TWINT位软件写1进行清零,然后使能TWI硬件接口,让TWI进行工作,发送TWDR寄存器中的数据IIC_Wait();//等待数据发送完毕TWINT重新置位if((TWSR&0xF8)!=0x58) return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回0Receive_Byte = TWDR; //读取到的数据放到局部变量里/*stop 停止*/IIC_Stop();//数据传输完成,发送STOP信号,释放对总线的控制return Receive_Byte; //将读取到的数据作为函数的输出}//################################################################### /*主函数*/void main(void){uchar c,d;Uart_Init();//串口初始化delay_us(20);Uart_Transmit(0x55); //测试串口c = twi_write(0x51,0xf8); //在地址0x51里写入数据0x22Uart_Transmit(c); //将返回值发送到串口测试是否写入成功delay_ms(2);d = twi_read(0x51); //将地址0x51里的数据读出来Uart_Transmit(d); //将读取到的数据发送串口while(1);}。