AVR MEGA16单片机有一个TWI总线接口,是基于IIC(说白了就是IIC加强版),基于TWI实现AT2402的访问网上有很多资料啊,但多机通信的不多,这里详细说明编写过程,本文在参考了多篇资料的基础上写的。在GCCAVR平台实现
1.TWI基本说明
TWI接口是两线制的串口,与I2C接口一样,2线的名称都是SDA(串行数据地址线)和SCL(串行时钟线)。在与设备相连时,2线都需要有上拉电阻相连
。如果设备输出为高阻时,上拉电阻将电平拉高。
所有连接到总线的设备有自己的地址。
面向字节和基于中断的,总线仲裁
在TWI总线协议中,SCL线是高电平时,SDA线的“下降沿”表示一个数据帧的开始,称为START信号,SDA线的“上升沿”表示一个数据帧的结束,称
为STOP信号。
2.操作
再将寄存器就烦了,就从编写过程讲吧
1)I/O端口初始化
Mega16机的TWI口在PC0和PC1上,要置输入状态并且上拉电阻:
DDRC&=~((1<<PC0)&(1<<PC1)); PORTC|= (1<<PC0)|(1<<PC1);
2)TWI初始化
先看一个比较重要的位:TWINT的BIT7位
Bit 7 – TWINT: TWI 中断标志
当TWI 完成当前工作,希望应用程序介入时TWINT 置位。
若SREG 的I 标志以及TWCR寄存器的TWIE 标志也置位,则MCU 执行TWI 中断例程。
当TWINT 置位时, SCL信号的低电平被延长。
TWINT 标志的清零必须通过软件写"1” 来完成。
执行中断时硬件不会自动将其改写为"0”。
要注意的是,只要这一位被清零,TWI 立即开始工作。
因此,在清零TWINT 之前一定要首先完成对地址寄存器TWAR,状态寄存器TWSR,以及数据寄存器TWDR 的访问。
主机
/*twi主机初始化*/ void twi_master_init(void) { TWCR= 0x00; //禁止 TWBR= 0x64; //设置频率 TWSR= 0x00; //预分频1 TWAR= 0x00; //设置从机地址,主机不用设 TWCR= 0x04; //使能写碰撞标志 }
从机
/*twi从机初始化*/ void twi_slave_init(uint8 add) { TWCR= 0x00; //禁止 TWBR= 0x64; //设置频率,要和主机一致 TWSR= 0x00; //预分频1 TWAR= (add<<1); //设置从机地址,高7位 TWCR= (1<<TWEN)|(1<<TWEA)|(1<<TWIE); //使能中断/使能TWI 应答/使能TWI 中断 //TWEN必须置位以使能TWI接口。TWEA也要置位以使主机寻址到自己(从机地址或广播) 时返回确认信息ACK }
3)主机流程:
A.主机写入
怎么才能跟从机通信呢?这里大致写一下流程:
主机初始(从机已待命)--》主机发送Start信号(等待从机回应)--》主机发送地址(从机接收到数据并且对照自己的地址)--》主机发送数据(只有地址正确的从机和主机在总线,其余放弃总线)--》发送完毕,主机发送Stop信号,撤出总线
先看:
/*主机发送*/ void i2c_maste_transt(uint8 addr, uint8 data) { i2c_start(); if(i2c_write_addr(addr, 0)==TW_MT_SLA_ACK) //发送地址成功并收到ACK { i2c_write_data(data); } i2c_stop(); }
然后看其中的细致实现:
i2c_start():Start信号发送
/*iic发送开始信号*/ void i2c_start(void) { /*TWI中断标志置位/TWSTA是告诉总线自己是主机/激活TWI接口*/ TWCR= (1<<TWINT)|(1<<TWSTA)|(1<<TWEN); /*这里等待对方应答,有信号则TWINT为0*/ while (!(TWCR & (1<<TWINT))); //等待START 信号成功发送 }
i2c_write_addr:向器件写一个字节地址
TWI 数据寄存器- TWDR
在发送模式, TWDR 包含了要发送的字节;
在接收模式, TWDR 包含了接收到的数据。
注意地址是放在高7位
/* 写地址操作 *把一个字节数据输入器件 *参数:addr:发送地址, r_w:1为读,0为写 *返回:TWSR&0b11111000 , Bits 7..3 – TWS: TWI 状态 这5位用来反映TWI 逻辑和总线的状态 */ uint8 i2c_write_addr(uint8 addr,uint8 r_w) { if(r_w) { TWDR = addr|r_w; //RW 为1:读操作 } else { TWDR = addr & 0xFE; // RW 为0: 写操作 } /*TWI中断标志置位/激活TWI接口*/ TWCR = (1<<TWINT)|(1<<TWEN); /*这里等待对方应答,有信号则TWINT为0*/ while (!(TWCR & (1<<TWINT))); asm("nop"); return(TWSR&0b11111000); //TWSR高五位为I2C工作状态。 }
i2c_write_data:写数据,和上面大同小异
/* 写数据操作 *把一个字节数据输入器件 *发送数据 返回:TWSR&0b11111000 , Bits 7..3 – TWS: TWI 状态 * 这5位用来反映TWI 逻辑和总线的状态 */ uint8 i2c_write_data(uint8 data) { TWDR = data; //放进TWDR /*TWI中断标志置位/激活TWI接口*/ TWCR = (1<<TWINT)|(1<<TWEN); /*这里等待对方应答,有信号则TWINT为0*/ while (!(TWCR & (1<<TWINT))); asm("nop"); return(TWSR&0b11111000); //TWSR高五位为I2C工作状态。 }
i2c_stop:发送停止信号
/*总线停止*/ void i2c_stop(void) { TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN); }
B.主机读取
流程:主机发送Start信号(等待从机回应)--》主机发送地址(从机接收到数据并且对照自己的地址,最后一位为0是写入)--》主机写入数据(只有地址正确的从机和主机在总线,其余放弃总线)--》发送完毕,主机发送Stop信号,撤出总线
/*主机读取操作*/ uint8 i2c_maste_read(uint8 addr) { uint8 tmp=0; i2c_start(); if(i2c_write_addr(addr, 1)==TW_MR_SLA_ACK) //发送地址成功并收到ACK { tmp=i2c_read(); } i2c_stop(); return tmp; }
i2c_read:主机读数据:
#define TWCR_CMD_MASK 0x0F
#define Twi_NoAcK() {TWCR=TWCR&TWCR_CMD_MASK|(1<<TWINT);}
//从器件读出一个字节 uint8 i2c_read(void) { TWCR = (1<<TWINT)|(1<<TWEN); Twi_NoAcK(); while (!(TWCR & (1<<TWINT))); return(TWDR); }
C.从机中断函数:
从机采用中断接收方式:
中断函数:(不同编译平台下需要修改)
void twi_isr(void) { //twi event switch (TWSR&0xF8) { //从接收 case TW_SR_SLA_ACK: case TW_SR_ARB_LOST_SLA_ACK: case TW_SR_GCALL_ACK: case TW_SR_ARB_LOST_GCALL_ACK: Twi_Ack(); //返回ACK break; case TW_SR_DATA_ACK: case TW_SR_DATA_NACK: main_tmp = TWDR; PORTA = ~ TWDR; //接收数据并显示 Twi_Ack(); //返回ACK break; case TW_SR_GCALL_DATA_ACK: case TW_SR_GCALL_DATA_NACK: Twi_Ack(); //返回ACK break; case TW_SR_STOP: Twi_Ack(); break; //从发送***************************** case TW_ST_SLA_ACK: // 0xA8: 自己的SLA+R 已经被接收,ACK 已返回 case TW_ST_ARB_LOST_SLA_ACK:// 0xB0: SLA+R/W 作为主机的仲裁失败;自己的SLA+R 已经被接收,ACK 已返回 // 被选中为从读出 (数据将从传回主机) TWDR=main_tmp; //发送全局变量中值 Twi_Ack(); break; case TW_ST_DATA_ACK: // 0xB8: TWDR 里数据已经发送,接收到ACK //发送数据位 TWDR=main_tmp; break; case TW_ST_DATA_NACK: // 0xC0: TWDR 里数据已经发送接收到NOT ACK case TW_ST_LAST_DATA: // 0xC8: TWDR 的一字节数据已经发送(TWAE = “0”);接收到ACK // 全部完成 // 从方式开放 Twi_NoAcK(); twi_slave_init(addr); //重新回到初始化状态,等待接收模式的到来 break; case TW_NO_INFO: // 0xF8: 没有相关的状态信息;TWINT = “0” // 无操作 break; case TW_BUS_ERROR: // 0x00: 由于非法的START 或STOP 引起的总线错误 // 内部硬件复位,释放总线 TWCR=TWCR&TWCR_CMD_MASK|(1<<TWINT)|(1<<TWSTO)|(1<<TWEA); break; default: break; } }