Skip to content

Commit 3148b97

Browse files
committed
Add support for PC speaker using PWM
1 parent 4edc215 commit 3148b97

File tree

6 files changed

+303
-20
lines changed

6 files changed

+303
-20
lines changed

neotron-bmc-commands/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,24 @@ pub enum Command {
137137
/// * Length: 4
138138
/// * Mode: R/W
139139
I2cBaudRate = 0x64,
140+
/// # Speaker Duration
141+
/// Duration of note, in milliseconds
142+
/// * Length: 1
143+
/// * Mode: R/W
144+
SpeakerDuration = 0x70,
145+
/// # Speaker Period (High byte)
146+
/// High byte of period (in 48kHz ticks)
147+
/// * Length: 1
148+
/// * Mode: R/W
149+
SpeakerPeriodHigh = 0x71,
150+
/// # Speaker Period (High byte)
151+
/// High byte of period (in 48kHz ticks)
152+
/// * Length: 1
153+
/// * Mode: R/W
154+
SpeakerPeriodLow = 0x72,
155+
/// # Speaker Duty Cycle
156+
/// Speaker Duty cycle, in 1/255
157+
/// * Length: 1
158+
/// * Mode: R/W
159+
SpeakerDutyCycle = 0x73
140160
}

neotron-bmc-pico/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The MCU has:
2626
| 12 | PA6 | SPI1_CIPO | SPI Data Output |
2727
| 13 | PA7 | SPI1_COPI | SPI Data Input |
2828
| 14 | PB0 | LED | Output for Power LED |
29-
| 15 | PB1 | BUZZER | PWM Output for Buzzer |
29+
| 15 | PB1 | SPEAKER | PWM Output for Speaker |
3030
| 18 | PA8 | IRQ_nHOST | Interrupt Output to the Host (active low) |
3131
| 19 | PA9 | USART1_TX | UART Transmit Output |
3232
| 20 | PA10 | USART1_RX | UART Receive Input |

neotron-bmc-pico/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use panic_probe as _;
77
use stm32f0xx_hal as _; // memory layout // panic handler
88

99
pub mod ps2;
10+
pub mod speaker;
1011
pub mod spi;
1112

1213
// same panicking *behavior* as `panic-probe` but doesn't print a panic message

neotron-bmc-pico/src/main.rs

Lines changed: 132 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use heapless::spsc::{Consumer, Producer, Queue};
1717
use rtic::app;
1818
use stm32f0xx_hal::{
1919
gpio::gpioa::{PA10, PA11, PA12, PA15, PA2, PA3, PA4, PA8, PA9},
20-
gpio::gpiob::{PB0, PB1, PB3, PB4, PB5},
20+
gpio::gpiob::{PB0, PB3, PB4, PB5},
2121
gpio::gpiof::{PF0, PF1},
2222
gpio::{Alternate, Floating, Input, Output, PullDown, PullUp, PushPull, AF1},
2323
pac,
@@ -26,7 +26,7 @@ use stm32f0xx_hal::{
2626
};
2727

2828
use neotron_bmc_commands::Command;
29-
use neotron_bmc_pico as _;
29+
use neotron_bmc_pico::{self as _, speaker};
3030
use neotron_bmc_protocol as proto;
3131

3232
/// Version string auto-generated by git.
@@ -66,6 +66,8 @@ pub struct RegisterState {
6666
/// without re-doing a FIFO read. This happens if our response gets a CRC
6767
/// error.
6868
last_req: Option<proto::Request>,
69+
/// The config of the speaker
70+
speaker: speaker::RegisterState,
6971
}
7072

7173
#[app(device = crate::pac, peripherals = true, dispatchers = [USB, USART3_4_5_6, TIM14, TIM15, TIM16, TIM17, PVD])]
@@ -94,16 +96,17 @@ mod app {
9496
ResetButtonShortPress,
9597
/// The UART got some data
9698
UartByte(u8),
99+
/// The speaker's config should be reset
100+
SpeakerDisable,
97101
}
98102

99103
#[shared]
100104
struct Shared {
101105
/// The power LED (D1101)
102106
#[lock_free]
103107
led_power: PB0<Output<PushPull>>,
104-
/// The status LED (D1102)
105-
#[lock_free]
106-
_buzzer_pwm: PB1<Output<PushPull>>,
108+
/// The speaker (J1006)
109+
speaker: speaker::Hardware,
107110
/// The FTDI UART header (J105)
108111
#[lock_free]
109112
serial: serial::Serial<pac::USART1, PA9<Alternate<AF1>>, PA10<Alternate<AF1>>>,
@@ -219,7 +222,7 @@ mod app {
219222
_pin_uart_cts,
220223
_pin_uart_rts,
221224
mut led_power,
222-
mut _buzzer_pwm,
225+
_speaker_pwm,
223226
button_power,
224227
button_reset,
225228
mut pin_dc_on,
@@ -245,8 +248,8 @@ mod app {
245248
gpioa.pa12.into_alternate_af1(cs),
246249
// led_power,
247250
gpiob.pb0.into_push_pull_output(cs),
248-
// _buzzer_pwm,
249-
gpiob.pb1.into_push_pull_output(cs),
251+
// speaker_pwm,
252+
gpiob.pb1.into_alternate_af0(cs),
250253
// button_power,
251254
gpiof.pf0.into_pull_up_input(cs),
252255
// button_reset,
@@ -289,8 +292,6 @@ mod app {
289292
pin_irq.set_high().unwrap();
290293
// Power LED is off
291294
led_power.set_low().unwrap();
292-
// Buzzer is off
293-
_buzzer_pwm.set_low().unwrap();
294295

295296
defmt::info!("Creating UART...");
296297

@@ -306,6 +307,10 @@ mod app {
306307
&mut rcc,
307308
);
308309

310+
led_power.set_low().unwrap();
311+
312+
speaker::RegisterState::default().setup(&mut rcc, &dp.TIM14);
313+
309314
// Set EXTI15 to use PORT A (PA15) - button input
310315
dp.SYSCFG.exticr4.modify(|_r, w| w.exti15().pa15());
311316

@@ -336,7 +341,7 @@ mod app {
336341
_pin_uart_cts,
337342
_pin_uart_rts,
338343
led_power,
339-
_buzzer_pwm,
344+
speaker: speaker::Hardware::new(dp.TIM14),
340345
button_power,
341346
button_reset,
342347
state_dc_power_enabled: DcPowerState::Off,
@@ -367,7 +372,7 @@ mod app {
367372
/// Our idle task.
368373
///
369374
/// This task is called when there is nothing else to do.
370-
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset], local = [rcc, pin_irq])]
375+
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset, speaker], local = [pin_irq, rcc, speaker_task_handle: Option<speaker_pwm_stop::MyMono::SpawnHandle> = None])]
371376
fn idle(mut ctx: idle::Context) -> ! {
372377
// TODO: Get this from the VERSION static variable or from PKG_VERSION
373378
let mut register_state = RegisterState {
@@ -436,20 +441,23 @@ mod app {
436441
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::Off {
437442
defmt::info!("Power up requested!");
438443
// Button pressed - power on system.
439-
// Step 1 - Note our new power state
444+
// Step 1 - enable speaker and play power-up tune
445+
ctx.shared.speaker.lock(|speaker| speaker.enable());
446+
speaker_init_tune::spawn().unwrap();
447+
// Step 2 - Note our new power state
440448
ctx.shared
441449
.state_dc_power_enabled
442450
.lock(|r| *r = DcPowerState::Starting);
443-
// Step 2 - Hold reset line (active) low
451+
// Step 3 - Hold reset line (active) low
444452
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
445-
// Step 3 - Turn on PSU
453+
// Step 4 - Turn on PSU
446454
ctx.shared.pin_dc_on.set_high().unwrap();
447-
// Step 4 - Leave it in reset for a while.
455+
// Step 5 - Leave it in reset for a while.
448456
// TODO: Start monitoring 3.3V and 5.0V rails here
449457
// TODO: Take system out of reset when 3.3V and 5.0V are good
450458
// Returns an error if it's already scheduled (but we don't care)
451459
let _ = exit_reset::spawn_after(RESET_DURATION_MS.millis());
452-
// Set 5 - unmask the IRQ
460+
// Set 6 - unmask the IRQ
453461
irq_masked = false;
454462
}
455463
}
@@ -469,7 +477,13 @@ mod app {
469477
// Is the board powered on? Don't do a reset if it's powered off.
470478
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
471479
defmt::info!("Reset!");
472-
// Step 1 - Stop any SPI stuff that's currently going on (the host is about to be reset)
480+
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
481+
482+
// play power-up tune
483+
ctx.shared.speaker.lock(|speaker| speaker.enable());
484+
speaker_init_tune::spawn().unwrap();
485+
486+
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
473487
ctx.shared.spi.lock(|s| s.reset(&mut rcc));
474488
// Step 2 - Hold reset line (active) low
475489
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
@@ -532,10 +546,43 @@ mod app {
532546
// TODO: Copy byte to software buffer and turn UART RX
533547
// interrupt off if buffer is full
534548
}
549+
Some(Message::SpeakerDisable) => {
550+
defmt::trace!("Speaker disabled");
551+
ctx.shared.speaker.lock(|speaker| speaker.disable());
552+
register_state.speaker.set_duration(0);
553+
}
535554
None => {
536555
// No messages
537556
}
538557
}
558+
559+
// The speaker PWM needs to be updated (register was updated)
560+
if register_state.speaker.needs_update() {
561+
defmt::info!("speaker PWM update");
562+
register_state.speaker.set_needs_update(false);
563+
564+
let register = &mut register_state.speaker;
565+
let task_handle = ctx.local.speaker_task_handle.take();
566+
567+
let keep_playing = ctx.shared.speaker.lock(|speaker| {
568+
speaker.update(register, || {
569+
if let Some(h) = task_handle {
570+
// if there's a running "stop" task, reschedule it
571+
defmt::trace!("Speaker task cancelled!");
572+
h.cancel().unwrap_or_default();
573+
}
574+
})
575+
});
576+
577+
if keep_playing {
578+
// otherwise, spawn a new one
579+
defmt::trace!("Speaker task spawned!");
580+
ctx.local.speaker_task_handle.replace(
581+
speaker_pwm_stop::spawn_after((register.duration() as u64).millis())
582+
.unwrap(),
583+
);
584+
}
585+
}
539586
// TODO: Read ADC for 3.3V and 5.0V rails and check good
540587
}
541588
}
@@ -602,6 +649,28 @@ mod app {
602649
}
603650
}
604651

652+
/// Initialization melody, played directly by the BMC
653+
#[task(shared = [speaker, msg_q_in])]
654+
fn speaker_init_tune(mut ctx: speaker_init_tune::Context) {
655+
defmt::trace!("Playing startup tone");
656+
657+
ctx.shared.speaker.lock(|speaker| {
658+
// F4
659+
speaker.set_note(100, 137, 10);
660+
});
661+
662+
speaker_pwm_stop::spawn_after(100.millis()).unwrap();
663+
}
664+
/// Task which stops the speaker from playing
665+
#[task(shared = [msg_q_in])]
666+
fn speaker_pwm_stop(mut ctx: speaker_pwm_stop::Context) {
667+
defmt::trace!("Speaker stopped");
668+
let _ = ctx
669+
.shared
670+
.msg_q_in
671+
.lock(|q| q.enqueue(Message::SpeakerDisable));
672+
}
673+
605674
/// This is the SPI1 task.
606675
///
607676
/// It fires whenever there is new data received on SPI1. We should flag to the host
@@ -752,6 +821,9 @@ where
752821
// We were not sent what we were sent last time, so forget the previous request.
753822
register_state.last_req = None;
754823

824+
// temporary buffer to hold serialized data while the response is generated
825+
let mut data = [0u8; 1];
826+
755827
// What do they want?
756828
let rsp = match (req.request_type, Command::try_from(req.register)) {
757829
(proto::RequestType::Read, Ok(Command::ProtocolVersion))
@@ -803,6 +875,48 @@ where
803875
proto::Response::new_without_data(proto::ResponseResult::BadLength)
804876
}
805877
}
878+
(proto::RequestType::Read, Ok(Command::SpeakerDuration)) => {
879+
defmt::debug!("Reading speaker duration");
880+
data[0] = (register_state.speaker.duration() / 10) as u8;
881+
proto::Response::new_ok_with_data(&data)
882+
}
883+
(proto::RequestType::ShortWrite, Ok(Command::SpeakerDuration)) => {
884+
defmt::debug!("Writing speaker duration ({})", req.length_or_data);
885+
register_state
886+
.speaker
887+
.set_duration(req.length_or_data as u16 * 10);
888+
proto::Response::new_without_data(proto::ResponseResult::Ok)
889+
}
890+
(proto::RequestType::Read, Ok(Command::SpeakerPeriodHigh)) => {
891+
defmt::debug!("Reading speaker period (high)");
892+
data[0] = register_state.speaker.period_high();
893+
proto::Response::new_ok_with_data(&data)
894+
}
895+
(proto::RequestType::ShortWrite, Ok(Command::SpeakerPeriodHigh)) => {
896+
defmt::debug!("Writing speaker period (high = {})", req.length_or_data);
897+
register_state.speaker.set_period_low(req.length_or_data);
898+
proto::Response::new_without_data(proto::ResponseResult::Ok)
899+
}
900+
(proto::RequestType::Read, Ok(Command::SpeakerPeriodLow)) => {
901+
defmt::debug!("Reading speaker period (low)");
902+
data[0] = register_state.speaker.period_low();
903+
proto::Response::new_ok_with_data(&data)
904+
}
905+
(proto::RequestType::ShortWrite, Ok(Command::SpeakerPeriodLow)) => {
906+
defmt::debug!("Writing speaker period (low = {})", req.length_or_data);
907+
register_state.speaker.set_period_high(req.length_or_data);
908+
proto::Response::new_without_data(proto::ResponseResult::Ok)
909+
}
910+
(proto::RequestType::Read, Ok(Command::SpeakerDutyCycle)) => {
911+
defmt::debug!("Reading speaker duty cycle");
912+
data[0] = register_state.speaker.duty_cycle();
913+
proto::Response::new_ok_with_data(&data)
914+
}
915+
(proto::RequestType::ShortWrite, Ok(Command::SpeakerDutyCycle)) => {
916+
defmt::debug!("Writing speaker duty cycle ({})", req.length_or_data);
917+
register_state.speaker.set_duty_cycle(req.length_or_data);
918+
proto::Response::new_without_data(proto::ResponseResult::Ok)
919+
}
806920
_ => {
807921
// Sorry, that register / request type is not supported
808922
proto::Response::new_without_data(proto::ResponseResult::BadRegister)

0 commit comments

Comments
 (0)