这个简单例子是这样的:
总线上只设一个主机,主机使用查询读写
总线上允许有多个从机,从机使用硬件中断功能。
*****************
我开始是把主机和从机都使用中断功能的,但是,我发现主机很容易受到线路的干扰而“TWI死机”(使用导线一端接地,另外一端不断的摩擦短接TWI的通讯线路),死机时,TWI的TWINT不再置位中断标记,致使中断程序无法运行,即使把通讯线路物理截断(单保留主机和上拉电阻),TWINT也无法置位。这时候,你的中断代码再完善都没有用了,也谈不上中断后判断仲裁成败的问题了,也就是说,这个AVR(ATMEGA48)硬件TWI可能有点问题,当然,也不排除我菜的缘故。
为了抗干扰,我使用这个办法,主机每次读写完毕后,都退出TWI功能一次。从机暂时没有发现死机现象,但是,为了保险,建议从机不断的检测总线是否被占用,一旦占用超时,也退出TWI一次,然后再进入待机状态
/*****************************************************
CodeWizardAVR V1.25.9 ProfessionalChip type : ATmega48
Clock frequency : 8.000000 MHz
Memory model : Small
External SRAM size : 0
Data Stack size : 128
*****************************************************/
// TWCR 寄存器说明
// Bit 7 – TWINT: TWI 中断标志
// Bit 6 – TWEA: 使能TWI 应答
// Bit 5 – TWSTA: TWI START 状态位
// Bit 4 – TWSTO: TWI STOP 状态位
// Bit 3 – TWWC: TWI 写冲突标志
// Bit 2 – TWEN: TWI 使能
// Bit 1 – Res: 保留
// Bit 0 – TWIE: TWI 中断使能
#include <mega48.h>
#include <delay.h>
#include <nokia3310.h>
#define iic_time 65535 //主机在单位时间内判断运行是否正常
#define iic_init TWCR=0b11000101; //待机
#define iic_start_run TWCR=0b10100100; //主机发出start信号
#define iic_byte_run TWCR=0b10000100; //清除中断.启动.停止的标记/启动主机数据收或发
#define iic_byte_run_ack TWCR=0b11000100; //清除中断.启动.停止的标记/启动主机数据收或发/应答使能
#define iic_stop_run TWCR=0b10010100; //主机停止/释放总线/TWI就绪
#define iic_off TWCR=0b10000000; //强制退出TWI,适用于总线受干扰时主机使用
#define iic_stop_no TWCR&16 //发送STOP是否结束
#define iic_int_ok TWCR>127 //中断标记置位
#define iic_status TWSR //TWI运行状态
#define iic_datas TWDR
unsigned char iic_news[10]; //数据量
unsigned int iic_n; //通讯数据量计数
interrupt [TWI] void twi_isr(void) //从机twi中断程序/请在相关位置处理被读写的数据
{
lcd_puthex(TWSR); //LCD监控
switch(iic_status)
{
case 0x60: //自己的SLA+W已经被接收ACK已返回
{
iic_n=0;
iic_init;
break;
}
case 0x70: //接收到广播地址ACK已返回
{
iic_n=0;
iic_init;
break;
}
case 0x80: //以前以自己的SLA+W被寻址,数据已经被接收,ACK已返回
{
iic_news[iic_n]=iic_datas;
iic_n++;
iic_init;
break;
}
case 0xA0: //在以从机工作时接收到STOP或重复START
{
iic_init;
break;
}
case 0xA8: //自己的SLA+R已经被接收ACK已返回
{
iic_n=0;
iic_datas=iic_news[iic_n];
iic_init;
break;
}
case 0xB8: //数据已经发送接收到ACK
{
iic_n++;
iic_datas=iic_news[iic_n];
iic_init;
break;
}
case 0xC0: //数据已经发送接收到NOT_ACK
{
iic_init;
break;
}
case 0x90: //以前以广播方式被寻址,数据已经被接收ACK已返回
{
iic_news[iic_n]=iic_datas;
iic_init;
iic_n++;
break;
}
default:
{
iic_off;
iic_init;
break;
}
}
}
/*/从机*********************************************************************
void main(void)
{
unsigned int n;
lcd_init();
lcd_cls();
TWBR=32; //8MHz/(16+2*32)=100KHz
TWAR=2+1; //设定自己的地址,广播接收使能
iic_init;
#asm("sei") //打开全局中断
while (1)
{
if(PINC.5==0 || PINC.4==0) if(n<65535) n++; //检测线路是否被长久占用/防止TWI死机
if(PINC.5==1 && PINC.4==1) n=0;
if(n==65535) {iic_off;iic_init;}
}
}
//*************************************************************************/
//主机写(不含STOP信号)/查询方式/参数:从机地址.数量.数组
unsigned char iic_write(unsigned char address, unsigned char n, unsigned char *p)
{
unsigned char i;
unsigned int a;
if(address%2 || n<1) return(4); //参数不对返回4
iic_start_run; //发送START
for(a=0; a<iic_time; a++) if(iic_int_ok) break; //简单的限时等待
if(iic_status!=0x08 && iic_status!=0x10) return(1); //如果(重复)START失败就返回1
iic_datas=address; iic_byte_run; //发送地址SLA+W
for(a=0; a<iic_time; a++) if(iic_int_ok) break;
if(iic_status!=0x18) return(2); //SLA+W发送失败就返回2
for(i=0;i<n;i++) //发送数据
{
iic_datas=p[i];
iic_byte_run;
for(a=0; a<iic_time; a++) if(iic_int_ok) break;
if(iic_status!=0x28) return(3); //数据发送失败就返回3
}
return(0); //成功就返回0
}
//主机读(不含STOP信号)/查询方式/参数:从机地址.数量.数组
unsigned char iic_read(unsigned char address, unsigned char n, unsigned char *p)
{
unsigned char i;
unsigned int a;
if(address%2 || n<1) return(4); //参数不对返回4
iic_start_run; //发送START
for(a=0; a<iic_time; a++) if(iic_int_ok) break; //简单的限时等待
if(iic_status!=0x08 && iic_status!=0x10) return(1); //如果(重复)START失败就返回1
iic_datas=address+1; iic_byte_run; //发送地址SLA+R
for(a=0; a<iic_time; a++) if(iic_int_ok) break;
if(iic_status!=0x40) return(2); //SLA+R发送失败就返回2
for(i=0;i<n;i++) //读取数据
{
if(i+1<n) iic_byte_run_ack; else iic_byte_run;
for(a=0; a<iic_time; a++) if(iic_int_ok) break;
if(iic_status!=0x50 && iic_status!=0x58) return(3); //数据读取失败就返回3
p[i]=iic_datas;
}
return(0); //成功就返回0
}
void iic_stop(void) //主机发送STOP
{
unsigned int a;
iic_stop_run;
for(a=0; a<iic_time; a++) if(iic_stop_no); else break;
}
//主机*********************************************************************
void main(void)
{
unsigned char iic_ok;
iic_news[0]=0xAA;iic_news[1]=0xBB;iic_news[2]=0xCC;iic_news[3]=0xDD;
lcd_init();
lcd_cls();
TWBR=32; //8MHz/(16+2*32)=100KHz
while (1)
{
lcd_gotoxy(0,0);
//请更新需要写的数据
iic_ok=iic_write(0,4,iic_news); //成功返回0/(地址,数据量,数组)
lcd_puthex(iic_ok);
//请更新需要写的数据
iic_ok=iic_write(2,4,iic_news);
lcd_puthex(iic_ok);
iic_ok=iic_read(2,4,iic_news);
lcd_puthex(iic_ok);
//请保存读取的数据
DDRD=255;PORTD=PORTD^255; //LED观测/正常时LED闪很快而看不见/不正常时闪很慢
iic_stop();
iic_off; //关闭.复位TWI,目的是防止线路受干扰而无法置位中断标记
}
}