Русский
Русский
English
Статистика
Реклама

Avr

Перевод Низкоуровневое программирование микроконтроллеров tinyAVR 0-series

10.10.2020 18:11:17 | Автор: admin


Вы 8-битный или 32-битный программист? Мы, в компании OMZLO, сосредоточили основные усилия на новых 32-битных ARM Cortex-чипах (STM32 и SAMD), которые, в сравнении с более старыми 8-битными микроконтроллерами (Micro Controller Unit, MCU) обычно предлагают больше RAM, более высокую производительность, поддержку большего количества периферийных устройств. И всё это за ту же, или за более низкую цену. Но 8-битные MCU ещё не утратили своей актуальности. В частности, компания Microchip выпустила новую серию чипов, tinyAVR 0-series, которые, в сравнении с AVR-чипами, выпущенными ранее, дают возможность работать с более современной периферией. Новые чипы, при этом, отличаются весьма привлекательной ценой. Возникает такое ощущение, что эти чипы отлично подойдут для разработки простых устройств, которым не нужны те возможности, что предлагают более новые 32-битные MCU. 8-битные микроконтроллеры, кроме того, значительно легче программировать, что приводит к увеличению скорости разработки программной части устройств, создаваемых на их основе.

Благодаря успеху Arduino UNO в интернете можно найти множество руководств, разъясняющих особенности программирования 8-битных микроконтроллеров ATmega328 и их собратьев вроде ATtiny85. Речь идёт о прямом доступе к регистрам без использования языка программирования, используемого для Arduino, и без применения IDE, созданных производителями чипов, вроде Atmel Studio. Чтобы в этом убедиться просто поищите в Google по словам atmega328 blinky. Для программирования микроконтроллеров вам понадобится лишь C-компилятор для AVR, текстовой редактор, avrdude и AVR-программатор. На некоторых ресурсах даже можно найти руководства, посвящённые тому, как, пользуясь универсальными макетными платами, завести ATmega328. Правда, если говорить о более новых чипах tinyAVR 0-series, по ним найти информацию такого рода непросто.

Конечно, Microchip предлагает все необходимые инструменты для программирования новых tinyAVR, представленные в виде IDE, рассчитанной исключительно на Windows. Для некоторых из новых чипов существуют ядра Arduino. Благодаря этому такие чипы можно программировать с использованием IDE Arduino. Но, опять же, если некто предпочитает писать код для микроконтроллеров в низкоуровневом стиле, используя свой любимый текстовой редактор, Makefile и компилятор C, то он сможет найти очень мало информации о таком подходе к работе с tinyAVR.

В этом материале мы расскажем о том, как, с нуля, применяя простейшие инструменты, создать прошивку blinky для ATtiny406. Большинство того, о чём пойдёт речь, справедливо и для других MCU tinyAVR. Это руководство рассчитано на тех, кто пользуется macOS и Linux, но нашими советами, с небольшими изменениями, смогут воспользоваться и те, кто работает в среде Windows.

Аппаратная часть проекта


Исследование ATtiny406


Мы решили поэкспериментировать с ATtiny406, рассчитывая на то, что в будущем этот микроконтроллер придёт на смену ATtiny45, который в настоящее время используется в PiWatcher в нашей разработке, которая позволяет, при возникновении такой необходимости, полностью выключить или перезагрузить Raspberry Pi. У ATtiny406 имеется 4 Кб флеш-памяти, 256 байт RAM, микрочип может работать на частоте 20 МГц без внешнего источника тактовых сигналов.

Одним из главных различий между новыми MCU tinyAVR и более старыми, широко известными чипами, вроде ATtiny85, является то, что более новые чипы используют протокол программирования UPDI. Для его работы нужно всего 3 пина, а для работы протокола ISP, используемого старыми чипами, нужно 6 пинов.

После непродолжительного изучения вопроса мы узнали, что программировать tinyAVR по UPDI можно, воспользовавшись простым USB-to-Serial-кабелем и резистором. Мы выяснили это благодаря Python-инструменту pyupdi, который предложил следующую схему подключения для загрузки прошивки на микроконтроллер.


Схема подключения микроконтроллера

Проектирование платы для ATtiny406


Мы создали минималистичную коммутационную плату для ATtiny406. На эту плату можно подать питание в 5В от USB. Кроме того, можно подать на неё более низкое напряжение в 3,3В, воспользовавшись для этого выделенными VCC/GND-пинами. На плате нашлось место для кнопки и светодиода. Для проведения экспериментов мы решили встроить в плату резистор на 4,7 кОм, необходимый для использования протокола UPDI (это резистор R2). В результате у нас получилась следующая схема платы.


Схема платы

Готовая плата


Готовая коммутационная плата оказалась весьма компактной, она хорошо стала на маленькую макетную плату. Схемы платы можно найти здесь.


Коммутационная плата, установленная на макетную плату

Для программирования ATtiny406 к плате, с использованием имеющихся на ней контактов, подключается USB-to-Serial-кабель.


Схема подключения кабеля

Программная часть проекта


pyupdi


Мы установили pyupdi, следуя инструкциям из репозитория проекта.

USB-to-Serial-кабель был подключён к плате с использованием четырёх UPDI-контактов. Наш USB-to-Serial-конвертер был виден в macOS как файл /dev/tty.usbserial-FTF5HUAV.

Для того чтобы убедиться в том, что программатор распознаёт ATtiny406, можно выполнить команду, напоминающую следующую, отредактировав путь к файлу:

pyupdi -d tiny406 -c /dev/tty.usbserial-FTF5HUAV -i

Если всё настроено правильно, то выполнение подобной команды должно привести к выводу примерно таких данных:

Device info: {'family': 'tinyAVR', 'nvm': 'P:0', 'ocd': 'D:0', 'osc': '3', 'device_id': '1E9225', 'device_rev': '0.1'}

C-компилятор


Оказалось, что обычный компилятор avr-gcc, который можно установить на macOS, используя Homebrew, не позволяет задать ATtiny406 в виде цели компиляции. Поэтому мы решили установить avr-gcc, предоставляемый компанией Microchip. Для загрузки компилятора надо создать учётную запись на сайте Microchip, что слегка раздражает.


Загрузка компилятора

После загрузки необходимых материалов, представленных в виде архива, мы распаковали этот архив в отдельную папку. Путь к директории bin, которая окажется в этой папке, нужно добавить в PATH. Это, в дальнейшем, облегчит работу. Если исходить из предположения о том, что компилятор хранится в папке $HOME/Src/avr8-gnu-toolchain-darwin_x86_64, то отредактировать PATH можно, добавив следующую команду в файл .bash_profile:

export PATH=$PATH:$HOME/Src/avr8-gnu-toolchain-darwin_x86_64/bin/

Самые новые MCU ATtiny не поддерживаются компилятором avr-gcc от Microchip без дополнительных настроек. Для обеспечения их поддержки нужно загрузить ATtiny Device Pack.


Загрузка ATtiny Device Pack

Мы, в результате, загрузили пакет Atmel.ATtiny_DFP.1.6.326.atpack (этот файл может называться иначе, в состав его имени может входить другой номер версии). Хотя расширением файла является .atpack, это, на самом деле, обычный .zip-архив. Мы поменяли его расширение на .zip и извлекли содержимое пакета в папку $HOME/Src/Atmel.ATtiny_DFP.1.6.326, то есть туда же, где уже имелись файлы компилятора.

Написание программы на C


Мы написали следующую программу, которая, с частотой в 1 Гц, мигает светодиодом, подключённым к выходу B5 на нашей плате для ATtiny.

#include <avr/io.h>#include <util/delay.h>int main() {_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // установлено в 20МГц (предполагается, что фьюз 0x02 установлен в 2)PORTB.DIRSET = (1<<5);for (;;) {PORTB.OUTSET = (1<<5);_delay_ms(500);PORTB.OUTCLR = (1<<5);_delay_ms(500);}}

Этот код очень похож на программу, мигающую светодиодом, написанную для привычных микроконтроллеров AVR. Первым заметным изменением является использование структур для доступа к регистрам MCU. Например, вместо обращения к PORTB выполняется обращение к PORTB.DIRSET.

Ещё одно изменение представлено кодом настройки частоты (_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0)). Новый ATtiny406 после перезагрузки работает на частоте 3,33 МГц, что соответствует базовой частоте в 20 МГц, которую разделили на 6. Для того чтобы чип работал бы на полной скорости в 20 МГц, мы очищаем регистр CLKCTRL.MCLKCTRLB. Так как этот регистр должен быть защищён от случайных изменений, на ATtiny406 для его модификации необходимо применить особую программную конструкцию. К счастью, решение этой задачи облегчает макрос _PROTECTED_WRITE. Подробности об этом можно почитать здесь.

Если сравнить этот код с тем, который пишут для STM32 или для SAMD21, то он окажется гораздо проще.

Файл Makefile


Тут мы пользуемся следующей структурой директорий:

  • src/Atmel.ATtiny_DFP.1.6.326/ путь к Microchip Device Pack.
  • src/attiny406-test/ папка, в которой, в файле main.c, хранится вышеприведённый код.

Компиляцию кода, находясь в директории attiny406-test/, можно выполнить следующей командой:

avr-gcc -mmcu=attiny406 -B ../Atmel.ATtiny_DFP.1.6.326/gcc/dev/attiny406/ -O3 -I ../Atmel.ATtiny_DFP.1.6.326/include/ -DF_CPU=20000000L -o attiny406-test.elf main.c

Флаг -O позволяет выполнить оптимизацию, необходимую для успешной работы вызовов функции _delay_ms(). То же самое относится и к переменной -DF_CPU, содержимое которой отражает ожидаемую частоту чипа. Остальные параметры содержат сведения о расположении файлов для ATtiny406, которые мы ранее скачали и извлекли из архива Device Pack.

Для загрузки прошивки на MCU нужно преобразовать то, что получилось в формат Intel HEX. После этого надо воспользоваться pyupdi. Мы создали простой Makefile, автоматизирующий решение этих задач:

OBJS=main.oELF=$(notdir $(CURDIR)).elfHEX=$(notdir $(CURDIR)).hexF_CPU=20000000LCFLAGS=-mmcu=attiny406 -B ../Atmel.ATtiny_DFP.1.6.326/gcc/dev/attiny406/ -O3CFLAGS+=-I ../Atmel.ATtiny_DFP.1.6.326/include/ -DF_CPU=$(F_CPU)LDFLAGS=-mmcu=attiny406 -B ../Atmel.ATtiny_DFP.1.6.326/gcc/dev/attiny406/CC=avr-gccLD=avr-gccall:  $(HEX)$(ELF): $(OBJS)$(LD) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)$(HEX): $(ELF)avr-objcopy -O ihex -R .eeprom $< $@flash: $(HEX)pyupdi -d tiny406 -c /dev/tty.usbserial-FTF5HUAV -f attiny406-test.hexread-fuses:pyupdi -d tiny406 -c /dev/tty.usbserial-FTF5HUAV -frclean:rm -rf $(OBJS) $(ELF) $(HEX)

Для компиляции кода достаточно выполнить команду make. Загрузка кода на микроконтроллер выполняется командой make flash. Представленный нами Makefile может быть, при необходимости, доработан.

Итоги


Программировать новые TinyAVR так же просто, как и MCU предыдущих поколений. Главное подобрать правильные инструменты. Если у вас есть советы по программированию AVRTiny, поделитесь ими с нами в Twitter или в комментариях ниже.

Планируете ли вы пользоваться новыми TinyAVR в своих проектах?



Подробнее..

Где электроника не сможет там Бог поможет. Молитвослов на атмеге328

24.06.2020 00:13:58 | Автор: admin
Идея данного проекта мне пришла почти год назад. Основная идея это коробочка, открыв которую, люди в возрасте могли легко и просто получить доступ к молитвам. Список вошедших молитв это топ самых высокочастотных запросов в ютубе со словом молитвы, кроме одной (думаю сами догадаетесь какой).

Первая версия моего молитвослова. В первой версии название молитв не совпадает с проигрываемыми молитвами.



После почти что 6000 км езды на поезде со мной в компании, он немного поломался и я решил сделать вторую версию.



Спасибо за внимание.
Подробнее..

Универсальные платы для умного дома на базе микроконтроллера ATmega128 (ATmega2561)

24.02.2021 22:18:59 | Автор: admin

Недавно я написал первый пост о том, как начал переделывать обычные светодиодные светильники в диммируемые. Многим не понравилось что свой диммер я делаю на базе микроконтроллера ATmega128. Поэтому хочу объяснить, почему используется именно этот микроконтроллер, и почему в наше время разрабатывая что-то ДЛЯ СЕБЯ, не стоит стремиться всё делать на самом слабеньком микроконтроллере, способном протянуть только лишь функционал разрабатываемого вами устройства.

Чтобы под каждое устройство умного дома не разрабатывать плату с нуля, я решил сделать универсальную плату на базе микроконтроллера ATmega128, к которой уже будут подключаться более специализированные платы для конкретных устройств.

Почему был выбран именно этот микроконтроллер? Да всё просто, потому что по цене ATmega128 всего на 20 центов дороже чем чем ATtiny2313. А ATtiny2313 стоит столько же, сколько и ATmega8. То есть уже про ATtiny2313 можно забыть как страшный сон.
Привожу пару картинок с ценами на AliExpress (а именно там я покупаю детальки) и идём дальше.

ATtiny2313:

Стоимость ATtiny2313Стоимость ATtiny2313

ATmega8:

Стоимость ATmega8Стоимость ATmega8

ATmega128:

Стоимость ATmega128Стоимость ATmega128

ATmega2561:

Стоимость ATmega2561Стоимость ATmega2561

Думаю комментарии излишни, сейчас даже для мигалки обычным светодиодиком куда выгоднее и рациональнее брать сразу ATmega128 чем 8 мегу, про тиньку и вообще молчу, забудьте про её существование как страшный сон. Да даже штук 5 транзисторов и резисторов для мигалки, уже будут стоить больше чем ATmega128. Так что забудьте про все микроконтроллеры слабее 128 меги, их использование в домашних проектах просто невыгодно и нерационально со всех сторон как ни посмотри. Да-да друзья мои, хочется вам или нет, но таковы реалии современного мира.

Следующий аргумент можно заметить если сравнить внимательно распиновку ножек ATmega128 и ATmega2561.

Сравнение между собой ATmega128 и ATmega2561Сравнение между собой ATmega128 и ATmega2561

Видим что распиновка ножек очень похожа, выводы SPI для программирования МК совпадают, так же совпадают и выводы питания, в общем почти всё совпадает, там буквально пару ножек различается которые ни на что не влияют, к чему я это веду, да к тому, что разработав плату для ATmega128, вы спокойно можете при необходимости купить и впаять в неё более производительный ATmega2561, а тут и памяти под программу больше в 2 раза и "оперативки". Например, мой главный модуль умного дома будет построен именно на ATmega2561, а остальные на ATmega128. Как итог, мне не нужно будет самому изготавливать плату для ATmega2561. Не знаю как кому, а лично мне, изготавливать в домашних условиях платы для smd микросхем тот ещё геморой. Ну не люблю я разводить такую мелюзгу, особенно ЛУТ-том (другой технологии я пока не освоил). Заказывать в Китае 10 плат ради одного модуля тоже не выгодно. А так мы разводим универсальную плату на базе ATmega128, и в одну из плат впаиваем ATmega2561 для главного модуля умного дома. Как итог, все наши платы для микроконтроллеров изготовлены на заводе в Китае, а в заводские платы даже впаивать smd микроконтроллеры проще, чем в платы собственного изготовления, во всяком случае для меня.

Ну и собственно к самой теме поста.
Схема моих универсальных плат для умного дома:

Схема платыСхема платы

Вот такие платы пришли из Китая:

Лицевая сторона платЛицевая сторона платЗадняя сторона платЗадняя сторона плат

После разрезания и впаивания компонентов платы выглядят так:

Лицевая сторона плат после впаивания компонентовЛицевая сторона плат после впаивания компонентовЗадняя сторона плат после впаивания компонентовЗадняя сторона плат после впаивания компонентов

Плата с модулем ADM488 для связывания всех модулей умного дома в единую сеть:

Плата с модулем ADM488Плата с модулем ADM488Плата с модулем ADM488Плата с модулем ADM488

Плата с модулем беспроводной связи nRF24L01+:

Плата с модулем nRF24L01+Плата с модулем nRF24L01+Плата с модулем nRF24L01+Плата с модулем nRF24L01+

Как видите, на универсальной плате есть 2 специализированных разъёма, для модуля ADM488 и для модуля nRF24L01+, вся остальная периферия подключаемая к таким универсальным платам будет подключаться шлейфами к выведенным штырькам.

Вот собственно и всё. Может кто-то подчерпнёт какие-нибудь полезные идеи и для себя.

Подробнее..

Ненормативная схемотехника ATmega8 кто сказал, что выше головы не прыгнешь?

30.04.2021 12:16:20 | Автор: admin
Вот уж несколько лет, как я увлёкся микроконтроллерами, а именно семейством AVR. Ещё на этапе освоения Ардуино (в этот момент часть аудитории поплевались и ушли читать другие статьи) я пытался выдавить из неё больше, чем задумано. Меня всегда больше интересовали нестандартные решения обычных задач. Сейчас я знаю об AVR намного больше, чем ещё пару лет назад, и всё больше убеждаюсь, что знаю очень мало.

С чего начинает среднестатистический начинающий электронщик? Правильно, с часов! Как только научился пользоваться голыми контроллерами, а не платами Ардуино, захотел сделать часы на Атмеге, голой Атмеге без кучи ключей и буферных микросхем. И обязательно со статической индикацией, а не с динамической (ну не люблю я её). Собственно, статическая индикация программно гораздо проще, а ведь мы простых путей не ищем. Но зато возникают другие проблемы, которые несколько раз меня останавливали в самом начале пути. Понятно, что если взять Атмегу пожирней, задействовать в ней половину ножек и полпроцента памяти, задача упрощается донельзя, собственно, такие настольные часы на ATmega128 работают у меня на столе пару лет.

Но это не наш метод. Я с самого начала хотел именно ATmega8, как самую доступную и самую дешёвую (в том числе в дип-корпусе). Всё то же самое можно и на ATmega48, но её попробуй ещё найди, разве что у Вас в ящике стола валяется их много с незапамятных времён.
Посмотрим на картинку, известную всем, кто недавно интересуется AVR.

Глядя на неё, легко посчитать, что мы можем задействовать на часовой индикатор 20 ножек, ещё на двух у нас будет кварц (куда от него денешься, внутреннее тактирование не прокатит, нам ведь от часов нужна точность какая-никакая). Ну и сброс. На четыре семисегментных индикатора нужно 28 ног, ну даже 27, ведь десятки часов можно отображать цифрами 1 и 2, а ноль не отображать. Ещё когда идея только зарождалась, я это количество сократил до 22 ножек, ведь для отображения десятков часов можно обойтись цифрой 1, и оба её сегмента зажигать от одной ноги контроллера. Но как ни крути, пары ног мне всё же не хватало, даже при том что я давно знал о возможности использования как ввода-вывода ножки Reset, но так ещё ни разу и не попробовал (если не считать ATtiny13, её не так жалко было), ведь параллельного программатора у меня нет, а любая отладка это минимум несколько итераций прошивки, вряд ли всё идеально выйдет с первого раза. Так и лежал этот замысел в закромах мозга, и ждал своего времени, пока как-то мне не пришла в голову ещё одна интересная идея.
Посмотрел я однажды на светодиодную ленту (120 светодиодов на метр) и понял, что можно легко и просто сделать электронные часы любого размера с её использованием. Просто нарезаем сегменты желаемой длины и наклеиваем их на подходящее основание, для больших размеров сегмент может складываться из двух и более лент по ширине. В моём случае отрезаем сегменты по шесть светиков, таким образом длина сегмента составляет 50 мм, высота цифры 100, для моей задумки офисных часов вполне достаточно.

Но вот незадача, напряжение питания ленты 12 вольт, а AVR хочет не больше 5 вольт (люди утверждают, что и 8 вольт выдерживают, но я проверять пока не буду, да и не поможет). То есть нужно ставить 22 ключа для включения всех сегментов. Если применить драйвер ULN2003, хватит (почти) трёх штук, это копейки, но, как я писал выше, это не наш метод, хотя этот вариант можно приберечь для часов побольше. Ну никак это не вписывается в концепцию голая Атмега.
И вот тут-то начинается самое интересное. Напряжение питания ленты 12 вольт, первые признаки жизни белые светодиоды (три штуки последовательно) начинают подавать при более чем 7.5 вольт, и, что очень важно, до этого напряжения ток через ленту практически равен нулю (можете проверить). На ножке контроллера, работающей в режиме выхода, может быть уровень 0 вольт или 5 вольт (третий вариант рассмотрим позже), и если подключить сегмент анодом к +12 вольт, а катодом к выходу контроллера, напряжение на сегменте будет равняться соответственно 12 вольт (светит) или 7 вольт (не светит). Даже если бы через погасший сегмент протекал мизерный ток, он ушёл бы к плюсу питания контроллера через защитный диод, которые в AVR гораздо более выносливые, чем нас пугали в обучающих статьях. Третий вариант ножка в режиме входа, напряжение на сегменте 7 вольт благодаря защитному диоду, стало быть сегмент не светит. Конечно, как только я обдумал всё это в теории, сразу же проверил на практике, залив в контроллер простой блинк для одной из ног.

А так как сейчас в силу некоторых обстоятельств мои возможности для экспериментов слегка ограничены, напряжение питания для сегмента случайно оказалось более 18 вольт, и сегмент гас не полностью, так что я успел разочаровано подумать, что подпалил выход. Когда уменьшил напряжение (на тот момент даже нечем было померить), всё стало на свои места, так что, благодаря нелепой случайности, теперь я точно знаю, что для схемы не смертельно небольшое повышение напряжения выше 12 вольт. С напряжением определились, а что с током? На моей ленте (и скорей всего на вашей тоже) стоят резисторы по 150 ом, ток через сегмент не более 20 мА, только сегмент в данном случае это три светика, а каждый сегмент моих часов состоят из двух сегментов ленты, так что 40 мА. Вроде даже вписываемся в даташит, но на 20 выходов это уже 800 мА, что намного выше дозволенных 200 мА на корпус. Ток через ленту очень зависит от напряжения на ней, и нелинейно падает даже при небольшом снижении, что является большим минусом при обычном использовании ленты, и плюсом в данном случае, ведь реальное напряжение на сегментах равно 12 вольт минус падение на ключе (около 0.6 вольт), а ещё при желании можно снизить напряжение питания, понизив тем самым яркость часов. Так что страшные 800 мА несложно снизить раза в два. В любом случае, я был уверен, что это не станет проблемой, да и приобретённый опыт ценнее, чем возможность потери одной Атмеги. Вот так просто ATmega8 коммутирует индикатор с напряжением в два с лишним раза выше, чем её собственное напряжение питания. Именно это я имел ввиду в заголовке статьи. Хотя способ совсем не нов, по такому же принципу работает советская микросхема К155ИД1 высоковольтный дешифратор управления газоразрядными индикаторами, где сравнительно низковольтные выходы (до 60 вольт) коммутировали индикаторы с напряжением зажигания 150 вольт и выше.

Дальнейшие эксперименты я продолжил с обычным четырёхсимвольным семисегментным индикатором с общими анодами и раздельными катодами, как раз припасён был для такого случая.

Аноды к +5 вольт через один на всех резистор, для отладки прошивки этого за глаза. Первоначально я остановился на варианте использования в качестве двух недостающих ног пинов подключения кварца (итого 22 выхода), а тактировать контроллер от внешнего генератора минутных импульсов через (внимание!)

вход сброса. Это ещё один занимательный лайфхак, который описан здесь Как сохранять переменные arduino, при reset
Можете попробовать залить в Ардуино простой код и посмотреть результат работы в Мониторе порта, периодически сбрасывая её кнопкой или с клавиатуры:
пробник
#define NO_INIT __attribute__((section(".noinit")))void setup() {    static unsigned NO_INIT nonInitCounter;    nonInitCounter=nonInitCounter+2048;    Serial.begin(9600);    Serial.print("Setup counter: ");    Serial.println(nonInitCounter);}void loop(){}

Не буду приводить свой код, дабы не подвергаться критике лишний раз (я не самый лучший
программист), но если в двух словах, можно создать глобальную переменную, которая сохраняется при перезагрузке контроллера. Раз в минуту под воздействием внешнего импульса сброса Атмега перезагружается, и начинает заново отрабатывать прошивку: считывает переменную, преобразует её в показания часов и минут, выводит на индикатор, увеличивает переменную на одну минуту. Дальше опять сброс, и всё по-новой. Пришлось порыться в интернете и опробовать два вида программного сброса, ведь внешнего генератора у меня пока не было, а жмакать сброс всё время вручную не сильно удобно.
пример функции
void softReset() {  //программный сброс //http://kazus.ru/forums/showthread.php?t=13540&page=3#  asm volatile ("rjmp 0x0000");//Sketch uses 2412 bytes  //программный сброс //https://www.cyberforum.ru/post11307314.html  //((void(*)(void))0)();//Sketch uses 2416 bytes}

Что интересно, эта переменная может сохраняться даже после отключения питания (не всегда). И это не EEPROM, если кто подумал. По мере отладки я пришёл к использованию нескольких таких переменных.
Кто-то скажет: какой внешний генератор, а как же концепция голой Атмеги? На момент опытов я был уверен, что в качестве генератора выступит некая козявка размером с рисинку и ценой в пару копеек. Но когда всё заработало в теории и пришло время с козявкой определиться, меня ждал облом. Единственное, что меня могло бы устроить, это старая добрая К176ИЕ12, кто бы мог подумать! На дворе двадцать первый век, а в природе нет простого доступного аппаратного генератора минутных импульсов.
Не беда, подумал я. Можно и раз в секунду. Немного переписал код, проверил работу, отлично. Но опять же, для внешнего генератора я нашёл только DS1307 (и её аналоги). Да, стоит копейки, для обвязки достаточно кварца (и ещё несколько необязательных элементов). Вот только чтобы она генерировала секундные импульсы, ей нужно подать команду по шине I2C. Чем подать? И какую? (Теперь я уже знаю, но на тот момент не было с чем пробовать). Короче, не подходит. Ладно, последняя надежда. Некоторые западные часы 90-х тактировались частотой сети 50 гц. Возможно, сейчас у нас частота стабильней, чем была 30 лет назад. Попробовал сымитировать работает. Если бы воплотил в жизнь, было бы оригинально контроллер, перезагружающийся 100 раз в секунду. Но я пока отмёл этот вариант и стал искать другие.
Итак, я мог использовать 22 выхода (что мне достаточно) и пин сброса в качестве входа. Но не срослось Вернёмся к варианту с кварцем. Минус две ноги на кварц получаем 20, даже если ножку сброса сделать портом ввода-вывода, получим 21, всё равно не хватает одного, хоть ты тресни! Но недавно я нашёл способ, о котором задумывался ещё два года назад, что-то не получилось тогда. У нас есть пин AREF для опорного напряжения, относительно которого происходят аналоговые измерения. И на этот пин мы можем программно подать напряжение питания в качестве опорного или встроенное опорное напряжение 2.56 (для ATmega8). Проверил, измерил, действительно можно получить на ножке AREF напряжение 0 вольт, 2.56 вольт, или 5 вольт.
превращаем AREF в ещё один выход
// превращаем AREF в ещё один выходvoid setup() {}void loop() {  ADMUX = 7 + 64; // уровень 1 (5 вольт) // 7-номер аналогового входа//почему 7 аналоговый вход? А потому что физически у Атмеги входы от 0 до 5,//и назначение аналоговым входом входа 7 не помешает работе остальных в качестве выходов  delay(3000);  ADMUX = 7 + 64 + 128; // уровень 2.56 вольт // 7-номер аналогового входа  delay(3000);  ADMUX = 7; // уровень 0  delay(3000);}

Нюанс: это если замерять относительно общего провода. Если мерить относительно +5 вольт, во всех трёх случаях получаем ноль. То есть нагрузку можно подключить между AREF и общим. Экспериментально установил, что можно получить ток до 20 мА при 2.56 вольт на ноге и до 40 мА при 5 вольт на ноге, этого вполне достаточно, чтоб зажечь светодиод, к примеру. Вот он, заветный двадцать второй выход! Конечно, напрямую управлять двенадцативольтовым сегментом не получится, нужен транзистор. Под руку попался легендарный КТ315, сколько лет я к нему не прикасался, как по мне, это одно из лучших произведений советской электроники, наряду с микрухой К155ЛА3. Когда-то я КТ315 и КТ361 даже в качестве ключей импульсного трансформатора питания применял, при напряжении порядка 60 вольт, и неоднократно. Здесь же нагрузка для него плёвая, и я принципиально даже резистор в базу не поставлю (20 мА вполне себе допустимый ток базы).
Ну что ж, думал я, 22 выхода есть, отлажу код с 21 выходом, а 22-й на ножке сброса оставлю напоследок, когда всё остальное уже заработало. Ещё ведь и кнопки установки времени нужно куда-то приткнуть, а ног не осталось вовсе. Если б Атмега была в SMD-корпусе, можно было бы воспользоваться входами А6 и А7, выходами они всё равно не умеют. Но у меня корпус DIP, так что такой роскоши позволить себе не могу. Зато ведь можно выходы сделать входами, правда, в это время придётся погасить индикацию, но для опроса кнопок достаточно нескольких миллисекунд, никто и не заметит (как я ошибался!). Значит, перевожу ножки кнопок в режим входов, подтягиваем программно к питанию, и ждём пару миллисекунд. Если в это время какая-то из кнопок замкнута на минус, ждём ещё 20 миллисекунд, убеждаемся, что кнопка всё ещё нажата, и производим соответствующее действие в программе. Кнопки: минуты плюс, минуты минус, часы плюс, коррекция плюс. Коррекция подстройка значения секунд раз в сутки в зависимости от суточного ухода показаний. Всё заработало почти с первого раза. Нюанс: при замкнутой кнопке сегмент, подключенный к той же ножке, оказывается включён, такой вот побочный эффект, что поделать. Поэтому кнопки изменения минут подключаю к выводам контроллера, которые управляют сегментами единиц часов, а кнопки изменения часов подключаю к выводам контроллера, которые управляют сегментами единиц минут. Выставляя минуты, совсем не обращаешь внимания, что с показаниями часов что-то не так, и наоборот.
Ну вот всё и получилось в макете с семисегментными индикаторами. Но параллельно я пытался накопать информации по поводу применения ноги сброса в качестве порта ввода-вывода. А там всё довольно туманно и неопределённо. Основное, что об этом пишут: 1 выход с открытым коллектором (не точно), 2 выход чрезвычайно слаботочный (тоже не наверняка). Даже если использовать внешний транзистор, нужно точно знать, открытый коллектор (сток) или нет, ведь тогда состояние выхода нужно инвертировать. А перепрошить я уже не смогу. В общем, опять двадцать пять! Вернее, двадцать один. Двадцать один гарантированный выход вместо 22. Опять одного не хватает. Опять я мечусь в поисках решения.
Возвращаюсь к AREF. Я могу получить три разных уровня на нём. Значит можно зажечь один сегмент, или два одновременно, или ни одного. Я обратился к своей же прошлой публикации (как давно это было!):Ненормативная схемотехника: семисегментный индикатор на ATtiny13
Чтобы попробовать использовать решение с совместным включением двух сегментов. Перебирая возможные пары сегментов, и осознавая, что подобное усложнение портит всю картину, я внезапно додумался, что для цифры десятков минут поле поиска значительно сужается цифра ведь может быть только от 0 до 5. И тут пришло озарение:

во всех этих цифрах сегменты А и D или вместе светятся, или вместе погашены! Не нужно никаких трёх состояний, достаточно просто соединить сегменты А и D вместе и подключить к одному выходу. И теперь 21 выхода хватит всем. Довожу код, проверяю на макетке с семисегментным индикатором, бинго!
Всё, пора делать финальный вариант. Нарезаю светодиодную ленту, наклеиваю сегменты на подходящее основание, которым оказался кусок пластиковой вагонки (примерно 150х350 для цифр высотой 100). К каждому сегменту нужно подвести +12 вольт и проводник от соответствующего выхода контроллера.

В качестве монтажного провода применил для +12 тонкую проволочку без изоляции, вынутую из обычного гибкого провода, подпаиваю к плюсу каждого сегмента, продеваю через проколотое отверстие на другую сторону основания и спаиваю их все вместе, а для остальных проводников обмоточный провод в лаковой изоляции, продеваю их через те же отверстия и подпаиваю к панельке контроллера. На каждую цифру получилось по три отверстия в местах, где сходятся сегменты, в одно отверстие выходит один провод на +12, и два или три провода в изоляции. Также к контроллеру подведены четыре провода от кнопок, кнопки замыкаются на минус, подтяжка внутренняя. Я при проверке обходился без кнопок, просто касался провода проводом.

Сегменты цифр разведены по контроллеру именно в таком порядке для упрощения кода.
Первая цифра (единица, напоминаю) состоит из цельного куска ленты длиной 10 см. Подключаю 5 вольт питания контроллера и 12 вольт питания индикации, включаю. Вот он, торжественный момент, всё красиво светится, часики работают. Кстати, на КДПВ в начале статьи в качестве светофильтра на индикаторах лежит лист обычной бумаги для принтера, яркости хватает с избытком.
Мне не очень нравится американская система отображения времени, когда часы дважды в сутки считают до двенадцати. Я применил свою: с нуля часов до 19, и затем 8, 9, 10, 11. А с учётом того, что часы офисные, в 8 вечера их редко кто увидит.

В такие моменты ощущаешь некоторое разочарование, что прям вот так сразу заработало, даже как-то неинтересно. Поначалу я упорно не хотел замечать некое мерцание индикаторов, пока мне на него не указали коллеги. В макете этого мерцания не было видно совсем, а тут прям бросается в глаза. Выше я писал, что при опросе кнопок после перевода ножек в режим ввода сделана пауза в пару миллисекунд, без этой паузы остаточный потенциал на ножке воспринимался как нажатие. Так вот тих двух миллисекунд, в течение которых индикаторы потушены, оказалось достаточно для мерцания индикации. И это при том, что опрос происходит два раза в секунду. То есть глаз замечает двухмиллисекундную паузу дважды в секунду, чего я никак не ожидал. Было подозрение, что вследствие переходных процессов контроллер каждый раз перепроверяет, действительно ли нажаты кнопки (а это по 20 мсек на каждую). Я внёс некоторые изменения в код, временно отключив все лишние функции, но подозрения не подтвердились. В результате помогли следующие изменения: для опроса кнопок перевожу те выходы, которые задействованы под кнопки, в высокий уровень, тем самым погасив соответствующие сегменты, только после этого перевожу те же ножки в режим входа. Таким образом от паузы в 2 мсек можно избавиться совсем, но я оставил на всякий случай 300 микросекунд, проблема исчезла полностью.
Что мы имеем в итоге? Ножка сброса контроллера работает по прямому назначению и используется только при заливке прошивки, ножки кварца подключены к кварцу, как и положено. Двадцать ног работают как порты ввода-вывода, и нога AREF управляет ключевым транзистором, как раз на него и навешена цифра десятков часов (1). Ещё четыре ножки питания, никто не отлынивает, все ноги задействованы. По поводу кварца: я всерьёз рассматривал вариант применения часового кварцевого резонатора на 32768 Гц (считал его более точным), но отказался от идеи, побороздив интернет. Оказывается, запустить Атмегу с часовым кварцем не так-то просто и нет никаких гарантий работоспособности, а плюсов от применения не особо. Экономичность нас не интересует в данном случае, основное потребление индикация. С точностью тоже всё неопределённо. А суточный уход вполне компенсируется программно. Зато большим плюсом является простота подключения, кварц на 8 или 16 МГц без проблем работает даже без конденсаторов. В результате вся схема состоит (если не считать индикаторы и питание) из Атмеги, кварцевого резонатора, и транзистора, припаянных прямо к панельке. Питание контроллера обеспечивается малогабаритным стабилизатором из серии 7805, ток через него мизерный, но на всякий случай я припаял его теплоотводом к кусочку оцинковки примерно 30х30 мм. В целом же часы питаются от внешнего блока питания на 12 вольт, который, по сути, дороже всех комплектующих. Фактический ток потребления часов 560 мА при показаниях часов 18-08 (это максимальное количество сегментов, которые можно засветить одновременно), получается около 28 мА на сегмент. Это при напряжении питания часов 11.7 вольт, ещё 0.3 вольта падает на диоде, включенном для защиты от неправильного подключения и чтоб немного снизить напряжение и ток соответственно. Падение на выходных ключах Атмеги около 0.56 вольт. Все токовые режимы превышены, но Атмега справляется, честь и хвала творцам! Запаса яркости избыточно, напряжение питания можно ещё снижать. Если тактировать Атмегу от внутреннего генератора, то можно ножки кварца отдать индикатору, а время считывать по I2C с DS1307. Опять же будут заняты все ноги, но зато питание часов можно будет отключать хоть на неделю, а время продолжит тикать. Хотя точность DS1307 совсем не радует, и по моему опыту, и по отзывам в интернете. Зато на ней есть дополнительный выход с открытым коллектором, которому можно дать команду мигать с частотой 1 Гц, и навесить на него разделительную точку. В моих часах разделительных точек пока нет, можно разрезать ту же ленту на отдельные светодиоды и подключить постоянно к напряжению питания. Мигать не будет, но я думаю над этим. Может, кто подскажет, как выжать ещё каплю из Атмеги?
И, напоследок, код для тех, кто захочет повторить. Компилировал и прошивал в ArduinoIDE. Я не программер, так что примите как есть, если кто предложит лучше, с удовольствием выложу.
Особо чувствительным не смотреть
Я предупреждал
bool Flag;uint8_t TimS;uint16_t TimH = 12; // время при включении 12-34uint16_t TimH_;uint16_t TimM = 34;uint16_t TimH0;uint16_t TimH1;uint16_t TimM0;uint16_t TimM1;uint16_t TimKorr = 7; // коррекия по умолчанию 7 - это 0 секунд, если 0 - это -28 сек, если 14 - это +28 сек// массив для цифрint semisegm_[10] = {B01011111, B00000110, B01101011, B01100111, B00110110, B01110101, B01111101, B00000111, B01111111, B01110111};void setup() {                                                         //  PORTB = 0;  PORTC = 0;  PORTD = 0;  // инициализация Timer1  cli();  // отключить глобальные прерывания  TCCR1A = 0;   // установить регистр в 0  TCCR1B = 0;   // установить регистр в 0  // Таймер переполняeтся каждые 65535 отсчетов при коэффициенте деления 1024 или за 4,194с  OCR1A = 62499; // установка регистра совпадения (4 секунд)  TCCR1B |= (1 << WGM12);  // включить CTC режим > сброс таймера по совпадению  TCCR1B |= (1 << CS10);   // Установить биты CS10 CS12 на коэффициент деления 1024  TCCR1B |= (1 << CS12);  TIMSK |= (1 << OCIE1A);  // для ATMEGA8  sei(); // включить глобальные прерывания}   void loop() {  if (TimM > 59) {    TimM = 0;    TimH++;    Flag = 0;  }  if (TimH > 23)TimH = 0;  if (TimM < 0) {    TimM = 59;  }  if (TimH == 0) {    if (TimM == 0) {      if (TimS == 7) {        if (Flag == 0) {          TimS = TimKorr;          Flag = 1;        }      }    }  }  TimH_ = TimH;  if (TimH > 19)TimH_ = TimH - 12;  TimH0 = TimH_ / 10;  TimH1 = TimH_ % 10;  TimM0 = TimM / 10;  TimM1 = TimM % 10;  Led(TimH0, TimH1, TimM0, TimM1);  Key();}// функция индикацииvoid Led(uint16_t TimH0, uint16_t TimH1, uint16_t TimM0, uint16_t TimM1) {  DDRB = semisegm_[TimM1];  DDRC = semisegm_[TimM0];  DDRD = semisegm_[TimH1];  bitWrite(DDRD, 7, bitRead(semisegm_[TimM1], 6));  ADMUX = 199 * TimH0; // уровень 2.56 на AREF  PORTB = 0;  PORTC = 0;  PORTD = 0;  delay(500); // полсекунды просто отображаем время}// функция опроса кнопокvoid Key() {  bitWrite(PORTB, 2, 1);  bitWrite(PORTD, 2, 1);  bitWrite(PORTD, 1, 1);  bitWrite(PORTD, 0, 1);  bitWrite(DDRB, 2, 0);  bitWrite(DDRD, 2, 0);  bitWrite(DDRD, 1, 0);  bitWrite(DDRD, 0, 0);  delayMicroseconds(300);   if (bit_is_clear(PINB, 2)) {      delay(20);    if (bit_is_clear(PINB, 2)) {      TimH++;    }  }  if (bit_is_clear(PIND, 1)) {    delay(20);    if (bit_is_clear(PIND, 1)) {      TimM++;    }  }  if (bit_is_clear(PIND, 0)) {    delay(20);    if (bit_is_clear(PIND, 0)) {      TimM--;    }  }  if (bit_is_clear(PIND, 2)) {    delay(20);    if (bit_is_clear(PIND, 2)) {      TimKorr++;      if (TimKorr > 14)TimKorr = 0;      Led(0, 8, TimKorr / 10, TimKorr % 10);      delay(500);    }  }}ISR(TIMER1_COMPA_vect) // Выполняем 1 раз в 4 секунды.{  TimS++;  if (TimS > 14) {    TimM++;    TimS = 0;  }}


Подробнее..

Портирование ModBus Slave RTUASCII на IAR AVR v3

27.11.2020 16:12:57 | Автор: admin


Я уже десять лет не писал под AVR А вдруг разучился?! Для проверки я решил портировать библиотеку ModBus Slave RTU/ASCII без смс и регистрации на платформу IAR AVR, а также, по просьбам читателей, показать демку подключения к панели оператора Weintek.


Для решения этой задачи в кустах обнаружился рояль ноутбук IBM ThinkPad, нужным комплектом программного обеспечения.


Спаян шнурок для программатора AVReal.


В хламе найдена макетная плата с ATMega48. Фотография макетной платы на на первом рисунке.

Поехали!


Для портирования библиотеки ModBus Slave RTU/ASCII без смс и регистрации необходимо написать интерфейсы системного таймера и последовательного порта. У автора нездоровая привычка, писать низкоуровневый ввод/вывод для AVR на ассемблере. В нашем случае, я не буду отказывать себе в своих привычках.
Заголовочный файл systimer.h
#ifndef __SYSTIMER_H#define __SYSTIMER_H#ifdef __SYSTIMER_ASM#define CLKSysTimer (8000000/64)#else#include "main.h"//Инициализацияvoid InitSysClock(void);//время от запуска в милисекундахunsigned long Clock(void);#endif#endif


Файл systimer.asm
#define __SYSTIMER_ASM#include <iom48.h>#include "systimer.h"MODULE __systimerCOMMON INTVECORG TIMER0_COMPA_vect  rjmp tim0_compRSEG CODEtim0_comp:  in r10,SREG  inc r11  add r12,r11  dec r11  adc r13,r11  adc r14,r11  adc r15,r11  out SREG,r10  retiPUBLIC InitSysClockInitSysClock:  cli  clr r11  clr r12  clr r13  clr r14  clr r15  push r16  ldi r16,(0<<COM0A1)|(0<<COM0A0)|(0<<COM0B1)|(0<<COM0B0)|(1<<WGM01)|(0<<WGM00)  out TCCR0A,r16  ldi r16,(0<<FOC0A)|(0<<FOC0B)|(0<<WGM02)|(3<<CS00)  out TCCR0B,r16  ldi r16,(CLKSysTimer/1000)  out OCR0A,r16  ldi r16,(0<<OCIE0B)|(1<<OCIE0A)|(0<<TOIE0)  sts TIMSK0,r16  pop r16  retiPUBLIC ClockClock:  cli  movw r16,r12  movw r18,r14   retiENDMODEND


В качестве системного таймера используется TIMER0. В прерывании по совпадению таймера (COMPA), происходящем каждую милисекунду, инкрементируется четырехбайтная переменная находящаяся в регистрах r12-r15. Эти регистры не поддерживают работу с константами, поэтому для инкремента приходится использовать регистр r11. Регистр r10 используется для сохранения регистра состояния процессора. Перечисленные регистры зарезервированы в настройках компилятора.
Значение переменной r12-r15, через атомарную операцию считывается функцией Clock(), необходимой для работы библиотеки ModBus Slave RTU/ASCII.
Частота прерываний таймера определяется константой CLKSysTimer в заголовочном файле. Значение константы отношение тактовой частоты процессора к пределителю таймера.

Интерфейс последовательного порта.
Заголовочный файл uart.h
#ifndef __UART_H#define __UART_H#ifdef __UART_ASM#define CLK_Uart (8000000)#define UartSpeed (19200)#define FIFORX (32)#define FIFOTX (64)#else#include "main.h"void UartInit(void);unsigned short Inkey16Uart(void);void PutUart(unsigned char a);#endif#endif


Файл uart.asm
#define __UART_ASM#include <iom48.h>#include "uart.h"MODULE __uartrxtxRSEG NEAR_Zrxfifo: //Буфер FIFO  DS FIFORXrxHead://голова, пишем на голову  DS 1 rxTail://хвост, читаем с хвоста  DS 1 txfifo://Буфер FIFO  DS FIFOTXtxHead://голова, пишем на голову  DS 1 txTail://хвост, читаем с хвоста и в UART  DS 1 COMMON INTVECORG USART_RX_vect  rjmp uart_rxORG USART_TX_vect  rjmp uart_tx  RSEG CODE//void UartInit(void);PUBLIC UartInitUartInit:  sbi PORTD,0  cli  push r16  //обнуление указателей  clr r16  sts rxHead,r16  sts rxTail,r16  sts txHead,r16  sts txTail,r16  //Скорость передачи  ldi r16,LOW((CLK_Uart/8+UartSpeed/2)/UartSpeed-1)  sts UBRR0L,r16  ldi r16,HIGH((CLK_Uart/8+UartSpeed/2)/UartSpeed-1)  sts UBRR0H,r16  //Enable receiver and transmitter, разрешение прерываний  ldi r16,(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<TXCIE0)  sts UCSR0B,r16  //Set frame format: 8data, 1stop bit, Parity No  ldi r16, (0<<UMSEL00)|(0<<UPM00)|(0<<USBS0)|(3<<UCSZ00)  sts UCSR0C,r16  //сброс флагов прерываний UART  lds r16,UCSR0A  ori r16,(1<<TXC0)|(1<<U2X0)   sts UCSR0A,r16  lds r16,UDR0  pop r16  reti//Обработчик прерывания по приемуuart_rx:  push r16  in r16,SREG  push r16  push XL  push XH//UART->FIFO    lds r16,rxHead  ldi XL,LOW(rxfifo)  ldi XH,HIGH(rxfifo)  add XL,r16  adc XH,r16  sub XH,r16  inc r16  andi r16,(FIFORX-1)  sts rxHead,r16  lds r16,UDR0  st X,r16  pop XH  pop XL      pop r16  out SREG,r16  pop r16  reti//unsigned short Inkey16Uart(void);//Если нет данных возвращает 0х0000, иначе возвращает 0х01ХХPUBLIC Inkey16UartInkey16Uart:  lds R17,rxHead  lds r16,rxTail  cp r16,r17  breq Inkey16Uart1  //читаем данные из FIFO    push XL  push XH  ldi XL,LOW(rxfifo)  ldi XH,HIGH(rxfifo)  add XL,r16  adc XH,r16  sub XH,r16    inc r16  andi r16,(FIFORX-1)  sts rxTail,r16    ld r16,X  pop XH  pop XL  ldi r17,1  retInkey16Uart1:  clr r16  clr r17  ret//обработчик прерывания по передачеuart_tx:  push r16  in r16,SREG  push r16  push r17  //проверяем наличие данных в буфере  lds r17,txHead  lds r16,txTail  cp r16,r17  brne uart_tx2    rjmp uart_tx_enduart_tx2://если данные есть - передаем    push XL  push XH  ldi XL,LOW(txfifo)  ldi XH,HIGH(txfifo)  add XL,r16  adc XH,r16  sub XH,r16    inc r16  andi r16,(FIFOTX-1)  sts txTail,r16    ld r16,X  sts UDR0,r16  pop XH  pop XLuart_tx_end:  pop r17  pop r16  out SREG,r16  pop r16  reti  //void PutUart(char a);PUBLIC PutUartPutUart:  push XL  push XH//проверяем наличие данных в буфере  lds XH,txHead  lds XL,txTail  cp XH,XL  brne PutUart1//проверякм регистр передачи  lds XL,UCSR0A  sbrs XL,UDRE0  rjmp PutUart1  sts UDR0,r16  pop XH  pop XL  retPutUart1://положить в txfifo[]    push r16  mov r16,XH  ldi XL,LOW(txfifo)  ldi XH,HIGH(txfifo)  add XL,r16  adc XH,r16  sub XH,r16  inc r16  andi r16,(FIFOTX-1)  sts txHead,r16  pop r16  st X,r16  pop XH  pop XL      retENDMODEND


Интерфейс последовательного порта реализован по классической схеме. Как на прием, так и на передачу реализован тип данных очередь на кольцевом буфере. Размер буфера приема и передачи определяется константами FIFORX, FIFOTX соответственно. В целях экономии вычислительных ресурсов процессора, размер буферов приема и передачи должен быть кратен 2^N (2,4,8,16,32...), но не больше 256.
Скорость приема/передачи последовательного порта определяется константой UartSpeed. При тактовой частоте микроконтроллера (определяется константой CLK_Uart) 8МГц, то есть, при использовании внутреннего RC-генератора нет возможности использовать высокие скорости передачи.

При попытке скомпилировать файл библиотеки modbus.c, IAR заругался страшными словами. Компилятор IAR AVR не умеет много чего из стандарта С99. Он также не умеет, при использовании модификатора const, размещать объекты в памяти программ, для этого служит специальный модификатор __flash. Пришлось потратить несколько минут для приведения кода в соответствие требованиям компилятора.
В файле библиотеки modbus.h необходимо определить макросы вызова функций последовательного интерфейса и системного таймера.
//Системный таймер, инкрементируется каждую милисекунду#define ModBusSysTimer Clock()//Запись байта в поток последовательного порта - void ModBusPUT(unsigned char A)#define ModBusPUT(A) PutUart(A) //Чтение байта из потока последовательного порта, - unsigned short ModBusGET(void)//Если нет данных возвращает 0х0000, иначе возвращает 0х01ХХ#define ModBusGET() Inkey16Uart()

А также определить количество дискретных входов/выходов, регистров для чтения и регистров для чтения/записи. На этом портирование библиотеки можно считать законченным.

Демка


Для демонстрации возможностей библиотеки ModBus Slave RTU/ASCII подключим наше устройство к панели оператора Weintek. В микроконтроллере организованы часы, значения часов, минут секунд выводятся в регистры Modbus, код содержится в файле ModBus2Prg.c:
void Prg2ModBusOutReg(void)  {//заполнение регистров 4Х регистры для чтения/записи  ModBusOutReg[0]=Seconds;  ModBusOutReg[1]=Minutes;  ModBusOutReg[2]=Hours;   return;  }void Prg2ModBusInReg(void)  {//заполнение регистов 3Х регистры для чтения  ModBusInReg[0]=Seconds;  ModBusInReg[1]=Minutes;  ModBusInReg[2]=Hours;    return;  }

Через регистры чтения/записи можно произвести установку часов:
void ModBus2PrgOutReg(void)  {//чтение регистров 4Х регистры для чтения/записи  Seconds=ModBusOutReg[0];  Minutes=ModBusOutReg[1];  Hours=ModBusOutReg[2];   return;  }

Дискретные входы/выходы Modbus подключены к портам вывода микроконтроллера. Так же к дискретному входу 4 подключен счетчик полусекунд:
void Prg2ModBusOutBit(void)  {//заполнение регистров дискретных выходов  ModBusOutBit[0].bit0=PORTC_Bit1;  ModBusOutBit[0].bit1=PORTC_Bit2;  ModBusOutBit[0].bit2=PORTC_Bit3;  ModBusOutBit[0].bit3=PORTC_Bit4;  return;  }void Prg2ModBusInBit(void)  {//заполнение регистров дискретных входов  ModBusInBit[0].bit0=PORTC_Bit1;  ModBusInBit[0].bit1=PORTC_Bit2;  ModBusInBit[0].bit2=PORTC_Bit3;  ModBusInBit[0].bit3=PORTC_Bit4;  ModBusInBit[0].bit4=PoluSeconds;  return;  }

Через дискретные выходы можно управлять состоянием портов микроконтроллера:
void ModBus2PrgOutBit(void)  {//чтение регистров дискретных выходов  PORTC_Bit1=ModBusOutBit[0].bit0;  PORTC_Bit2=ModBusOutBit[0].bit1;  PORTC_Bit3=ModBusOutBit[0].bit2;  PORTC_Bit4=ModBusOutBit[0].bit3;  return;  }

Программное обеспечение панели разрабатывается в среде EasyBuilder Pro v5.

Подключаем микроконтроллер через USB преобразователь к компьютеру, указываем в настройках проекта панели протокол обмена Modbud RTU и настройки COM-порта через который произошло подключение. Запускаем онлайн симуляцию панели.

Код демки с использованием библиотеки ModBus Slave RTU/ASCII со всеми опциями, после компиляции IAR AVR v3 с оптимизацией по скорости оказался на удивление компактным. Он занимает 3024 байта памяти программ, 398 байт памяти данных. Искренне надеюсь, что библиотека ModBus Slave RTU/ASCII найдет широкое применение для разработки Modbus устройств на маломощных микроконтроллерах.

Проект на GitHub
Видео демки
Подробнее..

Кому в микроконтроллере жить хорошо?

04.12.2020 00:04:03 | Автор: admin

В каком году рассчитывай, в какой земле угадывай, задачился вопросами. Насколько ARM быстрее AVR? Какая разновидность протокола Modbus более быстрая? ASCII или RTU?

Под быстротой, в данном случае, будем понимать количество машинных циклов процессора необходимых для исполнения всех действий протокола.
Исследование быстродействия будем проводить на, широко известной в узких кругах, библиотеке ModBus Slave RTU/ASCII, портированной на микроконтроллеры ATMega48 и STM32L052. Вывод информации будем осуществлять по протоколу Modbus в эмулятор панели Weintek. Тестирование будем производить на демонстрационном примере. Помимо результатов теста на панель выводятся состояния регистров Modbus: дискретных входов, дискретных выходов, регистров для чтения и регистров для чтения/записи. Также средствами панели производится подсчет количества ошибок обмена данными с микроконтроллером. Внешний вид тестового окна приведен на рисунке.



Оценку быстродействия будем проводить измеряя время выполнения функции обработки сообщений протокола. Измерение времени выполнения будем проводить рабоче-крестьянским способом. Перед запуском функции обнуляем аппаратный таймер, частота счета которого равна тактовой частоте микроконтроллера, после выполнения функции считываем значения таймера и проводим обработку результатов измерений. Вычисляем минимальное, максимальное и среднее значение времени выполнения функции обработки сообщений протокола Modbus.
while(1)    {    TIM6->CNT=0;    ModBusRTU();    //ModBusASCII();    tcurent=TIM6->CNT;    if(tcurent<tmin)tmin=tcurent;    if(tcurent>tmax)tmax=tcurent;    avg32=avg32-(avg32>>16)+tcurent;    tavg=avg32>>alfa;    ...

Результаты исследований, сведены в таблицу. Исследование проводились при различных опциях библиотеки:
  • ModBusUseTableCRC Использовать расчет CRC по таблице;
  • ModBusUseErrMes Использовать сообщения о логических ошибках протокола;

А также при различных стратегиях оптимизации компилятора.
Библиотека ModBus Slave RTU/ASCII поддерживает, в некоторых случаях, важную функцию пауза между получением запроса от Modbus Master и ответом Modbus Slave. Исследование проводились при значениях паузы 2 милисекунды и 0 (то есть без паузы), эти значения указаны в графе таблицы Пауза П/П. В графе Размер указан размер модуля, который включает в себя обе функции обработки сообщений Modbus (ModBusRTU(), ModBusASCII()).



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

Глубоко задумавшись над результатами исследований, можно сделать следующие выводы:
  1. AVR не такой уж медленный!!! В среднем он в полтора раза медленнее ARM при той же тактовой частоте. А в случае оптимизации по размеру (например варианты 13 и 15) практически приближается к ARM.
  2. Протокол ASCII, по сравнению c RTU, не только более медленный по скорости передачи, но и занимает гораздо больше ресурсов микроконтроллера.
  3. Использование сообщений о логических ошибках протокола, никак не влияет на быстродействие.
  4. Табличный метод вычисления CRC позволяет более чем в полтора раза снизить использование вычислительных ресурсов микроконтроллера.
  5. Использование паузы между приемом запроса и передачей ответа, позволяет не только избежать конфликтов на шине RS-485, но и уменьшить блокирующие действие функции обработки сообщений протокола Modbus.


Какие выводы еще можно сделать?

Проект на GitHub


Скачать одним файлом

Подробнее..

Excel как транслятор в ассемблер AVR

07.11.2020 02:14:23 | Автор: admin

Предпосылки


Ряд статей (http://personeltest.ru/aways/habr.com/ru/post/345320/, habr.com/ru/post/80893, habr.com/ru/post/246975 ) навел на мысли о том, что Excel можно использовать как транслятор в ассемблерный код AVR.

Сопоставим Excel с основными фичами обычного редактора кода. Список самых популярных фич следующий:
Фича редактора кода Как реализовать в Excel Как реализовано в Atmel Studio
Подсветка синтаксиса Условное форматирование ячеек в зависимости от содержания Подсветка команд
Автодополнение Пользовательские функции VBA;
Именованные диапазоны;
Ячейка как миниконсоль с макросом на cобытие Change;
Нет
Отступы Вручную переход в соседний столбец Вручную
Проверка правильности расстановки скобок Есть встроенный контроль Только при сборке
Контекстная помощь по коду Нет Есть список имен меток
Сворачивание блока кода Функция группировки столбцов и строк Нет
Статический анализатор На ходу показывает ошибки в ссылках Нет

Выглядит приемлемо. Кое-что достается даром, кое-что надо доработать.

Но главное отличие табличного процессора от текстового пользователь может размещать блок информации в произвольном месте пространства, а не только друг под другом. Эту особенность мы используем, чтобы превратить плоский код в почти полноценную блок-схему.

Первоначальные соглашения


Общий подход и терминологию примем отсюда: habr.com/ru/post/345320
Весь алгоритм разделим на ветки. Ветка играет роль самостоятельного программного блока.
У каждой ветки есть имя, которое является точкой входа в нее. Оно должно быть уникальным и осмысленным. Все имена веток размещаются в верхней строке, которую так и назовем строка имен.

Ветка заканчивается одним или несколькими переходами к другим веткам. Переходы от одной ветки к другой размещаются в самой нижней строке. Назовем ее строка переходов.
Также принимаем правило, что изнутри одной ветки нельзя переходить внутрь другой.
Общая схема алгоритма
image

Для ветвлений и циклов внутри ветки также применяются метки. То есть меток в программе очень много. Каждый раз придумывать новое уникальное имя это рутина. Здесь на помощь приходит базовая функциональность Excel. Каждая ячейка уже имеет уникальное имя, состоящее из имени столбца и номера строки.

На VBA создадим функцию label с аргументом типа Range. Метка в ассемблере AVR должна иметь на конце двоеточие. Т.е. наша функция labelполучает на вход ссылку на ячейку, в которой размещается сама и вычисляет ее адрес в формате A1. Затем добавляет к ней текст _M:. Фрагмент _M не случаен, мы будем использовать это дальше в форматировании ячеек.

Визуализация переходов


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

Если получается уместить весь код ветки на один экран, то можно не отрисовывать маршруты. Все команды перехода и метки будут перед глазами.

Для блок-схем существуют стандарты на обозначения отдельных элементов. Например, прямоугольник это вычисление; ромб условие и т.д. При работе с табличным процессором нам доступны управление цветом, начертанием и границами.

В Excel есть удобная функция условного форматирования. Использовать ее можно по-разному, но мы введем всего три простых правила:

  1. Непустая ячейка в строке имен и в строке переходов красится в желтый цвет;
  2. Непустая ячейка в теле ветки красится в оранжевый;
  3. Ячейки с локальными метками и командами перехода красятся в синий. Это непустая ячейка, которая содержит символы _М, о которых упоминалось выше.


На гифке ниже представлено, как работа с метками и переходами выглядит после всех настроек.

Работа с метками и командами перехода
image



Порядок формирования конечного листинга


Сборщик листинга обходит итоговую программу сверху вниз и справа налево. По одной ячейке за раз.
Как формируется конечный листинг
image

Алгоритм сборки пропускает пустые ячейки и ячейки, которые содержат символ ;, т.е. комментарии.

Отладка алгоритма и окончательная сборка проекта происходит в студии. Обратная связь кода в студии с ячейками Excel отсутствует. Поэтому для ловли ошибок в итоговый листинг напротив каждой команды указываем ссылку на ячейку. По этой ссылке мы находим проблемную команду на схеме в Excel.

Базовое алгоритмическое решение это ветвление вида:

If <условие> then     <действие>Else    <действие>End if

Для него в ассемблере AVR есть команды условного перехода (breq, brne, sbic и прочие). Эти команды указывают программе, куда перейти при выполнении условия.

Теперь представим, как должна выглядеть программа такого ветвления с учетом озвученных выше принципов:

Простое ветвление
image


У команды brne и ее аналогов есть ограничение. Дальность перехода составляет 64 слова в каждую сторону. Поэтому длинный макрос, вставленный в ответвление Нет может вызвать out of range.

Для обхода этого ограничения обработку Нет производить в отдельной ветке, которая будет правее метки перехода от brne. Либо использовать подпрограмму, в которой вызывается макрос.

Select Case


Если выбирать приходится из большого количества вариантов, в ЯВУ используется Select Case:

Select Case <переменная>    Case <условие1>    Case <условие2>    Case <условие3>    Case elseEnd select


На ассемблере под AVR доступны индексные переходы. Покажем вариант реализации.
Select Case
image


Здесь видно, что нарушен запрет прыжка из тела ветки к другой ветке. Это сделано преднамеренно, иначе программа займет слишком много места по горизонтали.

По этой же причине вектор прерываний следует выполнить в один столбик.

Цикл с постусловием


Цикл, когда необходимо минимум одно повторение, в нотации ЯВУ выглядит следующим образом:
Do    <действие>Loop while <условие>

Пример, как это выглядит в Excel и итоговый код в студии:
Цикл с постусловием
image


Цикл с предусловием


Пример на ЯВУ:
Do while <условие><Действие>Loop

Пример, как это выглядит в Excel и итоговый код в студии:
Цикл с предусловием
image


Цикл со вложенным циклом


Обработка массивов или работа с числами предполагает использование вложенных циклов. Общий вид на ЯВУ:

Do    Do        <действие>    Loop while <условие 2-го уровня>    <действие>Loop while <условие 1-го уровня>


Реализовать вложенный цикл можно двумя путями: либо внутри одной ветки, либо с помощью нескольких веток. Покажем оба варианта.
Вложенные циклы внутри одной ветки
image


Вложенные циклы с реализацией через ветки
image


Цикл с выходом по условию


Есть ситуации, когда из цикла необходимо выйти до его окончания. Например, логический ноль на 0-вой ножке порта D может говорить о какой-то неисправности. Программа должна покинуть основной цикл и запустить тревогу.

В ЯВУ используется команда break, а общий алгоритм имеет следующий вид:

Do     <действие>    If <условие прерывания> then breakLoop while <условие>


Снова покажем, как будет выглядеть алгоритм без веток и с ветками.
Прерывание цикла внутри одной ветки
image


Прерывание цикла, реализованного через ветки
image


Вызов подпрограмм


Подпрограммы вызываются командами call, icall, rcall. Тут есть важная особенность. Подпрограмму необходимо вызывать строго из тела ветки. Если же вызов разместить в строке переходов, возврат из подпрограммы почти гарантированно произойдет в неверное место.

Обязательные требования к подпрограмме:
  • сама подпрограмма обязательно начинается с имени-метки;
  • последняя команда подпрограммы обязательно должна быть ret и размещаться в строке переходов.

Внутренняя структура подпрограммы может быть произвольной применяем все те же автоматические метки label для реализации логики.

Пример с бегущим огоньком


Приведем код тестовой программы с бегущим огоньком. Пример включает циклы, ветвления и вызов подпрограммы.

Конечный результат выглядит так:
Схема бегущего огонька
image


Листинг всей программы в Excel со скрипом уместился на один экран:
Бегущий огонек. Код в Excel
image


Бегущий огонек. Итоговый листинг
image


Итоги


Что не понравилось


  1. Изложенный метод вряд ли подойдет для работы над большим проектом с участием целой команды. Контроль версий, диффы и т.д. все равно придется решать на стороне.
  2. В режиме отладки очень неудобно без двух мониторов. Сама отладка ведется в студии, а блок-схема в Excel. Это разные окна, требуются переключения.
  3. Программа быстро пухнет в горизонтальном направлении. При этом в Excel прокрутка в сторону грубая и неудобная.
  4. Болезнь блок-схема как таковых много пустого места. Для комфортной работы на одном экране разрешением 1280х1024 должно быть около 5 столбиков и 45 строк с информативными ячейками. Не более 20% от этого количества будет заполнено самими командами (это 45 команд). Остальное пространство жертва визуальному представлению и комментариям. Примерно столько же команд помещается в окно студии на один экран.


Что понравилось


  1. Проектирование алгоритма сверху вниз складывается очень естественно. Объявляем только имена и переходы.
  2. Код в Excel можно открыть в несколько окон. В Atmel Studio доступно только разделение экрана по горизонтали.
  3. Сокращение рутинных действий при работе с именованными сущностями.
  4. Контроль меток не требует внимания. Переместили или переименовали метку все переходы автоматически получат изменения. Удаление метки приведет к ошибке, которую легко найти визуально.
  5. Excel как транслятор очень просто расширять новыми функциями. Что-то доступно через формулы, что-то через макросы VBA.


Точки роста


  1. Оптимизация работы с метками и переходами. Если в итоговом листинге команда Переход на метку расположена прямо над самой меткой, команду с переходом можно убрать. Освободим 4-5 тактов. Если конструкция вида Переход на метку А Метка А Переход на метку Б Метка Б не содержит внутри никаких команд, то Переход на метку Б убираем, а Переход на метку А меняем на Переход на метку Б. Также сэкономим 4-5 тактов.
  2. Дополнительные проверки на ошибки. Макросом легко организовать проверку, нет ли висящих блоков, которые не кончаются переходами. Также макросом можем найти и удалить неиспользованные метки, которые при отладке отвлекают внимание.
  3. Можно ввести собственные правила раскрашивания ячеек.
  4. Если бы табличный процессор был встроен в студию как редактор кода, то производить отладку сразу на такой блок-схеме было бы очень удобно.
Подробнее..

Как подключить содержимое любых файлов для использования в коде C C

08.03.2021 02:20:33 | Автор: admin

Привет, Хабровчане!

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

О чем речь?

Задача состояла в подключении файлов: HTML, JS, CSS; без специальной подготовки. Так же неудобно подключать бинарные файлы (например картинки) конвертируя их в HEX. Так как не хотелось конвертировать в HEX или разделять на строки, искал способ подключения файла в адресное пространство программы.

Как обычно это выглядит

Пример, c разделением строк:

const char text[] =    "<html>"            "\r\n"    "<body>Text</body>" "\r\n"    "</html>";

Пример, с HEX (больше подходит для бинарных данных):

const char text[] =    {        0x3C, 0x68, 0x74, 0x6D, 0x6C, 0x3E, 0x0A, 0x3C,        0x62, 0x6F, 0x64, 0x79, 0x3E, 0x54, 0x65, 0x78,        0x74, 0x3C, 0x2F, 0x62, 0x6F, 0x64, 0x79, 0x3E,        0x0A, 0x3C, 0x2F, 0x68, 0x74, 0x6D, 0x6C, 0x3E,        0    };

Видел даже такое:

#define TEXT "<html>\r\n<body>Text</body>\r\n</html>"const char text[] = TEXT;

Все #define располагались в отдельном .h файле и подготавливались скриптом на Python. С аннотацией, что некоторые символы должны бить экранированы \ вручную в исходном файле. Честно немного волосы дыбом встали от такого мазохизма.

А хотелось, чтобы файлы можно было спокойно редактировать, просматривать и при компиляции всё само подключалось и было доступно, например так:

extern const char text[];

Оказалось всё просто, несколько строчек в Assembler.

Подключаем файл в Arduino IDE

Добавляем новую вкладку или создаём файл в папке проекта с названием text.S, там же размещаем файл text.htm.

Содержимое файла text.htm:

<html><body>Text</body></html>

Содержимое файла text.S:

.global text.section .rodata.myfilestext:    .incbin "text.htm"    .byte 0

Не забываем нулевой символ \0 в конце, он здесь в строке сам не добавится.

Сам скетч:

extern const char text[] PROGMEM;void setup(){    Serial.begin(115200);    Serial.println(text);}void loop() { }

Компилируем, загружаем и смотрим вывод:

Отлично, когда-то бы я от радости прыгал до потолка, от того что всё получилось.

Код работает в AVR8, но например в ESP8266 получим аппаратный сбой. Всё потому, что чтение из Flash доступно по 32 бита и по адресам кратным 32 бит. Чтобы было всё хорошо, каждому файлу требуется делать отступ для кратности, код будет выглядеть так:

.global text.section .rodata.myfiles.align 4text:    .incbin "text.htm"    .byte 0

Загрузить можно в секцию кода: .irom.text, если не хватает места в .rodata.

Для STM32 так же рекомендуется выравнивать по 32 бита, но не обязательно.

А как записать размер данных во время компиляции? Например, для бинарных данных не получится остановится по нулевому символу. Так же просто:

.global text, text_size.section .rodata.myfilestext:    .incbin "text.htm"    text_end:    .byte 0text_size:.word (text_end - text)

Объявление:

extern const char text[] PROGMEM;extern const uint16_t text_size PROGMEM;

Осталось написать макрос, для удобства подключения файлов:

.macro addFile name file    .global \name, \name\()_size//  .align 4    \name:        .incbin "\file"        \name\()_end:        .byte 0//  .align 4    \name\()_size:        .word (\name\()_end - \name).endm.section .rodata.myfilesaddFile text1   1.txtaddFile text2   2.txtaddFile text3   3.txt

И макрос для объявления:

#define ADD_FILE(name) \    extern const char name[] PROGMEM; \    extern const uint16_t name##_size PROGMEM;ADD_FILE(text1);ADD_FILE(text2);ADD_FILE(text3);void setup(){    Serial.begin(115200);    Serial.println(text1);    Serial.println(text1_size);    Serial.println(text2);    Serial.println(text2_size);    Serial.println(text3);    Serial.println(text3_size);}void loop() { }

Вывод:

Таким образом можно подключить любой файл и представить его любым типом, структурой или массивом.

Подключаем любой файл и не только, в GNU toolchain

Принцип тот же самый, ни чем не отличается для Arduino. В принципе в Arduino используется тот же toolchain от Atmel.

Только здесь у нас в руках Makefile и мы можем до компиляции и сборки запустить какой-нибудь скрипт.

Для примера возьму код из готового моего проекта на STM32, где автоматически при компиляции увеличивается версия сборки. Так же включаются в проект WEB-интерфейс для последующего использования в LWIP / HTTPD.

Скрипт version.sh:

#!/bin/bash# Version generator# running script from pre-buildMAJOR=1MINOR=0cd "$(dirname $0)" &>/dev/nullFILE_VERSION="version.txt"FILE_ASM="version.S"BUILD=$(head -n1 "$FILE_VERSION" 2>/dev/null)if [ -z "$BUILD" ]; thenBUILD=0elseBUILD=$(expr $BUILD + 1)fiecho -n "$BUILD" >"$FILE_VERSION"cat <<EOF >"$FILE_ASM"/*** no editing, automatically generated from version.sh*/.section .rodata.global __version_major.global __version_minor.global __version_build__version_major: .word $MAJOR__version_minor: .word $MINOR__version_build: .word $BUILD.endEOFcd - &>/dev/nullexit 0

Создаётся файл version.S в который из version.txt загружается номер версии предыдущей сборки.

В Makefile добавляется цель pre-build:

######################################## pre-build script#######################################pre-build:bash version.sh

В цель all надо дописать pre-build:

all: pre-build $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin

Объявление и макросы для printf у меня в macro.h:

extern const uint16_t __version_major;extern const uint16_t __version_minor;extern const uint16_t __version_build;#define FMT_VER             "%u.%u.%u"#define FMT_VER_VAL         __version_major, __version_minor, __version_build

В HTTPD из LWIP немного был удивлён когда увидел, что содержимое файлов надо хранить вместе с заголовками HTTP. Чтобы не менять архитектуру, загрузку делал как это организовано в примере fsdata.c. Использовал fsdata_custom.c, для этого установлен флаг HTTPD_USE_CUSTOM_FSDATA.

Код в fsdata_custom.c:

#include "lwip/apps/fs.h"#include "lwip/def.h"#include "fsdata.h"#include "macro.h"extern const struct fsdata_file __fs_root;#define FS_ROOT &__fs_root

Сборка файлов fsdata_make.S:

.macro addData name file mime\name\():.string "/\file\()"\name\()_data:.incbin "mime/\mime\().txt".incbin "\file\()"\name\()_end:.endm.macro addFile name next\name\()_file:.word \next\().word \name\().word \name\()_data.word \name\()_end - \name\()_data.word 1.endm.section .rodata.fsdata.global __fs_root/* Load files */addData __index_htm         index.htm           htmladdData __styles_css        styles.css          cssaddData __lib_js            lib.js              jsaddData __ui_js             ui.js               jsaddData __404_htm           404.htm             404addData __favicon_ico       img/favicon.ico     icoaddData __logo_png          img/logo.png        png/* FSDATA Table */addFile __logo_png          0addFile __favicon_ico       __logo_png_fileaddFile __404_htm           __favicon_ico_fileaddFile __ui_js             __404_htm_fileaddFile __lib_js            __ui_js_fileaddFile __styles_css        __lib_js_file__fs_root:addFile __index_htm         __styles_css_file.end

В начале каждого файла загружается заголовок, пару примеров из папки mime.

Файл html.txt:

HTTP/1.1 200 OKContent-Type: text/html; charset=UTF-8Connection: close

Файл 404.txt:

HTTP/1.1 404 Not foundContent-Type: text/plain; charset=UTF-8Connection: close

Нужно обратить внимание на пустую строку, чего требует спецификация HTTP для обозначения конца заголовка. Каждая строка должна заканчиваться символом CRLF (\r\n).

P.S. Код проекта из ветхого сундука, так что в реализации мог забыть чего ни будь уточнить.

В завершении

Долго искал, что изложить в статье полезного. Надеюсь мой опыт пригодится новичку и гуру.

Спасибо за внимание, удачных разработок!

Подробнее..

Power-line communication. Часть 2 Основные блоки устройства

26.01.2021 02:10:54 | Автор: admin

Часть 1 Основы передачи данных по линиям электропередач

В первой части статьи мы кратко ознакомились с основными понятиями при передаче данных по линиям электропередач. Узнали, что такое полезный сигнал, как с его помощью можно кодировать передаваемую информацию. Но ни слова не было о том, как это устройство должно быть выполнено физически, как оно будет генерировать сигнал, и каким образом его вообще подключить к сети 220 В.

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

- Введение
- Мозги устройства микроконтроллер
- Основные требования к микроконтроллеру
- Выбор подходящего микроконтроллера
- Особенности питания устройства

Дисклеймер: статья не является руководством по созданию устройства и не является справочником по электронным компонентам. Это просто результаты моих экспериментов и некоторый накопленный практикой опыт, который, надеюсь, будет полезен тем, кто интересуется темой. Если есть какие-то замечания, интересные ссылки и книги, обязательно оставляй это в комментах.

Введение

Для начала кратко вспомним из части 1, как происходит передача данных. На изображении одна из фаз ЛЭП. Красное устройство передает, синие слушают. Биты данных один за одним передаются в виде синусоидальных сигналов различной частоты (FSK модуляция).

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

Для примера: если передается символ 0, то генерируется полезный сигнал в виде синусоиды 74 кГц. А если передается 1, то генерируется синусоида с частотой, например, 80 кГц. Номиналы частот не особо важны, просто выбираются любые из разрешенных диапазонов. Главное, чтобы приемник смог их различить.

В первой части статьи упоминалось про третий символ S, который означал начало кадра. Он также кодировался своей определенной частотой. Когда устройство получало символ S, входной буфер очищался. Для простоты в этой статье будут упоминаться только 0 и 1.

Передающие и принимающие устройства синхронизируются между собой с помощью отдельного блока устройства zero cross детектора.

Представим передающее устройство, в котором есть подготовленный кадр данных некий массив нулей и единиц, и этот кадр нужно передать по PLC каналу связи (ЛЭП). Передача/прием кадра происходит по одному биту за один синхросигнал из ZC детектора.

Физически это значит, что за один синхросигнал из ZC детектора генерируется один полезный сигнал определенной частоты. В нашем случае это синусоиды 74 кГц или 80 кГц.

Таким образом, бит за битом, передается кадр данных в PLC канал. Совместно с микроконтроллером генерацией сигналов будет заниматься отдельная часть схемы. Назовём её Выходная цепь.

Задача принимающих устройств каждый раз, по сигналу ZC детектора, оцифровывать полезный сигнал из PLC канала и узнавать, какой символ там был закодирован.

Но сначала сырой сигнал нужно грубо очистить от шума, подогнать уровень очищенного сигнала под пригодный для оцифровки и уже затем передать на АЦП микроконтроллера для дальнейшего цифрового анализа. Этой подготовкой полезного сигнала для АЦП будет заниматься Входная цепь.

Чтобы не раздувать эту часть статьи, расскажу только про задачи, решаемые микроконтроллером и некоторые особенности питания устройства.

В следующей части рассмотрим как устройство можно согласовать с сетью 220 В, работу ZC детектора, а также про входную и выходную цепи.

Мозги устройства микроконтроллер

Центральная часть устройства микроконтроллер, который будет контролировать всю схему: обрабатывать входной сигнал, генерировать полезный сигнал, управлять усилителями, хранить данные и т.д.

Микроконтроллер это такой мини-компьютер, который в одном корпусе содержит процессор (ЦПУ), память (ПЗУ и ОЗУ), ввод-вывод и периферийные устройства. По сути, внутри уже все есть для работы: подаем питание и поехали. Дальше все зависит уже от программы прошивки, которую мы в него записали.

Рисунок с сайта digikey.comРисунок с сайта digikey.com

Сейчас выпускают микроконтроллеры с большим количеством различной встроенной периферии. Это очень удобно, так как меньше необходимости во внешних компонентах, что экономит место на печатной плате (и, конечно же, ваши денежки). Внутри может иметь ЦАП и АЦП, часы с календарем. Даже встроенный USB уже не удивляет.

На рынке огромное разнообразие микроконтроллеров с разной вычислительной мощностью и периферией. Обычно они группируются в серии и подходят под разные классы задач. Например, чтобы помигать светодиодом в миниатюрном устройстве, нам не нужен мощный камень, на котором можно запустить Linux, подойдет ATtiny. Но для нашего устройства его уже не хватит, так как нужны ЦАП, АЦП и быстрые вычисления в реальном времени.

Поэтому к выбору камня нужно подойти немного поразмыслив. Подумаем, что же требуется от микроконтроллера в нашем случае.

Основные требования к микроконтроллеру

Производительность

Основной нагрузкой на ЦПУ будет обработка оцифрованного входного сигнала с помощью ДПФ для выяснения того, какой символ был закодирован в сигнале: 0 или 1. Далее этот символ будет отправляться в протокол на уровень выше. Больше всего вычислений будет происходить именно при подсчете гармоник в ДПФ.

Циклично, с интервалом 10 миллисекунд, АЦП будет оцифровывать входящий сигнал и сохранять его в виде массива чисел. Затем этот массив несколько раз прогоняется через ДПФ для выяснения амплитуд гармоник каждой из интересующих нас частот в полезном сигнале.

Результат визуально можно представить в виде эквалайзера, на котором нарисованы полоски определенных частот разной высоты (амплитуды). Для подсчета высоты каждой отдельной полоски нужно сигнал прогонять через ДПФ.

После подсчета некоторого количества гармоник, делаются выводы о том, какой символ закодирован.

В самом простом случае можно просто сравнить амплитуды гармоник 74 и 80 кГц между собой. Если в сигнале преобладает гармоника с частотой 74 кГц, записываем в входной буфер бит 0.

Если в сигнале преобладает гармоника с частотой 80 кГц, записываем в входной буфер 1.

В таком случае, любой шум что-то означает: 0 либо 1, даже если ничего не передавалось. Отделением зерен от плевел будет заниматься отдельная подпрограмма уровнем выше которая будет проверять целостность кадра и прочие прелести.

Задача же этого уровня просто, как конвейер, подавать 0 и 1 наверх, а дальше из них будут складываться правильные целостные кадры данных. Или не будут.

Также можно заморочиться и дополнительно рассчитывать амплитуды смежных гармоник и узнавать уровень шума относительно полезного сигнала. Можно программно фильтровать сигнал и тд. Вариантов много.

Суть в том, что считать, возможно, придется много. Успевать считать нужно гарантированно, так как это реалтайм-конвейер.

Если разложить всю нагрузку на которую ЦПУ тратит время друг за другом, то получим примерно это:

  • оцифровка сигнала

  • подсчет амплитуд гармоник через ДПФ и анализ результата

  • прочая нагрузка (обработка прерываний из интерфейсов USB или CAN, обработчики таймеров, моргания светодиодами, работа с памятью, какие-то вычисления по протоколу и т.д.)

Это должно циклично выполняться каждые 10 миллисекунд снова и снова. ЦПУ никогда не должен быть загружен на 100%, иначе есть риск не успеть посчитать что-то важное. Поэтому всегда нужно оставлять запас по производительности.

Энергоэффективность

Обратная сторона быстрых вычислений большее потребление энергии. Чем быстрее контроллер считает, тем больше он потребляет энергии. Поэтому нам не нужен слишком мощный процессор.

Подобрать нужную производительность ЦПУ можно опытным путём: берём микроконтроллер с запасом вычислительной мощности и памяти, пишем код, запускаем и смотрим за какое время он справляется с конкретными задачами. Выбираем из линейки контроллеров подходящий, оставляя небольшой запас мощности и памяти (для возможных обновлений и улучшений).

Должен быть достаточно быстрый АЦП

Нам нужно оцифровывать входной аналоговый сигнал и желательно, чтобы был встроенный АЦП. Точность тут не так важна, как скорость. Так как измеряемый сигнал имеет частоту до сотни килогерц. Для корректных вычислений гармоник есть условие.

Частота дискретизации должна быть минимум в два раза больше частоты измеряемого сигнала [Теорема Котельникова].

Это значит, что для распознавания сигнала нужно сделать от двух точек измерения на период. А по-хорошему 4-5. Посмотрим на примере.

Представим, что мы измеряем сигнал, в котором есть нужная нам гармоника частотой 80 кГц. У сигнала с частотой 80 кГц период 1/80000 = 12,5 микросекунд. Чтобы оцифровать 5 точек на период нужно успевать делать измерение раз в 2.5 микросекунды для адекватного распознавания сигнала.

С пятью точками измерений на период уже выглядит неплохо. ДПФ с этим отлично справится.

Но для чего брать с запасом? Что если измерять по минимуму, только две точки на период? Вот такой сигнал мы оцифруем при удачном попадании.

А так будет выглядеть оцифрованный сигнал, если попасть в момент, когда сигнал в нуле.

Не похоже на синусоиду.

Если интересно посмотреть, что будет, если проводить измерения частотой меньше двух точек за период, то поищите в гугле картинки Эффект алиасинга.

Должен быть достаточно быстрый ЦАП

Для полезного сигнала нужно сгенерировать синусоиду большой частоты. Чем больше точек на период синусоиды успеет генерировать ЦАП, тем плавнее будет сигнал на выходе (меньше лесенка, которую затем сгладит конденсатор).

Представим на примере синусоиды с частотой 80 кГц, период 12.5 микросекунд. Возьмем для начала 4 точки на период. Генерация каждые 3.125 микросекунды.

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

Увеличим количество точек вдвое. Генерация каждые 1.56 микросекунды.

Нужна достаточная скорость ЦАП для того, чтобы сигнал был хотя бы похож на синус. В нашем случае, с сигналом частотой до 80 кГц, будет достаточно чтобы ЦАП успевал менять уровень сигнала раз в 1.5 микросекунды. Если успеет быстрее, то еще лучше.

С выхода ЦАП этот угловатый сигнал проходит через пассивный фильтр нижних частот и в сглаженном виде идет на усилитель выходной цепи.

Если нет АЦП

Помню, в самом начале я проводил эксперименты на 8-битных AVR от Atmel серии ATmega8, и у них в распоряжении не было АЦП. Но на них было очень удобно начинать знакомство с миром микроконтроллеров. Низкий порог вхождения и никаких танцев с бубнами при запуске.

Ну так вот, входной сигнал я решил оцифровывать простой ножкой в режиме входа. Если входное напряжение выше 2.5В, то у ножки было логическое состояние 1, если ниже 2.5В, то 0. В равные промежутки времени просто считывалось текущее состояние ножки и эти значения записывались в массив.

И этот оцифрованный сигнал прогонялся через ДПФ и вычислялось наличие нужных гармоник. Ни о какой точности или чувствительности речи и не шло, но это работало.

Если нет ЦАП

Аналогичная ситуация на ATmega8 была с ЦАП. Его там нет, и мне очень не хотелось заморачиваться с внешним ЦАП.

Оказалось, что можно пожертвовать логическими выходами микроконтроллера и подключить к ним резисторную матрицу R-2R. Таким образом из горстки резисторов собрать свой ЦАП с нужной разрядностью.

Картинка с сайта easyelectronics.ruКартинка с сайта easyelectronics.ru

Подавая 0 и 1 на выходы микроконтроллера, можно получать нужный уровень напряжения на выходе OUT. Чем больше выходов будет использовано, тем выше разрядность ЦАП. По схеме R-2R оставил ссылку в конце.

Выбор подходящего микроконтроллера

После экспериментов на ATmega8 мне захотелось улучшить то, что есть. Выбирая из разных вариантов, я положил глаз на STM32. А конкретно на STM32F103 это 32-битные микроконтроллеры на ядре ARM Cortex-M3 (до 72 MHz).

Эксперименты проводил на отладочной плате, которая, наверное, есть в любом магазине электроники. На отладочной плате сразу располагался программатор, с помощью которого прошивается на МК.

Немного пострадав с его запуском, сразу же побежал проверять его по своему чек листу.

Производительность?

Схема тактирования позволяет работать ЦПУ на частоте 72 MHz, что после 8-битных на 20 MHz было с запасом. Хватало для более точных расчетов по алгоритму ДПФ.

Энергоэффективность?

При почти максимальной нагрузке потреблял около 40-50 мА. Дешевый стабилизатор напряжения в схеме питания на 100 мА с этим справлялся. Даже с учетом остальной маложрущей периферии этого было достаточно.

Достаточно быстрый АЦП?

Разобрался, как разогнать до максимальной скорости АЦП при частоте ЦПУ 72 MHz. Так как ранее было сказано, что полезный сигнал будет частотой в районе 80 кГц, то будем считать исходя из этого.

В доках для STM32 нашел, как вычислять минимальное время преобразования: нужно к настраиваемому времени семплирования (минимум 1.5 цикла) прибавить 12.5 машинных циклов. Получается 14 машинных циклов на одну точку измерения.

При определенной настройке схемы тактирования на модуль АЦП приходится 14 MHz. Если перевести в секунды, то 14 циклов при частоте тактирования 14 MHz это одно измерение в 1 микросекунду.

Идеально! Даже если полезный сигнал будет частотой 100 кГц, я смогу измерить 10 точек за один период сигнала. С минимальной точностью, но быстро.

Примерно так будет выглядеть оцифровка синусоиды 80 кГц.

Достаточно быстрый ЦАП?

По той же логике нам нужно сгенерировать синусоиду частотой около 80 кГц. И если мы хотим, чтобы синусоида была похожа на синусоиду, а не на странную угловатую фигуру, нужно генерировать одну точку хотя бы раз в 1.5 микросекунды, как мы выяснили ранее.

Почитав документацию, я понял, что в ЦАП STM32F103 встроенный ОУ имеет ограничение в 1 MSPS. Получилось настроить генерацию каждой точки сигнала раз в 1 микросекунду.

Примерно так при этом будет выглядеть синусоида с частотой 80 кГц на выходе из ЦАП.

Периферия

Что еще мне понравилось в STM32F103 это наличие встроенного USB. Там есть режим эмуляции COM порта. Мне показалось это очень удобным, особенно после внешних преобразователей USB-UART.

Можно подключать устройство к ПК обычным шнурком от телефона и через терминал посылать на устройство какие-нибудь отладочные команды.

Для экспериментов подключал два PLC устройства к двум компам, и они посылали друг другу ASCII символы, вводимые с клавиатуры. Получилось что-то вроде чата через розетку 220 В.

Особенности питания устройства

Сразу отмечу, что я не спец в проектировании блоков питания, для подробностей есть специализированная литература. Но считаю важным отметить некоторые моменты, которые влияют на стабильность системы, и с которыми у меня были проблемы.

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

Схема питания устройства зависит от потребителей. В нашем случае основные жрущие потребители это микроконтроллер и выходная цепь, так как в ней усилитель для отправки сигнала в ЛЭП.

Остальные потребители вроде усилителей входного сигнала во входной цепи, EEPROM памяти или какие-то UART конвертеры потребляют немного.

Стабильное питание микроконтроллера

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

И тут у нас проблемка: как назло, под боком у микроконтроллера находится выходная цепь с усилителем полезного сигнала. Этот блок устройства во время генерации сигнала импульсами потребляет мощность, что может прилично потрясти всю систему питания, обнулив при этом микроконтроллер.

Примерная картина потребления мощностиПримерная картина потребления мощности

При передаче кадра это происходит каждые 10 миллисекунд длиной в 1 миллисекунду.

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

Для тяжелых случаев есть пара советов, которые помогут уменьшить влияние скачков напряжения.

Совет 1 - Разделить землю на аналоговую и цифровую

Первый важный момент это обеспечение минимального влияния аналоговой части схемы на цифровую.

Для этого нужно разделить дорожки GND в самом начале схемы питания возле минуса блока питания. Ни в коем случае нельзя их пересекать или как-то замыкать в других частях схемы.

Для питания условно цифровых компонентов схемы (микроконтроллер, EEPROM память и т.д.) от самого блока питания должна идти отдельная линия, можно назвать её DGND.

Для питания аналоговой схемы генерации полезного сигнала от блока питания, соответственно, должна идти отдельная линия AGND. Подробнее можно почитать в статьях или литературе по заземлению.

Совет 2 - Не забыть про керамику

Конденсаторы нужно ставить перед каждой ножкой питания микроконтроллера и как можно ближе к ним. Обязательно выполнить минимум обвеса, который указан в Datasheet на микроконтроллер.

Картинка с сайта allexpress.comКартинка с сайта allexpress.com

Также желательно добавить в цепи питания микроконтроллера дополнительную емкость, в виде электролитического или танталового конденсатора, чтобы он мог даже выдерживать кратковременное отключение питания.

С танталовыми осторожнее, они красиво взрываются :).

Еще надо не забыть защитить ножку Reset микроконтроллера, как указано в Datasheet. В противном случае микроконтроллер может внезапно перезагружаться. Если нет светодиодной индикации запуска программы микроконтроллера, то можно этого даже не заметить.

Совет 3 - Экранировать цифровые компоненты

Может получиться так, что недалеко от микроконтроллера расположен высокочастотный трансформатор, который во время передачи сигнала генерирует электромагнитные помехи.

Мне помогло расположение микроконтроллера на другой от ВЧ трансформатора стороне печатной платы и наличие земляного полигона под корпусом микроконтроллера.

Картинка с сайта caxapa.ru "Помехоустойчивые устройства, Алексей Кузнецов"Картинка с сайта caxapa.ru "Помехоустойчивые устройства, Алексей Кузнецов"

Подробнее можно почитать в статье по ссылке в конце.

Заключение

В этой части мы в общих чертах разобрали чем занимается микроконтроллер. Узнали некоторые особенности питания устройства и возможные проблемы.

Статья вышла довольно объемной. Я постарался максимально коротко передать основные моменты. Может сложиться ощущение незаконченности и это нормально. Для углубленного изучения оставлю ссылки внизу.

В следующей части подробнее разберём оставшиеся блоки устройства: входная и выходная цепи, zc детектор и согласование устройства в сетью 220 В. После этого должна сложиться целостная картинка. В заключительной части пробежимся по программной части: обработке оцифрованного сигнала, генерация синуса и т.д.

У кого был/есть какой-либо опыт в PLC обязательно делитесь этим с остальными в комментариях :)

Полезные ссылки

https://nag.ru/articles/article/24485/strasti-po-plc.html - интересная статья по истории PLC
https://www.electronshik.ru/catalog/interfeys-modemy-plc - заводские PLC микросхемы с datasheet (там много схем и характеристик)
https://ru.wikipedia.org/wiki/Частотная_манипуляция - FSK модуляция
http://www.atmega8.ru/ - про ATmega8

STM32
https://www.st.com/en/microcontrollers-microprocessors/stm32f103.html - STM32F103
https://themagicsmoke.ru/courses/stm32/led.html - Помигать светодиодом на stm32
https://blog.avislab.com/stm32-clock_ru - схема тактирования stm32
http://personeltest.ru/aways/habr.com/ru/post/312810/ - подробнее про ЦАП в stm32
https://blog.avislab.com/stm32-adc_ru/ - АЦП в stm32
https://blog.avislab.com/stm32-usb_ru/ - USB в stm32

Аналоговая часть
http://easyelectronics.ru/parallelnyj-cifro-analogovyj-preobrazovatel-po-sxeme-r-2r.html - преобразователь по схеме R-2R
http://caxapa.ru/lib/emc_immunity.html - "Помехоустойчивые устройства", Алексей Кузнецов
https://www.ruselectronic.com/passive-filters - пассивные фильтры

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru