Не хватает ОЗУ, дилемма

petrovich
Offline
Зарегистрирован: 06.07.2016

Добрый день, уважаемые форумчане.

Ситуация следующая: к ардуине к цифровому входу подключен энкодер оптический, и в ардуине считывается значение, которое записывается в массив, чтобы в последующем обработать эти данные, т.е. вычислить скорость, расстояние и т.д. (либо на самой ардуине, либо передать на компьютер сырые данные и там обработать). Задача состоит в том, чтобы скорость выдавалась каждую 1 мс (желательно), ну или чуть реже (если это вообще возможно. Это нужно для анализа динамики различных движущихся частей, например, тросов). Кроме того погрешность должна быть меньше процента. Просто найти скорость не проблема, мне удавалось выводить 5 раз в секунду с нужной погрешностью, но там способ был в том, чтобы за одинаковые промежутки считать сколько пришло импульсов. Для больших скоростей это работает, однако для маленьких (0.1 м/с и ниже) уже нет, нужен способ с вычислением периода между двумя импульсами, и для максимальной скорости в 0.1 м/с нужно делать выборки где-то каждые 10 мкс, тогда погрешность не вылезет за процент.

Код программы ниже. Столкнулся с дилеммой: либо не хватает ОЗУ, чтобы записать хотя бы 10 минут измерений, либо портит малину Serial, время передачи одного символа которого фактически равно заполнению всей оперативки, что не позволяет перезаписывать массив, одновременно передавая данные (или я в чем-то ошибаюсь?).

Т.е. как мне видится тут либо надо использовать какой-то другой способ вывода информации из ардуины (может другой интерфейс подрубить, или на SD карту, или во флеш, или вообще внешнюю оперативку использовать), или сделать что-то по-другому в коде, или для такой задачи ардуина вообще не подходит.

Подскажите, что лучше сделать в данной ситуации, или, если я где-то затупил конкретно (что вероятнее всего =D), то прошу направить на путь истинный.
 

int i, j = 0;

int TicsArray[600];

void setup() {

  Serial.begin(9600);

  pinMode (5, INPUT);

  // timer 2
  TCCR2A = 1 << WGM21; //CTC mode
  TIMSK2 = 1 << OCIE2A; OCR2A =  159 ; //прерывание каждые 10 мкс
  TCCR2B = (0 << CS22) | (0 << CS21) | (1 << CS20); //без прескалера

}

ISR (TIMER2_COMPA_vect) {
  if (i <= 600) {
    TicsArray[i] = digitalRead(5);
    i++;
  }

}

void loop() {

  if (j <= 600) {
    Serial.println(TicsArray[j]);
    j++;
  }

}

 

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

petrovich, а не жирно ли под запись одного бита выделять ДВА БАЙТА? Как минимум можно в 16 раз !!! меньше памяти жрать если побитно заполнять массивы. А если данные с большой повторяемостью, (длинные цепочки нулей и единиц) то можно использовать "архивирование" - записовать кол-во единиц и нулей до смены лог. уровня.

Logik
Offline
Зарегистрирован: 05.08.2014

Изначально неверный подход привел в тупик. С енкодером работать по прерыванию по крайней мере при справедливости условия "для максимальной скорости в 0.1 м/с нужно делать выборки где-то каждые 10 мкс". В прерывании по состоянию таймера расчитывать длительности импульсов, а по ним - скорость. Масив не нужен вообще ни при каких скоростях.

Задумайтесь о поставленных требованиях. Скорость каждую милисекунду (да еще и до процента) реально нужна, она может менятся так быстро? 

petrovich
Offline
Зарегистрирован: 06.07.2016

dimax пишет:

petrovich, а не жирно ли под запись одного бита выделять ДВА БАЙТА? Как минимум можно в 16 раз !!! меньше памяти жрать если побитно заполнять массивы.

Вы имеете в виду вместо int использовать byte? Пробовал, это позволяло записать только 6.4 импульса, или 64 мкс (88% ОЗУ) на скорости 0.1 м/с.

А по поводу  архивирования: если я правильно понял вы предлагаете записывать сколько пришло единиц и это записывать в массив? Я тоже думал над этим, получается, что один байт это один импульс (предположим, что кол-во единиц и нулей одинаковое, будем писать только единицы). Тогда получится, что я смогу записать 16 мс для 88% ОЗУ на 0.1 м/с.

petrovich
Offline
Зарегистрирован: 06.07.2016

Logik пишет:

Изначально неверный подход привел в тупик. С енкодером работать по прерыванию по крайней мере при справедливости условия "для максимальной скорости в 0.1 м/с нужно делать выборки где-то каждые 10 мкс". В прерывании по состоянию таймера расчитывать длительности импульсов, а по ним - скорость. Масив не нужен вообще ни при каких скоростях.

Да, это тот самый подход. Допустим я буду рассчитывать скорость прямо в прерывании и это не будет влиять на погрешность. Возникает другая проблема, где хранить или как выводить это значение скорости? Сериал принт слишком медленный, а хранить можно только около 2000 байт, чего очень мало.

petrovich
Offline
Зарегистрирован: 06.07.2016

Logik пишет:

Задумайтесь о поставленных требованиях. Скорость каждую милисекунду (да еще и до процента) реально нужна, она может менятся так быстро? 

Такая поставлена задача, увы. Для анализа динамики разгона, торможения, а так же выявления всех плохих явлений в скорости на всём пути троса или чего бы то ни было нужна большая точность. Можно взять каждые 2 мс, к примеру, пусть 5 мс. Давайте возьмем 5 мс, уже какой-то результат будет.

Logik
Offline
Зарегистрирован: 05.08.2014

Дак скорость надо расчитать не по одному импульсу, а усреднить за некоторое кол-во и выводить сразу в сериал. Хороше напрямую через регистры.  На 115200 очевидно пару байт за милисек улетит.

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

petrovich пишет:

Вы имеете в виду вместо int использовать byte? Пробовал, это позволяло записать только 6.4 импульса, или 64 мкс (88% ОЗУ) на скорости 0.1 м/с.

Нет, используйте любой тип, но заполняйте его целиком. В тип "инт" влезает 16 бит. Почитайте про битовые операции, станет понятней.

petrovich пишет:

А по поводу  архивирования: если я правильно понял вы предлагаете записывать сколько пришло единиц и это записывать в массив? Я тоже думал над этим, получается, что один байт это один импульс (предположим, что кол-во единиц и нулей одинаковое, будем писать только единицы). Тогда получится, что я смогу записать 16 мс для 88% ОЗУ на 0.1 м/с.

Снова вы считаете как-то не так. Один байт это не один импульс.  Формат записи может быть разным, я бы сделал так: в переменной int один бит показывает что у нас цепочка  нолей или единиц, в остальных 15  битах записано количество повторов. Таким образом одна переменная типа "int" может "заархивировать" 32767 повторений. Если столько много не нужно, то можно взять "byte" там будет счёт на 127 повторений.  Скетч конечно существенно усложнится, но это того стоит.

petrovich
Offline
Зарегистрирован: 06.07.2016

Я, пожалуй, не совсем корректно выразился. На скорости 0.1 м/с период - 250 тиков, т.е. приходит в ардуино 125 единиц, и 125 нулей. И я в i-ый элемент массива байтов записываю, что TicsArray[i] = 125. Для другой скорости будет другое число. Т.е. и получается, что один byte это один импульс энкодера. Аналогично для int. Поэтому можно записать только 2000 байтов (т.е. вся ОЗУ).

В чем разница с вашим алгоритмом?

 

 

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

petrovich, уже не знаю как ещё понятней сказать. 125 единиц или нулей идущих подряд  по Вашему способу занимают 125 байт, по моему -1 байт.(а по первому методу уплотнения -15 байт )

petrovich
Offline
Зарегистрирован: 06.07.2016

Почему они занимают 125 байт, если размер переменной byte - 1 байт? Я просто инициализировал переменную byte числом 125.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

petrovich, вы про битовые операции что-то слышали, хотя бы мельком?

vosara
vosara аватар
Offline
Зарегистрирован: 08.02.2014

Может так

int enkoder = 1;
int skor = 1;
int skorTemp = 0;
int i = 0;
bool flTimar = 0;

//int TicsArray[600];

void setup() {

  Serial.begin(9600);
  attachInterrupt(0, blink, CHANGE); // привязываем 0-е прерывание к функции blink().

  // timer 2
  TCCR2A = 1 << WGM21; //CTC mode
  TIMSK2 = 1 << OCIE2A; OCR2A =  159 ; //прерывание каждые 10 мкс
  TCCR2B = (0 << CS22) | (0 << CS21) | (1 << CS20); //без прескалера

}

ISR (TIMER2_COMPA_vect) {
  flTimar = 1;
}

void blink()
{
  ++enkoder;  // Считаем Енкодер
}

void loop() {

  if (flTimar) {
    flTimar = 0;
    if (enkoder > 1) {  //Если было прерывание
      skor = 10 * i * enkoder; //Вычисляем скорость (Формула Ваша)
      i = 1;
      enkoder = 1;
    }
    else ++i; //Если небыло прерывания екодера, время замера увеличуем но нужен ограничитель

  }
  if (skorTemp != skor) { //если скорость изменилась
    Serial.println(skor);
    skorTemp = skor;
  }

}

 

petrovich
Offline
Зарегистрирован: 06.07.2016

Спасибо всем за помощь, буду разбираться.

Okmor
Okmor аватар
Offline
Зарегистрирован: 16.10.2015

Для таймера Т2, СТС не работает от слова "вооообще".

Счетчик надо обнулять самостоятельно. Целій день потратил на выяснение причины. Вот вам кусок работающего кода, где задествован таймер Т2 , счетчик и делитель.

int n = 0;
void setup(){
TCCR2A=0;
TCCR2B=(1<<CS22)|(1<<CS21)|(1<<CS20); 
    // (1<<WGM21)  Clear Timer on Compare Match (CTC) mode
    //  Для таймера Т2 не работает, приходится вручную обнулять. 
    //001 - CLK
    //010 - CLK/8
    //011 - CLK/32
    //100 - CLK/64
    //101 - CLK/128
    //110 - CLK/256
    //111 - CLK/1024
OCR2A=40; // Максимум 255
TIMSK2=(1<<OCIE2A);
}

ISR (TIMER2_COMPA_vect) 
{ 
  TCNT2 = 0; // Работает, если вручную обнулять
  if (++n>250) {digitalWrite(13, !digitalRead(13)); n = 0;} // Тут чегото делаем.
}

void loop() {;}

 

Logik
Offline
Зарегистрирован: 05.08.2014

По коду vosara

Ежели выводить только при изменении скорости, то необходимо добавлять временную метку. Иначе буфера передатчика и приемника сильно исказят происходящее. Приведенная реализация имеет еще одну проблему, незначительные изменения скорости будут приводиь к передаче очередного значения. Условие в стр.42 надо доработать так, чтоб отправка шла при изменении скорости свыше некоторого предела, заданного нужной точностю.

А вобще подход нормальный.

vosara
vosara аватар
Offline
Зарегистрирован: 08.02.2014

По таймеру не смотрел - просто скопировал!

 

Змечания Logik имеют резон

petrovich
Offline
Зарегистрирован: 06.07.2016

Товарищи, посмотрите, пожалуйста, правильно я настроил таймеры? А то еще плаваю во всех этих регистрах.. Таймер 0 должен делать прерывание каждую 1 мс, таймер 0 - каждые 10 мкс, всё это через CTC mode.

// таймер 0 НА 1 МС
  TCCR0B = (1 << WGM01) | (0 << WGM00); //CTC mode
  TIMSK0 = 1 << OCIE0A; //
  OCR0A = 249 ; //прерывание каждые 1 мс
  TCCR0B = (0 << CS02) | (1 << CS01) | (1 << CS00); //прескалер 64

  // таймер 1  НА 10 мкс
  TCCR1B = (1 << WGM12) | (0 << WGM11)| (0 << WGM10); //CTC mode
  TCCR1B = (0 << CS12) | (1 << CS11) | (0 << CS10); //прескалер 8
  TIMSK1 = 1 << OCIE1A; //
  OCR1A = 19 ; //прерывание каждые 10 мкc

 

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

petrovich, правильно. Только нулевой сдвиг бит лучше не пишите, оно нафик не нужно, и читабельность понижает.

petrovich
Offline
Зарегистрирован: 06.07.2016

Спасибо, dimax.

petrovich
Offline
Зарегистрирован: 06.07.2016

Какие-то странные результаты. На частоте 400 Гц (Т = 2.5 мс) выдает по 20 отсчетов, т.е. на порядок меньше. Ведь должно за период в 1 мс на таймере 1 набежать около 100, а получается в 10 раз меньше. В чем может быть проблема, подскажите?

volatile int i, j, key, LastTicsValue  = 0;

void setup() {

  Serial.begin(250000);
  attachInterrupt(1, blink, RISING);

  // таймер 1  НА 10 мкс
  TCCR1B = (1 << WGM12); //CTC mode
  TCCR1B = (0 << CS12) | (1 << CS11) | (0 << CS10); //прескалер 8
  TIMSK1 = 1 << OCIE1A; //
  OCR1A = 19 ; //прерывание каждые 10 мкc

  // таймер 0 НА 1 МС
  TCCR0B = (1 << WGM01); //CTC mode
  TIMSK0 = 1 << OCIE0A; //
  OCR0A = 249 ; //прерывание каждые 1 мс
  TCCR0B = (0 << CS02) | (1 << CS01) | (1 << CS00); //прескалер 64

}

ISR (TIMER1_COMPA_vect) {
  i++;
}

ISR (TIMER0_COMPA_vect) {
  if (j < 9) {
    j++;
  }
  else {
    Serial.println(LastTicsValue);
    j = 0;
  }
}

void blink() {
    LastTicsValue = i;
    i = 0; 
}

void loop() {

}

Алгоритм такой: по изменению значения с 0 на 1 функция blink измеряет период (т.е. кол-во отсчетов по 10 мкс). Каждые 10 мс таймер 0 выводит последнее значение периода.

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

petrovich, не очень всматривался в ваш код, просто сразу бросилось в глаза: Скажите, при каком условии j может быть >0 ?

petrovich
Offline
Зарегистрирован: 06.07.2016

dimax пишет:

petrovich, не очень всматривался в ваш код, просто сразу бросилось в глаза: Скажите, при каком условии j может быть >0 ?

Спасибо, что заметили, это я когда сообщение писал ошибся, там if(j<9) :) Вообще нужно на каждой итерации хранить значение для последующей обработки, но пока я оставил только инкрементирование. И вот не работает даже просто таймер. Я проверил осциллографом, оставил только timer1 на 20 мкс, так вот что интересно: он срабатывает только два раза за 1 мс. Я меняю например на 100 мкс, меняется только форма сигнала, но по прежнему два импульса на 1 мс.

volatile int i, j, key, LastTicsValue  = 0;

void setup() {

  Serial.begin(250000);
  pinMode (6, OUTPUT);
  
  // таймер 1 
  TCCR1B = (1 << WGM12); //CTC mode
  TCCR1B = (0 << CS12) | (1 << CS11) | (0 << CS10); //прескалер 8
  TIMSK1 = 1 << OCIE1A; //
  OCR1A = 39 ; //прерывание каждые 20 мкc

}

ISR (TIMER1_COMPA_vect) {
  digitalWrite(6, key = !key;);
}

void loop() {

}

Какая-то ерунда выходит...

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

petrovich, у вас тут как минимум два косяка -вы отдаёте регистр таймера TCCR1A на волю умолчаний ардуино. -Это не стоит делать никогда, нужно туда что-то записать. В данном случае 0.

и второй косяк -в 10й строке вы отменяете всё, что сделали в 9й стоке. Подучите бинарные операции,  без четкого понимания их вы будете застревать то там то сям.

petrovich
Offline
Зарегистрирован: 06.07.2016

В очередной раз вы меня выручаете, всё заработало:) Подучу обязательно:)