Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Начало работы with scmRTOS
Форум разработчиков электроники ELECTRONIX.ru > Cистемный уровень проектирования > Операционные системы > scmRTOS
Страницы: 1, 2, 3, 4, 5, 6
URANst
Хочется научиться работать с этой штукой - scmRTOS & AVR(Atmega8) & IAR 4.30A ! Почитал темы которые есть на форуме, почитал User's Manual v2. Возникло некторое количество вопросов:
1. Какая последовательность создания проекта: мои предположения - создаем в IAR новый проект, тискаем добавить файлы в проект и добавляем OS_Kernel.cpp , OS_Services.cpp , OS_Target_asm.s90 , OS_Target_cpp.cpp , usrlib.cpp. В maim.cpp пишем
Код
#include <scmRTOS.h>

. Затем каким то образом нужно создать самому как я понял scmRTOS_TARGET_CFG.h и scmRTOS_CONFIG.h, но как не ясно или их нужно тупо скопировать из примера автора и если что нада то менять.
2. Почему в примерах автор добавляет
Код
void OS::SystemTimerUserHook() { }
void OS::IdleProcessUserHook() { }


Так нужно делать всегда ?
3. Дальше >> понятно что для AVR передачу управления можно осуществить сгенерировав прерывание например от компоратора как описано в документации, но непонятно как нужно оформить функцию обработки этого прерывания, и чем она будет отличаться от функции обработки других прерываний.

О взаимодействии между потоками пока вроде понятно.

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

Мог написать что - нибудь глупое, потому как в круг моих понятий scmRTOS пока входит очень туманно или вообще не входит.
dxp
Цитата(URANst @ Feb 20 2008, 20:56) *
. Затем каким то образом нужно создать самому как я понял scmRTOS_TARGET_CFG.h и scmRTOS_CONFIG.h, но как не ясно или их нужно тупо скопировать из примера автора и если что нада то менять.

Можно и так, и так. Как больше нравится. Чем плохо взять имеющиеся и откорретировать под свои потребности?

Цитата(URANst @ Feb 20 2008, 20:56) *
2. Почему в примерах автор добавляет
Код
void OS::SystemTimerUserHook() { }
void OS::IdleProcessUserHook() { }

В примерах для иллюстрации. Если они нужны, оставьте и используйте, не нужны, уберите. Все в ваших руках.

Цитата(URANst @ Feb 20 2008, 20:56) *
Так нужно делать всегда ?
3. Дальше >> понятно что для AVR передачу управления можно осуществить сгенерировав прерывание например от компоратора как описано в документации, но непонятно как нужно оформить функцию обработки этого прерывания, и чем она будет отличаться от функции обработки других прерываний.

О взаимодействии между потоками пока вроде понятно.

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

Есть полноценный рабочий пример. Под IAR. Возьмите его за основу.
  • Используйте в качестве "рыбы" для своего проекта.
  • Используйте его в качестве референса, когда возникает вопрос, как сделать ту или иную базовую вещь. Пока не придет собственное устойчивое понимание (тогда будете сразу делать по-своему).
  • Используйте его для исследования того, что реально происходит в процессоре, гоняя пример в отладчике.
URANst
Вот взял из примера , это обработчик прерывания, кторый как я понял работает для передачи управления, но каким образо не понятно :
Код
#pragma vector=TIMER1_COMPA_vect
OS_INTERRUPT void Timer1_period_ISR()
{
    OS::TISRW_SS ISRW;

    ENABLE_NESTED_INTERRUPTS();

    //--------------------------------------------------
    //
    //            Message test
    //
    //     Send data as message
    //
    TMamont m;           // create message content

    m.src  = TMamont::ISR_SRC;
    m.data = 10;
    MamontMsg = m;       // put the content to the OS::message object
    PORTB |= (1 << 4);
    MamontMsg.sendISR();    // send the message
}


В main разрешены 3 прерывания , а обработчик описан только один, че за .....
Код
int main()
{
    DDRB |= (1 << 4);

    TCCR1B |= (1 << WGM12);   // CTC mode
    OCR1A   = 10005;          //
    TCCR1B |= (1 << CS10);    // Timer1 run with prescaling 1
    TIMSK  |= (1 << OCIE1A);  // Timer1 OC interrupt enable--------------1 ое

    TCCR0 = 0x03;             // Start System Timer
    TIMSK |=  (1 << TOIE0);   //-----------------------------------------------2 ое

    ACSR |= (1 << ACBG) | (1 << ACIE); /* Ref ON, IE ON */  -----------3 е
    DDRB |= (1 << 3);                  /* AIN1*/


    OS::Run();
}
URANst
С предыдущим вроде разобрался, все оказалось проще чем я думал, вопросы о следующем:

Что зачит вот это
Код
ENABLE_NESTED_INTERRUPTS();

написанное в обработчике прерывания

Почему в main нету глобального разрешения прерывания, как без него может произойти прерывание от таймера да и вообще любое, или где то в ядре прописывается (я понял что так и есть ) ?,

Зачем нужны прерывания от системного таймера ?

Все относится к примерам из папки самой операционки.

И еще как в IAR можно изменить имя проекта ?
IgorKossak
ENABLE_NESTED_INTERRUPTS(); - разрешить вложенные прерывания.
Прерывания разрешаются при выполнении OS::Run(); которая, как изволите видеть, вызывается именно из main.
Прерывания от системного таймера нужны для выполнения функции Sleep() с параметром и для осуществления ожиданий сигналов, семафоров и прочих сервисов с задержкой.
Имя проекта можно заменить в проводнике Windows, а потом открыть новый проект в IAR.
URANst
Так получается что 1 прерывание сис таймера - это единица задержки для сервисов ?
Т.е. если кварц 8 МГц, то единица задержки для сервисов == 1/(8 MГц/64)*256 (установлен делитель 64 для TIMER0), тогда что возвращает GetTickCount == количество прерываний сис таймера с момента начала работы ОС ?

Зачем может понадобиться прерывать апаратное прерывание контроллера, разве это хорошо ?
spf
Цитата(URANst @ Feb 21 2008, 17:36) *
Так получается что 1 прерывание сис таймера - это единица задержки для сервисов ?
Т.е. если кварц 8 МГц, то единица задержки для сервисов == 1/(8 MГц/64)*256 (установлен делитель 64 для TIMER0), тогда что возвращает GetTickCount == количество прерываний сис таймера с момента начала работы ОС ?

Да и да.
Ответы на это есть в документации smile.gif
alux
Тоже решил попробовать эту ОС. Долго не мог решится из-за отсутствия опыта работы в С++. Хочу "прикрутить" эту ОС к своему проекту на Си. Кстати, как это правильней сделать : добавить мои исходники на Си к smcRTOS или можно еще как-то? Взял тестовый пример. Удачно скомпилился под Megs32. А под Megs324P выдает кучу ошибок (порядка 270). В чем засада, не пойму. Ведь Мега324 должна быть полностью совместима с Мегой32 ?

PS. Может быть проблема в этом:
Цитата
I/O Mapping and SRAM
The I/O memory space contains 64 addresses for CPU peripheral control registers.
The ATmega164P/324P/644P I/O space and I/O range are changed and extended
compared to ATmega16/32. The extended I/O space goes from 0x60 to 0xFF in data
memory space where ST/STS/STD and LD/LDS/LDD instructions must be used.
The memory map is slightly different between the ATmega16/32 and the
ATmega164P/324P/644P due to extended I/O space. The ATmega164P/324P/644P
internal data SRAM addressing starts at 0x100 as opposed to 0x60 in ATmega16/32.

Это из документа
Цитата
AVR505: Migration between ATmega16/32 and ATmega164P/324P/644P


PS2///
Вот так всегда. Ломаешь голову второй день... А как-только напишешь в форум, так сразу нашел в чем проблема. У Меги324 в файле iom324p.h есть определения:
Цитата
/* SREG */
#define I 7
#define T 6
#define H 5
#define S 4
#define V 3
#define N 2
#define Z 1
#define C 0

Выдает первые ошибки на эти строки:
Код
Error[Pe040]: expected an identifier

        template<typename T, word size, class S> friend class channel;
        template<typename T>                     friend class message;

Т.е. я так понял имеется переопределение идентификаторов T и S.
Главное, что раньше я сталкивался с такой проблемой, когда присваивал переменным одну букву... И все-таки, как бы эту проблему "красиво" решить?

PS3. Я могу ошибиться, но разве OS_Kernel не имеет собственного namespace дабы избегать подобные проблемы? unsure.gif
dxp
Цитата(alux @ Mar 2 2008, 23:02) *
Выдает первые ошибки на эти строки:
Код
Error[Pe040]: expected an identifier

        template<typename T, word size, class S> friend class channel;
        template<typename T>                     friend class message;

Т.е. я так понял имеется переопределение идентификаторов T и S.
Главное, что раньше я сталкивался с такой проблемой, когда присваивал переменным одну букву... И все-таки, как бы эту проблему "красиво" решить?

PS3. Я могу ошибиться, но разве OS_Kernel не имеет собственного namespace дабы избегать подобные проблемы? unsure.gif

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

Ну, а в данном конкретном случае можно только "порадоваться" за разработчиков IAR, слепивших такую пакостную "бомбочку". Это ж надо догадаться задефайнить такие короткие имена! Как-то я на MSP430 тоже плотно присел, пока выяснял, что локальное имя N работает не так, как задумано - тоже иаровский дефайн был. Только там хитрее получилось - не так явно вылезало, ошибка была совсем не про конфликт имен. Препроцессор мастдай.
alux
Так что же мне делать в этом конкретном случае кроме как закоментировать проблемные дефайны в iom324p.h ? Как бы потом это не вылезло боком...
Может не в тему(вопроса) будет сказано. Нашел еще одну интересную ОС - eXtreme Minimal Kernel XMK
Почему-то на форуме о ней даже не упоминалось. Разработчик утверждает, что нет других мультизадачных preemtive free RTOS, которая будет работать в такой минимальной конфигурации (340 bytes of ROM and 18 bytes of RAM).
IgorKossak
Цитата(alux @ Mar 3 2008, 08:53) *
Может не в тему(вопроса) будет сказано. Нашел еще одну интересную ОС - eXtreme Minimal Kernel XMK

Судя по последним обновлениям (октябрь 2004) автор утратил к ней интерес.
А вот что касается потребностей памяти, то по-моему в этих прогнозах кое-что умалчивается, а именно, расход ОЗУ на стеки процессов.
alux
При включении моих исходников проекта на Си в проект smcRTOS (пример 1-EventFlag)возникла проблема.
Код
Error[e18]: Range error,  
PC offset out of range. Valid range is -4096 (-0x1000) to 4094 (0x0FFE).
File: D:\...\scmRTOS\AVR\OS_Target_asm.s90, Line: 234  
Source:      xjmp ContextSwitcher_ISR
  Where $ = #no label found# + 0x5C  [0x5C]
            in module "scmRTOS_Asm" (D:\...\1-EventFlag\Release\Obj\OS_Target_asm.r90),
            offset 0x5C in segment part 1, segment INTVEC
  What: #no label found# - ($ + 2) [0x1388]
  Allowed range: 0xFFFFF000 - 0xFFF
  Operand: #no label found# [0x13e6]
           in module scmRTOS_Asm (D:\...\1-EventFlag\Release\Obj\OS_Target_asm.r90),
           Offset 0x0 in segment part 2, segment CODE

Мой проект пока состоит из последовательного вызовов функций инициализации различной переферии. Ошибка возникает при подключении lcd_Init(); Отдельно мой проект отлажен под Мега324P. Занимал около 20кБ ROM и около 500 байт RAM. Пробовал изменить на Мега 644P и изменить размеры CSTACK, RSTACK. Не помогло. Как решить эту проблему?
alux
Цитата(IgorKossak @ Mar 3 2008, 15:53) *
Судя по последним обновлениям (октябрь 2004) автор утратил к ней интерес.

В overview XMK, которое можно найти в исходниках указана сегодняшняя дата (March 4, 2008). Выглядит подозрительно wink.gif
Сергей Борщ
Цитата(alux @ Mar 4 2008, 16:13) *
Мой проект пока состоит из последовательного вызовов функций инициализации различной переферии.
Каким-то образом обработчик прерывания переключения контекста оказывается в месте, куда не "дотягивается" xjmp из области векторов. Судя по ограничению +/- 4К вместо макроса xjmp подставляется RJMP, хотя нужен JMP. Глянул в исходники:
Код
#if (A90_PROC_OPTION == 0) || (A90_PROC_OPTION == 1)
#define xcall   rcall
#define xjmp    rjmp
#else
#define xcall   call
#define xjmp    jmp
#endif
Где определяется A90_PROC_OPTION я не нашел - ни в исходниках, ни в описании ассемблера. Неопределенный символ считается равным нулю. Можно предположить, что перед этим должна быть (утерянная) строка
Код
#define A90_PROC_OPTION   ((__TID__ >> 4) & 0x0F)
dxp завтра разберется, откуда вылезла эта бага.
alux
Имеет ли значение уровень оптимизации компилятора для smcRTOS ? Я всегда ставлю MAX.
IgorKossak
Цитата(alux @ Mar 4 2008, 17:43) *
В overview XMK, которое можно найти в исходниках указана сегодняшняя дата (March 4, 2008). Выглядит подозрительно wink.gif

Сдаётся мне, что если откроете overview завтра, то там окажется завтрашнее число. cool.gif

Цитата(alux @ Mar 4 2008, 18:09) *
Имеет ли значение уровень оптимизации компилятора для smcRTOS ? Я всегда ставлю MAX.

И я тоже.
dxp
Цитата(Сергей Борщ @ Mar 4 2008, 22:02) *
Где определяется A90_PROC_OPTION я не нашел - ни в исходниках, ни в описании ассемблера. Неопределенный символ считается равным нулю. Можно предположить, что перед этим должна быть (утерянная) строка
Код
#define A90_PROC_OPTION   ((__TID__ >> 4) & 0x0F)

Действительно, косяк. Как он вкрался, не помню хоть убей. Пофиксено.

P.S. Лишний раз "порадовался" "красоте" иаровского подхода - поменял имя МК и началось... Регистра TIMSK там уже нет, вместо него есть TIMSK0 и т.д. Я понимаю, что одного регистра не хватает, но зачем было имя-то менять? Пусть бы для совместимости осталось. То же самое касается TCCR0, которого уже тоже нет, а есть два с буквами на конце. Почему бы было не оставить для совместимости старое имя? Не понимаю. И уж зачем вообще было менять имя вектора ANA_COMP_vect на ANALOG_COMP_vect? А если заменили, то и вставили бы код для совместимости. И такие косяки у них с незапамятных времен. Такое впечатление, что о портировании кода даже внутри семейства они вообще не думают, а заголовки для разных процов лабают разные люди, которые никак свои действия друг с другом не координируют. sad.gif

Про упомянутое

/* SREG */
#define I 7
#define T 6
#define H 5
#define S 4
#define V 3
#define N 2
#define Z 1
#define C 0

я вообще молчу - яркий образчик бездумного подхода и безответственности. Причем что интересно - это не у всех процов есть такое определение, а только у избранного круга:

iom164.h
iom324.h
iom644.h
iom644p.h
iopwm2.h
iopwm3.h

Т.е. больше похоже на чью-то личную инициативу, нежели на общий подход. Бардак, короче. sad.gif
alux
Цитата(dxp @ Mar 5 2008, 08:55) *
Регистра TIMSK там уже нет, вместо него есть TIMSK0. Бардак, короче. sad.gif
Вот только мне не понятно для чего добавили 0 в имена битов SPI ?
Цитата
/* SPSR */
#define SPIF0 7
#define WCOL0 6
#define SPI2X0 0

/* SPCR */
#define SPIE0 7
#define SPE0 6
#define DORD0 5
#define MSTR0 4
#define CPOL0 3
#define CPHA0 2
#define SPR01 1
#define SPR00 0
Не может же быть несколько SPI-интерфейсов на борту... Или может? Мне кажется, что это они скорее сделали по привычке... Хотя в даташите указаны "правильные" имена. По поводу регистра TIMSK0... Почему не исправлено? Добавить то всего три строчки.
Цитата
#ifndef TIMSK0
#define TIMSK TIMSK0
#endif

Если гора не идет к Магомету, то Магомет идет к горе wink.gif
Цитата(dxp @ Mar 5 2008, 08:55) *
Действительно, косяк. Как он вкрался, не помню хоть убей.
Еще не начал работать с OS, а уже отлавливаю косяки. То ли еще будет wink.gif
Цитата(IgorKossak @ Mar 5 2008, 00:09) *
Сдаётся мне, что если откроете overview завтра, то там окажется завтрашнее число. cool.gif
Точно smile.gif
Цитата(alux @ Mar 4 2008, 20:09) *
Имеет ли значение уровень оптимизации компилятора для smcRTOS ?

Дело в том, что у jacOS есть примечание для IAR C/EC++ for AVR 4.11A/W32 :
Цитата
Вероятны проблемы при установке опций оптимизации Code motion (?) Cross Call (?)

И последнее. Рискну задать глупый вопрос. Если в проекте используется прерывание, но оно не является источником события для процессов, обязательно ли обработчик прерывания должен иметь тип OS_INTERRUPT и на входе ISR создавать объект OS::TISRW_SS ISRW ? И можно ли разрешать глобальные прерывания до OS::Run() ?
Сергей Борщ
Цитата(alux @ Mar 5 2008, 09:36) *
Вот только мне не понятно для чего добавили 0 в имена битов SPI ?
Не может же быть несколько SPI-интерфейсов на борту... Или может?
Я думаю что может. И скорее всего добавление 0 вызвано тем, что они начали таки движение в сторону унификации. Для новых кристаллов биты и регистры будут "пронумерованы" а старые потихоньку вымрут. Процесс перехода всегда сложный, но что поделать...
Цитата(alux @ Mar 5 2008, 09:36) *
Еще не начал работать с OS, а уже отлавливаю косяки. То ли еще будет wink.gif
Вы же понимаете, что мы не можем проверить наши исходники на всех возможных процессорах sad.gif Мы можем только предполагать, что раз программа работает на ATmegaX (LPC2xxx, BFxxx и т.д.), то она будет работать и на ATmegaY (LPC2yyy, BFyyy и т.д.), потому что все AVR (LPC2, BF и т.д.) похожи. К сожалению, они похожи не во всем, отсюда и такие косяки. Чем больше их найдете вы - тем меньше их останется тем, кто будет использовать ее после вас. Кроме того, я, например, уже не работаю с ADuC70xx и STR71x, порты для которых написал. Для STR71x у меня не осталось железа, на котором я мог бы проверить вносимые изменения. Поэтому исправления найденных в других портах багов или улучшения переносятся в этот порт без проверки. Вы бы что предпочли - старую версию, в которой есть известные глюки или новую, в которой они исправлены, но, возможно, внесены новые? wink.gif

Цитата(alux @ Mar 5 2008, 09:36) *
Рискну задать глупый вопрос. Если в проекте используется прерывание, но оно не является источником события для процессов, обязательно ли обработчик прерывания должен иметь тип OS_INTERRUPT и на входе ISR создавать объект OS::TISRW_SS ISRW ?
При отсуствиии в этом прерывании разрешения вложенных - нет, не обязательно. Но помните о размере стека - прерывание будет работать на стеке текущего процесса, значит стеки всех процессов надо будет увеличить на размер, требуемый прерыванию. Если использовать ISRW_SS, то для прерываний организуется отдельный стек, но за это надо платить увеличением времени обработки прерывания.
Цитата(alux @ Mar 5 2008, 09:36) *
И можно ли разрешать глобальные прерывания до OS::Run() ?
Если в разрешаемых прерываниях не используются сервисы ОС, их обработчики не имеют OS::TISRW(__SS), то можно, но зачем?
dxp
Цитата(alux @ Mar 5 2008, 13:36) *
Почему не исправлено? Добавить то всего три строчки.

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

Цитата(alux @ Mar 5 2008, 13:36) *
Если гора не идет к Магомету, то Магомет идет к горе wink.gif

sad.gif

Цитата(alux @ Mar 5 2008, 13:36) *
Еще не начал работать с OS, а уже отлавливаю косяки. То ли еще будет wink.gif

Уже начали. smile.gif Кстати, у меня эта ошибка не проявилась. Видимо, потому, что вектор достал до адреса. В любом случае этот не опасный баг - он отлавливается на этапе сборки. Вот те, которые при сборке не проявляются, а на рантайме вылезают - эти опасные.

Цитата(alux @ Mar 5 2008, 13:36) *
И можно ли разрешать глобальные прерывания до OS::Run() ?

Можно. Но зачем? Ведь после OS::Run() управление будет передано сразу процессам, они загрузят свои стеки и свои значения регистра SREG, поэтому разрешение прерываний до запуска оси смысла имеет мало. Если только не планируете долго сидеть до запуска, чтобы успеть по прерываниям попрыгать.
Сергей С.
Цитата(Сергей Борщ @ Mar 5 2008, 14:49) *
Но помните о размере стека - прерывание будет работать на стеке текущего процесса, значит стеки всех процессов надо будет увеличить на размер, требуемый прерыванию.


Как узнать максимальное потребление стека задачами? Можно ли проинициализировать все стеки каким-либо значением вроде 0xA5, как в FreeRTOS, и главное, как сделать это проще всего в scmRTOS? Или например в бутлоадере забить всю память байтами 0xA5, а затем прыгать на начало основной программы?
Сергей Борщ
Цитата(Сергей С. @ Mar 6 2008, 08:53) *
Как узнать максимальное потребление стека задачами? Можно ли проинициализировать все стеки каким-либо значением вроде 0xA5, как в FreeRTOS, и главное, как сделать это проще всего в scmRTOS?
Память стеков уже по умолчанию проинициализирована значением, только не 0xA5 а 0. В портах для ARM на верхушке контекста лежит содержимое CPSR, который не может быть равен нулю (конструкция у него такая). Поэтому я смотрю визуально (отладчиком или через терминал), где первый "не-ноль" - там было занято. Если есть желание забить каким-то своим значением - допишите memset в конструктор TProcess.
Цитата(Сергей С. @ Mar 6 2008, 08:53) *
Или например в бутлоадере забить всю память байтами 0xA5, а затем прыгать на начало основной программы?
Не пройдет - стеки попадают в область глобальных неинициализированных переменных и cstartup обнуляет эту область.
Сергей С.
Цитата(Сергей Борщ @ Mar 6 2008, 17:26) *
Если есть желание забить каким-то своим значением - допишите memset в конструктор TProcess.


Всего одна строчка кода - а сколько пользы! Вся память как на ладони. Спасибо.
alux
Мне не понятно, почему для генерации программного прерывания удобней использовать прерывание от аналогового компаратора? На мой взгляд лучше и удобней использовать внешнее прерывание. Во-первых, приоритет у него выше. Во-вторых, у большинства мк есть прерывния PCINT (pin change interrupt). Таким образом можно назначить практически любой вывод под это дело. В третьих, для переключения вывода в противоположное состояние достаточно записать PINy |= (1<<PINx); т.е. атомарную операцию SBI PINy, X. И было бы неплохо, чтобы в конфигурационном файле была возможность выбора источника программного прерывания: от компаратора или от внешнего прерывния. И также назначить символические имена выводам, которые используются для прерывания. В общем, максимально "загрузить" препроцессор, чтобы пользователь делал минимум телодвижений. И ошибок...
Сергей Борщ
Цитата(alux @ Mar 9 2008, 23:15) *
Мне не понятно, почему для генерации программного прерывания удобней использовать прерывание от аналогового компаратора? На мой взгляд лучше и удобней использовать внешнее прерывание. Во-первых, приоритет у него выше.
Исторически сложилось, как самое ненужное. Приоритет ему как раз нужен в идеале самый низкий. ReAl в своем порте реализовал переключение по прерыванию SPM, в этом случае нога не нужна вообще.
Цитата(alux @ Mar 9 2008, 23:15) *
В третьих, для переключения вывода в противоположное состояние достаточно записать PINy |= (1<<PINx);
Посмотрите порт ReAl, в нем (до SPM) было реализовано именно такое поведение - если процессор умеет перекидывать ногу одной командой, компаратор срабатывал от каждого перепада.
Цитата(alux @ Mar 9 2008, 23:15) *
И было бы неплохо, чтобы в конфигурационном файле была возможность выбора источника программного прерывания: от компаратора или от внешнего прерывния.
Это и так реализовано - все функции, завязанные на прерывание переключения контекста находятся в файле scmROS_Target_cfg.h, который находится в папке приложения.
alux
Цитата(Сергей Борщ @ Mar 10 2008, 05:42) *
Приоритет ему как раз нужен в идеале самый низкий.

Почему?
Цитата(Сергей Борщ @ Mar 10 2008, 05:42) *
ReAl в своем порте реализовал переключение по прерыванию SPM, в этом случае нога не нужна вообще.
Так нужно как минимум три маш. цикла на переключение.

Пытаюсь настроить переключение по прерыванию PCINT. Сделал следующее:
Код
#if scmRTOS_CONTEXT_SWITCH_SCHEME == 1

    INLINE inline void RaiseContextSwitch()
    {
      PINC |= (1 << PINC7);
//        PORTB |= (1 << 3); PORTB &= ~(1 << 3); // set flag
    }
    INLINE inline void BlockContextSwitch() { PCMSK2 &= ~(1 << PCINT23); } // disable interrupt
    //{ ACSR &= ~(1 << ACIE); } // disable interrupt

Как изменится в таком случае этот кусок?
Код
    class TNestedISRW              
    {                              
    public:                        
        INLINE TNestedISRW() : State(ACSR) { BlockContextSwitch(); __enable_interrupt(); }
        INLINE ~TNestedISRW()
        {
            __disable_interrupt();
            ACSR = State;
            if(State & (1 << ACI)) RaiseContextSwitch();
        }
                                                                            
    private:                                                                
        byte State;                                                          
    };

Не судите строго, я еще не совсем ориентируюсь в объектно-ориентированном программировании smile.gif
ReAl
Цитата(alux @ Mar 10 2008, 06:40) *
Почему?
Так нужно как минимум три маш. цикла на переключение.

Потому что если вдруг возникнет ещё какое-то прерывание от аппаратуры, оно должно быть обработано, а оно задержится на время переключения контекстов. У задачи, на которую идёт переключение, это прерывание от аппаратуры хоть так, хоть сяк (хоть до переключения, хоть сразу после) время заберёт, т.е. она всё равно реально начнёт работать позже. А само будет задержано переключателем контекстов.
А "три цикла"... По сравнению с сотнями на собственно переключение - что три (in/ori/out для SPMCSR в IO), что два (sbi PIN для компаратора либо PCINT), что четыре (sbi PORT/cbi PORT либо in/ldi/eor/out для компаратора на старых кристаллах), что пять (lds/ori/sts для SPMCSR вне адресов IO) - на практике несущественны.

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


Цитата
Пытаюсь настроить переключение по прерыванию PCINT. Сделал следующее:
Код
    INLINE inline void BlockContextSwitch() { PCMSK2 &= ~(1 << PCINT23); } // disable interrupt
    //{ ACSR &= ~(1 << ACIE); } // disable interrupt

Кстати, где как, но если уж считать такты, то у меги168 PCMSK* расположены в области памяти и работа с ними сожрёт весь выигрыш от sbi PIN по сравнению с in SPMCSR/ori/OUTSPMCSR. А в tiny85 у меня как-то рука не поднимается scmRTOS ставить, на 512 байтах ОЗУ только демонстрашку сделать можно.
Да и там работа с SPMCSR и PCINT по сумме баллов мало отличаться будут.

Я бы для PCINT сделал что-то в духе (для той же меги168), всё равно остальные PCINT из этого порта как входы прерываний использовать нельзя:
Код
#define PCINT_PIN C,7,H
#define PCINT_BIT PCIE2

#if scmRTOS_CONTEXT_SWITCH_SCHEME == 1

    INLINE inline void RaiseContextSwitch() { CPL(PCINT_PIN);  }

    INLINE inline void BlockContextSwitch() { PCICR &= ~(1 << PCINT_BIT); } // disable PCINT interrupt

    class TNestedISRW
    {
    public:
        INLINE TNestedISRW() : State(PCICR) { BlockContextSwitch(); sei(); }
        INLINE ~TNestedISRW()
        {
            cli();
            if( State & (1 << PCINT_BIT) )
                PCICR |= (1 << PCINT_BIT);
        }
    private:
        byte State;
    };

Естественно, где-то в начале (до вызова OS::Run()) надо настроить разрешение для нужной ноги.
alux
Спасибо за разъяснение. Маленькое уточнение: sbi PINC, 0x01 занимает один такт. В моем проекте на Меге324P есть один лишний вывод на PC7. Вот я к PCINT и "привязался". Хотя после вышесказанного возможно настрою на SPM. Вывод дороже... Прокомментируйте, пожалуйста, этот код
Цитата(ReAl @ Mar 10 2008, 13:08) *
class TNestedISRW
{
public:
INLINE TNestedISRW() : State(PCICR) { BlockContextSwitch(); sei(); }
INLINE ~TNestedISRW()
{
cli();
if( State & (1 << PCINT_BIT) )
PCICR |= (1 << PCINT_BIT);
}
private:
byte State;
};[/code]
Не понятно в строке конструктора : State(PCICR).

PS. Кстати, только что обратил внимание, у Меги324P SPMCSR находится по адресу 0x37. Т.е можно применять команды IN / OUT. А PCMSK2 - 0x6D. Т.е. уже только ST/STS/STD и LD/LDS/LDD. Наверное в данном случае лучше применить прерывание от SPM .

PS2. Решил попробовать вариант с SPM.
Код
#if defined(SPMCSR)
  #define SPM_CONTROL_REG SPMCSR
#elif defined(SPMCR)
  #define SPM_CONTROL_REG SPMCR
#else
  #error "SPM control register not defined"
#endif
Компилятор выдает ошибку
Код
Fatal Error[Pe035]: #error directive: "SPM control register not defined"
Хотя файл <ioavr.h> подключен. Как правильно определить SPMCSR ? Неужели просто перед #if defined(SPMCSR) написать #define SPMCSR ? 05.gif
ReAl
Цитата(alux @ Mar 10 2008, 11:56) *
Маленькое уточнение: sbi PINC, 0x01 занимает один такт. В моем проекте на Меге324P есть один лишний вывод на PC7. Вот я к PCINT и "привязался". Хотя после вышесказанного возможно настрою на SPM. Вывод дороже...
Я уже думал, что я серьёзно отстал от жизни и mega324P отличается от других AVR-ок.
Глянул в описание - sbi/cbi выполняются за два такта. Под рукой нет для проверки.

Цитата
Прокомментируйте, пожалуйста, этот код
Не понятно в строке конструктора : State(PCICR).

Инициализация поля State значением PCICR (это не совсем одно и то же, что State = PCICR; в теле конструктора).

Цитата
PS2. Решил попробовать вариант с SPM.
...
Компилятор выдает ошибку
Код
Fatal Error[Pe035]: #error directive: "SPM control register not defined"
Хотя файл <ioavr.h> подключен. Как правильно определить SPMCSR ? Неужели просто перед #if defined(SPMCSR) написать #define SPMCSR ? 05.gif
Кто такой ioavr.h ?
Имеется ввиду <avr/io.h> ?

Только что взял снапшот http://scmrtos.ginps.com/scmrtos-avr-gcc-snapshot.rar
распаковал, заменил в 1-EventFlag/makefile тип процессора:
MCU := atmega324p
всё скомпилировалось.
Не знаю, в чём может быть проблема.


--------
Тьху, это, наверно, IAR ?
Ну тогда ничем не могу помочь, у меня его на компе нет.
alux
Цитата(ReAl @ Mar 10 2008, 19:36) *
Глянул в описание - sbi/cbi выполняются за два такта.
Прошу прощения. Заработался ..Не туда глянул 01.gif Действительно 2 такта.
А вопрос с ошибкой компиляции я перенес в другой (IAR) форум. Не могу одолеть. Ну в смысле, если просто везде заменить TIMSK на TIMSK0 , то все OK. А вот для SPMCSR просто тупо вставил
Код
#include <ioavr.h>
#define SPMCSR

#if defined(SPMCSR)
  #define SPM_CONTROL_REG SPMCSR
#elif defined(SPMCR)
  #define SPM_CONTROL_REG SPMCR
#else
  #error "SPM control register not defined"
#endif
Но это же за уши притянуто.. Хочется принципиально для себя решить этот вопрос.
Сергей Борщ
Цитата(alux @ Mar 10 2008, 18:00) *
Но это же за уши притянуто.. Хочется принципиально для себя решить этот вопрос.
Увы, 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
Возникло пару вопросов. Допустим, есть три процесса: 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
Цитата(alux @ Mar 11 2008, 18:37) *
И второй вопрос скорее по 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;
...



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


extern OS::TEventFlag ef;

где тут данное-член? И вообще, где тут член класса? Тут объект ef типа OS::TEventFlag объявлен как extern. Все в норме.
Сергей Борщ
Цитата(alux @ Mar 11 2008, 14:37) *
3) После окончания ввода времени, EditTime и Blink снова переводятся в ожидание, а KeyMenu переходит в состояние готовности.
А смысл делать EditTime отдельным процессом? Мне кажется он просится функцией (возможно - функцией-членом класса TTimer), вызываемой из KeyMenu, но никак не отдельным процессом. Что касается Blink - он может ожидать некое сообщение (скажем, BLINK_ON) и получив его - мигать, пока не получит сообщение BLINK_OFF. Он может также получать сообщения SHORT_FLASH (одна короткая вспышка), LONG_FLASH (одна длинная вспышка) и другие, или сообщение может представлять из себя структуру, в которой записаны частота мигания, скважность, длительность.
alux
Цитата(Сергей Борщ @ Mar 11 2008, 20:48) *
А смысл делать 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 ? Можно поподробней? Желательно с примером. Заранее спасибо.
Сергей Борщ
Цитата(alux @ Mar 11 2008, 22:31) *
Так изначально у меня ф-ция EditTime вызывалась из функции пункта меню "Настройка -> Время" . В этой ф-ции в вечном цикле опрашивались кнопки и происходило редактирование времени. Естественно, контроллер больше ничего не делал, кроме прерываний... Поэтому я решил сделать EditTime отдельным процессом.
Так ведь и теперь у вас он во время EditTime не делает ничего в TKeyMenu. При этом у вас получилось, что EditTime работает изредка, а память под свой стек занимает всегда. Если бы вы сделали его функцией и вызывали из TKeyMenu - в остальное время этот стек мог бы быть использован другими частями TKeyMenu.
Цитата(alux @ Mar 11 2008, 22:31) *
А что такое функция-член класса TTimer ? Можно поподробней?
Это я прочитал рекомендовавшуюся здесь книжку Гради Буча "Объектно-ориентированный анализ и проектирование" и пытаюсь применять полученные там знания. Он рекомендует вычленять в предметной области законченные абстракции и облекать их в вид классов. Вот у вас в системе судя по описанию есть некие часы, которые можно выделить в отдельную абстракцию. Они умеют считать время, их можно спросить "который час", их можно установить. Их можно реализовать в виде класса (обозвать его TTimer), который будет иметь только две открытые функции GetTime(), SetTime(). Внутрь этого класса спрятать (сделать private) счетчик времени и тем самым гарантировать, что никто его случайно не испортит - только вызвав явно SetTime(). Применяя в системе разные внешние микросхемы часов или программные часы, вам придется переписать только реализацию этого класса, вся остальная часть программы не будет зависеть от физической реализации часов.
Вот изложил все это и понял, что функция EditTime имеет отношение скорее к интерфейсу пользователя, чем к часам, ее нет смысла делать членом класса TTimer, скорее она должна в конце своей работы вызвать SetTime().
alux
Цитата(Сергей Борщ @ Mar 12 2008, 02:31) *
Так ведь и теперь у вас он во время 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, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс... Но пока не знаю, как это реализовать. Опыта с ++ маловато.
Сергей Борщ
Цитата(alux @ Mar 12 2008, 08:48) *
Как бы использовать один процесс TKey, только для редактирования времени выполнять одно действие, а для навигации меню - другое.? Подозреваю, что здесь напрашивается создать класс...
Могу предложить сделать абстрактный базовый класс с виртуальными функциями (методами) ActionUp(), ActionDown(), ActionLeft() ....От него унаследовать TMainMenu, TEditTime и остальные, в которых переопределить эти методы. Посмотрите пример 3 из комплекта ОСи - там как раз такой подход реализован при кормлении слонов.
dxp
Цитата(alux @ Mar 12 2008, 12:48) *
Согласен. Мой способ мне самому не нравится. Процессов не хватит на каждый пункт меню. В 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 - одна из самых подходящих областей для применения ООП, где преимущества ООП перед процедурным программированием являются просто гиганскими. smile.gif

Что касается конкретно ОС, то вот этот код обработки пунктов меню имеет смысл поместить в один процесс (как правило, низкоприоритетный), который получает сообщения от источников входной информации - кнопок и др. Процесс висит в саспенде, ждет сообщения. Получил сообщение, проснулся, обработал его, упал в ожидание следующего.
alux
Реализация меню - это отдельная тема для разговора. Если бы я сейчас начинал свой проект, то, возможно, и применил бы ОО подход в создании меню. Правда, пока не понятно, как это усложнится в случае иерархического меню. У меня меню создается макросом MAKE_MENU (Name, Next, Previous, Parent, Sibling, SelectFunc, EnterFunc, Text). Структура меню видна, как на ладони. И главное, что это уже отлажено и работает.
Цитата(dxp @ Mar 12 2008, 13:06) *
вот этот код обработки пунктов меню имеет смысл поместить в один процесс (как правило, низкоприоритетный)

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

PS. Ничего лучшего, кроме как при входе в меню "Настройка -> Время" установить обычный глобальный флаг (не OS::EventFlag), по которому в процессе TKey определять какие действия необходимо делать при нажатии на кнопки, не придумал. Спрашивается. Зачем было городить виртуальные функции с абстрактными классами? 05.gif
Сергей Борщ
Цитата(alux @ Mar 12 2008, 12:45) *
PS. Ничего лучшего, кроме как при входе в меню "Настройка -> Время" установить обычный глобальный флаг (не OS::EventFlag), по которому в процессе TKey определять какие действия необходимо делать при нажатии на кнопки, не придумал. Спрашивается. Зачем было городить виртуальные функции с абстрактными классами? 05.gif
Мимо. Заводите объекты ваших классов-потомков, заводите указатель на абстрактный класс 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
Спасибо, Сергей. Сам бы до этого не додумался. beer.gif
Я думаю достаточно будет EventFlag вместо message. Скан-код хранится в глобальной переменной.
Еще один момент. В функции EditTime используются цифровые кнопки (0...9), а для навигации по пунктам меню - нет. В конструкции switch в ветке default при редактировании времени вводятся цифры. В базовом абстрактном классе необходимо создавать все виртуальные функции, которые используются потомками? Если да, то в TMainMenu нужно просто создать вирт. ф-цию Default {} с пустым телом?
Цитата(Сергей Борщ @ Mar 12 2008, 16:30) *
Потом увидете, что все пункты меню можно построить точно так же как и TMainMenu, а красивую и понятную таблицу переходов между ними можно продолжать генерить макросом.
Это к разговору о создании иерархического меню?

PS.
Цитата(Сергей Борщ @ Mar 12 2008, 16:30) *
if(!Keyboard.Wait(timeout))
wait нужно писать с маленькой буквы wink.gif

PS2. А на счет функции Blink, которая нужна при редактировании времени, то на мой взгляд будет проще ее запускать отдельным таймером. К проекту подключен timer.c, в котором задан массив системных таймеров, с помощью которых можна запускать функции с заданным интервалом времени.
Сергей Борщ
Цитата(alux @ Mar 12 2008, 15:24) *
В конструкции switch в ветке default при редактировании времени вводятся цифры. В базовом абстрактном классе необходимо создавать все виртуальные функции, которые используются потомками? Если да, то в TMainMenu нужно просто создать вирт. ф-цию Default {} с пустым телом?
Думаю, да. Обозвать ее как-то вроде Numeric(). Причем если система попискивает на нажатие кнопки, то можно в базовом классе определить пару невиртуальных функций, BeepOk() и BeepError() и в реализации Numeric() для TMainMenu вызывать BeepError(), а в "правильных" обработчиках - BeepOk().

Цитата(alux @ Mar 12 2008, 15:24) *
Это к разговору о создании иерархического меню?
Да.
alux
Сделал так как Сергей сказал. За исключением того, что вместо 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.gif

PS. Проверил этот же проект без ОС и с ОС(jacOS) - все работает нормально: отрабатывает одно нажатие. Значит проблема в этой системе. Возможно, я в упор не вижу слона, а со стороны вам будет виднее. wink.gif Так в чем же может быть проблема? Второй день копаю... sad.gif
alux
Проблема с повтором решена. Спасибо Сергею Борщу. Проблема была в разрешении вложенных прерываний в ISR Timer2.
alux
Как узнать точное использование стека процессами? А то я все делаю вслепую: выставил 100 - работает. Добавил ф-цию - зависает... Использую AVR (Mega324P). JTAG исключен из-за отсутствия свободных выводов. В симуляторе тоже проблематично (как получить ответ от перефирии I2C, SPI, ?..). Было бы совсем неплохо, если бы средствами ОС была возможность в отладочном режиме посмотреть размер неиспользуемого стека каждой задачи через терминал. Такую возможность я увидел в ОС нашего соотечественника uOS. Там для этой цели используется ф-ция task_stack_avail().

И второе. По поводу ф-ции Blink в режиме редактирования времени. Я это реализовал с использованием очереди сообщений, как в примере 3- Channel. Создал от базового класса TMsg объект Blink с методом InvertRect(...); Вопрос, собственно, заключается в следующем : Как обеспечить периодический MsgQueue.push(&Blink) ? Конечно, можно для этой цели использовать отдельный таймер, но хотелось бы обойтись системным таймером. Но не хочу использовать SystemTimerUserHook() по понятным причинам. Может воспользоваться OS::GetTickCount(); которая, на сколько я понял, возвращает общее количество тиков с момента запуска ОС ? Тогда при входе в п. меню "Редактирование" сохранить T = OS::GetTickCount(), а в низкоприоритетном процессе LCDProc отсчитывать от значения T 500 тиков (500*2=1сек) и тогда класть сообщение Blink в очередь. Также нужно учитывать переполнение счетчика. Что скажете?
dxp
Цитата(alux @ Mar 18 2008, 13:50) *
Как узнать точное использование стека процессами?

Сергей Борщ советовал добавить в конструктор процесса memset, которая заполнит стековую память указанным значением. Стек для эксперимента надо взять с запасом. После этого делается прогон программы и анализируется глубина потребления стека.

Цитата(alux @ Mar 18 2008, 13:50) *
Вопрос, собственно, заключается в следующем : Как обеспечить периодический MsgQueue.push(&Blink) ? Конечно, можно для этой цели использовать отдельный таймер, но хотелось бы обойтись системным таймером. Но не хочу использовать SystemTimerUserHook() по понятным причинам. Может воспользоваться OS::GetTickCount(); которая, на сколько я понял, возвращает общее количество тиков с момента запуска ОС ? Тогда при входе в п. меню "Редактирование" сохранить T = OS::GetTickCount(), а в низкоприоритетном процессе LCDProc отсчитывать от значения T 500 тиков (500*2=1сек) и тогда класть сообщение Blink в очередь. Также нужно учитывать переполнение счетчика. Что скажете?

А что является инициатором (источником события) этого блинка? Вот оно и должно пихать в очередь сообщение. Если надо просто периодически мигать, то запуск по счетчику тиков, имхо, не самое плохое решение (если в отдельный процесс не выносить, а запускать из имеющегося, выполняющиегося с заданной периодичностью).
alux
Цитата(dxp @ Mar 18 2008, 13:02) *
Сергей Борщ советовал добавить в конструктор процесса memset, которая заполнит стековую память указанным значением. Стек для эксперимента надо взять с запасом. После этого делается прогон программы и анализируется глубина потребления стека.
Что такое memset ? Можно поподробней?
Цитата(dxp @ Mar 18 2008, 13:02) *
А что является инициатором (источником события) этого блинка? Вот оно и должно пихать в очередь сообщение.
Инициатором Blink является вход в п. меню "Настройка", в котором меняется режим работы клавиатуры (вернее процесса ScanKey):
..............................
CurrentMode = &EditTime;
}
Цитата(dxp @ Mar 18 2008, 13:02) *
если в отдельный процесс не выносить, а запускать из имеющегося, выполняющееся с заданной периодичностью).
Т.е. есть необходимо, чтобы был отдельный процесс, который запускается с заданной периодичностью? Я думал для этой цели использовать низкоприоритетный процесс LCDProc, а заданную периодичность в нем же осуществлять проверкой OS::GetTickCount()? и каждые 500 тиков в этом же процессе делать MsgQueue.push(&Blink). Но почему-то это не сработало:
Код
OS_PROCESS void TProcLCD::Exec()    //TProc5
{
    for(;;)
    {
      CurrentTick = OS::GetTickCount();
      
      if(((CurrentTick-T) > 500)&&(CurrentMode == &EditTime))
      {
        MsgQueue.push(&Blink);
        T = CurrentTick;
      }
      
      TMsg* msg;
      
      MsgQueue.pop(msg);
      msg->run();
    }
}
dxp
Цитата(alux @ Mar 18 2008, 15:38) *
Что такое memset ? Можно поподробней?

Функция такая. smile.gif

Цитата(alux @ Mar 18 2008, 15:38) *
Инициатором Blink является вход в п. меню "Настройка", в котором меняется режим работы клавиатуры (вернее процесса ScanKey):

Ну, вот из этого места и надо метать сообщение в очередь.

Я имел в виду нечто иное. Когда запускается Blink, то это самостоятельный процесс (просто запускаемый в качестве job), который сам отмеряет все времена и управляет требуемой периферией. Т.е. метнули сообщение в очередь, на том конце очереди сообщение извлекается и вызывается соответствующая функция, которая весь процесс мигания и осуществляет. Отработала, закончилась. Обработчик очереди готов к обработке следующего сообщения. Т.е. вся низкоуровневая работа по формированию времен возложена на виртуальную функцию объекта Blink. А код входа в меню только лишь мечет команду сделать это (путем помещения указателя на объект: &Blink).

Удобнее всего формировать времянки с помощью системных средств - той же функции Sleep(), например. Но для этого надо выделить целый процесс, что жалко, т.к. само по себе действие нечастое. Так вот делегирование выполнения операции позволяет совместить приятное с полезным - выполнять код в отдельном процессе (с использованием слипов и прочего) и в то же время не выделять индивидуально процесс под одну операцию, а зашарить его между всеми "желающими" - естественно, все их "желания" будут выполняться по очереди. Если все успевает, то все хорошо. Т.е. если, скажем, во время блинка срочно надо еще что-то сделать, то тогда ой. А если нет, то и пусть себе мигает отведенное ему время. Т.е. насколько этот вариант подходит под ваши требования, вам виднее.
alux
Цитата(dxp @ Mar 18 2008, 13:02) *
После этого делается прогон программы и анализируется глубина потребления стека.
Уж извините за назойливость. А каким образом проанализировать глубину потребления стека? Вернее, как просмотреть содержимое стека?
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.