メモリ-に注意

メモリーに注意

 amazonの「HiLetgo データ ロガー モジュール ロギング シールド データ レコーダ シールド Arduino UNOワット/ SDカードのために」という、SDとタイムモジュールが付いたシールドを利用して、水温記録装置を開発していた時のことです。
 下に紹介するようなプログラムで、TIMESETTEI が true の時、SD記録ができなくて、どこのプログラムがおかしいのか、時間設定周りや、記録ファイル名の決定関数などを、いつまでも点検していました。
 でも、これはプログラムの整合性がどうのという問題ではなくて、メモリーの消費量の問題でした。
 この、データロガーシールドは、時間にDS1307RTCというタイマーを使っています。この時使っていたタイマー関係のライブラリーがメモリーをかなり食うので、時間設定をONにした時だけ、ファイル名が勝手に何かに上書きされて、動作が不安定になっていたのでした。
 使うライブラリーをシンプルなものに変えたら、他のプログラムは一切いじらないでも、きちんと動くようになりました。

 arduinoは、用意されているモジュールやライブラリーを組み合わせれば、簡単に思ったものが作りやすい面白い道具です。
 でも、この例のように、ちょっと長いものやメモリーを食うものを作ろうとするときには、メモリーに注意が必要です。グローバル変数に92%も使っていると、ローカル変数で使える領域が極端に少なくなり、ローカルで作業をしているときに、記録領域が無くなって、勝手にグローバル変数の領域に侵食してしまうようです。
 この、変数にどれくらい使っているかというのは、「ファイル>環境設定」から、「コンパイラの警告」を「全て」にして、コンパイルだけすると見ることができます。
 arduinoは、ちょっとしたものを作るときには手軽でいいですが、少し大きなものを作ろうとすると、思わぬところで注意が必要なようです。

サンプルコード (不具合が出る、製作途中)

//  文字列変換用バッファー
#define  STR_HENKAN_BUFF  30

//  I2Cライブラリ

#include  <Wire.h>

//時間処理用
//  DS1307RTC
#include  <Time.h>
#include  <DS1307RTC.h>

//時間処理用
//  時間設定時  true    通常使用は  false  に戻してコンパイルし直す
//  パソコンに同期できないときtrueのままだと
//  同期出来た時の以前の時間に初期化される
#define  TIMESETTEI  true
//***********************
#define  TIMETYOUSEI  5    //  時間微調整
//********************
//変数
tmElements_t  tm;
//  ここから  DS3231の設定のまま  このプログラムを動かすための本来いらない設定
//  DS3231タイムモジュール使用時  true  不使用時  false
#define  DS_3231  false
#define  DS_3231_ONDO  false
//*************************  DS_3231温度取得時
#define  TMPTYOUSEI2  -1.8  //  温度微調整
//*******************
//  ここまで  
//  SDカード設定
#include  <SD.h>
#include  <SPI.h>
#define  SD_SS  10  //  SD使用ソケット番号
//*************
File  myFile;
//filename
#define  FILENAME  "ondo"    //  保存ファイル名  ondo1.csv  の様になる
//***********************
#define  FILENAME_SUU  8  //filename  の長さ設定
#define  FILEMAX  131072  //1024*128  100Kバイト以下で保存
//********************
//ディスプレイ用
//  LIQUIDCRYSTAL_I2C  使用時  true    LIQUIDCRYSTAL  使用時  false
#define  LIQUIDCRYSTALI2C  true
//****************************
//  ディスプレイ  アドレス・表示幅・桁
#define  LCD_ADDRESS  0x3f
#define  LCDSIZE_YOKO  20  //  16  or  20
#define  LCDSIZE_TATE  4    //  2  or  4
//*******************

#if  LIQUIDCRYSTALI2C==true  //  I2C  シリアルボード使用
  #include  <LiquidCrystal_I2C.h>
  LiquidCrystal_I2C  lcd(LCD_ADDRESS,LCDSIZE_YOKO,LCDSIZE_TATE);

  #define  printIIC(args)  Wire.write(args)
  inline  size_t  LiquidCrystal_I2C::write(uint8_t  value)  {
    send(value,  Rs);
    return  1;
  }

#else  //  LiquidCrystal使用
  //  include  the  library  code:
  #include  <LiquidCrystal.h>
  //  initialize  the  library  with  the  numbers  of  the  interface  pins
  LiquidCrystal  lcd(8,  9,  4,  5,  6,  7);
  //***********************************
#endif

//温度・湿度用
#include  <OneWire.h>
#include  <DallasTemperature.h>
//温度モジュル接続ピン
//  Data  wire  is  plugged  into  port  2  on  the  Arduino
#define  ONE_WIRE_BUS  2
#define  ONDOKEI_SUU  2  //  DS18B20  温度計の数
//******************************
  float  ondo1[ONDOKEI_SUU+1]={-126.90};  //温度記録用変数  DS18B20用
  float  tmp_tyousei[ONDOKEI_SUU+1]={0,0,+0.5,  };  //  温度微調整
//*********************************

//  DS18B20センサーモジュール  分解能セット
//0.5℃、0.25℃、0.125℃、0.0625℃に対応して、9、10、11、12ビットを選択す
#define  SENSER_BIT  12  //9~12  数字が大きいほど高精度  但し  時間がかかる
//********************
DeviceAddress  ondokei1[ONDOKEI_SUU];  //  温度計アドレス  DS18B20用
//***************************************
//  Setup  a  oneWire  instance  to  communicate  with  any  OneWire  devices  (not  just  Maxim/Dallas  temperature  ICs)
OneWire  oneWire(ONE_WIRE_BUS);
//  Pass  our  oneWire  reference  to  Dallas  Temperature.  
DallasTemperature  sensors(&oneWire);

void  setup()  {
    Serial.begin(115200);  //ディバッグ時  コメントアウトを外す
    while  (!Serial)  {
      ;  //  wait  for  serial  port  to  connect.  Needed  for  native  USB  port  only
    }
  //時間設定ONの時のみ  パソコンと同期  時間初期設定
  time_settei(TIMESETTEI);
    //LCD初期設定
  #if  LIQUIDCRYSTALI2C==true  //  I2C  シリアルボード使用
    lcd.init();
    lcd.backlight();
  #else  //  LiquidCrystal使用
    lcd.begin(LCDSIZE);
  #endif

  //  SDセットアップ
  sdsetup();

  //  DS18B20センサーモジュール温度計  アドレスセット
    int  i;
  for(i=0;i<ONDOKEI_SUU;++i){
    sensors.getAddress(ondokei1[i],i);
  }
    //  DS18B20センサーモジュール  分解能セット
    sensors.setResolution(SENSER_BIT);
}

void  loop()  {
  //ディスプレイ表示
  //日付・時間
  time_dsp_hyouji();
  //温度
  ondo_syutoku();
  //  データの保存
  data_hozon();
}

//  ディスプレイへの日・時の表示
void  time_dsp_hyouji(){
  //時間の取得
  if  (RTC.read(tm))  {
      Serial.print("tm.Month=");
      Serial.println(tm.Month);
      Serial.print("tm.Minute=");
      Serial.println(tm.Minute);
      Serial.print("tm.Second=");
      Serial.println(tm.Second);  

    int  yoko=1;
    int  haba=9;
    String  hyouji1;
      //  日時の表示開始
    if(LCDSIZE_YOKO  >=  20  ||  ONDOKEI_SUU<=1){
        hyouji(0,  0,1,  "x27");
        hyouji1  =  String(tmYearToCalendar(tm.Year)  -  2000)  +  "/";
    }else{
      hyouji1  =  "";
      haba=5;
      yoko=0;  
    }
    hyouji1  +=  String(tm.Month)  +  "/"  +  String(tm.Day);
    string_hyouji(yoko,  0,  haba,  hyouji1);
    if  (tm.Hour  <  10)  {
      hyouji(0,  1,  1,  "  ");
      ketahyouji(1,  1,  1,  tm.Hour);
    }  else  {
      ketahyouji(0,1,  2,  tm.Hour);
    }
    hyouji(2,  1,  1,  ":");
    ketahyouji(3,  1,  2,  tm.Minute);
    if(LCDSIZE_YOKO  >=  20  ||  ONDOKEI_SUU<=1){
      hyouji(5,  1,  1,  ":");
      ketahyouji(6,  1,  2,  tm.Second);
    }
  }  else  {
    if  (RTC.chipPresent())  {
      while  (RTC.chipPresent())  {
        hyouji(0,  0,  16,  "The  DS1307");
        hyouji(0,  1,  16,  "is  stopped.");
        delay(1000);
        hyouji(0,  0,  16,  "Please  run");
        hyouji(0,  1,  16,  "the  SetTime");
        delay(1000);
        hyouji(0,  0,  16,  "example  to");
        hyouji(0,  1,  16,  "the  SetTime");
        delay(1000);
        hyouji(0,  0,  16,  "example  to");
        hyouji(0,  1,  16,  "the  SetTime");
        delay(1000);
        hyouji(0,  0,  16,  "initialize");
        hyouji(0,  1,  16,  "the  time  and");
        delay(1000);
        hyouji(0,  0,  16,  "begin");
        hyouji(0,  1,  16,  "running.");
        delay(1000);
        hyouji(0,  0,  16,  "  ");
        hyouji(0,  1,  16,  "  ");
      }
    }  else  {
      while  (RTC.chipPresent())  {
        hyouji(0,  0,  16,  "DS1307  read");
        hyouji(0,  1,  16,  "  error!");
        delay(1000);
        hyouji(0,  0,  16,  "Please  check");
        hyouji(0,  1,  16,  "the");
        delay(1000);
        hyouji(0,  0,  16,  "circuitry.");
        hyouji(0,  1,  16,  "  ");
        delay(1000);
        hyouji(0,  0,  16,  "  ");        
        hyouji(0,  1,  16,  "  ");
      }
    }
  }
}

//  温度取得  →  表示
void  ondo_syutoku(){  
  int  ondohyouji_suu  =  ONDOKEI_SUU;  //温度表示の総数  DS_3231を含む

#if  DS_3231==true  
  //  DS3231内蔵温度計
  clock.forceConversion();
  if(DS_3231_ONDO){
    ondo1[0]  =clock.readTemperature()  +  TMPTYOUSEI2  ;  //  call  sensors.requestTemperatures()  to  issue  a  global  temperature
                                        //温度微調整
    tmp_tyousei[0]=  TMPTYOUSEI2  ;
    ondohyouji_suu  +=1;
  }

#endif
  //  DS18B20センサーモジュール温度計  
  //  call  sensors.requestTemperatures()  to  issue  a  global  temperature  
  //  request  to  all  devices  on  the  bus  
  sensors.requestTemperatures();  //  Send  the  command  to  get  temperatures
    int  i;
  for(i=0;i<ondohyouji_suu;++i){
    if(DS_3231_ONDO){
      ondo1[i+1]  =sensors.getTempCByIndex(i)  +  tmp_tyousei[i+1]  ;  //  call  sensors.requestTemperatures()  to  issue  a  global  temperature
                                                //温度微調整
    }else{
      ondo1[i]  =sensors.getTempCByIndex(i)  +  tmp_tyousei[i]  ;  //  call  sensors.requestTemperatures()  to  issue  a  global  temperature
                                                //温度微調整
    }
  }
  ondo_dsp_hyouji(ondohyouji_suu);
}

//  ディスプレイへの温度表示
void  ondo_dsp_hyouji(int  ondohyouji_suu){
    int  yoko=10;
  if(LCDSIZE_YOKO  <  20  &&  ondohyouji_suu>LCDSIZE_TATE){
    yoko=6;
  }
    int  i;
    char  *no;
    int  len;
    int  tate;
  for(i=LCDSIZE_TATE;i<ondohyouji_suu;++i){  //  2ページ目以降から表示
    no=str_to_char(String(i+1)+String(":"));
    len=strlen(no);
    tate=  i  %  LCDSIZE_TATE;
    hyouji(yoko,tate  ,len,no);
    if(  ondo1[i]>-125+  tmp_tyousei[i]){
        ondo1[i]=ondo_hyouji(yoko+len,  i  ,  ondo1[i]);
      }
    if(  tate==LCDSIZE_TATE-1){
      delay(2500);
    }
  }
  if(  tate!=LCDSIZE_TATE-1  &&  LCDSIZE_TATE  <  ondohyouji_suu){  //  ページ余白の消去
    for(tate=tate+1;tate<LCDSIZE_TATE;++tate){
      hyouji(yoko,tate  ,LCDSIZE_YOKO-yoko,"");      
    }
    delay(2500);
  }
    
  for(i=0;i<LCDSIZE_TATE  &&  i<  ondohyouji_suu  ;++i){  //  1ページ目
    if(LCDSIZE_YOKO  <  20  &&  ondohyouji_suu<=2){
      len=0;
    }else{
      no=str_to_char(String(i+1)+String(":"));
      len=strlen(no);
      hyouji(yoko,i  ,len,no);
    }
    if(  ondo1[i]>-125+  tmp_tyousei[i]){
        ondo1[i]=ondo_hyouji(yoko+len,  i  ,  ondo1[i]);
      }
  }
  if(  i<LCDSIZE_TATE){    //  ページ余白の消去
    for(i;i<LCDSIZE_TATE;++i){
      hyouji(yoko,i  ,LCDSIZE_YOKO-yoko,"");      
    }
  }
  if(  LCDSIZE_TATE<ondohyouji_suu){    
    delay(2500);
  }
}

//  データの保存
void  data_hozon(){
  static  int  h;

  char  *file_tmp;
  char  file[FILENAME_SUU+5];

  if  (h  !=  tm.Minute){
    h  =  tm.Minute;
    file_tmp=filename();
    strcpy(file,file_tmp);
    bool  ari=isfile(file);

    //  open  a  file
    myFile  =  SD.open(file,FILE_WRITE);

    if(myFile){
      int  kaisuu=ONDOKEI_SUU;
      if(DS_3231  ){
        kaisuu  +=  1;
      }
  
      if(ari==0){
        myFile.print(""ネン","ツキ","ヒ","ジ","フン","ビョウ"");
          int  i=0;
          String  str_s;
          char  *str_henkan;

        for(i=0;i<kaisuu;++i){
            str_s=String(","オンド")+String(i+1)+String(""");
            str_henkan=str_to_char(str_s);                  
            myFile.print(str_henkan);      
        }
        myFile.println("");
      }
      myFile.print(tmYearToCalendar(tm.Year));
      myFile.print(",");
      myFile.print(tm.Month);
      myFile.print(",");
      myFile.print(tm.Day);
      myFile.print(",");
      myFile.print(tm.Hour);
      myFile.print(",");
      myFile.print(tm.Minute);
      myFile.print(",");
      myFile.print(tm.Second);        

        int  i=0;
      for(i=0;i<kaisuu;++i){
        myFile.print(",");
        if(ondo1[i]>-125+  tmp_tyousei[i]){
          myFile.print(ondo1[i]);
        }
      }

      myFile.println("");

      hyouji(0,  1  ,  16,"カキコミシュウリョウ");  
    }else{
      hyouji(0,  1  ,  16,"ファイルオープンエラー");
    }
  
    myFile.close();
    delay(1000);
    hyouji(0,  1,  16,  "@  ");
  }
}

//液晶表示用関数  char
void  hyouji(int  yoko,  int  tate,  int  haba,  char  *text)  {
  utf_del_uni(text);
  
  //文字化け防止のため1字ずつ表示
  int  i  =  0;
  int  j  =  0;
  while  (text[i]  !=  ”  &&  i<haba  )  {
    lcd.setCursor(yoko  +  i,  tate);
    lcd.print(text[i]);
    i++;
  }
  while  (i  <  haba)  {
    lcd.setCursor(yoko  +  i,  tate);
    lcd.print(‘  ’);
    i++;
  }
}

//  UTF-8  to  ASCIIコード変換
void  utf_del_uni(char  *s)  {
  byte  i  =  0;
  byte  j  =  0;
  while  (s[i]  !=  ”)  {
    if  ((byte)s[i]  ==  0xEF)  {
      if  ((byte)s[i  +  1]  ==  0xBE)  s[i  +  2]  +=  0x40;
      i  +=  2;
    }
    s[j]  =  s[i];
    i++;
    j++;
  }  
  s[j]  =  ”;
}

//stringの表示
char  *string_hyouji(int  yoko,  int  tate,  int  haba,  String  hyouji1)  {
  //最後に文字を入れておかないと数字の最後が表示されない
  hyouji1  =  hyouji1  +  ’  ’;
  int  len  =  hyouji1.length()-1;  //  文字列長さ
  hyouji1[len]=”;
  char  *str_henkan;
  str_henkan=str_to_char(hyouji1);
  hyouji(yoko,  tate,  haba,str_henkan);
  //短いときに旧表示を消去
  return(str_henkan);
}

//string  to  char[]変換
char  *str_to_char(String  text1){
  static  char  str_henkan[STR_HENKAN_BUFF];
  int  len  =  text1.length();  //  文字列長さ
  text1.toCharArray(str_henkan,  len  +  1);  
  return  str_henkan;
}

//  数字桁合わせ表示    01  など
char  *  ketahyouji(int  yoko,  int  tate,  int  haba,  int  suu)  {
  String  hyouji1  =  String(suu);
  int  len  =  hyouji1.length();  //  文字列長さ
  int  i  =  0;
  while  (i  +  len  <  haba)  {
    hyouji1  =  "0"  +  hyouji1;
    ++i;
  }
  char  *str_henkan;
  str_henkan=str_to_char(hyouji1);
  hyouji(yoko,  tate,  haba,  str_henkan);
  return(str_henkan);
}

//温度  0.1℃表示
float  ondo_hyouji(int  yoko,  int  tate,  float  t)  {  
  //  小数第1位までで四捨五入する
  t  =  round1(t,-1);
  String  hyouji1  =  String(t);
  char  *str_henkan;
  str_henkan=str_to_char(hyouji1);
  int  len  =  hyouji1.length();  //  文字列長さ
  *(str_henkan+len-1)=’xDF’;
  *(str_henkan+len)=’C’;
  *(str_henkan+len+1)=”;
  hyouji(yoko,  tate,LCDSIZE_YOKO-yoko,  str_henkan);
  return(t);
}

/*四捨五入(10のn乗の位に処理)*/
float  round1(float  d,  int  n){
    double  dst;
    d  =  d  *  pow(10,  -n  );          ~/*処理を行う桁を10-1  の位にする*/
    if(d>=0){
      dst  =  (double)(int)(d  +  0.5);
    }else{        //  マイナスの時は絶対値にマイナスを付ける
      dst  =  (double)(int)(d  -  0.5);
    }
    return    dst  *  pow(10,  n  );        ~/*処理を行った桁を元に戻す*/
}

//時間取得関数
bool  getTime(const  char  *str){
  int  Hour,  Min,  Sec;
  if  (sscanf(str,  "  %d:%d:%d  ",  &Hour,  &Min,  &Sec)  !=  3)    return  false;
  tm.Hour  =  Hour;
  tm.Minute  =  Min;
  tm.Second  =  Sec;
  return  true;
}

bool  getDate(const  char  *str)
{
  const  char  *monthName[12]  =  {
  "Jan",  "Feb",  "Mar",  "Apr",  "May",  "Jun",
  "Jul",  "Aug",  "Sep",  "Oct",  "Nov",  "Dec"
  };
  char  Month[12];
  int  Day,  Year;
  uint8_t  monthIndex;

  if  (sscanf(str,  "  %s  %d  %d  ",  Month,  &Day,  &Year)  !=  3)  return  false;
  
  for  (monthIndex  =  0;  monthIndex  <  12;  monthIndex++)  {
    if  (strcmp(Month,  monthName[monthIndex])  ==  0)  break;
  }
  if  (monthIndex  >=  12)  return  false;
  tm.Day  =  Day;
  tm.Month  =  monthIndex  +  1;
  tm.Year  =  CalendarYrToTm(Year);
  return  true;
}

//時間設定ONの時のみ  パソコンと同期  時間初期設定

//  get  the  date  and  time  the  compiler  was  run

void  time_settei(bool  settei1){
  //時計初期設定
  bool  parse  =  false;
  bool  config  =  false;
  if  (settei1)  {
      if  (getDate(__DATE__)  &&  getTime(__TIME__))  {
      parse  =  true;
      
      //時計時間微調整
      if  (tm.Second  +  TIMETYOUSEI  >=  60)  {
        tm.Second  =  tm.Second  +  TIMETYOUSEI  -  60;
        tm.Minute  +=  1;
      }  else  {
        tm.Second  =  tm.Second  +  TIMETYOUSEI;
      }
    
      //  and  configure  the  RTC  with  this  info
      if  (RTC.write(tm)){
        config  =  true;
      }
    }
  }
    return;
}
  

//  ファイル関係関数

//  SDセットアップ
void  sdsetup(){
  if  (!SD.begin(SD_SS))  {
      hyouji(0,  0,  16,  "SDinitialization");
      hyouji(0,  1,  16,  "  failed!");
      delay(5000);
    return;
  }
}

//  fite有り無し
bool  isfile(char  *text){
  if  (SD.exists(text))  {
    return  1;
  }  else  {
    return  0;
  }
}

//保存filename決定
char  *filename(){
    String  filename_s;
    static  char  filename1[FILENAME_SUU+4]="";
    static  int  i=1;
    uint32_t  filesize=FILEMAX;  //  FILEMAXバイト以下で保存
    int  isfile=0;
    char  *filename_c;
    while(isfile==0  &&  filesize>=FILEMAX){  //  FILEMAXバイト以下で保存
        filename_s=String(FILENAME)+String(i)+String(".csv");
        filename_c=str_to_char(filename_s);
        strcpy(filename1,filename_c);
        myFile=SD.open(filename1,FILE_READ);  
        if(myFile){
          filesize=myFile.size();
          isfile=0;
        }else{
          isfile=1;
        }
        myFile.close();
        ++i;
    }
    –i;
    return  filename1;
}
タイトルとURLをコピーしました