我有兩個含溫度傳感的模塊,一個是AOSONG 奧松電子的 AM2320 溫度濕度,另一個是九軸里面的 Bosch BMP280。由於 AM2320 用 I2C MODBUS,直接用 I2C Tools 它不理我,掃描后地址沒在總線出現,不知道是它沒實現 SMBus 還是要給它功能碼 0x03 喚醒才有東西,代碼我還沒寫出來(其實到現在我都不知道是傳感器壞了還是姿勢不對),AM2320 稍后再試。
GY-91 MPU9250+BMP280
先玩個容易一點的,BMP280,溫度與氣壓傳感芯片。BBB 和 BMP280 都是 3.3V,不需要邏輯轉換,方便。這次我只看溫度,氣壓讀取除了地址和代碼外其他操作大致相同。
雖說二合一方便,但還是好貴…。耗那么多買這個當然是為了做灰機,做出來之前要學習,首先是 I2C。問客服拿原理圖,給了我這個:
看到 MPU9250 的 SDO 有下拉,但不知道是不是跟 BMP280 的 SDO 一起的,后面檢查地址時候發現,應該是的。從原理圖看到 SDA 和 SCL 都有上拉接了個 4.7KΩ,自己連時候就SDA與SCL都不需上拉電阻,直接插 BBB,方便。好,開始動手。
接線
默認 BBB 開機后 I2C-1 已經啟用,地址不沖突就直接插來試試。P9_20 接 SDA,P9_19 接 SCL,P9_1 GND,P9_3 3V3,上電后含BMP280的九軸模塊 LED 綠燈亮起。
命令行工具 i2c-tools
BBB 自帶的 i2c-tools 共有四個命令,用它們就不用寫代碼也能與從機交互:
i2cdetect 檢查元件地址和i2c slots
i2cdump 倒出所有 register 值
i2cset 對 register 寫入值
i2cget 從 register 取出值
除非特別說明,以下全部圖片和內容的地址和值都是 HEX,16進位。看看,I2C-1 當前元件,用 I2C Detect 工具,列出 i2c-1 的所有從機:
掃描總線上的從機地址
i2cdetect –y –r –a 1
參數 –y 是無視交互問題直接執行,-r 是 SMBus read byte 命令,-a 是所有地址,1 是指 i2c-1。
上圖的 UU 是代表系統驅動已占用,剛才說的沒被占用就是指這些。
如果各位在試 I2C 而你的的 I2C 元件地址剛好是 0x54 至 0x57,你可以啟用另一個 I2C,I2C-2 ,一次性連接的做法是 echo BB-I2C2 > /sys/devices/bone-capemgr.9/slots,然后它就會在 i2cdetect –l 里面出現,/dev/ 里面也有多了一個 i2c-2。要開機自動啟用的話請自行修改開機檔啟用。
上圖可見,其他元件的地址出現了,0x68 是我的九軸芯片 MPU9250,請無視。0x76 才是這次要通信的 BMP280,0x76 就是說明書第28頁介紹 I2C 上說的地址:
由於這 BMP280 與九軸是集成在一起成為一個模塊,我貌似是沒辦法控制 SDO 拉高拉低,不過地址不是 1110110 (0x76) 就是 1110111 (0x77)的了,經 i2cdetect 證實是前者 0x76(就是原理圖上 九軸的 SDO 和 BMP280 的 SDO 是一起拉低了,“Connecting SDO to GDN results in slave address 1110110(0x76)”)。
讀取 256 個 register 值
其實BMP280 沒有 256 個,但 i2cdump 默認是 0x00 至 0xFF 寄存器地址的,SPI 交互時 MSB(bit 7) 是 R/W,值 1 時候是讀取,地址 7 bit 長,所以下圖是 10000000(0x80)開始的。挑剔一點的話,應該指定 i2cdump 范圍,由 0x80 至 0xFF。
查到I2C地址后,我用 i2cdump 把 register 全部 dump 出來看看,快捷方便,(據說這樣操作對個別元件有風險,但 BMP280 不在此列,詳情請自行參考 i2cdetect 和 i2cdump 的相關說明):
i2cdump –y 1 0x76
參數 –y 是無視交互問題直接執行,1 是指 i2c-1,0x76 是元件的 I2C 地址。
BMP280 Device ID
很多其他元件的 ID 地址都是 0x00,這設計師把 Device ID 放了在 D0,上圖 i2cdump 可見,0xD0 (d0) 值為 0x58,與說明書 4.3.1 所示的一致:
溫度補償與溫度測量原始值 Register
然后看看我關心的溫度在哪里,說明書上有兩個部分的地址是溫度相關的,從 register 地址順序來說,0x88 至 0x8D 是溫度補償值合共三個,和 0xFA 至 0xFC 溫度測量結果,溫度原始值。
首先溫度補償值,看看說明書怎么講:
dig_T1 至 dig_T3 是溫度補償值。我一開始大意,沒看出這個坑,你看出來了么?LSB 和 MSB 地址是反的,我傻了拿着 LSB 以為是 MSB 花了兩個晚上反復計算和檢查后,才發現自己這個錯誤,在這里溫馨提示,要取溫度補償,記得看清楚它的 LSB 地址和 MSB 地址,希望大家不要步我后塵。
dig_P1 至 dig_P9 是氣壓相關的補償值,本實驗我不關心這些。
然后是溫度的測量值,說明書的 4.2 Memory map 可以看到是 0xFA 至 0xFC,這次沒有反了,0xFA 是 MSB:
注意,temp_xlsb (0xFC)是 <7:4>,temp_xlsb 地址的 bit0 至 bit3 值是零,永遠都是零,沒有用到的(說明書也寫着“Both pressure and temperature values are expected to be received in 20 bit format, positive ......”)。
就是說,要取得溫度原始值,需要讀取 0xFA,0xFB,0xFC 三個接在一起,去掉最后四個 bit 即可。
運行模式
在 Reset 狀態時候,MSB 值是 0x80,LSB 和 XLSB 是 0x00,從之前 dump 的結果可以驗證,啟動后什么都還沒做的時候,是 reset state,值就是 0x80。
這是省電模式嘛。要啟動它,最簡單的辦法就是用 i2cset 對執行模式的 register 寫入值,讓它測量然后放置值於 0xFA 至 0xFC 內。ctrl_meas 0xF4 就是控制相關的 register,從 4.2 Memory Map 可見,0xF4 的 8 bit 值分成了三個部分,osrs_t(3 bit)、osrs_p(3 bit) 和 mode(2 bit),分別代表溫度和氣壓的 oversampling (精度)和 mode 模式。做個試驗而已不講究了,全部調成 1就是全開。寫入 0xFF 進去 0xF4 它就工作了:
i2cset –y 1 0x76 0xF4 0xFF
然后 dump 出來看看:
這次 0xFA 到 0xFC 都有值了。
計算溫度值
用計算器算算現在溫度多少 ~ 公式如下,來自說明書的:
上圖中的 BMP280_S32_t 數據類型是 32 bit signed integer。adc_T 就是 0xFA 至 0xFC 去掉 4 bit 后的值,dig_T1 至 dig_T3 補償值中 T1 是 unsigned,T2 和 T3 是 signed,轉換見下表:
| Register | Addresses & Values (MSB first) | HEX | Binary | Signed ? | Actual Dec Value |
| adc_T | 0xFA = 7B, 0xFB = BB, 0xFC = 60 | 7BBB6(*) | 01111011 10111011 0110 (*) | YES | 506806 |
| dig_T1 | 0x89 = 6D, 0x88 = DB | 6DDB | 01101101 11011011 | NO | 28123 |
| dig_T2 | 0x8B = 67, 0x8A = 39 | 6739 | 01100111 00111001 | YES | 26425 |
| dig_T3 | 0x8D = FC, 0x8C = 18 | FC18 | 11111100 00011000 | YES | -1000 |
| var1 | YES | 91671.395874023438 | |||
| var2 | YES | -188.04323882795870 | |||
| T 實際溫度 | YES | 17.867842 |
(*) 去掉 bit3 至 bit0
成功,17.86 °C,涼快。最后是官方的 C 代碼溫度補償、和轉換原始溫度值和°C的,供參考。注:當然需要自行 define BMP280_S32_t ,類型為 32 bit Signed Integer。
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
BMP280_S32_t t_fine;
BMP280_S32_t bmp280_compensate_T_int32(BMP280_S32_t adc_T)
{
BMP280_S32_t var1, var2, T;
var1 = ((((adc_T>>3) - ((BMP280_S32_t)dig_T1<<1))) * ((BMP280_S32_t)dig_T2)) >> 11;
var2 = (((((adc_T>>4) - ((BMP280_S32_t)dig_T1)) * ((adc_T>>4) - ((BMP280_S32_t)dig_T1))) >> 12) *
((BMP280_S32_t)dig_T3)) >> 14;
t_fine = var1 + var2;
T = (t_fine * 5 + 128) >> 8;
return T;
}
我用以上的代碼算不出來,估計是跟 BBB 數據類型的大小有關,需要調整類型和/或 bit shift 大小,沒研究了不管了。
C 代碼(輸出 256 個 register 值)
首先來個 C 代碼 dump 出來:
/*
* app.c
*
* Created on: 2016-2-23
* Author: Lepton
*/
#include<stdio.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
#define DEVID 0xD0
#define BUFFER_SIZE 256
int main(){
int file;
if((file=open("/dev/i2c-1", O_RDWR)) < 0) {
perror("failed to open the bus\n");
return 1;
}
if(ioctl(file, I2C_SLAVE, 0x76) < 0) {
perror("Failed to connect to the sensor\n");
return 1;
}
char writeBuffer[1] = {0x00} ;
if(write(file, writeBuffer, 1)!=1) {
perror("Failed to reset the read address\n");
return 1;
}
char readBuffer[BUFFER_SIZE];
if(read(file, readBuffer, BUFFER_SIZE)!=BUFFER_SIZE){
perror("Failed to read in the buffer\n");
return 1;
}
int i;
printf("Device ID: 0x%02x\n", readBuffer[DEVID]);
for (i = 0; i<BUFFER_SIZE; i++){
if(i!=0 && i%16==0) printf("\n");
printf("%02x ",readBuffer[i]);
}
printf("\n");
close(file);
return 0;
}
256 bytes 一口氣讀出來,耗內存狂是我。效果:
以上代碼改自於 Exploring Beaglebone 一書上的示范。
C 代碼 (獲取溫度值)
再來,正式獲取溫度了,這次要省省內存,分開了兩個獨立方法做寫入 register 與讀取,讀取時,只讀僅需要的 byte。這次只打開溫度計,不管氣壓不測量。另外,溫度補償部分的數據其實基本不變的,所以,我分開了兩次讀取,相隔幾毫秒沒有太大關系:
/*
* app.c
*
* Created on: 2016-2-23
* Author: Lepton
*/
#include<stdio.h>
#include<stdint.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<linux/i2c.h>
#include<linux/i2c-dev.h>
#define BMP280_ADD 0x76
#define DIG_START 0x88
#define TEMP_START 0xFA
#define CTRL_MEAS 0xF4
#define TEMP_ONLY_NORMAL_MODE 0xE3 // 111 000 11
int writeReg(int pt, unsigned char add, char value){
unsigned char w_buff[2];
w_buff[0] = add;
w_buff[1] = value;
if (write(pt, w_buff, 2) != 2){
perror("Failed write to device");
return 1;
}
return 0;
}
int readReg(int pt, unsigned char s_add, char buf[], int size){
char writeBuff[1] = {s_add};
if(write(pt, writeBuff, 1)!=1) {
perror("Failed to reset the read address\n");
return 1;
}
if(read(pt, buf, size)!=size){
perror("Failed to read in the buffer\n");
return 1;
}
return 0;
}
float myFunc(uint32_t adc_T, unsigned short dig_T1, short dig_T2, short dig_T3){
uint32_t var1, var2;
float T;
var1 = (((double)adc_T)/16384.0-((double)dig_T1)/1024.0)*((double)dig_T2);
var2 = ((((double)adc_T)/131072.0-((double)dig_T1)/8192.0)*(((double)adc_T)/131072.0-((double)dig_T1)/8192.0))*((double)dig_T2);
T = (var1+var2)/5120.0;
return T;
}
int main(){
int file;
if((file=open("/dev/i2c-1", O_RDWR)) < 0) {
perror("failed to open the bus\n");
return 1;
}
if(ioctl(file, I2C_SLAVE, BMP280_ADD) < 0) {
perror("Failed to connect to the sensor\n");
return 1;
}
char dig_buff[6];
char tmp_buff[3];
if(writeReg(file, CTRL_MEAS, TEMP_ONLY_NORMAL_MODE)==1){
return 1;
}
if(readReg(file, DIG_START, dig_buff, 6)==1){
return 1;
}
if(readReg(file, TEMP_START, tmp_buff, 3)==1){
return 1;
}
close(file);
printf("Dump :\n");
int i;
for (i=0; i<6; i++){
printf("%02x ",dig_buff[i]);
}
printf("\n");
for (i=0; i<3; i++){
printf("%02x ",tmp_buff[i]);
}
printf("\n");
int adc_T = ((tmp_buff[0]<<16)|(tmp_buff[1]<<8)|(tmp_buff[2]))>>4;
unsigned short dig_T1 = (dig_buff[1]<<8)|(dig_buff[0]);
short dig_T2 = (dig_buff[3]<<8)|(dig_buff[2]);
short dig_T3 = (dig_buff[5]<<8)|(dig_buff[4]);
printf("adc_T is : %d \n", adc_T);
printf("dig_T1 is : %d \n", dig_T1);
printf("dig_T2 is : %d \n", dig_T2);
printf("dig_T3 is : %d \n", dig_T3);
printf("Temperature is : %f \n", myFunc(adc_T, dig_T1, dig_T2, dig_T3));
return 0;
}
效果:
還有那些看到就煩的拼音檔案名呢 ~ 現在幾度了~ 17.02°C ~
今天到此為止。










![]}L(_M6M(29%%WBBPVOAN[K ]}L(_M6M(29%%WBBPVOAN[K](/image/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvMTEyODkzLzIwMTYwMi8xMTI4OTMtMjAxNjAyMjMxNjE3Mzk1NTItMjk1NTU0ODYyLnBuZw==.png)




