эх, еще бы CAT пробросить......:tmi:
Вид для печати
эх, еще бы CAT пробросить......:tmi:
На Arduino можно CAT самому сочинить, тем более там уже показано какие пины можно задействовать. Пин для начала передачи есть, пин где появляется уровень когда что то приходит на прием тоже есть. Осталось согласовать с конкретной радиостанцией и вперед. Правда качество звука еще низкое. Более сложное управление с перестройкой частоты и режимов... Но это уже совершенно другая тема и программы совершенно другого уровня. Мы тут за то, что бы все было как можно проще.
А на Windows работает VOX, если и на радиостанции он есть, то можно обойтись и этим. На Linux попробую в обозримом будущем какой либо пример на RaspberryPI сделать. Он хоть дороже микроконтроллера, но дешевле персонального компа, тем более можно найти дешевые платы с Китая типа OrangePI. Только придется применять USB звуковые карты. Как правило входа звука на этих микрокомпьютерах чаще всего нет. Или выход звука пускай свой будет, а вход сделать на микроконтроллере, том же Arduino или STM32F103.
12.05.2020 Выложена новая версия программы под Андроид. Все ровно Анатолий при попытки перехода на передачу или нажатии на низ экрана программа вылетает...????
На дисплеи пишет (В приложении "Виртуальная рация" произошла ошибка) ??? Вот пока почему-то так. А во остальном все нормально (прием и прочее все работает)
Всем добрый день!
Вчера заметили еще одну проблему в андроидной версии. У двух станций с разным битрейтом были проблемы. У первой стоял дефолтный, на второй пониженый. Та станция у которой был дефолтный битрейт вторую не слышала. В обратную сторону все было нормально. При смене битрейта связь востановилась.
Тут есть один нюанс :s9: Андроидная версия может работать только в одном из двух режимов частот дискретизации, или 8000 или 16000. Мои программы под Windows могут по всякому работать, что Шарманка, что PMR-ка, но все это за счет математики. В той же PMR-ке из базовой частоты 16000 я делаю частоту 8000 методом децимации, то есть методом прореживания. Но если это делать без предварительной цифровой фильтрации, то звук приобретает металлический оттенок, то есть там есть не нужные помехи. Перенести Сишные библиотеки в Андроидную Javu не так то просто. Поэтому Анатолий сделал проще, на прием у него автоматически переключается или 16000 или 8000, и дальше внутри этой частоты дискретизации можно слышать 8 бит, 16 бит и G711, но только именно для этой частоты дискретизации. Поэтому между Андроидными версиями надо всем корреспондентам ставить один режим. А для того что бы связываться с микроконтроллером Ардуино есть один единственный режим, - 8 бит, 8000. Кстати выкладываю последнюю версию кода, которую мы с Владимиром причесали
Там где строка if(del > 10) // если уровень на входе изменяется больше определенного значения (подбирается под себя)Код:#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; // Для включения передатчика
int pinGoStart = 3; // Идет передача
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);
pinMode(pinGoStart,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);
digitalWrite(pinGoStart, 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; // если истинно (не ноль) звук перелается в сеть
volatile byte GoStart = 0; // если истинно (не ноль) звук перелается в сеть
// передающий буфер с указателем
int kol = 4;
byte buf[164];
volatile uint32_t tic = 0; // обслуживает сброс переменных если больше секунды не было приема звука
volatile boolean actionState = LOW; // обслуживает индикацию приема вызова, 1000 ГЦ
volatile uint16_t PrdCall = 0;
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(Start)
{
if(GoStart == 0)
{
GoStart = 1;
digitalWrite(pinGoStart, HIGH);
}
}
else
{
if(GoStart)
{
GoStart = 0;
digitalWrite(pinGoStart, LOW);
}
}
if(PrdCall)
{
if(GoStart == 0)
{
GoStart = 1;
digitalWrite(pinGoStart, HIGH);
}
}
else
{
if(GoStart)
{
GoStart = 0;
digitalWrite(pinGoStart, LOW);
}
}
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;
sprintf(&buf[4],"BELL");
Udp.beginPacket(ipout,localPortout+kanal_PRD); //
Udp.write(buf,24); // непосредвственная передача
Udp.endPacket(); //
call = 6000;
PrdCall = 6000;
digitalWrite(pinCall, HIGH);
}
}
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;
PrdCall = 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(PrdCall) PrdCall++;
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); // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ
}
число 10 можно менять до максимум 127. Это настраивается чувствительность VOX. Если конечно VOX включен. Сейчас Владимир работает над принципиальной схемой, над АЧХ, усилением, кнопками и т.д.
Добрый день. Вот что происходит с мобильной версией PMR, смотри видео подробнее - https://ok.ru/video/1850859129433
Ребята, качаем новую Андроидную версию , Анатолий устранил ошибку на которую было указано выше. Также прошу прощение за необъективный комментарий по поводу поста 109, у Анатолия должно все работать в данном случае. Я не совсем понял как все таки работает программа и неверно прокомментировал. Возможно то что описано в посте 109 имеет другие причины.
Доброго вечера Василий!
Скачал, установил, вроде теперь нормально. Кнопки заработали все нормально. Переходит по каналам на ура, так же отлично сохраняет указанные параметры. Наблюдается только вот это что на картинках. В старой версии изображение было так же.
Вложение 261868 Вложение 261869
Все равно Анатолий при попытки перехода на передачу или нажатии на низ экрана программа вылетает,приходиться запускать снова.
На дисплеи пишет (В приложении "Виртуальная рация" произошла ошибка), версия андроида 4.1.2. Все другие функции работают отлично.
Анатолий подправил прокрутку экрана, ссылка ВСЁ ТА ЖЕ.
Приветствую!
Во вкладке "Menu" сейчас все замечательно. Кнопки на панеле заработали все. Вроде все работает теперь нормально. Система 8.1 Остался вот этот косяк с наездом кнопок на динамик, хотя в промежутках между ними можно включить передачу.
Вложение 261970
Нашли с Владимиром ошибку в моей версии под Windows, которая приводила к искажению сигнала при частоте дискретизации 8000. Новую версию качать все по той же ссылке.
Вот так выглядит звук на Arduino Uno + W5100, Владимир еще работает над схемой, но я также экспериментирую на макетной плате
Видео
Не пойму, почему удален мой предыдущий пост про Arduino IDE... Вроде бы ничего запретного не писал. Название ветки почему то изменено без моего согласия. Несколько лет сюда не заходил, но вижу что на qrz.ru порядки за это время не поменялись.