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

Он говорит, что уменьшил делитель ADC в 8 раз.

1 лайк

Наконец, до меня дошло, причём здесь ADC - он Вам нужен для того самого потенциометра. Так?

Тогда это делается по-другому. Смотрите.

  1. отдельно настраиваете ADC в режим free running, чтобы он самостоятельно постоянно измерял;
  2. также при помощи бита ADLAR делаете, чтобы можно было читать только один байт результата (при таком делителе второй Вам всё равно не нужен);
  3. в прерывании ADC тупо копируете этот байт (результат) измерения в волатильную переменную. Таким образом в этой переменной у Вас всегда будет свежее, самое последнее измерение;
  4. всё, про ADC забудьте, больше с ним ничего не делаем.

Далее,

  1. настраиваете PWM как показали коллеги выше;
  2. устанавливаете таймера прерывание по переполнению и в каждом прерывании перенастраиваете PWM (как объяснял @Komandir ).

Вот Вы и получили то, что хотели, причём, заодно, перенастройка у Вас происходит в самом начале периода, т.е. Вы ею не сбиваете текущий период и не ломаете сигнал.

Если аккуратно всё сделать, то будет работать, никуда не денется.

P.S. И, да, кстати, можете оставить в покое делитель. Пуская себе 104 мкс измеряет, он при этом никак не будет мешать таймеру и вообще никому.

2 лайка

Благодарю за предоставленный пример кода.
При изучении ДШ по контроллеру, я никак не мог понять как работать одновременно с OCR1A и OCR1B в режиме на совпадение /сравнение.
Я еще более досконально изучу мат часть в ДШ. За месяц знакомства с Arduino не все нюансы получается усвоить в полном объеме.

Да, именно так.
У меня с самого начала включилась “паранойя” на медленную работу функций Arduino, типа analogRead(), и понесло прямиком в регистры ADC.
Я реализую на днях (сдача внешнего проекта по работе) ваши рекомендации в коде и протестирую результат.

Благодарю всех участников за помощь!

З.Ы. “Переехал” на Arduino MEGA 1280.
В Uno не хватает периферии, в частности 16 разрядных таймеров. Поскольку проект активно использует Timer1 для некоторых библиотек, а шаговым двигателям (2шт) требуется аппаратное асинхронное управление, поскольку требуется исключительная точность работы, то принято решение перенести проект на более расширенную платформу.

Не знаю, насколько аккуратно получилось, но вроде работает )

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

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





void setupTimer1() {
  cli(); // Отключение прерываний
  
  TCCR1A = 0;                         
  TCCR1B = 0;
  TCNT1 = 0;                          
  OCR1A = 0;                        
  OCR1B = 0;
  TCCR1A = (1 << COM1B1) | (1 << COM1B0) | (1 << WGM11) | (1 << WGM10);
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);

  
  sei(); // Включение прерываний

}

void setupADC() {

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


}


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


void loop() {
}


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

  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, 255, 64000, 800 );  // Преобразование среднего значения и установка регистра сравнения
  OCR1B = OCR1A - 48;
}

И даже в основной программе траблов не делает. Все работает ровно.

Благодарю за подсказки!

Отдельная благодарность Komandir, за пример кода работы с таймером и его настройки.

Potsamples для чего 16 битный, если вы храните там только adcH ??? Ну и что первые 9 значений, по вашему коду в прерывании, кривые выставляются вы наверное в курсе ??? До полного заполнения массива надо делить только на число сохраненных данных !!!

По мне, так Ваше “усреднение” нахрен не нужно. Я бы поставил на потенциометр два 104 конденсатора (от центральной ноги к крайним), а в прерывании просто присваивал бы переменной значение ADCH - больше там ничего не нужно.

Не могу сказать, что прям в курсе ), но чем обусловлена “кривость” этих первых 9 значений? Если я объявляю массив potSamples[numSamples] как uint8_t ( что верно. Не исправил переменную из раннего кода), то диапазон значений будет от 0 до 255. И если не было получено никаких значений от потенциометра до момента прерывания ADC, то первые 9 значений в массиве будут = 0 ?

Беда …

В целом согласен с вами. Лучше решить потенциальный “дребезг” потенциометра обычным фильтром, чем програмно. На момент решения проблемы со срывами импульсов, применял разные способы, вплоть до усреднения, выборки, разбиения на сегменты,… пока Вы не подсказали решение. Но часть кода осталась от предыдущих изысканий, и поскольку под рукой не было паяльника, то решил оставить усреднение, как “антидребезг”.
Но лучше действительно запаяю конденсаторы. Только не многовато ли 0.1uF на центральную ногу? Там вполне достаточно будет конденсатора в пределах 470pF.

Хех,…признаю… К программистам себе не отношу пока. Скорее к погромистам.

Один (первый) заход пройдите в отладке …

Чем больше, тем чище сигнал, но медленнее реакция на поворот. По мне так для ручного потенциометра скорости реакции, которую дают 104 ( и даже 204) вполне достаточно. А там - Вам виднее.

В массиве первые 9 значений инициализируются нулевыми значениями, пока он не заполнится. А при вычислении среднего значения потенциометра получаются не корректные значения.

Можно использовать флаг для указания, что массив еще не был полностью заполнен.
Типа :

volatile uint8_t potValue = 0;        // Переменная для хранения текущего значения потенциометра
// Переменные для усреднения значений потенциоометра ( антидребезг)
const int numSamples = 10;            // Количество сэмплов для усреднения
uint8_t potSamples[numSamples];      // Массив для хранения сэмплов
int potIndex = 0;                     // Индекс текущего сэмпла
uint16_t potAverage = 0;              // Переменная для хранения среднего значения потенциометра
bool isSamplesFull = false;           // Флаг полного заполнения массива

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

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

  if (!isSamplesFull && potIndex == 0) {
    isSamplesFull = true;                         // Установка флага, когда массив полностью заполнен
  }

  uint16_t potSum = 0;                           // Переменная для хранения суммы значений сэмплов
  int numSamplesUsed = isSamplesFull ? numSamples : potIndex;  // Использование фактического количества сохраненных данных
  for (int i = 0; i < numSamplesUsed; i++) {
    potSum += potSamples[i];                     // Суммирование значений сэмплов
  }

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

  OCR1A = map(potAverage, 0, 255, 64000, 800 );  // Преобразование среднего значения и установка регистра сравнения
  OCR1B = OCR1A - 48; 
}

Усреднение можно организовать проще:

static uint16_t x = analogRead(A0);

// усреднение по четырем последним замерам на аналоговом входе A0
x = (x * 3 + analogRead(A0)) / 4;

Согласен. Действительно проще.
До сих пор поражаюсь , как в Сцы можно выстрелить себе в ногу 100500 различными способами ))

Но в самом проекте пожалуй вообще откажусь от программного “антидребезга”. Потенциометр тупо меняет скорость шаговика, для которого точность совершенно не критична. Ограничусь конденсатором на средней ноге, и выкину кусок кода на усреднение.