Подписаться на получение новых статей на почту:

Интегрируем 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

Скачали. Следующий наш шаг – распаковываем библиотеку. Как Вы помните мы работаем c 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. На этой ноте сегодня и закончим. Всем пока.

Используемая литература:
1. https://freertos.org
2. http://microsin.net/programming/arm/freertos-part1.html

Просмотрено 291 раз.

Я на Google+

Интегрируем FreeRTOS in STM32 на CooCox CoIDE. Шаг №96: Один комментарий

  1. Очень полезный материал для обучения и практического применения. Спасибо.

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

Ваш 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