Частотомер на STM32F10x

How it all began…

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

Дано:
  • Cигнал прямоугольной формы
  • Меандр
  • Приведен к логическим уровням микроконтроллера
  • Выходная частота не более 6МГц
  • Погрешность измерений 1Гц


Последний пункт был прописан в ТЗ. Заказчик, как выяснилось позднее, ляпнул его туда с мыслью “лучше – не хуже, авось сделают и так”. Об этом я узнал позднее, а до того момента пришлось откинуть разные варианты реализации, погрешность которых выходила больше – например с делителями входного сигнала.
Сходу ничего готового не нашлось, хотя я был уверен по дороге домой, что на запрос “Частотомер STM32” Google поделится со мною массой готовых устройств – мне лишь останется только залить прошивку и отзвонить заказчику о работоспособном макете.
Не тут то было. Я не нашел практически ничего полезного, но благодаря этому чуть лучше разобрался в работе таймеров STM32, и теперь могу поделится этим с другими!


  • Макет сделан на самой простой демо-плате STM32VL-DISCOVERY.
  • Контроллер работает на частоте 24МГц, резонатор на 8МГц.


A little bit about timers…

Немножко нужных для понимания картинок из Reference manual, описывающих работу блока таймеров. Словами — читайте описание ниже, или же комментарии к тексту программы, которые даны на каждое действие с регистрами настройки.




How does it work...


  1. TIM3 генерирует прерывания раз в секунду. Так же TIM3 настроен как мастер для TIM1. Стартуя, TIM3 генерирует сигнал включения счёта (CNT_EN) для TIM1, который длится весь период работы TIM3, и спадает, когда в прерывании вручную сбрасывается TIM3_INTERRUPT_FLAG. Таким образом, TIM3 управляет промежутком времени, в который остальные таймеры ведут подсчёт входных импульсов.
  2. После сигнала разрешения работы от TIM3, TIM1 считает импульсы на внешнем входе TIM1_ETR. Событие переполнения TIM1 служит триггером для TIM4, инкрементирующим значение своего счетного регистра, в то время, как переполнившийся TIM1 начинает счет заново, до следующего переполнения. Таким образом, TIM1 работает как предделитель для TIM4, c коэффициентом 0xFFFF.
  3. В промежутках работы TIM3 нужно подсчитать количество импульсов на входе TIM1_ETR по формуле:
  4. Freq = (TIM4->CNT) * 0xFFFF) + (TIM1->CNT)


Если сразу же в прерывании перезапустить TIM3, частота выдачи измерений составит 1Hz.
Также же я не очень понял, обязательно ли заполнять регистр ARR у TIM1 значением 0xFFFF, но работает и так и так, а разбираться глубже мне уже лень.

Код:
//Настройка блока таймеров
void EXT_FrequencyCounterConfig(void)
{
	RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN;//TIM3, TIM4
	RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;//TIM1

	//------------------------------------------------------------------------------------------
	//TIM3 master counter, period = 1sec.
	//------------------------------------------------------------------------------------------
	TIM3->PSC	= 2400 - 1; //new clock = 10kHz
	TIM3->ARR	= 10000 - 1; //period = 1sec
	TIM3->CR1	|= TIM_CR1_DIR; //used as downcounter
	TIM3->CR1	|= TIM_CR1_OPM; //counter stops counting at the next update event
	TIM3->CR2	|= TIM_CR2_MMS_0; //COUNTER_ENABLE signal to TIM1, used as trigger output (TRGO)
	TIM3->DIER	|= TIM_DIER_UIE; //enable interrupt

	//------------------------------------------------------------------------------------------
	//TIM1 slave counter, TIM1_ETR pin is input
	//------------------------------------------------------------------------------------------
	TIM1->PSC	= 0;
	//TIM1->ARR	= 0xFFFF; //counter max value
	TIM1->CR1	&= ~TIM_CR1_DIR; //used as upcounter
	TIM1->CR1	&= ~TIM_CR1_OPM; //is not stopped at update event
	TIM1->CR2	|= TIM_CR2_MMS_1; //update event is selected as trigger output to TIM4
	TIM1->SMCR	|= TIM_SMCR_ECE; //ext. clock mode2 enabled, counter is clocked by ETRF signal
	TIM1->SMCR	&= ~TIM_SMCR_ETPS; //no external trigger prescaller
	TIM1->SMCR	&= ~TIM_SMCR_ETF; //no external trigger filter
	TIM1->SMCR	|= TIM_SMCR_TS_1; //internal trigger_2 (ITR2) from TIM3
	TIM1->SMCR	|= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2; //while trigger input (TRGI) is high, counter is on

	//------------------------------------------------------------------------------------------
	//TIM4 additional counter
	//------------------------------------------------------------------------------------------
	TIM4->PSC	= 0;
	//TIM4->ARR	= 0xFFFF; //counter max value
	TIM4->CR1	&= ~TIM_CR1_DIR; //used as upcounter
	TIM4->SMCR	&= ~TIM_SMCR_ETPS; //no external trigger prescaller
	TIM4->SMCR	&= ~TIM_SMCR_ETF; //no external trigger filter
	TIM4->SMCR	|= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2; //TIM1 (TRGI) clock the counter
}


//Запуск работы таймеров
void EXT_FrequencyCounterRun(void)
{
	NVIC_EnableIRQ(TIM3_IRQn);

	TIM4->CR1	|= TIM_CR1_CEN;
	TIM1->CR1	|= TIM_CR1_CEN;
	TIM3->CR1	|= TIM_CR1_CEN;
}


//Обработка прерывания TIM3
void TIM3_IRQHandler(void)
{
	TIM3->SR &= ~TIM_SR_UIF; //reset interrupt flag

	RawFreq = ((u32)(TIM4->CNT) << 16) | ((u32)(TIM1->CNT));
	DataReady = SET;
	TIM1->CNT = 0;
	TIM4->CNT = 0;

	TIM3->CR1 |= TIM_CR1_CEN; //another circle
}


//Основной код
void RCC_Config(void);
void EXT_FrequencyCounterConfig(void);
void EXT_FrequencyCounterRun(void);

u32 TrueFreq;
volatile u32 RawFreq;
volatile FlagStatus DataReady = RESET;

int main(void)
{
        RCC_Config();
	EXT_FrequencyCounterConfig();
	EXT_FrequencyCounterRun();

	while(1)
	{
		if (DataReady == SET)
		{
			DataReady = RESET;
			TrueFreq  = RawFreq;
		}
	}
}


Start test...

Выдадим на ножку MCO сигнал внешнего кварца(RCC_MCO_HSE), и посмотрим, что из этого получится:





Частотомер меряет сигналы, которые по частоте не больше, чем 24МГц / 2. Почему — объяснил OlegG в комментариях к похожему изделию:

Насколько я помню, таймеры в STM синхронные. Это означает, что даже если сделать тактирование от внешнего источника, все равно переключения будут происходить по сигналу внутреннего тактирования. То есть частота тактирования контроллера должна быть как минимум в 2 раза выше измеряемой частоты.

Поэтому даже выведенный на ножку RCC_MCO_PLLCLK_Div2(12МГц) измерился с точностью до герца.

Попробуем подключить внешний генератор прямоугольника(Tektronix AFG3022B) для окончательной проверки работы устройства. И вот тут первые грабли. Генератор настроен на 1МГц. Частотомер не досчитал 69Гц.





Bug fixes...

На самом деле, в этом ничего страшного нету. Эта ошибка — систематическая ошибка, которая постоянна, и практически не изменяется.
Докажем это. Произведем 10 замеров частот с шагом 1МГц, составим таблицу. Так как на всём протяжении измерений выдаваемое частотомером значение ошибки не вышло из первых трёх разрядов, запишем только их во втором столбце. В третьем столбце — разницу до истинного значения. В четвертом — частное деления третьего столбца на частоту в мегагерцах. Легко видеть, что ошибка растет пропорционально увеличению частоты, и неизменна.





Проблема заключается в том, что TIM3 отмеряет секундные импульсы по частоте кварцевого резонатора, от которого тактируется микроконтроллер. И он имеет собственную погрешность. Увы, кварцевые резонаторы тоже не идеальны, хотя очень даже близки. Конечно, можно найти и более точные — я же использовал кварц, идущий с stm32vl-discovery по дефолту.

Попытаемся скорректировать эту ошибку. Согласно таблице, в 1МГц содержится 69 ошибок, а значит 1 ошибка — (1МГц/69) = 14492.754Гц. То есть каждые 14 с лишним килогерц кол-во ошибок возрастает на 1. Перепишем основной код:

//Основной код
#define HZ_IN_ERROR	14492.754

void RCC_Config(void);
void EXT_FrequencyCounterConfig(void);
void EXT_FrequencyCounterRun(void);

u32 TrueFreq, ErrorNumbers;
volatile u32 RawFreq;
volatile FlagStatus DataReady = RESET;

int main(void)
{
	RCC_Config();
	EXT_FrequencyCounterConfig();
	EXT_FrequencyCounterRun();

	while(1)
	{
		if (DataReady == SET)
		{
			DataReady = RESET;
			ErrorNumbers = lroundf((float)(RawFreq) / HZ_IN_ERROR);
			TrueFreq  = RawFreq + ErrorNumbers;
		}
	}
}


lroundf — компонент math.h, округляет число типа float, возвращает целое. Считаем кол-во ошибок, корректируем итоговую частоту, ииии…








The end!

Ура! Частотомер работает. Хоть у него частота все же прыгает +- несколько герц(возможно из-за того же кварца, может быть зависимость от температуры/влажности и т.д.), все равно довольно таки неплохо для просто микроконтроллера и полного отсутствия внешних элементов между GPIO и измеряемой частотой.
Всем спасибо:-)
  • +12
  • 28 октября 2014, 00:15
  • Katbert

Комментарии (120)

RSS свернуть / развернуть
Был такой случай — коллеги заживляли на проце то ли UART, то ли SPI, не помню что, но зависящее от частоты кварца. И обнаружили, что частота немного ниже, чем расчетная. Думали кварц неправильный, перерыли литературу про работу кварцов, добавили кондеры, ! ВЫВЕЛИ ! частоту на нужную, а оказалось просто в делитель частоты нужно было записать число на 1 меньшее!
+1
Бывает! Зато узнали много интересного про кварцы=)
0
По моему, для точности порядка 10^-7 кварц уже нужно термостатировать. А так — 69ppm вполне нормальная точность для кварца.
Что до «голый МК» — этого в принципе и достаточно. Внешняя обвязка нужна в основном для формирования прямоугольного сигнала из произвольного и для счета высоких частот.
+1
  • avatar
  • Vga
  • 28 октября 2014, 01:09
для точности порядка 10^-7 кварц уже нужно термостатировать
Верно.
«Простые» резонаторы. Точность начальной настройки — (±1,0...±30)·10⁻⁶. Термостабильность — (±1,5...±75)·10⁻⁶ в рабочем диапазоне температур.
Термокомпенсированные генераторы уже поинтереснее. Возможность внешней подстройки, термостабильность — ±5,0·10⁻⁸...±4,5·10⁻⁶.
И автор, похоже, путает погрешность измерений и дискретность отображения.
0
Хм. Интересно, как температурная компенсация влияет на старение. И как печка влияет на зависимость от нестабильности питания.
0
Интересный вопрос. :-) Если при прочих равных, то одно на другое влиять не должно.
Предполагаю, что вместе с повышением температурной стабильности стремятся подтянуть и остальные параметры стабильности, применяя более дорогие решения.
Например, самые высококлассные резонаторы выполняют лишь некоторых определённых срезов, на частоты 2,5; 5 и 10 МГц.
0
Ну, если это табличка про разные варианты готовых генераторов, а не разные варианты включения одного и того же кристалла — тогда возможно.
0
11 лет назад делал на ПЛИС Altera до 100 МГц +/-1Гц, Двухслойка, ЛУТ, 0,2мм дорожки, делалось из того что было под руками. детальнее описание — www.uschema.com/vysokochastotnyj-chastotomer-na-plis/ и демонстранционное видео с ним снимал www.youtube.com/watch?v=Iu2q_TCNDnU
0
Здорово, что за кварц такой с оранжевой наклейкой? Хочу купить нечто похожее, но не могу найти…
0
Этот «с оранжевой наклейкой» как раз и есть генератор опорной частоты, знаю только что он дорогущий был еще тогда, около 40$, производство неизвестно, купили в Питере.
Когда работал на заводе, начальник отдела подарил.
Наклейку не отклеивал, что под ней не знаю.
Вообще в частотомерах достаточно знать точную частоту опорника и всё, в идеале еще знать несколько термо-точек и их частот, а дальше аппроксимации достоточно.
0
А какой смысл знать точную частоту опоры, если она нестабильна? Термокомпенсация — это уже ближе.
0
А что если для этих целей использовать режим PWM IN. Если верить описанию, например STM32F101 — STM32F107 то этот режим поддерживается таймерами 1-5, 9 и 12.
И в этом режиме происходит измерение периода и длинны импульса (duty cycle), причем в железе. Вам останется только считать эти значения и перевести в герцы.
Не?
0
Точность будет гораздо хуже.
0
Вы имеете ввиду input capture?
Он не подходит, я пробовал его первым.
Он хорош для низких частот, и в особенности хорош тем, что дает результат именно за период измерения, который длится от одного события триггера(фронт/спад) до другого такого же. У меня же период измерения — 1сек, и если в какой-нибудь, пусть маленький промежуток времени в течение 1й секунды сигнал будет нестабилен, измерение будет ложным.
А input capture подсчитывает кол-во тиков системной частоты между событиями триггера, ну и что же он подсчитает при входной в 6МГц, и системной в 24МГц?
0
Не совсем. Я имел ввиду именно PWM Input mode:
16.4.6 PWM input mode (only for TIM9/12)
This mode is a particular case of input capture mode. The procedure is the same except:
● Two ICx signals are mapped on the same TIx input.
● These 2 ICx signals are active on edges with opposite polarity.
● One of the two TIxFP signals is selected as trigger input and the slave mode controller is configured in reset mode.

Ну раз точность хуже, то ладно.
0
Респект автору!
Делал подобное на STM8L, но на частоты пониже. Таймеры в STM заслуживают уважения!
0
Спасибо) Согласен, таймеры тут хороши.
0
К тому, и у меня все работает в железе. Все, что нужно для одиночного измерения — один раз пнуть TIM3, и один раз остановить, через секунду. А потом вынуть данные из TIM4/TIM1.
0
Читал на форумах, что stm32f103cb измерял частоту до 90МГц, при тактовой 72, что логично, т.к. первые защелкивания начинаются после счетчика.
Я тоже делал частотомер по аналогичной схеме, еще и от 3 до 15 вольт на входе, измерения от долей Герц до 13МГц (свыше 13МГц источника не нашлось, но подозреваю, что буфер что ограничивал напряжение с 15 вольт был на пределе). С таймерами точно пришлось разобраться, но не могу сказать что знаю все досконально.

ЗЫ где дисплейчик брали? 20х4 да еще и 3.3В мне не попадались.
0
  • avatar
  • igorp
  • 28 октября 2014, 11:38
Дисплейчик — МЭЛТ MT-20S4A-2YLG. Еще и с кирилицей по дефолту идет. Он на 5В.
>>до 90МГц, при тактовой 72
Вот этого не понимаю. У меня все работает до f_sys/2.
0
Вот здесь кое что есть, и на форуме.
Трактовка тоже где-то пролетала, деталей не помню, но речь шла о том, что счетчик в свободном полете, а вот чтение данных с него — синхронно. По этому, если железо счетчика тянет 90МГц, то он будет считать на 90МГц.
К сожалению, пока не придумал, где взять хотя бы 50МГц, чтобы проверить как дело на 103. Если только удастся на втором 103 сделать подобие «генератора по SPI», или что-то такое.
0
А ради интереса, на других платах дискавери не запускали? Какое там отклонение кварца.
И ещё ради любопытства, что если погреть градусов до 50-70, насколько изменятся показания?
0
  • avatar
  • ACE
  • 28 октября 2014, 12:45
Ради интереса я запускал на более 10 кварцах=)))Видите, тут кварц помечен циферкой два)
Он дал наименьший из всех результат отклонения. Греть не пробовал, но попробую.
0
Круто :) А какой худший результат был? Отклонения в обе стороны?
0
У меня куча дискавери просто, я все кварцы из них брал(на 8МГц). Видимо из-за того, что одна серия(фирма, поставщик?) у них используется для демо-плат, все кварцы практически одинаковые были. Отклонения были только в одну сторону. То есть при некомпенсированном измерении частоты в 1МГц, результаты были от самого лучшего = 0.999.931 до самого худшего = 0.999.908. Причем интересно, что худший результат давали кварцы с маркировкой 8.0000, а лучший с маркировкой 8.000
+1
radiokot.ru/forum/viewtopic.php?p=1492756#p1492756 — алгоритм как у вашего частотомера увеличить разр способность примерно в 4 раза для Ваших частот (6 и 24 мегагерца).
0
  • avatar
  • OlegG
  • 28 октября 2014, 13:18
О! кто то тоже не использует стдпериф. Я думал таких людей не осталось
СПАСИБО!
-1
Спасибо. Я ради интереса пытался настроить такую связку таймеров через SPL. Через пол-часа интерес умер=)
+1
а я через полчаса в гугль кодах нашел готовое решение.
-1
Лучше бы вы пол-часа потратили на чтение даташита о работе таймеров, и написали свой код, чем искать чужие решения=)
+1
Я инженер, а не магистр. Моя задача найти и рассмотреть имеющиеся велосипеды с целью обойти уже кем-то найденные грабли.
+1
Вы не один! Я использую SPL иногда (например, FatFS пробовал). Потом заменяю. Обычная работа с портами как-то мне дала 1.5Кб разницы объема кода! Я конечно понимаю, флеша хватает, но тем не менеее…
0
P.S. Как-то товарищ спрашивал, какие есть годные книги для перехода на STM. Мой ответ — лучше даташита книги нет!
0
Я иногда юзаю SPL для инициализации. Да класть, насколько она быстродейственна, когда код выполняется один раз при старте. Зато читабельно и понятно. А когда уже лучше вникаешь во все постепенно, в один момент понимаешь, что SPL адово мозолит глаза, да и регистры вроде как уже почти наизусть помнишь все =)…
+1
Абсолютно согласен, бро!
А вот джокера че-то минуснули, хотя он всего лишь высказал свое мнение…
+1
Видимо, кто-то портировал такой код в дальнейшие разработки и на переписывание инициализаторов ушло 90% времени, а 10% времени на основной алгоритм.
Библиотеки очень сильно повышают читаемость, и портируемость. Как следствие, обучаемость, т.е. прогресс быстрее. А вот использование регистров напрямую намекает на недостаточную зрелость адепта.
Если сильно жмут рамки, есть ассемблер, в конце-концов. Плотность вырастет раз в 8 (зависит от решаемых задач конечно, я чисто по частотомеру сужу).
-1
>>А вот использование регистров напрямую намекает на недостаточную зрелость адепта.
Да что за бред.

Я пишу код, смотря в референс мануал, а не в хедер spl.
Я смотрю в даташит, на регистр, и присваиваю нужным битам то, что мне нужно. Причем тут вообще асм? Регистрами вместо либ я пользуюсь не для увеличения плотности кода, а потому что в референсе описаны регистры, и что с ними нужно сделать, что бы заработало, а не функции из spl.
Одно дело, когда в spl написано GPIO_Pin_On, GPIO_Pin_Off, но вы видели, что там наделано для таймеров? Да это же жесть какая-то. А потом ищи, что они внутри всего этого сделали, и почему ничего не работает.


-2
При такой дичайшей гибкости таймеров SPL может и не хватить в некоторых случаях, или я ошибаюсь?
0
Ну вот в этом-то и дело. Я просто не осилил построить эту связку на spl.
0
Даташит пишет производитель, внезапно, основные библиотеки — тоже.
Проблемы начинаются, когда «я» пытается перерасти в «мы».
+1
асм тут при том, что если инициализацию регистров переписать на асм, как и процедурки типа вывода на дисплей и т.п. памяти освободится поболее, чем 1.6к. Лишь лень и осознание того факта, что это очередной асм, про который через 5 лет никто не вспомнит, останавливают.
0
Я не пытаюсь освободить память.
0
Не хочу холиварить, Вы случайно не с той темы форума, которую недавно закрыли?
0
Относилось к igorp
0
нет, я обычно равнодушен к такого рода вещам. Хотя балуюсь программированием уже лет 20, делюсь вот «открытием» :)
0
А вот использование регистров напрямую намекает на недостаточную зрелость адепта.
Но показывает способность программиста разобраться в мануале без костылей.
К тому же никто не запрещает работу с регистрами делать в написанных самим функциях инициализации.
Библиотеки очень сильно повышают читаемость
На все случаи библиотеки не предусмотришь — и тогда никуда не уйдёшь от работы с регистрами.
+3
Тут-то ровно ничего такого, чего нет в библиотеках, в том-то и дело.
0
ровно ничего такого, чего нет в библиотеках, в том-то и дело
C библиотекой разобраться тоже нужно время. А так как задача простая, то можно и сразу без библиотек её решать.
0
Ну а библиотеках ровно ничего такого, чего бы не было в даташите. А не наоборот. Ну да ладно, бесконечно можно об этом спорить.
+1
Выскажу свое ИМХО.

А когда уже лучше вникаешь во все постепенно, в один момент понимаешь, что SPL адово мозолит глаза, да и регистры вроде как уже почти наизусть помнишь все =)…

Да, Вы правы, если постоянно (или регулярно) работаешь с определенной платформой – запоминаешь регистры, биты в них, особенности использования. Это не проблема.

Проблемы начинаются, когда работаешь с несколькими платформами, причем поочередно. Я, например, лет 6 назад активно использовал AT91SAM7 и тоже, на тот момент, «помнил наизусть» если не все, то многое из низкоуровневой работы на уровне регистров. А теперь практически все забылось, т к. я перешел на другую платформу и новые девайсы делаю на ней. Но часто приходится поддерживать старый код, и рассчитывать, что ты будешь помнить все регистры и тонкости для разных платформ – не вариант.

Вариант – оборачивать обращения к регистрам в макросы/инлайн функции/функции/методы и давать этому осмысленные имена. Что такое
ResetTimer(TIM0);
Вы поймете всегда, а что такое
TIM0->BLABLA = BITAA | BITGG;

вы достаточно быстро забудете.

Поэтому, настоятельно рекомендую делать такие обертки с осмысленными именами. А вот делать свои обёртки или использовать готовые (например, SPL) – это уже другой вопрос.
+5
void EXT_FrequencyCounterConfig(void) для меня вполне достаточная обертка, особенно с комментариями к каждому действию)
Но окей, я понял вас.
В любом случае переписывать код для топика ради никому не нужного холивара я не буду.
0
How does it works
+1
fixed)
0
С какой доступной точностью возможно мерить частоту допустим до 1 МГц? Какие есть реализованные варианты?
0
Зависит от точности опорного генератора. Судя по табличке чуть выше — где-то до 10^-8 при использовании термостатированного кварцевого генератора. сли надо выше — нужен атомный эталон частоты (aka атомные часы).
0
Ещё возможен вариант радиочасов, как покупных, так и самодельных.
ФГУП ВНИИФТРИ обеспечивает точность частот радиостанций ГСВЧ около (2...5)·10⁻¹².
www.vniiftri.ru/index.php/ru/struct/gsvch
ftp://ftp.vniiftri.ru/BULLETINS/D
0
В журнале «Радио» №2, 2000 г., с. 67‒69 была опубликована статья «Эталонные сигналы частоты и времени».

Официально в бюллетенях ГСВЧ серии В публикуются характеристики (позывные, частоты, местоположение) и программы передач через радиостанции, телевидение и сеть звукового вещания.

В журнале Elektor, January 2012, c. 48‒53, была описана конструкция часов для станции DCF77. Приёмник для синхронизации использует в том числе и псевдослучайную последовательность, передаваемую этой станцией. Авторская страница.

Опять же, в Elektor была опубликована серия (2012, № 3, 4, 5, 6, 9, 10) из 6 статей AVR Software Defined Radio. Посвящена приёму СДВ, ДВ, СВ радиостанций с модуляцией АМ, ЧМ, ФМ, декодированию их сигналов. В том числе и станций точного времени и частоты. Скачивайте, пока не убрали.
+2
SDR на AVR? И каких характеристик удалось добиться?
0
Э нет, мы любопытны, но ленивы)
0
Ну а почитать? Там есть кое-что. Например, как АЦ-преобразователем с макс. скоростью 15 квыб./с цифровать сигнал 600 кГц с лишком. Трюк очевидный, после того как его объяснили.
0
На алиасинге? Или разгоном АЦП?
0
На алиасинге.
0
Т.е. оно обрабатывает полосу в 15кГц. Это примерно то, что меня и интересовало. И все же, каковы прочие полученные параметры? Производительность AVR не так уж высока для задач ЦОС.
0
 Какие параметры?
 Это не радиовещательный приёмник. Это, можно сказать, учебный курс и учебный стенд. И там ни 15 кГц, ни даже 1500 Гц не нужно. Нужно захватить несущую и получить данные об амплитуде и фазе, потом декодировать время, или погоду, или ещё что.
 По крайней мере, засинхронизироваться по частоте и фазе несущей радиостанции ГСВЧ он способен. И, следовательно, способен служить домашним стандартом частоты.
 А в другой статье описан приёмник, цифрующий DCF77 на частоте 310 кГц, т. е. на четверной. Там уже и время до единиц или десятков микросекунд можно получить. В «бюллетене В» приведены оценки достижимой точности для разных диапазонов и различных условий приёма.
 Уже бы могли сами прочитать и ответить на все вопросы.
0
Спасибо, будет что почитать!
0
Можно использовать гетеродин(опорный генератор). Перемножить 2 меандра можно на логике. Биения отфильтровать парой интегрирующих цепочек (ФНЧ). НЧ сигнал можно оцифровать и обработать алгоритмами ЦОС. Тут время накопления определит разрешение по частоте f=1/Т, где f=разрешение по частоте и Т-время накопления. Варианты определения частоты — БПФ, ДТФ, дискриминатор нулевых биений.
0
Katbert, можете скинуть main.c проекта? Т.е. полный код в одном файле.
0
Благодарю! Попробую на основе Вашего кода L-метр собрать.
0
Пользуйтесь на здоровье =)
0
В чем может быть ошибка? Немного изменил код общения с дисплеем. Подправил общий код под stm32f103c8t6. И почему то показания появляются, если я пин ра12 быстро кидаю на землю. Если мерить частоту на пине РВ8, то ничего на экране нету. Хотя осцил показывает меандр.
Код:

#include "hd44780.h"
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include <math.h>

#define HZ_IN_ERROR	14492.754

void RCC_Config(void);
void EXT_FrequencyCounterConfig(void);
void EXT_FrequencyCounterRun(void);

volatile u32 RawFreq, TrueFreq ;
volatile u32 ErrorNumbers;
volatile FlagStatus DataReady = RESET;

//void TIM6_Config(void);
void GPIO_Config(void);
void RCC_Configuration(void);
void Print(void);
void TIM2_Config(void);
int main(void)
{
	RCC_Config();
GPIO_Config();
lcd_init();

	//LCD_Init();
	//LCD_WriteCommand(LCD_CLEAR);
	TIM2_Config();
	//NVIC_EnableIRQ(TIM6_DAC_IRQn);
	lcd_clrscr();
	EXT_FrequencyCounterConfig();
	EXT_FrequencyCounterRun();

	while(1)
	{
	
		
		if (DataReady == SET)
		{
			DataReady = RESET;

			ErrorNumbers = lroundf((float)(RawFreq) / HZ_IN_ERROR);
			TrueFreq  = RawFreq;// + ErrorNumbers;

			Print();
		}
	}
}

void TIM3_IRQHandler(void)
{
	TIM3->SR &= ~TIM_SR_UIF; //reset interrupt flag

	RawFreq = ((u32)(TIM4->CNT) << 16) | ((u32)(TIM1->CNT));
	DataReady = SET;
	TIM1->CNT = 0;
	TIM4->CNT = 0;

	TIM3->CR1 |= TIM_CR1_CEN; //another circle
}

void EXT_FrequencyCounterConfig(void)
{
	RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN;//TIM3, TIM4
	RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;//TIM1

	//------------------------------------------------------------------------------------------
	//TIM3 master counter, period = 1sec.
	//------------------------------------------------------------------------------------------
	TIM3->PSC	= 2400 - 1; //new clock = 10kHz
	TIM3->ARR	= 10000 - 1; //period = 1sec
	TIM3->CR1	|= TIM_CR1_DIR; //used as downcounter
	TIM3->CR1	|= TIM_CR1_OPM; //counter stops counting at the next update event
	TIM3->CR2	|= TIM_CR2_MMS_0; //COUNTER_ENABLE signal to TIM1, used as trigger output (TRGO)
	TIM3->DIER	|= TIM_DIER_UIE; //enable interrupt

	//------------------------------------------------------------------------------------------
	//TIM1 slave counter, TIM1_ETR pin is input
	//------------------------------------------------------------------------------------------
	TIM1->PSC	= 0;
	TIM1->ARR	= 0xFFFF; //counter max value, may be don't set
	TIM1->CR1	&= ~TIM_CR1_DIR; //used as upcounter
	TIM1->CR1	&= ~TIM_CR1_OPM; //is not stopped at update event
	TIM1->CR2	|= TIM_CR2_MMS_1; //update event is selected as trigger output to TIM4
	TIM1->SMCR	|= TIM_SMCR_ECE; //ext. clock mode2 enabled, counter is clocked by any active edge on the ETRF signal
	TIM1->SMCR	&= ~TIM_SMCR_ETPS; //no external trigger prescaller
	TIM1->SMCR	&= ~TIM_SMCR_ETF; //no external trigger filter
	TIM1->SMCR	|= TIM_SMCR_TS_1; //internal trigger_2 (ITR2) from TIM3
	TIM1->SMCR	|= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_2; //while trigger input (TRGI) is high, counter is on

	//------------------------------------------------------------------------------------------
	//TIM4 additional counter
	//------------------------------------------------------------------------------------------
	TIM4->PSC	= 0;
	//TIM4->ARR	= 0xFFFF; //counter max value
	TIM4->CR1	&= ~TIM_CR1_DIR; //used as upcounter
	TIM4->SMCR	&= ~TIM_SMCR_ETPS; //no external trigger prescaller
	TIM4->SMCR	&= ~TIM_SMCR_ETF; //no external trigger filter
	TIM4->SMCR	|= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2; //rising edges of TIM1 (TRGI) clock the counter
}

void EXT_FrequencyCounterRun(void)
{
	NVIC_EnableIRQ(TIM3_IRQn);
TIM_Cmd(TIM2, ENABLE);
	TIM4->CR1	|= TIM_CR1_CEN;
	TIM1->CR1	|= TIM_CR1_CEN;
	TIM3->CR1	|= TIM_CR1_CEN;
}

void RCC_Config(void)
{
	FlagStatus HSE_Status = RESET;

	RCC->CR |= RCC_CR_HSEON;

	do
	{
		(((RCC->CR) & RCC_CR_HSERDY) == RESET) ? (HSE_Status = RESET) : (HSE_Status = SET);
	}
	while(HSE_Status != SET);

	FLASH->ACR |= FLASH_ACR_PRFTBE;
	FLASH->ACR &= (u32)((u32)~FLASH_ACR_LATENCY);
	FLASH->ACR |= (u32)FLASH_ACR_LATENCY_0;

	RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
	RCC->CFGR |= RCC_CFGR_PPRE1_DIV1;
	RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;

	RCC->CFGR &= (u32)((u32)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
	RCC->CFGR |= (u32)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL3);

	RCC->CR |= RCC_CR_PLLON;

	while((RCC->CR & RCC_CR_PLLRDY) == RESET);

	RCC->CFGR &= (u32)((u32)~(RCC_CFGR_SW));
	RCC->CFGR |= (u32)RCC_CFGR_SW_PLL;

	while (((RCC->CFGR) & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}

void GPIO_Config(void)
{
	//var
	GPIO_InitTypeDef GPIO_InitStructure;

	//clock
	//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |  RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
	RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
	//led
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	//lcd
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 |  GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 ;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM1, ENABLE);
//AFIO->MAPR = AFIO_MAPR_TIM1_REMAP_NOREMAP;
	RCC_MCOConfig(RCC_MCO_HSE);
}

void TIM2_Config(void)
{
	//var
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

	//clock
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    //timer base setup
	TIM_TimeBaseStructure.TIM_Prescaler = 0;//24 - 1; //10kHz
	TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

	//timer enable
	//TIM_Cmd(TIM2, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);
}

void TIM2_IRQHandler(void)
{
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	GPIOB->ODR ^= GPIO_Pin_8;
	//DataReady = 1;
}

void Print(void)
{
	/*LCD_WriteCommand(LCD_LINE_1);
	LCD_WriteData((RawFreq / 10000000) + 48);
	LCD_WriteData((RawFreq / 1000000) % 10 + 48);
	LCD_WriteData('.');
	LCD_WriteData((RawFreq / 100000) % 10 + 48);
	LCD_WriteData((RawFreq / 10000) % 10 + 48);
	LCD_WriteData((RawFreq / 1000) % 10 + 48);
	LCD_WriteData('.');
	LCD_WriteData((RawFreq / 100) % 10 + 48);
	LCD_WriteData((RawFreq / 10) % 10 + 48);
	LCD_WriteData((RawFreq % 10) + 48);
	LCD_WriteData(' ');
	LCD_WriteData('H');
	LCD_WriteData('z');*/

	lcd_return();
	lcd_putc((TrueFreq / 100000000) + 48);
	lcd_putc((TrueFreq / 10000000) + 48);
	lcd_putc((TrueFreq / 1000000) % 10 + 48);
	lcd_putc('.');
	lcd_putc((TrueFreq / 100000) % 10 + 48);
	lcd_putc((TrueFreq / 10000) % 10 + 48);
	lcd_putc((TrueFreq / 1000) % 10 + 48);
	lcd_putc('.');
	lcd_putc((TrueFreq / 100) % 10 + 48);
	lcd_putc((TrueFreq / 10) % 10 + 48);
	lcd_putc((TrueFreq % 10) + 48);
	lcd_putc(' ');
	lcd_putc('H');
	lcd_putc('z');
	lcd_putc(' ');
	lcd_putc(' ');

	/*LCD_WriteCommand(LCD_LINE_3);
	LCD_WriteData((ErrorNumbers / 100) % 10  + 48);
	LCD_WriteData((ErrorNumbers / 10) % 10 + 48);
	LCD_WriteData((ErrorNumbers % 10) + 48);*/
}
0
Нашел ошибку: вывод РА12 на вход не установил.
0
объясните, пожалуйста, как работает это действие LCD_WriteData((RawFreq / 10000000) + 48);
Я так думаю, что узнаем сколько десятков МГц в частоте. А зачем здесь число 48?
Не могу понять. Без добавления 48, на экране вместо цифр отображаются залитые ячейки дисплея hd44780.
0
Похоже так определяется код цифры в таблице hd44870? Верно?
0
Не только в таблице 44780, но и вообще в ASCII, цифры там занимают коды 0x30..0x39. Правда, обычно пишут +'0' на С или +Ord('0') на Паскале.
Хотя я бы просто использовал функцию преобразования числа в строку или printf. Проще, читабельней, а на оверхед в задаче, где свободны 99% ресурсов — глубоко пофигу.
+1
printf за собой тащит плавучку и ещё кучу всего. Оверхед на порядки должен всё же вызывать хоть какое-то неудобство. В иаре, правда, была облегченная версия printf по выбору.
ltoa/ultoa же не позволяет задать форматирование. Так что куда лучше написать свою функцию.
0
Тащит и тащит, что с того? В данной программе, если только она не встраивается в более крупную систему, 95-99% флеша просто выбрасывается впустую. Процентов 5 из них спокойно можно выделить printf'у.
Так что куда лучше написать свою функцию.
Лучше использовать библиотечную функцию, а не велосипедить без нужды.
0
Не надо лишнее тащить без нужны. А «велосипед» можно копипастить из проекта в проект (а лучше встроить в либу lcd).
0
Не надо писать лишнее без нужды. А экономить ресурс, излишки которого выбрасываются — бессмысленно.
А «велосипед» можно копипастить из проекта в проект
Этот велосипед копипасту не подлежит, он полностью завязан на задачу «отобразить переменную RawFreq в определенном формате на LCD», любое изменение задачи требует переписывания велосипеда. При этом он не обладает никакими плюсами, чтобы таскать его вместо использования библиотечной функции.
+2
Я же и говорю, функцию надо написать. Типа
lcd_put_uint32(uint32_t x, uint8_t width, uint8_t flags);
0
Ага. И десяток таких функций на каждый тип данных. Которые, к тому же, к интерфейсу LCD отношения не имеют и по хорошему — нечего им там делать.
Впрочем, в некоторых случаях это может быть оправдано. А в случае ориентирования на платформу вроде ARM, где оверхед printf'а обычно несущественен — я бы сделал lcd_printf(s, ...).
+1
Ну на платформах вроде ARM в принципе идеология соответствующая — количество а не качество.
Для нормальных МК лучше просто написать 4 функции — для int, int32, fixint, fixint32. Знаковость передать в флагах.
0
Причем тут идеология? Это исключительно инженерный подход — взвесить плюсы и минусы (веса зависят от кучи параметров, специфичных для проекта) и выбрать оптимальное. В данном случае практически все параметры несущественны, так что стоит выбрать самый читабельный и простой вариант. И это не написание велосипеда.
Идеология — это то, чем руководствуешься ты. Оверхед — неправославно, даже если вопрос стоит как «сэкономить и выбросить», и все такое.
+1
4 функции — для int, int32, fixint, fixint32

Угу… 4 функции для вывода числа на индикатор, потом 4 аналогичные функции для вывода в USART, потом …

Мне нравиться, как сделан форматируемый ввод/вывод в avr-libc. Там fprintf позволяет «перенаправлять» вывод куда угодно без буферизации (через callback функцию). И есть три реализации fprintf (если быть точным, то три версии vfprintf()) Версия, которая линкуется по умолчанию, не поддерживает плавающую точку. Есть еще «минимальная» (printf_min) и «максимальная» (printf_flt) версия.
+2
А, точно. Читал в описании библиотеки, но забыл, поскольку *printf в МК не юзаю.
Всё равно даже функция по умолчанию без плавучки жрёт пару килобайт. При этом покрывает далеко не все задачи.
0
мой велосипед:
const uint32_t digits[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};

uint8_t itoa_flag(char* Buffer, int32_t a, uint8_t flag)
{
  uint32_t b;
  int8_t i;
  uint8_t Length = 0;
  if (a<0)
  {
    *Buffer = '-' ;
    Length++;
    a=-a;
  }
  for (i=9;i>=0;i--)
  {
    b=a/digits[i];
    if ((flag>i)||( b))
    {
      Buffer[Length] = '0'+b;
      Length++;
      flag=i;
    }
    a = a % digits[i];
  }
  Buffer[Length] = 0;
  return Length;
}

uint8_t itoa(char* Buffer, int32_t a)
{
  return itoa_flag(Buffer,a,1);
}

uint8_t ftoa(char* Buffer, float a, uint8_t digit_after_point)
{
  float b;
  uint8_t Length;
  b=a+(0.5001/digits[digit_after_point]);
  Length = itoa_flag(Buffer,b,1);
  if(digit_after_point)
  {
    if (b<0)
      b=-b;
    Buffer[Length] = '.';
    Length++;
    b=(b - ((uint32_t)b))*(digits[digit_after_point]);
    Length += itoa_flag(&Buffer[Length],b,digit_after_point);
  }
  return Length;
}
0
И чем он интересен? Вообще, neiver уже рассматривал множество вариантов преобразования числа в строку, можно выбрать наиболее подходящий к задаче.
Но здесь задача из разряда «под ваши требования подходит любой ноутбук, так что выбирайте как истинная дама — по цвету». Я бы выбрал библиотечный вариант, как простой, читабельный и проверенный временем.
0
мне пришлось это нагородить самому, поскольку KEIL помему-то не включил эти функции в стандартные библиотечки. Пришлось самому накропать. Заодно и подумал, что именно мне нужно в данном варианте. А так, да. Стандартные библиотечки рулят.
0
Быть может, надо было покрутить какие-то параметры? Вроде полновесности библиотеки.
0
не, там у них это написано на сайте, дескать, извиняйте… Нетути. Хотя мне лично не понятно это. Моэет не договорились о цене с кем то?
0
Стандартные библиотечки рулят.
Функции преобразования числа в строку как раз нестандартные (хотя распространённые).
0
Стандартные библиотечки рулят.

Однозначно. Более того, реализация стандартных библиотек, как правило, выверена, протестирована и т. д. Написание своих велосипедов чревато ошибками. Даже в простых вещах есть тонкие моменты. Вот, в Вашем коде увидел унарный минус
a=-a;

и вспомнил, как когда-то из-за аналогичной строки валился софт. Притом валился спонтанно, у заказчика, раз в несколько месяцев. И повторить баг локально мы не могли… И этот код много раз ревьювился, но никто не замечал ошибки…
+1
На всякий случай уточню: мы не сам минус провтыкали, мы провтыкали случай с
а = -2147483648
0
Даже в простых вещах есть тонкие моменты.
Ну дык волков бояться — в лес не ходить.
0
Ну дык волков бояться — в лес не ходить.
Через лес уже проложена автострада, которой все успешно пользуются.

Но мы по ней не пойдем, мы попытаемся создать свою, «уникальную тропу» (с целью срезать пару метров на расстоянии в несколько км.) — вот с этого и начинается большинство проблем…
+2
Ну и неправильная аналогия, т.к. цель — написать программу, а не получить конкретную функцию.
Ни к чему делать крюк по автостраде, лишь бы сократить путь по лесу на пустяк.
0
цель — написать программу, а не получить конкретную функцию.
Нет. Эта ваша «программа» нафиг никому не нужна, кроме программистов, которые ее пишут :) Есть «заказчик» или «пользователь» Он вообще не понимает в этом вашем программировании.

Для него важно решение его задачи – перебраться через лес.

Цель – удовлетворить заказчика, минимизировать его риски…

З.Ы. Я не говорю, что это хорошо или плохо, но таковы современные реалии.
0
Есть «заказчик» или «пользователь»
Ну так. Всё хорошее в мире создаётся энтузиастами и уничтожается торгашами.
0
Ну дык волков бояться — в лес не ходить.
Велосипелы писать — баги плодить.
+2
Не велосипеды, а программы.
0
Я вот чего подумал: А не логичнее ли настроить TIM3 на «реальную» секунду, чем городить какие-то коэффициенты и производить некие вычисления? По-моему, так можно гораздо точнее настроить. И показания будут справедливыми, указывая на точное кол-во периодов за секунду, чем некий абстрактный пересчет…
0
А там разве не так? Или ты предлагаешь вносить поправку на неточность кварца в делитель TIM3?
0
да, именно в делитель.
0
Да, по идее можно. Разрешение у делителя (насколько видно по коду, в даташит мне лезть лень) вполне достаточное, вместо 24М туда можно забить 24М*(1+69/1000000).
0
Как то попадалась схема на микроконтроллере в котором используются два таймера — на один подается сетевое напряжение пропущенное через усилитель ограничитель до симметричного меандра, а на второй через фильтр НЧ 50Гц (похож на интегратор) (первая гармоника сетевой частоты), а далее контроллер обрабатывает эти два сигнала и определяет точно частоту и фазу сетевого напряжения. Изучение бинарника кода не дало информации об алгоритме, а только понятно, что используется табличный метод определения частоты и фазы. Какие будут соображения у местных Гуру по поводу алгоритма?
0
А фазу чего и относительно чего оно меряет?
0
вообще это регулятор мощности IR ламп — мерит частоту очень точно и находит точку перехода и максимум U сетевого напряжения.
0
Забыл добавить — не влияет форма сетевого напряжения, а она далеко не синус при общей нагрузке в сотни киловатт.
0
Я по прежнему не понимаю что это и что оно меряет, так что никаких соображений изложить не могу. Приведи больше информации — схему, подробное описание чего это за штука и что она делает и все такое.
0
P.S. И дизасм бинарника тоже.
0
С бинарником сложнее сейчас сложно найти, но там точно была выборка значений из таблицы констант (не таблица синуса точно) после обработки получалась чвстота и фаза сетевого напряжения относительно точки перехода через ноль.
Это был сименский регулятор, который менял пропуском фаз сетевого напряжения температуру нагрева инфрокрасных ламп.Мощности большие — канал 10КВт до 100 каналов (один регулятор 10 каналов). Устройства были все синхронизированы к частоте и фазе сетевого напряжения т.е. пропуски все распределялись по 100 каналам равномерно.
0
Если там управление пропуском полупериодов, то достаточно ловить момент перехода через ноль.
0
Нет, там идет периодическое измерение тока и напряжения на лампах и есть к пропуску еще дополнительно фазовый ШИМ. Мне просто интересно какие простые способы существуют нахождения фазы и частоты сетевого напряжения используя внутренние возможности DSP в M4F Cortexю хотелось бы поменьше аналоговой части и побольше внутренней математики кортексов использовать.
0
Да и в догонку — про больших переадах тока в сети иногда синус превращается в фигню где после одного перехода через ноль появляется еще один или больше.
0
Прошу прощения за ошибки — набираю с клавы смарта
0
да. Такое тоже наблюдал, это когда гармоники хлещут. Часто бывает там, где используется симисторное регулирование.
0
если там задача с переходом через ноль, то достаточно схемы на термисторе, трех транзисторах и оптосимисторе с функцией отлавливания нуля. Что-то типа MOC3063
0
у меня задача возникла — измерять скорость вращения оптического энкодера от мышки. Вращаться об будет доольно медленно — импульсы частотой около герца будут идти. Как лучше поступить? я планировал запускать таймер в режиме захвата. По фронту сбрасывать и считать импульсы до изменения сигнала на входе. Частота в данном случае мне не нужна Мне нужно снимать относительные величины.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.