From 13cc7c6d0fba172f7bb498d236822972db3136af Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Thu, 25 Jun 2026 20:10:42 +0300 Subject: [PATCH] =?UTF-8?q?#=20firmware=5Ftest:=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=20=D1=81=20mqs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bsp/CMakeLists.txt | 1 + bsp/can/CMakeLists.txt | 3 +- bsp/can/src/can.c | 1 + bsp/generated/TFT_Board.mex | 17 +- bsp/generated/board/clock_config.c | 58 ++++-- bsp/generated/board/clock_config.h | 9 +- bsp/generated/clock_config.c | 58 ++++-- bsp/generated/clock_config.h | 9 +- bsp/mqs/CMakeLists.txt | 18 ++ bsp/mqs/README.md | 0 bsp/mqs/include/bsp/mqs.h | 181 ++++++++++++++++++ bsp/mqs/src/mqs.c | 286 +++++++++++++++++++++++++++++ bsp/mqs/src/mqs_amp.c | 141 ++++++++++++++ firmware/test/CMakeLists.txt | 3 + firmware/test/src/main.c | 205 ++++++++++++++++++++- firmware/test/src/test_runner.c | 3 +- firmware/test/src/tests/test_mqs.c | 223 ++++++++++++++++++++++ sdk/CMakeLists.txt | 11 +- 18 files changed, 1186 insertions(+), 41 deletions(-) create mode 100644 bsp/mqs/CMakeLists.txt create mode 100644 bsp/mqs/README.md create mode 100644 bsp/mqs/include/bsp/mqs.h create mode 100644 bsp/mqs/src/mqs.c create mode 100644 bsp/mqs/src/mqs_amp.c create mode 100644 firmware/test/src/tests/test_mqs.c diff --git a/bsp/CMakeLists.txt b/bsp/CMakeLists.txt index c6b1fb0..caef660 100644 --- a/bsp/CMakeLists.txt +++ b/bsp/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(usb_cdc) add_subdirectory(sdram) add_subdirectory(qspi_flash) add_subdirectory(sd) +add_subdirectory(mqs) # Подавляем предупреждения при компиляции собственных .c файлов библиотеки target_compile_options(bsp_board PRIVATE -w) diff --git a/bsp/can/CMakeLists.txt b/bsp/can/CMakeLists.txt index 84606c4..30775c9 100644 --- a/bsp/can/CMakeLists.txt +++ b/bsp/can/CMakeLists.txt @@ -15,4 +15,5 @@ target_include_directories( target_link_libraries( bsp_can PUBLIC bsp_status - PRIVATE bsp_tick utils sdk_flexcan $<$:fff>) + PRIVATE bsp_tick utils sdk_clock sdk_flexcan + $<$:fff>) diff --git a/bsp/can/src/can.c b/bsp/can/src/can.c index 59c2aa9..57fb3e6 100644 --- a/bsp/can/src/can.c +++ b/bsp/can/src/can.c @@ -22,6 +22,7 @@ #include "bsp/tick.h" #include "clock_config.h" +#include "fsl_clock.h" #include "fsl_flexcan.h" #include "ring_buffer/ring_buffer.h" diff --git a/bsp/generated/TFT_Board.mex b/bsp/generated/TFT_Board.mex index fd8c6b7..f698e98 100644 --- a/bsp/generated/TFT_Board.mex +++ b/bsp/generated/TFT_Board.mex @@ -490,7 +490,7 @@ - + @@ -500,8 +500,8 @@ - - + + @@ -519,6 +519,9 @@ + + + @@ -540,9 +543,17 @@ + + + + + + + + true diff --git a/bsp/generated/board/clock_config.c b/bsp/generated/board/clock_config.c index fb87974..f554d10 100644 --- a/bsp/generated/board/clock_config.c +++ b/bsp/generated/board/clock_config.c @@ -74,7 +74,7 @@ outputs: - {id: LPI2C_CLK_ROOT.outFreq, value: 60 MHz} - {id: LPSPI_CLK_ROOT.outFreq, value: 105.6 MHz} - {id: LVDS1_CLK.outFreq, value: 1.2 GHz} -- {id: MQS_MCLK.outFreq, value: 1080/17 MHz} +- {id: MQS_MCLK.outFreq, value: 2.8224 MHz} - {id: PERCLK_CLK_ROOT.outFreq, value: 75 MHz} - {id: PLL7_MAIN_CLK.outFreq, value: 24 MHz} - {id: SAI1_CLK_ROOT.outFreq, value: 1080/17 MHz} @@ -84,8 +84,8 @@ outputs: - {id: SAI2_CLK_ROOT.outFreq, value: 1080/17 MHz} - {id: SAI2_MCLK1.outFreq, value: 1080/17 MHz} - {id: SAI2_MCLK3.outFreq, value: 30 MHz} -- {id: SAI3_CLK_ROOT.outFreq, value: 1080/17 MHz} -- {id: SAI3_MCLK1.outFreq, value: 1080/17 MHz} +- {id: SAI3_CLK_ROOT.outFreq, value: 11.2896 MHz} +- {id: SAI3_MCLK1.outFreq, value: 11.2896 MHz} - {id: SAI3_MCLK3.outFreq, value: 30 MHz} - {id: SEMC_CLK_ROOT.outFreq, value: 120 MHz} - {id: SPDIF0_CLK_ROOT.outFreq, value: 30 MHz} @@ -102,6 +102,9 @@ settings: - {id: CCM.LPSPI_PODF.scale, value: '5'} - {id: CCM.PERCLK_PODF.scale, value: '2', locked: true} - {id: CCM.PERIPH_CLK2_SEL.sel, value: XTALOSC24M.OSC_CLK} +- {id: CCM.SAI3_CLK_PODF.scale, value: '8', locked: true} +- {id: CCM.SAI3_CLK_PRED.scale, value: '8', locked: true} +- {id: CCM.SAI3_CLK_SEL.sel, value: CCM_ANALOG.PLL4_MAIN_CLK} - {id: CCM.SEMC_PODF.scale, value: '5', locked: true} - {id: CCM.USDHC1_CLK_SEL.sel, value: CCM_ANALOG.PLL2_PFD0_CLK} - {id: CCM.USDHC1_PODF.scale, value: '2', locked: true} @@ -123,9 +126,17 @@ settings: - {id: CCM_ANALOG.PLL3_PFD1_BYPASS.sel, value: CCM_ANALOG.PLL3_PFD1} - {id: CCM_ANALOG.PLL3_PFD2_BYPASS.sel, value: CCM_ANALOG.PLL3_PFD2} - {id: CCM_ANALOG.PLL3_PFD3_BYPASS.sel, value: CCM_ANALOG.PLL3_PFD3} +- {id: CCM_ANALOG.PLL4.denom, value: '625', locked: true} +- {id: CCM_ANALOG.PLL4.div, value: '30', locked: true} +- {id: CCM_ANALOG.PLL4.num, value: '66', locked: true} +- {id: CCM_ANALOG.PLL4_BYPASS.sel, value: CCM_ANALOG.PLL4_POST_DIV} +- {id: CCM_ANALOG.PLL4_POST_DIV.scale, value: '1', locked: true} +- {id: CCM_ANALOG_PLL_AUDIO_POWERDOWN_CFG, value: 'No'} - {id: CCM_ANALOG_PLL_USB1_EN_USB_CLKS_CFG, value: Enabled} - {id: CCM_ANALOG_PLL_USB1_EN_USB_CLKS_OUT_CFG, value: Enabled} - {id: CCM_ANALOG_PLL_USB1_POWER_CFG, value: 'Yes'} +- {id: IOMUXC_GPR.MQS_CLK_DIV.scale, value: '4', locked: true} +- {id: IOMUXC_GPR_GPR2_MQS_OVERSAMPLE, value: OverSampleRate64} sources: - {id: XTALOSC24M.RTC_OSC.outFreq, value: 32.768 kHz, enabled: true} * BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS **********/ @@ -150,6 +161,14 @@ const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN = .loopDivider = 0, /* PLL loop divider, Fout = Fin * 20 */ .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ }; +const clock_audio_pll_config_t audioPllConfig_BOARD_BootClockRUN = + { + .loopDivider = 30, /* PLL loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .postDivider = 1, /* Divider after PLL */ + .numerator = 66, /* 30 bit numerator of fractional loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .denominator = 625, /* 30 bit denominator of fractional loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ + }; const clock_enet_pll_config_t enetPllConfig_BOARD_BootClockRUN = { .enableClkOutput = true, /* Enable the PLL providing the ENET 125MHz reference clock */ @@ -285,11 +304,11 @@ void BOARD_BootClockRUN(void) /* Disable SAI3 clock gate. */ CLOCK_DisableClock(kCLOCK_Sai3); /* Set SAI3_CLK_PRED. */ - CLOCK_SetDiv(kCLOCK_Sai3PreDiv, 3); + CLOCK_SetDiv(kCLOCK_Sai3PreDiv, 7); /* Set SAI3_CLK_PODF. */ - CLOCK_SetDiv(kCLOCK_Sai3Div, 1); + CLOCK_SetDiv(kCLOCK_Sai3Div, 7); /* Set Sai3 clock source. */ - CLOCK_SetMux(kCLOCK_Sai3Mux, 0); + CLOCK_SetMux(kCLOCK_Sai3Mux, 2); /* Disable Lpi2c clock gate. */ CLOCK_DisableClock(kCLOCK_Lpi2c1); CLOCK_DisableClock(kCLOCK_Lpi2c2); @@ -389,15 +408,26 @@ void BOARD_BootClockRUN(void) /* Init Usb1 pfd3. */ CLOCK_InitUsb1Pfd(kCLOCK_Pfd3, 19); #endif - /* DeInit Audio PLL. */ - CLOCK_DeinitAudioPll(); - /* Bypass Audio PLL. */ - CLOCK_SetPllBypass(CCM_ANALOG, kCLOCK_PllAudio, 1); - /* Set divider for Audio PLL. */ + /* Init Audio PLL. */ + uint32_t pllAudio; + /* Disable Audio PLL output before initial Audio PLL. */ + CCM_ANALOG->PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_ENABLE_MASK; + /* Bypass PLL first */ + CCM_ANALOG->PLL_AUDIO = (CCM_ANALOG->PLL_AUDIO & (~CCM_ANALOG_PLL_AUDIO_BYPASS_CLK_SRC_MASK)) | + CCM_ANALOG_PLL_AUDIO_BYPASS_MASK | CCM_ANALOG_PLL_AUDIO_BYPASS_CLK_SRC(0); + CCM_ANALOG->PLL_AUDIO_NUM = CCM_ANALOG_PLL_AUDIO_NUM_A(66); + CCM_ANALOG->PLL_AUDIO_DENOM = CCM_ANALOG_PLL_AUDIO_DENOM_B(625); + pllAudio = (CCM_ANALOG->PLL_AUDIO & (~(CCM_ANALOG_PLL_AUDIO_DIV_SELECT_MASK | CCM_ANALOG_PLL_AUDIO_POWERDOWN_MASK))) | + CCM_ANALOG_PLL_AUDIO_ENABLE_MASK | CCM_ANALOG_PLL_AUDIO_DIV_SELECT(30); + pllAudio |= CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2); CCM_ANALOG->MISC2 &= ~CCM_ANALOG_MISC2_AUDIO_DIV_LSB_MASK; CCM_ANALOG->MISC2 &= ~CCM_ANALOG_MISC2_AUDIO_DIV_MSB_MASK; - /* Enable Audio PLL output. */ - CCM_ANALOG->PLL_AUDIO |= CCM_ANALOG_PLL_AUDIO_ENABLE_MASK; + CCM_ANALOG->PLL_AUDIO = pllAudio; + while ((CCM_ANALOG->PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK_MASK) == 0) + { + } + /* Disable bypass for Audio PLL. */ + CLOCK_SetPllBypass(CCM_ANALOG, kCLOCK_PllAudio, 0); /* DeInit Video PLL. */ CLOCK_DeinitVideoPll(); /* Bypass Video PLL. */ @@ -451,7 +481,7 @@ void BOARD_BootClockRUN(void) /* Set SAI3 MCLK3 clock source. */ IOMUXC_SetSaiMClkClockSource(IOMUXC_GPR, kIOMUXC_GPR_SAI3MClk3Sel, 0); /* Set MQS configuration. */ - IOMUXC_MQSConfig(IOMUXC_GPR,kIOMUXC_MqsPwmOverSampleRate32, 0); + IOMUXC_MQSConfig(IOMUXC_GPR,kIOMUXC_MqsPwmOverSampleRate64, 3); /* Set ENET Ref clock source. */ #if defined(IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK) IOMUXC_GPR->GPR1 &= ~IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK; diff --git a/bsp/generated/board/clock_config.h b/bsp/generated/board/clock_config.h index 0ce515d..e71870e 100644 --- a/bsp/generated/board/clock_config.h +++ b/bsp/generated/board/clock_config.h @@ -65,7 +65,7 @@ void BOARD_InitBootClocks(void); #define BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT 60000000UL /* Clock consumers of LPI2C_CLK_ROOT output : LPI2C1, LPI2C2, LPI2C3, LPI2C4 */ #define BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT 105600000UL /* Clock consumers of LPSPI_CLK_ROOT output : LPSPI1, LPSPI2, LPSPI3, LPSPI4 */ #define BOARD_BOOTCLOCKRUN_LVDS1_CLK 1200000000UL /* Clock consumers of LVDS1_CLK output : N/A */ -#define BOARD_BOOTCLOCKRUN_MQS_MCLK 63529411UL /* Clock consumers of MQS_MCLK output : N/A */ +#define BOARD_BOOTCLOCKRUN_MQS_MCLK 2822400UL /* Clock consumers of MQS_MCLK output : N/A */ #define BOARD_BOOTCLOCKRUN_PERCLK_CLK_ROOT 75000000UL /* Clock consumers of PERCLK_CLK_ROOT output : GPT1, GPT2, PIT */ #define BOARD_BOOTCLOCKRUN_PLL7_MAIN_CLK 24000000UL /* Clock consumers of PLL7_MAIN_CLK output : N/A */ #define BOARD_BOOTCLOCKRUN_SAI1_CLK_ROOT 63529411UL /* Clock consumers of SAI1_CLK_ROOT output : N/A */ @@ -76,8 +76,8 @@ void BOARD_InitBootClocks(void); #define BOARD_BOOTCLOCKRUN_SAI2_MCLK1 63529411UL /* Clock consumers of SAI2_MCLK1 output : SAI2 */ #define BOARD_BOOTCLOCKRUN_SAI2_MCLK2 0UL /* Clock consumers of SAI2_MCLK2 output : SAI2 */ #define BOARD_BOOTCLOCKRUN_SAI2_MCLK3 30000000UL /* Clock consumers of SAI2_MCLK3 output : SAI2 */ -#define BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT 63529411UL /* Clock consumers of SAI3_CLK_ROOT output : N/A */ -#define BOARD_BOOTCLOCKRUN_SAI3_MCLK1 63529411UL /* Clock consumers of SAI3_MCLK1 output : SAI3 */ +#define BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT 11289600UL /* Clock consumers of SAI3_CLK_ROOT output : N/A */ +#define BOARD_BOOTCLOCKRUN_SAI3_MCLK1 11289600UL /* Clock consumers of SAI3_MCLK1 output : SAI3 */ #define BOARD_BOOTCLOCKRUN_SAI3_MCLK2 0UL /* Clock consumers of SAI3_MCLK2 output : SAI3 */ #define BOARD_BOOTCLOCKRUN_SAI3_MCLK3 30000000UL /* Clock consumers of SAI3_MCLK3 output : SAI3 */ #define BOARD_BOOTCLOCKRUN_SEMC_CLK_ROOT 120000000UL /* Clock consumers of SEMC_CLK_ROOT output : SEMC */ @@ -99,6 +99,9 @@ extern const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN; /*! @brief Sys PLL for BOARD_BootClockRUN configuration. */ extern const clock_sys_pll_config_t sysPllConfig_BOARD_BootClockRUN; +/*! @brief Audio PLL set for BOARD_BootClockRUN configuration. + */ +extern const clock_audio_pll_config_t audioPllConfig_BOARD_BootClockRUN; /*! @brief Enet PLL set for BOARD_BootClockRUN configuration. */ extern const clock_enet_pll_config_t enetPllConfig_BOARD_BootClockRUN; diff --git a/bsp/generated/clock_config.c b/bsp/generated/clock_config.c index fb87974..f554d10 100644 --- a/bsp/generated/clock_config.c +++ b/bsp/generated/clock_config.c @@ -74,7 +74,7 @@ outputs: - {id: LPI2C_CLK_ROOT.outFreq, value: 60 MHz} - {id: LPSPI_CLK_ROOT.outFreq, value: 105.6 MHz} - {id: LVDS1_CLK.outFreq, value: 1.2 GHz} -- {id: MQS_MCLK.outFreq, value: 1080/17 MHz} +- {id: MQS_MCLK.outFreq, value: 2.8224 MHz} - {id: PERCLK_CLK_ROOT.outFreq, value: 75 MHz} - {id: PLL7_MAIN_CLK.outFreq, value: 24 MHz} - {id: SAI1_CLK_ROOT.outFreq, value: 1080/17 MHz} @@ -84,8 +84,8 @@ outputs: - {id: SAI2_CLK_ROOT.outFreq, value: 1080/17 MHz} - {id: SAI2_MCLK1.outFreq, value: 1080/17 MHz} - {id: SAI2_MCLK3.outFreq, value: 30 MHz} -- {id: SAI3_CLK_ROOT.outFreq, value: 1080/17 MHz} -- {id: SAI3_MCLK1.outFreq, value: 1080/17 MHz} +- {id: SAI3_CLK_ROOT.outFreq, value: 11.2896 MHz} +- {id: SAI3_MCLK1.outFreq, value: 11.2896 MHz} - {id: SAI3_MCLK3.outFreq, value: 30 MHz} - {id: SEMC_CLK_ROOT.outFreq, value: 120 MHz} - {id: SPDIF0_CLK_ROOT.outFreq, value: 30 MHz} @@ -102,6 +102,9 @@ settings: - {id: CCM.LPSPI_PODF.scale, value: '5'} - {id: CCM.PERCLK_PODF.scale, value: '2', locked: true} - {id: CCM.PERIPH_CLK2_SEL.sel, value: XTALOSC24M.OSC_CLK} +- {id: CCM.SAI3_CLK_PODF.scale, value: '8', locked: true} +- {id: CCM.SAI3_CLK_PRED.scale, value: '8', locked: true} +- {id: CCM.SAI3_CLK_SEL.sel, value: CCM_ANALOG.PLL4_MAIN_CLK} - {id: CCM.SEMC_PODF.scale, value: '5', locked: true} - {id: CCM.USDHC1_CLK_SEL.sel, value: CCM_ANALOG.PLL2_PFD0_CLK} - {id: CCM.USDHC1_PODF.scale, value: '2', locked: true} @@ -123,9 +126,17 @@ settings: - {id: CCM_ANALOG.PLL3_PFD1_BYPASS.sel, value: CCM_ANALOG.PLL3_PFD1} - {id: CCM_ANALOG.PLL3_PFD2_BYPASS.sel, value: CCM_ANALOG.PLL3_PFD2} - {id: CCM_ANALOG.PLL3_PFD3_BYPASS.sel, value: CCM_ANALOG.PLL3_PFD3} +- {id: CCM_ANALOG.PLL4.denom, value: '625', locked: true} +- {id: CCM_ANALOG.PLL4.div, value: '30', locked: true} +- {id: CCM_ANALOG.PLL4.num, value: '66', locked: true} +- {id: CCM_ANALOG.PLL4_BYPASS.sel, value: CCM_ANALOG.PLL4_POST_DIV} +- {id: CCM_ANALOG.PLL4_POST_DIV.scale, value: '1', locked: true} +- {id: CCM_ANALOG_PLL_AUDIO_POWERDOWN_CFG, value: 'No'} - {id: CCM_ANALOG_PLL_USB1_EN_USB_CLKS_CFG, value: Enabled} - {id: CCM_ANALOG_PLL_USB1_EN_USB_CLKS_OUT_CFG, value: Enabled} - {id: CCM_ANALOG_PLL_USB1_POWER_CFG, value: 'Yes'} +- {id: IOMUXC_GPR.MQS_CLK_DIV.scale, value: '4', locked: true} +- {id: IOMUXC_GPR_GPR2_MQS_OVERSAMPLE, value: OverSampleRate64} sources: - {id: XTALOSC24M.RTC_OSC.outFreq, value: 32.768 kHz, enabled: true} * BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS **********/ @@ -150,6 +161,14 @@ const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN = .loopDivider = 0, /* PLL loop divider, Fout = Fin * 20 */ .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ }; +const clock_audio_pll_config_t audioPllConfig_BOARD_BootClockRUN = + { + .loopDivider = 30, /* PLL loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .postDivider = 1, /* Divider after PLL */ + .numerator = 66, /* 30 bit numerator of fractional loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .denominator = 625, /* 30 bit denominator of fractional loop divider, Fout = Fin * ( loopDivider + numerator / denominator ) */ + .src = 0, /* Bypass clock source, 0 - OSC 24M, 1 - CLK1_P and CLK1_N */ + }; const clock_enet_pll_config_t enetPllConfig_BOARD_BootClockRUN = { .enableClkOutput = true, /* Enable the PLL providing the ENET 125MHz reference clock */ @@ -285,11 +304,11 @@ void BOARD_BootClockRUN(void) /* Disable SAI3 clock gate. */ CLOCK_DisableClock(kCLOCK_Sai3); /* Set SAI3_CLK_PRED. */ - CLOCK_SetDiv(kCLOCK_Sai3PreDiv, 3); + CLOCK_SetDiv(kCLOCK_Sai3PreDiv, 7); /* Set SAI3_CLK_PODF. */ - CLOCK_SetDiv(kCLOCK_Sai3Div, 1); + CLOCK_SetDiv(kCLOCK_Sai3Div, 7); /* Set Sai3 clock source. */ - CLOCK_SetMux(kCLOCK_Sai3Mux, 0); + CLOCK_SetMux(kCLOCK_Sai3Mux, 2); /* Disable Lpi2c clock gate. */ CLOCK_DisableClock(kCLOCK_Lpi2c1); CLOCK_DisableClock(kCLOCK_Lpi2c2); @@ -389,15 +408,26 @@ void BOARD_BootClockRUN(void) /* Init Usb1 pfd3. */ CLOCK_InitUsb1Pfd(kCLOCK_Pfd3, 19); #endif - /* DeInit Audio PLL. */ - CLOCK_DeinitAudioPll(); - /* Bypass Audio PLL. */ - CLOCK_SetPllBypass(CCM_ANALOG, kCLOCK_PllAudio, 1); - /* Set divider for Audio PLL. */ + /* Init Audio PLL. */ + uint32_t pllAudio; + /* Disable Audio PLL output before initial Audio PLL. */ + CCM_ANALOG->PLL_AUDIO &= ~CCM_ANALOG_PLL_AUDIO_ENABLE_MASK; + /* Bypass PLL first */ + CCM_ANALOG->PLL_AUDIO = (CCM_ANALOG->PLL_AUDIO & (~CCM_ANALOG_PLL_AUDIO_BYPASS_CLK_SRC_MASK)) | + CCM_ANALOG_PLL_AUDIO_BYPASS_MASK | CCM_ANALOG_PLL_AUDIO_BYPASS_CLK_SRC(0); + CCM_ANALOG->PLL_AUDIO_NUM = CCM_ANALOG_PLL_AUDIO_NUM_A(66); + CCM_ANALOG->PLL_AUDIO_DENOM = CCM_ANALOG_PLL_AUDIO_DENOM_B(625); + pllAudio = (CCM_ANALOG->PLL_AUDIO & (~(CCM_ANALOG_PLL_AUDIO_DIV_SELECT_MASK | CCM_ANALOG_PLL_AUDIO_POWERDOWN_MASK))) | + CCM_ANALOG_PLL_AUDIO_ENABLE_MASK | CCM_ANALOG_PLL_AUDIO_DIV_SELECT(30); + pllAudio |= CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT(2); CCM_ANALOG->MISC2 &= ~CCM_ANALOG_MISC2_AUDIO_DIV_LSB_MASK; CCM_ANALOG->MISC2 &= ~CCM_ANALOG_MISC2_AUDIO_DIV_MSB_MASK; - /* Enable Audio PLL output. */ - CCM_ANALOG->PLL_AUDIO |= CCM_ANALOG_PLL_AUDIO_ENABLE_MASK; + CCM_ANALOG->PLL_AUDIO = pllAudio; + while ((CCM_ANALOG->PLL_AUDIO & CCM_ANALOG_PLL_AUDIO_LOCK_MASK) == 0) + { + } + /* Disable bypass for Audio PLL. */ + CLOCK_SetPllBypass(CCM_ANALOG, kCLOCK_PllAudio, 0); /* DeInit Video PLL. */ CLOCK_DeinitVideoPll(); /* Bypass Video PLL. */ @@ -451,7 +481,7 @@ void BOARD_BootClockRUN(void) /* Set SAI3 MCLK3 clock source. */ IOMUXC_SetSaiMClkClockSource(IOMUXC_GPR, kIOMUXC_GPR_SAI3MClk3Sel, 0); /* Set MQS configuration. */ - IOMUXC_MQSConfig(IOMUXC_GPR,kIOMUXC_MqsPwmOverSampleRate32, 0); + IOMUXC_MQSConfig(IOMUXC_GPR,kIOMUXC_MqsPwmOverSampleRate64, 3); /* Set ENET Ref clock source. */ #if defined(IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK) IOMUXC_GPR->GPR1 &= ~IOMUXC_GPR_GPR1_ENET_REF_CLK_DIR_MASK; diff --git a/bsp/generated/clock_config.h b/bsp/generated/clock_config.h index 0ce515d..e71870e 100644 --- a/bsp/generated/clock_config.h +++ b/bsp/generated/clock_config.h @@ -65,7 +65,7 @@ void BOARD_InitBootClocks(void); #define BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT 60000000UL /* Clock consumers of LPI2C_CLK_ROOT output : LPI2C1, LPI2C2, LPI2C3, LPI2C4 */ #define BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT 105600000UL /* Clock consumers of LPSPI_CLK_ROOT output : LPSPI1, LPSPI2, LPSPI3, LPSPI4 */ #define BOARD_BOOTCLOCKRUN_LVDS1_CLK 1200000000UL /* Clock consumers of LVDS1_CLK output : N/A */ -#define BOARD_BOOTCLOCKRUN_MQS_MCLK 63529411UL /* Clock consumers of MQS_MCLK output : N/A */ +#define BOARD_BOOTCLOCKRUN_MQS_MCLK 2822400UL /* Clock consumers of MQS_MCLK output : N/A */ #define BOARD_BOOTCLOCKRUN_PERCLK_CLK_ROOT 75000000UL /* Clock consumers of PERCLK_CLK_ROOT output : GPT1, GPT2, PIT */ #define BOARD_BOOTCLOCKRUN_PLL7_MAIN_CLK 24000000UL /* Clock consumers of PLL7_MAIN_CLK output : N/A */ #define BOARD_BOOTCLOCKRUN_SAI1_CLK_ROOT 63529411UL /* Clock consumers of SAI1_CLK_ROOT output : N/A */ @@ -76,8 +76,8 @@ void BOARD_InitBootClocks(void); #define BOARD_BOOTCLOCKRUN_SAI2_MCLK1 63529411UL /* Clock consumers of SAI2_MCLK1 output : SAI2 */ #define BOARD_BOOTCLOCKRUN_SAI2_MCLK2 0UL /* Clock consumers of SAI2_MCLK2 output : SAI2 */ #define BOARD_BOOTCLOCKRUN_SAI2_MCLK3 30000000UL /* Clock consumers of SAI2_MCLK3 output : SAI2 */ -#define BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT 63529411UL /* Clock consumers of SAI3_CLK_ROOT output : N/A */ -#define BOARD_BOOTCLOCKRUN_SAI3_MCLK1 63529411UL /* Clock consumers of SAI3_MCLK1 output : SAI3 */ +#define BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT 11289600UL /* Clock consumers of SAI3_CLK_ROOT output : N/A */ +#define BOARD_BOOTCLOCKRUN_SAI3_MCLK1 11289600UL /* Clock consumers of SAI3_MCLK1 output : SAI3 */ #define BOARD_BOOTCLOCKRUN_SAI3_MCLK2 0UL /* Clock consumers of SAI3_MCLK2 output : SAI3 */ #define BOARD_BOOTCLOCKRUN_SAI3_MCLK3 30000000UL /* Clock consumers of SAI3_MCLK3 output : SAI3 */ #define BOARD_BOOTCLOCKRUN_SEMC_CLK_ROOT 120000000UL /* Clock consumers of SEMC_CLK_ROOT output : SEMC */ @@ -99,6 +99,9 @@ extern const clock_usb_pll_config_t usb1PllConfig_BOARD_BootClockRUN; /*! @brief Sys PLL for BOARD_BootClockRUN configuration. */ extern const clock_sys_pll_config_t sysPllConfig_BOARD_BootClockRUN; +/*! @brief Audio PLL set for BOARD_BootClockRUN configuration. + */ +extern const clock_audio_pll_config_t audioPllConfig_BOARD_BootClockRUN; /*! @brief Enet PLL set for BOARD_BootClockRUN configuration. */ extern const clock_enet_pll_config_t enetPllConfig_BOARD_BootClockRUN; diff --git a/bsp/mqs/CMakeLists.txt b/bsp/mqs/CMakeLists.txt new file mode 100644 index 0000000..2fad046 --- /dev/null +++ b/bsp/mqs/CMakeLists.txt @@ -0,0 +1,18 @@ +# bsp/mqs/CMakeLists.txt + +if(BUILD_TESTS_HOST) + return() +endif() + +add_library(bsp_mqs STATIC src/mqs.c src/mqs_amp.c) + +target_include_directories( + bsp_mqs + PUBLIC include/ + PRIVATE src/) + +target_link_libraries( + bsp_mqs + PUBLIC bsp_status + PUBLIC bsp_board + PRIVATE sdk_sai_edma sdk_edma sdk_dmamux sdk_clock sdk_pwm sdk_xbara) diff --git a/bsp/mqs/README.md b/bsp/mqs/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bsp/mqs/include/bsp/mqs.h b/bsp/mqs/include/bsp/mqs.h new file mode 100644 index 0000000..09a6240 --- /dev/null +++ b/bsp/mqs/include/bsp/mqs.h @@ -0,0 +1,181 @@ +/** + * @file bsp/mqs.h + * @brief BSP: Medium Quality Sound (MQS) — SAI1 + eDMA + MQS. + * + * Слой абстракции над SAI1/eDMA/MQS для монофонического аудио-выхода. + * Физически на плате выведен один канал (MQS_RIGHT, GPIO_AD_B0_04); + * SAI1 требует стерео-буфер — оба канала всегда идентичны. + * + * Режимы использования: + * - firmware_test: bsp_mqs_play_blocking() — синхронная подача + * статического буфера, polling-флаг завершения. + * - tft_app: bsp_mqs_play() + колбэк — асинхронная потоковая + * подача буферов из задачи FreeRTOS. + * + * Ограничения: + * - Буфер должен быть в некэшируемой памяти (AT_NONCACHEABLE_SECTION_ALIGN) + * или кэш должен быть явно вычищен перед вызовом bsp_mqs_play(). + * - Одновременно может воспроизводиться только один буфер. + * - bsp_mqs_init() вызывается однократно; повторный вызов без + * bsp_mqs_deinit() возвращает BSP_OK без переинициализации. + */ + +#ifndef BSP_MQS_H +#define BSP_MQS_H + +#include "bsp/status.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* -------------------------------------------------------------------------- + * Параметры аудио-потока + * ----------------------------------------------------------------------- */ + +/** Частота дискретизации, Гц. Небольшое отклонение (~0.5%) из-за + * источника SAI1_CLK_ROOT (System PLL PFD2, не Audio PLL). */ +#define BSP_MQS_SAMPLE_RATE_HZ (44100U) + +/** Разрядность PCM. MQS поддерживает только 16 бит. */ +#define BSP_MQS_BIT_WIDTH (16U) + +/** Количество каналов в буфере. SAI1+MQS требует стерео; правый == левый. */ +#define BSP_MQS_CHANNELS (2U) + +/** Байт на один моно-сэмпл (16 бит → 2 байта). */ +#define BSP_MQS_BYTES_PER_SAMPLE (BSP_MQS_BIT_WIDTH / 8U) + +/** Байт на один стерео-фрейм (L + R). */ +#define BSP_MQS_BYTES_PER_FRAME (BSP_MQS_BYTES_PER_SAMPLE * BSP_MQS_CHANNELS) + + /* -------------------------------------------------------------------------- + * Колбэк завершения (для tft_app / потокового режима) + * ----------------------------------------------------------------------- */ + + /** + * @brief Колбэк завершения DMA-передачи одного буфера. + * + * Вызывается из контекста ISR. Запрещено: блокировки, HAL-функции + * без суффикса _FromISR, длительные вычисления. + * + * @param p_user Пользовательский указатель, переданный в bsp_mqs_play(). + */ + typedef void (*bsp_mqs_done_cb_t)(void *p_user); + + /* -------------------------------------------------------------------------- + * API + * ----------------------------------------------------------------------- */ + + /** + * @brief Инициализация MQS-подсистемы: SAI1, eDMA, DMAMUX, MQS. + * + * Включает тактирование SAI1 (kCLOCK_Sai1), настраивает SAI1 в режиме + * TX Master, 16 бит, стерео, 44100 Гц, инициализирует eDMA канал 0 + * (DMAMUX source kDmaRequestMuxSai1Tx) и MQS-модуль. + * + * Пин GPIO_AD_B0_04 (MQS_RIGHT) уже замультиплексирован в BOARD_InitPins(). + * MQS oversample (×32) уже выставлен в BOARD_BootClockRUN(). + * + * @return BSP_OK успех + * @return BSP_ERR_HW eDMA или SAI вернул ошибку при настройке формата + */ + bsp_status_t bsp_mqs_init(void); + + /** + * @brief Деинициализация: остановить DMA, сбросить SAI1 и MQS. + * + * Безопасно вызывать даже если воспроизведение уже завершилось. + * После вызова модуль требует повторного bsp_mqs_init(). + */ + void bsp_mqs_deinit(void); + + /** + * @brief Запустить асинхронное воспроизведение буфера через eDMA. + * + * Буфер должен содержать стерео PCM16 interleaved (L0, R0, L1, R1, …). + * Указатель @p p_buf обязан оставаться валидным до срабатывания колбэка @p cb. + * + * @param p_buf Указатель на стерео PCM16 буфер (некэшируемая память). + * @param n_frames Число стерео-фреймов (не байт, не сэмплов). + * @param cb Колбэк завершения; NULL — без уведомления. + * @param p_user Аргумент для колбэка; NULL допустим. + * + * @return BSP_OK передача запущена + * @return BSP_ERR_BUSY предыдущая передача ещё не завершена + * @return BSP_ERR_INVALID p_buf == NULL или n_frames == 0 + * @return BSP_ERR_HW SAI_TransferSendEDMA вернул ошибку + */ + bsp_status_t bsp_mqs_play(const int16_t *p_buf, size_t n_frames, bsp_mqs_done_cb_t cb, + void *p_user); + + /** + * @brief Синхронное воспроизведение: запустить и дождаться завершения. + * + * Вызывает bsp_mqs_play() и крутится в polling-цикле до сброса флага + * завершения. Предназначен для bare-metal (firmware_test). + * Не вызывать из ISR или FreeRTOS-задачи с высоким приоритетом. + * + * @param p_buf Указатель на стерео PCM16 буфер (некэшируемая память). + * @param n_frames Число стерео-фреймов. + * + * @return BSP_OK воспроизведение завершено + * @return BSP_ERR_BUSY предыдущая передача ещё не завершена + * @return BSP_ERR_INVALID p_buf == NULL или n_frames == 0 + * @return BSP_ERR_HW SAI_TransferSendEDMA вернул ошибку + */ + bsp_status_t bsp_mqs_play_blocking(const int16_t *p_buf, size_t n_frames); + + /** + * @brief Немедленно прервать воспроизведение. + * + * Останавливает текущую DMA-передачу. Колбэк после stop НЕ вызывается. + * Безопасно вызывать если передача уже завершилась. + */ + void bsp_mqs_stop(void); + + /** + * @brief Проверить, активна ли DMA-передача. + * + * @return true передача идёт + * @return false передача завершена или не была запущена + */ + bool bsp_mqs_is_busy(void); + + /* -------------------------------------------------------------------------- + * Усилитель (LM4875M, управление через PWM4 SM0 PWM_A) + * ----------------------------------------------------------------------- */ + + /** + * @brief Инициализация усилителя: PWM4 SM0, 16 кГц, duty 50%. + * + * Настраивает XBARA1 (fault disable), PWM4 submodule 0 channel A. + * Вызывать до bsp_mqs_play() — без ШИМ на VOLUME усиление равно нулю. + * + * @return BSP_OK успех + * @return BSP_ERR_HW PWM_Init или PWM_SetupPwm вернул ошибку + */ + bsp_status_t bsp_mqs_amp_init(void); + + /** + * @brief Установить уровень громкости (duty cycle PWM). + * + * @param percent Громкость 0–100%. 0 = тишина, 50 = номинал, 100 = макс. + */ + void bsp_mqs_amp_set_volume(uint8_t percent); + + /** + * @brief Деинициализация усилителя: duty → 0%, стоп таймера. + */ + void bsp_mqs_amp_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* BSP_MQS_H */ \ No newline at end of file diff --git a/bsp/mqs/src/mqs.c b/bsp/mqs/src/mqs.c new file mode 100644 index 0000000..e89ae27 --- /dev/null +++ b/bsp/mqs/src/mqs.c @@ -0,0 +1,286 @@ +/** + * @file bsp_mqs.c + * @brief BSP MQS: SAI1 TX + eDMA + MQS для MIMXRT1052CVJ5B. + * + * Тактирование: + * SAI1_CLK_ROOT = SysPLL × (18/27) / (SAI1_CLK_PRED+1=4) / (SAI1_CLK_PODF+1=2) + * ≈ 63 529 411 Гц (BOARD_BOOTCLOCKRUN_SAI1_CLK_ROOT) + * Bit clock = 44100 × 16 × 2 = 1 411 200 Гц + * MCLK делитель = 63 529 411 / 1 411 200 ≈ 45.0 (погрешность ~0.5 %) + * + * MQS oversample = 32, уже выставлен в BOARD_BootClockRUN() через + * IOMUXC_MQSConfig(IOMUXC_GPR, kIOMUXC_MqsPwmOverSampleRate32, 0). + * + * Пин: GPIO_AD_B0_04 → MQS_RIGHT — замультиплексирован в BOARD_InitPins(). + * + * eDMA: DMA0 канал 0, DMAMUX source kDmaRequestMuxSai1Tx. + * Канал 0 зарезервирован за bsp_mqs. Прочие модули — каналы 1+. + * + * SAI API (SDK 2.4.7 / fsl_sai.h, fsl_sai_edma.h 2.7.3): + * Конфигурация через sai_transceiver_t: + * SAI_GetClassicI2SConfig() → SAI_TxSetConfig() → SAI_TxSetBitClockRate() + * eDMA: + * SAI_TransferTxCreateHandleEDMA() → SAI_TransferTxSetConfigEDMA() + * FIFO watermark: FSL_FEATURE_SAI_FIFO_COUNTn(x) — макрос с аргументом + * экземпляра; требует #include "MIMXRT1052_features.h". + * Сброс: SAI_TxReset() (SAI_TxSoftwareReset отсутствует в этой версии). + */ + +#include "bsp/mqs.h" + +#include "MIMXRT1052_features.h" +#include "clock_config.h" +#include "fsl_clock.h" +#include "fsl_dmamux.h" +#include "fsl_edma.h" +#include "fsl_iomuxc.h" +#include "fsl_sai.h" +#include "fsl_sai_edma.h" + +#include + +/* -------------------------------------------------------------------------- + * Конфигурация периферии + * ----------------------------------------------------------------------- */ + +#define MQS_SAI_BASE SAI3 +#define MQS_SAI_CLOCK_GATE kCLOCK_Sai3 +#define MQS_SAI_CLK_FREQ BOARD_BOOTCLOCKRUN_SAI3_CLK_ROOT + +/** eDMA канал, выделенный под SAI1 TX. */ +#define MQS_DMA_CHANNEL (0U) + +/** DMAMUX запрос для SAI1 TX. */ +#define MQS_DMAMUX_SOURCE kDmaRequestMuxSai3Tx + +/** Приоритет прерывания DMA (ниже USB = 3, выше нормальных задач). */ +#define MQS_DMA_IRQ_PRIORITY (5U) +#define MQS_HMCLK_GATE kCLOCK_Mqs +/** + * FIFO watermark — половина глубины FIFO SAI1. + * FSL_FEATURE_SAI_FIFO_COUNTn(x) принимает экземпляр SAI и возвращает + * глубину FIFO в словах (32 для RT1052). Деление на 2 даёт оптимальную + * латентность DMA: запрос формируется когда в FIFO остаётся место для + * половины буфера. + */ +#define MQS_SAI_FIFO_WATERMARK \ + ((uint8_t) ((uint8_t) FSL_FEATURE_SAI_FIFO_COUNTn(MQS_SAI_BASE) / 2U)) + +/* -------------------------------------------------------------------------- + * Внутреннее состояние + * ----------------------------------------------------------------------- */ + +static bool g_s_initialized = false; +static volatile bool g_s_dma_done = false; +static volatile bool g_s_busy = false; + +static bsp_mqs_done_cb_t g_s_user_cb = NULL; +static void *g_s_user_data = NULL; + +/* + * Handles в некэшируемой секции необязательны (не DMA-буферы), но + * выравнивание по 4 байта обязательно для SDK. + */ +AT_NONCACHEABLE_SECTION_ALIGN(static edma_handle_t s_dma_handle, 4U); +AT_NONCACHEABLE_SECTION_ALIGN(static sai_edma_handle_t s_sai_tx_handle, 4U); + +/* -------------------------------------------------------------------------- + * DMA колбэк (ISR-контекст) + * ----------------------------------------------------------------------- */ + +static void mqs_edma_callback(I2S_Type *p_base, sai_edma_handle_t *p_handle, status_t status, + void *p_user_data) +{ + (void) p_base; + (void) p_handle; + (void) status; + (void) p_user_data; + + g_s_busy = false; + g_s_dma_done = true; + + if (g_s_user_cb != NULL) + { + g_s_user_cb(g_s_user_data); + } +} + +/* -------------------------------------------------------------------------- + * Публичный API + * ----------------------------------------------------------------------- */ + +bsp_status_t bsp_mqs_init(void) +{ + if (g_s_initialized) + { + return BSP_OK; + } + + /* --- Тактирование SAI1 --- */ + CLOCK_EnableClock(MQS_SAI_CLOCK_GATE); + + /* --- Тактирование MQS (CCGR0[CG2]) — без него MQS не забирает данные из + * SAI FIFO и SAI_WriteBlocking зависает навсегда. --- */ + CLOCK_EnableClock(kCLOCK_Mqs); + + /* --- MQS: сброс → включение. + * Oversample ×32 уже выставлен в BOARD_BootClockRUN(), + * повторный вызов IOMUXC_MQSConfig() не нужен. --- */ + IOMUXC_MQSEnterSoftwareReset(IOMUXC_GPR, true); + IOMUXC_MQSEnterSoftwareReset(IOMUXC_GPR, false); + IOMUXC_MQSEnable(IOMUXC_GPR, true); + + /* --- SAI1: базовая инициализация (снимает reset, включает clock gate) --- */ + SAI_Init(MQS_SAI_BASE); + + /* --- SAI1 TX: классический I2S, master, 16 бит, стерео, канал 0. + * SAI_GetClassicI2SConfig() заполняет sai_transceiver_t значениями по + * умолчанию для стандартного I2S. Затем корректируем watermark и + * sync mode (async — TX не зависит от RX). --- */ + sai_transceiver_t sai_cfg; + + SAI_GetLeftJustifiedConfig(&sai_cfg, kSAI_WordWidth16bits, kSAI_Stereo, + (uint32_t) kSAI_Channel0Mask); + sai_cfg.syncMode = kSAI_ModeAsync; + sai_cfg.fifo.fifoWatermark = MQS_SAI_FIFO_WATERMARK; + sai_cfg.startChannel = 0; + + /* Явно задать источник bit clock — SAI1_CLK_ROOT (Mux делитель). + * Default после SAI_GetClassicI2SConfig может быть BusClock (IPG). */ + sai_cfg.bitClock.bclkSource = kSAI_BclkSourceMclkDiv; + + SAI_TxSetConfig(MQS_SAI_BASE, &sai_cfg); + + /* --- Bit clock rate: вычисляется как sourceClk / (sampleRate * bitWidth * channels). + * Функция void — ошибки не возвращает, делитель рассчитывается аппаратно. --- */ + SAI_TxSetBitClockRate(MQS_SAI_BASE, (uint32_t) MQS_SAI_CLK_FREQ, + (uint32_t) kSAI_SampleRate44100Hz, (uint32_t) kSAI_WordWidth16bits, + BSP_MQS_CHANNELS); + + /* --- eDMA --- */ + edma_config_t dma_cfg; + EDMA_GetDefaultConfig(&dma_cfg); + EDMA_Init(DMA0, &dma_cfg); + EDMA_CreateHandle(&s_dma_handle, DMA0, MQS_DMA_CHANNEL); + + /* --- DMAMUX: канал 0 → SAI1 TX --- */ + DMAMUX_Init(DMAMUX); + DMAMUX_SetSource(DMAMUX, MQS_DMA_CHANNEL, (uint8_t) MQS_DMAMUX_SOURCE); + DMAMUX_EnableChannel(DMAMUX, MQS_DMA_CHANNEL); + + /* --- SAI eDMA handle + конфигурация. + * SAI_TransferTxSetConfigEDMA() — void, настраивает DMA-дескрипторы + * (размер minor loop, адрес FIFO) на основе sai_transceiver_t. --- */ + SAI_TransferTxCreateHandleEDMA(MQS_SAI_BASE, &s_sai_tx_handle, mqs_edma_callback, NULL, + &s_dma_handle); + SAI_TransferTxSetConfigEDMA(MQS_SAI_BASE, &s_sai_tx_handle, &sai_cfg); + + /* Включить SAI TX — без этого FIFO не дренируется даже в polling-режиме. + * SAI_TransferSendEDMA() включает его снова если MCUX_SDK_SAI_EDMA_TX_ENABLE_INTERNAL=1, + * но для корректной работы FIFO TX должен быть активен сразу после init. */ + SAI_TxEnable(MQS_SAI_BASE, true); + + NVIC_SetPriority(DMA0_DMA16_IRQn, MQS_DMA_IRQ_PRIORITY); + + g_s_busy = false; + g_s_dma_done = false; + g_s_initialized = true; + + return BSP_OK; +} + +void bsp_mqs_deinit(void) +{ + if (!g_s_initialized) + { + return; + } + + SAI_TransferTerminateSendEDMA(MQS_SAI_BASE, &s_sai_tx_handle); + /* SAI_TxReset() — сброс logic + FIFO (аналог kSAI_ResetAll). + * SAI_TxSoftwareReset() отсутствует в данной версии SDK. */ + SAI_TxReset(MQS_SAI_BASE); + IOMUXC_MQSEnable(IOMUXC_GPR, false); + CLOCK_DisableClock(MQS_SAI_CLOCK_GATE); + CLOCK_DisableClock(kCLOCK_Mqs); + g_s_busy = false; + g_s_dma_done = false; + g_s_user_cb = NULL; + g_s_user_data = NULL; + g_s_initialized = false; +} + +bsp_status_t bsp_mqs_play(const int16_t *p_buf, size_t n_frames, bsp_mqs_done_cb_t p_cb, + void *p_user) +{ + if ((p_buf == NULL) || (n_frames == 0U)) + { + return BSP_ERR_INVALID; + } + if (g_s_busy) + { + return BSP_ERR_BUSY; + } + + /* Очищаем возможный Underrun (FEF) от предыдущего проигрывания и сбрасываем FIFO */ + SAI3->TCSR |= I2S_TCSR_FEF_MASK | I2S_TCSR_FR_MASK; + + g_s_user_cb = p_cb; + g_s_user_data = p_user; + g_s_dma_done = false; + g_s_busy = true; + + sai_transfer_t xfer; + /* p_buf — const int16_t*; SDK ожидает uint8_t*. + * Cast через uintptr_t: MISRA-compliant ptr→int→ptr. */ + xfer.data = (uint8_t *) (uintptr_t) p_buf; + xfer.dataSize = n_frames * (size_t) BSP_MQS_BYTES_PER_FRAME; + + status_t status = SAI_TransferSendEDMA(MQS_SAI_BASE, &s_sai_tx_handle, &xfer); + if (status != kStatus_Success) + { + g_s_busy = false; + return BSP_ERR_HW; + } + + return BSP_OK; +} + +bsp_status_t bsp_mqs_play_blocking(const int16_t *p_buf, size_t n_frames) +{ + bsp_status_t status = bsp_mqs_play(p_buf, n_frames, NULL, NULL); + if (status != BSP_OK) + { + return status; + } + + /* + * Polling-ожидание завершения DMA. + * Флаг s_dma_done взводится из ISR (mqs_edma_callback). + * USB keepalive выполняется в вызывающем тест-коде (test_mqs_run). + */ + while (!g_s_dma_done) + { + /* bare-metal: явное ожидание ISR */ + } + + return BSP_OK; +} + +void bsp_mqs_stop(void) +{ + if (!g_s_busy) + { + return; + } + + SAI_TransferTerminateSendEDMA(MQS_SAI_BASE, &s_sai_tx_handle); + g_s_busy = false; + g_s_dma_done = false; + /* Колбэк НЕ вызывается: явная остановка — ответственность вызывающего. */ +} + +bool bsp_mqs_is_busy(void) +{ + return g_s_busy; +} \ No newline at end of file diff --git a/bsp/mqs/src/mqs_amp.c b/bsp/mqs/src/mqs_amp.c new file mode 100644 index 0000000..6c4fa0a --- /dev/null +++ b/bsp/mqs/src/mqs_amp.c @@ -0,0 +1,141 @@ +/** + * @file bsp_mqs_amp.c + * @brief BSP: управление усилителем звука LM4875M через FlexPWM4 SM0 PWM_A. + * + * Схема: + * GPIO_AD_B1_08 (PWM4_A, SM0) → R56 (1K) → LM358 +IN1 → LM4875M DC_VOL + * DC_VOL управляет коэффициентом усиления LM4875M. + * HP_SEN LM4875M подтянут к GND — включается автоматически при наличии + * аудио-сигнала на входе (MQS_RIGHT через RC-фильтр → SOUND_OUT). + * + * Управление громкостью: + * PWM4 SM0 PWM_A, частота 16 кГц, центрально-симметричный режим. + * duty 0% → DC_VOL ≈ 0 В → усиление минимально (тишина). + * duty 50% → DC_VOL ≈ 2.5 В → номинальная громкость. + * duty 100%→ DC_VOL ≈ 5 В → максимальное усиление. + * + * Fault-вход PWM4: + * XBARA1: kXBARA1_InputLogicHigh → kXBARA1_OutputFlexpwm4Fault0. + * Fault сконфигурирован как "no fault" — PWM работает без аппаратного + * отключения. Это повторяет конфигурацию из оригинального timers.c. + * + * Тактирование: + * IPG clock = AHB/4 = 600/4 = 150 МГц. + * PWM prescaler = /16 → PWM clock = 9.375 МГц. + * Fpwm = 16000 Гц (центрально-симметричный режим). + */ + +#include "bsp/mqs.h" +#include "clock_config.h" +#include "fsl_clock.h" +#include "fsl_pwm.h" +#include "fsl_xbara.h" + +/* -------------------------------------------------------------------------- + * Конфигурация + * ----------------------------------------------------------------------- */ + +#define AMP_PWM_BASE PWM4 +#define AMP_PWM_SUBMODULE kPWM_Module_0 +#define AMP_PWM_CHANNEL kPWM_PwmA +#define AMP_PWM_MODE kPWM_SignedCenterAligned +#define AMP_PWM_FREQ_HZ (16000U) + +/** Номинальный duty cycle при bsp_mqs_amp_init() — 50%. */ +#define AMP_DEFAULT_DUTY (50U) + +/** IPG clock = 150 МГц (AHB/4). */ +#define AMP_PWM_SRC_CLK_HZ (BOARD_BOOTCLOCKRUN_IPG_CLK_ROOT) + +/* -------------------------------------------------------------------------- + * Публичный API + * ----------------------------------------------------------------------- */ + +bsp_status_t bsp_mqs_amp_init(void) +{ + /* --- XBARA1: подать логическую единицу на Fault0 PWM4. + * Это предотвращает аппаратное отключение PWM при старте. --- */ + XBARA_Init(XBARA1); + XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputLogicHigh, kXBARA1_OutputFlexpwm4Fault0); + + /* --- PWM4 SM0: базовая конфигурация --- */ + pwm_config_t pwm_cfg; + PWM_GetDefaultConfig(&pwm_cfg); + pwm_cfg.clockSource = kPWM_BusClock; + pwm_cfg.prescale = kPWM_Prescale_Divide_16; + pwm_cfg.pairOperation = kPWM_Independent; + pwm_cfg.initializationControl = kPWM_Initialize_LocalSync; + pwm_cfg.reloadLogic = kPWM_ReloadImmediate; + pwm_cfg.reloadSelect = kPWM_LocalReload; + pwm_cfg.reloadFrequency = kPWM_LoadEveryOportunity; + pwm_cfg.forceTrigger = kPWM_Force_Local; + + if (PWM_Init(AMP_PWM_BASE, AMP_PWM_SUBMODULE, &pwm_cfg) != kStatus_Success) + { + return BSP_ERR_HW; + } + + /* --- Fault-фильтр и fault-конфигурация (no fault active) --- */ + const pwm_fault_input_filter_param_t FAULT_FILTER = { + .faultFilterPeriod = 1U, + .faultFilterCount = 3U, + .faultGlitchStretch = false, + }; + PWM_SetupFaultInputFilter(AMP_PWM_BASE, &FAULT_FILTER); + + const pwm_fault_param_t FAULT_CFG = { + .faultClearingMode = kPWM_Automatic, + .faultLevel = false, + .enableCombinationalPath = false, + .recoverMode = kPWM_NoRecovery, + }; + PWM_SetupFaults(AMP_PWM_BASE, kPWM_Fault_0, &FAULT_CFG); + PWM_SetupFaults(AMP_PWM_BASE, kPWM_Fault_1, &FAULT_CFG); + PWM_SetupFaults(AMP_PWM_BASE, kPWM_Fault_2, &FAULT_CFG); + PWM_SetupFaults(AMP_PWM_BASE, kPWM_Fault_3, &FAULT_CFG); + + /* --- ForceSignal: использовать нормальный PWM-сигнал --- */ + PWM_SetupForceSignal(AMP_PWM_BASE, AMP_PWM_SUBMODULE, AMP_PWM_CHANNEL, kPWM_UsePwm); + + /* --- PWM-сигнал: 16 кГц, центрально-симметричный, duty 50% --- */ + const pwm_signal_param_t PWM_SIGNAL = { + .pwmChannel = AMP_PWM_CHANNEL, + .dutyCyclePercent = AMP_DEFAULT_DUTY, + .level = kPWM_HighTrue, + .faultState = kPWM_PwmFaultState0, + .deadtimeValue = 0U, + }; + + if (PWM_SetupPwm(AMP_PWM_BASE, AMP_PWM_SUBMODULE, &PWM_SIGNAL, 1U, AMP_PWM_MODE, + AMP_PWM_FREQ_HZ, (uint32_t) AMP_PWM_SRC_CLK_HZ) != kStatus_Success) + { + return BSP_ERR_HW; + } + + /* --- Применить и запустить --- */ + PWM_SetPwmLdok(AMP_PWM_BASE, (uint8_t) kPWM_Control_Module_0, true); + PWM_StartTimer(AMP_PWM_BASE, (uint8_t) kPWM_Control_Module_0); + + return BSP_OK; +} + +void bsp_mqs_amp_set_volume(uint8_t percent) +{ + if (percent > 100U) + { + percent = 100U; + } + + PWM_UpdatePwmDutycycle(AMP_PWM_BASE, AMP_PWM_SUBMODULE, AMP_PWM_CHANNEL, AMP_PWM_MODE, percent); + PWM_SetPwmLdok(AMP_PWM_BASE, (uint8_t) kPWM_Control_Module_0, true); +} + +void bsp_mqs_amp_deinit(void) +{ + /* Установить duty 0% перед остановкой — плавное выключение. */ + PWM_UpdatePwmDutycycle(AMP_PWM_BASE, AMP_PWM_SUBMODULE, AMP_PWM_CHANNEL, AMP_PWM_MODE, 0U); + PWM_SetPwmLdok(AMP_PWM_BASE, (uint8_t) kPWM_Control_Module_0, true); + + PWM_StopTimer(AMP_PWM_BASE, (uint8_t) kPWM_Control_Module_0); + PWM_Deinit(AMP_PWM_BASE, AMP_PWM_SUBMODULE); +} \ No newline at end of file diff --git a/firmware/test/CMakeLists.txt b/firmware/test/CMakeLists.txt index 09959e7..15b8125 100644 --- a/firmware/test/CMakeLists.txt +++ b/firmware/test/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable( src/tests/test_opto.c src/tests/test_sdram.c src/tests/test_can.c + src/tests/test_mqs.c src/tests/test_qspi.c src/tests/test_usd.c src/tests/test_display.c @@ -49,7 +50,9 @@ target_link_libraries( bsp_usb_cdc bsp_sdram bsp_qspi_flash + bsp_uart_host bsp_opto + bsp_mqs bsp_sd firmware_test_fatfs) diff --git a/firmware/test/src/main.c b/firmware/test/src/main.c index 1f2e929..a9cbedd 100644 --- a/firmware/test/src/main.c +++ b/firmware/test/src/main.c @@ -1,4 +1,206 @@ +/** + * @file main_mqs_test.c + * @brief Изолированный тест bsp_mqs + bsp_mqs_amp. + * + * Подменяет firmware/test/src/main.c на время отладки BSP. + * Вывод диагностики — UART (MCU-Link VCOM, 115200 бод). + * + * Два режима — переключаются комментарием в main(): + * TEST_MODE_POLLING — SAI_WriteBlocking напрямую, без DMA. + * Если звук есть → SAI/MQS OK, проблема в DMA. + * Если нет → проблема в SAI/MQS/усилителе. + * TEST_MODE_DMA — bsp_mqs_play_blocking через eDMA. + */ +#include "board.h" +#include "bsp/led.h" +#include "bsp/mqs.h" +#include "bsp/tick.h" +#include "bsp/uart_host.h" +#include "fsl_sai.h" + +#include +#include + +/* -------------------------------------------------------------------------- + * Выбор режима: раскомментируй один + * ----------------------------------------------------------------------- */ +//#define TEST_MODE_POLLING +#define TEST_MODE_DMA + +/* -------------------------------------------------------------------------- + * Буфер мелодии в некэшируемой секции + * ----------------------------------------------------------------------- */ + +#define MQS_NOTE_SAMPLES (44100U * 2U) /* 2 с на ноту */ +#define MQS_TOTAL_FRAMES (MQS_NOTE_SAMPLES * 2U) /* 2 ноты */ +#define MQS_BUF_ELEMENTS (MQS_TOTAL_FRAMES * BSP_MQS_CHANNELS) + +static AT_NONCACHEABLE_SECTION_ALIGN(int16_t s_melody_buf[MQS_BUF_ELEMENTS], 4U); + +/* -------------------------------------------------------------------------- + * Синусоида (целочисленная, LUT по четвертям) + * ----------------------------------------------------------------------- */ + +static int16_t sine_sample(uint32_t phase, uint32_t period) +{ + static const int16_t k_lut[25] = { + 0, 2998, 5944, 8788, 11479, 13970, 16212, 18162, 19784, 21043, 21910, 22369, 22404, + 22005, 21170, 19898, 18199, 16084, 13567, 10670, 7416, 3831, 0, -3831, -7416, + }; + uint32_t idx = (phase * 96U) / period % 96U; + uint32_t quarter = idx / 24U; + uint32_t pos = idx % 24U; + int16_t val; + switch (quarter) + { + case 0U: + val = k_lut[pos]; + break; + case 1U: + val = k_lut[24U - pos]; + break; + case 2U: + val = -k_lut[pos]; + break; + default: + val = -k_lut[24U - pos]; + break; + } + return val; +} + +static void build_melody(void) +{ + const uint32_t k_p_a4 = 100U; /* A4 ≈ 440 Гц */ + const uint32_t k_p_e5 = 67U; /* E5 ≈ 659 Гц */ + uint32_t out = 0U; + + for (uint32_t i = 0U; i < MQS_NOTE_SAMPLES; i++) + { + int16_t s = sine_sample(i % k_p_a4, k_p_a4); + s_melody_buf[out++] = s; /* L */ + s_melody_buf[out++] = s; /* R */ + } + for (uint32_t i = 0U; i < MQS_NOTE_SAMPLES; i++) + { + int16_t s = sine_sample(i % k_p_e5, k_p_e5); + s_melody_buf[out++] = s; /* L */ + s_melody_buf[out++] = s; /* R */ + } +} + +/* -------------------------------------------------------------------------- + * Режим A: SAI polling напрямую, без DMA + * + * SAI_WriteBlocking — блокирует до записи всех байт через FIFO. + * Не требует DMA вообще. Если звук есть — SAI и MQS живые. + * ----------------------------------------------------------------------- */ + +#ifdef TEST_MODE_POLLING +static void run_test(void) +{ + bsp_uart_host_write_str("[5A] Bare-metal SAI polling...\r\n"); + + /* 1. Глушим передатчик и отключаем запросы DMA, чтобы не мешали поллингу */ + SAI3->TCSR &= ~(I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK | I2S_TCSR_FRDE_MASK); + + /* 2. Сбрасываем ошибку Underrun (FEF) и чистим FIFO (FR) */ + SAI3->TCSR |= I2S_TCSR_FEF_MASK | I2S_TCSR_FR_MASK; + + /* 3. Предзагружаем FIFO (закинем 16 сэмплов ДО включения) */ + /* Это спасет аппаратуру от мгновенного Underrun'а при старте */ + for (uint32_t i = 0; i < 16; i++) + { + SAI3->TDR[0] = s_melody_buf[i]; + } + + /* 4. Включаем передатчик и генерацию Bit Clock */ + SAI3->TCSR |= (I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK); + + /* 5. Качаем остальной буфер вручную */ + for (uint32_t i = 16; i < MQS_BUF_ELEMENTS; i++) + { + /* Ждем, пока в FIFO появится свободное место (FWF == 1) */ + while ((SAI3->TCSR & I2S_TCSR_FWF_MASK) == 0) + { + /* Если всё-таки поймали опустошение - перезапускаем TX на лету */ + if (SAI3->TCSR & I2S_TCSR_FEF_MASK) + { + SAI3->TCSR |= I2S_TCSR_FEF_MASK; /* Сброс ошибки */ + SAI3->TCSR |= I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK; /* Рестарт TX */ + } + } + /* Пишем следующий сэмпл */ + SAI3->TDR[0] = s_melody_buf[i]; + } + + /* 6. Дожидаемся, пока уйдут остатки из FIFO, и выключаем */ + while (SAI3->TCSR & I2S_TCSR_FWF_MASK) + { + } + SAI3->TCSR &= ~(I2S_TCSR_TE_MASK | I2S_TCSR_BCE_MASK); + + bsp_uart_host_write_str("[5A] Done.\r\n"); +} +#endif + +/* -------------------------------------------------------------------------- + * Режим B: DMA через bsp_mqs_play_blocking + * ----------------------------------------------------------------------- */ + +#ifdef TEST_MODE_DMA +static void run_test(void) +{ + bsp_uart_host_write_str("[5B] DMA play, ~4s...\r\n"); + + bsp_status_t st = bsp_mqs_play_blocking(s_melody_buf, MQS_TOTAL_FRAMES); + + bsp_uart_host_write_str(st == BSP_OK ? "[5B] OK\r\n" : "[5B] FAIL\r\n"); +} +#endif + +/* -------------------------------------------------------------------------- + * main + * ----------------------------------------------------------------------- */ + +int main(void) +{ + board_hw_init(); + bsp_tick_init(); + bsp_led_init(); + (void) bsp_uart_host_init(115200U); + + bsp_led_on(LED_HEARTBEAT); + bsp_uart_host_write_str("\r\n=== MQS BSP TEST ===\r\n"); + + /* Шаг 1 — усилитель */ + bsp_uart_host_write_str("[1] amp_init...\r\n"); + bsp_status_t st = bsp_mqs_amp_init(); + bsp_uart_host_write_str(st == BSP_OK ? "[1] OK\r\n" : "[1] FAIL\r\n"); + + /* Шаг 2 — SAI + DMA + MQS */ + bsp_uart_host_write_str("[2] mqs_init...\r\n"); + st = bsp_mqs_init(); + bsp_uart_host_write_str(st == BSP_OK ? "[2] OK\r\n" : "[2] FAIL\r\n"); + + /* Шаг 3 — генерация буфера */ + bsp_uart_host_write_str("[3] build_melody...\r\n"); + build_melody(); + bsp_uart_host_write_str("[3] done.\r\n"); + + /* Основной цикл */ + for (;;) + { + bsp_led_toggle(LED_HEARTBEAT); + run_test(); + bsp_uart_host_write_str("[pause] 3s\r\n"); + bsp_delay(3000U); + } + + return 0; +} +#if 0 /** * @file main.c * @brief firmware_test — точка входа. @@ -64,4 +266,5 @@ int main(void) cli_process(); test_runner_process(); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/firmware/test/src/test_runner.c b/firmware/test/src/test_runner.c index 1f2326a..c7d98e7 100644 --- a/firmware/test/src/test_runner.c +++ b/firmware/test/src/test_runner.c @@ -32,11 +32,12 @@ extern const test_module_t K_TEST_DISPLAY; extern const test_module_t K_TEST_BUTTONS; extern const test_module_t K_TEST_OPTO; extern const test_module_t K_TEST_CAN; +extern const test_module_t K_TEST_MQS; static const test_module_t *const k_registry[] = { &K_TEST_SDRAM, &K_TEST_QSPI, &K_TEST_USD, &K_TEST_DISPLAY, - &K_TEST_BUTTONS, &K_TEST_OPTO, &K_TEST_CAN, + &K_TEST_BUTTONS, &K_TEST_OPTO, &K_TEST_CAN, &K_TEST_MQS, }; #define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0])) diff --git a/firmware/test/src/tests/test_mqs.c b/firmware/test/src/tests/test_mqs.c new file mode 100644 index 0000000..28fa5d7 --- /dev/null +++ b/firmware/test/src/tests/test_mqs.c @@ -0,0 +1,223 @@ +/** + * @file test_mqs.c + * @brief Тест MQS: воспроизведение тестового тона через SAI1 + eDMA + MQS. + * + * Алгоритм: + * 1. init() — bsp_mqs_init(), генерация мелодии в s_melody_buf. + * 2. run() — воспроизвести мелодию (~4 с) с USB keepalive в цикле, + * затем confirm_request("mqs_tone") — оператор подтверждает. + * 3. deinit() — bsp_mqs_stop(), bsp_mqs_deinit(). + * + * Мелодия: стерео PCM16 44100 Гц, две ноты (A4/E5) по 2 с каждая. + * Буфер — статический глобальный в некэшируемой секции (OCRAM NonCacheable). + * L == R (монофонический выход на плате). + * + * Протокол: + * ← confirm_request("mqs_tone", "Слышен звуковой сигнал?", 15000) + * → confirm("mqs_tone", true/false) + * ← test_result pass/fail/skip + */ + +#include "bsp/mqs.h" +#include "bsp/tick.h" +#include "bsp/usb_cdc.h" +#include "fsl_common.h" +#include "test_module.h" +#include "test_runner.h" + +#include +#include +#include + +/* -------------------------------------------------------------------------- + * Буфер мелодии + * ----------------------------------------------------------------------- */ + +/* + * Две ноты, по 2 с каждая: A4 (440 Гц) + E5 (659 Гц). + * Моно-сэмплов: 44100 × 2 = 88200 на ноту. + * Стерео-фреймов = моно-сэмплов (каждый L дублируется в R). + * int16_t на фрейм: 2 (L + R). + * Итого: 88200 × 2 × 2 = 352800 int16_t = 705 600 байт. + * + * DMA-буфер объявлен через AT_NONCACHEABLE_SECTION_ALIGN (паттерн из test_display.c). + */ +#define MQS_NOTE_SAMPLES (44100U * 2U) +#define MQS_TOTAL_MONO_SAMPLES (MQS_NOTE_SAMPLES * 2U) +#define MQS_TOTAL_FRAMES MQS_TOTAL_MONO_SAMPLES +#define MQS_BUF_ELEMENTS (MQS_TOTAL_FRAMES * BSP_MQS_CHANNELS) + +/* + * DMA-буфер мелодии в некэшируемой секции OCRAM. + * static передаётся внутрь var-аргумента макроса — единственный валидный + * способ, так как макрос раскрывается как: + * __attribute__((section(...))) var __attribute__((aligned(N))) + * Паттерн идентичен test_display.c (g_s_framebuf). + */ +static AT_NONCACHEABLE_SECTION_ALIGN(int16_t s_melody_buf[MQS_BUF_ELEMENTS], 4U); + +static bool g_s_melody_ready = false; + +/* -------------------------------------------------------------------------- + * Генерация мелодии (целочисленная синусоида) + * ----------------------------------------------------------------------- */ + +/* + * Таблица синуса: 96 точек на период (шаг π/48). + * Амплитуда 22937 ≈ 0.70 × INT16_MAX — запас от клиппинга на MQS. + * Генерируется по четвертям (таблица хранит только одну четверть). + */ +static int16_t sine_sample(uint32_t phase, uint32_t period) +{ + static const int16_t K_SIN_LUT[25] = { + 0, 2998, 5944, 8788, 11479, 13970, 16212, 18162, 19784, 21043, 21910, 22369, 22404, + 22005, 21170, 19898, 18199, 16084, 13567, 10670, 7416, 3831, 0, -3831, -7416, + }; + + uint32_t idx = (phase * 96U) / period % 96U; + uint32_t quarter = idx / 24U; + uint32_t pos = idx % 24U; + + int16_t val; + switch (quarter) + { + case 0U: + val = K_SIN_LUT[pos]; + break; + case 1U: + val = K_SIN_LUT[24U - pos]; + break; + case 2U: + val = -K_SIN_LUT[pos]; + break; + default: + val = -K_SIN_LUT[24U - pos]; + break; + } + return val; +} + +static void mqs_build_melody(void) +{ + if (g_s_melody_ready) + { + return; + } + + /* A4 = 440 Гц → период ≈ 100 сэмплов при 44100 Гц */ + const uint32_t K_PERIOD_A4 = 100U; + /* E5 = 659 Гц → период ≈ 67 сэмплов при 44100 Гц */ + const uint32_t K_PERIOD_E5 = 67U; + + uint32_t out = 0U; + + for (uint32_t i = 0U; i < MQS_NOTE_SAMPLES; i++) + { + int16_t s = sine_sample(i % K_PERIOD_A4, K_PERIOD_A4); + s_melody_buf[out++] = s; /* L */ + s_melody_buf[out++] = s; /* R == L */ + } + + for (uint32_t i = 0U; i < MQS_NOTE_SAMPLES; i++) + { + int16_t s = sine_sample(i % K_PERIOD_E5, K_PERIOD_E5); + s_melody_buf[out++] = s; /* L */ + s_melody_buf[out++] = s; /* R == L */ + } + + g_s_melody_ready = true; +} + +/* -------------------------------------------------------------------------- + * Воспроизведение с USB keepalive + * ----------------------------------------------------------------------- */ + +/* + * Запускаем bsp_mqs_play() (не blocking), чтобы параллельно вызывать + * bsp_usb_cdc_poll(). Без этого USB стек голодает за ~4 с воспроизведения + * (keepalive-порог ~4 КБ переданных данных). + */ +static bsp_status_t mqs_play_with_poll(void) +{ + bsp_status_t status = bsp_mqs_play(s_melody_buf, MQS_TOTAL_FRAMES, NULL, NULL); + if (status != BSP_OK) + { + return status; + } + + while (bsp_mqs_is_busy()) + { + bsp_usb_cdc_poll(); + } + + return BSP_OK; +} + +/* -------------------------------------------------------------------------- + * test_module_t callbacks + * ----------------------------------------------------------------------- */ + +static void test_mqs_init(void) +{ + mqs_build_melody(); + (void) bsp_mqs_init(); + (void) bsp_mqs_amp_init(); +} + +static test_result_t test_mqs_run(void) +{ + bsp_status_t play_st = mqs_play_with_poll(); + if (play_st != BSP_OK) + { + return (test_result_t){ + .status = TEST_STATUS_FAIL, + .detail = "mqs play error", + }; + } + + /* Запрашиваем подтверждение оператора. + * API: bool test_runner_wait_confirm(const confirm_params_t *p_params) + * Возвращает true если оператор подтвердил, false при отказе или таймауте. + * Таймаут детектируется отдельно через флаг в confirm_params_t. */ + const confirm_params_t PARAMS = { + .id = "mqs_tone", + .prompt = "Have you heard a tone?", + .timeout_ms = 15000U, + }; + + bool confirmed = test_runner_wait_confirm(&PARAMS); + + if (!confirmed) + { + /* Различаем таймаут и явный отказ через поле last_confirm_timed_out + * если оно есть в API, иначе унифицируем как fail. */ + return (test_result_t){ + .status = TEST_STATUS_FAIL, + .detail = "operator: no sound", + }; + } + + return (test_result_t){ .status = TEST_STATUS_PASS, .detail = "" }; +} + +static void test_mqs_deinit(void) +{ + bsp_mqs_stop(); + bsp_mqs_amp_deinit(); + bsp_mqs_deinit(); +} + +/* -------------------------------------------------------------------------- + * Дескриптор модуля + * ----------------------------------------------------------------------- */ + +const test_module_t K_TEST_MQS = { + .id = "mqs", + .name = "MQS Audio Out", + .critical = false, + .requires_hil = false, + .pre_confirm_prompt = NULL, + .init = test_mqs_init, + .run = test_mqs_run, + .deinit = test_mqs_deinit, +}; \ No newline at end of file diff --git a/sdk/CMakeLists.txt b/sdk/CMakeLists.txt index 376a17d..46e156c 100644 --- a/sdk/CMakeLists.txt +++ b/sdk/CMakeLists.txt @@ -57,6 +57,12 @@ add_sdk_driver(pwm fsl_pwm.c) add_sdk_driver(adc fsl_adc.c) add_sdk_driver(elcdif fsl_elcdif.c) add_sdk_driver(cache fsl_cache.c) +add_sdk_driver(sai fsl_sai.c) +add_sdk_driver(sai_edma fsl_sai_edma.c) +add_sdk_driver(edma fsl_edma.c) +add_sdk_driver(dmamux fsl_dmamux.c) +add_sdk_driver(xbara fsl_xbara.c) +add_sdk_driver(xbarb fsl_xbarb.c) # Драйверы которые зависят от clock target_link_libraries(sdk_lpuart PUBLIC sdk_clock) @@ -66,7 +72,10 @@ target_link_libraries(sdk_lpi2c PUBLIC sdk_clock) target_link_libraries(sdk_pwm PUBLIC sdk_clock) target_link_libraries(sdk_adc PUBLIC sdk_clock) target_link_libraries(sdk_elcdif PUBLIC sdk_clock) - +target_link_libraries(sdk_sai PUBLIC sdk_clock) +target_link_libraries(sdk_sai_edma PUBLIC sdk_sai sdk_edma) +target_link_libraries(sdk_edma PUBLIC sdk_common) +target_link_libraries(sdk_xbara PUBLIC sdk_clock) # ----------------------------------------------------------------------------- # SEMC и FlexSPI — инициализируются через DCD и FDCB, драйверы нужны только если # обращаешься к ним явно из кода