wiring, использование delayMicroseconds на частотах кварца, отличающихся от 8,16,20

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Большинству из вас это не понадобится (раве что для общего развития), но, дабы не наступать на грабли повторно, я проанализирую эту ситуацию в разрезе функции http://arduino.ru/Reference/DelayMicroseconds.
Необходимо сразу сказать, что для миллисекундных задержек (функция delay) используется функция micros(), поэтому на любых частотах она будет работать корректно.

Цель данной статьи - найти решение для микросекундных задержек на нестандартных частотах кварца.

Итак, delayMicroseconds. В комментария функции (см. файл wiring.c) написано, что использование стандартной функции delay_us из avrlib не дает желаемых задержек на 1-2мкс, потому реализована своя функция.

Данная функция реализована на обычном цикле (loop) задержки, который выполняется за 4 такта (назовем его ЦЗ). Уже можно напрячься, потому что длительность одного такта напрямую зависит от частоты кварца, бинго! А перед выполнением ЦЗ производится расчет кол-ва выполняемых циклов задержки. Дополнительно выполняется оптимизация для задержек равных 1-2мкс.

Для начала рассчитаем, за какое время производится выполнение 4 тактов при разных значениях частот кварца:

Частота (МГц) 8 12 16 20
1 такт 0,125 0,0833333333 0,0625 0,05
4 такта 0,5 0,3333333333 0,25 0,2

Главное, что нужно помнить, что в коде выделены (оптимизированы) три варианта:

>= 20 МГц,
>= 16MHz
прочие (подразумевается 8 MHz)

Выводы:

1. Вариант, тупой:

Чтобы понять, какую задержку нужно задавать на вход delayMicroseconds, необходимо:

1. Определить в какую область попадает частота нужного кварца. Например, для частоты 12МГц - это 8-16МГц.
2. Взять нижнюю частоту диапазона. Для 12МГц - это 8МГц.
3. Рассчитать относительное отклонение частоты. Для 12МГц это 12/8 = 1.5 раза.
4. При задании задержки, умножить её значение на полученную величину отклонения. Например, для 10мкс, необходимо задать 10 * 1.5 = 15. В этом случае мы получим задержку в 10мкс.

Практически, коэффициент можно задать так:

#if F_CPU >= 20000000L
#define F_DELAY_MULT (F_CPU/20000000L)
#elif F_CPU >= 16000000L
#define F_DELAY_MULT (F_CPU/16000000L)
#else
#define F_DELAY_MULT (F_CPU/8000000L)
#endif

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

2. Вариант, более эффективен и даст более точные задержки:

Чтобы снизить нелинейность, добавить в delayMicroseconds свой вариант задержки, для конкретной частоты кварца. Этот вариант более простой, возможно это решение будет выложено позже и на основании него можно будет реализовать задержку по аналогии для других частот кварца.

 

maksim
Offline
Зарегистрирован: 12.02.2012
kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Я не даром написал про комментарии из исходника wiring.c, что они не используют delay_us по причине того, что на малых задержках дает некорректные задержки. Цитирую: "В комментария функции (см. файл wiring.c) написано, что использование стандартной функции delay_us из avrlib не дает желаемых задержек на 1-2мкс, потому реализована своя функция."

В общем дело хозяйское, жедлающие могут вызвать delay_us.
Пока проверить не могу, но решение для 12МГц кварца достаточно простое, (добавлены строки 65-68):

/* Delay for the given number of microseconds.  Assumes a 8 or 16 MHz clock. */
void delayMicroseconds(unsigned int us)
{
	// calling avrlib's delay_us() function with low values (e.g. 1 or
	// 2 microseconds) gives delays longer than desired.
	//delay_us(us);
#if F_CPU >= 20000000L
	// for the 20 MHz clock on rare Arduino boards

	// for a one-microsecond delay, simply wait 2 cycle and return. The overhead
	// of the function call yields a delay of exactly a one microsecond.
	__asm__ __volatile__ (
		"nop" "\n\t"
		"nop"); //just waiting 2 cycle
	if (--us == 0)
		return;

	// the following loop takes a 1/5 of a microsecond (4 cycles)
	// per iteration, so execute it five times for each microsecond of
	// delay requested.
	us = (us<<2) + us; // x5 us

	// account for the time taken in the preceeding commands.
	us -= 2;

#elif F_CPU >= 16000000L
	// for the 16 MHz clock on most Arduino boards

	// for a one-microsecond delay, simply return.  the overhead
	// of the function call yields a delay of approximately 1 1/8 us.
	if (--us == 0)
		return;

	// the following loop takes a quarter of a microsecond (4 cycles)
	// per iteration, so execute it four times for each microsecond of
	// delay requested.
	us <<= 2;

	// account for the time taken in the preceeding commands.
	us -= 2;
#else
	// for the 8 MHz internal clock on the ATmega168

	// for a one- or two-microsecond delay, simply return.  the overhead of
	// the function calls takes more than two microseconds.  can't just
	// subtract two, since us is unsigned; we'd overflow.
	if (--us == 0)
		return;
	if (--us == 0)
		return;

	// the following loop takes half of a microsecond (4 cycles)
	// per iteration, so execute it twice for each microsecond of
	// delay requested.
	us <<= 1;
    
	// partially compensate for the time taken by the preceeding commands.
	// we can't subtract any more than this or we'd overflow w/ small delays.
	us--;
#endif

	// busy wait
	__asm__ __volatile__ (
		"1: sbiw %0,1" "\n\t" // 2 cycles
#if F_CPU == 12000000L
		"nop" "\n\t"
		"nop" "\n\t"
#endif
		"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
	);
}

В двух словах. Только для 12МГц ЦЗ увеличивается в полтора раза, с 4 циклов до 6 циклов, т.е. то же время 0.5мкс на ЦЗ, как и для 8МГц, т.е. всё должно работать нормально.

PS Исправлялся исходник ArduinoIDE v1.0.5

 

maksim
Offline
Зарегистрирован: 12.02.2012

Не знаю кто это там написал, сам тоже не проверял, других источников об этой проблеме не нашел и в описании самой библиотеки об этом ничего нет . Может это справедливо только при вызове _delay_us() из тела функции delayMicroseconds(), но никто не мешает воспользоваться директивой #define.

#define delayMicroseconds(us) _delay_us(us)

 

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012
/* Delay for the given number of microseconds.  Assumes a 8 or 16 MHz clock. */
void delayMicroseconds(unsigned int us)
{
	// calling avrlib's delay_us() function with low values (e.g. 1 or
	// 2 microseconds) gives delays longer than desired.
	//delay_us(us);

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

Вот исходник, если кому интересно (из тулчейна ArduinoIDE v1.0.5)

/**
   \ingroup util_delay

   Perform a delay of \c __us microseconds, using _delay_loop_1().

   The macro F_CPU is supposed to be defined to a
   constant defining the CPU clock frequency (in Hertz).

   The maximal possible delay is 768 us / F_CPU in MHz.

   If the user requests a delay greater than the maximal possible one,
   _delay_us() will automatically call _delay_ms() instead.  The user
   will not be informed about this case.
 */
void
_delay_us(double __us)
{
	uint8_t __ticks;
	double __tmp = ((F_CPU) / 3e6) * __us;
	if (__tmp < 1.0)
		__ticks = 1;
	else if (__tmp > 255)
	{
		_delay_ms(__us / 1000.0);
		return;
	}
	else
		__ticks = (uint8_t)__tmp;
	_delay_loop_1(__ticks);
}

 

Клапауций
Offline
Зарегистрирован: 10.02.2013

Теперь не нужно калечить LiquidCrystal #15 для корректной работы на 12 мегагерц.

*проверил - всё работает.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Спасибо! А то пока не на чем проверить. ЛА подключать надо, по хорошему. Да прогнать по малым величинам.

spa-sam
Offline
Зарегистрирован: 14.12.2012

Ребята, как решили проблему?...что-то я не вкурил) 

У меня кварц внутренний т.е. на 8 МГц...проблема в дисплее - вылазят иногда кракозяблы, хотя до этого вроде не было проблем сним и все работало. Решит ли эту проблему ваше решение?

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Для 8 не нужен патч и так должно работать, я фиксил для 12МГц.
Могу предположить, что на 8 никто не тестил. К сожалению мне проверить сейчас не на чем

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

на 8 тестировал, вроде правильно все

spa-sam
Offline
Зарегистрирован: 14.12.2012

Да у меня тоже работало всё....видимо причина в другом. Буду искать

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Как правило задержки виноваты, если проблемы при инициализации, иначе может и питание, хотя у меня проблем не было ни с атмега16, ни с стм32, на Ардуино не пробовал.