|
| 1 | +#![no_std] |
| 2 | +#![no_main] |
| 3 | + |
| 4 | +// I2S `controller mode` demo |
| 5 | +// Generates Morse code audio signals for text from UART, playing back over I2S |
| 6 | +// Tested with nRF52840-DK and a UDA1334a DAC |
| 7 | + |
| 8 | +use embedded_hal::digital::v2::{InputPin, OutputPin}; |
| 9 | +use heapless::{ |
| 10 | + consts::*, |
| 11 | + spsc::{Consumer, Producer, Queue}, |
| 12 | +}; |
| 13 | +use small_morse::{encode, State}; |
| 14 | +use { |
| 15 | + core::{ |
| 16 | + panic::PanicInfo, |
| 17 | + sync::atomic::{compiler_fence, Ordering}, |
| 18 | + }, |
| 19 | + hal::{ |
| 20 | + gpio::{Input, Level, Output, Pin, PullUp, PushPull}, |
| 21 | + gpiote::*, |
| 22 | + i2s::*, |
| 23 | + pac::{TIMER0, UARTE0}, |
| 24 | + timer::Timer, |
| 25 | + uarte::*, |
| 26 | + }, |
| 27 | + nrf52840_hal as hal, |
| 28 | + rtic::cyccnt::U32Ext, |
| 29 | + rtt_target::{rprintln, rtt_init_print}, |
| 30 | +}; |
| 31 | + |
| 32 | +#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] |
| 33 | +const APP: () = { |
| 34 | + struct Resources { |
| 35 | + i2s: hal::i2s::I2S, |
| 36 | + #[init([0; 32])] |
| 37 | + signal_buf: [i16; 32], |
| 38 | + #[init([0; 32])] |
| 39 | + mute_buf: [i16; 32], |
| 40 | + #[init(None)] |
| 41 | + queue: Option<Queue<State, U256>>, |
| 42 | + producer: Producer<'static, State, U256>, |
| 43 | + consumer: Consumer<'static, State, U256>, |
| 44 | + #[init(5_000_000)] |
| 45 | + speed: u32, |
| 46 | + uarte: Uarte<UARTE0>, |
| 47 | + uarte_timer: Timer<TIMER0>, |
| 48 | + gpiote: Gpiote, |
| 49 | + btn1: Pin<Input<PullUp>>, |
| 50 | + btn2: Pin<Input<PullUp>>, |
| 51 | + led: Pin<Output<PushPull>>, |
| 52 | + } |
| 53 | + |
| 54 | + #[init(resources = [queue, signal_buf, mute_buf], spawn = [tick])] |
| 55 | + fn init(mut ctx: init::Context) -> init::LateResources { |
| 56 | + let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc(); |
| 57 | + // Enable the monotonic timer (CYCCNT) |
| 58 | + ctx.core.DCB.enable_trace(); |
| 59 | + ctx.core.DWT.enable_cycle_counter(); |
| 60 | + rtt_init_print!(); |
| 61 | + |
| 62 | + let p0 = hal::gpio::p0::Parts::new(ctx.device.P0); |
| 63 | + |
| 64 | + // Configure I2S controller |
| 65 | + let mck_pin = p0.p0_28.into_push_pull_output(Level::Low).degrade(); |
| 66 | + let sck_pin = p0.p0_29.into_push_pull_output(Level::Low).degrade(); |
| 67 | + let lrck_pin = p0.p0_31.into_push_pull_output(Level::Low).degrade(); |
| 68 | + let sdout_pin = p0.p0_30.into_push_pull_output(Level::Low).degrade(); |
| 69 | + |
| 70 | + let i2s = I2S::new_controller( |
| 71 | + ctx.device.I2S, |
| 72 | + Some(&mck_pin), |
| 73 | + &sck_pin, |
| 74 | + &lrck_pin, |
| 75 | + None, |
| 76 | + Some(&sdout_pin), |
| 77 | + ); |
| 78 | + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); |
| 79 | + i2s.enable().start(); |
| 80 | + |
| 81 | + // Fill signal buffer with triangle waveform, 2 channels interleaved |
| 82 | + let signal_buf = ctx.resources.signal_buf; |
| 83 | + let len = signal_buf.len() / 2; |
| 84 | + for x in 0..len { |
| 85 | + signal_buf[2 * x] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; |
| 86 | + signal_buf[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; |
| 87 | + } |
| 88 | + |
| 89 | + // Configure buttons |
| 90 | + let btn1 = p0.p0_11.into_pullup_input().degrade(); |
| 91 | + let btn2 = p0.p0_12.into_pullup_input().degrade(); |
| 92 | + let gpiote = Gpiote::new(ctx.device.GPIOTE); |
| 93 | + gpiote.port().input_pin(&btn1).low(); |
| 94 | + gpiote.port().input_pin(&btn2).low(); |
| 95 | + gpiote.port().enable_interrupt(); |
| 96 | + |
| 97 | + // Configure the onboard USB CDC UARTE |
| 98 | + let uarte = Uarte::new( |
| 99 | + ctx.device.UARTE0, |
| 100 | + Pins { |
| 101 | + txd: p0.p0_06.into_push_pull_output(Level::High).degrade(), |
| 102 | + rxd: p0.p0_08.into_floating_input().degrade(), |
| 103 | + cts: None, |
| 104 | + rts: None, |
| 105 | + }, |
| 106 | + Parity::EXCLUDED, |
| 107 | + Baudrate::BAUD115200, |
| 108 | + ); |
| 109 | + |
| 110 | + *ctx.resources.queue = Some(Queue::new()); |
| 111 | + let (producer, consumer) = ctx.resources.queue.as_mut().unwrap().split(); |
| 112 | + |
| 113 | + rprintln!("Morse code generator"); |
| 114 | + rprintln!("Send me text over UART @ 115_200 baud"); |
| 115 | + rprintln!("Press button 1 to slow down or button 2 to speed up"); |
| 116 | + |
| 117 | + ctx.spawn.tick().ok(); |
| 118 | + |
| 119 | + init::LateResources { |
| 120 | + i2s, |
| 121 | + producer, |
| 122 | + consumer, |
| 123 | + gpiote, |
| 124 | + btn1, |
| 125 | + btn2, |
| 126 | + led: p0.p0_13.into_push_pull_output(Level::High).degrade(), |
| 127 | + uarte, |
| 128 | + uarte_timer: Timer::new(ctx.device.TIMER0), |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + #[idle(resources=[uarte, uarte_timer, producer])] |
| 133 | + fn idle(ctx: idle::Context) -> ! { |
| 134 | + let idle::Resources { |
| 135 | + uarte, |
| 136 | + uarte_timer, |
| 137 | + producer, |
| 138 | + } = ctx.resources; |
| 139 | + let uarte_rx_buf = &mut [0u8; 64][..]; |
| 140 | + loop { |
| 141 | + match uarte.read_timeout(uarte_rx_buf, uarte_timer, 100_000) { |
| 142 | + Ok(_) => { |
| 143 | + if let Ok(msg) = core::str::from_utf8(&uarte_rx_buf[..]) { |
| 144 | + rprintln!("{}", msg); |
| 145 | + for action in encode(msg) { |
| 146 | + for _ in 0..action.duration { |
| 147 | + producer.enqueue(action.state).ok(); |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + Err(hal::uarte::Error::Timeout(n)) if n > 0 => { |
| 153 | + if let Ok(msg) = core::str::from_utf8(&uarte_rx_buf[0..n]) { |
| 154 | + rprintln!("{}", msg); |
| 155 | + for action in encode(msg) { |
| 156 | + for _ in 0..action.duration { |
| 157 | + producer.enqueue(action.state).ok(); |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | + } |
| 162 | + _ => {} |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + #[task(resources = [consumer, i2s, signal_buf, mute_buf, led, speed], schedule = [tick])] |
| 168 | + fn tick(ctx: tick::Context) { |
| 169 | + let i2s = ctx.resources.i2s; |
| 170 | + match ctx.resources.consumer.dequeue() { |
| 171 | + Some(State::On) => { |
| 172 | + // Move TX pointer to signal buffer (sound ON) |
| 173 | + i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); |
| 174 | + ctx.resources.led.set_low().ok(); |
| 175 | + } |
| 176 | + _ => { |
| 177 | + // Move TX pointer to silent buffer (sound OFF) |
| 178 | + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); |
| 179 | + ctx.resources.led.set_high().ok(); |
| 180 | + } |
| 181 | + } |
| 182 | + ctx.schedule |
| 183 | + .tick(ctx.scheduled + ctx.resources.speed.cycles()) |
| 184 | + .ok(); |
| 185 | + } |
| 186 | + |
| 187 | + #[task(binds = GPIOTE, resources = [gpiote, speed], schedule = [debounce])] |
| 188 | + fn on_gpiote(ctx: on_gpiote::Context) { |
| 189 | + ctx.resources.gpiote.reset_events(); |
| 190 | + ctx.schedule.debounce(ctx.start + 3_000_000.cycles()).ok(); |
| 191 | + } |
| 192 | + |
| 193 | + #[task(resources = [btn1, btn2, i2s, speed])] |
| 194 | + fn debounce(ctx: debounce::Context) { |
| 195 | + if ctx.resources.btn1.is_low().unwrap() { |
| 196 | + rprintln!("Go slower"); |
| 197 | + *ctx.resources.speed += 600_000; |
| 198 | + } |
| 199 | + if ctx.resources.btn2.is_low().unwrap() { |
| 200 | + rprintln!("Go faster"); |
| 201 | + *ctx.resources.speed -= 600_000; |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + extern "C" { |
| 206 | + fn SWI0_EGU0(); |
| 207 | + fn SWI1_EGU1(); |
| 208 | + } |
| 209 | +}; |
| 210 | + |
| 211 | +fn triangle_wave(x: i32, length: usize, amplitude: i32, phase: i32, periods: i32) -> i32 { |
| 212 | + let length = length as i32; |
| 213 | + amplitude |
| 214 | + - ((2 * periods * (x + phase + length / (4 * periods)) * amplitude / length) |
| 215 | + % (2 * amplitude) |
| 216 | + - amplitude) |
| 217 | + .abs() |
| 218 | + - amplitude / 2 |
| 219 | +} |
| 220 | + |
| 221 | +#[inline(never)] |
| 222 | +#[panic_handler] |
| 223 | +fn panic(info: &PanicInfo) -> ! { |
| 224 | + cortex_m::interrupt::disable(); |
| 225 | + rprintln!("{}", info); |
| 226 | + loop { |
| 227 | + compiler_fence(Ordering::SeqCst); |
| 228 | + } |
| 229 | +} |
0 commit comments