OSBoy notes.

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

ATtiny13 - Регулятор ШИМ с использованием АЦП

В предыдущем примере был подробно рассмотрен принцип работы ШИМ в AVR микроконтроллерах, на примере ATtiny13. Теперь же рассмотрим пример реализации ШИМ регулятора.

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

/*
 * tiny13_board_adc_pwm
 * Демо-прошивка отладочной платы на ATtiny13.
 * Демонстрация работы ШИМ-регулятора (в режиме коррекции фазы) на двух каналах:
 * неинверсный сигнал на выходе OC0A, инверсный - на выходе OC0B.
 * ШИМ-сигнал регулируется переменным резистором:
 * напряжение с него подаётся на вход АЦП
 * и, в зависимости от величины измеренного напряжения,
 * изменяется время отсчёта для таймера ШИМ.
 */
 
#define F_CPU 1200000UL
#include <avr/io.h>
#include <avr/interrupt.h>

#define LED0 PB0 // OC0A
#define LED1 PB1 // OC0B
#define ANALOG_IN PB4 //ADC2

// Обработчик прерывания по завершению преобразования АЦП
ISR(ADC_vect)
{
  // Записываем значение, полученное на выходе АЦП в регистры сравнения таймера. 
  // Здесь же, задаём минимальный порог свечения светодиода, после которого он будет гарантированно гаснуть
  OCR0A=(ADCH < 10) ? 0 : ADCH;
  OCR0B=(ADCH > 245) ? 255: ADCH;
}

int main(void)
{
  // Светидиоды:
  DDRB |= (1 << LED0)|(1 << LED1); // выходы = 1
  PORTB &= ~((1 << LED0)|(1 << LED1)); // по умолчанию отключены = 0
  // Перем. резистор:
  DDRB &= ~(1 << ANALOG_IN); // вход = 0
  // Таймер для ШИМ:
  TCCR0A = 0xB1; // режим коррекции фазы ШИМ, неинверсный сигнал на выходе OC0A, инверсный - на выходе OC0B
  TCCR0B = 0x02; // предделитель тактовой частоты CLK/8
  TCNT0 = 0; // начальное значение счётчика
  OCR0A = 0; // регистр сравнения A
  OCR0B = 0; // регистр сравнения B
  // Настройка АЦП:
  ADMUX = 0x22; // опорное напряжение - VCC, левое ориентирование данных, выбран вход ADC2 (на нём висит перем. резистор)
  ADCSRA = 0xEA; // АЦП включен, запуск преобразования, режим автоизмерения, прерывание по окончанию преобразования, частота CLK/4
  ADCSRB = 0x00; // режим автоизмерения: постоянно запущено
  DIDR0 |= (1 << ANALOG_IN); // запрещаем цифровой вход на ноге аналогового входа
  
  sei(); //разрешаем глобально прерывания
  
  while(1)
  {
  }
}

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

Настройка АЦП

Рассмотрим подробнее, какие регистры отвечают за работу АЦП.

Регистр ADMUX (ADC Multiplexer Selection Register).
Для ATtiny13 его структура выглядит следующим образом:

Структура регистра ADMUX для ATtiny13
Структура регистра ADMUX для ATtiny13

Бит 6 - REFS0 (Reference Selection Bit): выбирает опорный сигнал напряжения для АЦП: *

  • 0 - в качестве опорного используется напряжение Vсс;
  • 1 - используется внутренний источник опорного напряжения 1.1В.

Бит 5 - ADLAR (ADC Left Adjust Result): Определяет порядок записи результатов преобразования в регистры ADCL и ADCH (см. ниже).

Биты 1-0 - MUX1-0 (Analog Channel Selection Bits): Отвечают за выбор входного канала: *

  • 00 - ADC0 (PB5);
  • 01 - ADC1 (PB2);
  • 10 - ADC2 (PB4);
  • 11 - ADC3 (PB3).

* Если какой-либо из битов REFS0, MUX0, MUX1 изменяется во время преобразования, изменение не будет действовать до тех пор, пока это преобразование не будет завершено (пока не установится бит ADIF в регистре ADCSRA).

Остальные биты не используются (зарезервированы) и доступны только для чтения (в них всегда нули).

Регистр ADCSRA (ADC Control and Status Register A):

Структура регистра ADCSRA
Структура регистра ADCSRA

Бит 7 - ADEN (ADC Enable): Установка этого бита активирует АЦП. Если сбросить этот бит во время преобразования, то преобразование остановится.

Бит 6 - ADSC (ADC Start Conversion): Запускает преобразование. В режиме одиночного преобразования, нужно устанавливать этот бит для каждого преобразования. В режиме автоматического преобразования - нужно установить этот бит для запуска первого преобразования.

Бит 5 - ADATE (ADC Auto Trigger Enable): Активирует автопреобразование. АЦП начнет преобразование на положительном фронте триггерного сигнала. Источник триггера выбирается установкой бита выбора триггера АЦП: ADTS регистра ADCSRB.

Бит 4 - ADIF (ADC Interrupt Flag): Флаг прерывания АЦП. Устанавливается по завершению преобразования, когда регистры данных обновились.

Бит 3 - ADIE (ADC Interrupt Enable): Разрешает прерывания АЦП.

Биты 2-0 - ADPS2-0 (ADC Prescaler Select Bits): Устанавливают предделитель тактовой частоты для АЦП:

  • 000 - 2;
  • 001 - 2;
  • 010 - 4;
  • 011 - 8;
  • 100 - 16;
  • 101 - 32;
  • 110 - 64;
  • 111 - 128.

Регистры данных ADCH и ADCL.

Результат аналого-цифрового преобразования может принимать значения от 0 до 2n-1 (где n - число разрядов АЦП). В ATtiny13 на борту десятиразрядный АЦП. Соответственно, результат АЦП может принимать 1024 значения (от 0 до 1023).
0 (0x00) - будет соответствовать входному напряжению 0В;
1023 (0x3FF) - входному напряжению, равному выбранному опорному (в зависимости от значения бита REFS0 регистра ADMUX).

По завершению преобразования, результат записывается в регистры данных ADCH и ADCL (соответственно, cтарший и младший регистры данных АЦП), в зависимости от значения бита ADLAR регистра ADMUX. Если бит ADLAR установлен - то ориентирование данных левое, если сброшен (по умолчанию) - то правое:

Порядок записи результата АЦП в регистры данных в зависимости от значения бита ADLAR
Порядок записи результата АЦП в регистры данных в зависимости от значения бита ADLAR

Когда ADCL считывается, регистр данных ADC не обновляется, пока не будет прочитан ADCH. Таким образом, если выбрано левое ориентирование данных (ADLAR=1) и нам достаточно 8-битной точности, то достаточно прочитать значение ADCH. В противном случае - сначала нужно считать значение ADCL, затем ADCH.

Регистр ADCSRB (ADC Control and Status Register B):

Структура регистра ADCSRB
Структура регистра ADCSRB

Биты 2-0 - ADTS2-0 (ADC Auto Trigger Source): Выбирают источник сигнала для старта преобразования в режиме автоизмерения. Преобразование будет запускаться каждый раз при установке выбранного флага прерывания:

  • 000 - постоянно запущено;
  • 001 - аналоговый компаратор;
  • 010 - внешнее прерывание INT0;
  • 011 - таймер/счётчик T0, по совпадению с регистром сравнения A;
  • 100 - таймер/счётчик T0, по переполнению;
  • 101 - таймер/счётчик T0, по совпадению с регистром сравнения B;
  • 110 - внешнее прерывание PCINT0.

Бит 6 - ACME (Analog Comparator Multiplexer Enable): в этой статье я не буду затрагивать применение этого бита, т.к. аналоговый компаратор - это уже отдельная тема.

Остальные биты не используются (зарезервированы) и доступны только для чтения (в них всегда нули).

Регистр DIDR0 (Digital Input Disable Register 0).

Структура регистра DIDR0
Структура регистра DIDR0

Биты 5-2 - ADC3D-ADC0D (ADC3-0 Digital Input Disable): Если какой-либо из входов ADC3-0 используется как аналоговый, то в соответствующий бит ADC3D-ADC0D нужно устанавливать логическую единицу. При этом, буфер цифрового ввода на соответствующем выводе АЦП отключается для экономии энергии. Соответствующий бит PIN-регистра при этом всегда будет содержать ноль.

Возвращаемся к нашей программе.

Итак, мы настроили таймер для ШИМ, и запустили АЦП: он будет постоянно отслеживать напряжение на входе ADC2, регулируемое переменным резистором, а каждый раз по окончанию преобразования будет срабатывать прерывание ISR(ADC_vect), в теле которого будут изменяться значения регистров сравнения для ШИМ-таймера, в зависимости от измеренного напряжения на входе АЦП. И не забываем, после настройки прерывания, разрешить прерывания глобально!

Полезные ссылки:
ATtiny13 Datasheet (официальный)
ATtiny13 Datasheet (урезанный вариант)
AVR. Учебный курс. Использование АЦП
Модуль АЦП

Теги : attiny13, AVR, микроконтроллеры, ШИМ, PWM, АЦП, ADC, программирование, си, C

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

Вадим
#Ответить
Отлично разжевал тему по ШИМу в двух статьях. Спасибо. А то по другим сайтам заумно очень.
Владимир
#Ответить
if (ADCH < 10) ADCH = 0x00;

Регистр ADCH только на чтение
То есть Вы сами не проверяли что пишите
OSBoy
#Ответить
Да, тут Вы правы - то что ADCH и ADCL доступны только для чтения - я не доглядел. Код я поправил, спасибо за замечание. Однако, это не значит, что я не проверяю то, что пишу. В железе это разглядеть невооружённым взглядом не так просто.
Максим
#Ответить
А разве в ADMUX дело обстоит не так?
"• Bits 4:2 – Res: Reserved Bits
These bits are reserved bits in the ATtiny13 and will always read as zero."
То есть, раз биты 2,3,4 - нули, то строка:
ADMUX = 0x22;
по идее должна быть заменена на:
ADMUX = 0x34; // биты такие: 00100010

Правильно я понимаю? Я просто перевожу с "оленекода" устройства на Ардуине на Тини13, а заодно пытаюсь познать прелести работы с портами и прочим, чтобы как-то ужать 3 кБ в 1 кБ :-)

Вашу статью как раз пытаюсь использовать для настройки АЦП. Как осилю, надо будет и с ШИМ разобраться.. Пока туго идёт.
Максим
#Ответить
Это же HEX !! Блин))))
34 в десятичной это 22 в HEX,
Понял (заметил) это когда стал побитно раскладывать вашу запись:
ADCSRA = 0xEA;
Не.. нельзя до 6 утра копаться в этом.. мозги дают сбои))
Максим
#Ответить
ADCSRA = 0xEA; - это CLK/4 , а не CLK/8

Для CLK/8 вроде бы надо так:
ADCSRA = 0xEB;

В связи с этим как раз вопрос: по какому принципу вы выбираете делитель? Методом проб или есть какие-то рекомендации?
Мне вот надо брать аналоговый сигнал 0.6-4.2В с аналогового датчика Холла раз в 100 мс или чаще (в идеале ещё бороться с шумом, а то наблюдаются дрожания - мне подходит только программный способ, но в тиньку это всё конечно не влезет), и затем выводить на выход ШИМ (PB0) с последующей RC-фильтрацией и получением сигнала, аналогичного входному по напряжению, только с ньюансами:
при нарастании - с задержкой во времени по определённому математическому закону, а при уменьшении - спад до уровня равному входному без задержки.
Это "плавное газование с резким спадом" для лектровелосипеда. Плюс ещё там замут с памятью недавнего состояния, чтобы было отличие когда газуешь с места или после езды на скорости и кратковременного сброса. Но это не так важно..

Не представляю какие частоты (делители) брать что на АЦП, что на ШИМ..
OSBoy
#Ответить
ADCSRA = 0xEA; - это CLK/4 , а не CLK/8 - да, всё верно, спасибо. Исправил.
Впрочем, в моём случае - в выборе частоты ШИМ разницы особо никакой (тут главное, чтобы глазу были незаметны мерцания светодиоды и подходит любая частота больше 50 Гц). Единственное - подозреваю, что при большей частоте, будет больше нагрузка на МК, а значит и тепловыделение и энергопотребление.
А вот для управления электромотором, может быть и есть смысл с частотой поэксперементировать, хотя бы для того, чтобы уйти подальше от звукового диапазона (чтобы двигатель не свистел).
OSBoy
#Ответить
А по поводу АЦП - Вы же сами сказали, что "раз в 100 мс или чаще" - отсюда и отталкивайтесь!
Раз в 100мс =10 Гц
Если тинька работает на 1,2МГц, то например с делителем (CLK/128) и по переполнению счётчика Т0 можно выставить частоту: (1200000/128):256=36,6Гц
Это как вариант.
СС
#Ответить
В какой строчке мы зажигаем светодиоды??)
Александр
#Ответить
" ADMUX = 0x22; // опорное напряжение - VCC, левое ориентирование данных, выбран вход ADC2 (на нём висит перем. резистор)"
Если ADMUX = 0x22 (100010), то опорное напряжение 1.1 в. Откуда взять это напряжение,как включить резистор и как отобразить все это в тексте программы?
OSBoy
#Ответить
Если речь идёт о tiny13, то у него при ADMUX = 0x22 - опорное напряжение берётся Vcc, то есть напряжение питания! Для использования внутреннего напряжениея 1.1В должно быть ADMUX = 0x62 (0b1100010) - смотрите внимательнее даташит. Брать 1.1В ниоткуда не надо, МК его сам вырабатывает и подключает, если оно выбрано в регистрах. Перем. резистор можно подключить на любой вход ADCn. У меня он на ADC2 подключен на отладочной плате, поэтому соответственно его указываем в программе: биты MUX1:0 = 10 (два младших бита регистра ADMUX). Пример, как подключать переменник, можно посмотреть на схеме моей отладочной платы (ссылка в начале статьи).

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

Отменить