好久没有在****上面做笔记了,主要是最近琐碎的事情太多,乱七八糟的事情让自己不能坚定下来做自己喜欢做的事情。上了星期花了两天的时间模拟了I2C的主机和从机通信。一般都是主机模拟,从机直接用硬件I2C的,但是由于所谓的项目里面没有I2C,但是要用到I2C了,因此就不得不用I/O口去模拟I2C了。
1、I2C协议
I2C的协议相信网上已经有很多资料了,这里就不做详细介绍,只做简单说明即可。
a、I2C协议有两根总线:SDA和SCL。SDA为数据线,而SCL就是主机的时钟线。
b、I2C是主机控制从机,时钟线只能主机改变。
c、每个从机都有唯一的地址,主机通过发送从机地址来选择从机。
d、I2C开始信号:SCL为高电平的时候,SDA由高电平向低电平跳变。
e、I2C结束信号:SCL为高电平的时候,SDA由低电平向高电平跳变。
f、主机传输信号的时候,SCL为高电平的时候,传输信号,SCL为低电平的时候改变信号。
g、主机接收信号的时候,SCL为高电平的时候,接收信号。
2、如果用I/O模拟I2C的时候,一定要记住,是主机控制从机,从机根据主机SCL信号的改变而改变。
3、主机代码:
/**************************************************************************** I2C模拟条件: 1、HOST先发地址和控制命令给SLAVE; 2、地址和控制命令占一个字节; 3、字节格式: 7~2 1 0 地址 单/多字节(0/1) 读/写(1/0) 4、发送多字节时候,第一个字节是地址和控制命令、第二个字节是长度、接下来是数据 5、发送多字节时候,第一个字节是地址和控制命令、第二个字节是要发送的 ******************************************************************************/ #include "ioCC1110.h" #include "hal.h" #define SCL P1_2 #define SDA P1_3 #define IN 0 #define OUT 1 BYTE ACK_Flag = ; BYTE I2C_count;//计数器 BYTE receive_slave[] = {0x00}; //接收从机的字节 BYTE send_slave[] = {0xaa,0x55,0xbb,0x55,0xaa}; //发送字节给从机 /*初始化I2C*/ void SDA_(BYTE input) { ) //SDA输出,p1.3 P1DIR |= 0X08; else P1DIR &= 0XF7; //SDA输入,p1.3 } void SCL_(BYTE input) { ) //SCL输出,P1.2 P1DIR |= 0X04; else //SCL输入,P1.2 P1DIR &= 0XFB; } /*启动I2C工作*/ void START_I2C(void) { SDA = ; SCL = ; // Delay_us(20); //这个没有多大影响,可以不要 SCL = ; Delay_us(); //最开始50,5us太短了,不能判断,10us可以。 SDA = ; Delay_us(); //最开始50, SCL = ; Delay_us(); //最开始50,这个延时和上面的延时可以不要,但是为了SLAVE有足够时间退出中断,就加上 } /*停止I2C工作*/ void STOP_I2C(void) { // SDA_OUT; SDA = ; Delay_us();; SCL = ; Delay_us();; SDA = ; Delay_us();; SCL = ; Delay_us();; } /*收到从器件的ACK帧,用于写完一个字节后检查*/ void Receive_SLAVE_ACK(void) { SCL = ; // Delay_us(50); //这里没有必要 SDA = ; SDA_(IN); SCL = ; Delay_us(); //15us短了,经常出错 == SDA) // 若SDA=1表明非应答,置位非应答标志ACK_Flag ACK_Flag = ; SDA_(OUT); SCL = ; // Delay_us(50); //这里也没有必要 } /*主器件往从器件里写一个字节*/ void WriteByte(BYTE writedata) { //SDA_OUT; SCL = ; //SCL为低电平的时候可以改变数据状态 ;i<;i++){ )&0x01) == 0x01){//先写最高位 SDA = ; } else{ SDA = ; } // Delay_us(10); //这个可以不要 SCL = ; Delay_us(); //这个是等待SLAVE进入中断并接收数据,退出中断,15us不行,要20us writedata = writedata << ;//写完一位后将低位移到高位 SCL = ; // Delay_us(50); //这个也可以不要 } // Delay_us(50); //这个其实可以不要 SCL = ; } /*主器件从从器件里面读取一个字节*/ BYTE ReadByte(void) { BYTE TempData = ; SCL = ; ;i<;i++){ SDA = ; SDA_(IN); // Delay_us(10); //这个没有什么影响 SCL = ; Delay_us(); //这个时间不能太短,不然的话就会读错1位 TempData <<= ; == SDA) TempData |= 0x01; else TempData |= 0x00; SCL = ; } SCL = ; SDA_(OUT); // Delay_us(50); return (TempData); } void I2C_Bytes_Test(void) { /***********************写单字节正常*****************************/ #if 0 START_I2C(); WriteByte(0xa4);//写单字节的命令 Receive_SLAVE_ACK(); ){ return; } WriteByte(0xaa); Receive_SLAVE_ACK(); ){ return; } STOP_I2C(); #endif /***********************************************************/ /*****************************写多字节********************/ #if 0 START_I2C(); WriteByte(0xa6);//写字多节的命令 Receive_SLAVE_ACK(); ){ return; } WriteByte(0x05);//写多字节长度 Receive_SLAVE_ACK(); ){ return; } ;i<;i++){ //开始写多字节 WriteByte(send_slave[i]); Receive_SLAVE_ACK(); ){ return; } } STOP_I2C(); #endif /**********************************************************/ /*****************I2C读正常**************************/ START_I2C(); WriteByte(0xa5); Receive_SLAVE_ACK(); ){ return; } WriteByte(0x03); //读的长度 Receive_SLAVE_ACK(); ){ return; } ;i<;i++){ receive_slave[i] = ReadByte(); Receive_SLAVE_ACK(); ) return; // UART1_Send_BYTE(receive_slave[i]); } STOP_I2C(); LED0 = ; UART1_Send_String(receive_slave,); /**********************************************************/ }
4、从机是在中断里面接收的,每次SCL上升沿的时候进入中断。代码:
#define START_STATE 0 //开始 #define CONTROL_STATE 1 //控制命令 #define ACK_STATE 2 //ACK应答 #define NOACK_STATE 3 //非ACK应答 #define WRITE_STATE 4 //写从机 #define READ_STATE 5 //读从机 #define STOP_STATE 6 //停止 BYTE STATE = ; BYTE address = ; //接收到的从机地址 BYTE count = ; //接收到一位计数,产生一个字节的计数 BYTE receive_BYTE; //从机接收主机单个字节 BYTE receive_buf[] = {0x00}; //从机接收主机数据的buffer BYTE write_buf[] = {0x47,0x55,0x11};//从机发送数据给主机的buffer BYTE receive_len = ; //从接接收主机多字节时的长度 BYTE send_len = ; //从机要发送给主机字节的长度 BYTE write_end = ; //主机是否对从机写完 BYTE read_end = ; //主机是否接收完从机发送的数据 BYTE length_ACK = ; //从机是否接收到了要发送数据给主机的长度 BYTE read_num = ; BYTE Temp = ; void PORT1_InterruptInit(void) { EA = ; P1DIR &= 0XFB;//P1.2输入,scl IEN2 |= 0X10;//P1中断使能,scl P1IEN |= 0x04;//P1.2中断使能,scl PICTL &= ~0X02; //P1上升沿中断,0:rising edge P1IFG &= ~0x04; } #pragma vector = P1INT_VECTOR __interrupt void P1_ISR(void) { /* if(P1IFG>0) //按键中断 { P1IFG = 0; LED1 = ~LED1; } P1IF = 0; //清中断标志 */ ) { P1IFG = ; switch(STATE){ case START_STATE: SDA_(IN); if(SDA){ while(SDA); while(SCL); STATE = CONTROL_STATE; } break; case CONTROL_STATE: address <<= ; == SDA) address |= 0x01; else address |= 0x00; count++; == count){ count = ; if((address & 0xfc) == 0xa4){ STATE = ACK_STATE; } else STATE = NOACK_STATE; } break; case ACK_STATE: SDA_(OUT);//SDA设置为输出 SDA = ; ) || (read_end == ) ){ //已经读完或者写完 STATE = STOP_STATE; write_end = ; read_end = ; } else{ if((address & 0x01) == 0x00) //主机写SLAVE STATE = WRITE_STATE; else{ STATE = READ_STATE; // UART1_Send_BYTE(STATE); } } break; case NOACK_STATE: SDA_(OUT); SDA = ; address = ; STATE = START_STATE; break; case WRITE_STATE: //主机写从机 SDA_(IN); //这里将SDA置为输入,是因为发送ACK时候置为输出了 if((address & 0x02) == 0x00){ //写单字节 receive_BYTE <<= ; == SDA) receive_BYTE |= 0X01; else receive_BYTE |= 0X00; count++; == count){ STATE = ACK_STATE; // UART1_Send_BYTE(receive_BYTE); count = ; write_end = ; } } else{ receive_buf[receive_len] <<= ; == SDA) receive_buf[receive_len] |= 0x01; else receive_buf[receive_len] |= 0x00; count++; == count){ //接收到了8个字节 count = ; receive_len++; UART1_Send_BYTE(receive_buf[receive_len-]); ]+)){ //这里+1是因为要先写长度 write_end = ; receive_len = ; } STATE = ACK_STATE; } } break; case READ_STATE: //主机读从机 if(!length_ACK){ //主机发送过从机长度 SDA_(IN); send_len <<= ; == SDA) send_len |= 0x01; else send_len |= 0x00; count++; == count){ length_ACK = ; //主机发送长度给从机 LED0 = ; // UART1_Send_BYTE(send_len); count = ; STATE = ACK_STATE; } } else{ SDA_(OUT); Temp = write_buf[read_num]; Temp <<= count; UART1_Send_BYTE(Temp); if((Temp & 0x80) == 0x80) SDA = ; else SDA = ; count++; ){ //移了7位,正好读一个字节 count = ; read_num++; if(read_num >= send_len){//读完了所有数据 read_num = ; read_end = ; length_ACK = ; //将接收长度置0 } STATE = ACK_STATE; } } break; case STOP_STATE: SDA_(IN); while(!SDA); address = ; // receive_BYTE = 0; receive_len = ; send_len = ; LED1 = ~LED1; STATE = START_STATE; break; default: break; } } P1IF = ; }