Книги по микроэлектронике
(все книги одного автора)
Сайт, создан автором книг по микроэлектроники для поддержки своих книг. Автор сайта и книг Белов Александр Владимирович
| На главную | Сайт "Мир микроконтроллеров" | Сайт "Симферополь вчера и сегодня" | Сайт "Простые логические игры" | Связь с автором |

Материалы для книги
"Самоучитель по микропроцессорной технике"

Ко мне, как к автору книги постоянно поступают различные замечания от читателей. Все такие замечания я внимательно изучаю. Многие такие письма помогли мне обнаружить различные досадные ошибки, которые я обязательно учту при переиздании книги. Но иногда письма вызваны тем, что материал, изложенный в книге оказался непонятным для читателя. В таких случаях в ответном письме я стараюсь более подробно объяснить непонятный момент. Если читателю не понятно - это недостаток моей книги. Значит я плохо объяснил. Я решил помещать такие поснения на этом сайте, что бы каждый желающий мог найти здесь ответы на все возникающие вопросы. Сегодня я помещаю ответ на вопрос одного из самых активных моих читателей из молдавии.


Переменные и константы

Письмо:

Здравствуйте уважаемый Александр Белов! Я живу в Кишинёве. Если помните я как-то нашел в Вашей книге самоучителя пару неточностей. Так вот, в процессе дальнейшего чтения книги я наткнулся на ещё одно непонятное место. А именно на странице 112 в подзаголовке "Описание констант и переменных "даётся определение - что есть константа, а что переменная .

Так вот, в нижнем абзаце говориться что константа - это некоторое число. А на странице 115 также в последнем абзаце говориться что переменная - это имя регистра или ячейки памяти. Если перелистнуть страницу, (стр. 116), то в первом верхнем абзаце мы встретим утверждение, прямо противоположное первому. Цитирую: Константа - это адрес ячейки памяти, а переменная - это значение, хранящееся в этой ячейке.

Ну давайте разберемся. В любой программа применяются переменные и константы. Константа - это некое фиксированное значение (число), которое обычно определяется в начале программы и применяется в программе далее в одном или нескольких разных местах. Например, вы можете задать код каждой клавиши при помощи константы. Допустим, у вас есть десять клавиш. Вы можете ввести десять констант: Kl1, Kl2, Kl3, Kl4, Kl5, Лl6, Kl7, Kl8, Kl9 и Kl10. В начале программы вы присваиваете им значения кодов клавишь. Я возьму коды произвольно. Просто для примера. Допустим это будет так:

Kl1 EQU 31 ; константе Kl1 присваиваем значение 31
Kl2 EQU 32 ; константе Kl2 присваиваем значение 32
Kl3 EQU 33 ; константе Kl3 присваиваем значение 33
Kl4 EQU 34 ; константе Kl4 присваиваем значение 34
Kl5 EQU 35 ; константе Kl5 присваиваем значение 35
Kl6 EQU 36 ; константе Kl6 присваиваем значение 36
Kl7 EQU 37 ; константе Kl7 присваиваем значение 37
Kl8 EQU 38 ; константе Kl8 присваиваем значение 38
Kl9 EQU 39 ; константе Kl9 присваиваем значение 39
Kl0 EQU 40 ; константе Kl10 присваиваем значение 40

Затем, в тексте программы везде, где нужно использовать код клавиши, можно поставить имя соответствующей константы. Если вы всегда будете ставить именно имя константы, то, во первых, программа станет более читаемой. В самом деле. Если вы встретите команду "Записать в ячейку памяти число 31" будет не понятно - это вы записываете код клавиши, или какую либо другую величину. А если команда будет звучать так: "Записать в ячейку памяти константу Kl1", то тут не нужны никакие комментарии. И второе преимущество. Предположим, вы решили изменить схему вашего устройства. При изменении схемы поменялись коды всех клавиш. Естественно, вам придется поменять программу. Однако, в том случае, если вы использовали константы, эта задача не вызовет никаких затруднений. Вам нужно лишь поменять значение констант лишь в одном месте - в начале программы, где константам присваиваются какие либо значения. Если же в тексте программы вы вместо констант использовали коды, то вам придется перелопатить всю программу (а реальные программы могут занимать до несколько сотен строк). И найти все места, где используются коды клавиш. Это осложняется еще и тем, что встретив код 31, вы еще должны разбираться, а действительно ли это код клавиши, или это что то другое.
Это, что касается констант. Что такое переменная, вам надеюсь известно из математики. Это неизвестная величина, которая в процессе выполнения программы будет принимать различные значения. В любой программе (на любом языке) имеется возможность определять переменные. Каждая переменная, так же, как и каждая константа, имеет свое уникальное имя. Причем имена переменных и констант не могут быть одинаковыми. В этот же ряд входят и другие символьные идентификаторы. Например, имена
меток и др.

Теперь о специфике языка Ассемблера. Ассемблер - это машинно-ориентированный язык. По сути, это описание системы команд конкретного процессора. А система команд процессора не оперирует с такими понятиями, как константа или переменная. Процессор работает с командами и ячейками. Оджнако Ассемблер имеет и константы и переменные. Как это совмещается? А очень просто. Константа - это исключительно конструкция языка. После трансляции программы мы увидим только коды. И от имен Kl1, Kl2 и т. д. не останется и следа.
Символьные константы заменятся своими значениями. Объясню на примере. Допустим программа определила код нажатой клавиши. И нам нужно выполнить ответное действие. Например, если нажата клавиша Kl1, перейти к операции по метке M1. Для этого в программе применяется следующая команда:

cjne a,#Kl1,M1

Которая означает "Если содержимое аккумулятора равно константе Kl1, перейти по метке M1. После трансляции этой строки программы, в коде программы появится следующие три байта:

B4 1F 12 (все числа в шестнадцатеричной форме)

Где B4 - это код операции cjne a,#data,addres
1F - это шестнадцатеричное представление числа 31 (значение константы Kl1)
12 - я показал условно. На самом деле тут будет стоять относительный адрес команды, отмеченной в программе меткой M1.

Переменная, с точки зрения Ассемблера, имеет другой смысл. Переменные в процессе выполнения программы могут принимать различные значения. Эти значения нужно где то хранить. В описываемом микропроцессоре для хранения разных чисел имеется целый набор регистров, а так же 128 внутреннего ячеек ОЗУ. Переменную можно хранить в любой из этих ячеек. Определение переменной - это определение регистра или ячейки ОЗУ, где она будет храниться. Например, если коды ваших клавишь будут меняться в процессе работы программы (случай не очень вероятный, но для примера сойдет), то коды клавиш можно было бы разместить в ячейках ОЗУ. Например, код первой клавиши можно поместить в ячейку ОЗУ с адресом 31, код второй клавиши, в ячейку 32 и т.д. Обратите внимание на то, что я выбрал одни и те же значения для констант (в первом примере) и для адресов ячеек (во втором). Я сделал это намеренно, что бы показать, что одни и те же цифры могут означать совершенно разные вещи. Но это вовсе не обязательно. Адреса ячеек можно выбирать любые в пределах адресного пространства микроконтроллера. Если коды клавиш - это переменные и мы будем хранить их в ячейках памяти, то нам нужно в начале программы сначала определить переменные Kl1, Kl2, Kl3 и т. д. Их можно определить точно так же, как и константы. Просто те же команды (см. выше) При этом за символьным обозначением Kl1 закрепится число 31, За Kl2 - число 32 и так далее. Точно так же, как и в первом случае. Разница будет только в том, что использовать эти числа мы будем по разному. Теперь это будут адреса ячеек памяти. Далее, программа, в процессе своей работы должна записать в каждую эту ячейку код клавиши. С точки зрения языка, это будет означать - присвоение переменной начального значения. Сделать это можно при помощи следующих команд:

mov Kl1,#131 ; Записать в ячейку Kl1 число 131
mov Kl2,#132 ; Записать в ячейку Kl1 число 132
mov Kl3,#133 ; Записать в ячейку Kl1 число 133
mov Kl4,#134 ; Записать в ячейку Kl1 число 134
mov Kl5,#135 ; Записать в ячейку Kl1 число 135
mov Kl6,#136 ; Записать в ячейку Kl1 число 136
mov Kl7,#137 ; Записать в ячейку Kl1 число 137
mov Kl8,#138 ; Записать в ячейку Kl1 число 138
mov Kl9,#139 ; Записать в ячейку Kl1 число 139
mov Kl10,#140 ; Записать в ячейку Kl1 число 140

Как видим, для понятности я применил другие коды для клавиш. Ну а теперь посмотрим, как будет выглядеть команда, проверяющая код клавиши Kl1, аналогичная предыдущему случаю для случая, когда код клавиши хранится в переменной. На языке Ассемблера она будет выглядеть следующим образом:

cjne a,Kl1,M1

Как видим, в этой новой команде у второй константы отсутствует символ #. Этот символ имеет смысл признака непосредственных данных. Если в команде на языке ассемблера перед числом или символьным идентификатором стоит этот символ, то данное число - непосредственно данные. Как в предыдущем примере. Там содержимое аккумулятора сравнивается с числом Kl1 (то есть с числом 31). Сдесь же содержимое аккумулятора сравнивается с содержимым ячейки памяти, которая имеет адрес Kl1 (то есть адрес 31). А в этой ячейке у нас хранится число 131 (код клавиши). После трансляции этой команды в программном коде появятся следующие три числа:

B5 1F 12 (все числа в шестнадцатеричной форме)

В этом случае B5 - это код команды. Теперь это другая команда. Команда, работающая с ячейкой памяти а не с константой. В общем виде эта операция записывается так cjne a,direct,addres
1F - в данном случае - это адрес ячейки.
12 - это относительный адрес перехода к метке M1.

Именно в этом суть фразы, которую вы посчитали неправильной: "Константа - это адрес ячейки памяти, а переменная это значение, хранящееся в этой ячейке". Константа может иметь самый разный смысл. Это может быть и код для проверки (код клавиши) и какой то коэффициент, и адрес ячейки памяти и любое числовое значение. В том конкретном случае - константой обозначен адрес ячейки памяти, где хранится значение переменной. Возможно, в книге я плохо это объяснил. С вашего разрешения я помещу весь текст этого письма на сайт, посвященный моим книгам http://book.mikroprocessor.by.ru .


Из вышесказанного можно найти ответ и на следующий ваш вопрос. Вы пишете:

Но это ещё не всё. Есть ещё вопрос. Далее приведены два примера команд:

mov a, #count
mov a, count

И говориться(в следующем абзаце), что первая (т.е. я так понимаю что верхняя)команда загружает адрес, а вторая загружает содержимое. И тут же, в следующем за этим пояснении говориться прямо противоположное. Цитирую:
Символ # применяется, когда нужно показать, что следующее за ним число - это байт данных. Если символ отсутствует - то это интерпретируется как адрес.

Тут нет ничего противоположного. Константа count - это адрес ячейки памяти, где хранится значение счетчика. Программа изменяет это значение в процессе работы. Приведенные два примера показывают как используется символ #. Если вам нужно загрузить в аккумулятор содержимое этой ячейки, мы пишем mov a,count. Если вам для чего-то потребуется обрабатывать сам адрес ячейки. Например, прибавить к адресу ячейки смещение для того, что бы извлечь значение из ячейки, отстоящей на заранее определенном расстоянии от исходной, то применяется команда mov a, #count. Например, если константа count равна 30, то команда mov a, count запишет в аккумулятор содержимое ячейки с адресом 30. А команда mov a, #count запишет в аккумулятор число 30. В данном случае - это адрес ячейки. Но это знаете только вы - программист. Для микроконтроллера, это просто число 30.

Вот такие объяснения :)

--
С уважением, Белов А.В.