Запуск FreeRTOS на MIK32

Материал из 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)
{
	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");
	GPIO_2->OUTPUT &= ~((0b1)<<(7));
	for (long int i = 0; i < 3*100000l; i++)
		__asm volatile( "ADDI x0, x1, 0");

	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 end

    /* 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);