« カノープスが2回南中する日 | トップページ | 固有運動や年周視差はアマチュアには無縁なものなのか? »

2014年12月23日 (火)

PIC/I2C大気圧センサーLPS331APの測定値をSDカード(SPI)に記録する - ソース編

概要を

  「PIC/I2C大気圧センサーLPS331APの測定値をSDカード(SPI)に記録する - はじめに

に書きましたので今度はプログラムの方です。

前記事と重複しますが開発環境は以下のとおりです。

開発環境(?)

PIC: PIC18F26K22
機能豊富、メモリーもいっぱいついています。ピン数が多いのが難だと思ってましたがそのうちピンも使い切るかも (^^;;

コンパイラ: Microchip MPLAB XC8 C Compiler (Free Mode) V1.32
浮動小数点の計算(math.lib)が必要です。(あんまり意味がないと思いますが)LPS331APのデータをすべて使うのだったら“--float=32”でのコンパイルが必要です(私は“--float=24”でやってます)

大気圧・温度センサーは「PICでI2C - 大気圧・温度センサーLPS331APの使い方」で記事にしたLPS331APを使います。気圧の測定精度はなかなかのものがあります。温度はちょっとあやしいですが実用的にはそう気にするほどではありません。温度も精度がほしいという方はAM2321なんかいいかもしれません。これもI2CなのでLPS331APといっしょに使えます。「温度センサー3種の精度比較(摂氏0度~40度編)」などを参考にしてください。

LCDは「PICでI2C - 液晶(LCD)ディスプレイ(ACM1602N1-FLW-FBW)に表示する」にあるACM1602を使っています。これもI2Cです。測定値をSDカードに記録するだけならLCDは不要ですがあった方が何かと便利です。

SDカードは4GbyteのSDHCを“デバイスの既定値”でフォーマットして使っています。ソケットは秋月電子通商で売っていたDIP化基板です。

RTCは「PICでSPI - 超高精度SPIバスRTC(リアルタイムクロック)DS3234Sの実装」です。なかなか正確です。そこまで正確でなくてもいいならI2CのRTCもあります(参考「PICでI2C - リアルタイムクロック(RTC) DS1307+の使い方」)

I2CライブラリとLCDのライブラリ
I2Cのソース - PIC12F1822/16F1705/16F1938/18F26K22 - LCD(ACM1602)を例にして

SPIのライブラリとRTC/DS3234Sのライブラリ
PICでSPI - 超高精度SPIバスRTC(リアルタイムクロック)DS3234Sの使い方

SDカードのライブラリ
PICでSPI SDカードを読み書きする

いずれもはきむしげさん(きむ茶工房ガレージハウス)の記事にあるソースがベースです。特にSDカードのは「MMC/SDカードを接続し読書きを行って見ます」をそのまま使っています(ほんのちょっと手を加えてあるのでいちおう私の記事も読んでください)

(「PIC/I2C大気圧センサーLPS331APの測定値をSDカード(SPI)に記録する - 実装編」へ続く)

関連

  趣味の電子工作
    PIC

  「PICでSPI SDカードを読み書きする
  「PICでSPI - 超高精度SPIバスRTC(リアルタイムクロック)DS3234Sの使い方」(ソース付き)
  「PICでSPI - 超高精度SPIバスRTC(リアルタイムクロック)DS3234Sの実装

  「PICでI2C - リアルタイムクロック(RTC) DS1307+の使い方

  「PICでI2C - 大気圧・温度センサーLPS331APの使い方
  「温度センサー3種の精度比較(摂氏0度~40度編)

======

main.c

前記事では、SPI mode 0のRTCのクロックはインバータで反転しているので見かけ上mode 2になっている、だからmodeの変更は不要と書いたのですが、回路をインバータを使わないふつうの状態に戻しました。だからこのソースではSDカードの操作とRTCの操作の前でmodeの切り替え=SSPCON1の変更が行われています。

SDカードのファイルをオープンするなどした後割込み待ちのループに入ります。よく考えると割込みを許可するタイミングが早過ぎるような気がします。今修正して動作を確かめる余裕がないのでこのままにしておきます <== 割込み待ちループの直前に移動しました。

初期化は最低限何が必要かという観点で行ってあります。

ループで割込みを検知したら時刻と気圧データを取得してLCDに表示します。そして4分に一回5分に一回それをSDカードに書き込んでいます。時刻はBCDで格納されているので整数(バイナリ)として扱いたいときは変換が必要です。
5分ごとだったら素直に

  if( ( ct[5] & 0xF0 == ||  ct[5] & 0xF0 ) && ct[6] == 0 ) .....

の方がよかったのかも....

気象庁の発表値は10分ごとだったと思うので4分おきじゃなくて5分おきにしておいた方がよかったです  <== ソース修正済みです。

RA1がLowに落とされるとループは抜けファイルをクローズして次のループに入ります。こちらは永遠に時刻とデータ(温度、気圧)を表示し続けます。

だから電源を切るときは一度RA1をLowに落としておく必要があります。また一度ループから抜けた場合再度データをSDカードに記録したいときは電源を入れ直します。

なお先頭に3秒のウェイトが入っていますがこれは必須だと思います。細かい確認を行っていないのでこれについてはわかったらあらためて記事にします(きむしげさん(きむ茶工房ガレージハウス)のところに何か書いてあったかもしれません。いいかげんですみません)

#include <xc.h>
#include <stdio.h>
#include <string.h>
#include "..\..\include\common\MyDef.h"
#include "..\..\include\common\I2CLib.h"
#include "..\..\include\common\ACM1602lib.h"
#include "..\..\include\common\lps331ap.h"
#include "..\..\include\common\SPILib.h"
#include "..\..\include\common\SDlib.h"
// C:\Program Files (x86)\Microchip\xc8\v1.32\docs\chips\18f26k22.html
#ifdef _18F26K22
#pragma config FOSC = INTIO67
#pragma config WDTEN = OFF
#pragma config MCLRE = INTMCLR
#endif
static int Interrupt=0;
void putLCDhex(unsigned char ch)
{
    char hex[]="0123456789ABCDEF";
    LCD_Putc(hex[ch>>4]) ;
    LCD_Putc(hex[ch & 0x0f]) ;
}
void interrupt InterFunction( void )
{
    // INT0割込み
    if( INT0IF == 1 ) {
        Interrupt = 1;
        INT0IF = 0;
    // I2C関連の割り込み処理
    } else {
        InterI2C() ;
    }
}
void main()
{
    unsigned char ct[8];
    unsigned char d1;
    char filename[16];
    union {
        char c[2] ;
        int  i ;
    } ans ;
    char    dt_w[32] ="Start!\r\n";
    char    dt_m[32];
    int     j,k,sec,min;
    int     status=0;
    float   val_LPS331[2];
    OSCCON = MyOSCCON ;
// 入力ピンは一般的に電源を含めどこに接続してもたいていヘーキですが
// 出力ピンが電源や他の出力ピンに接続されるは避けた方がいいと思います。
// だから事故防止のため出力として使わないピンは入力のままにしてあります。
// for I2C
    ANSELCbits.ANSC3 = 0;
    ANSELCbits.ANSC4 = 0;
// Select bus for I2C
// 同じアドレスのI2Cデバイスを使うのにSCLをアナログ・スイッチで切り替えるため
    TRISC2 = 0;
    PORTCbits.RC2 = 1;
// for SPI
    ANSELBbits.ANSB2 = 0;
    ANSELBbits.ANSB1 = 0;
    TRISB1 = 0;
    TRISB3 = 0;
// OUTPUT
// SDカードの/CS
    TRISB4 = 0;
    CS_SD = 1;
// RTC DS3234Sの/CS
    TRISA7 = 0;
    CS_RTC = 1;
// INT0
    ANSELBbits.ANSB0 = 0;
// INPUT
// RA1がLowに落ちたら記録をやめます。
    ANSELAbits.ANSA1 = 0;
// SPIはひとまずmode 2として初期化します。
    SPI_Init(SPI_MODE2,SPI_CLOCK_DIV16) ;
    InitI2C_Master() ;
    LCD_Init() ;
    LCD_Clear();
    LCD_Puts("Start!");
    // これが重要みたいです。
    for(j=0; j < 300; j++ ) __delay_ms(10);
    // RTCの1PPSでINT0から割込みをかけます。
    //INT0IE = 1;  // よく考えたらこれはwhileの前にするのが正しいような....
    // 時刻を取得します。
    SSP2CON1 &= 0b11101111;
    CS_RTC = 0;
    d1=SPI_transfer(0x00) ;
    for( j =0; j < 7; j++ ) {
        ct[6-j]=SPI_transfer(0x00) ;
    }
    CS_RTC = 1;
    LCD_SetCursor(0,0);
    for( j=0; j < 7; j++ ) {
        putLCDhex(ct[j]);
    }
    LCD_SetCursor(0,1);
    // ファイル名は8.3形式である必要があります。
    sprintf(filename,"%02x%02x%02x%02x.TXT",ct[1],ct[2],ct[4],ct[5]);
    // SDカードの初期化とファイルのオープン
    SSP2CON1 |= 0b00010000;
    ans.i = SD_Init() ;
    if (ans.i != 0) {
        LCD_Puts("Error-Init ") ;            // 初期化エラー
        putLCDhex(ans.c[1]) ;
        putLCDhex(ans.c[0]) ;
        while( PORTAbits.RA1 == 1 );
        status = 0x01;
    } else {
        ans.i = SD_Open(filename,O_RDWR) ;
        if (ans.i != 0) {
            LCD_Puts("Error-Open ") ;      // オープンエラー
            while( PORTAbits.RA1 == 1 );
            status = 0x02;
        } else {
            ans.i = SD_Write(dt_w,strlen(dt_w)) ;
            if( ans.i == -1 ) {
                LCD_Puts("Error-Write  ") ;      // オープンエラー
                while( PORTAbits.RA1 == 1 );
                status = 0x04;
            }
        }
    }
    // RTCの1PPSでINT0から割込みをかけます。
    INT0IE = 1;

    k = 0;
    LCD_SetCursor(0,0);
    while( status == 0 ) {
        // 1PPSの割込みがなかったとき
        if( Interrupt == 0 ) continue;
        // RA1がLowに落ちたら測定終了です。
        // RA1をHighにしたら再測定、ということにはなっていません。
        if( PORTAbits.RA1 == 0 ) break;
        // DS3234Sから時刻を取得します。
        SSP2CON1 &= 0b11101111;
        CS_RTC = 0;
        d1 = SPI_transfer(0x00) ;
        for( j =0; j < 7; j++ ) {
            ct[6-j]=SPI_transfer(0x00) ;
        }
        CS_RTC = 1;
        // 時刻をLCDに表示します。
        for( j=0; j < 7; j++ ) {
            if( j == 3 || j == 4 ) LCD_Putc('-');
            putLCDhex(ct[j]);
        }
        LCD_SetCursor(0,1);
        // LPS331APから気圧と温度を取得します。
        lps331ap(val_LPS331);
        // 温度と気圧をLCDに表示します。
        sprintf(dt_m,"%5.1f,%8.1f",val_LPS331[0],val_LPS331[1]);
        LCD_Puts(dt_m) ;
        // 4分毎5分毎に測定結果をSDカードに書き込みます。
        sec = ((ct[6] & 0xF0) >> 1) + ((ct[6] & 0xF0) >> 3) + (ct[6] & 0x0F);
        min = ((ct[5] & 0xF0) >> 1) + ((ct[5] & 0xF0) >> 3) + (ct[5] & 0x0F);
        // if( (min & 0x03) == 0 && sec == 0) {
        if( (min % 5 ) == 0 && sec == 0) {
            // 時刻をSDカードに記録します。
            SSP2CON1 |= 0b00010000;
            sprintf(dt_w,"%02x/%02x/%02x,%02x:%02x:%02x,",
                    ct[0],ct[1],ct[2],ct[4],ct[5],ct[6]);
            ans.i = SD_Write(dt_w,strlen(dt_w)) ;
            // 温度と気圧をSDカードに記録します。
            strcat(dt_m,"\r\n");
            ans.i = SD_Write(dt_m,strlen(dt_m)) ;
            // 測定結果をSDカードに書き込んだことをLCDに表示します。
            LCD_Puts(" R") ;
        } else {
            // SDカードへの書き込みをしないとき
            LCD_Puts("  ") ;
        }
        LCD_SetCursor(0,0);
        // 次の割込みを待ちます。
        Interrupt = 0;
    }
    // SDカードをクローズします。
    if( status == 0 ){
        SSP2CON1 |= 0b00010000;
        SD_Close() ;
        LCD_SetCursor(0,1);
        LCD_Puts("End.            ") ;
    }
    // 測定の記録が終わっても時刻の表示は続けます。
    LCD_SetCursor(0,0);
    while( 1 ) {
        if( Interrupt == 0 ) continue;
        // DS3234Sから時刻を取得します。
        SSP2CON1 &= 0b11101111;
        CS_RTC = 0;
        for( j=0; j < 25; j++ ) __delay_ms(20);
        ct[0]=SPI_transfer(0x00) ;
        for( j =0; j < 7; j++ ) {
        ct[6-j]=SPI_transfer(0x00) ;
        }
        CS_RTC = 1;
        for( j=0; j < 7; j++ ) {
            if( j == 3 || j == 4 ) LCD_Putc(' ');
            putLCDhex(ct[j]);
        }
        // LPS331APから気圧と温度を取得します。
        lps331ap(val_LPS331);
        // 温度と気圧をLCDに表示します。
        sprintf(dt_m,"%5.1f %8.1f *",val_LPS331[0],val_LPS331[1]);
        LCD_SetCursor(0,1);
        LCD_Puts(dt_m) ;
        LCD_SetCursor(0,0);
        Interrupt = 0;
    }
}

LPS331APのデータ取得とそれから温度・気圧を求める方法については「PICでI2C - 大気圧・温度センサーLPS331APの使い方」も参考にしていただければと思います。
データの取得はコメントにしたような形でできればすっきりするのですが、これでは動かなかったような。コーディングが間違っているのかも。

lps331ap.h

/*
* File:   lps331ap.h
* Author: Seppina
*
* Created on 2014/12/22, 12:55
*/
#ifndef LPS331AP_H
#define LPS331AP_H
// 秋月電子通商 LPS331AP
// 超小型大気圧DIP化モジュール
#define LPS331_ADRES 0x5d
float getPrsLPS331(unsigned char *ip);
float getTmpLPS331(unsigned char *ip);
void lps331ap(float *val_LPS331);
#endif /* LPS331AP_H */


lps331ap.c

#include <xc.h>
#include "..\..\include\common\MyDef.h"
#include "..\..\include\common\I2CLib.h"
#include "..\..\include\common\lps331ap.h"
float getPrsLPS331(unsigned char *ip)
{
    float fp;
            fp = ip[2];     // これはなくてもいいでしょう。ここまでの精度はありません。
                            // --float=24でコンパイルするときはあっても無意味です。
            fp = fp/256.0 + ip[1];  // ip[2]を使わない場合ここは fp = ip[1]; となります。
            fp = fp/16.0 + ip[0] * 16.0;
            return fp;
}
float getTmpLPS331(unsigned char *ip)
{
    float fp;
            fp = ip[0];
            fp = fp*256.0+ip[1];
            if(ip[0] & 0x80 ) {
                fp -= 65536.0;
            }
            fp=42.5 + fp/480;
            return fp;
}
void lps331ap(float val_LPS331[])
{
    int ans;
    int i;
    unsigned char ip_LPS331[5];
    float val;
//  LPS331APのデータを取り込む
    ans = I2C_Start(LPS331_ADRES,RW_0);
    if (ans == 0) {
        I2C_Send(0x20) ;  // レジスタ0x20(CTRL_REG1) へ書き込みます
        I2C_Send(0x90) ;  // 0x80 アクティブにする+0x10 1秒1回のレートで変換
    }
    I2C_Stop() ;
    for( i=0; i < 10; i++ ) __delay_ms(10) ;
    for( i=0x2c;i>0x27;i--){
        ans = I2C_Start(LPS331_ADRES,RW_0);
        if (ans == 0) {
            I2C_Send(i) ;  // センサーのデータは0x28-0x2cに格納されています。
            ans = I2C_rStart(LPS331_ADRES,RW_1) ;
            if (ans == 0) {
                ip_LPS331[0x2c-i] = I2C_Receive(NOACK) ;
            }
        }
        I2C_Stop() ;
    }
/*
これでもよさそうな気がするんですが....
    ans = I2C_Start(LPS331_ADRES,RW_0);
    if (ans == 0) {
        I2C_Send(0x28) ;  // センサーのデータは0x28-0x2cに格納されています。
        ans = I2C_rStart(LPS331_ADRES,RW_1) ;
     if( ans == 0 ) {
      for( i=0x28;i<0x2C;i++){
             ip_LPS331[0x2c-i] = I2C_Receive(ACK) ;
         }
           ip_LPS331[0] = I2C_Receive(NOACK) ;
        }
}
    I2C_Stop() ;
*/
    val_LPS331[0] = getTmpLPS331(&ip_LPS331[0]);
    val_LPS331[1] = getPrsLPS331(&ip_LPS331[2]);
}

MyDef.h

これは「PICでSPI SDカードを読み書きする」と同じものです。PIC18F26K22以外のPICの定義がありますが、これは他のプログラムようです。このプログラムは18F26K22じゃないと動きません。

/*
* File:   MyDef.h
* Author: Seppina
*
* Created on 2014/11/22, 14:38
*/
#ifndef MYDEF_H
#define MYDEF_H
// http://www.microchip.jp/docs/41406B_JP.pdf
#ifdef _12F1822
#define _XTAL_FREQ 16000000
#define MySSPADD    0x27
#define SSPIE   SSP1IE
#define SSPIF   SSP1IF
#define BCLIE   BCL1IE
#define BCLIF   BCL1IF
//#define CS   PORTAbits.RA0
#endif
// http://ww1.microchip.com/downloads/en/DeviceDoc/40001729A.pdf
// http://ww1.microchip.com/downloads/jp/DeviceDoc/40001722A_JP.pdf
// (下は16F1703のものですが参考になります。これもきむしげさん情報です)
#ifdef _16F1705
#define _XTAL_FREQ 8000000
#define MySSPADD    0x13
#define SSPIE   SSP1IE
#define SSPIF   SSP1IF
#define BCLIE   BCL1IE
#define BCLIF   BCL1IF
//#define CS   PORTAbits.RA0
#endif
// http://ww1.microchip.com/downloads/en/DeviceDoc/40001574C.pdf
#ifdef _16F1938
#define _XTAL_FREQ 8000000
#define MySSPADD    0x13
//#define CS   PORTAbits.RA0
#endif
// http://ww1.microchip.com/downloads/en/DeviceDoc/41412F.pdf
#ifdef _18F26K22
#define MyOSCCON    0b01110010
#define _XTAL_FREQ  16000000
#define MySSPADD   0x27
#define CS_SD   PORTBbits.RB4                     // カード選択信号
#define CS_RTC  PORTAbits.RA7                     // RTC選択信号
// #define SSPCON1   SSP1CON1  // エイリアス(?)がきってあるみたいで
// #define SSPBUF   SSP1BUF  // これ以後はなくてもよかったと思います。
// #define SSPSTAT   SSP1STAT  // 今すぐ確かめられないのでそのままにしておきます。
// #define SSPIE   SSP1IE
// #define SSPIF   SSP1IF
// #define BCLIE   BCL1IE
// #define BCLIF   BCL1IF
#endif
#endif /* MYDEF_H */

			

« カノープスが2回南中する日 | トップページ | 固有運動や年周視差はアマチュアには無縁なものなのか? »

趣味の電子工作」カテゴリの記事

コメント

この記事へのコメントは終了しました。

トラックバック

« カノープスが2回南中する日 | トップページ | 固有運動や年周視差はアマチュアには無縁なものなのか? »

フォト

サイト内検索

  • 記事を探されるんでしたらこれがいちばん早くて確実です。私も使ってます (^^;; 検索窓が表示されるのにちょっと時間がかかるのはどうにかしてほしいです。

新着記事

リンク元別アクセス数

  • (アクセス元≒リンク元、原則PCのみ・ドメイン別、サイト内等除く)

人気記事ランキング

  • (原則PCのみ、直近2週間)
無料ブログはココログ