From bf31740faa03420e5603c4d5720a507d56cabd3f Mon Sep 17 00:00:00 2001 From: muzkr Date: Wed, 29 Oct 2025 03:23:03 +0800 Subject: [PATCH] Voice driver --- App/CMakeLists.txt | 2 +- App/audio.c | 141 +++++++++++++++++++--------- App/board.c | 4 +- App/driver/dac.c | 50 ---------- App/driver/voice.c | 169 ++++++++++++++++++++++++++++++++++ App/driver/{dac.h => voice.h} | 30 +++++- CMakePresets.json | 2 +- 7 files changed, 296 insertions(+), 102 deletions(-) delete mode 100644 App/driver/dac.c create mode 100644 App/driver/voice.c rename App/driver/{dac.h => voice.h} (51%) diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index c6212f01..b216aee0 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -106,7 +106,7 @@ enable_feature(ENABLE_AIRCOPY ) enable_feature(ENABLE_NOAA) enable_feature(ENABLE_VOICE - driver/dac.c + driver/voice.c ) enable_feature(ENABLE_VOX) enable_feature(ENABLE_ALARM) diff --git a/App/audio.c b/App/audio.c index adfadb7e..9a286247 100644 --- a/App/audio.c +++ b/App/audio.c @@ -1,4 +1,5 @@ -/* Copyright 2023 Dual Tachyon +/* Copyright 2025 muzkr https://github.com/muzkr + * Copyright 2023 Dual Tachyon * https://github.com/DualTachyon * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +26,8 @@ #include "driver/gpio.h" #include "driver/system.h" #include "driver/systick.h" +#include "driver/voice.h" +#include "driver/py25q16.h" #include "functions.h" #include "misc.h" #include "settings.h" @@ -198,31 +201,10 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep) #ifdef ENABLE_VOICE -static const uint8_t VoiceClipLengthChinese[58] = -{ - 0x32, 0x32, 0x32, 0x37, 0x37, 0x32, 0x32, 0x32, - 0x32, 0x37, 0x37, 0x32, 0x64, 0x64, 0x64, 0x64, - 0x64, 0x69, 0x64, 0x69, 0x5A, 0x5F, 0x5F, 0x64, - 0x64, 0x69, 0x64, 0x64, 0x69, 0x69, 0x69, 0x64, - 0x64, 0x6E, 0x69, 0x5F, 0x64, 0x64, 0x64, 0x69, - 0x69, 0x69, 0x64, 0x69, 0x64, 0x64, 0x55, 0x5F, - 0x5A, 0x4B, 0x4B, 0x46, 0x46, 0x69, 0x64, 0x6E, - 0x5A, 0x64, -}; - -static const uint8_t VoiceClipLengthEnglish[76] = -{ - 0x50, 0x32, 0x2D, 0x2D, 0x2D, 0x37, 0x37, 0x37, - 0x32, 0x32, 0x3C, 0x37, 0x46, 0x46, 0x4B, 0x82, - 0x82, 0x6E, 0x82, 0x46, 0x96, 0x64, 0x46, 0x6E, - 0x78, 0x6E, 0x87, 0x64, 0x96, 0x96, 0x46, 0x9B, - 0x91, 0x82, 0x82, 0x73, 0x78, 0x64, 0x82, 0x6E, - 0x78, 0x82, 0x87, 0x6E, 0x55, 0x78, 0x64, 0x69, - 0x9B, 0x5A, 0x50, 0x3C, 0x32, 0x55, 0x64, 0x64, - 0x50, 0x46, 0x46, 0x46, 0x4B, 0x4B, 0x50, 0x50, - 0x55, 0x4B, 0x4B, 0x32, 0x32, 0x32, 0x32, 0x37, - 0x41, 0x32, 0x3C, 0x37, -}; +uint16_t gVoiceBuf[VOICE_BUF_CAP][VOICE_BUF_LEN]; +uint8_t gVoiceBufReadIndex = 0; +uint8_t gVoiceBufWriteIndex = 0; +uint8_t gVoiceBufLen = 0; VOICE_ID_t gVoiceID[8]; uint8_t gVoiceReadIndex; @@ -231,29 +213,100 @@ volatile uint16_t gCountdownToPlayNextVoice_10ms; volatile bool gFlagPlayQueuedVoice; VOICE_ID_t gAnotherVoiceID = VOICE_ID_INVALID; - -static void AUDIO_PlayVoice(uint8_t VoiceID) +static const uint16_t VOICE_SAMPLES[256] = { - unsigned int i; + 0x06a8, 0x06b8, 0x0688, 0x0698, 0x06e8, 0x06f8, 0x06c8, 0x06d8, // + 0x0628, 0x0638, 0x0608, 0x0618, 0x0668, 0x0678, 0x0648, 0x0658, // + 0x0754, 0x075c, 0x0744, 0x074c, 0x0774, 0x077c, 0x0764, 0x076c, // + 0x0714, 0x071c, 0x0704, 0x070c, 0x0734, 0x073c, 0x0724, 0x072c, // + 0x02a0, 0x02e0, 0x0220, 0x0260, 0x03a0, 0x03e0, 0x0320, 0x0360, // + 0x00a0, 0x00e0, 0x0020, 0x0060, 0x01a0, 0x01e0, 0x0120, 0x0160, // + 0x0550, 0x0570, 0x0510, 0x0530, 0x05d0, 0x05f0, 0x0590, 0x05b0, // + 0x0450, 0x0470, 0x0410, 0x0430, 0x04d0, 0x04f0, 0x0490, 0x04b0, // + 0x07ea, 0x07eb, 0x07e8, 0x07e9, 0x07ee, 0x07ef, 0x07ec, 0x07ed, // + 0x07e2, 0x07e3, 0x07e0, 0x07e1, 0x07e6, 0x07e7, 0x07e4, 0x07e5, // + 0x07fa, 0x07fb, 0x07f8, 0x07f9, 0x07fe, 0x07ff, 0x07fc, 0x07fd, // + 0x07f2, 0x07f3, 0x07f0, 0x07f1, 0x07f6, 0x07f7, 0x07f4, 0x07f5, // + 0x07aa, 0x07ae, 0x07a2, 0x07a6, 0x07ba, 0x07be, 0x07b2, 0x07b6, // + 0x078a, 0x078e, 0x0782, 0x0786, 0x079a, 0x079e, 0x0792, 0x0796, // + 0x07d5, 0x07d7, 0x07d1, 0x07d3, 0x07dd, 0x07df, 0x07d9, 0x07db, // + 0x07c5, 0x07c7, 0x07c1, 0x07c3, 0x07cd, 0x07cf, 0x07c9, 0x07cb, // + 0x0958, 0x0948, 0x0978, 0x0968, 0x0918, 0x0908, 0x0938, 0x0928, // + 0x09d8, 0x09c8, 0x09f8, 0x09e8, 0x0998, 0x0988, 0x09b8, 0x09a8, // + 0x08ac, 0x08a4, 0x08bc, 0x08b4, 0x088c, 0x0884, 0x089c, 0x0894, // + 0x08ec, 0x08e4, 0x08fc, 0x08f4, 0x08cc, 0x08c4, 0x08dc, 0x08d4, // + 0x0d60, 0x0d20, 0x0de0, 0x0da0, 0x0c60, 0x0c20, 0x0ce0, 0x0ca0, // + 0x0f60, 0x0f20, 0x0fe0, 0x0fa0, 0x0e60, 0x0e20, 0x0ee0, 0x0ea0, // + 0x0ab0, 0x0a90, 0x0af0, 0x0ad0, 0x0a30, 0x0a10, 0x0a70, 0x0a50, // + 0x0bb0, 0x0b90, 0x0bf0, 0x0bd0, 0x0b30, 0x0b10, 0x0b70, 0x0b50, // + 0x0815, 0x0814, 0x0817, 0x0816, 0x0811, 0x0810, 0x0813, 0x0812, // + 0x081d, 0x081c, 0x081f, 0x081e, 0x0819, 0x0818, 0x081b, 0x081a, // + 0x0805, 0x0804, 0x0807, 0x0806, 0x0801, 0x0800, 0x0803, 0x0802, // + 0x080d, 0x080c, 0x080f, 0x080e, 0x0809, 0x0808, 0x080b, 0x080a, // + 0x0856, 0x0852, 0x085e, 0x085a, 0x0846, 0x0842, 0x084e, 0x084a, // + 0x0876, 0x0872, 0x087e, 0x087a, 0x0866, 0x0862, 0x086e, 0x086a, // + 0x082b, 0x0829, 0x082f, 0x082d, 0x0823, 0x0821, 0x0827, 0x0825, // + 0x083b, 0x0839, 0x083f, 0x083d, 0x0833, 0x0831, 0x0837, 0x0835 // +}; - GPIO_SetBit(&GPIOA->DATA, GPIOA_PIN_VOICE_0); - SYSTEM_DelayMs(20); - GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_VOICE_0); +static struct +{ + uint32_t Addr; + uint32_t Size; +} VoiceClipState = {0}; - for (i = 0; i < 8; i++) +static bool LoadVoiceClip(uint8_t VoiceID) +{ + if (VoiceID >= VOICE_ID_END) { - if ((VoiceID & 0x80U) == 0) - GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_VOICE_1); - else - GPIO_SetBit(&GPIOA->DATA, GPIOA_PIN_VOICE_1); - - SYSTICK_DelayUs(1000); - GPIO_SetBit(&GPIOA->DATA, GPIOA_PIN_VOICE_0); - SYSTICK_DelayUs(1200); - GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_VOICE_0); - VoiceID <<= 1; - SYSTICK_DelayUs(200); + return false; } + + uint32_t Addr = gEeprom.VOICE_PROMPT == VOICE_PROMPT_CHINESE ? 0x14c000 : 0x14c800; + struct + { + uint32_t Offset; + uint32_t Size; + } Info; + PY25Q16_ReadBuffer(Addr + 8 * VoiceID, &Info, 8); + + if (Info.Offset > 0x0b0000 || Info.Size > 0x019000) + { + return false; + } + + VoiceClipState.Addr = 0x14d000 + Info.Offset; + VoiceClipState.Size = Info.Size; + return true; +} + +static inline uint32_t CalcDelay(uint32_t Size) +{ + return 2 * Size; // in ms!! +} + +static void LoadVoiceSamples() { + if (0 == VoiceClipState.Addr || 0 == VoiceClipState.Size) + { + return; + } + if (gVoiceBufLen >= VOICE_BUF_CAP) + { + return; + } + + extern uint8_t **gFrameBuffer; + uint8_t *Buf = (uint8_t *)gFrameBuffer; + PY25Q16_ReadBuffer(VoiceClipState.Addr, Buf, VOICE_BUF_LEN); + VoiceClipState.Addr += VOICE_BUF_LEN; + VoiceClipState.Size -= VOICE_BUF_LEN; + + for (uint32_t i = 0; i < VOICE_BUF_LEN; i++) + { + gVoiceBuf[gVoiceBufWriteIndex][i] = VOICE_SAMPLES[Buf[i]]; + } + VOCIE_BUF_ForwardWriteIndex(); + gVoiceBufLen++; } void AUDIO_PlaySingleVoice(bool bFlag) diff --git a/App/board.c b/App/board.c index 2a771716..b1b94efd 100644 --- a/App/board.c +++ b/App/board.c @@ -25,7 +25,7 @@ #include "py32f071_ll_gpio.h" #include "py32f071_ll_rcc.h" #include "py32f071_ll_adc.h" -#include "driver/dac.h" +#include "driver/voice.h" #include "driver/backlight.h" #ifdef ENABLE_FMRADIO #include "driver/bk1080.h" @@ -180,7 +180,7 @@ void BOARD_Init(void) BACKLIGHT_InitHardware(); BOARD_ADC_Init(); #ifdef ENABLE_VOICE - DAC_Init(); + VOICE_Init(); #endif PY25Q16_Init(); ST7565_Init(); diff --git a/App/driver/dac.c b/App/driver/dac.c deleted file mode 100644 index 8671cda2..00000000 --- a/App/driver/dac.c +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright 2025 muzkr https://github.com/muzkr - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "driver/dac.h" -#include "driver/systick.h" -#include "py32f071_ll_bus.h" -#include "py32f071_ll_gpio.h" -#include "py32f071_ll_dac.h" -#include "py32f071_ll_tim.h" - -#define TIMx TIM6 -#define DAC_CHANNEL LL_DAC_CHANNEL_1 - -static inline void TIM_Init() -{ - LL_TIM_SetPrescaler(TIMx, 3); - LL_TIM_SetAutoReload(TIMx, 1499); - LL_TIM_SetTriggerOutput(TIMx, LL_TIM_TRGO_UPDATE); -} - -void DAC_Init() -{ - // Channel 1: PA4 - LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); - LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_4, LL_GPIO_MODE_ANALOG); - - LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_DAC1); - LL_DAC_SetTriggerSource(DAC1, DAC_CHANNEL, LL_DAC_TRIG_EXT_TIM6_TRGO); - LL_DAC_SetOutputBuffer(DAC1, DAC_CHANNEL, LL_DAC_OUTPUT_BUFFER_ENABLE); - LL_DAC_EnableDMAReq(DAC1, DAC_CHANNEL); - LL_DAC_Enable(DAC1, DAC_CHANNEL); - - SYSTICK_DelayUs(15); - - TIM_Init(); - - LL_DAC_EnableTrigger(DAC1, DAC_CHANNEL); -} diff --git a/App/driver/voice.c b/App/driver/voice.c new file mode 100644 index 00000000..92fa0636 --- /dev/null +++ b/App/driver/voice.c @@ -0,0 +1,169 @@ +/* Copyright 2025 muzkr https://github.com/muzkr + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "driver/voice.h" +#include "driver/systick.h" +#include "py32f071_ll_bus.h" +#include "py32f071_ll_gpio.h" +#include "py32f071_ll_dac.h" +#include "py32f071_ll_tim.h" +#include "py32f071_ll_dma.h" +#include "py32f071_ll_system.h" +#include + +#define TIMx TIM6 +#define DAC_CHANNEL LL_DAC_CHANNEL_1 +#define DMA_CHANNEL LL_DMA_CHANNEL_3 + +#define VOICE_BUF_SIZE (sizeof(uint16_t) * VOICE_BUF_LEN) + +static uint16_t DAC_Buf[VOICE_BUF_LEN * 2]; + +static inline void DMA_Init() +{ + LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1); + LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG); + + LL_SYSCFG_SetDMARemap(DMA1, DMA_CHANNEL, LL_SYSCFG_DMA_MAP_DAC1); + + LL_DMA_InitTypeDef InitStruct; + // LL_DMA_StructInit( &InitStruct) ; + InitStruct.PeriphOrM2MSrcAddress = (uint32_t)DAC_Buf; + InitStruct.MemoryOrM2MDstAddress = LL_DAC_DMA_GetRegAddr(DAC1, DAC_CHANNEL, LL_DAC_DMA_REG_DATA_12BITS_RIGHT_ALIGNED); + InitStruct.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + InitStruct.Mode = LL_DMA_MODE_CIRCULAR; + InitStruct.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + InitStruct.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + InitStruct.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; + InitStruct.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; + InitStruct.NbData = sizeof(DAC_Buf) / sizeof(uint16_t); + InitStruct.Priority = LL_DMA_PRIORITY_HIGH; + LL_DMA_Init(DMA1, DMA_CHANNEL, &InitStruct); + + LL_DMA_EnableIT_HT(DMA1, DMA_CHANNEL); + LL_DMA_EnableIT_TC(DMA1, DMA_CHANNEL); + LL_DMA_EnableChannel(DMA1, DMA_CHANNEL); + + NVIC_SetPriority(DMA1_Channel2_3_IRQn, 3); + NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); +} + +static inline void TIM_Init() +{ + // Update freq = 48 MHz / 4 / 1500 == 8 KHz + LL_TIM_SetPrescaler(TIMx, 3); + LL_TIM_SetAutoReload(TIMx, 1499); + LL_TIM_SetTriggerOutput(TIMx, LL_TIM_TRGO_UPDATE); +} + +void VOICE_Init() +{ + DMA_Init(); + + // Channel 1: PA4 + LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); + LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_4, LL_GPIO_MODE_ANALOG); + + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_DAC1); + LL_DAC_SetTriggerSource(DAC1, DAC_CHANNEL, LL_DAC_TRIG_EXT_TIM6_TRGO); + LL_DAC_SetOutputBuffer(DAC1, DAC_CHANNEL, LL_DAC_OUTPUT_BUFFER_ENABLE); + LL_DAC_EnableDMAReq(DAC1, DAC_CHANNEL); + LL_DAC_Enable(DAC1, DAC_CHANNEL); + + SYSTICK_DelayUs(15); + + TIM_Init(); + + LL_DAC_EnableTrigger(DAC1, DAC_CHANNEL); +} + +void VOICE_Start() +{ + LL_DAC_Enable(DAC1, DAC_CHANNEL); + LL_TIM_DisableCounter(TIMx); + + if (gVoiceBufLen > 0) + { + memcpy(DAC_Buf, gVoiceBuf[gVoiceBufReadIndex], VOICE_BUF_SIZE); + VOCIE_BUF_ForwardReadIndex(); + gVoiceBufLen--; + } + else + { + memset(DAC_Buf, 0, VOICE_BUF_SIZE); + } + if (gVoiceBufLen > 0) + { + memcpy(DAC_Buf + VOICE_BUF_SIZE, // + gVoiceBuf[gVoiceBufReadIndex], // + VOICE_BUF_SIZE // + ); + VOCIE_BUF_ForwardReadIndex(); + gVoiceBufLen--; + } + else + { + memset(DAC_Buf + VOICE_BUF_SIZE, 0, VOICE_BUF_SIZE); + } + + LL_DMA_ConfigAddresses(DMA1, DMA_CHANNEL, DAC_Buf, // + LL_DAC_DMA_GetRegAddr(DAC1, DAC_CHANNEL, LL_DAC_DMA_REG_DATA_12BITS_RIGHT_ALIGNED), // + LL_DMA_DIRECTION_MEMORY_TO_PERIPH // + ); + LL_DMA_EnableChannel(DMA1, DMA_CHANNEL); + LL_TIM_EnableCounter(TIMx); +} + +void VOICE_Stop() +{ + LL_TIM_DisableCounter(TIMx); + LL_DMA_DisableChannel(DMA1, DMA_CHANNEL); + LL_DAC_Disable(DAC1, DAC_CHANNEL); +} + +void DMA1_Channel2_3_IRQHandler() +{ + if (LL_DMA_IsActiveFlag_HT3(DMA1)) + { + LL_DMA_ClearFlag_HT3(DMA1); + if (gVoiceBufLen > 0) + { + memcpy(DAC_Buf, gVoiceBuf[gVoiceBufReadIndex], VOICE_BUF_SIZE); + VOCIE_BUF_ForwardReadIndex(); + gVoiceBufLen--; + } + else + { + memset(DAC_Buf, 0, VOICE_BUF_SIZE); + } + } + if (LL_DMA_IsActiveFlag_TC3(DMA1)) + { + LL_DMA_ClearFlag_TC3(DMA1); + if (gVoiceBufLen > 0) + { + memcpy(DAC_Buf + VOICE_BUF_SIZE, // + gVoiceBuf[gVoiceBufReadIndex], // + VOICE_BUF_SIZE // + ); + VOCIE_BUF_ForwardReadIndex(); + gVoiceBufLen--; + } + else + { + memset(DAC_Buf + VOICE_BUF_SIZE, 0, VOICE_BUF_SIZE); + } + } +} diff --git a/App/driver/dac.h b/App/driver/voice.h similarity index 51% rename from App/driver/dac.h rename to App/driver/voice.h index b2438479..8c49aa73 100644 --- a/App/driver/dac.h +++ b/App/driver/voice.h @@ -13,9 +13,31 @@ * limitations under the License. */ -#ifndef DRIVER_DAC_H -#define DRIVER_DAC_H +#ifndef DRIVER_VOICE_H +#define DRIVER_VOICE_H -void DAC_Init(); +#include -#endif // DRIVER_DAC_H +#define VOICE_BUF_CAP 4 +#define VOICE_BUF_LEN 160 + +extern uint16_t gVoiceBuf[VOICE_BUF_CAP][VOICE_BUF_LEN]; +extern uint8_t gVoiceBufReadIndex; +extern uint8_t gVoiceBufWriteIndex; +extern uint8_t gVoiceBufLen; + +static inline void VOCIE_BUF_ForwardReadIndex() +{ + gVoiceBufReadIndex = (1 + gVoiceBufReadIndex) % VOICE_BUF_CAP; +} + +static inline void VOCIE_BUF_ForwardWriteIndex() +{ + gVoiceBufWriteIndex = (1 + gVoiceBufWriteIndex) % VOICE_BUF_CAP; +} + +void VOICE_Init(); +void VOICE_Start(); +void VOICE_Stop(); + +#endif // DRIVER_VOICE_H diff --git a/CMakePresets.json b/CMakePresets.json index c836396c..ac17c871 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -76,7 +76,7 @@ "name": "Custom", "inherits": "Broadcast", "cacheVariables": { - "ENABLE_VOICE": false, + "ENABLE_VOICE": true, "ENABLE_SWD": true, "ENABLE_AIRCOPY": false, "EDITION_STRING": "Custom",