|
|
  |
Оператор "printf" и функция "putchar" для работы с символьным LCD |
|
|
|
Mar 19 2010, 21:20
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(zltigo @ Mar 19 2010, 20:37)  Некошерно смотрится дергание бита разрешения прерывания  Нормально смотрится. Нужны прервания - разрешаем, не нужны - запрещаем. Флаг-то аппаратно торчит всегда, когда свободен буфер передачи. Это вам не '550. Цитата(zltigo @ Mar 19 2010, 20:37)  Ну и volatile для UART_TxHead0 лишнее - только warning вызывает. Формально - не лишняя. Переменная меняется в другой относительно прерывания нити. Цитата(zltigo @ Mar 19 2010, 20:37)  tmptail0 лишняя сущность... Вовсе нет. Без нее получаем предупреждение компилятора на неопределенный порядок чтения двух volatile в сравнении.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 19 2010, 21:53
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(Сергей Борщ @ Mar 20 2010, 00:20)  Нормально смотрится. Нужны прервания - разрешаем, не нужны - запрещаем. Флаг-то аппаратно торчит всегда, когда свободен буфер передачи. Свободен сдвиговый регистр - записываем прямо в UART, не свободен в буфер и по прерыванию уже вычитываем, либо по анализу указателей, либо флаг можно завести. Цитата Формально - не лишняя. Переменная меняется в другой относительно прерывания нити. Абсолютно лишняя, ибо пока выполняется обработчик она измениться за его пределами не может. для Tail volatile нужен, Head - нет. Цитата Вовсе нет. Без нее получаем предупреждение компилятора на неопределенный порядок чтения двух volatile в сравнении. Два volatile, повторяю, ну совсем не нужны, а warning, гарантировано при if ( UART_TxHead0 != UART_TxTail0 ) вылезет хоть есть tmptail0, хоть нет.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Mar 20 2010, 09:54
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(zltigo @ Mar 19 2010, 23:53)  Свободен сдвиговый регистр - записываем прямо в UART, не свободен в буфер и по прерыванию уже вычитываем, либо по анализу указателей, либо флаг можно завести. И все эти телодвижения только ради того, чтобы не запрещать/разрешать прерывание? Нафиг. Хорошо, пусть даже идем вашим путем. В прерывании вычитываем из буфера. Буфер пуст - все данные переданы. Что делать дальше? Флаг прерывания сбросить невозможно - для этого нужно что-то начать передавать. Если выйдем из прерывания не запретив его - тут же попадем в это же прерывание снова. Что мы можем сделать кроме как запретить это прерывание? Еще раз повторяю - это не '550, тут не получается начать генерить прерывания положив первый байт в передатчик. И такая реализация дает более компактный код, без всяких лишних проверок "свободен/не свободен сдвиговый регистр". Цитата(zltigo @ Mar 19 2010, 23:53)  Абсолютно лишняя, ибо пока выполняется обработчик она измениться за его пределами не может. Ну хорошо, тут соглашусь. Цитата(zltigo @ Mar 19 2010, 23:53)  Два volatile, повторяю, ну совсем не нужны, а warning, гарантировано при if ( UART_TxHead0 != UART_TxTail0 ) вылезет хоть есть tmptail0, хоть нет. Тюю... Невнимательно посмотрел исходник. Рассмотрел только TransmitByte, увидел, что она очень похожа на один из вариантов используемого мной кода и экстраполировал этот вывод и на обработчик прерывания. Виноват. У автора ветки действительно лишний. Код void uart_t::send(uint8_t byte) { uint8_t Tmp = (TxHead + 1) & (UART_TX_BUFF_SIZE - 1); while(Tmp == TxTail) ; // Buffer full TxBuffer[ TxHead ] = byte; TxHead = Tmp; UCSRB |= (1<<UDRIE); }
inline void uart_t::UDRE_handler(void) { uint8_t Tmp = TxTail; UDR = TxBuffer[ Tmp ]; TxTail = Tmp = (Tmp + 1) & (UART_TX_BUFF_SIZE - 1); if(Tmp == TxHead) UCSRB &= ~(1<<UDRIE); }
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 20 2010, 10:34
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(Сергей Борщ @ Mar 20 2010, 12:54)  И все эти телодвижения.... Это не все, это на вскидку три разных варианта. Каждый из 3x не сложнее суммарного дергания периферии с целью разрешения/запрещения прерывания в функции передачи и обработчике. Цитата И такая реализация дает более компактный код, без всяких лишних проверок "свободен/не свободен сдвиговый регистр". Очень очень зависит от конкретики и железа. Компактность кода, тоже не всегда синоним скорости - для тех контроллеров, у которых периферия находится на медленной шине безусловное действие прочитать-изменить-записать флаг в при каждом вызове передачи байта катострофически проигрывает по времени, например, анализу софтового флага. Цитата У автора ветки действительно лишний. Код inline void uart_t::UDRE_handler(void) { uint8_t Tmp = TxTail; UDR = TxBuffer[ Tmp ]; TxTail = Tmp = (Tmp + 1) & (UART_TX_BUFF_SIZE - 1); if(Tmp == TxHead) UCSRB &= ~(1<<UDRIE); } Ну вообще-то и у тебя лучше обойтись  без. Код union { volatile uint8_t TxTail; uint8_t TxTail_int; };
inline void uart_t::UDRE_handler(void) { UDR = TxBuffer[ TxTail_int ]; TxTail_int = (TxTail_int + 1) & (UART_TX_BUFF_SIZE - 1); if(TxTail_int == TxHead) UCSRB &= ~(1<<UDRIE); } Меньше указивок, меньше думать и больше свободы компилятору. P.S. Кстати, в таких случаях я предпочитаю НЕ накладывать маску на индексы при их наращивании - пусть крутятся по полному циклу, а маска накладывается при использовании индекса. В этом случае одними действием вычитания индексов можно узнать сколько байтов в буфере - бывает нужно при пакетной передаче.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Mar 20 2010, 11:11
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(zltigo @ Mar 20 2010, 12:34)  Очень очень зависит от конкретики и железа. Компактность кода, тоже не всегда синоним скорости - для тех контроллеров, у которых периферия находится на медленной шине безусловное действие прочитать-изменить-записать флаг в при каждом вызове передачи байта катострофически проигрывает по времени, например, анализу софтового флага. В данном случае код был под конкретный контроллер - AVR. В котором, во-первых, периферия висит на быстрой шине (других просто нет), а во-вторых - без сброса разрешения прерывания просто не получится. Цитата(zltigo @ Mar 20 2010, 12:34)  Кстати, в таких случаях я предпочитаю НЕ накладывать маску на индексы при их наращивании - пусть крутятся по полному циклу, а маска накладывается при использовании индекса. В этом случае одними действием вычитания индексов можно узнать сколько байтов в буфере - бывает нужно при пакетной передаче. Так я же написал - это один из вариатнтов. Опять же, речь идет об AVR, у которого FIFO нет (двойная буферизация не считается), и толку от знания "сколько конкретно байт в буфере" нет никакого. Код void uart_t::send(uint8_t byte) { uint8_t Tmp = TxHead; while((uint8_t)(Tmp - TxTail) >= (uint8_t) UART_TX_BUFF_SIZE) ; // Buffer full TxBuffer[ Tmp++ & (UART_TX_BUFF_SIZE - 1) ] = byte; TxHead = Tmp; UCSRB |= (1<<UDRIE); }
inline void uart_t::UDRE_Handler() { uint8_t Tmp = TxTail; UDR = TxBuffer[ Tmp++ & (UART_TX_BUFF_SIZE - 1) ]; TxTail = Tmp; if(Tmp == TxHead) UCSRB &= ~(1<<UDRIE); } Но результирующий код получается длинее на 6 байт. И чего я на него перешел? Надо вернуться к первому - память кода может пригодиться. Цитата(zltigo @ Mar 20 2010, 12:34)  Меньше указивок, меньше думать и больше свободы компилятору. Фу. union в таком применении - грязный хак. Как раз таки и не дающий никакой дополнительной свободы компилятору, но чреватый какими-нибудь граблями если компилятор окажется черезчур умным. А думать надо всегда  Впрочем, мы уклонились от темы ветки.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 20 2010, 11:14
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(Сергей Борщ @ Mar 20 2010, 14:08)  Опять же, речь идет об AVR, у которого FIFO нет (двойная буферизация не считается), и толку от знания "сколько конкретно байт в буфере" нет никакого. А причем тут наличие или отсутствие FIFO и AVR??? Это уже конкретные проблемы верхнего уровня - надо ему знать сколько есть места в софтовом буфере передачи, или нет. При необходимости поместить в буфер за один вызов функции не один байт, а фрейм - весьма не лишняя возможность. Цитата Но результирующий код получается длинее на 6 байт. Это уже конкретные заморочки компилятора  , кстати, возможно, и из-за неоптимальных volatile. То,что я описал, это просто тот вариант работы с индексами который я реализовывал "для начала". В случае каких-то дополнительных/особых требований, условий - естественно никаких догм.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Mar 20 2010, 12:57
|

Любитель
    
Группа: Свой
Сообщений: 1 864
Регистрация: 20-08-06
Из: Тольятти
Пользователь №: 19 695

|
Кстати, можно вопрос по поводу UARTов микроконтроллеров? Мне пока доводилось поработать с двумя реализациями их передатчиков: 1. как только сдвиговый регистр передатчика становится пуст - устанавливается соответствующее прерывание. Сбросить его невозможно до тех пор, пока передатчик пуст. Тут получается весьма простой обработчик - надо передавать данные - разрешаем прерывания и в обработчике происходит загрузка. Данные закончились - запрещаем прерывание. 2. в LPC1768 другая система (так называемый 550-ый?) - триггер прерывания "передатчик пуст" устанавливается только после завершения передачи данных (а не присутствует постоянно). То есть изначально его нет (хотя передачтик пуст!), и после вызова обработчика он сбрасывается. Здесь уже погеморройнее - загрузку FIFO приходится делать как в обработчике прерывания, так и снаружи (для инициирования передачи). Причём дополнительно есть какой-то механизм откладывания прерывания: The UARTn THRE interrupt (UnIIR[3:1] = 001) is a third level interrupt and is activated when the UARTn THR FIFO is empty provided certain initialization conditions have been met. These initialization conditions are intended to give the UARTn THR FIFO a chance to fill up with data to eliminate many THRE interrupts from occurring at system start-up. The initialization conditions implement a one character delay minus the stop bit whenever THRE = 1 and there have not been at least two characters in the UnTHR at one time since the last THRE = 1 event. This delay is provided to give the CPU time to write data to UnTHR without a THRE interrupt to decode and service. A THRE interrupt is set immediately if the UARTn THR FIFO has held two or more characters at one time and currently, the UnTHR is empty. The THRE interrupt is reset when a UnTHR write occurs or a read of the UnIIR occurs and the THRE is the highest interrupt (UnIIR[3:1] = 001).Здесь я до конца не понял конкретику, лишь то, что флаг THRE может устанавливаться не сразу по опустошению передатчика, а спустя какое-то время. То есть проанализировав в регистре статуса бит Transmitter Empty (TEMT), в случае, если он установлен, нельзя однозначно понять, был ли уже вызов прерывания, или только ещё будет - из-за one character delay? В общем, некоторый гиморрой получается  Может, кто нибудь более популярно разъяснит механику работы LPCшного UART?
|
|
|
|
|
Mar 20 2010, 13:10
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(sonycman @ Mar 20 2010, 15:57)  В общем, некоторый гиморрой получается  Пальцем покажите: Код //---------------------------------------------------------------------------- void boutchar( char ch ) { if( status_word & STW_THR_BUSY ) { tbuf.buf[tbuf.tail & TBUF_SIZE_MSK] = ch; tbuf.tail++; } else { status_word |= STW_THR_BUSY; U0THR = ch; } } //---------------------------------------------------------------------------- __irq __arm void uart_isr(void) { bint ch;
while( !((ch = (U0IIR&IIR_MASK)) & IIR_IP) ) // Check Pending Bit { // What caused the interrupt? switch( ch ) {
case IIR_THRE: // The THRE+TEMPT+FIFO is empty. If there is another // characters in the TX buffer, load its now to FIFO->THRE. if( tbuf.tail != tbuf.head ) { for( int ii=0; ( tbuf.tail != tbuf.head )&&( ii < TR_FIFO_SIZE ); ii++ ) U0THR = tbuf.buf[(tbuf.head++) & TBUF_SIZE_MSK]; } else status_word &= (~STW_THR_BUSY); break;
....... Цитата Тут получается весьма простой обработчик - надо передавать данные - разрешаем прерывания и в обработчике происходит загрузка. Данные закончились - запрещаем прерывание. Если выкинуть работу с FIFO отсутствующим у AVR, то получаем вот такой "сложный" обработчик прерывания для LPC: Код if( tbuf.tail != tbuf.head ) U0THR = tbuf.buf[(tbuf.head++) & TBUF_SIZE_MSK]; else status_word &= (~STW_THR_BUSY); И такой "простой" ( © Сергея ) для AVR: Код uint8_t Tmp = TxTail; UDR = TxBuffer[ Tmp++ & (UART_TX_BUFF_SIZE - 1) ]; TxTail = Tmp; if(Tmp == TxHead) UCSRB &= ~(1<<UDRIE);
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Mar 20 2010, 14:00
|

Любитель
    
Группа: Свой
Сообщений: 1 864
Регистрация: 20-08-06
Из: Тольятти
Пользователь №: 19 695

|
Цитата(zltigo @ Mar 20 2010, 17:10)  Пальцем покажите: Спасибо за пример. У Вас используется софтовый флаг (STW_THR_BUSY) для исключения коллизии при загрузке данных. На AVR, благодаря "упрощённому" железу он совсем не нужен. Наверное, тоже введу такой флаг, вместо принудительного вызова прерывания установкой pending бита... Цитата(zltigo @ Mar 20 2010, 17:10)  void boutchar( char ch ) { if( status_word & STW_THR_BUSY ) { tbuf.buf[tbuf.tail & TBUF_SIZE_MSK] = ch; tbuf.tail++; } else { status_word |= STW_THR_BUSY; U0THR = ch; } } Кстати, у Вас в этой функции потенциальная проблема с опросом флага status_word. Для корректной работы необходимо запрещать прерывание THRE перед обращением к status_word. Жаль, не хотелось бы этого...
|
|
|
|
|
Mar 20 2010, 14:41
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(sonycman @ Mar 20 2010, 17:00)  У Вас используется софтовый флаг (STW_THR_BUSY) для исключения коллизии при загрузке данных. Прежде всего из-за много более медленного доступа к железным флагам. Цитата Кстати... Да, в реальности я байтовое занесение даже в буфер печати не использую - просто болталась неиспользуемая функция  . Используется свой xprintf() в котором в том числе оптимизирован контроль за флагом, который контролируется только в конце заполнения буфера.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Mar 20 2010, 15:08
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(zltigo @ Mar 20 2010, 13:14)  При необходимости поместить в буфер за один вызов функции не один байт, а фрейм - весьма не лишняя возможность. А если весь фрейм не лезет, что будем делать? Ждать, пока влезет или все же запихаем сколько сможем, чтобы избежать пауз в потоке? Будет кошерный вариант для AVR без дерганья флага? Цитата(zltigo @ Mar 20 2010, 13:14)  Это уже конкретные заморочки компилятора  С чего бы это? В одном случае сравниваются две переменные, во втором - их разность сравнивается с константой. Очевидно, что второй вариант требует больше команд.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Mar 20 2010, 15:35
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(Сергей Борщ @ Mar 20 2010, 18:08)  А если весь фрейм не лезет, что будем делать? Лично у меня в большинстве случаев буфера разумно-большие и отсутствие места для фрейма это аварийное состояние. Предпринимаются разборки, торможение уровней и встечной стороны... Если это просто отладочная печать захлебывается, то тем более, ждать нечего - как мы начали идти до жизни такой уже видно в начале буфера, остальное в мусорник. Цитата ..чтобы избежать пауз в потоке? Понятие фрейма само по себе не завязано на непрерывность потока. Иногда даже наоборот Цитата С чего бы это? В одном случае сравнисаются две переменные, во втором - их разность сравнивается с константой. Очевидно, что второй ввариант требует больше команд. На ШЕСТЬ байт больше??? Кроме вышеуказанного сравнения остальные действия достаточно равнозначны, даже для AVR различие в 6 байт это проблемы компилятора  . Для ARM, полагаю, как минимум, что в лоб, что по лбу. P.S. Глянул для AVR IAR. "Плохой" вариант с константой Код 82 void send1( BYTE ch ) \ send1: 83 { \ 00000000 .... LDI R30, LOW(TxTail) \ 00000002 .... LDI R31, (TxTail) >> 8 \ 00000004 8121 LDD R18, Z+1 84 while( (BYTE)(TxHead - TxTail) >= UART_TX_BUFF_SIZE) \ ??send1_0: \ 00000006 8130 LD R19, Z \ 00000008 2F12 MOV R17, R18 \ 0000000A 1B13 SUB R17, R19 \ 0000000C 3410 CPI R17, 64 \ 0000000E F7D8 BRCC ??send1_0 85; // Buffer full 86 87 TxBuffer[ TxTail++ & (UART_TX_BUFF_SIZE - 1) ] = ch; \ 00000010 8120 LD R18, Z \ 00000012 2F12 MOV R17, R18 \ 00000014 9513 INC R17 \ 00000016 8310 ST Z, R17 \ 00000018 E0F0 LDI R31, 0 \ 0000001A 732F ANDI R18, 0x3F \ 0000001C 2FE2 MOV R30, R18 \ 0000001E .... SUBI R30, LOW((-(TxBuffer) & 0xFFFF)) \ 00000020 .... SBCI R31, (-(TxBuffer) & 0xFFFF) >> 8 \ 00000022 8300 ST Z, R16 88 } \ 00000024 9508 RET "Хороший" вариант: Код 91 void send2( BYTE ch ) \ send2: 92 { \ 00000000 2F3B MOV R19, R27 \ 00000002 2F4A MOV R20, R26 93 BYTE Tmp = ( TxTail + 1 )&(UART_TX_BUFF_SIZE - 1); \ 00000004 .... LDI R30, LOW(TxTail) \ 00000006 .... LDI R31, (TxTail) >> 8 \ 00000008 8110 LD R17, Z \ 0000000A 9513 INC R17 \ 0000000C 731F ANDI R17, 0x3F \ 0000000E 8121 LDD R18, Z+1 94 95 while( TxHead == Tmp ) \ ??send2_0: \ 00000010 1721 CP R18, R17 \ 00000012 F3F1 BREQ ??send2_0 96; // Buffer full 97 98 TxBuffer[ TxTail ] = ch; \ 00000014 81A0 LD R26, Z \ 00000016 E0B0 LDI R27, 0 \ 00000018 .... SUBI R26, LOW((-(TxBuffer) & 0xFFFF)) \ 0000001A .... SBCI R27, (-(TxBuffer) & 0xFFFF) >> 8 \ 0000001C 930C ST X, R16 99 TxTail = Tmp; \ 0000001E 8310 ST Z, R17 100 } \ 00000020 2FA4 MOV R26, R20 \ 00000022 2FB3 MOV R27, R19 \ 00000024 9508 RET Где 6 байт проигрыша? P.P.S. Это была оптимизация по скорости. При оптимизации по размеру "Плохой" выиграл два байта у "Хорошего" Код 82 void send1( BYTE ch ) \ send1: 83 { \ 00000000 .... LDI R30, LOW(TxTail) \ 00000002 .... LDI R31, (TxTail) >> 8 84 while( (BYTE)(TxHead - TxTail) >= UART_TX_BUFF_SIZE) \ ??send1_0: \ 00000004 8111 LDD R17, Z+1 \ 00000006 8120 LD R18, Z \ 00000008 1B12 SUB R17, R18 \ 0000000A 3410 CPI R17, 64 \ 0000000C F7D8 BRCC ??send1_0 85 ; // Buffer full 86 87 TxBuffer[ TxTail++ & (UART_TX_BUFF_SIZE - 1) ] = ch; \ 0000000E 8120 LD R18, Z \ 00000010 2F12 MOV R17, R18 \ 00000012 9513 INC R17 \ 00000014 8310 ST Z, R17 \ 00000016 E0F0 LDI R31, 0 \ 00000018 732F ANDI R18, 0x3F \ 0000001A 2FE2 MOV R30, R18 \ 0000001C .... SUBI R30, LOW((-(TxBuffer) & 0xFFFF)) \ 0000001E .... SBCI R31, (-(TxBuffer) & 0xFFFF) >> 8 \ 00000020 8300 ST Z, R16 88 } \ 00000022 9508 RET Код 91 void send2( BYTE ch ) \ send2: 92 { \ 00000000 2F3B MOV R19, R27 \ 00000002 2F4A MOV R20, R26 93 BYTE Tmp = ( TxTail + 1 )&(UART_TX_BUFF_SIZE - 1); \ 00000004 .... LDI R30, LOW(TxTail) \ 00000006 .... LDI R31, (TxTail) >> 8 \ 00000008 8110 LD R17, Z \ 0000000A 9513 INC R17 \ 0000000C 731F ANDI R17, 0x3F 94 95 while( TxHead == Tmp ) \ ??send2_0: \ 0000000E 8121 LDD R18, Z+1 \ 00000010 1721 CP R18, R17 \ 00000012 F3E9 BREQ ??send2_0 96 ; // Buffer full 97 98 TxBuffer[ TxTail ] = ch; \ 00000014 81A0 LD R26, Z \ 00000016 E0B0 LDI R27, 0 \ 00000018 .... SUBI R26, LOW((-(TxBuffer) & 0xFFFF)) \ 0000001A .... SBCI R27, (-(TxBuffer) & 0xFFFF) >> 8 \ 0000001C 930C ST X, R16 99 TxTail = Tmp; \ 0000001E 8310 ST Z, R17 100 } \ 00000020 2FA4 MOV R26, R20 \ 00000022 2FB3 MOV R27, R19 \ 00000024 9508 RET В общем, как и ожидалось, веских причин генерировать сильно отличающийся по размеру код у компиляторов нет.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|