esp32s3 驱动pcm5102a 的 wav播放器,mqtt控制
mqtt 命令:
1.音量: mosquitto_pub -h 192.168.101.233 -t esp32/volume -m 0.54
2.停止播放:mosquitto_pub -h 192.168.101.233 -t esp32/control -m stop
3.选曲:mosquitto_pub -h 192.168.101.233 -t esp32/play -m 20.wav
4.全部播放:mosquitto_pub -h 192.168.101.233 -t esp32/playall -m on
接着发:mqtt门窗传感器制作和代码,mqtt 智能开关和代码,mqtt 控制的esp32cam 摄像头和代码
准备写自制一个家庭智能系统的过程,从deepin安装开始,接着idf安装的过程。
也考虑拍视频上传抖音,准备中
home assistant 控制已完成的部分esp32硬件
代码中sd卡的spi读取频率非常重要,太大太小都影响音质。由于设置错误一直破音排查了好久。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"#include "esp_log.h"
#include "esp_err.h"
#include "esp_vfs_fat.h"#include "driver/i2s_std.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"
#include <dirent.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "mqtt_client.h"
#include <math.h>
#define TAG "WAV_PLAYER"// SD 卡 SPI 引脚
#define PIN_NUM_MISO 2
#define PIN_NUM_MOSI 3
#define PIN_NUM_CLK 4
#define PIN_NUM_CS 1#define SZ 48000// PCM5102A I2S 引脚
#define I2S_BCK_IO 17
#define I2S_WS_IO 8
#define I2S_DATA_IO 18// 音量控制变量(由 MQTT 控制)
float g_volume = 0.5f;char current_song[272] = "/sdcard/7.wav"; // 默认歌曲
bool should_play = true;
bool stop_playback = false;
bool pause_playback = false;
bool play_all=false;
// 采样率和高通滤波器参数
#define SAMPLE_RATE 48000.0f
#define HPF_CUTOFF_FREQ 100.0fstatic i2s_chan_handle_t tx_chan;// WAV 文件头结构体
typedef struct {char riff[4];uint32_t chunk_size;char wave[4];char fmt[4];uint32_t fmt_len;uint16_t audio_format;uint16_t num_channels;uint32_t sample_rate;uint32_t byte_rate;uint16_t block_align;uint16_t bits_per_sample;char data[4];uint32_t data_size;
} wav_header_t;// 初始化 I2S
static void init_i2s(void) {i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, NULL));i2s_std_config_t std_cfg = {.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SZ),.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,.bclk = I2S_BCK_IO,.ws = I2S_WS_IO,.dout = I2S_DATA_IO,.din = I2S_GPIO_UNUSED,},};ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg));ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
}// 初始化 SD 卡
static esp_err_t init_sd_card(void) {esp_err_t ret;sdmmc_card_t *card;const char mount_point[] = "/sdcard";ESP_LOGI(TAG, "Initializing SD card...");sdmmc_host_t host = SDSPI_HOST_DEFAULT();host.slot = SPI2_HOST;host.max_freq_khz = 10000;spi_bus_config_t bus_cfg = {.mosi_io_num = PIN_NUM_MOSI,.miso_io_num = PIN_NUM_MISO,.sclk_io_num = PIN_NUM_CLK,.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz =2*4096,};ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);if (ret != ESP_OK) return ret;sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();slot_config.gpio_cs = PIN_NUM_CS;slot_config.host_id = host.slot;esp_vfs_fat_sdmmc_mount_config_t mount_config = {.format_if_mount_failed = false,.max_files = 3,.allocation_unit_size = 16 * 1024};ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to mount SD card: %s", esp_err_to_name(ret));return ret;}ESP_LOGI(TAG, "SD card mounted: %s", card->cid.name);return ESP_OK;
}//----------------------------------------
static void play_wav(const char *path) {FILE *f = fopen(path, "rb");if (!f) {ESP_LOGE(TAG, "Failed to open file: %s", path);return;}// 跳过 WAV 头(44 字节)fseek(f, 44, SEEK_SET);// 注意:这里你假设了44100Hz,如果你的WAV不是这个采样率,建议解析头部取样率i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100);i2s_channel_disable(tx_chan);ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg));i2s_channel_enable(tx_chan);uint8_t buffer[1024];size_t bytes_read, bytes_written;while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) > 0) {if (stop_playback) {ESP_LOGI(TAG, "Playback stopped");int16_t silence[128] = {0}; // 256字节静音size_t written;i2s_channel_write(tx_chan, silence, sizeof(silence), &written, portMAX_DELAY);// 可选:延时一点点,给 DAC 稳定时间vTaskDelay(pdMS_TO_TICKS(10));// 停止通道i2s_channel_disable(tx_chan);break;}int16_t *samples = (int16_t *)buffer;int num_samples = bytes_read / sizeof(int16_t);for (int i = 0; i < num_samples; i++) {// 音量调节,确保在有效范围内float adjusted = samples[i] * g_volume;if (adjusted > 32767.0f) adjusted = 32767.0f;if (adjusted < -32768.0f) adjusted = -32768.0f;samples[i] = (int16_t)adjusted;}i2s_channel_write(tx_chan, buffer, bytes_read, &bytes_written, portMAX_DELAY);}fclose(f);ESP_LOGI(TAG, "Playback finished: %s", path);
}// WiFi事件标志
static EventGroupHandle_t wifi_event_group;
const int WIFI_CONNECTED_BIT = BIT0;static void wifi_event_handler(void *arg, esp_event_base_t event_base,int32_t event_id, void *event_data) {if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {esp_wifi_connect();} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {esp_wifi_connect();xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT);} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);}
}static void wifi_init_sta(void) {wifi_event_group = xEventGroupCreate();ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_sta();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));wifi_config_t wifi_config = {.sta = {.ssid ="CMCC-6ZN9",.password = "2KJKWEML",.threshold.authmode = WIFI_AUTH_WPA2_PSK,.pmf_cfg = {.capable = true,.required = false,},},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());ESP_LOGI(TAG, "wifi_init_sta finished.");// 等待连接完成EventBits_t bits = xEventGroupWaitBits(wifi_event_group,WIFI_CONNECTED_BIT,pdFALSE,pdFALSE,portMAX_DELAY);if (bits & WIFI_CONNECTED_BIT) {ESP_LOGI(TAG, "Connected to AP SSID:%s","CMCC");} else {ESP_LOGE(TAG, "Failed to connect to SSID:%s","CMCC");}
}// MQTT事件回调
static esp_mqtt_client_handle_t mqtt_client = NULL;static void mqtt_event_handler(void *handler_args, esp_event_base_t base,int32_t event_id, void *event_data) {esp_mqtt_event_handle_t event = event_data;switch ((esp_mqtt_event_id_t)event_id) {case MQTT_EVENT_CONNECTED:ESP_LOGI(TAG, "MQTT Connected");esp_mqtt_client_subscribe(mqtt_client, "esp32/volume", 0);esp_mqtt_client_subscribe(mqtt_client, "esp32/control", 0);esp_mqtt_client_subscribe(mqtt_client, "esp32/play", 0);esp_mqtt_client_subscribe(mqtt_client, "esp32/playall", 0);break;case MQTT_EVENT_DISCONNECTED:ESP_LOGW(TAG, "MQTT Disconnected");break;case MQTT_EVENT_DATA:ESP_LOGI(TAG, "MQTT Data Received: topic=%.*s data=%.*s",event->topic_len, event->topic,event->data_len, event->data);if (strncmp(event->topic, "esp32/volume", event->topic_len) == 0) {char payload[16] = {0};int len = event->data_len < 15 ? event->data_len : 15;memcpy(payload, event->data, len);float vol = atof(payload);if (vol >= 0.0f && vol <= 1.0f) {g_volume = vol;ESP_LOGI(TAG, "Volume set to %.2f via MQTT", g_volume);} else {ESP_LOGW(TAG, "Invalid volume value received: %s", payload);}} else if (strncmp(event->topic, "esp32/control", event->topic_len) == 0) {char cmd[32] = {0};int len = event->data_len < 31 ? event->data_len : 31;memcpy(cmd, event->data, len);// if (strcmp(cmd, "pause") == 0) {pause_playback = true;// ESP_LOGI(TAG, "Playback paused");// } else if (strcmp(cmd, "resume") == 0) {pause_playback = false;// ESP_LOGI(TAG, "Playback resumed");// } elseif (strcmp(cmd, "stop") == 0) {stop_playback = true;ESP_LOGI(TAG, "Playback stopped");}}else if (event->topic_len == strlen("esp32/play") &&strncmp(event->topic, "esp32/play", event->topic_len) == 0) {char song[56] = {0};int len = event->data_len < 55 ? event->data_len : 55;memcpy(song, event->data, len);song[len] = '\0';snprintf(current_song, sizeof(current_song), "/sdcard/%s", song);stop_playback = true; // 停掉当前播放should_play = true; // 标记准备播放新曲目ESP_LOGI(TAG, "Switching to song: %s", current_song);
}else if (strncmp(event->topic, "esp32/playall", event->topic_len) == 0) {char cmd[32] = {0};int len = event->data_len < 31 ? event->data_len : 31;memcpy(cmd, event->data, len);if (strcmp(cmd, "on") == 0) {play_all = true;should_play = false;stop_playback = true;pause_playback = false;ESP_LOGI(TAG, "Playall music");}
}
//---------------------------------------------break;default:break;}
}// MQTT启动函数
static void mqtt_app_start(void) {esp_mqtt_client_config_t mqtt_cfg = {.broker.address.uri = "mqtt://192.168.101.233",};mqtt_client = esp_mqtt_client_init(&mqtt_cfg);esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);esp_mqtt_client_start(mqtt_client);
}void app_main(void) {ESP_ERROR_CHECK(nvs_flash_init());ESP_ERROR_CHECK(init_sd_card());wifi_init_sta();init_i2s();mqtt_app_start();while (1) {if (should_play) {should_play = false;stop_playback = false;pause_playback = false;play_wav(current_song);}if (play_all) {play_all = false;should_play = false;stop_playback = false;pause_playback = false;DIR *dir = opendir("/sdcard");if (dir == NULL) {ESP_LOGE(TAG, "Failed to open /sdcard directory");} else {struct dirent *entry;while ((entry = readdir(dir)) != NULL) {if (entry->d_type == DT_REG) {const char *name = entry->d_name;size_t len = strlen(name);if (len > 4 && strcasecmp(&name[len - 4], ".wav") == 0) {snprintf(current_song, sizeof(current_song), "/sdcard/%s", name);ESP_LOGI(TAG, "Playing file: %s", current_song);play_wav(current_song);if (stop_playback) {ESP_LOGW(TAG, "Stopped during play all");break;}}}}closedir(dir);ESP_LOGI(TAG, "Play all finished.");}}vTaskDelay(pdMS_TO_TICKS(100)); // 避免占满 CPU}}