Интернет-магазин

Просмотр корзины
В корзине:

товаров - 0 шт.



§ 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Банк
000
011
102
113
Таблица 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



© KERNELCHIP 2006 - 2017