Проблема синхронизации OCR1A и прерывания ADC

Здравия всем!

Помогите советом, как решить проблему :

Делаю некий проект на Arduino Uno. Много кода и функционала. В виду этого, процесс контроля скорости вращения шагового двигателя принято было перенести на аппаратный таймер 1 и прерывание ADC.
Если писать функции и переносить их запуск в цикл loop, то это наглухо вешает остальную программу.
Задача состоит в том, что бы на Timer1 генерировать импульсы длительностью 3 микросекунды, а длительностью пауз между импульсами управлять потенциометром в пределах от 4 миллисекунды, до 50 микросекунд.
В приведенном участке кода, отвечающем только за работу шагового двигателя, при работе этого кода возникает одна проблема, а именно срывы импульсов при достижении коротких пауз между импульсами. Т.е когда потенциометр принимает значение , от 500 и выше, до 1023.
( см. приведенные снимки с лог. анализатора).
Я реально зашел в тупик, и не вижу выхода из этой ситуации.
Что только не применял, и атомарные блокировки, и усреднение, и даже задержки, но не удается получить синхронизации, как я предполагаю, работы таймера и прерывания ADC. Такое впечатление, что когда частота пауз увеличивается, то ADC требуется больше времени на преобразование, и в этот момент OCR1A, ожидающий непрерывного обновления значения , как бы простаивает, что вызывает срывы.

Подскажите направление для решения задачи.

const uint8_t pulStep1 = 9;           // Пин для задания скорости вращения Main Stepper
const uint8_t potPin = A0;            // Пин для подключения потенциометра


const int numSamples = 10;            // Количество сэмплов для усреднения
uint16_t potSamples[numSamples];      // Массив для хранения сэмплов
int potIndex = 0;                     // Индекс текущего сэмпла
uint16_t potAverage = 0;              // Переменная для хранения среднего значения потенциометра
uint16_t potValue = 0;                // Переменная для хранения текущего значения потенциометра




void setupTimer1() {
  cli(); // Отключение прерываний
  
  TCCR1A = 0;                         // Сброс регистров настройки таймера
  TCCR1B = 0;
  TCNT1 = 0;                          // Сброс счетчика таймера
  OCR1A = 0;                          // Задание значения регистра сравнения для нужной частоты прерывания
  TCCR1B |= (1 << WGM12);             // Настройка режима CTC (сброс по совпадению)
  TCCR1B |= (1 << CS10);              // Настройка предделителя таймера на деление на 1
  TIMSK1 |= (1 << OCIE1A);            // Разрешение прерывания по совпадению счетчика A
  
  sei(); // Включение прерываний

}

void setupADC() {

  ADCSRA = 0;                        // Сбрасываем регистр ADCSRA
  ADCSRB = 0;                        // Сбрасываем регистр ADCSRB
  ADMUX = (1 << REFS0) | (0 & 0x07); // Настройка регистра ADMUX для выбора опорного напряжения и мультиплексирования аналогового вход
  
  
          
  ADCSRA |= (1 << ADEN);             // Включаем АЦП
  ADCSRA |= (1 << ADSC);             // Запускаем преобразование
  ADCSRA |= (1 << ADATE);            // Непрерывный режим работы АЦП
  ADCSRA &= ~(1 << ADPS0) | ~(1 << ADPS1);   //  предделитель ADC - 16
  ADCSRA |= (1 << ADPS2);     
  ADCSRA |= (1 << ADIE);             // Разрешение прерывания ADC


}


void setup() {
  Serial.begin(115200);
  analogReference(DEFAULT);          // Установка опорного напряжения как внутренний источник (5 вольт)
  setupTimer1();                     // Настройка аппаратного таймера 1
  setupADC();                        // Настройка ADC прерывания
  pinMode(pulStep1, OUTPUT);
 
}


void loop() {
}


// Обработчик прерывания ADC
ISR(ADC_vect) {
  potValue = ADC;                     // Сохранение текущего значения потенциометра

  potSamples[potIndex] = potValue;    // Запись значения потенциометра в массив
  potIndex = (potIndex + 1) % numSamples;  // Обновление индекса сэмпла

  uint16_t potSum = 0;                // Переменная для хранения суммы значений сэмплов
  for (int i = 0; i < numSamples; i++) {
    potSum += potSamples[i];          // Суммирование значений сэмплов
  }

  potAverage = potSum / numSamples;   // Вычисление среднего значения потенциометра

  OCR1A = map(potAverage, 0, 1023, 65000, 600);  // Преобразование среднего значения и установка регистра сравнения
}


// Прерывание по совпадению счетчика A
ISR(TIMER1_COMPA_vect) {
  PORTB |= (1 << PB1);               // Включаем пин pulStep1 (9)

  for (int i = 0; i < 9; i++) {      // Пустой цикл на 9 повторений инструкции "nop"    
    __asm__ __volatile__ ("nop \n\t");
  }

  PORTB &= ~(1 << PB1);              // Выключаем пин pulStep1 (9)

  
}

Бери нормальное железо, которое тянет задачу.

Почему не хотите формировать длительности импульсов и пауз аппаратно ?
И длительность и период можно задать через OCR1A OCR1B

Uno не потянет?

А как это реализовать, если счетчик для Timer1 общий, как и для OCR1A, так и для OCR1B?
Счетчик для Timer1 считает тики до переполнения, а потом генерируется прерывание, которое переключает пин 9 в HIGH , удерживает 3 микросекунды ( цикл ассемблерной вставки), и сбрасывает в LOW.

Прочитайте даташит раздел про режимы PWM
OCR1A - может задавать макс значение счета, т.е. фактически период.

Вы никогда не интересовались скоростью работы ADC?

Он работает 13 своих тактов. При рекомендованном в даташите для этой частоты делителе 128, это соответствует 1664 тактов контроллера. А это при частоте 16МГц составляет 104 микросекунды.

Т.е. одно измерение длится 104 микросекунды. И о каких там 3-х или 50-ти Вы говорите?

Можете уменьшить делитель, но это понизит аккуратность измерений, не знаю насколько она Вам критична.

1 лайк

Я уменьшил делитель до 16. Это минимальный делитель при котором наименьшее число срывов. Аккуратность измерений не столь критична. Потенциометр выставляет требуемую скорость шаговика и в целом не изменяет ее , до окончания работы.
Я понимаю, что скорость работы ADC значительно ниже того же Timer1,2… и приоритет прерываний у него так же ниже, чем у таймера.
Но поскольку я только осваиваю Arduino, то возможно не знаю каких ни будь “фишек” , как можно синхронизировать работу прерываний от таймера и ADC.

Значит, длительность одного измерения у Вас 104/8 = 13 микросекунд. ВОт от этого и пляшите. Быстрее он Вам не измерит, какие бы Вы интервалы не задавали.

А вот этого я просто не понимаю, вы как-то непонятно объяснили. Что Вам нужно-то?

То, что Вам нужны какие-то интервалы, я понял, а причём тут ADC - нет.

Так он и может, т.е OCR1A, то , что мне нужно. Принимать значение до 65365, что будет соответствовать длительности пауз между импульсами ~ 5.1 миллисекунды, что мне и нужно при самой медленной работе двигателя.
И при этой задержке, кстати, срывов не возникает. Вплоть до задержки около 100 микросекунд. А вот потом начинаются срывы. При этом шаговик уже не может сходу стартовать на такой скорости и стопориться.
А если применить OCR1B, для формирования пауз, как Вы предлагаете, то поскольку таймер работает от одного счетчика, то изменение значения OCR1B будет влиять на прерывание OCR1A.
Я пробовал мудрить и импульсами и паузами на одном таймере. На выходе получалось что то невообразимое.

Возможно я не точно, или не верно выражаюсь, т.к. новичок в этом деле.
Но мне нужно, что бы аппаратным способом генерировался импульс , длительностью 3 микросекунды. Что я решил реализовать на прерывании по переполнению на Timer1 . А вот скорость следования этих импульсов, пауз между ними, регулировать при помощи потенциометра, влияя на значение OCR1A таймера.

Наверное я вас не понимаю.
Для формирования Импульса 3 мкс с периодом до 4 мс достаточно использовать режим PWM с изменяемой частотой. Тогда прерывания таймера не нужны, импульсы формируются автоматически, и “срывов” быть не может. В даташите достаточно подробно описано.

Мне нужно получить вот такую форму импульсов ( см. картинку).
Сам импульс имеет фиксированную длительность - 3 микросекунды.
А вот на частоту следования этих импульсов, в интервале от 5 миллисекунд до 50 микросекунд ( паузы), надо влиять при помощи потенциометра.
Режим PWM дает четкий меандр, где я могу влиять на скважность. Но мне это не подходит, т.к. импульс должен быть постоянной длительности.

Это делается при помощи PWM.

Опять же непонятно причём тут ADC, но если его надо запускать по импульсу, то ждя этого вообще ничего программировать не надо, т.к ADC умеет запускаться от любого из следующих собыи=тий:

  1. Timer/Counter0 Compare Match A
  2. Timer/Counter0 Overflow
  3. Timer/Counter1 Compare Match B
  4. Timer/Counter1 Overflow
  5. Timer/Counter1 Capture Event

Надо просто его настроить и он будет запускаться сам от таймера.

Сейчас я уже “сплю”. Завтра могу продолжить, но при непременном условии. Вы больше не говорите мне, что “уменьшили делитель”), а выкладываете короткий, но полный код, чтобы я мог его запустить и посмотреть на Вашу проблему. Если по ходу что-то меняется - опять же, свежий полный код “на бочку”. Без кода разговора не будет, т.к. у меня нет никаких оснований полагать, что, например, тот же делитель Вы установили правильно, а без этого я буду просто терять время.

Что именно Вам не подходит? Меняйте не только скважность, а одновременно период и скважность так, чтобы импульс всегда оставался 3мкс. В чём проблема?

В общем, сейчас я ухожу. Если хотите продолжать, своё условие я Вам уже написал. До завтра.

Принял ).
А код я привел в шапке темы.

А вот это мне трудно для понимания. Как это реализовать? На одном таймере?

Используйте 15 режим работы таймера и делитель 1.
В OCR1B ВСЕГДА прописывайте значение на 48 (3 мкс) меньше чем в OCR1A.
В OCR1A задаётся период следования импульсов (не длительность паузы в чистом виде) меняйте от 800 (50 мкс) до 64000 (4 мс) в зависимости от значения измеренного ADC.
Прерывания вообще не нужны.
Пример импульсов на 10 выводе:

void setup () {
    pinMode(10, OUTPUT);
    TCNT1 = 0;
    OCR1A = 800;
    OCR1B = OCR1A - 48;
    TCCR1A = (1 << COM1B1) | (1 << COM1B0) | (1 << WGM11) | (1 << WGM10);
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
}

void loop() {
    OCR1A = map(analogRead(0), 0, 1023, 800, 64000);
    OCR1B = OCR1A - 48;
}

2 лайка

А вы его считали? Есть datasheet там все расписано! 50 uS это в два раза меньше одного преобразования ацп примерно!