AVR. Учебный курс. Простейшая программа.

Распечатать

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

Поскольку в демоплате Pinboard используется процессор ATmega16, то рассматривать мы будем именно его. Впрочем, как я уже говорил, для других контроллеров AVR это также будет справедливо. Отличия, конечно, есть, но они не существенные.

Запускаем AVR Studio (далее просто студия) и в выскочившем мастере сразу же создаем проект:

Откроется окно:

Увеличить

Оно может выглядеть чуток не так. Дело в том, что интерфейс студии очень легко конфигурируется и перекраивается под себя. Так что можно перетащить панели как нам удобно. Что я и сделал.

Систему команд хорошо бы распечатать себе на листочке. Их там всего около 130, кратким списком (тип команды, что делает и какие операнды) занимает пару листов формата А4. Учить не надо, прочитать раз на десять, чтобы помнить знать что у нас есть. Даже я периодически подглядываю в систему команд, хотя пишу на ассемблере уже много лет.
Я же команды которые буду использовать буду описывать по мере появления.

В центральном окне пишем код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
		.include "m16def.inc"   ; Используем ATMega16
;= Start macro.inc ========================================
 
; Тут будут наши макросы, потом. 
 
;= End macro.inc  ========================================
 
 
; RAM =====================================================
		.DSEG			; Сегмент ОЗУ
 
 
; FLASH ===================================================
		.CSEG			; Кодовый сегмент
 
 
; EEPROM ==================================================
		.ESEG			; Сегмент EEPROM

Это вроде шаблона для начала любого проекта. Правда кода тут 0 байт :) Только директивы.
Обрати также внимание на оформление кода. Дело в том, что в ассемблере нет никаких структурных
элементов — весь код плоский, тупо в столбик. Так что поэтому чтобы он был читабелен его надо в обязательно порядке разбивать на блоки, активно пользоваться табуляцией и расставлять комментарии. Иначе уже через пару дней ты не сможешь понять, что вообще написал.

В коде будут активно использоваться макросы. Поначалу они будут писаться в основном файле, в секции macro.inc, но потом я их вынесу в отдельный файл, чтобы не мешались. Так удобней.

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

У обычного PC компа используется другая архитектура — Фон Неймановская. Там данные и код находятся в одном адресном пространстве. Т.е., скажем, с адреса 0000 по 0100 идет код, а с 100 до FFFF данные.

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

Возьмем и напишем:

1
2
3
4
5
; FLASH ===================================================
		.CSEG		; Кодовый сегмент
		NOP
		NOP
		NOP

Что мы сделали? А ничего! Команда NOP это команда затычка. Она не делает ничего, просто занимает 2 байта и 1 такт.

Речь не о команде NOP (там и обсуждать то нечего), а о том как оно все будет выполняться.

Запускай симуляцию (Ctrl+F7) когда пробежит прогресс бар компиляции/симуляции и возле первого NOP возникнет стрелочка нажми ALT+O — выскочит диалог настройки симуляции. Там тебе надо там только выставить частоту 8Мгц. Почему 8Мгц? Просто на Pinboard частота главного проца по дефолту 8Мгц. Если у тебя свои идеи на этот счет — можешь поправить как угодно. На симуляцию это не влияет.

Вернемся к нашей симуляции. Давай посмотрим как выглядит наша программа с точки зрения машинных кодов, как размещается в памяти. Интерес, по большей части, чисто теоретический, редко когда пригождается и по этому во многих учебных курсах по AVR на это даже внимание не заостряют. А зря! Т.к. упускается глубинное ощущение кода. Открой окно просмотра программ. View — Memory или Alt+4.

Там выбери тип памяти Programm. Это и есть наш Flash сегмент. Выставим в списке cols две колонки, чтобы было наглядней.

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

0000 это код команды NOP. У нас три команды, поэтому шесть нулей.

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

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

Поскольку мы только стартанули, то он равен нулю — нулевая инструкция. Нажми F11, процессор сделает шаг, программный счетчик изменится на 1 и покажет следующую инструкцию. И так до тех пор, пока не дойдет до третьего NOP, а что у нас после него? А после него у нас FF до конца памяти. FF это несуществующая инструкция, на ней виртуальный контроллер перезагрузится с ошибкой invalid opcode, а реальный контроллер ее проигнорирует, пробежав по ним, как по NOP, до конца памяти.

Сбрось симуляцию микроконтроллера (Shift+F5) и вручную выстави в Program Counter адрес 0×000002 — проц сам перепрыгнет на последнюю команду NOP. Если менять Program Counter то проц будет выполнять те команды, которые мы ему укажем. Но как это сделать в реальном контроллере? В него то мышкой не залезешь!

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

Добавим в наш код команду JMP и ее аргумент — адрес 0×000001.

1
2
3
4
5
		.CSEG			; Кодовый сегмент
		NOP
		NOP
		NOP
		JMP 0x000001

Команда JMP, как и все команды перехода, работает просто — записывает в Program Counter свой аргумент. В нашем случае — 0×000001.

Перекомпиль проект и посмотри на то, как меняется Program Counter (далее буду звать его PC) и вообще как теперь идет процесс выполнения программы. Видишь, после JMP в программный счетчик заносится новый адрес и процессор сразу же перепрыгивает в начало кода, но не на первую, а на вторую инструкцию. Программа зациклилась.

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

OC 94 — это код нашей комады, а 01 00 адрес перехода. Длина команды стала четыре байта. Два байта ушло на адрес. Вот, кстати, особенность памяти в том, что там данные записываются зеркально, т.е. младший байт числа по младшему адресу (порядок little-endian).

Но поскольку дальнобойные команды применяются редко, основной командой перехода в AVR является относительный переход RJMP. Основное отличие тут в том, что в PC не записывается не точный адрес куда надо перейти, а просто к PC прибавляется смещение. Вот так:

1
2
3
4
5
6
7
8
9
		.CSEG				; Кодовый сегмент
		NOP
		NOP
		NOP
		RJMP PC+2
		NOP
		NOP
		RJMP PC-6
		NOP

По идее, должно работать напрямую, т.е. RJMP +2. Но компилятор такую запись не понимает, он оперирует абсолютными адресами, приводя их потом в нужные смещения машинного кода. Поэтому применим макроопределение PC — это текущее значение счетчика в данном месте программы. Т.е.

1
JMP PC

Наглухо зациклит программу в этой строчке.

Благодаря относительному переходу, смещение можно запихать в те же два байта, что занимает команда. При этом код относительного перехода выглядит так Сх хх, где ххх это смещение от +/-2047 команд. Что обычно хватает с лихвой. В памяти же команда перехода выглядит как хх Сх, то есть байты переставлены.

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

Вот только что же, каждый раз самому вручную высчитывать длину перехода? Ладно тут программка в три команды, а если их сотни? Да если дописал чуток то все переходы заново высчитывать?

Угу. Когда то давно, когда компиляторов еще не было так и делали. Cейчас же все куда проще — компилятор все сделает сам. Ему только надо указать откуда и куда. Делается это с помощью меток.

1
2
3
4
5
6
7
8
9
		.CSEG		; Кодовый сегмент
M1:		NOP
		NOP
		NOP
		RJMP M2
		NOP
M2:		NOP
		RJMP M1
		NOP

Метки могут быть из букв или цифр, без пробелов и не начинаться с цифр. Заканчиваются двоеточием. По факту, метка означает текущий адрес в словах. Так что ее можно использовать и в операциях. Надо только помнить, что она двубайтная, а наш контроллер однобайтный. Разобрать двубайтное значение по байтам можно с помощью директивы компилятора Low и High

Например,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
		.CSEG			; Кодовый сегмент
M1:		NOP
		NOP
		LDI	ZL,low(M2)	; Загрузили в индекс 
		LDI	ZH,High(M2)
 
		IJMP
 
		NOP
		NOP
		NOP
M2:		NOP
		RJMP M1
		NOP

Команда LDI загружает непосредственное значение в регистр старшей (от R16 до R31) группы. Например, LDI R17,3 и в R17 будет число 3. А тут мы в регистры R30 (ZL) загрузили младший байт адреса на который указывает метка М2, а в R31 (ZH) старший байт адреса. Если протрассируешь выполнение (F11) этого кода, то увидишь как меняются значения регистров R30 и R31 (впрочем 31 может и не поменяться, т.к. там был ноль, а мы туда ноль и запишем — адрес то мал). Смену значений регистров можно поглядеть в том же окне где и Program Counter в разделе Registers.

Команда IJMP это косвенный переход. Т.е. он переходит не по адресу который заложен в коде операции или идет после него, а по адресу который лежит в индексной регистровой паре Z. Помните я говорил, что шесть последних регистров старшей регистровой группы образуют три регистровые пары X,Y,Z используются для адресации? Вот это я и имел ввиду.

После IJMP мы переходим на нашу же М2, но хитровывернутым способом. Зачем вообще так? Не проще ли применить RJMP и JMP. В этом случае да, проще.

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

Давай подвинем нашу кодовую конструкцию в памяти на десяток байт. Добавь в код директиву ORG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; FLASH ===================================================
		.CSEG		; Кодовый сегмент
		NOP
 
		.ORG 0x0010
M1:		NOP
		NOP
		LDI	ZL,low(M2)	; Загрузили в индекс 
		LDI	ZH,High(M2)
 
		IJMP
 
		NOP
		NOP
		NOP
M2:		NOP
		RJMP M1
		NOP

Скомпиль и запусти. Открой память и увидишь что в точке с адресом 0000 у нас стоит наш NOP (Точка входа должна быть, иначе симулятор скукожит от такого когнитивного диссонанса. А вот дальше сплошные FF FF и лишь начиная с нашего адреса 0×0010 пошли коды остальных команд.

То есть мы разбили код, заставив директивой ORG компилятор перетащить его дальше, начиная с 0×0010 адреса.

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

Во-вторых, такая мера нужна чтобы записать код в конец флеша, при написании бутлоадеров (о них тоже дальше в курсе будет).

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

Да, у кристалла есть биты защиты. Выставил которые и все, программу можно считать только спилив крышку кристалла с помощью электронных микроскопов, что очень и очень недешево. Но иногда надо оставить прошивку на виду, но не дать сорцов. И вот тогда превращение прошивки в кашу очень даже поможет =)

Ну или написать ее на Си ;))))))) (Есть вариант шифровки прошивки и дешифровки ее на лету бутлоадером, но это отдельная тема. О ней может быть расскажу)

Еще, там, в студии, же где и программный счетчик, есть ряд интересных параметров.
Во-первых, там отдельно вынесены наши индексные пары X,Y,Z и можно не лазать на дно раздела Registers.
Во-вторых, там есть Cycle counter — он показывает сколько машинных циклов протикал наш контроллер. Один машинный цикл в AVR равен одному такту. Frequency содержит частоту контроллера в данный момент, но мы задаем ее вручную.
А в-третьих, есть Stop Watch который показывает время выполнения. В микросекундах. Но можно переключить и в миллисекунды (пошарь в контекстном меню).

Если тебе кажется, что все слишком просто и я чрезмерно все разжевываю. Хы, не расслабляйся, у меня сложность растет по экспоненте. Скоро пойдет работа на прерываниях, а потом будем писать свою операционную систему :))))))

Запись опубликована в рубрике AVR. Учебный курс с метками , , . Добавьте в закладки постоянную ссылку.

169 комментариев: AVR. Учебный курс. Простейшая программа.

  1. Beams говорит:

    «Если вместо стройной иерархии структур в нашей голове образовалась каша, не стоит волноваться — это нормально. Постепенно она утрясётся и все структуры встанут на свои места, так что оставим их дозревать, а сами сосредоточимся на текущих проблемах.» Крис Касперски.

    Думается мне само то вставить в эпиграф ;)

    • DI HALT говорит:

      Примерно так. Я действую тем же способом, что и учился сам. Взял и поставил задачу, а уж как она решается, из каких кусков делается… Разберемся по ходу дела.

  2. Arhangelsk говорит:

    «Поехали»! Все. Начали. Можно пробовать эмулировать схему в протеусе.
    PS: А когда планируется первая «протеусная» статья?

  3. Softy говорит:

    Что такое стек? Это область памяти, работающая по принципу первый вошел-последний вышел.А не наоборот, последний вошёл, первый вышел (LIFO)? Хотя по логике вроде тоже самое.

  4. sinobi говорит:

    Интересно о ассемблере пишут,а о компиляторе нет ни слова,непонятно.Или я где-то просмотрел?Люди которые незнают как компильнуть как это будут делать это?

  5. Shorst говорит:

    Вопрос по строчке OUTI SPL,low(RAMEND): функция low(RAMEND) выбирает из RAMEND младший байт и по нему устанавливает конец ОЗУ МК, а старший байт подразумевается равным нулю.Т.к. значение RAMEND равно $045f,то мы ограничиваем размер ОЗУ стеком до 95 байт (5F)?

    • DI HALT говорит:

      Эт косяк. Я думал что уже везде поправил. Сей кусок кода была скопипащен из кода для Тини2313 у которой 128байт ОЗУ и значит там не требуется второй байт для адресации. У меги 8 уже 1кб. Конечно же надо еще загрузить старший байт

      OUTI SPH,high(RAMEND)

      Копипаст зло!

  6. VVV говорит:

    Возможно я чето пропустил, по этому не до конца понимаю почему все-таки ты всегда зажигаешь светодиоды именно нолем? Здесь есть какие-то приемущества или это просто дело привички?
    И еще, где можно посмотреть перечень контролеров Atmel (можно и не только Atmel) и их возможности? А то я пробовал что-то разузнать на их сайте — ничего для «домашнего» применения не встретил и ваше там только какие-то супермодные все…

    • DI HALT говорит:

      1) Дело привычки — С51 не умел тянуть ноги вверх

      UPD:
      Потер те две причины. Они к подтягу на 1 %) Остается только привычка. А вообще, последнее время, на реальных схемах делаю так, как удобней разводить. Иногда бывает и так и эдак в одной и той же схеме. А чтобы не путаться, то увязываю все в макросы. Типо ON/OFF
      Блин гоню с недосыпа.

      promelec.ru/catalog_info/48/70/212/73/
      ну вот например.

      Там же, в каталоге промэлектроники можно позырить такие же таблицы и на пики, и на МСП430 и на С51

  7. vanchester говорит:

    DI HALT, нужна помощь.

    Прошил в ATMega8535 твою программу Mega8Robot.asm, предварительно подправив inc-файл и прерывания. Воткнул в собранную схемку — все работает с точностью до наоборот (команда CBI заставляет светодиод светиться, команда SBI — гаснуть)… (для прошивки использую uniprof, программатор собрал по схемке, которая у тебя на сайте)

    Например, такая программка:
    .include «m8535def.inc»
    .def acc = R16 ; Accumulator

    .MACRO outi
    LDI R16,@1
    OUT @0,R16
    .ENDMACRO

    .CSEG
    .ORG 0×0030
    RJMP Reset

    Reset: OUTI SPL,low(RAMEND)
    OUTI SPH,High(RAMEND)
    OUTI DDRB, 0xFF
    main:
    OUTI PORTB, 0×00
    RJMP main

    В симуляторе все как положено, все выводы PORTB «выключенные», а реально если межу выводами и Vcc поставить диоды, они светятся. Попробовал заменить 0×00 на 0xFF, светодиоды перестают светиться. Где я чего напутал? Спасибо.

    • DI HALT говорит:

      а ты диод как ставишь? Тут ситуация такая:

      Если диод подключен между +5 и выводом мк то от 0 на выходе он будет светится.
      (ток идет с +5 через диод на 0 на ножке)

      Если же включить между выводом и GND то будет светится от 1.
      (Ток идет с +5 на ножке через диод на землю.)

      Проследи путь тока через диод и все станет ясно :)

  8. Sergey говорит:

    DI HALT возник вопрос по программе из статьи «Домашний терминатор» журнала Хакер.
    Посмотрел текст прграммы:
    1. ждем нажатия кнопки
    2. если да, то зажигаем лампу и подаем 1 к левому и правому двиг. и разрешения
    и т.д.
    Потом зашил скомпиленый файл .hex, проверил па макетке — никаких реакций на нажатие кнопки (PB0 подключаем к земле) и последующие нажатия кнопок датчика касания.
    Скомпилировал файл с прошивкой из исходных текстов — размер оказался другим (больше). Проверил на макетке. При подачи питания едет (есть разрешения и 1 на двигателях) нажимаю кнопку правый двигатель останавливается, не реагирует на датчики касания.
    Подскажи где ошибка. Спасибо.

  9. Timy говорит:

    долго мучил DiHalt-а ))) потом в сети нашол :

  10. Ridik911 говорит:

    так.. получается у нас для определения будет ли порт вход или выход — ДДР если 1 то выход, если 0 то вход?
    например

    ldi r16,0xff ;весь порт на выход
    ldi r17,123
    out ddrc,r16 ;вкл. порт на выход
    out portc,r17;выводиим число

    я правильно понимаю надеюсь.. только студия тупит чота а мож я… когда уже непосредственно вывожу на ПОРТЦ некоторое число до 255 то оноже идет и по ПИНЦ.. иль так задумано (хапсим не поддерживает новую стройку студии)

    • DI HALT говорит:

      Так и есть. Значение DDR определяет направление порта. 0 вход, 1 выход.

      Если у тебя линия на выход настроена, то выдача значения в PORT одновременно вылазит и в PIN, что естественно. Т.к. PIN это значение которое непосредственно на ноге.

      Т.е. грубо говоря так:
      DDR — направление
      PORT — что мы хотели получить на выходе
      PIN — что у нас реально получилось.

      Т.е. пример, ты делаешь DDR на выход, выдаешь в PORT 1, ножка пытается установиться в единицу. Но вот засада — она на ноль оказалась по ошибке закрочена. В итоге транзистор сгорает, нога безвольно повисает в нуле, и несмотря на то что в PORT=1 из PIN считается 0, т.к. ножка сгорела и в единицу встать не может.

      • Ridik911 говорит:

        >Но вот засада — она на ноль оказалась по ошибке закрочена. В итоге транзистор сгорает, нога безвольно повисает в нуле, и несмотря на то что в PORT=1 из PIN считается 0, т.к. ножка сгорела и в единицу встать не может.
        —-
        это какбы контрпример таксказать..
        а так понял. спасибо… надежды ждать разработчиков хапсим нет.. такчто запустил для проверки в VMLAB (чтото типо хапсим для кодевизон).. только никак не получается подключить диоды нога-земля (получается нога-Vdd(+5)) — вообщем инвертированость дает

        .POWER VDD=5 VSS=0 ; Power nodes
        .CLOCK 32k ; Micro clock
        .STORE 250m ; Trace (micro+signals) storage time

        D1 VDD PD0 ; LED code.
        D2 VDD PD1 ; (since no more LEDs are available

        D8 VDD PD7 ;in the control panel)

        , но для проверки и наглядности достаточно

  11. Security говорит:

    незнал куда написать, но проблема в программировании.
    подключил 12-ти разрядный АЦП(AD7892-2) к микроконтроллеру (ATmega16), к порту Ц(младшие 8) и к порту Б(старшие 4). выставляю данные на выход АЦП (на входы портов), читаю порты:
    buffer[0]=PINC;
    buffer[1]=PINB & 0x0F;
    получаю например такой результат:
    buffer[0]: 0х42
    buffer[1]: 0×00
    потом беру меряю тестером напруги на ножках микроконтроллера:
    порт С = 0х78
    порт В = 0х00
    я так понимаю что непрально я считываю инфу с портов. оба порта настроены так:
    DDRC=0×00; // порт С вход
    PORTC=0xFF; // подтягивающие резисторы подключены.
    подскажите в чем я ошибся. или предложите альтернативу решения данной проблемы.

    • DI HALT говорит:

      Считываешь правильно и настроил порт ты тоже как надо.

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

      • Security говорит:

        «Т.е. у тебя считаное не равно тому что реально на входе?» да именно.
        выход АЦП не плавает (тыкал в каждый вывод счетчик, на ТТЛ, т.е. скорость реакции бешанная 5нс) ниче не щитает. четко показывает «1″ «0″.
        А не может быть такой хитрости, что порт С вообще не предназначен для считывания инфы, а только на вывод? Просто в примере по которому я делаю, использвется вместо порта С порт А, но я подумал, что это не принципиально и подпаял на С.

        • DI HALT говорит:

          Кажись понял в чем косяк! На этом порту ЕМНИП висит JTAG и он по дефолту вроде бы включен. Выруби нах в фузах бит JTAGEN

          • Security говорит:

            Пусть за мной будет замечена гомосятина, но я тебя расцелую!!! ;)
            Да, по умолчанию оказалось JTAG включен. Скинул флаг начало читать то что подает АЦП.
            Огромное спасибо!

          • DI HALT говорит:

            Старые грабли. Я сам никогда мегу16 не юзал, а про них где то упоминалось уже.

  12. bdp говорит:

    Проемулировал програмку в AVRStudio, и тут такая лажа приключилась с кнопкой старта:
    Значения DDRB,0 = 0 и PORTB,0 = 1 а вот значение PINB,0 осталось равным «0″, и поэтому програма проходила дальше (а кнопку то я типа и не нажимал). Таже лажа с контролем припятствий.
    Насколько я помню по предидущей лекции это глюк самой AVRStudio, или я гдето лоханулся.
    Набраний код перепроверил три раза О_о

  13. SIM говорит:

    В предыдущем примере где на ноги выводили похожее на 0101010 (hex АА) небыло теблицы прерываний. Начинало работать сразу основное тело программы?
    Программа начиналась по адресу
    .ORG 0×0030
    я так понимар при включении микроконтроллер кидается на адрес 0×0000 там «nop» и так ползет 30 тактов до адреса 0х0030? или сразу как-то переходит?

    2. Вход reset я так понял может использоваться как порт ввода-вывода? В том же Attiny2313 он помечен как (RESET/dW) PA2. Как тогда сбросить контроллер? непонятно пока что :-)

    • DI HALT говорит:

      Да там стартует на 0000 потом ползет через всю таблицу прерываний до 30 адреса и оттуда выполняет.

      Но по правильному бы лучше сделать так:
      ORG 0000
      RJMP RESET
      [таблица векторов прерываний]
      ORG [end vectors size]
      RESET:

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

      2. Да его можно использовать как порт, при этом теряется возможность внешнего сброса и ТЕРЯЕТСЯ ВОЗМОЖНОСТЬ ПРОШИВКИ ЧЕРЕЗ SPI только параллельным программатором на 12вольтах.

  14. RunningWolf говорит:

    Во-первых, огромное спасибо автору. Хорошая подача материала, действительно помогло перейти от раздумий «хорошо бы…» к делу не имея никаких навыков в электронике.
    Респект!

    А теперь по теме: =)
    Собрал программатор Громова. Воткнул USB-COM — получил COM26. Черт! Uniprof только до COM5. Ладно, бог с ним — попробовал avrdude — фиг вам! А, чтоб тебя..!
    Достал с антресолей маму с PIII и COM-портами на маме, воткнул программатор — как ни странно, заработало сразу. Ура!
    Т.к. на кошках тренироваться скучно, решил прошить программку из этой статьи.
    Подключил, правда, только:
    - кпноку «Старт» (PB0)
    - кнопку «Левый передний датчик» (PC0)
    - светодиоды: отладочный(PC3), левый разрешен(PB2), левый направление(PD2, PD3)
    Прошил, включил и… оно не работает. МК: Atmega8-16PU. После запуска загораются вообще все светодиоды и на кнопки не реагирует.

    Подумав, пошел в обход и таки получил, простую прогу, которая зажигает желаемые лампы при нажатии на те или иные кнопки.
    Т.о. проблемы либо в схеме, либо в программке, либо в Mege либо, во мне =)

    По проге из этой статьи:
    1) Входы подтянуты к 1-це. Это логический 0 или логическая 1?
    2) Светодиоды подключены к +5В. Когда контроллер чисты (не прошит) — не горит ничего. Т.е. на всех выходах — логическая 1.
    Судя по тому, что при запуске проги из этой статьи сразу загораются все светодиоды — значит на всех выходах сразу устанавливаются нули. Т.е. проходят CBI для каждого порта.
    Где они есть в этой программке? Это макросы LIGHT_ON, LDR_DISABLE, L_Run_F, L_Run_B, L_Stop. Т.е. в самых разных частях. Такое впечатление, что оно после запуска крутится в главном цикле, игнорируя все условия.

    3) Изначально все входы находятся в одном состоянии (либо 0, либо 1, в зависимости от ответа на вопрос 1 =)). Нажатие кнопки меняет состояние порта на противоположное (пока кнопка нажата). Команда SBIС проверяет вход=0. SBIS проверяет, что вход = 1. Как тогда в одном месте для проверки нажатия используется SBIC, а в другом — SBIS? Ведь изменение уровня от кнопки в обоих случаях одинаковое. Или как?

    • DI HALT говорит:

      «Собрал программатор Громова. Воткнул USB-COM — получил COM26″
      А толку, Громов все равно не работает с этими конвертерами. Хотя номер порта можно и сменить в диспетчере оборудования.

      1) Это вход с подтягом. Они подтянуты к 1це. Т.е. когда кнопка не нажата на порту 1. Нажатая кнопка садит порт на 0

      2) Когда контроллер чист на всех выводах HiZ т.е. считай что они вообще никуда не подключены.
      А теперь смотрим что в программе:
      Мы при инициализации порты светодиодов сразу же выставляем на выход:

      SBI DDRD,LDR0 ; А тут..
      SBI DDRD,LDR1 ; микросхемой..
      SBI DDRB,LDRE ; двигатели….
      SBI DDRC,RDR0
      SBI DDRC,RDR1
      SBI DDRB,RDRE

      Но! При старте во всех регистрах PORT по дефолту стоят нули. А когда мы DDR устанавливаем в 1, то порт становится на выход и в результате (т.к. PORT = 0) на ноге у него получается 0, вот диоды и зажигаются все сразу.

      3) Ты не совсем вкурил в алгоритм. SBIS и SBIC это не команды проверки кнопки. Это проверка условия.

      В первом случае (кнопка) нам надо понять когда она нажата и по этому факту выскочить из цикла.

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

      использовать можно sbic и sbis в зависимости от тогокак нам удобней в данный момент.
      т.е. пропустить команду если бит есть или пропустить команду если бита нет.

      • RunningWolf говорит:

        Спасибо за ответ. Картинка стала полнее. Однако предложенный тобой код все равно не хочет жить. И есть у меня подозрение, что не живет он из-за того, что подтяг не работает.

        Вот мой код:

        .include «m8def.inc»

        .MACRO outi
        LDI R16,@1
        OUT @0,R16
        .ENDMACRO

        .CSEG
        .ORG 0×0000
        RJMP RESET

        .ORG 0×0030

        RESET:

        CBI DDRB,0
        SBI PORTB,0

        CBI DDRC,0
        SBI PORTC,0

        SBI DDRC,3
        SBI DDRB,2

        SBI DDRD,2
        SBI DDRD,3

        ; Init stack
        OUTI SPL,low(RAMEND)
        OUTI SPH,High(RAMEND)

        Main:
        ;SBIS PINB,0
        SBIC PINB,0
        RJMP Pause

        ; Off light
        SBI PORTC,3
        SBI PORTB,2

        ; On Light
        CBI PORTD,2
        CBI PORTD,3

        Pause:
        ;SBIS PINC,0
        SBIC PINC,0
        RJMP Main

        SBI PORTD,2
        SBI PORTD,3

        CBI PORTC,3
        CBI PORTB,2

        RJMP Pause

        Как оно живет «в натуре»: После запуска загорается все (PD2, PD3, PC3, PB2). При замыкании на землю PB0 остаются гореть только PD2, PD3. При заземлении PC0 наоборот горят только PC2, PB3.

        Если я правильно понимаю, то при наличии подтяга (он, вроде обеспечивается инструкциями CBI DDRB,0 и SBI PORTB,0) на порту, если его никто не трогает, всегда висит 1-ца. Т.е. пока держишь землю — идет ноль. Отключил землю — порт ушел опять в 1. Так?
        Если да, то не понятно как может крутится блок Pause (горят только PC2, PB3), когда я уже отпустил кнопку (снял землю) на PC0? Ведь тогда PC0 опять встанет в 1, SBIC PINC,0 не сработает и при следующем же цикле оно должно уйти по RJMP Main. Так?

        С другой стороны, если подтяга нет, то не понятно, как крутится блок Main (горят только PD2, PD3)? Ведь опять-таки тогда должны все время срабатывать оба условия и гореть все диоды.

        Вобщем, что-то я не догоняю :( Не дай помереть дураком =)

        • DI HALT говорит:

          Подтяг проверяется просто — в свободном состоянии на выводе должен быть +5в

        • DI HALT говорит:

          Так, стоп. Где ты у меня паузу увидел?

          • RunningWolf говорит:

            Ты про «блок Pause»? Это я логику своей проги (см. пред. пост) имел в виду.
            У тебя, я так понимаю, циклы только:
            1) Ожидание нажатия старт (Main: … RJMP Main)
            2) Ожидание сигнала от датчиков (Seek: … RJMP Seek)
            3) Общий цикл (Main: … последний RJMP Main)

            Я про свою прогу. Никак не могу понять, как оно уходит в цикл Pause когда PC0 уже НЕ заземлен. А в реальности он туда уходит (стоит только коснуться землей PC0 и затем убрать землю), т.к. PB2 и PC3 продолжают гореть, а PD2, PD3 — погашены.

          • DI HALT говорит:

            Дребезг контактов учел?

      • RunningWolf говорит:

        А про USB-COM и Громова. Вроде кто-то писал, что иногда оно работает. Надо же было попробовать =) Да и все-равно оно надо для USBASP.

  15. SergeyDon говорит:

    чем отличаются например команды: JMP и CALL? ну кроме количества тактов исполнения.
    ставил и так и так работает…

    • DI HALT говорит:

      Принципиально отличаются.

      JMP это просто переход.
      А CALL это переход с запоминанием точки перехода и по команде RET мы в эту точку вернемся.

      Мало того, часто делая CALL мы можем сорвать стек. Так что за каждым CALL должен следовать RET На какую глубину ушли из нее же возвращаемся.

      • SergeyDon говорит:

        СПС.
        я разобрался!
        просто я стек не инициализировал, и у меня при вызове что JMP, что CALL
        при достижении Ret взвращалось на адресс 0х0000.
        на сайте gaw.ru там даташит с переводом на русскй, так там вообщенет про стек и его инициализацию… ничего нет!

  16. Sergas5 говорит:

    Привет всем! DI HALT, о какой отладочной плате идет речь? О той, которая в разделе «Демоплата» или которая в разделе «Создание печатной платы методом лазерного утюга». Кстати, где можно прочитать о последней — что-то нигде не нашел. Заранее спасибо!!!

  17. A_ndrej говорит:

    Не могу понять.
    В вашей программе в этом куске:
    » ; Датчики касания
    .equ L_FNT = 0 ; Правый передний
    .equ R_FNT = 5 ; Левый передний

    .equ L_BCK = 4 ; Левый задний
    .equ R_BCK = 3 ; Правый задний

    ; Кнопка запуска
    .equ ST_BTN = 0

    ; Двигатели.
    .equ LDR0 = 2 ; Левый двигатель бит 0
    .equ LDR1 = 3 ; Левый двигатель бит 1
    .equ LDRE = 2 ; Левый двигатель разрешение

    .equ RDR0 = 2 ; Правый двигатель бит 0
    .equ RDR1 = 1 ; Правый двигатель бит 1
    .equ RDRE = 1 ; Правый двигатель разрешение

    ; Сигнальные лампы
    .equ LIGHT = 3

    /* Откуда я взял эти цифры? А поглядите на номера портов к которым прицеплены
    все эти датчики и двигатели. Вот! Т.е. я просто обозвал биты как мне было
    удобно. Чтобы не держать в памяти где у меня что подключено. */ »

    я не понимаю, не откуда взялись эти цифры(уже понял), а как эти биты не путаются с такими же битами других портов?
    Ведь в каждом порту есть биты 0…7. Как же ваши биты не путаются с остальными?

  18. MWAndry говорит:

    DI-HALT, обьясни пожалуйста, зачем ты в определниях обозвал регистр R16
    .def ACC = R16 ; Accumulator
    Ведь дальше по коду это имя не встречается.
    В макросе OUTI ты используеш R16 а не АСС.
    Это очепятка или я что-то пропустил?

    • DI HALT говорит:

      Да это привычка из C51 ассемблера. Там был АСС как основной регистр. А тут как то забылось уже.

      • MWAndry говорит:

        Тобиш если я эту строку удалю из кода, ни на что это не повлияет?

        • DI HALT говорит:

          Скорей всего нет.

          • MWAndry говорит:

            Спасибо за опаретивный ответ.
            Отдельное спасибо вообще за все учебные статьи. Спаял програматор Громова, удалось прошить контроллер.
            Но что то не так со статьей про UART. Жму «читать полностью» а мне выбрасывается вот такой символ «<l» и ничего больше.

          • DI HALT говорит:

            Какая именно статья глючит? Только что проверил статью «Передача данных через UART» нормально она открывается.

          • MWAndry говорит:

            С рабочего компьютера всё открылось и у меня. Значит домашняя сеть барахлит. Звыняйте за беспокойство.

  19. MWAndry говорит:

    Оперативный ответ. Звиняйте, часто очепятываюсь.

  20. A_ndrej говорит:

    Есть один вопрос.
    Хочу в качестве своей первой программы сделать спидометр на моторолер.
    Поцеплю на диск колеса магнит, на барабан геркончи или датчик Холла, и буду ловить их показания ATмега восьмеркой.
    После прочитаного, казалось, что может быть проще)))
    А когда начал разбиратся то появилось сколько подводных камней, что я теперь не вылажу с нета в поискак каких то аналогий. А везде всякие спидометры и тахометры настроены только на свои параметры колеса, и даются только hex файлы.
    Хожу теперь в ступоре, по пол ночи не могу заснуть(все это в голове копошится, а отказыватся от идеи не хочется(уже сколько времени на нее потратил).
    Как я понимаю для того что бы измерить скорость, нужно измерить период приходящего импульса.
    Я думаю что один таймер нужно запитать от внешнего источника(от геркона), а второй пустить от встроенного генератора.
    Когда первый импульс с геркона приходит, то второй таймер запускается и начинает считать.
    Как только приходит второй импульс то второй таймер выдает свои подсчеты(время между импульсами), и начинает считать заново до прихода нового импульса.
    Все это потом считается по формуле V=l/t? где V- скорость, L-окружность колеса(то есть пройденый путь),t- время(результат подсчета второго таймера).
    Так, все понятно, а как это росписать в коде ума не приложу.
    Может что подскажете?
    Или ссылочку какую то дадите с примером?

    P.S. Еще подскажите пожалуйста как делить и умножать в AVR?

    • DI HALT говорит:

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

      Городить вычисления думаю тебе не нужно. У тебя же не высокоточная схема. Проще сделай табличные вычисления. Забабахай себе таблицу скоростей с шагом в 0.5км/ч под твое колесо или как те еще надо. И последовательно сравнивай натиканное по таблице пока не будет больше/меньше (смотря с какой стороны пойдешь сравнивать) это и будет текущая скорость. Будет быстрей и компактней.

      • A_ndrej говорит:

        Спасибо.
        Как отсеживать понял. А как изобразить в коде сравнение с задаными таблице значениями?
        Это надо чтобы ставился флаг С, а потом отлавливать его командами BRSH и BRLO?
        Если да, то как сделать так чтобы флаг С ставился, и куда его вообще ставить чтобы потом можно было сравнивать с заранее заданыыми значениями?

  21. A_ndrej говорит:

    Дошло))))
    Большое спасибо))

  22. A_ndrej говорит:

    А как задать таймер?
    Что то типа:

    ldi temp, 0х01; без деления в предделитете
    out TCCR0, temp
    ?

    Так как прерывания по переполнению не надо, то разрешение на прерывание по переполнению не ставить?
    Чтобы сбить таймер в ноль надо в предыдущем коде в temp записать ноль, или
    написать clr TCCR0?
    А как снять количество натиканых импульсов с таймера?
    Написать out temp, TCCR0 или как?
    Если можете — ответьте.
    Не обижайтесь пожалуйста на бесконечные расспросы :-), — хочется все таки разобратся.

    • DI HALT говорит:

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

      У таймера дофига регистров. TCCR0 это только один из них- управляющий. Есть еще регистры масок прерываний, и еще два управляющих регистра. ;)))

      А счет идет в счетном регистре TCNT И вот оттуда надо считать. Ну и про сброс предделителя не стоит забывать ;)

  23. Igor говорит:

    День добрый.
    У меня есть один вопрос.
    Можно ли реализовать как на некоторых китайских игрушках.
    Несколько кнопок и задаю программу,кнопка вперед,влево,право и тд. после нажимаем
    старт и танк исполняет то,что там напрограммировал.для каждого нажатия к примеру:
    20 см вперед или назад,10 град. поворот влево или право и тд.
    Реально это или нет?

  24. Dmitri говорит:

    Команда JMP 0×000001 не работает что-то в mega8. Здесь говорят http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=33273&start=0 что она не айс и её закрыли.

  25. krestic говорит:

    Чудицца мне что коменты тут совсем не к этой статье:) Диоды, «Домашний терминатор»- а в статье только про НОПы…
    А в чем великий смысл ставить такое количество нулей в 0х000001 ? Почему именно шестизначное число, а не восьми или двух?

  26. Shuterkent говорит:

    Читал я читал множество статей твоих прочитал ДиХалт но так и не вкурил, допустим мне нужно выставить к примеру Порт РВ4 на вход, так вот как я понимаю мне нужно регистр DDRB выставить на 0, а порт в зависимости, что я хочу там увидить или PORT равен 1, или PORTB=0, и еще правильно ли я понял, если у нас в регистре PORTB равен 0 то при нажатие на кнопку он менятся на 1???. А теперь собственно вопрос что я должен увидеть в AVR studio, когда соответствинные регистры выставлены, и как понять что это 1 и что это 0 и где???. Просто я только начинаю разбираться в микроконтроллерах, а сайт у тебя классный очень много интересного даже его выкачал себе)))!

    • DI HALT говорит:

      На вход DDR=0 это всегда.

      А PORT определяет режим работы входа.

      1 — «активный» вход с подтягом к +5, например для отслеживания кнопок.
      0 — пассивный вход с Hi-Z. Для отслеживания шин всяких.

      Реальное Значение из порта БЕРЕТСЯ В РЕГИСТРЕ PIN!!!

  27. Shuterkent говорит:

    тоесть, если я выставил DDR=0, PORT=1 то только тогда я могу подключить кнопку, а реальное значение вкл. она или не вкл. я просматриваю в PIN. Правильно я понял???

  28. Shuterkent говорит:

    Вот только начиал читать про макроассамблер там где простейшая программа, так вот я не понял как работает логика контроллера, а может и не увидил. Ну к примеру есть у меня:
    Порт первый
    DDR=0 Port=1-это я подкл. кнопку
    Порт второй
    DDR=1 Port=0-это я подкл. светодиод
    Так вот это первоначальное состояние.
    Так вот нажал я кнопку, мне нужно что б светодиод засветился так что Port=0 должен выставиться на Port=1.
    Как это пишеться с помощью ассамблера??!

    • DI HALT говорит:

      Эм… вообщето PORT это целый байт. И в нем 8 бит, а всего таких портов у МК несколько может быть. Так что у тебя на одну ногу кнопка, на другую светодиод. А говоря про DDR=0 я имел ввиду КОНКРЕТНЫЙ БИТ порта.

      Например DDRD.7=0, а DDRD.6=1 и одна нога работает на вход, другая на выход.

      Соответственно на ассемблере настройка порта таким образом делается так:

      LDI R16,0<<7|1<<6
      OUT DDRD,R16

  29. Shuterkent говорит:

    аааа понял! спасибо

  30. Kots говорит:

    DI HALT, а какой прогой выделение жёлтым организуется? Оч удобно и наглядно, а сам вкурить метод никак не могу. =)

  31. sanjok говорит:

    можна поподробнее о регистрах R0-R31, не могу понять с чем их есть,
    ето временная память которая роботает с всеми функциями,
    я должен постоянно в нее загружать, а потом с ней роботать, и обратно в оперативку?

    • DI HALT говорит:

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

  32. Sergas5 говорит:

    DI HALT, а где можно взять систему команд для микроконтроллера желательно на русском языке?

    • DI HALT говорит:

      Я пользуюсь той, что в книге Евстифеева Контроллеры AVR серии Mega как то так. (у меня в разделе книги вроде бы еще есть ссылка). Вообще хорошая книжка — перевод даташита по сути.

  33. k0ndr говорит:

    Странно… При запуске кода с командами LDI и IJMP студия (v.4.18.700) выдаёт такую ощибку:
    D:\Program Files\Atmel\Projects\3.asm(4): error: Undefined symbol: ZL
    D:\Program Files\Atmel\Projects\3.asm(4): error: Invalid register
    D:\Program Files\Atmel\Projects\3.asm(5): error: Undefined symbol: ZH
    D:\Program Files\Atmel\Projects\3.asm(5): error: Invalid register
    D:\Program Files\Atmel\Projects\3.asm(15): No EEPROM data, deleting D:\Program Files\Atmel\Projects\3.eep

    Assembly failed, 4 errors, 0 warnings

    Что может быть не так?

  34. k0ndr говорит:

    Оу, точно, когда перезапускал студию, забыл inc подгрузить. Спасибо, заработало нормально.

  35. dima_m говорит:

    Чисто условный пример, чтоб понять как идет запись данных во флеш память.
    1. Допустим я хочу записать число по адресу $0000. Как я понял оно запишется в ячейку 0, а не в 1?
    2. И если число увеличивать, то после 255, данные начнут записываться в ячейку 1?
    3. То есть запись будет идти как обычно только задом наперед (little-endian) ?

    ячейка ячейка
    $0000 — 0 1
    $0001 — 2 3
    $0002 — 4 5

    • DI HALT говорит:

      Адресация флеша идет в словах (компилятору так удобней) т.е. 0000 это адрес первых двух байт (начала) 0001 адрес вторых двух байт и так далее.

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

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

      2. Не понял вопроса.

      Да, там младший байт по младшему адресу.

    • SergeyDon говорит:

      разум отказывается въезжать:

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

      пример:
      0000 $A1 $F0 // коды от балды
      0001 $00 $0С // тоже от балды
      0002 $94 $02 // аналогично
      0003 $A2 $FF // ну вы поняли

      1. каким образом например прочитать только младший байт по адрессу 0001 (который в примере =$0С)?

      2. если контроллер может оперировать с точностью до байта, то тогда должна быть возможность перехода на «половинчатые» адреса… (например в промежуток между 0001 и 0002, таким образом код следующей команды будет интерпретироваться как $0С $94 — а это уже RJMP… вроде так делали защиту кода от «ламеров дизасемблеристов» на Z80).

      3. если в код примера добавить строчку чтения байта по адресу на который указывает метка то там будит обломс…:

      M1:
      NOP
      NOP
      LDI ZL,low(M2) ;
      LDI ZH,High(M2)
      lpm r16,Z+ // тут обломс…. ожидаем в R16 код комады NOP, а там лажа!

      IJMP // а тут отрабатываем как надо ;)
      NOP
      M2:
      NOP
      RJMP M1
      NOP

      -=-=-
      LPM — чтение из памяти программ и инкремент.
      до выполнения инкремента Z=$09, после Z=$0A. Вроде все логично прибавили 1 и команда IJMP отработала как надо.

      а вот в R16 считали лабуду с непонятного адреса… опытные люди подсказали что нужно *2 но разве это правильно?

      P.S. что-то тут коменты 90% вообще отношения к теме не имеют… какие-то порты… компиляторы… программаторы… коды своих нерабочих творений по выкладывали… ВСЕ на форум :)

      • DI HALT говорит:

        LDI ZL,low(M2*2)
        LDI ZH,High(M2*2)

        Вот так надо. Т.к. у компилятора адреса в словах. Умножая их на два мы переводим их в байты и тогда с ними можно работать. И тогда ты загрузишь в R16 код NOP

        • SergeyDon говорит:

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

          директива .org 0×0010 реально в памяти начинается с адреса 0х0020!
          только студия так извращается?

          • DI HALT говорит:

            Просто метки в студии они для компилятора и переходов. А переходы адресуются в словах, потому так и сделано.

      • DI HALT говорит:

        1.
        В словах только компилятор оперирует. Видимо для его удобства. В реальных адресах карта памяти будет выглядеть так:
        0000 $A1 $F0 // коды от балды
        0002 $00 $0С // тоже от балды
        0004 $94 $02 // аналогично
        0006 $A2 $FF // ну вы поняли

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

        • SergeyDon говорит:

          «Команда JMP, как и все команды перехода, работает просто — записывает в Program Counter свой аргумент. В нашем случае — 0×000001.»

          странно но в PC записывается 0х000002; и вообще что-то я в PC записать нечетное число никак не могу! даже в hex правка не помогает все время четное тыкает (я в протеусе проверяю).

          через стек тоже адрес умножает на 2:
          LDI R16,0×11
          PUSH R16
          LDI R16,0×00
          PUSH R16
          RET // в PC грузит 0х000022!

          надо в реальном МК попробовать…

          • DI HALT говорит:

            В AVR Studio только что записал в PC 1 и сразу же произошел переход на первый вектор прерывания (фактический адрес в байтах 0х0002 (следующий после Reset) PC то щелкает словами. Т.е. 1 ведет на адрес 0х0002 и так далее.

          • DI HALT говорит:

            Меньше пользуйся всякими левыми глючными симуляторами :)

  36. A_ndrej говорит:

    Подскажите пожалуйста можно ли писать так:
    in XL,ICR1L
    in XH,ICR1H

    ldi Y,868
    cp Y,X

    В смысле можно ли оперировать сразу X или Y не расписывая на старшие и младшие разряды.
    И нет ли ошибки в выражении ldi Y,868
    или вообще в том что я накалякал?

    • DI HALT говорит:

      Нельзя. Проц то у нас 8ми разрядный. Он не может сравнить два байта сразу. Да и регистра такого Y или Х нету как таковых. Они сделаны лишь для удобства — не более чем просто обозначение. На деле есть только XL XH (которые тоже не более чем обозначение, т.к. это R26,R27)

      А вот IN XL,ICR1L вполне возможно. Т.к. это тоже самое что и
      IN R26,ICR1L

      • A_ndrej говорит:

        Спасибо, дошло)

        • A_ndrej говорит:

          Спасибо, дошло)
          А как тогда выполнить сравнение с числом
          di Y,868
          cp Y,X
          ?
          Разделять его на два разряда?

          • DI HALT говорит:

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

          • A_ndrej говорит:

            Щас мозг лопнет:-)

          • DI HALT говорит:

            Смотри. Есть Х и Y

            CP XL,YL
            CPC XH,YH
            BREQ ….

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

  37. A_ndrej говорит:

    А как игреку присвоить число 868? преобразовать в двоичный код — и разделить на 11 и 01100100.
    ldi YL,1101100100
    ldi YH,11
    Так что ли?

    • DI HALT говорит:

      Ага делишь число просто на биты и все. Но есть способ проще — доверь это компилятору!

      LDI YL,Low(868)
      LDI YH,High(868)

      И все, компилятор сам разложит все по байтам.

      • A_ndrej говорит:

        Понял.
        Блин, ну и головоломка:-)
        Это я пытаюсь писать прогу для спидометра которую закинул полгода назад.
        Использую вывод ICP1 для геркона и сравниваю то что нащелкал счетчик до захвата, с просчитанными цифрами чтобы индицировать скорость на экране.
        А вот когда мне нужно будет сравнить не двух разрядное число с таким же 2х разрядным показателем счетчика, а одноразрядное число(например 23) с двух разрядным показанием счетчика? Как быть?

        Также:
        LDI YL,Low(23)
        LDI YH,High(23)
        просто он поставит в YH сплошные ноли, или надо делать как то по другому?

  38. A_ndrej говорит:

    Да не, просто интересно, пусть ставит, лишь бы работало:-)

  39. Anhanger говорит:

    Объясните, пожалуйста…начинающему)
    ПОчему может не сбрасываться память в студииАВР 4?
    Допустим, вбиваю код 3 NOPa, в «программ мемори» в трех ячейках 6 байтов нулей после запуска эмуляции. Далее стираю один из NOP, т е остается их два. Нажимаю РЕЗЕТ, затем снова ЭМулящию. По идее, должно остаться четыре байта нулей…(?) А-н-нет, их остается все также шесть. Почему)? (криво установилась прога или криво с ней работаю)?)

    • DI HALT говорит:

      А перекомпиляцию проекта кто будет делать?

      • Anhanger говорит:

        Что нужно для этого нажать)?

          • Anhanger говорит:

            Нажимал(((
            Напишите, пожалуйста, тогда последовательность нажатия клавиш
            Допустим, запущена эмуляция с тремя этими командами НОП. Далее жму РЕЗЕТ, далее СТОП, далее Ф7, далее КОНТРЛ Ф? или Старт Дебаггинг.
            ПЕРЕД нажатием на резет удаляю третью команду НОП)

          • DI HALT говорит:

            В эмуляции удали любую команду NOP и попробуй испольнить эмуляцию на еще один шаг дальше. Компилятор спросит у тебя разрешение перекомпилить проект (вопрос Rebuild project?) отвечай да и проект будет перекомпилирован, а дебаг запущен заново. Тут то та команда и исчезнет

          • Anhanger говорит:

            …Простите за мое невежество…
            1. Проект с тремя НОП запущен
            2. Удаляю один последний НОП
            3. Нажимаю Ф11
            4. Прога спрашивает «РеБилд?» — нажимаю ДА
            5 Проект перезапускается.
            5а В Программ мемори остаются все те же три строки нулей(

          • DI HALT говорит:

            Ааа ну есть такой прикол. Меньше не больше. А напиши туда какую нибудь другую команду — она отобразится. Это, кстати, прикол авр студии. Она не стирает память перед запуском, оставляя там предыдущие значения. Особенно это вставляет в оперативке (RAM). КОгда будешь работать с ОЗУ учитывай этот факт. И стирай память ВСЮ в самом начале работы, специальным циклом инициализации памяти (тупо записывая туда в цикле нули). Ну там дальше будет пример.

          • Anhanger говорит:

            Большое Спасибо, DI HALT!

          • DI HALT говорит:

            Можешь сохранить проект, закрыть студию, открыть проект заново. И запустить. Будет два нопа.

  40. koyot говорит:

    А где можно почитать про этот “Домашний терминатор”? а то в коментах в основном про него, а в статье ни слова)

  41. koyot говорит:

    можно ли как то сделать что бы в AVR Studio содержимое регистров, памяти и портов отображалось в двоичном виде? и где посмотреть содержимое стека?

    • DI HALT говорит:

      Значения портов видно в двоичном виде в окне IOView
      Регистры в двоином виде можно увидеть если открыть спец окно Registry (Alt+0) и там в контестном меню настроить.
      Память вроде бы нельзя в двоичном показать.
      Стек наблюдается в конце памяти.

  42. Mercury говорит:

    а как сделать так, чтобы в окне кода проекта отображались блоки как на скринах выше?? а то у меня там все пусто((

  43. l-kaktus говорит:

    DI, у меня возник вопрос:
    В примере про использование команды LDI, если поменять регистр с Z (30,31) на X(26,27) или Y(28,29), то при выполнении команды IJMP переход идет к 0×000000

  44. Zubilo говорит:

    поправьте если ошибаюсь, но инструкция
    .ORG 0×0010

    должна нас привести на позицию с адресом 16

    ведь 0х0010 — это хекс

  45. Kostik28 говорит:

    хотел спросить в разделе AVR. Учебный курс. Подпрограммы и прерывания, но чёт там не получается… глюк наверн
    вот мой быдлокод. почему во время счета не срабатывает прерывание? ссылка на листинг http://easyelectronics.ru/repository.php?act=view&id=47

    • DI HALT говорит:

      А сами импульсы считает? Флаги выставляются?

      • Kostik28 говорит:

        да, это всё работает, старший регистр не проверял (устал тыкать:)), а младший считает

        • DI HALT говорит:

          А зачем много тыкать? Вручную в счетном регистре выставил 254 и два раза протыкнул — вот тебе и переход в старший разряд. Поставил за пару раз до переполнения/сравнения — вот тебе и прерывающее событие. Заодно увидишь что происходит

          • Kostik28 говорит:

            до 256 я дошёл, но прерывания нет

          • Kostik28 говорит:

            прерывания работают, спасибо:)
            но происходит затык в этом месте, в реале контроллер здесь встанет?

            Wait_uart_00: ; передача по UART (во время прерывания по совпадению)
            sbis ucsra,udre
            rjmp Wait_uart_00
            out udr,temp
            Wait_uart_10:
            sbis ucsra,udre
            rjmp Wait_uart_10
            out udr,XL
            Wait_uart_20:
            sbis ucsra,udre
            rjmp Wait_uart_20
            out udr,XH

  46. 444c-c4 говорит:

    Уважаемые, может для кого-нибудь ситуация прозрачна? Tn2313, Asm, AVRStud. MCUCR — 0,
    GIMSK — 0xC0 (прерывание вызывается все время, пока 0 на ПД2 или ПД3), на соответствующих векторах прерываний есть команды перехода для выполн. прерываний.
    Подпрограмма прерывания заканчивается: 1-когда уровень на ПД2 становится 1; 2-когда подпрограмма прерывания получает команду reti? Если подпрограмма прерывания по reti
    возвращается к выполнению основн. проги, при этом SREG7 — 1, почему нулевой уровень на ПД2, не вызывает вновь выполнения прерывания?
    Может кто подскажет как здесь делать гиперссылку? Всего наилучшего.

  47. bdp говорит:

    Решил повторно перечитать Ваш курс (тем более он обновился).
    Ну чтож — как всегда супер, так держать!!!

    А теперь ложка дегтя :)
    Директива .ORG указывает компилятору начало сдедующего куска кода. По вашему примеру програма сначала делает NOP а потом прыгает через 10 слов(через таблицу векторов прерываний) на продолжение куска програмы. И тут кизяк припрятался :) — копилятор то понял где следующий кусок кода, а контролерное ядро откуда будет знать про это.
    Вся проблемнойсть усугубляеться полным провтиком симулятора при движении по коду, но вот если открыть окно дизасемблера и потыкать F11, там все коректно указывает на подкравшийся кизяк.

    К слову — в старом курсе Ви правильно указывали в нолевом адресе RJMP main, где main метка адреса после таблици векторов прерываний.

    • DI HALT говорит:

      >По вашему примеру програма сначала делает NOP а потом прыгает через 10 слов(через таблицу векторов прерываний) на продолжение куска програмы.

      Это вы сами уже домыслили. Я такого не говорил. Цель была показать как ORG гоняет код по памяти. в дальнейших примерах везеде и RJMP и так далее. Кроме того число 10 взято просто от балды, т.к. таблица векторов она поболее будет.

      • bdp говорит:

        у про число 10 я понял :)
        А за домыслы извини, меня на это сподвигнула фраза:
        >А еще таким образом можно прятать в коде данные, перемешивая данные с кодом.
        Если чесно, реверсный инженеринг не сильно збивает с толку простое запихивание данных между частями кода, ни и тому подобное. Я видел как ребята в поисках дыр (не преступники, а нормальная кантора которую нанимают для поиска дыр) используют инструменты которые лехко разгребают такой хлам.

  48. Alex0720 говорит:

    Доброго времени суток.
    Цитата: «Если заглянешь в память, то увидишь там такую картину:…. OC 94 — это код нашей команды, а 01 00 адрес перехода.»
    А теперь вопрос: Почему на код:

    NOP
    RJMP 0×000002
    RJMP 0×000000

    в окне Memory/Disassembler видим???

    11: NOP
    +00000000: 0000 NOP No operation
    12: RJMP 0×000002
    +00000001: C000 RJMP PC+0×0001 Relative jump
    13: RJMP 0×000000
    +00000002: CFFD RJMP PC-0×0002 Relative jump

    Почему коды команд RJMP разные??? Как я понимаю коды команд это что то из разряда забитого гвоздиком?

    • DI HALT говорит:

      Так и есть. Тут команды двубайтные и операнд вписан в код команды. Т.е. касаемо RJMP то код там такой 1100xxxxxxxxxxxx где xxxxxxx это смещение перехода. Т.е. он вписан в код команды или, другими словами, у нас есть 4096 команды группы RJMP каждая со своим фиксированым смещением перехода.

      • Alex0720 говорит:

        Спасибо за ответ. С этим понятно. Кажется. =) Но попробую потупить еще раз.. =)
        Рассмотрим подробно строку 12 Disassembler-a:
        Даем команду программе сделать переход на строку кода 0×000002(RJMP 0×000002). И при прогоне он таки туда и переходит. Но…
        Посчитаем переход для RJMP: PC+0×0001=00000001+0001=00000010. С учетом кода команды — 1100_0000_0000_0010(C002h). А AVR утверждает что — C000h.
        Со вторым RJMP такая же засада.. =((

  49. Gena говорит:

    Вопрос про RJMP.
    Цитата: “Если заглянешь в память, то увидишь там такую картину:…. OC 94 — это код нашей команды, а 01 00 адрес перехода.”
    Почему именно «адрес перехода» если это команда относительного перехода и в команде должно указываться смещение перехода. Или смещение высчитывается в момент компиляции и хранится в команде как адрес перехода?

    Изменил пример:
    .include «tn2313def.inc» ; Используем tiny2313
    .CSEG ; Кодовый сегмент
    M1: NOP
    NOP
    LDI ZL,low(M2) ; Загрузили в индекс
    LDI ZH,High(M2)

    IJMP

    NOP
    NOP
    NOP
    M2: NOP
    RJMP 3
    NOP
    NOP
    NOP
    NOP
    NOP
    JMP M1

    При компилировании почему-то говорит что команды JMP не бывает. При трассировке переход RJMP идет на M1, а не на три команды вперед. Отрицательные значения смещения не принимает. Я знаю, что на практике указывают метки а не числа.

  50. X-Ray говорит:

    В AVR Studio 5, при нажатии Alt+O не появляется окно настройки симуляции и негде выставить частоту. Можно вручную вписать частоту в окошке Processor. Влияет ли выставленная частота на еще что то кроме расчета времени обработки операций?

  51. X-Ray говорит:

    И еще в AVR Studio 5, адреса указываются не словами а байтами. Поэтому где мы смещаем код на 0х0010, в памяти он отображается на 0х0020.
    Смотрим скриншот https://picasaweb.google.com/lh/photo/BUQd-77AWuGRNSURndHPXA?feat=directlink если плохо видно, слева вверху около фотографии есть кнопка сохранить. Там полное разрешение

  52. Anton говорит:

    DI HALT, у вас на 4 рисунке в окне кода включена нумерация строк. Подскажите как это сделать, в studio 4.19 не могу найти

    • DI HALT говорит:

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

  53. number_nine говорит:

    Приветствую!

    Вопрос по директиве .ORG. При пошаговой трассировке, после прохождения директивы .ORG Program Counter становится равным 0×000010, а Cycle Counter — 16. Контроллер «прощелкивает» адреса или загружает откуда-то значение программного счетчика? Если «прощелкивает» — почему не выполняется код таблицы прерываний? Если загружает — то откуда он берет значение, заданное директивой .ORG? В окне memory я ничего не обнаружил… Прощу прощения за корявый вопрос, заранее спасибо!

    • DI HALT говорит:

      ORG существует ТОЛЬКО для компилятора и программиста, который указывает компилятору, что код надо разместить здесь.

      Контроллер прощелкивает адреса строго по очереди, либо сразу заносит нужный адрес если ему попадается команда перехода. Таблица векторов не выполняется, т.к. в нулевом векторе обычно пишут RJMP main. Ну а саму майн посредством ORG смещают за таблицу векторов.

      • number_nine говорит:

        Всё понял, упустил из виду rjmp main. В общем, я так и думал, смутило то, что значение программного счетчика поменялось сразу, хотя счетчик циклов должен был насторожить :) Спасибо за ответ, думаю, вопрос далеко не последний!

  54. Vobar говорит:

    Привет всем!
    Начал делать диплом ( в техникуме ). Вначале нужно реализовать ИК-связь между пультом и кондиционером. Я для начала попытался просто создать меандр с максимальной частотой. Программа получилась такая: контроллер (ATtiny13a)
    sbi 0×16, 0 ;установка режима работы потра В/В
    sbi 0×17, 0
    nop
    nop
    cbi 0×17, 0
    rjmp pc-4
    В AVR Studio на операцию генерации уходит 0.83 микросекунды, а на осциллограмме длина периода — 7 микросекунд при частоте 9.6 МГц от внутреннего генератора. Осциллограф — С1-93, перед измерением калибровался. Я практически на 100% уверен, что частота для тактировки выбрана такая, т.к. после перезаписи фьюзов из (1:0) в (0:1) импульсы стали в 2 раза короче. Откуда такое расхождение?

  55. akocur говорит:

    Я пользуюсь программой AVR STUDIO 5.0.1223 Вопрос в следующем: когда запускаю симуляцию, то в окне MEMORY адрес не попорядку идет, а через один. Это в том случае когда выставляю в две колонки http://narod.ru/disk/37987044001/AVR.jpg.html Когда же выставляю в одну колонку то получается вот так http://narod.ru/disk/37987495001/AVR1.jpg.html Хотя в примерах выше адрес в окне MEMORY по другому. Может я что то не так настроил?

    • DI HALT говорит:

      Я юзаю авр студию 4.19. Пятая версия — сырое, глючное, неприспособленное к жизни говно. Оно еще и старые контроллеры не поддерживает в полной мере. Не говоря уж про старые отладочные средства.

  56. sea говорит:

    Уважаемый DI_HALT ! Микроконтроллер ATmega128. Объём программы: Code=2260, Data=2092, Used=4352. Всё компилируется нормально.
    Но, как стоит мне добавить любую одну инструкцию на место «Здесь вся программа», даже в самом начале где от rcall до метки print не изменяется разрыв до 2Кб.
    Сразу компиляция не проходит = Error: Operand(s) out of range in LDI R16,0×100′ ?
    Я где-то понимаю что это связано с 2Кб страницей, но Z может адресовать 64Кб напрямую, тем более метка metfont находится рядом !
    В чём причина ошибки компиляции, дальнейшее увеличение программы вводит в ступор ?

    .DEF rga=r16

    Здесь вся программа.

    print: ld rga,y+ ;печать символа из Y, по адресу X
    printat: ldi rgb,8 ;печать символа в rga, по адресу X
    mul rga,rgb
    movw zl,r0
    ldi rga,low(metfont)*2
    add zl,rga
    ldi rga,high(metfont)*2
    adc zh,rga
    fontm01: lpm rga,z+
    fontm04: st x+,rga
    dec rgb
    breq fontm03
    cpi rga,0
    brne fontm01
    fontm02: lpm rga,z+
    cpi rga,0
    brne fontm04
    fontm03: ret
    metfont: .include «font_4x8.asm»

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