Точная задержка между двумя короткими импульсами

Доброго времени суток.
Пытаюсь которую неделю реализовать следующее при помощи таймера1:
(Пытался сделать на обработки прерывания по совпадению, а после на fast pwm. Уже думаю, что невозможно сделать на ардуино и нужно esp32 так как там частоты выше.)

1)Выставляю на D9 и D10 высокий уровень.
2)С D9 посылаю через PORTB низкий уровень, потом опять высокий. (Длина отрицателтного импульса получается около 120нс)
3) Потом жду нужное количество тактов процессора (от 3 до 35 микросекунд где то)
4) Подаю с D10 так же отрицательный импульс длительностью 2 такта процессора.

Основная загвоздка в точности задержки. Так как у меня с частотой в 1гмгц прямо впритык и важен каждый цикл процессора.
Пытался реализовать на прерывании по совпадению. В прерывании ISR(TIMER1_COMPA_vect) останавливал таймер, а так же управлял D10.
Но при использовании прерывания по совпадению точность варьируется в 2-3такта процессора. А иногда больше. То есть например задаю переменную , по которой будет отработка прерывания равной 500. Прерывание сработает на 500. Задаю 501, прерывание все равно на 500. Задаю 502, прерывание сработало на 502.
Пример кода:

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>

//LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line displa

bool flagBtn = false; //high or low for btnStart
int OCR1AmyValue = 30; //time setting value
int currentStep = 1; //current step
uint32_t btnTimer = 0;


void setup() {
  Serial.begin(9600);
  //lcd.init();                      // initialize the lcd
  //lcd.backlight();

  pinMode(8, OUTPUT); //RESET
  pinMode(9, OUTPUT); //START
  pinMode(10, OUTPUT); //STOP

  //High
  digitalWrite(8, HIGH);
  digitalWrite(9, HIGH);
  digitalWrite(10, HIGH);

cli();                      //stop interrupts for till we make the settings
  TCCR1A = 0;                 // Reset entire TCCR1A to 0
  TCCR1B = 0;                 // Reset entire TCCR1B to 0
  TIMSK1 |= (1 << OCIE1A); //Set OCIE1A to 1 so we enable compare to match OCR1A
  TCNT1 = 0; //reset clock value
  sei();                     //Enable back the interrupts

  prepareMethod(); //initialize variables and show on display
}

void loop() {
  for (int i = 1; i < 500; i++) {
    prepareMethod();
    startBtnPressed();
    showInfoSerial();
    OCR1AmyValue++;
    delay(100);
  }
  delay(1500000);
}

void showInfoSerial() { //SHOW ALL THE INFORMATION ON THE LCD
  Serial.print("My/RC:");
  Serial.print(OCR1AmyValue + 25);
  Serial.print(" ");
  Serial.println(TCNT1); //

}

void prepareMethod() {
//  lcd.clear();
//  lcd.setCursor(0, 0); //Column, Row
//  lcd.print("#");
//  lcd.print(currentStep);
//
//  lcd.print("ET:");
//  lcd.print((OCR1AmyValue + 25) * 0.0625);
//
//  //number of clocks i set
//  lcd.print("MC:");
//  lcd.print(OCR1AmyValue + 25); //20 interrupt, 4 H>L>H, 1 stop timer

  //OCR1A = OCR1AmyValue;             //Finally we set compare register A to this value
  OCR1A = OCR1AmyValue;
  TCNT1  = 0;                  //First, set the timer back to 0 so it resets for next interrupt
}

//SEND SIGNALS
void startBtnPressed() {
  //Reset L
  PORTB = B110; //8 L 687,5ns lenght
  //9nop x 0.0625us = 0.5625uS +2 ticks for PORTB
  __asm__ __volatile__ (
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
    " nop\n"
  );
  //Reset H
  PORTB = B111; //8 L

  //delay ~20uS before start
  delayMicroseconds(20);

  //START 9 H>L 4 clocks
  PORTB = B101; //8 L; 9 L
  PORTB = B111; //8 L; 9 H

  TCCR1B = B1;  //T1 ON; running at 16 MHz //1такт
}

//COMP A TIMER
ISR(TIMER1_COMPA_vect) { //~20 clock takes interrupt

  //STOP 10 H > L
  PORTB = B011; //8 H; 9 H; 10 L 2 clocks
  PORTB = B111; //8 H; 9 H 10 H 2 clocks
  TCCR1B = 0;    //Timer-1 OFF 1 clock
}

Второй вариант это мне посоветовали режим Fast pwm.
При COM1A1 =1; COM1A0 = 1 (Set OC1A/OC1B on compare match, clear OC1A/OC1B at BOTTOM (inverting mode))
И OCR1B = 1; OCR1A = 1000; если не останавливать генерацию, то имеется одиночный отрицательный импульс длиной 2 такта процессора, все как и должно работать. Но если пытаться сразу остановить его, что бы получить одиночный импульс то ничего не работает.

Так же пробовал с не инвертированным режимом:
COM1A1 =1 and COM1A0 = 0 (Clear OC1A/OC1B on compare match, set OC1A/OC1B at BOTTOM (non-inverting mode)).
При
OCR1B = 999; //compare match
OCR1A = 1000; //top value
Длина импульса почему то 3uS.
А что бы получить 120нс надо выставить OCR1B =1022. Ничего не понимаю…

void setup() {
  pinMode(9, OUTPUT); //START
  pinMode(10, OUTPUT); //STOP

  digitalWrite(9, HIGH);
  digitalWrite(10, HIGH);

  cli(); //stop interrupts for till we make the settings

  TCCR1A = 0; // Reset entire TCCR1A to 0
  TCCR1B = 0; // Reset entire TCCR1B to 0
  TCNT1 = 0; //reset clock value

  //Set OC1B on compare match, clear OC1A/OC1B at BOTTOM (inverting mode)
  TCCR1A |= (1 << COM1B1); //
TCCR1A |= (1 << COM1B0); //

  ////set to fast PWM mode 15 via WGM bits(count from 0 to OCR1A):
  ////TOP = OCR1A (output compare register timer1 channelA)
  TCCR1A |= (1 << WGM10) | (1 << WGM11) ;
  TCCR1B |= (1 << WGM12) | (1 << WGM13) ;

  ////Timer/Counter1 Interrupt Mask Register:
  ////bit OCIE1B - (COMPARE B)The corresponding interrupt vector is executed when the OCF1B flag, located in TIFR1 register, is set
  ////bit OCIE1A - (COMPARE A)The corresponding interrupt vector is executed when the OCF1A flag, located in TIFR1 register, is set
  ////bit TOIE1  - (OVERFLOW) The corresponding interrupt vector is executed when the TOV1  flag, located in TIFR1 register, is set;
  //TIMSK1 |= (1 << OCIE1A); //Set OCIE1A to 1 so we enable compare counter to match OCR1A and call vector - ISR(TIMER1_COMPA_vect) {//something}
  //TIMSK1 |= (1 << OCIE1B); //Set OCIE1B to 1 so we enable compare counter to match OCR1A and call vector - ISR(TIMER1_COMPB_vect) {//something}
  //TIMSK1 |= (1 << TOIE1); //Set TOIE1 to 1 so we call overflow vector when overflow happens - ISR(TIMER1_OVF_vect) {//something}
  sei();
////The extreme values for the OCR1x register represents special cases when generating a PWM waveform output in the fast
  ////PWM mode. If the OCR1x is set equal to BOTTOM (0x0000) the output will be a narrow spike for each TOP+1 timer clock
  //////cycle. Setting the OCR1x equal to TOP will result in a constant high or low output (depending on the polarity of the output set
  ////by the COM1x1:0 bits.)

  OCR1B = 1; //compare match
  OCR1A = 1000; //top value
  TCCR1B = B1;  //T1 ON; running at 16 MHz no prescaling; //1 clock
}
void loop() {
TCCR1B = B0; // stop
  delay(100000);
}
ISR(TIMER1_OVF_vect) { //OVERFLOW VECTOR
  //TCCR1B &= ~_BV(CS10); //turn off timer1 29 clocks
  TCCR1B = B0; //turn off timer1 (CS10 = 0) 18 clocks
}

а что такое B1 и B0 ? Это макросы такие?

Тут та же проблема, что я вам писал в начале вашего прошлого обсуждения. Прерывание не срабатывает точно, а у вас даже один такт просрочки уже дает паразитный импульс. Поэтому таймер надо останавливать заранее. Например по прерыванию по совпадению по тому же каналу В, что управляет вашим импульсом.
Не помню, можно ли одновременно настроить режим PWM и прерывание по совпадению на одном таймере. Если нельзя - значит делаете прерывание по другому таймеру, чтобы он выключил таймер1 после импульса.

ЕСП32 не подходящий для этого проц, там нет точных таймеров вообще.
А вот на СТМ32 это сделать можно в разы легче, чем на АВР.

1 лайк

ТС, я видимо что то пропустил.
Как давно регистры стали 16-битными ?

OCR1A=1000; //WTF ???

Кмк, нормально все, Таймер1 16 битный и его регистры сравнения тоже.

Каюсь, затупил

0b1 и 0b0 по вашему.)

Я завтра на свежую голову почитаю, пока что ошибка есть это должно быть
TCCR1B |= B1; а не просто =
Это при wgm 15

а просто 1 и 0 нельзя написать? чтоб себя и других не путать? :slight_smile:

Конечно нельзя. Потому что нужно хотя бы: OCR1B = 1<<CS10;)

Возможно это связано с “The 16-bit comparator continuously compares TCNT1 with the output compare register (OCR1x). If TCNT equals OCR1x the comparator signals a match.
**A match will set the output compare flag (OCF1x) at the next timer clock cycle. **
If enabled (OCIE1x = 1), the output compare flag generates an output compare interrupt”

Т.е. после Match выставляется OCF1x, но уже на следующем клоке. И только затем, раз OCF1x установлен, МК дёргает функцию.

Не ожидал увидеть здесь знакомый ник :slight_smile:

Не удобно напрягать, но можно пример, а то перечитываю 10ый раз и не понимаю как это все реализовать и до конца смысл всего этого.

Не хочется уходить с ардуино, так как вроде бы уже почти все готово, осталось чуть чуть и все ни как.
Заказал esp32 на всякий случай, должны уже скоро прийти. Я рассчитывал на то, что частота таймера как я понял 80мгц, а следовательно ошибки в тактах будут не столь критичны из-за меньшего интервала. Мне уж сверх точность не нужна. Просто с ардуиновскими 16мгц приходиться цепляется за каждый такт. Не знаю пока, можно ли на esp32 как то задействовать в задержке между стартовым и стоповым сигналом, а так же в задании длительности сигналов все 240мгц.

Касательно stm, как я сейчас понимаю, с ними надо будет дольше разбираться, порог вхождения выше. Я голову сломал пока с Ардуино разбирался :slight_smile:
Но если есть совет, какие платкис Алли заказать под это дело, на подобии ардуиновских/есп что бы поставить драйвер и воткнуть в usb и можно работать буду благодарен.

Как мне писали, это скорей связано с неточностью работы таймера. Я в коде закладывал ~20тактов на обработку прерывания и прочии поправки. Но если закинуть тестовый, первый код и прогнать, то в основное значение разнится с итоговым на котором сработало прерывание в ±2такта. Но необъяснимо для меня иногда и гораздо больше. На OCR1A равное 150-170 там улетело сильно почему-то. Сплошные загадки, пока детально во все не вникнуть :wink:


И TCCR1B = B1; это косяк, надо было конечно же TCCR1B |= bit(0); или bitSet(TCCR1B, 0); или TCCR1B |= 0b00000001; … столько вариантов нагуглил. Может из за этого не получалось с Fast pwm. Дома нет прибора, на котором можно было бы посмотреть выход, так что только на работе.
Еще раз спасибо всем кто откликается :slight_smile:

На STM не будет затрат на прерывания. Таймеры сами всё посчитают. Блю пил дешевле наны. Порог входа в таймеры не выше ардуиновского таймера, а возможностей куда больше. На ESP так не получится. Там таймеры ущербные.
Если не лезть в железо, то на уровне только ардуновских команд в программах замена наны на СТМовской блю пил проходит вообще без затруднений.

2 лайка

Спасибо. Заказал, пускай пока идут. Вдруг в итоге это останется единственный рабочий вариант.

Вопрос - два импульса обязательно должны быть на разных пинах или можно на одном и том же последовательно?

Короче сделал. На PWM в итоге ничего не вышло, а на прерываниях - пожалуйста.
Два отрицательных импульса каждый длиной 1 такт (62 нс) с настраиваемым расстоянием между ними.

================
Update - нет, рано радовался. Еще потестировал - точность плавающая, как у ТС, два цикла. Выставляешь задержку 1000 циклов - получается 1002, выставляешь 999 - на выходе 1000, выставляешь 998 - все равно 1000.
Как будто квантуется кратно двум циклам.

( на всякий случай - картинку смотрю на ЛА с разрешением 40 МГц, так что дело не в дискретизации)

Первый сигнал с одного пина второй сигнал с другого.
2такта длина низкого уровня на каждом из пинов.

Может другие прерывания отработают точнее, переполнение?

Возможно уже говорилось, но я не нашел.
Зачем таймер или прерывания для задержек, чем не устроило изначальное
“3) Потом жду нужное количество тактов процессора (от 3 до 35 микросекунд где то)”
Не через микрос, а именно “нужное количество тактов процессора”

Пока не представляю как это все реализовать. Я разными способами пытался, но какой код в итоге получится после компиляции в ассемблер и сколько будет занимать задержка в итоге непредсказуемо.

Удалось вроде сделать стабильное увеличение задержки по 1такту, не тестировал еще на всем диапазоне. Но самое первое нажатие на кнопку приводит к первому кривому сигналу, эдакая инициализация. А последующие нажатия кнопки отрабатывает корректно.
Я так понимаю это возможно связано с тем, что пин находится в первоначальном состоянии LOW и может какие то еще инициализации? Как это обойти? Пока нет времени полностью протестировать финальный код с такой реализацией задержки, но предварительно вроде бы работает…
(таймер 2 для тестов, он не участвует)

#define btnStart 16                      // Кнопка   A2 - СТАРТ

#include <Wire.h>

bool flagBtn = false; //high or low for btn
uint32_t btnTimer = 0;
int OCR1AmyValue; //a value in cpu clocks when D10(0C1B) going LOW
int currentStep = 1; //текущий шаг в проверке
int correctionTime = 0; //поправка от -4 до +4, каждая единица будет равна 0.0625us
int correctionTicks = 0; //поправка тиков 0тк HW от таймера
int correctValue = 0; //поправка тестовая по шагам


void setup() {
  Serial.begin(9600);
  pinMode(btnDecr, INPUT_PULLUP); //btn pin
  pinMode(btnStart, INPUT_PULLUP); //btn pinMode

  pinMode(9, OUTPUT); //START
  pinMode(10, OUTPUT); //STOP

  digitalWrite(9, HIGH);
  digitalWrite(10, HIGH);

  ////FAST PWM SET UP:
  cli(); //stop interrupts for till we make the settings

  /////TIMER2
  TCCR2A = 0; // Reset entire TCCR1A to 0
  TCCR2B = 0; // Reset entire TCCR1B to 0
  TCNT2 = 0; //reset clock value
  //wgm mode 0

  TIMSK2 |= (1 << OCIE2A);  //compare match to OCR2A and throw a vector
  /////TIMER2


  TCCR1A = 0; // Reset entire TCCR1A to 0
  TCCR1B = 0; // Reset entire TCCR1B to 0
  TCNT1 = 0; //reset clock value

  //Set OC1B on compare match, clear OC1A/OC1B atBOTTOM (inverting mode)
  //TCCR1A |= (1 << COM1B1); // 11 = set on compare, clear at bottom; OCR1B =1; OCR1A = 1000;
  //TCCR1A |= (1 << COM1B0); // 10 = clear on compare, set at bottom; OCR1B =1022; OCR1A = 1000;
  //01 - toggle;

  ////set to fast PWM mode 15 via WGM bits(count from 0 to OCR1A):
  ////TOP = OCR1A (output compare register timer1 channelA)
  TCCR1A |= (1 << WGM10) | (1 << WGM11) ;
  TCCR1B |= (1 << WGM12) | (1 << WGM13) ;


  ////Timer/Counter1 Interrupt Mask Register:
  //TIMSK1 |= (1 << OCIE1A); //Set OCIE1A to 1 so we enable compare counter to match OCR1A and call vector - ISR(TIMER1_COMPA_vect) {//something}
  //TIMSK1 |= (1 << OCIE1B); //Set OCIE1B to 1 so we enable compare counter to match OCR1A and call vector - ISR(TIMER1_COMPB_vect) {//something}
  TIMSK1 |= (1 << TOIE1); //Set TOIE1 to 1 so we call overflow vector when overflow happens - ISR(TIMER1_OVF_vect) {//something}
  sei();                     //Enable back the interrupts

  ////The extreme values for the OCR1x register represents special cases when generating a PWM waveform output in the fast
  ////PWM mode. If the OCR1x is set equal to BOTTOM (0x0000) the output will be a narrow spike for each TOP+1 timer clock
  //////cycle. Setting the OCR1x equal to TOP will result in a constant high or low output (depending on the polarity of the output set
  ////by the COM1x1:0 bits.)

  OCR1B = 1;
  OCR1A = 54;
  //TIMER2
  OCR2A = OCR1B; //set compare match for timer2


  //01 - TOOGLE B
  //TCCR1A = B00010011; //7-COM1A1; 6-COM1A0; 5-COM1B1; 4-COM1B0; 3-;2-;1-WGM11;0-WGM10

  //11 - SET ON COMPARE; OCR1B =1; OCR1A = 1000;
  TCCR1A = B00110011; //7-COM1A1; 6-COM1A0; 5-COM1B1; 4-COM1B0; 3-;2-;1-WGM11;0-WGM10

  //10 - CLEAR ON COMPARE, SET AT BOTTOM; OCR1B = 1022; OCR1A = 1000
  //TCCR1A = B00100011; //7-COM1A1; 6-COM1A0; 5-COM1B1; 4-COM1B0; 3-;2-;1-WGM11;0-WGM10

}

void loop() {
  //START pressed
  if (!digitalRead(btnStart) && !flagBtn && millis() - btnTimer > 150) { //if btwn is pressed with debouncing
    flagBtn = true; //btn pressed flag
    btnTimer = millis();
    setValues();
    myTest();
    //currentStep++;
    //if (currentStep > 10) //reset steps
    //currentStep = 1;
  }
  //START RELEASE
  if (digitalRead(btnStart) && flagBtn && millis() - btnTimer > 150) { //release with debouncing
    flagBtn = false; //btn released flag
    btnTimer = millis();
  }
}

void myTest() { //ПОСЫЛАЕМ СТАРТОВЫЙ СИГНАЛ+ ЗАПУСК ТАЙМЕРА
  TCNT1 = 0; //reset clock value
  PORTB = B101; //8 L; 9 L
  PORTB = B111; //8 L; 9 H 2clocks

  TCCR1B |= B1;  //T1 ON; running at 16 MHz no prescaling; //1 clock
}

void setValues() {
  switch (currentStep) {
    case 1: // 3.4uS
      OCR1AmyValue = 54 - 1 - correctionTicks + correctionTime; //3.4 / 510
      break;
  }


  OCR1A =  OCR1AmyValue + correctValue;
  correctValue++;
}

ISR(TIMER1_OVF_vect) { //OVERFLOW VECTOR
  //TCCR1B &= ~_BV(CS10); //turn off timer1 29 clocks
  TCCR1B &= ~bit(0); //turn off timer1 (CS10 = 0) 18 clocks
}
ISR(TIMER1_COMPA_vect) {
  TCCR1B &= ~bit(0); //turn off timer1 (CS10 = 0)
}
ISR(TIMER1_COMPB_vect) {
  TCCR1B &= ~bit(0);//turn off timer1 (CS10 = 0)
}


//TIMER2
ISR(TIMER2_COMPA_vect) {
  TCCR2B &= ~bit(0); //turn off timer1 (CS10 = 0)
}

Функцию писать на ассемблере, считать такты.

Так и написано, но точность получается +/- 1 такт:
1000 => 1002
999 => 1000
998 => 1000

А через PWM все портит то, что ТС нужен “отрицательный импульс”. То есть до и после импульса сигнал на пине должен быть стабильно HIGH без перерывов, а сам импульс LOW.
Играясь с таймерами - обнаружил интересную особенность, описания которой в даташите не нашел. В момент перевода таймера в режим управления пином путем записи битов COMnA(B)1 COMnA(B)0 :

TCCR1A |= (1 << COM1B1) | (1 << COM1B0); 

пин принудительно переводится в LOW. Причем всегда именно в LOW, независимо ни от режима ШИМ (прямой или инвертированый), ни от соотношения текущих значений TCNT1 и OCR1A…
То есть в любом случае по пину проскакивает отрицательный импульс, что является недопустим по условиям задачи ТС.

Если кто хорошо знает таймеры АВР - прокомментируйте пожалуйста.