Мы продолжаем серию обучающих статей про основы работы с микроконтроллерами PIC. Ранее на нашем сайте мы уже рассмотрели основы работы с данными микроконтроллерами, использование в них АЦП, подключение к ним ЖК дисплея и семисегментного индикатора и др. В этой же статье мы рассмотрим использование широтно-импульсной модуляции (ШИМ, в англ. PWM) в микроконтроллерах PIC.
Микроконтроллеры PIC имеют специальный модуль CCP (Compare Capture module – модуль сравнения и захвата), который можно использовать для формирования ШИМ сигналов. В нашем проекте мы будем формировать ШИМ сигнал с частотой 5 кГц с переменным коэффициентом заполнения от 0% до 100%. Изменять коэффициент заполнения ШИМ мы будем с помощью потенциометра – более подробно про работу с ним вы можете прочитать в статье про использование АЦП в микроконтроллерах PIC. Также рекомендуем прочитать статью и про использование таймеров в микроконтроллерах PIC – в нашем случае мы будем использовать таймер для установки частоты ШИМ сигнала. Далее мы будем использовать RC-цепь для преобразования цифрового сигнала в аналоговый, чтобы потом использовать его для управления яркостью свечения светодиода, то есть, фактически, сделаем самодельный диммер на основе микроконтроллера PIC.
Ранее на нашем сайте мы рассматривали использование ШИМ в следующих микроконтроллерах (платах):
- в микроконтроллере AVR;
- в плате Arduino Uno;
- в плате Arduino Due;
- в плате Raspberry Pi;
- в плате STM 32 Blue Pill;
- в модуле ESP32.
Что такое ШИМ сигнал
ШИМ (широтно-импульсная модуляция) – это способ управления аналоговыми сигналами с помощью цифровых значений. Таким образом, можно управлять скоростью вращения двигателей, яркостью свечения светодиода и т.д.
Внешний вид ШИМ сигнала при различных коэффициентах заполнения показан на следующих рисунке.
Важным параметром ШИМ сигнала является его коэффициент заполнения (скважность, в англ. - duty cycle). Коэффициент заполнения ШИМ сигнала представляет собой процент времени, в течение которого сигнал имеет высокий уровень (HIGH, ON). Если ШИМ сигнал всегда находится в состоянии HIGH, то его коэффициент заполнения равен 100%, а если ШИМ сигнал всегда находится в состоянии LOW, то его коэффициент заполнения равен 0%.
ШИМ в микроконтроллере PIC16F877A
ШИМ сигнал в микроконтроллере PIC16F877A может быть сформирован с помощью модуля CCP (модуль сравнения и захвата). Разрешение ШИМ сигнала у нас составляет 10 бит, поэтому значению 0 будет соответствовать коэффициент заполнения 0%, а значению 1024 (2^10) – коэффициент заполнения 100%. В микроконтроллерах PIC есть два модуля CCP – CCP1 и CCP2, что означает что мы можем одновременно формировать два различных ШИМ сигнала на двух разных контактах (CCP1 и CCP2). В нашем проекте мы будем использовать модуль CCP1 для формирования ШИМ сигнала на контакте 17.
Для формирования ШИМ сигнала нам потребуются следующие специальные регистры микроконтроллера PIC:
- CCP1CON (CCP1 control Register).
- T2CON (Timer 2 Control Register).
- PR2 (Timer 2 modules Period Register).
- CCPR1L (CCP Register 1 Low).
Программирование микроконтроллера PIC для формирования ШИМ сигнала
В нашем проекте мы будем считывать аналоговое значение 0-5v с потенциометра и преобразовывать его в диапазон 0-1024 с помощью модуля АЦП. Затем мы будем формировать ШИМ сигнал с частотой 5000 Гц и коэффициентом заполнения на основе считанного аналогового значения. Таким образом, диапазон 0-1024 с выхода АЦП будет преобразован в диапазон 0%-100% коэффициента заполнения. Начало нашей программы будет такое же, как и в статье про использование АЦП в микроконтроллере PIC.
Когда биты конфигурации микроконтроллера установлены и написана часть программы для считывания аналогового значения с помощью АЦП, далее мы можем перейти к процессу формирования ШИМ сигнала.
Для этого необходимо сконфигурировать модуль CCP и выполнить следующую последовательность шагов:
- Установить период ШИМ сигнала с помощью регистра PR2.
- Установить коэффициент заполнения ШИМ сигнала с помощью регистра CCPR1L и битов <5:4> регистра CCP1CON.
- Установить режим работы контакта CCP1 на вывод данных с помощью очистки бита TRISC<2>.
- Установить значение предделителя TMR2 и разрешить (enable) работу Timer2 с помощью регистра T2CON.
- Сконфигурировать модуль CCP1 для работы с ШИМ.
В нашей программе будет две основные функции для работы с ШИМ сигналами. Первой из них будет PWM_Initialize(), в которой мы будем инициализировать все регистры, необходимые для настройки модуля формирования ШИМ сигнала, и устанавливать частоту ШИМ сигнала. Во второй функции, PWM_Duty(), мы будем устанавливать коэффициент заполнения (duty cycle) ШИМ сигнала, конфигурируя необходимые регистры.
1 2 3 4 5 6 7 |
PWM_Initialize() { PR2 = (_XTAL_FREQ/(PWM_freq*4*TMR2PRESCALE)) - 1; //Setting the PR2 formulae using Datasheet // Makes the PWM work in 5KHZ CCP1M3 = 1; CCP1M2 = 1; //Configure the CCP1 module T2CKPS0 = 1;T2CKPS1 = 0; TMR2ON = 1; //Configure the Timer module TRISC2 = 0; // make port pin on C as output } |
В представленной функции производится настройка модуля CCP1 для работы с ШИМ сигналом при помощи установки битов CCP1M3 и CCP1M2 в состояние high.
Предделитель модуля таймера настраивается при помощи установки бита T2CKPS0 в состояние high, а бита T2CKPS1 – в состояние low. Бит TMR2ON устанавливается для начала работы таймера.
Теперь нам необходимо установить частоту ШИМ сигнала. Значение этой частоты должно быть записано в регистр PR2. Необходимое значение частоты ШИМ сигнала определяется по следующей формуле:
PWM Period = [(PR2) + 1] * 4 * TOSC * (TMR2 Prescale Value)
Выражая из этой формулы PR2, получаем:
PR2 = (Period / (4 * Tosc * TMR2 Prescale )) - 1
Мы знаем что Period = (1/PWM_freq) и Tosc = (1/_XTAL_FREQ), следовательно, получаем:
PR2 = (_XTAL_FREQ/ (PWM_freq*4*TMR2PRESCALE)) – 1;
Когда частота ШИМ сигнала будет установлена нам не нужно будет снова вызывать эту функцию до тех пор пока нам не понадобится снова сменить данную частоту. В нашем проекте мы использовали частоту ШИМ сигнала равную 5 кГц (PWM_freq = 5000).
Функция для задания коэффициента заполнения ШИМ сигнала будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 |
PWM_Duty(unsigned int duty) { if(duty<1023) { duty = ((float)duty/1023)*(_XTAL_FREQ/(PWM_freq*TMR2PRESCALE)); // On reducing //duty = (((float)duty/1023)*(1/PWM_freq)) / ((1/_XTAL_FREQ) * TMR2PRESCALE); CCP1X = duty & 1; //Store the 1st bit CCP1Y = duty & 2; //Store the 0th bit CCPR1L = duty>>2;// Store the remining 8 bit } } |
Наш ШИМ сигнал имеет разрешение 10 бит, поэтому его значение не может поместиться в один регистр поскольку наш микроконтроллер 8-битный. Поэтому нам необходимо использовать два бита регистра CCP1CON<5:4> (CCP1X и CCP1Y) чтобы хранить два младших бита (LSB), а оставшиеся 8 бит хранить в регистре CCPR1L.
Коэффициент заполнения ШИМ сигнала можно определить по следующей формуле:
PWM Duty Cycle = (CCPRIL:CCP1CON<5:4>) * Tosc * (TMR2 Prescale Value)
Выражая из этой формулы значения для CCPR1L и CCP1CON получим:
CCPRIL:CCP1Con<5:4> = PWM Duty Cycle / (Tosc * TMR2 Prescale Value)
Значение с выхода АЦП лежит в диапазоне 0-1024, а диапазон коэффициента заполнения лежит в пределах 0..1 (0%-100%), поэтому чтобы его получить необходимо значение с выхода АЦП разделить на 1023 (PWM Duty Cycle = duty/1023). Далее, чтобы преобразовать полученное значение коэффициента заполнения в период времени необходимо умножить его на период колебания (1/ PWM_freq). Также мы знаем что Tosc = (1/PWM_freq). В итоге получаем:
Duty = ( ( (float)duty/1023) * (1/PWM_freq) ) / ( (1/_XTAL_FREQ) * TMR2PRESCALE);
Упрощая данное выражение получим:
Duty = ( (float)duty/1023) * (_XTAL_FREQ / (PWM_freq*TMR2PRESCALE));
Полностью код программы нашего проекта приведен в конце статьи.
Схема проекта
Схема для демонстрации возможностей ШИМ в микроконтроллерах PIC представлена на следующем рисунке.
Потенциометр в представленной схеме подключен к контакту 7 микроконтроллера PIC. Модуль CCP1 находится на контакте 17 (RC2), поэтому для проверки формируемого на нем ШИМ сигнала подключим к нему осциллограф. Также для преобразования цифрового значения в напряжения в аналоговое мы используем RC-фильтр. Светодиод используется для индикации подачи сигнала на осциллограф.
Что такое RC-фильтр
RC-фильтр – это фильтр нижних частот (ФНЧ), состоящий из двух пассивных элементов: резистора и конденсатора. Они используются для фильтрации нашего ШИМ сигнала и тем самым делают его похожим на обычный аналоговый сигнал, изменяющийся по уровню.
Если мы посмотрим на схему RC-фильтра мы увидим что когда переменное напряжение подается на резистор R, конденсатор C начнет заряжаться. Когда конденсатор полностью зарядится он блокирует протекание постоянного тока. Следовательно в этот момент времени напряжение, приложенное ко входу схемы, появится на ее выходе. Высокие частоты ШИМ сигнала будут заземлены с помощью конденсатора. Для данного проекта в схеме RC-фильтра мы использовали резистор сопротивлением 1000 Ом и конденсатор емкостью 1 мкФ.
Работу схемы мы проверили в симуляторе Proteus с помощью цифрового осциллографа как показано на рисунке ниже. Вращая ручку потенциометра вы должны заметить как изменяется коэффициент заполнения ШИМ сигнала. Также напряжение на выходе RC-фильтра можно измерить с помощью вольтметра. Когда в Proteus все заработает так, как надо, можно приступать к сборке проекта в "железе". Более подробно эти процессы вы можете посмотреть на видео, приведенном в конце статьи.
Тестирование работы проекта
Внешний вид собранной на перфорированной плате конструкции нашего проекта приведен на следующем рисунке.
Также, чтобы подавать на вход микроконтроллера PIC аналоговое значение напряжения, мы подключили к нему потенциометр как показано на следующих рисунках.
И, наконец, чтобы проверить работу схемы, мы использовали RC-фильтр и светодиод (чтобы контролировать его яркость свечения), которые мы спаяли на отдельной маленькой перфорированной плате.
Можно использовать обычные соединительные провода типа "мама-мама" чтобы соединить вместе отдельные части схемы. После того как аппаратная часть схемы будет готова, вы можете загрузить программу в микроконтроллер PIC с помощью программатора pickit3 и приступить к тестированию работы проекта. Вы должны заметить как с вращением ручки потенциометра изменяется яркость свечения светодиода.
Также для измерения напряжения на выходе схемы можно использовать мультиметр как показано на следующем рисунке.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// CONFIG #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled) #pragma config LVP = ON // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 #define TMR2PRESCALE 4 #include <xc.h> long PWM_freq = 5000; PWM_Initialize() { PR2 = (_XTAL_FREQ/(PWM_freq*4*TMR2PRESCALE)) - 1; //Setting the PR2 formulae using Datasheet // Makes the PWM work in 5KHZ (частота ШИМ сигнала будет равна 5 кГц) CCP1M3 = 1; CCP1M2 = 1; //настраиваем модуль CCP1 T2CKPS0 = 1;T2CKPS1 = 0; TMR2ON = 1; //настраиваем модуль таймера TRISC2 = 0; // конфигурируем порт C на вывод данных } PWM_Duty(unsigned int duty) { if(duty<1023) { duty = ((float)duty/1023)*(_XTAL_FREQ/(PWM_freq*TMR2PRESCALE)); // On reducing //duty = (((float)duty/1023)*(1/PWM_freq)) / ((1/_XTAL_FREQ) * TMR2PRESCALE); CCP1X = duty & 1; //сохраняем 1-й бит CCP1Y = duty & 2; //сохраняем 0-й бит CCPR1L = duty>>2;// сохраняем оставшиеся 8 bit } } void ADC_Initialize() { ADCON0 = 0b01000001; //ADC ON and Fosc/16 is selected (включаем АЦП и выбираем частоту Fosc/16) ADCON1 = 0b11000000; // используем внутреннее опорное напряжение } unsigned int ADC_Read(unsigned char channel) { ADCON0 &= 0x11000101; //очищаем биты выбора канала ADCON0 |= channel<<3; //устанавливаем необходимый канал АЦП __delay_ms(2); //Acquisition time to charge hold capacitor GO_nDONE = 1; //Initializes A/D Conversion (начинаем процесс АЦП) while(GO_nDONE); //Wait for A/D Conversion to complete (ждем пока завершится процесс АЦП) return ((ADRESH<<8)+ADRESL); //возвращаем результат } void main() { int adc_value; TRISC = 0x00; //PORTC на вывод данных TRISA = 0xFF; //PORTA на ввод данных TRISD = 0x00; ADC_Initialize(); //инициализируем модуль АЦП PWM_Initialize(); //устанавливаем частоту ШИМ do { adc_value = ADC_Read(4); //Reading Analog Channel 0 PWM_Duty(adc_value); __delay_ms(50); }while(1); //бесконечный цикл } |
У вас тут ошибка:
CCP1X = duty & 1; //сохраняем 1-й бит
CCP1Y = duty & 2; //сохраняем 0-й бит
правильно так:
CCP1X = duty>>1 & 1; //сохраняем 1-й бит
CCP1Y = duty & 1; //сохраняем 0-й бит
Хорошо, спасибо за поправку