Межпроцессная коммуникация в scmRTOS. Часть 2. События и сообщения

Итак, продолжаем рассматривать межпроцессную коммуникацию в scmRtos, на этот раз будем говорить о событиях и сообщениях.
Событие или правильнее Флаг события (Event Flag) используется для информирования остальных процессов о возникновении какого-либо события (положили данные в буфер, получили команду по линии связи, да что угодно) опять же, название хорошо отражает суть, что-то случилось — махаем флагом, все кто этот флаг ждал, начинают действовать :)

У объекта-события есть методы:
  • Wait(timeout) – вызывается процессом, ожидающем событие, если событие уже произошло на момент опроса, немедленно возвращается true, если событие не произошло, то процесс засыпает, управление передается ядру и происходит перепланирование, в timeout можно указать, сколько необходимо ждать событие в тиках системного таймера (число от 1 — 65535), если за указанное время событие не произошло, то процесс просыпается и фукция возвращает false, если в качестве timeout указать 0 — то процесс будет ожидать событие вечно.
  • Signal — просигналить событие (помахать флагом), сообщает всем процессам, о том что событие произошло, управление отдается ядру, все процессы, ожидающие событие, переходят в состояние «готов к выполнению», дальше происходит перепланирование процессов и управление отдается самому приоритетному.
  • IsSignaled – проверяет произошло ли событие, возвращает true, если произошло и false иначе,
    отличается от Wait тем, что процесс не впадает в спячку.
  • Clear – очищает(сбрасывает) флаг события, мол все ок, событие принял, может вызываться в любом процессе и ожидающем событие и его инициировавшем.

Сообщение — в scmRtos это то же событие, но вместе с ним может передаваться объект произвольного типа т.е. мы уже не просто махаем флагом, но еще и передаем данные при этом.
Несколько методов:
  • send — аналог Signal
  • wait – аналог Wait
  • is_non_empty – аналог IsSignaled
  • reset — аналог Clear

Теперь интересная особенность, касается работы is_non_empty и reset:


Если посмотреть код самой scmRtos (пока речь идет только про версию 3.10), то можно увидеть, что данные методы работают с переменной логического типа (bool), reset сбрасывает ее в false, is_non_empty просто возвращает ее значение, вопрос, кто же ставит ее в true, а вот в true ее ставит send(), но только при условии, что нет процессов ожидающих событие по wait, кстати, а wait данную переменную сбрасывает (ставит в false) т.е. если мы в своей проге используем send и wait, то пользоваться reset и is_non_empty смысла нет, т.к. is_non_empty будет всегда возвращать false, во всяком случае надо очень сильно постараться, чтобы этот метод вернул true :) а вызов reset просто безполезен
Причем аналогичные функции в событиях работают так же

Вооружившись новыми знаниями, вернемся к нашему счетчику посетителей,
Теперь понятно, что механизм сообщений очень удобно использовать для передачи данных на дисплей
Вот такой получается код, мы выбросили наш глобальный массив, точнее обернули его в структуру, которую будем передавать вместе с сообщением, главное чтобы данная структура была определена в глобальной области видимости, иначе передаваться ничего не будет.
Также вызов метода wait в процессе вывода данных на дисплей можно заменить на is_non_empty, но тогда процесс не будет засыпать, ожидая событие, и процессорное время будет тратиться впустую, что не есть гут (этот фрагмент есть в коде)


#define F_CPU 8000000
#include <util/delay.h> 
#include <avr/io.h> 
#include <scmRTOS.h>

#include <stdlib.h>
#include <string.h>
#include "hd44780.h"

//создаем наши процессы
typedef OS::process<OS::pr0, 100> TProcCustomButtonHandler; 
typedef OS::process<OS::pr1, 100> TProcVisitorCounter1;
typedef OS::process<OS::pr2, 100> TProcVisitorCounter2;
typedef OS::process<OS::pr3, 100> TProcVisitorCounter3;
typedef OS::process<OS::pr4, 100> TProcLCDWriter;

TProcCustomButtonHandler Proc1;
TProcVisitorCounter1 Proc2;
TProcVisitorCounter2 Proc3;
TProcVisitorCounter3 Proc4;
TProcLCDWriter Proc5;

struct SLcdData
{
  int lcdData[2]; //глобальный массив с данными для дисплея, в ячейке [0] храним номер входа, в [1] – число посетителей
} _lcdDataS;


int processToRun = 0;       //в этой переменной храним номер процесса, который запустится следующим
OS::TMutex LCDMutex;        //семафор
OS::message<SLcdData> msg;  //наше сообщение

//int lcdData[2];	    //раскомментировать для использования событий
//OS::TEventFlag lcdEvent;

//функция вывода на дисплей
void lcd_puts(const char *s)
{
    register char c;

    while ( (c = *s++) ) {
        HD44780_SEND_CHAR(c);
    }

}


int main()
{
    hd44780_init();
    HD44780_SEND_CURSOR_POS(0, 0);
    DDRB = 0xFE;  //1 пин DDRB На вход
    PORTB = 0x01; //подрубаем pullup 
    //инициализируем таймер
    TCCR0 = 0x03;     
    TIMSK |=  (1 << TOIE0);  
    OS::Run();
}


//процедура обработки нажатия кнопки
namespace OS 
{
    template<> OS_PROCESS void TProcCustomButtonHandler::Exec()
    {
		for(;;)
		{
			if ((PINB & 0x01) == 0)
			{
			   _lcdDataS.lcdData[0] = 10;
			   _lcdDataS.lcdData[1] = 10;
			   msg = _lcdDataS;
			   msg.send();
			   /*lcdData[0] = 10; 
			   lcdData[1] = 10;
			   lcdEvent.Signal();*/						
			   LCDMutex.Lock();
			   Sleep(600/2);        //передаем управление другим процессам на 600 мсек, т.к. семафор залочен, 
						//то ни один процесс, отправляющий инфу на экран, не сможет ее вывести
			   LCDMutex.Unlock();   //разблокируем семафор, т.к. процесс самый приоритетный, то переключение не происходит
			} 
			Sleep(1); 
		}
    }
}


namespace OS 
{
	template<> OS_PROCESS void TProcVisitorCounter1::Exec()
	{
		int i = 0; //наш счетчик
		for(;;)
		{
			i++;  
			if (processToRun == 0)            //смотрим не наша ли очередь выводить данные на экран
			{
				LCDMutex.Lock();          //лочим семафор
				_lcdDataS.lcdData[0] = 1; //готовим данные для дисплея, номер счетчика
				_lcdDataS.lcdData[1] = i; //количество посетителей
				msg = _lcdDataS;
				msg.send();
				/*
				lcdData[0] = 1; 
				lcdData[1] = i;
				lcdEvent.Signal();*/
				Sleep(600/2);            //передаем управление другим процессам на 600 мсек, 
					                 //т.к. семафор залочен, никто не сможет вывести новые данные
				processToRun++;          //устанавливаем очередность вывода на экран
				LCDMutex.Unlock();  
			}
			Sleep(1);                        //отдаем управление другим процессам
		}
	}
}


namespace OS 
{
	template<> OS_PROCESS void TProcVisitorCounter2::Exec()
	{
		int i2 = 0; 
		for(;;)
		{
			i2++;
			if (processToRun == 1)
			{
				LCDMutex.Lock();
				_lcdDataS.lcdData[0] = 2;
				_lcdDataS.lcdData[1] = i2;
				msg = _lcdDataS;
				msg.send();
				/*
				lcdData[0] = 2; 
				lcdData[1] = i2;
				lcdEvent.Signal();*/
				Sleep(600/2);
				processToRun++;
				LCDMutex.Unlock();  
			}
			Sleep(1);
		}
	}
}

//3й процесс счетчика
namespace OS 
{
	template<> OS_PROCESS void TProcVisitorCounter3::Exec()
	{
		int i3 = 0; 
		for(;;)
		{
			i3++;
			if (processToRun == 2)
			{
				LCDMutex.Lock();
				_lcdDataS.lcdData[0] = 3;
				_lcdDataS.lcdData[1] = i3;
				msg = _lcdDataS;
				msg.send();
				/*lcdData[0] = 3; 
				lcdData[1] = i3;
				lcdEvent.Signal();*/
				Sleep(600/2);
				processToRun = 0;
				LCDMutex.Unlock();
			}
			Sleep(1);
		}
	}
}

/*
 процесс вывода на экран
*/
namespace OS 
{
	template<> OS_PROCESS void TProcLCDWriter::Exec()
	{
		for(;;)
		{
			//if (msg.is_non_empty())      //можно заменить вызов wait на is_non_empty, тогда надо 
			                               //раскомментировать вызов reset ниже 
			if (msg.wait())
			//if (lcdEvent.Wait())
			{
				SLcdData data = msg;
				//msg.reset();         //сразу сбрасываем сообщение, чтобы больше сюда не заходить
				char buf[5];
				HD44780_SEND_CMD_CLEAR;
				//itoa(lcdData[0], buf, 10);
				itoa(data.lcdData[0], buf, 10);
				lcd_puts(buf);
				lcd_puts("--");
				itoa(data.lcdData[1], buf, 10);
				//itoa(lcdData[1], buf, 10);
				lcd_puts(buf);
			}  
				Sleep(1);
		}
	}
}





Построить процесс с использованием событий также просто,
для этого необходимо сделать еще меньше изменений в первоначальной программе,
достаточно оставить наш глобальный массив с данными для дисплея, а все остальное использовать так же, как
с сообщениями, только вместо OS::message будет OS::TEventFlag
Собственно все это есть в коде в закомментированном состоянии

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

Как обычно, в аттачах проект для AVR Studio (с вариантами работы для сообщений и для событий, код для событий закомментирован)
и для протеуса

В следующей статье, будем работать с каналами… всем до скорого :)
  • 0
  • 05 октября 2011, 23:11
  • Lekster
  • 2
Файлы в топике: proteus.zip, scmRtos_events.zip

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

RSS свернуть / развернуть
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.