6.17. Подключение внешнего статического оперативного запоминающего устройства
Разработчики устройств на микроконтроллерах постоянно сталкиваются с задачей временного хранения данных, однако объема внутреннего SRAM (Static Random Access Memory — статическое запоминающее устройство с произвольным доступом, далее — память) зачастую недостаточно.
Для облегчения решения такой задачи микроконтроллер AT90S8515 (AT90LS8515) снабжен интерфейсом для подключения внешней памяти объемом до 64 Кбайт.
Интерфейс для подключения внешней памяти
Интерфейс включает в себя:
• порт А: шина младшего байта адреса / шина данных;
• порт С: шина старшего байта адреса;
• контакт ALE (Address Latch Enabled): разрешение фиксации адреса;
• контакты RD и WR: стробы записи и считывания.
Для инициализации работы интерфейса надо установить (в единицу) бит SRE (Static RAM Enable — разрешение работы с внешней памятью) регистра MCUCR (MCU Control Register — регистр управления микроконтроллера). Теперь при использовании стандартных команд работы с памятью (команды LD, LDD, LDS, ST, STD и STS) микроконтроллер может обращаться к ячейкам памяти, адреса которых лежат в диапазоне $0060…$FFFF (знак «$» используется в тексте для обозначения шестнадцатеричных чисел, так же, как это должно быть записано в программе на Ассемблере для AVR Studio; указанный диапазон адресов в десятичной форме будет иметь вид 96…65535).
Свободно размещать данные в памяти микроконтроллера AT90S8515 можно, начиная с адреса $0060, как при работе с внутренней оперативной памятью, так и при использовании интерфейса внешней памяти, поскольку адреса оперативной памяти $0000…$001F заняты регистрами общего назначения r0…r31 (десятичное число 31 равно шестнадцатеричному числу $001F), адреса $0020…$005F отведены для регистров ввода-вывода.
Замечание.
Если до инициализации интерфейса линии порта А, порта С и контакты PD6 (WR) и PD7 (RD) порта D были запрограммированы как линии ввода или вывода, установка бита SRE перепрограммирует эти линии для работы с внешней памятью.
Если бит SRE сбросить (в ноль), работа с внешней памятью прекращается, устанавливается обычный режим работы портов А, С и контактов PD6 и PD7 порта D, а область оперативной памяти, к которой может обращаться микроконтроллер AT90S8515 ограничивается диапазоном S0060…S025F (всего 512 байт).
Примечание. При работе в диапазон адресов, например, S2220…S222F количество адресуемых ячеек не 15 ($222F-$2220=$F), а 16 ($F+1), так как обращение происходит и к ячейке с адресом $2220.
Порт А служит как для формирования младшего байта адреса, так и для передачи данных, в то время, как порт С поддерживает на своих линиях старший байт адреса в течение всего цикла обращения к внешней памяти.
Для фиксации младшего байта адреса необходимо использовать дополнительный элемент хранения с записью положительным уровнем. Для этого подойдет 8-разрядный регистр 74HCT573N. При высоком уровне сигнала на контакте ALE младший байт адреса, сформированный на восьми линиях порта А, записывается в регистр. При низком уровне на контакте ALE через порт А передаются данные. Сигналы на контактах WR и RD микроконтроллера активны только во время обращения к внешней памяти.
Обращение к внутренней памяти происходит за три цикла микроконтроллера.
Обращение к внешней памяти происходит за три цикла микроконтроллера, однако можно удлинить время обращения на один дополнительный цикл, установив бит SRW (External SRAM Wait State — состояние ожидания внешней памяти) в регистре MCUCR.
Пример подключения внешней оперативной памяти к микроконтроллеру AT90S8515
Фрагмент схемы, в котором реализовано подключение внешней памяти 8Кх8 (8 Кбайта) к микроконтроллеру, приведен на рис. 6.20. Для подключения с минимальным количеством дополнительных элементов пригодны микросхемы памяти, имеющие двунаправленную 8-разрядную шину данных (D0…D7), шину адреса и инверсные входы управления записью и считыванием (WE, ОЕ).
На фрагменте схемы к микроконтроллеру AT90S8515 (DD2) подключена микросхема внешней памяти НМ62256 (DD5) с организацией 32Кх8, из которых используется только 8Кх8, что требует использования тринадцати линий адреса (А0…А12) из пятнадцати имеющихся. Свободные контакты А13 и А14 шины адреса микросхемы DD5 соединены с общим проводом. Замечу, что эти контакты можно было соединить и с линией питания, что никак не сказалось бы ни на работе, ни на программе, физически использовалась бы другая область ячеек микросхемы памяти.
Не использующиеся в приведенной схеме для работы с внешней памятью линии адреса микроконтроллера А13, А14 и А15 могут использоваться, скажем, для обращения к дополнительным регистрам, обеспечивающим передачу данных к внешним устройствам.
Рис. 6.20. Подключение внешней памяти
Установка адреса
По высокому уровню сигнала на контакте ALE микроконтроллера в регистр 74НСТ573 (DD3) производится запись младшего байта адреса, сформированного микроконтроллером на линиях A/D0…A/D7. Записанный в регистр младший байт адреса появляется на выходах регистра, соединенных с адресными входами микросхемы памяти (линии А0…А7).
На контактах порта С микроконтроллера формируется старший байт адреса (линии А8…А15).
Регистр 1533HP33 является функциональным аналогом регистра 74НСТ573, однако они выполнены по разной технологии. Опыт использования регистра 1533ИРЗЗ для фиксации младшего байта при работе микроконтроллера с внешней памятью показал, что при записи — считывании массива (0, 1…., 254, 255) появляются сбои, число которых сокращается при подключении конденсатора (десятки пикофарад) между контактом С регистра и общим проводом, использование же регистра 74НСТ573 полностью решало проблему и без конденсатора. Поэтому следует аккуратно относиться к заменен регистра.
Для качественного тестирования памяти удобно использовать подключение контроллера к компьютеру, используя UART микроконтроллера (Universal asynchronous receiver-transmitter — универсальный асинхронный приемопередатчик).
Считывание данных из внешней памяти
Если производится считывание данных из внешней памяти, то после того, как адрес установлен, линии порта А переводятся в режим ввода данных в микроконтроллер, на линии RD микроконтроллер формирует импульс отрицательной полярности. По сигналу RD шина данных D0…D7 микросхемы памяти DD5 переводится в режим вывода данных, и по линиям A/D0…A/D7 из адресуемой ячейки в порт А микроконтроллера передаются хранимые данные.
Запись данных во внешнюю память
Для записи данных во внешнюю память, после того, как адрес установлен, линии порта А переводятся в режим вывода данных из микроконтроллера, на линии WR микроконтроллер формирует импульс отрицательной полярности. По сигналу WR шина данных D0…D7 микросхемы памяти DD5 переводится в режим ввода данных, и по линиям A/D0…A/D7 из порта А микроконтроллера в адресуемую ячейку внешней памяти передаются данные для хранения.
Программный доступ к оперативной памяти
Начнем с простой программы обращения к оперативной памяти. Для этого необходимо, чтобы на вашем компьютере была установлена среда разработки AVR Studio корпорации Atmel (), в которой создаются и отлаживаются программы для микроконтроллеров AVR.
Кроме того, в любую программу обязательно включается файл, определяющий регистры и имена битов микроконтроллера, для которого пишется программа. Для микроконтроллера AT90S8515 — это файл 8515def.inc, для AT90S4433 — файл 4433def.inc. Все файлы с расширением. inc на момент написания главы хранились в самораспаковывающемся файле avr000.exe.
Найти ссылки на файл для установки AVR Studio 3.хх и на файл avr000.exe можно по адресу: .
Инструкции и картинки, размещенные далее, соответствуют версии AVR Studio 3.51.
Создайте директорию, в которой будут размещаться файлы вашего проекта, например, C: \Avr\Try. Запустите скачанный файл avr000.exe, а появившийся в результате распаковки файл 8515def.inc скопируйте в созданную директорию C: \Avr\Try.
Запустите AVR Studio, на экране появится окно AVR Studio (обрамленное сверху синей полосой окно с надписью AVR Studio).
Создадим новый проект: Project | New.
Запись вида «Project | New» обозначает, что надо установить указатель мышки на меню Project, расположенном в первой строке окна AVR Studio, в открывшемся окне установить указатель мышки на слове New и нажать левую клавишу мышки. Далее вместо слов «нажать левую клавишу мышки» будем употреблять слова «щелкнуть» или «выбрать»; если требуется нажатие правой клавиши мышки — будем использовать слова «щелкнуть правой клавишей мышки».
Начальная строка окна содержит его наименование (AVR Studio…), только следующая строка доступна при работе, ее будем считать первой строкой окна.
В появившемся окне Select new project (рис. 6.21):
• введите имя проекта в строке ввода Project name (например, Memory);
• впишите вручную созданную в Windows директорию для проекта C: \Avr\Try в строку ввода Location или найдите эту директорию в дереве каталога, щелкнув по кнопке справа от строки ввода;
• выберите строку AVR Assembler для определения типа проекта в окне Project type;
• щелкните мышкой по кнопке ОК.
Рис. 6.21. Окно Select nez project
Теперь на экране активно окно Project: Memory (рис. 6.22).
Рис. 6.22. Окно Project
Создадим новый файл, который будет содержать программу на Ассемблере:
• щелкните правой клавишей мышки в окне Project: Memory;
• в появившемся окне выберите строку Create New File;
• в строку Name открывшегося окна Create new file (рис. 6.23) введите имя файла программы, например, SRAM.asm, затем щелкните по кнопке ОК.
Рис. 6.23. Окно Create new file
В появившемся окне SRAM.asm наберите следующую программу, не игнорируя знаков препинания в начале строк.
Команды лучше писать строчными буквами, тогда они выделяются цветом. В тексте программы команды и метки выделены прописными буквами только для снижения вероятности ошибок при наборе программы. В AVR Studio 3.51 нет различия между прописными и строчными буквами. Между меткой, командой и ее операндами удобно вставлять знак табуляции вместо пробела, тогда программа лучше читается.
; Программа SRAM.asm
; Обращение к внешней памяти
; =================
.INCLUDE "8515def.inc"
; Вставка содержимого файла 6515def.inc в нашу программу
; Файл 8515def.inc не обязательно располагать в директории
; проекта тогда следует указать и путь к нему, например:
;.include "d:\Def_dir\8515def.inc"
.DEF tmp =r16 ; Регистру r16 присвоить имя tmp
; (значение переменной tmp будет храниться в
; регистре общего назначения r16)
.DEF cnt =r16 ; Переменная cnt — счетчик цикла
; Замечание: одному регистру в программе могут присваиваться
; разные имена, хотя в данном случае в этом и не было
; необходимости, микроконтроллер имеет 32 регистра, но; переменных в программе обычно гораздо больше
.EQU ArSize =10
; Константа ArSize=10 — используем эту константу
; для определения размера массива, записываемого в память
.EOU aArBgn =$0170
; Используем константу аАгВhn как адрес начальной ячейки; для хранения массива
RESET: IN tmp,HCUCR
; Ввод содержимого регистра MCUCR в регистр tmp. Физические
; адреса регистров MCUCR,ZL,ZH и константы SRE и SRW,
; используемые в программе, определены в файле 8515def.inc
; если по директиве. include файл 8515def.inc не будет найден,
; команды с операндами MCUCR,ZL,ZH,SRE и SRW дадут ошибки
ORI tmp,(1<<SRE)+(1<<SRW)
; Операция V (логическое ИЛИ) содержимого регистра и константы.
; Для работы с внешней памятью надо установить
; биты SRE и SRW в регистре MCUCR (установка SRW — если
; нужно продлить состояние ожидания; для "медленной" микросхемы памяти).
; Открыв файл 8515def.inc, мы обнаружим, что SRE=7, SRW=6,
; значит величина, которую надо занести; в регистр MCUCR в двоичном коде: 11bb bbbb,
; Ь — это значения битов, которые мы не должны изменять; запись (1<<SRE)+(1<<SRW) равна сумме
; единицы, сдвинутой влево SRE (или 7) раз (=1000 0000),
; и единицы, сдвинутой влево SRW (или 6) раз (0100 0000)
; Сумма 1000 0000+0100 0000=1100 0000
; операция V (bbbb bbbb VI100 0000 =11bb bbbb)
; дает нужный результат
OUT MCUCR,tmp;
Вывод содержимого регистра tmp в регистр MCUCR
LDI ZL,low(aArBgn)
LDI ZH,high(aArBgn)
; LDI — операции загрузки в регистр однобайтной константы
; аАгВgn — двухбайтная константа — должна быть загружена
; в пару однобайтных регистров ZH: ZL, которые составляют
; двухбайтный регистр Z. Регистр Z и аналогичные ему двухбайтные
; регистры X и Y используются в операциях с памятью (st,Id….).
; aArBgn=$0170, в ZL загрузится low($0170), т. е., младший байт $70
; в ZH загрузится high($0170), то есть, старший байт $01
; в Z (или в ZH: ZL) образуется двухбайтное слово $01:$70.
; то есть, адрес, выбранный для начальной ячейки массива
LDI cnt,ArSize; Загрузить в cnt размер массива
NEXT: ST Z+,cnt
; ST — операция записи содержимого регистра cnt в ячейку памяти,
; адрес которой — в регистре Z, знак "+" после Z — значит
; с последующим увеличением адреса в регистре Z на единицу.
; В первом цикле данные занесутся по адресу $0170, a Z = $0171:
; во втором цикле — по адресу $0171, Z = $0172 и т. д.
; в память пишется состояние счетчика циклов
DEC cnt
; Уменьшить содержимое регистра cnt на единицу
NOP ; Команда добавляет один пустой цикл
NOP ; То же
BRNE NEXT
; Если бит (флаг) Z в регистре состояния процессора SREG
; (не путать бит Z регистра SREG с регистром адреса Z)
; не установлен (не равен) — перейти на команду с меткой NEXT:
; последняя команда, воздействующая на бит Z
; регистра SREG — DEC cnt. Команды N0P введены для
; демонстрации отсутствия их влияния на бит Z
; если вместо них вставить, например, команду INC tmp, влияющую
; на состояние бита Z, работа программы будет нарушена
; Далее — блок считывания данных из памяти:
LDI ZL,low(aArBgn)
LDI ZH.high(aArBgn)
LDI cnt.ArSize
RD_BLK:
LD tmp, Z+
; Здесь какие-то действия с tmp, например,
; передача в компьютер через UART
DEC cnt
BRNE RD_BLK
RJMP RESET ; Перейти на команду с меткой RESET:
Вы заметили, что имена регистров и константы, определенные во включаемом в программу файле 8515def.inc довольно длинны? Не думайте, что, сокращая используемые имена до одной-двух букв, можно сэкономить время: через пару недель вы не разберетесь в собственной программе! А вот имена MCUCR, SRE, являясь сокращениями английских наименований MCU control register и Static RAM Enable, быстро запоминаются. Те же рекомендации можно предложить и для определяемых вами имен: и в чужой программе можно понять, что ArSize это Array Size (размер массива), a aArBgn — начальный адрес массива.
Отладка программы
Ассемблируем программу: Project|Assemble. Обращайте внимание на наличие горячих клавиш: на строке Assemble указана горячая клавиша F7. Так как эта операция повторяется часто, удобнее пользоваться именно клавишей F7.
При первом ассемблировании автоматически открывается окно Simulator options. В строке Device этого окна надо выбрать микроконтроллер, на который ориентирована программа, в нашем случае это AT90S8515 with external SRAM. Затем нажмите кнопку ОК.
Замечание: для микроконтроллеров с программно-аппаратной поддержкой подключения внешней памяти симулятор программируется как для поддержки внешней памяти (выбрать AT90S8515 with external SRAM), так и для работы только с внутренней памятью (выбрать AT90S8515). Последовательность команд в программе, задающая режим работы с внешней памятью, достаточна лишь для самого микроконтроллера; на работу в режиме отладки в AVR Studio влияет именно выбор устройства в окне Simulator options.
В окне Simulator options следует установить частоту кварцевого резонатора, использующегося вместе с микроконтроллером. Частоту можно либо выбрать из предлагаемого списка, либо ввести вручную, если надо задать точное значение, например, 6,425 МГц. Это позволит симулятору корректно выводить время работы программы не только в циклах микроконтроллера, но и в единицах времени.
Вызвать окно Simulator options вновь можно только после успешного ассемблирования в режиме отладки (переход в режим отладки — по нажатию клавиши F11).
В результате ассемблирования появится окно Project output.
Если программа набрана без ошибок, а файл 8515def.inc находится в рабочей директории проекта — в последней строке окна Project output будет сообщение об отсутствии ошибок (Assembly complete with no errors).
Интересной информацией в этом окне является:
• сообщение о включении файла 8515def.inc в программу;
• предостережение: C: \Avr\Jry\SRAM.asm(15): warning: Register already defined by the.DEF directive — предупреждение о том, что в строке (15) программы директивой. DEF назначается имя регистру, которому уже назначено другое имя; хотя программа невелика, считать строки нет необходимости, достаточно щелкнуть мышкой на любой строке, чтобы увидеть ее номер, а также номер колонки, в правом нижнем углу всего окна AVR Studio: Lnl5, Со15;
• общий объем памяти, занимаемый программой (Total). Обратите внимание на то, что объем занимаемой памяти указывается в словах (words), каждое слово в памяти программы микроконтроллера двухбайтное, поэтому размер программы в байтах будет в два раза больше.
Займемся пошаговой отладкой программы: Debug|Trace into (F11).
Замечание. При отладке (смотрите меню Debug) удобно пользоваться горячей клавишей F11, когда требуется пошаговая отладка каждой команды, если Вы уже отладили часть программы в предыдущих сеансах работы, установите курсор на команду, с которой хотели бы продолжить отладку и нажмите Ctrl+F10.
На левой границе окна против команды, которая будет выполняться, устанавливается стрелка — указатель выполнения команд.
Первая команда:
in tmp.MCUCR
Для просмотра содержимого MCUCR выполнить: View|New IO view, в открывшемся окне IO выполнить: CPU | Control register (щелкнуть по знаку «+» слева в строке CPU, затем щелкнуть по знаку «+» слева в открывшейся строке Control register).
В строке Control register индицируется состояние этого регистра 0x00 и адрес этого регистра в оперативной памяти микроконтроллера 0x35 (это одна из форм записи шестнадцатеричного числа, соответствующая записи $35). Ниже можно увидеть состояние интересующих нас битов SRE и SRW, а при необходимости установить состояние какого-либо бита прямо здесь, например, бита SM (sleep mode), состояние которого не влияет на работу этой программы. Для этого установите галочку (щелкните) в квадратике, помеченном цифрой 4, находящемся правее слова SM. Заметьте, состояние регистра (строка Control register) изменилось на 0x10.
Для проверки состояния регистра tmp выполним:
• Watch|Add watch;
• в строке ввода появившегося окна Watch введите tmp.
Дважды щелкнув по величине, хранимой в регистре (0x00), можно ввести с клавиатуры новое значение. Щелкнув правой клавишей мышки в окне Watch на имени переменной tmp, можно изменить формат вывода состояния регистра на десятичный, добавить новую переменную для просмотра или удалить выделенную переменную.
Теперь, когда отладка началась, можно изменить опции симулятора: Options|Simulator options. После их изменения программу надо вновь ассемблировать (F7) и перейти к пошаговой отладке (F11).
Выполним команду, нажав кнопку F11 на клавиатуре. Состояние переменной tmp (окно Watches) стало равным состоянию регистра MCUCR (0x10). Если окно Watches развернуть, то можно увидеть тип переменной, а также в каком регистре она хранится.
Следующий шаг (нажать F11 — далее просто нажать F11) — в переменной tmp величина 0xD0 или 1101 0000 в двоичном представлении, два старших бита установлены, остальные биты остались без изменения.
Нажать F11 — теперь в MCUCR установлены биты SRE и SRW, работа с внешней памятью разрешена.
Добавим две переменные ZH и ZL в окно переменных (Выполнить Watches|Add (или в открытом ранее окне Watches щелкнуть правой клавишей мышки, в появившемся окне выбрать Add watch), ввести имя ZH, повторить процедуру, введя имя ZL).
Откроем окно просмотра состояния процессора: View | Processor.
Дважды нажать F11, наблюдая за изменением переменных ZH и ZL в окне Watches. В окне Processor наблюдайте состояние Z-register.
Замечание. В окне Watches удобно группировать переменные в соответствии с размещением их в программе. Для одной части программы переменные можно разместить в группе, выбрав страницу окна Watch 1 (смотрите нижнюю строку окна Watches), для другой части программы разместите переменные на странице Watch2 и т. д.
Если вы вводите группу переменных, после набора имени первой переменной дважды нажмите клавишу ENTER — теперь можно вводить имя следующей переменной, после набора имени последней переменной нажмите ENTER один раз.
Добавить переменную cnt в окно Watches, нажать F11.
Ввод имени константы в окно Watches в AVR Studio 3.51 может привести к неустранимой ошибке (например, константа aArBgn из нашей программы). То же может произойти, если Вы будете удалять переменные, пользуясь клавишей Del. Это внутренние ошибки AVR Studio 3.51.
Следующая часть программы — это цикл записи элементов массива в память. Откроем окно просмотра содержимого памяти (выполнить View|New memory view). В появившемся окне Memory будем просматривать оперативную память (выбрать режим Data в первой строке окна слева). В первой строке окна справа установлен адрес ячейки оперативной памяти 0x0060, расположенной в первой строке области просмотра памяти. Наша программа должна записывать массив, начиная с адреса 0x0170, для того, чтобы в окне Memory было удобно наблюдать занесение данных в память, переместим просматриваемую область памяти так, чтобы область просмотра памяти начиналась адресом 0x0170. Для этого в адресе 0x0060 в первой строке окна справа заменим 0060 на 0170, либо воспользуемся вертикальным скроллером окна Memory.
Теперь на экране находятся четыре окна, которые нам надо контролировать. Чтобы видеть все окна, надо выбрать их размеры и место размещения. Место размещения можно изменять, для чего, установив указатель мышки на наименование окна (например, Watches), нажать левую клавишу мышки и, не отпуская нажатую клавишу, перемещать мышку, передвигая окно по экрану к нужной позиции. Переместив окно, клавишу мышки отпустить.
Выбор размера окна, например, для окна Watches, начнем с уменьшения ширины колонок. Установите указатель мышки в первой строке окна на вертикальной разграничительной линии между наименованиями колонок Watch и Value. При попадании на разделительную линию форма курсора изменится, приобретя вид жирной вертикальной линии с горизонтальными стрелками, направленными вправо и влево, как на рис. 6.24. Нажмите левую клавишу мышки и, не отпуская нажатую клавишу, перемещайте вместе с мышкой вертикальную разделительную линию влево, пока колонка не сузится до минимально необходимой ширины. На рисунке третья колонка, не представляющая интереса в данной программе, максимально сужена. Установите указатель мышки на любую стенку или любой угол окна (при этом должны появиться стрелки, направленные в противоположные стороны), манипулируя мышкой так же, как при перемещении разделительной линии, измените размер окна. Если окно развернуто полностью, изменить его размеры можно после нажатия кнопки «Восстановить» (в строке наименования окна средняя кнопка справа, помечена квадратами).
Рис. 6.24. Вид окна
На рис. 6.25 представлены окна, использующиеся при отладке программы SRAM.asm. Размеры и размещение окон позволяют одновременно наблюдать происходящие в них изменения.
Рис. 6.25. Вид окна
Все до сих пор выполнявшиеся команды занимали по одному циклу микроконтроллера, мы выполнили 6 команд, что соответствует состоянию счетчика циклов (Cycle counter 00000006) в окне Processor.
Нажмите F11. В ячейку памяти 0x0170 записалось число $0А (окно Memory). Состояние счетчика циклов увеличилось лишь на два (окно Processor). Для команды ST такое увеличение соответствует обращению лишь к внутренней памяти микроконтроллера. В нашем случае обращения к внешней памяти количество циклов должно было увеличиться на четыре, так как один дополнительный цикл требуется для обращения к внешней памяти и еще один цикл, так как мы задали режим работы с дополнительным циклом ожидания установкой бита SRW в регистре MCUCR.
При обращении к внешней памяти количество циклов, подсчитанное симулятором, оказывается ошибочным.
Нажмите F11 четыре раза, наблюдая за состоянием счетчика циклов. При выполнении условия команды BRNE (если бит Z в окне Processor | Flags сброшен) состояние счетчика циклов увеличивается на два. Обратите на это внимание, так как в описании для этой команды указано число циклов 1/2 (один или два). Один цикл потребуется при невыполнении условия команды (смотрите ниже).
Теперь мы перешли ко второму циклу записи массива в память. Ничего нового в каждом отдельном цикле мы не увидим, поэтому воспользуемся сочетанием клавиш Ctrl+F10 (нажать клавишу Ctrl, не отпуская ее, нажать F10).
По каждому нажатию Ctrl+F10 контролируйте переменную cnt в окне Watches. Число нажатий — пока переменная cnt не станет равной единице. При каждом нажатии можно также следить за записью данных в память (окно Memory), адресом, по которому производится запись (окно Processor|Z-register и переменные ZH, ZL в окне Watches).
Если в окне Processor | Stop watch нажать кнопку Clear, то после следующего нажатия Ctrl+F10 правее кнопки Clear появится информация о длительности одного цикла.
Теперь, когда мы снова остановились на команде ST, a cnt=1, воспользуемся клавишей F11.
Дважды нажать F11. Выполнение команды DEC привело к нулевому результату в регистре cnt, поэтому бит Z установлен (окно Processor | Flags).
Дважды нажать F11. Состояние бита Z не изменилось, так как команды NOP не влияют на него.
Нажать F11. Так как бит Z установлен, выполнение команды BRNE не приводит к переходу на метку NEXT, выполняется переход к следующей команде. Состояние счетчика циклов при невыполнении условия команды BRNE (когда бит Z в окне Processor|Flags установлен) увеличивается на единицу.
Замечание. Команду BRNE лучше ставить непосредственно за командой, определяющей, как работать команде BRNE (в нашем случае правильнее было бы разместить перед ней команду DEC cnt).
Отладка блока считывания данных из памяти не отличается от отладки блока записи в память. Вы можете самостоятельно отладить эту часть программы.
Замечу лишь, что блок будет отлаживаться нормально, так как в память записывалось состояние счетчика циклов cnt. Попробуйте перед началом отладки блока считывания из памяти занесенное в ячейку 0x0170 значение 0А заменить вручную на значение 01 и отладить программу. Выполнится лишь один цикл считывания, так как переменные cnt и tmp хранятся в одном регистре r16.
До начала цикла в регистр r16 командой LDIcnt,ArSize записывается начальное состояние счетчика циклов (10 или $0А). Затем в тот же регистр r16, как в переменную tmp, из памяти считывается значение 01 (команда LDtmp,Z+), далее содержимое регистра r16, как переменной cnt, декрементируется (DECcnt), становясь равным нулю и приводя к завершению цикла.
При присвоении одному регистру нескольких имен переменных, исключайте использование этих имен в одних и тех же блоках программы.
Завершая работу, закрываем окно AVR Studio, в ответ на запрос «Сохранить изменения в проекте Метогу. арr?» нажимаем кнопку Да.
Теперь в директории C: \Avr\Try\ находится файл проекта Метогу. арг, файл программы SRAM.asm, включенный в проект, файл 8515def.inc, а также файл SRAM.hex, который может быть загружен с помощью программатора и соответствующего программного обеспечения во флеш-память микроконтроллера.
При следующем запуске AVR Studio наш проект можно вызвать из меню File. Здесь можно выбрать один из файлов, с которыми мы уже работали. Для нашего проекта следует выбрать строку C: \Avr\Try\Memory.apr. Другой вариант — выполнить Project|Open и в появившемся каталоге выбрать необходимый файл проекта (Метогу. арг). В открывшемся окне Project дважды щелкните на имени файла программы SRAM.asm. Теперь все готово к новому сеансу работы с нашим проектом.
Подключение буферов вывода с использованием адресного пространства внешней памяти микроконтроллера AT90S8515.
В предыдущем примере рассматривалось подключение внешней памяти к AT90S8515. Из четырех портов микроконтроллера порты А и С, а также два контакта порта D заняты обслуживанием внешней памяти.
Как быть, если в дополнение к подключению внешней памяти необходимо организовать передачу полутора — двух десятков управляющих сигналов от микроконтроллера к внешним устройствам? Возможный вариант — использование необходимого количества 8-разрядных регистров (буферов) с обращением к ним, как к ячейкам памяти, расположенным в общем адресном пространстве внешней памяти. На рис. 6.26 — фрагмент схемы, реализующий такое подключение. Данный фрагмент и фрагмент, изображенный на рис. 6.20, являются частями одной схемы, а шина BUS1 является общей для обоих рисунков.
Три адресные линии А13…А15 остались свободными при подключении внешней памяти объемом 8Кх8, используем их для записи данных в три 8-разрядных регистра DD6…DD8.
Использование дополнительной микросхемы дешифратора адреса А13…А15 позволило бы подключить до восьми (23) регистров.
Входные контакты D0…D7 регистров DD6…DD8 подключены к порту А микроконтроллера (линии A/D0…A/D7). К выходным контактам 00…07 регистров можно подключить до 24-х линий внешних устройств (линии В0…В23). Выходы регистров всегда активны и не переводятся в третье состояние, поэтому контакты ЕО регистров соединены с общим проводом.
Рис. 6.26. Электрическая принципиальная схема
Запись данных в регистры происходит при появлении высокого уровня на их контактах С. Микроконтроллер же формирует низкий уровень записи во внешнюю память (сигнал WR). В схеме для формирования сигнала записи высокого уровня применены элементы ИЛИ-HE (микросхема DD4).
В этой схеме сигнал высокого уровня на линии WB3 для записи данных в регистр DD6 образуется при низких уровнях сигналов на линиях А15 и WR. То есть, чтобы запись произошла в один регистр DD6, необходимо, чтобы на линиях WR и А15 был низкий уровень, на линиях А13 и А14 — высокие уровни.
Одновременное присутствие низких уровней на всех четырех линиях (WR, А13…А15) вызовет параллельную запись данных в три регистра.
Приведенная схема подключения регистров требует тщательного подхода к выбору адресов при написании программы.
Запись в регистр DD6 должна происходить при А15=0, А14=1 и А13=1, то есть, в двоичном коде адрес
регистра DD6: 011х хххх хххх хххх,
регистра DD7: 101х хххх хххх хххх,
регистра DD8: 110х хххх хххх хххх.
Для того, чтобы данные не записывались в регистры, адрес должен иметь вид:
111х хххх хххх хххх, где х — состояние бита (0 или 1) не имеет значения.
Хотя состояния битов х не имеют значения, они должны быть определены. Это значит, что при записи в какой-либо регистр на контактах А0…А12 микросхемы памяти (рисунок bvn_Pic1.gif) будет сформирован адрес, а в ячейку с этим адресом будет произведена запись того же значения, которое посылается в регистр. Надо позаботиться, чтобы ячейка с таким адресом не использовалась для хранения данных.
Удобно определить все биты х равными единице. Тогда адреса регистров примут вид:
адрес DD6: 0111 1111 1111 1111 или $7FFF,
адрес DD7: 1011 1111 1111 1111 или SBFFF,
адрес DD6: 1101 1111 1111 1111 или SDFFF,
а в ячейке памяти с адресом xxx1 1111 1111 1111 нельзя хранить данные, линии адреса А13…А15, соответствующие битам ххх в этом адресе, к микросхеме памяти не подключены.
Поэтому, какими бы они ни были, запись производится в одну и ту же ячейку. Однако при записи в память ххх следует определить как 111 для того, чтобы не произошло параллельной записи в регистры. Значит, данные нельзя хранить в ячейке 1111 1111 1111 1111 или SFFFF.
Для хранения данных доступны ячейки памяти с адресами
1110 0000 0000 0000… 1111 1111 1111 1110 или $E000…$FFFE.
В разделе «Интерфейс микроконтроллера AT90S8515 для подключения внешней памяти» была обозначена нижняя граница доступной для безопасного хранения данных области памяти: адрес $0060.
С учетом этого ограничения для безопасного хранения данных доступны ячейки памяти с адресами $E060…$FFFE.
Есть более привлекательный вариант распределения памяти: при записи в регистр параллельно производить запись в соответствующую этому регистру ячейку памяти. При этом всегда можно проверить, что именно записывалось в регистр, считав данные из ячейки памяти.
Выберем для регистров следующие адреса:
адрес DD6: 0111 1111 1111 1101 или $7FFD,
адрес DD7: 1011 1111 1111 1110 или $BFFE,
адрес DD6: 1101 1111 1111 1111 или $DFFF.
Теперь для безопасного хранения данных доступны ячейки памяти с адресами $E060…$FFFC, а из ячеек с адресами $FFFD, $FFFE и $FFFF можно считать данные, которые были записаны в регистры DD8, DD7 и DD6 соответственно.
Считывая данные из ячеек, соответствующих регистрам, мы получаем информацию о том, что записывалось в эти регистры. Следовательно, считывание данных правомерно только после первой записи в регистры. Поэтому рекомендую в начале программы записать исходные данные в регистры.
Состояние выходов регистров может не соответствовать считанным из соответствующих ячеек памяти данным
• из-за неисправности регистров;
• из-за возможного соединения выхода регистра с общим проводом во внешнем устройстве.
Создадим новую директорию C: \Avr\Buff\. В AVR Studio создадим новый проект, назовем его Buffers, воспользуемся уже созданной директорией C: \Avr\Buff\, в проекте создадим новый файл BUFSRAM.asm, в котором напишем следующую программу.
; Программа BUF_SRAM.asm
; обращение к внешней памяти, запись данных в буферы
; ========================
.INCLUDE "С: \Avr\Try\8515def.inc”
; Вставка содержимого файла 8515def.inc, находящегося
; в директории C: \Avr\Try в нашу программу
.DEF tmp=r16 ; Регистру r16 присвоить имя tmp
; (значение переменной tmp будет храниться в
; регистре общего назначения г16)
.DEF cnt = r17 ; Переменная cnt — счетчик цикла
RESET: IN tmp,MCUCR
; Ввод содержимого регистра MCUCR в регистр tmp
ORI tmp,(1<<SRE)
; Установка бита SRE (режим работы с внешней памятью)
; Бит SRW не устанавливаем; (работа без дополнительного цикла ожидания)
OUT MCUCR,tmp
; Вывод содержимого регистра tmp в регистр MCUCR
;=========ЗАГРУЗКА ДАННЫХ В БУФЕРЫ
.EQU wrBuf1=$7FFD
; Адрес для записи данных в буфер 1 (по схеме — элемент DD6)
.EQU wrBuf2=$BFFE
; Адрес для записи данных в буфер 2 (DD7)
.EQU wrBuf3=$DFFF
; Адрес для записи данных в буфер 3 (DD8)
.EQU rdBuf1=$FFFD
; Адрес для считывания данных из ячейки, соотв. буферу 1
.EQU rdBuf2=$FFFE
; Адрес для считывания данных из ячейки, соотв. буферу 2
.EQU rdBuf3=$FFFF
; Адрес для считывания данных из ячейки, соотв. буферу 3
LDI tmp,$AA ; Загрузка константы в tmp
STS wrBuf1, tmp
; Сохранение содержимого регистра tmp в буфере 1
LDI tmp, $55 ; Загрузка константы в tmp
STS wrBuf2, tmp
LDI tmp,$71 ; Загрузка константы в tmp
STS wrBuf3, tmp
;======ЧТЕНИЕ / ИЗМЕНЕНИЕ ДАННЫХ В БУФЕРАХ
LDS tmp, rdBuf1
; Передача данных из ячейки с адресом rdBuf1 в регистр tmp
ORI tmp, (1<<7)+(1<<6)+(1<<5)+(1<<4); Установка битов 4.. 7
STS wrBuf1, tmp
; Сохранение содержимого регистра tmp в буфере 1
LDS tmp, rdBuf2
ORI tmp, 0b11110000
; Установка тех же битов, что и для буфера 1,
; только константа представлена в двоичной форме,
; для обозначения двоичной константы 1111 0000; перед ней ставят символы "ноль” и “Ь”
STS wrBuf2, tmp
; Сохранение содержимого регистра tmp в буфере 2
LDS tmp, rdBuf3
ANDI tmp, 0b11110000 ; сброс тех же битов
STS wrBuf3, tmp
; Сохранение содержимого регистра tmp в буфере 2
; ===== ЗАГРУЗКА В ПАМЯТЬ ПЕРВОГО МАССИВА
.EQU ArSize =10; Размеры массивов
.EQU aArBgn1 =$Е060
: Используем константу аАrВgn как адрес начальной ячейки для хранения массива 1
LDI ZL,low(aArBgn1)
LDI ZH,high(aArBgn1 )
; Загрузка в регистр Z адреса начала массива 1
LDI cnt,ArSize ; Загрузка в cnt размера массива
LDI tmp,$FF; Загрузка константы в tmp
ARR1: ST Z+, tmp;
; Запись содержимого регистра tmp в ячейку памяти,
; адрес которой — в регистре Z,
; с последующим увеличением на 1 адреса в регистре Z
; В первом цикле содержимое tmp запишется
; по адресу aArBgn1 ($Е060), во втором цикле —
; по адресу aArBgn1+1 ($Е061) и т. д.
INC tmp; Увеличить содержимое tmp на единицу
DEC cnt ; Уменьшить содержимое счетчика циклов на единицу
BRNE ARR1
; Если бит (флаг) Z в регистре состояния процессора SREG
; не установлен — перейти на команду с меткой ARR1:
; ======ЗАГРУЗКА В ПАМЯТЬ ВТОРОГО МАССИВА
.EQU аАгВgn2 =$FFFC
; Используем константу аАгВgn2 как адрес последней ячейки
; для хранения массива 2
LDI ZL,lоw(аАгВgn2+1)
LDI ZH,high(aArBgn2+1 )
; Загрузка в двухбайтный регистр Z адреса конечного
; элемента массива 2, увеличенного на единицу
LDI cot,ArSize ; Загрузка в cnt размера массива
LDI tmp,$03; Загрузка константы в tmp
ARR2: ST -Z, tmp;
; Сначала уменьшается на единицу адрес,
; хранящийся в регистре Z (поэтому в Z загружался
; адрес, увеличенный на единицу), затем по новому
; адресу запишется содержимое регистра tmp
; В первом цикле содержимое tmp запишется; по адресу (аАrВgn2+1)-1, то есть, по адресу аАrВgn2
; во втором цикле — по адресу аАrВgn2-1, и т. д.
DEC tmp ; Уменьшить содержимое tmp на единицу
DEC cnt ; Уменьшить содержимое счетчика циклов на единицу
BRNE ARR2
; Если бит (флаг) Z в регистре состояния процессора SREG
; не установлен — перейти на команду с меткой ARR2:
STOP: RJMP STOP ; Зацикливание программы
; (перейти на команду с меткой STOP:)
Ассемблируем программу (клавиша F7). При обнаружении ошибок проверьте правильность набора программы в строках, содержащих ошибки — номера строк с ошибками и комментарии к этим ошибкам выводятся в окне Project output, появляющемся после ассемблирования.
Директивой. INCLUDE в программу вставлен файл 8515def.inc, использовавшийся в предыдущем примере и находящийся с проектом в разных директориях. Поэтому ассемблирование пройдет нормально, если файл c: \avr\try\8515def.inc еще существует.
Начнем отладку программы, нажав клавишу F11. В появившемся окне Simulator options выберем микроконтроллер: Device | AT90S8515 with External SRAM.
Для контроля отладки вызовем окна переменных, процессора и памяти: View | Watches, View | Processor и View | New Memoiy view.
В окно Watches введем переменные tmp и cnt.
Первые три команды были отлажены в проекте Memory.apr, поэтому переместим курсор на первую команду ЗАГРУЗКИ ДАННЫХ В БУФЕР: LDItmp,$AA и нажмем Ctrl+F10.
Нажмем F11 — в переменную tmp (смотрите в окне Watch) загрузилась константа $АА.
В окне просмотра памяти Memoiy перейдем на адрес S7FFD (адрес wrBuf1), для этого в окне ввода, расположенном в первой строке окна справа, заменим 0x0060 на 0x7FFD. Жмем F11 — в ячейке с адресом 7FFD появились данные АА.
Таким же способом переходим к ячейке SBFFE (wrBu£2), дважды жмем F11, проверяем состояние ячейки, оно равно 55.
Повторяем действия для ячейки SDFFF — ее состояние станет равным 71.
Теперь указатель выполнения команд находится на первой команде ЧТЕНИЯ/ИЗМЕНЕНИЯ ДАННЫХ В БУФЕРАХ:
LDS tmp, rdBuf1
К сожалению, отладчик не позволит правильно отобразить процессы, происходящие в нашей схеме из-за того, что симулятор предполагает подключение к микроконтроллеру лишь внешней памяти размером 65536 Кбайт, поэтому параллельная запись данных в буфер и в соответствующую ячейку памяти никак не отражается симулятором.
Возможный выход — до выполнения команды LDS вручную загрузить в ячейки с адресами rdBuf1, rdBuf2 и rdBuf3 данные, которые должны были в них загрузиться при записи в буферы 1…3 ($АА, $55 и $71 соответственно).
Итак, в окне Memory перейдем к ячейке с адресом $FFFD (rdBuf1). В этом случае удобнее воспользоваться скроллером, переместив его в самый низ, так как интересующие нас ячейки с адресами $FFFD, $FFFE, $FFFF (rdBuf1, rdBuf2 и rdBuf3 соответственно) являются тремя последними ячейками, которые могут отображаться в окне Memory.
Щелкнем на третьей от конца ячейке — ее адрес в виде 0xFFFD отобразится в окошке адреса (справа в первой строке окна Memory). Вместо содержимого этой ячейки (скорее всего 00) введем АА, курсор автоматически перейдет на следующую ячейку с адресом 0xFFFE, введем в нее значение 55, в ячейку OxFFFF — значение 71.
Второй вариант (здесь не реализован, проверьте его отладку самостоятельно): после команд записи типа
STS wrBuf1,tmp
добавлять команды
STS rdBuf1.tmp
то же для буферов 2 и 3. Что при этом произойдет?
В реальной схеме:
• при выполнении первой из команд запись произойдет как в буфер, так и в микросхему памяти (подробности смотрите в описании схемы);
• вторая команда еще раз запишет те же данные в ту же ячейку микросхемы памяти.
При отладке: одинаковые данные запишутся в два адреса: один — имитирующий ячейку микросхемы памяти, второй — имитирующий буфер.
При этом программа несколько удлиняется. Однако после отладки дополнительные команды можно удалить.
Продолжим отладку. Следя за ячейками памяти wrBuf1 wrBuf1 и wrBu3, а также за переменной tmp, выполним 9 команд (9 нажатий F11).
Теперь указатель выполнения команд находится на первой команде ЗАГРУЗКИ В ПАМЯТЬ ПЕРВОГО МАССИВА.
Интерес в записи первого массива в память представляет первый цикл.
В окне Memoiy перейдем к ячейке с адресом 0хЕ060 (aArBgn1). Переведем курсор на команду NCtmp и нажмем Ctrl+F10. В ячейке с адресом 0хЕ060 появился байт данных FF, соответствующий значению переменной tmp.
Нажмем F11 — выполнение команды INC привело к тому, что переменная tmp стала равна нулю, в результате чего установился бит Z (окно Processor). Поэтому если бы команда INC стояла перед командой BRNE, выполнился бы лишь один цикл записи элементов массива в память.
Замечание: SFF+1 = $100, результат — двухбайтная величина, но поскольку регистр может хранить только один байт, в нем остается младшая часть, то есть, ноль. Аналогичным образом $00-1 = $FF, при этом вычитание происходит как бы из двухбайтного числа $100.
Жмем F11 — выполнение команды DEC уменьшило до 9 значение переменной cnt, то есть, результат не нулевой, поэтому флаг Z сбросился (окно Processor).
Отладка аналогичного цикла уже комментировалась, поэтому можно установить курсор на команде с меткой ARR2: и нажать Ctrl+F10. Заметьте, в регистр Z (окно Processor) записался адрес OxFFFD, соответствующий константе aArBgn2+1.
В окне Memoiy перейдем к ячейке 0xFFFD, жмем F11: запись содержимого регистра tmp произошла в ячейку с адресом OxFFFC (aArBgn2), как и было необходимо.
Отладка цикла не требует комментариев.
Установим курсор на последней команде программы и нажмем Ctrl+F10. Проконтролируйте содержимое ячеек памяти, в которые записался массив 2.
Что будет, если убрать последнюю команду? Вообще-то для проверки удобнее заменить ее командой NOP. Ассемблируем программу, нажмем FI 1, установим курсор на последней команде NOP. В окне Processor | Cycle counter заметим число циклов процессора (153). Нажмем F11: выполнение программы опять началось с первой команды, но посмотрите на число циклов процессора: их стало 4206! После того, как все команды нашей программы были извлечены из памяти программ и выполнены, продолжался поиск команд в свободной части памяти программ, при переходе к новой свободной ячейке счетчик циклов процессора увеличивал свое состояние, а вместе с ним и счетчик команд (Processor | Program counter). Максимальное значение, которое может храниться в этом счетчике для микроконтроллера AT90S8515, равно 4095, следующее значение — снова ноль, поэтому и произошел переход в начало программы, а к содержимому счетчика циклов процессора добавились циклы загрузки отсутствующих команд.
Обычно микроконтроллеры работают в бесконечном цикле. Если программа завершила свое выполнение и ожидает прерывания, установите в конце команду зацикливания на себя:
Stop: ijmpStop
Автор: Баранов Вадим Николаевич (E-mail: ).
Подключение внешней памяти 512 Кбайт к микроконтроллеру AT90S8535
На момент написания книги лишь два микроконтроллера серии АТ90 были снабжены интерфейсом подключения внешней памяти: AT90S4414 и AT90S8515. К тому же первый из них уже снят с производства.
В этой же серии есть микроконтроллеры со встроенным аналого-цифровым преобразователем (АЦП). Часто встречающаяся задача, возлагаемая на такие микроконтроллеры, — преобразование аналоговых сигналов в код с записью кода в память в реальном времени. Однако внутренней памяти микроконтроллеров для этого обычно не достаточно, интерфейса для подключения внешней памяти они не имеют.
Здесь представлен возможный вариант подключения внешней памяти большого объема к микросхеме AT90S8535. Микроконтроллер снабжен встроенным 10-разрядным АЦП с восемью входами, коммутируемыми программно. В качестве входов АЦП в микроконтроллере используются контакты порта А, поэтому не будем задействовать этот порт для организации связи с внешней памятью.
На рис. 6.27 представлен фрагмент схемы, реализующий подключение микросхемы статической памяти K6T4008CIB-GB55 (DD5) к микроконтроллеру AT90S8535 (DD6).
Рис. 6.27. Подключение микросхемы статической памяти К6Т4008CIB-GB55
Микросхема памяти K6T4008CIB-GB55 производства фирмы Samsung Electronics по своим функциям не отличается от аналогичной микросхемы НМ62256, использованной в схеме подключения внешней памяти к микроконтроллеру AT90S8515 (смотрите предыдущий пример). Процедура записи и считывания данных для обеих микросхем одинакова.
Одинаково функционируют их двунаправленные 8-разрядные шины данных (контакты IO0…IO7), а также линии
• записи (контакт WR);
• управления шиной данных (контакт ОЕ);
• выбора микросхемы (контакт CS).
Отличие состоит в объеме памяти: 19-разрядная шина адреса (контакты А0…А18) микросхемы K6T4008CIB-GB55 обеспечивает обращение к 219 ячейкам памяти, это значит, что в микросхеме можно хранить до 512 Кбайт данных (512К х 8).
Описание схемы
В таблице описаны линии, по которым микроконтроллер производит управление микросхемой памяти.
В исходном состоянии микроконтроллер устанавливает высокие уровни на линиях WR и RD, низкие уровни на линиях BUF1 и BUF2.
Программно память как бы разбита на 8 страниц по 64 Кбайт в каждой. Три бита адреса из 19-ти микроконтроллер формирует на линиях AR0…AR2, выбирая одну из страниц памяти.
Адрес внутри страницы формируется двумя 8-разрядными регистрами 74НСТ573 (DD3 и DD4).
С этой целью на шину данных (линии В0…В7) через порт В микроконтроллера (контакты РВ0…РВ7) выводится младший байт адреса, после чего микроконтроллер устанавливает высокий уровень на линии BUF2 для записи младшего байта адреса в регистр DD3. Затем на линии BUF2 устанавливается низкий уровень.
Далее на шину данных через порт В выводится старший байт адреса, запись которого в регистр DD4 происходит по установке микроконтроллером высокого уровня на линии BUF1. После записи на линии BUF1 устанавливается низкий уровень.
Теперь на линиях А0…А18, следовательно, и на контактах А0…А18 микросхемы памяти адрес ячейки памяти установлен, можно производить запись или считывание.
Некоторое смущение может вызвать то, что к контактам микросхемы памяти А0…А19 подсоединены линии адреса с другими именами (к контакту А17, например, линия А14). Это сделано для удобства трассировки печатной платы, на которой размещены детали схемы. Для микросхемы памяти безразлично, в какой последовательности выбираются ячейки, последовательность же выбора при записи и при считывании будет одинаковой.
Если вы занимаетесь трассировкой, то представляете, как были перепутаны дорожки до оптимизации соединений линий адреса с контактами адреса микросхемы памяти.
Запись в ячейку
После формирования адреса микроконтроллер выводит через порт В на шину данных В0…В7 байт информации. Затем микроконтроллер устанавливает низкий уровень на линии WR, что приводит к записи байта информации в ячейку памяти. После записи на линии WR устанавливается высокий уровень.
Считывание из ячейки
После формирования адреса микроконтроллер переводит порт В в режим ввода данных в микроконтроллер. Затем на линии RD микроконтроллер устанавливает низкий уровень, что приводит к выводу данных из адресуемой ячейки памяти на шину данных В0…В7. Микроконтроллер через порт В считывает байт данных, установленный на линиях В0…В7, после чего устанавливает высокий уровень на линии RD. Обратите внимание на резисторы R43 и R44.
При программировании линии портов микроконтроллера находятся в высокоимиедансном состоянии, а поскольку входы WE и ОЕ микросхемы памяти также имеют высокий импеданс, наводки на подключенных к ним линиях могут привести как к записи в произвольную ячейку памяти, так и к выводу данных из произвольной ячейки на линии В0…В7. Последняя ситуация опасна, так как запись программы в микроконтроллер производится с использованием линий В5…В7 (разъем для подключения программатора ХР1 изображен справа на схеме). Поэтому возможны сбои при программировании (наблюдались в отсутствие резистора R43), выход из строя программатора (маловероятно из-за достаточной стойкости используемой в нем микросхемы) или микросхемы памяти (вполне возможно).
Установка резистора R43, подключенного к источнику питания +5 В, создает в описанной ситуации на линии RD высокий уровень и переводит выходы Ю0…Ю7 микросхемы памяти в высокоимпедансное состояние, исключая подобную ситуацию, резистор R44 устраняет возможность хаотичной записи при программировании. В обычном режиме работы эти резисторы не мешают микроконтроллеру управлять микросхемой памяти.
Программа записи данных в ОЗУ 512 Кбайт
В рабочей программе, взятой за основу для нашего примера, АЦП микроконтроллера производил группу преобразований, результаты которых записывались во внутреннюю оперативную память микроконтроллера. Результаты группы преобразований обрабатывались и также записывались во внутреннюю оперативную память в виде массива размером в 45 байтов, начиная с ячейки с адресом аРаск. Этот массив переносился во внешнюю память для хранения. АЦП выполнял новую группу преобразований, которые тем же способом обрабатывались, а очередной массив размером в 45 байтов добавлялся во внешнюю память. Процесс продолжался до заполнения страницы внешней памяти (64 Кбайт), после чего вся информация со страницы внешней памяти через микроконтроллер передавалась в компьютер.
Представленная программа включает в себя подпрограммы, взятые из рабочей программы, оттуда же заимствовано распределение внутренней оперативной памяти микроконтроллера, а также имена констант и переменных.
Создадим новую директорию c: \Avr\Ram512, поместим в нее файл 8535def.inc, полученный при распаковке файла avr000.exe (ищите его там же, где распаковался файл 8515def.inc). В директории c: \Avr\Ram512 создадим проект Ram512 с новым файлом программы Ram512.asm, в который следует перенести следующую программу.
; Подключение 03У512К к AT90S8535
;================================
.include "с: \atmel\8535def.inc"
.equ RamH =$16а ; Адрес ячейки внутренней SRAM, хранящей
; старший байт адреса страницы внешней памяти
.equ RamL =$16b ; Адрес ячейки внутренней SRAM, хранящей
; младший байт адреса внешней памяти
; Три старших бита адреса внешней памяти
; (номер страницы внешней памяти):
.equ Ar0 =PC0
.equ Ar1 =PC1
.equ Ar2 =PC2
.equ Wr =PC4 ; Управление записью во внешнюю память
.equ Rd =PC3 ; Управление считыванием из внешней памяти
.equ Buf1 =PD3 ; Управление записью в буфер 1
.equ Buf2 =PD4 ; Управление записью в буфер 2
.equ aADC =$60 ; Адрес для переноса массива из внешней памяти во внутреннюю SRAM
.equ aPack =$Ь0 ; Адрес 45-байтового массива во; внутренней SRAM микроконтроллера
.def tm =r16
.def cnt =r19
;====
RESET:
ldi tm, (1<<Wr)+(1<<Rd) ; Устанавливаем высокие уровни
out PORTC,tm ; на линиях WR и RD
ldi tm,$ff ; контакты порта С -
out DDRC,tm ; в режиме выходов
clr tm ; На линиях порта D низкие уровни
out PORTD,tm ; (в том числе BUF1, BUF2=0)
ldi tm,$ff ; контакты порта D -
out DDRD,tm ; в режиме выходов
ser tm ; Установка регистра tm (tm=$ff)
out DDRB,tm ; вывод в порт DDRB содержимого tm
; контакты порта В — в режиме выходов
ldi tm,low(RAMEND)
; Стек — начиная с конца внутренней SRAM
out SPL,tm
ldi tm,high(RAMEND)
out SPH,tm
; Загрузка во внутреннее ОЗУ микроконтроллера,
; начиная с ячейки с адресом аРаск
; 45-ги байтов массива: $20, $21, $22….
ldi XL,low(aPack)
ldi XH,high(aPack)
ldi tm,$20
ldi cnt,45
StRAM:
st X+,tm
inc tm
dec cnt
brne StRAM
; Перепишем этот массив во внешнюю память на страницу № 2
; (старшие биты адреса внешней памяти AR2 AR1 AR0 = 101)
in tm,PORTC
andi tm, $ff-((1<<AR2)+(1<<AR1)+(1<<AR0))
; очистка битов AR2, AR1, AR0
; 1111 1000 = $ff-@K0D = ((K<AR2)+(1«AR1)+(1«AR0))
ori tm, (1<<AR1)
; Установка бита AR1(aдpec страницы 010
; (страница № 2))
out PORTC,tm ; Вывод в порт С содержимого tm
clr tm ; Очистка содержимого tm (tm=0)
sts RamH,tm ; Сохранение в ячейке RamH содержимого tm
sts RamL,tm
rcall St45bt ; Вызов подпрограммы St45bt
; Скопируем массив из внешней памяти на странице № 2
; (старшие биты адреса внешней памяти AR2 AR1 AR0 = 101)
; во внутреннее ОЗУ микроконтроллера
clr tm ; Очистка содержимого tm (tm=0)
sts RamH,tm ; Сохранение в ячейке RamH содержимого tm
sts RamL,tm
rcall DOutPrp
cycle: rjmp cycle
; Подпрограммы:
; ==============
Копирование массива из внутренней во внешнюю память
St45bt:
ldi ZL,low(aPack)
ldi ZH,high(aPack)
ldi cnt,45
mSt45:
rcall SetAddr
rcall DataSt
dec cnt
brne mSt45
ret
; ======
; Подпрограмма копирования 45 байтов из внешней памяти во внутреннее ОЗУ
DOutPrp:
ldi ZL,low(aADC)
ldi ZH,high(aADC)
ldi cnt,45
DOut1:
rcall SetAddr
rcall DataLd
dec cnt
brne DOut1
ret
; ========
; Подпрограмма установки
SetAddr:
Ids XL,RamL ; Скопировать содержимое ячейки RamL в XL
Ids XH,RamH
out PORTB,XL ; Вывести в порт В содержимое XL
nop ; Задержка 156 нc
nop ; Задержка 156 нc
sbi PORTD,Buf1 ; Установить бит Buf1 порта D
nop
nop
cbi PORTD,Buf1 ; Сбросить бит Buf1 порта D
nop
nop
out PORTB,XH ; Вывести в порт В содержимое ХН
nop
nop
sbi PORTD,Buf2 ; Установить бит Buf2 порта D
nop
nop
cbi PORTD,Buf2 ; Сбросить бит Buf2 порта D
adiw XL,1 ; Увеличить содержимое пары XH:
sts RamH,XH ; Сохранить содержимое ХН в ячейке RamH
sts RamL,XL
ret ; Возврат из подпрограммы
; =========
Подпрограмма копирования байта из внутреннего ОЗУ во внешнюю память
DataSt:
Id tm,Z+
out PORTB,tm
nop
nop
cbi PORTC,Wr
nор
nор
sbi PORTC,Wr
ret
; ==========
; Подпрограмма копирования байта из внешней памяти во внутреннее ОЗУ
DataLd:
clr tm ; Очистка tm
out DDRB,tm ; Контакты порта В в режиме входов
cbi PORTC,Rd ; Сбросить бит Rd порта С
nор
nор
in tm,PINB ; Считать данные на контактах порта В в tm
sbi PORTC,Rd; Установить бит Rd порта С
St Z+, tm; Сохранить содержимое tm в ячейке ОЗУ
ser tm ; Установить tm
out DDRB,tm ; Все контакты порта В в режиме выходов
ret
Отладка программы
После ассемблирования (клавиша F7) при отсутствии ошибок приступим к отладке (клавиша F11), в появившемся окне Simulator options | Device выберем микроконтроллер AT90S8535.
Рассмотрим определение констант в программе. Ячейки внутренней памяти микроконтроллера с адресами RamH и RamL хранят старший и младший байты адреса внешней памяти. То есть, в этих ячейках будет отражаться информация об адресе внешней памяти, которая выводится в буферы адреса (регистры DD3 и DD4 по схеме электрической).
Константа AR0 равна константе РС0; РС0 в свою очередь определена в файле, вставляемом в программу директивой. include.
Остальные константы, не определенные в тексте программы, также определяются в файле 8535def.inc.
Нажмем F11. Выполним View | New 10 view, в открывшемся окне выполним PortB | +, здесь же выполним PortC | + и PortD | + для контроля информации, выводимой на контакты портов В, С и D, а следовательно и на шину данных нашей схемы (порт В), и на линии управления внешней памятью (соответствующие контакты портов С и D).
Замечание. Имена констант в программе соответствуют именам линий управления внешней памятью на электрической схеме.
Переносим курсор на команду ldiXL,low(aPack) блока загрузки внутренней SRAM массивом в 45 байтов и жмем Ctrl+F10.
Произошла инициализация портов В, С и D, а также установлен начальный адрес стека.
Проверяем состояние линий портов в окне 10. Порт В инициализирован для вывода данных из микроконтроллера. Линии РС4 и РСЗ порта С установлены (в электрической схеме на соответствующих линиях WR и RD будут высокие уровни). На линиях PD3 и PD4 порта D (соответствуют линиям BUF1, BUF2 в схеме) — низкие уровни. В этом же окне 10 выполняем CPU | + и проверяем состояние регистров указателя стека SPH и SPL.
Для имитации массива, получаемого в результате одной группы преобразований АЦП, в область памяти, начинающуюся адресом аРаск, записывается простой массив размером в 45 байтов.
Вызовем окно Memory, выполнив View | New memory view, перенесем курсор на команду, следующую за циклом записи массива (команда intm,PINC) и нажмем Ctrl+F10. Во внутренней памяти микроконтроллера создан массив, первый байт которого ($22) находится в ячейке с адресом аРаск (0х00В0).
Жмем FI 1 пять раз, проверяем состояние порта С в окне 10. Линии РС2 PCI РС0 приобрели состояние 010, таким же будет состояние трех старших линий адреса (AR2 AR1 AR0), определяющих номер рабочей страницы внешней памяти.
Трижды жмем F11, очищаем старший и младший адреса внешней памяти в ячейках RamH, RamL.
Указатель выполнения команд остановился на команде вызова подпрограммы переноса данных во внешнюю память. В окне Memory перейдем в самый конец внутренней памяти, переместив скроллер окна вниз до конца, выполним View | Processor, в открывшемся окне процессора заметим состояние счетчика команд (0x00001D — это номер команды rcall, которая будет выполняться) и указателя стека (=0x000025F).
Нажмем F11. Указатель выполнения команд перескочил на команду с меткой St45bt: (окно программы), в счетчике команд — номер команды с этой меткой. Во внутренней памяти по адресу 25F находится двухбайтный номер команды, на которую надо вернуться после выполнения подпрограммы (001Е — именно эта команда cледует за командой с номером 001D, и именно адрес 001Е загрузится в счетчик команд после выполнения подпрограммы). Указатель стека сместился на две ячейки влево (25D), если внутри нашей подпрограммы будет подпрограмма следующего уровня вложения, в ячейки 25D:25C запишется двухбайтный адрес возврата из этой подпрограммы.
Устанавливаем курсор на команду с меткой mSt45:, жмем Ctrl+F10. Регистр Z содержит адрес начала массива во внутренней оперативной памяти микроконтроллера. Указатель выполнения команд находится в начале цикла побайтного копирования данных из внутренней оперативной памяти микроконтроллера во внешнюю память. Отличие цикла от уже рассматривавшихся в этой главе циклов — в наличии двух вызовов подпрограмм. Первый из них: rcall SetAddr записывает младший и старший байты адреса внешней памяти в два буфера (по электрической схеме — в регистры DD3 и DD4). Второй вызов подпрограммы rcallDataSt извлекает из ячейки внутренней оперативной памяти микроконтроллера (внутреннего ОЗУ) байт данных и записывает его в ячейку внешней памяти.
Наблюдая содержимое счетчика команд и указателя стека в окне Processor, а также изменения в окне Memory (последняя строка, начинающаяся адресом 0x025F), нажимаем F11, указатель выполнения команд перемещается на первую команду подпрограммы установки адреса внешней памяти.
Подпрограмма установки адреса SetAddr:
• из ячеек RamH, RamL внутреннего ОЗУ в регистры ХН, XL загружаются младший и старший байты адреса внешней памяти (две команды Ids);
• младший байт адреса выводится на шину данных В0…В7 (смотрите схему) через порт В (команда out PORTB,XL);
• после задержки, необходимой для завершения переходных процессов (две команды пор), на линии BUF1 устанавливается высокий уровень (команда sbi PORTD,BUFl), по которому в регистр DD4 записывается информация, установленная на шине данных;
• после задержки (две команды пор) на линии BUF1 устанавливается низкий уровень (команда cbi PORTD,BUFl), запись в регистр завершена;
• старший байт адреса выводится на шину данных В0…В7 через порт В (команда out PORTB,ХН);
• после задержки на линии BUF2 устанавливаегся высокий уровень (команда sbi PORTD,BUF2), по которому в регистр DD3 записывается информация, установленная на шине данных;
• после задержки (две команды пор) на линии BUF2 устанавливается низкий уровень (команда cbi PORTD,BUF2), завершая запись в регистр DD3;
• команда adiw увеличивает содержимое пары регистров XH: XL, теперь в них хранится адрес следующей ячейки внешней памяти, запись в нее будет произведена в следующем цикле установки адреса.
Проведите пошаговую отладку подпрограммы установки адреса, наблюдая за тем, какие данные выводятся в порты. Сопоставьте эти данные с состояниями, которые должны устанавливаться на соответствующих линиях электрической схемы. При выполнении команды выхода из подпрограммы (ret) наблюдайте за изменением состояний счетчика команд и указателя стека. Постарайтесь понять, где хранился адрес возврата из подпрограммы, на какой адрес возврата установлен указатель стека теперь.
В регистре ввода-вывода PORTC хранится то, что программа вывела в порт, в регистре ввода — вывода PINC — то, что присутствует на контактах порта (так в PORTC можно вывести байт 11111111, контакты микроконтроллера РСО и РС2 соединить с общим проводом, тогда состояние PORTC останется без изменений, состояние PINC станет равным 11111010). Для команд типа out PORTC,tm в окне Ю информация появляется сначала в строке Port С Data (PORTC), а после выполнения следующей команды — в строке Input pins (PINC).
Перед входом в цикл, начинающийся меткой mSt45:, в регистр Z был занесен адрес начала массива во внутреннем ОЗУ микроконтроллера.
Подпрограмма копирования байга из внутреннего ОЗУ DataSt.
• первая команда подпрограммы (Id tm,Z+) копирует первый байт массива из ячейки, адрес которой хранится в регистре Z, в регистр tm, затем адрес, хранящийся в регистре Z, увеличивается на единицу, теперь Z указывает на следующий элемент массива;
• элемент массива выводится на шину данных через порт В (команда out PORTB,tm);
• после задержки (команды пор) на линии WR устанавливается низкий уровень (команда cbi PORTC,Wr), информация, установленная на шине данных, записывается в ячейку внешней памяти, адрес которой был определен при выполнении подпрограммы SetAddr;
• после задержки (команды пор) на линии WR устанавливается высокий уровень (команда sbi PORTC,Wr), копирование байта данных во внешнюю память завершено;
• выполняется возврат из подпрограммы.
Выполните один раз пошаговую отладку подпрограммы DataSt. Отладка цикла, в котором находится эта подпрограмма, не представляет интереса.
Отследить состояние внешней памяти с занесенным в нее массивом мы не сможем, так как наш вариант подключения внешней памяти к микроконтроллеру не предусмотрен симулятором AVR Studio.
По этой же причине мы не сможем полностью провести отладку копирования массива из внешней памяти во внутреннее ОЗУ микроконтроллера, однако можно воспользоваться приемами, которые предлагались в предшествующем примере:
• отразить массив в области внутреннего ОЗУ специально для отладки;
• или после команды ввода в порт информации с шины данных вводить эти данные в порт вручную.
Воспользуемся вторым приемом при отладке подпрограммы копирования данных из внешней памяти во внутреннее ОЗУ микроконтроллера DataLD.
Переместите курсор на команду rcall DoutPrp и нажмите Ctrl+F10.
Первая пара команд подпрограммы загружает в регистр Z адрес новой области внутреннего ОЗУ микроконтроллера для массива, копируемого из внешней памяти.
Далее выполняется команда загрузки счетчика циклов, затем цикл, начинающийся меткой Dout1:.
Цикл не отличается от ранее рассматривавшихся циклов. Подпрограмма SetAddr рассматривалась ранее. Поэтому, доходя до команды вызова этой подпрограммы, нажимайте клавишу F10, чтобы не производить повторную отладку.
В результате выполнения подпрограммы SetAddr на линиях адреса внешней памяти сформирован адрес.
Подпрограмма копирования данных из внешней памяти во внутреннее ОЗУ DataLd.
В порт DDRB выводится нулевое значение, переводящее порт В на прием информации (все контакты порта В работают как входы).
На линии RD устанавливается низкий уровень (команда cbi PORTC,Rd), поэтому из ячейки внешней памяти, адрес которой сформирован при выполнении подпрограммы SetAddr, на шину данных выводится информационный байт.
После задержки (команды пор) через порт В с шины данных в регистр микроконтроллера tm считывается байт информации (команда in tm, PINB).
На линии RD устанавливается высокий уровень (команда sbi PORTC,Rd), контакты микросхемы памяти IO0…IO7 переводятся в высокоимпедансное состояние.
Данные из регистра tm переносятся в ячейку внутреннего ОЗУ микроконтроллера, адрес которой хранится в регистре Z, после чего содержимое Z увеличивается на единицу, теперь регистр Z указывает на следующую ячейку ОЗУ, в которую будет произведена запись в следующем цикле (команда stZ+,tm).
Порт В переводится в режим вывода данных (все контакты порта — выходы).
Возврат из подпрограммы.
Переводить порт В из режима передачи данных (выход) в режим приема (вход) только на время выполнения подпрограммы DataLd удобно, так как в других подпрограммах порт В должен всегда находиться в режиме передачи.
При отладке подпрограммы DataLd перед командой in tm,PINB для имитации ввода данных установите какие-нибудь флажки в строке Input pins для окна Ю | Port В, тогда соответствующие данные будут занесены в ячейку внутреннего ОЗУ микроконтроллера.
В заключение следует заметить, что приведенные программы адаптированы для лучшего понимания работы устройства, программы и отладчика. Однако считать их завершенными, а файлы hex, полученные при их ассемблировании, загружать в микроконтроллер не следует. В реальной рабочей программе сторожевой таймер (Watch dog timer) должен периодически программно сбрасываться командами wdr, размещенными по всей программе, иначе программа будет регулярно сбрасываться этим таймером, возвращаясь на метку RESET. В приведенных примерах программ не размещены векторы прерываний, являющиеся неотъемлемой частью практически любой программы. Перечисленные темы в этой главе не рассматриваются.
Автор: Баранов Вадим Николаевич (E-mail: ).