habrahabr

Предел минимального Hello Word на AVR составляет 2 байта

  • суббота, 18 октября 2014 г. в 03:11:07
http://habrahabr.ru/post/240689/

За эту неделю появилось сразу две статьи на хабре и одна в блоге, где авторы соревновались в написании минимально возможной программы мигания светодиодом для микроконтроллеров AVR.
В самой последней статье автор предложил программу длиной всего в 4 байта (!)
Ну как тут можно устоять перед брошенным вызовом?!
И в этой статье я предлагаю программу мигания светодиодом с частотой, заметной на глаз и размером всего в 2 байта.

2 байта – это минимально возможная длина программы, поскольку размер адресуемой ячейки программной памяти в микроконтроллерах AVR составляет 16 бит или 2 байта. Таким образом, программа, а точнее одна инструкция (которая может располагаться по абсолютно любому адресу) будет занимать одну ячейку программной памяти.

Но, к сожалению, предложенная мной программа будет работать далеко не во всех микроконтроллерах AVR, а только лишь в некоторых моделях из семейств Tiny и Mega.

Секрет программы состоит в том, что некоторых микроконтроллерах семейств Tiny и Mega есть примечательная фича, которая позволяет всего одной командой инвертировать состояние разрядов регистров PORTА, PORTB и PORTD. Такую интересную фичу реализует команда sbi A,b

Команда sbi A,b предназначена для установки бита регистров ввода/вывода, которые имеют адреса от 0 до 31.
Эта команда в двоичном виде выглядит следующим образом:

1001 1010 AAAA Abbb

Первые восемь бит 1001 1010 являются кодом операции – это постоянная составляющая команды.
В битах ААААА записывается адрес регистра ввода/вывода в адресном пространстве ввода/вывода. Для адреса отводится 5 бит и именно поэтому, команда имеет ограничение по адресам (0..31).
В младших трех битах bbb располагается номер изменяемого бита (0..7).

Теперь вернемся к той самой примечательной фиче, которая позволяет инвертировать состояние разрядов регистров PORTB и PORTD.

Давайте заглянем в даташит на микроконтроллер ATtiny2313:

image

Такая возможность точно присутствует в микроконтроллерах ATtiny2313, ATtiny13, ATtiny24/44/84 .
В микроконтроллерах из семейства mega такая удобная функция есть в ATmega48A/PA/88A/PA/168A/PA/328/P. А вот в ATmega8A/16A/32A эта функция отсутствует.
А про остальные микроконтроллеры с уверенностью сказать не могу.
Для того что бы узнать, присутствует ли такая функция в микроконтроллере, можно поискать в даташите словосочетание Toggling the Pin.

Таким образом, прописав, к примеру, команду sbi PINB,0 можно проинвертировать состояние нулевого разряда регистра PORTB.

А что нам это даст?

А это нам дает включение/отключение подтягивающего резистора в нулевом разряде порта B (не забывайте, что после сброса все регистры ввода/вывода обнуляются, поэтому порты B и D настроены на вход). Сопротивление встроенного резистора составляет 30..50 кОм. Однако, даже такого сопротивления вполне хватит, чтобы зажечь низко потребляющие синие, красные и белые светодиоды. А вот на зеленые светодиоды тока уже будет не хватать.
Лично я такой способ запитки светодиодов использую довольно часто. Красные и синие светодиоды типоразмера 0805 и 0603 горят вполне сносно при напряжении питания 5 В.

То есть, для того, чтобы зажечь светодиод, его надо включить между выводом порта и общим проводом (без внешнего токоограничивающего резистора!) и включить внутренний подтягивающий резистор. В такой схеме включения экономится еще и внешний токоограничивающий резистор.

image


Итак, управлять светодиодом при помощи одной команды мы научились.
Теперь, если эту команду записать в программную память микроконтроллера, например по адресу 0х0000, то микроконтроллер будет работать следующим образом:
По нулевому адресу будет встречена команда sbi PINB,0 и после ее выполнения установиться в единицу нулевой разряд PORTB, включится подтягивающий резистор и зажгет светодиод. А далее, до конца программной памяти, микроконтроллер будет встречать только 0xFFFF. Такие инструкции микроконтроллеру неизвестны, поэтому микроконтроллер просто будет пропускать их, затрачивая на каждую один такт и увеличивая на единицу программный счетчик.
После того как программный счетчик досчитает до конца памяти, он переполниться и сбросится в ноль, то есть произойдет переход на адрес 0х0000, где вновь будет выполнена команда sbi PINB,0. После выполнения команды нулевой разряд PORTB сбросится в ноль, отключится подтягивающий резистор и светодиод погаснет.

Теперь давайте посчитаем, с какой частотой будет мигать светодиод. В качестве микроконтроллера выберем, к примеру, ATtiny2313. В этом микроконтроллере программная память составляет 2048 байт. Каждая ячейка программной памяти занимает 2 байта. Адресное пространство программной памяти представлено адресами от 0 до 1023. То есть всего 1024 адреса. После подачи питания микроконтроллер начнет выполнение программы с адреса 0х0000. Когда микроконтроллер дойдет до адреса 1023, то на следующий такт произойдет обнуление программного счетчика. То есть произойдет переход на адрес 0х0000.

Все команды в нашей программе выполняются за один такт. Поэтому, светодиод будет переключаться в противоположное состояние один раз за 1024 тактов. Допустим, микроконтроллер работает на частоте 1 МГц. На этой частоте один такт составляет 1 мкс. Таким образом, переключение светодиода будет происходить каждые 1024 мкс. Как известно частота – это обратная величина периода. А период одного мигания составляет удвоенное время между переключениями светодиода 1024 мкс * 2 = 2048 мкс. Откуда получаем частоту мигания светодиода 1/(2048 мкс) = 488 Гц.

Многовато получилось. Человеческий глаз на такой частоте мигания не заметит. Чтобы частота мигания была заметна на глаз, нужно будет понизить частоту. Для этого можно затактировать микроконтроллер от тактового генератора watchdog таймера, частота которого составляет около 128 кГц. Но в реальности частота может отличаться на 3..4 кГц, так как таймер не предназначен для точных отсчетов времени.

128 кГц – это в 8 раз ниже 1 МГц. Поэтому и частота мигания будет в 8 раз ниже 488 Гц / 8 = 61 Гц. То же много. На глаз мигание заметно не будет.

Чтобы еще понизить частоту, следует включить fuse-бит CKDIV8, отвечающий за деление частоты на 8. Тогда тактовая частота составит 128 кГц/8 = 16 кГц, а частота мигания уменьшится еще в 8 раз и составит 61 Гц / 8 = 7,6 Гц. А вот такая частота уже вполне будет заметна на глаз.

Но будьте очень аккуратны при понижении частоты до таких значений!

Помните важное правило: если вы программируете микроконтроллер последовательным внутрисхемным программатором (программирование через SPI), то тактовая частота микроконтроллера должна быть, по крайней мере, в 4 раза выше, чем частота программирования.
То есть, при тактовой частоте микроконтроллера 128 кГц частота работы программатора (частота SPI) должна быть ниже

128 кГц/4 = 32 кГц.

А при тактовой частоте 16 кГц частота программирования должна быть ниже

16 кГц/4 = 4 кГц.

Прежде чем понижать тактовую частоту до 16 кГц, убедитесь, что ваш последовательный программатор способен программировать на частоте ниже 4 кГц.
А иначе вы рискуете «заблокировать» микроконтроллер!
К параллельным программаторам это ограничение не относится.


Теперь давайте, наконец, напишем нашу программу.
Писать программу будем в блокноте и сразу в машинных кодах.

Мы уже знаем, как выглядит команда sbi A,b

1001 1010 AAAA Abbb

Осталось подставить нужные биты.

Зажигать будем светодиод, который подключен к нулевому разряду порта В. В микроконтроллере ATtiny2313 адрес PINB = 0x16. В двоичном виде 0x16 = 0b10110.
Изменять будем нулевой бит, поэтому bbb = 000.

Подставляем биты и получаем: 1001 1010 1011 0000 или 0x9AB0

Теперь создадим HEX файл для прошивки микроконтроллера.

Состав HEX файла выглядит следующим образом:

image


Наш HEX-файл будет состоять из двух строк: в первой строке будет прописана двухбайтная инструкция 0x9AB0, а вторая строка будет указывать на окончание HEX-файла.
В качестве адреса, где будет расположена наша команда, выберем нулевой адрес. Хотя, можно выбрать абсолютно любой адрес из всего адресного пространства.
Контрольная сумма вычисляется вычитанием из нуля всех байт строки. Младший байт получившегося значения как раз и является контрольной суммой.
То есть, для нашего случая 0x00 — 0x02 — 0xB0 — 0x9A. Младший байт получается равным 0xB4. Его и вписываем в конец строки.

Последняя строка должна указывать на окончание HEX файла и должна выглядеть следующим образом:

:00000001FF

И так же нужно помнить, что в HEX-файле байты команд поменяны местами. То есть сначала записываем младший байт 0xB0, затем старший байт 0x9A.

Итоговый HEX файл будет вот таким:

:02000000B09AB4
:00000001FF


Записываем получившиеся строки в блокнот и сохраняем с расширением .hex (впрочем, сохранить можно с любым расширением).

Теперь осталось зашить эту прошивку в микроконтроллер, установить тактирование от генератора watchdog таймера и включить деление на 8.

Вот пример настройки fuse-бит в среде Atmel Studio 6

image


Получаем результат в виде мигающего светодиода. Проверено в железе. Исправно мигает.

Таким образом, самая простая программа состоит из двух байт, а самая простая схема – из элемента питания, микроконтроллера и светодиода.

Кто-нибудь напишет программу мигания светодиодом еще короче?