Страница 7 из 9 ПерваяПервая 123456789 ПоследняяПоследняя
Показано с 91 по 105 из 128

Тема: Программа - имитатор PMR радио

  1. #91
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Тимофей, благодарю вас за советы, но хотелось бы еще раз пояснить, что у этого проекта своя ниша и в первом посте я описал его предназначение. Создавать полную копию виртуального эфира никогда не стремился, но полу копии у меня есть. Вот тут виртуальная шарманка, где общаются люди у которых проблемы с антеннами или просто нравится общение в формате радио эфира. Красивой мордашки трансивера нет, жаль тратить на это свое время которого всегда не хватает. Официальный сайт проекта. Есть проект приближенный более и менее к реальному звучанию эфира. Там меньше народу, но все равно есть свои поклонники которые, что бы не засорять реальный эфир проводят тренировочные связи телеграфом.
    А что касается монетизации моих проектов, так слава Богу есть на что жить. Есть работа и к тому поклонники моих программ периодически помогают своими пожертвованиями в поддержке серверов, их оплаты и модернизации. Анатолий свою версию данного проекта так же предоставляет бесплатно, по крайней мере версию с теми же возможностями что и моя версия под Windows. Но какие то другие проекты конечно же ему надо монетизировать, но это уже его выбор.

  2. #92
    Nestandart Power Аватар для RW3FB
    Регистрация
    26.04.2012
    Адрес
    Ивантеевка
    Возраст
    45
    Сообщений
    462
    Поблагодарили
    215
    Поблагодарил
    281
    Цитата Сообщение от RN6LIQ Посмотреть сообщение
    Дорабатывать тут нечего. Единственная серьезная доработка, которую сделал - это отображение числа пользователей в канале. Это действительно удобно, но сразу ограничило некоторые перспективные направления развития. Не клиентской программы, а серверной. Для различных возможностей у меня есть другие проекты. А в этом только подыгрывать микроконтроллерам. Ну и простота главное удобство программы.
    Василий, привет.
    А не планируется ли управление PTT через COM порт для возможности сопряжения с RoIP шлюзами? И VOX тоже было бы неплохо.

  3. #93
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Цитата Сообщение от RW3FB Посмотреть сообщение
    А не планируется ли управление PTT через COM порт для возможности сопряжения с RoIP шлюзами?
    Евгений, версии под Windows и Андроид это просто дань совместимости с популярными операционными системами где будет работать конечная программа. А с другой стороны, рядом с радиостанцией, будет работать версия под Linux или версия на микроконтроллере, и вот там все будет как вы хотите. Если вы еще не юзали микрокомпьютеры, типа RaspberryPI, то советую это сделать. Это более экономически оправданное решение чем использовать для этого полноценный компьютер под управлением Windows. Но и тут можно использовать Windows версию с функцией VOX, как ее включить могу подсказать. А еще круче прикрутить к радиостанции микроконтроллер. Сейчас, когда базовая система создана, я возвращаюсь к совместной работе с Владимиром, RZ6AT, и мы будем делать версию этой программы на микроконтроллерах, результаты этой работы будем выкладывать тут же, на этой ветке.

  4. #94
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Итак, мы с Владимиром(RZ6AT) покажем применение микроконтроллера Arduino UNO + Ethernet модуль W5500 в данном проекте. Для начала пример попроще. Трансляция только в одну сторону. Выход радиостанции FT-817 будем транслировать во второй канал PMR с индексом 1. Для этого понадобится

    Нажмите на изображение для увеличения. 

Название:	example1.jpg 
Просмотров:	12 
Размер:	105.8 Кб 
ID:	259895

    Arduino UNO + Ethernet модуль W5500 + FT817 + очень простая схема сопряжения

    Нажмите на изображение для увеличения. 

Название:	arpmr.png 
Просмотров:	9 
Размер:	944.7 Кб 
ID:	259896

    Ну и сама программа, если есть вопросы по инициализации микроконтроллера, то адресуйте их Владимиру ( RZ6AT )

    Код:
    #include "SPI.h"
    #include "Ethernet.h"
    #include "EthernetUdp.h"
    
    int pinIn = A0; // АЦП
    //ДОБАВЛЯЕМ ПАРАФАЗНЫЕ ВЫХОДЫ ЦАП
    int pinTest = 2; // ТЕСТ ПРЕРЫВАНИЯ 8 кГц
    byte mac[] = {0xDE, 0xAD, 0xEE, 0xEF, 0xFE, 0xED};
    IPAddress ip(192,168,0,84);
    IPAddress ipout(194,63,141,124);
    unsigned int localPort = 8984;
    unsigned int localPortout = 16000;
    EthernetUDP Udp;
    
    
    
    // Непосредственно для работы с сервером нужны
    byte kanal_PRD;  // номер канала 0 - 255
    uint16_t kanal_Secret; // и секретный код 0 - 65536
    // но в PMR проекте необходим 
    byte PChannel = 2; // номер канала 0 - 8
    uint32_t MailIndex = 1; // и индекс 0 - 999999
    
    void setup() 
    {
    // put your setup code here, to run once:
     pinMode(pinIn,INPUT); // АЦП
     Serial.begin(115200); // последовательный порт нужен только для отладки и наглядности
     // put your setup code here, to run once:
     Ethernet.begin(mac,ip);
     Udp.begin(localPort);
     
     // Тут происходят настройки АЦП и ЦАП и прерывания 8000. В данном примере нужен только АЦП  
     //ДОБАВЛЯЕМ   АЦП
     ADCSRA = 0x87; // ВКЛЮЧАЕМ АЦП, ДЕЛИМ НА 128
     ADCSRB = 0x00;
     ADMUX = 0x60; // СДВИГАЕМ РЕЗУЛЬТАТ ВЛЕВО, REF=5V, A0 ЧИТАЕМ
     // ТАЙМЕР1 ДЛЯ ШИМА
     //  TCCR1A = 0xB1;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
     TCCR1A = 0x81;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
     TCCR1B = 0x09;  // PWM PHASE CORRECT 8 BIT, PRESCALER=1, CLOCK=16MHZ
     TCCR1C = 0x80;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
     
     TCCR2A = 0x02;  // ПРЕСКАЛЕР=8
     TCCR2B = 0x02;  // РЕЖИМ СТС
     TIMSK2 = 0x02;  // ПРЕРЫВАНИЯ ОТ КОМПАРАТОРА А ТАЙМЕРА 2
     OCR2A = 249;    // ДЕЛИМ НА 250  sei();  
     ///////////////////////////////////////////////////////////////////////////////////////////////////
    
     
     Serial.println("START");
     // Преобразуем настройки из формата PMR в формат сервера
     if(PChannel == 0) kanal_PRD = 0;
     else              kanal_PRD = (byte)(((MailIndex & 0xF) * 8) + PChannel);
     kanal_Secret = (uint16_t)((MailIndex & 0xFFFFFFF0) >> 4);         // секретное число 0
    
     Serial.print("MailIndex = ");
     Serial.print(MailIndex,DEC);
     Serial.print("\tPChannel = ");
     Serial.print(PChannel,DEC);
     Serial.print("\tkanal_PRD = ");
     Serial.print(kanal_PRD,DEC);
     Serial.print("\tkanal_Secret = ");
     Serial.println(kanal_Secret,DEC);
    }
      
    volatile uint16_t ticTest = 0; // счетчик прерываний, наращивается на 1 с каждым прерыванием
    char packetBuffer[164]; // Передающий буфер
    
    volatile uint16_t Save0; // указатель записи
    volatile uint16_t Load0; // указатель чтения
    volatile byte buf0[200]; // кольцевой буфер куда из прерывания размещаются данные АЦП
    
    volatile byte Start = 0; // если значение положительное то начинаем передачу
    byte buf[164]; // буфер для передачи в сеть
    int kol = 4; // указатель заполнения буфера передачи (первых 4 байта являются служебными)
    
    volatile uint16_t ticVox = 0; //  
    volatile byte oldVox = 0;     //
    
    
    void loop() {
     if(ticTest > 8000) // если было 8000 прерываний ( одна секунда ), то пора напоснить серверу о себе
     {
      ticTest = 0; // счетчик сбрасываем
      buf[0] = 7;  // тип пакета таймера
      buf[1] = 0;  
      memcpy(&buf[2],&kanal_Secret,2); // значение секретного кода, на основании этого кода сервер будет коммутировать нам нужные пакеты
      Udp.beginPacket(ipout,localPortout+kanal_PRD); //
      Udp.write(buf,4);                              // непосредвственная передача
      Udp.endPacket();                               //
     }
    
    
     int packetSize = Udp.parsePacket(); // проверяем есть ли для нас принятые пакеты
     if(packetSize) // есть
     {
      if(packetSize > 164) packetSize = 164;  // в данном режиме нам не нужны пакеты размером более 164 байта 
      Udp.read(packetBuffer,packetSize); // чтения пакета
      switch (packetSize)
      {
       case 4: // в данном примере нам на прием нужны только служебные пакеты размером 4 байта 
        if(packetBuffer[0] == 110) // пакет с количеством соединений в канале
        {
         uint16_t count;
         memcpy(&count,&packetBuffer[2],sizeof(count));
         Serial.print("Count \t");
         Serial.println(count,DEC);
        }
       break;
       default: 
       {
       };
      }
     }
    
    
     while(Load0 != Save0) // мониторим заполнения кольцевого буфера
     {
      buf[kol] = buf0[Load0]; // следуя за указателем чтения заполняем буфер для передачи
      kol++; // наращиваем счетчик заполнения буфера передачи
      if(kol >= 164) // если буфер заполнен
      {
       if(Start) // и если есть признак разрешения передачи в сеть
       {
        buf[0] = 27; // режим 8000 раз в секунду 8 бит
        Udp.beginPacket(ipout,localPortout+kanal_PRD);
        Udp.write((byte *)buf,164); // передача 160 байт данных с АЦП вместе с 4 байтами служебного заголовка
        Udp.endPacket();
       } 
       kol = 4; // указатель записи в буфер передачи сбрасываем в начальное состояние
      }
    
      if(Load0 < 199) Load0++; // указатель чтения кольевого буфера наращиваем
      else Load0 = 0; // или если достигнут конец буфера сбрасываем в ноль
     }
    }
    
    void(* resetFunc) (void) = 0; // объявляем функцию reset
    
    // функция прерывания 8000 раз в секунду
    ISR(TIMER2_COMPA_vect) 
    {
     int del;     //
    
      
     PORTD |= (1<<pinTest);  // СМОТРИМ НА ВЫВОДЕ D2 ПЕРИОД ПРЕРЫВАНИЙ 8 КГц
     //ДОБАВЛЯЕМ
     byte v = ADCH;  // ЧИТАЕМ СТАРШИЙ БАЙТ ИЗ АЦП
     if(oldVox > v) del = oldVox - v; // Измеряем изменения уровня на входе АЦП относитель прошлого отсчета
     else           del = v - oldVox; // 
     oldVox = v; 
     v = v - 128; // данные с АЦП преобразоваем в значение со знаком из диапазона 0 - +255 в -127 - +128
     buf0[Save0] = v; // заносим в кольцевой буфер
     if(Save0 < 199) Save0++; // наращиваем счетчик записи
     else Save0 = 0; // а если он достиг конца буфера сбрасываем в ноль
    
     
     ticTest++; // счетчик обслуживает передачу тестовых сообщений и ...
     // к сожалению периодически все это подвисает, причина скорее всего во взаимодействии с Ethernrt модулем по SPI
     // если в течении 1,5 секундыв в теле loop не произойдет сброс счетчика в ноль, значит где то на сетевых функциях что то зависло
     if(ticTest > 13000) resetFunc(); // поэтому вызываем программный reset
    
     
     if(del > 10) // если уровень на входе изменяеться больше определенного значения (подбиратся под себя)
     {
      ticVox = 1; // сбрасывем счетчик VOX в еденицу
      if(Start == 0) Start = 1; // если до этого не было признака передачи, то включаем его
     }
     if(ticVox) // пока счетчик Vox истинен
     {
      ticVox++; // наращиваем его
      if(ticVox > 8000) // если уже больше секунды не было на входе АЦП изменений сигнала ( а следовательно ticVox не сбрасывался в 1 )
      {
       ticVox = 0; // выключаем счетчик, теперь он не будет наращиваться
       Start = 0;  // выключаем признак передачи 
      }
     }
    
    
    
    
     ADCSRA |= (1<<ADSC); // ЗАПУСКАЕМ АЦП
     PORTD &= ~(1<<pinTest);  // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ
    }
    Да, звук не высокого качества, с частотой дискретизации 8000 и всего 8 бит динамического диапазона. Но для ардуинки это шикарно, цена вопроса то что надо. Для чего это может пригодится. Например вы далеко от дома, где осталось включенное радио настроенное на местный репитер. Вместе с ним работает представленная схема на ардуинке. Таким образом вы удаленно можете периодически прослушивать далекий эфир. Причем для этого не надо держать включенным персональный компьютер. Версия PMR под Windows автоматически понимает 8 бит 8000, если нет, то обновите ее. Для того что бы Андроид понимал этот режим надо скачать новую версию. Анатолий провел очень большую работу и программа теперь имеет еще больше возможностей.
    До утра подержу эту схему включенной, что бы желающие могли послушать качество звука при такой схеме трансляции. Благо не надо держать компьютер включенным. Но с утра все выключу, что бы продолжить работу над примером двухсторонней связи. Добавлю купленные для ардуинки модули микрофонного усилителя и усилителя для вывода звука на динамик и попробую сделать что то типа ГГС ( громко говорящая связь ) по каналу PMR.

  5. #95
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Итак мы с Владимиром(RZ6AT) представляем версию с дуплексной работой. Код ниже

    Код:
    #include "SPI.h"
    #include "Ethernet.h"
    #include "EthernetUdp.h"
    
    int pinIn = A0; // АЦП
    //ДОБАВЛЯЕМ ПАРАФАЗНЫЕ ВЫХОДЫ ЦАПА
    int pinOutP = 9; // ШИМ +
    //int pinOutM = 10; // ШИМ -
    int pinTest = 2; // ТЕСТ ПРЕРЫВАНИЯ 8 кГц
    int pinStart = 8; // Для начала передачи на этот пин надо подать землю
    int pinCall = 7; // если прийдет вызов на этом пине какое то время будет переменный сигнал 50 ГЦ
    byte mac[] = {0xDE, 0xAD, 0xEE, 0xEF, 0xFE, 0xED};
    IPAddress ip(192,168,0,84);
    IPAddress ipout(194,63,141,124);
    unsigned int localPort = 8984;
    unsigned int localPortout = 16000;
    EthernetUDP Udp;
    
    // Непосредственно для работы с сервером нужны
    byte kanal_PRD;  // номер канала 0 - 255
    uint16_t kanal_Secret; // и секретный код 0 - 65536
    // но в PMR проекте необходим 
    byte PChannel = 2; // номер канала 0 - 8
    uint32_t MailIndex = 1; // и индекс 0 - 999999
    
    
    
    void setup() 
    {
    // put your setup code here, to run once:
     pinMode(pinIn,INPUT); // АЦП
     // В этом примере уже убираю работу с последовательным портом, поскольку хронически не хватет памяти
     // put your setup code here, to run once:
     Ethernet.begin(mac,ip);
     Udp.begin(localPort);
     
      pinMode(pinOutP,OUTPUT);  //ВЫХОД НА НАУШНИК ПЛЮСОВОЙ
      //pinMode(pinOutM,OUTPUT);  //ВЫХОД НА НАУШНИК МИНУСОВОЙ, тут я изменил настройки Владимира (RZ6AT) поскольку у меня немного другая схема вывода звука
      pinMode(pinTest,OUTPUT);
      pinMode(pinCall,OUTPUT);
      pinMode(pinStart,INPUT);
      digitalWrite(pinStart, 1); // поджимаем резистром ножку, что бы не бвло ложного срабатывания
      
    //ДОБАВЛЯЕМ   АЦП
      ADCSRA = 0x87; // ВКЛЮЧАЕМ АЦП, ДЕЛИМ НА 128
      ADCSRB = 0x00;
      ADMUX = 0x60; // СДВИГАЕМ РЕЗУЛЬТАТ ВЛЕВО, REF=5V, A0 ЧИТАЕМ
      
    // ТАЙМЕР1 ДЛЯ ШИМА
    //  TCCR1A = 0xB1;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB // тут изменил, потому что другая у меня схема вывода звука
      TCCR1A = 0x81;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
      TCCR1B = 0x09;  // PWM PHASE CORRECT 8 BIT, PRESCALER=1, CLOCK=16MHZ
      TCCR1C = 0x80;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
     
      TCCR2A = 0x02;  // ПРЕСКАЛЕР=8
      TCCR2B = 0x02;  // РЕЖИМ СТС
      TIMSK2 = 0x02;  // ПРЕРЫВАНИЯ ОТ КОМПАРАТОРА А ТАЙМЕРА 2
      OCR2A = 249;    // ДЕЛИМ НА 250
      sei();
     // Преобразуем настройки из формата PMR в формат сервера
     if(PChannel == 0) kanal_PRD = 0;
     else              kanal_PRD = (byte)(((MailIndex & 0xF) * 8) + PChannel);
     kanal_Secret = (uint16_t)((MailIndex & 0xFFFFFFF0) >> 4);         // секретное число 0
    }  
    volatile uint16_t ticTest = 0; // обслуживает посылку тестовых посылок и перезапуск при зависании
    char packetBuffer[164]; // приемный буфер
    // кольцевой буфер с указателями записи и чтения для работы с АЦП
    volatile uint16_t Save0; 
    volatile uint16_t Load0; 
    volatile byte buf0[200];
    // кольцевой буфер с указателями записи и чтения для ЦАП реализованного на ШИМ
    volatile uint16_t Save1;
    volatile uint16_t Load1;
    volatile byte buf1[640];
    
    volatile uint32_t kolrecv = 0; // хранит количество байт в очереди на воспроизведение
    
    volatile uint16_t play = 0; // индикатор воспроизведения, нужен для корректного вывода звука в прерывании
    volatile byte Start = 0; // если истинно (не ноль) звук перелается в сеть
    //  передающий буфер с указателем
    int kol = 4;
    byte buf[164];
    
    volatile uint32_t tic = 0; // обслуживает сброс переменных если больше секунды не было приема звука
    
    volatile boolean actionState = LOW; // обслуживает индикацию приема вызова, 50 ГЦ
    volatile uint16_t call = 0;
    volatile uint8_t call_50 = 0;
    
    uint8_t d_reg; // в прерывании сюда заносится состояние региста PINB
    
    volatile int16_t Kto = -1; // хранит IP принимаемого пользователя, значение от 0 и выше, если -1 то никого не принимаем
    
    
    void loop() {
    
     
     if(kolrecv < 480) // проверяем, что бы было место под воспроизведение звука, в сязи с катастрофической нехваткой памяти на Ардуино 
     {                 // приходится с Ethernet модуля забирать данные только тогда, когда есть куда их разместить. Таким образом W5100 помогает избавится от рывков звука
       int packetSize = Udp.parsePacket(); // проверяем есть ли данные на приеме
       if(packetSize) // если есть
       {
        if(packetSize > 164) packetSize = 164;  // в данном режиме нам не нужны пакеты размером более 164 байта 
    
        Udp.read(packetBuffer,packetSize); // читаем их
        if(packetSize == 4) // обрабатываем служебные пакеты, они всегда имеют размер 4 байта
        {
         if(packetBuffer[0] == 110) // пакет с количеством соединений в канале, тут на ардуинке это некуда притулить, поскольку нет экрана индикации
         {
          //uint16_t count;
          //memcpy(&count,&packetBuffer[2],sizeof(count));
          //Serial.print("Count \t");
          //Serial.println(count,DEC);
         }
        }
        else // остальные, не служебные пакеты
        {
         switch (packetBuffer[0]) // в первом байте приемного буфера помещен тип пакета
         {
          case 20: // пришел сигнал вызова
           call = 1; 
           digitalWrite(pinCall, HIGH);
          break;
          
          case 27: // пришел звуковой пакет
          {
           int16_t kto,i;
           memcpy(&kto,&packetBuffer[2],sizeof(kto)); // узнаем ID пользователя кто его прислал
           if(Kto == -1) // если до этого никто не от кого не принимали, то 
           {
            Kto = kto; // начинаем принимать этого пользователя
           }
           else
           {
            if(Kto != kto) break; // если уже кого то принимаем, но пакет пришел не от того пользователя, то игнорируем
           }
           tic = 0; // сбрасывется счетчик индикации приема звука
           for(i = 4; i < 164; i++) // заносим принятые данные в кольцевой буфер 
           {
            buf1[Save1]  = packetBuffer[i] + 128;
            if(Save1 < 639) Save1++;
            else Save1 = 0;
           }
           kolrecv = kolrecv + 160;
          }
          break;
          default: 
          {
          };
         }
        }
       }
     }
    
     if(tic > 8000) // нет приема звука уже секунду, 
     {
      tic = 0;
      kolrecv = 0;
      Kto = -1;
     } 
    
     if(!(d_reg & 0x01)) // в этом проекте нет кнопки вызова, вместо этого есть алгоритм поднятия телефонной трубки, если трубка была поднята
     {
      if(call) // но мы синалим внешний вызов
      { // то прекращаем это делать
       call = 0;
       call_50 = 0;
       actionState = LOW;
       digitalWrite(pinCall, LOW);
      }
    
      Start = 1; // Начинаем передачу
     }
     else // земля на 8 ножке пропала ( положили трубку )
     {    //  
      Start = 0; // выключаем передачу
     }
    
    
    
     if(ticTest > 8000) // если прошла секунда
     {
      ticTest = 0; // счетчик сбрасываем
      buf[0] = 7;  // тип пакета таймера
      buf[1] = 0;  
      memcpy(&buf[2],&kanal_Secret,2); // значение секретного кода, на основании этого кода сервер будет коммутировать нам нужные пакеты
      Udp.beginPacket(ipout,localPortout+kanal_PRD); //
      Udp.write(buf,4);                              // непосредвственная передача
      Udp.endPacket();                               //
     }
    
    
    
     while(Load0 != Save0) // Забираем данные с АЦП
     {
      buf[kol] = buf0[Load0];
      kol++;
      if(kol >= 164)
      {
       if(Start) // если разрешена передача, передаем звук
       {
        buf[0] = 27; // режим 8000 раз в секунду 8 бит
        Udp.beginPacket(ipout,localPortout+kanal_PRD);
        Udp.write((byte *)buf,164); // передача 160 байт данных с АЦП вместе с 4 байтами служебного заголовка
        Udp.endPacket();
       } 
       kol = 4; // указатель сбрасываем (первых 4 байта это служебный заголовок)
      }
    
      if(Load0 < 199) Load0++;
      else Load0 = 0;
     }
    
    
     if(call) // обслуживаем прием вызова
     {
      if(call > 24000) // прошло 4 секунды с момента звучания, значит выключаем
      {
       call = 0;
       call_50 = 0;
       actionState = LOW;
       digitalWrite(pinCall, LOW);
      }
      else // звучит сигнал вызова
      {
    //    if(call_50 > 159) // 50 Hz, на моем DECT телефоне куда я подключал данную плату надо на определенную ножку микросхемы подать 50 гц, тогда в телефоне звучит звонок
       if(call_50 > 7) // 1000 Hz, но можно поросто подать 1000 герц на любую пищалку, в тихой комнате ее хорошо слышно. 
       {
        call_50 = 0;
        if(actionState)
        {
         actionState = 0;
        }
        else
        {
         actionState = 1;
        }
        digitalWrite(pinCall, actionState); // сообственно на ножке меняем постоянно TTL уровень
       }
      } 
     }
    
    }
    
    void(* resetFunc) (void) = 0; // объявляем функцию reset
    // функция прерывания
    ISR(TIMER2_COMPA_vect) 
    {
     d_reg = PINB; // Считываем порт, узнаем какие ножки в каком состоянии, за одно считывание сразу 8 ножек, правильное решение для прерывания в котором нельзя долго задерживаться
      
    
     
       
     PORTD |= (1<<pinTest);  // СМОТРИМ НА ВЫВОДЕ D2 ПЕРИОД ПРЕРЫВАНИЙ 8 КГц
     //ДОБАВЛЯЕМ
     byte v = ADCH;  // ЧИТАЕМ СТАРШИЙ БАЙТ ИЗ АЦП
     v = v - 128; // обязательно делаем число со знаком, звуковые карты другие значения не понимают
     buf0[Save0] = v; // заносим значения АЦП в кольцевой буфер
     if(Save0 < 199) Save0++;
     else Save0 = 0;
    
     if(play == 0) // нет было приема звука до этого
     {
      if(kolrecv > 320) play = 1; // если в теле loop приняты уже три звуковых пакета, то начинаем воспроизведение. Упреждение необходимо, что бы избавится от рывков звука
     }
     else // воспроизведение включено
     {
      if(Load1 != Save1) // есть звук для воспроизведения, отправляем его в ЦАП ( ШИМ )
      {
       v = buf1[Load1];
       OCR1A = v; // КИДАЕМ В КОМПАРАТОР А ТАЙМЕРА1
       OCR1B = v; // КИДАЕМ В КОМПАРАТОР В ТАЙМЕРА1
       if(Load1 < 639) Load1++;
       else Load1 = 0;
       if(kolrecv) kolrecv--; // отмечаем уменьшение очереди байт на воспроизведение
       play = 1; // можно конечно не делать этого, но на всякий случай сбрасываю счетчик еще раз
      }
      else // нет звука
      {
       if(play) // если до этого было воспроизведение
       {
        play++; // наращиваем счетчик
        if(play > 500) // если прошло времени с момента прошлого пакета больше 500 тиков, то считаем, что связь разорвана
        {
         play = 0;
         Kto = -1;
         kolrecv = 0;
        }
       }
      }
     }
     ticTest++;
     tic++;
    
     // если программа подвисла, то 
     if(ticTest > 13000) resetFunc(); //вызываем reset
    
    
     if(call) // обслуживание звучания зуммера 
     {
      call++;
      call_50++;
     }
    
     // Тут Владимир проверял на себя АЦП на ЦАП
     // OCR1A = v; // КИДАЕМ В КОМПАРАТОР А ТАЙМЕРА1
     // OCR1B = v; // КИДАЕМ В КОМПАРАТОР В ТАЙМЕРА1
     
     ADCSRA |= (1<<ADSC); // ЗАПУСКАЕМ АЦП
     PORTD &= ~(1<<pinTest);  // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ
    }
    Честно говоря уже устал сидеть за компьютером, поэтому схему расскажу на словах. Относительно входа ничего не поменялось, просто вместо выхода FT-817 был подан сигнал на АЦП с микрофонного усилителя. Выход ЦАП снимается с 9 ножки, на Ардуинке, да и на картинке в предыдущем посте, она подписана этим номером. Обязательно нужно поставить RC фильтр как сделано по входу, номиналы те же 470 и 0.1 и затем подать этот сигнал на высокоомную нагрузку, Владимир настоятельно это рекомендует, так как за наш ЦАП работает ШИМ. Я же просто подал на усилитель. Проверили с Владимиром, он с Андроидной версии, а я с Ардуинки. Все хорошо работает, качество служебной связи. Для того что бы стать на передачу нужно подать землю на восьмую ножку. Если пришел вызов, то на 7 ножке будет слышен зуммер 1000 герц. Подачу вызова не делал, но это уже не сложно сделать самостоятельно. Для этого надо сделать анализ состояния нужного pin и послать служебный пакет из 4 байт, первым байтом где будет значение 20.

  6. #96
    Standart Power
    Регистрация
    18.03.2006
    Возраст
    44
    Сообщений
    272
    Поблагодарили
    67
    Поблагодарил
    13
    Работает! только вот приложение под андроид вылетает при попытке сменить номер канал или нажатии на низ экрана

  7. #97
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    В предыдущем посте допустил оговорку, плату W5100 назвал W5500. Ну кто соображает наверное все понял правильно, на фотографии сразу видно что там изображено. На Arduino Nano нами как раз пробовалась и плата W5500. Она компактнее и современнее, да и по размеру больше соответствует Arduino Nano. Но в макетном варианте у меня лично они работали неустойчиво, длинные провода по интерфейсу SPI глючили. А у Владимира таких глюков не было. Тут надо сразу паять все короткими проводами. А так это почти то же самое, что и описанная выше конструкция. Только надо свою библиотеку использовать EthernetUdp2.h
    Есть соблазн использовать вместо провода Ethernet, - связь WiFi, и для этого ESP8266 очень симпатично просится. Там правда нет того красивого протокола взаимодействия. У меня есть две ESP8266, одна по круче, другая попроще, с минимум GPIO. На мой взгляд единственный путь приспособить ее для проекта это не использовать стандартную прошивку, а залить свой скетч. Взаимодействие с другими устройствами организовать по последовательному порту. И у Arduino и у ESP8266 последовательный порт железно работает со скоростью 1 Мбит, этого вполне достаточно. Если бы стояла такая практическая задача, то уже сделал бы. Наверное и сделаю, но уже не с Arduino в паре, а с STMкой. Тут опять понадобиться помощь Владимира, придется мне осваивать новую среду программирования. Но STMки того стоят. Есть где разгуляться. памяти поболее, а процессоры уже 32 разрядные. В 8-ми разрядных процессорах напрягает то, что трудно применять прежние наработки, вечно где то прячутся ошибки связанные с неявным представлением типов данных. А в 32 разрядных с этим все нормально, готовые куски исходного кода компилируются также как и на персональных компьютерах. Выходные дни заканчиваются, завтра на работу. Хоть и короткая неделя, но рабочая. В принципе, для всех интересующихся данным проектом, основную часть заявленных готовых программ и некоторого исходного кода мы выложили. Есть "пища" для практических экспериментов. У Анатолия наверное еще будет чем удивить с Андроид версией, а мы с Владимиром берем тайм-аут и даст Бог вернемся с уже готовыми вариантами на 32 разрядных процессорах. Главная цель улучшить качество звука и расширить функционал.

  8. #98
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Цитата Сообщение от UA0YAS Посмотреть сообщение
    Работает! только вот приложение под андроид вылетает при попытке сменить номер канал или нажатии на низ экрана
    Тут видимо все зависит от версии Андроида, у меня работает нормально, у Владимира то же. Ребята с которыми общался то же не жалуются.

  9. #99
    Standart Power
    Регистрация
    18.03.2006
    Возраст
    44
    Сообщений
    272
    Поблагодарили
    67
    Поблагодарил
    13
    видимо не для всех версий- не работает на старых 4.2 и на новых 7.0, нужны средние....

  10. #100
    High Power Аватар для UD4FD
    Регистрация
    02.10.2011
    Адрес
    Воронеж
    Возраст
    55
    Сообщений
    961
    Поблагодарили
    1593
    Поблагодарил
    421
    Цитата Сообщение от RN6LIQ Посмотреть сообщение
    В предыдущем посте допустил оговорку, плату W5100 назвал W5500. Ну кто соображает наверное все понял правильно, на фотографии сразу видно что там изображено. На Arduino Nano нами как раз пробовалась и плата W5500. Она компактнее и современнее, да и по размеру больше соответствует Arduino Nano. Но в макетном варианте у меня лично они работали неустойчиво, длинные провода по интерфейсу SPI глючили. А у Владимира таких глюков не было. Тут надо сразу паять все короткими проводами. А так это почти то же самое, что и описанная выше конструкция. Только надо свою библиотеку использовать EthernetUdp2.h
    Есть соблазн использовать вместо провода Ethernet, - связь WiFi, и для этого ESP8266 очень симпатично просится. Там правда нет того красивого протокола взаимодействия. У меня есть две ESP8266, одна по круче, другая попроще, с минимум GPIO. На мой взгляд единственный путь приспособить ее для проекта это не использовать стандартную прошивку, а залить свой скетч. Взаимодействие с другими устройствами организовать по последовательному порту. И у Arduino и у ESP8266 последовательный порт железно работает со скоростью 1 Мбит, этого вполне достаточно. Если бы стояла такая практическая задача, то уже сделал бы. Наверное и сделаю, но уже не с Arduino в паре, а с STMкой. Тут опять понадобиться помощь Владимира, придется мне осваивать новую среду программирования. Но STMки того стоят. Есть где разгуляться. памяти поболее, а процессоры уже 32 разрядные. В 8-ми разрядных процессорах напрягает то, что трудно применять прежние наработки, вечно где то прячутся ошибки связанные с неявным представлением типов данных. А в 32 разрядных с этим все нормально, готовые куски исходного кода компилируются также как и на персональных компьютерах. Выходные дни заканчиваются, завтра на работу. Хоть и короткая неделя, но рабочая. В принципе, для всех интересующихся данным проектом, основную часть заявленных готовых программ и некоторого исходного кода мы выложили. Есть "пища" для практических экспериментов. У Анатолия наверное еще будет чем удивить с Андроид версией, а мы с Владимиром берем тайм-аут и даст Бог вернемся с уже готовыми вариантами на 32 разрядных процессорах. Главная цель улучшить качество звука и расширить функционал.
    Не трогайте ESP8266 - лучше сразу берите ESP32 - у него есть интерфейс I2S для подключения полноценных микросхем аудио-кодеков 16/24 бита, да и проц там мощнее и памяти больше - можно использовать сжатие данных...

  11. #101
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    "Допилил" кнопку вызова (6 pin ) и кнопку включения режима VOX ( 5 pin ). Подавать нужно "землю". Когда идет прием, - на 4 контакте присутствует напряжение, и наоборот. Это пригодится для того, что бы включать/выключать передатчик, в случае удаленного управления. Правда на "железке" не проверял.

    Код:
    #include "SPI.h"
    #include "Ethernet.h"
    #include "EthernetUdp.h"
    
    int pinIn = A0; // АЦП
    //ДОБАВЛЯЕМ ПАРАФАЗНЫЕ ВЫХОДЫ ЦАПА
    int pinOutP = 9; // ШИМ +
    //int pinOutM = 10; // ШИМ -
    int pinTest = 2; // ТЕСТ ПРЕРЫВАНИЯ 8 кГц
    int pinStart = 8; // Для начала передачи на этот пин надо подать землю
    int pinCall = 7; // если поступил вызов на этом пине какое то время будет переменный сигнал 1000 ГЦ
    int pinSendCall = 6; // Для вызова на этот пин надо подать землю
    int pinOnVox = 5; // Для включения VOX на этот пин надо подать землю
    int pinGoPlay = 4; // Для включения передатчика
    byte mac[] = {0xDE, 0xAD, 0xEE, 0xEF, 0xFE, 0xED};
    IPAddress ip(192,168,0,84);
    IPAddress ipout(194,63,141,124);
    unsigned int localPort = 8984;
    unsigned int localPortout = 16000;
    EthernetUDP Udp;
    
    // Непосредственно для работы с сервером нужны
    byte kanal_PRD;  // номер канала 0 - 255
    uint16_t kanal_Secret; // и секретный код 0 - 65536
    // но в PMR проекте необходим 
    byte PChannel = 2; // номер канала 0 - 8
    uint32_t MailIndex = 1; // и индекс 0 - 999999
    
    
    
    void setup() 
    {
    // put your setup code here, to run once:
     pinMode(pinIn,INPUT); // АЦП
     // В этом примере уже убираю работу с последовательным портом, поскольку хронически не хватет памяти
     // put your setup code here, to run once:
     Ethernet.begin(mac,ip);
     Udp.begin(localPort);
     
      pinMode(pinOutP,OUTPUT);  //ВЫХОД НА НАУШНИК ПЛЮСОВОЙ
      //pinMode(pinOutM,OUTPUT);  //ВЫХОД НА НАУШНИК МИНУСОВОЙ, тут я изменил настройки Владимира (RZ6AT) поскольку у меня немного другая схема вывода звука
      pinMode(pinTest,OUTPUT);
      pinMode(pinCall,OUTPUT);
      pinMode(pinStart,INPUT);
      digitalWrite(pinStart, 1); // поджимаем внутренним резистром ножку, что бы не бвло ложного срабатывания
      pinMode(pinSendCall,INPUT);
      digitalWrite(pinSendCall, 1); // поджимаем внутренним резистром ножку, что бы не бвло ложного срабатывания
      pinMode(pinOnVox,INPUT);
      digitalWrite(pinOnVox, 1); // поджимаем внутренним резистром ножку, что бы не бвло ложного срабатывания
      pinMode(pinGoPlay,OUTPUT);
      
       
      
    //ДОБАВЛЯЕМ   АЦП
      ADCSRA = 0x87; // ВКЛЮЧАЕМ АЦП, ДЕЛИМ НА 128
      ADCSRB = 0x00;
      ADMUX = 0x60; // СДВИГАЕМ РЕЗУЛЬТАТ ВЛЕВО, REF=5V, A0 ЧИТАЕМ
      
    // ТАЙМЕР1 ДЛЯ ШИМА
    //  TCCR1A = 0xB1;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB // тут изменил, потому что другая у меня схема вывода звука
      TCCR1A = 0x81;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
      TCCR1B = 0x09;  // PWM PHASE CORRECT 8 BIT, PRESCALER=1, CLOCK=16MHZ
      TCCR1C = 0x80;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
     
      TCCR2A = 0x02;  // ПРЕСКАЛЕР=8
      TCCR2B = 0x02;  // РЕЖИМ СТС
      TIMSK2 = 0x02;  // ПРЕРЫВАНИЯ ОТ КОМПАРАТОРА А ТАЙМЕРА 2
      OCR2A = 249;    // ДЕЛИМ НА 250
      sei();
     // Преобразуем настройки из формата PMR в формат сервера
     if(PChannel == 0) kanal_PRD = 0;
     else              kanal_PRD = (byte)(((MailIndex & 0xF) * 8) + PChannel);
     kanal_Secret = (uint16_t)((MailIndex & 0xFFFFFFF0) >> 4);         // секретное число 0
    
     digitalWrite(pinGoPlay, LOW);
     
    }  
    volatile uint16_t ticTest = 0; // обслуживает посылку тестовых посылок и перезапуск при зависании
    char packetBuffer[164]; // приемный буфер
    // кольцевой буфер с указателями записи и чтения для работы с АЦП
    volatile uint16_t Save0; 
    volatile uint16_t Load0; 
    volatile byte buf0[200];
    // кольцевой буфер с указателями записи и чтения для ЦАП реализованного на ШИМ
    volatile uint16_t Save1;
    volatile uint16_t Load1;
    volatile byte buf1[640];
    
    volatile uint32_t kolrecv = 0; // хранит количество байт в очереди на воспроизведение
    
    volatile uint16_t play = 0; // индикатор воспроизведения, нужен для корректного вывода звука в прерывании
    volatile byte GoPlay = 0; // если истинен, то значит идет прием звука 
    
    volatile byte Start = 0; // если истинно (не ноль) звук перелается в сеть
    //  передающий буфер с указателем
    int kol = 4;
    byte buf[164];
    
    volatile uint32_t tic = 0; // обслуживает сброс переменных если больше секунды не было приема звука
    
    volatile boolean actionState = LOW; // обслуживает индикацию приема вызова, 1000 ГЦ
    volatile uint16_t call = 0;
    volatile uint8_t call_50 = 0;
    
    uint8_t b_reg; // в прерывании сюда заносится состояние региста PINB
    uint8_t d_reg; // в прерывании сюда заносится состояние региста PIND
    
    uint8_t SendCall = 0; // При переходе с нуля в еденицу посылаем вызов 
    uint8_t StartVox = 0; // Если истинно, то работает режим VOX
    
    
    volatile int16_t Kto = -1; // хранит IP принимаемого пользователя, значение от 0 и выше, если -1 то никого не принимаем
    
    
    void loop() {
    
     
     if(kolrecv < 480) // проверяем, что бы было место под воспроизведение звука, в сязи с катастрофической нехваткой памяти на Ардуино 
     {                 // приходится с Ethernet модуля забирать данные только тогда, когда есть куда их разместить. Таким образом W5100 помогает избавится от рывков звука
       int packetSize = Udp.parsePacket(); // проверяем есть ли данные на приеме
       if(packetSize) // если есть
       {
        if(packetSize > 164) packetSize = 164;  // в данном режиме нам не нужны пакеты размером более 164 байта 
    
        Udp.read(packetBuffer,packetSize); // читаем их
        if(packetSize == 4) // обрабатываем служебные пакеты, они всегда имеют размер 4 байта
        {
         if(packetBuffer[0] == 110) // пакет с количеством соединений в канале, тут на ардуинке это некуда притулить, поскольку нет экрана индикации
         {
          //uint16_t count;
          //memcpy(&count,&packetBuffer[2],sizeof(count));
          //Serial.print("Count \t");
          //Serial.println(count,DEC);
         }
        }
        else // остальные, не служебные пакеты
        {
         switch (packetBuffer[0]) // в первом байте приемного буфера помещен тип пакета
         {
          case 20: // пришел сигнал вызова
           call = 1; 
           digitalWrite(pinCall, HIGH);
          break;
          
          case 27: // пришел звуковой пакет
          {
           int16_t kto,i;
           memcpy(&kto,&packetBuffer[2],sizeof(kto)); // узнаем ID пользователя кто его прислал
           if(Kto == -1) // если до этого никто не от кого не принимали, то 
           {
            Kto = kto; // начинаем принимать этого пользователя
           }
           else
           {
            if(Kto != kto) break; // если уже кого то принимаем, но пакет пришел не от того пользователя, то игнорируем
           }
           tic = 0; // сбрасывется счетчик индикации приема звука
           for(i = 4; i < 164; i++) // заносим принятые данные в кольцевой буфер 
           {
            buf1[Save1]  = packetBuffer[i] + 128;
            if(Save1 < 639) Save1++;
            else Save1 = 0;
           }
           kolrecv = kolrecv + 160;
          }
          break;
          default: 
          {
          };
         }
        }
       }
     }
    
     if(tic > 8000) // нет приема звука уже секунду, 
     {
      tic = 0;
      kolrecv = 0;
      Kto = -1;
     } 
    
     if(play)
     {
      if(GoPlay == 0)
      {
       GoPlay = 1; 
       digitalWrite(pinGoPlay, HIGH);
      }
     }
     else
     {
      if(GoPlay)
      {
       GoPlay = 0; 
       digitalWrite(pinGoPlay, LOW);
      }
     }
     
     
    
     if(!(d_reg & 0x40)) // 
     {
      if(SendCall == 0)
      {
       SendCall = 1;
       buf[0] = 20;  // тип пакета вызова
       buf[1] = 0;  
       buf[2] = 0;  
       buf[3] = 0;  
       Udp.beginPacket(ipout,localPortout+kanal_PRD); //
       Udp.write(buf,4);                              // непосредвственная передача
       Udp.endPacket();                               //
      }
     }
     else
     {
      SendCall = 0;
     }
    
     if(!(d_reg & 0x20)) // 
     {
      StartVox = 1; // Режим VOX включен
     }
     else
     {
      StartVox = 0; // Режим VOX выключен
     }
    
    
     if(StartVox == 0)
     {
       if(!(b_reg & 0x01)) // в этом проекте нет кнопки вызова, вместо этого есть алгоритм поднятия телефонной трубки, если трубка была поднята
      {
       if(call) // но мы синалим внешний вызов
       { // то прекращаем это делать
        call = 0;
        call_50 = 0;
        actionState = LOW;
        digitalWrite(pinCall, LOW);
       }
    
       Start = 1; // Начинаем передачу
      }
      else // земля на 8 ножке пропала ( положили трубку )
      {    //  
       Start = 0; // выключаем передачу
      }
     }
    
    
     if(ticTest > 8000) // если прошла секунда
     {
      ticTest = 0; // счетчик сбрасываем
      buf[0] = 7;  // тип пакета таймера
      buf[1] = 0;  
      memcpy(&buf[2],&kanal_Secret,2); // значение секретного кода, на основании этого кода сервер будет коммутировать нам нужные пакеты
      Udp.beginPacket(ipout,localPortout+kanal_PRD); //
      Udp.write(buf,4);                              // непосредвственная передача
      Udp.endPacket();                               //
     }
    
    
    
     while(Load0 != Save0) // Забираем данные с АЦП
     {
      buf[kol] = buf0[Load0];
      kol++;
      if(kol >= 164)
      {
       if(Start) // если разрешена передача, передаем звук
       {
        buf[0] = 27; // режим 8000 раз в секунду 8 бит
        Udp.beginPacket(ipout,localPortout+kanal_PRD);
        Udp.write((byte *)buf,164); // передача 160 байт данных с АЦП вместе с 4 байтами служебного заголовка
        Udp.endPacket();
       } 
       kol = 4; // указатель сбрасываем (первых 4 байта это служебный заголовок)
      }
    
      if(Load0 < 199) Load0++;
      else Load0 = 0;
     }
    
    
     if(call) // обслуживаем прием вызова
     {
      if(call > 24000) // прошло 4 секунды с момента звучания, значит выключаем
      {
       call = 0;
       call_50 = 0;
       actionState = LOW;
       digitalWrite(pinCall, LOW);
      }
      else // звучит сигнал вызова
      {
    //    if(call_50 > 159) // 50 Hz, на моем DECT телефоне куда я подключал данную плату надо на определенную ножку микросхемы подать 50 гц, тогда в телефоне звучит звонок
       if(call_50 > 7) // 1000 Hz, но можно поросто подать 1000 герц на любую пищалку, в тихой комнате ее хорошо слышно. 
       {
        call_50 = 0;
        if(actionState)
        {
         actionState = 0;
        }
        else
        {
         actionState = 1;
        }
        digitalWrite(pinCall, actionState); // сообственно на ножке меняем постоянно TTL уровень
       }
      } 
     }
    
    }
    
    volatile uint16_t ticVox = 0; //  
    volatile byte oldVox = 0;     //
    
    
    void(* resetFunc) (void) = 0; // объявляем функцию reset
    // функция прерывания
    ISR(TIMER2_COMPA_vect) 
    {
     b_reg = PINB; // Считываем порт, узнаем какие ножки в каком состоянии, за одно считывание сразу 8 ножек, правильное решение для прерывания в котором нельзя долго задерживаться
     d_reg = PIND; // 
      
    
       
     PORTD |= (1<<pinTest);  // СМОТРИМ НА ВЫВОДЕ D2 ПЕРИОД ПРЕРЫВАНИЙ 8 КГц
     //ДОБАВЛЯЕМ
     byte v = ADCH;  // ЧИТАЕМ СТАРШИЙ БАЙТ ИЗ АЦП
     int del;
     if(StartVox)
     { 
      if(oldVox > v) del = oldVox - v; // Измеряем изменения уровня на входе АЦП относитель прошлого отсчета
      else           del = v - oldVox; // 
     }
     v = v - 128; // обязательно делаем число со знаком, звуковые карты другие значения не понимают
     buf0[Save0] = v; // заносим значения АЦП в кольцевой буфер
     if(Save0 < 199) Save0++;
     else Save0 = 0;
    
     if(play == 0) // нет было приема звука до этого
     {
      if(kolrecv > 320) play = 1; // если в теле loop приняты уже три звуковых пакета, то начинаем воспроизведение. Упреждение необходимо, что бы избавится от рывков звука
     }
     else // воспроизведение включено
     {
      if(Load1 != Save1) // есть звук для воспроизведения, отправляем его в ЦАП ( ШИМ )
      {
       v = buf1[Load1];
       OCR1A = v; // КИДАЕМ В КОМПАРАТОР А ТАЙМЕРА1
       OCR1B = v; // КИДАЕМ В КОМПАРАТОР В ТАЙМЕРА1
       if(Load1 < 639) Load1++;
       else Load1 = 0;
       if(kolrecv) kolrecv--; // отмечаем уменьшение очереди байт на воспроизведение
       play = 1; // можно конечно не делать этого, но на всякий случай сбрасываю счетчик еще раз
      }
      else // нет звука
      {
       if(play) // если до этого было воспроизведение
       {
        play++; // наращиваем счетчик
        if(play > 500) // если прошло времени с момента прошлого пакета больше 500 тиков, то считаем, что связь разорвана
        {
         play = 0;
         Kto = -1;
         kolrecv = 0;
        }
       }
      }
     }
     ticTest++;
     tic++;
    
     // если программа подвисла, то 
     if(ticTest > 13000) resetFunc(); //вызываем reset
    
    
     if(call) // обслуживание звучания зуммера 
     {
      call++;
      call_50++;
     }
    
     if(StartVox)
     { 
      if(del > 10) // если уровень на входе изменяеться больше определенного значения (подбиратся под себя)
      {
       ticVox = 1; // сбрасывем счетчик VOX в еденицу
       if(Start == 0) Start = 1; // если до этого не было признака передачи, то включаем его
      }
      if(ticVox) // пока счетчик Vox истинен
      {
       ticVox++; // наращиваем его
       if(ticVox > 8000) // если уже больше секунды не было на входе АЦП изменений сигнала ( а следовательно ticVox не сбрасывался в 1 )
       {
        ticVox = 0; // выключаем счетчик, теперь он не будет наращиваться
        Start = 0;  // выключаем признак передачи 
       }
      }
     }
    
     // Тут Владимир проверял на себя АЦП на ЦАП
     // OCR1A = v; // КИДАЕМ В КОМПАРАТОР А ТАЙМЕРА1
     // OCR1B = v; // КИДАЕМ В КОМПАРАТОР В ТАЙМЕРА1
     
     ADCSRA |= (1<<ADSC); // ЗАПУСКАЕМ АЦП
     PORTD &= ~(1<<pinTest);  // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ
    }

  12. #102
    Standart Power
    Регистрация
    18.03.2006
    Возраст
    44
    Сообщений
    272
    Поблагодарили
    67
    Поблагодарил
    13
    Василий, а сам сервер это " секрет фирмы"?? Не распространяется?

  13. #103
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Цитата Сообщение от UA0YAS Посмотреть сообщение
    Василий, а сам сервер это " секрет фирмы"?? Не распространяется?
    Писал уже ранее, что сейчас используем халявный сервер который оплачен до октября месяца этого года, одновременно тестируем нагрузку на сервер. По окончании аренды я предоставлю сервер в версиях под Windows и Linux в виде исполняемых файлов. Кто то из доверенных людей, получит и в исходных кодах. Я предпочитаю публично не делиться исходными кодами, особенно серверов. Хотя там ничего умудренного нет, но облегчать жизнь разным хулиганам не хочу. Они и в исполняемых файлах находят как напакостить. А тратить свое время на блокировки их злостных действий мне не хочется, в виртуальной шарманке это надоело. Так что пока сервер един, до октября, сервера выкладывать не буду. А после, - пожалуйста. Весь смак этого простого, а значит незащищенного, проекта в его простоте. А защита будет делаться наличием множества неопубликованных серверов( с неизвестными для широкой публики IP и портами ) и секретным кодом внутри их.

  14. #104
    QRP Аватар для Старшина
    Регистрация
    13.02.2019
    Сообщений
    35
    Поблагодарили
    4
    Поблагодарил
    0
    Василий спасибо программу работает на все 100%. Анатолий спасибо за программу работает пока на 90% под андроидом 4.1.2 вылетает программа при попытке вкл. на передачу.
    С праздником с Днём Победы!!! Здоровья,Удачи и Успехов во всём.

  15. #105
    Standart Power Аватар для RN6LIQ
    Регистрация
    12.12.2006
    Адрес
    Ростов-на-Дону
    Возраст
    56
    Сообщений
    354
    Поблагодарили
    44
    Поблагодарил
    46
    Для более правильного подключения к интернету добавлено несколько строк где указан шлюз ( ваш роутер ), маска подсети и DNS-сервер. Исходный код ниже
    Код:
    #include "SPI.h"
    #include "Ethernet.h"
    #include "EthernetUdp.h"
    
    int pinIn = A0; // АЦП
    //ДОБАВЛЯЕМ ПАРАФАЗНЫЕ ВЫХОДЫ ЦАПА
    int pinOutP = 9; // ШИМ +
    //int pinOutM = 10; // ШИМ -
    int pinTest = 2; // ТЕСТ ПРЕРЫВАНИЯ 8 кГц
    int pinStart = 8; // Для начала передачи на этот пин надо подать землю
    int pinCall = 7; // если поступил вызов на этом пине какое то время будет переменный сигнал 1000 ГЦ
    int pinSendCall = 6; // Для вызова на этот пин надо подать землю
    int pinOnVox = 5; // Для включения VOX на этот пин надо подать землю
    int pinGoPlay = 4; // Для включения передатчика
    byte mac[] = {0xDE, 0xAD, 0xEE, 0xEF, 0xFE, 0xED};
    IPAddress ip(192,168,0,84);
    IPAddress ipout(194,63,141,124);
    IPAddress myDns(192, 168, 0, 1);
    IPAddress gateway(192, 168, 0, 1);
    IPAddress subnet(255, 255, 255, 0);
    unsigned int localPort = 8984;
    unsigned int localPortout = 16000;
    EthernetUDP Udp;
    
    // Непосредственно для работы с сервером нужны
    byte kanal_PRD;  // номер канала 0 - 255
    uint16_t kanal_Secret; // и секретный код 0 - 65536
    // но в PMR проекте необходим 
    byte PChannel = 2; // номер канала 0 - 8
    uint32_t MailIndex = 1; // и индекс 0 - 999999
    
    
    
    void setup() 
    {
    // put your setup code here, to run once:
     pinMode(pinIn,INPUT); // АЦП
     // В этом примере уже убираю работу с последовательным портом, поскольку хронически не хватет памяти
     // put your setup code here, to run once:
     //Ethernet.begin(mac,ip);
     Ethernet.begin(mac, ip, myDns, gateway, subnet); 
     Udp.begin(localPort);
     
      pinMode(pinOutP,OUTPUT);  //ВЫХОД НА НАУШНИК ПЛЮСОВОЙ
      //pinMode(pinOutM,OUTPUT);  //ВЫХОД НА НАУШНИК МИНУСОВОЙ, тут я изменил настройки Владимира (RZ6AT) поскольку у меня немного другая схема вывода звука
      pinMode(pinTest,OUTPUT);
      pinMode(pinCall,OUTPUT);
      pinMode(pinStart,INPUT);
      digitalWrite(pinStart, 1); // поджимаем внутренним резистром ножку, что бы не бвло ложного срабатывания
      pinMode(pinSendCall,INPUT);
      digitalWrite(pinSendCall, 1); // поджимаем внутренним резистром ножку, что бы не бвло ложного срабатывания
      pinMode(pinOnVox,INPUT);
      digitalWrite(pinOnVox, 1); // поджимаем внутренним резистром ножку, что бы не бвло ложного срабатывания
      pinMode(pinGoPlay,OUTPUT);
      
       
      
    //ДОБАВЛЯЕМ   АЦП
      ADCSRA = 0x87; // ВКЛЮЧАЕМ АЦП, ДЕЛИМ НА 128
      ADCSRB = 0x00;
      ADMUX = 0x60; // СДВИГАЕМ РЕЗУЛЬТАТ ВЛЕВО, REF=5V, A0 ЧИТАЕМ
      
    // ТАЙМЕР1 ДЛЯ ШИМА
    //  TCCR1A = 0xB1;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB // тут изменил, потому что другая у меня схема вывода звука
      TCCR1A = 0x81;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
      TCCR1B = 0x09;  // PWM PHASE CORRECT 8 BIT, PRESCALER=1, CLOCK=16MHZ
      TCCR1C = 0x80;  // ДЁРГАЕМ ПИНЫ ПО СОМРА & COMPB 
     
      TCCR2A = 0x02;  // ПРЕСКАЛЕР=8
      TCCR2B = 0x02;  // РЕЖИМ СТС
      TIMSK2 = 0x02;  // ПРЕРЫВАНИЯ ОТ КОМПАРАТОРА А ТАЙМЕРА 2
      OCR2A = 249;    // ДЕЛИМ НА 250
      sei();
     // Преобразуем настройки из формата PMR в формат сервера
     if(PChannel == 0) kanal_PRD = 0;
     else              kanal_PRD = (byte)(((MailIndex & 0xF) * 8) + PChannel);
     kanal_Secret = (uint16_t)((MailIndex & 0xFFFFFFF0) >> 4);         // секретное число 0
    
     digitalWrite(pinGoPlay, LOW);
     
    }  
    volatile uint16_t ticTest = 0; // обслуживает посылку тестовых посылок и перезапуск при зависании
    char packetBuffer[164]; // приемный буфер
    // кольцевой буфер с указателями записи и чтения для работы с АЦП
    volatile uint16_t Save0; 
    volatile uint16_t Load0; 
    volatile byte buf0[200];
    // кольцевой буфер с указателями записи и чтения для ЦАП реализованного на ШИМ
    volatile uint16_t Save1;
    volatile uint16_t Load1;
    volatile byte buf1[640];
    
    volatile uint32_t kolrecv = 0; // хранит количество байт в очереди на воспроизведение
    
    volatile uint16_t play = 0; // индикатор воспроизведения, нужен для корректного вывода звука в прерывании
    volatile byte GoPlay = 0; // если истинен, то значит идет прием звука 
    
    volatile byte Start = 0; // если истинно (не ноль) звук перелается в сеть
    //  передающий буфер с указателем
    int kol = 4;
    byte buf[164];
    
    volatile uint32_t tic = 0; // обслуживает сброс переменных если больше секунды не было приема звука
    
    volatile boolean actionState = LOW; // обслуживает индикацию приема вызова, 1000 ГЦ
    volatile uint16_t call = 0;
    volatile uint8_t call_50 = 0;
    
    uint8_t b_reg; // в прерывании сюда заносится состояние региста PINB
    uint8_t d_reg; // в прерывании сюда заносится состояние региста PIND
    
    uint8_t SendCall = 0; // При переходе с нуля в еденицу посылаем вызов 
    uint8_t StartVox = 0; // Если истинно, то работает режим VOX
    
    
    volatile int16_t Kto = -1; // хранит IP принимаемого пользователя, значение от 0 и выше, если -1 то никого не принимаем
    
    
    void loop() {
    
     
     if(kolrecv < 480) // проверяем, что бы было место под воспроизведение звука, в сязи с катастрофической нехваткой памяти на Ардуино 
     {                 // приходится с Ethernet модуля забирать данные только тогда, когда есть куда их разместить. Таким образом W5100 помогает избавится от рывков звука
       int packetSize = Udp.parsePacket(); // проверяем есть ли данные на приеме
       if(packetSize) // если есть
       {
        if(packetSize > 164) packetSize = 164;  // в данном режиме нам не нужны пакеты размером более 164 байта 
    
        Udp.read(packetBuffer,packetSize); // читаем их
        if(packetSize == 4) // обрабатываем служебные пакеты, они всегда имеют размер 4 байта
        {
         if(packetBuffer[0] == 110) // пакет с количеством соединений в канале, тут на ардуинке это некуда притулить, поскольку нет экрана индикации
         {
          //uint16_t count;
          //memcpy(&count,&packetBuffer[2],sizeof(count));
          //Serial.print("Count \t");
          //Serial.println(count,DEC);
         }
        }
        else // остальные, не служебные пакеты
        {
         switch (packetBuffer[0]) // в первом байте приемного буфера помещен тип пакета
         {
          case 20: // пришел сигнал вызова
           call = 1; 
           digitalWrite(pinCall, HIGH);
          break;
          
          case 27: // пришел звуковой пакет
          {
           int16_t kto,i;
           memcpy(&kto,&packetBuffer[2],sizeof(kto)); // узнаем ID пользователя кто его прислал
           if(Kto == -1) // если до этого никто не от кого не принимали, то 
           {
            Kto = kto; // начинаем принимать этого пользователя
           }
           else
           {
            if(Kto != kto) break; // если уже кого то принимаем, но пакет пришел не от того пользователя, то игнорируем
           }
           tic = 0; // сбрасывется счетчик индикации приема звука
           for(i = 4; i < 164; i++) // заносим принятые данные в кольцевой буфер 
           {
            buf1[Save1]  = packetBuffer[i] + 128;
            if(Save1 < 639) Save1++;
            else Save1 = 0;
           }
           kolrecv = kolrecv + 160;
          }
          break;
          default: 
          {
          };
         }
        }
       }
     }
    
     if(tic > 8000) // нет приема звука уже секунду, 
     {
      tic = 0;
      kolrecv = 0;
      Kto = -1;
     } 
    
     if(play)
     {
      if(GoPlay == 0)
      {
       GoPlay = 1; 
       digitalWrite(pinGoPlay, HIGH);
      }
     }
     else
     {
      if(GoPlay)
      {
       GoPlay = 0; 
       digitalWrite(pinGoPlay, LOW);
      }
     }
     
     
    
     if(!(d_reg & 0x40)) // 
     {
      if(SendCall == 0)
      {
       SendCall = 1;
       buf[0] = 20;  // тип пакета вызова
       buf[1] = 0;  
       buf[2] = 0;  
       buf[3] = 0;  
       Udp.beginPacket(ipout,localPortout+kanal_PRD); //
       Udp.write(buf,4);                              // непосредвственная передача
       Udp.endPacket();                               //
      }
     }
     else
     {
      SendCall = 0;
     }
    
     if(!(d_reg & 0x20)) // 
     {
      StartVox = 1; // Режим VOX включен
     }
     else
     {
      StartVox = 0; // Режим VOX выключен
     }
    
    
     if(StartVox == 0)
     {
       if(!(b_reg & 0x01)) // в этом проекте нет кнопки вызова, вместо этого есть алгоритм поднятия телефонной трубки, если трубка была поднята
      {
       if(call) // но мы синалим внешний вызов
       { // то прекращаем это делать
        call = 0;
        call_50 = 0;
        actionState = LOW;
        digitalWrite(pinCall, LOW);
       }
    
       Start = 1; // Начинаем передачу
      }
      else // земля на 8 ножке пропала ( положили трубку )
      {    //  
       Start = 0; // выключаем передачу
      }
     }
    
    
     if(ticTest > 8000) // если прошла секунда
     {
      ticTest = 0; // счетчик сбрасываем
      buf[0] = 7;  // тип пакета таймера
      buf[1] = 0;  
      memcpy(&buf[2],&kanal_Secret,2); // значение секретного кода, на основании этого кода сервер будет коммутировать нам нужные пакеты
      Udp.beginPacket(ipout,localPortout+kanal_PRD); //
      Udp.write(buf,4);                              // непосредвственная передача
      Udp.endPacket();                               //
     }
    
    
    
     while(Load0 != Save0) // Забираем данные с АЦП
     {
      buf[kol] = buf0[Load0];
      kol++;
      if(kol >= 164)
      {
       if(Start) // если разрешена передача, передаем звук
       {
        buf[0] = 27; // режим 8000 раз в секунду 8 бит
        Udp.beginPacket(ipout,localPortout+kanal_PRD);
        Udp.write((byte *)buf,164); // передача 160 байт данных с АЦП вместе с 4 байтами служебного заголовка
        Udp.endPacket();
       } 
       kol = 4; // указатель сбрасываем (первых 4 байта это служебный заголовок)
      }
    
      if(Load0 < 199) Load0++;
      else Load0 = 0;
     }
    
    
     if(call) // обслуживаем прием вызова
     {
      if(call > 24000) // прошло 4 секунды с момента звучания, значит выключаем
      {
       call = 0;
       call_50 = 0;
       actionState = LOW;
       digitalWrite(pinCall, LOW);
      }
      else // звучит сигнал вызова
      {
    //    if(call_50 > 159) // 50 Hz, на моем DECT телефоне куда я подключал данную плату надо на определенную ножку микросхемы подать 50 гц, тогда в телефоне звучит звонок
       if(call_50 > 7) // 1000 Hz, но можно поросто подать 1000 герц на любую пищалку, в тихой комнате ее хорошо слышно. 
       {
        call_50 = 0;
        if(actionState)
        {
         actionState = 0;
        }
        else
        {
         actionState = 1;
        }
        digitalWrite(pinCall, actionState); // сообственно на ножке меняем постоянно TTL уровень
       }
      } 
     }
    
    }
    
    volatile uint16_t ticVox = 0; //  
    volatile byte oldVox = 0;     //
    
    
    void(* resetFunc) (void) = 0; // объявляем функцию reset
    // функция прерывания
    ISR(TIMER2_COMPA_vect) 
    {
     b_reg = PINB; // Считываем порт, узнаем какие ножки в каком состоянии, за одно считывание сразу 8 ножек, правильное решение для прерывания в котором нельзя долго задерживаться
     d_reg = PIND; // 
      
    
       
     PORTD |= (1<<pinTest);  // СМОТРИМ НА ВЫВОДЕ D2 ПЕРИОД ПРЕРЫВАНИЙ 8 КГц
     //ДОБАВЛЯЕМ
     byte v = ADCH;  // ЧИТАЕМ СТАРШИЙ БАЙТ ИЗ АЦП
     int del;
     if(StartVox)
     { 
      if(oldVox > v) del = oldVox - v; // Измеряем изменения уровня на входе АЦП относитель прошлого отсчета
      else           del = v - oldVox; // 
     }
     v = v - 128; // обязательно делаем число со знаком, звуковые карты другие значения не понимают
     buf0[Save0] = v; // заносим значения АЦП в кольцевой буфер
     if(Save0 < 199) Save0++;
     else Save0 = 0;
    
     if(play == 0) // нет было приема звука до этого
     {
      if(kolrecv > 320) play = 1; // если в теле loop приняты уже три звуковых пакета, то начинаем воспроизведение. Упреждение необходимо, что бы избавится от рывков звука
     }
     else // воспроизведение включено
     {
      if(Load1 != Save1) // есть звук для воспроизведения, отправляем его в ЦАП ( ШИМ )
      {
       v = buf1[Load1];
       OCR1A = v; // КИДАЕМ В КОМПАРАТОР А ТАЙМЕРА1
       OCR1B = v; // КИДАЕМ В КОМПАРАТОР В ТАЙМЕРА1
       if(Load1 < 639) Load1++;
       else Load1 = 0;
       if(kolrecv) kolrecv--; // отмечаем уменьшение очереди байт на воспроизведение
       play = 1; // можно конечно не делать этого, но на всякий случай сбрасываю счетчик еще раз
      }
      else // нет звука
      {
       if(play) // если до этого было воспроизведение
       {
        play++; // наращиваем счетчик
        if(play > 500) // если прошло времени с момента прошлого пакета больше 500 тиков, то считаем, что связь разорвана
        {
         play = 0;
         Kto = -1;
         kolrecv = 0;
        }
       }
      }
     }
     ticTest++;
     tic++;
    
     // если программа подвисла, то 
     if(ticTest > 13000) resetFunc(); //вызываем reset
    
    
     if(call) // обслуживание звучания зуммера 
     {
      call++;
      call_50++;
     }
    
     if(StartVox)
     { 
      if(del > 10) // если уровень на входе изменяеться больше определенного значения (подбиратся под себя)
      {
       ticVox = 1; // сбрасывем счетчик VOX в еденицу
       if(Start == 0) Start = 1; // если до этого не было признака передачи, то включаем его
      }
      if(ticVox) // пока счетчик Vox истинен
      {
       ticVox++; // наращиваем его
       if(ticVox > 8000) // если уже больше секунды не было на входе АЦП изменений сигнала ( а следовательно ticVox не сбрасывался в 1 )
       {
        ticVox = 0; // выключаем счетчик, теперь он не будет наращиваться
        Start = 0;  // выключаем признак передачи 
       }
      }
     }
    
     // Тут Владимир проверял на себя АЦП на ЦАП
     // OCR1A = v; // КИДАЕМ В КОМПАРАТОР А ТАЙМЕРА1
     // OCR1B = v; // КИДАЕМ В КОМПАРАТОР В ТАЙМЕРА1
     
     ADCSRA |= (1<<ADSC); // ЗАПУСКАЕМ АЦП
     PORTD &= ~(1<<pinTest);  // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ
    }
    Вчера попробовал этот код без изменений залить в Arduino Nano, за Ethernet Shield работал W5500. Все прекрасно работает, при этом размеры конструкции меньше.

    Все по той же ССЫЛКЕ выложена новая версия программы под Андроид. Добавлено 5 кнопок памяти, очень удобно сохраняются настройки , в том числе для разных серверов ( пригодится в дальнейшем ). Для того что бы сохранить, надо нажать кнопку и удерживать. Если кликнуть по кнопке один раз, то вызываются сохраненные настройки.

    Для версии программы под Windows есть возможность менять режимы текстовыми файлами, которые необходимо расположить в рабочем каталоге программы. Файл ip.txt позволяет после старта программы подключаться по указанному адресу. В файле в первой строке адрес указывается так, например:
    192.168.0.5:16000
    Обязательно надо нажать Enter в конце строки. Файл vox.txt включает VOX. Пример:
    5
    1000

    5 - уровень при котором срабатывает передача ( диапазон до 127 ), 1000 - количество миллисекунд после которых передача выключится, если на входе не будет достаточного уровня. Windows версия умеет принимать все заложенные в проект режимы качества звука, но для того что бы выставить нужный режим передачи необходимо создать в рабочем каталоге программы файл root.txt с содержимым:
    1
    0,1 - 16000 G711 - 128 Kbit/s
    2 - 16000 16 bit - 256 Kbit/s
    3 - 16000 8 bit - 128 Kbit/s
    4 - GSM - 32 Kbit/s ( должно быть 13.2 Kbit/s, но реально заголовки пакетов UDP увеличивают трафик )
    5 - 8000 16 bit - 128 Kbit/s
    6 - 8000 G711 - 64 Kbit/s
    7 - 8000 8 bit - 64 Kbit/s

    Первая строка определяет режим передачи, остальные строки служат подсказкой.
    Программа делалась с расчетом на неподготовленных пользователей, которых нельзя было пугать множеством настроек, поэтому для подготовленных пользователей была реализована вот такая немного за умудренная система. Есть также файлы tx.txt, rx.txt и rxfortx.txt в них можно прописать соответствующие уровни числами с плавающей запятой (на самом деле вместо запятой надо в файле прописывать точку). rxfortx.txt работает только тогда, когда в программе установлен симплекс. На всякий случай КАЧАЕМ актуальную версию все по той же ССЫЛКЕ.

    Таким образом версии программ которые мы вам выложили позволяют создавать свои каналы удаленного управления радио станциями и не только ими. Это может быть оперативная связь по принципу - один сказал, - все услышали. Это может быть индивидуальный телефон подключенный через Arduino к интернету на который всегда можно позвонить через интернет с мобильного устройства. Когда закончится тестирование нынешнего сервера и нами будут выложены сервера, то такая связь станет еще более независимой от авторов данного проекта.

Похожие темы

  1. Вопрос по поводу мощности в PMR
    от kerosin в разделе УКВ аппаратура
    Ответов: 21
    Последнее сообщение: 29.02.2012, 06:47
  2. Не могу связать Joker TH-UVF1 с Voxtel mr550 (PMR)
    от kerosin в разделе УКВ аппаратура
    Ответов: 2
    Последнее сообщение: 12.06.2011, 06:00
  3. Схема имитатора звука костра
    от Leonid51 в разделе Бытовая техника
    Ответов: 3
    Последнее сообщение: 19.09.2006, 22:18
  4. Важное решение ГКРЧ по диапазону 446 MHz и рациям PMR
    от UA3IRS в разделе 70-сантиметровый диапазон (430,0 ÷ 440,0 МГц)
    Ответов: 4
    Последнее сообщение: 01.12.2005, 01:48
  5. Имитатор радиостанции
    от в разделе Общие вопросы
    Ответов: 1
    Последнее сообщение: 17.05.2002, 07:52

Социальные закладки

Социальные закладки

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •  
Похоже, что вы используете блокировщик рекламы :(
Форум QRZ.RU существует только за счет рекламы, поэтому мы были бы Вам благодарны если Вы внесете сайт в список исключений!
как отключить
×
Рейтинг@Mail.ru
eXTReMe Tracker


Похоже, что вы используете блокировщик рекламы :(
Форум QRZ.RU существует только за счет рекламы, поэтому мы были бы Вам благодарны если Вы внесете сайт в список исключений!
как отключить
×