MyTetra Share
Делитесь знаниями!
Время создания: 14.02.2024 16:58
Автор: alensav
Текстовые метки: esp32_RAD
Раздел: ESP32
Запись: alensav/MyTetra2/main/base/1707919106y7to2x1g0t/text.html на raw.githubusercontent.com

ESP32 + PCM5102A — интернет


























































































радио (Arduino)


Arduino  Микроконтроллеры (разное)  Электроника в быту

07.01.2022

Автор: liman28

На базе ESP32 (ESP32 DevKit v1 Wi-Fi Bluetooth ESP32-WROOM-32 ) и звукового ЦАП PMC5102A можно создать простое Интернет радио для качественного воспроизведения потокового радио (до 320Kbit/s16-bit 96kHz).

В Интернет радио использованы следующие компоненты:

  • ESP32 DevKit v1 Wi-Fi Bluetooth ESP32-WROOM-32
  • DAC PCM5102A
  • LCD1602 с модулем I2C
  • Энкодер KY-040 (модуль)
  • Тактовая кнопка  — 2 шт

ESP32

ESP32 — серия недорогих микроконтроллеров с низким энергопотреблением. Представляют собой систему на кристалле с интегрированным Wi-Fi и Bluetooth контроллерами и антеннами. В серии ESP32 используется микроконтроллерное ядро Tensilica Xtensa LX6 в вариантах с двумя и одним ядром. В систему интегрирован радиочастотный тракт: симметрирующий трансформатор, встроенные антенные коммутаторы, радиочастотные компоненты, малошумящий усилитель, усилитель мощности, фильтры и модули управления питанием. ESP32 создан и разработан компанией Espressif Systems, китайской компанией, расположенной в Шанхае, а производится компанией TSMC по техпроцессу 40 нм. Серия является преемником микроконтроллеров ESP8266.

Характеристики ESP32 DevKit v1:

  • микроконтроллер: ESP32-WROOM-32
  • процессор: 2-ядерный Xtensa Dual-Core 32-bit LX6
  • тактовая частота процессора: 80, 160 или 240 МГц
  • оперативная память: 520 Кбайт;
  • флэш-память: 448 Кбайт;
  • преобразователь USB – UART
  • количество выводов платы: 30;
  • Bluetooth: спецификации 4.2 с функциями  BR/EDR и Low Energy
  • WiFi: стандарта IEEE 802.11b/g/n/e/i безопасность WFA, WPA/WPA2 и WAPI на частоте 2,4 ГГц со скоростью до 150 Мбит/с, встроенный стек TCP/IP
  • антенна: PCB
  • режимы беспроводной связи: STA/AP/STA+AP
  • расстояние приема/передачи в идеальных условиях: 400 м;
  • периферия: АЦП 12 бит до 18 каналов, ЦАП 8 бит 2 канала, датчик температуры, 4x SPI, 2x I2S, 2x I2C, 3x UART, Ethernet контроллер, CAN 2.0, ведущий SD/eMMC/SDIO, ведомый SDIO/SPI, инфракрасный приемопередатчик, ШИМ до 16 каналов, датчик Холла, аналоговый предусилитель, шифровальщики, хешеры, генератор случайных чисел
  • поддерживаемые среды разработки: Arduino IDE, PlatformIO, Espressif IDF (IoT Development Framework), Micropython, JavaScript, LUA

PCM5102A

  • Напряжение однополярное … 3,3 В
  • Отношение сигнал/шум … 112 дБ
  • Динамический диапазон … 112 дБ
  • Уровень нелинейных искажений (THD+N) … -93 дБ
  • Выходное напряжение … 2.1 Vrms
  • Поддерживаемая частота дискретизации от 8 кГц до 384 кГц
  • Поддержка входных форматов данных … I2S, Left-Justified / 16, 24 и 32 бит

Схема Интернет радио

Основная информация Интернет радио выводится на LCD1602, которая разделена не несколько пунктов меню:

  • Основное меню

  1. Название станции
  2. Номер станции
  3. Скорость потока
  4. Уровень громкости (0..21)
  • Баланс (±16 дБ)

  • Регулировка тембра BASS (-40…+16 дБ)

  • Регулировка тембра MIDDLE (-40…+16 дБ)

  • Регулировка тембра TREBLE (-40…+16 дБ)

Интернет радио не содержит WEB страницы, все параметры и url адреса станций необходимо заносить в скетч:

авторизация в сети

 String ssid =     "Keenetic-9009";     // ssid сети WI-FI

 String password = "32481975";     // пароль от сети WI-FI

список станций

 "https://rusradio.hostingradio.ru/rusradio96.aacp",

 "https://str.pcradio.ru/funradio_sk_80s90s-hi",

 "http://radio.promodj.com:8000/186mph-192",

 "http://live.novoeradio.by:8000/narodnoe-radio-128k",

 "http://listen1.myradio24.com:9000/3355",

 "http://101.ru/api/channel/getServers/192/channel/AAC/128/dataFormat/mobile",

кол-во станций

#define CH             6  // кол-во станций

Управление Интернет радио осуществляется при помощи энкодера и двух кнопок. Кнопки позволяют переключать каналы станций, а энкодер регулировать параметры громкости, баланса и тембра. Кнопка энкодера осуществляет переход по пунктам меню.

Как добавить ESP32 в среду Ardiuno IDE можно узнать на странице http://rcl-radio.ru/?p=92558

Версия платы должна быть не ниже 1.0.5

Плата ESP32 Dev Module

#include "Arduino.h"

#include "WiFi.h"

#include "Audio.h" // https://github.com/schreibfaul1/ESP32-audioI2S.git

#include <Wire.h>

#include <LiquidCrystal_I2C.h> // http://forum.rcl-radio.ru/misc.php?action=pan_download&item=45&download=1

#include <ESP32Encoder.h> // https://github.com/madhephaestus/ESP32Encoder.git

#include <EEPROM.h>

 

#define I2S_DOUT 25 // DIN connection

#define I2S_BCLK 27 // Bit clock

#define I2S_LRC 26 // Left Right Clock

#define CLK 19 // CLK ENCODER

#define DT 18 // DT ENCODER

#define SW 5 // SW ENCODER

#define CH_UP 13 // CH_UP BUTTON

#define CH_DOWN 12 // CH_DOWN BUTTON

 

#define CH 6 // кол-во станций

 

Audio audio;

LiquidCrystal_I2C lcd(0x27,16,2); // Устанавливаем дисплей

ESP32Encoder encoder;

 

String ssid = "Keenetic-9009"; // ssid сети WI-FI

String password = "32481975"; // пароль от сети WI-FI

String ch = "connection ";

String bitr;

unsigned long oldPosition = -999,newPosition,times1;

bool w=1,w1,ball_1,bass_1,mid_1,treb_1,vol_1;

int ct,old_ct = 1,menu,ball,bass,mid,treb,vol=15;

bool e_vol,e_ball,e_bass,e_mid,e_treb,e_ct;

 

const char *listch[]{

"https://rusradio.hostingradio.ru/rusradio96.aacp",

"https://str.pcradio.ru/funradio_sk_80s90s-hi",

"http://radio.promodj.com:8000/186mph-192",

"http://live.novoeradio.by:8000/narodnoe-radio-128k",

"http://listen1.myradio24.com:9000/3355",

"http://101.ru/api/channel/getServers/192/channel/AAC/128/dataFormat/mobile",

};

 

void setup() {

ESP32Encoder::useInternalWeakPullResistors=UP;

encoder.attachHalfQuad(DT, CLK);

encoder.setCount(0);

Serial.begin(9600);

WiFi.disconnect();

WiFi.mode(WIFI_STA);

WiFi.begin(ssid.c_str(), password.c_str());

lcd.init();lcd.backlight();

lcd.setCursor(0,0);lcd.print(" ESP32 RADIO ");

lcd.setCursor(0,1);lcd.print(" PCM5102A ");

delay(2000);

while (WiFi.status() != WL_CONNECTED) delay(100);

lcd.clear();

lcd.setCursor(0,0);lcd.print("IP:");lcd.print(WiFi.localIP());

delay(2000);

lcd.clear();

EEPROM.begin(10);

vol = EEPROM.read(0);

ball = EEPROM.read(1)-16;

bass = EEPROM.read(2)-40;

mid = EEPROM.read(3)-40;

treb = EEPROM.read(4)-40;

ct = EEPROM.read(5);

if(vol>21){vol=0;}

if(ball>16){ball=0;}

if(bass>16){bass=0;}

if(mid>16){mid=0;}

if(treb>16){treb=0;}

if(ct>CH-1){ct=0;}

audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);

delay(2000);

audio.setBalance(ball);//+-16

audio.setVolume(vol); // 0...21

audio.setTone(0,0,0);//-40+16

pinMode(CH_UP, INPUT_PULLUP);

pinMode(CH_DOWN, INPUT_PULLUP);

pinMode(SW,INPUT); // ENCODER SW

}

 

void loop(){

newPosition = encoder.getCount()/2;

//// ENCODER VOLUME //////////////////////////////////////////////

if(menu==0){

if(newPosition != oldPosition){oldPosition = newPosition;vol=vol-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(vol>21){vol=21;}if(vol<0){vol=0;}audio.setVolume(vol);vol_1=1;e_vol=1;}

if(vol_1==1){vol_1=0;lcd.setCursor(9,1);lcd.print("VOL ");lcd.print(vol);lcd.print(" ");}}

///// ENCODER BALANCE ////////////////////////////////////////////

if(menu==1){

if(newPosition != oldPosition){oldPosition = newPosition;ball=ball-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(ball>16){ball=16;}if(ball<-16){ball=-16;}audio.setBalance(ball);ball_1=1;e_ball=1;}

if(ball_1==1){ball_1=0;lcd.setCursor(0,0);lcd.print("BALANCE ");lcd.print(ball);lcd.print(" dB ");}}

///// ENCODER BASS ////////////////////////////////////////////

if(menu==2){

if(newPosition != oldPosition){oldPosition = newPosition;bass=bass-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(bass>16){bass=16;}if(bass<-40){bass=-40;}audio.setTone(bass,mid,treb);bass_1=1;e_bass=1;}

if(bass_1==1){bass_1=0;lcd.setCursor(0,0);lcd.print("BASS ");lcd.print(bass);lcd.print(" dB ");}}

///// ENCODER MIDDLE ////////////////////////////////////////////

if(menu==3){

if(newPosition != oldPosition){oldPosition = newPosition;mid=mid-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(mid>16){mid=16;}if(mid<-40){mid=-40;}audio.setTone(bass,mid,treb);mid_1=1;e_mid=1;}

if(mid_1==1){mid_1=0;lcd.setCursor(0,0);lcd.print("MIDDLE ");lcd.print(mid);lcd.print(" dB ");}}

///// ENCODER TREBLE ////////////////////////////////////////////

if(menu==4){

if(newPosition != oldPosition){oldPosition = newPosition;treb=treb-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(treb>16){treb=16;}if(treb<-40){treb=-40;}audio.setTone(bass,mid,treb);treb_1=1;e_treb=1;}

if(treb_1==1){treb_1=0;lcd.setCursor(0,0);lcd.print("TREBLE ");lcd.print(treb);lcd.print(" dB ");}}

//// BUTTON //////////////////////////////////////////////

if (digitalRead(13)==LOW){ct++;if(ct>CH-1){ct=0;}w=1;times1=millis();w1=1;ch="connection ";lcd.setCursor(0,0);lcd.print(ch);menu=0;e_ct=1;}

if (digitalRead(12)==LOW){ct--;if(ct<0){ct=CH-1;}w=1;times1=millis();w1=1;ch="connection ";lcd.setCursor(0,0);lcd.print(ch);menu=0;e_ct=1;Serial.println(ct);}

if (digitalRead(5)==LOW){menu++;if(menu>4){menu=0;}times1=millis();w1=1;lcd.clear();vol_1=1;ball_1=1;bass_1=1;mid_1=1;treb_1=1;if(menu==0){w=1;}delay(300);}

//////////////////////////////////////////////////////////

if (ct != old_ct) {ch!="connection ";audio.connecttohost(listch[ct]);Serial.println(ct);old_ct = ct;w=1;}

 

if(menu==0){

if(w==1&&ch!="connection "){

lcd.clear();w=0;

lcd.setCursor(0,0);lcd.print(ch);

lcd.setCursor(0,1);lcd.print("CH");lcd.print(ct);lcd.print(" ");

lcd.print(float(bitr.toInt()/1000),0);lcd.print("k ");

lcd.setCursor(9,1);lcd.print("VOL ");lcd.print(vol);lcd.print(" ");

}}

 

audio.loop();

 

//// EEPROM ///////////////////////////////////

if(millis()-times1>5000&&w1==1){

if(e_vol==1){e_vol=0;EEPROM.write(0,vol);}

if(e_ball==1){e_ball=0;EEPROM.write(1,ball+16);}

if(e_bass==1){e_bass=0;EEPROM.write(2,bass+40);}

if(e_mid==1){e_mid=0;EEPROM.write(3,mid+40);}

if(e_treb==1){e_treb=0;EEPROM.write(4,treb+40);}

if(e_ct==1){e_ct=0;EEPROM.write(5,ct);}

EEPROM.commit();}

if(millis()-times1>10000&&w1==1){

w1=0;if(menu!=0){w=1;menu=0;}}

 

} // loop

 

void audio_showstation(const char *info) {Serial.print("station "); ch=info; Serial.println(info);}

void audio_showstreamtitle(const char *info) {Serial.print("streamtitle "); Serial.println(info);}

void audio_bitrate(const char *info) {Serial.print("bitrate ");bitr = info; Serial.println(info);}

void audio_icyurl(const char *info) {Serial.print("icyurl "); Serial.println(info);}

void audio_lasthost(const char *info) {Serial.print("lasthost "); Serial.println(info);}

Форум — http://forum.rcl-radio.ru/viewtopic.php?pid=5075#p5075


Доработка

Сохранение списка станций (адресов) на сервере. 

Для удобства использования интернет радио был разработан небольшой онлайн сервис позволяющий создавать файл (html) в котором будут хранится адреса потоков интернет станций. Файл с адресами можно обновлять. При включении интернет радио ESP32 сделает запрос к Вашему файлу со списком станций и загрузит его в память.

Онлайн приложение — http://forum.rcl-radio.ru/internet_radio_esp32.php

Придумайте уникальное название Вашего файла, далее укажите список из 10 станций, адреса должны быть разделены запятыми. Далее запишите файл, проверьте чтение файла.

Для обновления списка станций нужно указать имя файла, нажать кнопку «Прочитать», после получения списка станций в нижнем поле, копировать их и вставить в верхнее поле, отредактировать список станций и нажать кнопку «Записать».

В скетче поменяйте строку:

const String endpoint = «http://forum.rcl-radio.ru/internet_radio_esp32/data.html»;

data — это название файла.

Далее загрузите со всеми изменениями (указать логин и пароль Вашей сети, отредактировать строку со списком станций) скетч для 10 станций:

#include "Arduino.h"

#include "WiFi.h"

#include "Audio.h" // https://github.com/schreibfaul1/ESP32-audioI2S.git

#include <Wire.h>

#include <LiquidCrystal_I2C.h> // http://forum.rcl-radio.ru/misc.php?action=pan_download&item=45&download=1

#include <ESP32Encoder.h> // https://github.com/madhephaestus/ESP32Encoder.git

#include <EEPROM.h>

#include <HTTPClient.h>

 

#define I2S_DOUT 25 // DIN connection

#define I2S_BCLK 27 // Bit clock

#define I2S_LRC 26 // Left Right Clock

#define CLK 19 // CLK ENCODER

#define DT 18 // DT ENCODER

#define SW 5 // SW ENCODER

#define CH_UP 13 // CH_UP BUTTON

#define CH_DOWN 12 // CH_DOWN BUTTON

 

#define CH 10 // кол-во станций

const String endpoint = "http://forum.rcl-radio.ru/internet_radio_esp32/data.html";

 

Audio audio;

LiquidCrystal_I2C lcd(0x27,16,2); // Устанавливаем дисплей

ESP32Encoder encoder;

 

String ssid = "Redmi Note 3"; // ssid сети WI-FI

String password = "32481975"; // пароль от сети WI-FI

String payload;

String ch = "connection ";

String bitr;

unsigned long oldPosition = -999,newPosition,times1;

bool w=1,w1,ball_1,bass_1,mid_1,treb_1,vol_1;

int ct,old_ct = 1,menu,ball,bass,mid,treb,vol=15;

bool e_vol,e_ball,e_bass,e_mid,e_treb,e_ct;

const char *listch[CH];

String xval[CH];

 

void setup() {

ESP32Encoder::useInternalWeakPullResistors=UP;

encoder.attachHalfQuad(DT, CLK);

encoder.setCount(0);

Serial.begin(9600);

WiFi.disconnect();

WiFi.mode(WIFI_STA);

WiFi.begin(ssid.c_str(), password.c_str());

Wire.begin();Wire.setClock(400000L);

lcd.init();lcd.backlight();

lcd.setCursor(0,0);lcd.print(" ESP32 RADIO ");

lcd.setCursor(0,1);lcd.print(" PCM5102A ");

delay(2000);

while (WiFi.status() != WL_CONNECTED) delay(100);

lcd.clear();

lcd.setCursor(0,0);lcd.print("IP:");lcd.print(WiFi.localIP());

delay(2000);

lcd.clear();

EEPROM.begin(10);

vol = EEPROM.read(0);

ball = EEPROM.read(1)-16;

bass = EEPROM.read(2)-40;

mid = EEPROM.read(3)-40;

treb = EEPROM.read(4)-40;

ct = EEPROM.read(5);

if(vol>21){vol=0;}

if(ball>16){ball=0;}

if(bass>16){bass=0;}

if(mid>16){mid=0;}

if(treb>16){treb=0;}

if(ct>CH-1){ct=0;}

audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);

delay(2000);

audio.setBalance(ball);//+-16

audio.setVolume(vol); // 0...21

audio.setTone(0,0,0);//-40+16

pinMode(CH_UP, INPUT_PULLUP);

pinMode(CH_DOWN, INPUT_PULLUP);

pinMode(SW,INPUT); // ENCODER SW

 

HTTPClient http;

http.begin(endpoint);

int httpCode = http.GET();

if (httpCode > 0) {payload = http.getString();}

http.end();

 

for(int kol_i=0;kol_i<CH;kol_i++){

xval[kol_i] = getValue(payload, ',', kol_i);

Serial.println(xval[kol_i]);

}

Serial.println();

for(int kol_i=0;kol_i<CH;kol_i++){

listch[kol_i] = xval[kol_i].c_str();

}

}

 

String getValue(String data1, char separator, int index){

int found = 0;

int strIndex[] = { 0, -1 };

int maxIndex = data1.length() - 1;

for (int i = 0; i <= maxIndex && found <= index; i++) {

if (data1.charAt(i) == separator || i == maxIndex) {

found++;

strIndex[0] = strIndex[1] + 1;

strIndex[1] = (i == maxIndex) ? i+1 : i;

}}

return found > index ? data1.substring(strIndex[0], strIndex[1]) : "";

}

 

void loop(){

newPosition = encoder.getCount()/2;

//// ENCODER VOLUME //////////////////////////////////////////////

if(menu==0){

if(newPosition != oldPosition){oldPosition = newPosition;vol=vol-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(vol>21){vol=21;}if(vol<0){vol=0;}audio.setVolume(vol);vol_1=1;e_vol=1;}

if(vol_1==1){vol_1=0;lcd.setCursor(9,1);lcd.print("VOL ");lcd.print(vol);lcd.print(" ");}}

///// ENCODER BALANCE ////////////////////////////////////////////

if(menu==1){

if(newPosition != oldPosition){oldPosition = newPosition;ball=ball-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(ball>16){ball=16;}if(ball<-16){ball=-16;}audio.setBalance(ball);ball_1=1;e_ball=1;}

if(ball_1==1){ball_1=0;lcd.setCursor(0,0);lcd.print("BALANCE ");lcd.print(ball);lcd.print(" dB ");}}

///// ENCODER BASS ////////////////////////////////////////////

if(menu==2){

if(newPosition != oldPosition){oldPosition = newPosition;bass=bass-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(bass>16){bass=16;}if(bass<-40){bass=-40;}audio.setTone(bass,mid,treb);bass_1=1;e_bass=1;}

if(bass_1==1){bass_1=0;lcd.setCursor(0,0);lcd.print("BASS ");lcd.print(bass);lcd.print(" dB ");}}

///// ENCODER MIDDLE ////////////////////////////////////////////

if(menu==3){

if(newPosition != oldPosition){oldPosition = newPosition;mid=mid-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(mid>16){mid=16;}if(mid<-40){mid=-40;}audio.setTone(bass,mid,treb);mid_1=1;e_mid=1;}

if(mid_1==1){mid_1=0;lcd.setCursor(0,0);lcd.print("MIDDLE ");lcd.print(mid);lcd.print(" dB ");}}

///// ENCODER TREBLE ////////////////////////////////////////////

if(menu==4){

if(newPosition != oldPosition){oldPosition = newPosition;treb=treb-newPosition;encoder.setCount(0);

newPosition=0;times1=millis();w1=1;if(treb>16){treb=16;}if(treb<-40){treb=-40;}audio.setTone(bass,mid,treb);treb_1=1;e_treb=1;}

if(treb_1==1){treb_1=0;lcd.setCursor(0,0);lcd.print("TREBLE ");lcd.print(treb);lcd.print(" dB ");}}

//// BUTTON //////////////////////////////////////////////

if (digitalRead(13)==LOW){ct++;if(ct>CH-1){ct=0;}w=1;times1=millis();w1=1;ch="connection ";lcd.setCursor(0,0);lcd.print(ch);menu=0;e_ct=1;}

if (digitalRead(12)==LOW){ct--;if(ct<0){ct=CH-1;}w=1;times1=millis();w1=1;ch="connection ";lcd.setCursor(0,0);lcd.print(ch);menu=0;e_ct=1;Serial.println(ct);}

if (digitalRead(5)==LOW){menu++;if(menu>4){menu=0;}times1=millis();w1=1;lcd.clear();vol_1=1;ball_1=1;bass_1=1;mid_1=1;treb_1=1;if(menu==0){w=1;}delay(300);}

//////////////////////////////////////////////////////////

if (ct != old_ct) {ch!="connection ";audio.connecttohost(listch[ct]);Serial.println(ct);old_ct = ct;w=1;}

 

if(menu==0){

if(w==1&&ch!="connection "){

lcd.clear();w=0;

lcd.setCursor(0,0);lcd.print(ch);

lcd.setCursor(0,1);lcd.print("CH");lcd.print(ct);lcd.print(" ");

lcd.print(float(bitr.toInt()/1000),0);lcd.print("k ");

lcd.setCursor(9,1);lcd.print("VOL ");lcd.print(vol);lcd.print(" ");

}}

 

audio.loop();

 

//// EEPROM ///////////////////////////////////

if(millis()-times1>5000&&w1==1){

if(e_vol==1){e_vol=0;EEPROM.write(0,vol);}

if(e_ball==1){e_ball=0;EEPROM.write(1,ball+16);}

if(e_bass==1){e_bass=0;EEPROM.write(2,bass+40);}

if(e_mid==1){e_mid=0;EEPROM.write(3,mid+40);}

if(e_treb==1){e_treb=0;EEPROM.write(4,treb+40);}

if(e_ct==1){e_ct=0;EEPROM.write(5,ct);}

EEPROM.commit();}

if(millis()-times1>10000&&w1==1){

w1=0;if(menu!=0){w=1;menu=0;}}

 

} // loop

 

void audio_showstation(const char *info) {Serial.print("station "); ch=info; Serial.println(info);}

void audio_showstreamtitle(const char *info) {Serial.print("streamtitle "); Serial.println(info);}

void audio_bitrate(const char *info) {Serial.print("bitrate ");bitr = info; Serial.println(info);}

void audio_icyurl(const char *info) {Serial.print("icyurl "); Serial.println(info);}

void audio_lasthost(const char *info) {Serial.print("lasthost "); Serial.println(info);}


Так же в этом разделе:
 
MyTetra Share v.0.65
Яндекс индекс цитирования