Как сделать будильник из массива строк

Клапауций 234
Offline
Зарегистрирован: 24.10.2016

 (551%) динамической памяти

это не ошибка - это ИИ пытается убежать в реал

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ardoinshik, то что вы начали изначально делать -это неправильно. Программе не нужны данные в том формате, в котором они удобны человеку. У вас гигантская избыточность массивов. Какой смысл в каждой ячейке хранить полную дату? наверняка в схемах алармов есть или повторы или закономерности, используя которые  можно принципиально изменить всю логистику хранения и упаковать в гораздо меньшие объёмы.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

1. Не совсем понятно, зачем для хранения времени использовать текстовые строки, когда как раз на этот случай придумали unixtime.

2. Не разбирался с особенностями реализации PROGMEM, но по смыслу это должны быть статические констранты. У Вас нескаолько нестандартное описание массива myStrings для этого случая. Что в этом случае делает компилятор, я не знаю, но, судя по тому, что у Вас происходит перерасход оперативной памяти квалификатор PROGMEM попросту игнорируется. Опишите, как пололжено: с квалификатором const и вне пределов функции.

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Компилятор avr-gcc не умеет хранить массив строк во флеш напрямую. Это можно сделать способами, которые я указал ранее. При этом для сравнения строки во флеш и ОЗУ нужно использовать функцию strcmp_P, специально для этого предназначенную.

ardoinshik
Offline
Зарегистрирован: 14.11.2016

Спасибо всем за ответы.

Подскажите пож-та код, в котором в переменную можно занести дату и время хотя бы одного будильника с помощью unixtime?  А потом бы как то сравнить её с данными с модуля времени, который выдает в виде "01.12.2016" и "09:26:00"...

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Клапауций 234 пишет:

uni пишет:

Этого кода в результирующем файле не будет (на картинке я это показал, там 4 асм команды и call Serial.println() для каждой строки, где идёт обращение к Date[n]). 0x0820 - это вызов метода println().

На этапе компиляции любое обращение к объекту Dates через оператор индексирования развернётся в готовый указатель на область flash, где содержится строка. Ключевое слово inline означает включить тело функции operator[] в место вызова, а оптимизация сократит код функции до значения указателя, которое известно на момент компиляции. Поэтому не важно к какой строке мы обращаемся.

П.С. Т.е. я не совсем правльно пояснил, сам inline не сократит код, это сделает оптимизатор. inline лишь уберёт лишний вызов.

это как бы хорошо, НО! это же какое-то безобразие - я пишу код и желаю, что бы исполнялся switch, а не происходило обращение к индексу.

"Коль праведно то, чего он вожделеет, то то чего ты вожделеешь - грешно" О.Хаям

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Можно использовать такую промежуточную структуру для работы со временем компактно и понятно:

typedef union _MYDATETIME {
    
    struct 
    {
        uint32_t mday   : 5; // 0 - 31
        uint32_t mon    : 4; // 0 - 15
        uint32_t year   : 6; // 0 - 63
        uint32_t hour   : 5; // 0 - 31
        uint32_t min    : 6; // 0 - 63
        uint32_t sec    : 6; // 0 - 63       
    } fields;
    
    uint32_t time;
    
} * PMYDATETIME, MYDATETIME;

const MYDATETIME dt[] PROGMEM = 
{
// d - день месяца - [ 1 to 31 ]
// m - месяц, начиная с Января - [ 0 to 11 ]
// y - год, начиная с 2000

// H - часы - [ 0 to 23 ]
// M - минуты - [ 0 to 59 ]
// S - секунды - [ 0 to 59 ]

//               d   m   y   H   M   S 
    { fields: { 14, 10, 16, 00, 00, 00 } }, // 0
    { fields: { 14, 10, 16, 01, 00, 00 } }, // 1
    { fields: { 14, 10, 16, 02, 00, 00 } }, // 2
    { fields: { 14, 10, 16, 03, 00, 00 } }, // 3
    { fields: { 14, 10, 16, 04, 00, 00 } }, // 4
    { fields: { 14, 10, 16, 05, 00, 00 } }, // 5
    { fields: { 14, 10, 16, 06, 00, 00 } }, // 6
};

Использовать так, например:

    Serial.println( sizeof( MYDATETIME ) );
    
    Serial.println( dt[0].fields.year );
    Serial.println( dt[1].fields.hour );

Размер структуры равен 4 байтам, также как и для time_t, которая обычно 32-разрядная. Таким образом удобно заполнять массив вручную и просто преобразовать в srtuct tm и затем в time_t для сравнения со значениями от  rtc, которые наверняка тоже можно получить не в строковом формате. Либо вообще по полям делать сравнение.

ardoinshik
Offline
Зарегистрирован: 14.11.2016

Пытаюсь сделать перебор минут, используя ваш код:

int i; 
for (i=0;i<=6;i=i+1){
Serial.println( dt[i].fields.min );
}

На выходе получаю непонятные значения:

15
0
0
48
0
0
32
32
0
0
48
10
0
32
32
0
0
48
11
0
32
32
0

Этих значений даже нет в массиве.

Если же просто вот так сделать:

int i=1; 
Serial.println( dt[i].fields.min );

То получаю правильные минуты из массива.

(в ступоре)
 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

а я бы делал через int - хранение минут от предшествующей даты )))
Укладываемся в 1500 int-ов

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Я забыл, что методы print(...) реализованы только для переменных, расположенных в ОЗУ, кроме строк, указатели на которые специально указаны. Печатается билиберда, т.к. указатели берутся для флеш, а используются для ОЗУ. Тут всё немного сложнее получается.

Нужно копировать содержимое флеши в ОЗУ:

int n = sizeof( dt) / sizeof( MYDATETIME );

for ( int i = 0; i < n; i++ )
{   
    MYDATETIME tmp;
    
    memcpy_P( & tmp, & dt[i], sizeof( MYDATETIME ) );
    
    Serial.println( tmp.fields.hour );
}

 

ardoinshik
Offline
Зарегистрирован: 14.11.2016

Спасибо, так работает. Как теперь склеить значения tmp.fields.Х  чтобы получить char* вот такого вида "01.12.201611:30:20" ? Именно в таком виде модуль реального времени выдает дату и время, после маленькой склейки:

char* z1;
z1 = rtc.getDateStr();
char* z2;
z2 = rtc.getTimeStr();
//date+time склейка
char result[100];   
strcpy(result,z1); 
strcat(result,z2); 

Т.е. Serial.print(result) выдает 01.12.201615:45:20

Надо ведь ещё как-то ноль добавить к числам, которые меньше 10

Кстати, теперь стало понятно, почему PROGMEM игнорируется и память переполняется. Это происходит потому, что каждую строку массива нужно объявлять в отдельную строку, это такая фишка PROGMEM. На середине страницы объяснено и разжевано если кому-то интересно

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Думается мне, что кроме строковых методов там должны быть методы, которые возвращают дату и время в других форматах. Лучше использовать их, например, getHour() или getTime(). С их помощью было бы проще делать сравнение.

ardoinshik
Offline
Зарегистрирован: 14.11.2016

В используемой библиотеке нет других методов. надо попробовать другую библиотеку

ardoinshik
Offline
Зарегистрирован: 14.11.2016

посмотрел другие библиотеки, думаю что не смогу сам. uni, вы можете помочь за вознаграждение?(если в этой ветке форума не запрещено договариваться). через teamviewer например, могу написать вам на почту через часа эдак четыре

uni
uni аватар
Offline
Зарегистрирован: 24.09.2015

Мне сейчас некогда этим заниматься. Попросите кого-нить другого, тут найдутся, я думаю.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

ardoinshik пишет:

Спасибо всем за ответы.

Подскажите пож-та код, в котором в переменную можно занести дату и время хотя бы одного будильника с помощью unixtime?  А потом бы как то сравнить её с данными с модуля времени, который выдает в виде "01.12.2016" и "09:26:00"...

Смотрите в стандартных римерах Ардуино.

Еще, кажется, чуть ли не в любую библиотеку RTC входит функция преобразования.

svm
Offline
Зарегистрирован: 06.11.2016

Зачем использовать библиотеку, а потом преобразовывать ? Обращайтесь напрямую к регистрам RTC, это и упростит преобразования и уменьшит размер кода. Например так:

#include <Wire.h>
 #define DS3231_I2C_ADDRESS 0x68
  
 byte seconds, minutes, hours, day, date, month, year;
 char weekDay[4];  
 byte tMSB, tLSB;
 float temp3231;
  
 void setup()
 {
   Wire.begin();
   Serial.begin(9600);
   //set control register to output square wave on pin 3 at 1Hz
   Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
   Wire.write(0x0E); //выставляемся в 14й байт 
   Wire.write(B00000000); //сбрасываем контрольные регистры 
   Wire.write(B10001000); //выставляем 1 на статус OSF и En32kHz
   Wire.endTransmission();
 }
  
 void loop() {    
   watchConsole(); //читаем консоль. Если нажата "T" читаем из консоли следующий 7 байт и устанавливаем время.    
   get3231Date(); //получаем данные 

          //и потом их выводим через Serial.print()    
   Serial.print(weekDay); Serial.print(", "); Serial.print(month, DEC); Serial.print("/"); Serial.print(date, DEC); Serial.print("/"); Serial.print(year, DEC); Serial.print(" - ");
   Serial.print(hours, DEC); Serial.print(":"); Serial.print(minutes, DEC); Serial.print(":"); Serial.print(seconds, DEC);    
   Serial.print("   Temperature: "); Serial.print(get3231Temp());Serial.print("; ");
          Serial.println(get3231Register(0x0F)); //получаем и выводим статусовый регистр
   delay(1000);
 }
  
 // Convert normal decimal numbers to binary coded decimal
 byte decToBcd(byte val)
 {
   return ( (val/10*16) + (val%10) );
 }
  
 void watchConsole()
 {
   if (Serial.available()) {      // Look for char in serial queue and process if found
     if (Serial.read() == 84) {      //If command = "T" Set Date
       set3231Date();
       get3231Date();
       Serial.println(" ");
     }
   }
 }
    
 void set3231Date()
 {
 //T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
 //T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
 //Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
  
   seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result. 
   minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   day     = (byte) (Serial.read() - 48);
   date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));

   Wire.beginTransmission(DS3231_I2C_ADDRESS); //начали сессию
   Wire.write(0x00); //поставляемся в нулевой байт для чтения данных
   Wire.write(decToBcd(seconds));
   Wire.write(decToBcd(minutes));
   Wire.write(decToBcd(hours));
   Wire.write(decToBcd(day));
   Wire.write(decToBcd(date));
   Wire.write(decToBcd(month));
   Wire.write(decToBcd(year));
   Wire.endTransmission();//закрыли сессию
 }
  
 byte get3231Register(byte regNo) {
   // send request to receive data starting at register regNo
   Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
   Wire.write(regNo); // start at register regNo
   Wire.endTransmission();
   Wire.requestFrom(DS3231_I2C_ADDRESS, 1); // request one byte
   if(Wire.available()) return  Wire.read();
 }
  
 void set3231Register(byte regNo, byte value) {
   Wire.beginTransmission(DS3231_I2C_ADDRESS);
   Wire.write(regNo);
   Wire.write(value);
   Wire.endTransmission();
 }
  
 void get3231Date()
 {
   // send request to receive data starting at register 0
   Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
   Wire.write(0x00); // start at register 0
   Wire.endTransmission();
   Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes
  
   if(Wire.available()) {
     seconds = Wire.read(); // get seconds
     minutes = Wire.read(); // get minutes
     hours   = Wire.read();   // get hours
     day     = Wire.read();
     date    = Wire.read();
     month   = Wire.read(); //temp month
     year    = Wire.read();

//Конвертация происходит очень просто - битовым оператором & мы очищаем МЛАДШИЕ (0,1,2,3) четыре бита , сдвигаем все вправо на 4 позиции (очищенные биты
//вылезут при этом с левой стороны и результат умножаем на 10, т.к. в старших битах хранится информация о десятках секунд.
//После этого битовым оператором & очищаем СТАРШИЕ (4,5,6,7)четыре бита и получаем секунды. Затем суммируем десятки секунд и единиц.
     seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111));
     minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111));
     hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); 
     day     = (day & B00000111); // 1-7
     date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
     month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
     year    = (((year & B11110000)>>4)*10 + (year & B00001111));
   }
   else {
     //oh noes, no data!
   }
    
   switch (day) {     
     case 1:
       strcpy(weekDay, "Mon");
       break;
     case 2:
       strcpy(weekDay, "Tue");
       break;
     case 3:
       strcpy(weekDay, "Wed");
       break;
     case 4:
       strcpy(weekDay, "Thu");
       break;
     case 5:
       strcpy(weekDay, "Fri");
       break;
     case 6:
       strcpy(weekDay, "Sat");
       break;
            case 7:
       strcpy(weekDay, "Sun");
       break;
   }
 }
  
 float get3231Temp()
 {
   //temp registers (11h-12h) get updated automatically every 64s
   Wire.beginTransmission(DS3231_I2C_ADDRESS);
   Wire.write(0x11);
   Wire.endTransmission();
   Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
    
   if(Wire.available()) {
     tMSB = Wire.read(); //2's complement int portion
     tLSB = Wire.read(); //fraction portion      
     temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
     temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
   }
   else {
     //oh noes, no data!
   }
      
   return temp3231;
 }

В зависимости от точности будильника ,объем занимаемый им может колебаться от 1 до 3 байт. Информация о годе, месяце числе и дне недели абсолютно не нужна. Она может быть получена при просмотре массива будильником простым вычислением. Вот что остается для получения времени, если убрать все лишнее


#include <Wire.h>
byte minutes, hours;
void setup()
{
  //Serial.begin(9600);    // this is here so that we can print the result
} 
void loop()
{
  

Wire.beginTransmission(0x68); // обращаемся устройству 0x68 (RTC)
   Wire.write(0x01);             // устанавливаем начальный адрес
   Wire.endTransmission();       // конец передачи
   Wire.requestFrom((0x68), 2);  // запрашиваем 3 байт
  
     minutes = Wire.read();      // get minutes
     hours   = Wire.read();      // get hours
     
     minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111));
     hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); 
     
 
      //Serial.print(hours,DEC); Serial.print(":"); Serial.println(minutes,DEC);
 
  delay(1000);
}

 

ardoinshik
Offline
Зарегистрирован: 14.11.2016

нельзя математически просчитать времена срабатывания будильников, дан их список и в нем нет закономерностей. Сейчас попробую ваш первый код

svm
Offline
Зарегистрирован: 06.11.2016

ardoinshik пишет:

нельзя математически просчитать времена срабатывания будильников, дан их список и в нем нет закономерностей. 

Я имел в виду вычисление не времени срабатывания будильника, а даты. Её можно вычислить, для этого в начале группы будильников на конкретную дату установить флаг смены даты.

Клапауций 234
Offline
Зарегистрирован: 24.10.2016

ardoinshik пишет:

нельзя математически просчитать времена срабатывания будильников, дан их список и в нем нет закономерностей. Сейчас попробую ваш первый код

кем дан? пристрели этого упоротого дятла.