Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: char + char = int как такое сделать
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
bugor
Ситуатция известная -
- результат АЦП их двух байт в разных банках.
- работает три канала.
- есть массив из трех int.
Надо младший и старший байты результата АЦП обьединить в три целых переменных в массиве.

Чегото не получается никак - уже который раз пытаюсь. В AVR наких проблем нету, а на PIC редко пишу - направьте где посмотреть как это делается? Надо на СИ.
*antzol*
Это не подойдёт?
int result = ((int)highByte << 8) + lowByte;
bugor
Цитата(*antzol* @ Aug 27 2009, 21:24) *
Это не подойдёт?
int result = ((int)highByte << 8) + lowByte;


HTEC PIC ругается - degenerate unsigned comparison

volatile unsigned int U[3] @ 0x20; //массив

ADRESH и ADRESL - это байты результата

надо в U[0] поместить ADRESH и ADRESL первого преобразования
в U[1] -----------------------------------второго преобразования и тд

Ммссив надо как то в цикле индексировать по счетчику каналов.
*antzol*
За правильность не ручаюсь, т.к. с пиками давно не работал, а в HTEC PIC вообще не писал. smile.gif
Код
char i;
for (i = 0; i < 3; i++)
{
   ADCON0 &= 0xC3;     // сброс выбора входа
   ADCON0 |= (i<<3);    // выбор входа

   // тут надо вставить задержку

   ADCON0 |= (1<<2);  // запуск преобразования
   while (ADCON0 & (1<<2));  // ожидание завершения преобразования
   u[i] = ADRESH;
   u[i] <<=8;
   u[i] +=ADRESL;
}
rezident
Цитата(bugor @ Aug 28 2009, 00:01) *
HTEC PIC ругается - degenerate unsigned comparison
Видимо потому, что перед сдвигом надо приводить к типу unsigned int, а не (signed) int. Или использовать временную локальную переменную типа unsigned int .
*antzol*
...
Student Pupkin
А нельзя типа такого?
Код
short int adc_mass[3];  // или int16_t в духе С99 ???
char *adc_byte;           // или int8_t в духе С99 ???
adc_byte = (char*)adc_mass;
*(adc_byte++) = LOW_BYTE_ADC1;    // не знаю как называются, но
*(adc_byte++) = HIGH_BYTE_ADC1;  // допустим так...
/*
и т.д. для остальных каналов АЦП
*/

Я есесно в пиках и хитеке ни гугу. Это, так сказать, лишь идея.... laughing.gif
bugor
Цитата(*antzol* @ Aug 27 2009, 23:03) *
За правильность не ручаюсь, т.к. с пиками давно не работал, а в HTEC PIC вообще не писал. smile.gif


Скомпилировался и в железе запустился вроде - что за значения получаются завтра проверю. Спасибо.
DpInRock
char как и int - переменные со знаком. Если не знаете зачем вам нужен знак используйте unsigned.
И компилятор тут не при делах.
bugor
Цитата(Student Pupkin @ Aug 27 2009, 23:50) *
А нельзя типа такого?


Не работает - преобразование вешается, я с указателями пробовал и не работает с ними у меня этот компилятор - не пойму почему.
Судя по листингу он сам активно использует косвенную адресацию и гдето там и происходит глюк, а как найти не знаю.
Вот полный код который компилируется но не работает - подскажите где ошибка.
CODE
//---------------------------------------
// Процессор PIC12F675
// Fosc = 4MHz Генератор RC-внутренний
// HI-TECH 8.05LP1
//---------------------------------------

#include <pic.h>
#include <math.h>

__CONFIG(WDTDIS & INTIO & UNPROTECT & MCLRDIS & PWRTEN & BOREN );

#define warm_out GPIO5 //выход нагревателя
#define sound_out GPIO4 //выход pреле звука
#define start_out GPIO3 //вход выключателя нагрева

volatile unsigned char Temp, Count, Channel;
volatile unsigned int U[3];// @ 0x20; //массив из трех двухбайтных элементов
volatile unsigned char *pU =(char*)U; //указатель на адрес 1го элемента массива

//МАКРОСЫ
//-----------------------------------------
#define TESTBIT(ADRESS,BIT) (ADRESS & (1<<BIT))
#define SETBIT(ADRESS,BIT) (ADRESS |= (1<<BIT))
#define CLEARBIT(ADRESS,BIT) (ADRESS &= ~(1<<BIT))
//================================
//задержка для A/D преобразования
//================================
void ADDelay() { //задержка 10uS
static unsigned char i;
i = 3; //Генератор 4Mhz, период 3uS,
while(i > 0) { //переменная равна 3 плюс смещение
i--;
}
}
//================================
//Инициализация A/D преобразования
//================================
void InitAD() {
ADIE = 1; //Разрешить прерывания от АЦП
ADCON0 = Channel; //Генегатор внутр RC + номер канала
ADIF = 0; //очистить флаг прерывания от АЦП
PEIE = 1; //Разрешить переферийные прерывания
asm("nop");
}
//==========================================================================
// MAIN
//==========================================================================
void main(void) {

GPIO = 0b00000000; // порты в ноль
TRISIO = 0b10001111; // GP4-5 на выход
OPTION = 0b00000110; // R-up выкл, делитель 1:128 к TMR0
VRCON = 0x00; // выключить источнок опорного напряжения
WPU = 0b00000000; // gp0 gp5 подтягивающие резисторы выключены
IOCB = 0x00; // запрет прерываний по входам
CMCON = 0b00000111; // компаратор выключен
INTCON =0b10000000; // разреш прерывания
ANSEL =0b01010111;
warm_out = 1;

Count = 0; //первый запуск ацп
Channel = 129; //канал ацп 0
InitAD();
ADDelay();
SETBIT(ADCON0,1);

while(1) {
ei();
asm("nop");
di();
}
}
//================================
//Обработка прерываний
//================================
void interrupt ADC() {

sound_out = 1; //индикатор работы ацп

*(pU) = ADRESH; //старший байт в первый элемент массива
*(pU++) = ADRESL; //младший байт во второй элемент массива
//точно также в 3-4 и в 5-6 элементы по циклу
if(Count++ < 3) {
Channel = Channel + 8; //загрузить другой канал
}else {
Channel = 129; //10000001b загрузка канала АЦП 0
Count = 0; //перезагрузить счетчик каналов
*pU = U[0]; //перегружаем адрес первого элемента массива
}

InitAD();
ADDelay(); //задержка на 2xTad для восстановления

sound_out=0;

ADDelay();
SETBIT(ADCON0,1); //Запуск A/D преобразования
}


По посту №4 работает АЦП и даже есть результат но понятно это по изменению младшего байта результата, а вот когда пытаюсь запихать два байта в целое то сравнения не работают то есть не получается нормальног INT из двух CHAR.
Kane
Вот на что обратил внимание:

Цитата(bugor @ Aug 28 2009, 11:35) *
Код
    while(1) {
         ei();
         asm("nop");
         di();
    }


Зачем в цикле запрещаете прерывания и снова разрешаете (если я правильно угадал смысл ei() и di())?

Цитата(bugor @ Aug 28 2009, 11:35) *
Код
      *(pU) = ADRESH;             //старший байт в первый элемент массива
      *(pU++) = ADRESL;           //младший байт во второй элемент массива
                                  //точно также в 3-4 и в 5-6 элементы по циклу


в этом куске младший байт всегда будет перетираться старшим байтом.
bugor
Цитата(Kane @ Aug 28 2009, 12:13) *
Вот на что обратил внимание:

Зачем в цикле запрещаете прерывания и снова разрешаете (если я правильно угадал смысл ei() и di())?

в этом куске младший байт всегда будет перетираться старшим байтом.


После запрещения прерывания будут вычисления и чтобы в момент вычислений не изменились данные по прерыванию АЦП так сделано.

Я уже посмотрел по листингу что происходит затирка, а как сделать чтобы этого небыло не знаю.
Но дело еще в том, что код не работает не в смысле неправильного формирования данных а в том смысле что преобразования АЦП не производятся - то есть на выходе индикатора должен быть что то подобие меандра, а ничего не происходит - висит 1 - то есть в прерывание вход есть, а выхода нет wacko.gif
Причем это наблюдается если начинаю использовать указатели - если без них то циклическое преобразование есть - но с данными непонятно что происходит.
DpInRock
Цитата
После запрещения прерывания будут вычисления

При зазоре в один ноп могут быть очень большие проблемы с прерываниями. У пика конвейер из 4 слов, насколько припоминаю...

Не запрещайте прерываний без четкого понимания - для чего именно вам это нужно именно в данный момент. В данной программе данный код не имеет смысла. Даже для якобы будущих вычислений.
bugor
Убрал запрещения и собственно ничего не изменилось - нет преобразований АЦП.
tag
Код
  *(pU) = ADRESH;             //старший байт в первый элемент массива
      *(pU++) = ADRESL;           //младший байт во второй элемент массива


не понятно где и как инициализируется pU?
bugor
Цитата(tag @ Aug 28 2009, 13:57) *
Код
  *(pU) = ADRESH;             //старший байт в первый элемент массива
      *(pU++) = ADRESL;           //младший байт во второй элемент массива


не понятно где и как инициализируется pU?


Пост №10 там полный код приведен.
Kane
Цитата(tag @ Aug 28 2009, 13:57) *
Код
  *(pU) = ADRESH;             //старший байт в первый элемент массива
      *(pU++) = ADRESL;           //младший байт во второй элемент массива


не понятно где и как инициализируется pU?


Это есть. в самом верху...
Цитата(tag @ Aug 28 2009, 13:57) *
Код


volatile unsigned int U[3];// @ 0x20;        //массив из трех двухбайтных элементов
volatile unsigned char *pU =(char*)U;        //указатель на адрес 1го элемента массива


не понятно где и как инициализируется pU?


Какой pic используется? Я глядел в описалово на 16f87x, так там для регистра ADCON0 бит 1 - не определён.
Получается, SETBIT(ADCON0,1); - просто не туда попадает.
bugor
ну так там написано в заголовке что pic12f675 rolleyes.gif

ёлки - чего еще обнаружил - этот компилятор не понимает вот это
Код
#asm
movwf OSCCAL
#endasm

а вот это понимает
Код
#asm
movwf 0x90
#endasm

хотя адрес этого регистра описан в хедере!
чегото мне подумалось, что если в этом такой глюк - чего говорить об указателях crying.gif

не подскажет ли кто поддерживает ли IAR мелкие процы, замучал меня чегото HT-TECH
Herz
С Хай-теком всё в порядке, Вы сами себя замучили такой кашей, компилятор тут ни при чём. Ни к чему здесь наверняка ни асм-овые вставки, ни хитрости с прерываниями. Разберитесь сначала с битами конфигурации, работой АЦП. Выравнивание происходит правильно? MPLAB используете? В нём всё просто и удобно. Для объединения байтов в int есть несколько вариантов. Я использовал такой:
Код
.......
     union {unsigned int wor; unsigned char by[2];} voltIN;
     .......
    
     Vcm.by[0]=ADRESL;            // младший байт
     Vcm.by[1]=ADRESH;            // старший байт
     .......
     HEXDEC(Vcm.wor);            // обращение к целому int-у
     .......

И зачем Вам это:
Код
<span class="postcolor">volatile unsigned int U[3];// @ 0x20;        //массив из трех двухбайтных элементов </span>

почему не просто:
Код
unsigned int U[3];        //массив из трех двухбайтных элементов
bugor
Ну вот что получилось - ацп работает но результата по прежнему толком нет.
В ацп есть какоето число но какое так и не смог выяснить.
Уже голову сломал не могу найти ошибку.
CODE
//;---------------------------------------
//; Процессор PIC12C672 или PIC12F675
//; Fosc = 4MHz Генератор RC-внутренний
//;---------------------------------------

#include <pic.h>

__CONFIG(WDTDIS & INTIO & UNPROTECT & MCLRDIS & PWRTEN & BOREN );

#define warm_out GPIO5 //выход нагревателя
#define sound_out GPIO4 //выход pреле звука
#define start_out GPIO3 //вход выключателя нагрева

unsigned char Temp, Count, Channel;
unsigned int U[3]; //массив данных с 3х каналов

union{
unsigned int Vin;
struct{
unsigned char UL:8;
unsigned char UH:8;
} byte;
}volt;

#define TESTBIT(ADRESS,BIT) (ADRESS & (1<<BIT))
#define SETBIT(ADRESS,BIT) (ADRESS |= (1<<BIT))
#define CLEARBIT(ADRESS,BIT) (ADRESS &= ~(1<<BIT))
//================================
//задержка для A/D преобразования
//================================
void ADDelay() { //задержка 10uS
static unsigned char i;
i = 3; //Генератор 4Mhz, период 3uS,
while(i > 0) { //переменная равна 3 плюс смещение
i--;
}
}
//================================
//Инициализация A/D преобразования (ADCON1 после сброса уже установлен)
//================================
void InitAD() {
ADIE = 1; //Разрешить прерывания от АЦП
ADCON0 = Channel; //Генегатор внутр RC + номер канала
ADIF = 0; //очистить флаг прерывания от АЦП
PEIE = 1; //Разрешить переферийные прерывания
}
//==========================================================================
// MAIN
//==========================================================================
void main(void) {

#asm
bsf status,5 // КАЛИБРОВКА ГЕНЕРАТОРА !!!
call 0X3ff
movwf 0X90
bcf status,5
#endasm

GPIO = 0b00000000; // порты в ноль
TRISIO = 0b10001111; // GP4-5 на выход
OPTION = 0b00000110; // R-up выкл, делитель 1:128 к TMR0
VRCON = 0x00; // выключить источнок опорного напряжения
WPU = 0b00000000; // gp0 gp5 подтягивающие резисторы выключены
IOCB = 0x00; // запрет прерываний по входам
CMCON = 0b00000111; // компаратор выключен
INTCON = 0b10000000; // разрешить общее прерывание
ANSEL =0b00010111; // F/8 и три канала аналоговый вход
warm_out = 1; // вкл выход

Count = 0; //первичная инициализация АЦП
Channel = 129;
InitAD();
ADDelay();
SETBIT(ADCON0,1);

while(1) { //бесконечный цикл

#asm
nop
nop
nop
nop
nop
nop
#endasm

if(U[0] < 2900 ) { //вот тут не работает при напряжении на ацп 3,8 вольт
sound_out = 1; //результат должен быть в районе 3114 а его нет
}else {
sound_out = 0;
}
}
}

//================================
//Обработка прерываний
//================================
void interrupt ADC() {

// sound_out = 1;

volt.byte.UL=ADRESL;
volt.byte.UH=ADRESH;
U[Count]=volt.Vin;

if(Count++ < 3) {
Channel = Channel + 8; //загрузить другой канал
}else {
Channel = 129; //10000001b загрузка канала АЦП 0
Count = 0; //перезагрузить счетчик каналов
}
InitAD();
ADDelay(); //задержка на 2xTad для восстановления

// sound_out=0;

ADDelay();
SETBIT(ADCON0,1); //Запуск A/D преобразования
}


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

Модератор. В очередной раз фиксирую нарушение Вами п.3.4 Правил форума. Либо упаковывайте цитаты исходников в тэги [ codebox ] самостоятельно, либо прилагайте их к сообщению в виде архива так, как это рекомендуют Правила форума.
С уважением, rezident.
Student Pupkin
Цитата(bugor @ Aug 28 2009, 19:45) *
Ну вот что получилось - ацп работает но результата по прежнему толком нет.

1) Есть предложение попробовать "unsigned unt U[3]" переобозвать в "volatile unsigned int U[3]".
2) (это так... мелочь.... smile.gif ) В функции ADDelay() к переменной-счетчику "static unsigned char i" тоже наверное желательно "volatile" присовокупить. А еще - зачем вы ее static сделали?
Herz
Не пойму я, во-первых, зачем Вы структуру сюда прикрутили (было же просто и понятно), во-вторых, откуда у 10-разрядного АЦП возмётся результат больше, чем 1024, в-третьих, в обработчике прерывания флаг не очищается? И всё-таки: MPLAB есть? И почему бы не использовать примеры функций, включенных в HT-PICC? Там есть и для АЦП, и для программной задержки, и для прерываний...
bugor
1. В функции задержки переменная i только для счетчика и нигде больше не видна и не нужна - так что static.
2. Флаг ADIF сбрасывается в ноль в функции InitAD.
3. Попробую и volatile еще тем более, что изменяется в прерывании - раньше так и было но мне чего то код в асме не понравился вот и убрал.
4. Структуру прикрутил по совету выше и опыту проектов в IAR - да и скомпилировалось сразу без ошибок и по коду вроде все верно делает, объединяя два char в один int.
5. MPLAB есть но никогда им не пользовался так как есть среда ФИТОН но вот их hard-ключ уже третий раз глючит а пересылать в москву надоело и они чего то (как все нормальные конторы) за свой счет делать не предлагают - вот по этому HI-TECH........ И проблемы.
5. А вот в том что результат больше чем 1024 - ЭТО КОСЯК!!!! - какой меня дернуул думать что там 4096 - 12 бит!!!!!
ума не приложу wacko.gif Спасибо всем буду копать. КАК накопаю и получу рабочий код - расскажу.
XVR
Вот это if(Count++ < 3) { должно быть так if(++Count < 3) {. Иначе у вас Count будет принимать значение 3, что выходит за границы массива U
Student Pupkin
Цитата(bugor @ Aug 29 2009, 21:14) *
1. В функции задержки переменная i только для счетчика и нигде больше не видна и не нужна - так что

Для локальных переменных класс памяти static означает, что переменная сохраняется между вызовами функции. Не видна - это когда static применяется к глобальным переменным. Или нет?
Однако тут на форуме говорили, что особо свирепый компилятор может так сильно "оптимизировать" подобную программную задержку, что ее совсем не будет (ну вроде как считаем без дела, время тратим, значит выкидываем). И вроде как для программной задержки переменную-счетчик надо объявлять volatile, чтоб оптимизацию над ней подавить...
Herz
Цитата(bugor @ Aug 29 2009, 19:14) *
4. Структуру прикрутил по совету выше и опыту проектов в IAR - да и скомпилировалось сразу без ошибок и по коду вроде все верно делает, объединяя два char в один int.
Так там не нужна структура, достаточно объединения.
Цитата
5. MPLAB есть но никогда им не пользовался так как есть среда ФИТОН но вот их hard-ключ уже третий раз глючит а пересылать в москву надоело и они чего то (как все нормальные конторы) за свой счет делать не предлагают - вот по этому HI-TECH........ И проблемы.

И напрасно: MPLAB - удобная штука (может, бывают и получше среды, но не суть), осваивается легко, бесплатная, есть симулятор, чего же голову морочить...
demiurg_spb
Цитата(bugor @ Aug 29 2009, 21:14) *
1. В функции задержки переменная i только для счетчика и нигде больше не видна и не нужна - так что static.
Ошибаетесь: static для локальной переменной функции и static для локальной переменной модуля имеют разный смысл.
Код
int get_cnt(void)
{
    ststic int cnt = 100;
    return (cnt++);
}

for (int i=0; i<3; i++)  get_cnt();
/*
    Вернёт:
    100
    101
    102
*/
rezident в следующем сообщении более грамотно изложил суть...
А для доступа к слову побайтно удобно пользоваться объединением с безымянной структурой (если Ваш компилятор это позволяет).
Код
typedef union
{
    uint16_t word;
    struct
    {
        uint8_t byte0;
        uint8_t byte1;
    };
} my_int16_t;

my_int16_t x = {.word = 0xABCD};

void swap_bytes(my_int16_t* x)
{
    uint8_t temp = x->byte0;
    x->byte0 = x->byte1;
    x->byte1 = temp;
}
Единственное о чём ещё следует позаботиться, так это о выравнивании полей структуры по границе байта
(#pragma pack(1) или через атрибуты или через makefile)...
И более того, даже так Вам не удастся избежать возможных граблей с big-little endian...
Единственное дубовое решение - это макросы со сдвигами типа:
Код
#define GET_BYTE(X,Y)  ((uint8_t)((X)>>((Y)*8)))
#define BYTE0(X)     GET_BYTE((X),0)
#define BYTE1(X)     GET_BYTE((X),1)
#define BYTE2(X)     GET_BYTE((X),2)
#define BYTE3(X)     GET_BYTE((X),3)
...
#define LH2WORD(LBYTE,HBYTE)      ((uint16_t)((HBYTE)<<8) | (LBYTE))
rezident
Цитата(Student Pupkin @ Aug 30 2009, 00:39) *
Для локальных переменных класс памяти static означает, что переменная сохраняется между вызовами функции. Не видна - это когда static применяется к глобальным переменным. Или нет?
Для переменных типа static выделяется постоянное место в ОЗУ, точно также, как и для глобальных переменных. И инициализация static начальным значением (очистка, если значение явно не задано) осуществляется точно также, как и у глобальных переменных. Но область видимости переменной типа static действительно ограничена, также, как и у автоматической (локальной) переменной. Тут вы правы.
bugor
Ну вот собственно заработало.
Проиллюстрирую поподробнее, может пригодится кому.
Обязательно надо настроить все переиферийные устройства, не полагаясь на автоматические установки после сброса.

Это переменная куда заносятся байты результата. Главное, чтобы они были именно в этом порядке.
Пока сделал так - потом без структуры попробую.
CODE
union{
volatile unsigned int Vin;
struct{
volatile unsigned char UL:8;
volatile unsigned char UH:8;
} byte;
}volt;

Это программы задержки и переинициализации АЦП
CODE
void ADDelay() { //задержка 10uS
static unsigned char i;
i = 3; //Генератор 4Mhz, период 3uS,
while(i > 0) { //переменная равна 3 плюс смещение
i--;
}
}
void InitAD() {
ADIE = 1; //Разрешить прерывания от АЦП
ADCON0 = Channel; //Генегатор внутр RC + номер канала
ADIF = 0; //очистить флаг прерывания от АЦП
PEIE = 1; //Разрешить переферийные прерывания
}


Это необходимые процедуры для запуска АЦП
CODE
void main(void) {

#asm
bsf status,5 // КАЛИБРОВКА ГЕНЕРАТОРА !!!
call 0X3ff
movwf 0X90
bcf status,5
#endasm

WPU = 0; // все подтягивающие резисторы выключены
ANSEL = 0b01010011; // F/16 для АЦП и 2 канала аналоговых
GPIO = 0b00000000; // порты в ноль
TRISIO = 0b00001111; // GP4-5 на выход
OPTION = 0b10000111; // R-up выкл, делитель 1:256 к TMR0
VRCON = 0x00; // выключить источник опорного напряжения
IOCB = 0x00; // запрет прерываний по входам
CMCON = 0b00000111; // выключить компаратор
INTCON = 0b10000000; // разрешить общее прерывание

Count = 0; //первичная инициализация АЦП
Channel = 129;
InitAD();
ADDelay();
SETBIT(ADCON0,1);

while(1) { //бесконечный цикл
#asm
nop
nop
nop
nop
nop
nop
#endasm
......................................
//основная программа
......................................

}
}

А это обработка прерывания АЦП.
CODE
void interrupt ADC() {

if(ADIF == 1) {

volt.byte.UL=ADRESL; //ВОТ В ЭТИХ ТРЕХ СТРОКА ФОРМИРУЕТСЯ МАССИВ РЕЗУЛЬТАТОВ АЦП
volt.byte.UH=ADRESH; //В ДАННОМ СЛУЧАЕ - ЭТО ДВА ЦЕЛЫХ ЧИСЛА
U[Count]=volt.Vin; //ТУТ ПОЛУЧАЕМ ЦЕЛОЕ С ПРАВИЛЬНЫМ РАСПОЛОЖЕНИЕМ БАЙТОВ
//ПРИ ДАЛЬНЕЙШЕЙ МАТЕМАТИЧЕСКОЙ ОБРАБОТКЕ ВСЕ ПРОИСХОДИТ ПРАВИЛЬНО

if(++Count < 2) {
Channel = Channel + 4; //загрузить другой канал
InitAD();
ADDelay(); //задержка на 2xTad для восстановления
ADDelay();
SETBIT(ADCON0,1);
}else {
Channel = 129; //10000001b загрузка канала АЦП 0
Count = 0; //перезагрузить счетчик каналов
InitAD();
ADDelay();
ADDelay();
SETBIT(ADCON0,1);
}
}

............................
//Тут могут быть обработчики других прерываний
...........................
}


Итого был неправильно оформлен счетчик массива, разрядность АЦП 12 бит вместо десяти ну и обьединение для собственно запихивания байт в целое.
Всем спасибо!
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.