Как у специалиста по силовой электроники и электроприводу, ассоциации с «асинхронный» вот такие:

Асинхронный двигатель фирмы AEG 1890-ых
Асинхронный двигатель фирмы AEG 1890-ых

И несправедливости, что возит, поит, греет творение М.О. Доливо-Добровольского, а поминают Н. Тесла. Причем АД второго, невероятно плох. Двухфазный (привет асимметрии линий электропередачи), концентрированная обмотка на полюсах (прощай КПД), ну и низкий пусковой момент. А вот Михаила Осиповича творение с первого включения показало КПД в районе 90 и двойной пусковой момент, что и стало стандартом де-факто с 1891г. и по сей день.

Когда управляешь инвертором двигателя, real time измеряется 10-30мкс. Это быстро, даже для современных микросхем. Архитектуру программы надо планировать без лишних пожирателей времени ядра. Однако есть входящие сигналы которые надо обрабатывать медленно. Еще есть интерфейсы, где есть коллизии и тайм ауты.

Приведу классический медленный пример — работа с GSM модемом. Этот черный ящик, требует заклинаний в виде AT команд, нужно дождаться ответа AT OK/ERROR, а ответ может и не прийти (таймаут 100мс), а иногда надо «пнуть» его еще раз, а бывает он вообще виснет и его перезагрузить надо по питанию.

Обычно там где медленные процессы или устройства, то разработчики склоняются к RTOS. Тут все понятно. Есть отдельные задачи, выполняемые параллельно с переключаемым контекстом и настроенным приоритетом. Недостаток, сложность использования, надо помнить про переключаемый контекст. Условно вызов printf в разных задачах может привести к непредсказуемым последствиям. Не забываем и про увеличивающийся размер кода.

Имеется еще один любимый подход программистов микроконтроллеров. А давайте мы сделаем примерно так:

	send_at(«AT»); 
	delay(100);
	switch(get_at_result()) … 

```

Соль кроется в функции delay, которая является просто циклом с командами nop, или через таймер, тормозит программу в этом месте на нужное время. Подход как бы нормальный, за исключением. Используемой периферии всегда много в реальных проектах, и опрос 1-wire может длиться до нескольких секунд, а на запросы мастера Modbus, надо отвечать с минимальной задержкой. В этом случае разработчики начинают использовать прерывания, и распихивать код по ним. После определенного количества прерываний, можно получить ситуацию, что одно будет мешать другому в непредсказуемых комбинациях которые отладить бывает очень трудно. По закону Мерфи это начинается в продукте. Из собственного опыта: мусоровозка, дождь, вонь, масло от гидравлики, грязь, с дебагером и ноутом на коленках искать иголку в «мусорном кузове» грузовика. А у людей нервы и планы куда приехать надо. У заказчиков, сколько надо поставок сделать. У верхнего уровня вопросы: "А что у вас машина координаты GPS не шлёт?". Также появляются петли в условной printf у которой есть внутренние переменные, и её вызовом в основном цикле программы и в неком прерывании. Может хаотично упасть и на столе лаборатории, бывает комбинацию не повторить.

Эволюционно я пришел собственно к асинхронности. Общий принцип таков. Контекст не переключается, нужен delay или событие, проверь счетчик или наличие событие и jump на другой участок кода. Основной недостаток данного подхода — это контроль времени жизни переменных. C — это не делает, как например Rust, тоже можно выстрелить в ногу. Из плюсов, не надо думать про распределение памяти как в RTOS и относительно легко контролировать наличие свободных ресурсов в отличие давайте здесь тормознём с помощью цикла с NOP. Архитектура программы в этом случае строится на минимальном использовании прерываний, всю периферию максимально на DMA, и далее три пути.

Путь первый: немного ассемблера. Из времен (где то 2007-9г.), когда использовал AVR8-AVR32:

#define la_set(lab) {asm volatile(#lab ": "::); }
#if (AVR == AVR32)
	#define la_save(var, lab) { asm volatile("lda.w   %[VAL], "#lab "  \n\t" \
											" ":[VAL]"=r" (var): "[VAL]" (var)); }
	
	#define la_save_set(var, lab) { asm volatile("lda.w   %[VAL], "#lab "  \n\t" \
												" ":[VAL]"=r" (var): "[VAL]" (var)); \
												asm volatile(#lab ": "::); }
	#define la_jmp(var) { asm volatile("mov  pc, %[VAL]":: [VAL]"r"(var)); } // lddpc 
	typedef struct la_str_jmp {
		U32 *timer;
		U32 jmp;
	} la_str_jmp;

```

Использование в программе: la_c_jmp(i_jmp); // перед асинхронным участком. От данного места будем прыгать на метку.

	switch(step)  {
	case : x
				*i_jmp->timer = 0; // сбросим таймер delay
				la_save(i_jmp->jmp, l_td_t3); // запоминаем куда перепрыгнуть 
				la_set(l_td_t3); // ставим куда перепрыгнуть
				if (*i_jmp->timer < 500) {
					// подождали и что то здесь делаем
				}
		break;
	}

```

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

Путь второй: указатели на функции C (примерно с 2010-2012г).

U8 (*_ij)(void); // указатель на функцию
U8 (*_ij_next)(void); // указатель на функцию

U32 _ij_delay = 0;
U8 f_hard_delay(void) {
	if (gsm_j_timer < _ij_delay) {
		return 0;
	}
	gsm_j_timer = 0;
	return 1;
}
U8 f_turnon(void) {
	// ….
	gsm_j_timer = 0; _ij_delay = 1500;
	_ij_next = f_turnon1;
	_ij = f_hard_delay;
	return 0x0;
}
U8 f_turnon1(void) {
	// ….	
	_ij_next = f_turnon2;
	gsm_j_timer = 0; _ij_delay = 3000;
	_ij = f_hard_delay; 
	return 0x0;
}
while(1) {

	// переключатель
	if (_ij != NULL) {
		if( (_ij)() ) {
			_ij = _ij_next;
			_ij_next = NULL;	
		}
	}
}

```

Все работает, но… Недостаток у данного подхода — ужасная читаемость кода. Поэтому пришлось искать дальше.

Путь третий async.h. Ссылка на решение https://github.com/naasking/async.h. Это красивая комбинация указателей на функцию и переключение на обычном switch().

Выглядит и работает элегантно:

static struct async pt_me;

// Задержка в мс
async me_delay(struct async *pt, uint32_t ticks)
{
    async_begin(pt);
    static uint32_t timer_delay;
    timer_delay = timer + ticks;
    while(!LoopCmp(timer, timer_delay)) {
        async_yield;
    }
    /* And we loop. */
	async_end;
}

async read_device(struct async *pt, uint8_t *dev)
{
	async_begin(pt);
	satatic uint8_t ret; 
	async_init(&pt_me);	
	await(wait_signal(&pt_me, *ret)); // подождать некий внешний сигнал 
	// что то сделать
	async_init(&pt_me);
	await(me_delay(&pt_me, 100)); // подождем … 
	*dev = ret; 
	async_end;
}

static struct async pt_main;
static struct async pt_process;
async app_main( struct async *pt) {
	async_begin(pt);
	while (1) {
		if (!signal1) { // сигнал поступил
			async_yield; 
		}
		async_init(&pt_process);
		await( read_device(&pt_process, &dev));
		async_init(&pt_process);
		uint8_t result; 
		await( send_at_command(&pt_process, «AT», &result));
		if (result) {
			// To do … 
		}
	}
	async_end;
} 

void main(void) {
	async_init(&pt_main);
	while (1) {
		app_main(&pt_main);
	}
}

```

Недостатки

  • Время жизни переменных компилятор может пропустить.

  • Переменных как правило требуется больше.

  • Не работает внутри switch() {case …}.

Преимущества:

  • Красивый читаемый код.

  • Полностью С совместимое.

  • Не надо погружаться в работу компилятора

  • Точно контролируем куда уходит ресурс и сколько еще осталось времени/тактов на вычисления.

По собственному опыту, async.h — это хорошее решение когда надо скрестить быстрые процессы типа управления электромагнитными процессами преобразователя и медленные типа интерфейсов.

Комментарии (0)


  1. rukhi7
    13.09.2025 15:33

    Когда управляешь инвертором двигателя, real time измеряется 10-30мкс.

    Что же вы самое интересное пропустили? Откуда у вас взялись такие цифры 10-30мкс?

    Я правда делал управление коллекторным двигателем, там слот времени принятия решения определялся полупериодом сетевого напряжения (50 Гц, 220В) это

    1000 миллисекунд / 50Гц / 2 полупериода = 10 миллисекунд.

    Семистр открывается до следующего пересечения нуля сетевым напряжением. Но некоторые вещи тем не менее требуют почти абсолютной точности расчетов по времени, это правда, как говорится "это real time детка!" :). Причем проблема не только минимальное разрешение, есть еще проблема больших интервалов для которых должна поддерживаться точность, например однажды выставленные 10мс должны колебаться вокруг именно 10 мс, а не 9.9 мс(например) на протяжении потенциально бесконечного времени работы драйвера двигателя...

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

    Эта задача решается не в языках программирования! В языках программирования нет такого понятия: "прерывания", ни в одном! Надо построить схему или диаграмму прерываний - то есть надо придумать как(!) конкретную схему нарисовать, для определенного набора прерываний и необходимого времени на их обработку так, чтобы они не накладывались непредсказуемым образом, а в коде надо просто контролировать что схема работает, то есть не происходит не продуманных наложений. Это проверяется на этапе отладки-тестирования, когда схема проверена это превращается в простую математическую задачу уровня 5-7 класса. А вот придумать как грамотно изобразить-начертить схему и как ее проверить - это действительно сложно.

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


    1. Ogura
      13.09.2025 15:33

      Что же вы самое интересное пропустили? Откуда у вас взялись такие цифры 10-30мкс?

      Я правда делал управление коллекторным двигателем, там слот времени принятия решения определялся полупериодом сетевого напряжения (50 Гц, 220В) это

      1000 миллисекунд / 50Гц / 2 полупериода = 10 миллисекунд.

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


    1. VauAG Автор
      13.09.2025 15:33

      Ну например BLDC покрутить. Им надо 2кГц. Защитный контур по транзисторам как раз 10-30мкс, иначе они успеют надежно сгореть. А симистором управлять, на современных МК как угодно можно, все равно хорошо будет.


      1. rukhi7
        13.09.2025 15:33

        2 кГц - это пол миллисекунды и это действительно уже практически на пределе возможностей процессора который работает на частоте до 100МГц, ну как на пределе - если заниматься ручной оптимизацией то запас есть, но он счетный, скажем пара тысяч тиков = примерно тысяча инструкций ассемблера запаса - это можно посчитать, но почему вы ориентируетесь на 10-30мкс все равно не понятно!

        Защитный контур это наверняка какая-то схема, пока эта схема не сработала с нее, ну может идет какой то дежурный сигнал, но вряд ли каждые 30 мкс! Сделать счетным время реакции на аварийное прерывание не так уж и сложно (если догадаться сделать аварийный сигнал прерыванием высшего приоритета). Это конечно еще вопрос насколько грамотно (тоже оптимально) сделана обвязка процессора. Это другой сложный вопрос, что сигналы должны грамотно распределяться по типам и по задержкам друг относительно друга. В обвязке это тоже нужно контролировать, а лучше спланировать.

        @audisergзаказ принял! К сожалению теперь я не работаю с механикой или с ПИД регулировками, месяца два понадобится, на фоне основной работы, чтобы воспроизвести что-то по памяти, думаю. Но я в принципе давно хотел - не мог определиться с темой, спасибо автору этой статьи, у меня сформировалось что-то вроде той печки от которой надо плясать формулировать, а еще, недавно правленые драйвера Линукса теперь помогают мне сопоставить то, что я делал на голом железе с тем, что можно найти в ядре Линукса.


    1. audiserg
      13.09.2025 15:33

      Интересно! Рассказывайте! )


    1. fivlabor
      13.09.2025 15:33

      Ковырялся (года 4 назад) в коде для 32 кГц BLDC + CAN и оно норм работает на stm32f4, можно быстрее, но уже в АЦП упирается - надо брать stm32g4 - на 64кГц целились


    1. Ig_B
      13.09.2025 15:33

      Контроллер с хорошей периферией позволяет решить задачу фазового управления аппаратно. (dsPIC33EP...).


  1. kovserg
    13.09.2025 15:33

    // Задержка в мс
    async me_delay(struct async *pt, uint32_t ticks)
    {
        async_begin(pt);
        static uint32_t timer_delay; // <<<--- если забыть static будут прикольно
        timer_delay = timer + ticks;
        while(!LoopCmp(timer, timer_delay)) {
    ...
    

    У себя использовал похожую схему. Я использовал - "постепенно исполняемые функции" (afn - async fn). Вообще любая такая функция просто набор 3х указателей: 1-состояние асинхронной функции (ctx), 2-обработчик сигналов (setup), 3-обработчик итерации (loop)

    afn.h
    typedef struct afn_t {
      void *ctx;
      int (*setup)(void *ctx,int sev);
      int (*loop)(void *ctx);
    } afn_t;
    
    enum SetupEvents { sevInit, sevDone };
    

    Если функция loop вернула 0 то она закончила, если 1-еще рабоает, остальные коды прерывания можно использовать для запросов. Функция setup тоже 0=ok. Фунция гарантируется что если вызвали setup(sevInit) то обязательно будет вызван setup(sevDone). И то что функция loop быполняет итерацию за конечное время. И самое главное предача кванта управления выполняется явно.

    А вот для реализации итераций в обработчике loop была такая же схема как и тут на switch-е. Но не на LINE а на COUNTER. И все переменные которые использует асинхронная функция она явно описывает в своём состоянии. Примерно так:

    example.c
    /* example.c */
    #include <stdio.h>
    #include "afn.h"
    #include "loop-fn.h"
    
    typedef struct fn1_ctx {
    	loop_t loop;
    	int i;
    } fn1_ctx;
    
    int fn1_setup(fn1_ctx *my,int sev) {
    	if (sev==sevInit) LOOP_RESET(my->loop);
    	return 0;
    }
    int fn1_loop(fn1_ctx *my) {
    	LOOP_BEGIN(my->loop)
    	printf(" fn1.begin");
    	LOOP_POINT
    	printf(" fn1.step1");
    	LOOP_POINT
    	for(my->i=0;my->i<3;my->i++) {
    		printf(" fn1.i=%d",my->i);
    		LOOP_POINT
    	}
    	printf(" fn1.end");
    	LOOP_END
    }
    
    int main(int argc,char** argv) {
    	fn1_ctx fn1[1]; int it,rc;
    	fn1_setup(fn1,sevInit);
    	for(it=0;it<10;it++) {
    		printf("it=%2d fn1=%2d",it,fn1->loop);
    		rc=fn1_loop(fn1);
    		if (!rc) printf(" [fn1 is done]");
    		printf("\n");
    		if (!rc) break;
    	}
    	fn1_setup(fn1,sevDone);
    	return 0;
    }
    

    output:

    it= 0 fn1= 0 fn1.begin
    it= 1 fn1= 1 fn1.step1
    it= 2 fn1= 2 fn1.i=0
    it= 3 fn1= 3 fn1.i=1
    it= 4 fn1= 3 fn1.i=2
    it= 5 fn1= 3 fn1.end [fn1 is done]
    
    loop-fn.h
    /* loop-fn.h - sequential execution function */
    #ifndef __LOOP_FN_H__
    #define __LOOP_FN_H__
    
    typedef int loop_t;
    
    #define LOOP_RESET(loop) { loop=0; }
    #if defined(__COUNTER__) && __COUNTER__!=__COUNTER__
    #define LOOP_BEGIN(loop) { enum { __loop_base=__COUNTER__ }; \
        loop_t *__loop=&(loop); __loop_switch: int __loop_rv=1; \
        switch(*__loop) { default: *__loop=0; case 0: {
    #define LOOP_POINT { enum { __loop_case=__COUNTER__-__loop_base }; \
        *__loop=__loop_case; goto __loop_leave; case __loop_case:{} }
    #else
    #define LOOP_BEGIN(loop) {loop_t*__loop=&(loop);__loop_switch:int __loop_rv=1;\
        switch(*__loop){ default: case 0: *__loop=__LINE__; case __LINE__:{
    #define LOOP_POINT { *__loop=__LINE__; goto __loop_leave; case __LINE__:{} }
    #endif
    #define LOOP_END { __loop_end: *__loop=-1; case -1: return 0; \
    	{ goto __loop_end; goto __loop_switch; } } \
    	}} __loop_leave: return __loop_rv; }
    #define LOOP_SET_RV(rv) { __loop_rv=(rv); } /* rv must be non zero */
    #define LOOP_INT(n) { __loop_rv=(n); LOOP_POINT } /* interrupt n */
    #define LOOP_POINT_(name) { *__loop=name; goto __loop_leave; case name:{} }
    #define LOOP_INT_(n,name) { __loop_rv=(n); LOOP_POINT_(name) }
    
    #endif /* __LOOP_FN_H__ */
    

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


  1. LexaK
    13.09.2025 15:33

    Мне одному это сильно напоминает Protothreads от Адама нашего Дакнкелса?


    1. VauAG Автор
      13.09.2025 15:33

      Спасибо за коммент, честно не знал его. Бонус от общения ))


    1. audiserg
      13.09.2025 15:33

      Спасибо! Тоже напомнило, лет 20 назад моргал лампочкой на протопотоках.


    1. vvzvlad
      13.09.2025 15:33

      Да-да, switch-case, внутри которого нельзя его использовать. Попили некоторое количество крови они у меня в Contiki.


  1. EmCreatore
    13.09.2025 15:33

    Когда управляешь инвертором двигателя, real time измеряется 10-30мкс. 

    Хорошо живете, столько времени иметь в запасе

    Однако чтобы прицизионно контроллером измерить напряжения и токи во всех фазах и еще внутренности разные измерить, делая это синхронно с шимом, то останется всего пара микросекунд. Это половинное время минимального пульса ШИМ.
    Потому что уложиться надо от середины пульса и пока не возникнет фронт или спад на какой либо фазе. На это, кстати, указывается во всех мануалах по управлению двигателями на однокристальных SoC.
    Поэтому всяческие коооперативные переключатели, как в этой статье, никак уже не подходят.
    Приходится применять вложенные прерывания с приоритетностью, и обычную RTOS типа FreeRTOS.
    Ничего лучшего человечество пока не придумало.


    1. VauAG Автор
      13.09.2025 15:33

      Да основные вычисления делаются в прерывании. По другому никак. Но скрещивать RTOS с управлением мотором это я еще нигде не видел ))). по поводу переключателей, есть куча доп интерфейсов, интерактивность для человека, меню, кнопки, сигналы внешние, все это надо фильтровать и обрабатывать перед принятием решением. Медленные сигналы в виде напряжения DC звена управления контактором. Там переключатели очень даже хорошо. внутри самой математики у меня лично есть ветвления и там применяется указанный подход. В целом соглашусь с одним из комментариев, что построение реал тайм мастерство. А у каждого мастера может быть свой подход.


      1. VelocidadAbsurda
        13.09.2025 15:33

        Но скрещивать RTOS с управлением мотором это я еще нигде не видел ))).

        Есть известный/успешный в своих кругах проект VESC, управляющий 1-2 моторами. Построен на ChibiOS.


      1. rukhi7
        13.09.2025 15:33

        Да основные вычисления делаются в прерывании. По другому никак.

        Это прям беда! Я такое как раз переделывал в том числе. По другому гораздо лучше, надежнее и все такое. В ядре Линукса есть такой термин bottom half, примерно та же идея замечательно работает для любых прерываний в любой системе. Ядро Линукса это, в общем то, тоже эмбеддед программа, наверно самая известная.


  1. kapojko
    13.09.2025 15:33

    Интересный подход! По названию, я подумал, что вы будете говорить о реализации асинхронный логики путем использования аппаратных контроллеров периферии и памяти. Но тут настоящий python/js async :) Выглядит впечатляюще!

    Хотя я бы так не писал. Во многих задачах, где мне доводилось применять микроконтроллеры, нужно так или иначе реальное время - а том смысле, что время реакции системы на входные сигналы должно быть однозначно предсказуемо. Применение async этому препятствует - очень трудно однозначно сказать, будет в конкретном месте когда yield или не будет, и не получится ли, что при каком-то редком сочетании факторов время реакции будет неожиданно сильно большим, чем в остальных случаях.

    Ну и к preprocessor hell это уже очень близко. :) Напоминает труды Александреску по C++, очень красиво, но лучше не применять в реальной жизни)


  1. Ogura
    13.09.2025 15:33

    И несправедливости, что возит, поит, греет творение М.О. Доливо-Добровольского, а поминают Н. Тесла. Причем АД второго, невероятно плох. Двухфазный (привет асимметрии линий электропередачи), концентрированная обмотка на полюсах (прощай КПД), ну и низкий пусковой момент. А вот Михаила Осиповича творение с первого включения показало КПД в районе 90 и двойной пусковой момент, что и стало стандартом де-факто с 1891г. и по сей день.

    Михаи́л О́сипович Доли́во-Доброво́льский внес существенное усовершенствование в переменный ток добавив еще одну третью фазу. Сместив фазы одна относительно другой на 120°. Вообще чем фаз больше тем лучше штук так сем вообще было бы замечательно. Жаль только ЛЭП располагать трудно, а выгода "мизерная". Тесла предложил только 2 фазы их было мало третья же фаза оказалась решением проблемы оставшейся после Теслы. Тоесть не следует противопоставлять, они скорее коллеги чем соперники.


    1. VauAG Автор
      13.09.2025 15:33

      Они были жесткими конкурентами. Тесла - это американский Westinghouse, у американцев Тесла не взлетел и они подтянули патенты Феррариса, Добровольский - это немецкий AEG. Где то в 1900ых они даже соглашение заключили о разделе "мира" по районам продаж. После первой мировой, немцев осадили, но AEG существует и по ныне. Основная проблема двух фаз проводов ЛЭП там три тоже, но они разного сечения. Единственная электростанция двухфазная была на Ниагаре, и то ее преобразовывали на 3 фазы трансформаторами Скотта в итоге.


      1. Ogura
        13.09.2025 15:33

        Westinghouse и AEG может и были конкурентами но Тесла и Добровольский уже вряд ли. Как и Эдисон так Westinghouse развели Tesl-у.

        P.S. Я похоже сам на какой-то процент Тесла, и мне удается повторять его ошибки 1 в 1.


    1. KonstantinC
      13.09.2025 15:33

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


  1. Ogura
    13.09.2025 15:33

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


    1. LeonidPr
      13.09.2025 15:33

      Имхо, понятие "реальное время", распространимо только на один, самый приоритетный процесс.

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


  1. romanetz_omsk
    13.09.2025 15:33

    "Условно вызов printf в разных задачах может привести к непредсказуемым последствиям." - под управлением RTOS каждая задача кладёт свои сообщения в LOGGER FIFO, откуда оно уже выгребается постепенно (или записывается куда-то). Суть в том, что форматирование строки - относительно медленная операция и её можно прерывать, а вот копирование из локального буфера сообщений в общий - быстрая, и прерывать её нельзя.
    Касательно BLDC и FreeRTOS - в том же MCSDK - это стандартное решение. Более того, и без RTOS там кооперативно сосуществуют несколько тасков, и обмен есть.
    в FOC петли (момента, скорости, позиции) имеют предсказуемое и примерно постоянное время вычисления, они просто на прерываниях сидят. А в RTOS уже всё остальное вешается.


  1. Flammmable
    13.09.2025 15:33

    В своё время в профильных сообществах интересовался, почему живы RTOS, если для задач, специфичных для них гораздо лучше походят ПЛИС? Не являются ли RTOS и аналогичные сущности попыткой "искать не там где потерял, а там, где светло"?

    "Вы шшшштооо! Как вы можете?! Это настолько оскорбительно, что мы даже обсуждать это не будем!" был наиболее частый ответ :))))


    1. AlexGfr
      13.09.2025 15:33

      Сложно конструктивно обсуждать сравнение теплого с мягким


      1. Flammmable
        13.09.2025 15:33

        Сложно конструктивно обсуждать сравнение теплого с мягким

        ...зато легко вставлять не по месту комментарии про "сравнение тёплого с мягким" :)))


        1. vvzvlad
          13.09.2025 15:33

          Ну потому что это реально теплое с мягким. FPGA и кремний это про парралельное исполнение кучи простой логики, RTOS это про жонглирование высокоуровнемым кодом, чтобы он весь исполнялся более-менее равномерно. Если надо датчик считать, на экранчик вывести и моторчиком подрыгать, то FPGA тут лишний.

          А вот если надо поток видео пережевать в реальном времени, да еще и в память его писать, то процессор способный на это будет дорогим и жирным. А на FPGA оно реализуетс вполне себе несложно.

          Ну и наоборот — попытка написать программу с кучей ветвлений и сложной логики с какой-нибудь математикой или wifi-http на FPGA будет очень больно, хотя на RTOS это пишется на изи. Но да, то что вы напишете на FPGA будет работать ОЧЕНЬ БЫСТРО. А стоит ли оно того? Надо ли в 99% задач?


          1. Flammmable
            13.09.2025 15:33

            Ну потому что это реально теплое с мягким

            Пока единственное, что реально - это бросание заезженым лозунгом.

            Если надо <...> моторчиком подрыгать, то FPGA тут лишний.

            FPGA for motor control

            попытка написать программу с кучей ветвлений <...> FPGA будет очень больно...

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

            с какой-нибудь математикой

            Приведите, пожалуйста конкретный пример, чтобы я вам наглядно продемонстрировал, что FPGA за ту же цену, что и MCU насчитает всё гораздо быстрее.

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


            1. vvzvlad
              13.09.2025 15:33

              Эм. Вы или не понимаете концепта FPGA или троллите.

              Пока единственное, что реально - это бросание заезженым лозунгом.

              Каким лозунгом-то?

              FPGA for motor control

              Ну вы бы хоть почитали по ссылке-то, что там говорится. Low latency, 100,000 RPM for sensorless Field Oriented Control (FOC), Surgical Robots, Actuator Control in Avionics, Heart Lung Machines и так далее. Это хардкорные применения, а не "подрыгать моторчиком". Ну и как бы пример не в тему, софистика какая-то: я говорю что для подрыгать моторчиком достаточно МК, а вы мне показываете что это задачу можно решить на FPGA. Ну можно-можно, я обратного не утверждал, только нафига, если этот моторчик не в "Surgical Robots, Actuator Control in Avionics, Heart Lung Machines" и так далее, а в штуке, которая шторы закрывает дома.

              В реальности программа, написанная, например в парадигме автоматного программирования будет что на процессорной архитектуре, что на FPGA даже по синтаксису очень похожей.

              Ага-ага. В индустрии уже которое десятилетие пытаются придумать что-нибудь, чтобы можно было из привычных императивных языков (или хотя бы из чего-нибудь с похожим подходом) синтезировать логику. Ну придумали разных HLS, DSL всякие, даже из логики на питоне чет можно синтезировать, но чет все до сих пор сидя на VHDL и Verilog, а все эти штуки применяются для отдельных применений или в крайнем случае для синтеза отдельных IP-блоков. Потому что если бездумно писать на си, а потом из этого синтезировать — часто получится неоптимальная фигня

              Приведите, пожалуйста конкретный пример, чтобы я вам наглядно продемонстрировал, что FPGA за ту же цену, что и MCU насчитает всё гораздо быстрее.

              Ну, какой-нибудь PADAUK PFC161-U06 за эээм $0.07 в розницу? Он мелкий и тупенький, но все же. Ближайшее что можно найти в стоке это SLG46108V за $0.34, но это даже не fpga, а так, cpld.

              Ну или ок, ESP32-C3. $2. 4 MB флеша, 400kb RAM (правда часть под кеш), поэтому 200кб, wifi+ble. Чо там, по какому курсу будем обменивать 4 MB асма под RISC-V на LUT-ы? Wifi+ble можно пока опустить.


              1. Flammmable
                13.09.2025 15:33

                Вы или не понимаете концепта FPGA или троллите.

                Вы допускаете, что этот концепт не понимаете вы?

                я говорю что для подрыгать моторчиком

                В ваш мозг у меня лаза нет и что вы понимаете под "подрыгать моторчиком" без вашего уточнение - вопрос интерпретации.

                Если речь про "штуку, которая закрывает шторы", то с этим и что-нибудь на NE555 и 74 логике справится. Кстати, любопытный вызов.

                PADAUK PFC161-U06

                И какую же RTOS вы на нём развернёте?


                1. vvzvlad
                  13.09.2025 15:33

                  Вы допускаете, что этот концепт не понимаете вы?

                  Допускаю, конечно. Но пока что-то никто аргументы привести не смог.

                  И какую же RTOS вы на ней развернёте?

                  Внезапно, для мультизадачности не нужна ртос.

                  Ну и вот на ESP32 замечательно FreeRTOS работает.


                  1. Flammmable
                    13.09.2025 15:33

                    Допускаю, конечно. Но пока что-то никто аргументы привести не смог.

                    Тогда как вы смотрите на такой вариант: я подсоберу аргументов на что-то среднее между заметкой и статьёй, потегаю вас и мы продолжим более обстоятельно в ней?


                    1. vvzvlad
                      13.09.2025 15:33

                      Звучит неплохо, да.


          1. Ogura
            13.09.2025 15:33

            Должен признаться, статья перекликается с задачей которая у меня на столе.

            Суть в чем со старых принтеров определенных годов можно добыть шаговые моторчики и применить для различных задач. Часть моторчиков 2-фазные, но еще часть 3-фазные. Мало того 3-фазные и моторы шпинделей жестких дисков. и приводы больших копировальных аппаратов с внешним ротором.

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

            Микроконтроллеры не обладает достаточным быстродействием для реализации токового драйвера. Где реакция на событие должна составлять ну хотя бы эдак 100нС (и то это очень долго). Исключение составляют stm32f3* у которого есть подходящая автоматика но и они и самые быстродействующие микроконтроллеры и FPGA обходятся дороже чем если бы купить готовые 2-фазные шаговые моторчики и драйверы.

            Вот возникла идея использовать дешевые микроконтроллеры и простейшую автоматику на *тм2 (*ls74). Реализовать драйвера сразу нескольких двигателей и в микроконтроллере расположить хотя бы часть контроллера самого устройства (например тот же интерпретатор g-кода). В таком случае получается некоторая выгода решается сразу несколько проблем. Не последней выгодой будет факт наличия обратной связи с детектором пропусков шагов которого в дешевых драйверах обычно не бывает.


            1. Flammmable
              13.09.2025 15:33

              В какую цену цифровой части вы хотите уложиться?

              Планируете ли вы, что у вас 1 цифровая микросхема может управлять сразу несколькими моторами? Или у вас "1 мотор - 1 микросхема"?


    1. romanetz_omsk
      13.09.2025 15:33

      В некоторых задачах тёплое и мягкое комбинируют - FPGA для time-critical вещей, где счёт идёт на нано- и микросекунды, а RTOS - там где десятки микросекунд и миллисекунды и сложная логика. Жирные плисины или плисины с интегрированным hard-processor (типичные примеры из недорогих - Xilinx Zynq, Ultrascale, Cyclone V SE) позволяют делать и то, и другое. Один из известных примеров - проект machinekit: в линуксе preempt-rt крутится парсер g-code и ядро линукса, на логической части - генераторы шагов, обработчики энкодеров, ШИМ и прочая реалтаймовая логика, типа развёртки изображения для гальванического сканера. Так же и в SDR поступают: базовые станции, начиная с 3G - тоже пример такого симбиоза. Радиочасть в плиске крутится - многоканальный модем и DDC/DUC, а процессор обрабатывает протокольную часть. Высокопроизводительные коммутаторы/маршрутизаторы - тоже реализуют эту схему. Процессор управляет записью правил в ПЛИС или ASIC, а пакеты (в труху) перемалывает конфигурируемая логика.


      1. vvzvlad
        13.09.2025 15:33

        Да в любом осцилле так делается. FPGA быстро семплирует, кладет в память, а мк читает и рисует на экране. МК не умеет быстро, тратить дорогой FPGA на рисование на экранчике жалко, да и писать долго.


        1. Mike-M
          13.09.2025 15:33

          Ну, не в любом. Например, свежеиспеченный отечественный осциллограф OSA103 сделан полностью на Спартане, без МК: https://www.osa103.ru/ru/hardware-ru/


          1. vvzvlad
            13.09.2025 15:33

            Ну им проще, у них ни экрана, ни крутилок, отдал поток по USB и все.


            1. NutsUnderline
              13.09.2025 15:33

              даже не по usb, там еще ftdi которая суть есть микроконтроллер непрошиваемый


  1. LinkToOS
    13.09.2025 15:33

    Все работает, но… Недостаток у данного подхода — ужасная читаемость кода.

    Под "читаемостью кода" подразумевается понятность логики работы программы? Когда реализуется сложная логика, то возникает конфликт между скоростью работы и "читаемостью кода". Разработчик старается писать текст программы так, чтобы хорошо была видна логика работы, а потом уговаривает компилятор оптимизировать это для максимальной скорости. Или наоборот, он сам оптимизирует, но из-за этого разобраться в логике работы программы становится очень сложно, и соответственно искать логические ошибки становиться очень сложно.
    Об этом идет речь?


    1. rukhi7
      13.09.2025 15:33

      Когда реализуется сложная логика, то возникает конфликт между скоростью работы и "читаемостью кода".

      нет, обычно сложная логика это когда есть, в самом простом (как это ни странно звучит) случае две логики и есть некоторая логика взаимодействия этих логик, тогда проблема как раз в попытке построить какую-то объединенную супер-логику, которая игнорирует это разделение и как раз и становится не адекватно сложной. Сложная бывает математика (дифференциальное исчисление, например, в реальном времени, бывает)! Математика должна быть как-то обособлена от логики, когда логику мешают и перемешивают с математикой (даже с простой!) логика становится просто невозможно сложной. Если вы внутри, например, какого-то вычисления БПФ или фильтра какого-то замешаете какую-то логику - это будет убийство здравого смысла, но так к сожалению бывает.


      1. LinkToOS
        13.09.2025 15:33

        Дано: сложная логика.
        Задача: реализовать эту логику в коде.
        Требование: код должен быть одновременно эффективным и "читаемым".

        Вопрос: Должен ли программист концентрироваться на читаемости кода, а не эффективности, и надеяться на то, что компилятор сделает любой код эффективным за счет оптимизации.

        На проблему "читаемости" указал автор. Это заставило его переписывать уже готовый и рабочий код.


        1. rukhi7
          13.09.2025 15:33

          Дано: сложная логика.

          надо бы разобраться что вы здесь имеете ввиду сначала, потому что дальше у вас на этом все основано, вдруг это неверная посылка? Можете привести пример сложной логики, причем обоснованно сложной?


          1. LinkToOS
            13.09.2025 15:33

            надо бы разобраться что вы здесь имеете ввиду сначала, потому что дальше у вас на этом все основано

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

            Можете привести пример сложной логики, причем обоснованно сложной?

            Сложной конкретно для Вас? Нет разумеется. Сложность это субъективный параметр. У кого-то в голове умещается логика описанная на тысячах страниц с сотнями диаграмм и таблиц соответствия. И он элементарно способен разглядеть эту логику в коде. Для такого человека, "сложность" это вымышленное понятие, несуществующее в природе.


            1. rukhi7
              13.09.2025 15:33

              Не знаю, может это, действительно, только для меня сложно. Вот вы видите логику в таком изложении принципа (некоторой идеи):

              Общий принцип таков. Контекст не переключается, нужен delay или событие, проверь счетчик или наличие событие и jump на другой участок кода. 

              Я не вижу. А как я понял код пытается реализовать именно этот принцип. Внимание вопрос, если мы пытаемся реализовать в коде некую идею, которая не имеет смысла (пусть здесь я жестко ошибаюсь по поводу наличия смысла, но просто для примера), какова будет читаемость такого кода?

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


              1. LinkToOS
                13.09.2025 15:33

                Вот вы видите логику в таком изложении принципа (некоторой идеи):
                Общий принцип таков. Контекст не переключается, нужен delay или событие, проверь счетчик или наличие событие и jump на другой участок кода.

                Это отрывок из потока мыслей автора. Естественно что в отрыве от контекста он не понятен.

                Я попробую изложить его мысли по своему, но не факт что это будет более понятным:

                Программа разбита на задачи (подпрограммы). Задачи выполняются последовательно в цикле main.
                Есть задачи, которые содержат циклы ожидания события (wait event), или задержки времени (delay). Когда такая задача доходит до точки wait event или delay, у нее есть два варианта действия:
                1. Зависнуть в ожидании, затормозив выполнение всей программы.
                2. Поставить флаг "Условие не выполнено", и перепрыгнуть к началу следующей задачи.

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


                1. rukhi7
                  13.09.2025 15:33

                  Вот вас я замечательно понял, и мне это пригодится! Спасибо.

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


  1. Flammmable
    13.09.2025 15:33

    А ещё в темы про ассемблер для Cortex-M, ведение микроконтроллера на ARM в каждый такт, устройство внутренних шин и гарантии реакции на то или иное событие, по опыту прибегает 10⁶ типов, рассказывающих в лучших традициях:

    "Не, а зачем вам? Вы расскажите конкретно какая у вас задача? Сейчас так не делают, сейчас не считают такты, сейчас у микроконтроллеров есть прерывания, таймеры и фигаймеры, всем всего всегда хватает".

    Визитной карточкой таких спецов это сравнение жигулей с мерседесом - видимо люди душой ещё в 90х :)

    Я думал, эта специфика и эти метафоры - отличительная черта профессиональных сообществ из СНГ, но нет! :) На electronics.stackexchange в теме про подсчёт тактов на Cortex-M мне тип начал говорить, что это всё равно, что после Кадилака 1950-х годов спрашивать как завести с крюка Фольксваген Гольф.

    Он очень был доволен собой ровно до момента, пока я не кинул ссылку на Sitara AM437x, у которого есть центральное высокопроизводительное ядро на ARM с частотой за ГГц и два дополнительных ядра с частотами по 200МГц и гарантией, что каждая ассемблерная инструкция на них выполняется ровно за 1 такт. Причём сам TI и говорит, что эти два ядра нужны для Ensuring real‑time predictability.


    1. EmCreatore
      13.09.2025 15:33

      Меня всегда настораживает, когда периферийным RISC модулям или примитивным FPGA присваивают способность к некоей точности или универсальности.
      Откуда у них точность-то с их целочисленной арифметикой. Скорее всего они расчитывают на свои оптимизированные для целочисленной арифметики и закрытые IP блоки. Все заточенные на традиционный FOC с PID-ами.
      Но кому они нужны теперь, когда есть Cloude?
      А если нужно эстиматоры посложнее и именно для того чтобы повысить точность регулирования, то там уже не так радужно.
      Тотже STM32N6 уделает эту ситару вместе с ее PRU как нечего делать. Потому как PRU не умеют в AI. А управлять STM32N6 может теми же двумя моторами и еще быстрее потому как 240 МГц на мотор. Но к этому он еще и видеть может чем управляет.


      1. Flammmable
        13.09.2025 15:33

        Потому как PRU не умеют в AI

        RPU, конечно, не умеет в AI. Только сама парадигма AI не подразумевает использования тех приёмов, про которые говорится в статье.

        Тут либо мы доверяем AI рулить реалтаймом - и тогда что RTOS, что RPU, что ПЛИС остаются не у дел в контексте задач реалтайма. Либо мы не доверяем AI и тогда STM32N остаётся не у дел - именно в нашем реалтайме.

        Не исключаю, что весь драматургический конфликт в статье и обсуждении - это потасовка в отцепленном вагоне истории. Но тем не менее, этот вагон един и для RTOS, крутящихся на одном ядре, и для Ситары и для какого-нибудь MAX-10.


        1. EmCreatore
          13.09.2025 15:33

          Ок. Я выше немного запутал понятие AI.
          Речь идет о нейросетевых алгоритмах типа NN-PID, PID + NN-feedforward, NN-Inverse и т.д. Их масса. Это самые что ни на есть риалтаймовые алгоритмы.
          И это самый писк моды. Прям локомотив истории. Cмотрим тот же Cortex-M55.
          Я даже не знаю как FPGA и доморощенные PRU с этим могут конкурировать.


          1. Flammmable
            13.09.2025 15:33

            Речь идет о нейросетевых алгоритмах типа NN-PID

            NN-PID - это в некотором смысле чёрный ящик. Сомнительно, чтобы для него была актуальна ручная экономия тактов.

            Повторюсь, либо у нас NN-PID (на оптимизированном под это железе), которому мы верим. И тогда "руками ничего не трогать!".
            Или мы не верим NN-PID (ну или STM32N в контексте нашего проекта слишком дорог). Тогда мы выносим его за скобки дискуссии.


  1. aao_1965
    13.09.2025 15:33

    интересно почему сложилось такое предвзятое отношение к rtos? на современных не очень быстрых (400мгц) микропроцессорах арм время реакции ртос на событие примерно 0.6-1.3мкс. причем наихудшее время показывает популярная freertos.


  1. VauAG Автор
    13.09.2025 15:33

    Отвечу на один из вопросов. Почему до сих пор уделяется вниманием затраченным тактам. Когда надо сделать что то разовое или мелкосерийное - проблем нет. Затраты до +10долл за мощное железо в единицу продукции, это намного меньше чем придется заплатить программисту. Можно ставить все что угодно. При тиражах от 1000 шт так уже не получится. От 10000 борьба будет за каждый цент. В принципе это справедливо в вопросе, а почему избегаете FPGA. Для серии это дороже.


    1. EmCreatore
      13.09.2025 15:33

      Логика немного хромает.
      Смотрим на айфоны и видим что туда вставляют все самое дорогое.
      И не трясутся за копейки. В современном обществе, где спрос воспитывают, копейки не имеют значения.
      Фишка в том что если что-то реально выгодное на FPGA делают, то это сразу превращается в SoC.


    1. Flammmable
      13.09.2025 15:33

      От 10000 борьба будет за каждый цент. В принципе это справедливо в вопросе, а почему избегаете FPGA.

      На каком микроконтроллере (ну плюс-минус, чтоб NDA не раскрывать) вы реализуете управление двигателем?