SHT30使用的學(xué)習(xí)過程2代碼篇
給各位道個(gè)歉,代碼拖得有點(diǎn)久了,最近事情頗多,抱歉抱歉!
綜述
嗯,代碼篇我想把我寫的所有的代碼給各位需要使用sht30的朋友們介紹一遍,由于我這版是測(cè)試版,所以很多函數(shù)沒有封裝的很好,不過代碼可以用了,我測(cè)試的代碼已經(jīng)通過,測(cè)量溫度和濕度精確到小數(shù)點(diǎn)后1位,在這里想仔細(xì)給各位介紹一下我代碼的寫作過程,因?yàn)榫W(wǎng)上的代碼僅僅是代碼,很多開發(fā)sht30的小白(像我這樣的)沒辦法移植,或者根本不知道怎么移植,在這里我想詳細(xì)敘述我的代碼,包括最基本的I2C通信,所以可能本次內(nèi)容很啰嗦,希望各位見諒哈[by zwx lvmm]
I2C代碼部分
這部分是SHT30和單片機(jī)通信的基礎(chǔ)協(xié)議,I2C有四根線組成,除去vcc和gnd之外還有SCL (時(shí)鐘線)與 SDA (數(shù)據(jù)線),STM32F407參考手冊(cè)上寫,I2C最大的通信周期是4MHz,普通模式下是2MHz。這部分涉及I2C通信的時(shí)序,可以參考原子哥(正點(diǎn)原子)的相關(guān)視頻資源,我的基礎(chǔ)也是和原子哥的視頻學(xué)習(xí)的,這里簡(jiǎn)單介紹一下,有什么不清楚的可以參考原子哥的視頻。
- I2C時(shí)序:
這個(gè)是I2C時(shí)序圖。整個(gè)I2C通信分為這樣幾個(gè)過程,I2C起始信號(hào),I2C數(shù)據(jù)寫入,I2C數(shù)據(jù)讀取,I2C應(yīng)答信號(hào),I2C結(jié)束信號(hào)等,接下來分別介紹。(代碼參考的原子哥代碼,我看懂了直接用的)
1.1 IO初始化:
上代碼,我的開發(fā)板是STM32F407,這部分屬于初始化配置,沒啥說的。
//IO方向設(shè)置
#define SDA_IN() {GPIOB->MODER&=~(3<<(11*2));GPIOB->MODER|=0<<11*2;} //PB11輸入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(11*2));GPIOB->MODER|=1<<11*2;} //PB11輸出模式
//IO操作函數(shù)
#define IIC_SCL PBout(10) //SCL
#define IIC_SDA PBout(11) //SDA
#define READ_SDA PBin(11) //輸入SDA
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB時(shí)鐘
//GPIOB10,B11初始化設(shè)置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通輸出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
IIC_SCL=1;
IIC_SDA=1;
}
此部分代碼看的原子哥的,IO配置的方法也是學(xué)習(xí)原子哥的設(shè)置的。
1.2 I2C起始信號(hào):
如最開始的圖所示當(dāng)SCL是高電平的時(shí)候,把SDA從高電平拉至低電平就可以了,先上這部分的代碼
void IIC_Start(void)
{
SDA_OUT(); //sda線輸出模式
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//開始信號(hào),scl=1,sda=1->sda=0。
delay_us(4);
IIC_SCL=0;//scl=0,拉低時(shí)鐘線,準(zhǔn)備數(shù)據(jù)的發(fā)送
}
這里為什么是延時(shí)4us,我猜測(cè)是按照標(biāo)準(zhǔn)頻率2MHz,最高4MHz計(jì)算一下,一個(gè)數(shù)據(jù)周期大概5us-2.5us之間,還要考慮信號(hào)的建立時(shí)間,所以選擇了4us作為一個(gè)周期。其余的不懂得可以看I2C的介紹。
1.3 I2C停止信號(hào):
void IIC_Stop(void)
{
SDA_OUT();//sda線輸出模式
IIC_SCL=0;
IIC_SDA=0;//
delay_us(4);
IIC_SCL=1; //結(jié)束信號(hào),scl=1,sda=0->sda=1。
IIC_SDA=1;
delay_us(4);
}
1.4 I2C應(yīng)答信號(hào)和等待應(yīng)答信號(hào):
這個(gè)東西就是說,機(jī)器之間的通信嘛,肯定是像我們使用對(duì)講機(jī)一樣,說完了一句話,就要加一句over,對(duì)方聽到了你的over,才能說他想說的,要不然就會(huì)兩個(gè)人一起說,肯定亂套了,所以才會(huì)有這兩個(gè)信號(hào),而有的時(shí)候我們不用說over,比如我們對(duì)話結(jié)束了,和對(duì)方說再見,說完就意味著對(duì)話結(jié)束了,所以也可以不產(chǎn)生應(yīng)答信號(hào)。代碼如下,具體的時(shí)序邏輯同樣參考原子哥,各位可以自己學(xué)習(xí)一下,如果你沒學(xué),就理解一下這個(gè)代碼是干啥的就行,也一樣可以寫代碼。
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA設(shè)置為輸入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//時(shí)鐘輸出0
return 0;
}
1.5 I2C發(fā)送數(shù)據(jù):
這部分是比較關(guān)鍵的,I2C發(fā)送數(shù)據(jù)這里是8位的模式,我看到的I2C一般都是數(shù)據(jù)8位8位發(fā)的,發(fā)送的時(shí)候一個(gè)周期是4us,SCL是高電平的時(shí)候要保證數(shù)據(jù)有效,所以數(shù)據(jù)必須要在SCL變高之前建立完畢,這里可能有些人不太明白數(shù)據(jù)的建立是什么意思,其實(shí)就是點(diǎn)平從0變?yōu)?不是一瞬間變化完成的,而實(shí)經(jīng)過一段時(shí)間漲上去的,其實(shí)就是需要一段時(shí)間才能點(diǎn)平0->1或者1->0,雖然這段時(shí)間很短,但是對(duì)于一些速度比較快的協(xié)議可能就不能忽略這個(gè)影響了。>>這個(gè)是移位操作符,不懂得可以去查一查。
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低時(shí)鐘開始數(shù)據(jù)傳輸
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(1);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(1);
}
}
1.6 I2C接收數(shù)據(jù):
這部分接收,和發(fā)送差不多,每個(gè)周期都分成了兩個(gè)2us
//一個(gè)參數(shù) ack 當(dāng)ack=1,發(fā)送應(yīng)答信號(hào),ack=0不發(fā)送應(yīng)答信號(hào)
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA設(shè)置為輸入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(2);
}
if (!ack)
IIC_NAck();//發(fā)送nACK
else
IIC_Ack(); //發(fā)送ACK
return receive;
}
2. SHT30代碼部分
這部分代碼是關(guān)于SHT30與單片機(jī)之間的通信的流程和對(duì)讀到的數(shù)據(jù)進(jìn)行處理,包括初始化,讀取數(shù)據(jù)與數(shù)據(jù)處理,這部分代碼是閱讀datasheet之后結(jié)合datasheet相關(guān)內(nèi)容寫的。
2.1 SHT30的初始化:
這個(gè)我是采用的SHT30的周期模式,這部分的內(nèi)容大家可以參考我上一篇博客所寫的,里面給出了相關(guān)內(nèi)容的介紹。
關(guān)于SHT30 datasheet的相關(guān)內(nèi)容
流程:
2C開始信號(hào)->7位I2C地址+0(寫操作標(biāo)志位)(前面介紹了,如果ADDR接低電平,那么這里就是0x88,如果接高電平就是0x8a)->命令MSB->命令LSB(eg 0x2130 高可重復(fù)性,1秒測(cè)量一次)-> I2C停止信號(hào)。
void SHT_Init(void)
{
delay_ms(250);
//0x2130 表示周期模式 周期1ms
IIC_Start();
IIC_Send_Byte(0x88);
IIC_Wait_Ack();
IIC_Send_Byte(0x21);
IIC_Wait_Ack();
IIC_Send_Byte(0x30);
IIC_Wait_Ack();
IIC_Stop();
delay_ms(150);
}
這樣設(shè)置完畢之后,SHT30每1秒就會(huì)自動(dòng)測(cè)量一次溫度,相關(guān)寄存器就更新一次值。
2.2 SHT30的讀取數(shù)據(jù):
我加了注釋,各位看代碼的注釋:
void sht30_read_temp_humi(u8 *p)
{
//這里和前面的一樣,也是寫一個(gè)命令給SHT30,這個(gè)命令是訪問SHT30轉(zhuǎn)換結(jié)果的寄存器的
IIC_Start();
IIC_Send_Byte(0x88);
IIC_Wait_Ack();
IIC_Send_Byte(0xe0);
IIC_Wait_Ack();
IIC_Send_Byte(0x00);
IIC_Wait_Ack();
//下面是開始讀取數(shù)據(jù),其中的數(shù)組p存放結(jié)果,前三個(gè)存放溫度值,后三個(gè)是濕度值,在前三個(gè)溫度值里面,
//p[0]是溫度的高八位,p[1]是低八位,p[2]是CRC校驗(yàn),有關(guān)CRC校驗(yàn)的知識(shí)我是百度上面看的,
//要是各位不懂得話,可以不用crc校驗(yàn),直接用p[0]、p[1]就可以轉(zhuǎn)換出來溫度的值。
IIC_Start();
IIC_Send_Byte(0x89);
IIC_Wait_Ack();
//前五次讀取都要發(fā)送ack信號(hào),最后一次就不用發(fā)了。
p[0] = IIC_Read_Byte(1);
p[1] = IIC_Read_Byte(1);
p[2] = IIC_Read_Byte(1);
p[3] = IIC_Read_Byte(1);
p[4] = IIC_Read_Byte(1);
p[5] = IIC_Read_Byte(0);
IIC_Stop();
}
2.3 數(shù)據(jù)處理函數(shù)
這里面有兩個(gè)全局變量用來存放轉(zhuǎn)換的溫度和濕度數(shù)據(jù),下面我也寫了數(shù)據(jù),各位可以參考
int sht30_data_process(void)
{
u8 temporary[3];
u16 data;
u8 crc_result;
//data_process.sht30_data_buffer這個(gè)變量是我程序里面使用的存放SHT30寄存器讀到的值的數(shù)組,
//定義類型為 unsigned short int sht30_data_buffer[6]
sht30_read_temp_humi(data_process.sht30_data_buffer);
//先處理溫度的相關(guān)數(shù)據(jù),位于數(shù)組的前三個(gè)
temporary[0]=data_process.sht30_data_buffer[0];
temporary[1]=data_process.sht30_data_buffer[1];
temporary[2]=data_process.sht30_data_buffer[2];
//crc校驗(yàn)
crc_result=sht30_crc8_check(temporary,2,temporary[2]);
//crc校驗(yàn)要是不成功就返回1,同時(shí)不會(huì)更新溫度值
if(crc_result==0)
{
//把2個(gè)8位數(shù)據(jù)拼接為一個(gè)16位的數(shù)據(jù)
data=((uint16)temporary[0] << 8) | temporary[1];
//溫度轉(zhuǎn)換,將16位溫度數(shù)據(jù)轉(zhuǎn)化為10進(jìn)制的溫度數(shù)據(jù),這里保留了一位小數(shù),data_process.SHT30_temperature這是一個(gè)全局變量,至于為什么變量名字里面有個(gè).不懂得各位可以百度一下c語言結(jié)構(gòu)體的相關(guān)說明。
data_process.SHT30_temperature = (int)((175.0 * ((float)data) / 65535.0 - 45.0) *10.0);
}
else
{
return 1;
}
temporary[0]=data_process.sht30_data_buffer[3];
temporary[1]=data_process.sht30_data_buffer[4];
temporary[2]=data_process.sht30_data_buffer[5];
//crc校驗(yàn)
crc_result=sht30_crc8_check(temporary,2,temporary[2]);
if(crc_result==0)
{
//參考上面溫度的代碼
data=((uint16)temporary[0] << 8) | temporary[1];
data_process.SHT30_humidity = (int)((100.0 * (float)data / 65535.0) *10.0);
return 0;
}
else
{
return 2;
}
}
2.4 CRC校驗(yàn)函數(shù)
關(guān)于CRC校驗(yàn)的相關(guān)知識(shí)可以百度查一下,我對(duì)這個(gè)了解不是很深,我只知道這就是一個(gè)校驗(yàn)碼,是確保通信過程中數(shù)據(jù)傳輸沒問題的,沒有缺少任何數(shù)據(jù),當(dāng)我們接收到數(shù)據(jù)后,按照一定的方式計(jì)算一個(gè)crc校驗(yàn)碼,經(jīng)過和提供的crc校驗(yàn)碼進(jìn)行比較,如果兩個(gè)碼一樣,那么數(shù)據(jù)就是沒問題的。以前做別的比賽的時(shí)候聽大佬說過計(jì)算過程,就寫在下面了,下面有一個(gè)數(shù)字0x31那個(gè)是crc校驗(yàn)所用的函數(shù),數(shù)據(jù)手冊(cè)上寫了使用0x31,這里就直接用了
int crc8_compute(u8 *check_data, u8 num_of_data)
{
uint8_t bit; // bit mask
uint8_t crc = 0xFF; // calculated checksum
uint8_t byteCtr; // byte counter
// calculates 8-Bit checksum with given polynomial
for(byteCtr = 0; byteCtr < num_of_data; byteCtr++) {
crc ^= (check_data[byteCtr]);
//crc校驗(yàn),最高位是1就^0x31
for(bit = 8; bit > 0; --bit) {
if(crc & 0x80) {
crc = (crc << 1) ^ 0x31;
} else {
crc = (crc << 1);
}
}
}
return crc;
}
int sht30_crc8_check(u8 *p,u8 num_of_data,u8 CrcData)
{
uint8_t crc;
crc = crc8_compute(p, num_of_data);// calculates 8-Bit checksum
if(crc != CrcData)
{
return 1;
}
return 0;
}
最后說一下怎么使用我的這些代碼,大家可以把所有的代碼都放到一個(gè)c文件里面,然后主函數(shù)里面先初始化一下IO口,然后初始化一下SHT30,之后就可以調(diào)用一次sht30_data_process();這個(gè)函數(shù)就可以得到溫度值了(別忘記那個(gè)函數(shù)里面提到的全局變量)(我設(shè)置的模式是1s一次,所以SHT30內(nèi)部寄存器只會(huì)1s改變一次數(shù)據(jù),如果程序設(shè)置的讀取周期太快,也不會(huì)一直變化哦,也是1s一次,如果想要刷新頻率快一些,可以嘗試其他模式,不過太快了個(gè)人感覺沒什么必要)。
親自測(cè)試過了,這個(gè)代碼可以用,測(cè)得的室溫是26.1度左右,數(shù)據(jù)不是很穩(wěn)定,有0.2度左右的波動(dòng)。由于數(shù)據(jù)顯示在0.96的OLED上,太小了,照片不是很清楚,就沒圖了~
技術(shù)小白自己摸爬滾打?qū)懙拇a,希望大佬指正~~感謝感謝,也希望幫助有需要的人。
|