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

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

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__ */Такие функции могут итерироваться супервизорорами. Они очень удобно отслеживают и реагируют на аварийные ситуации и итерируют подопечную функцию. А для выполнения множеста таких функций используются планировщики. Они довольно разнообразны и это целая отдельная тема.

EmCreatore
13.09.2025 15:33Когда управляешь инвертором двигателя, real time измеряется 10-30мкс.
Хорошо живете, столько времени иметь в запасе
Однако чтобы прицизионно контроллером измерить напряжения и токи во всех фазах и еще внутренности разные измерить, делая это синхронно с шимом, то останется всего пара микросекунд. Это половинное время минимального пульса ШИМ.
Потому что уложиться надо от середины пульса и пока не возникнет фронт или спад на какой либо фазе. На это, кстати, указывается во всех мануалах по управлению двигателями на однокристальных SoC.
Поэтому всяческие коооперативные переключатели, как в этой статье, никак уже не подходят.
Приходится применять вложенные прерывания с приоритетностью, и обычную RTOS типа FreeRTOS.
Ничего лучшего человечество пока не придумало.
VauAG Автор
13.09.2025 15:33Да основные вычисления делаются в прерывании. По другому никак. Но скрещивать RTOS с управлением мотором это я еще нигде не видел ))). по поводу переключателей, есть куча доп интерфейсов, интерактивность для человека, меню, кнопки, сигналы внешние, все это надо фильтровать и обрабатывать перед принятием решением. Медленные сигналы в виде напряжения DC звена управления контактором. Там переключатели очень даже хорошо. внутри самой математики у меня лично есть ветвления и там применяется указанный подход. В целом соглашусь с одним из комментариев, что построение реал тайм мастерство. А у каждого мастера может быть свой подход.

VelocidadAbsurda
13.09.2025 15:33Но скрещивать RTOS с управлением мотором это я еще нигде не видел ))).
Есть известный/успешный в своих кругах проект VESC, управляющий 1-2 моторами. Построен на ChibiOS.

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

kapojko
13.09.2025 15:33Интересный подход! По названию, я подумал, что вы будете говорить о реализации асинхронный логики путем использования аппаратных контроллеров периферии и памяти. Но тут настоящий python/js async :) Выглядит впечатляюще!
Хотя я бы так не писал. Во многих задачах, где мне доводилось применять микроконтроллеры, нужно так или иначе реальное время - а том смысле, что время реакции системы на входные сигналы должно быть однозначно предсказуемо. Применение async этому препятствует - очень трудно однозначно сказать, будет в конкретном месте когда yield или не будет, и не получится ли, что при каком-то редком сочетании факторов время реакции будет неожиданно сильно большим, чем в остальных случаях.
Ну и к preprocessor hell это уже очень близко. :) Напоминает труды Александреску по C++, очень красиво, но лучше не применять в реальной жизни)

Ogura
13.09.2025 15:33И несправедливости, что возит, поит, греет творение М.О. Доливо-Добровольского, а поминают Н. Тесла. Причем АД второго, невероятно плох. Двухфазный (привет асимметрии линий электропередачи), концентрированная обмотка на полюсах (прощай КПД), ну и низкий пусковой момент. А вот Михаила Осиповича творение с первого включения показало КПД в районе 90 и двойной пусковой момент, что и стало стандартом де-факто с 1891г. и по сей день.
Михаи́л О́сипович Доли́во-Доброво́льский внес существенное усовершенствование в переменный ток добавив еще одну третью фазу. Сместив фазы одна относительно другой на 120°. Вообще чем фаз больше тем лучше штук так сем вообще было бы замечательно. Жаль только ЛЭП располагать трудно, а выгода "мизерная". Тесла предложил только 2 фазы их было мало третья же фаза оказалась решением проблемы оставшейся после Теслы. Тоесть не следует противопоставлять, они скорее коллеги чем соперники.

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

Ogura
13.09.2025 15:33Westinghouse и AEG может и были конкурентами но Тесла и Добровольский уже вряд ли. Как и Эдисон так Westinghouse развели Tesl-у.
P.S. Я похоже сам на какой-то процент Тесла, и мне удается повторять его ошибки 1 в 1.

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

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

LeonidPr
13.09.2025 15:33Имхо, понятие "реальное время", распространимо только на один, самый приоритетный процесс.
Все остальные будут - мягкого реального времени, т.к. в любой момент могут быть прерваны тем, самым приоритетным процессом.

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

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

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

Flammmable
13.09.2025 15:33Сложно конструктивно обсуждать сравнение теплого с мягким
...зато легко вставлять не по месту комментарии про "сравнение тёплого с мягким" :)))

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

Flammmable
13.09.2025 15:33Ну потому что это реально теплое с мягким
Пока единственное, что реально - это бросание заезженым лозунгом.
Если надо <...> моторчиком подрыгать, то FPGA тут лишний.
попытка написать программу с кучей ветвлений <...> FPGA будет очень больно...
...для человека ничего не писавшего на FPGA. В реальности программа, написанная, например в парадигме автоматного программирования будет что на процессорной архитектуре, что на FPGA даже по синтаксису очень похожей.
с какой-нибудь математикой
Приведите, пожалуйста конкретный пример, чтобы я вам наглядно продемонстрировал, что FPGA за ту же цену, что и MCU насчитает всё гораздо быстрее.
То, что вы изложили - набор матёрых мифов. Единственное ключевое преимущество процессорных архитектур перед FPGA - армия людей, умеющих писать на Си и не умеющих на Верилоге.

vvzvlad
13.09.2025 15:33Эм. Вы или не понимаете концепта FPGA или троллите.
Пока единственное, что реально - это бросание заезженым лозунгом.
Каким лозунгом-то?
Ну вы бы хоть почитали по ссылке-то, что там говорится. 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 можно пока опустить.

Flammmable
13.09.2025 15:33Вы или не понимаете концепта FPGA или троллите.
Вы допускаете, что этот концепт не понимаете вы?
я говорю что для подрыгать моторчиком
В ваш мозг у меня лаза нет и что вы понимаете под "подрыгать моторчиком" без вашего уточнение - вопрос интерпретации.
Если речь про "штуку, которая закрывает шторы", то с этим и что-нибудь на NE555 и 74 логике справится. Кстати, любопытный вызов.
PADAUK PFC161-U06
И какую же RTOS вы на нём развернёте?

vvzvlad
13.09.2025 15:33Вы допускаете, что этот концепт не понимаете вы?
Допускаю, конечно. Но пока что-то никто аргументы привести не смог.
И какую же RTOS вы на ней развернёте?
Внезапно, для мультизадачности не нужна ртос.
Ну и вот на ESP32 замечательно FreeRTOS работает.

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

Ogura
13.09.2025 15:33Должен признаться, статья перекликается с задачей которая у меня на столе.
Суть в чем со старых принтеров определенных годов можно добыть шаговые моторчики и применить для различных задач. Часть моторчиков 2-фазные, но еще часть 3-фазные. Мало того 3-фазные и моторы шпинделей жестких дисков. и приводы больших копировальных аппаратов с внешним ротором.
Для 2-фазных доступно великое множество драйверов а для 3-фазных увы начинаются в основном от профессиональных, попытки приспособить интегральные микросхемы 3-фазных драйверов разбиваются о отличную таблицу дробления фаз.
Микроконтроллеры не обладает достаточным быстродействием для реализации токового драйвера. Где реакция на событие должна составлять ну хотя бы эдак 100нС (и то это очень долго). Исключение составляют stm32f3* у которого есть подходящая автоматика но и они и самые быстродействующие микроконтроллеры и FPGA обходятся дороже чем если бы купить готовые 2-фазные шаговые моторчики и драйверы.
Вот возникла идея использовать дешевые микроконтроллеры и простейшую автоматику на *тм2 (*ls74). Реализовать драйвера сразу нескольких двигателей и в микроконтроллере расположить хотя бы часть контроллера самого устройства (например тот же интерпретатор g-кода). В таком случае получается некоторая выгода решается сразу несколько проблем. Не последней выгодой будет факт наличия обратной связи с детектором пропусков шагов которого в дешевых драйверах обычно не бывает.

Flammmable
13.09.2025 15:33В какую цену цифровой части вы хотите уложиться?
Планируете ли вы, что у вас 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, а пакеты (в труху) перемалывает конфигурируемая логика.

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

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

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

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

LinkToOS
13.09.2025 15:33Все работает, но… Недостаток у данного подхода — ужасная читаемость кода.
Под "читаемостью кода" подразумевается понятность логики работы программы? Когда реализуется сложная логика, то возникает конфликт между скоростью работы и "читаемостью кода". Разработчик старается писать текст программы так, чтобы хорошо была видна логика работы, а потом уговаривает компилятор оптимизировать это для максимальной скорости. Или наоборот, он сам оптимизирует, но из-за этого разобраться в логике работы программы становится очень сложно, и соответственно искать логические ошибки становиться очень сложно.
Об этом идет речь?
rukhi7
13.09.2025 15:33Когда реализуется сложная логика, то возникает конфликт между скоростью работы и "читаемостью кода".
нет, обычно сложная логика это когда есть, в самом простом (как это ни странно звучит) случае две логики и есть некоторая логика взаимодействия этих логик, тогда проблема как раз в попытке построить какую-то объединенную супер-логику, которая игнорирует это разделение и как раз и становится не адекватно сложной. Сложная бывает математика (дифференциальное исчисление, например, в реальном времени, бывает)! Математика должна быть как-то обособлена от логики, когда логику мешают и перемешивают с математикой (даже с простой!) логика становится просто невозможно сложной. Если вы внутри, например, какого-то вычисления БПФ или фильтра какого-то замешаете какую-то логику - это будет убийство здравого смысла, но так к сожалению бывает.

LinkToOS
13.09.2025 15:33Дано: сложная логика.
Задача: реализовать эту логику в коде.
Требование: код должен быть одновременно эффективным и "читаемым".
Вопрос: Должен ли программист концентрироваться на читаемости кода, а не эффективности, и надеяться на то, что компилятор сделает любой код эффективным за счет оптимизации.
На проблему "читаемости" указал автор. Это заставило его переписывать уже готовый и рабочий код.
rukhi7
13.09.2025 15:33Дано: сложная логика.
надо бы разобраться что вы здесь имеете ввиду сначала, потому что дальше у вас на этом все основано, вдруг это неверная посылка? Можете привести пример сложной логики, причем обоснованно сложной?

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

rukhi7
13.09.2025 15:33Не знаю, может это, действительно, только для меня сложно. Вот вы видите логику в таком изложении принципа (некоторой идеи):
Общий принцип таков. Контекст не переключается, нужен delay или событие, проверь счетчик или наличие событие и jump на другой участок кода.
Я не вижу. А как я понял код пытается реализовать именно этот принцип. Внимание вопрос, если мы пытаемся реализовать в коде некую идею, которая не имеет смысла (пусть здесь я жестко ошибаюсь по поводу наличия смысла, но просто для примера), какова будет читаемость такого кода?
Прежде чем рассуждать о читаемости кода неплохо бы выяснить, а есть ли какое-то содержание у этого кода, которое можно прочитать. Дело в том что в данном случае у нас есть человеческое изложение принцыпа, который пытались реализовать в коде, поэтому после анализа принципа код можно не читать, в общем-то. К сожалению это практически исключительный случай, обычно к коду нет никакого изложения идеи реализованной в коде, поэтому приходится доказывать, что в коде нет содержания, или этого содержания совершенно не достаточно чтобы служить решением конкретной задачи. Я к тому что прежде чем рассуждать о читаемости какого-то кода надо выяснить, а стоит ли этот код читать.

LinkToOS
13.09.2025 15:33Вот вы видите логику в таком изложении принципа (некоторой идеи):
Общий принцип таков. Контекст не переключается, нужен delay или событие, проверь счетчик или наличие событие и jump на другой участок кода.Это отрывок из потока мыслей автора. Естественно что в отрыве от контекста он не понятен.
Я попробую изложить его мысли по своему, но не факт что это будет более понятным:
Программа разбита на задачи (подпрограммы). Задачи выполняются последовательно в цикле main.
Есть задачи, которые содержат циклы ожидания события (wait event), или задержки времени (delay). Когда такая задача доходит до точки wait event или delay, у нее есть два варианта действия:
1. Зависнуть в ожидании, затормозив выполнение всей программы.
2. Поставить флаг "Условие не выполнено", и перепрыгнуть к началу следующей задачи.
Первый вариант приводит к подвисанию и потере производительности.
Второй вариант усложняет логику, уменьшает точность delay, и скорость реакции на events.
rukhi7
13.09.2025 15:33Вот вас я замечательно понял, и мне это пригодится! Спасибо.
Но это все констатация известных проблем - ничего нового нет, возможно новым было бы что проблемы надо решать на каком-то другом уровне, я буду пробовать сформулировать все таки что-то похожее на решение, то что мне хорошо помогало.

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.

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

Flammmable
13.09.2025 15:33Потому как PRU не умеют в AI
RPU, конечно, не умеет в AI. Только сама парадигма AI не подразумевает использования тех приёмов, про которые говорится в статье.
Тут либо мы доверяем AI рулить реалтаймом - и тогда что RTOS, что RPU, что ПЛИС остаются не у дел в контексте задач реалтайма. Либо мы не доверяем AI и тогда STM32N остаётся не у дел - именно в нашем реалтайме.
Не исключаю, что весь драматургический конфликт в статье и обсуждении - это потасовка в отцепленном вагоне истории. Но тем не менее, этот вагон един и для RTOS, крутящихся на одном ядре, и для Ситары и для какого-нибудь MAX-10.

EmCreatore
13.09.2025 15:33Ок. Я выше немного запутал понятие AI.
Речь идет о нейросетевых алгоритмах типа NN-PID, PID + NN-feedforward, NN-Inverse и т.д. Их масса. Это самые что ни на есть риалтаймовые алгоритмы.
И это самый писк моды. Прям локомотив истории. Cмотрим тот же Cortex-M55.
Я даже не знаю как FPGA и доморощенные PRU с этим могут конкурировать.
Flammmable
13.09.2025 15:33Речь идет о нейросетевых алгоритмах типа NN-PID
NN-PID - это в некотором смысле чёрный ящик. Сомнительно, чтобы для него была актуальна ручная экономия тактов.
Повторюсь, либо у нас NN-PID (на оптимизированном под это железе), которому мы верим. И тогда "руками ничего не трогать!".
Или мы не верим NN-PID (ну или STM32N в контексте нашего проекта слишком дорог). Тогда мы выносим его за скобки дискуссии.

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

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

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

Flammmable
13.09.2025 15:33От 10000 борьба будет за каждый цент. В принципе это справедливо в вопросе, а почему избегаете FPGA.
На каком микроконтроллере (ну плюс-минус, чтоб NDA не раскрывать) вы реализуете управление двигателем?
rukhi7
Что же вы самое интересное пропустили? Откуда у вас взялись такие цифры 10-30мкс?
Я правда делал управление коллекторным двигателем, там слот времени принятия решения определялся полупериодом сетевого напряжения (50 Гц, 220В) это
1000 миллисекунд / 50Гц / 2 полупериода = 10 миллисекунд.
Семистр открывается до следующего пересечения нуля сетевым напряжением. Но некоторые вещи тем не менее требуют почти абсолютной точности расчетов по времени, это правда, как говорится "это real time детка!" :). Причем проблема не только минимальное разрешение, есть еще проблема больших интервалов для которых должна поддерживаться точность, например однажды выставленные 10мс должны колебаться вокруг именно 10 мс, а не 9.9 мс(например) на протяжении потенциально бесконечного времени работы драйвера двигателя...
Эта задача решается не в языках программирования! В языках программирования нет такого понятия: "прерывания", ни в одном! Надо построить схему или диаграмму прерываний - то есть надо придумать как(!) конкретную схему нарисовать, для определенного набора прерываний и необходимого времени на их обработку так, чтобы они не накладывались непредсказуемым образом, а в коде надо просто контролировать что схема работает, то есть не происходит не продуманных наложений. Это проверяется на этапе отладки-тестирования, когда схема проверена это превращается в простую математическую задачу уровня 5-7 класса. А вот придумать как грамотно изобразить-начертить схему и как ее проверить - это действительно сложно.
Если интересно я могу подробно, с примерами, рассказать как создавать такие, поддающиеся проверке, схемы прерываний. Я думаю на эту тему можно даже написать диссертацию, если руководитель найдется, который не постесняется руководить изучением такого нестандартного вопроса в нашей научной традиции.
Ogura
Неверное у вас двигатель был подключен к электрораспределительной сети, а у него в мобильной сети.
VauAG Автор
Ну например BLDC покрутить. Им надо 2кГц. Защитный контур по транзисторам как раз 10-30мкс, иначе они успеют надежно сгореть. А симистором управлять, на современных МК как угодно можно, все равно хорошо будет.
rukhi7
2 кГц - это пол миллисекунды и это действительно уже практически на пределе возможностей процессора который работает на частоте до 100МГц, ну как на пределе - если заниматься ручной оптимизацией то запас есть, но он счетный, скажем пара тысяч тиков = примерно тысяча инструкций ассемблера запаса - это можно посчитать, но почему вы ориентируетесь на 10-30мкс все равно не понятно!
Защитный контур это наверняка какая-то схема, пока эта схема не сработала с нее, ну может идет какой то дежурный сигнал, но вряд ли каждые 30 мкс! Сделать счетным время реакции на аварийное прерывание не так уж и сложно (если догадаться сделать аварийный сигнал прерыванием высшего приоритета). Это конечно еще вопрос насколько грамотно (тоже оптимально) сделана обвязка процессора. Это другой сложный вопрос, что сигналы должны грамотно распределяться по типам и по задержкам друг относительно друга. В обвязке это тоже нужно контролировать, а лучше спланировать.
@audisergзаказ принял! К сожалению теперь я не работаю с механикой или с ПИД регулировками, месяца два понадобится, на фоне основной работы, чтобы воспроизвести что-то по памяти, думаю. Но я в принципе давно хотел - не мог определиться с темой, спасибо автору этой статьи, у меня сформировалось что-то вроде той печки от которой надо
плясатьформулировать, а еще, недавно правленые драйвера Линукса теперь помогают мне сопоставить то, что я делал на голом железе с тем, что можно найти в ядре Линукса.audiserg
Интересно! Рассказывайте! )
fivlabor
Ковырялся (года 4 назад) в коде для 32 кГц BLDC + CAN и оно норм работает на stm32f4, можно быстрее, но уже в АЦП упирается - надо брать stm32g4 - на 64кГц целились
Ig_B
Контроллер с хорошей периферией позволяет решить задачу фазового управления аппаратно. (dsPIC33EP...).