第50回 プログラム完成

今回は実践編のプログラムを完成させます。湿度と気圧データはBME280から取得、温度データはADT7410から取得します。

目次

温度センサADT7410の動作設定

温度センサの初期化方法とI2Cの通信手順がわかりましたので、ADT7410の動作設定関数を作成しておきます。

ADT7410を16ビット測定、連続測定モードに設定するには、動作設定アドレス0x03に0x80を書き込めばOKです。I2C通信でスタートコンディション生成後、0x90(=スレーブアドレス0x48 + 書き込み指定0)、0x03、0x80の3バイトをマスターからスレーブに送信すれば、動作設定できます。マスターとスレーブのやり取りを図式化すると以下のようになります。

Pic practice 50 adt7410 configuration protocol

プログラムは、以下のようにスレーブアドレスや動作設定アドレス、動作設定値を#defineしておくことにします。

// ADT7410温度センサ
#define ADT7410_I2C_WRITE_ADDRESS 0x90  // ADT7410のI2Cアドレス(Write)
#define ADT7410_I2C_READ_ADDRESS  0x91  // ADT7410のI2Cアドレス(Read)
#define ADT7410_CONFIG_ADDRESS    0x03  // コンフィグレーション設定アドレス
#define ADT7410_TEMP_ADDRESS      0x00  // 温度格納アドレス(上位8ビット)
#define ADT7410_CONFIG_VALUE      0x80  // 設定値(16-bit分解能指定、他はデフォルト)

あとは今までの作成したI2C通信関数を呼べばOKです。設定値はあとで変更できるように引数にしてみました。

// ADT7410動作設定
void adt7410Config(uint8_t config_value) {
    
    i2cProtocolStart();                              // スタートコンディション生成
    i2cProtocolSendData(ADT7410_I2C_WRITE_ADDRESS);  // スレーブアドレス送信
    i2cProtocolSendData(ADT7410_CONFIG_ADDRESS);     // 動作設定アドレス送信
    i2cProtocolSendData(config_value);               // 動作設定値送信
    i2cProtocolStop();                               // ストップコンディション生成
    
    return;
}

温度センサADT7410の温度データ取得

温度データの取得は以下のようなI2C通信で取得できます。

Pic practice 50 adt7410 measurement protocol

あとはこの通りに関数を呼べばOKです。測定した温度データは関数の返り値にしています。小数点を含みますのでfloat型です。

// ADT7410温度取得
float adt7410GetTemperature(void) {
    
    uint8_t temp_high, temp_low;
    int16_t temp_value;
    
    i2cProtocolStart();                              // スタートコンディション生成
    i2cProtocolSendData(ADT7410_I2C_WRITE_ADDRESS);  // スレーブアドレス送信(書き込み指定)
    i2cProtocolSendData(ADT7410_TEMP_ADDRESS);       // 温度データ読み取りのアドレス送信
    i2cProtocolRepeatStart();                        // リピートスタートコンディション生成
    i2cProtocolSendData(ADT7410_I2C_READ_ADDRESS);   // スレーブアドレス送信(読み取り指定)
    temp_high = i2cProtocolReceiveData();            // 1バイトデータ受信
    i2cProtocolSendAck();                            // ACK送信
    temp_low  = i2cProtocolReceiveData();            // 1バイトデータ受信
    i2cProtocolSendNack();                           // NACK送信
    i2cProtocolStop();                               // ストップコンディション生成
    
    // 温度計算
    if( temp_high & 0x80 ) {
        temp_value = ( (temp_high & 0x7f) << 8 ) + temp_low - 32768;
    } else {
        temp_value = ( (temp_high & 0x7f) << 8 ) + temp_low;
    }

    return (float)temp_value / 128.0;
}

プログラム仕上げ

これで一通り必要な関数は揃いましたので、プログラム全体をまとめます。新しいことは特にありませんので詳細説明は省略しますが、不明点ありましたらコメント欄やお問い合わせフォームからご質問いただければと思います。

なお、LEDの接続が赤青逆になっているかもしれませんので、#defineで定義してあります。青が雨の予報用、赤が晴れの予報用にしてありますが、違う色のLEDを使用されてい場合は#defineで定義を変更していただければと思います。

/*
 * BME280温湿度大気圧センサ
 * ATD7410温度センサモジュール
 * AQM1602液晶ディスプレイモジュール
 * 制御プログラム
 * 
 * https://tool-lab.com/
 *
 * 更新
 *   2018. 2.12: 新規作成
 *   2018. 4. 1: 動作周波数を4MHz、I2Cクロック周波数を100kHzに変更
 *   2018. 7.30: 温湿度・気圧センサデータ表示
 *   2018. 8.12: ADT7410温度データ表示対応、プログラム完成
 * 
 */

//
// PIC16F18857コンィグレーション設定
//
// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits (Oscillator not enabled)
#pragma config RSTOSC = HFINT1  // Power-up default value for COSC bits (HFINTOSC (1MHz))
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; i/o or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable bit (FSCM timer enabled)
//
// CONFIG2
#pragma config MCLRE = OFF      // Master Clear Enable bit (IO)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out reset enable bits (Brown-out Reset Enabled, SBOREN bit is ignored)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices)
#pragma config ZCD = OFF        // Zero-cross detect disable (Zero-cross detect circuit is disabled at POR.)
#pragma config PPS1WAY = ON     // Peripheral Pin Select one-way control (The PPSLOCK bit can be cleared and set only once in software)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a reset)
//
// CONFIG3
#pragma config WDTCPS = WDTCPS_31// WDT Period Select bits (Divider ratio 1:65536; software control of WDTPS)
#pragma config WDTE = OFF       // WDT operating mode (WDT Disabled, SWDTEN is ignored)
#pragma config WDTCWS = WDTCWS_7// WDT Window Select bits (window always open (100%); software control; keyed access not required)
#pragma config WDTCCS = SC      // WDT input clock selector (Software Control)
//
// CONFIG4
#pragma config WRT = OFF        // UserNVM self-write protection bits (Write protection off)
#pragma config SCANE = available// Scanner Enable bit (Scanner module is available for use)
#pragma config LVP = ON         // Low Voltage Programming Enable bit (Low Voltage programming enabled. MCLR/Vpp pin function is MCLR.)
//
// CONFIG5
#pragma config CP = OFF         // UserNVM Program memory code protection bit (Program Memory code protection disabled)
#pragma config CPD = OFF        // DataNVM code protection bit (Data EEPROM code protection disabled)

//
// ヘッダファイル
//   int_t型を使用するため、stdint.hをインクルード
//   printfを使用するため、stdio.hをインクルード
//
#include <xc.h>
#include <stdint.h>
#include <stdio.h>


//
// 定数
//

// I2C Ack/Nack定義
#define I2C_ACK  0x00
#define I2C_NACK 0xff

// LCDモジュール
#define LCD_I2C_ADDRESS 0x7c  // LCDモジュールのI2Cアドレス

// ADT7410温度センサ
#define ADT7410_I2C_WRITE_ADDRESS 0x90  // ADT7410のI2Cアドレス(Write)
#define ADT7410_I2C_READ_ADDRESS  0x91  // ADT7410のI2Cアドレス(Read)
#define ADT7410_CONFIG_ADDRESS    0x03  // コンフィグレーション設定アドレス
#define ADT7410_TEMP_ADDRESS      0x00  // 温度格納アドレス(上位8ビット)
#define ADT7410_CONFIG_VALUE      0x80  // 設定値(16-bit分解能指定、他はデフォルト)

// SPIピン設定
#define SPI_SCK    LATCbits.LATC7
#define SPI_MISO   PORTCbits.RC6
#define SPI_MOSI   LATCbits.LATC5
#define SPI_CSB    LATCbits.LATC4

// LEDピン設定
#define LED_BLUE   LATBbits.LATB0  // 雨の予報用
#define LED_RED    LATBbits.LATB1  // 晴の予報用



//
// 関数プロトタイプ宣言
//

// LCDモジュール表示制御関数
void lcdInitialize(void);               // LCD初期化
void lcdClearDisplay(void);             // ディスプレイ全消去
void lcdSendCommandData(uint8_t);       // コマンド送信
void lcdSendCharacterData(uint8_t);     // 1文字表示
void lcdSendString(char *);             // 文字列表示
void lcdLocateCursor(uint8_t,uint8_t);  // カーソル位置指定

// LCDモジュールI2Cプロトコル関数
void lcdI2CProtocol(uint8_t, uint8_t, uint8_t);

// ADT7410温度センサ制御関数
void  adt7410Config(uint8_t);       // 動作設定
float adt7410GetTemperature(void);  // 温度取得

// I2Cプロトコル各信号の生成関数
void    i2cProtocolStart(void);        // スタートコンディション生成
void    i2cProtocolRepeatStart(void);  // リピートスタートコンディション生成
void    i2cProtocolStop(void);         // ストップコンディション生成
void    i2cProtocolSendData(uint8_t);  // 1バイトデータ送信
uint8_t i2cProtocolReceiveData(void);  // バイトデータ受信
uint8_t i2cProtocolCheckAck(void);     // ACK/NACK信号チェック
void    i2cProtocolSendAck(void);      // ACK送信
void    i2cProtocolSendNack(void);     // NACK送信

//   BME280センサー制御
void     bme280Initialization(void);            // BME280の初期化
void     bme280ForcedMeasurement(void);         // 温湿度・気圧データの測定指示
void     bme280ReadTrimmingParameters(void);    // 補正データの読み取り
void     bme280ReadMeasuredRawData(void);       // 補正前の温湿度・気圧データ読み取り
int32_t  bme280CompensateTemperature(void);     // 温度データ補正
uint32_t bme280CompensatePressure(void);        // 気圧データ補正
uint32_t bme280CompensateHumidity(void);        // 湿度データ補正

// SPI通信制御関数
uint16_t spiRead2BytesData(uint8_t address);
uint8_t  spiRead1ByteData(uint8_t address);
void     spiWrite1ByteData(uint8_t address, uint8_t data);
void     spiSend8bit(uint8_t data);
uint8_t  spiReceive8bit();



// クロック周波数
// __delay_ms()関数が時間基準に使用する
#define _XTAL_FREQ 4000000

// グローバル変数
//
// BME280温湿度・気圧読み取りデータ
uint32_t hum_raw, temp_raw, pres_raw;
int32_t  t_fine;

// BME280気温補正データ
uint16_t dig_T1;
int16_t  dig_T2;
int16_t  dig_T3;

// BME280湿度補正データ
uint8_t  dig_H1;
int16_t  dig_H2;
uint8_t  dig_H3;
int16_t  dig_H4;
int16_t  dig_H5;
int8_t   dig_H6;

// BME280気圧補正データ
uint16_t dig_P1;
int16_t  dig_P2;
int16_t  dig_P3;
int16_t  dig_P4;
int16_t  dig_P5;
int16_t  dig_P6;
int16_t  dig_P7;
int16_t  dig_P8;
int16_t  dig_P9;

//
// main関数
//
void main(void) {

    // 動作周波数設定
    OSCCON1bits.NDIV = 0b0000;  // 分周1:1
    OSCFRQbits.HFFRQ = 0b010;   // 4MHz
    
    // ピン属性設定
    ANSELA = 0b00000000;
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00000000;
    TRISB  = 0b00000000;
    TRISC  = 0b01001100;
    
    // SPI信号線初期設定
    SPI_SCK  = 0;  // クロックを0
    SPI_MOSI = 0;  // マスタ→スレーブを0
    SPI_CSB  = 1;  // チップセレクトを1(=無効)

    //
    // I2C通信ピンのPPS設定
    //
    // 設定ロック解除
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0x00;

    // SCL, SDAピンの割り当て
    SSP1DATPPS = 0x12; // RC2をMSSP1:SDA1に設定
    SSP1CLKPPS = 0x13; // RC3をMSSP1:SCL1に設定
    RC3PPS = 0x14;     // RC3をMSSP1:SCL1に設定
    RC2PPS = 0x15;     // RC2をMSSP1:SDA1に設定

    // 設定ロック
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0x01;
    
    
    //
    // I2C通信設定
    //
    // SMP Standard Speed; CKE disabled; 
    SSP1STAT = 0x80;
    // SSPEN enabled; CKP Idle:Low, Active:High; SSPM FOSC/4_SSPxADD_I2C; 
    SSP1CON1 = 0x28;
    // SBCDE disabled; BOEN disabled; SCIE disabled; PCIE disabled; DHEN disabled; SDAHT 300ns; AHEN disabled; 
    SSP1CON3 = 0x00;
    // Baud Rate Generator = 100kHz 
    SSP1ADD = 0x09;
    
    // LCDモジュール電源安定化時間待ち
    __delay_ms(100);
    
    // LCD初期化
    lcdInitialize();
    
    // LCD表示クリア
    lcdClearDisplay();
    
    // ADT7410温度センサ初期化
    adt7410Config(ADT7410_CONFIG_VALUE);

    // BME280初期化
    //   コントロールレジスタ、補正係数取得
    bme280Initialization();

    // 実際の温湿度・気圧
    float actual_temp;
    float actual_hum;
    float actual_press;

    // BME280補正データ
    int32_t  compensated_temp;
    uint32_t compensated_hum;
    uint32_t compensated_press;
    
    // 測定・データ表示
    while(1){

        // 温度センサのデータを取得して液晶ディスプレイに表示
        lcdLocateCursor(0, 0);
        printf("%5.2f", adt7410GetTemperature());
        lcdSendCharacterData(0xdf);
        lcdSendCharacterData('C');

        // SPI通信で温湿度センサの値を取得
        bme280ForcedMeasurement();
        bme280ReadMeasuredRawData();

        // 取得したデータをキャリブレーション
        compensated_temp  = bme280CompensateTemperature();
        compensated_hum   = bme280CompensateHumidity();
        compensated_press = bme280CompensatePressure();

        // 実際の温湿度に変換
        actual_temp  = (float)compensated_temp  / 100.0;
        actual_hum   = (float)compensated_hum   / 1024.0;
        actual_press = (float)compensated_press / 100.0;

        printf("  %3.0f%%", actual_hum);
        lcdLocateCursor(0, 1);
        printf("%7.2fhPa", actual_press);
        
        // とりあえずLED点灯
        LED_BLUE = 1;
        LED_RED  = 1;
        __delay_ms(1000);
        LED_BLUE = 0;
        LED_RED  = 0;
        __delay_ms(1000);
    }

}


//
// ADT7410温度センサ関数
//

// ADT7410動作設定
void adt7410Config(uint8_t config_value) {
    
    i2cProtocolStart();                              // スタートコンディション生成
    i2cProtocolSendData(ADT7410_I2C_WRITE_ADDRESS);  // スレーブアドレス送信
    i2cProtocolSendData(ADT7410_CONFIG_ADDRESS);     // 動作設定アドレス送信
    i2cProtocolSendData(config_value);               // 動作設定値送信
    i2cProtocolStop();                               // ストップコンディション生成
    
    return;
}

// ADT7410温度取得
float adt7410GetTemperature(void) {
    
    uint8_t temp_high, temp_low;
    int16_t temp_value;
    
    i2cProtocolStart();                              // スタートコンディション生成
    i2cProtocolSendData(ADT7410_I2C_WRITE_ADDRESS);  // スレーブアドレス送信(書き込み指定)
    i2cProtocolSendData(ADT7410_TEMP_ADDRESS);       // 温度データ読み取りのアドレス送信
    i2cProtocolRepeatStart();                        // リピートスタートコンディション生成
    i2cProtocolSendData(ADT7410_I2C_READ_ADDRESS);   // スレーブアドレス送信(読み取り指定)
    temp_high = i2cProtocolReceiveData();            // 1バイトデータ受信
    i2cProtocolSendAck();                            // ACK送信
    temp_low  = i2cProtocolReceiveData();            // 1バイトデータ受信
    i2cProtocolSendNack();                           // NACK送信
    i2cProtocolStop();                               // ストップコンディション生成
    
    // 温度計算
    if( temp_high & 0x80 ) {
        temp_value = ( (temp_high & 0x7f) << 8 ) + temp_low - 32768;
    } else {
        temp_value = ( (temp_high & 0x7f) << 8 ) + temp_low;
    }

    return (float)temp_value / 128.0;
}



//
// LCD制御関数
//

//
// printfがコールする1文字出力関数の定義
//
void putch(char character) {

    lcdSendCharacterData(character);
    
    return;
}

//
// LCDモジュールに制御コードまたはデータを送信
//
void lcdI2CProtocol(uint8_t address, uint8_t control_code, uint8_t data) {
    
    i2cProtocolStart();                 // スタートコンディション
    i2cProtocolSendData(address);       // アドレス送信
    i2cProtocolSendData(control_code);  // 制御コード送信 (動作設定=0x00/文字表示=0x40)
    i2cProtocolSendData(data);          // データ送信
    i2cProtocolStop();                  // ストップコンディション

    return;
}

//
// 表示文字データ送信
//   0x40の後にデータを送信
void lcdSendCharacterData(uint8_t data){

    // 表示文字のデータを送信する場合の制御コードは0x40
    lcdI2CProtocol(LCD_I2C_ADDRESS, 0x40, data);
    
    // ウエイト
    //   文字表示の場合はウエイトは必要なくても動作しているが
    //   表示されない場合は1ms程度のウエイトを入れる
    // __delay_ms(1);

    return;
}

//
// コマンド送信
//   0x00の後にコマンドを送信
//
void lcdSendCommandData(uint8_t command){

    // コマンドを送信する場合の制御コードは0x00
    lcdI2CProtocol(LCD_I2C_ADDRESS, 0x00, command);

    // ウエイト
    //   データシートではウエイト時間は26.3us以上になっているが、
    //   それより長くしないと初期化できないケースがあるため1msのウエイトを入れる
    __delay_ms(1);
    
    return;
}

//
// ディスプレイ消去
//
void lcdClearDisplay(void){
    
    lcdSendCommandData(0x01);
    
    return;
}

//
// カーソル位置移動
//    引数は水平方向右側プラスのX軸、垂直方向下側プラスのY軸で、それぞれ0から開始
//    左上の座標が(x=0, y=0)
//
void lcdLocateCursor(uint8_t position_x, uint8_t position_y){
    
    // 文字表示位置指定コマンド送信
    lcdSendCommandData( 0x80 + 0x40 * position_y + position_x );
    
    return;
}

//
// 文字列を送信
//
void lcdSendString(char *str){
    
    // strの文字列を*strが0になるまでLCDモジュールに送信
    while(*str) {
        lcdSendCharacterData(*str);
        str++;
    }
    
    return;
}


//
// LCDモジュール初期化
//
void lcdInitialize(void){

    // 初期化コマンド送信
    lcdSendCommandData(0x38); // 2行モードに設定
    lcdSendCommandData(0x39); // 拡張コマンド選択
    lcdSendCommandData(0x14); // 内部クロック周波数設定
    lcdSendCommandData(0x70); // コントラスト設定(C3:C0 = 0b0000に設定)
    lcdSendCommandData(0x56); // 電源電圧が3.3VなのでBooster=ON、コントラスト設定(C5:C4 = 0b10に設定)
    lcdSendCommandData(0x6c); // オペアンプのゲイン設定
    
    // モジュール内電源安定化のための時間待ち
    __delay_ms(200);
    
    // 初期化コマンド続き
    lcdSendCommandData(0x38); // 通常コマンド選択
    lcdSendCommandData(0x01); // ディスプレイ表示内容クリア
    lcdSendCommandData(0x0c); // ディスプレイ表示
    
    return;
}


//
// I2Cプロトコル制御関数
//

// スタートコンディション生成
void i2cProtocolStart() {
    
    // SSP1CON2レジスタのSENビットを1に設定すると
    // スタートコンディションが生成される
    // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
    SSP1IF = 0;
	SSP1CON2bits.SEN = 1;
	while (SSP1IF == 0) {}
    SSP1IF = 0;
    
	return;
}

// リピートスタートコンディション生成
void i2cProtocolRepeatStart() {
    
	SSP1IF = 0;
	SSP1CON2bits.RSEN = 1;
	while (SSP1IF == 0) {}
    SSP1IF = 0;

	return;
}

// ストップコンディション生成
void i2cProtocolStop() {

    // SSP1CON2レジスタのPENビットを1に設定すると
    // ストップコンディションが生成される
    // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
	SSP1IF = 0;
	SSP1CON2bits.PEN = 1;
	while (SSP1IF == 0) {}
	SSP1IF = 0;

	return;
}

// 1バイトデータ送信
void i2cProtocolSendData(uint8_t data) {

    // SSP1BUFに送信したいデータをセットすると、そのデータが送信される
    // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
	SSP1IF = 0;
	SSP1BUF = data;
	while (SSP1IF == 0) {}
    SSP1IF = 0;
    
	return;
}

// 1バイトデータ受信
uint8_t i2cProtocolReceiveData() {
    
	SSP1IF = 0;
	SSP1CON2bits.RCEN = 1;
	while (SSP1IF == 0) {}
    SSP1IF = 0;

	return SSP1BUF;
}

// Ack/Nackチェック
uint8_t i2cProtocolCheckAck() {
    
	uint8_t ackStatus;

	if (SSP1CON2bits.ACKSTAT) {
		ackStatus = I2C_NACK;
	} else {
		ackStatus = I2C_ACK;
	}

	return ackStatus;
}

// Ack送信
void i2cProtocolSendAck() {
    
    // ACKDTにACKをセット(負論理なので0を設定)
	SSP1CON2bits.ACKDT = 0;

    // NACK信号生成
	SSP1CON2bits.ACKEN = 1;
	while (SSP1CON2bits.ACKEN) {}

	return;
}

// Nack送信
void i2cProtocolSendNack() {
    
    // ACKDTにNACKをセット(負論理なので1を設定)
	SSP1CON2bits.ACKDT = 1;

    // NACK信号生成
	SSP1CON2bits.ACKEN = 1;
	while (SSP1CON2bits.ACKEN) {}

	return;
}



//
// BME280初期化関数
//
void bme280Initialization(void) {

    // 動作パラメータ設定
    uint8_t t_sb     = 0;  // スタンドバイ時間は使用しない
    uint8_t filter   = 0;  // フィルタOFF
    uint8_t spi3w_en = 0;  // SPIは4線式(=0)
    uint8_t osrs_t   = 1;  // 温度オーバーサンプリング x1
    uint8_t osrs_p   = 1;  // 大気圧オーバーサンプリング x1
    uint8_t osrs_h   = 1;  // 湿度オーバーサンプリング x1
    uint8_t mode     = 0;  // スリープモード

    // 設定値をフォーマットに合わせる
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    // BME280動作パラメータ書き込み
    spiWrite1ByteData(0xF2, ctrl_hum_reg);
    spiWrite1ByteData(0xF4, ctrl_meas_reg);
    spiWrite1ByteData(0xF5, config_reg);
    
    // センサ処理待ち
    __delay_ms(1000);

    // 補正値読み込み
    //   センサごとに固定ちのため、初期化時のみ読み込む
    bme280ReadTrimmingParameters();

}


//
// 温湿度・気圧データ測定指示
//
void bme280ForcedMeasurement(void) {

    // 動作パラメータ設定
    uint8_t t_sb     = 0;  // スタンドバイ時間は使用しない
    uint8_t filter   = 0;  // フィルタOFF
    uint8_t spi3w_en = 0;  // SPIは4線式
    uint8_t osrs_t   = 1;  // 温度オーバーサンプリング x1
    uint8_t osrs_p   = 1;  // 大気圧オーバーサンプリング x1
    uint8_t osrs_h   = 1;  // 湿度オーバーサンプリング x1
    uint8_t mode     = 1;  // 測定指示(1回測定したらスリープモードに移行)

    // 設定値をフォーマットに合わせる
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    // BME280動作パラメータ書き込み
    spiWrite1ByteData(0xF2, ctrl_hum_reg);
    spiWrite1ByteData(0xF4, ctrl_meas_reg);
    spiWrite1ByteData(0xF5, config_reg);
    
    // 測定待ち
    //   statusレジスタを監視して測定完了を判断すべきだが
    //   while文での動作ができなかったので時間待ちで代用
    __delay_ms(10);
    
    // この段階でBME280の温湿度大気圧レジスタに測定値が格納されている
    // このあと自動的にスリープモードに入る

}


//
// 補正データ読み込み
//
void bme280ReadTrimmingParameters(void) {

    // 気温データ用補正データ
    dig_T1 = spiRead2BytesData(0x88);
    dig_T2 = (int16_t)spiRead2BytesData(0x8A);
    dig_T3 = (int16_t)spiRead2BytesData(0x8C);

    // 気圧データ用補正データ用補正データ
    dig_P1 = spiRead2BytesData(0x8E);
    dig_P2 = (int16_t)spiRead2BytesData(0x90);
    dig_P3 = (int16_t)spiRead2BytesData(0x92);
    dig_P4 = (int16_t)spiRead2BytesData(0x94);
    dig_P5 = (int16_t)spiRead2BytesData(0x96);
    dig_P6 = (int16_t)spiRead2BytesData(0x98);
    dig_P7 = (int16_t)spiRead2BytesData(0x9A);
    dig_P8 = (int16_t)spiRead2BytesData(0x9C);
    dig_P9 = (int16_t)spiRead2BytesData(0x9E);

    // 湿度データ用補正データ用補正データ
    dig_H1 = spiRead1ByteData(0xA1);
    dig_H2 = (int16_t)spiRead2BytesData(0xE1);
    dig_H3 = spiRead1ByteData(0xE3);
    dig_H4 = (int16_t)((spiRead1ByteData(0xE4) << 4) | (spiRead1ByteData(0xE5) & 0x0F));
    dig_H5 = (int16_t)((spiRead1ByteData(0xE6) << 4) | (spiRead1ByteData(0xE5) >> 4));
    dig_H6 = (int8_t)spiRead1ByteData(0xE7);

}


// 
// 補正前の生の温湿度・気圧データ読み込み
// 
void bme280ReadMeasuredRawData() {

    // データ読み取り用配列
    uint32_t data[8];

    // スレーブセレクトアクティブ
    SPI_CSB = 0;

    // 読み込み開始アドレス指定
    spiSend8bit(0xF7 | 0b10000000);

    // 8バイト分のデータ読み込み
    for(int8_t i=0; i<8; i++){
      data[i] = spiReceive8bit();
    }

    // スレーブセレクトインアクティブ
    SPI_CSB = 0;

    // 読み込みしたデータから気温、湿度、気圧データを生成
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
    hum_raw  = (data[6] <<  8) | data[7];

}

//
// 気温データ補正
//   何をしているのかよくわからない
// 
int32_t bme280CompensateTemperature() {
  
    int32_t var1, var2, T;

    var1 = ((((temp_raw >> 3) - ((int32_t)dig_T1<<1))) * ((int32_t)dig_T2)) >> 11;
    var2 = (((((temp_raw >> 4) - ((int32_t)dig_T1)) * ((temp_raw>>4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14;
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;

    return T; 

}


//
// 気圧データ補正
//   何をしているのかよくわからない
// 
uint32_t bme280CompensatePressure() {

    int32_t var1, var2;
    uint32_t P;

    var1 = (((int32_t)t_fine)>>1) - (int32_t)64000;
    var2 = (((var1>>2) * (var1>>2)) >> 11) * ((int32_t)dig_P6);
    var2 = var2 + ((var1*((int32_t)dig_P5))<<1);
    var2 = (var2>>2)+(((int32_t)dig_P4)<<16);
    var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((int32_t)dig_P2) * var1)>>1))>>18;
    var1 = ((((32768+var1))*((int32_t)dig_P1))>>15);
    if (var1 == 0)
      return 0;

    P = (((uint32_t)(((int32_t)1048576)-pres_raw)-(var2>>12)))*3125;
    if(P<0x80000000)
      P = (P << 1) / ((uint32_t) var1);   
    else
      P = (P / (uint32_t)var1) * 2;

    var1 = (((int32_t)dig_P9) * ((int32_t)(((P>>3) * (P>>3))>>13)))>>12;
    var2 = (((int32_t)(P>>2)) * ((int32_t)dig_P8))>>13;
    P = (uint32_t)((int32_t)P + ((var1 + var2 + dig_P7) >> 4));

    return P;

}

//
// 湿度データ補正
//   何をしているのかよくわからない
// 
uint32_t bme280CompensateHumidity() {

    int32_t v_x1_u32r;

    v_x1_u32r = (t_fine - ((int32_t)76800));
    v_x1_u32r = (((((hum_raw << 14) -(((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * v_x1_u32r)) + 
             ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)dig_H6)) >> 10) * 
             (((v_x1_u32r * ((int32_t)dig_H3)) >> 11) + ((int32_t) 32768))) >> 10) + ((int32_t)2097152)) * 
             ((int32_t) dig_H2) + 8192) >> 14));
    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4));
    // v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
    if( v_x1_u32r < 0 ){
        v_x1_u32r = 0;
    }
    // v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
    if( v_x1_u32r > 419430400 ) {
        v_x1_u32r = 419430400;
    }

    return (uint32_t)(v_x1_u32r >> 12);

}


//
// SPIデータを指定アドレスから2バイト読み込み
// 
uint16_t spiRead2BytesData(uint8_t address) {

    uint8_t  data_low, data_high;  // 読み込んだ1バイトデータ格納用
    uint16_t data;  // 2バイトデータ用

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // 読み込みデータアドレス指定
    spiSend8bit(address);

    // 指定アドレスとその次のアドレスのデータを2バイト読み込み
    data_low  = spiReceive8bit();
    data_high = spiReceive8bit();

    //16ビットデータにする
    data = (data_high << 8) | data_low;

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;

    return data;

}


//
// SPIデータを指定アドレスから1バイト読み込み
// 
uint8_t spiRead1ByteData(uint8_t address) {

    // 受信データ格納変数
    uint8_t data;

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // SPI通信手順によりアドレスを送信
    spiSend8bit(address);

    // SPI通信手順によりデータを受信
    data = spiReceive8bit();

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;

    // 受信したーデータを返す
    return data;

}


//
// SPIデータを指定アドレスに1バイト書き込み
//
void spiWrite1ByteData(uint8_t address, uint8_t data) {

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // アドレス指定(書き込みは最上位ビット0)
    spiSend8bit(address & 0b01111111);

    // データ書き込み
    spiSend8bit(data);

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;
  
}

//
// SPIデータ8ビット書き込み
//   SCK/MOSI制御のための関数であるため
//   スレーブセレクト信号はこの関数の前後で制御すること
void spiSend8bit(uint8_t data) {

    // 8ビット分繰り返す
    for (int8_t i=7; i>=0; i--) {

        // (1)クロックを0にする
        SPI_SCK = 0;
        
        // (2)MOSIにデータをセットする
        if( data & (1<<i) ) {
            SPI_MOSI = 1;
        } else {
            SPI_MOSI = 0;
        }
      
        // (3)クロックを1にする
        SPI_SCK = 1;
    }

}

//
// SPIデータ8ビット読み込み
//   SCK/MOSI制御のための関数であるため
//   スレーブセレクト信号はこの関数の前後で制御すること
//
uint8_t spiReceive8bit() {

    // 受信データ格納変数
    uint8_t read_data = 0;

    // 8ビット分繰り返す
    for (int8_t i=7; i>=0; i--) {

        // 受信データ変数を1ビット左シフト
        read_data <<= 1;

        // (1)クロックを0にする
        SPI_SCK = 0;
        
        // (2)クロックを1にする
        SPI_SCK = 1;

        // (3)この時点でセンサからのデータを読めるので、MISOのピン状態を読む
        if(SPI_MISO){
            read_data |= 1;
        }
    }

    // 受信したデータを返す
    return read_data;

}

これで実践編のプログラムは完成ですが、今回製作した回路で天気予報のプログラムを作ってみます。

更新履歴

日付 内容
2018.8.15 新規投稿
2019.5.14 BME280の強制測定モードの際、測定が完了するまで待つ処理(10ms)を追加
通知の設定
通知タイミング
guest
14 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
通信初心者
通信初心者
1 月 前

管理人さん、こんにちは。
勉強させてもらっています。

第50回のプログラムですが、自分は、温度計だけの場合のプログラムに改造させていただきました。
理由は、センサーの値をLCDへ出力させる方法を分かり易くするために、単純化しました。

それで、結果ですが、最初、コンパイルが通りませんでした。
私と同じ状態を言っている人が第45回●●のプログラムに対して次の通り言っていました。
*******************
内容(抜粋)
「//」を付けると
コンパイルできるのですが、「//」を外すとコンパイルできません。
*******************

そこで、自分は、改造後の下のプログラム131行目にあるようにfをdに変更したら、ビルトしました。
無理やりかもしれませんが、実数を10進数にした状況かな。推測です。

最初、なぜコンパイルが通らなかったのか、ご教授願えませんでしょうか、どうぞ、宜しくご回答のほど、お願いします。

尚、エラーメッセージをプログラムの次に記載しました。御査収願います。

1 /*
2 * ATD7410温度センサモジュール
3 */
4
5 // CONFIG1
6 #pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
7 #pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled)
8 #pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled)
9 #pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
10 #pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled)
11 #pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled)
12 #pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled)
13 #pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
14 #pragma config IESO = OFF // Internal/External Switchover (Internal/External Switchover mode is disabled)
15 #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
16 // CONFIG2
17 #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
18 #pragma config PLLEN = OFF // PLL Enable (4x PLL disabled)
19 #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
20 #pragma config BORV = HI // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
21 #pragma config LVP = OFF // L
22
23 //
24 // ヘッダファイル
25 //  int_t型を使用するため、stdint.hをインクルード
26 //  printfを使用するため、stdio.hをインクルード
27 //
28 #include <xc.h>
29 #include <stdint.h>
30 #include <stdio.h>
31
32
33 //
34 // 定数
35 //
36
37 // I2C Ack/Nack定義
38 #define I2C_ACK 0x00
39 #define I2C_NACK 0xff
40
41 // LCDモジュール
42 #define LCD_I2C_ADDRESS 0x7c // LCDモジュールのI2Cアドレス
43 // ADT7410温度センサ
44 #define ADT7410_I2C_WRITE_ADDRESS 0x90 // ADT7410のI2Cアドレス(Write)
45 #define ADT7410_I2C_READ_ADDRESS 0x91 // ADT7410のI2Cアドレス(Read)
46 #define ADT7410_CONFIG_ADDRESS  0x03 // コンフィグレーション設定アドレス
47 #define ADT7410_TEMP_ADDRESS   0x00 // 温度格納アドレス(上位8ビット)
48 #define ADT7410_CONFIG_VALUE   0x80 // 設定値(16-bit分解能指定、他はデフォルト)
49
50
51 //
52 // 関数プロトタイプ宣言
53 //
54
55 // LCDモジュール表示制御関数
56 void lcdInitialize(void); // LCD初期化
57 void lcdClearDisplay(void); // ディスプレイ全消去
58 void lcdSendCommandData(uint8_t); // コマンド送信
59 void lcdSendCharacterData(uint8_t); // 1文字表示
60 void lcdSendString(char *); // 文字列表示
61 void lcdLocateCursor(uint8_t, uint8_t); // カーソル位置指定
62
63 // LCDモジュールI2Cプロトコル関数
64 void lcdI2CProtocol(uint8_t, uint8_t, uint8_t);
65 // ADT7410温度センサ制御関数
66 void adt7410Config(uint8_t); // 動作設定
67 float adt7410GetTemperature(void); // 温度取得
68
69
70 // I2Cプロトコル各信号の生成関数
71 void i2cProtocolStart(void); // スタートコンディション生成
72 void i2cProtocolRepeatStart(void); // リピートスタートコンディション生成
73 void i2cProtocolStop(void); // ストップコンディション生成
74 void i2cProtocolSendData(uint8_t); // 1バイトデータ送信
75 uint8_t i2cProtocolReceiveData(void); // バイトデータ受信
76 uint8_t i2cProtocolCheckAck(void); // ACK/NACK信号チェック
77 void i2cProtocolSendAck(void); // ACK送信
78 void i2cProtocolSendNack(void); // NACK送信
79
80 // クロック周波数
81 // __delay_ms()関数が時間基準に使用する
82 #define _XTAL_FREQ 8000000
83
84 //
85 // main関数
86 //
87
88 void main(void) {
89
90   // 動作周波数設定
91   OSCCON = 0b01110010; // 内部クロックは8MHzとする
92
93   // ピン属性設定
94   // 電圧レベルの初期設定
95   PORTA = 0x00;
96   PORTB = 0x00;
97   // 入出力設定
98   TRISA = 0b00100000;
99   TRISB = 0b01010011;
100   ANSELA = 0x00; // 全部デジタル
101   ANSELB = 0x00;
102
103   //
104   // I2C通信設定
105   //
106   // SMP Standard Speed; CKE disabled; 
107   SSP1STAT = 0x80;
108   // SSPEN enabled; CKP Idle:Low, Active:High; SSPM FOSC/4_SSPxADD_I2C; 
109   SSP1CON1 = 0x28;
110   // SBCDE disabled; BOEN disabled; SCIE disabled; PCIE disabled; DHEN disabled; SDAHT 300ns; AHEN disabled; 
111   SSP1CON3 = 0x00;
112   // Baud Rate Generator = 100kHz 
113   SSP1ADD = 0x09;
114
115   // LCDモジュール電源安定化時間待ち
116   __delay_ms(100);
117
118   // LCD初期化
119   lcdInitialize();
120
121   // LCD表示クリア
122   lcdClearDisplay();
123
124   // ADT7410温度センサ初期化
125   adt7410Config(ADT7410_CONFIG_VALUE);
126   // 測定・データ表示
127   while (1) {
128
129     // 温度センサのデータを取得して液晶ディスプレイに表示
130     lcdLocateCursor(0, 0);
131     printf(“%5.2d”, adt7410GetTemperature());
132     lcdSendCharacterData(0xdf);
133     lcdSendCharacterData(‘C’);
134
135     __delay_ms(1000);
136   }
137
138 }
139
140 //
141 // ADT7410温度センサ関数
142 //
143
144 // ADT7410動作設定
145
146 void adt7410Config(uint8_t config_value) {
147
148   i2cProtocolStart(); // スタートコンディション生成
149   i2cProtocolSendData(ADT7410_I2C_WRITE_ADDRESS); // スレーブアドレス送信
150   i2cProtocolSendData(ADT7410_CONFIG_ADDRESS); // 動作設定アドレス送信
151   i2cProtocolSendData(config_value); // 動作設定値送信
152   i2cProtocolStop(); // ストップコンディション生成
153
154   return;
155 }
156
157 // ADT7410温度取得
158
159 float adt7410GetTemperature(void) {
160
161   uint8_t temp_high, temp_low;
162   int16_t temp_value;
163
164   i2cProtocolStart(); // スタートコンディション生成
165   i2cProtocolSendData(ADT7410_I2C_WRITE_ADDRESS); // スレーブアドレス送信(書き込み指定)
166   i2cProtocolSendData(ADT7410_TEMP_ADDRESS); // 温度データ読み取りのアドレス送信
167   i2cProtocolRepeatStart(); // リピートスタートコンディション生成
168   i2cProtocolSendData(ADT7410_I2C_READ_ADDRESS); // スレーブアドレス送信(読み取り指定)
169   temp_high = i2cProtocolReceiveData(); // 1バイトデータ受信
170   i2cProtocolSendAck(); // ACK送信
171   temp_low = i2cProtocolReceiveData(); // 1バイトデータ受信
172   i2cProtocolSendNack(); // NACK送信
173   i2cProtocolStop(); // ストップコンディション生成
174
175   // 温度計算
176   if (temp_high & 0x80) {
177     temp_value = ((temp_high & 0x7f) << 8) + temp_low – 32768;
178   } else {
179     temp_value = ((temp_high & 0x7f) << 8) + temp_low;
180   }
181
182   return (float) temp_value / 128.0;
183 }
184
185
186
187 //
188 // LCD制御関数
189 //
190
191 //
192 // printfがコールする1文字出力関数の定義
193 //
194
195 void putch(uint8_t character) {
196
197   lcdSendCharacterData(character);
198
199   return;
200 }
201
202 //
203 // LCDモジュールに制御コードまたはデータを送信
204 //
205
206 void lcdI2CProtocol(uint8_t address, uint8_t control_code, uint8_t data) {
207
208   i2cProtocolStart(); // スタートコンディション
209   i2cProtocolSendData(address); // アドレス送信
210   i2cProtocolSendData(control_code); // 制御コード送信 (動作設定=0x00/文字表示=0x40)
211   i2cProtocolSendData(data); // データ送信
212   i2cProtocolStop(); // ストップコンディション
213
214   return;
215 }
216
217 //
218 // 表示文字データ送信
219 //  0x40の後にデータを送信
220
221 void lcdSendCharacterData(uint8_t data) {
222
223   // 表示文字のデータを送信する場合の制御コードは0x40
224   lcdI2CProtocol(LCD_I2C_ADDRESS, 0x40, data);
225
226   // ウエイト
227   //  文字表示の場合はウエイトは必要なくても動作しているが
228   //  表示されない場合は1ms程度のウエイトを入れる
229   // __delay_ms(1);
230
231   return;
232 }
233
234 //
235 // コマンド送信
236 //  0x00の後にコマンドを送信
237 //
238
239 void lcdSendCommandData(uint8_t command) {
240
241   // コマンドを送信する場合の制御コードは0x00
242   lcdI2CProtocol(LCD_I2C_ADDRESS, 0x00, command);
243
244   // ウエイト
245   //  データシートではウエイト時間は26.3us以上になっているが、
246   //  それより長くしないと初期化できないケースがあるため1msのウエイトを入れる
247   __delay_ms(1);
248
249   return;
250 }
251
252 //
253 // ディスプレイ消去
254 //
255
256 void lcdClearDisplay(void) {
257
258   lcdSendCommandData(0x01);
259
260   return;
261 }
262
263 //
264 // カーソル位置移動
265 //  引数は水平方向右側プラスのX軸、垂直方向下側プラスのY軸で、それぞれ0から開始
266 //  左上の座標が(x=0, y=0)
267 //
268
269 void lcdLocateCursor(uint8_t position_x, uint8_t position_y) {
270
271   // 文字表示位置指定コマンド送信
272   lcdSendCommandData(0x80 + 0x40 * position_y + position_x);
273
274   return;
275 }
276
277 //
278 // 文字列を送信
279 //
280
281 void lcdSendString(char *str) {
282
283   // strの文字列を*strが0になるまでLCDモジュールに送信
284   while (*str) {
285     lcdSendCharacterData(*str);
286     str++;
287   }
288
289   return;
290 }
291
292
293 //
294 // LCDモジュール初期化
295 //
296
297 void lcdInitialize(void) {
298
299   // 初期化コマンド送信
300   lcdSendCommandData(0x38); // 2行モードに設定
301   lcdSendCommandData(0x39); // 拡張コマンド選択
302   lcdSendCommandData(0x14); // 内部クロック周波数設定
303   lcdSendCommandData(0x70); // コントラスト設定(C3:C0 = 0b0000に設定)
304   lcdSendCommandData(0x56); // 電源電圧が3.3VなのでBooster=ON、コントラスト設定(C5:C4 = 0b10に設定)
305   lcdSendCommandData(0x6c); // オペアンプのゲイン設定
306
307   // モジュール内電源安定化のための時間待ち
308   __delay_ms(200);
309
310   // 初期化コマンド続き
311   lcdSendCommandData(0x38); // 通常コマンド選択
312   lcdSendCommandData(0x01); // ディスプレイ表示内容クリア
313   lcdSendCommandData(0x0c); // ディスプレイ表示
314
315   return;
316 }
317
318
319 //
320 // I2Cプロトコル制御関数
321 //
322
323 // スタートコンディション生成
324
325 void i2cProtocolStart() {
326
327   // SSP1CON2レジスタのSENビットを1に設定すると
328   // スタートコンディションが生成される
329   // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
330   SSP1IF = 0;
331   SSP1CON2bits.SEN = 1;
332   while (SSP1IF == 0) {
333   }
334   SSP1IF = 0;
335
336   return;
337 }
338
339 // リピートスタートコンディション生成
340
341 void i2cProtocolRepeatStart() {
342
343   SSP1IF = 0;
344   SSP1CON2bits.RSEN = 1;
345   while (SSP1IF == 0) {
346   }
347   SSP1IF = 0;
348
349   return;
350 }
351
352 // ストップコンディション生成
353
354 void i2cProtocolStop() {
355
356   // SSP1CON2レジスタのPENビットを1に設定すると
357   // ストップコンディションが生成される
358   // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
359   SSP1IF = 0;
360   SSP1CON2bits.PEN = 1;
361   while (SSP1IF == 0) {
362   }
363   SSP1IF = 0;
364
365   return;
366 }
367
368 // 1バイトデータ送信
369
370 void i2cProtocolSendData(uint8_t data) {
371
372   // SSP1BUFに送信したいデータをセットすると、そのデータが送信される
373   // 発行が完了するとSSP1IFが1になるのでwhile文で待つ
374   SSP1IF = 0;
375   SSP1BUF = data;
376   while (SSP1IF == 0) {
377   }
378   SSP1IF = 0;
379
380   return;
381 }
382
383 // 1バイトデータ受信
384
385 uint8_t i2cProtocolReceiveData() {
386
387   SSP1IF = 0;
388   SSP1CON2bits.RCEN = 1;
389   while (SSP1IF == 0) {
390   }
391   SSP1IF = 0;
392
393   return SSP1BUF;
394 }
395
396 // Ack/Nackチェック
397
398 uint8_t i2cProtocolCheckAck() {
399
400   uint8_t ackStatus;
401
402   if (SSP1CON2bits.ACKSTAT) {
403     ackStatus = I2C_NACK;
404   } else {
405     ackStatus = I2C_ACK;
406   }
407
408   return ackStatus;
409 }
410
411 // Ack送信
412
413 void i2cProtocolSendAck() {
414
415   // ACKDTにACKをセット(負論理なので0を設定)
416   SSP1CON2bits.ACKDT = 0;
417
418   // NACK信号生成
419   SSP1CON2bits.ACKEN = 1;
420   while (SSP1CON2bits.ACKEN) {
421   }
422
423   return;
424 }
425
426 // Nack送信
427
428 void i2cProtocolSendNack() {
429
430   // ACKDTにNACKをセット(負論理なので1を設定)
431   SSP1CON2bits.ACKDT = 1;
432
433   // NACK信号生成
434   SSP1CON2bits.ACKEN = 1;
435   while (SSP1CON2bits.ACKEN) {
436   }
437
438   return;
439 }

****************
エラーメッセージ
make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf
make[1]: Entering directory ‘C:/Users/vfr80/MPLABXProjects/LESSON.X’
make -f nbproject/Makefile-default.mk dist/default/production/LESSON.X.production.hex
make[2]: Entering directory ‘C:/Users/vfr80/MPLABXProjects/LESSON.X’
“C:\Program Files\Microchip\xc8\v2.50\bin\xc8-cc.exe” -mcpu=16F1827 -c -fno-short-double -fno-short-float -fasmfile -maddrqual=ignore -xassembler-with-cpp -Wa,-a -DXPRJ_default=default -msummary=-psect,-class,+mem,-hex,-file -ginhx032 -Wl,–data-init -mno-keep-startup -mno-osccal -mno-resetbits -mno-save-resetbits -mno-download -mno-stackcall  -std=c90 -gdwarf-3 -mstack=compiled:auto:auto   -o build/default/production/newfile.p1 newfile.c 
advisory: (2099) legacy C90 library is deprecated and will be discontinued in a future release; consult the release notes for more details 
advisory: (2095) legacy HI-TECH support is deprecated and will be discontinued in a future release; consult the release notes for more details
newfile.c:272:57: warning: (373) implicit signed to unsigned conversion
“C:\Program Files\Microchip\xc8\v2.50\bin\xc8-cc.exe” -mcpu=16F1827 -Wl,-Map=dist/default/production/LESSON.X.production.map -DXPRJ_default=default -Wl,–defsym=__MPLAB_BUILD=1 -fno-short-double -fno-short-float -fasmfile -maddrqual=ignore -xassembler-with-cpp -Wa,-a -msummary=-psect,-class,+mem,-hex,-file -ginhx032 -Wl,–data-init -mno-keep-startup -mno-osccal -mno-resetbits -mno-save-resetbits -mno-download -mno-stackcall -std=c90 -gdwarf-3 -mstack=compiled:auto:auto   -Wl,–memorysummary,dist/default/production/memoryfile.xml -o dist/default/production/LESSON.X.production.elf build/default/production/newfile.p1   
advisory: (2099) legacy C90 library is deprecated and will be discontinued in a future release; consult the release notes for more details 
advisory: (2095) legacy HI-TECH support is deprecated and will be discontinued in a future release; consult the release notes for more details
Non line specific message::: advisory: (1492) using updated 32-bit floating-point libraries; improved accuracy might increase code size
newfile.c:281:: warning: (520) function “_lcdSendString” is never called
newfile.c:398:: warning: (520) function “_i2cProtocolCheckAck” is never called
::: error: (1347) can’t find 0xA8 words (0xa8 withtotal) for psect “text13” in class “CODE” (largest unused contiguous range 0xD)
Non line specific message::: advisory: (1493) updated 32-bit floating-point routines might trigger “can’t find space” messages appearing after updating to this release; consider using the smaller 24-bit floating-point types
::: error: (1347) can’t find 0xA7 words (0xa7 withtotal) for psect “text14” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0xA4 words (0xa4 withtotal) for psect “text34” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x8A words (0x8a withtotal) for psect “text22” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x6F words (0x6f withtotal) for psect “text18” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x6C words (0x6c withtotal) for psect “text29” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x5E words (0x5e withtotal) for psect “maintext” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x5B words (0x5b withtotal) for psect “text4” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x58 words (0x58 withtotal) for psect “text17” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x51 words (0x51 withtotal) for psect “text3” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x4E words (0x4e withtotal) for psect “text23” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x48 words (0x48 withtotal) for psect “text16” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x3A words (0x3a withtotal) for psect “text10” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x34 words (0x34 withtotal) for psect “stringtext1” in class “STRCODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x34 words (0x34 withtotal) for psect “stringtext2” in class “STRCODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x32 words (0x32 withtotal) for psect “text9” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x31 words (0x31 withtotal) for psect “text25” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x2E words (0x2e withtotal) for psect “stringtext3” in class “STRCODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x23 words (0x23 withtotal) for psect “text12” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x20 words (0x20 withtotal) for psect “text19” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x1F words (0x1f withtotal) for psect “text15” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x14 words (0x14 withtotal) for psect “text28” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x14 words (0x14 withtotal) for psect “text36” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x12 words (0x12 withtotal) for psect “text7” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0x10 words (0x10 withtotal) for psect “text27” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0xE words (0xe withtotal) for psect “text24” in class “CODE” (largest unused contiguous range 0xD)
::: error: (1347) can’t find 0xD words (0xd withtotal) for psect “text39” in class “CODE” (largest unused contiguous range 0x5)
::: error: (1347) can’t find 0xB words (0xb withtotal) for psect “text21” in class “CODE” (largest unused contiguous range 0x5)
::: error: (1347) can’t find 0xB words (0xb withtotal) for psect “text32” in class “CODE” (largest unused contiguous range 0x5)
::: error: (1347) can’t find 0xB words (0xb withtotal) for psect “text37” in class “CODE” (largest unused contiguous range 0x5)
::: advisory: (1) too many errors (31)
(908) exit status = 1
nbproject/Makefile-default.mk:135: recipe for target ‘dist/default/production/LESSON.X.production.hex’ failed
make[2]: Leaving directory ‘C:/Users/vfr80/MPLABXProjects/LESSON.X’
nbproject/Makefile-default.mk:90: recipe for target ‘.build-conf’ failed
make[1]: Leaving directory ‘C:/Users/vfr80/MPLABXProjects/LESSON.X’
nbproject/Makefile-impl.mk:39: recipe for target ‘.build-impl’ failed
make[2]: *** [dist/default/production/LESSON.X.production.hex] Error 1
make[1]: *** [.build-conf] Error 2
make: *** [.build-impl] Error 2

BUILD FAILED (exit value 2, total time: 2s)

通信初心者
通信初心者
返信  管理者
1 月 前

正:void putch(char …にし、
c99にしましたが、ビルト失敗です。
mata,
また、
正:void putch(char …にし、
c90にしてもビルト失敗。

やはり、printf(“%5.2d”, adt7410GetTemperature());のように、
fをdにしないと駄目です。

この行がビルト失敗の原因のようです。

通信初心者
通信初心者
返信  管理者
1 月 前

そうすると、私が簡略化したプログラムの場合、PICを16F18857に変えれば、fをそのままにした
printf(“%5.2f”, adt7410GetTemperature());で
ビルトOKということですか?

通信初心者
通信初心者
返信  管理者
1 月 前

有難うございます。picとLCD・温度センサ間の通信について、自信が持てました。更に理解を深めたいと思います。

回避策ですが、
以下のとおり考えてみました。とりあえず、ビルト成功。
でも、自信がないので、お手すきの時にでも見てやって下さい。コメントいただけたら、幸いです。

考え方
modf関数により小数点以上と以下に分けた後、printf関数を利用する。


 // 温度センサのデータを取得して液晶ディスプレイに表示
    lcdLocateCursor(0, 0);
    //printf(“%5.2d”, adt7410GetTemperature());エラーを起こした文です。コメントアウトしました。以下、回避策です。
     
     
    double xvalue, result, integral;
    xvalue=adt7410GetTemperature();//xvalueは、温度センサーの値を一旦、xvalueに格納。
    result=modf(xvalue, &integral);//modf関数利用。
    printf(“%2d”, xvalue);//整数部を取り出す感じ。
    lcdSendCharacterData(0x2e);//AQM1602XAで表示できる文字表「.」
    printf(“%02d”, integral*100);//この辺は、未確認で、どうも、小数点以下第2位にした後、100倍して整数に戻す感じ。
     
    lcdSendCharacterData(0xdf);//温度表示用の「度」の部分です。
    lcdSendCharacterData(‘C’);//温度表示用の「C」の部分です。

    __delay_ms(1000);

通信初心者
通信初心者
返信  管理者
1 月 前

ご精査いただいて有難うございます。

そうですね。modf関数も初めてだったので、間違ってしまいました。ここに投稿した後に、間違いに気づきました。

で、ここに投稿後、一部修正した後に、ゴーグルのAI Geminiに下記の通り質問をしたところ、満足できた回答をいただきました。整数部と少数部に分けられる事が分かったので、prontf関数を使えば大丈夫ですね。実際、コンパイルも通りました。プログラム ↓
 while (1) {

    // 温度センサのデータを取得して液晶ディスプレイに表示
    lcdLocateCursor(0, 0);
    //printf(“%5.2d”, adt7410GetTemperature());

    double xvalue, result, integer;//result小数部_integer整数部
    xvalue = adt7410GetTemperature();
    result = modf(xvalue, &integer); 
    printf(“%2d”, integer); //整数部を散りだす
    lcdSendCharacterData(0x2e); //AQM1602XAで表示できる文字表「.」
    printf(“%02d”, (int) (result * 100)); //小数点以下2位までを取り出すために100倍して、%02dにより整数部2桁まで取り出す

    lcdSendCharacterData(0xdf);
    lcdSendCharacterData(‘C’);

    __delay_ms(1000);
  }

 
最後に、以下は、Gemini とのやり取り全文です。

C言語のprintf関数について質問です。

#include <math.h>
#include <stdio.h>
 
  double x, y, d;
 
  x = -14.876;
  y = modf(x, &d);

 printf(“%02d”, (int)(y*100));

戻り値を教えて下さい。

*************************************

 与えられたC言語のコードのprintf(“%02d”, (int)(y*100));の戻り値は-87です。以下に詳細な解説をします。

コードの解説:

変数宣言:
double x, y, d;で倍精度浮動小数点数型の変数x, y, dが宣言されます。
変数初期化:
x = -14.876;でxに-14.876が代入されます。
y = modf(x, &d);でmodf関数が呼び出されます。modf関数は、xを整数部分と小数部分に分け、整数部分をdに、小数部分を戻り値として返します。
結果として、dには-14.0が、yには-0.876が格納されます。
printf関数:
printf(“%02d”, (int)(y*100));でprintf関数が呼び出されます。
y * 100は-87.6となります。
(int)(y * 100)で-87.6が整数に型変換され、-87となります。
%02dは整数を2桁で出力する書式指定子ですが、今回の場合は負の値であり、また桁数も2桁以上であるため、そのまま-87が出力されます。
出力結果:

したがって、プログラムの出力は-87となります。

重要な注意点:

%02dは、整数を特定の桁数で出力するための指定子であり、浮動小数点数の小数点以下を制御するものではありません。
浮動小数点数から整数への型変換では、小数点以下は切り捨てられます。

jh3gpn
jh3gpn
6 年 前

十分に理解すること無くいただいたプログラムを書き込み動作させていますが温度・気圧等の補正はどのあたりでやれば良いのでしょうか?
地元の気象台の発表と気圧の表示がやや違います。
よく読めばかいてあるのかな?すみません、わかりませんのでご教授お願いいたします。

管理者
管理者
返信  jh3gpn
6 年 前

jh3gpnさま、
コメントどうもありがとうございます。

気象台発表の温湿度・気圧データは、気象台が設置している気象観測装置がある場所のテータになりますので、ご自宅で測定したデータとは異なります。

特に気圧については、気象台が発表するデータは海面気圧といって、標高0m地点の気圧になります。ご自分の家で測定された気圧は、標高で補正する必要があります。この連載記事の第35回で説明していますので、ご確認いただければと思います。

https://tool-lab.com/make/pic-practice-35/

jh3gpn
jh3gpn
返信  管理者
6 年 前

早速ありがとうございます。
地元の気象台発表の当地の気圧から14位低い値を表示します。
補正は「何をしているかわからない」の所でしょうか?
なかなか理解できません。トホホ

管理者
管理者
返信  jh3gpn
6 年 前

補正は第35回の記事の最後のセクション「動作確認」の項目になります。

目次