这个ESP32项目实现了一个非常实用的功能:通过串口0实时修改目标服务器的IP地址,并将修改后的IP地址存储到EEPROM中,实现断电记忆。核心功能包括:
这个设计特别适合需要频繁更换连接目标的物联网设备,比如在不同测试环境间切换的音频采集终端。我在实际工业项目中多次使用类似方案,确实能大幅提升调试效率。
这个项目使用了ESP32的以下硬件资源:
注意:ESP32的串口0默认用于下载调试,实际产品中建议改用其他串口,避免与下载冲突。
初始化顺序非常重要,错误的初始化顺序可能导致硬件冲突:
cpp复制void setup() {
Serial.begin(115200);
EEPROM.begin(32); // 分配32字节EEPROM空间
load_ip_from_eeprom();
// 必须先初始化I2S再启动WiFi
configure_i2s();
WiFi.begin(ssid, password);
// WiFi功率优化
WiFi.setTxPower(WIFI_POWER_8_5dBm);
// GPIO驱动能力配置
gpio_set_drive_capability((gpio_num_t)PDM_CLK_GPIO, GPIO_DRIVE_CAP_1);
gpio_set_drive_capability((gpio_num_t)PDM_DIN_GPIO, GPIO_DRIVE_CAP_1);
gpio_set_pull_mode((gpio_num_t)PDM_DIN_GPIO, GPIO_PULLUP_ONLY);
}
这里有几个关键点:
串口命令处理采用非阻塞方式,避免影响音频传输实时性:
cpp复制void loop() {
static uint8_t cmd_buf[32], cmdIdx = 0;
while (Serial.available()) {
char c = Serial.read();
if (c == '\n' || c == '\r') {
if (cmdIdx) {
cmd_buf[cmdIdx] = 0;
if (parse_ip_cmd((char *)cmd_buf)) {
save_ip_to_eeprom((char *)cmd_buf + 3);
ESP.restart();
}
cmdIdx = 0;
}
}
else if (cmdIdx < sizeof(cmd_buf) - 1) {
cmd_buf[cmdIdx++] = c;
}
}
// ...其他逻辑
}
这段代码实现了:
IP地址校验非常严谨,防止非法输入:
cpp复制bool parse_ip_cmd(const char *line) {
if (strncmp(line, "ip=", 3) != 0) return false;
// 第一层校验:格式和数值范围
unsigned int seg[4];
if (sscanf(line + 3, "%3u.%3u.%3u.%3u", &seg[0], &seg[1], &seg[2], &seg[3]) != 4)
return false;
for (int i = 0; i < 4; ++i)
if (seg[i] > 255) return false;
// 第二层校验:前导零检查
char ip_part[16];
strlcpy(ip_part, line + 3, sizeof(ip_part));
char *p = ip_part, *token;
while ((token = strtok(p, ".")) != nullptr) {
p = nullptr;
if (token[0] == '0' && token[1] != '\0') return false;
}
return true;
}
校验分为两层:
ESP32没有真正的EEPROM,而是用Flash模拟:
cpp复制void save_ip_to_eeprom(const char *ip) {
for (int i = 0; i < 15; i++)
EEPROM.write(i, i < strlen(ip) ? ip[i] : 0);
EEPROM.commit(); // 必须调用commit才会实际写入
}
void load_ip_from_eeprom() {
char tmp[16] = {0};
for (int i = 0; i < 15; i++)
tmp[i] = EEPROM.read(i);
if (strncmp(tmp, "192", 3) != 0) // 简单校验
strcpy(eeprom_ip, tmp);
}
注意事项:
Flash有擦写次数限制(约10万次),优化建议:
cpp复制void configure_i2s() {
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
chan_cfg.dma_desc_num = 16; // 缓冲区数量
chan_cfg.dma_frame_num = 1024; // 每缓冲区帧数
i2s_pdm_rx_config_t pdm_cfg = {
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(16000), // 16kHz采样率
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.clk = (gpio_num_t)PDM_CLK_GPIO,
.din = (gpio_num_t)PDM_DIN_GPIO,
.invert_flags = { .clk_inv = false }
},
};
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_chan, &pdm_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));
}
关键参数说明:
cpp复制// 在loop()中:
static int16_t buf[512];
size_t bytes_read = 0;
if (i2s_channel_read(rx_chan, buf, sizeof(buf), &bytes_read, portMAX_DELAY) == ESP_OK) {
client.write((uint8_t*)buf, bytes_read);
}
这种流式传输特点:
IP修改不生效
音频传输断断续续
WiFi连接不稳定
添加IP地址缓存机制
增强传输可靠性
远程配置扩展
这个项目最实用的价值在于展示了如何在资源受限的嵌入式设备上实现灵活的配置管理。我在多个工业物联网项目中都采用了类似的架构,特别是在需要频繁更换测试环境的场景下,能节省大量重复烧录程序的时间。