Skip to content

Commit dcb7f06

Browse files
bors[bot]YruamaLairbaburrbull
authored
Merge #637
637: Dual i2s core r=burrbull a=YruamaLairba Hi, i'm trying to implement the 'device' that is suitable for full duplex i2s communication. I'd like to have opinion about implementation, i'm not sure it follow the general style of the crate. I don't have example to follow, I'm in a unusual situation where the 'device' is composed by 2 peripherals. Co-authored-by: Yruama_Lairba <[email protected]> Co-authored-by: Andrey Zgarbul <[email protected]>
2 parents 0e48d22 + f8f1b71 commit dcb7f06

File tree

6 files changed

+594
-11
lines changed

6 files changed

+594
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
9+
- Integrate new version of stm32_i2s (v0.5) to enable full-duplex operation
10+
- Add a rtic example to show how to do full-duplex i2s
911

1012
### Changed
1113

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ version = "=1.0.0-alpha.8"
5757
package = "embedded-hal"
5858

5959
[dependencies.stm32_i2s_v12x]
60-
version = "0.4.0"
60+
version = "0.5.0"
6161
optional = true
6262

6363
[dev-dependencies]
@@ -414,6 +414,10 @@ required-features = ["stm32f411", "rtic"] # stm32f411
414414
name = "rtic-i2s-audio-in-out"
415415
required-features = ["stm32f411", "i2s", "rtic"]
416416

417+
[[example]]
418+
name = "rtic-dual-i2s-audio-in-out"
419+
required-features = ["stm32f411", "i2s", "rtic"]
420+
417421
[[example]]
418422
name = "rtic-serial-dma-rx-idle"
419423
required-features = ["stm32f411", "rtic"]
Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
//! # Full duplex I2s example with rtic
2+
//!
3+
//! This application show how to use DualI2sDriver with interruption to achieve a full duplex
4+
//! communication. Be careful to you ear, wrong operation can trigger loud noise on the DAC output.
5+
//!
6+
//! # Hardware required
7+
//!
8+
//! * a STM32F411 based board
9+
//! * I2S ADC and DAC, eg PCM1808 and PCM5102 from TI
10+
//! * Audio signal at ADC input, and something to ear at DAC output.
11+
//!
12+
//! # Hardware Wiring
13+
//!
14+
//! The wiring assume using PCM1808 and PCM5102 module that can be found on Aliexpress, ebay,
15+
//! Amazon...
16+
//!
17+
//! ## Stm32
18+
//!
19+
//! | stm32 | PCM1808 | PCM5102 |
20+
//! |-------|---------|---------|
21+
//! | pb12 | LRC | LCK |
22+
//! | pb13 | BCK | BCK |
23+
//! | pc6 | SCK | SCK |
24+
//! | pb14 | | DIN |
25+
//! | pb15 | OUT | |
26+
//!
27+
//! ## PCM1808 ADC module
28+
//!
29+
//! | Pin | Connected To |
30+
//! |-----|----------------|
31+
//! | LIN | audio in left |
32+
//! | - | audio in gnd |
33+
//! | RIN | audio in right |
34+
//! | FMT | Gnd or NC |
35+
//! | MD1 | Gnd or NC |
36+
//! | MD0 | Gnd or NC |
37+
//! | Gnd | Gnd |
38+
//! | 3.3 | +3V3 |
39+
//! | +5V | +5v |
40+
//! | BCK | pb13 |
41+
//! | OUT | pb15 |
42+
//! | LRC | pb12 |
43+
//! | SCK | pc6 |
44+
//!
45+
//! ## PCM5102 module
46+
//!
47+
//! | Pin | Connected to |
48+
//! |-------|-----------------|
49+
//! | SCK | pc6 |
50+
//! | BCK | pb13 |
51+
//! | DIN | pb14 |
52+
//! | LCK | pb12 |
53+
//! | GND | Gnd |
54+
//! | VIN | +3V3 |
55+
//! | FLT | Gnd or +3V3 |
56+
//! | DEMP | Gnd |
57+
//! | XSMT | +3V3 |
58+
//! | A3V3 | |
59+
//! | AGND | audio out gnd |
60+
//! | ROUT | audio out left |
61+
//! | LROUT | audio out right |
62+
//!
63+
//! Notes: on the module (not the chip) A3V3 is connected to VIN and AGND is connected to GND
64+
//!
65+
//!
66+
//! Expected behavior: you should ear a crappy stereo effect. This is actually 2 square tremolo
67+
//! applied with a 90 degrees phase shift.
68+
69+
#![no_std]
70+
#![no_main]
71+
72+
use core::panic::PanicInfo;
73+
use rtt_target::rprintln;
74+
75+
use stm32f4xx_hal as hal;
76+
77+
#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true,dispatchers = [EXTI0, EXTI1, EXTI2])]
78+
mod app {
79+
use core::fmt::Write;
80+
81+
use super::hal;
82+
83+
use hal::gpio::Edge;
84+
use hal::i2s::stm32_i2s_v12x::driver::*;
85+
use hal::i2s::DualI2s;
86+
use hal::pac::Interrupt;
87+
use hal::pac::{EXTI, SPI2};
88+
use hal::prelude::*;
89+
90+
use heapless::spsc::*;
91+
92+
use rtt_target::{rprintln, rtt_init, set_print_channel};
93+
94+
type DualI2s2Driver = DualI2sDriver<DualI2s<SPI2>, Master, Receive, Transmit, Philips>;
95+
96+
// Part of the frame we currently transmit or receive
97+
#[derive(Copy, Clone)]
98+
pub enum FrameState {
99+
LeftMsb,
100+
LeftLsb,
101+
RightMsb,
102+
RightLsb,
103+
}
104+
105+
use FrameState::{LeftLsb, LeftMsb, RightLsb, RightMsb};
106+
107+
impl Default for FrameState {
108+
fn default() -> Self {
109+
Self::LeftMsb
110+
}
111+
}
112+
#[shared]
113+
struct Shared {
114+
#[lock_free]
115+
i2s2_driver: DualI2s2Driver,
116+
#[lock_free]
117+
exti: EXTI,
118+
}
119+
120+
#[local]
121+
struct Local {
122+
logs_chan: rtt_target::UpChannel,
123+
adc_p: Producer<'static, (i32, i32), 2>,
124+
process_c: Consumer<'static, (i32, i32), 2>,
125+
process_p: Producer<'static, (i32, i32), 2>,
126+
dac_c: Consumer<'static, (i32, i32), 2>,
127+
}
128+
129+
#[init(local = [queue_1: Queue<(i32,i32), 2> = Queue::new(),queue_2: Queue<(i32,i32), 2> = Queue::new()])]
130+
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
131+
let queue_1 = cx.local.queue_1;
132+
let queue_2 = cx.local.queue_2;
133+
let channels = rtt_init! {
134+
up: {
135+
0: {
136+
size: 128
137+
name: "Logs"
138+
}
139+
1: {
140+
size: 128
141+
name: "Panics"
142+
}
143+
}
144+
};
145+
let logs_chan = channels.up.0;
146+
let panics_chan = channels.up.1;
147+
set_print_channel(panics_chan);
148+
let (adc_p, process_c) = queue_1.split();
149+
let (process_p, dac_c) = queue_2.split();
150+
let device = cx.device;
151+
let mut syscfg = device.SYSCFG.constrain();
152+
let mut exti = device.EXTI;
153+
let gpiob = device.GPIOB.split();
154+
let gpioc = device.GPIOC.split();
155+
let rcc = device.RCC.constrain();
156+
let clocks = rcc
157+
.cfgr
158+
.use_hse(8u32.MHz())
159+
.sysclk(96.MHz())
160+
.hclk(96.MHz())
161+
.pclk1(50.MHz())
162+
.pclk2(100.MHz())
163+
.i2s_clk(61440.kHz())
164+
.freeze();
165+
166+
// I2S pins: (WS, CK, MCLK, SD) for I2S2
167+
let i2s2_pins = (
168+
gpiob.pb12, //WS
169+
gpiob.pb13, //CK
170+
gpioc.pc6, //MCK
171+
gpiob.pb15, //SD
172+
gpiob.pb14, //ExtSD
173+
);
174+
let i2s2 = DualI2s::new(device.SPI2, device.I2S2EXT, i2s2_pins, &clocks);
175+
let i2s2_config = DualI2sDriverConfig::new_master()
176+
.direction(Receive, Transmit)
177+
.standard(Philips)
178+
.data_format(DataFormat::Data24Channel32)
179+
.master_clock(true)
180+
.request_frequency(48_000);
181+
let mut i2s2_driver = DualI2sDriver::new(i2s2, i2s2_config);
182+
rprintln!("actual sample rate is {}", i2s2_driver.sample_rate());
183+
i2s2_driver.main().set_rx_interrupt(true);
184+
i2s2_driver.main().set_error_interrupt(true);
185+
i2s2_driver.ext().set_tx_interrupt(true);
186+
i2s2_driver.ext().set_error_interrupt(true);
187+
188+
// set up an interrupt on WS pin
189+
let ws_pin = i2s2_driver.ws_pin_mut();
190+
ws_pin.make_interrupt_source(&mut syscfg);
191+
ws_pin.trigger_on_edge(&mut exti, Edge::Rising);
192+
// we will enable the ext part in interrupt
193+
ws_pin.enable_interrupt(&mut exti);
194+
195+
i2s2_driver.main().enable();
196+
197+
(
198+
Shared { i2s2_driver, exti },
199+
Local {
200+
logs_chan,
201+
adc_p,
202+
process_c,
203+
process_p,
204+
dac_c,
205+
},
206+
init::Monotonics(),
207+
)
208+
}
209+
210+
#[idle(shared = [], local = [])]
211+
fn idle(_cx: idle::Context) -> ! {
212+
#[allow(clippy::empty_loop)]
213+
loop {}
214+
}
215+
216+
// Printing message directly in a i2s interrupt can cause timing issues.
217+
#[task(capacity = 10, local = [logs_chan])]
218+
fn log(cx: log::Context, message: &'static str) {
219+
writeln!(cx.local.logs_chan, "{}", message).unwrap();
220+
}
221+
222+
// processing audio
223+
#[task(binds = SPI5, local = [count: u32 = 0,process_c,process_p])]
224+
fn process(cx: process::Context) {
225+
let count = cx.local.count;
226+
let process_c = cx.local.process_c;
227+
let process_p = cx.local.process_p;
228+
while let Some(mut smpl) = process_c.dequeue() {
229+
let period = 24000;
230+
if *count > period / 2 {
231+
smpl.0 >>= 1;
232+
}
233+
if *count > period / 4 && *count <= period * 3 / 4 {
234+
smpl.1 >>= 1;
235+
}
236+
*count += 1;
237+
if *count >= period {
238+
*count = 0;
239+
}
240+
process_p.enqueue(smpl).ok();
241+
}
242+
}
243+
244+
#[task(
245+
priority = 4,
246+
binds = SPI2,
247+
local = [
248+
main_frame_state: FrameState = LeftMsb,
249+
main_frame: (u32,u32) = (0,0),
250+
ext_frame_state: FrameState = LeftMsb,
251+
ext_frame: (u32,u32) = (0,0),
252+
adc_p,
253+
dac_c
254+
],
255+
shared = [i2s2_driver, exti]
256+
)]
257+
fn i2s2(cx: i2s2::Context) {
258+
let i2s2_driver = cx.shared.i2s2_driver;
259+
260+
// handling "main" part
261+
let main_frame_state = cx.local.main_frame_state;
262+
let main_frame = cx.local.main_frame;
263+
let adc_p = cx.local.adc_p;
264+
let status = i2s2_driver.main().status();
265+
// It's better to read first to avoid triggering ovr flag
266+
if status.rxne() {
267+
let data = i2s2_driver.main().read_data_register();
268+
match (*main_frame_state, status.chside()) {
269+
(LeftMsb, Channel::Left) => {
270+
main_frame.0 = (data as u32) << 16;
271+
*main_frame_state = LeftLsb;
272+
}
273+
(LeftLsb, Channel::Left) => {
274+
main_frame.0 |= data as u32;
275+
*main_frame_state = RightMsb;
276+
}
277+
(RightMsb, Channel::Right) => {
278+
main_frame.1 = (data as u32) << 16;
279+
*main_frame_state = RightLsb;
280+
}
281+
(RightLsb, Channel::Right) => {
282+
main_frame.1 |= data as u32;
283+
// defer sample processing to another task
284+
let (l, r) = *main_frame;
285+
adc_p.enqueue((l as i32, r as i32)).ok();
286+
rtic::pend(Interrupt::SPI5);
287+
*main_frame_state = LeftMsb;
288+
}
289+
// in case of ovr this resynchronize at start of new main_frame
290+
_ => *main_frame_state = LeftMsb,
291+
}
292+
}
293+
if status.ovr() {
294+
log::spawn("i2s2 Overrun").ok();
295+
// sequence to delete ovr flag
296+
i2s2_driver.main().read_data_register();
297+
i2s2_driver.main().status();
298+
}
299+
300+
// handling "ext" part
301+
let ext_frame_state = cx.local.ext_frame_state;
302+
let ext_frame = cx.local.ext_frame;
303+
let dac_c = cx.local.dac_c;
304+
let exti = cx.shared.exti;
305+
let status = i2s2_driver.ext().status();
306+
// it's better to write data first to avoid to trigger udr flag
307+
if status.txe() {
308+
let data;
309+
match (*ext_frame_state, status.chside()) {
310+
(LeftMsb, Channel::Left) => {
311+
let (l, r) = dac_c.dequeue().unwrap_or_default();
312+
*ext_frame = (l as u32, r as u32);
313+
data = (ext_frame.0 >> 16) as u16;
314+
*ext_frame_state = LeftLsb;
315+
}
316+
(LeftLsb, Channel::Left) => {
317+
data = (ext_frame.0 & 0xFFFF) as u16;
318+
*ext_frame_state = RightMsb;
319+
}
320+
(RightMsb, Channel::Right) => {
321+
data = (ext_frame.1 >> 16) as u16;
322+
*ext_frame_state = RightLsb;
323+
}
324+
(RightLsb, Channel::Right) => {
325+
data = (ext_frame.1 & 0xFFFF) as u16;
326+
*ext_frame_state = LeftMsb;
327+
}
328+
// in case of udr this resynchronize tracked and actual channel
329+
_ => {
330+
*ext_frame_state = LeftMsb;
331+
data = 0; //garbage data to avoid additional underrun
332+
}
333+
}
334+
i2s2_driver.ext().write_data_register(data);
335+
}
336+
if status.fre() {
337+
log::spawn("i2s2 Frame error").ok();
338+
i2s2_driver.ext().disable();
339+
i2s2_driver.ws_pin_mut().enable_interrupt(exti);
340+
}
341+
if status.udr() {
342+
log::spawn("i2s2 udr").ok();
343+
i2s2_driver.ext().status();
344+
i2s2_driver.ext().write_data_register(0);
345+
}
346+
}
347+
348+
// Look WS line for the "ext" part (re) synchronisation
349+
#[task(priority = 4, binds = EXTI15_10, shared = [i2s2_driver,exti])]
350+
fn exti15_10(cx: exti15_10::Context) {
351+
let i2s2_driver = cx.shared.i2s2_driver;
352+
let exti = cx.shared.exti;
353+
let ws_pin = i2s2_driver.ws_pin_mut();
354+
// check if that pin triggered the interrupt
355+
if ws_pin.check_interrupt() {
356+
// Here we know ws pin is high because the interrupt was triggerd by it's rising edge
357+
ws_pin.clear_interrupt_pending_bit();
358+
ws_pin.disable_interrupt(exti);
359+
i2s2_driver.ext().write_data_register(0);
360+
i2s2_driver.ext().enable();
361+
}
362+
}
363+
}
364+
365+
#[inline(never)]
366+
#[panic_handler]
367+
fn panic(info: &PanicInfo) -> ! {
368+
rprintln!("{}", info);
369+
loop {} // You might need a compiler fence in here.
370+
}

0 commit comments

Comments
 (0)