電子工作といえば色々なセンサーを使ってみたいですよね。
温度・湿度センサーや加速度センサー、ジェスチャーセンサーなんてものもあります。多くのセンサーは I2C 通信で扱えるので一つやり方を覚えてしまえば色々なセンサーに応用することができます。
今回は秋月電子で販売されているこちらの温湿度・気圧センサー BME280 を例にセンサーの値を読み取って前回紹介した LCD に表示させてみたいと思います。
前回の記事はこちら
I2C通信についてはこちらで詳しく解説しています。
使用するもの
LCDに関して、前回の記事では秋月電子の 8 x 2 小型LCDモジュールを使用しましたが、今回はストロベリーリナックスの 16 x 2 LCDモジュールを使用しました。どちらもライブラリを変更することなく全く同じように扱えます。
書き込み・読み取りの流れ
センサーを扱うときには、値を読み取るのはもちろん、設定などを書き込むことも必要になります。まず初めにそれぞれの通信の流れを紹介します。
●書き込み
- スレーブアドレス送信 (R/W = 0)
- 書き込むレジスタアドレス送信
- データ送信
●読み取り
- スレーブアドレス送信 (R/W = 0)
- 読み取るレジスタアドレス送信
- 通信再スタート
- スレーブアドレス送信 (R/W = 1)
- データ受信
書き込みと読み取りのどちらの場合でも、まずは Write モードで通信を開始して、最初のデータでレジスタのアドレスを指定します。
読み取りの通信再スタートは、RepeatedStartCondition を送信しても、一度通信を終了して再び StartCondition を送信してもどちらでも構いません。
書き込んだり読み取ったりすると、普通レジスタのアドレスが +1 されるので、連続したレジスタには連続してアクセスすることが可能です。ただし、この点はセンサーによって違うこともあるのでデータシートを確認した方が良いです。
ライブラリの紹介
前々回の記事で I2C 通信の基本ライブラリを作成しましたが、スタートビットの送信からいちいち書くのはあまり現実的ではないので、今回はそれを拡張して実用的なライブラリを作成しました。
最後の3つが今回拡張した関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#include<xc.h> #define _XTAL_FREQ 4000000 void I2C_Master_Init(const unsigned long c){ SSP1CON1 = 0x28; //SSP1 Module as Master SSP1CON2 = 0x00; SSP1CON3 = 0x00; SSP1ADD = (_XTAL_FREQ/(4*c))-1; //Setting Clock Speed SSP1STAT = 0x80; } void I2C_Master_Wait(){ while ((SSP1STAT & 0x04) || (SSP1CON2 & 0x1F)); //Transmit is in progress } void I2C_Master_Start(){ I2C_Master_Wait(); SSP1CON2bits.SEN = 1; //Initiate start condition } void I2C_Master_RepeatedStart(){ I2C_Master_Wait(); SSP1CON2bits.RSEN = 1; //Initiate repeated start condition } void I2C_Master_Stop(){ I2C_Master_Wait(); SSP1CON2bits.PEN = 1; //Initiate stop condition } void I2C_Master_Write(unsigned d){ I2C_Master_Wait(); SSP1BUF = d; //Write data to SSP1BUF } unsigned short I2C_Master_Read(unsigned short a){ unsigned short temp; I2C_Master_Wait(); SSP1CON2bits.RCEN = 1; I2C_Master_Wait(); temp = SSP1BUF; //Read data from SSP1BUF I2C_Master_Wait(); SSP1CON2bits.ACKDT = a; //Acknowledge bit 1:NACK 0:ACK SSP1CON2bits.ACKEN = 1; //Acknowledge sequence return temp; } void SendI2C(char adrs, char data){ I2C_Master_Start(); I2C_Master_Write(adrs<<1); //SlaveAdress + Write I2C_Master_Write(data); //mainly RegisterAdress I2C_Master_Stop(); } void CmdI2C(char adrs, char reg, char data){ I2C_Master_Start(); I2C_Master_Write(adrs<<1); //SlaveAdress + Write I2C_Master_Write(reg); //RegisterAdress I2C_Master_Write(data); I2C_Master_Stop(); } void GetDataI2C(char adrs, char* buf, char cnt){ I2C_Master_Start(); I2C_Master_Write((adrs<<1)+1); //SlaveAdress + Read for(char i=0; i<cnt-1; i++) buf[i] = I2C_Master_Read(0); //ACK buf[cnt-1] = I2C_Master_Read(1); //NACK I2C_Master_Stop(); } |
想定としては、書き込み時には CmdI2C() を、読み取り時には SendI2C() と GetDataI2C() を使う感じです。
読み取りは連続したレジスタのデータを複数読み取ることが多いので、GetDataI2C() に関してはデフォルトで複数バイトを取得するようにしています。
BME280について
BME280 は温度・湿度・気圧を高精度で測定できるセンサーですが、しっかり活用しようとすると(PICのメモリ容量的に)かなり大変なので、今回は温度だけ簡単に測定したいと思います。
このセンサーのレジスタはこのようになっています。
温度の測定結果は 0xFA, 0xFB, 0xFC に格納されていることがわかります。正直 20bit もの精度は不要だし処理も面倒なので、0xFA, 0xFB の上位 16bit だけを読み取りたいと思います。
また、このデータは温度そのものではなく、ADCの測定結果となっているので、℃に変換するには計算が必要となります。
この計算もすごく複雑なのですが、今回は簡単な近似式を計算したのでこちらを使います。
data は先程の上位 16bit のデータです。小数を扱うのは面倒なので小数第一位までの温度を10倍した整数で計算します。
動作確認
それでは実際に先程のライブラリを使ってセンサーの値を読み取り、温度を LCD に表示させてみたいと思います。
以下プログラム、回路図、動作写真となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#pragma config FOSC = ECH // Oscillator Selection (ECH, External Clock, High Power Mode (4-32 MHz): device clock supplied to CLKIN pin) #pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled) #pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input) #pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled) #pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled) #pragma config BOREN = OFF // Brown-out Reset Enable (Brown-out Reset disabled) #pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin) #pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled) #pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled) #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off) #pragma config PLLEN = ON // PLL Enable (4x PLL enabled) #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset) #pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.) #pragma config LVP = ON // Low-Voltage Programming Enable (Low-voltage programming enabled) #include <xc.h> #include <stdio.h> #include "i2c.h" #include "lcd_i2c.h" #define _XTAL_FREQ 4000000 void putch(unsigned char c){ lcd_DATA(c); } void Init(){ OSCCONbits.IRCF = 13; //4MHz TRISA = 0x00; TRISB = 0b00010110; ANSELB = 0x00; ANSELA = 0x00; } int main(void){ Init(); I2C_Master_Init(100000); lcd_Init(); char buf[2]; unsigned int data; int T; //Initialize BME280 CmdI2C(0x76, 0xE0, 0xB6); // reset CmdI2C(0x76, 0xF2, 0x01); // Ctrl_hum_reg CmdI2C(0x76, 0xF4, 0x27); // Ctrl_meas_reg CmdI2C(0x76, 0xF5, 0xA0); // config/reg lcd_SetCursor(0,0); printf("BME 280"); while(1){ SendI2C(0x76, 0xFA); // Send start Reg address GetDataI2C(0x76, buf, 2); // Get from 0xFA to 0xFB data = (buf[0]<<8) | buf[1]; T = ((long)data*101/2000 - 1418); lcd_SetCursor(0,1); printf("%d.%d degC", T/10, T%10); __delay_ms(500); } } |
I2Cライブラリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#include<xc.h> #define _XTAL_FREQ 4000000 void I2C_Master_Init(const unsigned long c){ SSP1CON1 = 0x28; //SSP1 Module as Master SSP1CON2 = 0x00; SSP1CON3 = 0x00; SSP1ADD = (_XTAL_FREQ/(4*c))-1; //Setting Clock Speed SSP1STAT = 0x80; } void I2C_Master_Wait(){ while ((SSP1STAT & 0x04) || (SSP1CON2 & 0x1F)); //Transmit is in progress } void I2C_Master_Start(){ I2C_Master_Wait(); SSP1CON2bits.SEN = 1; //Initiate start condition } void I2C_Master_RepeatedStart(){ I2C_Master_Wait(); SSP1CON2bits.RSEN = 1; //Initiate repeated start condition } void I2C_Master_Stop(){ I2C_Master_Wait(); SSP1CON2bits.PEN = 1; //Initiate stop condition } void I2C_Master_Write(unsigned d){ I2C_Master_Wait(); SSP1BUF = d; //Write data to SSP1BUF } unsigned short I2C_Master_Read(unsigned short a){ unsigned short temp; I2C_Master_Wait(); SSP1CON2bits.RCEN = 1; I2C_Master_Wait(); temp = SSP1BUF; //Read data from SSP1BUF I2C_Master_Wait(); SSP1CON2bits.ACKDT = a; //Acknowledge bit 1:NACK 0:ACK SSP1CON2bits.ACKEN = 1; //Acknowledge sequence return temp; } void SendI2C(char adrs, char data){ I2C_Master_Start(); I2C_Master_Write(adrs<<1); //SlaveAdress + Write I2C_Master_Write(data); //mainly RegisterAdress I2C_Master_Stop(); } void CmdI2C(char adrs, char reg, char data){ I2C_Master_Start(); I2C_Master_Write(adrs<<1); //SlaveAdress + Write I2C_Master_Write(reg); //RegisterAdress I2C_Master_Write(data); I2C_Master_Stop(); } void GetDataI2C(char adrs, char* buf, char cnt){ I2C_Master_Start(); I2C_Master_Write((adrs<<1)+1); //SlaveAdress + Read for(char i=0; i<cnt-1; i++) buf[i] = I2C_Master_Read(0); //ACK buf[cnt-1] = I2C_Master_Read(1); //NACK I2C_Master_Stop(); } |
1 2 3 4 5 6 7 8 9 10 11 |
void I2C_Master_Init(const unsigned long c); void I2C_Master_Wait(); void I2C_Master_Start(); void I2C_Master_RepeatedStart(); void I2C_Master_Stop(); void I2C_Master_Write(unsigned d); unsigned short I2C_Master_Read(unsigned short a); void SendI2C(char adrs, char data); void CmdI2C(char adrs, char reg, char data); void GetDataI2C(char adrs, char* buf, char cnt); |
LCDライブラリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include "i2c.h" #include <xc.h> #define _XTAL_FREQ 4000000 //#define CONTRAST 0x18 //for 3.3V #define CONTRAST 0x08 //for 5V const char lcd_DDRAM[4] = {0x00, 0x40, 0x14, 0x54}; void lcd_INST(char command){ I2C_Master_Start(); I2C_Master_Write(0x7C); //SlaveAdress + Write I2C_Master_Write(0x00); //Instruction I2C_Master_Write(command); //data byte I2C_Master_Stop(); __delay_us(30); } void lcd_DATA(char data){ I2C_Master_Start(); I2C_Master_Write(0x7C); //SlaveAdress + Write I2C_Master_Write(0x40); //Data I2C_Master_Write(data); //data byte I2C_Master_Stop(); __delay_us(30); } void lcd_Init(){ __delay_ms(40); lcd_INST(0x38); //Function Set lcd_INST(0x39); //Function Set lcd_INST(0x14); //Internal OSC Frequency lcd_INST(0x70 + (CONTRAST & 0x0f)); //Contrast Set lcd_INST(0x54 + ((CONTRAST & 0xf0)>>4)); //Power/ICON/Contrast control lcd_INST(0x6c); //Follower control __delay_ms(200); lcd_INST(0x38); //Function Set lcd_INST(0x0c); //Display ON/OFF control lcd_INST(0x01); //Clear Display __delay_ms(2); } void lcd_SetCursor(char x,char y){ char d = lcd_DDRAM[y] + x; lcd_INST( 0x80+d); } |
1 2 3 4 |
void lcd_INST(char command); void lcd_DATA(char data); void lcd_Init(); void lcd_SetCursor(char x,char y); |
無事に温度を測定することができました。センサーに触ると温度が上昇するのも確認できます。
今回の記事は以上となります。ここまで見ていただきありがとうございました。
このセンサーに限らず、いろんなセンサーを使ってみてはいかがでしょうか。
thank you. Achievements. Respects.
Uncool!
PICでは、どの回路でも
MCLR(RA5)はPullUpしないと安定稼働は望めません。
MCLR Float状態ではいつResetが掛かってもおかしくありません。