Учебный курс. Широтно-импульсная модуляция (ШИМ, 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.

Комментарии   

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

#asm("sei") //enable interrut
#asm("cli") //disale interrupt
Ответить | Ответить с цитатой | Цитировать
# Андрей_2 06.03.2013 11:33
Как изменить частоту генерируемого сигнала в СVAVR?
Ответить | Ответить с цитатой | Цитировать
# Иван 01.04.2013 09:07
Здравствуйте делаю управления колекторним двигателем неподскажете как зделать переключения ШИМ между виводами.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 01.04.2013 17:12
Вопроса не понимаю. Что значит "переключения ШИМ между выводами"? Простейший вариант управления - один вывод мотора к полюсу питания, второй к стоку или коллектору, в зависимости от используемого транзистора. Затвор или базу через резистор к микроконтроллер у. Схема аналогична приведенной выше, только вместо светодиода мотор и параллельно ему диод.
Ответить | Ответить с цитатой | Цитировать
# Иван 01.04.2013 18:34
Есть четыре транзистора для управления полярностью двигателя на один транзистор подаём ШИМ на другой логическую 1, двигатель крутится в одну сторону, если на третий транзистор подаем ШИМ, а на четвёртый 1 то ДВ крутится в обратную сторону.Есть два порта и между ними надо управлять ШИМОМ с помощью кнопки, С кнопками в принципе разобрался а от из ШИМОМ как-то не очень.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 02.04.2013 07:38
Ясно. Ну, например, у таймера Т1 есть возможность генерировать независимые ШИМ сигналы на двух выводах - OC1A, OC1B. Можно ими воспользоваться .
Ответить | Ответить с цитатой | Цитировать
# Илья21 15.08.2013 09:52
А можешь схему электрическую скинуть пожалуйста?
Ответить | Ответить с цитатой | Цитировать
# Иван 02.04.2013 08:26
Спасибо буду розбиратся:))
Ответить | Ответить с цитатой | Цитировать
# Иван 02.04.2013 12:09
Привет посмотрите пожалуйста на код
Код:
//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 нету. Мне кажется я с таймером что-то нетак понял.:)
Ответить | Ответить с цитатой | Цитировать
# Pashgan 02.04.2013 14:08
Вот в этих строчках ошибка
Код:
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);

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

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

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

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

Защитный код
Обновить