Как открыть доступ к sd карте. Как управлять разрешениями для отдельных приложений на Android

Мы затронули тему использования счётчика/таймера ATtiny13 в обычном режиме и в режиме подсчёта импульсов (CTC). В этой статье я продолжаю тему таймера, но теперь мы рассмотрим его применение для реализации широтно-импульсной модуляции (ШИМ).

Все микропроцессоры работают с цифровыми сигналами, т.е. с логическим нулем (0 В), и логической единицей (5 В или 3.3 В). Но что делать, если мы хотим получить на выходе какое-либо промежуточное значение? В таких случаях применяют Широтно-импульсную модуляцию (ШИМ, англ. pulse-width modulation (PWM)) — процесс управления мощностью, подводимой к нагрузке, путём изменения скважности импульсов, при постоянной частоте.
Широтно-импульсная модуляция представляет собой периодический импульсный сигнал. Существуют цифровые и аналоговые ШИМ, однополярные и двуполярные, и т.д. Но принцип их работы остается одинаковым вне зависимости от исполнения и заключается в сравнении двух видов сигналов: опорного (пилообразные или треугольные импульсы) и входного (постоянного, либо изменяемого нужным образом, в зависимости от конкретной задачи ШИМ). Эти сигналы сравниваются и, при их пересечении, изменяется уровень сигнала на выходе ШИМ. Выходное напряжение ШИМ имеет вид прямоугольных импульсов, изменяя их длительность, мы можем регулировать среднее значение напряжения на выходе ШИМ *.

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

Параметры ШИМ

  • T - период тактирования (опорного сигнала);
  • t - длительность импулься;
  • S - скважность;
  • D - коэффициент заполнения.

Скважность определяется отношением периода к длительности импульса. Коэффициент заполнения - величина, обратная скважности (может выражаться в процентах):

S=T/t=1/D

Рассмотрим подробнее, как работает ШИМ в AVR микроконтроллерах, на примере ATtiny13.
Как уже упоминалось в предыдущем примере , в ATtiny13 реализовано две разновидности ШИМ: так называемые "Быстрая ШИМ" (Fast PWM) и "ШИМ с коррекцией фазы" (Phase correct PWM). Оба варианта основаны на использовании встроенного в МК восьмибитного счётчика/таймера T0. Таймер тут используется вместо опорного сигнала. Тактовая частота таймера задаётся предделителем тактовой частоты процессора, либо от внешнего тактового генератора. Режим тактирования задаётся битами CS02 (2), CS01 (1), CS00 (0) регистра TCCR0B :

  • 000 - таймер/счетчик T0 остановлен
  • 001 - тактовый генератор CLK
  • 010 - CLK/8
  • 011 - CLK/64
  • 100 - CLK/256
  • 101 - CLK/1024
  • 110 - от внешнего источника на выводе T0 (7 ножка, PB2) по спаду сигнала
  • 111 - от внешнего источника на выводе T0 (7 ножка, PB2) по возрастанию сигнала

Настройка таймера для ШИМ

Режим работы таймера задаётся битами WGM01 (1) и WGM00 (0) регистра TCCR0A :

  • 00 - обычный режим
  • 01 - режим коррекции фазы ШИМ
  • 10 - режим подсчета импульсов (сброс при совпадении)
  • 11 - режим ШИМ

Здесь нас интересуют варианты "01" и "11".

Биты COM0A1 (7) и COM0A0 (6) регистра TCCR0A задают, какой сигнал появится на выводе OC0A (5 ножка, PB0) при совпадении счётчика (регистр TCNT0 ) с регистром сравнения A (OCR0A ).

В режиме "Быстрая ШИМ":

  • 10 - установка 0 на выводе OC0A при совпадении с A, установка 1 на выводе OC0A при обнулении счётчика (неинверсный режим)
  • 11 - установка 1 на выводе OC0A при совпадении с A, установка 0 на выводе OC0A при обнулении счётчика (инверсный режим)
  • 00 - вывод OC0A не функционирует
  • 01 - если бит WGM02 регистра TCCR0B установлен в 0, вывод OC0A не функционирует
  • 01 - если бит WGM02 регистра TCCR0B установлен в 1, изменение состояния вывода OC0A на противоположное при совпадении с A
  • 10 - установка 0 на выводе OC0A при совпадении с A во время увеличения значения счетчика, установка 1 на выводе OC0A при совпадении с A во время уменьшения значения счетчика (неинверсный режим)
  • 11 - установка 1 на выводе OC0A при совпадении с A во время увеличения значения счетчика, установка 0 на выводе OC0A при совпадении с A во время уменьшения значения счетчика (инверсный режим)

Биты COM0B1 (5) и COM0B0 (4) регистра TCCR0A задают, какой сигнал появится на выводе OC0B (6 ножка, PB1) при совпадении счётчика (регистр TCNT0 ) с регистром сравнения B (OCR0B ).

В режиме "Быстрая ШИМ":

  • 01 - резерв
  • 10 - установка 0 на выводе OC0B при совпадении с B, установка 1 на выводе OC0B при обнулении счётчика (неинверсный режим)
  • 11 - установка 1 на выводе OC0B при совпадении с B, установка 0 на выводе OC0B при обнулении счётчика (инверсный режим)

В режиме "ШИМ с коррекцией фазы":

  • 00 - вывод OC0B не функционирует
  • 01 - резерв
  • 10 - установка 0 на выводе OC0B при совпадении с B во время увеличения значения счетчика, установка 1 на выводе OC0B при совпадении с B во время уменьшения значения счетчика (неинверсный режим)
  • 11 - установка 1 на выводе OC0B при совпадении с B во время увеличения значения счетчика, установка 0 на выводе OC0B при совпадении с B во время уменьшения значения счетчика (инверсный режим)

Быстрая ШИМ (Fast PWM)

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

ШИМ с коррекцией фазы (Phase correct PWM)

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

Чтобы увидеть наглядно, как работает ШИМ, напишем небольшую программу (все опыты я провожу на своей отладочной плате , соответственно код привожу применительно к ней):

/* * tiny13_board_pwm * Демо-прошивка отладочной платы на ATtiny13. * Демонстрация работы ШИМ на двух каналах: * неинверсный сигнал на выходе OC0A, инверсный - на выходе OC0B. */ #define F_CPU 1200000UL #include #include #define LED0 PB0 // OC0A #define LED1 PB1 // OC0B int main(void) { // Светидиоды: DDRB |= (1 << LED0)|(1 << LED1); // выходы = 1 PORTB &= ~((1 << LED0)|(1 << LED1)); // по умолчанию отключены = 0 // Таймер для ШИМ: TCCR0A = 0xB3; // режим ШИМ, неинверсный сигнал на выходе OC0A, инверсный - на выходе OC0B TCCR0B = 0x02; // предделитель тактовой частоты CLK/8 TCNT0=0; // начальное значение счётчика OCR0A=0; // регистр совпадения A OCR0B=0; // регистр совпадения B while(1) { do // Нарастание яркости { OCR0A++; OCR0B = OCR0A; _delay_ms(5); } while(OCR0A!=255); _delay_ms(1000); // Пауза 1 сек. do // Затухание { OCR0A--; OCR0B = OCR0A; _delay_ms(5); } while(OCR0A!=0); _delay_ms(1000); // Пауза 1 сек. } }

Тут мы видим, что при старте МК в регистры сравнения A и B устанавливается 0, а счётчик запускается в режиме Fast PWM, с генерацией неинверсного ШИМ сигнала на выходе OC0A и инверсного - на выходе OC0B. В основном цикле значения регистров сравнения плавно меняются от 0 до максимума и обратно. В результате, светодиоды, подключенные к выводам OC0A и OC0B, будут поочерёдно плавно загораться и гаснуть, как бы в противофазе.
Но если приглядеться внимательнее, то видим, что один из светодиодов гаснет не до конца, а продолжает тускло светиться. Эта особенность характерна для Fast PWM режима. Дело в том, что в этом режиме, даже если записать в регистр сравнения 0, при обнулении счётчика на выходе всё равно устанавливается логическая единица, которая сбрасывается в следующем такте (по совпадению с регистром сравнения). Таким образом, в каждом периоде будет проскакивать по одному короткому импульсу длительностью 1 такт, но этого достаточно для засвечивания светодиода. Этот эффект отсутствует в инверсном режиме формирования выходных импульсов, т.к. в данном случае при обнулении счётчика будет происходить не короткий импульс, а наоборот - короткий провал во время максимального заполнения ШИМ. Этот провал можно увидеть на осциллографе, но такое мерцание светодиода человеческое зрение просто не заметит. Поэтому второй светодиод загорается и гаснет полностью. В режиме ШИМ с коррекцией фазы, этот эффект отсутствует независимо, инверсный сигнал формируется на выходе или нет. Поменяем значение бита WGM01 (1) регистра TCCR0A с 1 на 0.

26 сентября 2012 в 13:36

Многоканальный программный ШИМ в AVR

  • Разработка под Arduino

Что такое ШИМ и как он работает особо подробно расписывать не буду, информацию без труда найдёте на просторах интернета. Коснусь лишь общих понятий. ШИМ - это Широтно-Импульсная Модуляция, (по-английски 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-биты (см. выше).



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: