Телесистемы
 Разработка, производство и продажа радиоэлектронной аппаратуры
На главную   | Карта сайта | Пишите нам | В избранное
Требуется программист в Зеленограде
- обработка данных с датчиков; ColdFire; 40 тыс.
e-mail:jobsmp@pochta.ru

Телесистемы | Электроника | Конференция «Микроконтроллеры и их применение»

Не z1 и z2, а ZL и ZH. Это синонимы регистров r30 и r31, объявленные в файле 8535def.inc - в нем же объявлены и остальные символические имена, встречающиеся в тексте

Отправлено =AVR= 15 апреля 2007 г. 19:10
В ответ на: Продолжение учебной задачки (2) отправлено pio 15 апреля 2007 г. 15:59

Разумеется, можно написать и in r30,$19, и ldi r16,0b10000011, и даже вообще не пользоваться никакими символическими именами, указывая только цифровые значения констант и меток, но это сделает написание программы мучительным процессом, полным ошибок, а прочтение и понимание такой программы - сплошным потоком матерных слов из уст читателя. Поэтому люди придумали и используют символические присвоения, аналогичные тем, что встречались тебе в математике - пишется в общем виде уравнение x = a*b + 4*c*d, туда подставляются нужные численные значения a,b,c,d, и вычисляется нужный результат.

Немного пояснений по тексту. Во-первых - семисегментные коды и их привязка к подключению индикаторов:


.equ a = 1 << PB0 ; 7-segment LED segment assignments:
.equ b = 1 << PB1 ;
.equ c = 1 << PB2 ; f
.equ d = 1 << PB3 ; e a
.equ e = 1 << PB4 ; g
.equ f = 1 << PB5 ; d b
.equ g = 1 << PB6 ; c

Каждая строчка начинается с директивы .equ. Это указание ассемблеру отныне и впредь считать, что символическому имени, стоящему слева от знака равенства ("="), присвоено численное значение выражения, стоящего справа от знака равенства. Под выражением подразумевается любой набор допустимых для конкретного ассемблера арифметических и логических операций, имеющий результатом численное значение. Операндами такого выражения могут быть как числа, так и такие символические имена, которым ранее (т.е. в предыдущих строках) уже были присвоены численные значения.

В примере это имена PB0..PB7, и я знаю, что им в файле 8535def.inc были присвоены численные значения, но какие - не помню (и не хочу помнить - на это и есть ассемблер, и файл). Я назначил, что сегмент "a" подключен к выводу PB0, "b" - к PB1 и т.д. Для того, чтобы не писать двоичные коды вручную (и не путаться в этих 0b101001010), я применил хитрый на первый взгляд, но простой по сути прием - назначил каждому сегменту двоичный вес, соответствующий его позиции в байте порта. Это сделано операцией "1 << X", где Х - номер бита (ноги) в байте порта. "1 << X" означает "сдвинуть единицу влево на Х позиций", или "умножить единицу на 2 в степени Х". Заглянув в 8535def.inc, ты увидишь, что PB0=0, PB1=1, PB2=2,...PB7=7, значит, "a" будет присвоено значение 1, "b"=2, "c"=4, "d"=8,..."h"=128. В двоичной записи это будет 00000001, 00000010, 00000100, 00001000... 10000000 соответственно.

И теперь - ради чего это затевалось. Глядя на рисунок индикатора с поименованными сегментами (a..g), я быстро и без ошибок составил коды цифр, записывая не проклятые нули и единички, а просто складывая ранее вычисленные двоичные веса горящих в каждой из индицируемых цифр сегментов. К каждой цифре я добавил всегда "горящий" несуществующий сегмент "h", об этом чуть позже. Полученные коды я, естественно, назвал именами соответствующих индицируемых цифр, по ним дальше и буду к ним обращаться в программе:


.equ zero = a+b+c+d+e+f+h ; Seven-segment codes for 0..5, 7 digits (others are not used),
.equ one = a+b+h ; bit 7 set to 1 to keep the unused PORTB.7 pin pulled up internally
.equ two = a+c+d+f+g+h ;
.equ three = a+b+c+f+g+h ;
.equ four = a+b+e+g+h ;
.equ five = b+c+e+f+g+h ;
.equ seven = a+b+f+h ;

Теперь надо подумать о представлении данных твоих сенсоров в требуемых единицах измерения (отображения). В задании указано, что код, получаемый с сенсора в диапазоне 0b0000..0b1111 (т.е. 0..15) должен отображаться как 00..50. Это можно было вычислить, умножив код с сенсора на 3.33, преобразовав результат умножения в двоично-десятичный код, а потом еще и в семисегментный. Но так как значений всего 16, то я заменил все это вычисление-преобразование на табличную подстановку - размер кода в таком случае может быть даже меньше, чем при вычислительном методе, а программа упрощается очень существенно. Таблицу я сделал так, что каждая ее строка дает два байта - первый для десятков, второй для единиц, и расположил эту таблицу после всего кода программы. Писать эту таблицу было очень просто, т.к. все семисегментные коды были вычислены ранее и названы именами соответствующих индицируемых цифр:


segtab:
.db zero,zero ; 0 = 00 mCd
.db zero,three ; 1 = 03 mCd
.db zero,seven ; 2 = 07 mCd
.db one,zero ; 3 = 10 mCd
.db one,three ; 4 = 13 mCd
.db one,seven ; 5 = 17 mCd
.db two,zero ; 6 = 20 mCd
.db two,three ; 7 = 23 mCd
.db two,seven ; 8 = 27 mCd
.db three,zero ; 9 = 30 mCd
.db three,three ; 10 = 33 mCd
.db three,seven ; 11 = 37 mCd
.db four,zero ; 12 = 40 mCd
.db four,three ; 13 = 43 mCd
.db four,seven ; 14 = 47 mCd
.db five,zero ; 15 = 50 mCd

Эта таблица используется следующим образом. В системе команд МК AT90S8535 есть единственная команда чтения байта из программной Flash-памяти - lpm. У нее нет аргументов, но работает она с регистрами Z (r30 и r31, они же ZL и ZH) и r0. По этой команде в регистр r0 считывается байт, лежащий в программной Flash-памяти по адресу, задаваемому в регистре Z (адрес 16-битный, поэтому младшая половина задается в ZL, и старшая - в ZH). 16-битный регистр Z (образованный регистрами r30 и r31, они же ZL и ZH) должен указывать на тот байт Flash, откуда мы хотим прочитать табличное значение. На начальный (нулевой, т.е номер ноль от начала) байт таблицы Z будет указывать, если в него загрузить число, равное байтовому адресу начала таблицы. У нас там есть метка segtab, которой ассемблер присвоил численное значение, равное адресу начального (нулевого) 16-битного (двухбайтового) слова - это особенность Atmel AVR Assembler, и мы ее учитываем, умножая при загрузке Z эту константу на 2 (т.к. в 16-битном слове два 8-битных байта). Сначала мы грузим в ZH старшую половинку (MSB) байтового адреса таблицы (оператор high возвращает старший байт 16-битного аргумента в скобках):


ldi zh,high(segtab*2) ; Load a 7-segment coded display table index MSB to a Z pointer

Потом следовало бы подобным образом загрузить младшую половинку, а затем прибавить к Z значение индекса таблицы, равное удвоенному (нужен байтовый адрес, а у нас элементы таблицы двухбайтовые) показанию индицируемого сенсора - чтобы Z указывал на начало той пары семисегментных кодов, которую предстоит отобразить на индикаторах. Но начало программы специально написано так, что в ZL у нас уже есть показание нужного сенсора, и остается только удвоить ZL и прибавить к нему удвоенное значение (байтовый адрес) младшей половины (оператор low) адреса начала таблицы. Удвоение ZL делается командой логического сдвига ZL влево, а сложение ZL с константой заменяется командой вычитания отрицательного значения этой константы из ZL - в системе команд АВР нет команды сложения с константой, но это не страшно, есть команда вычитания константы, а 1+2=1-(-2):


lsl zl ; Multiply a table index by 2 (2 bytes per table entry)
subi zl,-low(segtab*2) ; Calculate a table offset LSB (zl already contains a sensor value*2)

Теперь Z указывает на ту пару семисегментных кодов в таблице, которая соответствует показанию сенсора - например, при показании 5 регистр Z будет указывать на пару "one,seven", что будет отображено как 17 (милликандел). Осталось вызвать подпрограмму индикации - для этого и используется команда rcall. Участки кода, используемые в программе хотя бы дважды, выгодно и удобно оформлять в виде подпрограмм, заканчивая их командой ret (возврат из подпрограммы). По команде rcall начинается исполнение подпрограммы, а по команде ret исполнение основной программы продолжается с команды, следующей сразу за командой rcall. Адрес команды, с которой должно продолжиться исполнение основной программы, автоматически сохраняется в стеке при исполнении команды rcall. Стек - это назначаемая программистом область памяти данных (ОЗУ), доступ к которой обычно осуществляется при помощи указателя стека SP - Stack Pointer. Работа стека похожа на работу рожка с патронами в автоматах - патроны (байты) туда закладываются бойцом сверху вниз, а выталкиваются пружиной (SP) снизу вверх. В начале программы SP и был установлен на самую верхушку (RAMEND) памяти данных (рожок пустой).

Порт D не используется в твоей программе, но ни одна нога работающего МК не должна болтаться в воздухе. Поэтому в программе приняты меры, обеспечивающие подтягивание всех неиспользуемых ног МК к плюсу питания при помощи внутренних подтягивающих резисторов (pullup). Неиспользуемые ноги сконфигурированы как входы (DDRx.y=0), а подтяжки включены записью "1" в регистры портов (PORTx.y=1)

Составить ответ | Вернуться на конференцию

Ответы


Отправка ответа
Имя*: 
Пароль: 
E-mail: 
Тема*:

Сообщение:

Ссылка на URL: 
URL изображения: 

если вы незарегистрированный на форуме пользователь, то
для успешного добавления сообщения заполните поле, как указано ниже:
введите число 567:

Перейти к списку ответов | Конференция | Раздел "Электроника" | Главная страница | Карта сайта

Rambler's Top100 Рейтинг@Mail.ru
 
Web telesys.ru