На Хабре уже было много статей, в которых описывалось применение FreeRTOS или портирование на широко распространенные архитектуры процессоров. В этой статье я хочу поделиться опытом портирования FreeRTOS на российскую архитектуру «Мультиклет» и показать как справляется процессор Multiclet P1 с несколькими параллельными задачами. В качестве примера будет создан GPS трекер с возможностью записи координат на SD, оболочкой через UART, небольшим текстовым редактором и возможностью работы с файловой системой FAT32.
Шаг 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 способны выполнять программы широко распространенные на других архитектурах, а с учётом низкого энергопотребления и аппаратного распараллеливания ещё и гораздо эффективнее.
Автор: aaefimov