Продолжим изучать модуль CCP и остановимся на самом интересном применении ШИМ.
Задача: регулировка яркости светодиода с помощью ШИМ.
Исходный материал: PIC16f628a и простенькая devboard + proteus.
Понять принцип работы ШИМа помогает его блок-схема:
За период ШИМа отвечает регистр 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 мс)Видео со светодиодом :)
+ регулировка ШИМ на осциллографе
Исходники здесь
откуда взято число 500?
500 мкс это период частоты 2 КГц
если честно -расчет слабо понятный ,можно как нибудь по подробнее?
Ширина импульса = (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 мкс)
Формулы приведены в даташите
Hi, вопрос такой: разбираю уже несколько примеров, каждый раз одно и тоже – pic с compiler не знает, что такое TRISA, PORTB. подключаю файлы .h именно для 16F877a, в них тоже эти переменные не описываются. Думаю, может проблема в htc.h, у меня его нигде нет и не знаю где взять. Может кто-нить чем-нибудь помочь, объяснить?
Это заголовочный файл из папки Hi-tech picc compiler, если вы не знаете где его взять – значит у вас другой компилятор 🙂
Спасибо) значит проблема в прокладке между монитором и стулом.
Наиболее распространенная проблема 🙂
подключи вместо htc.h – pic.h он точно должен быть
Второй вечер разбираюсь с ШИМ, кстати, спасибо всем, за уже оказанную помощь, вопрос такой, при скважности 5, такой же частоте(2кГц),
(20% от 500=100)->100u = 4*0.25u*x => x = 100 => CCPR1L = 0b1100100 и как определить CCP1X , CCP1Y ?
В CCPR1L расположена старшая часть байта, а значит для x = 100:
CCPR1L = 0b00011001
CCP1X = 0
CCP1Y = 0
Переделал код, чтобы сразу была 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. не забыли ли вы в протеусе в свойствах указать частоту кварца?
о инклюд у меня в коде пустой, а вы скопировали, там должно быть
#include
у меня pic.h в include, частоту 4 МГц выставил в протеусе, конфиг закоменчен, потому что mplab с ним не компилит, пробовал через #fuses прописать, тоже без толку.
Блин, не заметил какой у вас камень.
Конечно вы ничего не увидите, на 877 выходы шима на rc порту а не на RB.
Ставьте 628a и все будет работать.
спасибо, моя глупость, не могу привыкнуть к тому что они все разные. действительно работает)
Добрый вечер! Хочу сделать генератор синуса на 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 вольт конвертить.
Получился примерно такой код:
Далее с фильтром чутка помудрил, обычный RC дает нормальное подавление при слишком большой временной постоянной, поэтому взял за основу активный фильтр.
А вот как выглядит наш шим сигнал, после прохождения через этот фильтр.
Дальше в случае синусоиды я бы это дело наверное загонял на усилок, на базе операционника и с него уже на комплементарный повторитель, получая таким образом рэил ту рэил размахи на выходе.
Неа, задумку не угадал. Щас примерную схему на почту кину. Вчера пол вечера опробовал ваш код (большое спасибо!) только с подстановкой ширины импульсов из таблицы.При периоде шим 100us на ста дискретах общая ширина полу волны уходит до 12,5ms. Ну это ничего подправим. Хотел увидеть красивую синусоиду в железе на осциллографе, а получил как после диодного моста, полу волны в одну сторону. Хотя для моей мостовой схемы как раз то и надо будет, только на разных ногах.
У меня конкретный вопрос в том, могу ли я в подпрограмму __delay_US() вставить переменную, потому что в CCS-PIC этот номер проходил.
Ну написано же, что константу надо вставлять. Если хотите юзать именно этот макрос напишите функцию какого-нибудь такого вида:
Конечно интересно, что там в этом случае будет с точностью такого периода.
В любом случае я бы лучше пытался это провернуть с помощью регистров таймера.
Спасибо!
Нужно дописать первою строку программы.
Сделано!
Здравствуйте 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++;}
Доброго времени суток!
Баг репорт принят 🙂 Вы правы, ваш вариант правильный. Переделываю в статье сейчас и на репе.
Уведомление: Pic Lab, PIC16, Experiment #11, PWM | diymicro.org