Учебный курс. Широтно-импульсная модуляция (ШИМ, PWM). Управление яркостью светодиода. Генерация аналогового сигнала.

15/10/2009 - 23:50

Пример №1. Управление яркостью светодиода с помощью ШИМ

управление яркостью светодиода с помощью ШИМ

Пояснения к схеме

   R2 – переменный резистор номиналом 10К. Центральный вывод резистора через НЧ-фильтр подключен к нулевому каналу АЦП. Напряжение на резисторе преобразуется в цифровой код, который записывается в регистр сравнения таймера Т0. Таким образом с помощью резистора   осуществляется регулировка скважности ШИМ сигнала.
   Led1 – мощный светодиод белого цвета. Падение напряжения на нем около 4 Вольт. Максимальный ток светодиода, задается резистором  R5 и равен 100 мА.
(Uпит – Uled)/R5 = (5 - 4)/10 = 100 мА
   IRLU024N – N–канальный полевой транзистор. Вместо него можно использовать любые полевые транзисторы серии IRL (L - это значит, что управляются логическим уровнем)
   R3 – токоограничительный резистор на всякий случай. R4 – pull-down резистор. Затвор полевого транзистора нельзя оставлять "болтающимся в воздухе". 

Программа

//************************************************************
//
//  Учебный курс. Программирование микроконтроллеров AVR на Си
//  Управление нагрузкой с помощью
//  широтно-импульсной модуляции (ШИМ, PWM)                 
//
//************************************************************

#include
#include

int main(void)
{
  //инициализация портов
  PORTB = 0;
  DDRB = 0xff;
 
  //инициализация таймера Т0
  TIMSK = 0;
  //реж. - fast pwm, вывод OC0 - неинверт. шим, clk/64
  TCCR0 = (1<<WGM01)|(1<<WGM00)|(1<<COM01)|(0<<COM00)|(0<<CS02)|(1<<CS01)|(1<<CS00);
  TCNT0 = 0;
  OCR0 = 0;

   //инициализируем АЦП
  //ион - напряжение питания, выравнивание влево, нулевой канал

  ADMUX = (0<<REFS1)|(1<<REFS0)|(1<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0);
  //вкл. ацп, реж. непрерывн. преобр., разр. прерывания, частота преобр. = FCPU/128
  ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
  //режим непрерывного преобразования
  SFIOR = 0;
 
  __enable_interrupt();
  while(1);
  return 0;
}

//********************************
//прерывание АЦП

#pragma vector=ADC_vect
__interrupt void adc_my(void)
{
  //считываем старший регистр АЦП и
  //записываем в регистр сравнения
  OCR0 = ADCH;
}

Пояснения к коду

Логика работы

   Инициализация периферии - таймера Т0, модуля АЦП. Разрешение прерываний. Бесконечный цикл. В прерывании АЦП напряжение считанное с переменного резистора записывается в регистр сравнения OCR0. В микроконтроллере AVR для этого регистра организован буфер , и на самом деле запись происходит сначала в него. Из буфера же значение переписывается в OCR0, только когда происходит переполнение счетного регистра TCNT0. Параллельно циклу while и модулю АЦП, работает таймер Т0 и генерит на выводе OC0(PB2) ШИМ сигнал. Когда счетный регистр TCNT0 переполняется OC0 устанавливается в 1, когда значение счетного регистра совпадает с регистром сравнения OCR0, вывод устанавливается в 0. 

Инициализация таймера

   Прерывания таймера не используются, поэтому регистр TIMSK = 0. 
   За установки таймера Т0 отвечает конфигурационный регистр TCCR0.
   Биты WGM01, WHM00 определяют режим работы. 11 - режим Fast PWM. 
   Биты CS02, CS01, CS00 - устанавливают коэффициент предделителя таймера. 011 - соответствует предделителю 64. Тактовая частота таймера/счетчика будет Fcpu/64
   Биты COM01, COM00 -определяют поведение вывода OC0. 10 - соответствует неинвертированному ШИМ сигналу. 1 - при переполнении TCNT0, 0 - при совпадении TCNT0 c OCR0.
 
Инициализация АЦП   
 
   Здесь должно быть все понятно. АЦП используется в режиме непрерывного преобразования. Активируется после разрешения всех прерываний и перезапускается автоматически 
 

Пример №2. Генерация синусоидального сигнала с помощью ШИМ

генерация синусоидального сигнала с помощью ШИМ

Пояснение к схемe

   Резистор R2 просто забыл выкинуть из схемы. R3 и C8 интегратор. 

Программа

//************************************************************
//
//  Учебный курс. Программирование микроконтроллеров AVR на Си
//  Генерация синуса с помощью
//  широтно-импульсной модуляции (ШИМ, PWM)                
//
//************************************************************

#include
#include
#include "TableSin.h"

int main(void)
{
  //инициализация портов
  PORTB = 0;
  DDRB = 0xff;

  //инициализация таймера Т0
  TIMSK = (1<<OCIE0);
  //реж. - fast pwm, вывод OC0 - неинверт. шим, предделитель - на 8
  TCCR0 = (1<<WGM01)|(1<<WGM00)|(1<<COM01)|(0<<COM00)|(0<<CS02)|(1<<CS01)|(0<<CS00);
  //обнуляем счетный регистр и регистр сравнения
  TCNT0 = 0;
  OCR0 = 0;

   __enable_interrupt();
  while(1);
  return 0;

}

//**********************************************************
//Прерывание таймера/счетчика Т0
#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void)
{
  static unsigned char i = 0;
 
  //считываем элемент массива и записываем в регистр сравнения
  OCR0 = TableSin[i];
  i++;
  if (i == 32) i = 0;
}

Пояснения к коду

Таблица значений синуса

   TableSin.h - хедер, в котором прописан константный массив, содержащий 32 значения синуса.  

Инициализация таймера Т0

  В программе используется прерывание таймера Т0 по событию совпадение.Для разрешения прерывания устанавливается бит OCIE0 регистра TIMSK. Остальная часть инициализации таймера, как и в предыдущей программе. 

Обработчик прерывания таймера Т0

  В прерывании таймера микроконтроллер считывает из массива очередное значение синуса и записывает его в регистр сравнения OCR0. Действительная перезапись этого регистра происходит когда счетный регистр таймера Т0 переполняется. Для доступа к элементам массива используется статическая переменная, значение которой увеличивается на 1, пока не дойдет до 32. 

Полученная синусоида на экране осциллографа
Полученный синусоидальный сигнал. Частота 122 Гц. Размах почти 2 В
 
Увеличенный кусок синусоиды
Увеличенный кусок синусоиды.
 

Файлы проектов для IARa.

Comments   

# Zliva 2010-12-12 18:45
Пин PWM перепутан ;-)
Reply | Reply with quote | Quote
# Pashgan 2011-01-22 23:21
Слушай, действительно перепутан. :o Это в Eagle накосячили. Я же библиотечный элемент брал.
Reply | Reply with quote | Quote
# grbizly 2011-01-21 08:14
Спасибо за статью.
Только при моделировании в Proteus появляется провал синусоиды, из-за того что OC0=255, при i=7 и идет провал ШИМ в 0.
Поставил 254 стало намного красивее.
Reply | Reply with quote | Quote
# Pashgan 2011-01-22 23:24
Да? Надо проверить. ... тестил в железе, а там видимо из-за фильтра этот провал не вылез.
Reply | Reply with quote | Quote
# inch 2011-01-24 00:38
Если в примере1 кроме переменного резюка прикрутил кнопку понижаю скважность в два раза, то просто делить старший регист ацп пополам? Нужно еще что-то смещать?
Reply | Reply with quote | Quote
# Олег 2011-04-14 20:34
А как включить шим на таймере1?
Reply | Reply with quote | Quote
# Павел 2012-12-14 16:50
У вас код написан в синтаксе IAR, а я пишу в CVAVR, подскажите пожалуйста что означает сторчка:
__enable_interrupt();
и каков ее аналог в CVAVR?
Я понимаю что это связано как то с разрешением или обьявлением прерываний но разаобратья не могу.
Reply | Reply with quote | Quote
# Андрей 2013-01-27 07:46
Quoting Павел:
У вас код написан в синтаксе IAR, а я пишу в CVAVR, подскажите пожалуйста что означает сторчка:
__enable_interrupt();
и каков ее аналог в CVAVR?
Я понимаю что это связано как то с разрешением или обьявлением прерываний но разаобратья не могу.

#asm("sei") //enable interrut
#asm("cli") //disale interrupt
Reply | Reply with quote | Quote
# Андрей_2 2013-03-06 11:33
Как изменить частоту генерируемого сигнала в СVAVR?
Reply | Reply with quote | Quote
# Иван 2013-04-01 09:07
Здравствуйте делаю управления колекторним двигателем неподскажете как зделать переключения ШИМ между виводами.
Reply | Reply with quote | Quote
# Pashgan 2013-04-01 17:12
Вопроса не понимаю. Что значит "переключения ШИМ между выводами"? Простейший вариант управления - один вывод мотора к полюсу питания, второй к стоку или коллектору, в зависимости от используемого транзистора. Затвор или базу через резистор к микроконтроллер у. Схема аналогична приведенной выше, только вместо светодиода мотор и параллельно ему диод.
Reply | Reply with quote | Quote
# Иван 2013-04-01 18:34
Есть четыре транзистора для управления полярностью двигателя на один транзистор подаём ШИМ на другой логическую 1, двигатель крутится в одну сторону, если на третий транзистор подаем ШИМ, а на четвёртый 1 то ДВ крутится в обратную сторону.Есть два порта и между ними надо управлять ШИМОМ с помощью кнопки, С кнопками в принципе разобрался а от из ШИМОМ как-то не очень.
Reply | Reply with quote | Quote
# Pashgan 2013-04-02 07:38
Ясно. Ну, например, у таймера Т1 есть возможность генерировать независимые ШИМ сигналы на двух выводах - OC1A, OC1B. Можно ими воспользоваться .
Reply | Reply with quote | Quote
# Илья21 2013-08-15 09:52
А можешь схему электрическую скинуть пожалуйста?
Reply | Reply with quote | Quote
# Иван 2013-04-02 08:26
Спасибо буду розбиратся:))
Reply | Reply with quote | Quote
# Иван 2013-04-02 12:09
Привет посмотрите пожалуйста на код
Code:
//ATmega16 Внешний кварц 12МГц;
#include <ioavr.h>
#include <intrinsics.h>

int main(void)
{
DDRB=(0<<DDB0)|(0<<DDB1);//Порти кнопок;
PORTB=(1<<PB0)|(1<<PB1);//Под резистор;
DDRD=(1<<DDD4)|(1<<DDD5);//Порти ОС1В,ОС1А;
PORTD=0;
//таймер Т1;
TCCR1A = (0<<WGM11)|(1<<WGM10)|(1<<COM1A1)|(0<<COM1A1)|(1<<COM1B1)|(0<<COM1B1);
TCCR1B = (0<<WGM13)|(0<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
TCNT1 = 0;
OCR1A = 0;
OCR1B = 0;

ADMUX = (0<<REFS1)|(1<<REFS0)|(1<<ADLAR)|(0<<MUX3)|(0<<MUX2)|(0<<MUX1)|(0<<MUX0);
ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
SFIOR = 0;

__enable_interrupt();
while(1);
return 0;
}

#pragma vector=ADC_vect
__interrupt void adc_my(void)
{
if((PINB&(1<<PB0))==1)//кнопка1
{
OCR1A = 0;
}
else
{
OCR1A = ADCH;
}
if((PINB&(1<<PB1))==1)//кнопка2
{
OCR1B = 0;
}
else
{
OCR1B = ADCH;
}

}
.
Когда нажимаеш на первую кнопку ШИМ на порту OCR1A а когда вторую ШИМА на порту OCR1B нету. Мне кажется я с таймером что-то нетак понял.:)
Reply | Reply with quote | Quote
# Pashgan 2013-04-02 14:08
Вот в этих строчках ошибка
Code:
TCCR1A = (0<<WGM11)|(1<<WGM10)|(1<<COM1A1)|(0<<COM1A1)|(0<<CS12)|(1<<CS11)|(1<<CS10);
TCCR1B = (0<<WGM13)|(0<<WGM12)|(1<<COM1B1)|(0<<COM1B1)|(0<<CS12)|(1<<CS11)|(1<<CS10);

Нужно так
Code:
TCCR1A = (0<<WGM11)|(1<<WGM10)|(1<<COM1A1)|(0<<COM1A1)|(1<<COM1B1)|(0<<COM1B1);
TCCR1B = (0<<WGM13)|(0<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10);
Reply | Reply with quote | Quote
# Иван 2013-04-02 15:23
Ща посмотрим:))
Reply | Reply with quote | Quote
# Иван 2013-04-02 21:30
Спасибо помогло))Я так понимаю управления портами на-да записывать в регистр ACCR1A а предделительом в ACCR1B.Но теперь с кнопками, кнопка которая подключена к порту РВ0 работает а к порту РВ1 неработает я уже их и к другим виводам подключал ничего непоменялось на портах 0 работает а на 1,2,3,4,5,6,7 нет. Может опрос в безконечном цикле делать, или опрос как-то изменить.
Reply | Reply with quote | Quote
# Pashgan 2013-04-03 17:31
Должно быть так
Code:
if((PINB&(1<<PB0))==0){

Вывод работает в режиме входа, включен подтягивающий резистор. Нажатие на кнопку коммутирует вывод микроконтроллера на землю. Соответственно нужно проверять, что на выводе установился логический ноль. Обычно делают так.
Если делать как ты, то нужно сравнивать не с 1, а с ненулевым значением. Вот так:
Code:
if((PINB&(1<<PB0))>0){...

или так
Code:
if(PINB&(1<<PB0)){...
Reply | Reply with quote | Quote
# Иван 2013-04-05 16:38
Спасибо все работает на ура осталось разобраться с транзисторами :))Не думал написать материал по радиоуправлению между микроконтроллер ами.
Reply | Reply with quote | Quote
# Pashgan 2013-04-05 22:35
Нет, не сталкивался с этим. Самый простой вариант, это взять готовые радиомодули и на основе какого-то простого протокола организовать между ними обмен. А чем ты собрался управлять по радио каналу? Какой-то моделью?
Reply | Reply with quote | Quote
# Иван 2013-04-06 16:56
У меня подйомник решил для удобства переделать на нормальное управления из ШИМОМ как видиш вроде разобрался но управлять всьоравно по проводам,но сразу задумал радиосвязь модули нашол в интернете
но там два вида подключения по SPI и USARТ думаю брать SPI но еще не решил.
Reply | Reply with quote | Quote
# Иван 2013-04-08 16:25
Привет неплохо било небольшую статейку и по программному шиму. Извиняй если настойчиво, но просто у тебя стати класно сложены за первым разом процентов 60% усваивается. Впервые наткнулся на такое, бывает читаеш, читаеш и черт голову зломит что там написано. Спасибо за хорошо сложеные стати :roll:
Reply | Reply with quote | Quote
# Илья21 2013-08-14 07:19
Ведь можно же на этой основе регулятор мощности с обратной связью по току и напряжению забацать, как в импульсниках для компов
Reply | Reply with quote | Quote
# Егор27 2016-06-20 08:58
как программно записать значение в 16-разрядный регистр захвата ICR1H ICR1L?? например надо записать число 6500?
Reply | Reply with quote | Quote
# Егор27 2016-06-20 09:27
уже разобрался, там одновременно можно записывать младший и старший байт
Reply | Reply with quote | Quote
# Никита_Ващук 2016-08-25 06:53
День добрый, интересует вопрос по созданию файла "TableSin.h" - это идет как отдельный файл с 32-мя значениями для синуса (получается синус из 32 частей) или это встроенная библиотека.
Программирую на mikroC++
Reply | Reply with quote | Quote

Add comment