Implement display, SD card, I2S WAV audio player, and button sync logic
This commit is contained in:
186
sound_picture_app/Audio_Player.cpp
Normal file
186
sound_picture_app/Audio_Player.cpp
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
#include "Audio_Player.h"
|
||||||
|
#include <ESP_I2S.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
I2SClass i2s;
|
||||||
|
TaskHandle_t audioTaskHandle = NULL;
|
||||||
|
File audioFile;
|
||||||
|
volatile bool isAudioPlaying = false;
|
||||||
|
char currentPlayPath[128];
|
||||||
|
|
||||||
|
uint16_t Wav_Count = 0;
|
||||||
|
char Wav_Names[100][100];
|
||||||
|
|
||||||
|
void Search_Wavs(const char* directory) {
|
||||||
|
Wav_Count = Folder_retrieval(directory, ".wav", Wav_Names, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse WAV header and skip meta chunks (like LIST) to find the "fmt " and "data" segments
|
||||||
|
bool parseWavHeader(File& file, uint32_t& sampleRate, uint16_t& channels, uint16_t& bitsPerSample, uint32_t& dataStartOffset, uint32_t& dataSize) {
|
||||||
|
char id[4];
|
||||||
|
|
||||||
|
// Read RIFF header
|
||||||
|
file.seek(0);
|
||||||
|
if (file.readBytes(id, 4) != 4 || memcmp(id, "RIFF", 4) != 0) {
|
||||||
|
printf("WAV Header Error: RIFF signature not found!\r\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(8);
|
||||||
|
if (file.readBytes(id, 4) != 4 || memcmp(id, "WAVE", 4) != 0) {
|
||||||
|
printf("WAV Header Error: WAVE signature not found!\r\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t offset = 12;
|
||||||
|
bool foundFmt = false;
|
||||||
|
bool foundData = false;
|
||||||
|
|
||||||
|
while (offset < file.size() && (!foundFmt || !foundData)) {
|
||||||
|
file.seek(offset);
|
||||||
|
if (file.readBytes(id, 4) != 4) break;
|
||||||
|
|
||||||
|
uint32_t chunkSize;
|
||||||
|
if (file.read((uint8_t*)&chunkSize, 4) != 4) break;
|
||||||
|
|
||||||
|
offset += 8; // move past chunk ID and Size field
|
||||||
|
|
||||||
|
if (memcmp(id, "fmt ", 4) == 0) {
|
||||||
|
uint16_t audioFormat;
|
||||||
|
file.read((uint8_t*)&audioFormat, 2);
|
||||||
|
if (audioFormat != 1) { // 1 = PCM (uncompressed)
|
||||||
|
printf("WAV Header Error: Only uncompressed PCM WAV is supported!\r\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.read((uint8_t*)&channels, 2);
|
||||||
|
file.read((uint8_t*)&sampleRate, 4);
|
||||||
|
file.seek(offset + 8); // skip byte rate and block align
|
||||||
|
file.read((uint8_t*)&bitsPerSample, 2);
|
||||||
|
foundFmt = true;
|
||||||
|
}
|
||||||
|
else if (memcmp(id, "data", 4) == 0) {
|
||||||
|
dataStartOffset = offset;
|
||||||
|
dataSize = chunkSize;
|
||||||
|
foundData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundFmt && foundData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audioPlayTask(void* parameter) {
|
||||||
|
uint32_t sampleRate = 44100;
|
||||||
|
uint16_t channels = 2;
|
||||||
|
uint16_t bitsPerSample = 16;
|
||||||
|
uint32_t dataStartOffset = 44;
|
||||||
|
uint32_t dataSize = 0;
|
||||||
|
|
||||||
|
audioFile = SD_MMC.open(currentPlayPath, "r");
|
||||||
|
if (!audioFile) {
|
||||||
|
printf("Failed to open audio file: %s\r\n", currentPlayPath);
|
||||||
|
isAudioPlaying = false;
|
||||||
|
audioTaskHandle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parseWavHeader(audioFile, sampleRate, channels, bitsPerSample, dataStartOffset, dataSize)) {
|
||||||
|
printf("Failed to parse WAV header\r\n");
|
||||||
|
audioFile.close();
|
||||||
|
isAudioPlaying = false;
|
||||||
|
audioTaskHandle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("WAV File: Rate=%d Hz, Channels=%d, Bits=%d, Size=%d bytes\r\n",
|
||||||
|
sampleRate, channels, bitsPerSample, dataSize);
|
||||||
|
|
||||||
|
// Configure I2S based on WAV parameters
|
||||||
|
i2s_data_bit_width_t bitWidth = I2S_DATA_BIT_WIDTH_16BIT;
|
||||||
|
if (bitsPerSample == 8) bitWidth = I2S_DATA_BIT_WIDTH_8BIT;
|
||||||
|
else if (bitsPerSample == 24) bitWidth = I2S_DATA_BIT_WIDTH_24BIT;
|
||||||
|
else if (bitsPerSample == 32) bitWidth = I2S_DATA_BIT_WIDTH_32BIT;
|
||||||
|
|
||||||
|
i2s_slot_mode_t slotMode = (channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
||||||
|
|
||||||
|
// Set standard pins
|
||||||
|
i2s.setPins(I2S_BCLK, I2S_LRCK, I2S_DIN, -1, -1);
|
||||||
|
|
||||||
|
// Stop any active driver configuration
|
||||||
|
i2s.end();
|
||||||
|
|
||||||
|
if (!i2s.begin(I2S_MODE_STD, sampleRate, bitWidth, slotMode)) {
|
||||||
|
printf("Failed to initialize I2S!\r\n");
|
||||||
|
audioFile.close();
|
||||||
|
isAudioPlaying = false;
|
||||||
|
audioTaskHandle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek past the parsed headers to raw data
|
||||||
|
audioFile.seek(dataStartOffset);
|
||||||
|
|
||||||
|
const size_t bufferSize = 1024;
|
||||||
|
uint8_t buffer[bufferSize];
|
||||||
|
uint32_t bytesRead = 0;
|
||||||
|
uint32_t totalBytesWritten = 0;
|
||||||
|
|
||||||
|
while (isAudioPlaying && totalBytesWritten < dataSize) {
|
||||||
|
size_t toRead = std::min(bufferSize, (size_t)(dataSize - totalBytesWritten));
|
||||||
|
bytesRead = audioFile.read(buffer, toRead);
|
||||||
|
if (bytesRead == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t written = i2s.write(buffer, bytesRead);
|
||||||
|
if (written == 0) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
totalBytesWritten += written;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Playback done. Written %d bytes.\r\n", totalBytesWritten);
|
||||||
|
audioFile.close();
|
||||||
|
i2s.end();
|
||||||
|
isAudioPlaying = false;
|
||||||
|
audioTaskHandle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void playWav(const char* filePath) {
|
||||||
|
if (isAudioPlaying) {
|
||||||
|
stopWav();
|
||||||
|
}
|
||||||
|
strncpy(currentPlayPath, filePath, sizeof(currentPlayPath) - 1);
|
||||||
|
currentPlayPath[sizeof(currentPlayPath) - 1] = '\0';
|
||||||
|
isAudioPlaying = true;
|
||||||
|
|
||||||
|
xTaskCreatePinnedToCore(
|
||||||
|
audioPlayTask,
|
||||||
|
"audioPlayTask",
|
||||||
|
4096,
|
||||||
|
NULL,
|
||||||
|
5,
|
||||||
|
&audioTaskHandle,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopWav() {
|
||||||
|
if (isAudioPlaying) {
|
||||||
|
isAudioPlaying = false;
|
||||||
|
while (audioTaskHandle != NULL) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWavPlaying() {
|
||||||
|
return isAudioPlaying;
|
||||||
|
}
|
||||||
18
sound_picture_app/Audio_Player.h
Normal file
18
sound_picture_app/Audio_Player.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "FS.h"
|
||||||
|
#include "SD_Card.h"
|
||||||
|
|
||||||
|
// Define I2S Pins for external DAC (MAX98357A, etc.)
|
||||||
|
#define I2S_BCLK 1
|
||||||
|
#define I2S_LRCK 2
|
||||||
|
#define I2S_DIN 4
|
||||||
|
|
||||||
|
void playWav(const char* filePath);
|
||||||
|
void stopWav();
|
||||||
|
bool isWavPlaying();
|
||||||
|
|
||||||
|
void Search_Wavs(const char* directory);
|
||||||
|
extern uint16_t Wav_Count;
|
||||||
|
extern char Wav_Names[100][100];
|
||||||
|
|
||||||
78
sound_picture_app/Display_PNG.cpp
Normal file
78
sound_picture_app/Display_PNG.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include "Display_PNG.h"
|
||||||
|
|
||||||
|
PNG png;
|
||||||
|
File Image_file;
|
||||||
|
|
||||||
|
uint16_t Image_Count = 0;
|
||||||
|
char Image_Names[100][100];
|
||||||
|
|
||||||
|
int16_t xpos = 0;
|
||||||
|
int16_t ypos = 0;
|
||||||
|
|
||||||
|
void * pngOpen(const char *filePath, int32_t *size) {
|
||||||
|
Image_file = SD_MMC.open(filePath);
|
||||||
|
*size = Image_file.size();
|
||||||
|
return &Image_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pngClose(void *handle) {
|
||||||
|
File Image_file = *((File*)handle);
|
||||||
|
if (Image_file) Image_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t pngRead(PNGFILE *page, uint8_t *buffer, int32_t length) {
|
||||||
|
if (!Image_file) return 0;
|
||||||
|
return Image_file.read(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t pngSeek(PNGFILE *page, int32_t position) {
|
||||||
|
if (!Image_file) return 0;
|
||||||
|
return Image_file.seek(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t lineBuffer[MAX_IMAGE_WIDTH];
|
||||||
|
void pngDraw(PNGDRAW *pDraw) {
|
||||||
|
png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
|
||||||
|
uint32_t size = pDraw->iWidth;
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
lineBuffer[i] = (((lineBuffer[i] >> 8) & 0xFF) | ((lineBuffer[i] << 8) & 0xFF00));
|
||||||
|
}
|
||||||
|
LCD_addWindow(xpos, pDraw->y, xpos + pDraw->iWidth - 1, ypos + pDraw->y, lineBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Search_Images(const char* directory) {
|
||||||
|
Image_Count = Folder_retrieval(directory, ".png", Image_Names, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display_Image_By_Path(const char* filePath) {
|
||||||
|
printf("Displaying image: %s\r\n", filePath);
|
||||||
|
int16_t ret = png.open(filePath, pngOpen, pngClose, pngRead, pngSeek, pngDraw);
|
||||||
|
if (ret == PNG_SUCCESS) {
|
||||||
|
printf("Image dimensions: %d x %d, %d bpp\r\n", png.getWidth(), png.getHeight(), png.getBpp());
|
||||||
|
if (png.getWidth() > MAX_IMAGE_WIDTH) {
|
||||||
|
printf("Image is too wide for line buffer!\r\n");
|
||||||
|
} else {
|
||||||
|
ret = png.decode(NULL, 0);
|
||||||
|
png.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Failed to open PNG image! Error code: %d\r\n", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display_Image_By_Index(const char* directory, uint16_t index) {
|
||||||
|
if (Image_Count == 0) {
|
||||||
|
Search_Images(directory);
|
||||||
|
}
|
||||||
|
if (Image_Count > 0 && index < Image_Count) {
|
||||||
|
String filePath;
|
||||||
|
if (String(directory) == "/") {
|
||||||
|
filePath = String(directory) + Image_Names[index];
|
||||||
|
} else {
|
||||||
|
filePath = String(directory) + "/" + Image_Names[index];
|
||||||
|
}
|
||||||
|
Display_Image_By_Path(filePath.c_str());
|
||||||
|
} else {
|
||||||
|
printf("Invalid image index: %d (Total images: %d)\r\n", index, Image_Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
sound_picture_app/Display_PNG.h
Normal file
14
sound_picture_app/Display_PNG.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <PNGdec.h>
|
||||||
|
#include "SD_Card.h"
|
||||||
|
#include "Display_ST7789.h"
|
||||||
|
|
||||||
|
#define MAX_IMAGE_WIDTH 172
|
||||||
|
|
||||||
|
void Search_Images(const char* directory);
|
||||||
|
void Display_Image_By_Index(const char* directory, uint16_t index);
|
||||||
|
void Display_Image_By_Path(const char* filePath);
|
||||||
|
|
||||||
|
extern uint16_t Image_Count;
|
||||||
|
extern char Image_Names[100][100];
|
||||||
230
sound_picture_app/Display_ST7789.cpp
Normal file
230
sound_picture_app/Display_ST7789.cpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#include "Display_ST7789.h"
|
||||||
|
|
||||||
|
SPIClass LCDspi(FSPI);
|
||||||
|
#define SPI_WRITE(_dat) LCDspi.transfer(_dat)
|
||||||
|
#define SPI_WRITE_Word(_dat) LCDspi.transfer16(_dat)
|
||||||
|
void SPI_Init()
|
||||||
|
{
|
||||||
|
LCDspi.begin(EXAMPLE_PIN_NUM_SCLK,EXAMPLE_PIN_NUM_MISO,EXAMPLE_PIN_NUM_MOSI);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD_WriteCommand(uint8_t Cmd)
|
||||||
|
{
|
||||||
|
LCDspi.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, LOW);
|
||||||
|
SPI_WRITE(Cmd);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
||||||
|
LCDspi.endTransaction();
|
||||||
|
}
|
||||||
|
void LCD_WriteData(uint8_t Data)
|
||||||
|
{
|
||||||
|
LCDspi.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
||||||
|
SPI_WRITE(Data);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
||||||
|
LCDspi.endTransaction();
|
||||||
|
}
|
||||||
|
void LCD_WriteData_Word(uint16_t Data)
|
||||||
|
{
|
||||||
|
LCDspi.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
||||||
|
SPI_WRITE_Word(Data);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
||||||
|
LCDspi.endTransaction();
|
||||||
|
}
|
||||||
|
void LCD_WriteData_nbyte(uint8_t* SetData,uint8_t* ReadData,uint32_t Size)
|
||||||
|
{
|
||||||
|
LCDspi.beginTransaction(SPISettings(SPIFreq, MSBFIRST, SPI_MODE0));
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_DC, HIGH);
|
||||||
|
LCDspi.transferBytes(SetData, ReadData, Size);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, HIGH);
|
||||||
|
LCDspi.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD_Reset(void)
|
||||||
|
{
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_CS, LOW);
|
||||||
|
delay(50);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_RST, LOW);
|
||||||
|
delay(50);
|
||||||
|
digitalWrite(EXAMPLE_PIN_NUM_LCD_RST, HIGH);
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
void LCD_Init(void)
|
||||||
|
{
|
||||||
|
pinMode(EXAMPLE_PIN_NUM_LCD_CS, OUTPUT);
|
||||||
|
pinMode(EXAMPLE_PIN_NUM_LCD_DC, OUTPUT);
|
||||||
|
pinMode(EXAMPLE_PIN_NUM_LCD_RST, OUTPUT);
|
||||||
|
Backlight_Init();
|
||||||
|
SPI_Init();
|
||||||
|
|
||||||
|
LCD_Reset();
|
||||||
|
//************* Start Initial Sequence **********//
|
||||||
|
LCD_WriteCommand(0x11);
|
||||||
|
delay(120);
|
||||||
|
LCD_WriteCommand(0x36);
|
||||||
|
if (HORIZONTAL)
|
||||||
|
LCD_WriteData(0x00);
|
||||||
|
else
|
||||||
|
LCD_WriteData(0x70);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0x3A);
|
||||||
|
LCD_WriteData(0x05);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xB0);
|
||||||
|
LCD_WriteData(0x00);
|
||||||
|
LCD_WriteData(0xE8);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xB2);
|
||||||
|
LCD_WriteData(0x0C);
|
||||||
|
LCD_WriteData(0x0C);
|
||||||
|
LCD_WriteData(0x00);
|
||||||
|
LCD_WriteData(0x33);
|
||||||
|
LCD_WriteData(0x33);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xB7);
|
||||||
|
LCD_WriteData(0x35);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xBB);
|
||||||
|
LCD_WriteData(0x35);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xC0);
|
||||||
|
LCD_WriteData(0x2C);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xC2);
|
||||||
|
LCD_WriteData(0x01);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xC3);
|
||||||
|
LCD_WriteData(0x13);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xC4);
|
||||||
|
LCD_WriteData(0x20);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xC6);
|
||||||
|
LCD_WriteData(0x0F);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xD0);
|
||||||
|
LCD_WriteData(0xA4);
|
||||||
|
LCD_WriteData(0xA1);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xD6);
|
||||||
|
LCD_WriteData(0xA1);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xE0);
|
||||||
|
LCD_WriteData(0xF0);
|
||||||
|
LCD_WriteData(0x00);
|
||||||
|
LCD_WriteData(0x04);
|
||||||
|
LCD_WriteData(0x04);
|
||||||
|
LCD_WriteData(0x04);
|
||||||
|
LCD_WriteData(0x05);
|
||||||
|
LCD_WriteData(0x29);
|
||||||
|
LCD_WriteData(0x33);
|
||||||
|
LCD_WriteData(0x3E);
|
||||||
|
LCD_WriteData(0x38);
|
||||||
|
LCD_WriteData(0x12);
|
||||||
|
LCD_WriteData(0x12);
|
||||||
|
LCD_WriteData(0x28);
|
||||||
|
LCD_WriteData(0x30);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0xE1);
|
||||||
|
LCD_WriteData(0xF0);
|
||||||
|
LCD_WriteData(0x07);
|
||||||
|
LCD_WriteData(0x0A);
|
||||||
|
LCD_WriteData(0x0D);
|
||||||
|
LCD_WriteData(0x0B);
|
||||||
|
LCD_WriteData(0x07);
|
||||||
|
LCD_WriteData(0x28);
|
||||||
|
LCD_WriteData(0x33);
|
||||||
|
LCD_WriteData(0x3E);
|
||||||
|
LCD_WriteData(0x36);
|
||||||
|
LCD_WriteData(0x14);
|
||||||
|
LCD_WriteData(0x14);
|
||||||
|
LCD_WriteData(0x29);
|
||||||
|
LCD_WriteData(0x32);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0x21);
|
||||||
|
|
||||||
|
LCD_WriteCommand(0x11);
|
||||||
|
delay(120);
|
||||||
|
LCD_WriteCommand(0x29);
|
||||||
|
}
|
||||||
|
/******************************************************************************
|
||||||
|
function: Set the cursor position
|
||||||
|
parameter :
|
||||||
|
Xstart: Start uint16_t x coordinate
|
||||||
|
Ystart: Start uint16_t y coordinate
|
||||||
|
Xend : End uint16_t coordinates
|
||||||
|
Yend : End uint16_t coordinatesen
|
||||||
|
******************************************************************************/
|
||||||
|
void LCD_SetCursor(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend)
|
||||||
|
{
|
||||||
|
if (HORIZONTAL) {
|
||||||
|
// set the X coordinates
|
||||||
|
LCD_WriteCommand(0x2A);
|
||||||
|
LCD_WriteData(Xstart >> 8);
|
||||||
|
LCD_WriteData(Xstart + Offset_X);
|
||||||
|
LCD_WriteData(Xend >> 8);
|
||||||
|
LCD_WriteData(Xend + Offset_X);
|
||||||
|
|
||||||
|
// set the Y coordinates
|
||||||
|
LCD_WriteCommand(0x2B);
|
||||||
|
LCD_WriteData(Ystart >> 8);
|
||||||
|
LCD_WriteData(Ystart + Offset_Y);
|
||||||
|
LCD_WriteData(Yend >> 8);
|
||||||
|
LCD_WriteData(Yend + Offset_Y);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// set the X coordinates
|
||||||
|
LCD_WriteCommand(0x2A);
|
||||||
|
LCD_WriteData(Ystart >> 8);
|
||||||
|
LCD_WriteData(Ystart + Offset_Y);
|
||||||
|
LCD_WriteData(Yend >> 8);
|
||||||
|
LCD_WriteData(Yend + Offset_Y);
|
||||||
|
// set the Y coordinates
|
||||||
|
LCD_WriteCommand(0x2B);
|
||||||
|
LCD_WriteData(Xstart >> 8);
|
||||||
|
LCD_WriteData(Xstart + Offset_X);
|
||||||
|
LCD_WriteData(Xend >> 8);
|
||||||
|
LCD_WriteData(Xend + Offset_X);
|
||||||
|
}
|
||||||
|
LCD_WriteCommand(0x2C);
|
||||||
|
}
|
||||||
|
/******************************************************************************
|
||||||
|
function: Refresh the image in an area
|
||||||
|
parameter :
|
||||||
|
Xstart: Start uint16_t x coordinate
|
||||||
|
Ystart: Start uint16_t y coordinate
|
||||||
|
Xend : End uint16_t coordinates
|
||||||
|
Yend : End uint16_t coordinates
|
||||||
|
color : Set the color
|
||||||
|
******************************************************************************/
|
||||||
|
void LCD_addWindow(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend,uint16_t* color)
|
||||||
|
{
|
||||||
|
uint16_t Show_Width = Xend - Xstart + 1;
|
||||||
|
uint16_t Show_Height = Yend - Ystart + 1;
|
||||||
|
uint32_t numBytes = Show_Width * Show_Height * sizeof(uint16_t);
|
||||||
|
uint8_t Read_D[numBytes];
|
||||||
|
LCD_SetCursor(Xstart, Ystart, Xend, Yend);
|
||||||
|
LCD_WriteData_nbyte((uint8_t*)color, Read_D, numBytes);
|
||||||
|
}
|
||||||
|
// backlight
|
||||||
|
void Backlight_Init(void)
|
||||||
|
{
|
||||||
|
ledcAttach(EXAMPLE_PIN_NUM_BK_LIGHT, Frequency, Resolution);
|
||||||
|
ledcWrite(EXAMPLE_PIN_NUM_BK_LIGHT, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Set_Backlight(uint8_t Light) //
|
||||||
|
{
|
||||||
|
|
||||||
|
if(Light > 100 || Light < 0)
|
||||||
|
printf("Set Backlight parameters in the range of 0 to 100 \r\n");
|
||||||
|
else{
|
||||||
|
uint32_t Backlight = Light*10;
|
||||||
|
ledcWrite(EXAMPLE_PIN_NUM_BK_LIGHT, Backlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
sound_picture_app/Display_ST7789.h
Normal file
28
sound_picture_app/Display_ST7789.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#define LCD_WIDTH 172 //LCD width
|
||||||
|
#define LCD_HEIGHT 320 //LCD height
|
||||||
|
|
||||||
|
#define SPIFreq 80000000
|
||||||
|
#define EXAMPLE_PIN_NUM_MISO -1
|
||||||
|
#define EXAMPLE_PIN_NUM_MOSI 45
|
||||||
|
#define EXAMPLE_PIN_NUM_SCLK 40
|
||||||
|
#define EXAMPLE_PIN_NUM_LCD_CS 42
|
||||||
|
#define EXAMPLE_PIN_NUM_LCD_DC 41
|
||||||
|
#define EXAMPLE_PIN_NUM_LCD_RST 39
|
||||||
|
#define EXAMPLE_PIN_NUM_BK_LIGHT 48
|
||||||
|
#define Frequency 1000 // PWM frequencyconst
|
||||||
|
#define Resolution 10
|
||||||
|
|
||||||
|
#define VERTICAL 0
|
||||||
|
#define HORIZONTAL 1
|
||||||
|
|
||||||
|
#define Offset_X 34
|
||||||
|
#define Offset_Y 0
|
||||||
|
|
||||||
|
void LCD_SetCursor(uint16_t x1, uint16_t y1, uint16_t x2,uint16_t y2);
|
||||||
|
void LCD_Init(void);
|
||||||
|
void LCD_addWindow(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend,uint16_t* color);
|
||||||
|
void Backlight_Init(void);
|
||||||
|
void Set_Backlight(uint8_t Light);
|
||||||
119
sound_picture_app/SD_Card.cpp
Normal file
119
sound_picture_app/SD_Card.cpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#include "SD_Card.h"
|
||||||
|
|
||||||
|
bool SDCard_Flag;
|
||||||
|
bool SDCard_Finish;
|
||||||
|
|
||||||
|
uint16_t SDCard_Size;
|
||||||
|
uint16_t Flash_Size;
|
||||||
|
|
||||||
|
void SD_Init() {
|
||||||
|
if(!SD_MMC.setPins(SD_CLK_PIN, SD_CMD_PIN, SD_D0_PIN, SD_D1_PIN, SD_D2_PIN, SD_D3_PIN)){
|
||||||
|
printf("SD MMC: Pin change failed!\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (SD_MMC.begin("/sdcard", true, true)) {
|
||||||
|
printf("SD card initialization successful!\r\n");
|
||||||
|
} else {
|
||||||
|
printf("SD card initialization failed!\r\n");
|
||||||
|
}
|
||||||
|
uint8_t cardType = SD_MMC.cardType();
|
||||||
|
if(cardType == CARD_NONE){
|
||||||
|
printf("No SD card attached\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
printf("SD Card Type: ");
|
||||||
|
if(cardType == CARD_MMC){
|
||||||
|
printf("MMC\r\n");
|
||||||
|
} else if(cardType == CARD_SD){
|
||||||
|
printf("SDSC\r\n");
|
||||||
|
} else if(cardType == CARD_SDHC){
|
||||||
|
printf("SDHC\r\n");
|
||||||
|
} else {
|
||||||
|
printf("UNKNOWN\r\n");
|
||||||
|
}
|
||||||
|
uint64_t totalBytes = SD_MMC.totalBytes();
|
||||||
|
uint64_t usedBytes = SD_MMC.usedBytes();
|
||||||
|
SDCard_Size = totalBytes/(1024*1024);
|
||||||
|
printf("Total space: %llu\n", totalBytes);
|
||||||
|
printf("Used space: %llu\n", usedBytes);
|
||||||
|
printf("Free space: %llu\n", totalBytes - usedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool File_Search(const char* directory, const char* fileName)
|
||||||
|
{
|
||||||
|
File Path = SD_MMC.open(directory);
|
||||||
|
if (!Path) {
|
||||||
|
printf("Path: <%s> does not exist\r\n",directory);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File file = Path.openNextFile();
|
||||||
|
while (file) {
|
||||||
|
if (strcmp(file.name(), fileName) == 0) {
|
||||||
|
if (strcmp(directory, "/") == 0)
|
||||||
|
printf("File '%s%s' found in root directory.\r\n",directory,fileName);
|
||||||
|
else
|
||||||
|
printf("File '%s/%s' found in root directory.\r\n",directory,fileName);
|
||||||
|
Path.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
file = Path.openNextFile();
|
||||||
|
}
|
||||||
|
if (strcmp(directory, "/") == 0)
|
||||||
|
printf("File '%s%s' not found in root directory.\r\n",directory,fileName);
|
||||||
|
else
|
||||||
|
printf("File '%s/%s' not found in root directory.\r\n",directory,fileName);
|
||||||
|
Path.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles)
|
||||||
|
{
|
||||||
|
File Path = SD_MMC.open(directory);
|
||||||
|
if (!Path) {
|
||||||
|
printf("Path: <%s> does not exist\r\n",directory);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t fileCount = 0;
|
||||||
|
char filePath[100];
|
||||||
|
File file = Path.openNextFile();
|
||||||
|
while (file && fileCount < maxFiles) {
|
||||||
|
if (!file.isDirectory() && strstr(file.name(), fileExtension)) {
|
||||||
|
strncpy(File_Name[fileCount], file.name(), sizeof(File_Name[fileCount]));
|
||||||
|
if (strcmp(directory, "/") == 0) {
|
||||||
|
snprintf(filePath, 100, "%s%s", directory, file.name());
|
||||||
|
} else {
|
||||||
|
snprintf(filePath, 100, "%s/%s", directory, file.name());
|
||||||
|
}
|
||||||
|
printf("File found: %s\r\n", filePath);
|
||||||
|
fileCount++;
|
||||||
|
}
|
||||||
|
file = Path.openNextFile();
|
||||||
|
}
|
||||||
|
Path.close();
|
||||||
|
if (fileCount > 0) {
|
||||||
|
printf("%d <%s> files were retrieved\r\n",fileCount,fileExtension);
|
||||||
|
return fileCount;
|
||||||
|
} else {
|
||||||
|
printf("No files with extension '%s' found in directory: %s\r\n", fileExtension, directory);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_file_extension(char *file_name) {
|
||||||
|
char *last_dot = strrchr(file_name, '.');
|
||||||
|
if (last_dot != NULL) {
|
||||||
|
*last_dot = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flash_test()
|
||||||
|
{
|
||||||
|
printf("/********** RAM Test**********/\r\n");
|
||||||
|
uint32_t flashSize = ESP.getFlashChipSize();
|
||||||
|
Flash_Size = flashSize/1024/1024;
|
||||||
|
printf("Flash size: %d MB \r\n", flashSize/1024/1024);
|
||||||
|
printf("/******* RAM Test Over********/\r\n\r\n");
|
||||||
|
}
|
||||||
22
sound_picture_app/SD_Card.h
Normal file
22
sound_picture_app/SD_Card.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include "FS.h"
|
||||||
|
#include "SD_MMC.h"
|
||||||
|
|
||||||
|
#define SD_CLK_PIN 14
|
||||||
|
#define SD_CMD_PIN 15
|
||||||
|
#define SD_D0_PIN 16
|
||||||
|
#define SD_D1_PIN 18
|
||||||
|
#define SD_D2_PIN 17
|
||||||
|
#define SD_D3_PIN 21
|
||||||
|
|
||||||
|
extern uint16_t SDCard_Size;
|
||||||
|
extern uint16_t Flash_Size;
|
||||||
|
|
||||||
|
void SD_Init();
|
||||||
|
void Flash_test();
|
||||||
|
|
||||||
|
bool File_Search(const char* directory, const char* fileName);
|
||||||
|
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles);
|
||||||
|
void remove_file_extension(char *file_name);
|
||||||
100
sound_picture_app/sound_picture_app.ino
Normal file
100
sound_picture_app/sound_picture_app.ino
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#include "Display_ST7789.h"
|
||||||
|
#include "SD_Card.h"
|
||||||
|
#include "Display_PNG.h"
|
||||||
|
#include "Audio_Player.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#define BOOT_KEY_PIN 9
|
||||||
|
|
||||||
|
uint16_t currentIndex = 0;
|
||||||
|
bool lastButtonState = HIGH;
|
||||||
|
uint32_t lastDebounceTime = 0;
|
||||||
|
const uint32_t debounceDelay = 50;
|
||||||
|
bool buttonWasPressed = false;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
printf("\r\n==========================================\r\n");
|
||||||
|
printf("Starting Sound and Picture Player...\r\n");
|
||||||
|
printf("==========================================\r\n");
|
||||||
|
|
||||||
|
// Initialize peripherals
|
||||||
|
Flash_test();
|
||||||
|
SD_Init();
|
||||||
|
LCD_Init();
|
||||||
|
Set_Backlight(90);
|
||||||
|
|
||||||
|
// Setup button
|
||||||
|
pinMode(BOOT_KEY_PIN, INPUT_PULLUP);
|
||||||
|
|
||||||
|
// Retrieve media files from SD Card
|
||||||
|
Search_Images("/");
|
||||||
|
Search_Wavs("/");
|
||||||
|
|
||||||
|
printf("Initialization complete!\r\n");
|
||||||
|
printf("Found %d PNG images and %d WAV soundbites.\r\n", Image_Count, Wav_Count);
|
||||||
|
|
||||||
|
if (Image_Count > 0) {
|
||||||
|
printf("Displaying initial image (index 0): %s\r\n", Image_Names[0]);
|
||||||
|
Display_Image_By_Index("/", 0);
|
||||||
|
} else {
|
||||||
|
printf("WARNING: No PNG images found in SD Card root directory!\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void playCurrentPair() {
|
||||||
|
if (Image_Count == 0 && Wav_Count == 0) {
|
||||||
|
printf("Cannot play: missing PNG images and WAV soundbites!\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop current playing audio if any
|
||||||
|
stopWav();
|
||||||
|
|
||||||
|
printf("Playing item index %d:\r\n", currentIndex);
|
||||||
|
|
||||||
|
// Display the image
|
||||||
|
if (Image_Count > 0) {
|
||||||
|
uint16_t pngIndex = currentIndex % Image_Count;
|
||||||
|
Display_Image_By_Index("/", pngIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the soundbite
|
||||||
|
if (Wav_Count > 0) {
|
||||||
|
uint16_t wavIndex = currentIndex % Wav_Count;
|
||||||
|
String soundPath = String("/") + Wav_Names[wavIndex];
|
||||||
|
printf("Playing sound: %s\r\n", soundPath.c_str());
|
||||||
|
playWav(soundPath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
bool currentButtonReading = digitalRead(BOOT_KEY_PIN);
|
||||||
|
|
||||||
|
if (currentButtonReading != lastButtonState) {
|
||||||
|
lastDebounceTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((millis() - lastDebounceTime) > debounceDelay) {
|
||||||
|
if (currentButtonReading == LOW) {
|
||||||
|
if (!buttonWasPressed) {
|
||||||
|
buttonWasPressed = true;
|
||||||
|
printf("Button pressed! Cycling to next picture and playing sound...\r\n");
|
||||||
|
|
||||||
|
if (Image_Count > 0 || Wav_Count > 0) {
|
||||||
|
playCurrentPair();
|
||||||
|
currentIndex = (currentIndex + 1) % std::max(Image_Count, Wav_Count);
|
||||||
|
} else {
|
||||||
|
printf("No media found on SD card to cycle!\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buttonWasPressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastButtonState = currentButtonReading;
|
||||||
|
delay(10); // Yield to other RTOS tasks
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user