近日,应网友的要求,做了两个M16通过TWI进行通信的实验,现将相关代码公布如下,欢迎各位朋友拍砖!
硬件联接:第一个M16实验板通过232与PC通信,设为主机。第二个M16学习板设为从机,从机地址在这里设为0x06(相当于24C02的0xA0),其SPI接口通过74HC595接四位LED数码管段选,PB0-PB3为数码管位选。两板间通过三条杜帮线将SCL、SDA、GND对应联接起来。其中SCL、SDA两管脚接4.7K上拉电阻。
实验方法:PC机上的串口调试助手按要求发送数据到主M16,主M16通过TWI传送到从M16,从M16将收到的数据在数码管上显示,同时将显示内容传回主M16并回送PC显示。
串口发送到主M16的控制命令为8字节,十六进制数据帧格式:第一字节0X55,帧头;第二字节0X06,从机地址;第三字节0X00,从机子地址(即从哪位数码管开始),范围为0-3;第四、五、六、七字节为数据,对应随后数码管的位显示,范围为0-9、A-F;第八字节0XAA,帧结束标志。
主机程序:
CODE:
/************************************
* M16通过TWI向M16发送数据的程序 *
* 功 能:M16通过TWI读写M16 *
* 建立日期:2008年11月04日 *
* 设 计 者:铜河 *
* 版 本:V1.0 *
* 修改日期:2008年11月04日 *
* 主控芯片:ATmega16 *
* 时钟频率:外部晶体7.3728MHz *
* 编 译 器:ICCAVR6.31A *
************************************/
#include <iom16v.h>
#include <macros.h>
//-----------TWI状态定义-------------
//MT为主方式发送,MR为主方式接收
#define START 0x08 //启动TWI的响应
#define RE_START 0x10 //重新启动TWI的响应
#define MT_SLA_ACK 0x18 //发送地址已响应
#define MT_SLA_NOACK 0x20 //发送地址已非响应
#define MT_DATA_ACK 0x28 //发送数据已响应
#define MT_DATA_NOACK 0x30 //发送数据已非响应
#define MR_SLA_ACK 0x40 //主接收地址返应答
#define MR_SLA_NOACK 0x48 //主接收地址返非应答
#define MR_DATA_ACK 0x50 //主接收数据返应答
#define MR_DATA_NOACK 0x58 //主接收数据返非应答
//常用TWI主模式写和主模式读
#define Start() (TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN))
#define STOP() (TWCR=(1<<TWINT)|(1<<TWSTO)|(1<<TWEN))
#define WAIT() {while(!(TWCR&(1<<TWINT)));} //等待操作完成
#define TESTACK() (TWSR&0xF8) //读取状态数据
#define SETACK() (TWCR|=(1<<TWEA)) //应答
#define SETNOACK() (TWCR&=~(1<<TWEA)) //非应答
#define TWI() (TWCR=(1<<TWINT)|(1<<TWEN)) //启动TWI读方式
#define WRITE8BIT(x) {TWDR=(x);TWCR=(1<<TWINT)|(1<<TWEN);} //发送8位数据
unsigned char uart_buff[7]={0}; //串口接收缓冲
unsigned char xs[4]; //发送缓冲
unsigned char SLA_ADDR; //需读写的从器件地址:从串口接收的数据中获取
/********************************
* 函数名称: void port_init(void)*
* 函数功能:端口初始化 *
********************************/
void port_init(void)
{
PORTA = 0xFF;
DDRA = 0xFF;
PORTB = 0x00;
DDRB = 0xFF;
PORTC = 0x00;
DDRC = 0xFF;
PORTD = 0x00;
DDRD = 0x00;
}
/************************************
* 函数名称: void delay_ms(uint i) *
* 函数功能:延时函数 *
* 晶振频率:7.3728MHZ *
* 实际延时:i mS *
************************************/
void delay_ms(unsigned int i)
{
unsigned int a;
for(;i;i--)
{
for(a=1055;a;a--)
{;}
}
}
/************************************
* 函数名称: void timer1_init(void) *
* 函数功能:定时器1初始化 *
* 预分频值:64 *
* 工作方式:溢出中断 *
* 定时时间:500mSec *
* 时钟频率:外部晶体 7.3728MHz *
************************************/
void timer1_init(void)
{
TCCR1B = 0x00;
TCNT1H = 0x1F;
TCNT1L = 0x01;
TCCR1A = 0x00;
TCCR1B = 0x03;
}
/************************************
* 定时器1溢出中断入口 *
************************************/
#pragma interrupt_handler timer1_ovf_isr:iv_TIMER1_OVF
void timer1_ovf_isr(void)
{
TCNT1H = 0x1F;
TCNT1L = 0x01;
PORTC ^= 1<<3; //LED闪灯
}
/************************************
* 函数名称: void twi_init(void) *
* 函数功能:TWI 初始化 *
* 预分频值:1 *
* 工作方式:主机方式 高速 *
* 时钟频率:外部晶体 7.3728MHz *
************************************/
void twi_init(void)
{
TWCR = 0x44;
}
/************************************
* 函数功能:将指定数据写入指定地址 *
* 入口参数:地址、数据 *
* 出口参数:操作成功1,不成功0 *
************************************/
unsigned char iic_write(unsigned char addr,unsigned char data)
{
unsigned char m;
Start(); //启动IIC
WAIT();
WRITE8BIT(SLA_ADDR); //写从器件地址和写方式
WAIT();
WRITE8BIT(addr); //写子地址
WAIT();
WRITE8BIT(data); //写数据
WAIT();
STOP(); //停止IIC
delay_ms(10); //延时等待
return 0;
}
/************************************
* 函数功能:从指定地址读取数据 *
* 入口参数:地址 *
* 出口参数:成功返回数据,不成功0 *
************************************/
unsigned char iic_read(unsigned char addr)
{
unsigned char temp,m;
Start(); //启动IIC
WAIT();
WRITE8BIT(SLA_ADDR); //写从器件地址和写方式
WAIT();
if(TESTACK()!=MT_SLA_ACK)return 0;
WRITE8BIT(addr); //写子地址
WAIT();
if(TESTACK()!=MT_DATA_ACK)return 0;
Start(); //IIC重新启动
WAIT();
if(TESTACK()!=RE_START)return 0;
WRITE8BIT(SLA_ADDR+1); //写从器件地址和读方式
WAIT();
if(TESTACK()!=MR_SLA_ACK)return 0;//应答
TWI(); //启动主IIC读方式
WAIT();
if(TESTACK()!=MR_DATA_NOACK)return 0;//非应答
temp = TWDR; //读取接收到的数据
STOP(); //停止IIC
return temp;
}
/************************************
* 函数名称: void uart0_init(void) *
* 函数功能:串口UART0初始化 *
* 波 特 率:9600,8,n,1 *
* 时钟频率:外部晶体 7.3728MHz *
************************************/
void uart0_init(void)
{
UCSRB = 0x00;
UCSRA = 0x00;
UCSRC = BIT(URSEL) | 0x06;
UBRRL = 0x2F;
UBRRH = 0x00;
UCSRB = 0x98;
}
/****************************************
* 函数名称: void Uart_Transmit(uchar i) *
* 函数功能:串口字节数据发送函数 *
* 工作方式:查询方式 *
* 晶振频率:7.3728MHZ,波特率9600 *
****************************************/
void Uart_Transmit(unsigned char i)
{
UDR = i; //发送数据
while (!(UCSRA & (1<<UDRE))); //等待发送缓冲器为空
}
/***************************************
* 串口接收中断函数 *
***************************************/
#pragma interrupt_handler uart0_rx_isr:12
void uart0_rx_isr(void) //串口接收中断函数
{
unsigned char temp; //变量
static unsigned char count; //接收位置计数
temp = UDR;
switch (count)
{
case 0x00: //启动字节校对,如果对则开始接收数据
if (uart_buff[6] == 0x99) //判断主程序是否处理完前面的数据
{
return ;
}
if (temp == 0x55) //启动字节判定
{
PORTC |= 1<<7;
uart_buff[0] = temp; //提取第一个字节
count ++; //计数值增加
}
break;
case 0x01:
SLA_ADDR = temp;
count ++;
break;
case 0x02:
uart_buff[1] = temp;
count ++;
break;
case 0x03:
uart_buff[2] = temp;
count ++;
break;
case 0x04:
uart_buff[3] = temp;
count ++;
break;
case 0x05:
uart_buff[4] = temp;
count ++;
break;
case 0x06:
uart_buff[5] = temp;
count ++;
break;
case 0x07:
if (temp == 0xAA) //数据校验
{
uart_buff[6] = 0x99; //接收正确,设置标志,等待处理
}
PORTC &=~(1<<7);
count = 0x00; //接收完,计数变量清除
break;
default : //计数位置异常捕获
count = 0x00;
PORTC &=~(1<<7);
break;
}
}
/************************************
* 函数名称: void init_devices(void) *
* 函数功能:器件初始化 *
************************************/
void init_devices(void)
{
CLI();
port_init();
timer1_init();
twi_init();
uart0_init();
MCUCR = 0x00;
GICR = 0x00;
TIMSK = 0x04;
SEI();
}
/***************************
* 主函数 *
***************************/
void main(void)
{
unsigned char m;
init_devices();
while(1)
{
if(uart_buff[6]==0x99) //收到新的串口数据
{
PORTC ^= 1<<5;
for(m=0;m<4;m++) //通过TWI发送4个显示数据
{
iic_write((uart_buff[1]+m),uart_buff[m+2]);
}
uart_buff[6]=0; //处理完毕,串口可接收新数据
delay_ms(50);
for(m=0;m<4;m++)
{
xs[m] = iic_read(m); //读取从机数据
Uart_Transmit(xs[m]); //通过串口发回
}
}
}
}
从机程序:
/************************************
* 学习板接收TWI显示程序 *
* 功 能:用TWI接收主机数据显示 *
* 建立日期:2008年11月04日 *
* 设 计 者:铜河 *
* 版 本:V1.0 *
* 修改日期:2008年11月04日 *
* 主控芯片:ATmega16 *
* 时钟频率:外部晶体7.3728MHz *
* 编 译 器:ICCAVR6.31A *
************************************/
#include <iom16v.h>
#include <macros.h>
volatile unsigned char IIC_STATE; //IIC通信状态机
//-----------TWI状态定义-------------
//从机编程设计的几个状态机
#define STATE_IIC_ADDR 0xC3
#define STATE_IIC_WDATA 0xA5
#define STATE_IIC_RDATA 0x5A
#define STATE_IIC_STOP 0
volatile unsigned char IIC_ADDR;
volatile unsigned char IIC_DATA; //当前数据
#define SLA_Device_Addr 0x06 //定义器件地址
//从机方式中断响应状态码
#define SR_SLA_ACK 0x60 //从机接收地址响应
#define SR_ALL_ACK 0x70 //从机接收广播响应
#define SR_DATA_ACK 0x80 //从机接收数据响应
#define SR_DATA_NOACK 0x88 //从机接收数据非应答
#define SR_ALL_DATA_ACK 0x90 //从机接收广播数据应答
#define SR_ALL_DATA_NOACK 0x98 //从机接收广播数据非应答
#define SR_STOP_RESTART 0xA0
#define ST_SLA_ACK 0xA8 //从机发送地址应答
#define ST_DATA_ACK 0xB8 //从机发送数据应答
#define ST_DATA_NOACK 0xC0 //从机发送数据非应答
#define ST_LAST_DATA_ACK 0xC8
//从机各种操作
#define TWI_STATE() (TWSR&0xF8) //读取状态数据
#define SLA_AUTOACK() (TWCR=(1<<TWEA)|(1<<TWINT)|(1<<TWEN)|(1<<TWIE)) //自动产生应答
#define SLA_SEND8BIT(x) {TWDR=(x);TWCR=(1<<TWEA)|(1<<TWINT)|(1<<TWEN)|(1<<TWIE);} //发送8位数据
#define SLA_RESUME() (TWCR=(1<<TWEA)|(1<<TWINT)|(1<<TWEN)|(1<<TWIE)) //恢复等待状态
//定义全局变量
unsigned char s[4]; //定义显示缓冲数组:S0为个位、S3为千位
unsigned char i;
//定义显示代码:0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,暗
unsigned char disp[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
//---------端口初始化----------
void port_init(void)
{
PORTB = 0xFF;
DDRB = 0xFF;
PORTC = 0x00;
DDRC = 0xFF;
PORTD = 0x00;
DDRD = 0xFF;
}
/************************************
* SPI初始化函数:主模式,1/4fck *
************************************/
void SPI_Init(void)
{
DDRB |= (1<<PB3)|(1<<PB5); //设置MOSI和SCK为输出,其他为输入
SPCR = (1<<SPE)|(1<<MSTR); //使能SPI主机模式,设置时钟速率为fck/4
}
/************************************
* SPI数据发送函数 *
************************************/
void SPI_T(unsigned char i)
{
SPDR = i; //启动数据传输
while (!(SPSR & (1<<SPIF))) //等待传输结束
{
;
}
}
/************************************
* 通过595向数码管输出数据 *
* 入 口:data 待显示的数据 *
* i 数据显示的位 *
************************************/
void SPI_595_Out(unsigned char data,unsigned char i)
{
PORTB &= ~(1<<4); //准备锁存
PORTB |= 0x0F; //先关位码
SPI_T(data); //再送数据
PORTB |= 1<<4; //锁存数据
PORTB &= ~(0x08>>i); //最后开位码
}
/************************************
* 定时器0中断函数组 *
* 溢出中断:预分频256 *
* 功 能:扫描显示 *
* 定时时长:4MS (7.3728MHz) *
* 实际时长:4.000mSec (误差0.0%) *
* 设 计 者:tonghe *
************************************/
//------------初始化函数---------------
void timer0_init(void)
{
TCCR0 = 0x00; //先关闭定时器0
TCNT0 = 0x8D; //装载初值
TCCR0 = 0x04; //启动定时器0
}
//--------定时器0中断向量入口----------
#pragma interrupt_handler timer0_ovf_isr:10
void timer0_ovf_isr(void)
{
TCNT0 = 0x8D; //重装初值
i++;
if(i>3)i=0;
SPI_595_Out(disp[s[i]],i); //送显数据
}
//---------TWI初始化----------
void twi_init(void)
{
TWAR = SLA_Device_Addr;
TWCR = 0x45;
}
//----------TWI中断入口----------
#pragma interrupt_handler twi_isr:iv_TWI
void twi_isr(void)
{
unsigned char nc;
nc = TWI_STATE();
switch(nc)
{
case SR_SLA_ACK: //从地址匹配,写传输,ACK已返回
IIC_STATE = STATE_IIC_ADDR; //下一步接收数据的地址
SLA_AUTOACK();
break;
case SR_DATA_ACK: //接收主机送来的从机数据地址或数据,ACK已返回
if(IIC_STATE==STATE_IIC_ADDR)//如果是地址
{
IIC_ADDR = TWDR;
IIC_STATE = STATE_IIC_WDATA;//下一步接收数据
}
else
{
IIC_DATA = TWDR; //是数据
s[IIC_ADDR] = IIC_DATA; //将数据存入显示缓冲
}
SLA_AUTOACK();
break;
case ST_SLA_ACK: //从地址匹配读数据
case ST_DATA_ACK: //TWDR里数据已发送,接收到ACK
IIC_STATE = STATE_IIC_RDATA;
SLA_SEND8BIT(s[IIC_ADDR++]); //发送数据
break;
case ST_DATA_NOACK: //TWDR里数据已发送,接收到NOACK
IIC_STATE = STATE_IIC_STOP;
SLA_AUTOACK();
break;
case SR_STOP_RESTART: //主机写命令结束或读命令重新开始
SLA_AUTOACK();
break;
default: //从机模式转到未被寻址状态
SLA_RESUME();
break;
}
}
//------------器件初始化----------
void init_devices(void)
{
CLI();
port_init();
timer0_init();
twi_init();
SPI_Init();
MCUCR = 0x00;
GICR = 0x00;
TIMSK = 0x05;
SEI();
}
//------------主函数------------
void main(void)
{
init_devices();
while(1);
}