PIC мк. Эксперимент №11. Модуль ШИМ.

Продолжим изучать модуль CCP и остановимся на самом интересном применении ШИМ.

Задача: регулировка яркости светодиода с помощью ШИМ.

Исходный материал: PIC16f628a и простенькая devboard + proteus.

pwm

Понять принцип работы ШИМа помогает его блок-схема:

pw

За период ШИМа отвечает регистр PR2, когда таймер TMR2 дотикает до значения регистра PR2 на выходе устанавливается высокий уровень (одновременно с обнулением таймера).

В то же время содержимое регистра CCPR1L + два бита CCP1X, CCP1Y копируются в регистр CCPR1H, когда таймер дотикает до значения в этих регистрах происходит сброс и выход переходит в 0.

Все логично и просто.

Рассмотрим базовую настройку. Для настройки ШИМа нужно знать два следующих уравнения:

Период ШИМ = (PR2 + 1)⋅4⋅Tosc⋅TMR2_precaller_value

Ширина импульса  = (CCPR1L:CCP1CON<5:4>)⋅Tosc ⋅ TMR2_precaller_value

В даташите подробно описан поэтапный способ настройки, приведу его еще и здесь:

1.  Установка периода ШИМ посредством записи в регистр PR2.

2.  Установка скважности – регистр CCPR1L и CCP1CON<5:4>

3. Определить RB3 как выходной порт

4. Задать коэффициент деления предделителя таймера и включить таймер

Перейдем к конкретным действиям:

Задача: Регулировка яркости светодиода с помощью ШИМ

Параметры нашего эксперимента:

  • Частота ШИМ – 2 КГц
  • Скважность меняется в 8 раз (от 0 до 100%)
  • Скважность регулируется посредством кнопок

Чтобы получить такую частоту, в PR2 должно быть записано число 124  (коэффициент деления предделителя таймера установим равным 4).

Ну и ради примера расчета скважности, рассчитаем значение CCPR1L:CCP1CON<5:4> для 50% (50% от 500 = 250) :

250u = 4*0.25u*x => x = 250 => CCPR1L = 0b00111110, CCP1X = 1, CCP1Y = 0

Вот и все расчеты, ниже привожу мой код:

#include
#define _XTAL_FREQ 4000000

__CONFIG(WDTDIS & UNPROTECT & MCLREN & LVPDIS & HS);

void pwm() {
unsigned char x = 0;
unsigned char state = 1;

while (state) {
if (!RB0) {
__delay_ms(100);
if (!RB0) {
if (x > 0) { x--;} else x = 0;
}
}

if (!RB1) {
__delay_ms(100);
if (!RB1) {
if (x < 8) { x++;} else x = 8; } } if (!RB2) {                               // Выход из прерывания (в данном контексте можно и не делать) __delay_ms(100); if (!RB2) { state = 0; INTF = 0; } } switch (x) { case 0:                     // 0% CCPR1L = 0; CCP1X = 0; CCP1Y = 0;  break; case 1:                     // 12.5% CCPR1L = 0b00001111; CCP1X = 1; CCP1Y = 0;  break; case 2:                     // 25% CCPR1L = 0b00011111; CCP1X = 0; CCP1Y = 0;  break; case 3:                     // 37.5% CCPR1L = 0b00101110; CCP1X = 1; CCP1Y = 1;  break; case 4:                     // 50% CCPR1L = 0b00111110; CCP1X = 0; CCP1Y = 1;  break; case 5:                     // 62.5% CCPR1L = 0b01001110; CCP1X = 0; CCP1Y = 0;  break; case 6:                     // 75% CCPR1L = 0b01011101; CCP1X = 1; CCP1Y = 0;  break; case 7:                     // 87.5% CCPR1L = 0b01101101; CCP1X = 0; CCP1Y = 1;  break; case 8:                     // 100% CCPR1L = 0b011111100; CCP1X = 1; CCP1Y = 1;  break; } } } void main() { PORTB = 0; TRISB = 0b00000111; PR2 = 0b01111100; CCPR1L = 0;                               // По умолчанию 0% CCP1X = 0; CCP1Y = 0; CCP1M3 = 1;                               // Включаем ШИМ CCP1M2 = 1; T2CKPS0 = 1; T2CKPS1 = 0;                 // Предделитель на 4 TMR2ON = 1;                               // Включаем 2 таймер GIE = 1; INTE = 1; for (;;) { // Пустой бесконечный циклл } } void interrupt isr() {                    // Прерывание по RB0 if (INTF) { pwm(); } } [/code] Осциллограма с 50% скважностью (1 деление = 1 мс) oscill

Видео со светодиодом :)

+ регулировка ШИМ на осциллографе

Исходники здесь

PIC мк. Эксперимент №11. Модуль ШИМ.: 38 комментариев

  1. Ширина импульса = (CCPR1L:CCP1CON<5:4>)⋅Tosc ⋅ TMR2_precaller_value

    500us – период нашего ШИМ, здесь я делал скважность 50%, поэтому считаем ширину импульса равной 250 us

    250u = 4*0.25u*x => x = 250 => CCPR1L = 0b00111110, CCP1X = 1, CCP1Y = 0

    где 0.25 us – значение таймера2 после предделителя (считает по 1 мкс)

    Формулы приведены в даташите

  2. Hi, вопрос такой: разбираю уже несколько примеров, каждый раз одно и тоже – pic с compiler не знает, что такое TRISA, PORTB. подключаю файлы .h именно для 16F877a, в них тоже эти переменные не описываются. Думаю, может проблема в htc.h, у меня его нигде нет и не знаю где взять. Может кто-нить чем-нибудь помочь, объяснить?

  3. Второй вечер разбираюсь с ШИМ, кстати, спасибо всем, за уже оказанную помощь, вопрос такой, при скважности 5, такой же частоте(2кГц),
    (20% от 500=100)->100u = 4*0.25u*x => x = 100 => CCPR1L = 0b1100100 и как определить CCP1X , CCP1Y ?

    • Переделал код, чтобы сразу была 25% скважность, без кнопок. Загрузил в протеусе на picdem2 с pic16F877a. С RB3 ничего не снимается, код такой:

      #include
      #define _XTAL_FREQ 4000000
      //_CONFIG(WDTDIS & UNPROTECT & MCLREN & LVPDIS & HS);
      void main(void)
      {
      PORTB= 0;
      TRISB= 0b00000111;
      PR2= 0b01111100;

      CCPR1L = 0b00011111;
      CCP1X = 0;
      CCP1Y = 0;

      CCP1M3 = 1;
      CCP1M2 = 1;
      T2CKPS0 = 1; T2CKPS1 = 0;
      TMR2ON = 1;

      GIE = 1;
      INTE = 1;
      for (;;){}

      }
      Подскажите в чем проблема, пжлст.

      • 1. Почему инклюд пустой?
        2. Почему конфигурация закоменчена?
        3. Прерывания вам не нужны, их можно отключить.

        p.s. не забыли ли вы в протеусе в свойствах указать частоту кварца?

        • у меня pic.h в include, частоту 4 МГц выставил в протеусе, конфиг закоменчен, потому что mplab с ним не компилит, пробовал через #fuses прописать, тоже без толку.

          • Блин, не заметил какой у вас камень.
            Конечно вы ничего не увидите, на 877 выходы шима на rc порту а не на RB.
            Ставьте 628a и все будет работать.

  4. Добрый вечер! Хочу сделать генератор синуса на 16f676, в нем нет модуля шим, пробовал пользоваться просто задержками __delay_us(…) получается при 200 точках на период у меня не хватает памяти в 1к. Тогда я попробовал взять массив с переменной sin[i]{…….} в котором поочередно хотел записать длительности пауз и единиц, хотя бы на пол периода. Потом в цикле for(i=0;i<99;i++){__delay_us(i);RC1=~RC1;} а потом соответственно вторую полу волну. Но компилятор ругается inline delay argument must be constant,
    can't generate code for this expression
    Теперь думаю может значение из массива подставлять в регистры таймера чтоб на нем организовать шим для синусойды.
    Как посоветуете быть?

    • Посоветую купить контроллер с ШИМ.
      Серьезно, зачем изобретать велосипеды?

      Еще можно посмотреть на другие способы временной импульсной модуляции, там вроде есть какая-то простая основанная на двоичном коде. Только подойдет ли она для генерации синуса…

      • C Новым годом!
        Взял 12F683, 8 ног и есть шим. Благодаря вашему живому примеру и даташиту разобрался как делать шим на НЁМ. Только не представляю, как можно менять ширину импульса с каждым последующим периодом. Для начала возьмем 100 импульсов, у первого я выставил ширину 3us(может работать сколь угодно долги), а как привязать момент смены ширины импульса к моменту начала следующего импульса? Логично думать что можно было бы опрашивать флаг но в шиме по моему он не выставляется.

        • Вас также с наступившим!
          В моем представлении это вроде как не сложно делается- по идее привязываться надо к щелчкам таймера, нужно отсчитать весь цикл шима, то есть для когда TMR2=PR2, период окончен, можно наверное даже к прерываниям таймера подвязаться.

          • Получается что сам шим как бы мне и не нужен, снова возвращаемся к таймерам… Но для начала попробую именно это сравнение в коде и железе.

            • Блин если так рассуждать то модуль шима и есть ни что иное как железные операции с таймером. Просто за вас эти функции уже реализованы и вы стабильно можете получить 1024 уровня, в чем сложность контролировать один флаг и менять значение регистра ccp?

              • Я просто не знаю как это будет. Такой код жизнеспособен?

                [/
                #include //PIC12F683
                __CONFIG(MCLREN & WDTDIS & HS & UNPROTECT); // Program config. word 1
                #define _XTAL_FREQ 4000000
                unsigned char sin_1[50]={3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,50,53,56,58,61,63,66,68,
                70,75,77,79,80,82,84,86,87,89,90,91,92,94,95,96,96,97,98,98,99,99,99,99,99,99}; // таблица синусойды

                void main (void) {
                __delay_ms(5);
                ANSEL = 0; //Отключаем аналоговые входы
                TRISIO = 0x00; //
                GPIO2= 0x00; //
                PR2 = 99; //Период PR2=(Tшим/4*0.25*1)-1
                CCPR1L = 1;//98; // Определяем ширину импульса в ШИМ в моем случае в десятичной форме задаем ширину в микросекундах
                DC1B1=0; //CCP1X = 0; //
                DC1B0=0; //CCP1Y = 0; //
                CCP1M3 = 1; // Включаем ШИМ CCP1CON бит3 и бит4 = 1
                CCP1M2 = 1;
                T2CKPS1 = 0; T2CKPS0 = 0; // Предделитель 0:0=1 0:1=4 1:1=16
                TMR2ON = 1; // Включаем 2 таймер
                GPIO2=0;
                unsigned char x;
                unsigned char i;
                for(;;) // Запускаем бесконечный цикл
                {
                for(i=0;i<50;i++)
                {
                if(TMR2==PR2)
                {x=sin_1[i];
                CCPR1L = x;
                }
                }
                }
                } //main end
                ]

                Правда пик кит 2 как лог.анализатор показывает одно значение ширины импульса 44us. Хотя при таблице всего из пяти значений работает правильно, почти. Каждый импульс повторяется по три раза.

                • А где сброс значения таймера тмр2? Да и я бы через прерывания делал всетки + на 100 мкс не получится 50 герцовая синусоида с разрением 255, так что я бы кварц пошустрее сразу заложил.

                  • Пока нет кварца на 20, найду обязательно попробую. А по пдфу если все правильно понял флаг выставляется и сбрасывается при совпадение на компараторе, с учетом посткаллера.
                    Что то не понял, почему не получится 50 герцовая!? 100 дискретов по 100us на один полупериод и 100 на 100 на другой, и того 20ms – мои 50Гц. На самом деле остается очень мало времени на перезапись CCPRL1 когда ширина импульса у нас достигает максимума 98, 99 us.
                    А обработку в прерывании делать по переполнению тмр2?

                    • Что ж у вас так мозг бурно после нг работает 🙂
                      Я не успеваю отвечать с телефона 🙂
                      Да, посмотрел блок диаграммку, на самом деле регистр чистится и я раньше точно также делал.
                      Не получится синусоида с разрешением 255 я писал, 100 кратная да, получится, я почему то думал, что вы хотите 255 уровней.
                      Обработку да, я бы по прерыванию тмр2 делал.
                      А в протеусе не пробовали код проверять?

                    • Попробовал по быстрому собрать генератор треугольника, с периодом максимальным 256 мкс – https://www.dropbox.com/s/48bwdz4fyp85kqa/Sine_Wave.rar

                      Чтобы не подгенеривало пришлось фильтр запускать подальше и, таким образом, подрезать максимальные размахи, получилось по 0.5 вольт меньше сверху и снизу.
                      В архиве файлы проекта, исходники и протеусовский файл.

                    • Короче мои нервы не выдержали и я еще чутка поковырялся.
                      В итоге пришел к тому, что на малых значениях скважности, минимальные напряжения бывают неадекватны (по крайней мере в протеусе), поэтому поднял нижнюю границу повыше, тем более, что по вашей задумке все равно напряжение надо в 12 вольт конвертить.
                      Получился примерно такой код:

                      #include <htc.h>
                      #define _XTAL_FREQ 4000000
                      
                      volatile unsigned char count = 50;
                      volatile bit sign = 0;
                      //__CONFIG(WDTE_OFF & CP_OFF & LVP_OFF & MCLRE_OFF);
                      __CONFIG(WDTDIS & UNPROTECT & LVPDIS & MCLRDIS);
                      
                      void main()
                      {
                      TRISB = 0x00;
                      
                      PR2 = 0b11111111;
                      
                      CCPR1L = count;                               // По умолчанию 0%
                      CCP1X = 0;
                      CCP1Y = 0;
                      
                      CCP1M3 = 1;                               // Включаем ШИМ
                      CCP1M2 = 1;
                      T2CKPS0 = 0; T2CKPS1 = 0;                 // Предделитель на 4
                      TMR2ON = 1;                               // Включаем 2 таймер
                      TMR2IE = 1;
                      GIE = 1;
                      PEIE = 1;
                      while (1);
                      
                      }
                      
                      void interrupt isr()
                      {
                      
                          if (TMR2IF)
                          {
                              TMR2ON = 0;
                              if ((!sign) && (count < 255))
                                  count++;
                              else
                              {
                                  if ((!sign) && (count >= 255))
                                  {
                                      sign = 1;
                                      count--;
                                  }
                              }
                              if ((sign) && (count > 80))
                                  count--;
                              else
                              {
                                  if ((sign) && (count == 80))
                                  {
                                      sign = 0;
                                      count++;
                                  }
                              }
                      
                              CCPR1L = count;
                              TMR2IF = 0;
                              TMR2ON = 1;
                          }
                      
                      }
                      

                      Далее с фильтром чутка помудрил, обычный RC дает нормальное подавление при слишком большой временной постоянной, поэтому взял за основу активный фильтр.
                      А вот как выглядит наш шим сигнал, после прохождения через этот фильтр.

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

                    • Неа, задумку не угадал. Щас примерную схему на почту кину. Вчера пол вечера опробовал ваш код (большое спасибо!) только с подстановкой ширины импульсов из таблицы.При периоде шим 100us на ста дискретах общая ширина полу волны уходит до 12,5ms. Ну это ничего подправим. Хотел увидеть красивую синусоиду в железе на осциллографе, а получил как после диодного моста, полу волны в одну сторону. Хотя для моей мостовой схемы как раз то и надо будет, только на разных ногах.

  5. У меня конкретный вопрос в том, могу ли я в подпрограмму __delay_US() вставить переменную, потому что в CCS-PIC этот номер проходил.

    • Ну написано же, что константу надо вставлять. Если хотите юзать именно этот макрос напишите функцию какого-нибудь такого вида:

      void DelayUs(unsigned char us)
      {
      unsigned char i = 0;   
        for (i=0;i<=us;i++)
            __delay_us(1);
      }
      

      Конечно интересно, что там в этом случае будет с точностью такого периода.
      В любом случае я бы лучше пытался это провернуть с помощью регистров таймера.

  6. Здравствуйте Sarge, спасибо большое за ваши эксперименты, очень полезный и качественный материал делаете. Хотел немного поправить немного вашу программу, у вас закралась одна мелкая ошибочка условии работы кнопки увеличения скважности :
    if (!RB1) {
    __delay_ms(100);
    if (!RB1) {
    if (x < 9) { x++;} else x = 8; // вот тут надо "if (x < 8) { x++;} else x = 8; "
    }
    }

    И я бы рекомендовал в таких случаях использовать if с двойным условием, пример:
    if (RB1=1 && x < 8)
    {
    __delay_ms(100);
    x++;}

    • Доброго времени суток!

      Баг репорт принят 🙂 Вы правы, ваш вариант правильный. Переделываю в статье сейчас и на репе.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.