ещё одна OS (для STM8S003)

Я люблю писать прошивки, которые не содержат всяких циклов ожидания и при случае используют переход в ждущий или спящий режим, чтоб продолжиться дальше после прихода прерывания. Но в последнее время, когда понадобилось написать прошивку с пользовательским диалогом через терминал, я понял, что начиная с некоторого объёма, машину состояний запрограммировать не так-то просто (хотя я это сделал-таки).

Запарившись с программированием машины состояний, решил на будущее освоить какую-нибудь мелкую ОСь. Наткнулся на OSA, Atomthreads и SCMRtos. OSA не годилась, т.к. сильные ограничения на вложенность вызовов функций, поскольку нет такого понятия, как контекст. SCMRtos — на C++, памяти полюбому больше съест, чем на C. В итоге попробовал Atomthreads. Atomthreads вела себя очень странно: при создании трёх нитей (точно так же, как в моём примере ниже), её планировщик застревал в одной из них и дальше ничего не двигалось. Причём, иногда (при изменении времени sleep или при кооперативной передаче управления) все три нити всё-таки работали. Такая рулетка мена не устраивала, а разбираться в недрах ядра было в лом. В-принципе, там всё понятно, но с таймерами и мутексами было как-то нелогично накручено. Потом, все потоки вызывались по приоритету (не проверял, так ли это), если они вообще вызывались как положено, а надо было на уровне ОС поддерживать особый поток, который отвечает за ждущий режим. Т.е. планировщик ОС должен запускать этот поток, только если в очереди больше никого нет. Потом, Atomthreads безнадёжно вешались, если я выставлял делители периферия-CPU как 8-8, даже с более маленькими делителями не работало. Вот тут уж х.з., почему.

Короче, решил накатать свою ОС с шахматами и поэтессами. Умеет она пока по-минимуму, но это то основное, что мне было пока нужно. Т.е.
  1. Переключение нитей, планировщик типа CFQ. За один проход с большой вероятностью выбирает 1 нить. Структуры нитей организованы в виде циклического списка. Контекст сохраняется на стеке текущей нити.
  2. Таймеры. Засыпание нитей. Таймеры организованы в виде стека. Обработка планировщиком стека таймеров происходит только при совпадении времени ближайшего таймера с текущим. Проснувшаяся нить сразу становится в очередь первой.
  3. Мутексы. Логика такова: нить, которая захватила мутекс и освободила его, не может его тут же сразу захватить. Это нужно для того, чтоб не было сложных косяков, особенно, с вводом-выводом. Разблокированная мутексом нить сразу становится в очередь первой.

Вот репозиторий на GitHub.

Пример:

#include <stdio.h>

#include "ltkrn.h"
#include "uart.h"
#include "stm8s.h"
#include "stm8s_tim4.h"

#define OS_THREAD_STACK 80
#define MAIN_THREAD_STACK 80

#define main_thread_stack (void*)(KRN_STACKFRAME - 1 * MAIN_THREAD_STACK)
#define btn_thread_stack (void*)(KRN_STACKFRAME - 2 * MAIN_THREAD_STACK)
#define io_thread_stack (void*)(KRN_STACKFRAME - 3 * MAIN_THREAD_STACK)

krn_mutex mutex_printf;
extern char uart_putchar (char c);

static krn_thread thr_main, thr_btn, thr_io;

NEAR static uint8_t adc_data[560];

static uint8_t flag_led;
static uint8_t g_cnt;

static void main_thread_func (void)
{
  uint32_t sleep_ticks;
  int j;
  while (1)
    {
        sleep_ticks = (flag_led) ? (KRN_FREQ / 1) : (KRN_FREQ / 4);
        //for(i = 0; i < sleep_ticks; i--) GPIO_WriteHigh(GPIOD, GPIO_PIN_0);
        //for(i = 0; i < sleep_ticks; i--) GPIO_WriteLow(GPIOD, GPIO_PIN_0);
        GPIO_WriteReverse(GPIOD, GPIO_PIN_0);
        krn_mutex_lock(&mutex_printf);
        for(j = 0; j < 500; j++) uart_putchar('-');
        krn_mutex_unlock(&mutex_printf);
        krn_sleep(sleep_ticks);
    }
}

static void btn_thread_func (void)
{
  int btn, btn_old;
  int j;
  while(1)
  {
    g_cnt += 1;
    btn = GPIO_ReadInputPin(GPIOB, GPIO_PIN_7) ? 1 : 0;
    if( btn != btn_old)
    {
      if (btn == 1) flag_led ^= 1;
    }
    btn_old = btn;
    btn = adc_data[btn_old];
    krn_mutex_lock(&mutex_printf);
    for(j = 0; j < 1; j++) uart_putchar(((char)g_cnt & 0x1F) + 0x41);
    krn_mutex_unlock(&mutex_printf);
    krn_sleep(KRN_FREQ/100);
    //krn_dispatch();
  }
}

static void io_thread_func (void)
{
  int i, j, k;
  while(1)
  {
    adc_data[i] = i&0xFF;
    i = (i+1) & 0xFF;
    krn_mutex_lock(&mutex_printf);
    j = GPIO_ReadInputPin(GPIOB, GPIO_PIN_1) ? 1 : 0;
    k = GPIO_ReadInputPin(GPIOB, GPIO_PIN_0) ? 1 : 0;
    printf("%d\t%d\tsec=%d\tT=%d\tP=%d\n", i, g_cnt, krn_timer_cnt / KRN_FREQ, j, k);
    krn_mutex_unlock(&mutex_printf);
    krn_sleep(KRN_FREQ/10);
  }
}

//krn_dispatch();

int main( void )
{
  int8_t stat;
  flag_led = 0;
  CLK_DeInit();
  CLK_HSICmd(ENABLE);
  CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1); //8-8 still normal
  CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
  GPIO_DeInit(GPIOD);
  GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST);
  GPIO_Init(GPIOB, GPIO_PIN_7, GPIO_MODE_IN_PU_NO_IT);
  GPIO_Init(GPIOB, GPIO_PIN_0, GPIO_MODE_IN_PU_NO_IT);
  GPIO_Init(GPIOB, GPIO_PIN_1, GPIO_MODE_IN_PU_NO_IT);
  GPIO_Init(GPIOA, GPIO_PIN_3, GPIO_MODE_OUT_PP_HIGH_FAST);
  GPIO_WriteHigh(GPIOA, GPIO_PIN_3);
  uart_init(115200);
  krn_tmp_stack();
  krn_thread_init();
  krn_thread_create(&thr_main, main_thread_func, (void*)1, 1, main_thread_stack, MAIN_THREAD_STACK);
  krn_thread_create(&thr_btn, btn_thread_func, (void*)2, 6, btn_thread_stack, MAIN_THREAD_STACK);
  krn_thread_create(&thr_io, io_thread_func, (void*)3, 1, io_thread_stack, MAIN_THREAD_STACK);
  krn_timer_init();
  krn_mutex_init(&mutex_printf);
  krn_run();
  while (1);
  return 0;
}

В общем, думаю, всё понятно из примера.
Если спросите: «а где же ввод-вывод на прерываниях-то?», скажу — будет. Пока вывод сделан так, чтоб продемонстрировать работу мутексов, в том числе и вложенных.

Насчёт прерываний и событий: нить может заблокировать себя изнутри через krn_thread_lock(krn_thread_current); а обработчик прерывания может её разблокировать.

Если кто захочет попользоваться и найдёт косяки — буду рад. В планах же сделать буферизованный ввод-вывод на прерываниях, более логичную обработку событий, и может, быть, реализовать виртуальные каналы как в ChibiOS.

Да, пример написан для STM8SVL-Discovery.

  • +1
  • 25 ноября 2012, 18:05
  • scaldov

Комментарии (63)

RSS свернуть / развернуть
cut. Не, не слышали.

DI впадлу дописать макрос автоматического вставления кута.
0
  • avatar
  • a9d
  • 25 ноября 2012, 19:05
не, всё норм. это на предпросмотре подсветки нет.
0
Это не ответ на вопрос о подсветке кода (который пора бы удалить из статьи). Это к тому, что вся статья целеком висит на странице «Новые» и в ридере. Поставьте тег cut после первого абзаца (или где-то внутрях второго), что бы устранить этот неприятный момент.
0
done
0
SCMRtos — на C++, памяти полюбому больше съест, чем на C.
Вот интересно, хоть один пишущий подобную чушь задумывался над ее обоснованностью?
0
  • avatar
  • evsi
  • 25 ноября 2012, 19:08
пустышка scmRTOS ~1Кбай.
пустышка OSAL ~2Кбайт. И это притом, что она написана на Си и является простейшей кооперативкой.

Вывод очевиден. Размер зависит от радиуса кривизны рук.
+1
лол. вы что, объём бинарника чтоль сравниваете?? а как бы сколько ОЗУ сожрёт при работе — не?
пустышка — тоже широкое понятие. как бы если воткнуть printf или ещё что-то в этом роде, бинарник увеличится в разы. Потом, IAR втыкает вместо 8-ми PUSH ?b0-?b8 два CALL на какую-то хрень из порядка 20 инструкций, которые копируют в стек 2 32-битных числа. Так что, тут ещё от компилятора зависит.
Тот же AVR-GCC компилит так, что сам на асме лучше не сделаешь, даже удивительно, как это он догадывается до этого.
-1
Бредус манус. Несколько не сожрет. Сколько выделишь стека столько и будет использовано.
0
латинский подучите.
стека где? у каждой нити свой стек вообще-то. вы наверное, имели ввиду кучу? Т.е. если я вообще не выделю, оно вообще не сожрёт?
у вас какой-то бессвязный набор слов.
0
клепайте оси еще…
0
ты так говоришь, как будто это что-то плохое…
0
scmrtos займёт больший объём озу, но не потому, что она Си++ написана, а потому, что она вытесняющая.
0
у меня тоже вытесняющая. Остаётся при том ещё из 1К 650 байт под массив. Это при том, что 4 нити и одна даже с printf.
Ждём тест с scmRTOS.
ещё раз: эта ОС не для того чтоб меряться письками, а чтоб работало всё по-минимуму как часы и ещё ОЗУ прилично оставалось.
0
пипец. Я тебе 10 задач в 500 байт затолкаю и че? Что это покажет? То, что каждой задаче выдали стек 50байт?
0
сначала втолкай а потом поговорим.
0
Ох и фома не верующий. Я выделил процессу 50 байт. А теперь объясни почему этот процесс должен забрать больше 50 байт???
0
элементарно.
стек наезжает на другой стек. так понятно?
а неявный расход стека в Си и Си++ может происходить по-разному.
И потом, чего вы к Си пристали? я где сказал, что Си++ — говно? Я сам на нём программлю. Только считаю, что в микроконтроллерах он излишен. И, возможно, больше расходует стек.
0
язык программирования не имеет какого либо значения. Берем абсолютно любой язык. К примеру асм.

С какого это перепугу произойдет переполнение стека? А может аборигену надоело писать календарь и наступит конец света?
И вообще как это влияет на размер стека?
0
Например потому, что IAR для STM8 создает так называемые «виртуальные регистры», которые при переключение контекста надо бы сохранить. Куда? Правильно, в стек. В стеке у нас занято 40 байт из 50 выделенных, и надо засунуть туда еще 12\16 виртуальных помимо реальных. Что имеем? правильно, срыв стека. Дальше объяснять?
0
Это верно для любой ваытесняющей RTOS, независимо от языка, на котором она написана. А вопрос в том, будет ли RTOS на С++ жрать памяти больше, чем на С.
0
Может быть у кого-то количество регистров зависит от языка или локальный контекст функции от локального контекста метода отличается…
0
еще один туда же. А допустим у меня занято 0 байт, то тогда тоже произойдет переполнение? А допустим я выделил 1000 байт а занято 990 то переполнение тоже произойдет?
-2
господа, прежде чем минусовать, разберитесь с ассемблером STM8 и тем, что делает IAR при компиляции. как бы 2 большие разницы.
0
кстати беру свои слова обратно. отключил cross-call в настройках и дикие вызовы для пихания в стек регистров пропали.
0
И что он далает?
0
Видно, что Вам реально скучно на данном этапе жизни. Чуть ли не под каждым топиком похоливорить стремитесь))
+1
Ну, а почему бы не похоливарить :) evsi говорит правду о мифе: «любое применение С++ это дополнительные затраты ресурсов». Это просто миф. Например, таблица вызова вириальных методов появляется, если в классе есть виртуальные методы. В противном случае, класс без виртуальных методов ничем не отличается (по затратам ОЗУ) от структуры в С. Аналогично с другими вещами. Например, использование исключений требует дополнительных ресурсов для «разворачивания стека». Но, никто Вас не заставляет использовать исключения в проектах на МК.
+1
Есть даже абсолютно неявные преимущества. По моим скромным наблудим ( в контексте последнего стандарта С++), вариант с анонимным кодом оптимизируется лучше, чем классический вариант с передачей указателя на callback функцию.
0
Нет, мне не скучно. А троллить стереотипы и мифы просто одно из развлечений.
0
понятно))
0
Мне вот тож интересно, хоть один пишущий подобную чушь задумывался над ее обоснованностью?
По Си++:
Каждый класс представлен так называемой virtual method table. где уж она расположена при компиляции под STM8 — я не знаю, но есть подозрение, что в ОЗУ.
В двух словах дело обстоит так: под объект выделяется память, чтоб в неё поместилась VMT и поля класса. После чего туда копируется VMT (а может ссылка на неё, хз как в каком компиляторе) и вызывается конструктор. Вызов члена происходит через двойной указатель объект->VMT->член.
Потом, есть такая вещь, как неявное копирование. Трудно за этим следить, если программил на крестах под большие процессоры и вдруг сел за микроконтроллер.
Все эти штуки с памятью и VMT и как там происходят вызовы членов, я видел на интеле в компиляторе MSVC. Если вы видели, как это работает на микроконтроллере — напишите об этом статью или дайте ссылку.
0
VTM будет, если виртуальные методы есть.
0
вполне верю, по крайней мере, в GCC именно так.
Однако, если вас не затруднит, проверьте, сколько ОЗУ остаётся в scmRTOS. самому уже любопытно.
0
Я могу проверить… но только на АВР или CortexM. Для STM8 у меня компилятора нет. Вам самому проще скачать и сравнить. Всем интересно будет узнать результат.
0
на кортексе не интересно. там уже другие ОС рулят.
0
Интересно чем Cortex принципиально отличается в этом смысле от stm8?
Имеет значение размер сохраняемого на задачу контекста. И сколько помяти отведено на стеки задач.

>>там уже другие ОС рулят
Какие например? scmRtos там не никак не годится что-ли?
0
Единственное отличие это наличие специального прерывания в кортексах. А по факту они почти идентичны. Т.к. в обоих случаях есть полноценный стек.
0
сколько выделишь стека столько и возьмет.
0
вполне верю, по крайней мере, в GCC именно так.
Это везде так.
0
VMT появляется только с появлением первой виртуальной функции. До этого момента варианты — «класс с методами» и «структуры + функции для работы с ней» ничем не отличаются с точки зрения того, что получается на выходе компилятора и в рантайме. Если же вы собираетесь пользоваться виртуальными методами и реализуете подобную функциональность на С, то на выходе получите ровно такие же затраты памяти обоих видов. В любом из этих вариантов писать код на плюсах проще и удобнее.
По поводу неявного копирования. Ровно такие же фишки есть в C (передача и возврат структуры по значению), единственное отличие — этой фишкой традиционно мало пользуются. Но если ею пользоваться, опять-таки, результат будет тот же самый. Замечу, что в плюсах поведение в таком случае можно контролировать. В отличие от.

P.S. как это все устроено я знаю весьма и весьма давно. миф о бОльшем потреблении памяти примерно такого же возраста. и за все это время ни один человек, который его упомянул не смог его обосновать.
0
Правильно ли уразумел, что задачи поднимаются из сна только по таймеру?
0
В STM32 кнопку можно повесить на прерывание, тогда опроса не будет (но надо будеть чуть иначе делать обработку, если захочется что-то через усарт послать). Подозреваю ровно та же шняга возможна и в STM8.
0
Я о void krn_sleep(int16_t ticks) — бегло взглянул на код и не увидел там сообщений об источниках побудки.
0
из сна её можно вывести из пррывания или жругой нити с помощью _unlock или _cont
0
На момент фактического выполнения команды засыпания может возникнуть запрос прерывания. Встречалась статья по этому поводу
0
в статье по ссылке принципиально другой случай — нить решает, входить ли ей в idle или нет, основываясь на анализе переменных, обрабатываемых в ISR. И если ISR решит, что idle не нужно, в тот момент, когда нить уже проанализировала переменные и решила войти в idle то тут может произойти косяк.
У меня такого принципиально не может быть, т.к. idle-thread планируется к выполнению внутри ISR. А прерывание таймера всегда сработает, т.к. при переключении нитей прерывания разрешаются.
Только один шанс есть на это: если нити все поочереди запретят прерывания и сделают кооперативную передачу управления.
0
Считал, что обычно в кремнии (безо всяких нитей) НЕВОЗМОЖНО корректно заснуть и ПРОСНУТЬСЯ из контекста ISR. Расскажите, плз, как у Вас это получается
0
так я и не засыпаю в контексте ISR.
ISR только планирует нить, которая делает засыпание.
0
Статья как бы намекает, что чтобы не потерять запрос прерывания, возникающий в момент засыпания, нужно снять глобальное разрешение прерываний, проверить аппаратные флаги прерываний, если они не сбрасываются при чтении, или программные, устанавливаемые ручками в обработчиках, далее при отсутствии таковых заснуть с установкой глобального разрешения прерываний, а при наличии — то же, но не засыпать и выполнить постобработку (запустить задачи, ожидавшие эти флаги). Засыпание заметно отличается от обычной работы тем, что из-за проблемы пропуска запроса не стОит даже пытаться срочно поднимать и запускать задачи, требующие побудки основного диспетчера, прямо из ISR, потому что всё-равно нужна отложенная обработка.
0
устанавливать ручками флаги прерывания — некультурно. А засыпание, как я уже объяснил, происходит не в ISR, и прерывания, конечно, при этом разрешены. Но я понял вашу мысль о недообработанных прерываниях, добавлю в нить несколько nop'ов перед wfi
0
NOP-ы тут ни при чём — не помогут. Проблема более глубокая. Если стараться не потерять запрос, то появляются отложенные обработчики, становятся необходимыми того или иного рода месседжи об источниках побудки, а всякое запланированное по времени становится вызовом отложенных процедур:) Короче, другая архитектура.
0
кстати, и NOP не нужен. по-моему, вы наводите тень на плетень. обрисуйте со стороны железа ситуацию.
0
Я Вам предлагал почитать статью. В первоисточнике есть ещё что почитать. Успехов.
0
Источник отличный, спасибо!
0
господа, здесь опрос — только для демонстрации, как и UART без прерываний. чтоб посмотреть как работают мутексы. кстати, нить может делать кооперативную передачу управления через krn_dispatch();
0
Если конечный автомат кажется запутанным — его всегда можно переорганизовать более просто и логично. Про потоки такого не скажешь — они переключаются подло и неявно, создавая массу проблем.
0
поток общения с пользователем, скажем может стоять в ожидании. В это время в другом потоке будет работать цикл какого-нибудь относительно сложного расчёта. На автоматах я это делал, но это слишком трудночитаемо, и да, постоянно надо было переорганизовывать, как вы заметили.
0
Аккуратно строить автоматы непросто, но зато они позволяют получить прозрачное, однозначное решение. Потоки же создают множество скрытых проблем.
0
Взаимоисключающие параграфы — «правильно организовать не просто» и «позволяют получить прозрачное решение». Если что-то в программировании делается не просто, то решение прозрачным не получается.

У обоих подходов есть свои плюсы и минусы.
0
Ничего не взаимоисключающие. Простота — это мастерство, его достичь нелегко (сейчас модно вместо этого городить всё большие уродства).
0
По поводу моды это вы явно со зла написали, как раз сейчас модно писать простые и понятные программы. Правда, простые не в смысле функциональности, а в смысле простоты реализации нужной функциональности. И, при всей моей любви к автоматам, писать на них прозрачные решения можно только в узком смысле, то есть в пределах переходов из состояния в состояние. Общая сложность системы и сложность взаимодействия остаются теми же, другой вопрос, что автоматы позволяют относительно просто предсказуемое по длительности время реакции и время обработки. С потоками это получить заметно сложнее (точнее, требует больше внимательности и аккуратности в отработке взаимодействия потоков). Зато писать поток значительно проще, поскольку не нужно разбивать задачу на шаги, этим занимается система. Так что, как я писал выше, у обоих подходов есть свои плюсы и минусы.
0
по ссылке много академической мути и ничего конкретного.
0
Потоки же создают множество скрытых проблем.
Автор явно не ковырялся в железе, тем более в процессах происходящих в полупроводнике в процессе работы. Иначе он мог бы придумать еще несколько железнодорожных составов проблем на ровном месте. Явное упущение с его стороны.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.