Перейти к содержанию
    

Начало работы с scmRTOS

Но это же за уши притянуто.. Хочется принципиально для себя решить этот вопрос.
Увы, IAR и GCC несколько по-разному описывают регистры:
GCC:
#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)
#define TIMSK0  _SFR_MEM8 (0x6E)
IAR:
#define SFR_B_N(_ADDR, _NAME, _B7, _B6, _B5, _B4, _B3, _B2, _B1, _B0) \
                 SFR_B_BITS_N(_NAME, _ADDR, \
                              Bit0,Bit1,Bit2,Bit3,Bit4,Bit5,Bit6,Bit7, \
                              _B0,_B1,_B2,_B3,_B4,_B5,_B6,_B7)
#define SFR_B_BITS_N(_NAME, _ADDR, _A,_B,_C,_D,_E,_F,_G,_H, \
                                   _A2,_B2,_C2,_D2,_E2,_F2,_G2,_H2) \
    __io union { \
      unsigned char   _NAME;           /* The sfrb as 1 byte */ \
      struct {                        /* The sfrb as 8 bits */ \
        __BYTEBITS(_NAME, _A,_B,_C,_D,_E,_F,_G,_H) \
      };  \
      struct {                        /* The sfrb as 8 bits */ \
        __BYTEBITS(_NAME, _A2,_B2,_C2,_D2,_E2,_F2,_G2,_H2) \
      };  \
    } @ _ADDR;SFR_B_N(0x6E,TIMSK0,Dummy7,Dummy6,Dummy5,Dummy4,Dummy3,OCIE0B,OCIE0A,TOIE0)

Поэтому в GCC можно использовать #ifdef к именам регистров, а в IAR - увы, нет. Поэтому вам проще всего выкинуть лишние строчки условий и оставить только

#define SPM_CONTROL_REG SPMCSR
#define TIMSK TIMSK0

или заменить SPM_CONTROL_REG на SPMCSR и TIMSK на TIMSK0 дальше в исходнике.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Возникло пару вопросов. Допустим, есть три процесса: KeyMenu, EditTime, Blink. Порядок работы должен быть такой :

1) KeyMenu - навигация по пунктам меню. На этом этапе остальные два процесса находятся в ожидании.

2) Заходим в пункт меню "Настройка -> Время". После нажатия ENTER запускается функция, которая считывает время с RTC в переменные и выводит эти значения на ЖКИ. После этого необходимо запустить процессы EditTime и Blink. При этом необходимо перевести KeyMenu в ожидание, пока работают эти процессы. Процесс Blink - это просто инвертирование текущего знакоместа с периодом 1 сек.

3) После окончания ввода времени, EditTime и Blink снова переводятся в ожидание, а KeyMenu переходит в состояние готовности.

 

Первый вопрос. Как это лучше организовать?

И второй вопрос скорее по C++. Мой проект состоит из нескольких С-файлов. В tasks.c я вынес все процессы и объявления объектов. Как в другом С-файле использовать методы объекта?

 

PS. В другом С-файле, в котором необходимо вызвать ef.Signal(); просто объявил extern OS::TEventFlag ef; как это обычно делается с переменными и компилятора это устроило, хотя в учебнике С++ сказано, что данные-члены не могут определятся с модификаторами auto, extern, register

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

И второй вопрос скорее по C++. Мой проект состоит из нескольких С-файлов. В tasks.c я вынес все процессы и объявления объектов. Как в другом С-файле использовать методы объекта?

В каком-нибудь загововке, который всем виден:

 

typedef OS::process<OS::pr0, 768>  TMainProc;
typedef OS::process<OS::pr1, 768>  TFPGAProc;
typedef OS::process<OS::pr2, 1024> TControlsProc;
typedef OS::process<OS::pr3, 1024> TGUIProc;

extern TMainProc     MainProc;
extern TFPGAProc     FPGAProc;
extern TControlsProc ControlsProc;
extern TGUIProc      GUIProc;

 

В исходных файлах объявить сами объекты:

 

TMainProc     MainProc;
...
TFPGAProc     FPGAProc;
...

 

 

PS. В другом С-файле, в котором необходимо вызвать ef.Signal(); просто объявил extern OS::TEventFlag ef; как это обычно делается с переменными и компилятора это устроило, хотя в учебнике С++ сказано, что данные-члены не могут определятся с модификаторами auto, extern, register

 

extern OS::TEventFlag ef;

 

где тут данное-член? И вообще, где тут член класса? Тут объект ef типа OS::TEventFlag объявлен как extern. Все в норме.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3) После окончания ввода времени, EditTime и Blink снова переводятся в ожидание, а KeyMenu переходит в состояние готовности.
А смысл делать EditTime отдельным процессом? Мне кажется он просится функцией (возможно - функцией-членом класса TTimer), вызываемой из KeyMenu, но никак не отдельным процессом. Что касается Blink - он может ожидать некое сообщение (скажем, BLINK_ON) и получив его - мигать, пока не получит сообщение BLINK_OFF. Он может также получать сообщения SHORT_FLASH (одна короткая вспышка), LONG_FLASH (одна длинная вспышка) и другие, или сообщение может представлять из себя структуру, в которой записаны частота мигания, скважность, длительность.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А смысл делать EditTime отдельным процессом? Мне кажется он просится функцией (возможно - функцией-членом класса TTimer), вызываемой из KeyMenu, но никак не отдельным процессом.

Так изначально у меня ф-ция EditTime вызывалась из функции пункта меню "Настройка -> Время" . В этой ф-ции в вечном цикле опрашивались кнопки и происходило редактирование времени. Естественно, контроллер больше ничего не делал, кроме прерываний. Только ждал нажатия ESC, которое вывело бы его из вечного цикла. Это было до использования ОС. Поэтому я решил сделать EditTime отдельным процессом. Scan_code нажатой кнопки получаю в прерывании таймера 2, который запускается в обработчике внешнего прерывания от нажатия кнопки.

OS_PROCESS void TKeyMenu::Exec()    
{
  for(;;)
  {
    if(key_code.scan & KEY_PRESSED)
    {
      key_code.scan &= (0xff-KEY_PRESSED);   // Clear MSB of scan_code (key_pressed)
    
      switch(key_code.scan)      
      {
         case UP:
              SET_MENU(PREVIOUS);
              break;
         case DOWN:
              SET_MENU(NEXT);
              break;
         case LEFT:
              SET_MENU(PARENT);
              break;
         case RIGHT:
              SET_MENU(SIBLING);
              break;
      }
    }
    Sleep(10);
  }
}

//-----------------------------------------------------
OS_PROCESS void TEditTime::Exec()  
{
  for(;;)
  {
    ef.Wait();
    
    if(key_code.scan & KEY_PRESSED)
    {
      key_code.scan &= (0xff-KEY_PRESSED);   // Clear MSB of scan_code (key_pressed)
    
      switch(key_code.scan)    
      {
        case LEFT:        //k_esc
             // выйти из редактирования. Возврат к предыдущему пункту меню
             break;
             
        case RIGHT:       //k_enter 
            // переход к следующему параметру
             break;
             
        case DOWN:        //k_left
            // перевод курсор на один символ влево
             break;
             
        case UP:          //k_right
             // перевод курсора на один символ вправо
             break;  
             
        default:          // 0...9
             //цифровые кнопки
             break;            
      }
    }
    Sleep(3);
  }
}

//---------------------------------------------------------------------------
OS_PROCESS void TBlink::Exec()    
{
    for(;;)
    {
      ef.Wait();
      ks0108InvertRect(x, y, w, 9);
      Sleep(500);
    }
}

EditTime и Blink ждут сигнала от функции, которая вызывается при входе в пункт меню. Это мой первый опыт работы с ОС. Предложите что-нибудь лучше. А что такое функция-член класса TTimer ? Можно поподробней? Желательно с примером. Заранее спасибо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так изначально у меня ф-ция EditTime вызывалась из функции пункта меню "Настройка -> Время" . В этой ф-ции в вечном цикле опрашивались кнопки и происходило редактирование времени. Естественно, контроллер больше ничего не делал, кроме прерываний... Поэтому я решил сделать EditTime отдельным процессом.
Так ведь и теперь у вас он во время EditTime не делает ничего в TKeyMenu. При этом у вас получилось, что EditTime работает изредка, а память под свой стек занимает всегда. Если бы вы сделали его функцией и вызывали из TKeyMenu - в остальное время этот стек мог бы быть использован другими частями TKeyMenu.

А что такое функция-член класса TTimer ? Можно поподробней?
Это я прочитал рекомендовавшуюся здесь книжку Гради Буча "Объектно-ориентированный анализ и проектирование" и пытаюсь применять полученные там знания. Он рекомендует вычленять в предметной области законченные абстракции и облекать их в вид классов. Вот у вас в системе судя по описанию есть некие часы, которые можно выделить в отдельную абстракцию. Они умеют считать время, их можно спросить "который час", их можно установить. Их можно реализовать в виде класса (обозвать его TTimer), который будет иметь только две открытые функции GetTime(), SetTime(). Внутрь этого класса спрятать (сделать private) счетчик времени и тем самым гарантировать, что никто его случайно не испортит - только вызвав явно SetTime(). Применяя в системе разные внешние микросхемы часов или программные часы, вам придется переписать только реализацию этого класса, вся остальная часть программы не будет зависеть от физической реализации часов.

Вот изложил все это и понял, что функция EditTime имеет отношение скорее к интерфейсу пользователя, чем к часам, ее нет смысла делать членом класса TTimer, скорее она должна в конце своей работы вызвать SetTime().

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так ведь и теперь у вас он во время EditTime не делает ничего в TKeyMenu. При этом у вас получилось, что EditTime работает изредка, а память под свой стек занимает всегда. Если бы вы сделали его функцией и вызывали из TKeyMenu - в остальное время этот стек мог бы быть использован другими частями TKeyMenu.
Согласен. Мой способ мне самому не нравится. Процессов не хватит на каждый пункт меню. В TEditTime и TKeyMenu используются одинаковые конструкции

  for(;;)
  {
    if(key_code.scan & KEY_PRESSED)
    {
      key_code.scan &= (0xff-KEY_PRESSED);   // Clear MSB of scan_code (key_pressed)
    
      switch(key_code.scan)      
      {
         case UP:
              .........
              break;
         case DOWN:
              .........
              break;
         case LEFT:
              .........
              break;
         case RIGHT:
              .........
              break;
      }
    }
    Sleep(...);
  }

Как бы использовать один процесс TKey, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс... Но пока не знаю, как это реализовать. Опыта с ++ маловато.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Как бы использовать один процесс TKey, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс...
Могу предложить сделать абстрактный базовый класс с виртуальными функциями (методами) ActionUp(), ActionDown(), ActionLeft() ....От него унаследовать TMainMenu, TEditTime и остальные, в которых переопределить эти методы. Посмотрите пример 3 из комплекта ОСи - там как раз такой подход реализован при кормлении слонов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Согласен. Мой способ мне самому не нравится. Процессов не хватит на каждый пункт меню. В TEditTime и TKeyMenu используются одинаковые конструкции

...

Как бы использовать один процесс TKey, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс... Но пока не знаю, как это реализовать. Опыта с ++ маловато.

Вы тут углубились в вещи, которые к осям имеют очень опосредованное отношение - проектирование и разработка пользовательского интерфейса. Для пользовательского интерфейса идеально подходит та часть С++, которая реализует ООП. Вкратце: чтобы не городить мегатонны кода и запутаться в конце концов, применяется тот самый ОО подход - создается абстрактрый класс пункта меню, например, TMenuItem, в нем определяется интерфейс - набор чисто виртуальных функций, которые и будут реализовывать функциональность. Далее, от этого абстрактного класса наследуете уже конкретные классы пунктов меню, в которых определяете конкретное наполнение тех виртуальных функций.

 

    class TMenuItem // абстрактрый базовый класс пункта меню
    {
    public:
        TMenuItem(const char * str);
        virtual void draw() = 0;      // чисто виртуальная фукнция
        virtual void change(int x) = 0;
    
    private:
        const char* caption;
        ...
    };


    class TMenuItem1 : public TMenuItem
    {
    public:
        TMenuItem1(const char * str) : TMenuItem(str)  { ... }
        ...
    };

    ...

 

Таких классов надо родить столько, сколько у вас разных пунктов меню. В каждом из этих классов переопределяете фукнции draw и change (или какие там у вас будут функции).

 

void TMenuItem1::draw() { ... } // конкретная реализация данной функции - отрисовка этого конкретного пункта меню.

void TMenuItem1::change(int x) { ... } // изменение данного конкретного пункта меню

 

Для остальных классов тоже определить точно так же эти функции. Если есть одинаковые куски кода, то можно их использовать - например, у меня все пункты меню отрисовываются одинаково, поэтому у меня функция draw() невиртуальная и общая для всех классов потомков. А change() - виртуальная, т.к. содержимое пункта меню в каждом случае индивидуально и изменяется, соответственно, тоже индивидуально (к примеру, часть пунктов задает числовые параметры, а часть - строковые).

 

Теперь организуете сами пункты меню в группы - например, создав массив указателей на объекты этих типов.

TMenuItem1 MenuItem1;
TMenuItem2 MenuItem2;
...
TMenuItemN MenuItemN;

TMenuItem *Menu[] = 
{
    &MenuItem1,
    &MenuItem1,
    ...
    &MenuItemN,
}

 

Теперь в соответствии с событиями, получаемыми от кнопок, выполняете действия над объектами (тут для простоты использую частью псевдокод):

 

byte index;

if( RIGHT )
{
    Menu[++index]->draw(); // проверка перехода границы массива для простоты опущена
}
else if( LEFT )
{
    Menu[--index]->draw(); // проверка перехода границы массива для простоты опущена
}
else if ( UP )
{
    Menu[index]->change(1);  
}
else if ( DOWN )
{
    Menu[index]->change(-1);  
}

 

Т.е. тут события LEFT/RIGHT задают перемещение по пунктам меню, UP/DOWN - изменение значения пункта. Конечно, это все кратко, для иллюстрации только. На практике, обычно, все сложнее - все упирается в конкретную организацию меню и требуемую функциональность. Но схема построения будет та же и она, как видно, достаточно проста.

 

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

 

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

 

Вообще, наверное, GUI - одна из самых подходящих областей для применения ООП, где преимущества ООП перед процедурным программированием являются просто гиганскими. :)

 

Что касается конкретно ОС, то вот этот код обработки пунктов меню имеет смысл поместить в один процесс (как правило, низкоприоритетный), который получает сообщения от источников входной информации - кнопок и др. Процесс висит в саспенде, ждет сообщения. Получил сообщение, проснулся, обработал его, упал в ожидание следующего.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Реализация меню - это отдельная тема для разговора. Если бы я сейчас начинал свой проект, то, возможно, и применил бы ОО подход в создании меню. Правда, пока не понятно, как это усложнится в случае иерархического меню. У меня меню создается макросом MAKE_MENU (Name, Next, Previous, Parent, Sibling, SelectFunc, EnterFunc, Text). Структура меню видна, как на ладони. И главное, что это уже отлажено и работает.

вот этот код обработки пунктов меню имеет смысл поместить в один процесс (как правило, низкоприоритетный)

Этот процесс должен обрабатывать не только пункты меню, но и другие функции, в частности EditTime. По совету Сергея я создал от базового абстрактного класса TKey два новых класса: TMainMenu и TEditTime , которые имеют собственные реализации виртуальных методов Up, Down, Right, Left. Теперь мне надо, чтобы процесс, который получает сообщение в виде scan_code нажатой кнопки от ISR Timer2 отрабатывался по-разному, в зависимости от того, что в данный момент нужно: навигация по пунктам меню или редактирование времени. Как это сделать?

 

PS. Ничего лучшего, кроме как при входе в меню "Настройка -> Время" установить обычный глобальный флаг (не OS::EventFlag), по которому в процессе TKey определять какие действия необходимо делать при нажатии на кнопки, не придумал. Спрашивается. Зачем было городить виртуальные функции с абстрактными классами? :05:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

PS. Ничего лучшего, кроме как при входе в меню "Настройка -> Время" установить обычный глобальный флаг (не OS::EventFlag), по которому в процессе TKey определять какие действия необходимо делать при нажатии на кнопки, не придумал. Спрашивается. Зачем было городить виртуальные функции с абстрактными классами? :05:
Мимо. Заводите объекты ваших классов-потомков, заводите указатель на абстрактный класс TKey:

TMainMenu MainMenu;
TEditTime  EditTime;
TKey *CurrentMode = &MainMenu; // начинаем с меню.
enum TKeyCode { KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN };
OS::message<TKeyCode> Keyboard;

Указателю вы можете присвоить адрес любого класса-потомка этого базового класса. Т.е. надо меню - CurrentMode = &MainMenu. Надо редактировать время - CurrentMode = &EditTime; Теперь в потоке, который собственно организует управление делаете вызов виртуальных функций:

 for(;;)
  {
    if(!Keyboard.Wait(timeout))
    {
         CurrentMode = &MainMenu;  // пользователь ушел, выход в основной режим по таймауту
         // или CurrentMode->Timeout();
    }
    TKeyCode Key = Keyboard;
    switch (Key)
    {
    case LEY_LEFT:
          CurrentMode->Left();
          break;
    case LEY_RIGHT:
          CurrentMode->Right();
          break;
    .....................
    }
  }

Примерно так... Потом увидете, что все пункты меню можно построить точно так же как и TMainMenu, а красивую и понятную таблицу переходов между ними можно продолжать генерить макросом.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо, Сергей. Сам бы до этого не додумался. :beer:

Я думаю достаточно будет EventFlag вместо message. Скан-код хранится в глобальной переменной.

Еще один момент. В функции EditTime используются цифровые кнопки (0...9), а для навигации по пунктам меню - нет. В конструкции switch в ветке default при редактировании времени вводятся цифры. В базовом абстрактном классе необходимо создавать все виртуальные функции, которые используются потомками? Если да, то в TMainMenu нужно просто создать вирт. ф-цию Default {} с пустым телом?

Потом увидете, что все пункты меню можно построить точно так же как и TMainMenu, а красивую и понятную таблицу переходов между ними можно продолжать генерить макросом.
Это к разговору о создании иерархического меню?

 

PS.

if(!Keyboard.Wait(timeout))
wait нужно писать с маленькой буквы ;)

 

PS2. А на счет функции Blink, которая нужна при редактировании времени, то на мой взгляд будет проще ее запускать отдельным таймером. К проекту подключен timer.c, в котором задан массив системных таймеров, с помощью которых можна запускать функции с заданным интервалом времени.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В конструкции switch в ветке default при редактировании времени вводятся цифры. В базовом абстрактном классе необходимо создавать все виртуальные функции, которые используются потомками? Если да, то в TMainMenu нужно просто создать вирт. ф-цию Default {} с пустым телом?
Думаю, да. Обозвать ее как-то вроде Numeric(). Причем если система попискивает на нажатие кнопки, то можно в базовом классе определить пару невиртуальных функций, BeepOk() и BeepError() и в реализации Numeric() для TMainMenu вызывать BeepError(), а в "правильных" обработчиках - BeepOk().

 

Это к разговору о создании иерархического меню?
Да.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Сделал так как Сергей сказал. За исключением того, что вместо message из ISR посылается TEventFlag::SignalISR(). Теперь наблюдаю следующее: При удержании кнопки при навигации по пунктам меню курсор постоянно "бегает". Никак не пойму из-за чего. Скан-код нажатой клавиши передается через глобальную переменную scan_code. В ней же старшим битом передается информация о нажатии кнопки. При обработке старший бит сбрасывается и таким образом избегаем повторной обработки нажатия кнопки. А на самом деле происходит повтор. :/ Если нет нажатия, то scan_code=0, и это состояние не обрабатывается.

if(key_code.scan & KEY_PRESSED)
    {
      key_code.scan &= (0xff-KEY_PRESSED);   // Clear MSB of scan_code (key_pressed)
    
      switch(key_code.scan)      
      {
.............

Коротко о том, как у меня устроена клавиатура. При нажатии попадаем в ISR PCINT, запрещаем PCINT. Там запускаем таймер2. Через 35 мс в ISR Timer2 останавливаю таймер2, вычисляю скан-код, разрешаю прерывание PCINT. До этого все работало замечательно :05:

 

PS. Проверил этот же проект без ОС и с ОС(jacOS) - все работает нормально: отрабатывает одно нажатие. Значит проблема в этой системе. Возможно, я в упор не вижу слона, а со стороны вам будет виднее. ;) Так в чем же может быть проблема? Второй день копаю... :(

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Проблема с повтором решена. Спасибо Сергею Борщу. Проблема была в разрешении вложенных прерываний в ISR Timer2.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...