wiring, использование delayMicroseconds на частотах кварца, отличающихся от 8,16,20
- Войдите на сайт для отправки комментариев
 
Большинству из вас это не понадобится (раве что для общего развития), но, дабы не наступать на грабли повторно, я проанализирую эту ситуацию в разрезе функции 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 свой вариант задержки, для конкретной частоты кварца. Этот вариант более простой, возможно это решение будет выложено позже и на основании него можно будет реализовать задержку по аналогии для других частот кварца.
          
      
Решение http://www.nongnu.org/avr-libc/user-manual/group__util__delay.html
Я не даром написал про комментарии из исходника 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
Не знаю кто это там написал, сам тоже не проверял, других источников об этой проблеме не нашел и в описании самой библиотеки об этом ничего нет . Может это справедливо только при вызове _delay_us() из тела функции
delayMicroseconds(), но никто не мешает воспользоваться директивой #define./* 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); }Теперь не нужно калечить LiquidCrystal #15 для корректной работы на 12 мегагерц.
*проверил - всё работает.
Спасибо! А то пока не на чем проверить. ЛА подключать надо, по хорошему. Да прогнать по малым величинам.
Ребята, как решили проблему?...что-то я не вкурил)
У меня кварц внутренний т.е. на 8 МГц...проблема в дисплее - вылазят иногда кракозяблы, хотя до этого вроде не было проблем сним и все работало. Решит ли эту проблему ваше решение?
Для 8 не нужен патч и так должно работать, я фиксил для 12МГц.
Могу предположить, что на 8 никто не тестил. К сожалению мне проверить сейчас не на чем
на 8 тестировал, вроде правильно все
Да у меня тоже работало всё....видимо причина в другом. Буду искать
Как правило задержки виноваты, если проблемы при инициализации, иначе может и питание, хотя у меня проблем не было ни с атмега16, ни с стм32, на Ардуино не пробовал.