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

// Timer Fast PWM pulse

#define KEY_START         2
#define PIN_START         9             //OC1A
#define PIN_STOP          10            //OC1B

#define start_pulse()     PINB = 1<<(PIN_START - 8), PINB = 1<<(PIN_START - 8) 
#define get_key(x)        !digitalRead(x)

#define start_timer()     TCCR1B = 1<<WGM12 | 1<<WGM13 | 1<<CS10
#define stop_timer()      TCCR1B = 1<<WGM12 | 1<<WGM13 | 0<<CS10


void setup() {
  Serial.begin(9600);
  Serial.println("Timer Fast PWM pulse " __DATE__ " " __TIME__);
  
  pinMode(KEY_START, INPUT_PULLUP);

  pinMode(PIN_START, OUTPUT);
  pinMode(PIN_STOP, OUTPUT);
  digitalWrite(PIN_START, HIGH);
  digitalWrite(PIN_STOP, HIGH);

  cli();
  TCCR1A = 0; 
  TCCR1B = 0; 

  OCR1A = 55;
  OCR1B = 54;
  //set to fast PWM mode 15, TOP = OCR1A
  //clear OC1B on Compare Match, set OC1B at BOTTOM (inverting mode)
  TCCR1A = 1<<COM1B1 | 0<<COM1B0 | 1<<WGM10 | 1<<WGM11;
  stop_timer();
  TIMSK1 = 1<<TOIE1;
  TIFR1  = 1<<TOV1;
  sei();
  start_timer();
}


void loop() {
  if (get_key(KEY_START)) {
    TCNT1 = 0;
    start_pulse();
    start_timer();
    delay(500); 
  }
}


ISR(TIMER1_OVF_vect) {
  stop_timer();
}

1 лайк

во, наверно это “оно”, то чего я не нашел

Знаю по СТМ32, что при старте таймера надо флаг прерывания сразу ресетить, чтобы все регистры верно инициализировались… но что-то не нашел этого в АВР.

Спасибо, вечером в железе гляну.

Задача чисто теоретическая или практическая?
Если практическая, по поставить инвертор и дело с концом.

1 лайк

проще поставить пустой стакан … это же классика

Дело в том, что OCR1A обновляется не сразу, а по прерыванию, и поэтому вы используете предыдущее значение OCR1A. При инициализации вы его задаете = 54, оно и используется в первом цикле не зависимо от того, что вы запишите потом в OCR1A. А после первого цикла OCR1A обновляется и становится равным 50, как вы и ожидаете.
В окончательной версии расширьте границы запрета прерываний до конца setup. И добавьте запрет в функции myTest()

1 лайк

Большое спасибо! Надеюсь это был последний не решенный вопрос :slight_smile: . Завтра надеюсь потестирую полностью все в сборке и отпишусь, работает ли все как нужно.
То есть мне надо будет остановить таймер > выключить прерывания > задать новое значение для OCR1A > включить опять прерывания?

Про прерывания я писал не применительно к OCR1A, а просто потому, что и в существующем алгоритме их лучше там запретить. Как в данном алгоритме принудительно обновить OCR1A я пока не думал.

@Evgeniy в чём смысл применения таймера в этой задаче, что бы что?

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

для “познавательности” хотелось бы найти вариант решения только на таймерах. Пока все что я вижу в ветке - у всех один пин переключается через порт и только второй через таймер.

У меня просто не тот уровень, что бы делать это на ассемблере. Его я не знаю. Ну разве что команду nop задействовал.
А таймер как то понятнее и проще и то закопался в деталях :slight_smile:

Любое прерывание, с моей точки зрения, это зло в одно процессорной / одно ядерной системе, так что ТС проще выучить stm32 для своей задачи.

Вроде получилось сделать только на compare match выходах, без генерации импульса вручную в прерывании.

Задержка между сигналами стабильна и равна точно периоду таймера , длительность каждого импульса 1 тик. В момент запуска таймера в setup() проскаивают несколько паразитных импульсов, однако далее импульсы генерятся в loop() только по условию (flag == true) строго парами. Порядок импульсов по каналам в каждой паре меняется, AB - BA -AB -BA

// interval between falling edge 1st pulse and 
// raising edge 2nd pulse (in timer ticks)
#define AB_INTERVAL 1000

volatile uint8_t cc =0;
void setup() {
  
  DDRB |= (1<<PB1) | (1<<PB2);
  PORTB |= (1<<PB1) | (1<<PB2);
  
  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 to fast PWM mode 14 via WGM bits(count from 0 to ICR1):
  ////TOP = ICR1 
  TCCR1A |= (0 << WGM10) | (1 << WGM11) ;
  TCCR1B |= (1 << WGM12) | (1 << WGM13) ;

  ////Timer/Counter1 Interrupt Mask Register:
  TIMSK1 |= (1 << OCIE1A); // enable compare OCR1A itrerrupt 
  TIMSK1 |= (1 << OCIE1B); // enable compare OCR1B itrerrupt 
  //TIMSK1 |= (1 << TOIE1); //enable overflow vector interrupt
  

  OCR1A = AB_INTERVAL - 1; //match 1 tick before TOP 
  ICR1 = AB_INTERVAL;      //top value
  OCR1B = 0xFFFE;          // never match value
  
  //Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at
  // BOTTOM (non-inverting PWM mode)
  TCCR1A |=  (1 << COM1A1) | (1 << COM1B1);
  // reset all irq flags
  TIFR1  = B111; // (1<<TOV1)| (1<< OCFB) |(1<< OCFA);;
  sei();
  TCCR1B|= 1;  //start timer at 16 MHz, no prescaling
 
}

ISR(TIMER1_COMPB_vect) {
 OCR1A = AB_INTERVAL -1;        // match 1 tick before TOP
 OCR1B = 0xFFFE;                // never match value
 if (cc==0) TCCR1B &= ~bit(0);  // stop the timer
 cc = !cc;
}

ISR(TIMER1_COMPA_vect) {
 OCR1A = 0xFFFE;                // never match value
 OCR1B = AB_INTERVAL -1;        // match 1 tick before TOP
 if (cc==0) TCCR1B &= ~bit(0);  // stop the timer
 cc = !cc;
}

void loop() {
static bool flag = true;
if (flag) 
 {
   delay(100);
   TCCR1B|= 1; //start timer at 16 MHz, no prescaling
   flag = false;
 }
}
1 лайк

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

А ICR1 вроде как сразу обновляется сразу при записи. Буду с ним пробовать

Попробуйте код для обновления OCR1A в вашем случае. Не симуляторе работает нормально, но симуляторы в этом случае могут быть не точными.

void myTest() {
	//TCNT1 = 0; //reset clock value
	cli();
	// update OCR1A
	TCCR1B &= ~bit(0);		// stop timer						
	TCNT1 = OCR1A;				// reset TCNT1 on next clock
	TCCR1B |= B1;					// start timer
	asm volatile("nop\n\t");							//
	asm volatile("nop\n\t");
	TCCR1B &= ~bit(0);		// stop timer
	TIFR1 = bit(TOV1);		// clear int flag TOV1
	TCNT1 = 0; //reset clock value
	
	//посылаем СТАРТ (D9)
	PORTB = B101; //8 H; 9 L
	PORTB = B111; //8 H; 9 H 2clocks

	TCCR1B |= B1;  //T1 ON; running at 16 MHz no prescaling; //1 clock
	sei();
}
1 лайк

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

Стоповый подготовительный импульс выглядит странно. При OCR1A-1 лучше.

Попробую с icr1

Да, предыдущей мой совет был неправильным.

Возможно ли код привести в:
A > задержка > В

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

не вполне понимаю.
А сейчас код какой, не А - задержка - В ?

Оно чередуется a>b>a итд
А надо что бы нажали a>b, нажали a>b итд
И предварительно выбрав задержку перед пуском. С wgm15 и OCR1A придется давать “настроечный” стоповый сигнал что бы поменять задержку, если вдруг придется давать отличную от предустановленной задержки, что не очень желательно. Вот и цепляюсь за этот режим