Код:
#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); // СБРАСЫВАЕМ ТЕСТОВЫЙ СИГНАЛ
}
Там где строка
Социальные закладки