Администрация форума не несёт ответственности за достоверность информации и оставляет за собой право редактировать или в особых случаях даже удалять посты без предупреждения. Спасибо за понимание.

Программирование ATMEL в BASCOM.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Программирование ATMEL в BASCOM. » Кирпичи... » Константы, указатели на переменные и процедуры в операторе DATA


Константы, указатели на переменные и процедуры в операторе DATA

Сообщений 1 страница 9 из 9

1

Оператор DATA позволяет указывать не только фиксированные числа, но и именованные константы. Это гораздо удобнее, когда вы используете некоторые данные в качестве типовых значений.

Иногда в операторе DATA бывает удобно использовать не только фиксированные данные, но и каким-то образом сделать так, чтобы при считывании каждый раз считывалось актуальное значение какой-либо переменной.
На такой случай компилятор позволяет в операторе DATA поставить указатель на переменную.
Саму переменную затем можно побайтно вытащить по указателю при помощи inp().

Что вообще замечательно, так это то, что компилятор позволяет вставить в блок данных даже указатель на пользовательскую процедуру!
Это бывает очень полезно, например, при создании системы меню, где, помимо данных, содержится указатель на функцию, которая их должна обработать.
Для реализации такой возможности есть два способа: первый работает с использованием ключевого слова adr, второй -- с применением указателя на переменную (слово), в которую программа заранее (на этапе инициализации, скорее всего) должна поместить указатель на адрес требуемой процедуры. При этом второй способ хоть и несколько более громоздок, зато позволяет назначать процедуру-обработчик динамически.

Чтобы лучше усвоить материал, запустите на пошаговое выполнение в симуляторе пример реализации указанных возможностей. При этом наблюдайте содержимое регистров R8,9, которые BASCOM использует как указатель чтения операторов DATA, а также регистров R30,31, задающих адрес перехода по команде ! icall.

Дополнительно посмотрите справку по встроенным функциям adr, adr2 - там в качестве примера дана компактная реализация многоуровневого меню.

Код:
$regfile = "m328pdef.dat"
$crystal = 16000000
$hwstack = 48
$swstack = 48
$framesize = 64

$sim

' **** размещение и чтение из оператора DATA констант, переменных и адресов процедур (меток) ***

dim mystring as string * 4
dim s as string * 4
dim w as word
dim w2 as word
dim b as byte
dim b2 as byte
const myconst = 1

' --- чтение указателя на строку ---

mystring = "ABCD"

restore loc_1                                               ' указатель на начало таблицы данных
read b
read b
read w                                                      ' в w прочитаем адрес строки mystring

print hex(w)                                                ' напечатать

s = ""                                                      ' }
do                                                          ' }
   b = inp(w)                                               ' }
   s = s + chr(b)                                           ' }
   incr w                                                   ' }
loop until b = 0                                            ' } прочитать строку в s по адресу w

print s

' --- чтение адреса процедуры и ее выполнение ---

read dataptr                                                ' загрузить в указатель DATA R8,9 адрес процедуры foo
!movw r30,r8                                                ' R8,9 -> R30,31 (адрес перехода)
!icall                                                      ' вызвать процедуру foo по адресу в R30,31
print "done"

restore loc_2                                               ' если после READ были операции, то указатель чтения R8, 9 возможно испорчен. обновить.
read dataptr                                                ' загрузить в указатель DATA R8,9 адрес процедуры foo2
!movw r30,r8                                                ' R8,9 -> R30,31 (адрес перехода)
!icall                                                      ' вызвать процедуру foo2 по адресу в R30,31
print "done 2"

' ---- альтернативный способ --------------

w = loadlabel(foo3)                                         ' получить адрес процедуры foo3 в переменную w
shift w , right                                             ' чтобы получить адрес, надо поделить пополам
print "proc foo3 ptr = " ; hex(w)

restore loc_3                                               ' переместить указатель DATA в место, где хранится указатель на w
read w2                                                     ' прочитать его в w2
b = inp(w2)                                                 ' и получить по указателю значение w
incr w2
b2 = inp(w2)
print "proc from data = " ; hex(b2) ; hex(b)
!LDS R30,{b}                                                ' }
!LDS R31,{b2}                                               ' } b,b2 -> R30,31 (адрес перехода)
!icall                                                      ' вызвать процедуру foo3 по адресу в R30,31
print "done 3"

' --- цепочечные данные --------------

restore loc_4

read b                                                      ' }
read b                                                      ' } прочитать пару dummy-значений
read w                                                      ' прочитать адрес следующей цепочки
print hex(w)

!lds R8, {w}                                                ' }
!LDS R9, {w + 1}                                            ' } установить указатель DATA на следующую метку

while w <> 0
   read b                                                   ' }
   read b                                                   ' } прочитать пару dummy-значений
   read w                                                   ' прочитать адрес следующей цепочки
   print hex(w)
wend

end

' --- процедуры ------------------------------------

foo:
   print "foo"
return

foo2:
   print "foo 2"
return

foo3:
   print "foo 3"
return

' --- данные -----------------------------------
loc_1:
data myconst , 1 , varptr(mystring)
adr foo                                                     ' ссылка на процедуру в DATA должна быть выровнена по СЛОВУ (четное число байт)!!!
                                                            ' в противном случае нужно поставить метку и сделать restore метка
loc_2:
adr foo2                                                    ' ссылка на процедуру в DATA должна быть выровнена по СЛОВУ (четное число байт)!!!
                                                            ' в противном случае нужно поставить метку и сделать restore метка
loc_3:
data varptr(w)

loc_4:
data 1 , 2                                                  ' какие-то данные
adr2 loc_5                                                  ' адрес следующих данных или 0, если конец цепочки

data 1 , 2 , 3 , 4 , 5 , 6                                  ' какие-то совсем другие данные

loc_5:                                                      ' следующая цепочка:
data 7 , 8 , 0%                                             ' какие-то данные и 0 (слово) = конец цепочки


UPD: а еще в операторе DATA можно вычислять простые выражения. Когда это может пригодиться? Ну, например, какие-то константы у вас описаны как битовые поля, и вы можете использовать их как составные. Пример: data DATATYPE_DYNAMIC or DATATYPE_STRING задает битовые флаги признаков последующих данных.

UPD2: перед чтением командой read всегда инициализируйте указатель (командой restore метка или прямой загрузкой R8,9)! В начале программы он смотрит вообще неизвестно куда; и в процессе выполнения некоторых команд тоже может испортиться.

UPD3: не забывайте, что ссылка на процедуру в DATA должна быть выровнена по СЛОВУ (четное число байт). При этом следует учитывать: если среди данных имеются строки, то строка занимает число_символов + 1 байт.
Таким образом, код

data1:
data 1 , 2 , "ABCD" , 3
adr foo

будет работать, поскольку указатель на процедуру имеет четное смещение (перед ним 8 байт: 3 байта представлены числами 1, 2 и 3, и 5 байт занимает строка "ABCD".
В то же время код

data1:
data 1 , 2 , "ABC" , 3
adr foo

не сработает, поскольку указатель на процедуру расположен по нечетному адресу.

Что делать, если при какой-либо строке указатель не выровнен пословно? Добавьте к строке пробел, а после чтения обработайте ее при помощи trim.

Отредактировано Cirno_9 (2019-08-19 15:22:26)

+6

2

Cirno_9
Рады видеть вас у нас на форуме!
Пост действительно полезный и я себе его в закладки сохранил.

У меня к вам есть вопрос, думаю что по теме.

Красиво я его так и не смог решить, пришлось ухищряться.
Суть вопроса такова.

Есть много блоков данных, например:

Eff_0:
data 13 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 70 , 80 , 4 , 5

Eff_1:
data 21, 20 , 30 , 40 , 50, 60 , 70 , 80 , 4 , 5 , 6 , 7 , 8 , 9 , 10  , 4 , 5 , 6 , 7 , 8 , 9 , 10

Eff_2:
data 10 , 24 , 34 , 44 , 54, 64 , 74 , 84 , 30 , 40 , 5

Eff_3:
data 7 , 2 , 3 , 4 , 5 , 6 , 7 , 8

Мне нужно получать данные из любого блока, обращаясь к нему по составному имени с помощью Lookup или restore.
То есть  имя блока должно быть сформировано из одинакового для всех "Eff_" и последующего номера, который может быть любым числом, не превышающим количество блоков.

name_block  = "Eff_" + 0
. . .

name_block  = "Eff_" + 3

Сможете помочь?

0

3

Насколько я понимаю, вам требуется даже не составное имя, а просто доступ к блокам данным по индексу, который представляет собой номер блока.

Если вы внимательно разобрали пример, то решение там уже есть.

Ключик к управлению указателем на чтение данных из блоков DATA - манипуляция регистрами R8,9, которые BASCOM использует в качестве указателя чтения для READ.
Загружая в регистры R8,9 свои значения, мы можем установить этот указатель на любое интересующее нас место блока данных.
Если блоки данных - фиксированной длины, то задача решается элементарно. Для этого нужно получить адрес начала данных. Сделать это можно как через loadlabel (метка начала блока данных), так и путем установки указателя чтения штатными средствами (restore метка) и последующего чтения в переменную (cкажем, word wBaseDataPointer) адреса указателя из регистров R8,R9:

Код:
restore data_1
! sts {wBaseDataPointer}, R8                          
! sts {wBaseDataPointer+ 1}, R9                                  


Теперь, если размер блока данных известен, нам достаточно вычислить нужный адрес, помножив wBaseDataPointer на индекс на размер блока, а затем прибавив результат к wBaseDataPointer (исправлено). Потом установить указатель чтения блока DATA, загрузив полученное значение в R8,9:

Код:

' начало исправления
wDataOffset = i * bDataSize
wDataPointer = wBaseDataPointer + wDataOffset
' конец исправления )
! lds R8, {wDataPointer}
! lds R9, {wDataPointer + 1}

for i = 1 to bDataSize                           
   read bMyDataSet(i)                  ' читать данные из блока
next

print bMyDataSet (DEV_TYPE)            ' }
print bMyDataSet (DEV_VALUE)           ' }
...
print bMyDataSet (DEV_SOMETHING)       ' } вывести считанные данные из блока


Если же размер блоков данных не фиксирован, то задача немного сложнее, и сводится к последнему примеру, приведенному в посте выше - цепочечной организации данных ака chain data.
Для этого в начале каждого блока данных следует указать его длину, а в конце - прописать указатель на следующий блок (поставить adr2 метка_след_блока или 0% ("длинный 0" размерности word) если этот блок последний).
В программе нужно завести индексный массив размерности word и глубиной, равной числу предполагаемых блоков. Затем, при старте, следует проиндексировать всю цепочку с занесением адресов меток начала каждого блока данных в индексный массив, проверяя блок на "последнесть" )
В дальнейшем адрес можно будет брать из этого индексного массива по индексу, загружать его в R8,9, как описано выше, и считывать данные.
Если быстродействие не критично, а зато памяти маловато, можно обойтись без предварительного индексирования, каждый раз пробегая всю цепочку данных, прибавляя индекс для каждого блока и остановившись на нужном.

UPD: исправила: адрес начала блока надо, конечно же, не домножать на индекс i, а прибавлять к нему размер * i

Отредактировано Cirno_9 (2019-08-18 11:49:02)

+4

4

Cirno_9
Большущее вам спасибо! Успехов в творчестве!

0

5

Cirno_9
оригинально! Спасибо.

0

6

Если внимательно разобрали пример в первом посте, то наверняка заметили, что в BASCOM есть не слишком документированная фича dataptr, которая, по сути, и являет собой указатель чтения из DATA, т.е. пару регистров R8,9.
К сожалению, я не нашла способа загрузить данные в или прочитать этот указатель напрямую. Только read dataptr работает, что позволяет нам загрузить из блока DATA словное значение (word) в регистровую пару R8,9, и тем самым управлять последовательностью чтения значениями, указанными в самом блоке DATA. Во всех остальных случаях приходится напрямую манипулировать R8,9.

+1

7

Это конечно всё супер, но вносит большую путаницу в прозрачность кода.
Да, такое решение возможно поможет упростить написание алгоритма и свести к минимуму лишние телодвижения (отдельно обрабатывая/контролируя кодом), но это может стать и проблемой, т.к. компилятор уже сам начинает что-то там додумывать в переменных/коде. Отсюда может стать проблемой в нерациональном использовании памяти и скорости выполнения. Как пример помню казалось бы простую операцию, чтения переменной WORD из SPI, долго не мог понять, почему медленно оно работает, оказалось компилятор генерил лишнее, пришлось на ассм уходить (для быстроты), а это была простая задача.. :)

0

8

RDW написал(а):

компилятор уже сам начинает что-то там додумывать в переменных/коде. <...>  компилятор генерил лишнее, пришлось на ассм уходить


Ничего тут компилятор не "додумывает" - использование регистров R8, 9 в качестве указателя для чтения данных из блоков DATA - это вполне документированная особенность работы BASCOM, тянется с дремучих времен, и не видно причин, чтобы изменилось в следующих версиях.
Как раз небольшое использование асма позволяет нам добиться очень важных возможностей -- а именно индексированного доступа к табличным данным, а использование в таблицах указателей на переменные и процедуры - динамического доступа к данным и гибкому исполнению кода.

А хотите без асма? С версии 2.8.0.1, кажется, BASCOM делает все регистры доступными как обычные переменные, с именами Rx, где х -- номер регистра.
То есть нам даже не надо прибегать к ассемблеру и писать что-то вроде:

! lds R8, {wDataPointer}
! lds R9, {wDataPointer + 1}

, а вместо это просто:

R8 = low (wDataPointer)
R9 = high (wDataPointer)

и дело в шляпе!

+3

9

Cirno_9 написал(а):

и дело в шляпе

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

Cirno_9 написал(а):

Ничего тут компилятор не "додумывает"

Я писал образно, в разных применениях, у меня были случаи (и не раз).
А если вы так уверенны в компиляторе Баскома, то посмотрите детально, что он и как генерит, если писать всё изначально на асме с использованием архитектуры, то код явно будет выглядеть по другому (подобную тему мы уже тут (на форуме) рассматривали).

0


Вы здесь » Программирование ATMEL в BASCOM. » Кирпичи... » Константы, указатели на переменные и процедуры в операторе DATA