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、主机代码:
[html] view plain copy
/****************************************************************************
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 = 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上升沿的时候进入中断。代码:
[html] view plain copy
#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}; //从机接收主机数据的buffer
BYTE write_buf[20] = {0x47,0x55,0x11};//从机发送数据给主机的buffer
BYTE 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;
}
上一篇:GPIO实现I2C从机的设计
下一篇:GPIO模拟I2C程序实现
推荐阅读最新更新时间:2024-03-16 16:03