要用GPIO模拟I2C,那么首先就得弄清楚I2C的时序。
1.I2C总线通信时序
2.起始与结束时序
//起始信号 uint8_t I2c_StartCondition() { uint8_t rv = NO_ERROR; SDA_OUT(); SCL_OUT(); /* StartCondition(S): A high to low transition on the SDA line while SCL is high. _______ SCL: |___ _____ SDA: |_____ */ SDA_H(); delay_us(2); SCL_H(); delay_us(2); SDA_L();//先于SCL前拉低 delay_us(2); SCL_L();//拉低SCL便于之后的操作 delay_us(2); return rv; }
//终止信号 uint8_t I2c_StopCondition(void) { uint8_t rv = NO_ERROR; SDA_OUT(); /* StopCondition(P): A low to high transition on the SDA line while SCL is high. ———————————— SCL: — — — _____ SDA: ___| */ SCL_L(); SDA_L(); delay_us(2); SCL_H(); delay_us(2); SDA_H(); delay_us(2); return rv; }
3.读写时序
//写一个byte uint8_t I2c_WriteByte(uint8_t byte) { uint8_t rv = NO_ERROR; uint8_t mask; /* Data line changes must happened when SCL is low */ SDA_OUT(); SCL_L(); /* 1Byte=8bit, MSB send: bit[7]-->bit[0] */ for(mask=0x80; mask>0; mask>>=1) { if((mask & byte) == 0) { SDA_L(); } else { SDA_H(); } delay_us(5); // data set-up time (t_SU;DAT) SCL_H(); delay_us(5); // SCL high time (t_HIGH) SCL_L(); delay_us(5); // data hold time(t_HD;DAT) } /* clk #9 wait ACK/NAK from slave */ SDA_IN(); SCL_H(); // clk #9 for ack delay_us(5); // data set-up time (t_SU;DAT) //从机应答 if( READ_SDA() ) rv = ACK_ERROR; OUT: SCL_L(); delay_us(20); return rv; }
//读一个byte uint8_t I2c_ReadByte(uint8_t *byte, uint8_t ack) { uint8_t rv = NO_ERROR; uint8_t mask; *byte = 0x00; SDA_IN(); /* 1Byte=8bit, MSB send: bit[7]-->bit[0] */ for(mask = 0x80; mask > 0; mask >>= 1) { SCL_H(); // start clock on SCL-line delay_us(2); // clock set-up time (t_SU;CLK) if(READ_SDA()) *byte |= mask; // read bit SCL_L(); delay_us(2); // data hold time(t_HD;DAT) } /* clk #9 send ACK/NAK to slave */ if(ack == ACK) { SDA_OUT(); SDA_L(); // send Acknowledge if necessary } else if( ack == NAK ) { SDA_OUT(); SDA_H(); // send NotAcknowledge if necessary } delay_us(1); // data set-up time (t_SU;DAT) SCL_H(); // clk #9 for ack delay_us(2); // SCL high time (t_HIGH) OUT: SCL_L(); delay_us(2); // wait to see byte package on scope return rv; }
注意:
1.无论是写入一个byte还是读出一个byte,都是从MSB到LSB
2.要改变SDA状态时,都是在SCL为低时改变,如果在SCL为高时改变,将被视为开始或结束信号。
4.封装收发信号
//接受从机发来的信号 int I2C_Master_Receive(uint8_t addr, uint8_t *buf, int len) { int i; int rv = NO_ERROR; uint8_t byte; I2c_StartCondition(); rv = I2c_WriteByte(addr); if( rv ) { sht20_printf("Send I2C read address failure, rv=%d\n", rv); goto OUT; } for (i=0; i<len; i++) { if( !I2c_ReadByte(&byte, ACK) ) { buf[i] = byte; } else goto OUT; } OUT: I2c_StopCondition(); return rv; }
//发送一个命令 int I2C_Master_Transmit(uint8_t addr, uint8_t *data, int bytes) { int i; int rv = NO_ERROR; if(!data) { return PARM_ERROR; } sht20_printf("I2C Mastr start transimit [%d] bytes data to addr [0x%02x]\n", bytes, addr); I2c_StartCondition(); rv = I2c_SendAddress(addr); if( rv ) { goto OUT; } for (i=0; i<bytes; i++) { if( NO_ERROR != (rv=I2c_WriteByte(data[i])) ) { break; } } OUT: I2c_StopCondition(); return rv; }
5.主函数
采用默认的采样精度,温度采样精度14bit,湿度采样精度12bit,采样测量时间如下图1:
图1采样测量时间
图2命令
注意:
1. I2C总线上的从机都有自己的地址,方便主机选择通信,sht20的地址为0x40,加上最后一位读写位的话,写地址为0x80,读地址为0x81。(非保持主机模式采样)
2. 发送采样命令后要等待采样测量时间之后再去读取数据。
//没有检查检验和
short sht20_sample_temp_or_hum(uint8_t cmd)
{
uint8_t data[2];
unsigned short sht20_data=0;
int rv;
// uint8_t tran_cmd=0xF3;
rv=I2C_Master_Transmit(0x80,&cmd,1);
if(0!=rv)
return -1;
if(cmd==0xf3)
HAL_Delay(85);
else if(cmd==0xf5)
HAL_Delay(29);
rv=I2C_Master_Receive(0x81,data,sizeof(data));
if(0!=rv)
return -1;
sht20_data=data[0];
sht20_data=(sht20_data<<8);
sht20_data+=data[1]&0xFC;
if(cmd==0xf3)
{
sht20_data=(short)((-46.85+175.72*sht20_data/65536)*(10));//计算
}
else if(cmd==0xf5)
{
sht20_data=(short)((-6.0+125.0*sht20_data/65536)*(10));//计算
}
printf("sht20_data=%d\n",sht20_data);
return sht20_data;
}
运行结果:
温度测量:
湿度测量:(单位用H代替)