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 <htc.h>
#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();
}
}

Осциллограма с 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, у меня его нигде нет и не знаю где взять. Может кто-нить чем-нибудь помочь, объяснить?

    Ответить

    sarge Ответил:

    Это заголовочный файл из папки Hi-tech picc compiler, если вы не знаете где его взять – значит у вас другой компилятор :)

    Ответить

    Alex Sedov Ответил:

    Спасибо) значит проблема в прокладке между монитором и стулом.

    Ответить

    sarge Ответил:

    Наиболее распространенная проблема :)

    Ответить

    electronic255 Ответил:

    подключи вместо htc.h – pic.h он точно должен быть

    Ответить

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

    Ответить

    sarge Ответил:

    В CCPR1L расположена старшая часть байта, а значит для x = 100:

    CCPR1L = 0b00011001
    CCP1X = 0
    CCP1Y = 0

    Ответить

    Alex Sedov Ответил:

    Переделал код, чтобы сразу была 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 (;;){}

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

    Ответить

    sarge Ответил:

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

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

    Ответить

    sarge Ответил:

    о инклюд у меня в коде пустой, а вы скопировали, там должно быть

    #include

    Ответить

    Alex Sedov Ответил:

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

    Ответить

    sarge Ответил:

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

    Ответить

    Alex Sedov Ответил:

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

    Ответить

  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
    Теперь думаю может значение из массива подставлять в регистры таймера чтоб на нем организовать шим для синусойды.
    Как посоветуете быть?

    Ответить

    sarge Ответил:

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

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

    Ответить

    Alexey Ответил:

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

    Ответить

    sarge Ответил:

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

    Ответить

    Alexey Ответил:

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

    Ответить

    sarge Ответил:

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

    Ответить

    Alexey Ответил:

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

    [/
    #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. Хотя при таблице всего из пяти значений работает правильно, почти. Каждый импульс повторяется по три раза.

    Ответить

    sarge Ответил:

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

    Ответить

    Alexey Ответил:

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

    Ответить

    sarge Ответил:

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

    Ответить

    Alexey Ответил:

    А че ещё делать, столько я не выпиваю :)

    Ответить

    sarge Ответил:

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

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

    Ответить

    sarge Ответил:

    Короче мои нервы не выдержали и я еще чутка поковырялся.
    В итоге пришел к тому, что на малых значениях скважности, минимальные напряжения бывают неадекватны (по крайней мере в протеусе), поэтому поднял нижнюю границу повыше, тем более, что по вашей задумке все равно напряжение надо в 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 дает нормальное подавление при слишком большой временной постоянной, поэтому взял за основу активный фильтр.
    А вот как выглядит наш шим сигнал, после прохождения через этот фильтр.

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

    Ответить

    Alexey Ответил:

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

    Ответить

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

    Ответить

    sarge Ответил:

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

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

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

    Ответить

    Alexey Ответил:

    Спасибо!

    Ответить

  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++;}

    Ответить

    sarge Ответил:

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

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

    Ответить

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

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

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">