I2C的主机从机模拟
来源:互联网 发布:js代码 引号 换行 编辑:程序博客网 时间:2024/05/16 14:23
好久没有在csdn上面做笔记了,主要是最近琐碎的事情太多,乱七八糟的事情让自己不能坚定下来做自己喜欢做的事情。上了星期花了两天的时间模拟了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 1BYTE ACK_Flag = 0;BYTE I2C_count;//计数器BYTE receive_slave[100] = {0x00}; //接收从机的字节BYTE send_slave[5] = {0xaa,0x55,0xbb,0x55,0xaa}; //发送字节给从机/*初始化I2C*/void SDA_(BYTE input){ if(input == 1) //SDA输出,p1.3 P1DIR |= 0X08; else P1DIR &= 0XF7; //SDA输入,p1.3}void SCL_(BYTE input){ if(input == 1) //SCL输出,P1.2 P1DIR |= 0X04; else //SCL输入,P1.2 P1DIR &= 0XFB; }/*启动I2C工作*/void START_I2C(void){ SDA = 1; SCL = 0;// Delay_us(20); //这个没有多大影响,可以不要 SCL = 1; Delay_us(10); //最开始50,5us太短了,不能判断,10us可以。 SDA = 0; Delay_us(2); //最开始50, SCL = 0; Delay_us(5); //最开始50,这个延时和上面的延时可以不要,但是为了SLAVE有足够时间退出中断,就加上}/*停止I2C工作*/void STOP_I2C(void){// SDA_OUT; SDA = 0; Delay_us(50);; SCL = 1; Delay_us(50);; SDA = 1; Delay_us(50);; SCL = 0; Delay_us(50);;}/*收到从器件的ACK帧,用于写完一个字节后检查*/void Receive_SLAVE_ACK(void){ SCL = 0;// Delay_us(50); //这里没有必要 SDA = 1; SDA_(IN); SCL = 1; Delay_us(20); //15us短了,经常出错 if(1 == SDA) // 若SDA=1表明非应答,置位非应答标志ACK_Flag ACK_Flag = 1; SDA_(OUT); SCL = 0;// Delay_us(50); //这里也没有必要}/*主器件往从器件里写一个字节*/void WriteByte(BYTE writedata){ //SDA_OUT; SCL = 0; //SCL为低电平的时候可以改变数据状态 for(int i=0;i<8;i++){ if(((writedata>>7)&0x01) == 0x01){//先写最高位 SDA = 1; } else{ SDA = 0; }// Delay_us(10); //这个可以不要 SCL = 1; Delay_us(20); //这个是等待SLAVE进入中断并接收数据,退出中断,15us不行,要20us writedata = writedata << 1;//写完一位后将低位移到高位 SCL = 0;// Delay_us(50); //这个也可以不要 }// Delay_us(50); //这个其实可以不要 SCL = 0;}/*主器件从从器件里面读取一个字节*/BYTE ReadByte(void){ BYTE TempData = 0; SCL = 0; for(int i=0;i<8;i++){ SDA = 1; SDA_(IN);// Delay_us(10); //这个没有什么影响 SCL = 1; Delay_us(150); //这个时间不能太短,不然的话就会读错1位 TempData <<= 1; if(1 == SDA) TempData |= 0x01; else TempData |= 0x00; SCL = 0; } SCL = 0; SDA_(OUT);// Delay_us(50); return (TempData);}void I2C_Bytes_Test(void){/***********************写单字节正常*****************************/#if 0 START_I2C(); WriteByte(0xa4);//写单字节的命令 Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } WriteByte(0xaa); Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } STOP_I2C();#endif/***********************************************************/ /*****************************写多字节********************/#if 0 START_I2C(); WriteByte(0xa6);//写字多节的命令 Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } WriteByte(0x05);//写多字节长度 Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } for(int i=0;i<5;i++){ //开始写多字节 WriteByte(send_slave[i]); Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } } STOP_I2C();#endif /**********************************************************//*****************I2C读正常**************************/ START_I2C(); WriteByte(0xa5); Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } WriteByte(0x03); //读的长度 Receive_SLAVE_ACK(); if(ACK_Flag == 1){ return; } for(int i=0;i<3;i++){ receive_slave[i] = ReadByte(); Receive_SLAVE_ACK(); if(ACK_Flag == 1) return;// UART1_Send_BYTE(receive_slave[i]); } STOP_I2C(); LED0 = 0; UART1_Send_String(receive_slave,3);/**********************************************************/}
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 = 0;BYTE address = 0; //接收到的从机地址BYTE count = 0; //接收到一位计数,产生一个字节的计数BYTE receive_BYTE; //从机接收主机单个字节BYTE receive_buf[20] = {0x00}; //从机接收主机数据的bufferBYTE write_buf[20] = {0x47,0x55,0x11};//从机发送数据给主机的bufferBYTE receive_len = 0; //从接接收主机多字节时的长度BYTE send_len = 0; //从机要发送给主机字节的长度BYTE write_end = 0; //主机是否对从机写完BYTE read_end = 0; //主机是否接收完从机发送的数据BYTE length_ACK = 0; //从机是否接收到了要发送数据给主机的长度BYTE read_num = 0;BYTE Temp = 0; void PORT1_InterruptInit(void){ EA = 1; 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; //清中断标志 */ if(P1IFG>0) { P1IFG = 0; switch(STATE){ case START_STATE: SDA_(IN); if(SDA){ while(SDA); while(SCL); STATE = CONTROL_STATE; } break; case CONTROL_STATE: address <<= 1; if(1 == SDA) address |= 0x01; else address |= 0x00; count++; if(8 == count){ count = 0; if((address & 0xfc) == 0xa4){ STATE = ACK_STATE; } else STATE = NOACK_STATE; } break; case ACK_STATE: SDA_(OUT);//SDA设置为输出 SDA = 0; if((write_end == 1) || (read_end == 1) ){ //已经读完或者写完 STATE = STOP_STATE; write_end = 0; read_end = 0; } else{ if((address & 0x01) == 0x00) //主机写SLAVE STATE = WRITE_STATE; else{ STATE = READ_STATE;// UART1_Send_BYTE(STATE); } } break; case NOACK_STATE: SDA_(OUT); SDA = 1; address = 0; STATE = START_STATE; break; case WRITE_STATE: //主机写从机 SDA_(IN); //这里将SDA置为输入,是因为发送ACK时候置为输出了 if((address & 0x02) == 0x00){ //写单字节 receive_BYTE <<= 1; if(1 == SDA) receive_BYTE |= 0X01; else receive_BYTE |= 0X00; count++; if(8 == count){ STATE = ACK_STATE;// UART1_Send_BYTE(receive_BYTE); count = 0; write_end = 1; } } else{ receive_buf[receive_len] <<= 1; if(1 == SDA) receive_buf[receive_len] |= 0x01; else receive_buf[receive_len] |= 0x00; count++; if(8 == count){ //接收到了8个字节 count = 0; receive_len++; UART1_Send_BYTE(receive_buf[receive_len-1]); if(receive_len >= (receive_buf[0]+1)){ //这里+1是因为要先写长度 write_end = 1; receive_len = 0; } STATE = ACK_STATE; } } break; case READ_STATE: //主机读从机 if(!length_ACK){ //主机发送过从机长度 SDA_(IN); send_len <<= 1; if(1 == SDA) send_len |= 0x01; else send_len |= 0x00; count++; if(8 == count){ length_ACK = 1; //主机发送长度给从机 LED0 = 0;// UART1_Send_BYTE(send_len); count = 0; STATE = ACK_STATE; } } else{ SDA_(OUT); Temp = write_buf[read_num]; Temp <<= count; UART1_Send_BYTE(Temp); if((Temp & 0x80) == 0x80) SDA = 1; else SDA = 0; count++; if(count == 8){ //移了7位,正好读一个字节 count = 0; read_num++; if(read_num >= send_len){//读完了所有数据 read_num = 0; read_end = 1; length_ACK = 0; //将接收长度置0 } STATE = ACK_STATE; } } break; case STOP_STATE: SDA_(IN); while(!SDA); address = 0;// receive_BYTE = 0; receive_len = 0; send_len = 0; LED1 = ~LED1; STATE = START_STATE; break; default: break; } } P1IF = 0; }
4、从机是在中断里面接收的,每次SCL上升沿的时候进入中断。代码:
- I2C的主机从机模拟
- 标准80C51单片机模拟I2C总线的主机程序
- 标准AVR单片机模拟I2C总线的主机程序
- 用51模拟I2C从机程序-改动实测OK
- STM8 I2C从机
- STM32 I2C从机
- GPIO模拟的I2C操作
- i2c设备/模拟i2c
- LPC2200的I2C从模式
- 模拟I2C
- 模拟I2C
- I2C模拟
- I2C之知(四)--I2C总线的7bit从机地址
- I2C之知(四)--I2C总线的7bit从机地址
- I2C之知(四)--I2C总线的7bit从机地址
- GPIO实现I2C从机的设计[1]
- GPIO实现I2C从机的设计[2]
- linux gpio模拟i2c的使用
- 斯密特:未来六个月Android全胜iOS
- JAVA正则表达式入门
- C++ 指针数组,数组指针,以及函数指针,以及堆中的分配规则
- windows外壳扩展编程之windows右键菜单
- FTP的命令
- I2C的主机从机模拟
- find常用命令备忘录
- C++ main函数详解
- mysql --
- C++ memset详解
- BindingUtils绑定失效-模块化
- oracle 初探内存结构
- C#枚举
- 常用工具