Как организовать систик в AVR

systick — это системный таймер.

Довольно часто бывает нужно иметь чёткую «нарезку» времени — получение событий (импульсов) с заданной частотой.

Например, для моего очередного проекта мне нужно через каждые 50 мкс изменять состояние ножки микроконтроллера, а через каждые 200 мкс запускать АЦП. Кроме того в системе нужно еще сформировать односекундные интервалы времени. Налицо целая сетка частот или временнЫх интервалов.

Однако, что бы не усложнять себе жизнь, давайте реализуем сначала самые короткие интервалы времени — 50 мкс.

Мой проект базируется на ATMEGA328P. Не плохой процессор. Народный. Но мне отчего-то хочется слегка попинать его.

Начну с того, что в нормальных процах систик уже заложен сразу. Я имею ввиду Кортексы, там систик вообще сидит в ядре. По моему даже STM8 имеют систик. Более древние процессоры — AVR и MSP430 систика как такового не имеют, и поэтому в них приходится городить его из штатного таймера.

MSP430 позволяет в качестве систика использовать даже вотчдог (watchdog). Причем, с моей точки зрения организация систика на MSP430 выглядит намного изящнее, чем в AVR-ках.

Однако, это еще не всё! Соприкоснувшись с еще одной МЕГА-ой, я осознал, что реальной повторяемости от МЕГИ к МЕГЕ не наблюдается. ATMEL наплодила какой-то зоопарк AVR-ок! Все звери в этом зоопарке чуточку похожи, но в тоже время — разные. И перенести с одного камня на другой какой-нибудь проект легко и быстро не получается.

У одинаковых, казалось бы, периферийных устройств — разные названия регистров, разные названия и расположения битов в этих регистрах. У одних и тех же регистров — разные адреса…

На примере ATMEGA328P, я еще раз убедился, что похожесть камней у ST Microelectronics всё-таки выше, чем у ATMEL.

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

Итак, для того чтобы в ATMEGA328P создать таймер на 50 мкс, мне понадобится использовать его таймер.

В системе используется кварцевый резонатор на 8 МГц, и если поделить его частоту на 400, мы получим частоту в 20 кГц.

F = 1 / T = 1 / 50 мкс = 20 кГц

N = F_CPU / F = 8 МГц / 20 кГц = 400

У каждого таймера в АВР-ках есть предделитель, который делит входную частоту на одно из фиксированных значений (8, 64, 256, 1024). После предделителя сигнал поступает на вход таймера. Таким образом, если нам воспользоваться делителем на 8, то на вход таймера будет поступать частота 1 МГц.

Чтобы получить нужную частоту нам остается поделить ее таймером ещё на 50.

400 = 8 * 50

400

Поскольку коэффициент деления таймера меньше 256, то можно использовать 8-разрядный таймер T0.

Замечу так же, что выход таймер не будет никуда подключен, но таймер будет вызывать прерывания.

Вот предельно упрощенный вариант программы для реализации систика:

// systick.c

#include <avr/io.h>
#include <avr/interrupt.h>

// Прерывание возникает каждые 50 мкс
ISR(TIMER0_COMPA_vect)
{
  // тут что-нибудь будем делать немного, но очень быстро
}

void init_timer(void)
{
  TCCR0A = _BV(WGM01);  // Режим CTC
  TCCR0B = _BV(CS01);   // Предделитель :8
  TCNT0  = 0;
  OCR0A  = 49;  // OCR0 = F_CPU / (Prescaler * F_interrupt) - 1 =
                //        8000000 / (8 * 20000) - 1 = 49
  OCR0B  = 0;
  TIMSK0 = _BV(OCIE0A);  // Разрешить прерывания от таймера
}

int main(void)
{
  // Инициализация периферии
  initTimer();

  sei();      // Разрешаем прерывания глобально и ...
  while (1)   //   понеслась!
  {
    // Тут выполняем фоновую работу
  }

  return 0; // сюда мы никогда не попадем, но язык Си требует чтобы
            // функция возвращала значение.
}

Если вы обратили внимание, то мы с вами говорили о микроконтроллере ATMEGA328P, но я нигде не указал его тип. Тип МК указывается в Make-файле. В нём же указываются и другие параметры проекта. Я, признаться, уже совсем оборзел от этого Линуха, теперь в Make-файле я начал пихать еще и Git-команды для работы с репозиторием на bitbucket.org. Но о Make-файлах мы поговорим как-нибудь потом.

Make-файл в Линухе — это наше всё!

Как мы можем проверить, что наш код работает? Можно заставить МК в обработчике прерывания дергать ножкой. Конечно, поморгать светиком не получится — слишком высокая частота (20 кГц) чтобы увидеть отдельные вспышки. Но у тех, у кого имеется осциллограф, проблемы убедиться в правильности работы программы — не возникнет.

Однако, нам еще нужно было получить пару нарезок времени — 200 мкс и на 1 с. Для этого давайте изменим код в обработчике прерывания от таймера следующим образом:

// Для использования таких типов как uint8_t допишите этот инклюд:
#include <stdint.h>

...

volatile uint8_t g_1s;  // Флаг односекундных интервалов

// Прерывание возникает каждые 50 мкс
ISR(TIMER0_COMPA_vect)
{
  static uint8_t  counter_50us;
  static uint16_t counter_200us;

  if (++counter_50us >= 4)
  { // Прошло 200 мкс
    counter_50us = 0;
    // Тут нужно что-то сделать, только очень быстро.
    // Например, запускаем АЦП
    ADCSRA |= _BV(ADSC);

    if (++counter_200us >= 5000)
    { // Прошла одна секунда
      counter_200us = 0;
      // Тут нужно что-то сделать, только очень быстро.
      // Например, устанавливаем флаг секундного интервала
      g_1s = 1;
    }
  }
}

Ну, алгоритм получения отметок времени, надеюсь, понятен. Поясню только пару моментов. Во первых, я здесь не показал инициализацию АЦП, понятно, что прежде чем запустить преобразование, нужно сначала проинициализировать АЦП. Во вторых, флаг односекундных интервалов времени должен обрабатывается в главном цикле программы примерно так:

int main(void)
{
  ...
  g_1s = 0;
  sei();      // Разрешаем прерывания глобально и ...
  while (1)   //   понеслась!
  {
    // Тут выполняем фоновую работу
    if (g_1s == 1)
    {
      g_1s = 0;
      // Тут можно выполнять длительные по времени операции.
      // Можно вызывать другие функции.
    }
  }

  return 0;

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

Почему не желательно навешивать на обработчик прерывания много вычислений?

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

Ну, например, на моей практике был случай, когда руководство предприятия, где я работал, вместо того чтобы заплатить мне денег за разработку стенда для проверки и регулировки серийных изделий, решило отдать это дело на аутсорс (разработка и изготовление на стороне). В результате все четыре стенда работали через пень-колоду и часто выходили из строя.

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

Анализ схемы стенда, однако, показал, что сигнал с датчика (с коллектора транзистора) заводился на вход прерываний МК. Все вроде бы грамотно — как только возникает перегрузка потоку, срабатывает датчик, возникает прерывание, а обработчик прерывания отключает источник питания.

Но дело в том, что в МК это был не единственный обработчик прерывания! Если бы он был единственным, то проблемы бы не было! На всё про всё по очень приближенным оценкам должно было бы уходить от 1-2 до 10-20 мкс. За это короткое время схема источника питания не успеет разрушится. Но в стенде было несколько прерываний…

Для большей радости разработчиков, у ATMEGA отсутствует механизм приоритетов прерываний — они все одинаковые. Поэтому, пока не отработает одно, то есть — не закончит работу один обработчик прерывания, второй обработчик прерывания не сможет получить управление. Чуете, как интересно работать с AVR-ками?

Я не знаю, что там в стендах конкретно происходило, но стенды большую часть времени находились в ремонте, а не в работе.

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

Да пребудет с вами Дух Великого Джа!

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s