» »

Что такое 10 битный шим. Генерация аналоговых сигналов микроконтроллером. Ч1. Схема для демонстрации широтно-импульсной модуляции в Arduino

15.10.2020

Что такое ШИМ и как он работает особо подробно расписывать не буду, информацию без труда найдёте на просторах интернета. Коснусь лишь общих понятий. ШИМ - это Широтно-Импульсная Модуляция, (по-английски PWM - Pulse Width Modulation) уже из самого названия ясно, что здесь что-то связанное с импульсами и их шириной. Если изменять ширину (длительность) импульсов постоянной частоты, то можно управлять, например, яркостью источника света, скоростью вращения вала электродвигателя или температурой какого-либо нагревательного элемента. Обычно, именно с помощью ШИМ микроконтроллер управляет подобной нагрузкой. Микроконтроллеры имеют аппаратную реализацию ШИМ, но, к сожалению, количество аппаратных ШИМ-каналов ограничено, например, в AТmega88 их аж шесть штук, в ATtiny2313 - четыре, в ATmega8 - три, а в ATtiny13 только два. В AVR ШИМ-каналы используют таймеры и их регистры сравнения OCRxx. Изменяя их содержимое и задавая параметры таймеров, в зависимости от задач, можно управлять состоянием, связанного с регистром, выхода - подавать на него 1 либо 0. То же самое можно организовать программно, управляя любым выводом контроллера, а главное, реализовать большее количество ШИМ-каналов, чем имеется на борту аппаратных. Практически, количество каналов ограничено лишь количеством ножек-выводов микроконтроллера (по крайней мере, если говорить о семействах Mega или Tiny). Как оказалось, алгоритм довольно прост, но у меня ушло некоторое время на его понимание и полное осознание.

Данный алгоритм подробно изложен в оригинальном Appnote AVR136: Low-Jitter Multi-Channel Software PWM. Принцип работы программной реализации заключается в имитации работы таймера в режиме ШИМ. Требуемая длительность импульсов задаётся переменными, соответственно, по одной на каждый канал (в моём коде lev_ch1, lev_ch2, lev_ch3), а так же задаются «близнецы» этих переменных, которые хранят значение для конкретного периода работы таймера (в моём коде buf_lev_ch1, buf_lev_ch2, buf_lev_ch3). Восьмибитный таймер запускается на основной частоте МК и генерирует прерывание по переполнению, то есть, каждые 256 тактов. Это накладывает ограничение на длительность процедуры обработки прерывания - необходимо уложиться в 256 тактов, чтобы не пропустить следующее прерывание. В результате, один полный период ШИМ равняется 256*256=65536-и тактам. Восьмибитная переменная-счетчик (в моём примере counter) увеличивается на единицу каждое прерывание и действует, как указатель позиции внутри цикла ШИМ. Всё это обеспечивает разрешение (минимальный шаг) ШИМ в 1/256, а частоту импульсов в ƒ/(256*256), где ƒ-частота задающего генератора микроконтроллера. Следует заметить, что тактовая частота микроконтроллера должна быть довольно высокой. В моём примере ATtiny13 работает на максимально возможной частоте, без применения внешнего генератора - 9,6МГц. Это даёт период ШИМ в 9600000/65536≈146,5Гц чего вполне достаточно в большинстве случаев.
Код на C, пример реализации идеи для МК ATtiny13 (три канала ШИМ на выводах PB0, PB1, PB2):

#define F_CPU 9600000 //fuse LOW=0x7a #include #include uint8_t counter=0; uint8_t lev_ch1, lev_ch2, lev_ch3; uint8_t buf_lev_ch1, buf_lev_ch2, buf_lev_ch3; void delay_ms(uint8_t ms) //функция задержки { while (ms) { _delay_ms(1); ms--; } } int main(void) { DDRB=0b00000111; // установка PortB пины 0,1,2 выходы TIMSK0 = 0b00000010; // включить прерывание по переполнению таймера TCCR0B = 0b00000001; // настройка таймера, делитель выкл sei(); // разрешить прерывания lev_ch1=0; //начальные значения lev_ch2=64; //длительности ШИМ lev_ch3=128; //трёх каналов while (1) //бесконечная шарманка { for (uint8_t i=0;i<255;i++) { lev_ch1++; //увеличиваем значения lev_ch2++; //длительности ШИМ lev_ch3++; //каждого канала delay_ms(50); //пауза 50мс } } } ISR (TIM0_OVF_vect) //обработка прерывания по переполнению таймера { if (++counter==0) //счетчик перехода таймера через ноль { buf_lev_ch1=lev_ch1; //значения длительности ШИМ buf_lev_ch2=lev_ch2; buf_lev_ch3=lev_ch3; PORTB |=(1<Думаю, всё достаточно наглядно и пояснения излишни. Для значений длительности и их буферов, при большем числе каналов, возможно, будет лучше использовать массивы, но в данном примере, я этого делать не стал, ради большей наглядности.
Проверено на avr-gcc-4.7.1 и avr-libc-1.8.0. Компиляция и получение файла прошивки:
avr-gcc -mmcu=attiny13 -Wall -Wstrict-prototypes -Os -mcall-prologues -std=c99 -o softPWM.obj softPWM.c
avr-objcopy -O ihex softPWM.obj softPWM.hex
Для правильной работы нужно выставить младшие fuse-биты в 0x7a (частота 9,6МГц). в avrdude это, например, делается так:
avrdude -p t13 -c usbasp -U lfuse:w:0x7a:m

Мой вариант реализации на ассемблере. Программа делает абсолютно то же самое, что и предыдущий код на C.
;чтобы не тянуть include-файл.list .equ DDRB= 0x17 .equ PORTB= 0x18 .equ RAMEND= 0x009f .equ SPL= 0x3d .equ TCCR0B= 0x33 .equ TIMSK0= 0x39 .equ SREG= 0x3f ;это лишь демонстрация, потому регистров и не жалеем.def temp=R16 .def lev_ch1=R17 .def lev_ch2=R18 .def lev_ch3=R19 .def buf_lev_ch1=R13 .def buf_lev_ch2=R14 .def buf_lev_ch3=R15 .def counter=R20 .def delay0=R21 .def delay1=R22 .def delay2=R23 .cseg .org 0 ;таблица прерываний из даташита: rjmp RESET ; Reset Handler rjmp EXT_INT0 ; IRQ0 Handler rjmp PIN_CHG_IRQ ; PCINT0 Handler rjmp TIM0_OVF ; Timer0 Overflow Handler rjmp EE_RDY ; EEPROM Ready Handler rjmp ANA_COMP ; Analog Comparator Handler rjmp TIM0_COMPA ; Timer0 CompareA Handler rjmp TIM0_COMPB ; Timer0 CompareB Handler rjmp WATCHDOG ; Watchdog Interrupt Handler rjmp ADC_IRQ ; ADC Conversion Handler ;RESET: EXT_INT0: PIN_CHG_IRQ: ;TIM0_OVF: EE_RDY: ANA_COMP: TIM0_COMPA: TIM0_COMPB: WATCHDOG: ADC_IRQ: reti RESET: ldi temp,0b00000111 ; назначаем PortB пины PB0, PB1 out DDRB,temp ; и PB2 выходами ldi temp,0 ; выставляем все выводы out PORTB,temp ; PortB в 0 ldi temp,low(RAMEND) ; инициализация out SPL,temp ; стека ldi temp,0b00000001 ; вкл. таймер out TCCR0B,temp ; без делителя ldi temp,0b00000010 ; вкл. прерывание out TIMSK0,temp ; таймера по переполнению sei ; разрешить прерывания start_pwm: ; бесконечная шарманка inc lev_ch1 ; увеличиваем значения inc lev_ch2 ; длительности ШИМ inc lev_ch3 ; по всем каналам rcall delay ; небольшая пауза для плавности rjmp start_pwm delay: ; процедура задержки ldi delay2,$01 ; выставляем число ldi delay1,$77 ; до скольки считать ldi delay0,$00 ; $017700 - даст задержку в 50мс loop: subi delay0,1 ; считаем sbci delay1,0 ; считаем sbci delay2,0 ; считаем brcc loop ret TIM0_OVF: ; обработка прерывания таймера push temp ; на всякий пожарный сохраняем in temp,SREG ; temp и SREG в стеке push temp inc counter ; счетчик перехода таймера через 0 cpi counter,0 ; если не 0, то проверяем brne ch1_off ; не надо ли чего погасить mov buf_lev_ch1,lev_ch1 ; если счетчик 0 mov buf_lev_ch2,lev_ch2 ; то задаем новые mov buf_lev_ch3,lev_ch3 ; значения длительности ШИМ каналов ldi temp,0b00000111 ; включить все out PORTB,temp ; три выхода ch1_off: ; а не погасить ли нам cp counter,buf_lev_ch1 ; первый канал? brne ch2_off ; нет, рано - проверяем второй cbi PORTB,0 ; да погасить ch2_off: ; а не погасить ли нам cp counter,buf_lev_ch2 ; второй канал? brne ch3_off ; нет, рано - проверяем третий cbi PORTB,1 ; да погасить ch3_off: ; а не погасить ли нам cp counter,buf_lev_ch3 ; третий канал? brne irq_end ; нет, рано - двигаемся к выходу из прерывания cbi PORTB,2 ; да, погасить irq_end: ; достаем из стека pop temp ; SREG и temp out SREG,temp pop temp reti ;выходим из прерывания
Компилируется с помощью avra или tavrasm. Не забыть про fuse-биты (см. выше).

Обновлено 16.12.15. Всем привет. Разобравшись в прошлой записи с памятью EEPROM, сегодня мы поговорим о том что такое ШИМ (PWM)? Расшифруется как широтно-импульсная модуляция (pulse-width modulation), это среднее значение напряжения, которое изменяется скважностью импульса. В свою очередь скважность это длительность импульса с некоторой частотой повторения. Т.е. более простыми словами все это изменение ширины импульса при неизменной их величине. Для чего это нам надо?

ШИМ (PWM) используется в транзисторной схеме для регулирования напряжения без механики, в свою очередь управление мощностью. Например управление яркостью светодиодов, управление яркостью подсветки на LCD-мониторе, управление двигателями и т.д. Если отобразить на рисунке, то выход с микроконтроллера примерно будет следующим, как на картинке ниже. Где видно что скважность это заполнение импульса, если вся ширина импульса это 5 В, то при 30% заполнении импульса, в среднем на выходе мы получим примерно 1,5В. В микроконтроллерах AVR ШИМ управление задается в восьмиразрядных таймерах/счетчиках T0/(T2) и шестнадцатиразрядный T1 (T3 в некоторых моделях). А также есть другие модели где битность ШИМа можно задавать, например ATmega 128. Рассмотрим настройку шестнадцатиразрядного таймера/счетчика Т1. Данные берем соответственно таблицам либо из справочника, либо из даташита (литература — статья №1).

В общем для такого счетчика в мк можно выбрать три режима: Fast PWM, Phase Correct PWM, Phase and Frequency Correct PWM (зависит от модели )

Рассмотрим второй режим - ШИМ с точной фазой . Здесь счетный регистр функционирует как реверсивный счетчик, изменения состояния которого изменяется от $0000 до максимального значения а затем обратно до $0000. Для управления таймером/счетчиком используем три регистра управления TCCR1A, TCCR1B, TCCR1C. В которых для выбора режима таймера/счетчика необходимо установить разряды WGMn1: WGMn0 и WGMn1: WGMn0. В зависимости от их установки максимальное значение счетчика(Разрешение ШИМ сигнала) является либо фиксированным значением, либо определяется содержимым определенных регистров таймера/счетчика. Разрешающая способность определяется выражением:

g = log (TOP+1)/log2, где ТОР – модуль счета, выбирается из таблицы соответственно разрешающей способности.

После того как определились с режимом работы таймера счетчика, необходимо выбрать режим работы блока сравнения COMnA1:COMnA0, COMnB1:COMnB0, COMnC1:COMnC0, который определяет поведение вывода OCnx при наступлении события “Совпадение”.

Ну и последний штрих определимся с частотой. Нам необходимо выставить разряды CSn2…CSn0 регистра TCCR1B, которые отвечают за определение источника тактового сигнала. Вот таким программным образом выглядит настройка ШИМ-управления на выходе OC1A. Например:

/*Настройки ШИМ */
TCCR1A=(1< /*На выводе OC1A единица, когда OCR1A==TCNT1, Сбрасывается в 0 при OCR1A==TCNT1 и устанавливается в 1 при достижении максимального значения восьми битный ШИМ Phase Correct PWM , номер режима 1 . модуль счета ТОР $00FF*/
TCCR1B=(1<OCR1A = 50; /* при модуле счета 255 и при напряжении 5 В на выходе OC1A получим примерно 1 В*/

Из программы видно, что для получения ШИМ используем регистр сравнения OCR1A. При достижении счетчиком максимального значения, в данном случае 255, происходит смена направления счета, но счетчик остается в этом состоянии в течении одного периода сигнала. В этом и заключается более медленная частота работы по сравнению с первым режимом. Но в этом и состоит симметричность изменения счетчика. Что более подходит для управления двигателем. В этом же такте происходит обновления содержимого регистра сравнения. При достижении счетчиком минимального значения также происходит смена направления счета и одновременно устанавливается флаг прерывания TOV1 регистра TIFR. Пр равенстве содержимого счетного регистра и какого-либо регистра сравнения устанавливается соответствующий флаг OCF1A/OCF1B/OCF1C регистра TIFR. Одновременно изменяется состояние выхода блока сравнения OCnx. Частота генерируемого сигнала fOCn= f/(2*N*TOP), где N – коэффициент деления пред делителя, f — частота кварца. Также можно посмотреть еще примеры настройки и использования ШИМ, например .

На этом сегодня все. В следующем посте рассмотрим контроллер любительского станка ЧПУ . Я постараюсь использовать предыдущие посты из этого блога для набора программы, как конструктор. Так будет более понятно когда один раз написали и его использовали в следующем проекте. Всем пока.

Был рассмотрен аппаратный ШИМ генератор микроконтроллера. Всё в нем хорошо, но есть несколько "но":
- аппаратный ШИМ жёстко привязан к определенным выводам МК, его невозможно переназначить на другую ногу
- количество аппаратных ШИМ каналов ограничено, их количество зависит от модели МК
- разрядность аппаратного ШИМ невозможно изменить

В этом случае может пригодиться программный метод получения ШИМ сигнала. Делается он не сложно, но требователен к частоте работы микроконтроллера и занимает достаточно много процессорного времени, в отличие от аппаратного, работающего незаметно для основной программы. Но так как применяется он, как правило, для светодиодных мигалок, то это не столь важно.

Нам необходимо в начале периода ШИМ сигнала выставлять определенную ногу МК в 1 или 0 (в зависимости от того, какой сигнал нам нужен), а потом, по достижении заданной длительности импульса, инвертировать значение ножки. Делать это удобнее всего в прерывании по переполнению. Так мы и поступим, воспользуемся прерыванием по переполнению таймера T0. Управлять будем RGB светодиодом, поэтому и названия переменных и макроопределения для портов сделаем удобочитаемыми.

/*блок дефайнов***************************************************************************************************/ #define RED PORTB.0 #define GREEN PORTB.1 #define BLUE PORTB.2 /*****************************************************************************************************************/ /*объявляем прерменные********************************************************************************************/ unsigned char red=255, green, blue; //переменные, для изменения скважности ШИМ в программе unsigned char red_b, green_b, blue_b; //переменные, для буферизации значений скважности ШИМ unsigned char count; //переменная- счетчик вызовов обработчика прерываний unsigned char temp=1; //переменная для работы алгоритма смены цветов /*****************************************************************************************************************/

Когда наступает прерывание, необходимо увеличить программный счетчик на 1 и проверить, не переполнился ли он. Если таймер переполнен, то нужно на все ножки, на которые выводится ШИМ, вывести логическую 1, а так же сохранить переменные в буфер. Переменные в буфер сохраняются для того, чтобы данные о скважности обновлялись раз в начале каждого периода, это исключает непредсказуемое поведение выхода. Далее сравниваем значение счетчика со значением буфера скважности каждого канала. Если счетчик достиг этого значения- выводим в соответствующую ногу МК логический 0.

/*обработчик прерывания*******************************************************************************************/ interrupt void timer0_ovf_isr(void) { count++; if (count == 0){ //если счетчик переполнился и принял значение 0 red_b = red; //сохранием значения в буфер green_b = green; blue_b = blue; RED =1; //выставляем ноги, отвечающие за ШИМ в логическую 1 GREEN =1; BLUE =1; } if (red_b == count) { RED = 0;} //по достижении заданной скважности выводим логический 0 в ножку МК if (green_b == count) { GREEN = 0;} if (blue_b == count) { BLUE = 0;} } /*****************************************************************************************************************/

Для демонстрации работы будем выводить на светодиод плавную смену цвета по цветам радуги (Каждый Охотник Желает Знать Где Сидит Фазан). Для этого воспользуемся нехитрым алгоритмом, который будем крутить в бесконечном цикле.

/*главная функция*************************************************************************************************/ void main(void) { PORTB=0x08; //конфигурируем порт DDRB=0x07; TCCR0=0x01; //настраиваем таймер TCNT0=0x00; TIMSK=0x01; //разрешаем генерацию прерывания по переполнению таймера T0 #asm("sei") //глобально разрешаем прерывания /*бесконечный цикл************************************************************************************************/ while (1) { if (temp==1) {if (green < 255) green += 1; else temp = 2;} if (temp==2) {if (red > 0) red -= 1; else temp = 3;} if (temp==3) {if (blue < 255) blue += 1; else temp = 4;} if (temp==4) {if (green > 0) green -= 1; else temp = 5;} if (temp==5) {if (red < 255) red += 1; else temp = 6;} if (temp==6) {if (blue > 0) blue -= 1; else temp = 1;} delay_ms(2); }; /*****************************************************************************************************************/ } /*****************************************************************************************************************/

Например);

  • резистор номиналом 190…240 Ом (вот отличный набор резисторов самых распространённых номиналов);
  • персональный компьютер со средой разработки Arduino IDE.
  • Инструкция по использованию ШИМ в Arduino

    1 Общие сведения о широтно-импульсной модуляции

    Цифровые выводы Arduino могут выдавать только два значения: логический 0 (LOW, низкий уровень) и логическую 1 (HIGH, высокий). На то они и цифровые. Но есть у Ардуино «особые» выводы, которые обозначаются PWM . Их иногда обозначают волнистой чертой "~" или обводят кружочками или ещё как-то выделяют среди прочих. PWM расшифровывается как Pulse-width modulation или широтно-импульсная модуляция , ШИМ .

    Широтно-импульсно модулированный сигнал - это импульсный сигнал постоянной частоты, но переменной скважности (соотношение длительности импульса и периода его следования). Из-за того, что большинство физических процессов в природе имеют инерцию, то резкие перепады напряжения от 1 к 0 будут сглаживаться, принимая некоторое среднее значение. С помощью задания скважности можно менять среднее напряжение на выходе ШИМ.

    Если скважность равняется 100%, то всё время на цифровом выходе Arduino будет напряжение логическая "1" или 5 вольт. Если задать скважность 50%, то половину времени на выходе будет логическая "1", а половину - логический "0", и среднее напряжение будет равняться 2,5 вольтам. Ну и так далее.


    В программе скважность задаётся не в процентах, а числом от 0 до 255. Например, команда analogWrite(10, 64) скажет микроконтроллеру подать на цифровой PWM выход №10 сигнал со скважностью 25%.

    Выводы Arduino с функцией широтно-импульсной модуляции работают на частоте около 500 Гц. Значит, период следования импульсов - около 2 миллисекунд, что и отмеряют зелёные вертикальные штрихи на рисунке.

    Получается, что мы можем сымитировать аналоговый сигнал на цифровом выходе! Интересно, правда?!

    Как же мы можем использовать ШИМ? Применений масса! Например, управлять яркостью светодиода, скоростью вращения двигателя, током транзистора, звуком из пьезоизлучателя и т.д.…

    2 Схема для демонстрации широтно-импульсной модуляции в Arduino

    Давайте рассмотрим самый базовый пример - управление яркостью светодиода с помощью ШИМ. Соберём классическую схему.


    3 Пример скетча с ШИМ

    Откроем из примеров скетч "Fade": Файл Образцы 01.Basics Fade .


    Немного изменим его и загрузим в память Arduino.

    Int ledPin = 3; // объявляем пин, управляющий светодиодом int brightness = 0; // переменная для задания яркости int fadeAmount = 5; // шаг изменения яркости void setup() { pinMode(ledPin, OUTPUT); } void loop() { analogWrite(ledPin, brightness); // устанавливаем яркость brightness на выводе ledPin brightness += fadeAmount; // изменяем значение яркости /* при достижении границ 0 или 255 меняем направление изменения яркости */ if (brightness == 0 || brightness == 255) { fadeAmount = -fadeAmount; // изменяем знак шага } delay(30); // задержка для большей видимости эффекта }

    4 Управление яркостью светодиода с помощью PWM и Arduino

    Включаем питание. Светодиод плавно наращивает яркость, а затем плавно уменьшает. Мы сымитировали аналоговый сигнал на цифровом выходе с помощью широтно-импульсной модуляции.


    Посмотрите приложенные видео, где наглядно показано изменение яркости светодиода, на подключённом осциллографе видно, как при этом меняется сигнал с Arduino.

    В устройствах на микроконтроллерах иногда требуется генерировать аналоговый сигнал. В зависимости от частоты аналогового сигнала, требуемого разрешения и типа используемого микроконтроллера, выполнить это можно несколькими способами. А именно: с помощью широтно-импульсной модуляции, используя функционал аппаратных таймеров или программную реализацию, с помощью встроенного цифроаналогового преобразователя (ЦАП`а), с помощью внешних схем цифроаналоговых преобразователей на дискретных элементах или с помощью внешних микросхем цифроаналоговых преобразователей.

    1. Принцип генерации аналогового сигнала с помощью ШИМ (PWM)

    ШИМ сигнал представляет собой цифровой сигнал, у которого период повторения постоянный, а длительность меняется. Отношение длительности ШИМ сигнала к его периоду называется коэффициентом заполнения. Пропустив такой сигнал через низкочастотный фильтр, что по сути равносильно интегрированию, мы получим на выходе фильтра уровень напряжения пропорциональный коэффициенту заполнения.


    Таким образом, меня этот коэффициент, можно генерировать аналоговые сигналы произвольной формы. Причем как переменные, например, синусоида, пила или человеческая речь, так и постоянные (произвольный уровень напряжения).

    1.1 Характеристики сигнала

    Максимальная амплитуда выходного аналогового сигнала будет определяться амплитудой логической единицы цифрового ШИМ сигнала. Если микроконтроллер питается от +5 В, то грубо говоря, амплитуда выходного аналогового сигнала будет от 0 до 5 В.

    Минимальный шаг изменения аналогового сигнала (разрешение) будет определяться выражением:


    dUa = Umax/2^n,


    где Umax максимальная амплитуда аналогового сигнала (В), а n - разрядность счетчика реализующего ШИМ.

    Например, ШИМ сигнал формируется с помощью программного 8-ми разрядного счетчика. Количество градаций ШИМ сигнала, которые можно получить с помощью этого счетчика, равно 2^8 = 256. Тогда разрешение аналогового сигнала при Umax = 5 В будет равно


    dUa = 5/256 = 0,0195 В.


    Частота ШИМ сигнала будет определять так:


    Fpwm = Fcpu/(K*2^n),


    где Fcpu - тактовая частота микроконтроллера (Гц), K - коэффициент предделителя счетчика, n - разрядность счетчика.

    Например, тактовая частота микроконтроллера 8 МГц, коэффициент предделителя равен 8, разрядность счетчика 8 бит. Тогда частота выходного ШИМ сигнала будет равна:

    Fpwm = 8000000/(8*256) = ~3906 Гц


    Частота выходного аналогового сигнала будет определяться выражением:

    Fa = Fpwm/Ns = Fcpu/(K*2^n*Ns),


    где Fpwm - частота ЩИМ сигнала, а Ns - количество отсчетов аналогового сигнала.

    Например, ШИМ сигнал реализуется на 8-ми разрядном счетчике с коэффициентом предделителя равным 8 и тактовой частотой микроконтроллера 8 МГц. В памяти микроконтроллера записано 32 отсчета синусоидального сигнала, которые представляют собой один его период. Тогда частота выходной синусоиды будет равна:

    Fa = 8000000/(8*2^8 * 32) = ~122 Гц

    Разрядность ЦАП`a сделанного на основе ШИМ эквивалентна разрядности используемого счетчика.

    1.2 Аппаратная реализация ШИМ

    Все современные микроконтроллеры имеют в своем составе таймеры/счетчики. Один или несколько режимов этих таймеров предназначены для генерации ШИМ сигнала. Как правило этот сигнал генерируется на специальных выводах. Например, у микроконтроллера mega16 фирмы Atmel 8-ми разрядный таймер/счетчик Т0 имеет два режима генерации ШИМ сигнала (быстрый ШИМ и ШИМ с точной фазой), а для вывода сигнала используется пин порта B - OC0 (PINB3).

    Достоинство аппаратной реализации ШИМ сигнала - это низкая загрузка микроконтроллера (прерывание вызывается один раз в период ШИМ сигнала), простота использования и точность (если в системе мало прерываний). Из недостатков можно отметить - ограниченное разрешение счетчиков, невысокая частота, ограниченное число каналов, на которых можно генерировать ШИМ сигналы. Хотя существуют специальные микроконтроллеры специально "заточенные" для генерации большого количества ШИМ сигналов.

    1.3 Программная реализация ШИМ

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

    Преимущество программной реализации - простота, неограниченное количество каналов, неограниченное разрешение. Конечно, условно неограниченное, с учетом доступной памяти. Недостатки программной реализации - высокая загрузка микроконтроллера. Прерывания должны вызываться на каждый инкремент счетчика и каждый раз нужно проверять не достиг ли он одного из крайних значений. Также программная реализация имеет меньшую точность (большее дрожание фронтов сигнала) и еще меньшую частоту (из-за первого недостатка).

    Однако, несмотря на это, программная реализация ШИМ`а тоже имеет место быть, если требуется генерировать постоянный аналоговый сигнал или переменный, но с невысокой частотой.

    Ниже приведен пример кода, который выполняет функцию генерацию аналогового сигнала с помощью аппаратной и программной широтно-импульсной модуляции. Код написан для микроконтроллера atmega16, тактовая частота 8 МГц, компилятор IAR. На выходах PB2 и PB3 генерируются две синусоиды (разной частоты) из 32 двух отсчетов.


    #include
    #include
    #include

    #define SPWM_PIN 2

    //таблица синуса
    __flash uint8_t tableSin =
    {
    152,176,198,218,234,245,253,255,
    253,245,234,218,198,176,152,128,
    103, 79, 57, 37, 21, 10, 2, 0,
    2, 10, 21, 37, 57, 79,103,128
    };

    uint8_t softCount = 0;
    uint8_t softComp = 0;

    int main(void)
    {
    //настройка портов
    PORTB = 0;
    DDRB = 0xff;

    //разрешение прерывания по совпадению Т0
    TIMSK = (1< //режим FastPWM, неинв. шим сигнал, предделитель 8
    TCCR0 = (1< (0<

    //обнуляем счетный регистр
    TCNT0 = 0;
    OCR0 = 0;

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

    //прерывание таймера Т0
    #pragma vector = TIMER0_COMP_vect
    __interrupt void Timer0CompVect(void)
    {
    static uint8_t i = 0;
    static uint8_t j = 0;

    OCR0 = tableSin[i];
    i = (i + 1) & 31;

    //программный ШИМ
    softCount++;
    if (softCount == 0){
    PORTB |= (1< softComp = tableSin[j];
    j = (j + 1) & 31;
    }

    If (softCount == softComp){
    PORTB &= ~(1< }
    }

    1.4 Фильтр для ШИМ

    Частота среза фильтра должна быть между максимальной частотой генерируемых аналоговых сигналов и частотой ШИМ сигнала. Если частота среза фильтра будет выбрана близко к границе полосы аналогового сигнала, это приведет к его ослаблению. А если частота среза фильтра будет близко к частоте ШИМ сигнала, аналоговый сигнал просто не "выделится". Чем выше частота ШИМ сигнала, тем проще реализовать выходной фильтр.

    Рассмотрим пример. ШИМ сигнал генерируется аппаратным 8-ми разрядным счетчиком с коэффициентом предделителя равным 8, тактовая частота микроконтроллера 8МГц, количество отсчетов аналогового сигнала - 32.

    Частота ШИМ сигнала будет равна:

    Fpwm = Fcpu/(K*2^n) = 8000000/(8*256) = ~3906 Гц

    Частота аналогового сигнала будет равна:

    Fa = Fpwm/Ns = 3906/32 = 122 Гц

    Выберем частоту среза равную 200 Гц и рассчитаем номиналы пассивного низкочастотного RC фильтра. Частота среза такого фильтра определяется выражением:

    Fc = 1/(2*Pi*R*C),

    где R - номинал резистора (Ом), а C -емкость конденсатора (Ф).

    Задавшись номиналом одного из компонентов можно вычислить номинал второго. Для резистора номиналом 1 кОм, емкость конденсатора будет равна:


    C = 1/(2*Pi*Fc*R) = 1/(6.28 * 1000*200) = ~0.8 мкФ


    Выбираем ближайшее значение из ряда E12 - 0.82 мкФ. При таких номиналах фильтра мы получим уже похожий аналоговый сигнал.

    Однако, как правило, одного звена пассивного фильтра будет не достаточно. Потому что после него аналоговых сигнал все еще будет содержать большое количество гармоник.