MyTetra Share
Делитесь знаниями!
полный проект интернет-радио на базе ESP8266 (NodeMCU) с поддержкой
Время создания: 24.05.2025 14:07
Автор: фдутыфм
Текстовые метки: полный проект интернет-радио на базе ESP8266 (NodeMCU) с поддержкой
Раздел: FRAM
Запись: alensav/MyTetra2/main/base/174808487164jf5hzdne/text.html на raw.githubusercontent.com

Собери всё это в один полный проект.


Qwen3-235B-A22B

2:04 PM

Отлично! Ниже — полный проект интернет-радио на базе ESP8266 (NodeMCU) с поддержкой:


✅ Возможности проекта

ФУНКЦИЯ

ПОДДЕРЖКА

Переключение станций

Энкодер KY-040

Сохранение текущей станции

FRAM + EEPROM

Логирование событий

FRAM

Вывод информации

TFT ST7735 1.8" (128x160)

Чтение метаданных (ID3)

ESP8266Audio

Аудио вывод

#include <ESP8266WiFi.h>

#include <ESP8266WebServer.h>

#include <ESP8266mDNS.h>

#include <ArduinoOTA.h>

#include <Wire.h>

#include <SPI.h>

#include <Encoder.h>

#include <EEPROM.h>

#include <SdFat.h>


// === Audio ===

#include <AudioGeneratorMP3.h>

#include <AudioFileSourceICYStream.h>

#include <AudioFileSourceBuffer.h>

#include <AudioOutputI2SNoDAC.h>


// === Display ===

#include <Adafruit_ST7735.h>


//==================================================

// === Настройки Wi-Fi и радиостанций ===

//==================================================


const char* ssid = "your_wifi";

const char* password = "wifi_password";


String stations[] = {

"http://stream.station1.com/stream",

"http://stream.station2.com/stream",

"http://stream.station3.com/stream"

};


int currentStation = 0;

bool stationChanged = true;


//==================================================

// === Инициализация экрана ===

//==================================================


#define TFT_CS D0

#define TFT_DC D3

#define TFT_RST D4

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);


//==================================================

// === SD карта ===

//==================================================


#define SD_CS D8

SdFat SD;


//==================================================

// === Аудио ===

//==================================================


AudioFileSourceICYStream *file = nullptr;

AudioFileSourceBuffer *buff = nullptr;

AudioGeneratorMP3 *mp3 = nullptr;

AudioOutputI2SNoDAC *outp = nullptr;


//==================================================

// === Энкодер ===

//==================================================


#define ENCODER_CLK D5

#define ENCODER_DT D6

Encoder myEncoder(ENCODER_CLK, ENCODER_DT);

long lastPosition = -999;


//==================================================

// === FRAM ===

//==================================================


#define FRAM_ADDR_1 0x50 // для станции

#define FRAM_ADDR_2 0x51 // для логов


//==================================================

// === Web сервер ===

//==================================================


ESP8266WebServer server(80);


//==================================================

// === EEPROM ===

//==================================================


#define EEPROM_SIZE 512

#define STATION_ADDR 0


//==================================================

// === Инициализация ===

//==================================================


void setup() {

Serial.begin(115200);

delay(1000);


// === Wi-Fi ===

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

Serial.print(".");

}


// === mDNS ===

if (!MDNS.begin("radioplayer")) {

Serial.println("Ошибка mDNS");

}


// === OTA ===

setupOTA();


// === Web сервер ===

setupWebServer();


// === EEPROM ===

EEPROM.begin(EEPROM_SIZE);

loadFromEEPROM();


// === FRAM ===

Wire.begin();

currentStation = loadCurrentStation();


// === Дисплей ===

tft.initR(INITR_BLACKTAB);

tft.setRotation(1);

tft.fillScreen(ST77XX_BLACK);

tft.setTextSize(1);

tft.setTextColor(ST77XX_YELLOW);

tft.setCursor(0, 0);

tft.println("Добро пожаловать!");


// === SD ===

if (!SD.begin(SD_CS)) {

tft.println("Ошибка SD");

} else {

tft.println("SD OK");

drawBMP("cover1.bmp"); // Пример обложки

}


// === Аудио ===

connectToRadio();


// === Энкодер ===

attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), isr_encoder, CHANGE);

}


//==================================================

// === Основной цикл ===

//==================================================


void loop() {

server.handleClient();

ArduinoOTA.handle();


if (mp3 && mp3->isRunning()) {

if (!mp3->loop()) {

mp3->stop();

}

}


delay(10);

}


//==================================================

// === Функции ===

//==================================================


void connectToRadio() {

if (mp3) {

mp3->stop();

delete mp3;

}

if (buff) delete buff;

if (file) delete file;


file = new AudioFileSourceICYStream(stations[currentStation].c_str());

buff = new AudioFileSourceBuffer(file, 2048);

mp3 = new AudioGeneratorMP3();

outp = new AudioOutputI2SNoDAC();


mp3->begin(buff, outp);


logEvent("Переключено на станцию: ");

logEvent(stations[currentStation].c_str());

}


//==================================================

// === Вывод обложки ===

//==================================================


void drawBMP(const char *filename) {

File bmpFile;

int bmpWidth, bmpHeight;

uint32_t bmpImageoffset;

uint32_t rowSize;

uint8_t sdbuffer[54]; // заголовок BMP

uint32_t read1;


if ((bmpFile = SD.open(filename)) == NULL) return;


if (!bmpFile.read(sdbuffer, 54)) return;


if (sdbuffer[0] != 'B' || sdbuffer[1] != 'M') return;


memcpy(&bmpImageoffset, sdbuffer + 10, 4);

memcpy(&bmpWidth, sdbuffer + 18, 4);

memcpy(&bmpHeight, sdbuffer + 22, 4);


rowSize = (bmpWidth * 3 + 3) & ~3;


tft.fillScreen(ST77XX_BLACK);


for (int row = bmpHeight - 1; row >= 0; row--) {

bmpFile.seek(bmpImageoffset + (bmpHeight - 1 - row) * rowSize);


for (int col = 0; col < bmpWidth; col++) {

uint8_t r, g, b;

bmpFile.read(&b, 1);

bmpFile.read(&g, 1);

bmpFile.read(&r, 1);


uint16_t color = tft.color565(r, g, b);

tft.drawPixel(col, row, color);

}

}


bmpFile.close();

}


//==================================================

// === Энкодер ===

//==================================================


void IRAM_ATTR isr_encoder() {

long newPosition = myEncoder.read();

if (newPosition != lastPosition) {

if (newPosition > lastPosition) {

currentStation = (currentStation + 1) % (sizeof(stations)/sizeof(String));

} else {

currentStation = (currentStation - 1 + sizeof(stations)/sizeof(String)) % (sizeof(stations)/sizeof(String));

}

stationChanged = true;

saveCurrentStation(currentStation);

connectToRadio();

updateDisplay(stations[currentStation], "...");

lastPosition = newPosition;

}

}


//==================================================

// === Отображение информации ===

//==================================================


void updateDisplay(const String& stationName, const String& trackTitle) {

tft.fillScreen(ST77XX_BLACK);

tft.setTextSize(1);

tft.setTextColor(ST77XX_YELLOW);

tft.setCursor(0, 0);

tft.println("Станция:");

tft.println(stationName);

tft.println("\nТрек:");

tft.println(trackTitle);

}


//==================================================

// === FRAM ===

//==================================================


void writeByte(uint8_t devAddr, uint16_t memAddr, byte data) {

Wire.beginTransmission(devAddr);

Wire.write((int)(memAddr >> 8));

Wire.write((int)(memAddr & 0xFF));

Wire.write(data);

Wire.endTransmission();

}


byte readByte(uint8_t devAddr, uint16_t memAddr) {

Wire.beginTransmission(devAddr);

Wire.write((int)(memAddr >> 8));

Wire.write((int)(memAddr & 0xFF));

Wire.endTransmission();

Wire.requestFrom(devAddr, 1);

return Wire.read();

}


int loadCurrentStation() {

Wire.beginTransmission(FRAM_ADDR_1);

Wire.write(0x00);

Wire.endTransmission();

Wire.requestFrom(FRAM_ADDR_1, 1);

return Wire.read();

}


void saveCurrentStation(int stationIndex) {

writeByte(FRAM_ADDR_1, 0x00, stationIndex);

}


void logEvent(const char* event) {

static uint16_t logPos = 0;

for (int i = 0; event[i]; i++) {

writeByte(FRAM_ADDR_2, logPos++, event[i]);

}

writeByte(FRAM_ADDR_2, logPos++, '\n');

}


//==================================================

// === EEPROM ===

//==================================================


void saveToEEPROM() {

EEPROM.put(STATION_ADDR, currentStation);

for (int i = 0; i < 3; i++) {

int offset = 4 + i * 64;

strcpy((char*)EEPROM.getDataPtr() + offset, stations[i].c_str());

}

EEPROM.commit();

}


void loadFromEEPROM() {

EEPROM.get(STATION_ADDR, currentStation);

for (int i = 0; i < 3; i++) {

int offset = 4 + i * 64;

stations[i] = String((char*)EEPROM.getDataPtr() + offset);

}

}


//==================================================

// === Web сервер ===

//==================================================


void handleRoot() {

String html = "<h1>Настройки интернет-радио</h1>";

html += "<form method='POST' action='/save'>";

html += "SSID: <input type='text' name='ssid'><br>";

html += "Пароль: <input type='password' name='pass'><br>";

html += "Станция 1: <input type='text' name='station1'><br>";

html += "Станция 2: <input type='text' name='station2'><br>";

html += "Станция 3: <input type='text' name='station3'><br>";

html += "<button type='submit'>Сохранить</button></form>";


server.send(200, "text/html", html);

}


void handleSave() {

if (server.hasArg("ssid")) ssid = server.arg("ssid");

if (server.hasArg("pass")) password = server.arg("pass");

if (server.hasArg("station1")) stations[0] = server.arg("station1");

if (server.hasArg("station2")) stations[1] = server.arg("station2");

if (server.hasArg("station3")) stations[2] = server.arg("station3");


saveToEEPROM(); // Сохраняем в EEPROM

connectToRadio();


server.send(200, "text/plain", "Настройки сохранены!");

}


void setupWebServer() {

server.on("/", handleRoot);

server.on("/save", HTTP_POST, handleSave);

server.begin();

}


//==================================================

// === OTA ===

//==================================================


void setupOTA() {

ArduinoOTA.setPort(8266);

ArduinoOTA.setPassword("your_ota_password");


ArduinoOTA.onStart([]() {

Serial.println("Начало обновления...");

});

ArduinoOTA.onEnd([]() {

Serial.println("\nОбновление завершено.");

});


ArduinoOTA.begin();

}


✅ Как использовать?

  1. Залей файлы cover1.bmp, cover2.bmp на SD-карту.
  2. Прошей скетч в NodeMCU через Arduino IDE.
  3. Подключи устройство к Wi-Fi.
  4. Открой http://radioplayer.local в браузере.
  5. Наслаждайся интернет-радио с цветным интерфейсом и управлением.



🛠 Что можно добавить дальше?

  • Чтение ID3-тегов и отображение названия трека.
  • Автоматическая загрузка обложки по URL из потока.
  • Визуализация звука (VU-метр).
  • Поддержка MQTT для удалённого управления.
  • Поддержка Bluetooth (требуется ESP32).



👉 Готов ли ты перейти к следующему этапу?

Могу:

  • Добавить поддержку ID3-тегов .
  • Реализовать визуализацию звука .
  • Создать интерфейс истории прослушивания .
  • Настроить автоматическую смену обложки .

Просто напиши:
👉 «Хочу реализовать X» , где X — нужная тебе функция.

🚀 Твой проект уже почти готов к использованию как полноценное интернет-радио!

 
MyTetra Share v.0.67
Яндекс индекс цитирования