メモリ-に注意
メモリーに注意
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;
}