Конечный автомат на ассемблере

Обсуждаем контроллеры компании Atmel.
Ответить
Собутыльник Кота
Аватара пользователя
Сообщения: 2896
Зарегистрирован: Сб ноя 13, 2010 12:53:25
Откуда: приходит весна?

Сообщение B@R5uk »

Для распознавания вводимых через USART команд хочу замутить конечный автомат. У меги (да и у тиньки тоже) на такой случай даже есть инструкция IJMP. Засовываем в регистр Z адрес процедуры, обрабатывающей данное конкретное состояние, а при приходе события (символа с USART), вызываем эту процедуру. В теле указатель может меняться для вызова процедуры для другого состояния конечного автомата.

Мой вопрос в следующем: как правильно использовать комбо инструкций LDI, ZL, addr_l / LDI ZH, addr_h / RJMP ? Адрес же не нужно умножать на два как при чтении с помощью инструкции LPM ?
Последний раз редактировалось B@R5uk Пт ноя 25, 2022 14:00:00, всего редактировалось 1 раз.
Реклама
Собутыльник Кота
Аватара пользователя
Сообщения: 2516
Зарегистрирован: Пт июл 12, 2019 22:52:01

Сообщение Eddy_Em »

Если прямо-таки так асм хочется, можно для начала посмотреть, что gcc сгенерит на элементарный switch…
Но таки понятней сразу на С писать, т.к. сложный код на асме выложится в тысячи страниц, которые прочесть далеко не так просто, как сишный код!
Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда.
Я на гитхабе, в ЖЖ
Контактная информация:
Реклама
VNS
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1635
Зарегистрирован: Пт дек 10, 2021 12:48:46
Откуда: Тюмень

Сообщение VNS »

[uquote="B@R5uk",url="/forum/viewtopic.php?p=4325946#p4325946"]Адрес же не нужно умножать на два как при чтении с помощью инструкции LMP ?[/uquote]
Нет, не нужно. Но задачу так и не понял… :dont_know:
Открыл глаза
Сообщения: 60
Зарегистрирован: Вс ноя 13, 2022 14:58:17

Сообщение Bill_ »

[uquote="B@R5uk",url="/forum/viewtopic.php?p=4325946#p4325946"]Адрес же не нужно умножать на два как при чтении с помощью инструкции LMP ?[/uquote]
А что это за инструкция такая - LMP? Что-то я такой не помню.
Реклама
Эиком - электронные компоненты и радиодетали
OKF
Это не хвост, это антенна
Сообщения: 1443
Зарегистрирован: Вт июн 07, 2011 08:03:18

Сообщение OKF »

Код: Выделить всё

.macro LMP
       LPM
.endmacro
:)
Реклама
Это не хвост, это антенна
Сообщения: 1492
Зарегистрирован: Ср июн 25, 2008 15:19:44

Сообщение Demiurg »

Спойлер

Код: Выделить всё

//==================
.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)
Контактная информация:
Реклама
Собутыльник Кота
Аватара пользователя
Сообщения: 2896
Зарегистрирован: Сб ноя 13, 2010 12:53:25
Откуда: приходит весна?

Сообщение B@R5uk »

[uquote="VNS",url="/forum/viewtopic.php?p=4326077#p4326077"]Нет, не нужно. Но задачу так и не понял… :dont_know:[/uquote] Спасибо. Ну, вопрос был не в задаче, а в использовании инструкции. Если задача всё-таки интересна, то я вот такой вот код замутил и отладил:
Спойлер

Код: Выделить всё

;==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
;   Инициализация переменных
                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
;==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
Для трёх состояний можно, конечно, было бы обойтись и 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. Вопрос: что надо в стек совать раньше: младший байт адреса или старший?
Модератор
Аватара пользователя
Сообщения: 19088
Зарегистрирован: Сб авг 14, 2010 15:05:51
Откуда: г. Озерск, Челябинская обл.

Сообщение Starichok51 »

B@R5uk, вообще-то испокон веков сначала отправляется младший байт, потом старший.
пора бы давно это простое правило знать. можно сказать, что это закон для работы с различными данными.
это правило всегда используется и при записи данных в ОЗУ и при записи данных в файлы.
например, файлы графических форматов (картинки), музыкальные файлы содержат длину блоков данных, начиная с младшего байта.
Мудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
Друг Кота
Сообщения: 12867
Зарегистрирован: Сб дек 18, 2021 19:25:32

Сообщение Martian »

Starichok51, а разве не обе формы существуют?
Контактная информация:
OKF
Это не хвост, это антенна
Сообщения: 1443
Зарегистрирован: Вт июн 07, 2011 08:03:18

Сообщение OKF »

Как и бесконечные войны между остроконечниками и тупоконечниками...)
Собутыльник Кота
Аватара пользователя
Сообщения: 2896
Зарегистрирован: Сб ноя 13, 2010 12:53:25
Откуда: приходит весна?

Сообщение B@R5uk »

Martian, существует обе формы. Они даже специальное название имеют.

Starichok51, спасибо. Но как бы я не старался запомнить, к стеку у меня будет всегда подозрение. Потому что вот положили мы младший байт раньше в стек, а в памяти он оказался позже старшего, потому что стек переворачивает очерёдность. Тут сразу становится закономерен вопрос: что важнее, процесс или результат? Какую мнемоничность я бы себе не пытался заучить, всегда останется сомнение из-за этой дуальности. Так что уж не обессудьте за казалось бы глупый вопрос.
Друг Кота
Сообщения: 12867
Зарегистрирован: Сб дек 18, 2021 19:25:32

Сообщение Martian »

Да и очередность стеков тоже вроде в двух формах...
а я не заметил (опять), что речь об атмеге... тогда да.
Контактная информация:
Модератор
Аватара пользователя
Сообщения: 19088
Зарегистрирован: Сб авг 14, 2010 15:05:51
Откуда: г. Озерск, Челябинская обл.

Сообщение Starichok51 »

B@R5uk, стек потому и стек что, что последним положил, то первым вышло.
у меня есть программы, где я из прерывания таймера выхожу в начало цикла через стек.
если конкретно, то в этой программе мне нужно делать работу цикла 1 раз в секунду. соответственно, таймер отмеряет эту секунду.
прерывание таймера у меня ничего специального не делает. в прерывании я загружаю в указатель стека начало стека, потом в стек отправляю адрес начала цикла и выхожу из прерывания.
а в конце цикла у меня стоит такая "заглушка":

Код: Выделить всё

wait_timer:
rjmp wait_timer			; ожидаем прерывания таймера
то есть, топчемся на месте и ждем прерывания таймера.
Мудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
Это не хвост, это антенна
Сообщения: 1492
Зарегистрирован: Ср июн 25, 2008 15:19:44

Сообщение Demiurg »

Тоже подобное делал. Пока не понял, что в этом нет необходимости. Конечный автомат сам себе и диспетчер и так далее.
Контактная информация:
VNS
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1635
Зарегистрирован: Пт дек 10, 2021 12:48:46
Откуда: Тюмень

Сообщение VNS »

[uquote="B@R5uk",url="/forum/viewtopic.php?p=4326317#p4326317"]Если задача всё-таки интересна, то я вот такой вот код замутил и отладил[/uquote]
Задача интересная, но только лишь при описании её просто словами, а не кодом, так как большинство инструкций ассемблера к своему стыду я не знаю. :oops: Если у Вас всё получилось как задумано, то я очень рад что хоть чем-то смог помочь. :)

Добавлено after 8 minutes 57 seconds:
[uquote="Martian",url="/forum/viewtopic.php?p=4326344#p4326344"]а разве не обе формы существуют?[/uquote]
Существуют обе, но если отдать компилятору решать данную задачу "самостоятельно", то он поступит стандартно, вначале младший затем старший… но можно принудительно поступить иначе, если это кому то так необходимо… :)
Друг Кота
Аватара пользователя
Сообщения: 15736
Зарегистрирован: Вт мар 16, 2010 22:02:27
Откуда: ДОНЕЦК

Сообщение BOB51 »

IJMP/ICALL с передачей двух байт адреса начала подпрограммы обработки в теле команды.
Для загрузки Zh:Zl используем адреса, взятые из листинга основной программы.
8)
В случае необходимости сверхоперативного обмена данными (помимо пересылок через стек) используем для хранения любую регистровую пару рон созу и команду MOVW rd,rr.
К примеру
MOVW Zl,R0 ; она же в полной форме MOVW Zh:Zl,R1:R0
:wink:
Это не хвост, это антенна
Сообщения: 1339
Зарегистрирован: Вт ноя 19, 2019 06:10:18

Сообщение tonyk »

Во дичь-то! А переход в узел КА через таблицу адресов слабО сделать? И перед началом анализа привести все символы к одному регистру.
Это не хвост, это антенна
Сообщения: 1492
Зарегистрирован: Ср июн 25, 2008 15:19:44

Сообщение Demiurg »

Тут я показал пример конечного автомата. Для символов с уарт сделать свои таблицы.
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 15736
Зарегистрирован: Вт мар 16, 2010 22:02:27
Откуда: ДОНЕЦК

Сообщение BOB51 »

[uquote="tonyk",url="/forum/viewtopic.php?p=4326741#p4326741"]Во дичь-то! А переход в узел КА через таблицу адресов слабО сделать? И перед началом анализа привести все символы к одному регистру.[/uquote]
В самом начале топикстартер указал на получение команд по UART.
Смысл в дополнительной таблице векторов? Лишний расход ПЗУ?
Другое дело как-то через ОЗУ "подменные" вектора ставить - но то более "многофункциональное устройство с субменюшками" нежели простая дистанционка.
В случае получения команд без контроля достоверности (несинхронный канал при отсутствии контроля целостности данных) посылать ОДИН байт весьма рискованное дело.
А синхронная пересылка это уже лишние проводки (то же SPI как минимум две витые пары и уж никак не ИК или радиоканал).
При пересылке пакета (хотя бы подобие микролан далласа) смысл гнать единичный байт пропадает - достаточно использовать полный адрес обработчика команды, корректируя при необходимости содержимое программы передатчика дистанционки под имеющийся/обновляемый набор команд внешнего устройства.
8)
Друг Кота
Аватара пользователя
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)

Сообщение DX168B »

Моя любимая тема.
Проворачивал косвенные вызовы на ассемблере 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!
Контактная информация:
Ответить

Вернуться в «AVR»