Реклама ⓘ
Главная » Микроконтроллеры
Призовой фонд
на апрель 2024 г.
1. 100 руб.
От пользователей

Похожие статьи:


Реклама ⓘ

mikroPascal for AVR. Урок 8. Программный ШИМ

В прошлой статье-уроке была затронута тема ШИМ. А точнее - использование аппаратного ШИМ в МК семейства AVR в программах, написанных в mikroPascal. Но, как вы уже заметили, число каналов аппаратно реализованных ШИМ сильно ограничено. В лучшем случае, их количество около шести. Конечно, если вам нужно всего лишь регулировать яркость небольшого количества светодиодов, то хватит и их. Но если нужно большее количество каналов (например, если захотите на основе МК сделать трехфазный генератор)? Вот тут и пригодится программный ШИМ.

В предыдущих статьях уже проскакивало понятие "программный ШИМ", но я на нем не останавливался. Сейчас постараюсь восполнить этот пробел.

Как и аппаратный, программный ШИМ так же работает за счет таймеров МК. Но в отличии от него, проверяется на совпадение не регистр МК, а какая-то произвольная переменная.

Реализовать программный ШИМ можно по-разному. Например, в прерывании по переполнению таймера просто инкрементировать значение счетной переменной, а уже в основной программе проверять на совпадение с заданным значением. Если же у вас программа не из простых, то лучше это делать в прерывании. В случае простой программы, не перегруженной задержками, можно все действия перенести в главный цикл, и отказаться от использования прерываний. Как будет лучше поступить в вашем случае, решать вам, в этом уроке я постараюсь показать различные реализации программного ШИМ а так же библиотеку для простой работы с ним.

Начнем с простого: программный ШИМ, сформированный в главном цикле программы. Будем управлять яркостью светодиода (к сожалению, у меня сечйчас нет под рукой макетной платы, так что все испытания будут проходить в Proteus 8.2).

Схема предельно проста: микроконтроллер, светодиод и токоограничительный резистор (хотя в симуляторе он не очень то и нужен).

На предполагаемый выход ШИМ я "подцепил" канал А виртуального осциллографа, так как наблюдать изменение яркости светодиода у нас не получится, в силу некоторых особенностей Proteus.

Код, загруженный в МК, выглядит так:

program _demo_pwm_light;

var set_duty, n: byte;
begin
     PORTB := 0xff;   //Настраиваем порт
     DDRB := 0xff;
     
     set_duty := 35;  //Задаем длительность
     
     while TRUE do
     begin
          if (set_duty = 0) then  //Проверка на граничные значения
             PORTB.0 := 0
          else
          if (set_duty = 255) then  // ---//---
             PORTB.0 := 1
          else
          if (set_duty <= n) then  //Если нам не нужна инверсия, ставим set_duty > n
             PORTB.0 := 1
          else
              PORTB.0 := 0;

          inc(n);
     end;
end.

Логика программы предельно проста. Первым делом настраиваем порт, далее нам необходимо создать бесконечный цикл, в котором мы и будем имитировать ШИМ. Проверки на совпадение с 0 и 255 необходимы для предотвращения неприятных моментов, когда вроде бы сигнала быть не должно, а он есть. Сам сигнал генерируется следующим образом (возможно, мои слова не совсем соответствуют истине, но постараюсь объяснить так, как сам это представляю):

Верхняя строка соответствует переменной n = 255. Нижняя - переменной set_duty = 120. Для получения ШИМ сигнала нам необходимо увеличивать в цикле значение перменной n от 0 до 255. Пока переменная n > set_duty, на выходе низкий логический уровень, как только n > set_duty, то устанавливаем высокий лог. уровень. После обнуления переменной n все повторяется и т.д. Тоесть для генерации ШИМ сигнала нам требуется проверять переменные на совпадение и устанавливать лог. уровни в зависимости от результата.

Вот скриншот полученной осциллограммы:

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

В целом, отличий между первым и вторым примером не много. Главное из них - это использования прерывания по переполнению таймера T0. В остальном же, полная аналогия с предыдущим примером.

Ниже привожу код второго примера:

program _demo_pwm_interrupt;

var set_duty, n: byte;

procedure _TIMER0(); iv IVT_ADDR_TIMER0_OVF; ics ICS_AUTO;
begin
     //Мы просто взяли кусок кода из прошлого примера
     if (set_duty = 0) then  //Проверка на граничные значения
        PORTB.0 := 0
     else
     if (set_duty = 255) then  // ---//---
        PORTB.0 := 1
     else
     if (set_duty < n) then  //Если нам не нужна инверсия, ставим set_duty > n
        PORTB.0 := 1
     else
         PORTB.0 := 0;

     inc(n);
end;

begin
     PORTB := 0xff;
     DDRB := 0xff;
     
     TCCR0 := %00000001; //Настраиваем предделитель таймера (1:1)
     TIMSK := %00000001; //Разрешаем прерывания по переполнению T0
     SREG_I_bit := 1; //Глобальное разрешение прерываний
     
     while TRUE do
     begin
          set_duty := 100; //Тут можно дать волю фантазии =)
          delay_ms(1000);
          set_duty := 200;
          delay_ms(1000);
          set_duty := 30;
          delay_ms(1000);
     end;
end.

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

Вот результат выполнения этой программы:

Похожим образом можно сделать и многоканальный ШИМ.

Хочу представить вашему вниманию библиотеку для mikroPascal foe AVR , которая позволяет использовать программный ШИМ. На ее примере и будет рассмотрена возможность создания многоканального ШИМ. Файл библиотеки прикреплен в конце статьи.

Посредством этой библиотеки возможно реализовать программный ШИМ на несколько каналов. Что немаловажно, для ее использования совершенно что-либо знать о прерываниях, достаточно всего лишь убедиться, что у вашего микроконтроллера имеется Таймер/Счетчик 0 (пока что реализовано для микроконтроллеров ATmega8, 48 и т.д. , регистры Таймера/Счетчика 0 не отличаются), и он не используется. Сверх этого, придется еще посчитать, на какой частоте будет работать ШИМ. Но об этом несколько позже.

Первое, что бросается в глаза при открытии файла libpwm.pas (файл библиотеки), это большое количество меток.

////////////////////////////////////////////////////////////////////////////////
var CH0: sbit at PORTB.0;                                                     //
var CH1: sbit at PORTB.1;                                                     //
var CH2: sbit at PORTB.2;                                                     //
var CH3: sbit at PORTB.3;                                                     //
var CH4: sbit at PORTB.4;                                                     //
var CH5: sbit at PORTB.5;                                                     //
var CH6: sbit at PORTB.6;                                                     //
var CH7: sbit at PORTB.7;                                                     //
                                                                              //
var CHD0: sbit at DDRB.0;                                                     //
var CHD1: sbit at DDRB.1;                                                     //
var CHD2: sbit at DDRB.2;                                                     //
var CHD3: sbit at DDRB.3;                                                     //
var CHD4: sbit at DDRB.4;                                                     //
var CHD5: sbit at DDRB.5;                                                     //
var CHD6: sbit at DDRB.6;                                                     //
var CHD7: sbit at DDRB.7;                                                     //
////////////////////////////////////////////////////////////////////////////////

Это сделано для упрощения обращения к портам, а так же для универсальности. Достаточно изменить строки

var CH0: sbit at PORTB.0 
.....
var CHD0: sbit at DDRB.0

на следующие

var CH0: sbit at PORTC.0
...
var CHD0: sbit at DDRC.0

как для первого канала ШИМ будет использоваться не нулевой бит порта B, а аналогичный бит порта C. Как уже стало понятным из вышесказанного, для использования библиотеки, необходимо как минимум проверить (если вас устраивает использование порта B), есть ли порт, указанный в заголовке, и свободен ли он.

Следующий шаг - это прерывание по переполнению Таймера/Счетчика 0. В нем и происходит обработка и вывод ШИМ сигнала.

procedure _TIMER0(); iv IVT_ADDR_TIMER0_OVF; ics ICS_AUTO;
begin
     if (CH_array[n] = 0) then
     begin
          case n of
          0: CH0 := 0;
          1: CH1 := 0;
          2: CH2 := 0;
          3: CH3 := 0;
          4: CH4 := 0;
          5: CH5 := 0;
          6: CH6 := 0;
          7: CH7 := 0;
          end;
     end
     else
     if (CH_array[n] = 255) then
     begin
          case n of
          0: CH0 := 1;
          1: CH1 := 1;
          2: CH2 := 1;
          3: CH3 := 1;
          4: CH4 := 1;
          5: CH5 := 1;
          6: CH6 := 1;
          7: CH7 := 1;
          end;
     end
     else
     begin
          case n of
          0: if (CH_array[n] > count) then CH0 := 1 else CH0 := 0;
          1: if (CH_array[n] > count) then CH1 := 1 else CH1 := 0;
          2: if (CH_array[n] > count) then CH2 := 1 else CH2 := 0;
          3: if (CH_array[n] > count) then CH3 := 1 else CH3 := 0;
          4: if (CH_array[n] > count) then CH4 := 1 else CH4 := 0;
          5: if (CH_array[n] > count) then CH5 := 1 else CH5 := 0;
          6: if (CH_array[n] > count) then CH6 := 1 else CH6 := 0;
          7: if (CH_array[n] > count) then CH7 := 1 else CH7 := 0;
          end;
     end;
     if (n < n_ - 1) then
        inc(n)
     else
         n := 0;
     inc(count);
end;

Выглядит она несколько громоздко, но на самом деле делится на три блока, из которых выполняется всегда только один (конструкции if...else). Первоначално, происходит проверка на граничные значения(0, 255), это сделано для предотвращения появления лог. единицы или нуля на выходе, когда их там быть не должно. Массив "CH_array[]" является образцом - с ним сравнивается значения переменной - счетчика "count". Пока значение соответствующего элемента массива меньше счетчика, то на выходе будет лог. ноль, в противном случае - единица (это не считая граничных значений, когда элемент массива равен нулю или 255). Так как переменная "count" является 8-ми битной, то ее нет смысла обнулять, при ее переполнении (после значения 255) она вновь равна 0.

Это были основные моменты, все остальное является лишь "оберткой".

procedure sPWM_init(clock_div: word; n: byte);
var i: byte;
begin
     n_ := n;
     for i := 0 to n_ - 1 do
     begin
          case i of
          0: CHD0 := 1;
          1: CHD1 := 1;
          2: CHD2 := 1;
          3: CHD3 := 1;
          4: CHD4 := 1;
          5: CHD5 := 1;
          6: CHD6 := 1;
          7:CHD7 := 1;
          end;
     end;
     //
     case clock_div of
          1: TCCR0 := %00000001;
          8: TCCR0 := %00000010;
          64: TCCR0 := %00000011;
          256: TCCR0 := %00000100;
          1024: TCCR0 := %00000101;
     end;
     TOIE0_bit := 1;
end;

procedure sPWM_set_duty(channel, duty: byte);
begin
     CH_array[channel] := duty;
end;

procedure sPWM_start();
begin
     SREG_I_bit := 1;
end;

procedure sPWM_stop();
begin
     SREG_I_bit := 0;
end;

procedure sPWM_destroy();
var i: byte;
begin
     for i := 0 to n_ - 1 do
     begin
          case i of
          0: CHD0 := 0;
          1: CHD1 := 0;
          2: CHD2 := 0;
          3: CHD3 := 0;
          4: CHD4 := 0;
          5: CHD5 := 0;
          6: CHD6 := 0;
          7: CHD7 := 0;
          end;
     end;
     //
     TCCR0 := %00000000;
     TOIE0_bit := 0;
end;

Еще заслуживают внимания только две процедуры: "procedure sPWM_init(clock_div: word; n: byte)", и "procedure sPWM_destroy".

Первая , как понятно из названия, отвечает за инициализацию ШИМ, вторая за его "уничтожение". Конечно, о никаком реальном уничтожении речь не идет, просто при ее вызове отключается предделитель Таймера/Счетчика 0, сбрасываются каналы и отключается прерывание по переполнению Т0. Это может быть полезным, если вам в программе стал необходим Т0.

Что касается "sPWM_init(clock_div: word; n: byte)", то тут также все просто. С помощью цикла for подключается нужное число каналов, а следом и предделитель к Таймеру/Счетчику 0.

Собственно, вот и все библиотека. На закуску мизерная программка с использованием libpwm.pas:

program _demo_pwm;

uses libpwm;

begin
          sPWM_init(1, 6);
          sPWM_start();
          sPWM_set_duty(0, 100);
          sPWM_set_duty(1, 200);
          sPWM_set_duty(2, 100);
          sPWM_set_duty(3, 200);
          sPWM_set_duty(4, 100);
          sPWM_set_duty(5, 200);
          delay_ms(5000);
          sPWM_stop();
          delay_ms(1000);
          sPWM_destroy();
end.

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

Результат:

Спасибо за внимание! Надеюсь, эта статья хотя бы немного вам помогла!

Прикрепленные файлы:

Теги:

Опубликована: 0 1
Я собрал 0 0
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 0 Проголосовало: 0 чел.

Комментарии (6) | Я собрал (0) | Подписаться

0
Александр #
Не шим работает не так. Счётчик досчитывает до максимума, а потом уходит в минимум. Поэтому частота шима получится = кварц/предделитель счётчика/разрядность шим/2
Ответить
0

[Автор]
zeconir #
Вы описали Phase Correct PWM (для AVR), а Fast PWM работает по принципу 0-x-0-x-...-0-x, где x - значение регистра сравнения.
Ответить
0
Brumel #
Я новичок, паяльник в руках держать умею и в коде немного понимаю что тут написано и как работает. Но встает такой вопрос есть atmega8. Я хочу сделать моргалку при этом используя все порты для шим с сохранением spi программирования. Как я понимаю СН это что то типа переменной которую где-то объявили значит для того, чтобы заработали еще 2 порта надо объявить где то еще переменные ну например CJ и CK. И дописать как на скрине ну и там далее в коде чтоб он стал в 3 раза больше?
Прикрепленный файл: Безымянный.png
Ответить
0
Матин Алексей #
Здравствуйте, zeconir! Спасибо Вам за эти уроки, из них я действительно почерпнул кое-что полезное! У меня есть несколько вопросов:

1. Где можно скачать дополнительные библиотеки для MicroPascal (бес###тно и не раздавая номер мобилки)? Может быть, энтузиасты этой среды уже чего-то наваяли, и готовы поделиться?
2. Если доп. библиотеки уже скачанны, то как их установить, чтобы они были видны в Library Manadger и готовы к использованию (у меня стоит MicroPascal v 1.25)?
3. И немного не по теме, но все же - где-нибудь можно скачать дата-шиты к МК на русском языке (на тех же условиях, что и библиотеки в вопросе №1)? Конкретно, интересуют модели Tiny2313, Tiny13, Mega8. У меня есть один такой дата-шит, очень качественно сделанный перевод фирменного на AT90S1200 (авторы - Ю.Андриенко и А.Труш); но этот проц уже порядком "поседел" - может, есть переводы для более новых чипов? Не то, чтобы я вообще плаваю в английской документации, но своя-то речь ближе...
Ответить
0
Alexandr #
Можно задать вопрос не по теме?
Можно ли к проекту в микропаскале прикрутить библиотеку на "си"?
Ответить
0

[Автор]
zeconir #
Я не встречал такой возможности...
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется электрическое сопротивление?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

AVR-программатор USB ASP
AVR-программатор USB ASP
Тестер ESR, полупроводников, резисторов, индуктивностей Солнечная панель 10Вт 12В поликристаллическая
вверх