OSBoy notes.

Записки обо всём...

ATtiny13 - Регулятор ШИМ с управлением кнопками

В одном из предыдущих примеров я уже показывал, как на ATtiny13 можно реализовать ШИМ регулятор с управлением потенциометром (изменением уровня сигнала на входе АЦП). Теперь, по просьбам трудящихся, усложним задачу и рассмотрим, как можно управлять ШИМ сигналом на выходе с помощью кнопок.
Сложность тут заключается в том, что при управлении кнопками, скважность ШИМ сигнала на выходе будет изменяться ступенчато при удерживании кнопки, а между этими "ступенями" необходимо реализовать определённые временные задержки для более или менее плавного изменения ШИМ сигнала. Кроме того, даже если мы будем управлять выходом более грубо, отдельными короткмим нажатиями кнопки, задержки всё равно потребуются - для устранения эффекта "дребезга" контактов при нажатии и отпускании кнопок. Но, так как на выходе у нас должен постоянно генерироваться ШИМ сигнал, то вносить задержки в основной цикл программы (например с помощью библиотеки delay) мы не можем, потому что при этом наш ШИМ сигнал будет прерываться во время выдержки каждой паузы. Поэтому для обработки нажатий на кнопки логично будет задействовать внешние прерывания, а временные задержки (паузы) организовать с помощью таймера. Тут вроде бы опять незадача: ведь таймер на борту у тини13 всего один, а он у нас уже задействован для формирования ШИМ сигнала... Но на самом деле всё совсем не так плохо. Ведь таймер, при желании, может выполнять и сразу несколько задач...

Все опыты я провожу на своей отладочной плате, соответственно код привожу применительно к ней:

/*
* tiny13_board_pcint_pwm
* Демо-прошивка отладочной платы на ATtiny13.
* Демонстрация работы ШИМ-регулятора (в режиме коррекции фазы):
* неинверсный сигнал на выходе OC0A.
* ШИМ-сигнал регулируется двумя кнопками (+/-):
* по нажатию кнопки срабатывает прерывание pcint0
* и, в зависимости от того, какая из кнопок нажата,
* увеличивается либо уменьшается значение регистра сравнения OCR0A для таймера ШИМ.
*/ #define F_CPU 1200000UL #include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #define PWM PB0 // OC0A #define BUTTONPLUS PB3 // PCINT3 #define BUTTONMINUS PB4 // PCINT4 #define BUTTONPLUS_PRESSED (!((PINB >> BUTTONPLUS) & 1)) #define BUTTONMINUS_PRESSED (!((PINB >> BUTTONMINUS) & 1)) volatile uint8_t state = 0; // зафиксированное состояние volatile uint8_t counter = 0; // счётчик для обработки нажатий кнопок int main(void) { // Выход ШИМ: DDRB |= (1 << PWM); // выход = 1 PORTB &= ~(1 << PWM); // по умолчанию отключен = 0 // Входы кнопок: DDRB &= ~((1<<BUTTONPLUS)|(1<<BUTTONMINUS)); // входы = 0 PORTB |= (1<<BUTTONPLUS)|(1<<BUTTONMINUS); // подтянуты = 1 // Настройка таймера T0 для ШИМ и обработки нажатий кнопок: TCCR0A = 0b10000001; // режим коррекции фазы ШИМ, неинверсный сигнал на выходе OC0A TCCR0B = 0b00000010; // предделитель тактовой частоты CLK/8 TCNT0 = 0; // начальное значение счётчика OCR0A = 0; // начальное значение регистра сравнения A (для формирования ШИМ сигнала на выходе) TIMSK0 = 0b00000010; // разрешаем прерывания по переполнению счётчика // Настройка внешних прерываний GIMSK |= (1<<PCIE); // Разрешаем внешние прерывания PCINT0. PCMSK |= (1<<BUTTONPLUS)|(1<<BUTTONMINUS); // Разрешаем по маске прерывания на ногак кнопок sei(); //разрешаем глобально прерывания // Основной цикл while(1) { } } // Обработчик прерывания PCINT0 ISR(PCINT0_vect) { if ( state == 0 ) { if ( BUTTONPLUS_PRESSED && !BUTTONMINUS_PRESSED ) // если нажата кнопка "+" state = 1; else if ( !BUTTONPLUS_PRESSED && BUTTONMINUS_PRESSED ) // если нажата кнопка "-" state = 2; } } ISR(TIM0_OVF_vect) { if ( state != 0 ) { if (counter < 29) counter++; // отсчитываем ~100мс else { if (state == 1) { if (OCR0A <= 250) OCR0A += 5; // увеличиваем значение регистра совпадения для ШИМ таймера, пока не достигнет максимума if ( !BUTTONPLUS_PRESSED ) // если кнопка "+" была отпущена state = 0; // сбрасываем состояние } else if (state == 2) { if (OCR0A >= 5) OCR0A -= 5; // уменьшаем значение регистра совпадения для ШИМ таймера, пока не достигнет нуля if ( !BUTTONMINUS_PRESSED ) // если кнопка "-" была отпущена state = 0; // сбрасываем состояние } counter = 0; // сбрасываем счётчик } } }

Я взял код из предыдущего примера ШИМ регулятора и переделал его под нашу задачу - управление двумя кнопками: при нажатии на кнопку "+" (PCINT3) скважность ШИМ сигнала будет уменьшаться, а среднее значение сигнала при этом будет увеличиваться до тех пор, пока коэффициент заполнения не станет максимальным; а при нажатии на кнопку "-" (PCINT4) - соответственно, наоборот.

Теперь поподробнее рассмотрим ключевые моменты нашего кода.

Так как кнопок у нас будет две, то использовать аппаратное прерывание INT0 тут не катит, поэтому используем програграммное прерывание PCINT0, которому по маске PCMSK разрешаем использование ножек PCINT3 и PCINT4, к которым у нас подключены кнопки. В обработчике прерывания PCINT0_vect проверяем, какая из кнопок была нажата и присваиваем соответствующее значение глобальной переменной state. Таким образом мы, как бы, фиксируем факт нажатия кнопки и всё. Всю же остальную рутину, включая реализацию антидребезга, оставляем на откуп прерыванию по таймеру.

Итак, мы дошли до таймера, давайте разбираться с ним.
В данном случае таймер у нас настраивается для генерирования ШИМ сигнала на выходе OC0A. Изменяя значение регистра сравнения OCR0A, мы будем изменять скважность ШИМ сигнала. Но, пока таймер генерирует ШИМ сигнал, ничто нам не мешает, так же, использовать его прерывания. Тут нам доступны два прерывания по совпадению с регистрами сравнения: OCR0A (он у нас занят для генерирования ШИМ сигнала) и OCR0B. А так же, прерывание по переполнению счётчика - вот его мы и будем использовать для выполнения всей основной рутины. Чтобы разрешить прерывания по переполнению счётчика, выставляем бит TOIE0 регистра TIMSK0.
Таким образом, одним таймером мы убили сразу двух зайцев. И при этом, у нас ещё в запасе осталось прерывание по совпадению с OCR0B, на которое, при необходимости, тоже можно повесить выполнение какой-нибудь полезной работы!

Ну и, наконец, посмотрим, что же у нас происходит в обработчике прерывания по переполнению таймера TIM0_OVF_vect.
Каждый раз, при срабатывании, обработчик проверяет значение глобальной переменной state. Если значение state отлично от нуля, значит была нажата одна из кнопок, поэтому нужно выдержать некоторую паузу, в течение которой утихнет дребезг контактов, в данном случае 100мс. Для этого внутри обработчика, образно говоря, запускается свой счётчик, с каждым очередным прерыванием увеличивая значение глобальной переменной counter. Значение, до которого должен увеличиваться  счётчик, определяется следующим образом:

counter = t*Fcpu/(8*2*T) = 0,1с*1200000Гц/(8*2*256) = 29,29

Где:

  • t - требуемое время отсчёта (100мс = 0,1с);
  • Fcpu - тактовая частота МК;
  • 8 - предделитель частоты, выставленный в TCCR0B;
  • T - период счёта таймера (от 0 до 255);
  • 2 - т.к. используется ШИМ с коррекией фазы, то переполнение счётчика будет наступать каждые 2 периода счёта.

Таким образом, для выдержки паузы в 100мс, прерывание по переполнению счётчика должно наступить примерно 29 раз.

Затем, проверив значение переменной state, обработчик проверяет, какая именно из двух кнопок была нажата и, соответственно, изменяет значение регистра сравнения OCR0A в ту или иную сторону. После этого остаётся только проверить, осталась  ли кнопка нажатой: если да, то процесс повторяется (при этом паузы в 100мс будут играть роль задержек между "ступеньками" изменения ШИМ сигнала). Если же кнопка была отпущена, то значение state сбрасывается в 0 и при следующем срабатывании прерывания обработчик, проверив значение state, просто завершится, ничего больше не делая.

Полезные ссылки:
ATtiny13 Datasheet (официальный)
ATtiny13 Datasheet (урезанный вариант)

Теги : attiny13, AVR, микроконтроллеры, ШИМ, PWM, внешние прерывания, кнопка, PCINT0, программирование, си, C

Комментариев: 1

Игорь
#Ответить
Очень прикольно
Добавив несколько строк кода и использовав 2 свободных вывода + PB5 можно и индикацию на светодиодах сделать сразу сделать на 6 уровней :)
Не знаю правда памяти хватит или нет Надо поэксперементировать :)
Ещё раз спасибо за пример :)

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

Отменить