AVR TWI读写范例程序

来源:本站
导读:目前正在解读《AVR TWI读写范例程序》的相关信息,《AVR TWI读写范例程序》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《AVR TWI读写范例程序》的详细说明。
简介:本程序简单的示范了如何使用ATMEGA16的TWI 读写AT24C02 IIC EEPROM
TWI协议(即IIC协议,请认真参考IIC协议的内容,否则根本就不能掌握) 一主多从的应用,M16作主机,中断模式:AVR的速度很高,而IIC的速度相对较低,采用查询模式会长时间独占CPU,令CPU的利用率明显下降。特别是IIC速度受环境影响只能低速通讯时,对系统的实时性产生严重的影响。

AT24C02/04/08的操作特点

出于简化程序考虑,各种数据没有对外输出,学习时建议使用JTAG ICE硬件仿真器

*/

#include <avr/io.h>

#include <avr/signal.h>

#include <avr/interrupt.h>

#include <avr/delay.h>

//时钟定为外部晶振7.3728MHz,F_CPU=7372800

#include <compat/twi.h>

//定义了各种模式下的状态码列表(TWSR已屏蔽预分频位),本文后面附上中文描述

//管脚定义

#define pinSCL 0 //PC0 SCL

#define pinSDA 1 //PC1 SDA

//为保险起见,最好在SCL/SDA接上1~10K的外部上拉电阻到VCC。

#define fSCL 100000 //TWI时钟为100KHz

//预分频系数=1(TWPS=0)

#if F_CPU < fSCL*36

#define TWBR_SET 10; //TWBR必须大于等于10

#else

#define TWBR_SET (F_CPU/fSCL-16)/2; //计算TWBR值

#endif

#define TW_ACT (1<<TWINT)|(1<<TWEN)|(1<<TWIE)

//TWCR只能IN/OUT,直接赋值比逻辑运算(|= &=)更节省空间

#define SLA_24CXX 0xA0 //24Cxx系列的厂商器件地址(高四位)

#define ADDR_24C02 0x00

// AT24C02的地址线A2/1/0全部接地,SLAW=0xA0+0x00<<1+0x00,SLAR=0xA0+0x00<<1+0x01

//TWI_操作状态

#define TW_BUSY 0

#define TW_OK 1

#define TW_FAIL 2

//TWI_读写命令状态

#define OP_BUSY 0

#define OP_RUN 1

//TWI读写操作公共步骤

#define ST_FAIL 0 //出错状态

#define ST_START 1 //START状态检查

#define ST_SLAW 2 //SLAW状态检查

#define ST_WADDR 3 //ADDR状态检查

//TWI读操作步骤

#define ST_RESTART 4 //RESTART状态检查

#define ST_SLAR 5 //SLAR状态检查

#define ST_RDATA 6 //读取数据状态检查,循环n字节

//TWI写操作步骤

#define ST_WDATA 7 //写数据状态检查,循环n字节

#define FAIL_MAX 20 //重试次数最大值

//定义全局变量

unsigned char ORGDATA[8]=

{0xAA,0xA5,0x55,0x5A,0x01,0x02,0x03,0x04}; //原始数据

unsigned char CMPDATA[8]; //比较数据

unsigned char BUFFER[256]; //缓冲区,可以装载整个AC24C02的数据

struct str_TWI //TWI数据结构

{

volatile unsigned char STATUS; //TWI_操作状态

unsigned char SLA; //从设备的器件地址

unsigned int ADDR; //从设备的数据地址

unsigned char *pBUF; //数据缓冲区指针

unsigned int DATALEN; //数据长度

unsigned char STATE; //TWI读写操作步骤

unsigned char FAILCNT; //失败重试次数

};

struct str_TWI strTWI; //TWI的数据结构变量

//仿真时在watch窗口,监控这些全局变量。

//AT24C02的读写函数(包括随机读,连续读,字节写,页写)

//根据sla的最低位决定(由中断程序中判断)

//bit0=1 TW_READ 读

//bit0=0 TW_WRITE 写

// sla 器件地址(不能搞错)

// addr EEPROM地址(0~1023)

// *ptr 读写数据缓冲区

// len 读数据长度(1~1024),写数据长度(1 or 8 or 16)

// 返回值 是否能执行当前操作

unsigned char TWI_RW(unsigned char sla,unsigned int addr,unsigned char *ptr,unsigned int len)

{

unsigned char i;

if (strTWI.STATUS==TW_BUSY)

{//TWI忙,不能进行操作

return OP_BUSY;

}

strTWI.STATUS=TW_BUSY;

i=(addr>>8)<<1;

i&=0x06; //考虑了24C04/08的EEPROM地址高位放在SLA里面

strTWI.SLA=sla+i;

strTWI.ADDR=addr;

strTWI.pBUF=ptr;

strTWI.DATALEN=len;

strTWI.STATE=ST_START;

strTWI.FAILCNT=0;

TWCR=(1<<TWSTA)|TW_ACT; //启动start信号

return OP_RUN;

}

/*

TWI中断函数

这个函数流程只是考虑了器件地址后有一个字节数据(命令)地址的IIC器件

(大部分IIC接口器件都是这种类型,常见的例如AT24C01/02/04/08/16,DS1307,DS1721等)

对于有两个字节数据地址的IIC器件(例如AT24C32/64/128/256等大容量EEPROM),请稍作改动

//根据strTWI.SLA的最低位决定

//bit0=1 TW_READ 读

//bit0=0 TW_WRITE 写

虽然中断服务程序很长,但每次只执行一个 case,所以耗时并不长。

*/

SIGNAL(SIG_2WIRE_SERIAL)

{//IIC中断

unsigned char action,state,status;

action=strTWI.SLA&TW_READ; //取操作模式

state=strTWI.STATE;

status=TWSR&0xF8; //屏蔽预分频位

if ((status>=0x60)||(status==0x00))

{//总线错误或从机模式引发的中断,不予处理

return;

}

switch(state)

{

case ST_START: //START状态检查

if(status==TW_START)

{//发送start信号成功

TWDR=strTWI.SLA&0xFE; //发送器件地址写SLAW

TWCR=TW_ACT; //触发下一步动作,同时清start发送标志

}

else

{//发送start信号出错

state=ST_FAIL;

}

break;

case ST_SLAW: //SLAW状态检查

if(status==TW_MT_SLA_ACK)

{//发送器件地址成功

TWDR=strTWI.ADDR; //发送eeprom地址

TWCR=TW_ACT; //触发下一步动作

}

else

{//发送器件地址出错

state=ST_FAIL;

}

break;

case ST_WADDR: //ADDR状态检查

if(status==TW_MT_DATA_ACK)

{//发送eeprom地址成功

if (action==TW_READ)

{//读操作模式

TWCR=(1<<TWSTA)|TW_ACT; //发送restart信号,下一步将跳到RESTART分支

}

else

{//写操作模式

TWDR=*strTWI.pBUF++; //写第一个字节

strTWI.DATALEN--;

state=ST_WDATA-1; //下一步将跳到WDATA分支

TWCR=TW_ACT; //触发下一步动作

}

}

else

{//发送eeprom地址出错

state=ST_FAIL;

}

break;

case ST_RESTART: //RESTART状态检查,只有读操作模式才能跳到这里

if(status==TW_REP_START)

{//发送restart信号成功

TWDR=strTWI.SLA; //发器件地址读SLAR

TWCR=TW_ACT; //触发下一步动作,同时清start发送标志

}

else

{//重发start信号出错

state=ST_FAIL;

}

break;

case ST_SLAR: //SLAR状态检查,只有读操作模式才能跳到这里

if(status==TW_MR_SLA_ACK)

{//发送器件地址成功

if (strTWI.DATALEN--)

{//多个数据

TWCR=(1<<TWEA)|TW_ACT; //设定ACK,触发下一步动作

}

else

{//只有一个数据

TWCR=TW_ACT; //设定NAK,触发下一步动作

}

}

else

{//发送器件地址出错

state=ST_FAIL;

}

break;

case ST_RDATA: //读取数据状态检查,只有读操作模式才能跳到这里

state--; //循环,直到读完指定长度数据

if(status==TW_MR_DATA_ACK)

{//读取数据成功,但不是最后一个数据

*strTWI.pBUF++=TWDR;

if (strTWI.DATALEN--)

{//还有多个数据

TWCR=(1<<TWEA)|TW_ACT; //设定ACK,触发下一步动作

}

else

{//准备读最后一个数据

TWCR=TW_ACT; //设定NAK,触发下一步动作

}

}

else if(status==TW_MR_DATA_NACK)

{//已经读完最后一个数据

*strTWI.pBUF++=TWDR;

TWCR=(1<<TWSTO)|TW_ACT; //发送停止信号,不会再产生中断了

strTWI.STATUS=TW_OK;

}

else

{//读取数据出错

state=ST_FAIL;

}

break;

case ST_WDATA: //写数据状态检查,只有写操作模式才能跳到这里

state--; //循环,直到写完指定长度数据

if(status==TW_MT_DATA_ACK)

{//写数据成功

if (strTWI.DATALEN)

{//还要写

TWDR=*strTWI.pBUF++;

strTWI.DATALEN--;

TWCR=TW_ACT; //触发下一步动作

}

else

{//写够了

TWCR=(1<<TWSTO)|TW_ACT; //发送停止信号,不会再产生中断了

strTWI.STATUS=TW_OK;

//启动写命令后需要10ms(最大)的编程时间才能真正的把数据记录下来

//编程期间器件不响应任何命令

}

}

else

{//写数据失败

state=ST_FAIL;

}

break;

default:

//错误状态

state=ST_FAIL;

break;

}

if (state==ST_FAIL)

{//错误处理

strTWI.FAILCNT++;

if (strTWI.FAILCNT<FAIL_MAX)

{//重试次数未超出最大值,

TWCR=(1<<TWSTA)|TW_ACT; //发生错误,启动start信号

}

else

{//否则停止

TWCR=(1<<TWSTO)|TW_ACT; //发送停止信号,不会再产生中断了

strTWI.STATUS=TW_FAIL;

}

}

state++;

strTWI.STATE=state; //保存状态

}

int main(void)

{

unsigned char i;

//上电默认DDRx=0x00,PORTx=0x00 输入,无上拉电阻

PORTA=0xFF; //不用的管脚使能内部上拉电阻。

PORTB=0xFF;

PORTC=0xFF; //SCL,SDA使能了内部的10K上拉电阻

PORTD=0xFF;

//TWI初始化

TWSR=0x00; //预分频=0^4=1

TWBR=TWBR_SET;

TWAR=0x00; //主机模式,该地址无效

TWCR=0x00; //关闭TWI模块

sei(); //使能全局中断

strTWI.STATUS=TW_OK;

TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_WRITE,0x10,&ORGDATA[0],8);

//从0x10地址开始写入8个字节数据

while(strTWI.STATUS==TW_BUSY); //等待操作完成

if (strTWI.STATUS==TW_FAIL)

{

//操作失败?

}

_delay_ms(10); //延时等待编程完成

while(1)

{

i=TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_READ,0x10,&CMPDATA[0],8);

//从0x10地址开始读出8个字节数据

while(strTWI.STATUS==TW_BUSY); //等待操作完成

//如果不加等待,则需要检测返回值i才能知道当前操作是否执行了

// 0 OP_BUSY 之前的操作没完成,没执行当前操作

// 1 OP_RUN 当前操作执行中

if (strTWI.STATUS==TW_FAIL)

{

//操作失败?

}

//读取成功,对比ORGDATA和CMPDATA的数据

i=TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_READ,0x00,&BUFFER[0],256);

//从0x00地址开始读出256个字节数据(整个ATC24C02)

while(strTWI.STATUS==TW_BUSY); //等待操作完成

};

}

/*

两线串行接口总线定义

两线接口TWI很适合于典型的处理器应用。

TWI协议允许系统设计者只用两根双向传输线就可以将128个不同的设备互连到一起。

这两根线一是时钟SCL,一是数据SDA。外部硬件只需要两个上拉电阻,每根线上一个。

所有连接到总线上的设备都(必须)有自己的地址。

注意:就是说不能有两个相同地址的设备

TWI协议解决了总线仲裁的问题。

所有 TWI 兼容的器件的总线驱动都是漏极开路或集电极开路的。这样就实现了对接口操作非常关键的线与功能。

TWI器件输出为"0”时,TWI总线会产生低电平。

当所有的TWI器件输出为三态时,总线会输出高电平,允许上拉电阻将电压拉高。

注意:为保证所有的总线操作,凡是与TWI 总线连接的AVR 器件必须上电。

与总线连接的器件数目受如下条件限制:

总线电容要低于400pF,而且可以用7 位从机地址进行寻址。

两个不同的规范,一种是总线速度低于100 kHz,而另外一种是总线速度高达400 kHz。

SCL和SDA引脚

SCL与SDA为MCU的 TWI接口引脚。

引脚的输出驱动器包含一个波形斜率限制器以满足TWI 规范。

引脚的输入部分包括尖峰抑制单元以去除小于50ns 的毛刺。

当相应的端口设置为SCL与SDA引脚时,可以使能I/O口内部的10K上拉电阻,这样可省掉外部的上拉电阻

注意:如果要作高速通讯或者从机数量较多,最好还是外接合适的上拉电阻

比特率发生器单元

TWI工作于主机模式时,比特率发生器控制时钟信号SCL的周期。

具体由TWI状态寄存器TWSR的预分频系数以及比特率寄存器TWBR设定。

当TWI工作在从机模式时,不需要对比特率或预分频进行设定,但从机的CPU时钟频率必须大于TWI时钟线SCL频率的16倍。

注意,从机可能会延长SCL 低电平的时间,从而降低TWI 总线的平均时钟周期。

SCL的频率根据以下的公式产生:

fSCL=fCPU/((16+2(TWBR)(4^TWPS))

TWBR = TWI比特率寄存器的数值

TWPS = TWI状态寄存器预分频的数值

Note:TWI 工作在主机模式时,TWBR 值应该不小于10,否则主机会在SDA 与 SCL 产生错误输出作为提示信号。

问题出现于TWI 工作在主机模式下,向从机发送Start + SLA + R/W 的时候(不需要真的有从机与总线连接)。

控制单元

控制单元监听TWI 总线,并根据 TWI 控制寄存器TWCR 的设置作出相应的响应。

当TWI总线上产生需要应用程序干预处理的事件时,TWI 中断标志位TWINT 置位。

在下一个时钟周期, TWI 状态寄存器TWSR 被表示这个事件的状态码字所更新。

在其它时间里,TWSR 的内容为一个表示无事件发生的特殊状态字。

一旦TWINT 标志位置"1”,时钟线SCL 即被拉低,暂停TWI 总线上的数据传输,让用户程序处理事件。

在下列状况出现时, TWINT 标志位置位:

? 在TWI 传送完START/REPEATED START 信号之后

? 在TWI 传送完SLA+R/W 数据之后

? 在TWI 传送完地址字节之后

? 在TWI 总线仲裁失败之后

? 在TWI 被主机寻址之后( 广播方式或从机地址匹配)

? 在TWI 接收到一个数据字节之后

? 作为从机工作时, TWI 接收到STOP 或REPEATED START 信号之后

? 由于非法的START 或STOP 信号造成总线错误时

TWI 寄存器说明

TWI 比特率寄存器- TWBR

? Bits 7..0 – TWI 比特率寄存器

TWBR 为比特率发生器分频因子。

比特率发生器是一个分频器,在主机模式下产生SCL时钟频率。

比特率计算公式请见前面的[比特率发生器单元]

TWI 控制寄存器- TWCR

TWCR 用来控制TWI操作。

它用来使能TWI,通过施加START到总线上来启动主机访问,产生接收器应答,产生STOP 状态,以及在写入数据到TWDR 寄存器时控制总线的暂停等。

这个寄存器还可以给出在TWDR 无法访问期间,试图将数据写入到TWDR 而引起的写入冲突信息。

? Bit 7 – TWINT: TWI 中断标志

当TWI 完成当前工作,希望应用程序介入时TWINT 置位。

若SREG 的I 标志以及TWCR寄存器的TWIE 标志也置位,则MCU 执行TWI 中断例程。

当TWINT 置位时, SCL信号的低电平被延长。

TWINT 标志的清零必须通过软件写"1” 来完成。

执行中断时硬件不会自动将其改写为"0”。

要注意的是,只要这一位被清零,TWI 立即开始工作。

因此,在清零TWINT 之前一定要首先完成对地址寄存器TWAR,状态寄存器TWSR,以及数据寄存器TWDR 的访问。

? Bit 6 – TWEA: 使能TWI 应答

TWEA 标志控制应答脉冲的产生。

若TWEA 置位,出现如下条件时接口发出ACK 脉冲:

1. 器件的从机地址与主机发出的地址相符合

2. TWAR 的TWGCE 置位时接收到广播呼叫

3. 在主机/ 从机接收模式下接收到一个字节的数据

将TWEA 清零可以使器件暂时脱离总线。

置位后器件重新恢复地址识别。

? Bit 5 – TWSTA: TWI START 状态标志

当CPU 希望自己成为总线上的主机时需要置位TWSTA。

TWI 硬件检测总线是否可用。

若总线空闲,接口就在总线上产生START 状态。

若总线忙,接口就一直等待,直到检测到一个STOP 状态 ,然后产生START 以声明自己希望成为主机。

发送START之后软件必须清零TWSTA。

? Bit 4 – TWST TWI STOP 状态标志

在主机模式下,如果置位TWSTO,TWI 接口将在总线上产生STOP 状态,然后TWSTO自动清零。

在从机模式下,置位TWSTO 可以使接口从错误状态恢复到未被寻址的状态。

此时总线上不会有STOP 状态产生,但TWI 返回一个定义好的未被寻址的从机模式且释放SCL 与SDA 为高阻态。

? Bit 3 – TWWC: TWI 写碰撞标志

当TWINT 为低时写数据寄存器TWDR 将置位TWWC。

当TWINT 为高时,每一次对TWDR 的写访问都将更新此标志。

? Bit 2 – TWEN: TWI 使能

TWEN 位用于使能TWI操作与激活TWI接口。

当TWEN位被写为"1”时,TWI引脚将I/O引脚切换到SCL 与SDA 引脚,使能波形斜率限制器与尖峰滤波器。

如果该位清零, TWI接口模块将被关闭,所有TWI 传输将被终止。

? Bit 0 – TWIE: 使能TWI 中断

当SREG 的I 以及TWIE 置位时,只要TWINT 为"1”, TWI 中断就激活。

TWI 状态寄存器- TWSR

? Bits 7..3 – TWS: TWI 状态

这5位用来反映TWI 逻辑和总线的状态。

不同的状态代码将会在后面的部分描述。

注意从TWSR 读出的值包括5 位状态值与2 位预分频值。

检测状态位时设计者应屏蔽预分频位为"0”。这使状态检测独立于预分频器设置。

? Bits 1..0 – TWPS: TWI 预分频位

这两位可读/ 写,用于控制比特率预分频因子。

预分频系数为4的n次方

计算比特率的公式见前面的[比特率发生器单元]

TWI 数据寄存器- TWDR

在发送模式, TWDR 包含了要发送的字节;

在接收模式, TWDR 包含了接收到的数据。

当TWI 接口没有进行移位工作(TWINT 置位) 时这个寄存器是可写的。

在第一次中断发生之前用户不能够初始化数据寄存器。

只要TWINT 置位,TWDR 的数据就是稳定的。

在数据移出时,总线上的数据同时移入寄存器。

TWDR 总是包含了总线上出现的最后一个字节,除非MCU 是从掉电或省电模式被TWI 中断唤醒。此时TWDR 的内容没有定义。

总线仲裁失败时,主机将切换为从机,但总线上出现的数据不会丢失。

ACK 的处理由 TWI逻辑自动管理, CPU 不能直接访问ACK。

? Bits 7..0 – TWD: TWI 数据寄存器

根据状态的不同,其内容为要发送的下一个字节,或是接收到的数据。

TWI(从机) 地址寄存器-TWAR

TWAR 的高7 位为从机地址。

工作于从机模式时,TWI 将根据这个地址进行响应。

主机模式不需要此地址。

在多主机系统中, TWAR需要进行设置以便其他主机访问自己。

TWAR 的LSB 用于识别广播地址 (0x00)。

器件内有一个地址比较器。一旦接收到的地址和本机地址一致,芯片就请求中断。

? Bits 7..1 – TWA: TWI 从机地址寄存器

其值为从机地址。

? Bit 0 – TWGCE: 使能TWI 广播识别

置位后MCU 可以识别TWI 总线广播。

使用TWI

AVR的TWI接口是面向字节和基于中断的。

所有的总线事件,如接收到一个字节或发送了一个START 信号等,都会产生一个TWI 中断。

由于TWI 接口是基于中断的,因此TWI接口在字节发送和接收过程中,不需要应用程序的干预。

TWCR寄存器的TWI中断允许位[TWIE]和全局中断允许位[I]一起决定了应用程序是否响应TWINT标志位产生的中断请求。

如果TWIE 被清零,应用程序只能采用轮询TWINT 标志位的方法来检测TWI 总线状态。

当TWINT 标志位置"1” 时,表示TWI 接口完成了当前的操作,等待应用程序的响应。

在这种情况下,TWI 状态寄存器TWSR 包含了表明当前TWI 总线状态的值。

应用程序可以读取TWCR 的状态码,判别此时的状态是否正确,并通过设置TWCR 与TWDR 寄存器,决定在下一个TWI 总线周期TWI 接口应该如何工作。

各种模式下的状态码列表(TWSR已屏蔽预分频位)

twi.h里面有定义,现附上中文描述

主机发送状态码

#define TW_START 0x08 //START已发送

#define TW_REP_START 0x10 //重复START已发送

#define TW_MT_SLA_ACK 0x18 //SLA+W 已发送收到ACK

#define TW_MT_SLA_NACK 0x20 //SLA+W 已发送接收到NOT ACK

#define TW_MT_DATA_ACK 0x28 //数据已发送接收到ACK

#define TW_MT_DATA_NACK 0x30 //数据已发送接收到NOT ACK

#define TW_MT_ARB_LOST 0x38 //SLA+W 或数据的仲裁失败

从发送状态码

#define TW_ST_SLA_ACK 0xA8 //自己的SLA+R 已经被接收ACK 已返回

#define TW_ST_ARB_LOST_SLA_ACK 0xB0 //SLA+R/W 作为主机的仲裁失败;自己的SLA+R 已经被接收ACK 已返回

#define TW_ST_DATA_ACK 0xB8 //TWDR 里数据已经发送接收到ACK

#define TW_ST_DATA_NACK 0xC0 //TWDR 里数据已经发送接收到NOT ACK

#define TW_ST_LAST_DATA 0xC8 //TWDR 的一字节数据已经发送(TWAE = “0”);接收到ACK

AT24C02/04/08 IIC接口EEPROM的特点

(不同公司的24系列EEPROM特性有部分不同,请参考数据手册)

1 AT24C02/04/08 是一个2K/4K/8K位串行CMOS E2PROM 内部含有256/512/1024 个8位字节

2 AT24C02有一个8 字节页写缓冲器,AT24C04/08/16 有一个16字节页写缓冲器

3 通过器件地址输入端A0,A1,A2可以实现将最多

8个24C02器件

4个24C04器件

2个24C08器件

提醒:《AVR TWI读写范例程序》最后刷新时间 2024-03-14 01:09:31,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《AVR TWI读写范例程序》该内容的真实性请自行鉴别。