dac_njw1195.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. #include "dac_njw1195.h"
  2. #include "driver/spi_master.h"
  3. #include "driver/gpio.h"
  4. #include "nvs_flash.h"
  5. #include "nvs.h"
  6. #include "esp_log.h"
  7. #include "freertos/FreeRTOS.h"
  8. #include "freertos/task.h"
  9. #include "freertos/semphr.h"
  10. #include "rom/ets_sys.h"
  11. #include "esp_timer.h"
  12. static const char TAG[] = "NJW1195AV";
  13. // GPIO Pin Definitions
  14. #define NJW_MOSI_GPIO 23
  15. #define NJW_CLK_GPIO 18
  16. #define NJW_LATCH_GPIO 33
  17. // NJW1195 寄存器地址 D3~D0 (ADR1=GND, ADR0=GND -> 芯片地址 0x0)
  18. // 16-bit 帧结构: [D15~D8 = DATA] [D7~D4 = 芯片地址 0x0] [D3~D0 = 寄存器地址]
  19. // ESP32 发送: Byte[0] = DATA(高字节), Byte[1] = (ChipAddr<<4)|RegAddr(低字节)
  20. #define NJW_REG_VOL_1A 0x0 // D3~D0=0000 主音箱左声道音量 (1A)
  21. #define NJW_REG_VOL_1B 0x1 // D3~D0=0001 主音箱右声道音量 (1B)
  22. #define NJW_REG_VOL_2A 0x2 // D3~D0=0010 低音炮左声道音量 (2A)
  23. #define NJW_REG_VOL_2B 0x3 // D3~D0=0011 低音炮右声道音量 (2B)
  24. #define NJW_REG_SEL_A 0x4 // D3~D0=0100 输入选择器 A (所有左声道)
  25. #define NJW_REG_SEL_B 0x5 // D3~D0=0101 输入选择器 B (所有右声道)
  26. // 芯片地址 D7~D4: ADR1=GND, ADR0=GND -> 0x0
  27. #define NJW1195_ADDR 0x0
  28. // 音量衰减数据 (D15~D8):
  29. // 0x40 = 0dB (最大安全音量)
  30. // 0xFF = 静音 (-95dB)
  31. #define NJW_VOL_MUTE 0xFF // 静音 (最大衰减)
  32. #define NJW_VOL_0DB 0x40 // 0dB 最大安全音量
  33. #define NJW_VOL_MAX_ATT 0xFF // -95dB 最大衰减值 (同静音)
  34. // 输入选择器数据 (D15~D8):
  35. // D15~D13: 1A 通道选择 (001=Input1/BT, 010=Input2/AUX)
  36. // D12~D10: 2A 通道选择 (001=Input1/BT, 010=Input2/AUX)
  37. // D9~D8: 固定为 0
  38. // BT(Input1): 001 001 00 -> 0x24
  39. // AUX(Input2): 010 010 00 -> 0x48
  40. // GND(Input3): 011 011 00 -> 0x6C
  41. #define NJW_SEL_BT 0x24 // 选通 Input 1 (蓝牙)
  42. #define NJW_SEL_AUX 0x48 // 选通 Input 2 (AUX)
  43. #define NJW_SEL_GND 0x6C // 选通 Input 3 (物理强制静音)
  44. // Volume range in dB for AirPlay
  45. #define VOLUME_MIN_DB (-30.0f)
  46. #define VOLUME_MAX_DB 0.0f
  47. static spi_device_handle_t s_spi_handle = NULL;
  48. static SemaphoreHandle_t s_njw_mutex = NULL;
  49. static uint8_t s_vol_main = 100;
  50. static uint8_t s_vol_bass = 100;
  51. static uint8_t s_prompt_vol = 50;
  52. static njw_source_t s_source = NJW_SOURCE_BT;
  53. static bool s_muted = false;
  54. bool s_in_panel_update = false;
  55. extern void panel_uart_report(uint8_t item, uint8_t value);
  56. static void njw1195_write_reg(uint8_t reg, uint8_t data) {
  57. if (s_spi_handle == NULL) {
  58. ESP_LOGE(TAG, "错误:SPI 未初始化");
  59. return;
  60. }
  61. // 16-bit 帧组包 (MSB First, 高字节先发):
  62. // Byte[0] = DATA (D15~D8): 音量衰减值 或 选通控制位
  63. // Byte[1] = (寄存器地址 D7~D4) | (芯片地址 D3~D0)
  64. uint8_t low_byte = (uint8_t)((reg & 0x0F) << 4) | (NJW1195_ADDR & 0x0F);
  65. uint8_t tx_data[2];
  66. tx_data[0] = data; // 高字节: DATA
  67. tx_data[1] = low_byte; // 低字节: 地址
  68. ESP_LOGI(TAG, "SPI TX: %02X %02X (DATA=%02X, REG=0x%X)", tx_data[0], tx_data[1], data, reg);
  69. spi_transaction_t t = {
  70. .flags = 0,
  71. .length = 16,
  72. .tx_buffer = tx_data,
  73. .rx_buffer = NULL,
  74. };
  75. // 时序: 空闲时 LATCH=HIGH
  76. // 拉低 LATCH -> 发送 16 bits -> 拉高 LATCH (芯片锁存数据) -> 维持高电平空闲
  77. gpio_set_level(NJW_LATCH_GPIO, 0); // 拉低:芯片进入接收状态
  78. ets_delay_us(2); // 建立时间,让芯片稳定
  79. esp_err_t err = spi_device_transmit(s_spi_handle, &t);
  80. if (err != ESP_OK) {
  81. ESP_LOGE(TAG, "SPI 发送失败: %s", esp_err_to_name(err));
  82. gpio_set_level(NJW_LATCH_GPIO, 1); // 恢复高电平空闲态
  83. return;
  84. }
  85. ets_delay_us(5); // 建立时间: 数据手册要求最小 4μs,此处给 5μs 余量
  86. gpio_set_level(NJW_LATCH_GPIO, 1); // 拉高:芯片锁存数据到寄存器
  87. ets_delay_us(5); // LATCH 保持脉宽 >= 1.6μs,此处给 5μs 余量
  88. // LATCH 保持高电平作为空闲态,等待下一次操作
  89. }
  90. static void save_settings_nvs(void) {
  91. nvs_handle_t nvs;
  92. if (nvs_open("njw", NVS_READWRITE, &nvs) == ESP_OK) {
  93. nvs_set_u8(nvs, "vol_main", s_vol_main);
  94. nvs_set_u8(nvs, "vol_bass", s_vol_bass);
  95. nvs_set_u8(nvs, "source", (uint8_t)s_source);
  96. nvs_commit(nvs);
  97. nvs_close(nvs);
  98. }
  99. }
  100. static esp_timer_handle_t s_nvs_timer = NULL;
  101. static void nvs_timer_callback(void *arg) {
  102. ESP_LOGI(TAG, "音量已保存到 NVS");
  103. save_settings_nvs();
  104. }
  105. static void trigger_save_settings_deferred(void) {
  106. if (s_nvs_timer == NULL) {
  107. esp_timer_create_args_t timer_args = {
  108. .callback = nvs_timer_callback,
  109. .arg = NULL,
  110. .name = "njw_nvs_save"
  111. };
  112. esp_timer_create(&timer_args, &s_nvs_timer);
  113. }
  114. if (s_nvs_timer != NULL) {
  115. esp_timer_stop(s_nvs_timer);
  116. // 延迟 3 秒保存 (3,000,000 微秒),以降低 Flash 磨损
  117. esp_timer_start_once(s_nvs_timer, 3000000);
  118. }
  119. }
  120. static void load_settings_nvs(void) {
  121. nvs_handle_t nvs;
  122. if (nvs_open("njw", NVS_READONLY, &nvs) == ESP_OK) {
  123. nvs_get_u8(nvs, "vol_main", &s_vol_main);
  124. nvs_get_u8(nvs, "vol_bass", &s_vol_bass);
  125. nvs_get_u8(nvs, "prompt_vol", &s_prompt_vol);
  126. uint8_t src_val = 0;
  127. if (nvs_get_u8(nvs, "source", &src_val) == ESP_OK) {
  128. s_source = (njw_source_t)src_val;
  129. }
  130. nvs_close(nvs);
  131. }
  132. ESP_LOGI(TAG, "从 NVS 加载设置成功 - 主音量: %d%%, 低音音量: %d%%, 输入源: %s",
  133. s_vol_main, s_vol_bass, (s_source == NJW_SOURCE_BT) ? "蓝牙" : "AUX");
  134. }
  135. // 将 0~100% 百分比转换为 NJW1195 音量衰减代码 (D15~D8)
  136. // 数据手册: 0x00 = +31.5dB (危险!), 0x40 = 0dB (最大安全值), 0xFF = 静音(-95dB)
  137. // 衰减值越大(数字越大),音量越小!方向与直觉相反!
  138. // 0% -> 0xFF (静音)
  139. // 100% -> 0x40 (0dB, 最大安全音量)
  140. static uint8_t pct_to_code(uint8_t pct) {
  141. if (pct == 0) {
  142. return NJW_VOL_MUTE; // 0xFF 静音
  143. }
  144. if (pct > 100) {
  145. pct = 100;
  146. }
  147. // 使用更柔和的符合人耳听觉的二次方曲线 (Audio Taper)
  148. // 最大衰减限制为 120 级 (约 -60dB)。
  149. // 使得 50% 音量对应约 -15dB,解决前 50% 太小、后 50% 太大的问题。
  150. uint32_t x = 100 - pct;
  151. uint32_t offset = (120 * x * x) / 10000;
  152. uint8_t code = 0x40 + (uint8_t)offset;
  153. return code;
  154. }
  155. // 主声道重映射 0~100% 到实际的物理输出比例
  156. static uint8_t pct_to_code_main(uint8_t pct) {
  157. if (pct == 0) return NJW_VOL_MUTE;
  158. if (pct > 100) pct = 100;
  159. // 限制最大音量,例如 (pct * 50) / 100;
  160. uint8_t physical_pct = (pct * 68) / 100;
  161. if (physical_pct == 0) physical_pct = 1; // 避免计算为0变静音
  162. return pct_to_code(physical_pct);
  163. }
  164. // 低音重映射 0~100% 到实际的物理输出比例
  165. static uint8_t pct_to_code_bass(uint8_t pct) {
  166. if (pct == 0) return NJW_VOL_MUTE;
  167. if (pct > 100) pct = 100;
  168. uint8_t physical_pct = (pct * 38) / 100;
  169. if (physical_pct == 0) physical_pct = 1; // 避免计算为0变静音
  170. return pct_to_code(physical_pct);
  171. }
  172. // ==========================================
  173. // 音量消抖 (Debounce) 定时器机制
  174. // ==========================================
  175. static esp_timer_handle_t s_vol_debounce_timer = NULL;
  176. static uint8_t s_target_vol_main = 0;
  177. static bool s_vol_update_pending = false;
  178. static bool s_target_vol_from_panel = false;
  179. static void vol_debounce_timer_cb(void *arg) {
  180. if (!s_vol_update_pending) return;
  181. s_vol_update_pending = false;
  182. uint8_t pct = s_target_vol_main;
  183. bool from_panel = s_target_vol_from_panel;
  184. if (s_vol_main == pct) {
  185. return; // 防抖阶段已经被其他机制覆盖,无变化则跳过
  186. }
  187. if (s_njw_mutex != NULL) {
  188. xSemaphoreTake(s_njw_mutex, portMAX_DELAY);
  189. }
  190. s_vol_main = pct;
  191. if (!s_muted) {
  192. uint8_t code = pct_to_code_main(s_vol_main);
  193. njw1195_write_reg(NJW_REG_VOL_2A, code);
  194. ets_delay_us(500); // 连续写入保护延时
  195. njw1195_write_reg(NJW_REG_VOL_2B, code);
  196. ESP_LOGI(TAG, "Vol: %d%% (Mapped to %d%%, Reg: %02X)", s_vol_main, (s_vol_main * 100) / 100, code);
  197. }
  198. trigger_save_settings_deferred();
  199. if (!from_panel) {
  200. panel_uart_report(0x0C, s_vol_main);
  201. }
  202. if (s_njw_mutex != NULL) {
  203. xSemaphoreGive(s_njw_mutex);
  204. }
  205. }
  206. void dac_njw1195_set_vol_main(uint8_t pct) {
  207. if (s_vol_main == pct && !s_vol_update_pending) {
  208. return; // Prevent flooding from rapid identical volume commands
  209. }
  210. if (s_vol_debounce_timer == NULL) {
  211. esp_timer_create_args_t args = {
  212. .callback = vol_debounce_timer_cb,
  213. .arg = NULL,
  214. .name = "vol_deb"
  215. };
  216. esp_timer_create(&args, &s_vol_debounce_timer);
  217. }
  218. s_target_vol_main = pct;
  219. s_vol_update_pending = true;
  220. s_target_vol_from_panel = s_in_panel_update;
  221. // 延迟 40ms 执行,如果在 40ms 内有新指令则重置计时器 (过滤快速滑动)
  222. esp_timer_stop(s_vol_debounce_timer);
  223. esp_timer_start_once(s_vol_debounce_timer, 40000);
  224. }
  225. void dac_njw1195_set_vol_bass(uint8_t pct) {
  226. if (s_njw_mutex == NULL) {
  227. s_njw_mutex = xSemaphoreCreateMutex();
  228. }
  229. if (s_njw_mutex != NULL) {
  230. xSemaphoreTake(s_njw_mutex, portMAX_DELAY);
  231. }
  232. s_vol_bass = pct;
  233. if (!s_muted) {
  234. uint8_t code = pct_to_code_bass(s_vol_bass);
  235. njw1195_write_reg(NJW_REG_VOL_1A, code);
  236. ets_delay_us(500); // 连续写入保护延时
  237. njw1195_write_reg(NJW_REG_VOL_1B, code);
  238. ESP_LOGI(TAG, "Bass: %d%% (Mapped to %d%%, Reg: %02X)", s_vol_bass, (s_vol_bass * 90) / 100, code);
  239. }
  240. trigger_save_settings_deferred();
  241. // 通知面板更新低音音量 (0x0D)
  242. if (!s_in_panel_update) {
  243. panel_uart_report(0x0D, s_vol_bass);
  244. }
  245. if (s_njw_mutex != NULL) {
  246. xSemaphoreGive(s_njw_mutex);
  247. }
  248. }
  249. void dac_njw1195_set_prompt_volume(uint8_t pct) {
  250. if (pct > 100) pct = 100;
  251. if (s_prompt_vol == pct) return;
  252. s_prompt_vol = pct;
  253. // Save directly to NVS
  254. nvs_handle_t nvs;
  255. if (nvs_open("njw", NVS_READWRITE, &nvs) == ESP_OK) {
  256. nvs_set_u8(nvs, "prompt_vol", s_prompt_vol);
  257. nvs_commit(nvs);
  258. nvs_close(nvs);
  259. }
  260. ESP_LOGI(TAG, "Prompt Volume set to: %d%%", s_prompt_vol);
  261. }
  262. uint8_t dac_njw1195_get_prompt_volume(void) {
  263. return s_prompt_vol;
  264. }
  265. void dac_njw1195_set_prompt_mode(bool active) {
  266. if (s_njw_mutex != NULL) {
  267. xSemaphoreTake(s_njw_mutex, portMAX_DELAY);
  268. }
  269. if (!s_muted) {
  270. if (active && s_source == NJW_SOURCE_AUX) {
  271. // Force source to BT (I2S) for prompt tone
  272. njw1195_write_reg(NJW_REG_SEL_A, NJW_SEL_BT);
  273. ets_delay_us(500);
  274. njw1195_write_reg(NJW_REG_SEL_B, NJW_SEL_BT);
  275. }
  276. uint8_t code = pct_to_code_main(active ? s_prompt_vol : s_vol_main);
  277. njw1195_write_reg(NJW_REG_VOL_2A, code);
  278. ets_delay_us(500);
  279. njw1195_write_reg(NJW_REG_VOL_2B, code);
  280. if (!active && s_source == NJW_SOURCE_AUX) {
  281. // Restore source to AUX after prompt tone
  282. njw1195_write_reg(NJW_REG_SEL_A, NJW_SEL_AUX);
  283. ets_delay_us(500);
  284. njw1195_write_reg(NJW_REG_SEL_B, NJW_SEL_AUX);
  285. }
  286. ESP_LOGI(TAG, "Prompt Mode %s, Vol: %d%% (Reg: %02X)", active ? "ON" : "OFF", active ? s_prompt_vol : s_vol_main, code);
  287. }
  288. if (s_njw_mutex != NULL) {
  289. xSemaphoreGive(s_njw_mutex);
  290. }
  291. }
  292. void dac_njw1195_set_source(njw_source_t src) {
  293. if (s_njw_mutex == NULL) {
  294. s_njw_mutex = xSemaphoreCreateMutex();
  295. }
  296. if (s_njw_mutex != NULL) {
  297. xSemaphoreTake(s_njw_mutex, portMAX_DELAY);
  298. }
  299. s_source = src;
  300. uint8_t code = (s_source == NJW_SOURCE_BT) ? NJW_SEL_BT : NJW_SEL_AUX;
  301. njw1195_write_reg(NJW_REG_SEL_A, code);
  302. ets_delay_us(500); // 连续写入保护延时
  303. njw1195_write_reg(NJW_REG_SEL_B, code);
  304. ESP_LOGI(TAG, "设置输入源: %s (寄存器代码: %02X)",
  305. (s_source == NJW_SOURCE_BT) ? "蓝牙" : "AUX", code);
  306. trigger_save_settings_deferred();
  307. // 通知面板更新输入源 (ITEM_SOURCE = 0x01, PROTO_VAL_ON = 0x01/BT, PROTO_VAL_OFF = 0x00/AUX)
  308. if (!s_in_panel_update) {
  309. panel_uart_report(0x01, (s_source == NJW_SOURCE_BT) ? 0x01 : 0x00);
  310. }
  311. if (s_njw_mutex != NULL) {
  312. xSemaphoreGive(s_njw_mutex);
  313. }
  314. }
  315. void dac_njw1195_set_mute(bool mute) {
  316. if (s_njw_mutex == NULL) {
  317. s_njw_mutex = xSemaphoreCreateMutex();
  318. }
  319. if (s_njw_mutex != NULL) {
  320. xSemaphoreTake(s_njw_mutex, portMAX_DELAY);
  321. }
  322. s_muted = mute;
  323. if (s_muted) {
  324. njw1195_write_reg(NJW_REG_VOL_1A, NJW_VOL_MUTE);
  325. ets_delay_us(500);
  326. njw1195_write_reg(NJW_REG_VOL_1B, NJW_VOL_MUTE);
  327. ets_delay_us(500);
  328. njw1195_write_reg(NJW_REG_VOL_2A, NJW_VOL_MUTE);
  329. ets_delay_us(500);
  330. njw1195_write_reg(NJW_REG_VOL_2B, NJW_VOL_MUTE);
  331. ESP_LOGI(TAG, "NJW1195AV 静音");
  332. } else {
  333. uint8_t main_code = pct_to_code_main(s_vol_main);
  334. uint8_t bass_code = pct_to_code_bass(s_vol_bass);
  335. // 主音量现已调换到通道 2A/2B
  336. njw1195_write_reg(NJW_REG_VOL_2A, main_code);
  337. ets_delay_us(500);
  338. njw1195_write_reg(NJW_REG_VOL_2B, main_code);
  339. ets_delay_us(500);
  340. // 低音音量现已调换到通道 1A/1B
  341. njw1195_write_reg(NJW_REG_VOL_1A, bass_code);
  342. ets_delay_us(500);
  343. njw1195_write_reg(NJW_REG_VOL_1B, bass_code);
  344. ESP_LOGI(TAG, "NJW Init (Vol: %d%%, Bass: %d%%)", s_vol_main, s_vol_bass);
  345. }
  346. if (s_njw_mutex != NULL) {
  347. xSemaphoreGive(s_njw_mutex);
  348. }
  349. }
  350. bool dac_njw1195_get_mute(void) {
  351. return s_muted;
  352. }
  353. uint8_t dac_njw1195_get_vol_main(void) {
  354. return s_vol_main;
  355. }
  356. uint8_t dac_njw1195_get_vol_bass(void) {
  357. return s_vol_bass;
  358. }
  359. njw_source_t dac_njw1195_get_source(void) {
  360. return s_source;
  361. }
  362. // ============================================================================
  363. // dac_ops_t Implementations
  364. // ============================================================================
  365. static esp_err_t njw1195_init(void *i2c_bus) {
  366. (void)i2c_bus; // NJW1195 uses SPI, not I2C
  367. if (s_njw_mutex == NULL) {
  368. s_njw_mutex = xSemaphoreCreateMutex();
  369. if (s_njw_mutex == NULL) {
  370. ESP_LOGE(TAG, "Failed to create mutex");
  371. return ESP_ERR_NO_MEM;
  372. }
  373. }
  374. // Configure LATCH Pin as GPIO Output
  375. gpio_config_t latch_conf = {
  376. .pin_bit_mask = (1ULL << NJW_LATCH_GPIO),
  377. .mode = GPIO_MODE_OUTPUT,
  378. .pull_up_en = GPIO_PULLUP_DISABLE,
  379. .pull_down_en = GPIO_PULLDOWN_DISABLE,
  380. .intr_type = GPIO_INTR_DISABLE,
  381. };
  382. ESP_ERROR_CHECK(gpio_config(&latch_conf));
  383. gpio_set_level(NJW_LATCH_GPIO, 1); // LATCH 空闲态为高电平
  384. // Initialize SPI Bus
  385. spi_bus_config_t buscfg = {
  386. .mosi_io_num = NJW_MOSI_GPIO,
  387. .miso_io_num = -1,
  388. .sclk_io_num = NJW_CLK_GPIO,
  389. .quadwp_io_num = -1,
  390. .quadhd_io_num = -1,
  391. .max_transfer_sz = 32,
  392. };
  393. // SPI2_HOST is commonly used
  394. esp_err_t err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
  395. if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
  396. ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(err));
  397. return err;
  398. }
  399. // Add device to SPI Bus
  400. // NJW1195A 时序: CLOCK 空闲高电平 (CPOL=1),数据在下降沿被采样 (CPHA=0)
  401. // 对应 SPI Mode 2
  402. spi_device_interface_config_t devcfg = {
  403. .clock_speed_hz = 200000, // 200kHz 时钟速度(保守,确保稳定)
  404. .mode = 2, // SPI Mode 2: CPOL=1, CPHA=0
  405. .spics_io_num = -1, // 软件控制 LATCH(不使用硬件 CS)
  406. .queue_size = 1,
  407. };
  408. err = spi_bus_add_device(SPI2_HOST, &devcfg, &s_spi_handle);
  409. if (err != ESP_OK) {
  410. ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(err));
  411. return err;
  412. }
  413. // Load settings from NVS
  414. load_settings_nvs();
  415. // Initial sequence: Mute first, then restore values
  416. dac_njw1195_set_mute(true);
  417. // Apply source selection
  418. uint8_t src_code = (s_source == NJW_SOURCE_BT) ? NJW_SEL_BT : NJW_SEL_AUX;
  419. njw1195_write_reg(NJW_REG_SEL_A, src_code);
  420. njw1195_write_reg(NJW_REG_SEL_B, src_code);
  421. // Unmute with saved volume levels
  422. dac_njw1195_set_mute(false);
  423. ESP_LOGI(TAG, "NJW1195AV 初始化成功");
  424. // ================= 强制测试代码 =================
  425. /*
  426. ESP_LOGW(TAG, "执行强制暴力写入测试 (所有通道 0dB, Input 1)");
  427. // 主音箱 (1A, 1B)
  428. njw1195_write_reg(0x00, 0x40); // 1A 设为 0dB (0x40)
  429. ets_delay_us(500);
  430. njw1195_write_reg(0x01, 0x40); // 1B 设为 0dB (0x40)
  431. ets_delay_us(500);
  432. // 低音炮 (2A, 2B)
  433. njw1195_write_reg(0x02, 0x40); // 2A 设为 0dB (0x40)
  434. ets_delay_us(500);
  435. njw1195_write_reg(0x03, 0x40); // 2B 设为 0dB (0x40)
  436. ets_delay_us(500);
  437. // 强制选通蓝牙 (Input 1) -> 高中低位合并为 0x24
  438. njw1195_write_reg(0x04, 0x24); // 强制选通左声道 Input 1
  439. ets_delay_us(500);
  440. njw1195_write_reg(0x05, 0x24); // 强制选通右声道 Input 1
  441. */
  442. // ===============================================
  443. return ESP_OK;
  444. }
  445. static esp_err_t njw1195_deinit(void) {
  446. if (s_spi_handle) {
  447. spi_bus_remove_device(s_spi_handle);
  448. s_spi_handle = NULL;
  449. }
  450. if (s_njw_mutex != NULL) {
  451. vSemaphoreDelete(s_njw_mutex);
  452. s_njw_mutex = NULL;
  453. }
  454. return ESP_OK;
  455. }
  456. static void njw1195_set_volume(float volume_db) {
  457. // AirPlay volume is in dB scale (-30.0f to 0.0f)
  458. if (volume_db > VOLUME_MAX_DB) {
  459. volume_db = VOLUME_MAX_DB;
  460. }
  461. if (volume_db < VOLUME_MIN_DB) {
  462. volume_db = VOLUME_MIN_DB;
  463. }
  464. // Map -30..0 dB to 0..100%
  465. float pct_f = ((volume_db - VOLUME_MIN_DB) / (VOLUME_MAX_DB - VOLUME_MIN_DB)) * 100.0f;
  466. uint8_t pct = (uint8_t)(pct_f + 0.5f);
  467. ESP_LOGI(TAG, "AirPlay set volume DB: %.1f -> Pct: %d%%", volume_db, pct);
  468. dac_njw1195_set_vol_main(pct);
  469. }
  470. static void njw1195_set_power_mode(dac_power_mode_t mode) {
  471. switch (mode) {
  472. case DAC_POWER_ON:
  473. dac_njw1195_set_mute(false);
  474. break;
  475. case DAC_POWER_STANDBY:
  476. case DAC_POWER_OFF:
  477. dac_njw1195_set_mute(true);
  478. break;
  479. default:
  480. break;
  481. }
  482. }
  483. static void njw1195_on_i2s_started(void) {
  484. // No clock reconfiguration required for NJW1195 as it's not a codec
  485. dac_njw1195_set_mute(false);
  486. }
  487. static void njw1195_enable_speaker(bool enable) {
  488. dac_njw1195_set_mute(!enable);
  489. }
  490. static void njw1195_enable_line_out(bool enable) {
  491. (void)enable;
  492. // Main output is Out1, already controlled by speaker enable
  493. }
  494. const dac_ops_t dac_njw1195_ops = {
  495. .init = njw1195_init,
  496. .deinit = njw1195_deinit,
  497. .set_volume = njw1195_set_volume,
  498. .set_power_mode = njw1195_set_power_mode,
  499. .on_i2s_started = njw1195_on_i2s_started,
  500. .enable_speaker = njw1195_enable_speaker,
  501. .enable_line_out = njw1195_enable_line_out,
  502. };