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

    

Arlleex

Свой
  • Публикаций

    825
  • Зарегистрирован

  • Посещение

Репутация

0 Обычный

Информация о Arlleex

Контакты

  • Сайт
    http://
  • ICQ
    0

Посетители профиля

2 558 просмотров профиля
  1. Дело в том, что к реализации собственного механизма кооперативной многозадачности я пришел в некоторой степени факультативно. По работе требовалось написать ПО для МК STM32F0, который построен на базе Cortex-M3, в котором FPU нет. Поскольку это факультативное занятие я оставил себе на дом (посидеть вечерком, покумекать), нужно было на чем-то проверить свои мысли. А под рукой оказалась только плата с STM32F4. Отсюда, собственно, и получилось, что сейчас контекст FPU я не сохраняю. Но на самом деле, не составляет проблемы сохранять и его, таким образом, адаптируя мою недоОсь под Cortex-M4F. Планирую или нет - хм... Пока что, скорее всего, нет. Однако, если будет желание запустить свою систему на Cortex-M4F, допилю файл EDS.s, реализующий переключение контекста в части сохранения регистров FPU. Вопрос, по крайней мере для меня, скорее, из разряда риторических Дело в том, что поставить готовую ОС не составляет проблем. Однако, когда требуется написать достаточно простое ПО, ставя туда ОСРВ типа FreeRTOS, меня не покидает чувство стрельбы из пушки по воробьям - то есть чувство неоправданного использования ресурсов. С другой стороны, иногда просто полезно повторить/написать свое, даже если это уже сделано до тебя - так существенно улучшается понимание деталей и всех процессов. Это даже скорее как спортивный хобби-интерес Для жирных проектов я сразу нацеливаюсь использовать ОСРВ, поскольку обратно слезть с ее плюшек очень тяжело. Однако, глядя на объем кода своих исходников выше, пришел к мнению, что для простых проектов тащить ОСРВ ради двух-трех потоков с полудесятком семафоров не имеет смысла (тот же FreeRTOS на минималках будет выглядеть достаточно монструозно, к тому же не совсем оптимально, ИМХО). Вот да, про чувство избыточности я и хотел сказать выше К сожалению, я не достаточно популярен для гитхаба, да и регистрироваться ради пары исходников, думаю, не стоит. Тем, кто заинтересован, лучше просто сохранить себе исходники и иметь в виду такой вот нехитрый способ организации ПО.
  2. Спасибо Сейчас я реализовал таймерную службу. Теперь потоки полноценно похожи на 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-анимацию зеленый светодиод "пропадает", на деле - все ок) Если эта тема будет интересна/актуальна, распишу еще подробнее, как и что работает, а пока что выложу код (кое-что поправил, кое-что добавил). EDS.c EDS.h EDS.s Hardware.c Hardware.h main.c types.h
  3. RGB666 - это физический формат. Кодируется он целым числом байтов.
  4. Часть первая. Анализ рынка велосипедной продукции Когда ViKo только открыл трэд, прозвучала мысль о не желании использовать какую-либо ОС, а сделать все на флаговом автомате в суперцикле. Я предложил использовать модель событийного механизма - обычная Event-driven system. Этот механизм еще давно показался мне достаточно интересным и оправданным в случае проектирования ПО без ОСРВ. Напоминаю, в чем суть Event-driven system (EDS): в системе заводится некая очередь событий; объекты-источники этих событий (обычно формируемые прерываниями по таймерам, ADC, SPI и т.д., что угодно) помещают эти события в очередь. С другой стороны, основной суперцикл работает всегда и опрашивает эту очередь на предмет возникших в системе изменений. Обо всем этом можно прочитать, например, тут. Однако, как на мой взгляд, такой подход тоже не лишен недостатков: даже с таблицей состояний и переходов, построение программы окажется, в некоторой степени, головной болью - множество глобальных переменных-состояний, код отдельных процедур будет невозможно писать линейно без привязки к архитектурной модели самой EDS. Последнее значит, что, например, даже используя замечательный механизм protothreads от Adam Dunkels, все ранее написанные библиотеки придется "допиливать" под использование в суперцикле. Даже каждую функцию, если она использует хотя бы одну длительную операцию, ожидающую события. За примером-подтверждением мне далеко идти не пришлось - смотрите сами По сути, протопотоки - это та же самая 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(), которая, в свою очередь, выглядит так Функция переключения контекста имеет прототип 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-ом все должно работать Кстати хочу отметить, что для обмена данными между задачами можно использовать ту же глобальную очередь событий, поэтому никакие запреты прерываний для работы с семафорами не нужны. Если кому интересно - файлы проекта прилагаю, можете поизучать. Проект был написан и продуман за день, поэтому не ожидайте от него многого - многие вещи возможно будут обнаружены неоптимальными - в этом случае я буду только рад слышать рекомендации и пожелания. P.S. Не дописана функция EDS_Timeout() - пока что думаю как лучше сделать. EDS.c EDS.h EDS.s Hardware.c Hardware.h main.c types.h
  5. С некоторого момента с интересом слежу за трэдом чисто ради спортивного интереса Ничего, я скоро подключусь с очередным "гениальным" велосипедом - писал суперцикл для простейшего проекта (без FreeRTOS), а в итоге увлекся и сделал свою многозадачную микроОС
  6. Печалька какая-то В общем что: жду из Китая дисплеи IPS с SPI- и RGB-интерфейсами, платы для их подключения есть. Сравню скорости отрисовки анимаций, но уже склоняюсь снова к первоначальному варианту с LTDC
  7. Точняк. Посыпаю голову пеплом. Значит все еще хуже. Один фиг уже заказал экран - жду приезда. Буду экспериментировать и разгонять. А подключать по 16-битной шине уже смысла нет, потому что с таким успехом я мог бы подключиться к LTDC контроллера, а там плюшек побольше будет, нежели от простой шины FSMC или, что еще хуже, ногодрыжного варианта (помимо ногодрыга, работы для CPU с головой).
  8. Глянул даташит на ILI9486, там картинка с времянкой по SPI 14МГц - это при условии автоинкремента внутреннего счетчика пикселя в экране. Ну то есть если просто гнать данные в экран (пока что в подробности не вдавался, плохо будет, если промежуточные команды надо посылать). По картинке - в лучшем случае 30нс на период клока SPI это 33МГц. А 14МГц - это мои расчетные. В идеале - натравить DMA на постоянный вывод в дисплей сплошного потока видеобуфера (как в модуле LTDC), а в МК при отрисовке программно подпихивать разные адреса видеобуферов с подготовленными виджетами. SDRAM да, поставить придется. Ладно, думаю тупо заказать еще дисплеев с SPI на ILI-подобном контроллере и потестировать вживую.
  9. Вот мне тоже интересно... Есть задача (хобби) выводить анимированные меню, красивые переходы и прочие графические виджеты на экран. Вот смотрел на TouchGFX - у них круто выглядит все. Только рисовать хочу не руками (SetPixel, SetLine и т.д.), а на uSD-карте хранить заранее подготовленные отфотошопленные битмапы кнопочек, теней, виджетов - в общем, всю красоту, и выводить ее на экран. Поднимется ли такая же красота на SPI-дисплеях? Пока что вижу лишь связку uSD->SDRAM->STM32F429(LTDC)->TFT(RGB666) для реально крутой графики по умеренной цене. Хочу как-раз дешево сделать. Нашел на Aliexpress еле-еле экран с RGB интерфейсом (VSYNC, HSYNC), с ним выходит круто (уже есть другой девайс с графикой и такой цепочкой, что привел выше, заценил). А вот SPI-модулей полно и они гораздо дешевле. Но жутко напрягают видео на ютубе (подавляющее большинство), в которых отрисовка одного кадра (!) видна как тормозящая жутко. И никаких 30fps там нет Ну вот пример: https://www.youtube.com/watch?v=5HKlbpoErpg А хочется вот такого качества: https://www.youtube.com/watch?v=QcKX_Pc6ldU Просто цена моего решения (с RGB-дисплеем) существенно выше (многоногий корпус МК, наличие SDRAM, более редкий TFT, более сложная плата (4-слойка) и т.д.), чем SPI-решения, где даже видеобуфер хранить нужно только по факту один - один уже в экране хранится, а другой готовим в ОЗУ МК. С учетом этого хотелось бы услышать оценочное мнение о целесообразности связки с SDRAM+LTDC+TFT-RGB против SPI-TFT. P.S. Экран у меня 480x320. Глубина цвета - ну на сегодняшний момент 24 бита (RGB666) (именно хранение пикселей). Вот и думаю, 480*320*30fps*3Bpp = ~14МГц обмена по SPI, что, как бы, не много для STM32. Если еще и взять STM32F429 с DMA2D, то битмапы, вычитанные с SD-карты, можно произвольно расположить в видеобуфере, со всякими преобразованиями форматов пикселей и альфаканалом, при желании, что позволит сделать некие анимированные виджеты. Потом это дело отправить на дисплей по DMA, что не займет много процессорных сил для других дел. Но это только на первый взгляд... Как считаете, взлетит?
  10. В этом ничего плохого только в том случае, если Вы постоянно совершенствуете навыки проектирования и программирования. Я смотрю на свой код всегда критически. Даже спустя год можно найти для себя что-то новое, какой-то синтаксический сахар, который до этого момента писался в лоб и был нечитаем. А вот совсем запущенный случай, это когда всю жизнь пишешь в одном стиле, а спустя пару лет вообще не понимаешь что происходит в исходниках - это, ИМХО, результат легкомысленного отношения к ремеслу. То есть программа не продумана, возникают кучи связей, запутывание имён переменных и т.д. В этом случае нужно заняться самосовершенствованием. Ну по крайней мере я себя так успокаиваю
  11. Тогда понятно. Мне как раз и было интересно, зачем так делается, поскольку казалось архаизмом постсоветского производства.
  12. В общем это цирк получается. Я же ведь покупаю готовое изделие. Которое хочу просто запаять на плату по известному футпринту. Почему производители 99% всех микросхем сами гнут выводы и делают стандартизованные корпуса - LQFP, BGA, QFN и т.д.? Ведь, когда мне приходит микроконтроллер, я просто беру и запаиваю его сразу на плату, не гну ничего в нем, иначе все это бесполезная трата времени. Напротив, я достаю из ленты или паллеты корпус, например, LQFP, и паяю его. Выводы уже формованы и обрезаны. Вот мне и интересно, чем же таким отличаются эти керамические версии, что их самому допиливать нужно? Ведь это как купить автомобиль у дилера, а колеса отдельно купить - а то с завода машина на кирпичах стоит.
  13. Ну смотрите: в любом случае взяли микросхему, погнули ей выводы (сами), припаяли. Почему погнуть выводы не может сам производитель? Ведь мы не покупаем же STM32, например, и не начинаем гнуть ей выводы? Она в стандартном LQFP-корпусе, и ничего докусывать/догибать не нужно. К чему эти лишние телодвижения производитель перекидывает на конечного потребителя?
  14. Ну ок, не, я просто не понимаю, для чего они так делают?