Сергей Борщ 134 10 марта, 2008 Опубликовано 10 марта, 2008 · Жалоба Но это же за уши притянуто.. Хочется принципиально для себя решить этот вопрос.Увы, 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 дальше в исходнике. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 11 марта, 2008 Опубликовано 11 марта, 2008 · Жалоба Возникло пару вопросов. Допустим, есть три процесса: 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 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 58 11 марта, 2008 Опубликовано 11 марта, 2008 · Жалоба И второй вопрос скорее по 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. Все в норме. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 11 марта, 2008 Опубликовано 11 марта, 2008 · Жалоба 3) После окончания ввода времени, EditTime и Blink снова переводятся в ожидание, а KeyMenu переходит в состояние готовности.А смысл делать EditTime отдельным процессом? Мне кажется он просится функцией (возможно - функцией-членом класса TTimer), вызываемой из KeyMenu, но никак не отдельным процессом. Что касается Blink - он может ожидать некое сообщение (скажем, BLINK_ON) и получив его - мигать, пока не получит сообщение BLINK_OFF. Он может также получать сообщения SHORT_FLASH (одна короткая вспышка), LONG_FLASH (одна длинная вспышка) и другие, или сообщение может представлять из себя структуру, в которой записаны частота мигания, скважность, длительность. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 11 марта, 2008 Опубликовано 11 марта, 2008 · Жалоба А смысл делать 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 ? Можно поподробней? Желательно с примером. Заранее спасибо. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 11 марта, 2008 Опубликовано 11 марта, 2008 · Жалоба Так изначально у меня ф-ция EditTime вызывалась из функции пункта меню "Настройка -> Время" . В этой ф-ции в вечном цикле опрашивались кнопки и происходило редактирование времени. Естественно, контроллер больше ничего не делал, кроме прерываний... Поэтому я решил сделать EditTime отдельным процессом.Так ведь и теперь у вас он во время EditTime не делает ничего в TKeyMenu. При этом у вас получилось, что EditTime работает изредка, а память под свой стек занимает всегда. Если бы вы сделали его функцией и вызывали из TKeyMenu - в остальное время этот стек мог бы быть использован другими частями TKeyMenu. А что такое функция-член класса TTimer ? Можно поподробней?Это я прочитал рекомендовавшуюся здесь книжку Гради Буча "Объектно-ориентированный анализ и проектирование" и пытаюсь применять полученные там знания. Он рекомендует вычленять в предметной области законченные абстракции и облекать их в вид классов. Вот у вас в системе судя по описанию есть некие часы, которые можно выделить в отдельную абстракцию. Они умеют считать время, их можно спросить "который час", их можно установить. Их можно реализовать в виде класса (обозвать его TTimer), который будет иметь только две открытые функции GetTime(), SetTime(). Внутрь этого класса спрятать (сделать private) счетчик времени и тем самым гарантировать, что никто его случайно не испортит - только вызвав явно SetTime(). Применяя в системе разные внешние микросхемы часов или программные часы, вам придется переписать только реализацию этого класса, вся остальная часть программы не будет зависеть от физической реализации часов. Вот изложил все это и понял, что функция EditTime имеет отношение скорее к интерфейсу пользователя, чем к часам, ее нет смысла делать членом класса TTimer, скорее она должна в конце своей работы вызвать SetTime(). Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба Так ведь и теперь у вас он во время 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, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс... Но пока не знаю, как это реализовать. Опыта с ++ маловато. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба Как бы использовать один процесс TKey, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс...Могу предложить сделать абстрактный базовый класс с виртуальными функциями (методами) ActionUp(), ActionDown(), ActionLeft() ....От него унаследовать TMainMenu, TEditTime и остальные, в которых переопределить эти методы. Посмотрите пример 3 из комплекта ОСи - там как раз такой подход реализован при кормлении слонов. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 58 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба Согласен. Мой способ мне самому не нравится. Процессов не хватит на каждый пункт меню. В 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 - одна из самых подходящих областей для применения ООП, где преимущества ООП перед процедурным программированием являются просто гиганскими. :) Что касается конкретно ОС, то вот этот код обработки пунктов меню имеет смысл поместить в один процесс (как правило, низкоприоритетный), который получает сообщения от источников входной информации - кнопок и др. Процесс висит в саспенде, ждет сообщения. Получил сообщение, проснулся, обработал его, упал в ожидание следующего. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба Реализация меню - это отдельная тема для разговора. Если бы я сейчас начинал свой проект, то, возможно, и применил бы ОО подход в создании меню. Правда, пока не понятно, как это усложнится в случае иерархического меню. У меня меню создается макросом 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: Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба 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, а красивую и понятную таблицу переходов между ними можно продолжать генерить макросом. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба Спасибо, Сергей. Сам бы до этого не додумался. :beer: Я думаю достаточно будет EventFlag вместо message. Скан-код хранится в глобальной переменной. Еще один момент. В функции EditTime используются цифровые кнопки (0...9), а для навигации по пунктам меню - нет. В конструкции switch в ветке default при редактировании времени вводятся цифры. В базовом абстрактном классе необходимо создавать все виртуальные функции, которые используются потомками? Если да, то в TMainMenu нужно просто создать вирт. ф-цию Default {} с пустым телом? Потом увидете, что все пункты меню можно построить точно так же как и TMainMenu, а красивую и понятную таблицу переходов между ними можно продолжать генерить макросом.Это к разговору о создании иерархического меню? PS. if(!Keyboard.Wait(timeout)) wait нужно писать с маленькой буквы ;) PS2. А на счет функции Blink, которая нужна при редактировании времени, то на мой взгляд будет проще ее запускать отдельным таймером. К проекту подключен timer.c, в котором задан массив системных таймеров, с помощью которых можна запускать функции с заданным интервалом времени. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 134 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба В конструкции switch в ветке default при редактировании времени вводятся цифры. В базовом абстрактном классе необходимо создавать все виртуальные функции, которые используются потомками? Если да, то в TMainMenu нужно просто создать вирт. ф-цию Default {} с пустым телом?Думаю, да. Обозвать ее как-то вроде Numeric(). Причем если система попискивает на нажатие кнопки, то можно в базовом классе определить пару невиртуальных функций, BeepOk() и BeepError() и в реализации Numeric() для TMainMenu вызывать BeepError(), а в "правильных" обработчиках - BeepOk(). Это к разговору о создании иерархического меню?Да. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 12 марта, 2008 Опубликовано 12 марта, 2008 · Жалоба Сделал так как Сергей сказал. За исключением того, что вместо 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) - все работает нормально: отрабатывает одно нажатие. Значит проблема в этой системе. Возможно, я в упор не вижу слона, а со стороны вам будет виднее. ;) Так в чем же может быть проблема? Второй день копаю... :( Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
alux 0 14 марта, 2008 Опубликовано 14 марта, 2008 · Жалоба Проблема с повтором решена. Спасибо Сергею Борщу. Проблема была в разрешении вложенных прерываний в ISR Timer2. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться