period

Инженер, начинающий изучать STM32, может столкнуться с проблемой неточных временных промежутков. К примеру, вы можете написать код для задержки на 1мс с помощью таймера, а при проверке осциллографом увидеть что она отрабатывает быстрее — за 0.997 мс. Или наоборот медленнее, за 1.03 мс. Точно то же самое бывает и при генерации каких-либо частот — к примеру, мои 50МГц превращались в 49.95.

Почему так происходит?

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

1. Осциллятор.

  • Самое первое, что нужно помнить: точность встроенного RC-осциллятора (этот источник тактирования назвается HSI) хуже 1%. Она зависит от температуры, напряжения питания, партии микросхем. Каждый даташит на МК об этом пишет, и более того, приводит график зависимости частоты от температуры. На HSI точного времени не получить!
  • Внешний RC-осциллятор немного лучше (потому что компоненты сделаны не по микронным технологиям), но в целом то же самое. Тут добавляется ещё одна проблема:
  • Ёмкость SMD-конденсаторов сильно зависит от приложенного напряжения, и в меньшей степени от температуры. Это влияет и на RC-осцилляторы, и на кварцы с шунтирующими конденсаторами.
  • Частота кварца очень слабо зависит от температуры и прочих факторов. Для точных времён и частот — только кварц (HSE). Однако, от старости или брака (и даже от резких ударов!) его частота тоже может уплыть. Её можно немного (до 10 ppm) подстроить шунтирующими конденсаторами, таблица есть в даташите на МК. Ну и конечно, без этих кондёров кварц лучше не оставлять.

2. Метод измерений.

А точен ли ваш осциллограф? Обидно долгое время провозившись с поиском глюка, понять что глюк в самом измерителе.

3. Метод отсчёта временных промежутков.

Большая и важная тема.

Допустим, мы хотим мигать светодиодом раз в секунду. Самый очевидный и простой код, который приходит в голову, это «включили-подождали-выключили-подождали». Процедура wait_ms каким-то образом ждёт в течение 1000 миллисекунд, пускай просто гоняет длинный цикл. Что-то типа такого:


void wait_ms()
{
	int i;
	for(i=0; i<1000000; i++);
}

void main()
{
	while(1)
	{
		Led_ON();
		wait_ms(1000);
		Led_OFF();
		wait_ms(1000);
	}
}

Замечательный код, только в нём целая куча проблем.

Блокировать процессор длинным пустым циклом нельзя, да и неудобно. Допустим, если придёт прерывание — процессор перейдёт в обработчик прерывания, а вернувшись продолжит цикл с того же места. Время выполнения wait_ms() увеличится на время обработки того прерывания. Вот и первый источник ошибки. Лучше сделать таймером (благо, их в МК около десятка).

Окей, сделали таймером. Загрузили какое-то большое значение, запустили таймер и в цикле проверяем флаг «отсчёт закончен». Такая схема практически полностью не зависит от прерываний (разве что если оно произойдёт в самом конце временного промежутка, и таймер закончит отсчёт ещё во время обработки прерывания).

Но обратите внимание: каждая операция внутри цикла while(1) занимает какое-то время, программа работает так:

  1. вошли в процедуру таймера (очень долгая операция!)
  2. отсчитали время таймером
  3. вышли из процедуры таймера, вернулись в main (тоже долгая)
  4. записали значение в порт (быстрая операция, но всё равно не мгновенная)
  5. прокрутили цикл while (долгая операция, включает в себя арифметическое сравнение и условный переход)
  6. Только после этого снова пошли в процедуру таймера и запустили таймер на 1 мс.

Значит, нужно чтобы все эти операции не участвовали в увеличении общего времени цикла. И сделать это легко — нужно запустить таймер на постоянный отсчёт своих 1000мс, а после каждого переполнения он будет вызывать прерывание. В прерывании можно включать и выключать светодиод (и вообще делать любые долгие действия), а таймер в это время продолжит считать.