Підписатися на отримання нових постів на пошту:

Інтегруємо FreeRTOS in STM32 на CooCox CoIDE. Крок №96

Всім привіт. Розглянувши в минулій статті приклади практичності використання мови Сі, де одним з них є написання операційних систем, в цій статті ми ближче познайомимося з однією з них — FreeRTOS. Коротко опишемо її, розглянемо архітектуру, встановимо на налагоджувальну плату stm32discovery на базі мікроконтролера stm32f303vc на ядрі Cortex M4.Повторимо досвід статті №89 — передаючи дані з stm in android, але вже з вбудованою OC.

Навіщо її використовувати? Бува так що что проект розростається і настає момент коли закінчений пристрій має виконувати безліч різноманітних функцій, плюс мати можливість в майбутньому додавати нові завдання. Тут на допомогу приходить RTOS (операційна система реального часу, ОСРЧ), яка має блок управління пам'яттю (MMU) і механізми реалізації багатозадачності, відносно маленький (4-9 кілобайт в залежності від платформи і налаштувань ядра) і простий диспетчер (scheduler), який вміє ставити різні пріоритети процесів, що витісняє і не багатозадачність, семафори і черги.

Для початку роботи на сайті https://www.freertos.org можна завантажити останню версію, на сьогодні це FreeRTOS v10.2.0  — to have MIT licensed, and including RISC-V and ARMv8-M (Cortex-M33) demos от February 25 2019 року. Загалом доповнення і зміни релізів читаємо там же. Портована на більш ніж 35 мікропроцесорних систем. З них нас цікавить ARM Cortex-M4 для нашої налагоджувальної плати і Atmel AVR (в майбутньому також встановимо на ці «каміння»).

скрин-free_1

Завантажили. Наступний наш крок — розпаковуємо бібліотеку. Як Ви пам'ятаєте ми працюємо з CooCox, хоч це і не має сенса — бо головне закинути необхідні файли, а з яким редактором і компілятором працювати вже Ваш вибір. Перетягуємо бібліотеки в наш CoIDE – ми вже навчилися підключати бібліотеки CMSIS. Для роботи з ОС нам необхідно перетягнути файли собі в проект з наступних директорій (нижче). Також нижче скрін використовуваних файлів. Для зручності я створив окрему папку RTOS.
FreeRTOSv…/ FreeRTOS/ Source – всі файли з розширенням к.с. ми закидаємо собі в теку source, де:
queue.c — функції черг і мутексів;
tasks.c — функції роботи з завданнями;
timers.c —функції роботи з таймерами;
croutine.c — функції роботи з співпрограмами;
event_groups.c — функції роботи з прапорами;
list.c — тут все для налагодження;
stream_buffer.c – для передачі потоку байтів з підпрограми обробки переривань в задачу або з одного завдання в інше.
FreeRTOSv…/ FreeRTOS/ Source/ include — копіюємо всі файли в include.

Наступна директорія залежить від використовуваного компілятора і ядра — у мене GCC. Тут вказано для одного ядра дві папки. Справа в тому, що не всі Cortex-M4 мають модуль з плаваючою комою.У мене плата має підтримку DSP інструкцій з плаваючою точкою. Якщо у Вас немає, то необхідно або вручну повідомити GCC, що апаратний модуль з плаваючою комою використовується за допомогою окремої опції командного рядка, або останній варіант директорії. Тут я звернув на цей момент увагу, тому що можливі помилки в процесі компіляції. FreeRTOSv…/ FreeRTOS/ Source/ portable/GCC/ ARM_CM4_MPU or / и ARM_CM4F — тут лежить два файли де:
port.c — платформозалежні параметри, для кожного МК — свої.
portmacro.h — налаштування платформи. Теж індивідуальний для кожного типу МК.

FreeRTOSv…/ FreeRTOS/ Source/ portable/ MemMang — Тут вибираємо файл купи (файл управління пам'яттю) і також собі копіюємо, де:
heap_1 — найпростіший, не дозволяє звільняти пам'ять.
heap_2 — дозволяє звільнення пам'яті, але не об'єднує суміжні вільні блоки. Динамічна фрагментована пам'ять. Тобто пам'ять динамічно виділяється, звільняється, але при цьому ділянки пам'яті виходять фрагментованими, з дірками на місці звільненої пам'яті.
heap_3 — просто переносить стандартні malloc () і free () для безпеки потоків.
heap_4 — об'єднує суміжні вільні блоки, щоб уникнути фрагментації. Включає абсолютну можливість розміщення адреси.
heap_5 — згідно heap_4, з можливістю розподіляти купу по декільком несуміжних областей пам'яті.

Яку пам'ять вибрати, залежить від Вас. Я вибрав 2-у. Більш детально в офіційних доках — https://www.freertos.org/a00111.html. либо http://easyelectronics.ru/img/ARM_kurs/FreeRTOS/Kurniz.pdf. я вважаю, що для потреби завжди можна змінити управління пам'яттю і перекомпілювати проект.

І останнє це FreeRTOSConfig.h — налаштування ОС.Цей файл можна взяти або в директорії прикладів, якщо там присутній наш камінь. Якщо немає то «палити» офіційні доки. FreeRTOSv…/ FreeRTOS/Demo… or https://www.freertos.org/a00110.html. Мого на жаль не було, та й сидіти над ними особливо не хотілося. Нарив на гітхабе для stm32: https://github.com/gorthrec/stm32f3/blob/master/Project/FreeRTOSConfig.h . Також як варіант можна згенерувати файл в кубі і видерти його звідти. Відкриваємо STM32Cube, вибираємо плату, включаємо галочку навпроти FreeRTOS і збираємо проект як зазвичай. Ми з Вами з «кубом» вже познайомилися в статті № 78, але поки від бібліотек на HAL відмовилися…

На даний час не буду описувати налаштування файлу. Залишимо це на потім, коли наш проект трохи розростеться і файл під коригується.

FreeRtosAndCoocox_2

Перед тим як компілювати необхідно визначити 4-ри функції в main.c, без яких проект не збереться. Нехай це навіть будуть функції-заглушки. Щоб виключити наступні помилки:
[cc]C:\CocPrj\SmartHouse\RTOS\portable\MemMang/heap_2.c:202: undefined reference to `vApplicationMallocFailedHook'
[cc] collect2.exe: error: ld returned 1 exit status

void vApplicationIdleHook ( void ){
}
void vApplicationMallocFailedHook ( void ){
for ( ;; );
}
void vApplicationStackOverflowHook ( xTaskHandle pxTask, signed char *pcTaskName ){
( void ) pcTaskName;
( void ) pxTask;
for ( ;; );
}
void vApplicationTickHook ( void ){
}

А також підключити бібліотеки:
#include «FreeRTOSConfig.h»
#include «projdefs.h»
#include «portmacro.h»
#include «FreeRTOSConfig.h»
#include «FreeRTOS.h»
#include «croutine.h»
#include «task.h»
#include «queue.h»
Причому «portmacro.h» має бути нижче «FreeRTOSConfig.h» и «projdefs.h» , тому що компілятор видає помилку:
[cc] C:\CocPrj\SmartHouse\RTOS\portable\GCC\ARM_CM4F/portmacro.h:219:58: error: 'configMAX_SYSCALL_INTERRUPT_PRIORITY' undeclared (first use in this function).

Отже компілюємо проект з даними файлами. У мене відразу перша бага: C:\CocPrj\SmartHouse\RTOS\portable\GCC\ARM_CM4_MPU/portmacro.h:54:23: error: missing binary operator before token «long»
В файлі FreeRTOSConfig.h  #define configMAX_PRIORITIES ((unsigned portBASE_TYPE) 5) необхідно прибрати приведення до типу. Просто перевизначити як 5.

При наступний спробі компіляції з'явилася наступна проблема:
C:\Users\B4D2~1\AppData\Local\Temp\ccVy8f9Z.s: Assembler messages:
[cc] C:\Users\B4D2~1\AppData\Local\Temp\ccVy8f9Z.s:908: Error: selected processor does not support `vstmdbeq r0!,{s16-s31}' in Thumb mode
[cc] C:\Users\B4D2~1\AppData\Local\Temp\ccVy8f9Z.s:910: Error: instruction not allowed in IT block — `mrs r1,control'
Тут може бути що GCC не очікує присутності інструкцій з плаваючою комою. Дана проблема перемагається включенням в опціях компілятора hard FPU (або soft FPU).

настройка-компилятора_3

компиляция-ртос_4 Все скомпілювали проект. Тепер у нас вбудована ОС. Перед тим як повторити досвід з передачі даних. Давайте коротко познайомимося з архітектурою і як з нею працювати. Розглянемо «сьогодні» тільки основи, то що використовуємо в проекті. У міру розростання проги постараємося розглянути всі можливості системи.
Дана ОС керується диспетчером, який викликається перериванням таймера — квантом. Тобто виходить що для роботи ОС ми виділяємо з наших ресурсів один таймер, в ARM Cortex-M4 його вішають на SysTick таймер. Таймер генерує системні тики, при якому запускається диспетчер. Що робить диспетчер — викликає функції, кожна з яких має свій пріоритет, який в свою чергу задається при створенні і його можна на льоту вручну міняти через API функції RTOS. Перше що необхідно — реалізувати завдання у вигляді функції.
Конструкція задачі
void MyTask1 ( void *pvParameters ){...Тело...}
Таких завдань створюємо стільки скільки нам буде потрібно.
где pvParameters — параметр, що має тип покажчика на void (т. е. void*). Значення, вказане в pvParameters, буде передано в задачу. Ми його поки не використовуємо. Що я роблю далі. Я переміщаю циклічний код який у мене був в main () в функцію, де відбувається відправка даних модулю і миготіння світлодіодом на платі. Мені поки необхідна тільки одна задача.

void vLedBlinkAndSend (void *pvParameters){
  while (1){
    UART4_Send_String ("AT+CIPSEND=0,5\r\n");
    GPIOE->ODR|=0x100;
    uart_wite_for("OK");
    delay ();
    GPIOE->ODR&=~(0x100);
    UART4_Send_String("stm32\r\n");
    delay ();
  }
  vTaskDelete( NULL ); //Дескриптор завдання, яка буде видалена. Передача NULL призведе до видалення викликаного завдання.
}

Забігаючи на перед вона у нас буде крутиться постійно. Щоб її видалити в разі непотрібності можна використовувати API функцію vTaskDelete ( NULL), яка завершує поточну задачу з якого вона була викликана, наприклад по будь-якій умові. При цьому вона буде вивантажена з пам'яті, звільнить оперативку, але її поточний стан і локальні змінні будуть втрачені. Однак нічого не заважає запустити її знову, спочатку. В даному коді вона у мене ніколи не виконається.

Тепер нам необхідно створити завдання. Познайомимося з ще однією API функцією управління завданнями — xTaskCreate — створює нову задачу, виділяючи під неї пам'ять і передає її диспетчеру, де вказується ім'я завдання, її пріоритет, ім'я для налагодження і кількість пам'яті що виділяється під неї. В результаті під неї виділяється шматок пам'яті, заводиться свій стек і вона запускається в вільне життя. Про стани завданнь поговоримо коли підвернеться відповідний приклад. Прототип функції
portBASE_TYPE xTaskCreate (
pdTASK_CODE pvTaskCode, /*Ім'я функції.*/
const signed portCHAR * const pcName, /*Описова назва для завдання. Використовується тільки для налагодження.*/
unsigned portSHORT usStackDepth, /*Кількість слів, які можна зберегти в стеці, а не кількість байт. Наприклад, якщо стек має ширину 32 біта, і передане значення usStackDepth дорівнює 100, то під стек буде виділено 400 байт (100 * 4 байт).*/
void *pvParameters, /*Значення pvParameters буде передано в задачу.*/
unsigned portBASE_TYPE uxPriority, /*Задає пріоритет від 0 (tskIDLE_PRIORITY) мінімальний пріоритет до (configMAX_PRIORITIES — 1) максимальний пріоритет.*/
xTaskHandle *pxCreatedTask ); /*Використовується для передачі назовні хендла створеної завдання.*/
Функція може повертати одне з двох значень:
1. pdTRUE показує, що завдання успішно створено.
2. errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY показує, що завдання не створено, так як в купі (heap) недостатньо вільної пам'яті для FreeRTOS, щоб вона могла виділити місце для структур даних завдання і стека. 

Нижче наш шматочок коду:

int main (void) {
   UART_Init(); /*Ініціалізуємо інтерфейс.*/
   Init_WIFI(); /*Модуль Wi-Fi.*/
   xTaskCreate(vLedBlinkAndSend,"vLedBlinkAndSend", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
    /*где NULL,- не використовуємо параметр задачі.
             1,- задача буде запущена з пріоритетом 1.
         NULL);- не використовуємо хендл задачі.*/
    /*Запуск шедулера, після чого задача (задачі) запуститься на виконання.*/
   vTaskStartScheduler();
}//Кінецб функції main().

Функція main () ініціалізує інтерфейс, модуль и створює завдання перед запуском scheduler. Далі про все подбає диспетчер. Він перевіряє повернене значення з виклику xTaskCreate (), щоб упевнитися, що завдання було успішно створено. Якщо все добре, то управління в main () ніколи не дійде до кінця, і диспетчер буде крутити завдання і подбає про те, щоб у завданні все зберігалося і запам'ятовувалося: поточний стан, стек, змінні, регістри і з точки зору завдання нічого не відбувалося. Якщо main () все ж таки дійшов до кінця, то це може означати, що не вистачає пам'яті купи (heap) для створення спеціального завдання очікування (idle task). На мал. нижче результат експерименту.

В send-stm-android-rtos_5 даній статті ми описали процес і черговість установки системи FreeRTOS на мікроконтролер, можливі помилки при компіляції проекту, їх усунення, розглянули основу API функцій, щоб почати просту задачу в системі. Повторили досвід передачі даних з МК в Android. Функція крутиться, дані передаються. Наступним кроком ми спробуємо реалізувати різноманітні завдання в мікроконтролері на ОС у вигляді меню, паралельно розбираючи її нюанси, і спробуємо все це синхронізувати з інтерфейсом на Android. На цій ноті сьогодні і закінчимо. Всім до зустрічі.
Використовувана література:
1. https://freertos.org
2. http://microsin.net/programming/arm/freertos-part1.html

Переглянуто раз.

Я на Google+

Добавить комментарий

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting