Как стать автором
Обновить

Комментарии 35

Жду продолжения!
Будет. Еще 4 части, в конце «из чего-то там и палок» сделаем аппаратный сниффер UART :)
Вот мне всегда было интересно, зачем процессу с тупым дерганьем ножки аж ~200 байт оперативки?
Ну вообще-то ему столько не надо. Если именно просто дергать ножку, то вроде 20-25 байт хватает за глаза. Но простое дерганье ножки встречается очень редко :)
Спасибо за статью! Я думаю, было бы здорово, если бы Вы добавили кратко список из раздела «Обзор FreeRTOS» из статьи, ссылку на которую привел Disasm в первом комментарии.
Вообще, ОС для МК — неоднозначная штука. Например, если написать что-то вроде
static void thread1(const void * arg)
{
   while (1) {
     write_port(1, SET);
     write_port(1, RESET);
   }
}
static void thread2(const void * arg)
{
   while (1) {
     write_port(2, SET);
     write_port(2, RESET);
   }
}

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

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

У меня регулярно стоит задача показать «ардуинщикам» или «олдскульным» что под stm32 можно писать так же быстро, но возможностей гораздо больше и бояться не надо. Ибо ардуино, не смотря на всю красивость, очень сильно портит мозги навязываемым стилем программирования.

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

А участок, когда порты не будет переключаться, будет всегда и везде, ибо между вызовами write_port всегда будут другие вызовы (типа обновления счетчика времени работы, прерывания, регенерация памяти и так далее и тому подобное). Главное тут — понять, насколько важен этот участок времени и насколько он критичен. Но по моему опыту, еще не разу не было необходимости сделать одиночный импульс длительностью около нано- или пико-секунды (лень вспоминать величину сколько будет занимать 1 такт на 140МГц)
Одиночный импульс на 140 МГц это около 7 нс. Для сравнения — время распространения радиосигнала на 3 метра — 10 нс. Т.е. если вам задали сделать обработку в локаторе с разрешением по дальности 1.5 метра, то 10 нс для вас рабочий период. Другой пример, обработка временных интервалов низкочастотных процессов с высокой точностью. Скажем, есть процесс с максимальной частотой следования импульсов всего 10 кГц, но измерять время между импульсами необходимо с точностью 0.01% — получим тот рабочий период 10 нс. Примеры живые, это 2 реальных задачи из двух реальных приборов. Первый пришлось решать на рассыпухе 1554 серии, второй товарищ сейчас пытается допилить на отечественном аналоге Cortex M3 методом усреднения или еще чем-то подобным, но пока не слишком получается.
Когда в ТЗ стоит пунктик про отечественную элементную базу, любая задача внезапно на порядок усложняется :)
Про импульсы помнится еще несколько лет назад коллеги, используя Cyclone III, получали разрешение по времени в районе 1-2нс.
Отечественный ключ 590кн8, выпускающийся с начала 80-х, имеет время включения 3 нс. Импортные ключи примерно такой же стоимости сейчас имеют быстродействие вроде бы около 1 нс. STM интересна в плане что на них есть отечественные аналоги, Миландр делает. Сортех M3 у них уже идет серийно, М4 обещают скоро запустить. Отладочные платы у Миландра дорогие, за 30 тыр, дороже чем от STM на порядок и более.
А я вот замучался отлаживать трудноуловимый баг с потоками, но система ThreadX + arm926ejs (Cypress FX3 и их SDK). OpenOCD из коробки не поддерживает примитивы ThreadX для этого CPU, попытался добавить сам, посидел пару дней с дизассемблером, что бы понять как контекст сохраняется, вроде реализовал, треды отображаются, а вот стектрейсы какие-то эпические. Вот и не могу понять, где лыжи стоят. Если кому интересно, наработки: secure.cypress.com/?app=forum&id=167&rID=106353
Тут я извинюсь. По меркам хабра и программистов микроконтроллеров, я еще не настоящий сварщик :)
Потому что FreeRTOS не система реального времени и может позволить себе такие вольности.

Я дико извиняюсь, но FreeRTOS — это Free Real Time Operating System. И «вольности» она себе позволяет исключительно с вашего допущения.

От себя добавлю, что FreeRTOS достаточно удобная штука, но некоторые вещи там сделаны несколько странно.
И соглашение об именах лично мне не нравится, бесполезная венгерская нотация только мешает автоподстановке. А уж про имена макросов в стиле configCONFIG_SOMETHING режут глаз.

Но плюшек много.
Если подходить с такой точки зрения, то FreeRTOS да, имеет право на RT в названии. Но для «настоящей» RT ей не хватает предсказуемости. В том примере время переключение на задачу «плавает» от загрузки. Это объясняется бОльшими объемами для сохранения контекстов и прочего (мне реально не охота лезть туда).

И про странности согласен :)
Я может быть не совсем уловил, но вы изначально запускали OS в кооперативном режиме, круговом или вытесняющем?
В том примере, где время переключение начало плавать.
по умолчанию в FreeRTOS — вытесняющий.

Что бы сразу отсечь «так не бывает», могу рассказать, как быстро доказать «реалтаймовость FreeRTOS» на практике.

1. Ставим 1 тик на 1мс (ну это по умолчанию)
2. Берем STM32Lx и ставим ему минимальную тактовую частоту (хотя вроде 8МГц хватит уже)
3. Просим FreeRTOS дергать нас каждую миллисекунду.
4. Смотрим на осциллографе, как гуляют периоды.

Хинт: на такой частоте процессор успевает сделать примерно 80 тактов и время переключения задача-планировщик и обратно больше 1мс

Ну, эм, а чего вы хотели тогда, если время переключения больше 1 миллисекунды и тик каждую миллисекунду?
Ничего. Просто следовал анекдоту про русских мужиков и японскую бензопилу :)
Ы. Ладно.
Просто если я правильно понимаю, ни одна RTOS вам не обеспечит периодического процесса с периодом меньше, чем гарантированное время переключения контекста. Так что мне ваше доказательство показалось очень странным.
Ну да :) Но (ИМХО) настоящая RTOS просто не должна допускать такое. То есть в данном случае я должен был получить отлуп в создании задачи/таймера/ещечего и свалиться в обработку ошибок
Если вы знаете такую RTOS, то, пожалуйста, расскажите :)
По этим меркам я «ненастоящий сварщик» :) Но вроде по слухам какую-то из подверсий QNX можно заставить делать такое.

Но нам пока хватает и такого поведения. Хвала ST — простой сменой чипа увеличивается память/скорость. Даже плату не надо переразводить :)
Порт FreeRTOS на STM32 работает с памятью не байтами, а 32-битными словами, см. тип portSTACK_TYPE файл portmacro.h для выбранной архитектуры, размер данного типа используется при выделении памяти под стек. Поэтому строчки
#define configMINIMAL_STACK_SIZE ((unsigned short)128)
#define configTOTAL_HEAP_SIZE ((size_t)3000)
Означают что на таск выделяется 128*4=512 байт, а на всех 3000*4=12000 байт.

Когда сначала просматривал статью, первой мыслью было «О ужас! В новых FreeRTOS зачем-то изменили API». А оказалось это ST опять выпендрились и зачем-то понаделали оберток для функций FreeRTOS. Лучше этим чудом не пользоваться, во избежании проблем в будущем, а задействовать нормальный, свежий релиз FreeRTOS.
Вот тут не соглашусь. Да, действительно
#define portLONG		long
#define portSHORT		short
#define portSTACK_TYPE	unsigned portLONG


Но в самом коде

typedef struct xTASK_PARAMTERS
{
	pdTASK_CODE pvTaskCode;
	const signed char * const pcName;
	unsigned short usStackDepth;
	void *pvParameters;
	unsigned portBASE_TYPE uxPriority;
	portSTACK_TYPE *puxStackBuffer;
	xMemoryRegion xRegions[ portNUM_CONFIGURABLE_REGIONS ];
} xTaskParameters;



И дальше с ним работа идет примерно так

pxNewTCB->pxStack = ( portSTACK_TYPE * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( portSTACK_TYPE ) ), puxStackBuffer ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */


То есть просто нет смысла просить стек в некратное sizeof( portSTACK_TYPE ) число байт.

А про выпендривание ST. Ну по крайней мере они честно предупреждают, что ломают (ака SPL > HAL), а не втихушку что-то делают.

Что касается более свежих версий FreeRTOS — согласен. Но STM32Cube (вернее его firmware) пока использует старые. А надо стартануть сразу и быстро, иначе разбегутся :)
Поясните, с чем вы не соглашаетесь? Сами же нашли строчку:
( portSTACK_TYPE * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( portSTACK_TYPE ) ), puxStackBuffer );

usStackDepth это параметр, который вы передаете в xTaskCreate, т.е. в данном случае 128
portSTACK_TYPE = unsigned long, а sizeof(unsigned long) = 4
128 * 4 =?

Про не кратность ничего не понял.
Ндас, признаю. Закопался в не ту сторону. Ибо в определении треда четко прописано, что

uint32_t               stacksize;    /* stack size requirements in bytes; 0 is default stack size */


Плюс я ориентировался на показания xPortGetFreeHeapSize(), а он сообщает именно байты, а не слова

/* A few bytes might be lost to byte aligning the heap start address. */
#define heapADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
...
#if portBYTE_ALIGNMENT == 4
	#define portBYTE_ALIGNMENT_MASK	( 0x0003 )
#endif
....
/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = ( ( size_t ) heapADJUSTED_HEAP_SIZE ) & ( ( size_t ) ~portBYTE_ALIGNMENT_MASK );

size_t xPortGetFreeHeapSize( void )
{
	return xFreeBytesRemaining;
} 


В общем, перепроверю и сделаю тесткейс, что бы других в заблуждение не вводить.
А я тоже немного наврал. configTOTAL_HEAP_SIZE задается в байтах. Т.к. например в heap_1.c имеем
unsigned char ucHeap[ configTOTAL_HEAP_SIZE ];

В мозгах как-то отложилось, что стек в лонгах.
В общем сие есть потенциальные грабли, про которые просто помнить надо.
(мрачно) тут этих грабель… :)
Интересно, если взять более-менее реальный проект, где есть сложные вычисления, есть ли оценки эффективности? Если поток1 работает непрерывно (не привлекая delay) 100мкс, поток2 тоже 100мкс, то как ОС распеределит время между ними? Они досчитают одновременно или по очереди? И сколько пройдет времени в сумме (т.е. какой оверхеод от вмешательства RTOS?)
Вот хоть убей не помню, где народ сравнивал оверхед от ОС. Но навскидку, по памяти — где-то 3-4% «просадка» по сравнению с «чистым» железом.

Что касается распределения времени, то если ничего не делать, то делится честно и поровну.
Не могли бы вы описать алгоритм как правильно общаться с периферией в потоках, где требуется отмерять точные промежутки времени? Например имеем цифровой датчик влажности, датчик давления и, пусть будет термометр с интерфейсом 1-ware. К примеру, на опрос каждого датчик у нас есть поток (это наверное не очень правильно и лучше опрос организовать таймерами с прерываниями, но пусть будет для нашего примера). Так вот, как правильно организовать общение с внешним миром, чтоб быть уверенным, что поток не прервется на середине процедуры чтения и данные не будут потеряны?
У любой RTOS есть макросы/команды в духе «не сметь прерывать»

Для FreeRTOS это taskENTER_CRITICAL и taskDISABLE_INTERRUPTS

www.freertos.org/a00020.html
А могли при создании таска указать больший размер, а не копипастить пример и потом править конфиг?
Когда писалась эта статья, кубик не умел в это.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Публикации

Истории