AVR. Учебный курс. Конечный автомат

Каждый кто пытался разбираться с конечными автоматами наверняка натыкался на всякие замудреные графы, какие то графики. Многие посчитав это слишком сложным плюнули и забили. А Зря!

С простейшим конечным автоматом каждый из нас сталкивался с самого детства — это механическая авторучка. Объект с единственной функцией «Нажатие кнопки», но в зависимости от очередности результат разный. Стержень то прячется, то вылазит.

Так и в нашем случае — конечный автомат это функция которая запоминает свое состояние и при следующем вызове делает свое черное дело исходя из прошлого опыта. Простой пример — мигалка (псевдокод):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; Глобальные переменные
u08 Blink_State;
 
void Blink(void)
{
if (Blink_State == 1)
	{
	Led_On();
	Blink_State = 0;
	Return;
	}
if (Blink_State == 0)
	{
	Led_Off();
	Blink_State = 1;
	Return;
	}
}

Вызывая эту функцию подряд мы заставим диодик менять свое состояние при каждом вызове.

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

Приведу другой, более сложный пример. Генерацию сигнала сложной формы.

Пусть у нас есть сигнал:

Цифрами указана задержка, в каких нибудь величинах. Это не суть важно.

Обычно народ не парится и лепит его по быдлокодерски, через delay:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Set_Pin();	// Вывод в 1
Delay(10);	// Задержка 10
 
Clr_Pin();	// Вывод в 0
Delay(100);	// Задержка в 100
 
Set_Pin();
Delay(1);
 
Clr_Pin();
Delay(5);
 
Set_Pin();
Delay(2);
 
Clr_Pin();
Delay(3);
 
Set_Pin;
Delay(4);
 
Clr_Pin();

Это дает минимальный код, но затыкает работу контроллера на весь период посылки. А если слать надо постоянно? Да еще дофига всего попутно делать? Экран обновлять, данные обрабатывать, в память писать…
В таком случае у нас рулит RTOS, где на Delay происходит передача управления диспетчеру. Но если ОС нету? Вот тут то и идет в ход конечный автомат.

Проще всего тут применить таймер и его прерывание по переполнению. Таймер сам, в своем прерывании, будет загружать себя новыми значениями выдержки и щелкать дальше.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Timer_Overflow_Interrupt(void)
{
switch(TMR_State)			// Обработчик прерывания по переполнению
	{
	case 0:	
		{ 
		Clr_Pin();		// Вывод в 0
		TCNT = 255-100; 	// Задержка в 100 (до переполнения)
		TMR_State = 1; 		// Следующая стадия 1
		Break;			// Выход
		}
 
	case 1:	
		{ 
		Set_Pin();
		TCNT = 255-1; 
		TMR_State = 2; 
		Break;
		}
 
	case 2:	
		{ 
		Clr_Pin();
		TCNT = 255-5; 
		TMR_State = 3; 
		Break;
		}
 
	case 3:	
		{ 
		Set_Pin();
		TCNT = 255-2; 
		TMR_State = 4; 
		Break;
		}
 
	case 4:	
		{ 
		Clr_Pin();
		TCNT = 255-3; 
		TMR_State = 5; 
		Break;
		}
 
	case 5:	
		{ 
		Set_Pin();
		TCNT = 255-4; 
		TMR_State = 6; 
		Break;
		}
 
	case 6:
		{ 
		Clr_Pin();
		Timer_OFF(); 		// Выключаем таймер. Работа окончена
		TMR_State = 0; 		// Обнуляем состояние
		Break;			
		}
 
	default: 	break;		
	}
}

А запускается эта байда простым пинком:

1
2
3
4
5
6
Set_Pin();
TCNT=255-10;		// Грузим таймер на выдержку 10 тиков
TMR_State = 0;		// Устанавливаем начальное положение
Timer_ON();		// Поехали!
 
... // После чего можно заниматься чем угодно.

Когда автомат отработает — сама себя выключит. Можно еще какоенить событие сгенерировать, дабы основная программа поняла, что все уже готово. Хотя основная программа спокойно может палить переменную состояния автомата и все оттуда узнать сама.

Конечный автомат можно зациклить — скажем на стадии 6 сделать перенаправление на стадию 0, то получим генератор сигнала сложной формы. Причем он будет занимать минимум процессорного времени.

Если мы, например, захотим сделать десяток ШИМ сигналов? То что нам мешает повесить их на ОДИН таймер, главное отсортировать все скважности по возростанию, причем сортировать их вместе с ногами которыми нужно дрыгать. А потом прогнать по прерыванию таймера конечный автомат, да так чтобы он по стадиям передергивал ножки. Правда при изменении скважности любого из этих софтверных ШИМ каналов придется делать повторную сортировку всего массива. Разумеется больших скоростей мы на этом не получим. Таймер не может щелкать так быстро, да и математики там хватит. Но для многих задач, например, одновременное управление десятком сервомашинок этого более чем достаточно.

А если в том же прерывании таймера сделать выбор следующей стадии исходя не из тупой последовательности, а, скажем, на основе обрабатываемого байта, то мы получим программно-аппаратный генератор, к примеру, 1-Wire кода. Достаточно анализировать входной буфер и если там 1 — перебрасывать автомат в состояние соответствующее отработки генерации сигнала 1, а если 0, то в состояние генерящее выдержку нуля.

Более того, на автоматах можно полностью построить логику работы программы. От и до. Получается весьма торчково, пока не въедешь в это всей душой мозг сломать можно, но зато работает просто зверски и очень проста в отладке.

Не буду описывать это так как есть замечательный цикл статей Владимира Татарчевского, опубликованный в журнале Компоненты и Технологии. Настоятельно рекомендую к прочтению. Чтобы вам не шерстить по инету, я все части этой замечательной статьи выложил в архив. Качайте и вкуривайте.

Особенно автоматный метод доставляет тем, что готовые автоматы вписываются как родные в ЛЮБУЮ почти архитектуру. У меня они и во флаговых автоматах работают на ура, и из диспетчера RTOS я их гоняю как родные. Красота!

70 thoughts on “AVR. Учебный курс. Конечный автомат”

  1. Вообще, конечные автоматы — базовое знание для любого программиста.
    Нужно понимать, что и компьютер, и микроконтроллер как частный случай, и любая программа — конечный автомат, пусть и довольно сложный.
    Понимание этого факта дает возможность по-новому взглянуть на код, и нередко нефигово его упростить.
    Плюс можно сделать суперкомпактную выжимку алгоритма, отделив его от кода обработки самого автомата — загнать состояния и условия перехода между ними в массив какой-нибудь.
    Одним словом, КА — рулят!

    1. Ага, только к МК обычно идут от железа, от электроники, а там программированию не учат. В результате получаются такие программные монстры, что страшно становится. С ужасом вспоминаю свои программы пятилетней давности.

      1. Так если б программистов нормально учили, а то ведь та же фигня, КА нам прочли только на третьем курсе. Чего теперь стоит отучиваться писать говнокод…

      2. Нас на специальности 2205 «проектирование и производство электронно-вычислительных средств» тоже програмированию как таковому почти не учили (все свелось к изучению Паскаля), но вот микропрограммные автоматы шли сразу же в курсе цифровой схемотехники.

          1. Чего-чего? А, гугль подсказывает, есть такие кафедры…
            Не. Кафедра ЭВС (ранее — КЭВА), МарГТУ (ранее — МарПИ).

    2. Дада, есть такая фишка — табличная реализация автоматов. Код обработки таблицы пишется и отлаживается один раз и практически — навсегда. Правда, такой подход работает помедленнее switch-технологии, поскольку зачастую нужная строка в таблице ищется тупо перебором.

      Меня во время учёбы очень зацепили микропрограммные автоматы — простая ПЗУ плюс регистр-защелка плюс тактовый сигнал — и можно наворотить нечто, что казалось бы, требует целого процессора. Хотя процессор так и устроен, ага — только добавляются АЛУ, РОН и прочие блоки.

      1. Я с ПЗУ делал лет 10 назад много чего интересного :) Управление ксетником, робота маленького…. Даже делал 4ьитный калькулятор..

  2. Замечу: вместо номеров состояний лучше использовать осмысленные идентификаторы, объявив их перечислением. Легче будет писать и изменять программу, меньше вероятность перепутать.
    Конечно, если состояния меняются строго по порядку — то и с номерами никаких проблем, но часто граф переходов бывает весьма хитрым.

  3. Довелось мне делать технический диплом на заказ, так вот там впервые столкнулся с КА. С его помощью там была организована работа клавиатуры. Довольно сложная в реализации, с первого взгляда. А с помощью конечного автомата — не более страницы вот таких case’ов. Тогда я только понял как это работает в программе, а теперь, прочитав статью — «достиг просветления» =)
    Осталось что-то своё написать пару раз — и умение свернётся в навык.

  4. И у нас был курс по этим автоматам! Я его прошел и забыл, но суть уяснил. Успешно использую это знание в своих работах =)

  5. Уважаемый DI HALT, всегда с интересом и вдумчивостью читаю Ваши статьи, как «железные», так и «софтовые». Не могли бы Вы написать статью по поводу фильтров, рассчёт, применение, ну и всё сопутствующее. Уверен подобная статью будет интересна к прочтению для всех посетителей сайта.

        1. А что конкретно вас интересует в расчете? Использование Матлаба или другой способ подгонки коэффициентов? Или что то другое?

  6. Так вот как это называется, каонечные автоматы))) Я так серваками управлял, правда не знал как этот алгоритм наз-ся) Спасибо, посоветуйте, пож-та книжку какую-нибудь понятно написанную по теории конечных автоматов.

  7. Спасибо за интересную статью. А что если сделать по другому? Например, по каждому прерыванию таймера (CTC), вызывать функцию, которая циклически пролистывает задачи.
    Псевдокод:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    ISR (TIMER0_COMP_vect)
    {   SovokTaskMgr();   }
     
    void SovokTaskMgr(void)
    {
    	mode++;             // зашли в прерывание, выполняем следующую задачу
     
    	switch(mode)	    // цикл задач
    	{
    		case    0: break;
    		case 	1: task1();	break;
    		case	2: task2();	break;
    		case	3: task3();	break;
    		default: break;
    	}
     
    	if(mode == SOVOK_TASK)	{ mode = 0; }		// если выполнена последняя задача в диспетчере, то начинаем цикл заново. SOVOK_TASK = 3
    }

    Чем плох такой подход?

    1. Ничем не плох. Просто не везде он подойдет. Для тупого последовательного выполнения так лучше, а если переключать надо сложней, по более завилистому пути то тут у нас и таблицы состояний и прямая передача управления…

      1. Прямую передачу можно присобачить, тем более, что пример есть в статье :)
        Кстати, а зачем временная задержка нужна?

          1. case 0:
            {
            Clr_Pin(); // Вывод в 0
            TCNT = 255-100; // Задержка в 100 (до переполнения)
            TMR_State = 1; // Следующая стадия 1
            Break; // Выход
            }

            Вот эта -> // Задержка в 100 (до переполнения)

              1. Точно, протирание глаз иногда помогает :) Т.е. основная цель — это генерация такого сигнала. А так по сути, если такие интервалы не критичны, то задержка нафиг не нужна.

  8. imho, переменную состояния лучше запихнуть внутрь функции и объявить как статическую, ибо чем меньше область видимости, тем меньше потом придется разбираться с непонятными зависимостями и побочными эффектами:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    void Blink(void)
    {
    static u08 Blink_State;
    if (Blink_State == 1)
    	{
    	Led_On();
    	Blink_State = 0;
    	Return;
    	}
    if (Blink_State == 0)
    	{
    	Led_Off();
    	Blink_State = 1;
    	Return;
    	}
    }
      1. Вот как понадобится — так и вытаскивать.
        Чем меньше в системе связей, тем проще ее развивать или чинить-диагностировать.

        Еще удобная фишка — область видимости в пределах файла.
        Этакий «почти класс», но чистое Си без использования приплюснутых расширений.

        Кстати, по поводу стилей оформления исходников.
        Я предпочитаю так:
        фигурные скобки всегда с новой строки.
        Весь блок внутри скобок сдвинут вправо относительно скобок на два пробела.
        Не помню, откуда этот стандарт, но так реально удобнее читать, особенно если вложенность хотя бы пять-шесть.

        То есть, в «моём» примере кода 3,4,10 строки надо сдвинуть вправо на 2 пробела, 5,9,11,15 писать с отступом в те же два пробела, 6-8 и 12-14 писать с отступом в 4 пробела.

        Ну и, на правах паранойи, вместо «if ( var == 1 )» лучше писать «if ( 1 == var )», особенно если настройки компилятора не дают предупреждение на опечатку вида «if ( var = 1 )».

        Ага, всё это вполе субъективно-религиозно, а не Великая Истина.

            1. маньяки, блин! больше кнопок нажмете, больше калорий потратите ))
              чем ваc if(var) или if(!var) не устраивает?
              давайте тогда уж x=x+1 писать ))

  9. Хочется (как в случае с меню) найти способ компактной записи таблицы условий/переходов. Тогда прошивку писать вообще просто стало бы )) Но пока case лидирует, хоть и достаточно геморройно его модифицировать.

    1. Ну, например, так:

      Строка таблицы содержит
      1) номер текущего состояния
      2) код условия перехода («терминальный символ»)
      3) ссылка на действие, выполняемое при переходе
      4) номер нового состояния

      Причем, из одного и того же текущего состояния могут быть разные переходы по разным условиям — а, значит, несколько строк в таблице.

      Пример есть в статье Александра Черномырдина http://is.ifmo.ru/progeny/_autmicroc.pdf

      Только вот если входные условия разнородны, потребуется формирователь терминальных символов. (У Владимира Кожаева это названо «контейнер условий перехода» — см. http://www.slideshare.net/uafpug/ss-4604587 )

      1. вот этот «формирователь терминальных символов» всю малину и портит. Условия достаточно сложны (присутствуют вычисления и обработка массивов), чтобы получилась окидываемая взглядом программ автомата. И получается, что по сути терминальных символов всего три-четыре (авария, стоп, переход к следующей фазе, возврат к фазе N), но вычисление их — функция на пять листов, с зависимостью от текущего состояния. Действия тоже достаточно сложные (задать параметры ПИД-регуляторов, например). И красивой структуры не выходит. Красивой именно в том плане, что глазом посмотрел и все понятно.

        Наверно перфекционист в какой-то мере. Хочется чтобы был интерпретатор, вылизанный и не допускающий ошибок. И структура данных, также не допускающая двоякого толкования.

        А так забыл поставить break в каком-то развесистом case — и вылезет это боком через год на реальном оборудовании.

  10. По-моему получилось не менее былокодерски. Не лучше ли сделать массив с длительностью задержек и при каждом прерывании простым кодом брать из него значение? По идее такой способ и места в прошивке будет меньше занимать. А при генерации сигналов длиной 30 и более шагов разница будет очень очевидна :)

  11. Коллега!
    Мигалка на псевдокоде мигать не будет! Будет всегда выключена, потому что второй иф выполнится сразу же за первым

  12. Не упомянут еще один плюс автоматов — при проектировании логики диаграмма состояний очень наглядная штука. Более того, даже заказчик, глядя на нее, начинает понимать, где и что он упустил в требованиях.

  13. Статья как нельзя кстати. На дня нашел вот такое вот чудо:

    http://www.state-machine.com/index.php

    C/C++ Open Source State machine framework. Может интегрироваться в некоторые, популярные RTOS. Поддержка AVR (под WinAVR / IAR).

    Очень хотелось бы заюзать, да только для начала неплохо бы вникнуть в саму суть конечных автоматов.

  14. Любопытно! Но я не совсем понял..
    Суть — использование таймера. Таймер аппаратный, работает быстро, и не вешает программу на время задержек.
    Это замечательно и верно. Используем таймеры вместо задержек.
    Зачем введено понятие «конечный автомат»? Определения позволяют лаконично изъясняться, но в данном случае рекомендация «используй конечный автомат» разве несёт информации больше чем «используй таймер»?

    1. Таймер может сделать только ОДНУ какую либо выдержку. Как сделать так, чтобы ОДИН таймер поочередно реализовал массу выдержек, причем сделал это сам, без вмешательства фоновой программы? С помощью конечного автомата.

    1. Вариантов масса. Начиная с бального комплекта
      LDS R16,Avtom_state
      CPI R16,value
      BREQ nnn
      CPI R16,value
      BREQ mmm
      CPI Rn,value
      BREQ kkk

      До создания таблицы переходов, где Avtom_state будет создавать смещение в таблице, где лежит адрес перехода.

      1. О, большое спасибо. Но если у меня около 30 операций в автомате, разница-то во времени выбора каждого действия будет разная. Правильно я понимаю?

        1. Если на CPI банде то да. Чем глубже тем дольше. А если на таблицах перехода то нет. Каждый кейс будет обрабатываться за одинаковое количество тактов.

      2. error: Relative branch out of reach

        Не подскажете, что это за ошибка? Точнее из-за чего она? У меня сразу в обработчике стоят сравнения.

        TIMER_OV:

        CPI R19,1
        JMP stolb_1

        CPI R19,2
        BREQ stolb_2

        CPI R19,3
        BREQ stolb_3

        1. Бранч который в BREQ не умеет прыгать дальше чем 127 (или даже 64) команды вверх или вниз. У тебя просто он не достает до метки.

          Обычно в таких случаях делается островок вида:
          RJMP Obhod
          m: RJMP M
          m2: RJMP M2
          Obhod:
          …..

          и делается проброс бранча через джамп дальше,куда тебе надо. Но это херня, код превращается в кашу. Лучше юзай табличный переход.
          http://easyelectronics.ru/avr-uchebnyj-kurs-vetvleniya.html

  15. Примеры сделаны на пинбоорде v1.1, для тех кто не сильно понимает язык С++ (хотя я осознаю что рано или поздно надо будет и им овладеть, но пока мне и asm хватает, аппетит приходит как говориться во время еды _)
    Конечный автомат (state machine, машина состояний) представляет собой устройство, имеющее внутреннюю память (переменные состояния), а также набор входов и выходов.
    Работа автомата состоит в том, что он анализирует состояния своих входов, и, в зависимости от значений входов и своего внутреннего состояния, изменяет значения выходов и внутреннее состояние. Правила, в соответствии с которыми происходит изменение:
    1. диаграммой переходов или
    2. описываются таблицей.
    Диаграммы как я понял по ходу дела составляются из блок схем, нарисовать не могу, т.к. не знаю как вставит в коменты (отзывы) картинку, но код мне кажется должен выглядеть вот так:

    iini:		cbi		ddrD,6; инициализация, настройка кнопки на подтяжку
    		sbi		portD,6; PullUP
    knopka:		sbic		pinD,6; тест кнопки на
    		rjmp		knopka; 0 – GND
    ;------------------------------------------
    ;Задержка опроса кнопки тупым циклом т.к. таймерами я еще не овладел, в процессе еще … извините.
    			LDI	R26,255;LowByte;
    			LDI	R27,255;MidleByte;
    			LDI	R28,4;HighByte;
    loop1:			SUBI	R26,1;
    			SBCI	R27,0;
    			SBCI	R28,0;
    			BRCC	Loop1;
    ;--------------------------------------------
    status:		sbrc		r16,0; Пропустить если бит в регистре очшищен
    		rjmp		led_on; перескок к метке led_on
    		rjmp		led_of; перескок к метке led_of
    led_on:		sbi		ddrD,7; на выход нога
    		sbi		portD,7; поднять ногу
    		ldi		r16,0b00000000; сохранить состояние автомата в регистр
    		rjmp	knopka; переход на ожидание нажатия кнопки
    led_of:		cbi		ddrD,7; нога на  вход
    		cbi		portD,7; вырубить ногу
    		ldi		r16,0b00000001; сохранить состояние автомата
    		rjmp	knopka; переход на ожидание нажатия кнопки

    В качестве ячейки сохранения состояния автомата в данном случае был использован регистр r16, (что бы таким как я было понятнее суть) и в нем (r16) можно было бы сохранить 256 различных состояний автомата, НО как я понял можно использовать вместо одного регистра любую ячейку не только РОН, но и RAM, ROM, EEROM и т.д. (включая внешние накопители), где можно было бы сохранить и считать биты статуса. (вопрос лишь в скорости и целесообразности ясный пень что РОН и RAM самый быстрые ячейки).
    2. описываются таблицей.

    ;Резервирование ячейки в ОЗУ:
    .DSEG	; сегмент RAM  (ОЗУ) для Мега16 начинается с $0060, (см. *.inc на тип контроллера)
    status:		.BYTE 1; зарезервировали 1 байт в ОЗУ
    .CSEG	; кодовый сегмент
    ; Настройка кнопки D6  и очистка ячейка status в ОЗУ.
    ini:			cbi		ddrD,6; надо обнулить направление, 
    			sbi		portD,6; поднять PullUP
    			clr		r16; очистка регистра
    			sts		status,r16; для того что бы обнулить ячейку в ОЗУ
    ; Проверка на нажатие кнопки с помошью флага Т
    knopkaa:		in		r17,pinD; чтение из порта save в регистр
    ;------------------------------------------
    ;Задержка опроса кнопки тупым циклом т.к. таймерами я еще не овладел, в процессе еще … извините.
    			LDI	R26,255;LowByte;
    			LDI	R27,255;MidleByte;
    			LDI	R28,4;HighByte;
    loop1:			SUBI	R26,1;
    			SBCI	R27,0;
    			SBCI	R28,0;
    			BRCC	Loop1;
    ;--------------------------------------------
    			bst		r17,6; сохранить в 6 бит регистра в sreg T
    			brts		knopkaa; есть/нет sreg T, кнопка нажата или нет
    			ldi		r20,0b00000001; загрузить число 1, 
    			lds		r18,status; сохранить из ячейки ОЗУ в регистр 
    			sub		r20,r18; вычесть 1 – status.
    ; Косвенный переход (ijmp) по адресу в таблице (tablica1), бессовестно скопипастена у Di Halt’a, но пока еще до конца не осмыслена (в процессе т.с. близко к финишу).
    			lsl		r20; сдвиг влево (в данном случае получается значение 2 или 4)
    			ldi		zl,low (tablica1*2); умножаем адрес таблицы на 2, младший байт
    			ldi		zh,high (tablica1*2); умножаем адрес таблицы на 2 старший байт
    			add		zl,r20; сложить младший байт (r30) с ячейкой в таблице
    			clr		r21; обнулить регистр, хотя end FLASH = 0x1FFF, а не 0x00FF
    			adc		zh,r21; вносим в r31 значение 0, хотя если табл. в конце флеша будет то видимо надо будет переписать по другому
    			lpm		r20,z+; загрузка в r20 значения адреса с пост-инкрементом
    			lpm		r21,z; загрузка в r21 значения адреса 
    			movw	zh:zl,r21:r20; копировать регистровые пары
    			ijmp; переход по адресу указанному в ячейках r31:r30
    Вот пошла таблица с ячейками ссылок на метки:
    			rjmp	mimo; перескок через таблицу на всякий случай
    tablica1:	.dw	led_on_D7, led_of_D7; отсчет ячеек идет само собой с нуля, через запятые.
    mimo:			nop;
    led_on_D7:		sbi		ddrD,7; на выход 
    			sbi		portD,7; поднять ногу зажечь диод
    			ldi		r16,0b00000000; 0 значение в регистр
    			sts		status,r16; save значение в ОЗУ в ячейку status
    			rjmp	knopkaa;
    led_of_D7:		cbi		ddrD,7; на вход
    			cbi		portD,7; нога вниз
    			ldi		r16,0b00000001; 1 значение в регистр
    			sts		status,r16; save значение в ОЗУ в ячейку status
    			rjmp	knopkaa;

    Мне так кажется что табличный вариант лучше, удобнее (читабелнее и в отладке в самый раз), выглядит стройнее, не говоря о комбинации с косвенным переходом, (без перебора значений), не говоря о практически безграничной (размер таблицы переменных значений состояний автомата в ОЗУ, уже в голове созрел коварный план заныкать таблицу в ОЗУ в конце, а SP поднять выше таблицы.)
    p/s: примеры у меня работают не забывайте выставить конец стека и во втором примере зарезервировать байт в ОЗУ.

    1. ну вот опять выглядит не очень, табуляцию не поддерживает да? Видимо надо было пробелами двигать. Пожаулуйста Di Halt подправь мой комент так что бы было видно для тех кто только учится.

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

    2. Таблицы переходов они же статичные и нет никакого смысла ныкать их в ОЗУ. Только зря оперативку забивать. Клади их в флеш и доставай через LPM как я в примере переходов делал. И будет тебе счастье. А переменная состояния автомата она же индекс таблицы. На одном байте даст тебе 256 вариантов. Единственное надо терминировать или запрещать несуществующие варианты, иначе будет очень и очень плохо.
      Это можно сделать маскированием, а лучше сделать таблицу на полные 256 байт (не так уж это и много), но забить ее переходом, скажем, на ресет. Или, лучше, в цикл ловушку — бесконечный цикл, с прицелом на то, чтобы вачдог перезагрузил контроллер. Чтобы если автомат сбойнул, то автоматом сразу же сбросило контроллер.

      1. уже в голове созрел коварный план заныкать таблицу «STATUS» (таблицу-ячейки состояний) в ОЗУ в конце, а SP поднять выше таблицы»
        ошибочка в торопях вышла не дописал слово. А так понятно что таблица состояний в ОЗУ, а таблица косвенных переходов в Флеше. (спасибо что поправили -)).

        1. да и там (в ОЗУ) мы можем задать столько состояний автомата сколько захотим, т.к. уже два бита нам дадут 65 535 состояний автомата, а ячеек там за вычетом начальных адресов для Мега 16, 0x45F-0x60=0x3ff(если я правильно понимаю то это 256 в 1024 степени, короче говоря очень много), а РОН можно и не трогать, в любом случае ячеек в ОЗУ больше чем 32 ячейки РОН.

          1. Вообще обычно РОН рассматривают как ячейки временного хранения действительные только в данном обработчике или в данном прерывании. Целенаправленно какие то данные в течении всей программы в них никогда не хранят. Или делают так очень редко да в небольших программках. Иначе и ошибок больше и налажать больше шансов.

            Все данные нычут в озу. Благо в одну команду можно достать-сохранить данные без особых проблем.

      2. В коде так и есть значения автомата (status) в ОЗУ
        ______________________________________________________
        .DSEG ;
        status: .BYTE 1; зарезервировали 1 байт в ОЗУ
        ______________________________________________________
        А значения таблица переходов во Флеше:
        ______________________________________________________
        .CSEG ; кодовый сегмент
        …..
        rjmp mimo;
        tablica1: .dw led_on_D7, led_of_D7;
        mimo: nop;
        ______________________________________________________

    3. > Диаграммы как я понял по ходу дела составляются из блок схем

      Не совсем так. У диаграмм состояний другие элементы.
      А вообще, и блок-схемы и диаграммы состояний — это подмножества нотации UML (Unified Modeling Language).
      Есть куча софтин в которых можно рисовать эти диаграммы — UMLpad, ArgoUML и т.п.
      В универсальных рисовалках Visio и Dia тоже есть наборы элементов «State Chart».

  16. Еще есть такой интересный тренд — по нарисованной диаграмме состояний можно автоматически сгенерировать код переходов.
    А то ж при ручном написании, как верно заметил Steel.ne:» забыл поставить break в каком-то развесистом case — и вылезет это боком через год на реальном оборудовании.»

    В качестве примеров — IAR VisualState, Visio2SWITCH (плагин к Visio), Unimod и Acceleo (плагины к Eclipse), BOUML…

    Подпрограммы обработки, конечно, придется таки самому писать — но при правильной декомпозиции они получаются практически линейными.

  17. Может не по теме, но как найти сам «обучающий курс» с вашей платой. Или эти статьи и есть курс?
    Как-то не понятно организован сайт кажется.

    1. Статьи и есть курс. Правда после обновления движка они развернулись в обратном порядке, т.е. самые ранние статьи оказались в самом конце. Все примеры рассматриваются на мега16, которая стоит на плате.

  18. Хм…Специально потратил час, чтобы переделать свою прогу под конечный автомат. Вот что было:(пример)

  19. Ой….Вот код в общем:

    void func1()
    {
    if(Button()==3)
    {
    func2();
    break;
    }
    }
    void func2()
    {
    if(Button()==3)
    {
    func3();
    break;
    }
    }
    ............
    void funcn()
    {
    if(Button()==3)
    {
    break;
    }
    }

    Этот код занимает меньше места во флеше. Пробовал на Тини2313, последняя функция с конечным автоматом не влезла, а мой код в принципе влезает, да ещё и место есть.

    1. Тут очень много зависит от реализации и правильного построения всего этого. У меня часто на автомате вся структура какой либо фичи построена, а без него код превращается в полную кашу которую нельзя ни изменить и поправить, чтобы все не рухнуло.

      1. Ясно)) Тоже такое бывает, но вот так и оставил в конце концов) В принципе, пробовал вносить поправки, всё переделывать не приходится) Со вставкой/удалением функции, во всяком случае, проблем не было)

Добавить комментарий

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha