初始提交卡包 USB 工程

纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhengxin
2026-10-13 21:54:50 +08:00
co-authored by Cursor
commit 851e347cf5
260 changed files with 269349 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdint.h>
class AudioSourceFile {
public:
FILE* fp;
AudioSourceFile(const char* path) {
fp = fopen(path, "rb");
}
int read(uint8_t* buf, int len) {
if (!fp) return 0;
return fread(buf, 1, len, fp);
}
void seek(int offset) {
if (fp) fseek(fp, offset, SEEK_SET);
}
bool isOpen() {
return fp != nullptr;
}
~AudioSourceFile() {
if (fp) fclose(fp);
}
};
+29
View File
@@ -0,0 +1,29 @@
#include "micro_decoder/decoder_source.h"
#include "micro_decoder/types.h"
#include <stdio.h>
static FILE* g_fp = nullptr;
/*
extern "C" {
void md_init(const char* path)
{
g_fp = fopen(path, "rb");
}
int md_read(uint8_t* buf, int len)
{
if (!g_fp) return 0;
return fread(buf, 1, len, g_fp);
}
void md_deinit()
{
if (g_fp) {
fclose(g_fp);
g_fp = nullptr;
}
}
}
*/
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
//void md_init(const char* path);
//int md_read(uint8_t* buf, int len);
//void md_deinit();
#ifdef __cplusplus
}
#endif
+132
View File
@@ -0,0 +1,132 @@
#include "ns4168.h"
#include <stdint.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_log.h"
#include "esp_random.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "ns4168";
static void ns4168_white_noise_task(void *arg) //白噪音测试
{
uint8_t buffer[256];
size_t bytes_written = 0;
while (1) {
for (int i = 0; i < (int)sizeof(buffer); i += 2) {
uint16_t sample = (uint16_t)(esp_random() & 0xFFFF);
buffer[i] = (uint8_t)(sample & 0xFF);
buffer[i + 1] = (uint8_t)((sample >> 8) & 0xFF);
}
esp_err_t ret = i2s_write(NS4168_I2S_PORT, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_write failed: %s", esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
/**
* @brief 初始化 NS4168 的 CTRL 引脚并将其拉高(选择右声道,且退出关断模式)
*/
void ns4168_ctrl_init(void)
{
// 1. 配置 GPIO 参数结构体
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << NS4168_CTRL_GPIO), // 选中 GPIO33
.mode = GPIO_MODE_OUTPUT, // 设置为推挽输出
.intr_type = GPIO_INTR_DISABLE, // 禁止中断
.pull_up_en = GPIO_PULLUP_DISABLE, // 关闭内部上拉(不需要)
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 关闭内部下拉
};
// 2. 应用配置
esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE("NS4168", "GPIO33 配置失败!");
return;
}
// 3. 输出高电平(3.3V)
gpio_set_level(NS4168_CTRL_GPIO, 1);
ESP_LOGI("NS4168", "NS4168 CTRL 引脚已拉高 (GPIO33),选择右声道");
}
esp_err_t ns4168_i2s_init(void)
{
ns4168_ctrl_init();
esp_err_t ret = ESP_OK;
i2s_config_t i2s_cfg = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX,
.sample_rate = NS4168_I2S_SAMPLE_RATE,
.bits_per_sample = 16,
.channel_format = NS4168_I2S_CHANNELS,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 16,
.dma_buf_len = 1024,
.tx_desc_auto_clear = true,
.use_apll = false,
.fixed_mclk = 0,
};
i2s_pin_config_t pin_cfg = {
.mck_io_num = -1,
.bck_io_num = NS4168_I2S_BCK_PIN,
.ws_io_num = NS4168_I2S_WS_PIN,
.data_out_num = NS4168_I2S_DOUT_PIN,
.data_in_num = I2S_PIN_NO_CHANGE,
};
ret = i2s_driver_install(NS4168_I2S_PORT, &i2s_cfg, 0, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_driver_install failed: %s", esp_err_to_name(ret));
return ret;
}
ret = i2s_set_pin(NS4168_I2S_PORT, &pin_cfg);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_set_pin failed: %s", esp_err_to_name(ret));
return ret;
}
ret = i2s_zero_dma_buffer(NS4168_I2S_PORT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "i2s_zero_dma_buffer failed: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "NS4168 I2S init OK");
return ESP_OK;
}
esp_err_t ns4168_start_white_noise_test(void)
{
esp_err_t ret = ns4168_i2s_init();
if (ret != ESP_OK) {
return ret;
}
BaseType_t task_ret = xTaskCreatePinnedToCore(ns4168_white_noise_task, "ns4168_noise", 4096, NULL, 5, NULL, 1);
if (task_ret != pdPASS) {
ESP_LOGE(TAG, "create white noise task failed");
return ESP_FAIL;
}
return ESP_OK;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef __NS4168_H__
#define __NS4168_H__
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 先用宏定义占位引脚,后续再改成实际板子上的 GPIO */
#define NS4168_I2S_PORT I2S_NUM_1
#define NS4168_CTRL_GPIO GPIO_NUM_33
#define NS4168_I2S_BCK_PIN GPIO_NUM_31
#define NS4168_I2S_WS_PIN GPIO_NUM_32
#define NS4168_I2S_DOUT_PIN GPIO_NUM_30
#define NS4168_I2S_SAMPLE_RATE 24000
#define NS4168_I2S_BITS I2S_BITS_PER_SAMPLE_16BIT
#define NS4168_I2S_CHANNELS I2S_CHANNEL_FMT_ONLY_RIGHT
esp_err_t ns4168_i2s_init(void);
esp_err_t ns4168_start_white_noise_test(void);
#ifdef __cplusplus
}
#endif
#endif /* __NS4168_H__ */
+58
View File
@@ -0,0 +1,58 @@
#include "ogg_demux.h"
static uint32_t le32(const uint8_t* p)
{
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page)
{
if (buf_len < 27)
return -1;
/* 校验 "OggS" */
if (buf[0] != 'O' || buf[1] != 'g' || buf[2] != 'g' || buf[3] != 'S')
return -2;
/* 拷贝页头 */
for (int i = 0; i < 27; ++i)
page->header[i] = buf[i];
page->seg_count = buf[26];
if (buf_len < 27 + page->seg_count)
return -3;
/* 读取 segment table */
for (int i = 0; i < page->seg_count; ++i)
page->seg_table[i] = buf[27 + i];
/* 计算 body 大小 */
uint32_t body_size = 0;
for (int i = 0; i < page->seg_count; ++i)
body_size += page->seg_table[i];
page->body_size = body_size;
if (buf_len < 27 + page->seg_count + body_size)
return -4;
return (int)body_size;
}
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
uint8_t* packet, int packet_buf_size)
{
/* 极简版:假设一个页里只有一个完整 packet(常见于 OpusHead / OpusTags) */
uint32_t total = page->body_size;
if (total > (uint32_t)packet_buf_size)
return -1;
for (uint32_t i = 0; i < total; ++i)
packet[i] = body[i];
return (int)total;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef OGG_DEMUX_H
#define OGG_DEMUX_H
#include <stdint.h>
typedef struct {
uint8_t header[27]; /* Ogg 页头 */
uint8_t seg_count;
uint8_t seg_table[255];
uint32_t body_size;
} ogg_page_t;
/* 从数据缓冲中解析一页 Ogg,返回页体大小,<0 表示错误 */
int ogg_parse_page(const uint8_t* buf, int buf_len, ogg_page_t* page);
/* 根据 seg_table 把页体里的数据拼成一个 packet
返回 packet 长度,<0 表示错误 */
int ogg_extract_packet(const uint8_t* body, const ogg_page_t* page,
uint8_t* packet, int packet_buf_size);
#endif
+379
View File
@@ -0,0 +1,379 @@
#include <stdint.h>
#include "esp_log.h"
#include "NS4168/micro_decoder_wrapper.h"
#include "NS4168/wav_demux.h"
#include "NS4168/ns4168.h"
#include <math.h>
#include <stdio.h>
#include <stdint.h>
static FILE* g_fp = NULL;
void md_init(const char* path)
{
g_fp = fopen(path, "rb"); // 标准 C
}
int md_read(uint8_t* buf, int len)
{
if (!g_fp) return 0;
return fread(buf, 1, len, g_fp); // 标准 C
}
void md_deinit()
{
if (g_fp) {
fclose(g_fp); // 标准 C
g_fp = NULL; // 标准 C(替换 nullptr)
}
}
void play_wav(void)
{
ns4168_i2s_init();
uint8_t header[70];
size_t written = 0;
int ret;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
/**
* @param volume 音量系数,0.0=静音,1.0=原始音量,推荐 0.0~1.0
*/
void play_wav2(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
// 仅当音量不是 1.0 时才缩放(优化性能)
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 假定为 16-bit PCM
for (int i = 0; i < num_samples; i++) {
int32_t val = (int32_t)(samples[i] * volume);
// 饱和限幅,防止溢出破音
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav3(float volume)
{
// 1. 打开文件
md_init("/extflash/ring2.wav");
// 2. 读取头部(沿用 70 字节,兼容你的原逻辑)
uint8_t header[70];
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放,音量=%.2f", volume);
// 3. 将浮点音量转换为 Q15 定点数 (范围 0 ~ 32768)
int32_t vol_fixed = (int32_t)(volume * 32768.0f + 0.5f);
if (vol_fixed > 32768) vol_fixed = 32768;
if (vol_fixed < 0) vol_fixed = 0;
size_t written = 0;
uint8_t buf[1024];
int n;
// 4. 循环读取音频数据并处理
while ((n = md_read(buf, sizeof(buf))) > 0) {
// 确保处理偶数个字节,防止破坏样本对齐
int bytes_to_process = (n / 2) * 2;
int num_samples = bytes_to_process / 2;
int16_t *samples = (int16_t *)buf;
// 音量接近静音时直接填零(避免处理微小信号产生的噪声)
if (vol_fixed < 32) {
memset(buf, 0, bytes_to_process);
}
else if (vol_fixed != 32768) { // 不等于 1.0 时才缩放
for (int i = 0; i < num_samples; i++) {
// 整数定点缩放 + 四舍五入
int32_t val = ((int32_t)samples[i] * vol_fixed + 16384) >> 15;
// 饱和限幅(实际不会溢出,但保留保护)
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
// 发送对齐后的数据(丢弃奇数尾字节)
i2s_write(NS4168_I2S_PORT, buf, bytes_to_process, &written, portMAX_DELAY);
}
// 5. 关闭文件
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav4(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 浮点缩放
float scaled = (float)samples[i] * volume;
// 四舍五入(关键)
int32_t val = lrintf(scaled);
// 饱和限幅
if (val > 32767) val = 32767;
if (val < -32768) val = -32768;
samples[i] = (int16_t)val;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav5(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 将浮点音量转换为 8-bit 定点(0~256)
int32_t vol_fixed = (int32_t)(volume * 256.0f);
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (volume != 1.0f) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 原始 16-bit PCM
int32_t v = samples[i];
// 定点缩放:扩大到 24-bit 精度再缩回
v = (v * vol_fixed) >> 8;
// 饱和限幅
if (v > 32767) v = 32767;
if (v < -32768) v = -32768;
samples[i] = (int16_t)v;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
void play_wav_p4(float volume)
{
uint8_t header[70];
size_t written = 0;
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX("WAV", header, sizeof(header));
ESP_LOGI("WAV", "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 如果音量是 1.0,直接原样播放,完全无损
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
// 简单 dither 强度(可以微调)
const float dither_amp = 1.0f / 512.0f; // 很小的抖动
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (!bypass) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 提升到 float 32-bit 精度
float v = (float)samples[i];
// 音量缩放
v *= volume;
// 加一点点 dither,打散量化误差
// 简单线性同余随机数(不依赖 rand())
static uint32_t rnd = 0x12345678;
rnd = rnd * 1664525u + 1013904223u;
float noise = ((int32_t)(rnd >> 16) - 32768) * (dither_amp / 32768.0f);
v += noise;
// 高精度四舍五入
float v_rounded = floorf(v + 0.5f * (v >= 0 ? 1.0f : -1.0f));
// 饱和限幅
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
samples[i] = (int16_t)v_rounded;
}
}
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI("WAV", "WAV播放结束");
}
#include <stdbool.h>
#include "driver/i2s.h"
// 简单的 32-bit 伪随机数,用于 dither
static inline uint32_t prng32(void)
{
static uint32_t s = 0x12345678u;
s = s * 1664525u + 1013904223u;
return s;
}
void play_wav_ring_with_volume(float volume)
{
const char *TAG = "WAV_RING";
uint8_t header[70];
size_t written = 0;
// 初始化并读取 WAV 头
md_init("/extflash/ring2.wav");
md_read(header, sizeof(header));
ESP_LOG_BUFFER_HEX(TAG, header, sizeof(header));
ESP_LOGI(TAG, "WAV解析成功,开始播放");
uint8_t buf[1024];
int n;
// 音量为 1.0 时完全绕过处理,保证绝对无损
bool bypass = (fabsf(volume - 1.0f) < 0.0001f);
// dither 强度(非常小,人耳几乎感知不到)
const float dither_amp = 1.0f / 4096.0f; // 可微调
while ((n = md_read(buf, sizeof(buf))) > 0) {
if (!bypass) {
int16_t *samples = (int16_t *)buf;
int num_samples = n / 2; // 16-bit PCM
for (int i = 0; i < num_samples; i++) {
// 提升到 float 32-bit 精度
float v = (float)samples[i];
// 音量缩放
v *= volume;
// 三角 dither(TDPF):(rand1 - rand2) * scale
uint32_t r1 = prng32();
uint32_t r2 = prng32();
float noise = ((int32_t)(r1 & 0xFFFF) - (int32_t)(r2 & 0xFFFF))
* (dither_amp / 65536.0f);
v += noise;
// 高精度四舍五入
float v_rounded = floorf(v + 0.5f * (v >= 0.0f ? 1.0f : -1.0f));
// 轻微 soft-clip,避免极端削波毛刺
if (v_rounded > 32767.0f) {
float over = v_rounded - 32767.0f;
v_rounded = 32767.0f + over * 0.01f; // 把硬削波变成软削波
} else if (v_rounded < -32768.0f) {
float under = v_rounded + 32768.0f;
v_rounded = -32768.0f + under * 0.01f;
}
// 最终饱和限幅
if (v_rounded > 32767.0f) v_rounded = 32767.0f;
if (v_rounded < -32768.0f) v_rounded = -32768.0f;
samples[i] = (int16_t)v_rounded;
}
}
// 写入 I2S
i2s_write(NS4168_I2S_PORT, buf, n, &written, portMAX_DELAY);
}
md_deinit();
ESP_LOGI(TAG, "WAV播放结束");
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef WAV_DEMUX_H
#define WAV_DEMUX_H
#include <stdint.h>
void play_wav(void);
#endif