Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: AT90USB1287 не отрабатывает внешние прерывания
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
alux
Отлаженый проект переношу с MEGA324P на AT90USB1287. После соответствующей коррекции программы перестало отрабатывать нажатия на матричной клавиатуре 4х4. Изменилась схема подключения клавиатуры: раньше COL и RAW подключались к двум разным портам. Теперь занимают один PORTF. И, так как у AT90USB1287 только PORTB имеет PCINT (зачем было так ограничивать функциональность МК ?!!), то выводы RAW через диоды анодами подключены к выводу внешнего прерывания INT6 (PORTE6). Прерывания настраиваю как обычно:
Код
int main()
{
  // Initialise
  ACSR = (1<<ACD);  // Analog Comparator Disable
  .................
  PORTE = (1<<KEY_INT);  // INT6 -> input, pull-up - ON

  key_Init();
__enable_interrupt();    // set the Global Interrupt Enable Bit
.....................................
///////////////////////////////////////////

#define ENABLE_INT6   {EIFR |= (1<<INTF6); EIMSK |= (1<<INT6);}// Clear interrupt status flag, Enable external interrupt INT6
#define DISABLE_INT6  EIMSK &= ~(1<<INT6)    // Disable external interrupt INT6

void key_Init(void)
{
  /* Init ports */  
  KEYMATRIX_DDR = 0x0f;            // Set row lines to input, Set column lines to output  
  KEYMATRIX_PORT = 0xf0;    // Pull row lines high, Drive all column lines low  
    
  /* Enable external interrupt */
  ENABLE_INT6;            // Enable pin change interrupt INT6
}

Выяснилось, что не вызывается обработчик прерывания INT6. Проверяю по звуковому сигналу BEEP.
В симуляторе в AVRSTUDIO тоже прогнал - не вызываются прерывания: ни по низкому уровню, ни по фронтам (заднему и переднему). АЦП (PORTF) не использую. В чем дело?

PS. Схему и код выкладываю, чтобы не возникало лишних вопросов.
alux
Глас вопиющего в пустыне... У меня проект остановился из-за этой ерунды. Исчерпал все версии...
Жду хоть какой-нибудь ответ. Ребята, проявите, пожалуйста, активность.
Прошу прощения за bb-offtopic.gif
SysRq
Фьюз JTAGEN проверьте. Если включен JTAG (можно и программно отключать), он половину порта F съест (как раз ROW0...ROW3).
alux
Цитата(SysRq @ Aug 5 2008, 12:48) *
Фьюз JTAGEN проверьте.

JTAGEN=1 -> отключен.

Какие еще будут варианты?
GDI
Проверьте в симуляторе какие значения в регистры пишутся и реакцию там можно проверить. С таким камнем не работал, но внешние прерывания еще имеют настройки срабатывания по уровню, по спаду, по подъему и т.д. проверьте эти настройки для INT6
alux
По-умолчанию, при разрешении соответствующего внешнего прерывания, INTx настроен на срабатывание по низкому уровню. Пробовал разные настройки. Не в этом дело... В симуляторе (AVRSTUDIO) при симуляции внешнего прерывания (щелчок на PINE6) должен вызваться обработчик внешнего прерывния. А его нет sad.gif
GDI
В вшем коде не видно настройки DDRE, надеюсь на вход его переключили? Никакая периферия не включена, которая может PORTE занять? Если в АВРСтудии прерывание не наступает, нто значит где то в настройках ошибка. Вектор прерывания правильный? Кстати, прерывание не возникает, а флаг то взводится INT6?
alux
Цитата(GDI @ Aug 5 2008, 15:44) *
В вшем коде не видно настройки DDRE...
DDRE6 (INT6) = 0 (input). Я - не новичек smile.gif .
В Интернете пробегала информация, что у этого камня не правильно пронумерованы вектора прерывания. Прийду домой проверю, хотя на мой взгляд, эта версия очень уж невероятная...
GDI
Цитата
у этого камня не правильно пронумерованы вектора прерывания.

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

Цитата
Я - не новичек smile.gif
И на старуху бывает проруха smile.gif
sKWO
alux, прочитайте ответ Александра Редчука на телесиськах Вот оно, добавлять смысла нету
alux
Цитата(sKWO @ Aug 5 2008, 17:47) *
прочитайте ответ Александра Редчука на телесиськах
Этот пост я и раньше видел. Лишний раз подтверждает, что с прерываниями не все чисто. Но там у нее хоть одно прерывание срабатывало. У меня же ни в симуляторе, ни в железе не срабатывает ни по PCINT0, ни по INT0...INT7. При симуляции внешних прерываний никакие флаги прерываний не взводятся. Может стоит написать в поддержку Atmel?

PS.
Ради интереса создал тестовый проект для ATmega128. Те же условия, что и для AT90USB1287. Симулирую прерывания,- все срабатывают, как и положено. Нужно предъявлять рекламацию Atmel.
Или я чего-то не учел ?..
SysRq
А можно понтересоваться (в целях повышения образованности smile.gif ), у вас ОС какая-то используется?
Код
#pragma vector=INT6_vect
OS_INTERRUPT void INT6_ISR()
{
  OS::TISRW_SS ISRW;
  //...
}


Собрал на WinAVR простенький код, прерывание работает в отладчике AVRStudio правильно.

Код
#include <avr\io.h>
#include <avr\interrupt.h>

#define ENABLE_INT6   {EIFR |= (1<<INTF6); EIMSK |= (1<<INT6);}// Clear interrupt status flag, Enable external interrupt INT6
#define DISABLE_INT6  EIMSK &= ~(1<<INT6)    // Disable external interrupt INT6

volatile unsigned char a = 0, b = 0;

SIGNAL(INT6_vect)
{
    a++;    
    DISABLE_INT6;            // Prevent further external interrupts
}

int main(void)
{
    ACSR = (1 << ACD);  // Analog Comparator Disable
    PORTE = (1 << 6);  // INT6 -> input, pull-up - ON    
    
    sei();
    
    while(1)
    {
        b++;        
        ENABLE_INT6;
    }
    
    return 0;
}


Makefile стандартный, создан через MFile из комплекта WinAVR, и задан MCU = at90usb1287.
sKWO
Цитата(alux @ Aug 5 2008, 18:42) *
Этот пост я и раньше видел. Лишний раз подтверждает, что с прерываниями не все чисто. Но там у нее хоть одно прерывание срабатывало.

Читайте внимательно, второе не срабатывало изза того, что она перепутала порт на седьмое прерывание. Как по мне то INT даже удобнее и оперативнее чем РСINT(в качестве обработкаи) хотя, правда если захотите будить МК по нажатию любой кнопки то РСINT имеет преимущество. Поискал в нете, для винавр номера векторов в хедере отличны от ДШ, но это ничего страшного, всё работает. Есть правда какаято запара с вотчдогом, до конца не разобрался.
alux
Цитата(SysRq @ Aug 6 2008, 17:38) *
у вас ОС какая-то используется?

scmRTOS
Рекомендую!
Цитата(SysRq @ Aug 6 2008, 17:38) *
Собрал на WinAVR простенький код, прерывание работает в отладчике AVRStudio правильно.
А у меня тот же код, сгенеренный в IARе (ubrof8) для AT90USB1287 не работает в AVRStudio. Получается, что IAR v5.11B виноват? 05.gif
Я уже начал писать рекламацию Atmel.
SysRq
Цитата(alux @ Aug 6 2008, 19:43) *
Получается, что IAR v5.11B виноват?

Хм. Поставил под VirtualBox'ом на XPFundamentals себе IAR (30 дней триал) посмотреть что за зверь и за что его все любят smile.gif , собрал такой код:
Код
#include <ioavr.h>
#include <inavr.h>

#define INT6  6 // почему-то пришлось задать вручную; видимо, с IAR с разбегу не разобрался
#define INTF6 6 // почему-то пришлось задать вручную; видимо, с IAR с разбегу не разобрался

#define ENABLE_INT6   {EIFR |= (1<<INTF6); EIMSK |= (1<<INT6);}// Clear interrupt status flag, Enable external interrupt INT6
#define DISABLE_INT6  EIMSK &= ~(1<<INT6)    // Disable external interrupt INT6

volatile unsigned char a = 0, b = 0;

#pragma vector=INT6_vect
__interrupt void INT6_ISR()
{
    a++;    
    DISABLE_INT6;            // Prevent further external interrupts
}

int main( void )
{
    PORTE = (1 << 6);  // INT6 -> input, pull-up - ON    
    
    __enable_interrupt();    // set the Global Interrupt Enable Bit
    
    while(1)
    {
        b++;        
        ENABLE_INT6;
    }
    
    return 0;
}


При отладке в AVRStudio прерывание работает правильно! В опциях проекта ничего не менял, кроме установки --cpu=usb1287.

IAR:
Цитата
IAR Assembler for AVR 5.11B/W32 (5.11.2.5)
Translates Atmel assembler to IAR assembler 1.25 (1.2.4.109)
IAR C/C++ Compiler for AVR 5.11B/W32 [Evaluation] (5.11.2.3)
IAR XLINK 4.61C (4.61.3.0)
IAR Build Utility 5.1.1.453.7838 (5.1.1.453)

AVRStudio: 4.14 build 589
fmdost
По уровню? То-есть если на порту INTx = 0, то застрянет в прерывании? Так и надо? (Код не смотрел).
Поставте по переходу в 0.
alux
Цитата(Т.Достоевский @ Aug 7 2008, 03:44) *
если на порту INTx = 0, то застрянет в прерывании
Я уже говорил, что пробовал разные варианты (по фронту, спаду, по уровню). Не в этом дело. Читайте внимательно посты.

Цитата(SysRq @ Aug 7 2008, 02:20) *
Хм. Поставил под VirtualBox'ом на XPFundamentals себе IAR (30 дней триал) посмотреть что за зверь и за что его все любят smile.gif , собрал такой код:
.............
При отладке в AVRStudio прерывание работает правильно! В опциях проекта ничего не менял, кроме установки --cpu=usb1287.
Сделал то же самое (этот же пример кода, IAR v.5.11B, ubrof8) - не работает!!! Кто то из нас двоих не прав!
SysRq
Цитата(alux @ Aug 7 2008, 09:00) *
Сделал то же самое (этот же пример кода, IAR v.5.11B, ubrof8) - не работает!!! Кто то из нас двоих не прав!



Проект: http://stream.ifolder.ru/7620130

*тут смайл, который руками разводит*
alux
Проверить проект смогу вечером. Пока могу предположить две версии:
1) различия триальной и "вылеченной" версии IAR; (маловероятно)
2) вспомнил, что я раскомментировал определения PE0...PE7 в файле iousb1287.h; (более вероятно)
Это вызывало конфликт имени с PE1 (USART1). Я исправил PE1 на UPE1. Но, возможно, это еще на что-то повлияло. Надо проверить.
sKWO
Цитата(alux @ Aug 7 2008, 12:01) *
Проверить проект смогу вечером. Пока могу предположить две версии:
1) различия триальной и "вылеченной" версии IAR; (маловероятно)

не моловероятно, а исключено.
вылеч версия 4.21А нормально компилит и всё работает на ура.
Проджект прикрепляю.

Цитата(SysRq @ Aug 7 2008, 02:20) *
[code]
#define INT6 6 // почему-то пришлось задать вручную; видимо, с IAR с разбегу не разобрался
#define INTF6 6 // почему-то пришлось задать вручную; видимо, с IAR с разбегу не разобрался

SysRq, это всё решается настройками прожекта
Project\Options, на закладке General Options находим System, и здесь ставим галочку
напротив Enable bit definitions in I\O-include file.
alux
Цитата(sKWO @ Aug 7 2008, 13:12) *
не моловероятно, а исключено.
вылеч версия 4.21А нормально компилит и всё работает на ура.
Остается версия номер 2.

PS. Если не трудно, проверьте эту версию у себя. Т.е. в файле iousb1287.h раскомментировать определения для PE0...PE7 и изменить PE1 на UPE1.
SysRq
Цитата(alux @ Aug 7 2008, 14:16) *
...раскомментировать определения для PE0...PE7 и изменить PE1 на UPE1.
На этом тестовом коде эти изменения никак не отражаются, все работает.

--

Цитата(sKWO @ Aug 7 2008, 14:12) *
SysRq, это всё решается настройками прожекта
Благодарю! Этот пример выше был написан сильно с разбегу - за две минуты с момента установки IAR'а; сейчас я уже в курсе немного :)
alux
Цитата(SysRq @ Aug 7 2008, 17:08) *
На этом тестовом коде эти изменения никак не отражаются, все работает.

У меня с этими изменениями все-равно не работает sad.gif . Остается еще одно различие: у меня AVRStudio v.4.13. Качаю последнюю v.4.14. Там пофиксено:
Код
7163: The simulator subscripts for AT90USB128/64 and AT90USB162/82 have been fixed.
Хотя, на мой взгляд, это вряд ли поможет. Ведь в железе не работает!

Цитата(sKWO @ Aug 7 2008, 13:12) *
вылеч версия 4.21А нормально компилит и всё работает на ура.
Проджект прикрепляю.
У меня ни этот проект (у Вас AVRStudio какой версии?)
Цитата(SysRq @ Aug 7 2008, 08:53) *
... ни этот проект НЕ РАБОТАЕТ!!!
Это было бы смешно, если б не было так печально. Почему у меня не работают прерывания?

PS. Проблема решилась установкой последней версии AVRStudio v4.14 build 589. По крайней мере в симуляторе уже стал вызываться обработчик внешнего прерывания.
Спасибо всем за помощь.
SysRq
А в железе? smile.gif
alux
Цитата(SysRq @ Aug 7 2008, 21:17) *
А в железе? smile.gif
Именно этот тестовый пример работает. smile.gif
А с моим большим проектом буду разбираться дальше...
alux
Обработчик внешнего прерывания не вызывается из-за OS::Run();
Код
void main()
{
........................
  TCCR0B = 0x03;          // Start System Timer f_clk/64
  TIMSK0 |= (1<<TOIE0);   // Разрешить прерывания Timer0 по переполнению (OVF)
                          
  OS::Run();        // Период переполнения Timer0 при f_clk=7.3728 Mhz 2.222 ms
}

#pragma vector=INT6_vect
OS_INTERRUPT void INT6_ISR()
{
  OS::TISRW_SS ISRW;

  DISABLE_INT6;            // Prevent further external interrupts

  Int6.SignalISR();  

  PORTC |= (1<<BEEP);   // <<<<<<<<< Это для контроля входа в прерывание
}
А почему, не пойму никак. Раньше на Mega324P работало нормально... 05.gif

Этот вопрос скорее к отцам операционной системы scmRTOS.

PS. Проблема решилась опять-таки установкой последней версии scmRTOS v.3.05 smile.gif
Причина была скорее всего из-за следующего Bugfixes:
Код
EWAVR port: support for devices with more then 64 kbytes flash added in target assembler file.
alux
Снова проблема с внешним прерыванием. На этот раз при входе в POWER_DOWN не выходит из него при нажатии на кнопку. Сигнал внешнего прерывания от клавиатурной матрицы 4х4 подключен через аноды диодов на PE6(INT6). Прерывание INT6 настроено на low level. Вот пример кода:
Код
// Sleep Functions
#define SELECT_IDLE         SMCR &= ~((1<<SM0)|(1<<SM1)|(1<<SM2)) // Idle mode
#define SELECT_ADC          SMCR |= (1<<SM0)                      // ADC Noise Reduction Mode
#define SELECT_POWERDOWN    SMCR |= (1<<SM1)                      // Macro to select powerdown sleep mode
#define SELECT_POWERSAVE    SMCR |= (1<<SM0)|(1<<SM1)             // Power Save Mode
#define SELECT_STANDBY      SMCR |= (1<<SM1)|(1<<SM2)             // Standby Mode
#define SELECT_EXT_STANDBY  SMCR |= (1<<SM0)|(1<<SM1)|(1<<SM2)    // Extended Standby Mode

#define SLEEP_ENABLE        SMCR |= (1<<SE)    // Set the SE (sleep enable) bit
#define SLEEP_DISABLE       SMCR &= ~(1<<SE)   // Clear the SE (sleep enable) bit


int main()
{
................................
  SLEEP_ENABLE;
  SELECT_IDLE;

__enable_interrupt();    // set the Global Interrupt Enable Bit
  
  TCCR0B = 0x03;        // Start System Timer f_clk/64
  TIMSK0 |= (1<<TOIE0);   // Разрешить прерывания Timer0 по переполнению (OVF)
                    // Период переполнения Timer0 при f_clk=7.3728 Mhz 2.222 ms
  OS::Run();            
}


void TLCD::OnOff(bool On)
{
  if(On)  {;}
  else
  {
...............................................
    PORTD |= (1<<SHDN);     // Отключить аналоговую периферию
    //TIMSK0 &= ~(1<<TOIE0);
    SELECT_POWERDOWN;
    __sleep();
    
    SELECT_IDLE;
    //TIMSK0 |= (1<<TOIE0);
    PORTD &= ~(1<<SHDN);    // Включить аналоговую периферию
  }
По команде __sleep() входит в POWER_DOWN. Это подтверждает снижение токопотребления и высокий уровень на SHDN. Но почему не просыпается при нажатии на кнопку, не могу понять. wacko.gif .. Перепад в "0" на INT6 при нажатии наблюдаю тестером.

PS. В errata есть замечание по поводу:
2. High current consumption in sleep mode
If a pending interrupt cannot wake the part up from the selected mode, the current consumption
will increase during sleep when executing the SLEEP instruction directly after a SEI
instruction.

Problem Fix/workaround
Before entering sleep, interrupts not used to wake up the part from the sleep mode should
be disabled.

У меня используется прерывание таймера0. Но запрещение перед входом в режим и разрешение по выходу из режима Power_Down не решает проблему.
Какие будут предположения по данной проблеме?
SasaVitebsk
Надеюсь вы учли, что просыпаться только Only "INT3:0 or level interrupt INT7:4"
alux
Цитата(SasaVitebsk @ Sep 4 2008, 21:55) *
Надеюсь вы учли, что просыпаться только Only "INT3:0 or level interrupt INT7:4"

Повторю еще раз. Прерывание INT6 настроено на low level.

PS. По даташиту AT90USB1287 : Wake-up Sources from Power-down -> For INT7:4, only level interrupt.
ReAl
Цитата(alux @ Sep 4 2008, 18:38) *
Код
int main()
{
................................
  SLEEP_ENABLE;
  SELECT_IDLE;

__enable_interrupt();    // set the Global Interrupt Enable Bit
  
  TCCR0B = 0x03;        // Start System Timer f_clk/64
  TIMSK0 |= (1<<TOIE0);   // Разрешить прерывания Timer0 по переполнению (OVF)
                    // Период переполнения Timer0 при f_clk=7.3728 Mhz 2.222 ms
  OS::Run();            
}

не читал, но осуждаю
пролетая над Череповцом
Пробегая мимо интернета нет времени внимательно вчитываться во всю тему, поэтому по теме ответить могу лишь то, что в той старой теме "не всё чисто" было не с прерываниями у 90usb, а с "очиткой".
Автором были спутаны INT7 и PCINT7.

Не по основной теме:
не рекомендую разрешать прерывания до запуска OS::Run() - потребности в этом нет, а что-то левое может и вылезти.
Прерывания разрешаются автоматически в конце переключения на первый выполняемый процесс, так как стек каждого процесса инициализируется конструктором таким образом, что в нём сидит образ статусного регистра с установленным битом разрешения прерываний, таким образом при первом переключении на процесс "восстанавливается" SREG.I == 1 и прерывания разрешены.
alux
Цитата(ReAl @ Sep 5 2008, 10:33) *
не рекомендую разрешать прерывания до запуска OS::Run() - потребности в этом нет, а что-то левое может и вылезти.
Этот вопрос я уже поднимал
ранее:

Потребность иногда есть, например, когда необходимо проинициализировать АЦП, а SPI настроен по прерыванию:
Код
.........................
__enable_interrupt();    // set the Global Interrupt Enable Bit
  ad7799_Init();
  
  TCCR0B = 0x03;          // Start System Timer f_clk/64
  TIMSK0 |= (1<<TOIE0);   // Разрешить прерывания Timer0 по переполнению (OVF)
                          // Период переполнения Timer0 при f_clk=7.3728 Mhz 2.222 ms
  OS::Run();              //                            при f_clk=20 Mhz 0.8192 ms
}

Получается сразу после ad7799_Init(); до запуска ОС нужно сделать __disable_interrupt(); ?

Почему из POWER_DOWN не вызывается обработчик внешнего прерывания INT6 , настроенный по низкому уровню?
Вызов ISR контролирую по изменению вывода BEEP:
Код
#pragma vector=INT6_vect
OS_INTERRUPT void INT6_ISR()
{
  OS::TISRW_SS ISRW;

  DISABLE_INT6;            // Prevent further external interrupts

  Int6.SignalISR();  
PORTC |= (1<<BEEP);
}
alux
Второй раз наступаю на одни и те же грабли:
Код
void TLCD::OnOff(bool On)
{
  if(On)
  {;}
  else
  {
    TCritSect cs;       <<<<<<<<<<<!!!!!!!!!!!!!!!!!!!!!!!!
    ks0108ClearScreen();            // display clear
.............................................................................
    PORTD |= (1<<SHDN);     // Отключить аналоговую периферию
    SELECT_POWERDOWN;
    __sleep();
    
    SELECT_IDLE;
    PORTD &= ~(1<<SHDN);    // Включить аналоговую периферию
    powerdown.Signal();     // Сигнал для инициализации меню после выхода из Power_Down
  }
}

Когда входим в Power Down, прерывания запрещены (TCritSect cs;). Поэтому не вызывается обработчик прерывания.
Прошу прощения, за свою невнимательность. И код в предыдущем посте я привел без этого момента smile.gif
ReAl
Цитата(alux @ Sep 5 2008, 12:12) *
Потребность иногда есть, например, когда необходимо проинициализировать АЦП, а SPI настроен по прерыванию:
Код
.........................
__enable_interrupt();
  ad7799_Init();
  
  TCCR0B = 0x03;    
  TIMSK0 |= (1<<TOIE0);
  OS::Run();
}

А почему бы не сделать это в начале функции процесса, которого этот АЦП интересует?
Код
template<> OS_PROCESS void TProc1::Exec()
{
    ad7799_Init();
    for(;;) {
        // ну а тут как обычно
    }
}



Цитата(alux @ Sep 5 2008, 12:12) *
Получается сразу после ad7799_Init(); до запуска ОС нужно сделать __disable_interrupt(); ?
Уже не нужно. Т.е. количественно ситуацию это изменит, качественно - нет.
Смотрим.
Пусть между разрешением прерываний и началом нормальной работы ОС возникает какое-то "осевое" прерывание, то же клавиатурное INT6 (маловероятно? ну тогда пусть от UART, в который кто-то ломится).
В начале обработчика стоит OS::TISRW_SS ISRW;, смотрим код конструктора
Код
    class TISRW_SS
    {
    public:
        INLINE  TISRW_SS()  { ISR_Enter(); }
        INLINE  ~TISRW_SS() { ISR_Exit();  }

    private:
        //-----------------------------------------------------
        INLINE void ISR_Enter() // volatile
        {
            if(Kernel.ISR_NestCount++ == 0)
            {
                 SavedSP.DataSP   = GetDataSP();
                 SavedSP.ReturnSP = GetReturnSP();
                 SetISRStackPointers();                     // <---- ОЙ !!!
            }
        }
        . . .
    };

// А вот и ОЙ !!!
INLINE inline void SetISRStackPointers()
{
    ABS_WORD(28) = reinterpret_cast<word>(__segment_end("CSTACK"));
    SP = reinterpret_cast<word>(__segment_end("RSTACK")) - 1;
}
Итого обработчик прерывания переключил стек на осноной стек программы (он всё равно не используется при нормальном выполнении программы с scmRTOS. И имеет возможность затереть, например, находящийся там адрес возврата из того же любимого ad7799_Init();.
Конечно, может повезти. И обработчик прерывания не затрёт основной стек.
Но заглянем в другие порты - во многих (AVR/GCC, MSP430/*, ARM7) функция ISR_Enter() сделана одинаково и вот так:
Код
        INLINE void ISR_Enter()
        {
            if(Kernel.ISR_NestCount++ == 0)
            {
                Kernel.ProcessTable[Kernel.CurProcPriority]->StackPointer = GetStackPointer();
                SetISRStackPointer();
            }
        }
В данном случае вместо записанного конструктором процесса адреса "сэмулированного" тем же конструктором начального стекового кадра процесса будет записан адрес где-то в серёдке основного стека и OS::Run() для OS_Start() даст этот адрес.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.