tn_sys_time_get()
и tn_sys_time_set()
. Спасибо Дмитрию Кривецкову.
try_lock_mutex()
и do_unlock_mutex()
. При использовании мютексов с протоколом увеличения приоритетов мог возникнуть exception (взятие значения по нулевому адресу). Спасибо Vanizmaport_defs.h
каждой архитектуры.Настоятельно рекомендую обновиться
PIC32
port.S
TN_DEBUG
) определяется во внешнем файле tnkernel_conf.h
- см. пункт "11. Файл конфигурации"
tn_task_exit()
вообще не очень популярный, то баг был замечен только сейчас
MAKE_ALIG()
, который используется для выделения пула блоков памяти фиксированного размера. Спасибо vitalka!
TN_TCB_S
на TN_TCB
tn_timer_task_func()
tn_timer_task_func()
возникало системное прерывание, в котором разблокировалась одна из задач (устанавливался ожидаемый семафор, и т.п.), то при выходе их этого прерывания запускалась разблокированная задача. Таким образом, в определенных ситуациях, было возможно непроизвольное увеличение таймаутов ожидающих событие задачtask_wait_complete()
__TNKERNEL_VERSION
__TNKERNEL_VERSION
, которое будет обозначать номер текущей версии (2.5)
TN_TIMEOUT
для параметров сервисов с таймаутами
xxx.lib
и включить в модули, использующие сервисы RTOS файл tnkernel.h
. Файл tnkernel_rev.h
должен находиться в одной папке с tnkernel.h
, другие заголовочные файлы не требуются
MODCON
и XBREV
не сохраняются и не обнуляются. При этом косвенная адресация по прежнему подчиняется правилам модульной или битреверсивной адресации, что однозначно приведет к краху системы. MODCON
и XBREV
. Соответственно изменены файлы сборки библиотеки. В архиве четыре варианта - PIC24 coff/elf и dsPIC coff/elf с оптимизацией Os. В проект нужно просто подключить соответствующую библиотеку. tn_task_exit()
TNKernel (http://www.tnkernel.com) - вытесняющая RTOS, изначально разработанная для микроконтроллеров с ядром ARM7, имеющая ряд достоинств по сравнению с аналогичными некоммерческими и коммерческими продуктами. TNKernel распространяется по FreeBSD-like лицензии и написана в основном на языке Си, что сделало возможным ее портирование как на 16-битную архитектуру Microchip PIC24/dsPIC (для компилятора Microchip C30) так и на 32-битные PIC32 (архитектура MIPS32 M4K, компилятор Microchip C32)
При выборе RTOS для применения в проектах с PIC24/dsPIC рассматривались различные варианты: uC/OS-II, как наиболее распространенная коммерческая RTOS для однокристалльных контроллеров, freeRTOS - широко известная и бесплатная RTOS. Рассматривались варианты портирования ThreadX (оригинального порта тогда еще не было) и других платных и бесплатных "легких" планировщиков. Операционные системы с кооперативным планировщиком (Salvo, jacOS) были откинуты сразу, так как даже первые линейки PIC24/dsPIC имели достаточный объем оперативной памяти для реализации вытеснения.
Основные критерии выбора были следующие:
TNKernel изначально удовлетворяла всем критериям, поэтому и была выбрана за основу, несмотря на то что оригинальная версия разработана для контроллеров с ядром ARM7.
Существует большое количество коммерческих и бесплатных "легких" RTOS для однокристальных контроллеров, исходные коды которых либо выложены в свободном доступе (freeRTOS), либо доступны только для ознакомительных целей (uC/OS-II), либо можно найти в файлообменных сетях (ThreadX).
TNKernel распространяется по лицензии, допускающей модификацию исходников и коммерческое использование при сохранении копирайта. Это стало одним из решающих аргументов.
Несмотря на то, что лицензия freeRTOS так же допускает изменение исходных кодов и коммерческое использование, freeRTOS не удовлетворяет многим другим критериям (с моей точки зрения).
Простое портирование или существование портов для других популярных архитектур - так же очень важный критерий, так как бессмыслено постоянно менять (а значит и осваивать) инструментарий при переходе с одного семейства на другое.
Под "простым" портированием я понимаю четкое разделение собственно ядра системы (которое должно быть написано на С/С++) и модулей, зависящих от архитектуры (переключение контекста, работа с прерываниями и т.п.). Большинство известных RTOS для однокристальных контроллеров имеют как большое количество портов, так и сформировавшуюся структуру, которая позволяет быстро порты добавлять.
На момент выбора системы порты для 16-битных контроллеров Microchip имели только freeRTOS и uC/OS-II. Однако, порт для uC/OS-II не работал (ошибка в ассемблерном модуле - первый релиз, спишем на это), а в порте freeRTOS был найден комментарий типа "в этом месте может зависнуть, почему - пока не понятно…". Все это ставило под сомнение использование этих систем (тем более, что uC/OS-II еще и коммерческая). Сейчас ситуация изменилась - выходят обновления портов, обновления самих систем. Поэтому выбор может быть не столь очевидным, и, возможно, даже не в пользу TNKernel (если вы, например, уже используете freeRTOS).
Понятный код - еще один момент на котором стоит остановиться. Несомненно, что каждый программист или коллектив имеет сложившийся стиль, поэтому трудно оценивать чужой код - понятный он или нет. Мне наиболее ближе TNKernel - по такому критерию это несомненный лидер.
В этом случае явным лидером является uC/OS-II, так как имеет огромное количество дополнительного софта (за дополнительные же деньги): файловую систему, стек tcp/ip, библиотеку обслуживания USB, GUI и т.д. freeRTOS может включать в себя порт открытого tcp/ip стека uIP и большое количество примеров, построенных на его основе.
Оригинальная версия TNKernel имеет библиотеку USB Bulk Firmware для контроллеров NXP LPC2100 и урезанный порт Nano-X GUI. Если первое не может использоваться совместно с 16-битными контроллерами Microchip в силу понятных причин, то второе вполне можно успешно применять в проектах на PIC24/dsPIC и PIC32. Недавно автором TNKernel был выпущен TCP/IP стек, который, возможно будет работать и на PIC32.
По этому критерию TNKernel вне конкуренции - uC/OS-II и freeRTOS требовали почти в два раза больше программной памяти и в полтора раза больше ОЗУ (не считая стеков задач). Возможно сейчас ситуация изменилась, но в начале это произвело впечатление.
Следует заметить, что и uC/OS-II и freeRTOS требуют компиляции в составе проекта, а линкер Microchip C30 пока не умеет игнорировать неиспользуемые секции кода, подключая на выход целиком весь объектный файл, даже если из него используется одна функция, лежащая в отдельной секции. Поэтому в проект с использованием freeRTOS и uC/OS-II будет включаться весь исполняемый код RTOS.
Конечно это не является недостатком вышеназванных RTOS - это проблема компилятора Microchip C30. Частично она решается заголовочными файлами конфигурации, в которых указано, какие сервисы и объекты будут использоваться. Тем не менее даже полный вариант TNKernel использует меньше программной памяти чем freeRTOS или uC/OS-II.
Компилятор Microchip C32 основан на новой версии GCC и умеет игнорировать неиспользуемые функции в объектном файле, однако для совместимости с портом для PIC24/dsPIC структура системы оставлена прежней.
Под временем реакции будем понимать время, необходимое для входа в задачу ожидающую событие с момента наступления этого события. Как правило это время измеряется следующим образом: в задаче вызывается сервис ожидания семафора, а семафор освобождается в обработчике внешнего прерывания. Временем между возникновением прерывания и выходом из сервиса ожидания и будет время реакции.
Оценить время реакции и другие "скоростные" параметры RTOS, можно используя набор тестов известного производителя Express Logic Corp. (автора ThreadX), который называется Thread-Metric Test Suite. Результаты тестов для наиболее известных RTOS для PIC24/dsPIC приведены в таблице.
Cooperative Scheduling | Preemptive Scheduling | Interrupt Processing | Interrupt Preemption | Message Processing | Sync Processing | Memory Processing | |
---|---|---|---|---|---|---|---|
TNKernel | (1) | 4,139,983 | 7,784,007 | 3,179,511 | 5,721,174 | 13,621,698 | 9,746,870 |
AVIX | 9,069,625 | 5,100,589 | 7,400,858 | 3,293,937 | 5,020,698 | 19,030,131 | 10,609,717 |
ThreadX | 11,848,815 | 5,032,710 | 7,342,967 | 3,132,150 | 7,354,317 | 17,592,956 | 14,413,455 |
uc/OS-II | (1) | 3,909,085 | 5,259,998 | (1) | 7,387,612 | 10,293,318 | 6,814,817 |
FreeRTOS | (2) | 3,717,913 | 1,881,892 | 2,400,967 | 0,484,691 | 1,989,999 | (1) |
AVA | (1) | 1,724,948 | 5,207,762 | 1,260,190 | 2,761,154 | 7,514,799 | 10,235,182 |
(1) Функция или объект не поддерживается
(2) Ошибка выполнения теста
Проект с тестовым приложением для TNKernel можно скачать по ссылке.
В таблице присутствуют RTOS, которые раньше не упоминались, поэтому дадим краткую характеристику каждой из них:
Тестирование TNKernel и AVIX можно повторить самостоятельно, скачав проекты. Остальные результаты взяты с форума microchip.com. Результаты тестов так же опубликованы на сайте AVIX, однако они устаревшие и неполные.
Если за первое место в тесте начислять 3 балла, за второе - 2 балла и за третье - 1 балл, то AVIX займет лидирующую позицию, ThreadX - второе место, а TNKernel - третье.
Самая популярная freeware RTOS для однокристальных микроконтроллеров - freeRTOS оказалась по совокупности результатов на одном из последних мест. По просьбе автора, freeRTOS удалена из таблицы сравнения на странице AVIX - по его утверждению результаты тестов сильно отличаются от реальности. Однако, до сих пор результаты автор freeRTOS не привел, поэтому в этом документе опубликовано то, что было получено в изначальной дискуссии. Вполне возможно, что на самом деле freeRTOS ведет себя гораздо лучше…
Таким образом, можно сказать, что TNKernel является лучшим из вытесняющих планировщиков, распространяемых с открытыми исходными кодами и допускающих использование в коммерческих разработках.
Структура исходных текстов TNKernel значительно изменена по сравнению с оригинальной. Основное отличие - каждая функция находится в отдельном файле. Таким образом обходится проблема линкера C30, который не может игнорировать неиспользуемые секции кода.
При сборке проекта в исполняемый файл линкер добавляет только те функции, которые используются в пользовательском приложении. Это позволяет значительно сократить объем программной памяти, используемый ядром. Например, все сервисы требуют порядка 15 кБ программной памяти, тогда как в среднем приложении ядро RTOS занимает примерно 6-7 кБ.
Кроме разделения исходных кодов на файлы была предпринята попытка сделать TNKernel еще более портируемой - все основные типы переопределяются, все машинозависимые функции вынесены в отдельные модули (префикс port_
). Это позволило в свое время достаточно просто добавить порт для PIC32.
Итак, скачав архив с проектом вы увидите католог source
в котором и находятся исходные тексты TNKernel для PIC24/dsPIC. Файл _build_mchp_c30.bat
предназначен для сборки библиотеки. Его можно отредактировать для сборки файла с требуемыми параметрами - другим уровнем оптимизации, моделью памяти и пр. Текущая версия командного файла собирает четыре библиотеки - две для PIC24 с оптимизацией Os
и форматом coff
и elf
и две для dsPIC. То же самое и для PIC32, командный файл называется _build_mchp_c32.bat
Для сборки необходимо, чтобы в системе был прописан путь к исполняемому файлу компилятора.
Компания Microchip имеет две основные линейки 16-битных контроллеров: PIC24 - микроконтроллеры общего назначения и dsPIC - контроллеры цифровой обработки сигналов. По сути PIC24 являются усеченной версией dsPIC - в них отсутствует DSP ядро и специальные методы адресации.
Изначально TNKernel портировалась как под PIC24, так и под dsPIC, причем в версии для последнего в стеке задачи кроме всего прочего, сохранялся контекст DSP-ядра. Но как выяснилось это не имело большого смысла, потому, что полностью восстановить контекст DSP простыми способами невозможно - большинство статусных флагов DSP-ядра имеют доступ только для чтения, да и с аппаратным циклом DO все не так просто. Поэтому от отдельного порта для dsPIC было решено отказаться - в следующих вариантах TNKernel для PIC24/dsPIC DSP-ядро было предложено рассматривать как разделяемые ресурс и использовать для доступа к нему из разных задач мютекс.
Но как выяснилось напрасно. Благодаря камраду qas был найден серьезный баг, который мог однозначно порушить систему при использовании модульной или бит-реверсивной адресации DSP-ядра. Баг исправлен, но опять появилось две версии TNKernel - для PIC24 и для dsPIC.
Тем не менее ситуация с DSP-ядром прежняя - его контекст не сохраняется и его следует рассматривать как разделяемый ресурс, используя для доступа из разных задач мютекс.
Порт для PIC32 был реализован весной 2010 года по просьбам трудящихся. По сути он мало чем отличается от порта для PIC24/dsPIC. Начиная с версии 2.5.600 в комплекте идет пример, который может быть скомпилирован как под PIC24 так и под PIC32 без изменений.
Все стандартные типы данных (кроме void
) переопределены:
TN_CHAR
signed char
. Под char
в данном случае подразумевается 1 байт.TN_UCHAR
unsigned char
TN_WORD
signed int
, то есть 16-битному целому со знакомsigned int
, то есть 32-битному целому со знакомTN_UWORD
unsigned int
, то есть беззнаковому 16-битному целомуunsigned int
, то есть беззнаковому 32-битному целомуTN_SYS_TIM_T
TN_TIMEOUT
unsigned int
, то есть беззнаковому 16-битному целомуunsigned int
, то есть беззнаковому 32-битному целомуРекомендуется употреблять эти типы для объявления переменных и массивов, связанных непосредственно с системой - стеков задач, блоков памяти фиксированного размера, очередей сообщений и др.:
TN_UWORD task_1_stack[128] TN_DATA;
В оригинальной версии TNKernel задачи могут иметь приоритет от 1 до 30 (0 и 31 приоритет имеют системные задачи). В версии TNKernel для PIC24/dsPIC пользовательские задачи могут иметь приоритет от 1 до 14 (0 и 15 приоритет имеют системные задачи). Это связано с разрядностью слова контроллера и стремлением сократить время поиска следующей задачи, т.е. по сути время переключения контекста.
Следует сказать, что такого количества приоритетов вполне достаточно, так как опционально TNKernel обеспечивает карусельное (round-robin) переключение между задачами с одинаковым приоритетом.
Инициализация системы в оригинальной версии TNKernel выполняется с помощью функции tn_start_system()
, которая не имеет параметров. В порте TNKernel для PIC24/dsPIC и PIC32 эта функция выглядит следующим образом:
Вызов:
void tn_start_system (TN_UWORD *timer_task_stack, TN_UWORD timer_task_stack_size, TN_UWORD *idle_task_stack, TN_UWORD idle_task_stack_size, void (*app_in_cb)(void), void (*cpu_int_en)(void), void (*idle_user_cb)(void) );
Разрешен вызов:
В контексте задачи
Параметры функции:
timer_task_stack
timer_task_stack_size
idle_task_stack
idle_task_stack_size
app_in_cb
cpu_int_en
app_in_cb
idle_user_cb
Возвращаемые значения:
нет
Пример вызова:
#define TMR_TASK_STACK_SIZE 128 #define IDL_TASK_STACK_SIZE 128 TN_UWORD stk_tmr[TMR_TASK_STACK_SIZE] TN_DATA; /* стек задачи таймера */ TN_UWORD stk_idl[IDL_TASK_STACK_SIZE] TN_DATA; /* стек задачи простоя */ void appl_init(void); void intr_init(void); void idle_user(void); int main (void) { tn_start_system(stk_tmr, TMR_TASK_STACK_SIZE, stk_idl, IDL_TASK_STACK_SIZE, appl_init, intr_init, idle_user ); } void appl_init (void) { /* инициализация */ } void intr_init (void) { /* инициализация и разрешение прерываний */ } void idle_user (void) { /* когда нечего делать мы тут */ }
Функции app_in_cb
и cpu_int_en
заменяют tn_app_init()
и tn_cpu_int_enable()
в оригинальной версии TNKernel.
Введение параметров в функцию tn_start_system()
позволило более гибко настраивать систему, в частности, выбирать размеры стеков системных задач и выполнять полезные действия в задаче простоя (tn_task_idle()
).
По сути в точке входа приложения - функции main()
должен вызываться только сервис tn_start_system()
.
Изменен вызов сервиса создания задачи tn_task_create()
:
TN_RETVAL tn_task_create (TN_TCB *task, void (*task_func)(void *param), TN_UWORD priority, TN_UWORD *task_stack_start, TN_UWORD task_stack_size, void *param, TN_UWORD option );
Параметр task_stack_start
указывает на вершину (младший адрес) стека задачи, тогда как в оригинальной версии, task_stack_start
указывает на старший адрес стека. Это связано с тем, что в PIC24/dsPIC стек растет от младшего адреса к старшему.
Добавлены функции tn_sys_enter_critical()
и tn_sys_exit_critical()
, которые аналогичны используемым в оригинальной версии tn_disable_interrupt()
и tn_enable_interrupt()
. Функции используются следующим образом:
/* ... */ tn_sys_enter_critical(); /* критическая секция кода, в которой запрещено переключение контекста */ tn_sys_exit_critical(); /* ... */
Названия функций отражают их назначение - выделение части кода в критическую секцию в которой запрещено переключение контекста. tn_disable_interrupt()
и tn_enable_interrupt()
- не совсем корректное название для PIC24/dsPIC, которые имеют векторный приоритетный контроллер прерываний.
tn_sys_enter_critical()
запрещает все прерывания!
Добавлены следующие сервисы:
Функции задач могут объявляться с атрибутом TN_TASK
. Этот атрибут сообщает компилятору о том, что функция имеет бесконечный цикл и выхода из нее не будет. В большинстве случаев это позволяет уменьшить размер стека задачи. Пример:
void TN_TASK Task (void *par) { for (;;) { tn_task_sleep(10); } }
Объекты и стеки задач могут объявляться с атрибутом TN_DATA
. По сути он размещает переменные в отдельной секции ОЗУ - это позволяет контролировать объем памяти, занимаемой объектами RTOS и стеками задач. Для этого в скрипт линкера необходимо добавить следующие строки (см., например, файл ..\example1\p24FJ128GA006.gld
):
.tnk_data : { *(tnk_data); } > data
Пример использования атрибута:
TN_SEM Sem_From_IRQ TN_DATA; TN_DQUE que_test TN_DATA;
Все сервисы TNKernel размещаются в отдельную секцию кода. Это позволяет контролировать объем программной памяти, которую занимает ядро. Для этого в скрипт линкера необходимо добавить следующие строки (см., например, файл ..\example1\p24FJ128GA006.gld
):
.tnk_code : { *(tnk_code); } >program
Если в заголовочном файле tnkernel_conf.h
не объявить TN_DEBUG
, внутренняя структура всех объектов будет скрыта от пользователя, и структура объектов в окне Watch отладчика будет отображена в виде байтового массива.
Если TN_DEBUG
будет объявлен, структуры объектов будут раскрыты. Это позволит отлаживать приложение контролируя значения полей структур.
В порте TNKernel для PIC24/dsPIC и PIC32 имеется два набора сервисов - с проверкой параметров и без проверки параметров. Естественно, последние будут занимать меньше программной памяти и будут быстрее выполняться.
Объявление TN_NO_ERROR_CHECKING
в файле конфигурации системы tnkernel_conf.h
позволяет использовать более компактные и быстрые варианты сервисов без проверки параметров.
Микроконтроллеры PIC24/dsPIC имеют аппаратный механизм контроля переполнения стека, который полностью задействован в TNKernel для PIC24/dsPIC. Для того чтобы контролировать переполнение, необходимо объявить в коде исключение (trap) по ошибке стека:
void __attribute__((interrupt, no_auto_psv)) _StackError (void) { for (;;); /* при переполнении стека задачи попадем сюда */ }
Любой сервис, создающий объект (tn_task_create()
, tn_sem_create()
, tn_queue_create()
, tn_event_create()
, tn_fmem_create()
и tn_mutex_create()
), проверяет состояние объекта (уже создан или нет) и, либо продолжает работу, либо (если объект уже создан) возвращает код ошибки TERR_EXS
.
Наличие проверки состояния объекта не зависит от типа вызываемого сервиса (с проверкой или без проверки параметров).
Добавлен заголовочный файл tnkernel_rev.h
, в котором присутствуют следующие определения:
__TNKERNEL_VERSION
- текущая версия (float)__TNKERNEL_REVISION
- текущая ревизия (беззнаковое целое)__TNKERNEL_REVISION_TIME_STRING
- время и дата создания ревизии (строка)__TNKERNEL_BUILD_TIME_STRING
- время и дата сборки библиотеки (строка)Пример использования:
TN_UWORD tn_revision = __TNKERNEL_REVISION; char *tn_data = __TNKERNEL_REVISION_TIME_STRING; char *tn_build = __TNKERNEL_BUILD_TIME_STRING; #if (__TNKERNEL_VERSION == 2.5) #if (__TNKERNEL_REVISION == 977) /* ... */ #endif #else /*...*/ #endif printf(tn_data);
Системным таймером назван счетчик, инкрементируемый каждый системный тик. Для установки и получения значения системного таймера используются следующие сервисы:
Пользовательский проект должен включать в себя заголовочный файл конфигурации tnkernel_conf.h
, в котором определены (или не определены) дефайны TN_DEBUG
и TN_NO_ERROR_CHECKING
. Конечно, в свойствах проекта папка с этим файлом должна быть добавлена в пути поиска заголовочных файлов.
Основное предупреждение: все прерывания должны быть запрещены до момента запуска системы. Для конфигурации источников прерываний и разрешения прерываний предназначена функция cpu_int_en
, указатель на которую передается в сервисе tn_start_system()
.
Будем называть прерывания в которых вызываются сервисы системными, а все остальные прерывания - пользовательскими.
TN_INTERRUPT_LEVEL
(приоритет 1). Вызов сервисов RTOS в обработчике прерывания с другим (более высоким) приоритетом (т.е. в ISR пользовательского прерывания) запрещен - это приведет к краху системы. В текущей версии TNKernel защита от вызова системных сервисов в пользовательском прерывании не реализована.
В TNKernel для PIC32 системные прерывания должны иметь одинаковый приоритет, но не обязательно равный TN_INTERRUPT_LEVEL
. Однако для совместимости кода рекомендуется использовать приоритет 1, как и в версии TNKernel для PIC24/dsPIC.
В TNKernel для PIC24/dsPIC и PIC32 не реализована вложенность системных прерываний. С одной стороны это может привести к задержке обработки прерывания, с другой - экономит стек задачи, что на самом деле более важно, особенно для PIC32. Если задержка входа в прерывание недопустима, можно использовать пользовательское прерывание с приоритетом большим чем TN_INTERRUPT_LEVEL
. Однако, не нужно забывать, что вызов сервисов RTOS в пользовательском прерывании запрещен, поэтому задачи должны взаимодействовать с пользовательским прерыванием с помощью глобальных переменных. Это некрасиво и по большому счету неправильно, но другого выхода нет…
Системные прерывания объявляются с помощью макроса tn_sys_interrupt
, аргументом которого является зарезервированый псевдоним вектора прерывания:
/* PIC24/dsPIC */ tn_sys_interrupt (_INT0Interrupt) /* системное прерывание, источник INT0 */ { IFS0bits.INT0IF = 0; tn_queue_isend_polling(&que_test, transceived_buff); } /* PIC32 */ tn_sys_interrupt(_CHANGE_NOTICE_VECTOR) { INTClearFlag(INT_CN); /* обработка прерывания */ }
Пользовательские прерывания объявляются обычным для C30/C32 способом.
Одно из системных прерываний всегда должно быть зарезервировано для системного таймера. Как правило это прерывание от аппаратного таймера с периодом 1-10 мс:
/* PIC24/dsPIC */ tn_sys_interrupt (_T2Interrupt) /* системное прерывание, источник TMR2 */ { IFS0bits.T2IF = 0; tn_tick_int_processing(); } /* PIC32 */ tn_sys_interrupt(_CORE_TIMER_VECTOR) /* системное прерывание, источник - системный таймер MIPS32 */ { Sys_Tmr_Int_Handler(); tn_tick_int_processing(); }
tn_tick_int_processing()
должен вызываться только из системного прерывания.
tn_tick_int_processing()
, то вызов других сервисов RTOS в этом прерывании запрещен!
Следует заметить что сервисы tn_sys_enter_critical()
и tn_sys_exit_critical()
запрещают системные прерывания, в то время как прерывания с приоритетом, большим TN_INTERRUPT_LEVEL
остаются активными - только для PIC24/dsPIC. Для PIC32 эти сервисы запрещают ВСЕ прерывания.
tn_sys_enter_critical()
запрещает все прерывания, а не только с приоритетом TN_INTERRUPT_LEVEL
TN_INTERRUPT_LEVEL
, однако приоритет должен быть одинаковым для всех системных прерываний