Перипетии нуба с ШИМом на Attiny13 / Блог им. AnderWonder / Сообщество EasyElectronics.ru
Возникла давеча простенькая задачка. Нужен был регулируемый вручную ШИМ для теста одной идейки. Под рукой тинька 13 — т.е. цепляем переменник на АЦП и выводим что нам нужно. Казалось бы — проще только светодиодом помигать. Однако. Смеркалось.Всё быстро воткнуто в макетку, с помощью мастера в CodeVision несколькими щелчками собран проект. Для теста на выход — светодиод. Включаю: горит на полную, кручу резистор — реакции нет. Шустро проверяю все соединения, питание, схему, прошивку — эффект остается. После трехкратного повторения тех же действий возникло недоумение. Беру другую тиньку, прошиваю, включаю — болт. Похоже проблема не в чипе. Некоторое время созерцаю код сгенерированный CodeVision — придраться не к чему. Открываю datasheet, вникаю. Изучил ADC, проверил — всё по канонам. Перешёл к ШИМ.
Краткое описание устройства аппаратного ШИМ на Attiny13 для начинающих:
ШИМ сделан на базе таймера. Т.е. у таймера есть несколько режимов работы, два из них относятся к ШИМу (FastPWM и Phase Correct PWM). Таймер настраивается с помощью двух регистров: TCCR0A,TCCR0B. В них задается режим, частота (делитель), какие каналы используются (есть два — 0A и 0B), режим работы выхода(прямой, инвертированный). Значение ШИМа задаются в регистрах OCR0A и OCR0B — соответственно для каждого канала. Есть ещё у ШИМа такая настройка — чем определяется максимальное значение таймера(TOP), при достижении которого он сбрасывается и бежит с начала — это может быть либо 0xFF, либо значение в регистре OCR0A. У меня был установлен второй режим и значение ШИМа я задавал в регистре же OCR0A.
Короче, следующие грабли. Яркость регулируется, но вот беда: когда довожу ручку до минимума всё равно подсвечивает. Т.е. на АЦП у нас 0, а на выходе не 0. Обидно, понимаешь. Причина такого поведения в том, что в тот момент, когда таймер сбрасывается в 0, на выходе чип выставляет 1, и хотя значение ШИМа у нас задано 0, и уже в следующем такте он это видит и обнуляет выход, но вот этого несчастного скачка достаточно что бы светодиодик светил. Дискомфорт — ты ждешь на выходе чистый 0, а тут тебе гребеночка такая. Вообще говоря проблема известная. Решение приходит в голову практически сразу: когда меняем значение ШИМа, добавляем проверочку на 0 — при оном отключаем ШИМ совсем. Воникает вопрос: как отключать? Можно останавливать таймер. Не лучший вариант: а вдруг на этот таймер что-нибудь ещё посажено? Второй ШИМ, например, или прерывания, или отсчет времени до взрыва? Можно отключать выход таймера — это уже получше, и просто и понятно, получается примерно так:
if(OCR0A==0)TCCR0A&=0x3F; else TCCR0A=0x83;
Как вариант можно менять режима работы самого пина выход/вход.
P.S.
Из комментариев к статье были получены ещё такие решения:
1. Если не принципиально получать 100% заполнения ШИМ, то можно использовать инверсный режим работы выхода;
2. При работе ШИМ в режиме Phase Correct PWM проблема отсутствует.
we.easyelectronics.ru
attiny13a / Поиск по тегам / Сообщество EasyElectronics.ru
Возникла давеча простенькая задачка. Нужен был регулируемый вручную ШИМ для теста одной идейки. Под рукой тинька 13 — т.е. цепляем переменник на АЦП и выводим что нам нужно. Казалось бы — проще только светодиодом помигать. Однако. Смеркалось.Всё быстро воткнуто в макетку, с помощью мастера в CodeVision несколькими щелчками собран проект. Для теста на выход — светодиод. Включаю: горит на полную, кручу резистор — реакции нет. Шустро проверяю все соединения, питание, схему, прошивку — эффект остается. После трехкратного повторения тех же действий возникло недоумение. Беру другую тиньку, прошиваю, включаю — болт. Похоже проблема не в чипе. Некоторое время созерцаю код сгенерированный CodeVision — придраться не к чему. Открываю datasheet, вникаю. Изучил ADC, проверил — всё по канонам. Перешёл к ШИМ.
Краткое описание устройства аппаратного ШИМ на Attiny13 для начинающих:
ШИМ сделан на базе таймера. Т.е. у таймера есть несколько режимов работы, два из них относятся к ШИМу (FastPWM и Phase Correct PWM). Таймер настраивается с помощью двух регистров: TCCR0A,TCCR0B. В них задается режим, частота (делитель), какие каналы используются (есть два — 0A и 0B), режим работы выхода(прямой, инвертированный). Значение ШИМа задаются в регистрах OCR0A и OCR0B — соответственно для каждого канала. Есть ещё у ШИМа такая настройка — чем определяется максимальное значение таймера(TOP), при достижении которого он сбрасывается и бежит с начала — это может быть либо 0xFF, либо значение в регистре OCR0A. У меня был установлен второй режим и значение ШИМа я задавал в регистре же OCR0A.
Короче, следующие грабли. Яркость регулируется, но вот беда: когда довожу ручку до минимума всё равно подсвечивает. Т.е. на АЦП у нас 0, а на выходе не 0. Обидно, понимаешь. Причина такого поведения в том, что в тот момент, когда таймер сбрасывается в 0, на выходе чип выставляет 1, и хотя значение ШИМа у нас задано 0, и уже в следующем такте он это видит и обнуляет выход, но вот этого несчастного скачка достаточно что бы светодиодик светил. Дискомфорт — ты ждешь на выходе чистый 0, а тут тебе гребеночка такая. Вообще говоря проблема известная. Решение приходит в голову практически сразу: когда меняем значение ШИМа, добавляем проверочку на 0 — при оном отключаем ШИМ совсем. Воникает вопрос: как отключать? Можно останавливать таймер. Не лучший вариант: а вдруг на этот таймер что-нибудь ещё посажено? Второй ШИМ, например, или прерывания, или отсчет времени до взрыва? Можно отключать выход таймера — это уже получше, и просто и понятно, получается примерно так:
if(OCR0A==0)TCCR0A&=0x3F;
else TCCR0A=0x83;
Как вариант можно менять режима работы самого пина выход/вход.
Из комментариев к статье были получены ещё такие решения:
1. Если не принципиально получать 100% заполнения ШИМ, то можно использовать инверсный режим работы выхода;
2. При работе ШИМ в режиме Phase Correct PWM проблема отсутствует.
we.easyelectronics.ru
cool-hacker › Блог › Бинарная модуляция (Binary angle modulation) — альтернатива программному ШИМ-у. Примеры на Attiny13
Приветствую, любители микроконтроллеров!
Лирическое вступление
После того, как я выложил на общее обозрение свой открытый проект полунакальных ДХО, в личку посыпалось много вопросов. Причем, большинство вопросов было не по поводу самого проекта, а по тем или иным проблемам программирования контроллеров AVR. Здесь хотелось бы отметить, что для достижения своих целей многим просто не хватает терпения. Я и сам, можно сказать, только начинаю пробовать себя в этом деле. Но мне проще, я человек дотошный и терпеливый: изучаю документацию, затем смотрю примеры других, затем пробую на практике. Если захожу в тупик или надоедает, то откладываю в сторону, а после возвращаюсь. Сейчас вернулся к моему первому незаконченному проекту и решил делать по дороге маленькие статьи по узким темам для начинающих. Думаю, будет интересно.
И так, возвращаюсь к своему первому проекту — лампе настроения. Изначально хотел повторить готовую поделку на Atmega8 с инфракрасным сенсором (ИК-светодиод + ИК-приемник) для переключения режимов. Никак не смог нормально отладить работу сенсора. Сейчас эта идея разонравилась. Однако, пара плафонов из Икеи, мощные RGB-светодиоды валяются, и, изредка, жена меня ими попрекает 🙂 Вообщем, решено сделать поделку на Attiny13A. Ее достаточно для такой штуки, хотя, когда начинал, не думал что это реализуемо. По моей задумке, лампа будет иметь еще и емкостный сенсор и несколько режимов. Посмотрим что удастся втолкать в этот МК.
Т.к. я сторонник открытого ПО, то параллельно решил перейти на компилятор GCC-AVR — весь код будет под него. До этого писал в CodeVisionAVR. Сейчас изредка использую его генератор кода, когда лень шерстить даташиты.
Бинарная модуляция
Так что же такое бинарная модуляция (далее — БМ)? Многие знакомы с широтно-импульсной модуляцией (далее — ШИМ), которую поддерживают аппаратно многие микроконтроллеры. Попробую объяснить смысл БМ на пальцах в сравнении с ШИМ. Подразумеваю, что читающий с ШИМ знаком или ознакомится. Рассматривать будем 8-битный ШИМ и БМ. Если для 30% ШИМ будет выглядеть так:
ШИМ около 30%
В БМ кодировка идет битами двоичного числа. Каждый бит отвечает за свою длительность.
Длительность битов
Для наглядности мы возьмем число 85 — в двоичном это 01010101 и это около 33%. Значит БМ в этом случае будет выглядеть так:
8-битный БМ для числа 85 ( 01010101 в двоичной системе )
Плюсы и минусы бинарной модуляции
+ Необходимо меньше вычислительных ресурсов. Если для программного 8-битного ШИМ-а нам необходимо сделать 256 итераций, то для БМ всего 8 — по итерации на каждый бит.
+ При обработке старших битов полно времени в прерывании для выполнения другой работы.
— Я выделил для себя один минус. При управлении яркостью светодиодов на частоте около 150 Гц, когда плавно изменяешь яркость глаз видит короткие вспышки/рывки. Как только я не пробовал от них избавиться. Пробовал делать смену значения яркости с определенного бита. На одном из англоязычных форумов пишут, что помогает сделать зеркальную бинарную модуляцию. Т.е. наш сигнал примет примерно такой вид.
зеркальная бинарная модуляция
При этом мы теряем в частоте в двое. Так рывки были менее видны, но они все равно видны. Хотел показать вам их на видео, но камера их не видит! 🙂 В итоге помогает поднятие частоты — все становится плавным!
Переходим к практике
Далее я собрал вот такую схемку на Attiny13A с 5 светодиодами и пятью резисторами. Питание на схеме нет, но оно необходимо 🙂
Принципиальная схема
Микроконтроллер будет работать на частоте 9.6 МГц, для этого достаточно от фьюзов по умолчанию убрать делитель на 8 (сделать CKDIV8 = 1). Ра
www.drive2.ru
Сообщества › Электронные Поделки › Блог › Бинарная модуляция (Binary angle modulation) — альтернатива программному ШИМ-у. Примеры на Attiny13
Приветствую, любители микроконтроллеров!
Лирическое вступление
После того, как я выложил на общее обозрение свой открытый проект полунакальных ДХО, в личку посыпалось много вопросов. Причем, большинство вопросов было не по поводу самого проекта, а по тем или иным проблемам программирования контроллеров AVR. Здесь хотелось бы отметить, что для достижения своих целей многим просто не хватает терпения. Я и сам, можно сказать, только начинаю пробовать себя в этом деле. Но мне проще, я человек дотошный и терпеливый: изучаю документацию, затем смотрю примеры других, затем пробую на практике. Если захожу в тупик или надоедает, то откладываю в сторону, а после возвращаюсь. Сейчас вернулся к моему первому незаконченному проекту и решил делать по дороге маленькие статьи по узким темам для начинающих. Думаю, будет интересно.
И так, возвращаюсь к своему первому проекту — лампе настроения. Изначально хотел повторить готовую поделку на Atmega8 с инфракрасным сенсором (ИК-светодиод + ИК-приемник) для переключения режимов. Никак не смог нормально отладить работу сенсора. Сейчас эта идея разонравилась. Однако, пара плафонов из Икеи, мощные RGB-светодиоды валяются, и, изредка, жена меня ими попрекает 🙂 Вообщем, решено сделать поделку на Attiny13A. Ее достаточно для такой штуки, хотя, когда начинал, не думал что это реализуемо. По моей задумке, лампа будет иметь еще и емкостный сенсор и несколько режимов. Посмотрим что удастся втолкать в этот МК.
Т.к. я сторонник открытого ПО, то параллельно решил перейти на компилятор GCC-AVR — весь код будет под него. До этого писал в CodeVisionAVR. Сейчас изредка использую его генератор кода, когда лень шерстить даташиты.
Бинарная модуляция
Так что же такое бинарная модуляция (далее — БМ)? Многие знакомы с широтно-импульсной модуляцией (далее — ШИМ), которую поддерживают аппаратно многие микроконтроллеры. Попробую объяснить смысл БМ на пальцах в сравнении с ШИМ. Подразумеваю, что читающий с ШИМ знаком или ознакомится. Рассматривать будем 8-битный ШИМ и БМ. Если для 30% ШИМ будет выглядеть так:
ШИМ около 30%
В БМ кодировка идет битами двоичного числа. Каждый бит отвечает за свою длительность.
Длительность битов
Для наглядности мы возьмем число 85 — в двоичном это 01010101 и это около 33%. Значит БМ в этом случае будет выглядеть так:
8-битный БМ для числа 85 ( 01010101 в двоичной системе )
Плюсы и минусы бинарной модуляции
+ Необходимо меньше вычислительных ресурсов. Если для программного 8-битного ШИМ-а нам необходимо сделать 256 итераций, то для БМ всего 8 — по итерации на каждый бит.
+ Возможность управлять большим количеством каналов.
+ При обработке старших битов полно времени в прерывании для выполнения другой работы.
— Я выделил для себя один минус. При управлении яркостью светодиодов на частоте около 150 Гц, когда плавно изменяешь яркость глаз видит короткие вспышки/рывки. Как только я не пробовал от них избавиться. Пробовал делать смену значения яркости с определенного бита. На одном из англоязычных форумов пишут, что помогает сделать зеркальную бинарную модуляцию. Т.е. наш сигнал примет примерно такой вид.
зеркальная бинарная модуляция
При этом мы теряем в частоте в двое. Так рывки были менее видны, но они все равно видны. Хотел показать вам их на видео, но камера их не видит! 🙂 В итоге помогает поднятие частоты — все становится плавным!
Переходим к практике
Далее я собрал вот такую схемку на Attiny13A с 5 светодиодами и пятью резисторами. Питание на схеме нет, но оно необходимо 🙂
Принципиальная схема
Микроконтроллер будет работать на частоте 9.6 МГц, для этого достаточно от фьюзов по умолчанию убрать делитель на 8 (сделать CKDIV8 = 1). Разберу только ключевой момент реализации БМ на Attiny13. Для этого мы инициализируем регистр OCR0A числом в котором один бит будет равен единице, в принципе даже не важно какой.
OCR0A = 0b00000001;
Затем в прерывании таймера по совпадению счетчика таймера с регистром OCR0A нам необходимо:
— сбросить счетчик таймера;
— сдвинуть вправо или влево (не принципиально в какую, но все время в одну сторону) биты регистра OCR0A по кругу;
Тем самым мы как раз получаем временные промежутки для БМ
128 — 10000000,
64 — 01000000,
32 — 00100000,
16 — 00010000,
8 — 00001000,
4 — 00000100,
2 — 00000010,
1 — 00000001
Этот же регистр OCR0A мы будем использовать как маску для выбора нужного бита из переменной яркости.
— выбрать соответствующий бит яркости и вывести его значение на ногу микроконтроллера.
TCNT0 = 0x0; //Сбрасываем сч
www.drive2.ru
Цифровое умножение частоты ШИМ-сигнала на два(на микроконтроллере ATtiny13) — radiohlam.ru
В этой статье приводится пример простейшей реализации цифрового «умножения на два» частоты ШИМ-сигнала с сохранением скважности (по буржуйски такие штуки называются PWM-converter).
Нафига оно надо? Ну, например, можно увеличить частоту ШИМ контроллера светодиодов какой-нибудь подсветки в автомобиле или ещё где-нибудь.
В качестве сердца (а точнее мозга) нашего умножителя используется микроконтроллер ATTiny13. Он оцифровывает входной сигнал, определяет его частоту и скважность, вычисляет параметры и генерирует выходной сигнал.
Для оцифровки входного сигнала используется встроенный в ATtiny13 таймер. В режиме FastPWM этот таймер каждый раз при достижении значения, записанного в регистр OCR0A, генерирует прерывание, а сам при этом обнуляется. Это позволяет изменяя записанное в регистр OCR0A значение генерировать прерывания через нужные промежутки времени. Например, если OCR0A=127, предделитель таймера равен 1 и контроллер работает от встроенного генератора на 9,6 МГц, то прерывания будут генерироваться с частотой 75 кГц (9600/128=75). Эту частоту дискретизации мы и будем использовать.
Для отсчёта периода и длительности импульса входного сигнала будем использовать два 16-ти битных счётчика (выделим для этого две пары 8-ми битных регистров). Это позволит нам оцифровать с разрешением не менее 7 бит сигналы с частотой примерно от 1 Гц (75000/216) до примерно 600 Гц (75000/27). В этом случае минимальное разрешение выходного сигнала будет 6 бит. Таким образом минимальная точность нашего умножителя составит около 1,5% (100/26).
Ещё два 16-ти битных регистра (два раза по два регистра по 8 бит разумеется) будем использовать для хранения вычисленных значений периода и длительности импульса выходного сигнала и один 16-ти битный счётчик для отсчёта этих значений.
Вот в общем-то и вся идея, но прежде чем оформить её в виде алгоритма, давайте нарисуем графики входных и выходных сигналов, разделим все важные точки на этих графиках на несколько типов и для каждого типа точек распишем, что нам нужно в этих точках делать. Итак, график:
Как видно из графика, у нас есть 3 типа точек для входного сигнала:
Точки 1 на нашем графике могут обозначать сразу два события: «конец очередного периода» и «начало нового периода». Для каждого из этих собый нам необходимо выполнить определённые действия.
Для события «конец очередного периода» нужно рассчитать новые значения параметров выходного сигнала, а для события «начало нового периода» нужно обнулить счётчики периода и длительности входного сигнала, то есть засечь параметры входного сигнала.
Нужно понимать, что изменение сигнала с низкого до высокого уровня не всегда соответствует событию «конец очередного периода». Например, при первом после включении контроллера изменении сигнала с низкого до высокого уровня, никакого предыдущего периода у нас ещё нет, соответственно, и никакого события «конец очередного периода» тоже нет, и вычислять параметры выходного сигнала нам не из чего.
Для того, чтобы отличать, наступило ли событие «конец очередного периода» будем использовать специальный флаг STARTF. Будем устанавливать этот флаг каждый раз при обработке события «начало нового периода» и проверять перед обработкой события «конец очередного периода» (о том, когда этот флаг будет сбрасываться поговорим немного позже).
Точки 2 на нашем графике отмечают «конец импульса» входного сигнала. В этих точках нам нужно останавливать счётчик, отсчитывающий длительность импульса входного сигнала. Длительность паузы мы засекать не будем, поскольку засекаем длмтельность периода целиком. Признаком остановки счётчика, отсчитывающего длительность импульса, также назначим специальный флаг — IINCEN. В точках 2 будем этот флаг поднимать и считать это признаком остановки счётчика. Сбрасывать флаг будем в начале нового периода (то есть в точках 1).
В точках 3 не происходит ничего особенного, тут нужно просто увеличивать нужные счётчики. Либо только счётчик периода входного сигнала, либо вместе со счётчиком времени импульса входного сигнала.
Тут есть один важный момент. В любой из описанных выше точек может произойти переполнение счётчика периода входного сигнала. Это будет означать, что сигнал не изменялся в течении всего времени, которое мы можем засечь. В этом случае будем считать, что наш сигнал имеет коэффициент заполнения 0% или 100% в зависимости от того, при каком уровне сигнала произошло переполнение. Как раз в этом случае нужно будет сбросить флаг STARTF. Кроме того будем устанавливать в ноль время импульса выходного сигнала для случая, когда коэффициент заполнения равен нулю, и время импульса выходного сигнала больше периода выходного сигнала для случая, когда коэффициент заполнения равен 100%.
Признаком переполнения счётчика периода входного сигнала будет установка специального флага TINCF.
Теперь перейдём к точкам на графике выходного сигнала.
Точки 4 соответствуют времени импульса выходного сигнала. В этих точках должен быть установлен высокий уровень выходного сигнала. Определять их будем по факту того, что в этих точках значение счётчика для выходного сигнала меньше, чем вычисленное значение времени импульса выходного сигнала.
Ну и наконец точки 5 соответствуют времени паузы выходного сигнала. Эти точки будем определять по факту того, что в них значение счётчика для выходного сигнала больше или равно вычисленному времени импульса выходного сигнала.
Обнулять счётчик выходного сигнала будем в начале каждого нового периода входного сигнала, а также при достижении им вычисленного значения периода выходного сигнала.
Описанные для точек 4 и 5 действия будем выполнять только для случаев, когда коэффициент заполнения выходного сигнала не установлен равным 0% или 100%. В последних двух случаях будем просто устанавливать выход равным нулю или единице без всяких дополнительных манипуляций.
Всё, теперь можно приступать к написанию привычного алгоритма в виде блок-схемы. Весь алгоритм рисовать смысла нет, всё равно всё интересное происходит только по прерыванию таймера, вот алгоритм обработчика этого прерывания и нарисуем.
Алгоритм:
Текст программы под катом
.device ATtiny13 .include "tn13def.inc" .list ;-- определяем свои переменные .def w1=r16 ; это будет наш аккумулятор .def w2=r17 ; а это - ещё один .def Flags=r18 ; флаги .def TIN_H=r19 ; счётчик периода входного сигнала, старший регистр .def TIN_L=r20 ; счётчик периода входного сигнала, младший регистр .def IIN_H=r21 ; счётчик времени импульса вх-го сигнала, старший рег. .def IIN_L=r22 ; счётчик времени импульса вх-го сигнала, младший рег. .def TOUT_H=r23 ; расчёт.значение периода вых-го сигнала, старший рег. .def TOUT_L=r24 ; расчёт.значение периода вых-го сигнала, младший рег. .def IOUT_H=r25 ; расчёт.знач-е длит-ти имп-са вых-го сигнала, ст.рег. .def IOUT_L=r26 ; расчёт.знач-е длит-ти имп-са вых-го сигнала, мл.рег. .def OC_H=r27 ; счётчик для выходного сигнала .def OC_L=r28 ; счётчик для выходного сигнала .equ TINCF=0 ; флаг переполнения счётчика периода вх-го сигнала .equ IINCEN=1 ; флаг отсчёта времени импульса (сброшен - считаем) .equ STARTF=2 ; флаг начала цикла .equ TOprosa=127 ; 9,6 МГц/128 = 75 кГц ;-------------------------- .equ PWM_IN=1 ; вход ШИМ .equ PWM_OUT=0 ; выход ШИМ ;-- Используемые регистры ; SPL - указатель вершины стека ; ACSR - управление компаратором ; DDRB - направление работы ног ; PORTB - выходы порта ; TCCR0B - управление таймером ; TIMSK0 - прерывания от таймера ; TCNT0 - значение таймера ; OCR0A - значение модуля сравнения ; TIFR0 - флаги прерываний от таймера ; PB0 - выход ШИМ, PB1 - вход ;-- начало программного кода .cseg .org 0 rjmp Init ; переход на начало программы ;-- вектора прерываний reti ; INT0 reti ; Pin Change rjmp TimerOverflow ; Timer reti ; EEPROM reti ; comparator reti ; timer compare match A reti ; timer compare match B reti ; watchdog reti ; ADC ;-- начало программы Init: ldi w1,RAMEND ; устанавливаем указатель вершины out SPL,w1 ; стека на старший байт RAM sbi ACSR,7 ; выключаем компаратор ldi w1,1<<PWM_OUT ; определяем входы и выходы порта out DDRB,w1 clr w1 ; подтягивающие резисторы выключены, начальное out PORTB,w1 ; состояние выхода = 0 ;-- настраиваем таймер ldi w1,(1<<WGM00)|(1<<WGM01) out TCCR0A,w1 ; выходы OCR0A, OCR0B отключены, Fast PWM ldi w1,TOprosa ; значение, по которому будет срабат.прерыв. out OCR0A,w1 ldi w1, 1<<TOIE0 ; прерывание по переполнению out TIMSK0,w1 ;--- запрещаем считать время импульса sbr Flags,1<<IINCEN ; устанавливаем флаг ;-- ШИМ=0% соответствует случаю, когда (IOUT_H OR IOUT_L)=0 ;-- ШИМ=100% соответствует случаю, когда (IOUT_H OR IOUT_L) ;-- не равно 0, а (TOUT_H OR TOUT_L)=0 ;-- Первоначально ШИМ=0% ;-- включаем таймер и прерывания ldi w1,0b00001001 ; включить таймер, Fast PWM, без предделителя out TCCR0B,w1 sei ;-- начало работы Start: rjmp Start ;---------------------------------------------------------------------- ;--- Interrupt --- TimerOverflow: ;-- читаем значение на входе и определяем изменилось оно или нет in w2,PINB ; читаем входы порта rcall IncCounterTIN ; увеличиваем счётчик периода вх.сигнала clr w1 bld w1,PWM_IN ; выгружаем бит T в бит PWM_IN регистра W bst w2,PWM_IN ; загружаем новое значение в бит T andi w2,1<<PWM_IN ; стираем в w2 все биты, кроме вх.пина eor w1,w2 ; изменилось ли состояние? breq NoChanged ; прыгаем, если не изменилось ;--- Changed: brts FromLowToHi ; если T=1 - уров.измен-ся с низк.на выс. ;-- с высокого на низкий FromHiToLow: ; конец импульса sbrc Flags, TINCF ; если переполн.нет - пропустить команду rjmp Changed_HL_OVF Changed_HL_No_OVF: rcall IncCounterIIN ; увелич.счётчик длительности импульса sbr Flags,1<<IINCEN ; прекращаем отсчёт времени импульса rjmp Update_Output Changed_HL_OVF: ser IOUT_L clr TOUT_H clr TOUT_L ; ШИМ=100% rcall ClrCounters ; сбрасываем счётчики clr Flags ; сбрасываем флаги rjmp Update_Output ;-- с низкого на высокий FromLowToHi: ; конец цикла, начало нового цикла sbrc Flags, TINCF ; если переполн.нет - пропустить команду rjmp Changed_LH_OVF Changed_LH_No_OVF: sbrc Flags, STARTF ; пропустить команду, если флаг STARTF=0 rcall CalcToutIout Changed_LH_No_Calc: clr Flags sbr Flags, 1<<STARTF ; поднимаем флаг rcall ClrCounters rjmp Update_Output Changed_LH_OVF: clr IOUT_H ; ШИМ=0 clr IOUT_L rjmp Changed_LH_No_Calc ;-- не изменился NoChanged: sbrc Flags, TINCF ; если переполн.нет - пропустить команду rjmp NoChanged_OVF NoChanged_No_OVF: sbrs Flags,IINCEN ; пропустить команду, если флаг установлен rcall IncCounterIIN rjmp Update_Output NoChanged_OVF: clr IOUT_L clr IOUT_H clr TOUT_H clr TOUT_L ; ШИМ=0% brtc Next1 ; если флаг сброшен - оставляем ШИМ=0% ser IOUT_L ; ШИМ=100% Next1: rcall ClrCounters clr Flags ; сбрасываем флаги ;-- обрабатываем выход Update_Output: mov w1, IOUT_L or w1, IOUT_H ; IOUT_H v IOUT_L brne NotNull ; если ШИМ не 0% cbi PORTB,PWM_OUT ; clear PWM_OUT to 0 reti NotNull: mov w1, TOUT_H or w1, TOUT_L ; TOUT_H v TOUT_L brne NotFull ; если ШИМ не 100% sbi PORTB,PWM_OUT ; set PWM_OUT to 1 reti NotFull: rcall IncCounterOC cp OC_L, TOUT_L brne Next2 cp OC_H, TOUT_H brne Next2 clr OC_L clr OC_H Next2: cp OC_H, IOUT_H ; OC_H - TOUT_H brlo Lower ; jump if OC_H lower IOUT_H brne HierOrEqual ; jump if OC_H bigger IOUT_H cp OC_L, IOUT_L ; compare if OC_H equal IOUT_H brlo Lower HierOrEqual: cbi PORTB,PWM_OUT ; clear PWM_OUT to 0 reti Lower: sbi PORTB,PWM_OUT ; set PWM_OUT to 1 reti ;----------------------- ;--- Процедурки -------- IncCounterTIN: inc TIN_L brne No_Z1 inc TIN_H brne No_Z1 sbr Flags, 1<<TINCF ; set TINCF No_Z1: ret IncCounterIIN: inc IIN_L brne No_Z2 inc IIN_H No_Z2: ret IncCounterOC: inc OC_L brne No_Z3 inc OC_H No_Z3: ret CalcToutIout: mov TOUT_H,TIN_H ; copy TIN_H->T_OUTH, TIN_L->T_OUTL mov TOUT_L,TIN_L clc ; clear carry flag ror TOUT_H ; делим общую частоту на 2 ror TOUT_L mov IOUT_H,IIN_H ; copy IIN_H->IOUT_H, IIN_L->IOUT_L mov IOUT_L,IIN_L clc ror IOUT_H ; делим время импульса на 2 ror IOUT_L ret ClrCounters: clr TIN_L clr TIN_H clr IIN_L clr IIN_H ser OC_H ; устанавливаем OC = -1 ser OC_L ; (set OC to -1) ret |
.device ATtiny13 .include «tn13def.inc» .list ;— определяем свои переменные .def w1=r16 ; это будет наш аккумулятор .def w2=r17 ; а это — ещё один .def Flags=r18 ; флаги .def TIN_H=r19 ; счётчик периода входного сигнала, старший регистр .def TIN_L=r20 ; счётчик периода входного сигнала, младший регистр .def IIN_H=r21 ; счётчик времени импульса вх-го сигнала, старший рег. .def IIN_L=r22 ; счётчик времени импульса вх-го сигнала, младший рег. .def TOUT_H=r23 ; расчёт.значение периода вых-го сигнала, старший рег. .def TOUT_L=r24 ; расчёт.значение периода вых-го сигнала, младший рег. .def IOUT_H=r25 ; расчёт.знач-е длит-ти имп-са вых-го сигнала, ст.рег. .def IOUT_L=r26 ; расчёт.знач-е длит-ти имп-са вых-го сигнала, мл.рег. .def OC_H=r27 ; счётчик для выходного сигнала .def OC_L=r28 ; счётчик для выходного сигнала .equ TINCF=0 ; флаг переполнения счётчика периода вх-го сигнала .equ IINCEN=1 ; флаг отсчёта времени импульса (сброшен — считаем) .equ STARTF=2 ; флаг начала цикла .equ TOprosa=127 ; 9,6 МГц/128 = 75 кГц ;————————— .equ PWM_IN=1 ; вход ШИМ .equ PWM_OUT=0 ; выход ШИМ ;— Используемые регистры ; SPL — указатель вершины стека ; ACSR — управление компаратором ; DDRB — направление работы ног ; PORTB — выходы порта ; TCCR0B — управление таймером ; TIMSK0 — прерывания от таймера ; TCNT0 — значение таймера ; OCR0A — значение модуля сравнения ; TIFR0 — флаги прерываний от таймера ; PB0 — выход ШИМ, PB1 — вход ;— начало программного кода .cseg .org 0 rjmp Init ; переход на начало программы ;— вектора прерываний reti ; INT0 reti ; Pin Change rjmp TimerOverflow ; Timer reti ; EEPROM reti ; comparator reti ; timer compare match A reti ; timer compare match B reti ; watchdog reti ; ADC ;— начало программы Init: ldi w1,RAMEND ; устанавливаем указатель вершины out SPL,w1 ; стека на старший байт RAM sbi ACSR,7 ; выключаем компаратор ldi w1,1<<PWM_OUT ; определяем входы и выходы порта out DDRB,w1 clr w1 ; подтягивающие резисторы выключены, начальное out PORTB,w1 ; состояние выхода = 0 ;— настраиваем таймер ldi w1,(1<<WGM00)|(1<<WGM01) out TCCR0A,w1 ; выходы OCR0A, OCR0B отключены, Fast PWM ldi w1,TOprosa ; значение, по которому будет срабат.прерыв. out OCR0A,w1 ldi w1, 1<<TOIE0 ; прерывание по переполнению out TIMSK0,w1 ;— запрещаем считать время импульса sbr Flags,1<<IINCEN ; устанавливаем флаг ;— ШИМ=0% соответствует случаю, когда (IOUT_H OR IOUT_L)=0 ;— ШИМ=100% соответствует случаю, когда (IOUT_H OR IOUT_L) ;— не равно 0, а (TOUT_H OR TOUT_L)=0 ;— Первоначально ШИМ=0% ;— включаем таймер и прерывания ldi w1,0b00001001 ; включить таймер, Fast PWM, без предделителя out TCCR0B,w1 sei ;— начало работы Start: rjmp Start ;———————————————————————- ;— Interrupt — TimerOverflow: ;— читаем значение на входе и определяем изменилось оно или нет in w2,PINB ; читаем входы порта rcall IncCounterTIN ; увеличиваем счётчик периода вх.сигнала clr w1 bld w1,PWM_IN ; выгружаем бит T в бит PWM_IN регистра W bst w2,PWM_IN ; загружаем новое значение в бит T andi w2,1<<PWM_IN ; стираем в w2 все биты, кроме вх.пина eor w1,w2 ; изменилось ли состояние? breq NoChanged ; прыгаем, если не изменилось ;— Changed: brts FromLowToHi ; если T=1 — уров.измен-ся с низк.на выс. ;— с высокого на низкий FromHiToLow: ; конец импульса sbrc Flags, TINCF ; если переполн.нет — пропустить команду rjmp Changed_HL_OVF Changed_HL_No_OVF: rcall IncCounterIIN ; увелич.счётчик длительности импульса sbr Flags,1<<IINCEN ; прекращаем отсчёт времени импульса rjmp Update_Output Changed_HL_OVF: ser IOUT_L clr TOUT_H clr TOUT_L ; ШИМ=100% rcall ClrCounters ; сбрасываем счётчики clr Flags ; сбрасываем флаги rjmp Update_Output ;— с низкого на высокий FromLowToHi: ; конец цикла, начало нового цикла sbrc Flags, TINCF ; если переполн.нет — пропустить команду rjmp Changed_LH_OVF Changed_LH_No_OVF: sbrc Flags, STARTF ; пропустить команду, если флаг STARTF=0 rcall CalcToutIout Changed_LH_No_Calc: clr Flags sbr Flags, 1<<STARTF ; поднимаем флаг rcall ClrCounters rjmp Update_Output Changed_LH_OVF: clr IOUT_H ; ШИМ=0 clr IOUT_L rjmp Changed_LH_No_Calc ;— не изменился NoChanged: sbrc Flags, TINCF ; если переполн.нет — пропустить команду rjmp NoChanged_OVF NoChanged_No_OVF: sbrs Flags,IINCEN ; пропустить команду, если флаг установлен rcall IncCounterIIN rjmp Update_Output NoChanged_OVF: clr IOUT_L clr IOUT_H clr TOUT_H clr TOUT_L ; ШИМ=0% brtc Next1 ; если флаг сброшен — оставляем ШИМ=0% ser IOUT_L ; ШИМ=100% Next1: rcall ClrCounters clr Flags ; сбрасываем флаги ;— обрабатываем выход Update_Output: mov w1, IOUT_L or w1, IOUT_H ; IOUT_H v IOUT_L brne NotNull ; если ШИМ не 0% cbi PORTB,PWM_OUT ; clear PWM_OUT to 0 reti NotNull: mov w1, TOUT_H or w1, TOUT_L ; TOUT_H v TOUT_L brne NotFull ; если ШИМ не 100% sbi PORTB,PWM_OUT ; set PWM_OUT to 1 reti NotFull: rcall IncCounterOC cp OC_L, TOUT_L brne Next2 cp OC_H, TOUT_H brne Next2 clr OC_L clr OC_H Next2: cp OC_H, IOUT_H ; OC_H — TOUT_H brlo Lower ; jump if OC_H lower IOUT_H brne HierOrEqual ; jump if OC_H bigger IOUT_H cp OC_L, IOUT_L ; compare if OC_H equal IOUT_H brlo Lower HierOrEqual: cbi PORTB,PWM_OUT ; clear PWM_OUT to 0 reti Lower: sbi PORTB,PWM_OUT ; set PWM_OUT to 1 reti ;———————— ;— Процедурки ——— IncCounterTIN: inc TIN_L brne No_Z1 inc TIN_H brne No_Z1 sbr Flags, 1<<TINCF ; set TINCF No_Z1: ret IncCounterIIN: inc IIN_L brne No_Z2 inc IIN_H No_Z2: ret IncCounterOC: inc OC_L brne No_Z3 inc OC_H No_Z3: ret CalcToutIout: mov TOUT_H,TIN_H ; copy TIN_H->T_OUTH, TIN_L->T_OUTL mov TOUT_L,TIN_L clc ; clear carry flag ror TOUT_H ; делим общую частоту на 2 ror TOUT_L mov IOUT_H,IIN_H ; copy IIN_H->IOUT_H, IIN_L->IOUT_L mov IOUT_L,IIN_L clc ror IOUT_H ; делим время импульса на 2 ror IOUT_L ret ClrCounters: clr TIN_L clr TIN_H clr IIN_L clr IIN_H ser OC_H ; устанавливаем OC = -1 ser OC_L ; (set OC to -1) ret
[свернуть]
Вот и вся программа. Чтобы всё корректно работало — в контроллере должны быть «запрограммированы» фьюзы SPIEN, SUT0 и CKSEL0 (то есть в PonyProg напротив них должны стоять галочки).
Скачать одним архивом алгоритм, исходники и прошивку
P.S. Хочу кое-что уточнить относительно частоты дискретизации и точности.
Как я писал выше, изменять частоту дискретизации можно изменяя значение, загружаемое в регистр OCR0A. Однако, нужно понимать, что бездумно уменьшать это значение для увеличения частоты дискретизации нельзя. Это связано с тем, что для нормальной работы устройства обработчик прерывания от таймера должен успеть полностью отработать до того момента, когда произойдёт следующее прерывание. Другими словами, минимальное время между прерываниями таймера должно быть несколько больше, чем время, необходимое для работы обработчика.
Что касается точности, то 1,5% мы получили бы в идеальном случае, если бы обработчик прерывания выполнялся мгновенно. Поскольку в реальности на его выполнение затрачивается некоторое время, то точность будет несколько ниже. Однако, поскольку мы точно знаем, что выполняться обработчик должен ни в коем случае не дольше одного цикла таймера (от прерывания до прерывания), значит в реальности наихудшая точность, которую мы можем получить, будет равна не одному, а двум циклам нашего таймера, т.е. не полтора, а 3%.
P.P.S. Ниже можно увидеть осциллограммы, полученные в процессе тестирования. Слева — вход, справа — выход.
radiohlam.ru