Сбор данных из прерывания таймера и хранение их в массиве

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

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

Я разместил код ниже. Я опубликовал код, показывающий, как я реализовал свой ISR без хранения данных в массиве. Мне было интересно, что кто-нибудь может помочь мне с этим (как хранить эти данные в массиве). Спасибо.

const int A0_pin = PC0;
const int A1_pin = PC1;

const uint16_t t1_load = 0;
const uint16_t t1_comp = 3173;

float count = 0;
float num = 0;

void setup() {
 
  //Set A0 and A1 pins as inputs
  DDRC &= ~(1 << A0_pin);
  DDRC &= ~(1 << A1_pin);

  //Reset Timer1 Control Reg A
  TCCR1A = 0;

  //Set CTC mode
  TCCR1B &= ~(1 << WGM13);
  TCCR1B |= (1 << WGM12);

  //Set to prescaler of 256
  TCCR1B |= (1 << CS12);
  TCCR1B &= ~(1 << CS11);
  TCCR1B &= ~(1 << CS10);

  //Reset Timer1 and set compare value
  TCNT1 = t1_load;
  OCR1A = t1_comp;

  //Enable Timer1 compare interrupt
  TIMSK1 = (1 << OCIE1A);

  //Enable global interrupt
  sei();
  
  Serial.begin(9600);
  Serial.println("CLEARDATA"); //clears up any data left from previous projects
  Serial.println("LABEL,TIME,TIMER,AVG,GAUGE0, GUAGE1"); //always write LABEL, so excel knows the next things will be the names of the columns 
  Serial.println("RESETTIMER"); //resets timer to 0
}

void loop() {
}

ISR(TIMER1_COMPA_vect) {
  TCNT1 = t1_load;
  float Gauge0 = analogRead(A0_pin); 
  float Gauge1 = analogRead(A1_pin); //44
        
  //conversion of read data, to voltage range (0-100 percentVolts)
  float gauge0 = Gauge0 * (100.0 / 1023.0);
  float gauge1 = Gauge1 * (100.0 / 1023.0);
  float add = gauge0 + gauge1;
  float AVG = ((gauge0 + gauge1) / 2); 

  Serial.print("DATA,TIME,TIMER,"); 
  Serial.print(add); 
  Serial.print(",");
  Serial.print(gauge0);
  Serial.print(",");
  Serial.println(gauge1);
}

В коде я хочу сохранить значения gauge1 и gauge0 по существу в массив.

, 👍2

Обсуждение

Сколько прерываний в секунду вы ожидаете? Использование ' Serial.print()` в процедуре прерывания приводит к значительному снижению производительности, особенно если вы установили скорость связи всего на 9600 бод., @PMF


3 ответа


Лучший ответ:

2

Хранение данных в массивах не является самоцелью. Вопрос в следующем: что вы хотите сделать с этими массивами? От этого зависит, как действовать дальше.

Если вы хотите , чтобы сбор данных (analogRead()) и обработка (вычисление и печать) происходили параллельно, то вам понадобятся массивы для буферизации данных между этими двумя процессами. Как упоминал Мишель Кейзерс в своем ответе, вам придется исследовать концепцию кольцевого буфера.

В ядре Arduino есть реализация кольцевого буфера, которую вы можете использовать в качестве вдохновения: при чтении данных из последовательного порта входящие байты принимаются ISR и хранятся в кольцевом буфере. В Затем метод Serial.read() может быть использован для извлечения этих байтов и обработки их вне контекста прерывания.

Другой вариант - чередовать фазу сбора и фазу обработки. Это может быть интересным подходом, если скорость сбора данных слишком высока для выполнения обработки в реальном времени. В этом случае вам не нужен кольцевой буфер. Вместо этого вы заставляете ISR заполнять массивы, и когда они заполняются, основная программа может взять на себя и начать обработку. Для координации этих заданий вам просто нужно логическое значение, чтобы отслеживать, выполняете ли вы фазу сбора данных или фазу обработки. Обратите внимание, что, поскольку доступ к логическому значению является атомарной операцией, вам не нужно отключать прерывания для этого.

Вот пример реализации этой стратегии. Переменная sampling_done используется для координации двух процессов:

const size_t sample_count = 256;

volatile int gauge0[sample_count];
volatile int gauge1[sample_count];
volatile bool sampling_done = false;

ISR(TIMER1_COMPA_vect) {
    if (sampling_done) return;  // ничего не делать

    static size_t sample_idx = 0;
    gauge0[sample_idx] = analogRead(A0);
    gauge1[sample_idx] = analogRead(A1);
    if (++sample_idx >= sample_count) {
        sample_idx = 0;  // подготовка к следующему раунду
        sampling_done = true;
    }
}

void process_samples(int Gauge0, int Gauge1) {
    // Все, что нужно сделать с образцами:
    // вычисление, печать...
}

void loop() {
    if (sampling_done) {
        for (size_t i = 0; i < sample_count; i++) {
            process_samples(gauge0[i], gauge1[i]);
        }
        sampling_done = false;  // начать следующий раунд выборки
    }
}

Теперь несколько случайных замечаний:

  • В setup() нет необходимости использовать функцию sei(): прерывания уже включены инициализацией ядра Arduino.

  • Не сбрасывайте TCNT1 в ISR: это происходит автоматически в режиме CTC. Если вы сделаете это в программном обеспечении, вы рискуете пропустить тики таймера.

  • Не храните показания в переменных float: они занимают вдвое больше памяти, чем int, и все операции с float невероятно медленны по сравнению с эквивалентными операциями с int.

  • Масштабный коэффициент для преобразования показаний в процентную шкалу равен 100.0/1024: максимально возможное аналоговое считывание (1023) находится на один шаг АЦП ниже опорного напряжения: см.

  • Вы должны удалить неиспользуемые переменные, как только они станут неиспользуемыми (count, num, AVG). Как только вы это сделаете, вы должны заметить, что вам вообще не нужны float.

,

Спасибо за предложения. Несколько побочных заметок: что вы подразумеваете под process_samples(gauge0[i], gauge1[i]);, @Dema Govalla

@DemaGovalla: Я имею в виду “делайте с образцами все, что хотите”. См. Измененный ответ., @Edgar Bonet


1

Вы можете создать два массива, например:

#define MAX_LENGTH 20

volatile float _gauge0[MAX_LENGTH];
volatile float _gauge1[MAX_LENGTH];

volatile int _filled = 0;

Значение _filled показывает, что _gauge0 заполняется от 0 до (исключая) _filled.

Поэтому при добавлении двух значений вы используете:

_gauge0[_filled] = some value;
_gauge1[_filled] = some_other_value;
_filled++;

После обработки вы сбрасываете _filled в 0, чтобы снова пополнить оба массива.

Поскольку Arduino имеет ограниченное количество SRAM, лучше всего использовать фиксированный размер массива (в данном случае 20). Вы должны сделать его летучим, поскольку он используется изнутри ISR.

Я не уверен, что вы хотите делать с этими данными. Если вы хотите собрать около 20 (или фиксированное количество значений), вы можете использовать вышеприведенное.

Если вам нужен непрерывный объем памяти и вы получаете последние x результатов, то лучше использовать так называемый кольцевой буфер (поищите его, об этом есть много статей).

Обратите внимание, что из-за ограниченного объема SRAM лучше всего не хранить вычисленные значения (например, среднее значение и сумму), если только вам не нужна самая высокая производительность. Это компромисс, который я не могу сделать, так как не знаю ваших требований.

Во время обработки вы используете функцию noInterrupts() до и прерывания после обработки данных, в противном случае прерывания будут продолжаться во время обработки. Убедитесь, что обработка данных занимает как можно меньше времени, так как вы можете пропустить прерывания. Предпочтительно делать только вычисления и включать прерывания, например, перед отправкой результатов через последовательный порт.

,

Скорее всего, вам придется читать "_filled" при обработке данных, поэтому они тоже должны быть " volatile`., @Edgar Bonet

@EdgarBonet Хорошая мысль, я обновил свой ответ., @Michel Keijzers


1

Мой дахиноскоп https://www.daqarta.com/dw_rroo.htm использует скетч DaqPort с открытым исходным кодом https://www.daqarta.com/dw_rraa.htm чтобы сделать пробу партиями, а затем передать данные хозяину в одном высокоскоростном взрыве. Он принимает 1024 полных образца, которые в вашем случае 2 каналов были бы 512 в канал. Он хранит только необработанные данные, не масштабированные... хозяин-это место для этого. Хост обрабатывает и отображает результаты в режиме реального времени. Поскольку при этом используется UNO только с 2K оперативной ПАМЯТЬЮ, требовалось сложное хранилище массивов, чтобы вместить 1024 образца. В 8-битном режиме это всего лишь 1024-байтовый массив, но для 10-битного он использует дополнительный массив из 256 байт. На каждой выборке младший байт хранится в 1024-байтовом массиве, а верхние 2 бита сдвигаются в 4-байтовый байт 256-байтового массива. Не стесняйтесь скачивать и использовать Daqarta для этого... бесплатно для использования Arduino. Возможно, вы захотите сделать это просто для решения других проблем, даже если позже планируете использовать свое собственное программное обеспечение хоста. Или вы можете использовать макросы Daqarta, чтобы делать все, что вы в конечном счете хотите.

,