-
01.05.2020, 07:56 #91
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 354
- Поблагодарили
- 44
- Поблагодарил
- 46
Тимофей, благодарю вас за советы, но хотелось бы еще раз пояснить, что у этого проекта своя ниша и в первом посте я описал его предназначение. Создавать полную копию виртуального эфира никогда не стремился, но полу копии у меня есть. Вот тут виртуальная шарманка, где общаются люди у которых проблемы с антеннами или просто нравится общение в формате радио эфира. Красивой мордашки трансивера нет, жаль тратить на это свое время которого всегда не хватает. Официальный сайт проекта. Есть проект приближенный более и менее к реальному звучанию эфира. Там меньше народу, но все равно есть свои поклонники которые, что бы не засорять реальный эфир проводят тренировочные связи телеграфом.
А что касается монетизации моих проектов, так слава Богу есть на что жить. Есть работа и к тому поклонники моих программ периодически помогают своими пожертвованиями в поддержке серверов, их оплаты и модернизации. Анатолий свою версию данного проекта так же предоставляет бесплатно, по крайней мере версию с теми же возможностями что и моя версия под Windows. Но какие то другие проекты конечно же ему надо монетизировать, но это уже его выбор.73. Василий М.Колотуша (RN6LIQ)
-
01.05.2020, 10:44 #92
-
01.05.2020, 11:24 #93
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 354
- Поблагодарили
- 44
- Поблагодарил
- 46
Евгений, версии под Windows и Андроид это просто дань совместимости с популярными операционными системами где будет работать конечная программа. А с другой стороны, рядом с радиостанцией, будет работать версия под Linux или версия на микроконтроллере, и вот там все будет как вы хотите. Если вы еще не юзали микрокомпьютеры, типа RaspberryPI, то советую это сделать. Это более экономически оправданное решение чем использовать для этого полноценный компьютер под управлением Windows. Но и тут можно использовать Windows версию с функцией VOX, как ее включить могу подсказать. А еще круче прикрутить к радиостанции микроконтроллер. Сейчас, когда базовая система создана, я возвращаюсь к совместной работе с Владимиром, RZ6AT, и мы будем делать версию этой программы на микроконтроллерах, результаты этой работы будем выкладывать тут же, на этой ветке.
73. Василий М.Колотуша (RN6LIQ)
-
04.05.2020, 18:35 #94
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 354
- Поблагодарили
- 44
- Поблагодарил
- 46
Итак, мы с Владимиром(RZ6AT) покажем применение микроконтроллера Arduino UNO + Ethernet модуль W5500 в данном проекте. Для начала пример попроще. Трансляция только в одну сторону. Выход радиостанции FT-817 будем транслировать во второй канал PMR с индексом 1. Для этого понадобится
Arduino UNO + Ethernet модуль W5500 + FT817 + очень простая схема сопряжения
Ну и сама программа, если есть вопросы по инициализации микроконтроллера, то адресуйте их Владимиру ( 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); // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ }
До утра подержу эту схему включенной, что бы желающие могли послушать качество звука при такой схеме трансляции. Благо не надо держать компьютер включенным. Но с утра все выключу, что бы продолжить работу над примером двухсторонней связи. Добавлю купленные для ардуинки модули микрофонного усилителя и усилителя для вывода звука на динамик и попробую сделать что то типа ГГС ( громко говорящая связь ) по каналу PMR.73. Василий М.Колотуша (RN6LIQ)
-
05.05.2020, 12:05 #95
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 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); // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ }
73. Василий М.Колотуша (RN6LIQ)
-
05.05.2020, 15:55 #96
- Регистрация
- 18.03.2006
- Возраст
- 44
- Сообщений
- 272
- Поблагодарили
- 67
- Поблагодарил
- 13
Работает! только вот приложение под андроид вылетает при попытке сменить номер канал или нажатии на низ экрана
-
05.05.2020, 16:21 #97
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 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 разрядных процессорах. Главная цель улучшить качество звука и расширить функционал.73. Василий М.Колотуша (RN6LIQ)
-
05.05.2020, 16:24 #98
-
05.05.2020, 17:06 #99
- Регистрация
- 18.03.2006
- Возраст
- 44
- Сообщений
- 272
- Поблагодарили
- 67
- Поблагодарил
- 13
видимо не для всех версий- не работает на старых 4.2 и на новых 7.0, нужны средние....
-
06.05.2020, 01:24 #100
- Регистрация
- 02.10.2011
- Адрес
- Воронеж
- Возраст
- 55
- Сообщений
- 962
- Поблагодарили
- 1607
- Поблагодарил
- 421
RC3KZ ex UD4FD ex UA4FQY
Производство мачт www.antmast.ru
-
06.05.2020, 09:22 #101
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 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); // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ }
73. Василий М.Колотуша (RN6LIQ)
-
06.05.2020, 10:35 #102
- Регистрация
- 18.03.2006
- Возраст
- 44
- Сообщений
- 272
- Поблагодарили
- 67
- Поблагодарил
- 13
Василий, а сам сервер это " секрет фирмы"?? Не распространяется?
-
06.05.2020, 11:35 #103
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 354
- Поблагодарили
- 44
- Поблагодарил
- 46
Писал уже ранее, что сейчас используем халявный сервер который оплачен до октября месяца этого года, одновременно тестируем нагрузку на сервер. По окончании аренды я предоставлю сервер в версиях под Windows и Linux в виде исполняемых файлов. Кто то из доверенных людей, получит и в исходных кодах. Я предпочитаю публично не делиться исходными кодами, особенно серверов. Хотя там ничего умудренного нет, но облегчать жизнь разным хулиганам не хочу. Они и в исполняемых файлах находят как напакостить. А тратить свое время на блокировки их злостных действий мне не хочется, в виртуальной шарманке это надоело. Так что пока сервер един, до октября, сервера выкладывать не буду. А после, - пожалуйста. Весь смак этого простого, а значит незащищенного, проекта в его простоте. А защита будет делаться наличием множества неопубликованных серверов( с неизвестными для широкой публики IP и портами ) и секретным кодом внутри их.
Последний раз редактировалось RN6LIQ; 06.05.2020 в 11:40.
73. Василий М.Колотуша (RN6LIQ)
-
08.05.2020, 10:07 #104
Василий спасибо программу работает на все 100%. Анатолий спасибо за программу работает пока на 90% под андроидом 4.1.2 вылетает программа при попытке вкл. на передачу.
С праздником с Днём Победы!!! Здоровья,Удачи и Успехов во всём.
-
12.05.2020, 09:21 #105
- Регистрация
- 12.12.2006
- Адрес
- Ростов-на-Дону
- Возраст
- 57
- Сообщений
- 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); // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ }
Все по той же ССЫЛКЕ выложена новая версия программы под Андроид. Добавлено 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 к интернету на который всегда можно позвонить через интернет с мобильного устройства. Когда закончится тестирование нынешнего сервера и нами будут выложены сервера, то такая связь станет еще более независимой от авторов данного проекта.73. Василий М.Колотуша (RN6LIQ)
Социальные закладки