Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: [Вроде решено] Массив указателей на функции. Указатель на массив
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
Страницы: 1, 2
demiurg1978
Запутался. Честно скажу, с указателями я не всегда дружу. sm.gif Помогите написать правильно. Нужно: есть массивы указателей на функцию. И переменные-состояния конечных автоматов, индексы для массивов.
CODE

//************************************************************************
//************************ Главный автомат *******************************
//************************************************************************

//========================================================================
STATE (PROC_DEVICE_INIT, proc_device_init)
STATE (PROC_DEVICE_WAIT_SWITCH_MODE, proc_device_wait_switch_mode)
STATE (PROC_DEVICE_MANUAL, proc_device_manual_mode)
STATE (PROC_DEVICE_AUTOMAT, proc_device_automat_mode)
STATE (PROC_DEVICE_EMERG_MODE, proc_device_emerg_mode)
//========================================================================

enum _proc_device
{
#define STATE(name, func) name,
#include "_proc_device.h"
#undef STATE

PROC_DEVICE_STATES,
};

//========================================================================
typedef void (*FUNC)(void);
//========================================================================

__flash FUNC proc_device_func [PROC_DEVICE_STATES] =
{
#define STATE(name, func) func,
#include "_proc_device.h"
#undef STATE
};

static u08 _proc_device;
static u08 _proc_device_slave;

void proc_device (void)
{
// proc_sens_pwr (SAVE_EEPROM_PARAMETERS); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

proc_device_func [_proc_device] (); // В данный момент работает так.
}


Мне нужна функция, у которой в качестве параметров proc_device_func и _proc_device.
Что-то вроде proc_fsm_func (proc_device_func, get_proc_device_state ())

Мои пробы выдают ошибки.
psL
Цитата(demiurg1978 @ Jan 29 2017, 08:14) *
Нужно: есть массивы указателей на функцию. И переменные-состояния конечных автоматов, индексы для массивов.
Мне нужна функция, у которой в качестве параметров proc_device_func и _proc_device.
Что-то вроде proc_fsm_func (proc_device_func, get_proc_device_state ())


Ваш вопрос очень путанно сформулирован.
Как-то так.
Код
typedef void (*t_func)(void);

enum s_state{
    STATE1,
    STATE2
};

void func_state1()
{
    printf("func_state1\n");
}

void func_state2()
{
    printf("func_state2\n");
}

t_func funcs[] =
{
    [STATE1] func_state1 ,
    [STATE2] func_state2 ,
};

void func_run(t_func* funcs, enum s_state state)
{
    (*funcs[state])();
}


int main()
{
    func_run(funcs, STATE1);
    func_run(funcs, STATE2);
...
demiurg1978
Мне нужна функция, в качестве параметров которой указатель на массивы указателей на функции, и индексы-переменные-состояния КА. В вашем примере вызов функций конкретного массива по конкретному индексу. А мне нужен вызов функций по указанным в параметрах функции массивам и индексам.
psL
Цитата(demiurg1978 @ Jan 29 2017, 12:43) *
Мне нужна функция, в качестве параметров которой указатель на массивы указателей на функции, и индексы-переменные-состояния КА. В вашем примере вызов функций конкретного массива по конкретному индексу. А мне нужен вызов функций по указанным в параметрах функции массивам и индексам.

поправил
andrew_b
Цитата(demiurg1978 @ Jan 29 2017, 12:43) *
Мне нужна функция, в качестве параметров которой указатель на массивы указателей на функции

Нверное, как-то так:
Код
int func (int (*func_ptr[])(int), int y)
{

}

Синтксис Си в отношении скобок и звёздочек непротиворечив, но с первого взгляда бывает трудно разобраться, что там к чему. Поэтому надо разбить на части. Сначала определяем тип "указатель на функцию":
Код
typedef int (*pfunc)(int x);

Потом используем этот тип в определении функции
Код
int func (pfunc f[], int y)
{

}
Dog Pawlowa
Определяем тип переменной PROCEDURE - указатель на функцию

Код
typedef void (*PROCEDURE)(void);


Пишем текст функций.

Код
void function1(void) { ... }
void function2(void) { ... }
void function3(void) { ... }


Определяем, сколько у нас функций в массиве.

Код
#define STATES 3


Создаем массив указателей на функции в программной памяти, что у нас, AVR..

Код
__flash const PROCEDURE function[STATES_QTY] = { function1, function2, function3 };


Все, в программе вызываем вызываем функцию, соответствующую номеру состояния state:

Код
function[state]();





Цитата(demiurg1978 @ Jan 29 2017, 08:14) *
Мне нужна функция, у которой в качестве параметров proc_device_func и _proc_device.
Что-то вроде proc_fsm_func (proc_device_func, get_proc_device_state ())


А зачем так сложно - в функцию передавать указатель на функцию? Чтобы вызвать? Ну так вызовите добавлением скобок.
demiurg1978
Цитата(Dog Pawlowa @ Jan 29 2017, 16:27) *
А зачем так сложно - в функцию передавать указатель на функцию? Чтобы вызвать? Ну так вызовите добавлением скобок.

Покажите, как.
Dog Pawlowa
Цитата(demiurg1978 @ Jan 29 2017, 15:16) *
Покажите, как.

Уже сделано! Функция вызывается по индексу в таблице:

Код
function[state]();


Что не устраивает?
demiurg1978
Все приведенные примеры по сути повторяют то, что написано в первом сообщении. Излишняя навороченность кода в том сообщении, по сути enum и массив, обвернутые в макросы. И вызов функции один в один как у вас.
Код
   proc_device_func [_proc_device] ();
Dog Pawlowa
Цитата(demiurg1978 @ Jan 29 2017, 16:16) *
Все приведенные примеры по сути повторяют то, что написано в первом сообщении. Излишняя навороченность кода в том сообщении, по сути enum и массив, обвернутые в макросы. И вызов функции один в один как у вас.

Правильно, макросы вредны, а все остальное - классика, которую использует каждый второй.
Что не устраивает то?

Вам то зачем дополнительная прослойка в виде функции, в которую будете передавать указатель на функцию?
Будьте проще, пишите как есть, все же работает.


demiurg1978
Вроде решил вопрос.
CODE

//========================================================================
typedef void (*FUNC)(void);
//========================================================================

void proc_fsm_func (FUNC __flash *ptr_func, u08 s);

void func_1 (void);
void func_2 (void);
void func_3 (void);
void func_4 (void);

extern FUNC __flash list_func [];
void __flash * get_list_func (void);

void func_1 (void)
{

}

void func_2 (void)
{

}

void func_3 (void)
{

}

void func_4 (void)
{

}

__flash FUNC list_func [4] =
{
func_1,
func_2,
func_3,
func_4,
};

void proc_fsm_func (FUNC __flash *ptr_func, u08 s)
{
ptr_func [s] ();
}

void __flash * get_list_func (void)
{
return list_func;
}

proc_fsm_func (get_list_func (), 2);


Задумка такая: что-то вроде ядра обработчика конечных автоматов. Можно было и так оставить, как сделано в первом сообщении. Для каждого автомата своя строка вызова функции по индексу.
А когда задумал ядро обработчика, тут и возникли трудности. Честно говоря, я еще думаю, стоит ли дальше работать над этим ядром и применять его в проектах.
Dog Pawlowa
Цитата(demiurg1978 @ Jan 29 2017, 18:02) *
Для каждого автомата своя строка вызова функции по индексу.

Это реализуется очень просто - одним массивом всех функций, но группы индексов массива для каждого автомата не должны перекрываться.
Например, первые 6 функций - автомат А, следующие 19 функций автомат B, и еще 9 функций автомата C.
тогда если массив называется f, обработка всех автоматов будет выглядеть так:

Код
f[stateA]();
f[stateB]();
f[stateC]();


Я так делал один раз в очень специфическом проекте.
demiurg1978
Еще на ассемблере я использовал функции очистки ОЗУ. В сишные компиляторах это заложено как стандарт. И это дает нам замечательную штуку. Нулевое состояние конечного автомата используем для инициализации ввода-вывода, периферии, переменных. До использования КА я использовал для этого флаги.
В вашем случае есть начальные состояния КА, которые не равны нулю. А это неудобно. Должна быть обязательная функция инициализации.
psL
Цитата(Dog Pawlowa @ Jan 29 2017, 19:14) *
Например, первые 6 функций - автомат А, следующие 19 функций автомат B, и еще 9 функций автомата C.
Я так делал один раз в очень специфическом проекте.

Допустим нужно добавить еще несколько состояний в автомат A, что делать? Менять условия всякий раз?wink.gif
Насколько понимаю, ТС разбивает весь код на задачи и у каждой задачи - свой автомат состояний. Т.е. при переключении на соответствующую задачу должен подключаться соответствующий автомат состояния.
В принципе, очень типичная задача. Ничего специфического. Даже тема была https://electronix.ru/forum/index.php?showt...=136908&hl=
Dog Pawlowa
Цитата(psL @ Jan 29 2017, 19:56) *
Менять условия всякий раз?wink.gif

Дифайны и енумы уже отменили? wink.gif
psL
Цитата(Dog Pawlowa @ Jan 29 2017, 20:12) *
Дифайны и енумы уже отменили? wink.gif

Нет. Просто реализация, исходя из ваших рекомендаций, у ТС получится до безобразия кривой: один глобальный массив со всеми вытекающими. И все это только потому, что указатель на массив функций не осислили)
Dog Pawlowa
Цитата(psL @ Jan 29 2017, 20:47) *
Просто реализация, исходя из ваших рекомендаций, у ТС получится до безобразия кривой: один глобальный массив со всеми вытекающими.

А что там такое вытекает? Инициализация переменных состояния? Для Вас это тоже проблема? laughing.gif
psL
Цитата(Dog Pawlowa @ Jan 29 2017, 21:58) *
А что там такое вытекает? Инициализация переменных состояния? Для Вас это тоже проблема? laughing.gif


Для меня - не проблема, потому что сам так делать не буду и другим не посоветую
demiurg1978
Если конечный автомат сделан по индексному переходу, то не инициализированная переменная-состояние может наделать делов. Именно поэтому хорош способ, когда нулевое состояние - инициализация всего и вся. Тем более что такой способ бесплатен. ОЗУ инициализировано нулями.
Но если switch-case и состояний много, то код становится трудночитаемым. Опять же, switch-case нагляден. Индексный вызов функций - это блудить и искать нужную функцию. Но в этом случае можно частично облегчить задачу. Например, в одном месте вывести названия состояний и соответствующие названиям функции.
Пример:
Код
//************************************************************************
//************************ Главный автомат *******************************
//************************************************************************

//========================================================================
STATE (PROC_DEVICE_INIT,             proc_device_init)
STATE (PROC_DEVICE_WAIT_SWITCH_MODE, proc_device_wait_switch_mode)
STATE (PROC_DEVICE_MANUAL,           proc_device_manual_mode)
STATE (PROC_DEVICE_AUTOMAT,          proc_device_automat_mode)
STATE (PROC_DEVICE_EMERG_MODE,       proc_device_emerg_mode)
//========================================================================
Dog Pawlowa
Цитата(demiurg1978 @ Jan 29 2017, 22:05) *
ОЗУ инициализировано нулями.

ОЗУ будет инициализировано тем, что скажет программист:
int this_variable_is_not_zero=1;
jcxz
Цитата(demiurg1978 @ Jan 29 2017, 21:05) *
Но если switch-case и состояний много, то код становится трудночитаемым. Опять же, switch-case нагляден. Индексный вызов функций - это блудить и искать нужную функцию. Но в этом случае можно частично облегчить задачу. Например, в одном месте вывести названия состояний и соответствующие названиям функции.

Есть гораздо более наглядные способы организации автоматов состояний чем switch/case или туева хуча функций.
Например - переключение стека, когда значениями автомата состояний становятся значения PC (или как там в AVR называется счётчик команд?) И код автомата выглядит совершенно линейным и простым.

Цитата(Dog Pawlowa @ Jan 29 2017, 19:12) *
Дифайны и енумы уже отменили? wink.gif

Нет. PC - вот лучшее место для хранения состояния автомата состояния! yeah.gif
Dog Pawlowa
Цитата(jcxz @ Jan 31 2017, 00:34) *
Нет. PC - вот лучшее место для хранения состояния автомата состояния! yeah.gif

Как же, давайте пихать RTOS во все дыры.
А даже впихнем - все равно куски кода нужно как-то комментировать, можно сказать, что case - это комментарий wink.gif
jcxz
Цитата(Dog Pawlowa @ Jan 31 2017, 15:57) *
Как же, давайте пихать RTOS во все дыры.

Причём тут RTOS?? Где Вы её узрели в моём сообщении?
Dog Pawlowa
Цитата(jcxz @ Jan 31 2017, 17:23) *
Где Вы её узрели в моём сообщении?

вот:
Цитата(jcxz @ Jan 31 2017, 00:34) *
PC - вот лучшее место для хранения состояния автомата состояния!


Самый лучший автомат состояния - это последовательное выполнение команд, тогда состояние однозначно отражается местом в коде. Полностью согласен.
К сожалению, это возможно только для простейших устройств (один автомат), чтобы сделать то же самое для более сложных устройств (больше одного автомата) и сохранить привязку автомата к PC, нужно иметь инструмент, который принудительно переключает PC между "автоматами".
Такой инструмент называется RTOS.
Как? wink.gif
jcxz
Цитата(Dog Pawlowa @ Jan 31 2017, 17:39) *
нужно иметь инструмент, который принудительно переключает PC между "автоматами".
Такой инструмент называется RTOS.

Нет.
Вы очевидно полагаете, что ОС это нечто такое, божественное, состоящее не из команд, действующее непостижимым образом.
Нет, всё проще - ОС состоит из тех же самых команд, что и остальной код и переключение контекста (которое есть и в ОС), никто не мешает сделать вручную.
И принудительности никакой не надо - переключение надо делать там-же, где у ТС-а находится switch/case (ну или вызов очередной функции из таблицы),
только вместо этого будет сохранение контекста точки вызова (регистры, указатель стека, PC) и восстановление контекста тела автомата.
И в результате получаем тело автомата в виде линейного кода, а не разорванного на много частей switch/case-ами и не распиленное на отдельные функции.
И таких автоматов может быть сколько угодно - для каждого свой стек.

Единственное что правда, так это то, что тело такого автомата будет похоже на задачу ОС с корпоративной многозадачностью.
Dog Pawlowa
Цитата(jcxz @ Jan 31 2017, 20:03) *
тело такого автомата будет похоже на

Есть такая грубая поговорка - если девушка похожа на шлюху, и ведет себя, как шлюха, то она шлюха и есть.
Так и переключение автоматов wink.gif
jcxz
Цитата(Dog Pawlowa @ Feb 1 2017, 06:56) *
Есть такая грубая поговорка - если девушка похожа на шлюху, и ведет себя, как шлюха, то она шлюха и есть.
Так и переключение автоматов wink.gif

Следуя Вашей логике - если любую программу можно назвать RTOS. Ведь в ней те же самые команды, значит она похожа.
Переключение стека - это десяток команд всего. ОС - это не переключение стека, это гораздо больше. Переключение - это одна из мелких частей ОС. Эта мелкая часть может быть (и есть) и во многих других случаях, кроме ОС.
demiurg1978
Цитата(jcxz @ Jan 31 2017, 23:03) *
И в результате получаем тело автомата в виде линейного кода, а не разорванного на много частей switch/case-ами и не распиленное на отдельные функции.
И таких автоматов может быть сколько угодно - для каждого свой стек.

Во-первых: нет такого инструмента.
Во-вторых: текущий проект блок управления оборудованием. Покажите пример конечного автомата по вашему описанию. К примеру, у меня аварийный режим отдельное состояние. Линейный код в студию.
jcxz
Цитата(demiurg1978 @ Feb 1 2017, 12:05) *
Во-вторых: текущий проект блок управления оборудованием. Покажите пример конечного автомата по вашему описанию. К примеру, у меня аварийный режим отдельное состояние. Линейный код в студию.

Классический автомат:
CODE
enum {S0, S1, S2, S3, S4, ...};
int state = S0;
switch (state) {
case S0:
{...} //полезная работа0
state = S1;
break;
case S1:
{...} //полезная работа1
state = S2;
break;
case S2:
{...} //полезная работа2
if (условие) state = S3;
else state = S4;
break;
case S3:
{...} //полезная работа3
break;
case S4:
{...} //полезная работа4
break;
...
}
Очевидно - тело автомата - участки внутри case. Но тело разорванное, плохо читаемое при большом числе состояний S...
Соберём тело воедино:
CODE
{...} //полезная работа0
SwitchStack();
{...} //полезная работа1
SwitchStack();
{...} //полезная работа2
if (условие) {
SwitchStack();
{...} //полезная работа3
} else {
SwitchStack();
{...} //полезная работа4
}
Получили линейный, хорошо читаемый код.
Теперь там, где раньше было switch (state) (назовём это - телом хозяина), будет:
SwitchStack(); - переключение стека и контекста процессора на тело автомата.
Сама SwitchStack() внутри сначала сохраняет на стек все регистры, которые по соглашениям вызова компилятора не должны разрушаться при вызове функций. И сохраняет значение PC (адрес возврата).
Потом считывает из static переменной значение указателя стека для тела автомата и сохраняет туда значение указателя стека тела хозяина.
Затем - восстанавливает со стека автомата значения сохранённых регистров и PC и выходит в тело автомата.
При вызове SwitchStack() из тела автомата, она делает обратные действия - возвращается в контекст тела хозяина.
Реализация SwitchStack() - процессоро-зависима. Я не знаю архитектуры AVR. Для Cortex-M и IAR это будет:
CODE
SECTION .text:CODE:NOROOT(2)
PUBLIC SwitchStack
THUMB
SwitchStack:
PUSH {R4-R11, LR} ;сохраним все предохраняемые регистры и адрес возврата для точки вызова на стеке вызова
LDR R0, sp ;читаем указатель на переменную для сохранения SP
LDR R1, [R0] ;читаем из этой переменной вершину стека точки возврата
STR SP, [R0] ;сохраняем вершину стека точки вызова в эту переменную
MOVS SP, R1 ;переключаем стек
POP {R4-R11, PC} ;возвращаемся уже в точку возврата с восстановлением регистров
;всё - контекст переключен
EXTERN spForStateMachine ;хранит указатель стека SP тела автомата или тела хозяина
DATA
sp DC32 spForStateMachine
При желании можно выровнять данное сохранение на 8 (нужно для аппаратной плавучки в M4F) добавив в PUSH/POP регистр R12.
Естественно перед первым переключением на контекст тела автомата, необходимо проинитить стек автомата валидными значениями регистров и переменную spForStateMachine - вершиной этого стека.
demiurg1978
Если честно, немного извращенно. Прототреды и то проще. Еще проще написать функции линейно, и вызывать их по указателю. Состояние - ссылка на нужную функцию. И никаких трат на сохранение-восстановление контекста.
jcxz
Цитата(demiurg1978 @ Feb 1 2017, 16:18) *
Если честно, немного извращенно. Прототреды и то проще. Еще проще написать функции линейно, и вызывать их по указателю. Состояние - ссылка на нужную функцию. И никаких трат на сохранение-восстановление контекста.

Понятно когда таких состояний мало - я тоже не использую такой метод. Я же сразу говорил - это для сложных автоматов оправдано.
Когда состояний много, когда переходы между состояниями по разным условиям.
А особенно - если представьте:
Нужно чтобы после состояния S1 выполнялось несколько состояний S10-S11-S12 с последующим возвратом в основной поток к S2.
И такая-же последовательность нужна после скажем S3.
С классическим подходом (функциями или switch/case) придётся городить огород с сохранением переменной состояния автомата в отдельной переменной и восстановлением её потом.
Здесь же всё просто - просто пишем функцию. И всё.
То же самое - циклы с циклическим изменением состояний S1-S2-S3-...S1-.... Опять при таком подходе получается всё много нагляднее и проще чем классически.
demiurg1978
Я единственный раз использовал дополнительную переменную-состояние. Next_State. И только из-за того, что получались одинаковые куски кода. Как-то зашел разговор о культуре программирования. Я решил перелопатить код в соответствии с перечисленными участником форума правилами. В итоге перематерился. По сути я не программой занимался, а окультуриванием кода. Гламурность наводил. В особенности меня порой убивает запрет на GOTO. Хотя и понимаю, что GOTO порой опасно использовать.
Если где-то возникают трудности, значит нужно пересматривать подход в целом. Это правило меня никогда не подводило.
Честно сказать, я не программист в чистом виде. Увлекался электроникой, в определенный момент решил взяться за изучение МК. Так получилось, что моим выбором стали AVR-ки. Начинал с асма. На си переполз 3-4 года назад.
Dog Pawlowa
Цитата(jcxz @ Feb 1 2017, 15:58) *
Получили линейный, хорошо читаемый код.

Немного спорно.
А при наличии нескольких задач-автоматов SwitchStack() перестает быть симметричной.
jcxz
Цитата(Dog Pawlowa @ Feb 2 2017, 09:52) *
А при наличии нескольких задач-автоматов SwitchStack() перестает быть симметричной.

В смысле? Если нужно несколько независимых автоматов - для каждого свой стек и всё то же самое. В SwitchStack() просто добавится аргумент - указатель на рабочую переменную/стек.
Ну или своя SwitchStack() для каждого автомата.

Цитата(demiurg1978 @ Feb 1 2017, 16:45) *
Я единственный раз использовал дополнительную переменную-состояние. Next_State. И только из-за того, что получались одинаковые куски кода. Как-то зашел разговор о культуре программирования. Я решил перелопатить код в соответствии с перечисленными участником форума правилами. В итоге перематерился.

Это же тема - изначально про случаи сложных автоматов. Для простых достаточно switch/case.
А вот в случае сложных автоматов (а тем более если в них есть повторяющиеся или циклические участки) мой метод значительно упрощает код, он становится легче для анализа и отладки. Значит и матюкаться при его редактировании/отладке приходится меньше.
demiurg1978
Цитата(jcxz @ Feb 2 2017, 15:38) *
А вот в случае сложных автоматов (а тем более если в них есть повторяющиеся или циклические участки) мой метод значительно упрощает код, он становится легче для анализа и отладки. Значит и матюкаться при его редактировании/отладке приходится меньше.

Хотелось бы посмотреть реальный пример, который не жалко выложить на обозрение и который показывает причину, почему реализовано именно такое решение. Пока не вижу смысла так заморачиваться. Тем более - с накладными расходами. То бишь сохранение-восстановление контекста.
jcxz
Цитата(demiurg1978 @ Feb 2 2017, 11:56) *
Хотелось бы посмотреть реальный пример, который не жалко выложить на обозрение и который показывает причину, почему реализовано именно такое решение.

Причины у всех свои. Кто захочет - тот применит. Я тут никого не заставляю. Всё уже более чем разжёвано.
И почему такое решение не может быть реализовано?? Вариант с switch/case при кол-ве состояний >=20 много хуже получается и это, имхо, очевидно.
demiurg1978
Цитата(jcxz @ Feb 2 2017, 19:19) *
Вариант с switch/case при кол-ве состояний >=20 много хуже получается и это, имхо, очевидно.

В этом случае никто не спорит. И я сам об этом сказал. Что при большом кол-ве состоянии switch-case нечитабелен. Использую функции и индексный переход.
В вашем же случае я хотел посмотреть причину применения вашего подхода. Эти переходы по состояниям. Так сказать, наглядный реальный пример.
jcxz
Цитата(demiurg1978 @ Feb 2 2017, 16:26) *
В этом случае никто не спорит. И я сам об этом сказал. Что при большом кол-ве состоянии switch-case нечитабелен. Использую функции и индексный переход.
В вашем же случае я хотел посмотреть причину применения вашего подхода. Эти переходы по состояниям. Так сказать, наглядный реальный пример.

Ну например - использовал такой метод в ISR осуществляющем приоритетный арбитраж доступа к нескольким микросхемам (FLASH, FRAM) висящим на шине SPI, и управляющем транзакциями
(старт/стоп DMA-пересылок) записи, чтения к этим микросхемам по запросам от нескольких пользовательских каналов. Каждая транзакция состояла из нескольких шагов (подать команду разрешения записи, стартовать передачу блока, завершить обмен, опросить статус чипа и т.п.), которые разбивались на ещё более мелкие шаги. Эти транзакции собирались в ещё более крупные пакеты операций. Каждый такой уровень - это отдельный автомат со своей переменной состояния. Эти автоматы реализовал switch/case-ом.
В этом-же ISR работал процесс осциллографирования, который в реальном времени перегонял данные из кольцевого буфера во FRAM, во FLASH формируя осциллограммы в определённом формате, осуществляя синхронизацию, передискретизацию потока и многое другое. Вот именно его и реализовал вышеописанным способом.
Реализовать в виде задачи ОС это было неоптимально, из-за требований к скорости работы и других требований.
Этот процесс даже описанный указанным способом занимает около 24кБ в исходнике, а в виде классического switch/case - страшно даже подумать. И нереально отладить.
Выкладывать сюда эту реализацию не буду, так как во-первых - коммерческий проект; во-вторых - исходник этот - почти сотня КБ - смысла нет, всё равно никто не станет разбираться в таком объёме, если даже сможет wink.gif

Так же использовал такой метод при опросе через nRF24L01+ через короткие окна (неск. мсек) удалённого устройства, где тоже весь протокол обмена был описан в виде многоуровневого автомата, и протокол довольно объёмный, так что в виде классического автомата реализовывать - слишком громоздко, а в виде задачи ОС - по требованиям скорости не проходило, так как было нужно много мелких переключений задач.
Dog Pawlowa
Цитата(jcxz @ Feb 2 2017, 19:07) *
занимает около 24кБ в исходнике, а в виде классического switch/case - страшно даже подумать.

Оптимизацию включать пробовали? wink.gif Еще лет 10 назад тот ИАР для того же AVR при оптимизации заменял switch-case на массив функций.

Теперь сравниваем вызов функции по индексу, который используется автором, с Вашей системой.
Выход из функции и вызов ее опять (или вызов следующей при смене состояния) = восстановить регистры, а затем их сохранить.
Ваш вариант = сохранить регистры, а потом восстановить их опять.

Разница - да, она есть, и заключается вот в чем - в одном случае человек в поезде лицом в направлении движения, а в другом - спиной в направлении движения. А поезд тот же, и везет точно так же. Нету разницы!

Экономии программной памяти в огромном количестве - нет! Увеличения быстродействия - нет! (потому что восстановление/сохранение контекста не оптимизировано). Может вызов функции по индексу и требует пары лишних байт, зато позволяет легко организовать иерархию автоматов.
demiurg1978
Что-то мне это баловство со стеком не нравится. Хорошо, сохранили контекст. Убежали в другой обработчик. А тут бац. Ситуация в корне поменялась. И тут мы прибегаем в обработчик, где в сохраненных регистрах старые данные. Да такая система, что обезьяна с гранатой. Никогда не знаешь, что произойдет и куда прилетит...
jcxz
Цитата(demiurg1978 @ Feb 2 2017, 18:33) *
И тут мы прибегаем в обработчик, где в сохраненных регистрах старые данные. Да такая система, что обезьяна с гранатой. Никогда не знаешь, что произойдет и куда прилетит...

Какие старые данные в каких регистрах???
Вы что такое "переключение контекста" понимаете? Как например это используется в ОС понимаете?
Советую подучить теорию.

Цитата(Dog Pawlowa @ Feb 2 2017, 17:50) *
Оптимизацию включать пробовали? wink.gif Еще лет 10 назад тот ИАР для того же AVR при оптимизации заменял switch-case на массив функций.

А Вы читать, то что я написал - пробовали? Что такое "исходник" понимаете?
Я вообще ничего не говорил о скомпилённом размере. Я говорил о сложности "объёмного исходного кода", который для классического автомата будет ещё большего размера и очень запутанным.

И не знаю где там IAR заменяет что-то на массив функций, я по коду IAR для ARM вижу, что на месте switch() такого автомата IAR генерит огромную последовательность из
десятков пар инструкций CMP/BEQ. Вне зависимости - включена оптимизация или нет.
Так что даже по скорости вариант с переключением контекста будет быстрее.

Цитата(Dog Pawlowa @ Feb 2 2017, 17:50) *
Разница - да, она есть, и заключается вот в чем - в одном случае человек в поезде лицом в направлении движения, а в другом - спиной в направлении движения. А поезд тот же, и везет точно так же. Нету разницы!

Следуя Вашей аналогии: в Вашем случае человек добирается до цели кучей разных поездов, перепрыгивая из одного в другой; в моём - едет на одном поезде и какая разница как он там сидит - спиной или лицом - главное то, что ему удобно и он может в это время полезными делами заниматься, а не прыганьем из одного поезда в другой. laughing.gif

Цитата(Dog Pawlowa @ Feb 2 2017, 17:50) *
Экономии программной памяти в огромном количестве - нет! Увеличения быстродействия - нет! (потому что восстановление/сохранение контекста не оптимизировано). Может вызов функции по индексу и требует пары лишних байт, зато позволяет легко организовать иерархию автоматов.

Ещё раз - попробуйте прочитать хотя-бы мои сообщения, на которые Вы пытаетесь отвечать!
Цель не стояла - увеличение быстродействия или тем более - экономия пары байт программной памяти (что вообще полный бред в современных условиях).
Цель: кардинальное улучшение читаемости и соответственно - удобства написания/отладки кода больших сложных автоматов.
Но даже если заниматься ерундой и экономить байты памяти программ:
Попробуйте когда-нить открыть листинг скомпилённой программы. Увидите, что вызов/возврат из функции занимает далеко не пару байт, даже думаю на AVR.
Каждая такая функция это: её вызов (косвенный, который дороже прямого вызова + адресация в массиве указателей) + сохранение регистров на входе в неё + восстановление регистров на выходе + возврат. Сколько это байт будет на AVR?
В моём случае в таком месте будет только: прямой вызов функции. Всё! Сколько это байт будет на AVR?
Dog Pawlowa
Цитата(jcxz @ Feb 3 2017, 12:35) *
Цель: кардинальное улучшение читаемости и соответственно - удобства написания/отладки кода больших сложных автоматов.

Банальный макрос #define SwitchStack(N) } void Step##N##() {
делает то же самое "улучшение" читаемости и удобство. Все равно место вызова Вашей функции не берется с потолка.


Цитата(jcxz @ Feb 3 2017, 12:35) *
Но даже если заниматься ерундой и экономить байты памяти программ:
...Каждая такая функция это: её вызов (косвенный, который дороже прямого вызова + адресация в массиве указателей) + сохранение регистров на входе в неё + восстановление регистров на выходе + возврат. Сколько это байт будет на AVR?

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

Еще что-то есть? wink.gif
Obam
Цитата
Ваш вызов функции против моего вызова функции. Ваше переключение контекста гарантированно дольше


Простите, что вставляю свои 5 коп, но приводя исходники и рассказывая идею, jcxz чётко заявлял об ARM: а в нём конвейерная организации. В AVR тоже есть конвейер? Контекст сохраняется одинаково?
Так что, речь может идти только о удобочитаемости исходников.

И что останавливает от проверки одного и того же кода для ARM и AVR. Кстати, нужен только симулятор (а они есть и недурные, хоть в том же IAR) ядер.
jcxz
Цитата(Dog Pawlowa @ Feb 3 2017, 13:24) *
Банальный макрос #define SwitchStack(N) } void Step##N##() {
делает то же самое "улучшение" читаемости и удобство. Все равно место вызова Вашей функции не берется с потолка.

Ну да, а изменение значения переменной-состояния автомата где?
А если эта переменная меняется условно, в зависимости от результата выполнения предыдущего состояния автомата?
В N-й раз уже говорю, но похоже не доходит sad.gif( - Мой метод оптимален для сложных автоматов, с большим числом состояний, условными переходами между состояниями, циклическим выполнением каких-то шагов автомата, общих последовательностей шагов (которые моим методом оформляются в простые функции).
Покажите как Вы с помощью этого макроса реализуете условное изменение состояния автомата? А цикл?

Цитата(Dog Pawlowa @ Feb 3 2017, 13:24) *
Немало, тут я полностью согласен. Но вызов функции по индексу - это один раз на всю программу, преамбула/постамбула тоже одни на всех.

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

Цитата(Dog Pawlowa @ Feb 3 2017, 13:24) *
Ваше переключение контекста гарантированно дольше, так как должно покрыть все используемые всей функцией регистры, мое переключение (вызов-возврат) в среднем короче, потому что адаптировано к конкретному куску кода, выделенному в отдельную функцию.

Да ладно?! А переменные, которые Ваши состояния автомата используют их нет? Без переменных? В моём случае - они хранятся в регистрах и сохраняются/восстанавливаются одним и тем же кодом, а у Вас - для каждого состояния придётся их на входе в каждое состояние заново читать, а после - записывать.
Если уж Вас так волнует количество байт кода, то как раз мой вариант в любом случае будет короче.
Попробуйте на практике реализовать для автомата хотя бы с десятком состояний.

Цитата(Dog Pawlowa @ Feb 3 2017, 13:24) *
Еще что-то есть? wink.gif

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

Цитата(Obam @ Feb 3 2017, 13:46) *
Так что, речь может идти только о удобочитаемости исходников.

Именно об этом и шла речь в первую очередь.
Но некоторые товарищи погрязли в однобайтном мирке, похоже никогда не видели исходников размером хотя-бы несколько сотен строк и похоже объяснять что-то бесполезно....
Dog Pawlowa
>> Покажите как Вы с помощью этого макроса реализуете условное изменение состояния автомата? А цикл?
А как Вы со своей функцией реализуете условное изменение состояния автомата? - Никак. Вы хотели линейности кода - вот оно.

>> Ну да, а изменение значения переменной-состояния автомата где?
>> А если эта переменная меняется условно, в зависимости от результата выполнения предыдущего состояния автомата?
Так еще проще, чем у Вас - присвоил новое значение и вышел. Естественное структурирование автомата. А у Вас - if (..) goto ?

>>В N-й раз уже говорю, но похоже не доходит sad.gif( - Мой метод оптимален для сложных автоматов,
Вы в автоподпись добавьте - крут, как яйца wink.gif

>> с большим числом состояний
"Большим" - это сколько? У меня сотни - без проблем.

>> условными переходами между состояниями, циклическим выполнением каких-то шагов автомата
1. Бывают автоматы без условных переходов и циклического выполнения?
2. Вот не знал, что есть проблемы реализовать переходы (условные и безусловные) и циклы с массивами функций.

>>общих последовательностей шагов (которые моим методом оформляются в простые функции).
Вы наверное и сами функции изобрели? wink.gif
Общие последовательности шагов реализуются добавлением одной переменной, которая указывает куда выйти после окончания последовательности, она есть и в Вашей реализации.

>> Это почему это сохранение/восстановление контекста одно на всех? Разве в соглашениях вызова функций для AVR сохранение контекста делает вызывающий функцию,
>>а не сама функция? В любом случае - разница даже в десятки байт - ни о чём, смысла нет обсуждать.
Да, тут я погорячился, восстановление контекста одно на всех, а вот сохранение конечно раздельное.

>> А переменные, которые Ваши состояния автомата используют их нет? Без переменных? В моём случае - они хранятся в регистрах и сохраняются/восстанавливаются одним и тем же кодом,
>> а у Вас - для каждого состояния придётся их на входе в каждое состояние заново читать, а после - записывать.
А у Вас этой записи нет?! А в стек и обратно Вы переменные не гоняете? wink.gif Мои переменные лежат там спокойно и работа с ними идет, когда нужно, а Вы туда-сюда, туда-сюда.
Я же написал уже, что у меня запись и восстановление оптимизированы по месту, а у Вас всегда пишутся и читаются все регистры, которые задействованы во всей функции поддержки автомата.
А Вы не поняли и недостаток пытаетесь выдать за достоинство.

>> Если уж Вас так волнует количество байт кода, то как раз мой вариант в любом случае будет короче.
>> Попробуйте на практике реализовать для автомата хотя бы с десятком состояний.
Это называется "на слабо" sm.gif Да, наверное есть случаи, когда Ваш метод имеет смысл. Спасибо, я попробую.


>>Всё уже было. Если Вы не поняли - очевидно рано ещё, со временем может поймёте.
>>Но некоторые товарищи погрязли в однобайтном мирке, похоже никогда не видели исходников размером хотя-бы несколько сотен строк и похоже объяснять что-то бесполезно....
Спасибо, что снизошли!
k155la3
Цитата(jcxz @ Jan 31 2017, 01:34) *
. . . .
Нет. PC - вот лучшее место для хранения состояния автомата состояния! yeah.gif

Параллельное ПЗУ разнообразной емкости и разрядности данных, аналогичный регистр-защелка по данным,
обратная связь выхода регистра на адресные входы ПЗУ.
Входные сигналы - также на адресные входы ПЗУ.
Автомат однако.
Декодер манчестерского кода был сделан по такой схеме.
zltigo
Цитата(Dog Pawlowa @ Feb 4 2017, 12:28) *
Да, наверное есть случаи, когда Ваш метод имеет смысл. Спасибо, я попробую.

Да, наверное такой случай можно придумать и вообще в порыве энтузиазма притянуть еще пару случаев. Но в общем довольно НЕЧИТАЕМО на самом деле. Причина в том, что под читаемость попадает и привычность стиля. Для меня еще огромный недостаток, что при работе/отладке в реальных условиях, а не на столе под отладчиком, нет ЕСТЕСТВЕННОГО наглядного вывода развития событий автомата и истории вылета автомата.

Цитата(jcxz @ Feb 2 2017, 15:19) *
Вариант с switch/case при кол-ве состояний >=20 много хуже получается и это, имхо, очевидно.

МОНОЛИТНЫЕ Автоматы с большим количеством состояний просто незачем делать. У меня есть не один проект, где при лобовом использовании автоматов состояний были-бы тысячи.
Вместо этого есть порядка десятка связанных автоматов с десятком, ну двумя десятками состояний. При этом они находятся тоже не в одном switch/case. Например, часть из них уходит в ОТДЕЛЬНЫНУЮ функцию переключения состояний автомата. В этой же функции вся отладка консольная.
Dog Pawlowa
Цитата(zltigo @ Feb 4 2017, 16:29) *
нет ЕСТЕСТВЕННОГО наглядного вывода развития событий автомата и истории вылета автомата.

Если Вы откажетесь от любимой консоли, то будете в отладчике смотреть состояние по осмысленному наименованию одной строкой из enum wink.gif

Цитата(jcxz @ Feb 2 2017, 13:38) *
мой метод значительно упрощает код, он становится легче для анализа и отладки.

Кстати, расскажите подробнее про отладку - как то происходит? Вот как быстро понять текущее состояние автомата?
zltigo
Цитата(Dog Pawlowa @ Feb 5 2017, 09:53) *
Если Вы откажетесь от любимой консоли, то будете в отладчике смотреть состояние по осмысленному наименованию одной строкой из enum wink.gif

Использование отладчика, например, в заполярье на сопке не есть хорошая идея. Точнее очень плохая, поскольку там нет ни специалиста, ни компьютера и исходниками и проектом, ни отладчика. Ну а то, что выводится у консоль, то удаленно доступно и лог сбрасывается. При этом отладка может быть и не включена - состояния автомата с сопутствующими данными пишутся в некий буфер и при ошибке распечатывается предистория, как дошли до состояния такого.
И вообще - отладчик универсальный инструмент, но специализированный инструмент можно сделать лучше.
Dog Pawlowa
Цитата(zltigo @ Feb 5 2017, 15:14) *
При этом отладка может быть и не включена - состояния автомата с сопутствующими данными пишутся в некий буфер и при ошибке распечатывается предистория, как дошли до состояния такого.


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

Код
{27.01.17-08:09:46} "Filling"
Time Water A    B
>Cycle A c0 p0 w0 f11
4 4 A:1/6945525 B:0/0
>F reduced A factor10 prod714
14 4 A:2/2310679 B:0/0
{27.01.17-08:09:48} "Discharge finished"
Post pulses=4
W.counter: a/r/s/n 4/899/0/4;
W. volume: r/a=3979400/4752;
ConcA pulses r/a: 28/2 vol: r/a=20600/1428
ConcB pulses r/a: 0/0 vol: r/a=0/0
S. volume: r/a=4000000/6180;
16 4 A:2/2310679 B:0/0
Num     Date  Time   Liter  A% B%  Err
0240:27.01.17-08:09-000.0-23.106-0.000-15
{27.01.17-08:09:49} "Error" 15 Low water pressure
{27.01.17-08:09:56} "Error-2"
{27.01.17-08:09:57} "Waiting"
Stagnation 283572
0,5%  |0,5% 0,5% |0,5%
A:4,0l|A+B:1,5l  |B:1,5l

{27.01.17-08:09:58} "Filling"


Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2024 Invision Power Services, Inc.