|
| 1 | +/* |
| 2 | + * Copyright (C) EdgeTX |
| 3 | + * |
| 4 | + * Based on code named |
| 5 | + * opentx - https://github.com/opentx/opentx |
| 6 | + * th9x - http://code.google.com/p/th9x |
| 7 | + * er9x - http://code.google.com/p/er9x |
| 8 | + * gruvin9x - http://code.google.com/p/gruvin9x |
| 9 | + * |
| 10 | + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html |
| 11 | + * |
| 12 | + * This program is free software; you can redistribute it and/or modify |
| 13 | + * it under the terms of the GNU General Public License version 2 as |
| 14 | + * published by the Free Software Foundation. |
| 15 | + * |
| 16 | + * This program is distributed in the hope that it will be useful, |
| 17 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | + * GNU General Public License for more details. |
| 20 | + */ |
| 21 | + |
| 22 | +#include "board.h" |
| 23 | + |
| 24 | +#include "hal/gpio.h" |
| 25 | +#include "stm32_gpio.h" |
| 26 | +#include "stm32_dma.h" |
| 27 | +#include "stm32_hal_ll.h" |
| 28 | + |
| 29 | +#if defined(PDM_CAPTURE_DMA) |
| 30 | + |
| 31 | +static constexpr uint32_t PDM_SAI_MCKDIV = |
| 32 | + PDM_SAI_KER_FREQ / PDM_CLOCK_FREQ; |
| 33 | + |
| 34 | +static_assert(PDM_SAI_MCKDIV >= 1 && PDM_SAI_MCKDIV <= 63, |
| 35 | + "PDM_SAI MCKDIV out of range for SAI_xCR1_MCKDIV (6 bits)"); |
| 36 | + |
| 37 | +// Burst size: ~6 ms of PDM at 1.6 MHz (multiple of 32 required for word packing). |
| 38 | +static constexpr uint32_t PDM_BURST_BITS = 10080; |
| 39 | +static constexpr uint32_t PDM_BURST_WORDS = (PDM_BURST_BITS + 31) / 32; |
| 40 | + |
| 41 | +static uint32_t pdmBurstBuf[PDM_BURST_WORDS]; |
| 42 | +static uint8_t lastSoundLevel = 0; |
| 43 | + |
| 44 | +// 40000 bytes at 1.6 MHz = ~25 ms ring; gives ~21 ms slack for audio-task stalls. |
| 45 | +static constexpr uint32_t PDM_RING_BYTES = 40000; |
| 46 | +static uint8_t pdmRingBuf[PDM_RING_BYTES] __DMA_NO_CACHE; |
| 47 | +static uint32_t pdmRingReadPos = 0; |
| 48 | + |
| 49 | +// CIC integrator + comb state (used by pdmConvertToPCM, defined further down). |
| 50 | +// Declared here so pdmStart() can zero them at session boundaries — without a |
| 51 | +// reset the leftover integrator state from the previous session would emit a |
| 52 | +// DC transient on the first decoded burst. |
| 53 | +static int32_t cic_i1 = 0, cic_i2 = 0, cic_i3 = 0; |
| 54 | +static int32_t cic_p1 = 0, cic_p2 = 0, cic_p3 = 0; |
| 55 | +static uint32_t cic_bitsSeen = 0; |
| 56 | + |
| 57 | +static bool pdmRunning = false; |
| 58 | + |
| 59 | +void pdmStart() |
| 60 | +{ |
| 61 | + if (pdmRunning) return; |
| 62 | + |
| 63 | + // Per-session state reset. Must happen before the DMA starts producing data |
| 64 | + // so the audio-task path never sees a half-reset filter. |
| 65 | + cic_i1 = cic_i2 = cic_i3 = 0; |
| 66 | + cic_p1 = cic_p2 = cic_p3 = 0; |
| 67 | + cic_bitsSeen = 0; |
| 68 | + pdmRingReadPos = 0; |
| 69 | + lastSoundLevel = 0; |
| 70 | + |
| 71 | + gpio_init_af(PDM_CLOCK, PDM_CLOCK_GPIO_AF, GPIO_PIN_SPEED_VERY_HIGH); |
| 72 | + gpio_init(PDM_DATA, GPIO_IN, GPIO_PIN_SPEED_VERY_HIGH); |
| 73 | + |
| 74 | + SET_BIT(RCC->APB2ENR, RCC_APB2ENR_SAI1EN); |
| 75 | + (void)READ_BIT(RCC->APB2ENR, RCC_APB2ENR_SAI1EN); |
| 76 | + |
| 77 | + SAI_Block_TypeDef* block = PDM_SAI_BLOCK; |
| 78 | + |
| 79 | + CLEAR_BIT(block->CR1, SAI_xCR1_SAIEN); |
| 80 | + while (READ_BIT(block->CR1, SAI_xCR1_SAIEN)) {} |
| 81 | + |
| 82 | + block->CR1 = (0U << SAI_xCR1_MODE_Pos) // Master TX |
| 83 | + | (0U << SAI_xCR1_PRTCFG_Pos) // Free protocol |
| 84 | + | (4U << SAI_xCR1_DS_Pos) // 16-bit data |
| 85 | + | SAI_xCR1_NODIV // BCLK = ker_ck / MCKDIV |
| 86 | + | (PDM_SAI_MCKDIV << SAI_xCR1_MCKDIV_Pos); |
| 87 | + |
| 88 | + block->CR2 = (1U << SAI_xCR2_FTH_Pos); // FIFO threshold = 1/4 full |
| 89 | + |
| 90 | + block->FRCR = (15U << 0) // FRL = 15 (16-bit frame) |
| 91 | + | (7U << 8); // FSALL = 7 |
| 92 | + block->SLOTR = (0U << 8) // NBSLOT = 0 -> 1 slot |
| 93 | + | (1U << 16); // SLOTEN slot 0 |
| 94 | + |
| 95 | + // Prime the FIFO so the block starts clocking immediately (no underrun). |
| 96 | + for (int i = 0; i < 4; ++i) block->DR = 0U; |
| 97 | + |
| 98 | + SET_BIT(block->CR1, SAI_xCR1_SAIEN); |
| 99 | + |
| 100 | + // TIM15 is free because FLYSKY_GIMBAL is OFF. |
| 101 | + SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM15EN); |
| 102 | + (void)READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM15EN); |
| 103 | + |
| 104 | + stm32_dma_enable_clock(PDM_CAPTURE_DMA); |
| 105 | + |
| 106 | + PDM_CAPTURE_TIMER->CR1 = 0; |
| 107 | + PDM_CAPTURE_TIMER->PSC = 0; |
| 108 | + PDM_CAPTURE_TIMER->ARR = (PDM_CAPTURE_TIMER_FREQ / PDM_CLOCK_FREQ) - 1U; |
| 109 | + PDM_CAPTURE_TIMER->CNT = 0; |
| 110 | + PDM_CAPTURE_TIMER->EGR = TIM_EGR_UG; // load PSC/ARR |
| 111 | + PDM_CAPTURE_TIMER->SR = 0; // clear update flag |
| 112 | + PDM_CAPTURE_TIMER->DIER = TIM_DIER_UDE; // fire DMA request on every UEV |
| 113 | + |
| 114 | + LL_DMA_DeInit(PDM_CAPTURE_DMA, PDM_CAPTURE_DMA_STREAM); |
| 115 | + |
| 116 | + LL_DMA_InitTypeDef dmaInit; |
| 117 | + LL_DMA_StructInit(&dmaInit); |
| 118 | + dmaInit.PeriphRequest = PDM_CAPTURE_DMA_REQUEST; |
| 119 | + dmaInit.Mode = LL_DMA_MODE_CIRCULAR; |
| 120 | + dmaInit.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY; |
| 121 | + dmaInit.PeriphOrM2MSrcAddress = (uintptr_t)&PDM_DATA_GPIO_PORT->IDR; |
| 122 | + dmaInit.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; |
| 123 | + dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE; |
| 124 | + dmaInit.MemoryOrM2MDstAddress = (uintptr_t)pdmRingBuf; |
| 125 | + dmaInit.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; |
| 126 | + dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE; |
| 127 | + dmaInit.NbData = PDM_RING_BYTES; |
| 128 | + dmaInit.Priority = LL_DMA_PRIORITY_HIGH; |
| 129 | + LL_DMA_Init(PDM_CAPTURE_DMA, PDM_CAPTURE_DMA_STREAM, &dmaInit); |
| 130 | + |
| 131 | + LL_DMA_EnableStream(PDM_CAPTURE_DMA, PDM_CAPTURE_DMA_STREAM); |
| 132 | + |
| 133 | + PDM_CAPTURE_TIMER->CR1 |= TIM_CR1_CEN; |
| 134 | + |
| 135 | + pdmRunning = true; |
| 136 | +} |
| 137 | + |
| 138 | +void pdmStop() |
| 139 | +{ |
| 140 | + if (!pdmRunning) return; |
| 141 | + |
| 142 | + // Stop DMA pacing first so no further requests fire. |
| 143 | + PDM_CAPTURE_TIMER->CR1 &= ~TIM_CR1_CEN; |
| 144 | + PDM_CAPTURE_TIMER->DIER = 0; |
| 145 | + |
| 146 | + // Disable the DMA stream and wait for it to actually stop. A subsequent |
| 147 | + // pdmStart() calls LL_DMA_DeInit which expects the stream idle. |
| 148 | + LL_DMA_DisableStream(PDM_CAPTURE_DMA, PDM_CAPTURE_DMA_STREAM); |
| 149 | + while (LL_DMA_IsEnabledStream(PDM_CAPTURE_DMA, PDM_CAPTURE_DMA_STREAM)) {} |
| 150 | + |
| 151 | + // Disable the SAI block — this stops the PDM clock to the mic, putting it |
| 152 | + // into low-power mode. |
| 153 | + SAI_Block_TypeDef* block = PDM_SAI_BLOCK; |
| 154 | + CLEAR_BIT(block->CR1, SAI_xCR1_SAIEN); |
| 155 | + while (READ_BIT(block->CR1, SAI_xCR1_SAIEN)) {} |
| 156 | + |
| 157 | + pdmRunning = false; |
| 158 | +} |
| 159 | + |
| 160 | +static uint32_t pdmRingAvailable() |
| 161 | +{ |
| 162 | + const uint32_t ndtr = |
| 163 | + LL_DMA_GetDataLength(PDM_CAPTURE_DMA, PDM_CAPTURE_DMA_STREAM); |
| 164 | + const uint32_t writePos = PDM_RING_BYTES - ndtr; |
| 165 | + if (writePos >= pdmRingReadPos) return writePos - pdmRingReadPos; |
| 166 | + return PDM_RING_BYTES - pdmRingReadPos + writePos; |
| 167 | +} |
| 168 | + |
| 169 | +bool pdmCapture() |
| 170 | +{ |
| 171 | + uint32_t avail = pdmRingAvailable(); |
| 172 | + if (avail < PDM_BURST_BITS) return false; |
| 173 | + |
| 174 | + // Skip ahead if DMA is about to overwrite unread data. |
| 175 | + if (avail > PDM_RING_BYTES - PDM_BURST_BITS) { |
| 176 | + const uint32_t target_avail = PDM_BURST_BITS + PDM_BURST_BITS / 2; |
| 177 | + const uint32_t skip = avail - target_avail; |
| 178 | + pdmRingReadPos = (pdmRingReadPos + skip) % PDM_RING_BYTES; |
| 179 | + } |
| 180 | + |
| 181 | + static_assert(PDM_BURST_BITS % 32U == 0U, |
| 182 | + "PDM_BURST_BITS must be a multiple of 32"); |
| 183 | + |
| 184 | + uint32_t src = pdmRingReadPos; |
| 185 | + for (uint32_t w = 0; w < PDM_BURST_WORDS; ++w) { |
| 186 | + uint32_t acc = 0; |
| 187 | + for (uint32_t b = 0; b < 32; ++b) { |
| 188 | + const uint8_t s = pdmRingBuf[src]; |
| 189 | + if (++src >= PDM_RING_BYTES) src = 0; |
| 190 | + acc = (acc << 1) | ((s >> PDM_DATA_GPIO_PIN) & 1U); |
| 191 | + } |
| 192 | + pdmBurstBuf[w] = acc; |
| 193 | + } |
| 194 | + |
| 195 | + pdmRingReadPos = src; |
| 196 | + return true; |
| 197 | +} |
| 198 | + |
| 199 | +bool pdmUpdateSoundLevel() |
| 200 | +{ |
| 201 | + if (!pdmCapture()) return false; |
| 202 | + |
| 203 | + // Ones-density of the PDM bitstream approximates signal amplitude. |
| 204 | + uint32_t ones = 0; |
| 205 | + for (uint32_t w = 0; w < PDM_BURST_WORDS; ++w) { |
| 206 | + ones += __builtin_popcount(pdmBurstBuf[w]); |
| 207 | + } |
| 208 | + |
| 209 | + const uint32_t total = PDM_BURST_WORDS * 32U; |
| 210 | + const int32_t mid = (int32_t)(total / 2U); |
| 211 | + int32_t dev = (int32_t)ones - mid; |
| 212 | + if (dev < 0) dev = -dev; |
| 213 | + |
| 214 | + uint32_t level = (uint32_t)dev * 200U / total; |
| 215 | + if (level > 100U) level = 100U; |
| 216 | + lastSoundLevel = (uint8_t)level; |
| 217 | + return true; |
| 218 | +} |
| 219 | + |
| 220 | +uint8_t pdmGetSoundLevel() |
| 221 | +{ |
| 222 | + return lastSoundLevel; |
| 223 | +} |
| 224 | + |
| 225 | +// --------------------------------------------------------------------------- |
| 226 | +// PDM -> PCM conversion: 3rd-order CIC decimator (R = PDM_PCM_DECIMATION). |
| 227 | +// |
| 228 | +// Structure (Hogenauer): |
| 229 | +// integrator ×3 at 1 MHz -> down-sample by R -> comb ×3 at 1 MHz / R |
| 230 | +// |
| 231 | +// Integrators run on 1-bit input (0 or 1), so for R=64 and N=3 the worst-case |
| 232 | +// magnitude that has to be representable is R^N = 262 144 — fits easily in |
| 233 | +// int32_t. 2's-complement wrap-around through the integrators is intentional |
| 234 | +// and cancels in the comb stage. |
| 235 | +// |
| 236 | +// DC gain of the filter is R^N. Silence (50/50 bitstream) therefore sits at |
| 237 | +// R^N / 2, and the useful signal swing is ±R^N / 2. We subtract the DC offset |
| 238 | +// and scale so that full swing maps to the int16 range. |
| 239 | +// --------------------------------------------------------------------------- |
| 240 | + |
| 241 | +// PDM_POST_GAIN_SHIFT is defined in pdm_software_driver.h so trimSilence |
| 242 | +// can scale its threshold from the same value. |
| 243 | + |
| 244 | +// CIC integrator + comb state is declared near the top of the file (so |
| 245 | +// pdmStart() can reset it on session boundaries). |
| 246 | + |
| 247 | +uint32_t pdmConvertToPCM(int16_t* pcm, uint32_t max) |
| 248 | +{ |
| 249 | + if (!pcm || max == 0) return 0; |
| 250 | + |
| 251 | + constexpr uint32_t R = PDM_PCM_DECIMATION; |
| 252 | + constexpr int32_t G = (int32_t)(R * R * R); |
| 253 | + constexpr int32_t DC = G / 2; |
| 254 | + |
| 255 | + // floor_log2(DC) - 14: ensures (c3 - DC) >> SCALE_SHIFT fits in int16. |
| 256 | + // Works for any R, not just powers of two. |
| 257 | + constexpr int SCALE_SHIFT = (31 - __builtin_clz((unsigned)(DC))) - 14; |
| 258 | + static_assert(SCALE_SHIFT >= 0, "Decimation too small to fit PCM range"); |
| 259 | + |
| 260 | + uint32_t produced = 0; |
| 261 | + const uint32_t totalBits = PDM_BURST_WORDS * 32U; |
| 262 | + |
| 263 | + for (uint32_t idx = 0; idx < totalBits && produced < max; ++idx) { |
| 264 | + const uint32_t word = pdmBurstBuf[idx >> 5]; |
| 265 | + const int32_t bit = (int32_t)((word >> (31U - (idx & 31U))) & 1U); |
| 266 | + |
| 267 | + cic_i1 += bit; |
| 268 | + cic_i2 += cic_i1; |
| 269 | + cic_i3 += cic_i2; |
| 270 | + |
| 271 | + if (++cic_bitsSeen == R) { |
| 272 | + cic_bitsSeen = 0; |
| 273 | + |
| 274 | + const int32_t c1 = cic_i3 - cic_p1; cic_p1 = cic_i3; |
| 275 | + const int32_t c2 = c1 - cic_p2; cic_p2 = c1; |
| 276 | + const int32_t c3 = c2 - cic_p3; cic_p3 = c2; |
| 277 | + |
| 278 | + int32_t s = (c3 - DC) >> SCALE_SHIFT; |
| 279 | + s <<= PDM_POST_GAIN_SHIFT; |
| 280 | + if (s > 32767) s = 32767; |
| 281 | + if (s < -32768) s = -32768; |
| 282 | + pcm[produced++] = (int16_t)s; |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + return produced; |
| 287 | +} |
| 288 | + |
| 289 | +#endif // PDM_CAPTURE_DMA |
0 commit comments