Pull to refresh

Comments 17

имхо, если цель

копнуть в сторону «как оно там устроено» на уровне близком к железу

то нужно копать не в сторону async/await и прочей новомодной "асинхронности" а в сторону старой доброй cooperative multitasking. Ибо под копотом по сути (а для однопроцессорных/ядерных систем и по факту) она и есть.

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

Зато проблемы цветных функций не будет.

Думал про это, но uno даёт слишком мало ресурсов. Да и цель была переписать код робота на что-то понятное, понятное после дня работы java/c#. А так да, вы правы. Надо попробовать.

Как только вы всё состояние "треда" ручками вынесли в отдельный объект – остальное уже халява.

В этом плане очень интересно сравнить три разных подхода: полноценные треды (неважно, с кооперативной многозадачностью или вытесняющей: основное – отдельный стек и сохранение состояния CPU при переключении), реализацию async/await в C# (мощная поддержка от компилятора, выделяющая состояние и преобразующая функции в state-машину) и реализацию промисов в js с мощным использованием замыканий.

Понятно, что варианты C# и JS на ардуино не подойдут, так как сильно опираются на автоматическое управление памятью, но понимать здОрово.

ЗЫ: Ещё есть подход Swift, но я его пока так и не понял.

Для arduino же подход гораздо проще чем корутины: setup / loop
Первая функция вызывается при инициализации, а вторая "постоянно" либо с постоянной частотой (например 1000 раз в сек как в realtime контроллерах). Для постепенно исполняемых функций надо примерно 16 строк:

loop-fn.h
#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: \
    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: \
    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; } \
    }} __loop_leave: return 1; }

#endif
loop-fn-example.c
#include <stdio.h>
#include "loop-fn.h"

typedef struct fn1_s {
    loop_t loop;
    int i;
} fn1_t;
int fn1_setup(fn1_t *self); /* return 0 if no error */
int fn1_loop(fn1_t *self); /* return 0 when done */

int fn1_setup(fn1_t *self) {
    LOOP_RESET(self->loop);
    return 0;
}

int fn1_loop(fn1_t *self) {
    LOOP_BEGIN(self->loop)
    printf("fn1.1\n");
    LOOP_POINT()
    for(self->i=0;self->i<3;self->i++) {
        printf("fn1.2.%d\n",self->i+1);
        LOOP_POINT()
    }
    printf("fn1.3\n");
    LOOP_END()
}

void test() {
    fn1_t s[1]; int i,r;
    fn1_setup(s);
    for(i=0;i<10;i++) {
        printf("%2d: ",(int)s->loop);
        r=fn1_loop(s); if (r==0) break;
    }
}

int main(int argc, char** argv) {
    test();
    return 0;
}
/* output:
 0: fn1.1
 1: fn1.2.1
 2: fn1.2.2
 2: fn1.2.3
 2: fn1.3
*/

Основные проблемы начинаются дальше, т.к. помимо самой многозадачности надо поддерживать некоторые правила. Без которых будет всё очень печально. И самое главное, что за выделение ресурсов должен отвечать не исполняющий код, а тот кто это код запустил исполняться. То есть если подзадача остановилась, есть возможность освободить все ресурсы которые были выделены в том числе и дочерние подзадачи. То есть структурная многозадачность должна быть как в erlang. Если исполнителя надо прибить или ограничить в ресурсах то это не вызывает UB или головной боли. Из плюсов простота и есть полный контроль над происходящим и можно сохранять состояние на диск или передавать по сети. Никакие корутины таким похвастаться не могут. И еще если потоков миллионы то задержки и ожидания делаются более хитрым способом.

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

Есть ещё интересный фреймворк Embassy, позволяет писать асинхронный код на Rust для embedded. Есть поддержка STM32, ESP32, NRF52, CH32, RP2040. AVR не поддерживают, правда, но кому сейчас нужны восьмибитные камни?

Это издевательство над читателем. Код кривой, на каком-то странном языке, такие же кривые объяснения.

Язык называется C. Появился в 1972г как логическое развитие идей языка программирования B. Входит в топ5 языков программирования в мире.

Это не C.

>>send(to, from, count)

register short *to, *from;

register count;

А что это, если не старый диалект C language? Там рассматривается старый, знакомый многим трюк, код взят оригинальный, без перевода на новые диалекты. Даже я с таким диалектом сталкивался в университете на древних sun SPARC...

В спецификации все это есть. Полезно изучить https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html

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

А что бы и нет ) будет интересно

Sign up to leave a comment.

Articles