Перейти к содержанию
    

Как делить программу на объекты?

31 minutes ago, AHTOXA said:

Меня поражает ваша способность...

Я прекрасно понимаю, какую цель вы преследуете, роясь в моих постах (ведь не лень же), цитируя и выворачивая их тут в удобном для вас свете ))

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

Ведь сути это не меняет - как я понял, вы по-прежнему не понимаете, к чему все эти "делегаты".

А коли так, то продолжать с вами диалог на эту тему смысла не вижу.

Сменим тему.

 

1 hour ago, Arlleex said:

в итоге увлекся и сделал свою многозадачную микроОС

И что получилось в итоге?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

48 минут назад, Forger сказал:

Я прекрасно понимаю, какую цель вы преследуете, роясь в моих постах (ведь не лень же), цитируя и выворачивая их тут в удобном для вас свете ))

Да ладно, делов-то, поискать по теме по странному слову "ввиду", и всё:) И я ничего не выворачивал, только лишь процитировал. Вы прочувствовали глубину перлов?

49 минут назад, Forger сказал:

Ведь сути это не меняет - как я понял, вы по-прежнему не понимаете, к чему все эти "делегаты". 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, AHTOXA said:

что заводить по делегату на каждый вектор прерывания - это явный перебор.

Перебор - это если если чего-то не хватает или чего-то в притык, в данном случае - ОЗУ. Но у меня с этим проблем нет.

 

 

2 hours ago, AHTOXA said:

Я прекрасно понимаю, к чему делегаты.

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

Попробую еще раз объяснить.

Речь про проект со строгой иерархией (вложенные классы и т.п.), а не "плоский", как многие до сих применяют в МК.

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

И именно в таких проектах классические С-обработчики, как палка в колесе - напрочь все портят и разрушают всю конструкцию.

Ваше решение конечно же не спасает - экземпляр класса по сути объявлен глобально, т.е. никому не "принадлежит". Раньше так же делал.

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

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

 

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

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 часов назад, Forger сказал:

Речь про проект со строгой иерархией (вложенные классы и т.п.), а не "плоский", как многие до сих применяют в МК.

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

И именно в таких проектах классические С-обработчики, как палка в колесе - напрочь все портят и разрушают всю конструкцию.

Ну вот. Сначала вы не поняли приведённого вам примера (весьма простого), попросили упростить его ещё. Я упростил, и теперь вы делаете вывод, что у меня все проекты простые, а у вас - сложные, "с иерархиями"...

Вас совершенно определённо любит начальство.

Во-первых, я таки использую "иерархии". И в реальном проекте у меня в обработчике прерывания вызывается не uart.interruptHandler(), а, скажем, uplink.uart.interruptHandler().

Во-вторых, "проект со строгой иерархией (вложенные классы и т.п.)" совершенно ортогонален способу вызова обработчиков прерываний. То есть, в вашем примере можно просто вместо

timer.installVector(vector);

написать

extern "C" void TIM2_IRQHandler()
{
	module.thread.handler();
}

И всё! Вместо этого простейшего (и наиболее эффективного) решения вы нагородили кучу "делегатов", и теперь ещё и гордитесь своим решением.

И в третьих, самое главное. Та умная книжка, которой вы начитались ("Чистый код", Р. Мартин) - она от джависта. Поэтому надо фильтровать советы из этой книжки:) Да и вообще, все советы надо фильтровать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, AHTOXA said:

...

Вы ничего не поняли и того, что пытался донести :(

 

Последняя попытка: 

При таком вызове будет произведено много вложенных вызовов.

extern "C" void TIM2_IRQHandler()
{
	module.thread.handler();
}

 

Ведь напрямую обращаться к полям (данным) класса - неправильно, это напрочь нарушает ООП.

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

Чем сложнее иерархия, тем дольше будем добираться до самого "дна". Еще сложнее "выбираться наружу". Костыльные "inline" где-то помогут, а где-то и нет. Тут как "повезет".

 

Делегат в данном случае дает строго детерминированное время вызова. Глубина вложений не имеет значения. Сложность проекта также не имеет значения. Да и стек прерываний не требуется конский (хотя это - мелочи).

Более того, экземпляр module - не глобальный объект, его созданием занимается совсем другое объект (у меня некий Application). Он же управляет "связыванием" модулей другу с другом.

Модули НИЧЕГО друг о друге не знают, не никаких перекрестных include "" (посмотрите для примера какой-нибудь нормальный проект под Qt.)

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

 

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

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

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

Тут еще много ограничений возникает при такой "плоской" схеме построения, где все всё видят и имеют доступ ко всему, в том числе и полям (данным) вложенных классов.

 

Если и после этого вы не поняли зачем тут нужны делегаты (или на край функторы), и для чего все это, то, увы :(

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Forger сказал:

Вы ничего не поняли и того, что пытался донести :(

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

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

1 час назад, Forger сказал:

При таком вызове будет произведено много вложенных вызовов. 

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

1 час назад, Forger сказал:

Ведь напрямую обращаться к полям (данным) класса - неправильно, это напрочь нарушает ООП.

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

 

1 час назад, Forger сказал:

Если и после этого вы не поняли зачем тут нужны делегаты (или на край функторы), и для чего все это, то, увы :( 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

14 часов назад, Forger сказал:

И что получилось в итоге?

Часть первая. Анализ рынка велосипедной продукции

 

Когда ViKo только открыл трэд, прозвучала мысль о не желании использовать какую-либо ОС, а сделать все на флаговом автомате в суперцикле.

Я предложил использовать модель событийного механизма - обычная Event-driven system. Этот механизм еще давно показался мне достаточно интересным и оправданным в случае проектирования ПО без ОСРВ.

Напоминаю, в чем суть Event-driven system (EDS): в системе заводится некая очередь событий; объекты-источники этих событий (обычно формируемые прерываниями по таймерам, ADC, SPI и т.д., что угодно) помещают эти события в очередь. С другой стороны, основной суперцикл работает всегда и опрашивает эту очередь на предмет возникших в системе изменений. Обо всем этом можно прочитать, например, тут.

Однако, как на мой взгляд, такой подход тоже не лишен недостатков: даже с таблицей состояний и переходов, построение программы окажется, в некоторой степени, головной болью - множество глобальных переменных-состояний, код отдельных процедур будет невозможно писать линейно без привязки к архитектурной модели самой EDS. Последнее значит, что, например, даже используя замечательный механизм protothreads от Adam Dunkels, все ранее написанные библиотеки придется "допиливать" под использование в суперцикле. Даже каждую функцию, если она использует хотя бы одну длительную операцию, ожидающую события. За примером-подтверждением мне далеко идти не пришлось - смотрите сами

Спойлер

static int makeXmlLine(struct pt *pt, char* dst, char* src)
{
    static char* text; // должна быть static чтоб значение не потерялось до выхода из функции
    char * pch;
    PT_BEGIN(pt);
    strcpy(dst,"<?xml version=\"1.0\" encoding=\"Windows-1251\"?>");
    PT_YIELD(pt);
    strcpy(dst,"<text>");
    PT_YIELD(pt);
    text=strdup(src); // strtok портит исходный текст, нужно его сохранить
    pch = strtok (text," ,.!?:");
    while (pch != NULL){
        sprintf(dst,"    <word>%s</word>",pch);
        PT_YIELD(pt);
        pch = strtok (NULL, " ,.!?:");
    }
    strcpy(dst,"</text>");
    PT_YIELD(pt);
    *dst=0; // Пустая строка будет индикатором того, что поток строк закончился
    PT_YIELD(pt);
    free(text);
    PT_END(pt);
}

 

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

 

По сути, протопотоки - это та же самая switch-case технология, детально описанная В. Татарчевским в его статьях, но красиво обернутая в макросы.

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

Мне не понравился подход switch-case (и, соответственно, protothreads), потому как:

1. Каждая функция проекта должна быть переписана в автоматном стиле.

2. Из п. 1 вытекает следствие, что, если у меня сотни или тысячи функций, переписать придется их все, причем, помимо возможных внесенных багов, увеличится расход ОЗУ на глобальные статические флаги состояний этих функций.

 

Часть вторая. Своя недоОСька

 

В общем, что я хочу сделать (объяснение на пальцах).

Есть у меня, допустим, следующая задача: измерить температуру с нескольких датчиков DS18B20 и, раз в 1 секунду, отправлять ее по UART в запакованном байт-стаффингом виде третьей стороне. Также эта третья сторона может отправлять на нашу железку другие команды - управление дискретными выходами, отправка какого-нибудь набора байт по SPI и т.д. Задача простая и, поэтому, ставить ОС никакой необходимости нет. Используя подход EDS, получаем примерно такой main()

int main(void)
{
  HW_MCUInit();
  HW_PeriodicTimerStart();
  
  while(1)
  {
    u32 Event = EDS_GetEvent();
    if(Event)
      EDS_EventHandler(Event);
  }
}

 

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

Из импровизированного ТЗ мы выяснили, что раз в 1с устройство должно отправлять температуру по UART. Логично было бы тогда реализовать процедуру запуска преобразования, чтения и отправки по UART необходимых данных в самом обработчике события по секундному таймеру. То есть: таймер формирует прерывания раз в 1с, в прерывании мы записываем в очередь событий выделенный идентификатор этого события, а в суперцикле main() вычитаем это событие и вызываем обработчик

void PeriodicTimer_Handler(void)
{
  ONEWIRE_StartConversion();
  
  Delay(500);
  
  s16 Temperature[2];
  ONEWIRE_ReadTemperature(Temperature);
  
  HW_UARTSendTemperature(Temperature);
  
  return;
}

 

И вот здесь и возникает, собственно, проблема: наличие длительных операций, таких как ONEWIRE_StartConversion(), Delay(500) и всех остальных, будет жутко "тормозить" систему, делая ее однопоточной - то есть, пока тот же Delay(500) (полсекунды (!!!)) не отработает, управление не передастся диспетчерскому суперциклу, и мы можем пропустить своевременную обработку событий. Решение - сделать эту функцию асинхронной - при вызове ее сделать необходимые действия, а встретив длительную операцию, назначить обработчик для ее выполнения и выйти отсюда. Но программа получится просто адской сатаной - кучи обработчиков, ничего не ясно. Можно использовать другое решение - protothreads, но, как мы выяснили, читабельность кода пострадает, как и ресурсы времени выполнения кода и расхода ОЗУ МК.

На ум приходит механизм кооперативной многозадачности - при встрече длительной операции мы меняем контекст задачи. От его использования одни плюсы: не нужно дополнительных флагов в каждой функции всех библиотек (работа с 1-Wire в моем случае и т.д.), код выглядит также, как в проекте под управлением ОСРВ или даже без нее! Таким образом, можно построить сколько угодно функций-задач (по виду схожих с используемыми в ОСРВ), и они будут работать как бы "независимо".

Итак, идея понятна - реализовать кооперативную многозадачность без использования готовых ОСРВ, причем желательно максимально просто.

Чтобы "налегке" реализовать это, я пока что поставил задачу мигания двумя светодиодами из двух потоков - один из одного, другой из другого. С разной частотой. Дальше будет код с пояснениями в виде комментариев. МК - STM32F429.

Вот эти задачи

void TaskLED1(void)
{
  while(1)
  {
    EDS_SemaphoreTake(&LED1Semaphore); // ждем, когда дадут поработать этой задаче
    
    static u8 LedToggleFlag = 0;
    
    if(LedToggleFlag)
      HW_LEDOn(HW_LED1);
    else
      HW_LEDOff(HW_LED1);
    
    LedToggleFlag = !LedToggleFlag;
  }
}

void TaskLED2(void)
{
  while(1)
  {
    EDS_SemaphoreTake(&LED2Semaphore); // ждем, когда дадут поработать этой задаче
    
    static u8 LedToggleFlag = 0;
    
    if(LedToggleFlag)
      HW_LEDOn(HW_LED2);
    else
      HW_LEDOff(HW_LED2);
    
    LedToggleFlag = !LedToggleFlag;
  }
}

 

А вот теперь мой main()

int main(void)
{
  HW_MCUInit();                                  // инициализация периферии МК
  
  EDS_CreateTask(EDS_TASK_LED1, &TaskLED1, 128); // создание задачи мигания LED1
  EDS_CreateTask(EDS_TASK_LED2, &TaskLED2, 128); // создание задачи мигания LED2
  
  EDS_LaunchTask(EDS_TASK_LED1);                 // запуск задачи мигания LED1
  EDS_LaunchTask(EDS_TASK_LED2);                 // запуск задачи мигания LED2
  
  HW_PeriodicTimerStart();                       // запуск периодического таймера
  
  while(1)                                       // стандартный механизм EDS
  {
    u32 Event = EDS_GetEvent();
    if(Event)
      EDS_EventHandler(Event);
  }
}

 

HW_MCUInit() ничем особым не отличается - в нем обычная настройка периферии МК, требующейся для данного проекта.

EDS_CreateTask() создает задачу. Под созданием задачи понимается выделение ей стека и назначение обработчика. Смотрим реализацию

#define EDS_CreateTask(ID, HANDLER, STACK_SIZE) do                                                       \
                                                {                                                        \
                                                  static u32 __Stack##ID[STACK_SIZE];                    \
                                                  __Stack##ID[STACK_SIZE - 15] = 0x01000000;             \
                                                  __Stack##ID[STACK_SIZE - 1]  = (u32)HANDLER;           \
                                                  extern void *EDS_TaskContextSP[EDS_MAX_TASK_QUANTITY]; \
                                                  EDS_TaskContextSP[ID] = &__Stack##ID[STACK_SIZE - 15]; \
                                                }while(0)

 

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

 

Функция EDS_LaunchTask() запускает задачу. Она реализована как обыкновенный вызов функции EDS_SwitchContext(), которая, в свою очередь, выглядит так

Спойлер

    PRESERVE8
    THUMB
    AREA |.text|, CODE, READONLY
    
EDS_SwitchContext PROC
                  EXPORT EDS_SwitchContext
                  IMPORT EDS_CurrentTaskID
                  IMPORT EDS_TaskContextSP
                  
                  push {lr, r0-r12}
                  
                  mrs  r1, PSR
                  push {r1}
                  
                  ldr  r1, =EDS_TaskContextSP
                  ldr  r2, =EDS_CurrentTaskID
                  ldrb r3, [r2]
                  
                  str  sp, [r1, r3, lsl #2]
                  strb r0, [r2]
                  ldr  sp, [r1, r0, lsl #2]
                  dsb
                  
                  pop  {r1}
                  msr  PSR, r1
                  
                  pop  {r0-r12, pc}
                  
                  ENDP
                  END

 

Функция переключения контекста имеет прототип

void EDS_SwitchContext(u8 TaskID);

и принимает на вход уникальный идентификатор задачи, который ей присвоили при вызове EDS_CreateTask().

 

Теперь смотрим на реализацию семафоров

#define SEMAPHORE_RESET    0
#define SEMAPHORE_SET      1
#define SEMAPHORE_WAIT     2

typedef struct
{
  u8 TaskID;
  u8 Semaphore;
}sEDSSemaphore;

sEDSSemaphore LED1Semaphore = {EDS_TASK_LED1};
sEDSSemaphore LED2Semaphore = {EDS_TASK_LED2};

void EDS_SemaphoreGive(sEDSSemaphore *SHandle)
{
  u8 SemaphoreState  = SHandle->Semaphore;
  SHandle->Semaphore = SEMAPHORE_SET;
  
  if(SemaphoreState == SEMAPHORE_WAIT)
    EDS_SwitchContext(SHandle->TaskID);
  
  return;
}

void EDS_SemaphoreTake(sEDSSemaphore *SHandle)
{
  if(SHandle->Semaphore == SEMAPHORE_SET)
    SHandle->Semaphore = SEMAPHORE_RESET;
  else
  {
    SHandle->Semaphore = SEMAPHORE_WAIT;
    
    EDS_SwitchContext(EDS_TASK_ROOT);
    
    SHandle->Semaphore = SEMAPHORE_RESET;
  }
  
  return;
}

 

TaskID нужен для того, чтобы знать, какую задачу разбудить (нужно же ведь как-то знать, какая задача ждала именно этот семафор).

Когда вызывается EDS_SemaphoreGive(), мы устанавливаем семафор в активный уровень, а затем, если этот семафор уже ожидался какой-то задачей, переключаемся на эту задачу.

Когда вызывается EDS_SemaphoreTake(), мы сбрасываем семафор и выходим из функции (это значит, что мы находимся итак в том потоке, который потребовал ожидания семафора). Если же семафор не был установлен, мы переводим его в режим ожидания и переключаем контекст на корневой поток - суперцикл в main() для обработки всех других событий.

Вот, собственно, и все.

 

После того, как все заработало (светодиоды дружно моргают), можно сделать вывод, что и в той задаче с датчиками и UART-ом все должно работать:biggrin:

 

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

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

 

P.S. Не дописана функция EDS_Timeout() - пока что думаю как лучше сделать.

 

EDS.c

EDS.h

EDS.s

Hardware.c

Hardware.h

main.c

types.h

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, AHTOXA said:

Вы же не понимаете моего способа решения проблемы, и, что самое грустное, не пытаетесь понять.

Прекрасно понимаю, поскольку в более ранних проектах делал именно так, как вы сейчас делаете. Очень хорошо понимаю.

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

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

Да и понять-то даже не пытаетесь. 

Ну, что ж: надо, значит - не надо :)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, Forger сказал:

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

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

Да и понять-то даже не пытаетесь.

Понятно. Использую, но не понимаю.

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

Тем не менее, считаю это хорошим признаком. Вы по крайней мере перестали нести техническую чушь, и перешли на словесную эквилибристику. Значит, в технической части спора победил я:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

 

47 minutes ago, AHTOXA said:

мифических мегапроектов ... словесную эквилибристику

Ах вот оно что! Так бы сразу и сказали, что ни чего не поняли из моих описаний :)

Ну, извините, лучше "разжевать" не могу ((

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Изменено пользователем twix

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, AHTOXA said:

Кто бы сомневался:)

Можете забрать зачетку книжку. Перед переcдачей хорошенько подготовьтесь: перечитайте еще раз "лекции", обратитесь к доп. литературе.

Вот еще https://doc.qt.io/archives/qt-4.8/signalsandslots.html

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 hours ago, Arlleex said:

Часть первая. Анализ рынка велосипедной продукции . . . 

А вот Ваш пост я прочитал полностью.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

12 часов назад, k155la3 сказал:

А вот Ваш пост я прочитал полностью.

Спасибо:hi:

Сейчас я реализовал таймерную службу. Теперь потоки полноценно похожи на FreeRTOS-ные

void TaskLED1(void)
{
  while(1)
  {
    HW_LEDOn(HW_LED1);
    EDS_Timeout(50);
    HW_LEDOff(HW_LED1);
    EDS_Timeout(2100);
  }
}

void TaskLED2(void)
{
  while(1)
  {
    HW_LEDOn(HW_LED2);
    EDS_Timeout(50);
    HW_LEDOff(HW_LED2);
    EDS_Timeout(950);
  }
}

 

Таймерная служба работает с 1мс интервалом (настраивается аппаратным таймером, а можно вообще на SysTick повесить).

 

Результат работы вышеприведенных задач на плате STM32F429I-Disco (только щас заметил, что при переделке видео в gif-анимацию зеленый светодиод "пропадает", на деле - все ок)

doc101444220_491722799?hash=ba8673c2000d793d75&dl=2308b49d649af854b8&wnd=1&module=im

 

Если эта тема будет интересна/актуальна, распишу еще подробнее, как и что работает, а пока что выложу код (кое-что поправил, кое-что добавил).

EDS.c

EDS.h

EDS.s

Hardware.c

Hardware.h

main.c

types.h

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...