Что приходит на ум когда слышишь операционная система? Наверняка форточки, линукс, макось.. или что нибудь подобное. Верно, и на вопрос зачем она нужна, все уверенно ответят: послушать музыку, поиграть в игру (по интернету!), разговаривая при этом с другом по скайпу. Заодно созерцая, как мигает светодиодик, получив байт с юарта =).
А если копнуть глубже, то прослушивание музыки, пересылка данных по Интернету — это все одиночные процессы, а так как процессор у нас один, то одновременно он может выполнять только одну задачу. Поэтому задачи выполняются поочередно небольшими «порциями», суть ОС делать это незаметно для пользователя: чтобы звук не хрипел и байтики слались и все работало одновременно. При этом, если одна из задач «повиснет», то все остальное продолжало работать.
Если отбросить все лишние плюшки и оставить голую суть, то в первую очередь ОС это просто таймер, который отсчитывает равные промежутки времени, а также сам без участия пользователя переключается между задачами, выполняет какую то часть и снова переключается. Также нужно учесть, что большинство задач могут не успевать выполниться за один квант времени, поэтому нужно сохранять состояние задачи в момент переключения на другую, а в следующий раз восстанавливать состояние переменных. Управлением всего этого занимается планировщик заданий.
Есть два основных вида ОС: вытесняющая и кооперативная. В первом случае, переключение между задачами будет «жестким», т.е. если квант времени 1мс, то сначала первая задача будет выполняться ровно 1мс, затем вторая ровно 1мс и т.д. Такие оси называются реального времени (ОСРВ). У кооперативных немного попроще, процесс сам должен сказать что «я выполнился», поэтому к ОСРВ их отнести нельзя.
Впердолить вытесняющую на мелкие AVR не получится по причине небольшого количества ОЗУ. Из имеющихся вариантов кооперативок, мне приглянулась mRTOS, почитать подробности сей системы можно на сайте автора (легко гуглится). Главная причина ее использования — простота, наличие готовой версии под CAVR, для понимания общих принципов самое то.
Итак, остались главные вопросы, зачем и когда применять ось. Теоретически, все тоже самое, что вы сделаете с осью, вы можете сговнякать без нее, ибо ресурсы одни и те же. От того, что вы приладите ее к проекту, мегагерцы в космос не взлетят, железо останется тем же, следовательно ресурсы те же.
Поэтому стоит задать себе несколько вопросов:
1.Сможете ли вы распорядиться грамотно ресурсами?
2. Не предполагается ли в процессе написания прошивки изобретать такой же велосипед, подобный планировщику?
3. Насколько читаем Ваш код? Способны ли Вы через полгода-год открыть его и сходу разобраться?
4. Один ли Вы пишите или группой?
На первый вопрос дать ответ сложно, ибо все зависит от криворукости разработчика. Со вторым все более понятно, если есть много независимых задач и планируется выполнять их через определенные промежутки времени, то лучше посмотреть в сторону ОС. С третьим тоже понятно, гораздо проще разбираться в отдельной задаче, чем ковырять зависимости в основном цикле. Если Вы пишите не один, то тут тоже есть плюсы, ибо каждый может писать свою задачу отдельно, не мешая остальным.
Объединяя выше сказанное, сфера применения довольно специфична, под определенный круг задач. Не стоит пихать ее в каждый проект. Для большинства радиолюбительских поделок ось излишня, но имея представление о ней раньше, наверняка бы засунул ее пару проектов.
Теперь заглянем под капот. Для запуска mRTOS к проекту нужно подключить mrtos.c и заинклюдить mrtos.h. Структура кода немного отличается от привычного
#include <mega8.h> #include "mrtos.h" //тут тело функции где мы пишем свой супер код void task1() { while(1)//задачи в любой ОС построены на базе бесконечного цикла { //тут код Вашей задачи DISPATCH; //функция передачи управления планировщику }; } //обработчик прерывания таймера 0 interrupt [TIM0_OVF] void timer0_ovf_isr(void) { char ii; #asm("cli") TCNT0=0x9C; inc_systime(); for(ii = 0; ii<init_tasks;ii++) if(tasks[ii].delay) --tasks[ii].delay; #asm("sei") } void main(void) { //инициализация периферии Init_mRTOS(); //инициализация ос //тут создаем таски(задачи) здесь создано 3 задачи create_task(task1,1,Active); //создать задачу (имя задачи, приоритет, статус) create_task(task2,1,Active); create_task(task3,1,Active); Sheduler(); //запуск планировщика while (1); } |
Теперь подробнее. количество задач указывается в mrtos.h дефайном APPTASKS N. Задача объявляется внутри task1(){}, task2(){} и тому подобное, внутри while(1) не нужно ничего писать, вызывать функции тоже никак не нужно, все это сделает за вас планировщик. Как видно задача состоит из бесконечного цикла, это нормально так и должно быть, но внутри задачи нужно обязательно отдавать управление планировщику. Либо функцией WAIT, либо DISPATCH. Если этого не сделать, то задача будет выполняться бесконечно.
Как это работает? Создадим таск мигания светодиодом.
void task1() { while(1) { PORTB.0 = !PORTB.0; WAIT(100); }; } |
WAIT это некий аналог delay() только, во время делея микроконтроллер ничего не выполняет и гоняет пустые циклы. Во время же WAIT управление передается другим задачам. Т.е. можно создать кучу тасков миганиями разных светодиодов, с разным WAIT и все они будут мигать с разной частотой. Если задержки не нужны то в конце используетм DISPATCH.
При использовании WAIT важно понимать, что вся система имеет минимальный тик (квант времени), поэтому мы ждем не WAIT(50) 50 милисекунд, а 50 тиков системы. Настройка тика зависит от того, как часто вызывается прерывания таймера0, т.е. если мы настроили прерывание на 1мс, то мы можем предполагать, что наше действие выполнится в течение 1мс. Опыты показали что уменьшить системный тик можно до ~20 мкс при тактовой 16МГц.
Настройка таймера ничем не отличается изученного ранее, так как таймер0 имеет только прерывание по переполнению, то все настройки завязаны на это. В последних версиях CAVR очень удобно сделано можно писать time requiments, настройки автоматически сгенерятся.
Далее задачу нужно создать. Для этого достаточно достаточно указать ее имя, приоритет и состояние.
create_task(task1,1,Active); |
С приоритетами все довольно таки не просто. Если имеются две задачи с разными приоритетом и задача с большим приоритетом постоянно выполняется, то в задачу с низким приоритетом мы никогда не зайдем. Поэтому нужно организовывать работу так, чтобы все задачи получали доступ. Заострять внимание на этом не будем, припасем на следующий раз. Состояние задачи, Active — запущена, остановлена StopTask.
Итак, для желающих просто мигнуть светодиодом:
#include <mega8.h> #include "mRTOS.h" void task1() { while(1) { WAIT(1000); PORTB.0=!PORTB.0; } } // Timer 0 overflow interrupt service routine interrupt [TIM0_OVF] void timer0_ovf_isr(void) { char ii; #asm("cli") TCNT0=0xb2; inc_systime(); for(ii = 0;ii<init_tasks;ii++) if(tasks[ii].delay) --tasks[ii].delay; #asm("sei") } void main(void) { DDRB=(1<<DDB7) | (1<<DDB6) | (1<<DDB5) | (1<<DDB4) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1) | (1<<DDB0); PORTB=(0<<PORTB7) | (0<<PORTB6) | (0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (0<<PORTB2) | (0<<PORTB1) | (0<<PORTB0); // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: 7,813 kHz TCCR0=(0<<CS02) | (1<<CS01) | (1<<CS00); TCNT0=0x83; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=(0<<OCIE2) | (0<<TOIE2) | (0<<TICIE1) | (0<<OCIE1A) | (0<<OCIE1B) | (0<<TOIE1) | (1<<TOIE0); Init_mRTOS(); create_task(task1, 1, Active); Sheduler(); while (1); } |
В качестве бонуса я попробовал сделать полифоническую (двухголосую) мелодию «Dr.Mario chill». Идея в том, что каждая ножка контроллера постоянно инвертируется в отдельном таске, тем самым генерируя частоту. Меняя задежку можно менять высоту ноты.
void task2(void) { while(1) { if(mute == 0) //если разрешено играть { if(note_ch2[n_n] == 0) //если пауза то ждем, ничего не играем { PORTB.4 = 0; WAIT(5); } else { PORTB.4 = !PORTB.4; //если не пауза то дрыгаем ногой с нужной частотой WAIT(note_ch2[n_n]); } } } } |
Я не заморачивался с идеей, в 1 таске генерится меандр с частотой ноты для соло партии, во втором для баса. Высота каждой ноты берется из массивов. Длительность, переключение и обрывание в таске3.
void task3(void) { while(1) { WAIT(1500); //играем минимальную длительность ноты for(mute = 0; mute < 500; mute++) //обрываем ноту, чтобы не сливались { PORTB.3 = 0; PORTB.4 = 0; }; mute = 0; //выставляем флаг, что можно воспроизводить звук n_n++; //переходим на следующую ноту if(n_n == n_max) //если сыграли все то идем по кругу { n_n = 0; } } } |
Чтобы смешать два канала использовал простенькую схемку.
Итого небольшой кусочек
Для желающих прошивка
Супер ! Не то что-бы я никогда не слышал про всякие «ос» для микроконтроллера, но Ваша статья понравилась очень. Ярко, доступно и с интересными примерами. Спасибо, читал с удовольствием.
Очень интересно! А сколько голосов максимально можно воспроизвести с помощью микроконтроллера? И как это отразится на работоспособности? Не будет ли «зависать»?
То решение что в статье, по сути использует программный таймер, поэтому тактовая частота будет делиться пропорционально количеству каналов.
ЧТОТО ПОДОБНОЕ МНЕ ПРИХОДИЛО В ГОЛОВУ НО ВОТ НАПИСАТЬ КОНКРЕТНУЮ БИБЛИОТЕКУ ТАК И НЕ СМОГ. спасибо за материал будем пробовать
Кооперативная многозадачность отличается от вытесняющей тем, что планировщик самостоятельно не может прервать выполнение текущей задачи, даже готовая к выполнению задача с большим приоритетом.
mRTOS категорически отказывается работать. Пробовал на atmega8 вашу прошивку. Писал свою под Atmega32. В обоих случаях результат нулевой. В чем может быть проблема? Есть какие -то подводные камни при использовании данной ОС?
начать нужно с фьюзов
Помните ли вы ZX»Spectrum»…там и ударники и гитара и куча всего выходила разом через один канал…
Вы обошли стороной MAXEVENTS в mrtos.h Максимальное количество событий. Автор пишет что нужно прописывать в каждом проекте. Если добавите о событиях буду признателен.
Андрей, копать mrtos я больше не планирую.
ОС для Atmega, это как на ядре на луну.
Теоретически возможно, на практике бессмысленно.
ОС нужна, если программируешь контроллер, в среде самого контролера. Для чего? Какой практический смысл? Ведь создан он для других задач. По мощности это компьютер 80х, «BASIC» на него разве, что написать, и с помощью дополнительного контроллера к телевизору подключить.
Многозадачность на таких скоростях?
Забыть про «delay» и while(1){ } оставлять пустым, вопрос многозадачности решится.
С тех пор как ушел из «Arduino»
while(1){Что тут писать?}
Для ответственных задач, проще дополнительный контроллер подвесить. Что на практике и делается, сервы, дисплеи и т.п. модули. У них внутри собственные контроллеры с пришивкой.
Vital, таких пессимистов, как вы, нужно просто душить. Такие как вы на корню рубите всяческие инициативы, творческие порывы и стремления.
Здравствуйте,
никак не смог транслировать программу для MikroС AVR.
Какие регистры нужно менять кто-нибудь Может подсказать?
Заранее Спасибо.
Здравствуйте,
никак не смог транслировать программу для MikroС AVR.
Какие регистры нужно менять кто-нибудь Может подсказать?
Заранее Спасибо.
Большое спасибо автору 🙂