Запуск FreeRTOS на MIK32

Материал из MIK32 микроконтроллер
(перенаправлено с «Портирование FreeRTOS на MIK32»)

Введение

Большинство современных приложений на микроконтроллерах с 32-битной архитектурой требуют применения операционных систем реального времени (ОСРВ). Таких систем существует довольно много - ombOC, microC/OC-II, ThreadX, chibiOS, openRTOS и многие другие, но, наиболее получивших распространение стала система - FreeRTOS.

FreeRTOS - это бесплатное ответвление проекта openRTOS. Дополнительную прелесть этой системе придает легкая интеграция TCP/IP стека lwIP, так же проект с открытым исходным кодом.

В данной статье будет рассмотрено портирование этого проекта под платформу MIK32. Здесь не будет обучения работе с этой ОСРВ, однако таких материалов в интернете довольно много, поэтому здесь будет информация только про то, что нужно изменить в порте под RISC-V из стандартной поставки с сайта freertos.org.

Первые шаги

Загрузка файлов проекта

Есть два пути, по которым можно двигаться, один из них скорее более длительный и трудоемкий - взять за основу один из официальных портов RISC_V с сайта freertos.org и модернизировать его.

Freertos 1.png

Или же взять за основу порт от компании syntacore, который будет по максимуму приближен к нашему микроконтроллеру, так как он основан на ядре SCR1 разработки этой компании. И этим путем мы и пойдем. Исходный порт находится в SDK по ссылке https://syntacore.com/page/products/sw-tools , имя файла 202212-sp1-sc-dt-win.zip.

Freertos 1a.png

И мы пойдем именно этим путем!

Краткое пояснение - что-где

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

Freertos 2a.png

В подпапке include - все необходимые хидеры.

Папка portable находятся файлы, которые портируют систему на разные архитектуры. Здесь нас будет интересовать именно подпапка \GCC\RISC-V

Freertos 3.png

Рабочая среда Eclipse

Будем работать в среде Eclipse. Скачать уже сконфигурированную оболочку можно в разделе Быстрый старт в Eclipse IDE. После установки среды в рабочей папке можно найти проект-шаблон:

Freertos 8.png


Копируем содержимое этой директории во вновь созданную папку для нашего будущего проекта!

Далее, уже в Eclipse мы втягиваем проект в рабочий worspace, в меню выбираем "File->Import"

Freertos 5.png

и выбираем место, где мы ранее сохранили шаблон-проект

Freertos 10.png

остается нажать кнопку Finish. После этого проект должен вполне успешно собираться (Ctrl+B) и работать в микроконтроллере . Можно проверить это при желании.

Модификация проекта

Копирование файлов ОСРВ в проект

На данном этапе существует вариативность действий, но предлагается оставить структуру наиболее близкой к той, которая наблюдается в репозитории syntacore/freertos. Создадим папку FreeRTOS в директории src. Сначала копируем папку с хидерами и Си-файлы движка опtрационной системы в созданную src\FreeRTOS.

Freertos 11.png

В этом месте создадим папку portable и скопируем в нее только необходимое, это файлы репозитория которые находятся в \portable\GCC\RISC-V\, далее нужно скопировать папку portable\Common (работа с MPU при наличии такового), а так же MemMang (модели памяти). На данном этапе проект должен выглядеть следующим образом:

Freertos 12.png

Далее, так как мы должны выбрать только одну модель памяти из 5 вариантов, то удалим все файлы кроме heap_1.c - это самая базовая опция, без возможности очистки и удаления, более подробно про модели памяти можно узнать на сайте freertos.org.

Freertos 13.png

На этом структура проекта в части основных исходных кодов FreerRTOS сформирована.

Файл ctr0.s

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

Как было в случае копирования шаблона:

Freertos 14.png

Поменял путь к папке, чтобы она адресовала именно в директорию с проектом:

Freertos 15.png

После чего открываем этот файл и редактируем его. Около 90 строки находим код:

.org 0xC0
trap_entry:
    j raw_trap_handler

И меняем его на

//.org 0xC0
trap_entry:
    //j raw_trap_handler
    j freertos_risc_v_trap_handler

Конфигурирование порта

Далее, нужно добавить несколько пользовательских конфигурационных файлов в корневую директорию \src. Для этого нужно скопировать туда FreeRTOSConfig.h и scr_sys.c, найдя их в репозитории \202212-sp1-sc-dt-win.zip/\sc-dt_2022.12-sp1\workspace\freertos\Demo\RISC-V_Syntacore_SCRx_GCC\. Первый отвечает за конфигурацию опций операционной системы, второй - за её связь с аппаратной частью ядра процессора (настройка системного таймера ядра и его периодическое прерывание).

В файле FreeRTOSConfig.h следует отключить часть модулей, чтобы объем прошивки позволил поместиться в 8кБ.

  • Отключаем программные таймеры
#define configUSE_TIMERS 0 
....
#define INCLUDE_xTimerPendFunctionCall 0
  • Комментируем подключение файла arch.h
//#include "arch.h"
  • Меняем системную частоту на 32МГц
#define configCPU_CLOCK_HZ ( 32000000 )
  • Устанавливаем размер памяти для ОСРВ в 3 кБ. Этого не много, но для демонстрации более чем достаточно.
#define configTOTAL_HEAP_SIZE ( ( size_t ) 3*1024 )

На этом редактирование этого файла закончена. Следующий файл на редактирование - scr_sys.c. Комментируем эту строку, так как у нас иначе прописан системный таймер:

//#include "drivers/rtc.h"

И добавляем эти хидеры:

#include <scr1_csr_encoding.h>
#include "riscv_csr_encoding.h"
#include "scr1_timer.h"
#include <mcu32_memory_map.h>

Удаляем или комментируем эти строки:

//static const unsigned long rtc_ticks_per_timer_shot = PLF_HZ / configTICK_RATE_HZ;
//static sys_tick_t next_rtc_timer_shot = 0;

Далее следует изменить инициализацию системного таймера на ядре и его обработчик на следующие (старое закомментировано):

void vPortSetupTimerInterrupt( void )
{
//#if 0
//    rtc_setcmp_offset(rtc_us2ticks(1000000 / configTICK_RATE_HZ));
//#else
//    sys_tick_t t = rtc_now() + rtc_ticks_per_timer_shot;
//    rtc_setcmp(t);
//    next_rtc_timer_shot = t + rtc_ticks_per_timer_shot;
//#endif
//    /* enable timer interrupts */
//    rtc_interrupt_enable();
	SCR1_TIMER->TIMER_DIV = 320-1;//32000-1;//1kHz
	*(unsigned long long *) &SCR1_TIMER->MTIMECMP = 10;
	*(unsigned long long *) &SCR1_TIMER->MTIME = 0;

	SCR1_TIMER->TIMER_CTRL |= SCR1_TIMER_ENABLE_M;

	set_csr(mie, MIE_MTIE);
}

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

void ext_trap_handler(void);

void scr_systick_handler(void)
{
//#if 0
//    rtc_setcmp_offset(rtc_us2ticks(1000000 / configTICK_RATE_HZ));
//#else
//    sys_tick_t t = next_rtc_timer_shot;
//    rtc_setcmp(t);
//    next_rtc_timer_shot = t + rtc_ticks_per_timer_shot;
//#endif

	unsigned long mcause = read_csr(mcause);
	if ( (mcause & 0xF) == 7 && (mcause & (1<<31)) )
	{
		*(unsigned long long *) &SCR1_TIMER->MTIMECMP = *(unsigned long long *) &SCR1_TIMER->MTIME + 100;

		if (xTaskIncrementTick() != pdFALSE)
			vTaskSwitchContext();
	}
	else
	{
		ext_trap_handler();
	}
}

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

Так же ради экономии места следует избавиться от всех использований printf в этом файле (закомментировать их).

Настройки среды

Для успешной работы компилятора нужно прописать пути. Открываем свойства проекта и заходим в опции компилятора. И вносим директории, чтобы выглядело так:

Freertos 16.png

Так же настраиваем пути для ассемблера:

Freertos 18.png


И макрос для определения функции обработчика прерываний системного таймера (оно же общее прерывание)

Freertos 17.png

После этих правок проект должен собираться без ошибок, но это еще не всё.

Настройка линкера

По умолчанию в проекте есть подключенный скрипт линкера. Но он опять же адресуется в папку shared.

Freertos 19.png

Поэтому рекомендуется создать папку в своем проекте, скопировать туда нужный скрипт (мы будем использовать скрипт для загрузки прошивки в ОЗУ)

Freertos 20.png

Далее добавляем библиотеки c и gcc в опции линкера для корректной работы таких функций как memcpy и ряда системных утилит:

Freertos 21.png

Оптимизация на размер

Так как памяти мы выделили на прошивку всего 8кБ, то следует активировать оптимизацию кода по размеру, выставив такие опции:

Freertos 22.png

Добавление потоков и запуск ОСРВ

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

#include <mcu32_memory_map.h>
#include <pad_config.h>
#include <gpio.h>
#include <power_manager.h>

#include <gpio_irq.h>
#include <epic.h>
#include <scr1_csr_encoding.h>
#include "riscv_csr_encoding.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"


SemaphoreHandle_t xSemaphore;

void ext_trap_handler(void)
{
	GPIO_IRQ->CLEAR = 0xFFFF;
	EPIC->CLEAR = 0xFFFF;
	xSemaphoreGiveFromISR(xSemaphore, NULL);
}

static void test_task1(void *param)
{
    while (1) {
    	vTaskDelay(500);
    }
}

static void test_task2(void *param)
{
    while(1)
    {
    	if ( xSemaphore && xSemaphoreTake(xSemaphore, 100) == pdTRUE )
    	{
    		GPIO_2->OUTPUT |= (0b1)<<(7);
    		vTaskDelay(50);
    		GPIO_2->OUTPUT &= ~((0b1)<<(7));
    	}
	}
}

void main() {
	PM->CLK_APB_P_SET = PM_CLOCK_GPIO_0_M
			| PM_CLOCK_GPIO_1_M
			| PM_CLOCK_GPIO_2_M
			| PM_CLOCK_GPIO_IRQ_M;
	PM->CLK_APB_M_SET = PM_CLOCK_PAD_CONFIG_M
			| PM_CLOCK_WU_M
			| PM_CLOCK_PM_M
			| PM_CLOCK_EPIC_M;
	for (volatile int i = 0; i < 10; i++)
		;

	// LED init
	PAD_CONFIG->PORT_2_CFG |= (0b01)<<(7*2);
	GPIO_2->DIRECTION_OUT =  1<<(7);
	// led on shortly
	GPIO_2->OUTPUT |= (0b1)<<(7);
	for (long int i = 0; i < 3*100000l; i++)
		__asm volatile( "ADDI x0, x1, 0");  // это инструкция-замена NOP
	GPIO_2->OUTPUT &= ~((0b1)<<(7));
	for (long int i = 0; i < 3*100000l; i++)
		__asm volatile( "ADDI x0, x1, 0");  // это инструкция-замена NOP

	write_csr(mtvec, 0x02000000);           // конфигурация адреса вектора прерывания

	// extint on user key setup begin
	PAD_CONFIG->PORT_2_CFG |= (0b01) << (6*2);
	GPIO_2->DIRECTION_IN =  1<<(6);

	// GPIO_IRQ->LEVEL_SET = 1;
	GPIO_IRQ->EDGE = 1 << 2;
	GPIO_IRQ->LEVEL_SET = 1 << 2;
	// GPIO_IRQ->ANYEDGE_SET = 1;
	GPIO_IRQ->CFG = 9 << (2*4);
	GPIO_IRQ->ENABLE_SET = 1 << 2;

	EPIC->MASK_LEVEL_SET = 1 << EPIC_GPIO_IRQ_INDEX;
	// extint on user key setup

    /* Create work threads */
    xTaskCreate(test_task1,
                "Task1",
                128,
                ( void * ) 1,
                tskIDLE_PRIORITY + 1 ,
                NULL );
    xTaskCreate(test_task2,
                "Task2",
                128,
                ( void * ) 1,
                tskIDLE_PRIORITY + 1 ,
                NULL );
	xSemaphore = xSemaphoreCreateBinary();

	vTaskStartScheduler();
}

Так же рекомендуется всегда явно прописывать адрес вектора прерывания.

write_csr(mtvec, 0x02000000);

Выводы

Образ примера получился размером 5.5кБ и работает он из ОЗУ (для памяти программ выделено 8кБ, для памяти данных тоже 8кб). И его можно с тем же успехом запустить из встроенной энергонезависимой памяти, для этого следует откорректировать расположение памяти программ в файле .ld. И само собой, основная Модель памяти для ОСРВ будет вариант с внешней QSPI флэш.

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