Портируем scmRTOS на MCU 8051
Только хардкор
В конце концов меня задолбала OSAL. Основной проблемой стала не ее кривость а сам принцип кооперативных систем. По факту кооперативка это более развитая форма конечного автомата. А это означает, что при разрастании системы увеличивается количество состояний. Причем эта зависимость ни хрена не линейная. В конце концов, в большом проекте, вы получите дохренилион глобальных состояний, локальных состояний, еще более локальны состояний и еще… При этом названия у этих состояний будут самые безумные, в определенный момент вы просто задолбаетесь выдумывать новые имена.
Было решено использовать ртос. Погуглив я ничего не нашел (кроме FreeRTOS, uC, и т.п. систем). По непонятным причинам я не стал их использовать, уже не помню почему я от них отказался. В итоге захотелось портировать уже обкатанную систему scmRTOS.
Начало
Говорю сразу это оказалось намного легче чем я думал. Портировал всего за два дня. Намного сложней было отловить ошибки, на это я потратил полторы недели. Имея опыт в портировании я тоже самое могу проделать уже за вечер.
- Я буду использовать SoC CC2510F32 и CC2530F256. У них ядро 8051 но разные модели адресации flash памяти. Это Near(прошивка менее 32Кбайт) и Banked(прошивка более 32Кбайт). Есть еще Extended 1 и Extended 2, у меня камней с подобной адресацией нет но это тоже самое только регистр DPTR 24х битный.
- Среда разработки IAR 8051 8.10.4, в этом пункте особого выбора нет. Наличие хардварного отладчика обязательно.
- Документация datasheet и «8051 IAR C/C++ Compiler. Reference Guide for the MCS-51 Microcontroller Family». Дока на компилятор очень важна в нее будем смотреть постоянно.
MCS-51
Это одно из самых древнейших ядер. Оно поражает своей живучестью на рынке.
Ядро очень простое. Его очень легко освоить. Все мнемоники элементарные и их можно вызубрить за полчаса. В свое время я набросал проект на 1700 строк ассемблерного кода при этом толком не зная ядра.
Важные для нас регистры: A,B,R0..R7,PSW, SP,DPTR. Вот эти регистры мы и будем сохранять/восстанавливать при переключении контекста.
Но туже сразу замечаем, что чрезмерная простота и гробит 8051. Если в AVR стек паскудный, то в 8051 это просто тихий ужас! Нам дано всего 256 байт, при этом часть стека занимает банк регистров. Также он растет в неправильную сторону.
Делаем сразу заметку. В огромных проектах RTOS использовать невозможно. Банально не хватит стека.
Изучение «8051 IAR C/C++ Compiler. Reference Guide for the MCS-51 Microcontroller Family» в обязательном порядке!
Из неe мы узнаем, что есть еще виртуальные регистры V0..V31. Количество их задается в настройках проекта. Они используются как локальные буферы (связанно с особенностями ядра MCS-51, к некоторым байтам мы можем обращаться прямо а не косвенно).
Также есть регистр VB, адрес которого указывается в файле конфигурации линковщика.
Узнаем еще, что IAR отлично знает о проблемном стеке и предоставляет нам на выбор два программных стека PSTACK(размер 256 байт) и XSTACK( размер до 64Кбайт). В этих стеках сохраняется локальный контекст при входе в функцию/прерывание.
Параметры в функцию передаются через регистры 
Ну а если не влазит, то через программный стек.
Итого: по мимо основных регистров нам нужно сохранять еще и регистры IAR (VB,V0..V31,XSP).
Контекст
Как уже стало ясно контекст будет состоять из регистров 8051 и виртуальных регистров IAR. А еще забегая на перед скажу, что нужен еще флаг EA(глобальный флаг разрешения прерываний).
Я буду использовать только один банк регистров, т.к. использование всех 4х банков не дает преимуществ.
Контекст:
A — 1 байт
B — 1 байт
PSW — 1 байт
R0..R7 — 8 байт. Располагаются в начале стека по адресу 0x00-0x07
SP — 1 байт.
DPTR — 2 байта.
V0..V31 — от 8 до 32 байт. Располагаются сразу за банком регистров.
VB — 1 байт. Имеет фиксированный адрес 0x20. Адрес можно изменить в настройках линковщика.
SPX — 2 байта. Располагается сразу за виртуальными регистрами.
EA — 1 байт.
Итого размер контекста от 24 до 50 байт.

Кусок стека.
Желтый — регистры.
Красный — виртуальные регистры.
Голубой — SPX
Зеленый — VB

Начинаем портировать.
С теорией разобрались. Пора приступать к практике.
Наиболее подходящим по духу является порт для AVR. В нем тоже используется раздельный стек. Вот его мы и возьмем за основу.
Сначала создадим проект пустышку и добавим к нему все нужные файлы.
Я думаю вы уже удосужились прочитать доку по scmRTOS.
Выбираем максимально возможный(для этой конфигурации) размер ISTACK.
Забегая наперед скажу. Адрес расположения стека строго фиксированный. Мы можем менять только указатель на голову и только в пределах одной страницы. Естественно адреса возвратов должны хранится только в хардварном стеке. Также компилятор IAR подкидывает пакость. Мы не можем разместить свой массив в секции ISTACK во время компиляции. Можно только зарезервировать место вот так
Эта строка не может находиться внутри класса.
Такой метод резервирования стека возвратов для процесса не подходит. Т.к. придется менять код ядра.
Поэтому я пошел другим путем. Создаю большую секцию ISTACK и программно формирую указатели(это будет впереди). При использовании такого способа почти не нужно изменять ядро.
XDATA — это наш XSTACK. Под него не требуется много места. Болеет того он используется только на старте. Если вам остро не хватает оперативки, то вы запросто можете определить смещение на секцию XSTACK(я думаю вы удосужились прочитать доку на компилятор) и смело использовать это место в процессах.
Добавляем пути для асма и C++.
Пустышка готова. Хотя я еще рекомендую по удалять все лишнее из примера. Так будет проще работать.
Выбираем резонатор и настраиваем системный таймер. В качестве системного таймера используется Timer4.
OS_Target.h
Добавляем макросы для работы с прерываниями.
Об этом дефайне немного по подробней. Это указание компилятору делать функцию с этим дефайном всегда инлайновой. Но по каким-то причинам IAR любит активно ее игнорить. Даже когда вы выставите наиболее благоприятные для нее настройки оптимизации, то иногда IAR все равно будет ее игнорить. Возможно это связанно с особенностями архитектуры. Поэтому на нее не надеемся.
В MCS-51 прерывания могут вытесняться другими прерываниями. Поэтому важные моменты должны быть в критической секции. А также говорим системе, что стек используется раздельный.
Код крит. секции. Тут и так все понятно.
Добавляем скрипты для работы с указателем стека.
Правим структуру которая используется в обвертке TISRW_SS
В обвертке TISRW немного меняется только деструктор. В AVR на выходе из прерывания устанавливается флаг EA аппаратно. В 8051 его нужно выставлять руками, да и он может быть вовсе запрещен. Поэтому используем крит секцию.
А тут уже идут сильные изменения. Это связанно с игнорирование INLINE. Деструктор/конструктор переписан так, чтоб ее не проигнорировать.
Также в порте AVR происходит переключение софтварного стека. В этом случае этого делать нельзя. IAR подбросил еще одну свинку. Даже если функция инлайновая и IAR это принял, то он все рано перед входом в функцию сохраняет локальный контекст в XSTACK.
Получается следующее
Сохранение локального контекста -> переключаем софтварный стек -> восстанавливаем мусор из стека прерывания на выходе из конструктора.
Поэтому переключается только хардварный стек.
Создание процесса
Тут скрепя зубами придется изменить немного ядро. Это связанно с тем как выделяется место под стек возврата.
220 — локальный стек. С этим проблем нет, это обычный массив.
39 — стек возврата. Это указание сколько нужно отрезать от ISTACK.
Для начала топаем к шаблону в OS_Kernel.h
RStack у нас теперь не массив а указатель и он передается конструктору(по фату нужен адрес на указатель, но изменения ядра должны быть минимальны).
Кстати обратите внимание как передается указатель на софтварный стек процесса &Stack[stack_size/sizeof(stack_item_t). Т.е. передает указатель на последний элемент массива. Ведь он у нас растет в правильную сторону.
В конструкторе TBaseProcess тоже вносим изменения.
Мы добавляем переменную rs_size(размер стека возврата) и pr(приоритет, он же номер в массиве).
Метод init_stack_frame должен знать размер стека возврата и того кто передает(этот параметр нужен только для сброса или функций отладки). Опят же, это вынужденные изменения.
OS_Target_cpp.cpp
Тут у нас обработчик системного таймера. Изменений как таковых нет, кроме scmRTOS_SYSTIMER_NEST_INTS_ENABLE. Помним, что в 8051 есть вытеснения прерываний. Поэтому если scmRTOS_SYSTIMER_NEST_INTS_ENABLE=1, то мы ничего не делаем ибо уже все и так разрешено. А если =0, то ставим крит секцию.
Один из самых важных методов. Он один для всех процессов. Поэтому мы можем использовать статичную переменную для контроля свободного места.
free_space=0xFF; По умолчанию выставлена на конец стека.
Отрезаем кусочек и проверяем на переполнение. Если переполнение есть, то вешаемся. Лучше не работать вообще, чем работать неправильно. Ох если бы вы знали как меня заипали плавающие ошибки. Это ад их отлавливать! Одну из ошибок я отлавливал 8 часов подряд.
Формируем указатель.
К дефайну HARDWARE_STACK_ADDRESS мы еще вернемся.
Записываем в istack стек адрес возрата.
А это наш контекст на момент старта процесса. IAR за нас уже обнулил все массивы. Так, что нули будут гарантированно.
В начале записываем бит EA. После записываем, только младшую часть стека возврата. А под конец ставим указатель SPX на конец контекста.
Это выглядит вот так
Софтварный стек растет вниз поэтому он не портит контекст.
А, что если внутри процесса есть переменные? Произойдет следующее:
1) При входе в процесс XSP будет указывать на свободное место.
2) IAR затолкает все в софтварный стек. Т.е. в стек процесса.
3) При переключение контекста он обновиться и будет указывать на новое место.
scmRTOS_CONFIG.h
По факту здесь должны быть только настройки scmRTOS. Но я немного нарушил правила и добавил еще и настройки проекта.
HARDWARE_STACK_ADDRESS это значение мы берем из даташита на конкретный микроконтроллер. Оно указывает по какому адресу находиться стек.
OS_Target_asm.s51
Это казалось бы самое сложное, но это самое простое. Ведь асм на 8051 чрезвычайно прост.
Нужно портировать сразу два способа переключения контекста.
1) Прямая передача. Это когда мы говорим прямо, что нужно переключить контекст.
2) Использование программного прерывания. Это когда мы ОС просим переключить контекст а она уже его переключит когда будет наиболее удобно для этого.
Про прямую передачу говорить особо нечего. Там все просто. На входе получили два адреса. В один записали, из другого восстановили. И все.
Для сохранения контекста я использовал 6ть буферов.
Куда ведь надо сохранять промежуточный результат. В общем кому надо глянут ассемблерный файл.
С программным прерывание тоже оказалось все просто
Регистрируем свой обработчик.
На входе запрашиваем адрес текущего контекста
И на выходе адрес нового контекста
И на этом все. Стоит только заметить, что флаг EA должен восстанавливаться последним. Ведь прерывание может быть вытесненным.
А какое прерывание нужно использовать? Самое ненужное и самое не приоритетное.
Я использовал прерывание WDT_Timer это одно из самых ненужных прерываний да и на работу WDT оно никак не влияет.
Описывается оно в scmRTOS_TARGET_CFG.h
А это для его активации
Низший приоритет у него стоит по дефолту. И во время работы нельзя повышать его приоритет.
Почти конец
На форуме электроникса _Артём_ мне подсказал, что с вытесняющими прерываниями не все так просто.
Ведь когда мы попадаем в прерывание, то в 8051 глобально они все еще разрешены. Их нужно запретить до попадания в конструктор обвертки. Иначе это может привести к лишнему переключению контекста.
Сказано сделано. Первой командой так и сделал. Глянул, что сгенерил IAR и вижу, что запрет прерываний ни разу не первая команда. Сначала IAR в лучших своих традициях сохраняет локальный контекст а уже потом запрещает прерывания. Немного документации и выход нашелся. Дело в том, что в 8051 под вектор прерывания выделяется 8байт памяти. Вот туда мы и можем вставить свой мини обработчик.
Делается это легко.
Сначала делаем редефайн векторов на новый адрес.
2 — это размер инструкции сброса флага прерываний.
Говорю сразу. Адреса векторов прерываний у всех 8051 стандартны. Меняются только названия. Поэтому в другом камне максимум нужно изменить имя вектора.
А дальше в ассемблерном файле пишем свой обработчик
Вуаля. Первая инструкция это гарантированное запрещение прерывания. Ее вытеснить невозможно.
Конец
Как видим ничего сложно нет. Все элементарно. Забавно но на фрилансе подобная услуга стоит 1000 у.е. Хотя по факту работы всего на 300-400у.е.
Это не пособие HOWTO а пример как такое можно сделать уже с другим микроконтроллером.
Исходники можно взять тут. Естественно мелочь была опущена.
Думаю скоро они появятся в репозитории.
Тут еще нужно вставить фотку котят и ссыль на ютуб. Где будет показан пример мегающих светиков и я 2-3 часа рассказываю о том как это работает. Но это за отдельную плату.
PS: Вспомнил почему отверг все другие ОС. Дело в компиляторе и отладчике. IAR хорошо дружит с продукцией Ti.
Комментарии ( 29 )
Тут еще нужно вставить фотку котят и ссыль на ютуб. Где будет показан пример мегающих светиков и я 2-3 часа
рассказываю
о том как это работает. Но это за отдельную плату.
Не надо так явно демонстрировать своё непонимание содержательной части, опыт придёт со временем. В отличие о текста, видео сделать очень не просто, ибо оно покажет весь уровень владения инструментарием в комплексе и что на словах просто в тексте на практике объяснить и донести не каждый сможет. Это не глупые однообразные комменты травить по постам.
А по статье интересно замечание о паскудности стека в AVR. Это в каком смысле? Что в архитектуре не так?
Доступна только команда mov. Никакой косвенной адресации. Если локальных переменных дохрена(на них не хватает регистров), то это приводит к печальке.
В 8051 тоже нет косвенной адресации для SP. А вот для SPX есть. Ибо эта пара храниться в первой половине стека.








