Мой вопрос в следующем: как правильно использовать комбо инструкций LDI, ZL, addr_l / LDI ZH, addr_h / RJMP ? Адрес же не нужно умножать на два как при чтении с помощью инструкции LPM ?
Конечный автомат на ассемблере
Для распознавания вводимых через USART команд хочу замутить конечный автомат. У меги (да и у тиньки тоже) на такой случай даже есть инструкция IJMP. Засовываем в регистр Z адрес процедуры, обрабатывающей данное конкретное состояние, а при приходе события (символа с USART), вызываем эту процедуру. В теле указатель может меняться для вызова процедуры для другого состояния конечного автомата.
Мой вопрос в следующем: как правильно использовать комбо инструкций LDI, ZL, addr_l / LDI ZH, addr_h / RJMP ? Адрес же не нужно умножать на два как при чтении с помощью инструкции LPM ?
Мой вопрос в следующем: как правильно использовать комбо инструкций LDI, ZL, addr_l / LDI ZH, addr_h / RJMP ? Адрес же не нужно умножать на два как при чтении с помощью инструкции LPM ?
Последний раз редактировалось B@R5uk Пт ноя 25, 2022 14:00:00, всего редактировалось 1 раз.
- Реклама
- Сообщения: 2516
- Зарегистрирован: Пт июл 12, 2019 22:52:01
Если прямо-таки так асм хочется, можно для начала посмотреть, что gcc сгенерит на элементарный switch…
Но таки понятней сразу на С писать, т.к. сложный код на асме выложится в тысячи страниц, которые прочесть далеко не так просто, как сишный код!
Но таки понятней сразу на С писать, т.к. сложный код на асме выложится в тысячи страниц, которые прочесть далеко не так просто, как сишный код!
Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
Я на гитхабе, в ЖЖ
Я на гитхабе, в ЖЖ
[uquote="B@R5uk",url="/forum/viewtopic.php?p=4325946#p4325946"]Адрес же не нужно умножать на два как при чтении с помощью инструкции LMP ?[/uquote]
Нет, не нужно. Но задачу так и не понял…
Нет, не нужно. Но задачу так и не понял…
- Сообщения: 60
- Зарегистрирован: Вс ноя 13, 2022 14:58:17
[uquote="B@R5uk",url="/forum/viewtopic.php?p=4325946#p4325946"]Адрес же не нужно умножать на два как при чтении с помощью инструкции LMP ?[/uquote]
А что это за инструкция такая - LMP? Что-то я такой не помню.
А что это за инструкция такая - LMP? Что-то я такой не помню.
- Сообщения: 1443
- Зарегистрирован: Вт июн 07, 2011 08:03:18
- Реклама
- Сообщения: 1492
- Зарегистрирован: Ср июн 25, 2008 15:19:44
Спойлер
Код: Выделить всё
//==================
.macro Proc_FSM
ldz @0*2
rjmp _Proc_FSM
.endmacro
_Proc_FSM:
lpm XH, Z+ ; Считывание старшего байта адреса состояния автомата.
lpm XL, Z+ ; Считывание младшего байта адреса состояния автомата.
ld r16, X ; Считывание состояния автомата.
lpm r17, Z+ ; Считывание количества состояний автомата.
cp r16, r17
brsh Process_Errors_FSM
lpm XH, Z+
lpm XL, Z
movw ZH:ZL, XH:XL
add ZL, r16
adc ZH, RCLR
shiftlwz
lpm r17, Z+
lpm r16, Z
movw ZH:ZL, r17:r16
ijmp
Process_Errors_FSM:
cli
adiw ZL, 2
lpm r16, Z
rjmp Proc_Errors
//==================
Proc_FSM Tab_FSM_KBD_DRV
//==================
Tab_FSM_KBD_DRV:
.db tab_h(_KEYS), MAX_FSM_KBD_DRV_STATES, tab_h(Tab_Jmp_KBD_DRV), ERR_ID_KBD_DRV
Tab_Jmp_KBD_DRV:
.equ MAX_FSM_KBD_DRV_STATES = 5 // Количество состояний автомата.
.equ _KEYS_INIT = 0
.db tab_h(Keys_Init)
.equ _KEYS_NONE = 1
.db tab_h(Keys_None)
.equ _KEYS_DOWN = 2
.db tab_h(Keys_Down)
.equ _KEYS_WAIT_UP = 3
.db tab_h(Keys_Wait_Up)
.equ _KEYS_UP = 4
.db tab_h(Keys_Up)
[uquote="VNS",url="/forum/viewtopic.php?p=4326077#p4326077"]Нет, не нужно. Но задачу так и не понял…
[/uquote] Спасибо. Ну, вопрос был не в задаче, а в использовании инструкции. Если задача всё-таки интересна, то я вот такой вот код замутил и отладил:
Для трёх состояний можно, конечно, было бы обойтись и CP / BREQ связкой, но мне захотелось испытать и отладить что-нибудь новенькое. Тем более, что раньше инструкцией IJMP никогда не пользовался.
Добавлено after 10 minutes 10 seconds:
[uquote="Eddy_Em",url="/forum/viewtopic.php?p=4325974#p4325974"]...можно для начала посмотреть, что gcc сгенерит на элементарный switch...[/uquote]Так я и не научился на си под AVR писать. Пользуюсь AVR Studio 4 и калякаю на асме по мере необходимости. У меня есть большое подозрение, что сишный компилятор выдаст всё таки связку CP / BREQ.
А теперь вопрос на засыпку. Инструкция IJMP использует только регистр Z, так же как и инструкция LPM. Может случиться так, что необходимо будет использовать и то, и другое. С другой стороны не хотелось бы постоянно перекидывать информацию из регистра Z туда-сюда, поэтому адреса было бы не плохо хранить и использовать из других регистров. С чтением программной памяти ничего не поделаешь. А вот переход по динамическому адресу можно замутить с помощью комбо PUSH / RET. Вопрос: что надо в стек совать раньше: младший байт адреса или старший?
Спойлер
Код: Выделить всё
;==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
; Инициализация переменных
ldi r16, 0x0A ; Константа 10 для умножения
mov r10, r16
; Начальное состояние конечного автомата
ldi ZH, HIGH(FSM_st0)
ldi ZL, LOW (FSM_st0)
sei ; Разрешить прерывания
;==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
; Основная программа
mainLoop: rjmp mainLoop
;==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
; Обработка прерывания прихода символа по USART
USART_RXC: in r16, UDR ; Чтение пришедшего символа
out UDR, r16 ; Дублирования пришедшего символа назад
ijmp
;--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--
FSM_st0: ; Состояние 0: распознавание кода команды
cpi r16, 0x70 ; Символ 'p'
breq FSM_st0_0
cpi r16, 0x74 ; Символ 't'
breq FSM_st0_1
cpi r16, 0x50 ; Символ 'P'
breq FSM_st0_0
cpi r16, 0x54 ; Символ 'T'
breq FSM_st0_1
reti
FSM_st0_0: ; Переход в состояние 1
ldi ZH, HIGH(FSM_st1)
ldi ZL, LOW (FSM_st1)
reti
FSM_st0_1: clr r14 ; Инициализация коэффициента деления
clr r15
; Переход в состояние 2
ldi ZH, HIGH(FSM_st2)
ldi ZL, LOW (FSM_st2)
reti
;--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--
FSM_st1: ; Состояние 1: распознавание кода предделителя
subi r16, 0x30 ; Символ '0'
brcs FSM_st1_0
cpi r16, 0x06 ; Диапазон 0 -- 5
brcc FSM_st1_0
ori r16, 0x08
out TCCR1B, r16 ; Установка предделителя
; Переход в состояние 0
ldi ZH, HIGH(FSM_st0)
ldi ZL, LOW (FSM_st0)
FSM_st1_0: reti
;--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--~--
FSM_st2: ; Состояние 2: ввод коэффициента деления частоты
cpi r16, 0x20 ; Символ ' '
breq FSM_st2_0
cpi r16, 0x0D ; Символ перевода каретки
breq FSM_st2_0
; Ввод коэффициента деления частоты
subi r16, 0x30 ; Символ '0'
brcs FSM_st1_0
cpi r16, 0x0A ; Диапазон 0 -- 9
brcc FSM_st1_0
mul r15, r10 ; Умножение сохранённого значения на 10
mov r15, r0
mul r14, r10
mov r14, r0
add r14, r16 ; Добавление пришедшей цифры к результату
adc r15, r1
reti
FSM_st2_0: ; Установка таймера
out OCR1AH, r15
out OCR1AL, r14
; Переход в состояние 0
ldi ZH, HIGH(FSM_st0)
ldi ZL, LOW (FSM_st0)
reti
;==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
Добавлено after 10 minutes 10 seconds:
[uquote="Eddy_Em",url="/forum/viewtopic.php?p=4325974#p4325974"]...можно для начала посмотреть, что gcc сгенерит на элементарный switch...[/uquote]Так я и не научился на си под AVR писать. Пользуюсь AVR Studio 4 и калякаю на асме по мере необходимости. У меня есть большое подозрение, что сишный компилятор выдаст всё таки связку CP / BREQ.
А теперь вопрос на засыпку. Инструкция IJMP использует только регистр Z, так же как и инструкция LPM. Может случиться так, что необходимо будет использовать и то, и другое. С другой стороны не хотелось бы постоянно перекидывать информацию из регистра Z туда-сюда, поэтому адреса было бы не плохо хранить и использовать из других регистров. С чтением программной памяти ничего не поделаешь. А вот переход по динамическому адресу можно замутить с помощью комбо PUSH / RET. Вопрос: что надо в стек совать раньше: младший байт адреса или старший?
B@R5uk, вообще-то испокон веков сначала отправляется младший байт, потом старший.
пора бы давно это простое правило знать. можно сказать, что это закон для работы с различными данными.
это правило всегда используется и при записи данных в ОЗУ и при записи данных в файлы.
например, файлы графических форматов (картинки), музыкальные файлы содержат длину блоков данных, начиная с младшего байта.
пора бы давно это простое правило знать. можно сказать, что это закон для работы с различными данными.
это правило всегда используется и при записи данных в ОЗУ и при записи данных в файлы.
например, файлы графических форматов (картинки), музыкальные файлы содержат длину блоков данных, начиная с младшего байта.
Мудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
- Сообщения: 12867
- Зарегистрирован: Сб дек 18, 2021 19:25:32
Starichok51, а разве не обе формы существуют?
- Сообщения: 1443
- Зарегистрирован: Вт июн 07, 2011 08:03:18
Как и бесконечные войны между остроконечниками и тупоконечниками...)
Martian, существует обе формы. Они даже специальное название имеют.
Starichok51, спасибо. Но как бы я не старался запомнить, к стеку у меня будет всегда подозрение. Потому что вот положили мы младший байт раньше в стек, а в памяти он оказался позже старшего, потому что стек переворачивает очерёдность. Тут сразу становится закономерен вопрос: что важнее, процесс или результат? Какую мнемоничность я бы себе не пытался заучить, всегда останется сомнение из-за этой дуальности. Так что уж не обессудьте за казалось бы глупый вопрос.
Starichok51, спасибо. Но как бы я не старался запомнить, к стеку у меня будет всегда подозрение. Потому что вот положили мы младший байт раньше в стек, а в памяти он оказался позже старшего, потому что стек переворачивает очерёдность. Тут сразу становится закономерен вопрос: что важнее, процесс или результат? Какую мнемоничность я бы себе не пытался заучить, всегда останется сомнение из-за этой дуальности. Так что уж не обессудьте за казалось бы глупый вопрос.
- Сообщения: 12867
- Зарегистрирован: Сб дек 18, 2021 19:25:32
Да и очередность стеков тоже вроде в двух формах...
а я не заметил (опять), что речь об атмеге... тогда да.
а я не заметил (опять), что речь об атмеге... тогда да.
B@R5uk, стек потому и стек что, что последним положил, то первым вышло.
у меня есть программы, где я из прерывания таймера выхожу в начало цикла через стек.
если конкретно, то в этой программе мне нужно делать работу цикла 1 раз в секунду. соответственно, таймер отмеряет эту секунду.
прерывание таймера у меня ничего специального не делает. в прерывании я загружаю в указатель стека начало стека, потом в стек отправляю адрес начала цикла и выхожу из прерывания.
а в конце цикла у меня стоит такая "заглушка":
то есть, топчемся на месте и ждем прерывания таймера.
у меня есть программы, где я из прерывания таймера выхожу в начало цикла через стек.
если конкретно, то в этой программе мне нужно делать работу цикла 1 раз в секунду. соответственно, таймер отмеряет эту секунду.
прерывание таймера у меня ничего специального не делает. в прерывании я загружаю в указатель стека начало стека, потом в стек отправляю адрес начала цикла и выхожу из прерывания.
а в конце цикла у меня стоит такая "заглушка":
Код: Выделить всё
wait_timer:
rjmp wait_timer ; ожидаем прерывания таймераМудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
- Сообщения: 1492
- Зарегистрирован: Ср июн 25, 2008 15:19:44
Тоже подобное делал. Пока не понял, что в этом нет необходимости. Конечный автомат сам себе и диспетчер и так далее.
[uquote="B@R5uk",url="/forum/viewtopic.php?p=4326317#p4326317"]Если задача всё-таки интересна, то я вот такой вот код замутил и отладил[/uquote]
Задача интересная, но только лишь при описании её просто словами, а не кодом, так как большинство инструкций ассемблера к своему стыду я не знаю.
Если у Вас всё получилось как задумано, то я очень рад что хоть чем-то смог помочь. 
Добавлено after 8 minutes 57 seconds:
[uquote="Martian",url="/forum/viewtopic.php?p=4326344#p4326344"]а разве не обе формы существуют?[/uquote]
Существуют обе, но если отдать компилятору решать данную задачу "самостоятельно", то он поступит стандартно, вначале младший затем старший… но можно принудительно поступить иначе, если это кому то так необходимо…
Задача интересная, но только лишь при описании её просто словами, а не кодом, так как большинство инструкций ассемблера к своему стыду я не знаю.
Добавлено after 8 minutes 57 seconds:
[uquote="Martian",url="/forum/viewtopic.php?p=4326344#p4326344"]а разве не обе формы существуют?[/uquote]
Существуют обе, но если отдать компилятору решать данную задачу "самостоятельно", то он поступит стандартно, вначале младший затем старший… но можно принудительно поступить иначе, если это кому то так необходимо…
IJMP/ICALL с передачей двух байт адреса начала подпрограммы обработки в теле команды.
Для загрузки Zh:Zl используем адреса, взятые из листинга основной программы.

В случае необходимости сверхоперативного обмена данными (помимо пересылок через стек) используем для хранения любую регистровую пару рон созу и команду MOVW rd,rr.
К примеру
MOVW Zl,R0 ; она же в полной форме MOVW Zh:Zl,R1:R0

Для загрузки Zh:Zl используем адреса, взятые из листинга основной программы.
В случае необходимости сверхоперативного обмена данными (помимо пересылок через стек) используем для хранения любую регистровую пару рон созу и команду MOVW rd,rr.
К примеру
MOVW Zl,R0 ; она же в полной форме MOVW Zh:Zl,R1:R0
- Сообщения: 1339
- Зарегистрирован: Вт ноя 19, 2019 06:10:18
Во дичь-то! А переход в узел КА через таблицу адресов слабО сделать? И перед началом анализа привести все символы к одному регистру.
[uquote="tonyk",url="/forum/viewtopic.php?p=4326741#p4326741"]Во дичь-то! А переход в узел КА через таблицу адресов слабО сделать? И перед началом анализа привести все символы к одному регистру.[/uquote]
В самом начале топикстартер указал на получение команд по UART.
Смысл в дополнительной таблице векторов? Лишний расход ПЗУ?
Другое дело как-то через ОЗУ "подменные" вектора ставить - но то более "многофункциональное устройство с субменюшками" нежели простая дистанционка.
В случае получения команд без контроля достоверности (несинхронный канал при отсутствии контроля целостности данных) посылать ОДИН байт весьма рискованное дело.
А синхронная пересылка это уже лишние проводки (то же SPI как минимум две витые пары и уж никак не ИК или радиоканал).
При пересылке пакета (хотя бы подобие микролан далласа) смысл гнать единичный байт пропадает - достаточно использовать полный адрес обработчика команды, корректируя при необходимости содержимое программы передатчика дистанционки под имеющийся/обновляемый набор команд внешнего устройства.

В самом начале топикстартер указал на получение команд по UART.
Смысл в дополнительной таблице векторов? Лишний расход ПЗУ?
Другое дело как-то через ОЗУ "подменные" вектора ставить - но то более "многофункциональное устройство с субменюшками" нежели простая дистанционка.
В случае получения команд без контроля достоверности (несинхронный канал при отсутствии контроля целостности данных) посылать ОДИН байт весьма рискованное дело.
А синхронная пересылка это уже лишние проводки (то же SPI как минимум две витые пары и уж никак не ИК или радиоканал).
При пересылке пакета (хотя бы подобие микролан далласа) смысл гнать единичный байт пропадает - достаточно использовать полный адрес обработчика команды, корректируя при необходимости содержимое программы передатчика дистанционки под имеющийся/обновляемый набор команд внешнего устройства.
Моя любимая тема.
Проворачивал косвенные вызовы на ассемблере AVR.
Нужно завести сначала массив указателей на вызываемые подпрограммы - обработчики состояний автомата.
Далее сам автомат, который может вызываться в основном цикле.
Стоит заметить, что я не привожу тут загрузку и сохранение текущего состояния в ОЗУ.
Разумеется, такой метод работает только для конечных автоматов, у которых номера состояний идут подряд.
Для декодирования команд лучше проверять на значение, например, пропуском следующей инструкции, если условие не выполняется, а в пропускаемой инструкции может содержаться переход на нужную процедуру, если условие выполняется.
А вот один адрес в массиве занимает только два байта, что при большом количестве состояний нехило сэкономит ПЗУ и увеличит быстродействие. Быстродействие вообще почти мгновенное: Вычислил смещение, загрузил адрес, вызвал процедуру по этому адресу.
В жизни был проект, где был КА на 45 состояний (обработчик сложного протокола обмена) И там такой метод дал много выйгрыша.
Проворачивал косвенные вызовы на ассемблере AVR.
Нужно завести сначала массив указателей на вызываемые подпрограммы - обработчики состояний автомата.
Код: Выделить всё
;---------------------------------------------- Таблица переходов
STATES:
.dw STATE_0, STATE_1, STATE_2
STATE_0:
ldi temp0, 0x01 ;Переключаем на следующее состояние
ret
STATE_1:
ldi temp0, 0x02 ;Переключаем на следующее состояние
ret
STATE_2:
ldi temp0, 0x00 ;Переключаем на начальное состояние
ret
Стоит заметить, что я не привожу тут загрузку и сохранение текущего состояния в ОЗУ.
Код: Выделить всё
; Много телодвижений связано с тем, что в конкретном МК LPM и IJMP или ICALL работают только с парой Z
; Сначала грузим номер текущего состояния из ОЗУ в регистры temp0 и temp1
; За тем выполняем код ниже:
ldi ZL, Low(STATES*2) ; Грузим начальный адрес массива указателей на процедуры
ldi ZH, High(STATES*2)
lsl temp0 ; В регистрах temp0 и temp1 хранится номер текущего состояния. temp0 - младший байт
clr temp1 ; , temp1 - старший. Но тут он не используется, так как состояний не так много было
add ZL, temp0 ;Смещаемся по массиву для выбора нужного указателя
adc ZH, temp1
lpm temp2, Z ; Грузим младший байт адреса
adiw Z, 0x01 ; Смещаемся к старшему байту адрса
lpm temp3, Z ; Грузим старший байт адреса
mov ZL, temp2 ; Заносим байты алреса процедуры в Z
mov ZH, temp3
icall ; Вызываем процедуру
; Тут можем сохранить номер состояния в ОЗУ
Для декодирования команд лучше проверять на значение, например, пропуском следующей инструкции, если условие не выполняется, а в пропускаемой инструкции может содержаться переход на нужную процедуру, если условие выполняется.
Пара инструкций сравнение + переход по условию занимают четыре байта. А если процедура далеко, то и все шесть. Ну и проход по простыне условий затрачивает лишние такты.Смысл в дополнительной таблице векторов? Лишний расход ПЗУ?
А вот один адрес в массиве занимает только два байта, что при большом количестве состояний нехило сэкономит ПЗУ и увеличит быстродействие. Быстродействие вообще почти мгновенное: Вычислил смещение, загрузил адрес, вызвал процедуру по этому адресу.
В жизни был проект, где был КА на 45 состояний (обработчик сложного протокола обмена) И там такой метод дал много выйгрыша.
Последний раз редактировалось DX168B Сб ноя 26, 2022 11:25:25, всего редактировалось 1 раз.
I am DX168B and this is my favourite forum on internet!


