Точная задержка

Bayrn
Offline
Зарегистрирован: 14.06.2019

Доброго времени суток,

оговорюсь сразу я новичок и пришёл с твёрдым намереньем нервировать матёрую часть бывалых пользователей форума. ;)

Цель:

Настраиваемый 2x-канальный триггер сигнала с временой разницей триггера (задержкой) в районе (1-10 мкс, шаг 1 мкс).

К примеру: На пине D1 (по сигналу кнопки) состояние меняется с 0 на 1, далее в зависимости от выбранной задержки к примеру 2 мкс меняеся состояние с 0 на 1 на пине D2. Через 100-300 миллисекунд состояние на обоих пинах (D1, D2) возвращается снова в состояние 0.

Главная проблемма это задержка. По нижеуказанному скетчу принцип реализуется, но стабильно работает только с 5-6 мкс.

 void setup()
 {
 pinMode(13,INPUT_PULLUP);  // кнопка
 pinMode(12, OUTPUT);       // Trigger ch1
 pinMode(11, OUTPUT);       // Trigger ch2
}  
void loop() 
{
  if(digitalRead(13)==LOW)
{ 
  digitalWrite(12, LOW);
  delayMicroseconds(10);
  digitalWrite(11, LOW);
  delay (200);
  digitalWrite(12, HIGH);
  digitalWrite(11, HIGH);
}
}

Для начала использовал Arduino UNO, потом решил взять более шуструю платформу NodeMCU на базе ESP8266. Она отказалась работать по скетчу для ардуино, пришлось немного переписать но на команды delayMicroseconds() в области 1-50 мкс она отказывается реагировать. Минимальная задержка 5 мкс. 

 void setup()
 {
 pinMode(D3, INPUT);           // кнопка
 pinMode(D1, OUTPUT);          // Trigger ch1
 pinMode(D2, OUTPUT);          // Trigger ch2
}  
void loop() 
{
  if(digitalRead(D3)==LOW)
{ 
  digitalWrite(D1, LOW);
  delayMicroseconds(4);
  digitalWrite(D2, LOW);
}
else
{
  digitalWrite(D1, HIGH);
  digitalWrite(D2, HIGH);
}
}

Получается что проблемма в примитивном скетче с большими задержками а так же футкции "delay". Вероятно что micros() тоже не вариант из-за разрешения 4 мкс.

Каким способом можно реализовать стабильнуюб точную задержку на этих платформах в этом временном промежутке (1-10 мкс)? 

Заранее спасибо.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Bayrn пишет:
пришёл с твёрдым намереньем нервировать ...

Цель: Настраиваемый 2x-канальный триггер ....

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

Bayrn пишет:
Каким способом можно реализовать стабильную, точную задержку на этих платформах в этом временном промежутке (1-10 мкс)?

(я говорю только про ардуино)

Насколько точную? Шаг в 62,5 наносекунд (если считать кварц идеальным) устроит? Если да, то делается просто при помощи таймеров. Если нет - то никаким.

sadman41
Offline
Зарегистрирован: 19.10.2016

Можно запытать _delay_us(0.5);

Но от digitalWrite() желательно избавиться сразу, если речь зашла о таких интервалах.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

Можно запытать _delay_us(0.5);

Фига-се! А я и не знал, что она void _delay_us(double __us). Наверняка, видел когда-то, но прочно забыл :)

sadman41
Offline
Зарегистрирован: 19.10.2016

Да... но есть проблема - в последний раз, когда я пытался её использовать, она требовала inline указания числа, с переменной не прокатывает. Видимо как-то там, внутрях, разворачивается в NOP-ы при компиляции. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Так само-собой. Вот она

void
_delay_us(double __us)
{
	double __tmp ;
#if __HAS_DELAY_CYCLES && defined(__OPTIMIZE__) && \
  !defined(__DELAY_BACKWARD_COMPATIBLE__) &&	   \
  __STDC_HOSTED__
	uint32_t __ticks_dc;
	extern void __builtin_avr_delay_cycles(unsigned long);
	__tmp = ((F_CPU) / 1e6) * __us;

	#if defined(__DELAY_ROUND_DOWN__)
		__ticks_dc = (uint32_t)fabs(__tmp);

	#elif defined(__DELAY_ROUND_CLOSEST__)
		__ticks_dc = (uint32_t)(fabs(__tmp)+0.5);

	#else
		//round up by default
		__ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
	#endif

	__builtin_avr_delay_cycles(__ticks_dc);

#else
	uint8_t __ticks;
	double __tmp2 ;
	__tmp = ((F_CPU) / 3e6) * __us;
	__tmp2 = ((F_CPU) / 4e6) * __us;
	if (__tmp < 1.0)
		__ticks = 1;
	else if (__tmp2 > 65535)
	{
		_delay_ms(__us / 1000.0);
	}
	else if (__tmp > 255)
	{
		uint16_t __ticks=(uint16_t)__tmp2;
		_delay_loop_2(__ticks);
		return;
	}
	else
		__ticks = (uint8_t)__tmp;
	_delay_loop_1(__ticks);
#endif
}

 

Bayrn
Offline
Зарегистрирован: 14.06.2019

По поводу "нервировать..." это шутка)))  В основном новички на форумах хватают вершки, переспрашивают одно и тоже... мало чего понимают в тематике.... чем нервируют более опытных товарищей )))

Шаг в 62,5 нано было бы идеально....

Я был бы более чем доволен погрешностью в +/- 250 нано. )))

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, та в путь, всё пишется в три строки. Гуглите "работа с таймерами ардуино" или просто смотрите даташит (что лучше).

Только помните, что это низкоуровневые вещи, т.е. для разных контроллеров (например, для Uno и Mega) это пишется по-разному. Всё равно в три строчки, но по-разному.

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

sadman41 пишет:

Но от digitalWrite() желательно избавиться сразу, если речь зашла о таких интервалах.

Вот тут 10 раз согласен digitalWrite()- очень медленная функция с проверкой всего начиная от динозавров. Пишите прямо в порт.

Morroc
Offline
Зарегистрирован: 24.10.2016

Угу. Можно попробовать что то типа (с нумерацией мог накосячить)

#define PIN11_PORTB 3               // 11я нога ардуины
#define PIN12_PORTB 4               // 12я нога ардуины

...

      PORTB|= (1<<PIN12_PORTB);
      { 
        asm ("nop"); 
        asm ("nop"); 
        asm ("nop"); 
        ...
      }
      PORTB|= (1<<PIN11_PORTB);

Число nop подобрать. Если много получается - сунуть в цикл for.

Bayrn
Offline
Зарегистрирован: 14.06.2019

Morroc пишет:

Угу. Можно попробовать что то типа (с нумерацией мог накосячить)

#define PIN11_PORTB 3               // 11я нога ардуины
#define PIN12_PORTB 4               // 12я нога ардуины

...

      PORTB|= (1<<PIN12_PORTB);
      { 
        asm ("nop"); 
        asm ("nop"); 
        asm ("nop"); 
        ...
      }
      PORTB|= (1<<PIN11_PORTB);

Число nop подобрать. Если много получается - сунуть в цикл for.

Спасибо, большое за конкретный ответ, попробую и отпишусь что получилось.

Bayrn
Offline
Зарегистрирован: 14.06.2019

Доброго временни суток,

сново вернулся к этой теме. По поду работы с таимерами и управлнни портами через регисты проинформировался, но не попрактиковался ))) Как говорится на предложенных примерах всё понятно, а вот самому написать сложновато ))) Но об этом позже, когда будут конкретные вопросы.

Для начала решение от Morroc.

На вскидку прошил Ардуино предложенным скетчем:

#define PIN11_PORTB 3               // 11я нога ардуины
#define PIN12_PORTB 4               // 12я нога ардуины

void setup() 
{
  pinMode(13,INPUT_PULLUP);
}

void loop() 
{
  if(digitalRead(13)==HIGH)
{
    PORTB|= (1<<PIN12_PORTB);
      { 
        asm ("nop");
        asm ("nop");
        ...
      }
      PORTB|= (1<<PIN11_PORTB); 
}
}

работает, очень даже точно. Спасибо за простое решение. Но есть одна  несущественная мелоч. Изменение состояния происходит не мгновенно а логарифмически и занимает примерно 3-4 µs. Соединение к осцилографу ни каких поразитных емкостей не имеет.

Для сравнения, изменения состояния по скетчу который я вначале предложил, происходит мгновенно. При этом подключение остаётся тем же.

 {
 pinMode(13,INPUT_PULLUP);  // кнопка
 pinMode(12, OUTPUT);       // Trigger ch1
 pinMode(11, OUTPUT);       // Trigger ch2
}  
void loop() 
{
  if(digitalRead(13)==HIGH)
{ 
  digitalWrite(12, LOW);
  delayMicroseconds(1);
  digitalWrite(11, LOW);
  delay (200);
  digitalWrite(12, HIGH);
  digitalWrite(11, HIGH);
}
}

 

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Простите, т.е. Вы хотите сказать, что изменение состояния пина по команде

PORTB|= (1<<PIN12_PORTB); // КОМАНДА А

"происходит не мгновенно а логарифмически и занимает примерно 3-4 µs", в то время как изменение состояния пина по команде

digitalWrite(12, LOW); // КОМАНДА B

"происходит мгновенно"?

Простите, но это бред - этого не может быть потому, что этого не может быть никогда. Команда B, внтури кода всё равно исполняет исполняет команду A. Команда A  - это шататный метод изменения состояния пина - им пользуются все, в том числе и команда B.

Ищите ошибку в своих измерениях или их интерпретации.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

а порты на вывод настроить?
pinMode(11,OUTPUT) или DDRB = (1<<DDRB3)
pinMode(12,OUTPUT) или DDRB = (1<<DDRB4)

или так: DDRB = (1<<DDRB3)|(1<<DDRB4);

Bayrn
Offline
Зарегистрирован: 14.06.2019

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ищите ошибку. Например, то, о чём сказал коллега постом выше. Нет? Ищите ещё! Где-нибудь шнурок развязался контакт отпал, что угодно. Но так не бывает.

Bayrn
Offline
Зарегистрирован: 14.06.2019

Действиткльно причина была в этом. Нужно было задикларировать модус портов.

Спасибо большое! ;) 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Bayrn пишет:

Действиткльно причина была в этом. Нужно было задикларировать модус портов.

Спасибо большое! ;) 

ну так порты настроены на ввод, если не переключить на вывод, то запись 1 подключает внутренний резистор (аналогично команде pinMode(pin INPUT_PULLUP)), получается интегрирующая цепь, резистор, входная ёмкость осциллографа, оно передний фронт и затягивало ...
 )))

Abbat
Abbat аватар
Offline
Зарегистрирован: 23.08.2017

Вот выдрал из старого проекта точную задержку.

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

// Задержка в тактах CPU в диапазоне 15-65535.
inline void tunedDelay(uint16_t delay) {
  uint8_t tmp=0;
  asm volatile(
  "     sbiw R24, 15\n"
  "     mov %1, %A0 \n"
  "     andi %1, 0x03\n"
  "_cl_%=:\n"
  "     sbiw R24, 4\n"
  "     brsh _cl_%=\n"
  "		cpi %1, 1\n"
  "		breq _cl_1_%=\n"
  "		cpi %1, 0\n"
  "		breq _end_%=\n"
  "		cpi %1, 2\n"
  "		breq _end_%=\n"
  "		rjmp _end_%=\n"
  "_cl_1_%=:\n"
  "		nop\n"
  "		nop\n"
  "		nop\n"
  "_end_%=: "
  : "+r" (delay), "+a" (tmp)
  : "0" (delay)
  );
}