AVR. Учебный Курс. Виртуальные порты

Глядя на то, как раскиданы порой ножки портов по корпусу контроллеров, у меня возникают большое подозрение, что разводчик кристалла дунул что то сильно забористое. Когда вперемешку идут выводы разных портов, да еще почти в рандомном порядке… Когда к этим портам вешаем что-либо разнобойное, то пофигу. А если надо подцепиться на прямую линейку шины данных, вроде того же LCD дисплея? Вот тут и начинается круголяние дорожек по плате. А если плата фаршированная донельзя? Тут приходится вводить перемычки, дополнительные слои и извращаться как только можно. Короче, та еще проблема.

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

Итак. Подключаем куда-нибудь в область процедур блок кода виртуального порта.

1
	.include	"VPort.asm"

Теперь надо сконфигурировать виртуальный порт. Все делается в define записи в файле VPort.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
;====================================
; Virtual Port ==== Virtual Port
;====================================
.def	VP_REG	= R16
 
.equ	VPort0 	= PORTD
.equ	VPort1 	= PORTD
.equ	VPort2 	= PORTD
.equ	VPort3 	= PORTD
.equ	VPort4 	= PORTD
.equ	VPort5 	= PORTC
.equ	VPort6 	= PORTC
.equ	VPort7 	= PORTC
.equ	VDDR0	= DDRD
.equ	VDDR1	= DDRD
.equ	VDDR2	= DDRD
.equ	VDDR3	= DDRD
.equ	VDDR4	= DDRD
.equ	VDDR5	= DDRC
.equ	VDDR6	= DDRC
.equ	VDDR7	= DDRC
 
.equ	VPIN0	= PIND
.equ	VPIN1	= PIND
.equ	VPIN2	= PIND
.equ	VPIN3	= PIND
.equ	VPIN4	= PIND
.equ	VPIN5	= PINC
.equ	VPIN6	= PINC
.equ	VPIN7	= PINC
 
.equ	VP0	= 3
.equ	VP1	= 4
.equ	VP2	= 5
.equ	VP3	= 6
.equ	VP4	= 7
.equ	VP5	= 0
.equ	VP6	= 1
.equ	VP7	= 2

Параметр VP_REG — указывает какой регистр мы будем юзать для обращения с портом. Данный регистр юзается только для записи и чтения из порта. Данные в нем не хранятся. Но можно сделать три регистра для DDR, PORT и PIN. Просто мне как то западло это было делать.
Также можно, по принципу процедуры VRPIN считывать по мере надобности данные и из PORT с DDR, но это потребует дофига памяти.

К парметрам VPORTx, VDDRx и VPINx привязываем конкретные регистры портов, а к параметру VP номер пина. В скопированном сюда куске кода у меня виртуальный порт состоит наполовину из порта С на половину из порта D.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
VRPort:	SBRS	VP_REG,0
	CBI	VPORT0,VP0
	SBRC	VP_REG,0
	SBI	VPORT0,VP0
 
. . . 
	SBRS	VP_REG,7
	CBI	VPORT7,VP7
	SBRC	VP_REG,7
	SBI	VPORT7,VP7
	RET
 
VRDDR:	SBRS	VP_REG,0
	CBI	VDDR0,VP0
	SBRC	VP_REG,0
	SBI	VDDR0,VP0
 
. . . 
 
	SBRS	VP_REG,7
	CBI	VDDR7,VP7
	SBRC	VP_REG,7
	SBI	VDDR7,VP7
	RET
 
VRPIN:	CLR	VP_REG
	SBIC	VPIN0,VP0
	SBR	VP_REG,1
 
. . . 
 
	SBIC	VPIN7,VP7
	SBR	VP_REG,128
	RET

Эти три процедуры выполняют запись в реальные DDR, PORT и чтение из PIN порта. Выдавая все это дело в регистр виртуального порта.
При записи используется двойная проверка — вначале проверяем есть ли бит — если есть, то выставляем. Потом проверяем на отстствие бита — если нет, то ставим. Данный метод позволяет менять состояние отдельных битов, не трогая состояние соседней и не дергая состояние, если оно не изменилось. Для PORT и DDR это критично, так как появление там не того уровня черевато выгоранием порта или непредсказуемыми последствиями для периферии. А вот с чтением PIN все куда проще. Вначале мы сбрасываем выходной регистр виртуального порта, а потом выставляем биты, в соответствии с наличии битов в реальных PIN ячейках. Команда SBR выставляет указаные биты. Фактически она аналогична команде ORI. Нафига была делать две разные команды одной длины, делающие одно и то же, за то же число тактов, я так и не догнал. ИМХО это один из AVR маразмов. Кои тут встречаются порой.
Использование
Загрузка данных в виртуальный PORT или DDR:

1
2
3
4
5
	LDI	VP_REG,0xFF
	RCALL	VRPort
 
	LDI	VP_REG,0x00
	RCALL	VRDDR

А чтение осуществляется одной командой:

1
	RCALL	VRPIN

После чего данные достаются из регистра VR_REG

При реальном использовании процедуры эти желательно кромсать и обрезать. Например, срезая неиспользованые биты порта. Если дрыгание PORT и DDR не критично к импульсам смены состояния (скажем у клавиатуры). То можно сделать запись в VRDDR и VRPORT с предварительным обнулением, а потом с заносом единиц. Как это сделано в VRPIN.

Библиотека виртуального порта Vport.asm

Вот такая вот загогулина. Пользуюсь довольно давно. Всем рекомендую — удобно.

43 thoughts on “AVR. Учебный Курс. Виртуальные порты”

  1. а если я портА использую как ацп (только адц0, например) — можно ли другие ножки на вход-выход переназначить?

    1. По идее должно быть можно. Т.к. ацп работает подобно регистру PIN снимая данные с ножки. Просто не обрабатываешь их и все.

    2. Можно, но падает точность оцифровки. Подробно о работе с АЦП читай в даташите на кристал. Там достаточно подробно расписано =)

  2. А команды SBR и ORI это случаем не разное название одной и той же машинной инструкции? А то это практикуется иногда — для удобочитаемости.

    1. Я тоже так думал. Сравнил коды — разные команды… Хотя нет. Я ошибся когда сравнивал. Да, Это одна и та же команда.

  3. Разрешите задать вопрос не в тему :) Может ли сгореть один порт на контроллере? То есть при DDRA=0xFF и PORTA=0xFF получаю невнятные ~2V. То же самое на C даёт всё как надо.

  4. Разработчики чипов делают все правильно.
    Все IO делятся на группы, которые питаются от своих power domains. Никогда не задумывался почему есть куча VDD/VSS?
    Так вот, есть такое понятие как Spurious switching activity (SSA). Есть предельное значение, зависящее от количества пинов, педов питания и т.д. Чтобы не позволить всей группе сразу изменить значения их разводят на разные домейны.

    1. А какая связь между питанием и логической организацией портов? Ведь по сути дела на этапе проектирования любой порт можно развести на его питание как угодно, а потом, при микропрограммировании УУ, обозвать так как нужно. Например по порядку. Ведь в тех же большеногих МК порты идут последовательно. А вот в каких нибудь мелких Тини у которых и питание то одно, разбросаны черти как.

    1. Это понятно. Для больших кристаллов с кучей питания и земли это естественно. Но там то мне и придраться не к чему. Глядя на ту же мегу128 или даже 8535 — все пины идут по порядку. А вот перед глазами распиновка Тини2313 — питание у ней одно (20,10) а пины портов все в перемешку. A2,D0,D1,A1,A0,D2,D3 Смысл?

  5. Опять же restriction на одновременное изменение соседних пинов. Это особенно важно для чипов с маленьким количеством питания и земли.
    И еще есть наводки и т.д.

    1. И где вы такую траву берете?… Дополнительные выволы питания и земли у контроллеров малой и средней сложности делают для питания АНАЛОГОВЫХ цепей, чтобы снизить влияние шума цифровой части на точность АЦП. С той же целью для снижения цифрового шума у контроллеров с АЦП есть возможность запускать преобразование в «спящем» режиме, приостанавливая в это время работу большей части цифровой начинки. А раскидывать пины портов по разным группам питания… Это вам не Пентиум. Просто в даташите есть ограничение: суммарный ток ВСЕХ портов в ОБЩЕМ проводе не должен превышать определенной величины (обычно порядка 150 — 200мА в зависимости от типа контроллера). Потому что, например, при допустимом токе через каждую линию порта для PICов = +_25ма, для 40выводного корпуса с его 32линиями портов и их одновременной максимальной нагрузке суммарный ток мог бы составить 0,8A, что конечно, многовато для внутренних соединений не силовой микросхемы.
      А чередование выводов разных портов чаще связано с возможностью дальнейшего развития уже существующих устройств путем использования корпуса с большим числом выводов, но чтобы при этом не менять расположение и разводку уже существующих цепей. Гляньте, например, на подключение разных микросхем к программатору.
      Например, у PICов можно в длинной панельке программировать много более коротких микросхем, просто сдвигая их определенным образом.

      1. Не он верно глаголит. Посмотри на АТМЕГА 8535 или на многие плисины. Там куча входов питания. У той же меги8535 3 входа цифрового питания, 1 вход аналогового и 4ре земли. У Меги 128 вроде бы еще больше. Слышал про случаи выгорания микрух если не соединить все земли и питаловы — внутренние какие то цепи перегреваются и дохнут.

        1. Вот, смотрю ПДФ на ATmega8535. DIP 40:
          10 -Vcc, 11 — Gnd — для цифровой части.
          30 — AVcc, 31 — Gnd — для аналоговой части.
          У TQPF, PLCC — действительно, добавлено по паре земель и питаний, так у них 4 ноги лишних, надо было чем — то занять, а кроме того, — корпус мелкий, широкие шины земли и питания не подведещь, у тонких — резко растет индуктивность, вот и распараллелили. Как видим, для DIP 40 этого не требовалось.
          ATmega128: 64 ноги, кроме аналоговой земли и питания — всего по 2 линии цифровых земель и питания. Не так уж и много.

            1. Микроконтроллер такой. В Зеленограде вроде бы его разрабатывают.
              Ангстрем делает Сам не щупал, но беглым взглядом оглядел — прикольный.

                1. А готовый купить что, — дороже? Или негде? Стоит ли изобретать велосипед для мелкосерийного применения? Это ведь жутко дорого в пересчете на изделие. Правда, если это связано с безопасностью особо важных систем, то конечно…

                  1. 1. Посчитай выгоду: 5 миллионов чипов с экономией хотя бы по пол-доллара на каждом.
                    2. Нет готовых. У нас решениия построены на hardware acceleration. Иначе очень медленно.

                    1. Все ж затраты на разработку и запуск в производство заказных микросхем довольно велики… Трудно конкурировать с массовыми изделиями, у которых это уже окупилось.
                      Аппаратные специализированные ускорители — это конечно хорошо и эффективно, да вот только само их проектирование и оптимизация топологии — задачка тоже не из простых… В общем, я вам не завидую. Хотя, бывало, лет 20-30 назад и сам «лез на танки»… А теперь слишком ленивым стал. Устал, наверное. Или стимула нет…

                    2. Как раз наоборот. Это все очень интересно.
                      А насчет «не просто» — так это сначала. А потом уже все отработано.

  6. трудно сказать без знания внутреннего строения чипа.
    Иногда еще делают виртуальные группы питания. Может это и есть тот случай.

  7. Артём , давай разбирать I2C или SPI интерфейсы на АСМе , а то у мя всё встала разработка , никак не могу разобраться , микроэлектроника привязала каждые библиотеки к микросхемам а открытого кода не дала на паскале , (ГА_Д_Ы) , щас висит у мя всё, Тера не отвечает на мои вопросы , судя по всему так оно и есть я был прав что I2C висит в паскале 8,0,0,1.

    1. Посмотри после компиляции листинг в Асме, там все расписано.
      Кроме View Assembly, глянь еще View Statistics, там тоже подробно все расписано. (на вкладке POM также есть ассемблерный листинг). Попробуй прогнать HEX код на симуляторе PIC Simulator IDE, там все прекрасно видно в процессе прогона, и регистры, и сигналы на ногах, и дизассемблер встроенный есть. На микросхемах памяти с посл. шиной обычно есть ноги для задания адреса микросхемы. Обычно, если она одна, их садят на землю, что соответствует 0 адресу.
      Гляньте еще вот там: http://rifer.ucoz.ru/publ/1-1-0-13
      может что пригодится. Можно также взять описание какой — нибудь готовой разработки с энергонезависимой памятью, и посмотреть, как там сделано.

  8. Теперь, кроме разводчиков кристалла, дунули, что то сильно забористое и разработчики. Вы будете смеяться, в новых микроконтроллерах Xmega реализованы виртуальные порты. Может быть они читали эту страничку, но так нифига и не поняли? :)
    Виртуальный порт в их понимании, совсем не то же самое что и в этой статье :) к сожалению конечно.
    Виртуальные порты (регистры) расположены в адресном пространстве I/O памяти, таким образом, для работы с виртуальными портами можно использовать специфические команды этого адресного пространства, недоступные для расширенной области I/O. С помощью регистров управления, определяют на какой именно реальный порт будет отображаться виртуальный. Вот такие нововведения. Команды, как мы знаем, занимают один такт, а вот чтобы результат оказался на ножке контроллера потребуется все же два такта, но в это время процессор уже забудет о былой команде, и будет делать свои дела. Таким образом, с помощью виртуального порта мы выполняем за один такт то, на что при прямой адресации ушло бы 3 такта. При достаточно большом количестве операций с отдельными линиями порта — выгода на лицо.

    1. Любопытно. А это же удобно! МОжно теперь биты проверять прям в кишках не доставая их в регистры.

  9. Такой вопрос. Есть устройство, там применяется регистр 1533ир37. Плата готова, комплектуха куплена, и вот мне захотелось немного изменить логику работы. Для этого моя 1533ир37 должна при подаче на вывод 1 не переводить все выходы в Z состояние, а вывести нули на всех выходах. чтоб все не переделывать, решил из тини2313 сделать 1533ир37. Но в тини нога номер 1 — нога сброса. Можно ли, и как использовать ногу сброса, как обычный порт.

    1. О, месье знает толк в извращениях!

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

      1. ну почему же извращение. Неужели проще и дешевле менять схему, делать новую плату, отлавливать новые баги….. А так цена вопроса — 1,5уе на тини и 5 мин на написание кода для профессионала.(алгоритм то — простейший, при логической 1 на ноге 11, запомнить и вывести на ноги 12-19 то что было на ногах 2-9. При логической 1 на ноге 1, вывести на ноги 12-19 — логический ноль). У тини 2313 ноги 12-19 как раз целый порт С. А вот ноги 2-9 сборка из порта А и D. Вот как мне подсказали собрать виртуальный порт (были и покрасивее варианты, но после компиляции этот оказался самым коротким)
        x=0;
        if (PIND & _BV(0)) x=_BV(7);
        if (PIND & _BV(1)) x|=_BV(6);
        if (PINA & _BV(1)) x|=_BV(5);
        if (PINA & _BV(0)) x|=_BV(4);
        if (PIND & _BV(2)) x|=_BV(3);
        if (PIND & _BV(3)) x|=_BV(2);
        if (PIND & _BV(4)) x|=_BV(1);
        if (PIND & _BV(5)) x|=_BV(0);
        PORTС = x;

        Работает!
        Теперь нужно сделать так, чтоб при нуле на выводе 11 порт С не менял своего состояния. а при лог 1 на выводе1(RESET) весь порт С был равен 0.
        К сожалению мой уровень не позволяет решить даже такую задачу(мой единственный самостоятельный проект — гирлянда 12 каналов по 4 проводам(как же я был рад когда заработало)) Тут же я не могу понять как опрашивать какой то конкретный пин. Все, так называемые «обучалки с нуля» подразумевают что такие элементарные вещи человек уже знает. А я вот не знаю. Мне нужна обучалка не с 0, а с -1 наверное ))) .
        Идем дальше. Так в какой регистр нужно чего записать чтоб вывод RESET стал обычным выводом???
        А то что тини больше не прошьешь — не беда. Я сейчас строю уличные часы-термометр(на 1000 светодиодах!). Врятли их кто то будет кромсать в ближайшие годы.
        Извините за много букв.

        1. Опрашивать какой то конкретный пин — как ты и сделал. Т.е. читаем весь порт, потом снимаем с него маску. Компилятор сам подставит туда SBIC/SBIS если будет такая возможность.

          Обращайся к нему как обычно, на тиньке он ПА2 . А потом, Когда все уже будет проверено и отлажено. После последней прошивки выстави фуз бит RSTDSBL и все — ресет будет как вывод.

    1. Никак. А зачем? При желании можно добавить, чтобы оно где то сохранялось. Памяти только потребует больше. ДДР сама не изменяется, зачем ее менять?

  10. Приветствую.)
    Сорри, но не вкурил смысла двойной проверки (
    VRPort: SBRS VP_REG,0
    CBI VPORT0,VP0
    SBRC VP_REG,0
    SBI VPORT0,VP0
    Командой SBRS VP_REG,0 мы проверяем содержимое нулевого бита регистра VP_REG на наличие «1». Если там «0», то выполняем команду CBI. Если «1», то переход на проверку
    SBRC VP_REG,0 — на наличие в VP_REG «0». Но разве первая проверка нам не показывает содержимое нулевого бита р-ра VP_REG — «0» выполняем переход на CBI, «1» — переход сразу на SBI? Может быть команда SBRC VP_REG,0 лишняя?

    1. Смысл не проверить, а точно выставить в реальном порту тот бит, что у нас в виртуальном регистре. ВОт и получается, что если там 1, то мы принудительно делаем SBI, а если 0 то CBI Можно, конечно, обойтись и одной проверкой, но тогда придется делать перепрыгивания условий и получится длинней и медленней.

  11. Не знаю кому как, мне не нравится конфигурирование таким способом. Тем более, что для каждого бита нужно вручную отдельно прописывать букву порта в PORT, DDR и PIN.
    Лучше сделать макрос:
    .MACRO SVPORT ; порт0, бит0, … порт7, бит7
    .def VPREG = @0
    .if @1 == PORTB
    .equ VPORT0 = PORTB
    .equ VDDR0 = DDRB
    .equ VPIN0 = PINB
    .elif @1 == PORTC
    .equ VPORT0 = PORTC
    .equ VDDR0 = DDRC
    .equ VPIN0 = PINC
    .elif @1 == PORTD
    .equ VPORT0 = PORTD
    .equ VDDR0 = DDRD
    .equ VPIN0 = PIND
    .endif

    …. и т.д……

    .if @15 == PORTB
    .equ VPORT7 = PORTB
    .equ VDDR7 = DDRB
    .equ VPIN7 = PINB
    .elif @15 == PORTC
    .equ VPORT7 = PORTC
    .equ VDDR7 = DDRC
    .equ VPIN7 = PINC
    .elif @15 == PORTD
    .equ VPORT7 = PORTD
    .equ VDDR7 = DDRD
    .equ VPIN7 = PIND
    .endif
    .equ VP7 = @16
    .ENDM

    Далее, порт создается одной строкой SVPORT порт0, бит0, … порт7, бит7
    Например: SVPORT PORTC,0,PORTC,1,PORTC,2,PORTD,2,PORTD,3,PORTD,4,PORTD,5,PORTD,7

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

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha