小熊派GD32开发(7)— 使用软件模拟I2C读取SHT30温湿度传感器
一、编写软件模拟I2C驱动程序
在【Devices】下新建soft_i2c.c
和soft_i2c.h
首先,查看硬件原理图,可以看到文件,IIC_SCL使用PB6引脚,IIC_SDA使用PB7引脚可以初始化这两个引脚:
/* 软件模拟IIC引脚初始化 * IIC_SCL --> PB6 * IIC_SDA --> PB7 */ void IIC_Init(void) {
rcu_periph_clock_enable(RCU_GPIOB); /* 使能GPIOB时钟 */ /* 配置IIC_SCL引脚是推挽输出 */ gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6); /* 配置IIC_SDA引脚是推挽输出 */ gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7); gpio_bit_set(GPIOB, GPIO_PIN_6); gpio_bit_set(GPIOB, GPIO_PIN_7); }
因为模拟IIC需要在通信中使用us所以,写一个延迟,us延时函数
/* 描述:us级延时函数 * 参数nus:需要延时的us数 * 返回值:无*/ void delay_us(uint32_t nus) {
uint32_t ticks; uint32_t told, tnow, tcnt = 0; uint32_t reload = SysTick->LOAD; /* 滴答定时器的重装载值 */ ticks = nus * 120; /* 需要的节拍数 */ told = SysTick->VAL; /* 刚进入时的计数器值 */ while(1) {
tnow = SysTick->VAL; if(tnow != told) {
if(tnow < told)tcnt = told - tnow; else tcnt = reload - tnow
+ told
;
if
(tcnt
>= ticks
)
break
;
/* 时间超过/等于要延迟的时间,则退出. */ told
= tnow
;
}
}
}
模拟IIC通信的函数如下
/* 描述:启动I2C总线,即发送I2C起始条件. * 参数: 无 * 返回值:无 */
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA(1);
IIC_SCL(1);
delay_us(4);
IIC_SDA(0);
delay_us(4);
IIC_SCL(0);
}
/* 描述:结束I2C总线,即发送I2C结束条件. * 参数: 无 * 返回值:无 */
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL(0);
IIC_SDA(0);
delay_us(4);
IIC_SCL(1);
delay_us(4);
IIC_SDA(1);
delay_us(4);
}
/* 描述:发送应答 ACK * 参数: 无 * 返回值:无 */
void IIC_ACK(void)
{
SDA_OUT();
IIC_SCL(0);
delay_us(2);
IIC_SDA(0);
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
delay_us(1);
}
/* 描述:发送非应答 NACK * 参数: 无 * 返回值:无 */
void IIC_NACK(void)
{
SDA_OUT();
IIC_SCL(0);
delay_us(2);
IIC_SDA(1);
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
delay_us(1);
}
/* 描述:等待ACK * 参数: 无 * 返回值:等待应答返回0,没有等待到应答返回1 */
uint8_t IIC_wait_ACK(void)
{
uint8_t t = 200;
SDA_OUT();
IIC_SDA(1);
delay_us(1);
IIC_SCL(0);
delay_us(1);
SDA_IN(); /* 数据发送完后释放数据线,准备接收应答位 */
delay_us(1);
while(READ_SDA) /* 等待IIC应答*/
{
t--;
delay_us(1);
if(t==0)
{
IIC_SCL(0);
return 1;
}
delay_us(1);
}
delay_us(1);
IIC_SCL(1);
delay_us(1);
IIC_SCL(0);
delay_us(1);
return 0;
}
/* 描述:一个字节数据发送函数 * 参数: 无 * 返回值:无 */
void IIC_SendByte(uint8_t byte)
{
uint8_t BitCnt;
SDA_OUT();
IIC_SCL(0);
for(BitCnt=0;BitCnt<8;BitCnt++) /* 要传送的数据长度为8位 */
{
if(byte&0x80) IIC_SDA(1); /* 判断发送位 */
else IIC_SDA(0);
byte<<=1;
delay_us(2);
IIC_SCL(1);
delay_us(2);
IIC_SCL(0);
delay_us(2);
}
}
/* 描述:一个字节数据接收函数 * 参数: 无 * 返回值:接收到的字节数据 */
uint8_t IIC_RcvByte(void)
{
uint8_t retc;
uint8_t BitCnt;
retc=0;
SDA_IN(); /* 设置数据线为输入方式 */
delay_us(1);
for(BitCnt=0;BitCnt<8;BitCnt++)
{
IIC_SCL(0); /* 设置时钟线为低,准备接收数据位 */
delay_us(2);
IIC_SCL(1); /* 设置时钟线为高使数据线上数据有效 */
retc=retc<<1;
if(READ_SDA) retc |=1; /* 读数据位,接收的数据位放入retc中 */
delay_us(1);
}
IIC_SCL(0);
return(retc);
}
soft_i2c.h
文件如下,
#ifndef __SOFT_I2C_H_
#define __SOFT_I2C_H_
#include "gd32f30x.h"
/* 设置IIC_SDA的方向,输入还是输出 */
#define SDA_IN() {GPIO_CTL0(GPIOB)&=~GPIO_MODE_MASK(7);GPIO_CTL0(GPIOB)|=GPIO_MODE_SET(7,0x8);}//PB9输入模式
#define SDA_OUT() {GPIO_CTL0(GPIOB)&=~GPIO_MODE_MASK(7);GPIO_CTL0(GPIOB)|=GPIO_MODE_SET(7,0x3);}//PB9输出模式
/* 设置SCL和SDA输出电平,以及读取SDA电平 */
#define IIC_SCL(n) (n?gpio_bit_set(GPIOB, GPIO_PIN_6):gpio_bit_reset(GPIOB, GPIO_PIN_6))
#define IIC_SDA(n) (n?gpio_bit_set(GPIOB, GPIO_PIN_7):gpio_bit_reset(GPIOB, GPIO_PIN_7))
#define READ_SDA gpio_input_bit_get(GPIOB,GPIO_PIN_7)
void IIC_Init(void); /* 软件模拟IIC引脚初始化 */
void IIC_Start(void); /* 启动I2C总线,即发送I2C起始条件 */
void IIC_Stop(void); /* 结束I2C总线,即发送I2C结束条件 */
void IIC_ACK(void); /* 发送应答 ACK */
void IIC_NACK(void); /* 发送非应答 NACK */
uint8_t IIC_wait_ACK(void); /* 等待ACK */
void IIC_SendByte(uint8_t byte);/* 一个字节数据发送函数 */
uint8_t IIC_RcvByte(void); /* 一个字节数据接收函数 */
#endif
二、编写SHT30应用程序
在【Devices】下新建sht3x.c
和sht3x.h
文件,头文件如下
#ifndef __SHT3X_H_
#define __SHT3X_H_
#include "gd32f30x.h"
#include "systick.h"
uint8_t SHT3x_Init(void); /* 描述:SHT3x初始化函数 */
uint8_t SHT3x_ReadSerialNumber(uint32_t* serialNumber);
uint8_t SHT3x_Get_Humiture_single(double *Tem_val,double *Hum_val); /* 单次获取 */
uint8_t SHT3x_Get_Humiture_periodic(double *Tem_val,double *Hum_val); /* 周期获取 */
/* 枚举SHT3x命令列表 */
typedef enum
{
/* 软件复位命令 */
SOFT_RESET_CMD = 0x30A2,
/* 单次测量模式 命名格式:Repeatability_CS_CMD CS: Clock stretching */
HIGH_ENABLED_CMD = 0x2C06,
MEDIUM_ENABLED_CMD = 0x2C0D,
LOW_ENABLED_CMD = 0x2C10,
HIGH_DISABLED_CMD = 0x2400,
MEDIUM_DISABLED_CMD = 0x240B,
LOW_DISABLED_CMD = 0x2416,
/* 周期测量模式 命名格式:Repeatability_MPS_CMD MPS:measurement per second */
HIGH_0_5_CMD = 0x2032,
MEDIUM_0_5_CMD = 0x2024,
LOW_0_5_CMD = 0x202F,
HIGH_1_CMD = 0x2130,
MEDIUM_1_CMD = 0x2126,
LOW_1_CMD = 0x212D,
HIGH_2_CMD = 0x2236,
MEDIUM_2_CMD = 0x2220,
LOW_2_CMD = 0x222B,
HIGH_4_CMD = 0x2334,
MEDIUM_4_CMD = 0x2322,
LOW_4_CMD = 0x2329,
HIGH_10_CMD = 0x2737,
MEDIUM_10_CMD = 0x2721,
LOW_10_CMD = 0x272A,
/* 周期测量模式读取数据命令 */
READOUT_FOR_PERIODIC_MODE = 0xE000,
/* 读取传感器编号命令 */
READ_SERIAL_NUMBER = 0x3780,
} SHT3X_CMD;
#endif
在sht3x.c
文件中,先将读写标志以及SHT30的地址设置好:
#define write 0
#define read 1
uint8_t addr = 0x44; //设置传感器地址
然后就是发送命令和接收数据函数
/* 描述:向SHT30发送一条16bit指令 * 参数cmd:SHT30指令(在SHT30_MODE中枚举定义) * 返回值:发送成功返回0,发送失败返回1 */
static uint8_t SHT3x_Send_Cmd(SHT3X_CMD cmd)
{
uint8_t cmd_buffer[2];
uint8_t ret;
cmd_buffer[0] = cmd >> 8;
cmd_buffer[1] = cmd;
IIC_SendByte(addr<<1 | write); /* 写7位I2C设备地址加0作为写取位 */
ret = IIC_wait_ACK();
IIC_SendByte(cmd_buffer[0]);
ret |= IIC_wait_ACK();
IIC_SendByte(cmd_buffer[1]);
ret |= IIC_wait_ACK();
return ret;
}
/* 描述:从SHT3x读取数据 * 参数data_len:读取多少个字节数据 * 参数data_arr:读取的数据存放在一个数组里 * 返回值:读取成功返回0,读取失败返回1 */
static uint8_t SHT3x_Recv_Data(uint8_t data_len, uint8_t* data_arr)
{
uint8_t ret,i;
IIC_SendByte(addr<<1 | read); /* 写7位I2C设备地址加1为读取位 */
ret = IIC_wait_ACK();
if(ret != 0) return 1;
for(i = 0; i < (data_len - 1); i++)
{
data_arr[i]=IIC_RcvByte();
IIC_ACK();
}
data_arr[i]=IIC_RcvByte();
IIC_NACK();
return 0;
}
接下来我们测试一下读取传感器的编号
/* 描述:读取传感器编号 * 参数:存储编号数据的指针 * 返回值:0-读取成功,1-读取失败 */
uint8_t SHT3x_ReadSerialNumber(uint32_t* serialNumber)
{
uint8_t ret = 0;
uint8_t Num_buf[4] = {
0xFF,0xFF,0xFF,0xFF};
IIC_Start();
SHT3x_Send_Cmd(READ_SERIAL_NUMBER);
IIC_Stop();
delay_1ms(10); /* 有问题时需要适当延长!!!!!!*/
IIC_Start();
ret = SHT3x_Recv_Data(4,Num_buf);
IIC_Stop();
*serialNumber = ((Num_buf[0] << 24) | (Num_buf[1] << 16) |(Num_buf[2] << 8) |(Num_buf[3]));
if(0xFF == *serialNumber) return 1;
return ret;
}
在main函数中调用,编译下载运行,可以得到其编号: 然后我们将其设置为周期读取模式,周期读取数据
/* 描述:数据CRC校验 * 参数message:需要校验的数据 * 参数initial_value:crc初始值 * 返回值:计算得到的CRC码 */
#define CRC8_POLYNOMIAL 0x131
uint8_t CheckCrc8(uint8_t* const message, uint8_t initial_value)
{
uint8_t remainder; //余数
uint8_t i = 0, j = 0; //循环变量
/* 初始化 */
remainder = initial_value;
for(j = 0; j < 2;j++)
{
remainder ^= message[j];
/* 从最高位开始依次计算 */
for (i = 0; i < 8; i++)
{
if (remainder & 0x80)
remainder = (remainder << 1)^CRC8_POLYNOMIAL;
else
remainder = (remainder << 1);
}
}
/* 返回计算的CRC码 */
return remainder;
}
/* 描述:温湿度数据获取函数,周期读取,注意,需要提前设置周期模式 * 参数Tem_val:存储温度数据的指针, 温度单位为°C * 参数Hum_val:存储湿度数据的指针, 温度单位为% * 返回值:0-读取成功,1-读取失败 ********************************************************************/
uint8_t SHT3x_Get_Humiture_periodic(double *Tem_val,double *Hum_val)
{
uint8_t ret=0;
uint8_t buff[6];
uint16_t tem,hum;
double Temperature=0;
double Humidity=0;
IIC_Start();
ret = SHT3x_Send_Cmd(READOUT_FOR_PERIODIC_MODE);
IIC_Start();
ret = SHT3x_Recv_Data(6,buff);
IIC_Stop();
/* 校验温度数据和湿度数据是否接收正确 */
if(CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5])
{
printf("CRC_ERROR,ret = 0x%x\r\n",ret);
return 1;
}
/* 转换温度数据 */
tem = (((uint16_t)buff[0]<<8) | buff[1]);//温度数据拼接
Temperature= (175.0*(double)tem/65535.0-45.0) ; // T = -45 + 175 * tem / (2^16-1)
/* 转换湿度数据 */
hum = (((uint16_t)buff[3]<<8) | buff[4]);//湿度数据拼接
Humidity= (100.0*(double)hum/65535.0); // RH = hum*100 / (2^16-1)
/* 过滤错误数据 */
if((Temperature>=-20)&&(Temperature<=125)&&(Humidity>=0)&&(Humidity<=100))
{
*Tem_val = Temperature;
*Hum_val = Humidity;
return 0;
}
else
return 1;
}
在主函数中,每隔一秒读取一次数据
if( 0 == SHT3x_Init())
printf("SHT3x_Init OK \r\n");
else
printf("SHT3x_Init ERR \r\n");
while(1)
{
delay_1ms(1000);
if(SHT3x_Get_Humiture_periodic(&Tem_val,&Hum_val) == 0)
printf("Tem_val:%f C,\t&Hum_val:%f %%Rh\r\n",Tem_val,Hum_val);
else
printf("Get_Humiture ERR\r\n");
}
编译下载运行,可以得到,读取数据成功 另外,也可以单次读取
/* 描述:温湿度数据获取函数,单次获取 * 参数Tem_val:存储温度数据的指针, 温度单位为°C * 参数Hum_val:存储湿度数据的指针, 温度单位为% * 返回值:0-读取成功,1-读取失败 ********************************************************************/ uint8_t SHT3x_Get_Humiture_single(double *Tem_val,double *Hum_val) { uint8_t ret=0; uint8_t buff[6]; uint16_t tem,hum; double Temperature=0; double Humidity=0; IIC_Start(); SHT3x_Send_Cmd(HIGH_ENABLED_CMD); IIC_Stop(); delay_1ms(50); IIC_Start()