На Хабре уже было много статей, в которых описывалось применение FreeRTOS или портирование на широко распространенные архитектуры процессоров. В этой статье я хочу поделиться опытом портирования FreeRTOS на российскую архитектуру «Мультиклет» и показать как справляется процессор Multiclet P1 с несколькими параллельными задачами. В качестве примера будет создан GPS трекер с возможностью записи координат на SD, оболочкой через UART, небольшим текстовым редактором и возможностью работы с файловой системой FAT32.
В качестве примера был использован порт FreeRTOS для ATMega323, т. к. с архитектурой данного процессора я был давно знаком. После изучения файлов стало понятно, что в лоб эту задачу не решить, т. к. для FreeRTOS рекомендуется использовать набор компиляторов GCC (для конкретной архитектуры) с его __attribute__ и asm вставками, а в текущем компиляторе LCC от Multiclet данная возможность отсутствует. Это не стало большой проблемой, единственный атрибут, который был использован в порте для ATMega323, это __attribute__ ((naked)), который говорит компилятору не создавать пролог и эпилог функции. Этот атрибут приписан к функции, которая отвечает за смену контекста задачи и уход на выполнение новой, поэтому важно, чтобы при её вызове не создавался пролог и эпилог (иначе стек\память быстро закончится). Данную функцию и все функции с asm вставками пришлось писать на ассемблере Multiclet.
Следующая проблема, с которой я столкнулся, портируя ОС, это стек. Дело в том, что в процессорах Multiclet нет команд push и pop из-за особенностей архитектуры. Архитектура порта получилась, возможно, слегка мудрёной, но на данный момент я не вижу более
элегантного решения. Идея заключается в следующем: у каждой задачи в ОС своя область памяти, в которой располагаются значения её регистров и стек вызовов, и имеется общий стек ОС, в котором располагаются вызовы функций во время обработки прерываний. Подмена значений регистров #SP и #BP (указатели на базу и фрейм функции) осуществляется в первичном обработчике прерываний (файл crt0.s).
Имеются 2 задачи с одинаковым приоритетом. После того, как задачи созданы и добавлены ядром ОС в очередь, будет вызвана функция xPortStartScheduler(void), которая запустит системный таймер и вызовет функцию первичной смены контекста PRS(). В функции PRS() через указатель pxCurrentTCB (pxCurrentTCB — указатель ОС на контекст задачи) устанавливаются значение всех регистров, в том числе #SP и #BP (значение глобальных #SP и #BP сохраняются в переменных gSP и gBP соответственно), отвечающих за стек текущей задачи и переход на её выполнение.
В момент возникновения прерывания от системного таймера происходит передача управления на первичный обработчик прерываний.
В первичном обработчике прерываний (master.isr) заменяются указатели на стек и сохраняется адрес возврата из прерывания, затем вызывается функция смены контекста RTOS.
Во время смены контекста вызывается функция сохранения контекста текущей задачи, затем планировщик OC установит указатель pxCurrentTCB на задачу, которую необходимо восстановить, и вызывается функция восстановления контекста с передачей управления на эпилог первичного обработчика прерываний.
Так работает смена задач и стеков FreeRTOS Multiclet P1.
В качестве примера реального использования ОС на демонстрационной плате был собран небольшой макет GPS трекера.
Плата сверху это Pinboard II. Она была использована только в качестве разъёма для SD карты.
Основной задачей с наивысшим приоритетом был эмулятор терминала, реализованный через COM порт.
Параллельно выполняемые задачи: запись на SD карту, чтение SD карты, получение данных c GPS приёмника, запись и чтение координатного трека в\из файл(а).
Полученные данные не достоверны (т. к. GPS приёмник лежал на столе) и демонстрируют только работу с файловой системой. Поместив демонстрационную плату на окно получаем почти верные результаты.
Работа ОС с картой памяти осуществлялась при помощи библиотеки FatFS товарища Сhan'a [ elm-chan.org/fsw/ff/00index_e.html ].
В данной статье я хотел показать, что процессоры Multiclet способны выполнять программы широко распространенные на других архитектурах, а с учётом низкого энергопотребления и аппаратного распараллеливания ещё и гораздо эффективнее.
Шаг 1. Портирование.
В качестве примера был использован порт FreeRTOS для ATMega323, т. к. с архитектурой данного процессора я был давно знаком. После изучения файлов стало понятно, что в лоб эту задачу не решить, т. к. для FreeRTOS рекомендуется использовать набор компиляторов GCC (для конкретной архитектуры) с его __attribute__ и asm вставками, а в текущем компиляторе LCC от Multiclet данная возможность отсутствует. Это не стало большой проблемой, единственный атрибут, который был использован в порте для ATMega323, это __attribute__ ((naked)), который говорит компилятору не создавать пролог и эпилог функции. Этот атрибут приписан к функции, которая отвечает за смену контекста задачи и уход на выполнение новой, поэтому важно, чтобы при её вызове не создавался пролог и эпилог (иначе стек\память быстро закончится). Данную функцию и все функции с asm вставками пришлось писать на ассемблере Multiclet.
Следующая проблема, с которой я столкнулся, портируя ОС, это стек. Дело в том, что в процессорах Multiclet нет команд push и pop из-за особенностей архитектуры. Архитектура порта получилась, возможно, слегка мудрёной, но на данный момент я не вижу более
элегантного решения. Идея заключается в следующем: у каждой задачи в ОС своя область памяти, в которой располагаются значения её регистров и стек вызовов, и имеется общий стек ОС, в котором располагаются вызовы функций во время обработки прерываний. Подмена значений регистров #SP и #BP (указатели на базу и фрейм функции) осуществляется в первичном обработчике прерываний (файл crt0.s).
Пример смены задачи в момент прерывания.
Имеются 2 задачи с одинаковым приоритетом. После того, как задачи созданы и добавлены ядром ОС в очередь, будет вызвана функция xPortStartScheduler(void), которая запустит системный таймер и вызовет функцию первичной смены контекста PRS(). В функции PRS() через указатель pxCurrentTCB (pxCurrentTCB — указатель ОС на контекст задачи) устанавливаются значение всех регистров, в том числе #SP и #BP (значение глобальных #SP и #BP сохраняются в переменных gSP и gBP соответственно), отвечающих за стек текущей задачи и переход на её выполнение.
PRS:
jmp PRS_fin
PXCTCB:= rdl pxCurrentTCB
pxTopOfStack:= rdl @PXCTCB
setl #DI, @pxTopOfStack
complete
PRS_fin:
reg7 := rdl #DI, pStack
reg6 := rdl #DI, pStack + 4
reg5 := rdl #DI, pStack + 8
reg4 := rdl #DI, pStack + 12
reg3 := rdl #DI, pStack + 16
reg2 := rdl #DI, pStack + 20
reg1 := rdl #DI, pStack + 24
reg0:= rdl #DI, pStack + 28
pvp := rdl #DI, pStack + 32
pxCode := rdl #DI, pStack + 36
taskSP := rdl #DI, pStack + 40
taskBP := rdl #DI, pStack + 44
saveSP := getl #SP
saveBP := getl #BP
setl #R7,@reg7
setl #R6,@reg6
setl #R5,@reg5
setl #R4,@reg4
setl #R3,@reg3
setl #R2,@reg2
setl #R1,@reg1
setl #R0,@reg0
wrl @saveSP, gSP
wrl @saveBP, gBP
setl #SP, @taskSP
setl #BP, @taskBP
jmp @pxCode
complete
В момент возникновения прерывания от системного таймера происходит передача управления на первичный обработчик прерываний.
master.isr:
jmp mi.hendler
saveSP := getl #SP
saveBP := getl #BP
loadSP := rdl gSP
loadBP := rdl gBP
reta := getl #IRETADDR
wrl @saveSP, iSP </code>
wrl @saveBP, iBP
setl #SP, @loadSP
setl #BP, @loadBP
wrl @reta, master.isr.retaddr
complete
В первичном обработчике прерываний (master.isr) заменяются указатели на стек и сохраняется адрес возврата из прерывания, затем вызывается функция смены контекста RTOS.
mi.hendler:
getl #SP
getl mi.stop
getl #INTNUMR
getl irq.desc.tbl
mull @2, sizeof.I</code>
DT.item
addl @1, @2
rdl @1
jmp @1
subl @8, sizeof.ptr
wrl @8, @1
setl #SP, @2
complete
Во время смены контекста вызывается функция сохранения контекста текущей задачи, затем планировщик OC установит указатель pxCurrentTCB на задачу, которую необходимо восстановить, и вызывается функция восстановления контекста с передачей управления на эпилог первичного обработчика прерываний.
mi.stop:
jmp mi.PF
getl #SP
addl @1, sizeof.ptr
setl #SP, @1
complete
mi.PF:
rdl master.isr.retaddr
jmp @1
getq #PSW
or @1, PSW.ONIRQS
setq #PSW, @1
saveSP := getl #SP
saveBP := getl #BP
loadSP := rdl iSP
loadBP := rdl iBP
wrl @saveSP, gSP
wrl @saveBP, gBP
setl #SP, @loadSP
setl #BP, @loadBP
complete
Так работает смена задач и стеков FreeRTOS Multiclet P1.
Шаг 2. Пример использования.
В качестве примера реального использования ОС на демонстрационной плате был собран небольшой макет GPS трекера.
Плата сверху это Pinboard II. Она была использована только в качестве разъёма для SD карты.
Основной задачей с наивысшим приоритетом был эмулятор терминала, реализованный через COM порт.
Параллельно выполняемые задачи: запись на SD карту, чтение SD карты, получение данных c GPS приёмника, запись и чтение координатного трека в\из файл(а).
Полученные данные не достоверны (т. к. GPS приёмник лежал на столе) и демонстрируют только работу с файловой системой. Поместив демонстрационную плату на окно получаем почти верные результаты.
Работа ОС с картой памяти осуществлялась при помощи библиотеки FatFS товарища Сhan'a [ elm-chan.org/fsw/ff/00index_e.html ].
Заключение
В данной статье я хотел показать, что процессоры Multiclet способны выполнять программы широко распространенные на других архитектурах, а с учётом низкого энергопотребления и аппаратного распараллеливания ещё и гораздо эффективнее.