首先我采用的是,周期性读取的方法。
实际效果如下:
需要注意在手册单中明确的精度,以及测量数据如何计算。
其中的ST主要就是我们两个拼接的16位寄存器数据。简单换算就行。
为了防止与库函数当中的I2C重名,我自己另起名位MYI2C,自己可以修改:
其中延时的函数需要自己添加。如果需要可以留言我会发源码。
另外对于其中一些检测我没有添加。
#include "stm32f10x.h" // Device header
#include "Delay.h" // Device header
/**
* @brief SCL电平设置
* @param 需要设置的高低电平
* @retval 无返回值
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
/**
* @brief SDA电平设置
* @param 需要设置的高低电平
* @retval 无返回值
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
/**
* @brief SDA电平读取
* @param 无参数
* @retval 读取的SDA高低电平
*/
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
/**
* @brief I2C初始化
* @param 无参数
* @retval 无返回值
*/
void MYI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//配置位开漏输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);//使SDA和SCL均为高电平,空闲状态
}
/**
* @brief I2C开始信号
* @param 无形参
* @retval 无返回值
*/
void MYI2C_START(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
/**
* @brief I2C结束信号
* @param 无形参
* @retval 无返回值
*/
void MYI2C_STOP(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
/**
* @brief 发送字节
* @param 需要发送的字节
* @retval 无返回值
*/
void MYI2C_SENDBYTE(uint8_t byte)
{
uint8_t n=0;
for(n=0;n<8;n++)
{
MyI2C_W_SDA(byte&(0x80>>n));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
/**
* @brief 接收字节
* @param 无形参
* @retval 从SDA上接受的数据
*/
uint8_t MYI2C_RECEIVEBYTE(void)
{
uint8_t Data=0X00;
uint8_t n=0;
MyI2C_W_SDA(1);
for(n=0;n<8;n++)
{
MyI2C_W_SCL(1);
if( MyI2C_R_SDA()==1){Data|=(0x80>>n);}
MyI2C_W_SCL(0);
}
return Data;
}
/**
* @brief 发送应答位
* @param 主机需要发送的应答位
* @retval 无返回值
*/
void MYI2C_SENDACKBIT(uint8_t Ackbit)
{
// MyI2C_W_SCL(0);
MyI2C_W_SDA(Ackbit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
/**
* @brief 接收从机应答位
* @param 无形参
* @retval 从机给的应答
*/
uint8_t MYI2C_RECEIVEAckbit(void)
{
uint8_t Ackbit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
Ackbit=MyI2C_R_SDA();
MyI2C_W_SCL(0);
return Ackbit;
}
MYI2C.H
#ifndef _MYI2C__H__
#define __MYI2C_H__
void MyI2C_W_SCL(uint8_t BitValue);
void MyI2C_W_SDA(uint8_t BitValue);
uint8_t MyI2C_R_SDA(void);
void MYI2C_Init(void);
void MYI2C_START(void);
void MYI2C_STOP(void);
void MYI2C_SENDBYTE(uint8_t byte);
uint8_t MYI2C_RECEIVEBYTE(void);
void MYI2C_SENDACKBIT(uint8_t Ackbit);
uint8_t MYI2C_RECEIVEAckbit(void);
#endif
SHT30.c
其中数据读取部分,首先我们需要读取的是两个数据,但是返回值只能由一个。
方法我一时间想到两个,1.读取函数外创建数组,将指针传进去,我们操作指针自增存数据。
2.创建两个指针变量记得初始化,操作指针读取数据。由于数据就2个我采用第二个。
其中如果自己数据没有读取出来可以通过我自己添加的应答位显示检查时序非常直观,当然也可以用示波器如果条件允许。我一开始也是挨个检查发现问题在于没有给从机足够测量导致数据满格
如下。后面逐个打印应答位发现问题所在。
#include "stm32f10x.h" // Device header
#include "MYI2C.h"
#include "OLED.h"
#include "Delay.h"
#define SHT30_ADDRESS 0x44
#define MSBRegaddress 0Xe0
#define LSBRegaddress 0x00
void SHT30_Init(void)
{
MYI2C_Init();
}
/**
* @brief 需要写入从机的数据
* @param 地址,数据/数据,数据
* @retval 无返回值
*/
void SHT30_WriteByte(uint8_t x,uint8_t y)
{
uint8_t n;
MYI2C_START();
MYI2C_SENDBYTE((SHT30_ADDRESS<<1)|0);//写入
MYI2C_RECEIVEAckbit();
MYI2C_SENDBYTE(x);//0x21
MYI2C_RECEIVEAckbit();
MYI2C_SENDBYTE(y);//0x30
n=MYI2C_RECEIVEAckbit();
// OLED_ShowNum(1,5,n,3);
MYI2C_STOP();
Delay_ms(20);//给从机测量时间,非常重要!!!!
}
/**
* @brief 温湿度读取
* @param 传入温度,湿度指针
* @retval 无返回值
*/
void SHT30_ReadByte(float *Temperature,float *Humidity)
{
uint16_t temp,humi;
uint16_t data[6];
uint8_t n,m,a,b,c,d;
MYI2C_START();
MYI2C_SENDBYTE((SHT30_ADDRESS<<1)|0);//读取
a=MYI2C_RECEIVEAckbit();
// OLED_ShowNum(1,1,n,3);
MYI2C_SENDBYTE(MSBRegaddress);//0xe0
b=MYI2C_RECEIVEAckbit();
// OLED_ShowNum(2,1,n,3);
MYI2C_SENDBYTE(LSBRegaddress);//0x00
c=MYI2C_RECEIVEAckbit();
// OLED_ShowNum(3,1,n,3);
MYI2C_START();
MYI2C_SENDBYTE((SHT30_ADDRESS<<1)|1);//读取数据
n=MYI2C_RECEIVEAckbit();
// OLED_ShowNum(4,1,n,3);
//温度
data[0]=MYI2C_RECEIVEBYTE();
MYI2C_SENDACKBIT(0);
data[1]=MYI2C_RECEIVEBYTE();
MYI2C_SENDACKBIT(0);
//温度校验位
data[2]=MYI2C_RECEIVEBYTE();
MYI2C_SENDACKBIT(0);
//湿度
data[3]=MYI2C_RECEIVEBYTE();
MYI2C_SENDACKBIT(0);
data[4]=MYI2C_RECEIVEBYTE();
MYI2C_SENDACKBIT(0);
//CRC校验位
data[5]=MYI2C_RECEIVEBYTE();
MYI2C_SENDACKBIT(1);
MYI2C_STOP();
temp=(data[0]<<8)|data[1];
humi=(data[3]<<8)|data[4];
/*转换实际温度*/
*Temperature=(175.0*(float)temp/65535.0-45.0) ;// T = -45 + 175 * tem / (2^16-1)
*Humidity=(100.0*(float)humi/65535.0);// RH = hum*100 / (2^16-1);
}
/**
* @brief 温湿度数据显示
* @param 无形参
* @retval 无返回值
*/
void SHT30_SHOWVALUE(void)
{
float Temperature=0x0000;
float Humidity=0x0000;
float*PT=&Temperature;
float*PH=&Humidity;
SHT30_Init();
SHT30_WriteByte(0x21,0x30);
SHT30_ReadByte(PT,PH);//建立两个指针变量,操作指针读取数据
OLED_ShowString(1,1,"Temperature:");
OLED_ShowString(3,1,"Humidity:");
OLED_ShowNum(2,1,Temperature,3);//整数部分
OLED_ShowChar(2,4,'.');
OLED_ShowNum(2,5,(uint16_t)(Temperature*1000)%1000,2);//取出小数部分显示
OLED_ShowNum(4,1,Humidity,2);//整数部分
OLED_ShowChar(4,3,'.');
OLED_ShowNum(4,4,(uint16_t)(Humidity*1000)%1000,2);//取出小数部分显示
Delay_s(1);//防止数据测量太快不断闪烁,加1s延迟;
}
个人总结一下I2C需要注意地方:
1.从机地址是否正确;
2.要写入寄存器地址是否正确;
3.读取数据之前要查手册了解最短测量时间,留充足。
4.每个应答位是否发送正确,特别注意在读取的时候每读取一个主机要向从机发送应答0表示受到。以及在不读取之前主机要发送应答位1表示停止接收,不然容易出现数据紊乱。
5.考虑电平拉高低的时间,SHT30是1MHZ的通信速度考虑就比较少,实际要注意。
检查方法:
有钱的可以用逻辑分析仪/示波器直接看波形,没有的或者觉得麻烦看不懂的可以学我打印应答位直观明了。
相比较硬件简单配置而言,很多地方,譬如不好检查错误,不好查看波形,以及通用性不强,很多设备不具备I2C硬件外设。当然模拟有助于自己理解I2C通信。也可以看我之前写的I2C介绍了解更多。仅仅代表个人拙见,欢迎指出错误。需要具体源码可以留言。
补充实验现象:
我这是以前做的一个项目:小型气象站系统,写的温湿度代码,实际测量情况如下:
这里面包含了对于SHT30,BH1750 等等的测量。