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

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

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



§ 53. Первая программа на ассемблере для PIC16F877 (Часть 3)

Ирков Алексей Николаевич, Июнь 2007
Статья обновлена 26 Мая 2014

Итак, в сегодняшней статье мы доработаем нашу программу, добавив в нее задержку в 1 секунду. Кроме этого мы познакомимся с основными отладочными инструментами, которые поставляются вместе с MPLAB. Это поможет нам до конца разобраться во внутренней работе МК.


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

;==================================== «ШАПКА» ПРОГРАММЫ ===============================
;========================================================================================
; Имя файла: main.asm
; Дата: 07.07.07
; Автор: Ирков Алексей Николаевич
; E-mail: nsuirkov@ngs.ru
;========================================================================================
; Используется микроконтроллер PIC16F877. Частота кварца 20 МГц.
;========================================================================================
; Описание программы
; Данная программа каждые 1 секунду увеличивает значение порта PORTB
; и тем самым по-разному заставляет светиться светодиоды,
; которые подключены к ножкам порта PORTB
;========================================================================================


;================================================
; Настройка и конфигурация микроконтроллера
;================================================
	
	LIST  p=16F877
	#include 
	__CONFIG _HS_OSC & _WDT_OFF & _PWRTE_ON & _BODEN_ON & _LVP_OFF & _CPD_OFF& _CP_OFF

;================================================
; Инициализация переменных в памяти данных
;================================================

DelH			equ	0x20
DelM			equ	0x21
DelL			equ	0x22

;================================================
; Начало программы
;================================================
	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

	nop				;ничего не делаем
	nop
	nop

Loop:
	movlw .26			;заносим в аккумулятор число .26
	movwf DelH			;копируем содержимое аккумулятора в регистр DelH
	
	movlw .94			;заносим в аккумулятор число .94
	movwf DelM			;копируем содержимое аккумулятора в регистр DelM

	movlw .109			;заносим в аккумулятор число .109
	movwf DelL			;копируем содержимое аккумулятора в регистр DelL

	nop
	

Delay_L:
	decfsz DelL, F			;уменьшаем содержимое регистра DelL и сохраняем
					;результат в регистре DelL, если содержимое регистра
					;равно 0, то пропускаем сл. команду.

	goto Delay_L			;переходим на метку Delay_L
	
Delay_M:
	decfsz DelM, F			;уменьшаем содержимое регистра DelM и сохраняем
					;результат в регистре DelM, если содержимое регистра
					;равно 0, то пропускаем сл. команду.

	goto Delay_L			;переходим на метку Delay_L

Delay_H:
	decfsz DelH, F			;уменьшаем содержимое регистра DelH и сохраняем
					;результат в регистре DelH, если содержимое регистра
					;равно 0, то пропускаем сл. команду.

	goto Delay_L			;переходим на метку Delay_L


	incf PORTB, F			;увеличиваем содержимое регистра PORTB и сохраняем
					;результат в регистре PORTB

	goto Loop			;переходим на метку Loop

	End

Эта программа не сильно отличается от той, которую мы писали в прошлый раз, однако не стоит расстраиваться. Разбираться во внутренней работе МК лучше на примере простых программ. Итак, приступим. Первое, что мы изменили в нашей программе, это добавили строчки:

	#include 
	__CONFIG _HS_OSC & _WDT_OFF & _PWRTE_ON & _BODEN_ON & _LVP_OFF & _CPD_OFF& _CP_OFF

Первая строкой мы подключаем к нашему проекту файл p16f877.inc. Он находится в папке Microchip\MPASM Suite. Чтобы посмотреть содержимое этого файла нажимаем Project -> Add Files to Project… Должно появиться такое окошко:

Указываем тип файлов Header Files и выбираем файл P16F877.INC. Видим, что файл появился в менеджере проектов:

Открываем его и видим, что в нем происходит инициализация регистров специального назначения, а так же часто употребляемых констант и отдельных битов управляющих регистров. Таким образом, нам теперь не нужно каждый раз перед написанием программы вручную инициализировать регистры. Взгляните в самый конец этого файла. Там инициализированы биты конфигурации:

_CP_ALL                      	EQU     H'0FCF'
_CP_HALF                   	EQU     H'1FDF'
_CP_UPPER_256               	EQU     H'2FEF'
_CP_OFF                      	EQU     H'3FFF'
_DEBUG_ON                    	EQU     H'37FF'
_DEBUG_OFF                   	EQU     H'3FFF'
_WRT_ENABLE_ON               	EQU     H'3FFF'
_WRT_ENABLE_OFF              	EQU     H'3DFF'
_CPD_ON                      	EQU     H'3EFF'
_CPD_OFF                     	EQU     H'3FFF'
_LVP_ON                      	EQU     H'3FFF'
_LVP_OFF                     	EQU     H'3F7F'
_BODEN_ON                    	EQU     H'3FFF'
_BODEN_OFF                   	EQU     H'3FBF'
_PWRTE_OFF                   	EQU     H'3FFF'
_PWRTE_ON                    	EQU     H'3FF7'
_WDT_ON                      	EQU     H'3FFF'
_WDT_OFF                    	EQU     H'3FFB'
_LP_OSC                      	EQU     H'3FFC'
_XT_OSC                      	EQU     H'3FFD'
_HS_OSC                      	EQU     H'3FFE'
_RC_OSC                      	EQU     H'3FFF'

Они задают режим работы нашего МК. Подробнее о них уже рассказывал Дмитрий, так что я не буду останавливаться на этом, а перейду сразу к сути. На предыдущих уроках мы писали команду

	__CONFIG H'3F72'

Было не совсем понятно, откуда вообще взялось число H'3F72'. В данной программе мы немного изменим эту строчку:

__CONFIG _HS_OSC & _WDT_OFF & _PWRTE_ON & _BODEN_ON & _LVP_OFF & _CPD_OFF & _CP_OFF

Однако если заменить эти имена константами и произвести над ними логическую операцию “AND”, то у нас как раз и получится число H'3F72'. Единственная разница лишь в том, что новая запись стала нагляднее. Поехали дальше:

	DelH		equ	0x20
	DelM		equ	0x21
	DelL		equ	0x22

В прошлый раз для формирования задержки мы использовали одну переменную. В этот раз мы будем использовать целых три – DelH, DelM, DelL с адресами 0x20, 0x21, 0x22 соответственно. Это позволит увеличить длительность задержки между миганиями светодиодов до 1 секунды. Идем дальше:

	ORG 0x00
	goto Start

	ORG 0x05
	
Start:

Для понимания этих строчек придется сделать маленькое отступление. В самой первой статье я уже Вам рассказывал про организацию памяти. Как Вы помните, все инструкции программы у нас хранятся в памяти программ МК, и каждая инструкция занимает 14 бит. Предлагаю на примере разобраться, что же конкретно записано в этих битах. Допустим, вы написали команду clrf PORTB, которая, как вы уже знаете, очищает содержимое регистра PORTB. Так вот, компилятор переводит эту команду в машинное слово вида:


14-разрядный код
Бит13          Бит7
Бит6         Бит0
0  0  0  0  0  1  1
f  f  f  f  f  f  f

Первые семь бит этой команды хранят адрес регистра, который нужно отчистить. Максимальный адрес, который можно в него записать это 0x7F. Теперь, я думаю, стало понятно, почему у нас все четыре банка памяти не превышают этого размера. Вслед за адресом регистра у нас идет код команды, который также занимает семь бит. Для операции clrf, например, код команды имеет вид B’0000011’. На этом примере я описал структуру так называемых байт ориентированных команд. Такие команды производят действия над целым байтом данных. Однако у нашего контроллера есть несколько бит ориентированные команд (например, bsf или bcf), а также команды управления и операции с константами, причем их представление в машинном виде будет немного отличается от разобранного в примере выше.

Итак, после того, как компилятор перевел ассемблерные инструкции в машинный код их нужно записать в память программ. Однако перед этим нужно указать, с какой именно ячейки памяти мы хотим начать запись нашей программы. Как раз для этого мы в начале и пишем ORG 0x00, тем самым указывая компилятору, что запись всех последующих команд нужно начинать с нулевого адреса, куда и будет записана команда goto Start. Дальше снова стоит команда ORG 0x05, а за ней команда clrf INTCON (метка Start: в памяти не записывается, помните?). Тем самым по адресу 0x05 записывается команда clrf INTCON. Чтобы наглядно увидеть, как располагаются команды в памяти, скомпилируйте проект и нажмите View->Disassembly Listing. Появится такое окошко:

В первом столбце располагаются адрес ячейки, в которой хранится команда. Дальше идет представление команды в шестнадцатеричной системе счисления. Ну а потом реальная команда, которая там хранится. Посмотрите внимательнее. Мы писали команду goto Start, а компилятор изменил ее на goto 0x5. То есть компилятор автоматически заменил метку Start на физический адрес 0x5, в которой записана команда clrf INTCON. Тут нет ничего сложного. Единственное о чем осталось упомянуть – это счетчик команд PC (program counter). Счетчик команд – это 13-разрядный регистр, который хранит адрес выполняемой инструкции в памяти программ.

Младший регистр (8-битный) счетчика команд находится по адресу 0x2 (PCL). Он доступен как для записи, так и для чтения. А вот старший регистр (5-битный) для записи и чтения недоступен. Чтобы его изменить, нужно воспользоваться дополнительным регистром PCLATH, который находится по адресу 0x0A. При каждом старте микроконтроллера регистр PC очищается, поэтому выполнение инструкций всегда начинается с нулевого адреса. Микроконтроллер загружает на выполнение инструкцию, адрес которой находится в регистре PC, а после ее выполнения содержимое регистра PC инкрементируется и загружается следующая инструкция. Другими словами регистр PC просто указывает МК по какому адресу нужно загрузить инструкцию на выполнение. Все просто. Поехали дальше:

	clrf INTCON		;запрещаем все прерывания

	bsf STATUS, RP0 	;переходим в банк 1
	
	movlw B'00000000'	;помещаем в аккумулятор число 0 
	movwf TRISB		;устанавливаем линии порта PortB на выход

	bcf STATUS, RP0		;переходим в банк 0

	clrf PORTB 		;очищаем регистр порта PORTB

	nop			;ничего не делаем
	nop
	nop

Здесь Вам уже все знакомо. Единственное добавилось три команды nop. Пока опустим объяснения, для чего они нужны. Идем дальше:

Loop:
	movlw .26		;заносим в аккумулятор число .26
	movwf DelH		;копируем содержимое аккумулятора в регистр DelH
	
	movlw .94		;заносим в аккумулятор число .94
	movwf DelM		;копируем содержимое аккумулятора в регистр DelM

	movlw .109		;заносим в аккумулятор число .109
	movwf DelL		;копируем содержимое аккумулятора в регистр DelL

	nop

Эти строки тоже Вам знакомы. Мы просто записываем начальные значения в регистры таймера обратного отсчета. Эти значения были подобраны опытным путем, чтобы обеспечить задержку между миганием светодиодов, ровно в 1 секунду. Едем дальше:

Delay_L:
	decfsz DelL, F		;уменьшаем содержимое регистра DelL и сохраняем
				;результат в регистре DelL, если содержимое регистра
				;равно 0, то пропускаем сл. команду.
	goto Delay_L		;переходим на метку Delay_L
	
Delay_M:
	decfsz DelM, F		;уменьшаем содержимое регистра DelM и сохраняем
				;результат в регистре DelM, если содержимое регистра
				;равно 0, то пропускаем сл. команду.
	goto Delay_L		;переходим на метку Delay_L

Delay_H:
	decfsz DelH, F		;уменьшаем содержимое регистра DelH и сохраняем
				;результат в регистре DelH, если содержимое регистра
				;равно 0, то пропускаем сл. команду.
	goto Delay_L		;переходим на метку Delay_L

	incf PORTB, F		;увеличиваем содержимое регистра PORTB и сохраняем
				;результат в регистре PORTB
	goto Loop		;переходим на метку Loop

Этот цикл очень сильно напоминает тот, который мы использовали в прошлый раз. Единственная разница в том, что наш однобайтный таймер обратного отсчета превратился в трехбайтный. Принцип его работы почти такой же. Пока содержимое регистра DelL не станет равно нулю, мы будем крутиться в цикле Delay_L, при этом за каждый проход содержимое DelL будет декрементироваться. После этого мы перейдем в цикл Delay_M, в котором проверке подвергнется регистр DelM. А так как он не равен нулю, то мы опять перескочим на начало цикла Delay_L, и все начнется сначала. После нескольких таких витков содержимое регистра DelM станет равно нулю, мы попадем в цикл Delay_H, где будем проверять на ноль значение регистра DelH. И уж когда оно станет равно нулю, мы наконец-то увеличим значение порта PORTB и вернемся к началу цикла Loop. Все!!!

Вообще, здесь можно провести аналогию с обычными часами, у которых предусмотрена функция таймера. Представьте, что регистр DelH будет отображать часы, регистр DelM – минуты, а регистр DelL – секунды. Единственная разница лишь в том, что наш таймер будет работать гораздо быстрее обычных часов.

С работой программы мы разобрались, теперь давайте познакомимся и отладочными средствами, предусмотренными в среде MPLAB. Для начала включим симулятор. Нажимаем Debugger -> Select Tools -> MPLAB SIM. Справа вверху появится панель вида:

Нажимаем F6 (Reset) или желтую кнопку на панели справа, тем самым сбрасываем микроконтроллер. Посмотрите на код. Напротив команды goto Start должна появиться зеленая стрелка, которая показывает, какая инструкция готова на выполнение. Теперь нажимаем View -> File Registers. Должно появиться такое окошко:

Это как раз представление нашей карты памяти данных, которую я рисовал на первом уроке, только выглядит она немного по-другому. Все регистры, значение которых изменяется после выполнения команды, будут подсвечиваться красным светом. С помощью карты памяти очень просто отслеживать состояние регистров, в процессе работы микроконтроллера. Щелкаем кнопку F7. Наш микроконтроллер выполнил команду goto Start, а зеленая стрелка перескочила на команду clrf INTCON. А теперь посмотрите в File Registers. Регистр по адресу 0x2 высветился красным светом, а его значение изменилось и стало равно 0x5. Если Вы помните, это как раз наш счетчик команд. То есть теперь мы будем выполнять инструкции, начиная с адреса 0x5.

Поначалу очень непривычно пользоваться окном File Registers. Однако это не единственный инструмент, которым можно воспользоваться. Нажимаем View -> Watch. Появится вот такое окошко:

Здесь мы можем добавлять только интересующие нас регистры общего или специального назначения, и следить только за их работой. Кстати, здесь можно понаблюдать за содержимым аккумулятора. Для этого нужно добавить SFR регистр WREG. Советую какое-то время понаблюдать за состоянием регистров в процессе работы МК, чтобы немного привыкнуть.

Ну и напоследок давайте познакомимся еще с одним важным инструментом. Нажимаем Debugger -> StopWatch. Появится вот такое окошко:

С помощью этого инструмента мы будем измерять задержку. Но для начала давайте установим BreakPoint'ы. Для этого два раза щелкаем по строчке clrf PORTB и incf PORTB. Слева должна появиться белая буква B в красном кружке. Именно время задержки между ними нам нужно измерить.

Теперь нажимаем кнопку Play на панели симулятора, или щелкаем F9. В этом режиме будет происходить симуляция работы МК на компьютере, а остановится этот процесс тогда, когда мы доберемся до нашего первого BreakPoint'а. Посмотрите на окошко StopWatch. Его значения изменились:

Теперь нажимаем Zero. Этим мы сбрасываем наш секундомер в ноль. И снова жмем F9. Микроконтроллер начал работать дальше, и остановится он только когда доберется до второго BreakPoint’а. В этот момент время задержки будут составлять ровно 1 секунду (взгляните на значение в колонке Time). Теперь пришло время объяснить, зачем я везде вставлял команды nop. Дело в том, что если вы их уберете, то задержка между миганием светодиодов будет меньше одной секунды. Конечно, это не так критично, особенно в работе нашей программы, однако это очень простой способ точной корректировки задержки, который очень часто применяется.



© Ирков Алексей Николаевич
Июнь 2007
http://www.kernelchip.ru



© KERNELCHIP 2006 - 2023