§ 52. Первая программа на ассемблере для PIC16F877 (Часть 2)
|
Ирков Алексей Николаевич, Июнь 2007
|
Статья обновлена 26 Мая 2014
|
|
Ну что, надеюсь, первый урок не сильно Вас испугал? Информации конечно много, но будем разбираться. Итак, сегодня я более подробно расскажу про работу банков памяти, а так же мы немного модернизируем работу нашей программы, добавив в нее небольшую задержку, ну и напоследок мы прогоним нашу программу на симуляторе.
|
Обо всем, по порядку. На прошлом уроке я уже рассказывал Вам, про организацию памяти данных. Четыре банка памяти помните? Так вот, встает резонный вопрос, как же между этими банками переключаться? На первый взгляд непонятно, однако давайте вспомним про регистры специального назначения... Я уже говорил, что они служат для управления работой микроконтроллера. Вот ими мы сейчас и воспользуемся. А если конкретнее, то нас интересует регистр STATUS. Еще раз взгляните на таблицу памяти данных МК, которую я давал на прошлом уроке. Надеюсь, Вы последовали моему совету и распечатали ее на бумаге, чтобы она была у Вас всегда под рукой... Взгляните на адрес 0x003 (кстати, обозначение 0x перед числом указывает на то, что будет применяться шестнадцатеричная система счисления. Как вариант, можно использовать и запись вида Н'003' или ‘003’h, кому как нравится). Это и есть наш регистр STATUS. В принципе, при инициализации данного регистра ему можно присвоить любое имя. Ну, например, написать так:
SOSISKA equ 0x003
От этого назначение регистра не измениться, однако я советую Вам при инициализации регистров памяти данных использовать стандартные имена. Это очень сильно упростит чтение Вашей программы. Итак, теперь поподробнее рассмотрим назначение каждого из битов регистра. Посмотрите приложение 1. Каждый бит в регистре STATUS играет какую-то функциональную роль. Нас пока интересуют только биты 6 и 5 (кстати, обратите внимание, что нумерация битов в любом регистре МК идет справа налево, это ОЧЕНЬ важно, однако поначалу жутко непривычно). Именно эти два бита указывают МК с каким банком памяти он должен в данный момент работать. Устанавливая эти биты в 1 или сбрасывая в 0, мы тем самым производим переключение между банками. Взгляните на таблицу 1:
RP1:RP0 | Банк |
00 | 0 |
01 | 1 |
10 | 2 |
11 | 3 |
Таблица 1. Таблица переключения банков памяти. |
В левом столбце указаны состояния битов 6 и 5 (стандартные имена которых RP1 и RP0), а в правом столбце номер банка с которым мы будет работать. Все просто!!! Да, и еще один момент. При включении микроконтроллер по умолчанию работает с банком 0.
Ну что, пора перейти от теории к практике. Сегодня мы немного доработаем нашу программу и заставим светодиоды мигать. Создаем новый проект и пишем следующий текст:
;========================== «ШАПКА» ПРОГРАММЫ =======================
;====================================================================
; Имя файла: main.asm
; Дата: 10.06.07
; Автор: Ирков Алексей Николаевич
; E-mail: nsuirkov@ngs.ru
;====================================================================
; Используется микроконтроллер PIC16F877. Частота кварца 20 МГц.
;====================================================================
; Описание программы
; Данная программа увеличивает каждые 154 мкСек значение порта PORTB
; и тем самым по-разному заставляет светиться светодиоды,
; которые подключены к ножкам порта PORTB
;====================================================================
;================================================
; Настройка и конфигурация микроконтроллера
;================================================
LIST p=16F877
__CONFIG H'3F72'
;================================================
; Инициализация регистров специального назначения
;================================================
INTCON equ 0x0B
STATUS equ 0x03
PORTB equ 0x06
TRISB equ 0x86
;================================================
; Инициализация констант
;================================================
RP0 equ 0x05
F equ 0x01
;================================================
; Инициализация переменных в памяти данных
;================================================
DelL equ 0x20
;================================================
; Начало программы
;================================================
ORG 0x00
goto Start
ORG 0x05
Start:
clrf INTCON ;запрещаем все прерывания
;===============================================
; Настраиваем линии порта PORTB на выход
;===============================================
bsf STATUS, RP0 ;переходим в банк 1
movlw B'00000000' ;помещаем в аккумулятор число 0
movwf TRISB ;устанавливаем линии порта PORTB на выход
bcf STATUS, RP0 ;переходим в банк 0
;===============================================
; Закончили настройку
;===============================================
clrf PORTB ;очищаем регистр порта PORTB
movlw .255 ;помещаем в аккумулятор число 255
movwf DelL ;перемещаем число из аккумулятора в регистр DelL
Loop:
decfsz DelL, F ;уменьшаем содержимое регистра DelL и сохраняем
;результат в регистре DelL, если содержимое регистра
;равно 0, то пропускаем сл. команду.
goto Loop ;переходим на метку Loop
incf PORTB, F ;увеличиваем содержимое регистра PORTB и сохраняем
;результат в регистре PORTB
goto Loop ;переходим на метку Loop
End
Начинаем разбираться:
LIST p=16F877
__CONFIG H'3F72'
INTCON equ 0x0B
STATUS equ 0x03
PORTB equ 0x06
TRISB equ 0x86
Эти строчки Вам уже знакомы. Указываем тип МК, задаем режимы работы и инициализируем регистры специального назначение. Едем дальше:
RP0 equ 0x05
F equ 0x01
Здесь мы инициализируем константы RP0 и F, которые нам пригодятся при написании нашей программы. Заметьте, что синтаксис инициализации регистров специального назначения и констант абсолютно одинаковый. Зато смысл мы вкладываем разный. В первом случае мы указываем, что регистр с именем INTCON имеет адрес 0x0B в памяти программ, а во втором случае мы говорим, что константа RP0 у нас будет иметь значение 0x05. Имейте это в виду!!! На самом деле компилятор не выделяет память для этих переменных! Он просто заменяет имя числом, указанным Вами, и в зависимости от команды будет воспринимать это число либо как константу, либо как адрес в памяти. Дальше идет:
DelL equ 0x20
А вот это уже переменная, которая хранится по адресу 0x20. Снова взгляните на таблицу памяти данных. Нас интересует банк 0. Видите, у нашего МК, начиная с адреса 0x020 по 0x07F, идут регистры общего назначения. Их мы можем использовать по своему усмотрению. В частности для хранения переменных. В нашем случае регистр DelL будет играть роль таймера. Зачем это нужно я расскажу чуть позже, а пока идем дальше:
ORG 0x00
goto Start
ORG 0x05
Start:
clrf INTCON ;запрещаем все прерывания
Эти строки придется опять пропустить. Мы вернемся к ним на сл. уроке. Поехали дальше:
bsf STATUS, RP0 ;переходим в банк 1
movlw B'00000000' ;помещаем в аккумулятор число 0
movwf TRISB ;устанавливаем линии порта PORTB на выход
bcf STATUS, RP0 ;переходим в банк 0
А вот здесь остановимся поподробнее. Я не зря Вам вначале рассказывал про регистр STATUS и переключение между банками. Здесь мы как раз это и делаем. Возникает вопрос. Зачем? В прошлый раз, мы устанавливали все биты регистра PORTB в 1, для того чтобы у нас загорались светодиоды. Однако порт PORTB может работать как на выход, так и на вход данных. Но для этого его сначала нужно настроить. Для настройки порта PORTB в МК предусмотрен специальный регистр TRISB. Посмотрите на таблицу памяти. Регистр TRISB у нас находится в банке 1 по адресу 0x086. Каждый бит этого регистра отвечает за режим работы соответствующего бита регистра PORTB. Установка в регистре TRISB бита в 0 указывает, что линия порта МК работает на выход, то есть по данной ножке можно отправлять данные. Если же бит установлен в 1, то линия порта работает на вход, то есть по данной ножке можно принимать данные в МК. Давайте разберем на примере. Допустим, мы записали в регистр TRISB число B'11110000', тогда линии порта с 0 по 3 будут работать на выход, а линии с 4 по 7 будут работать на вход. То есть по ножкам с 33 по 36 МК мы будем отправлять данные, а по ножкам с 37 по 40 мы будем получать данные. Надеюсь, механизм Вам понятен. Обобщая все вышесказанное, подведем итог: для корректной работы с портом ввода/вывода нужно сначала задать режим работы порта, используя регистр TRISB, а потом уже можно приступать к записи или чтению данных. Однако регистр TRISB находится в банке 1, а наш МК при запуске работает с банком 0. Значит вначале нужно перейти из одного банка в другой, что мы и сделали, написав команду:
bsf STATUS, RP0
Данная команда взводит бит RP0 в единицу в регистре STATUS. В нашем случае, константа RP0 равна 5. Значит, мы в регистре STATUS установили 5й бит в единицу. Еще раз посмотрите на таблицу 1. Для перехода в банк 1 нужно 6й бит оставить нулевым, а 5й взвести в единицу. Что мы собственно и сделали. Теперь МК работает с банком 1. Следующие две команды вам уже знакомы:
movlw B'00000000' ;помещаем в аккумулятор число 0
movwf TRISB ;устанавливаем линии порта PORTB на выход
Тут мы записываем во все биты регистра TRISB нули, тем самым устанавливаем линии порта PORTB на выход. Ну а командой:
bcf STATUS, RP0
Мы зануляем бит RP0 регистра STATUS, и тем самым возвращаемся в банк 0. Надеюсь все понятно? Идем дальше:
clrf PORTB ;очищаем регистр порта PORTB
movlw .255 ;помещаем в аккумулятор число 255
movwf DelL ;перемещаем число из аккумулятора в регистр DelL
Первая команда Вам уже знакома. Она устанавливает все биты регистра PORTB в 0, так что светодиоды у нас гореть не будут. Следующие 2 строчки тоже уже встречались нам ни раз. Мы просто записываем в регистр DelL значение 255 (в двоичной системе это будет выглядеть B’11111111’). Этот регистр у нас будет играть роль однобитного таймера обратного отсчета. Дело в том, что в МК не предусмотрено команд задержки времени, поэтому мы искусственно «загрузим» МК бесполезной работой, а так как на выполнение любой (даже бесполезной) работы тратится время, то возникнет небольшая пауза. Давайте посмотрим, как это реализовано в нашей программе:
Loop:
decfsz DelL, F ;уменьшаем содержимое регистра DelL и сохраняем
;результат в регистре DelL, если содержимое регистра
;равно 0, то пропускаем сл. команду.
goto Loop ;переходим на метку Loop
incf PORTB, F ;увеличиваем содержимое регистра PORTB и сохраняем
;результат в регистре PORTB
goto Loop ;переходим на метку Loop
В цикле Loop как раз и происходят основные «боевые» действия. Первая команда у нас идет decfsz DelL, F. Она берет содержимое регистра DelL и уменьшает его на единицу. Идущая поле запятой константа F, которую мы определили как 0x01, указывает на то, что результат нужно сохранить в том же регистре, то есть в DelL. Но это еще не все!!! Дальше идет проверка числа. Если после декремента (декремент означает уменьшение, а инкремент – увеличение) содержимого DelL его значение не равно нулю, значит, происходит выполнение следующей команды, а если содержимое равно нулю, то следующая команда пропускается, а вместо нее вставляется команда nop. Что нам это дает?
После первого выполнения команды decfsz в регистре DelL будет лежать число 254. Так как оно не равно нулю, то дальше будет выполнена команда goto Loop, которая перекинет нас на метку Loop, а точнее на первую команду, стоящую после этой метки. То есть мы опять вернулись к команде decfsz… Получился маленький цикл:) Как только содержимое регистра DelL станет равно нулю, наша программа вместо команды goto Loop вставит nop и приступит к выполнению команды incf PORTB, которая инкрементирует содержимое регистра PORTB. Теперь в регистре PORTB будет лежать число B'00000001'. Так как нулевой бит данного регистра взведен, то светодиод, подключенный к 33 ножке микроконтроллера, загорится. Теперь осталось выполнить команду goto Loop и цикл полностью повторится заново. Однако теперь в регистре DelL у нас будет лежать число 0х00. Складывается впечатление, что после команды decfsz мы сразу же должны перескочить на incf, однако это не так. Команда decfsz сначала декрементирует регистр, а уже потом сравнивает его с нулем. Однако что же будет, если декрементировать нулевое значение регистра? На самом деле произойдет переполнение, и содержимое регистра снова станет равно 255, таким образом, цикл задержки повторится снова. После этого мы опять увеличим значение регистра PORTB и у нас уже будет гореть 2 светодиода:) Так наш МК и будет крутиться в бесконечном цикле, увеличивая значения регистра PORTB и включая набольшую задержку. К сожалению, наш таймер имеет маленький недостаток. Он не может обеспечить слишком большую задержку МК. В нашем случае, она будет равна 154мкСек. В следующий раз мы доработаем нашу программу, реализовав в ней задержку в 1 секунду.
Ну а напоследок давайте проверим работоспособность нашей программы в симуляторе PIC Simulator IDE. Взять это программу можно на сайте http://www.oshonsoft.com/. У меня стоит версия 5.92. Запускаем симулятор и видим главное окно программы:

Нажимает File -> Load Program и в появившемся окне выбираем наш исполняемый файл. У меня он называется main.HEX:

Теперь щелкаем Tools -> 8 x LED Board. Должно появиться такое окошко:

Это как раз наши восемь светодиодов, которые подключены к регистру PORTB. Теперь выбирает Rate -> Ultimate (No refresh). Этим мы задаем максимальную скорость работы программы. К сожалению, в компьютере, даже при максимальной скорости, наша программа будет выполняться гораздо медленнее, чем в МК. Однако в симуляторе мы можем бегло проверить работоспособность программы, плюс нам не надо тратить время на выпаивание схемы.
Итак, делаем последний шаг. Нажимаем Simulation -> Start и смотрим, как мигают наши светодиоды:)

Подведем итог. Сегодня мы научились переключаться между банками памяти данных МК. Для этого нужно было всего лишь изменить состояние битов RP1 и RP0 регистра STATUS. Кроме этого мы разобрались с тем, как нужно настраивается регистр PORTB, и научились искусственно создавать задержку (правда пока короткую) для нашего МК. Ну и напоследок запустили нашу программу в симуляторе и посмотрели на ее работу.
© Ирков Алексей Николаевич
Июнь 2007
http://www.kernelchip.ru