1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! High-level microphone capture API for the STM32H747I-DISCO onboard MEMS mic.
//!
//! Orchestrates SAI4 PDM + BDMA + CIC decimation filter to provide a simple
//! poll-based interface for reading PCM audio samples from the MP34DT05-A
//! digital microphone.
//!
//! # Memory requirements
//!
//! The PDM DMA buffers **must** reside in SRAM4 (`0x3800_0000`). The caller
//! is responsible for providing suitably placed buffers. SRAM4 is 64 KB and
//! not cached by the CM7 D-Cache, so no cache maintenance is needed.
//!
//! # Usage
//!
//! ```ignore
//! let mut mic = MicCapture::new();
//! mic.init(&mut pdm_buf0, &mut pdm_buf1, 64);
//! mic.start();
//! loop {
//! if let Some(count) = mic.poll(&mut pcm_out) {
//! // pcm_out[..count] contains 16-bit PCM samples
//! }
//! }
//! mic.stop();
//! ```
use crate::bdma::BdmaSai4Rx;
use crate::pdm_filter::CicFilter;
use crate::sai4_pdm::Sai4Pdm;
/// Microphone capture state.
#[derive(Clone, Copy, PartialEq, Eq)]
enum State {
Idle,
Ready,
Recording,
}
/// High-level microphone capture driver.
pub struct MicCapture {
sai: Sai4Pdm,
bdma: BdmaSai4Rx,
filter: CicFilter,
state: State,
/// Which half-buffer was last processed (0 or 1).
last_processed: u8,
/// Number of 16-bit PDM halfwords per buffer.
pdm_buf_len: usize,
}
impl MicCapture {
/// Create a new mic capture instance (unconfigured).
pub fn new() -> Self {
Self {
sai: Sai4Pdm::new(),
bdma: BdmaSai4Rx::new(),
filter: CicFilter::new(64),
state: State::Idle,
last_processed: 1, // so first poll processes buffer 0
pdm_buf_len: 0,
}
}
/// Initialize the capture pipeline.
///
/// - `pdm_buf0`, `pdm_buf1`: two equally-sized buffers in SRAM4 for PDM DMA
/// - `decimation`: CIC filter decimation ratio (64 or 128)
/// - `sai_clock_source`: kernel clock source for SAI4 (0=PLL1_Q, 1=PLL2_P, etc.)
/// - `mckdiv`: SAI4 master clock divider
///
/// After `init()`, call `start()` to begin recording.
pub fn init(
&mut self,
pdm_buf0: &mut [u16],
pdm_buf1: &mut [u16],
decimation: u32,
sai_clock_source: u8,
mckdiv: u8,
) {
assert_eq!(pdm_buf0.len(), pdm_buf1.len());
self.pdm_buf_len = pdm_buf0.len();
// Enable clocks
self.sai.enable_clock(sai_clock_source);
self.bdma.enable_clock();
// Configure SAI4 PDM
self.sai.configure(mckdiv);
// Configure BDMA
let buf_bytes = pdm_buf0.len() * 2;
self.bdma.configure(
self.sai.data_register_addr(),
pdm_buf0.as_mut_ptr(),
pdm_buf1.as_mut_ptr(),
buf_bytes,
);
// Set up CIC filter
self.filter = CicFilter::new(decimation);
self.state = State::Ready;
self.last_processed = 1;
}
/// Start recording.
pub fn start(&mut self) {
if self.state != State::Ready && self.state != State::Recording {
return;
}
self.filter.reset();
self.last_processed = 1;
// Enable DMA on SAI4
self.sai.enable_dma();
// Start BDMA first, then SAI
self.bdma.start();
self.sai.enable();
self.state = State::Recording;
}
/// Stop recording.
pub fn stop(&mut self) {
self.sai.disable_dma();
self.bdma.stop();
self.sai.disable();
self.state = State::Ready;
}
/// Poll for new PCM data.
///
/// When a PDM buffer has been filled by DMA, this method runs it through
/// the CIC decimation filter and writes PCM samples to `pcm_out`.
///
/// Returns `Some(n)` with the number of PCM samples written, or `None`
/// if no new data is available yet.
///
/// The `pdm_buf` parameter must be a reference to whichever SRAM4 buffer
/// was just completed by DMA. In practice, pass the buffer that
/// corresponds to the *non-active* DMA target.
pub fn poll_with_buffer(&mut self, pdm_buf: &[u16], pcm_out: &mut [i16]) -> Option<usize> {
if self.state != State::Recording {
return None;
}
// Check for transfer-complete (full buffer done)
let tc = self.bdma.transfer_complete();
let ht = self.bdma.half_transfer();
if !tc && !ht {
return None;
}
if tc {
self.bdma.clear_transfer_complete();
}
if ht {
self.bdma.clear_half_transfer();
}
// Clear any overrun
self.sai.clear_overrun();
// Run PDM data through CIC filter
let count = self.filter.process(pdm_buf, pcm_out);
Some(count)
}
/// Simple poll that checks DMA flags and returns which buffer index (0 or 1)
/// is ready for processing, or `None` if neither is ready.
pub fn poll_ready(&mut self) -> Option<u8> {
if self.state != State::Recording {
return None;
}
let tc = self.bdma.transfer_complete();
let ht = self.bdma.half_transfer();
if tc {
self.bdma.clear_transfer_complete();
self.sai.clear_overrun();
// TC means DMA just finished one full buffer and switched.
// The buffer that just completed is the opposite of current_target.
let completed = if self.bdma.current_target() == 0 {
1
} else {
0
};
if completed != self.last_processed {
self.last_processed = completed;
return Some(completed);
}
}
if ht {
self.bdma.clear_half_transfer();
// Half-transfer: first half of current buffer is done.
// For double-buffer mode, this is less commonly used but we
// handle it for completeness.
}
None
}
/// Returns a reference to the CIC filter for direct access.
pub fn filter(&mut self) -> &mut CicFilter {
&mut self.filter
}
/// Returns `true` if currently recording.
pub fn is_recording(&self) -> bool {
self.state == State::Recording
}
}