213 lines
6.1 KiB
Rust
213 lines
6.1 KiB
Rust
#![no_std]
|
|
#![no_main]
|
|
|
|
use panic_semihosting as _;
|
|
use rtic::app;
|
|
use stm32f1xx_hal::{
|
|
adc,
|
|
dma,
|
|
pac::{ADC1, TIM2},
|
|
timer::monotonic::MonoTimer,
|
|
prelude::*,
|
|
gpio::{self, Analog},
|
|
serial::{Config, Serial},
|
|
};
|
|
use embedded_midi::{self, MidiMessage};
|
|
|
|
// Number of control knobs
|
|
const CONTROL_CNT: usize = 9;
|
|
|
|
// Concrete midi out type alias
|
|
type MidiOut = embedded_midi::MidiOut<
|
|
stm32f1xx_hal::serial::Tx<
|
|
stm32f1xx_hal::pac::USART1
|
|
>
|
|
>;
|
|
|
|
// Adc channels to scan
|
|
pub struct AdcPins(
|
|
gpio::gpioa::PA0<Analog>,
|
|
gpio::gpioa::PA1<Analog>,
|
|
gpio::gpioa::PA2<Analog>,
|
|
gpio::gpioa::PA3<Analog>,
|
|
gpio::gpioa::PA4<Analog>,
|
|
gpio::gpioa::PA5<Analog>,
|
|
gpio::gpioa::PA6<Analog>,
|
|
gpio::gpioa::PA7<Analog>,
|
|
gpio::gpiob::PB0<Analog>,
|
|
);
|
|
|
|
impl adc::SetChannels<AdcPins> for adc::Adc<ADC1>
|
|
{
|
|
fn set_samples(&mut self) {
|
|
for ch in 0..(CONTROL_CNT - 1) as u8 {
|
|
self.set_channel_sample_time(ch, adc::SampleTime::T_28);
|
|
}
|
|
}
|
|
fn set_sequence(&mut self) {
|
|
self.set_regular_sequence(&[0, 1, 2, 3, 4, 5, 6, 7, 8]);
|
|
//self.set_continuous_mode(true);
|
|
}
|
|
}
|
|
|
|
// San DMA recources, cunsumed by the DMA controller
|
|
pub struct ScanDmaResources(
|
|
adc::Adc<ADC1>,
|
|
AdcPins,
|
|
dma::dma1::C1,
|
|
);
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub struct MidiControl{
|
|
value: u8, // Actually a 7 bit value
|
|
update: bool,
|
|
}
|
|
|
|
#[app(
|
|
device = stm32f1xx_hal::pac,
|
|
peripherals = true,
|
|
dispatchers = [SPI1]
|
|
)]
|
|
mod app {
|
|
use super::*;
|
|
|
|
#[shared]
|
|
struct Shared {
|
|
controls: [MidiControl; 9],
|
|
}
|
|
|
|
#[local]
|
|
struct Local {
|
|
dma_buffer: Option<&'static mut [u16; CONTROL_CNT]>,
|
|
scan_dma_rescources: Option<ScanDmaResources>,
|
|
midi_out: MidiOut,
|
|
}
|
|
|
|
#[monotonic(priority = 1, binds = TIM2, default = true)]
|
|
type MainMono = MonoTimer<TIM2, 1_000>;
|
|
|
|
#[init(local = [buffer: [u16; CONTROL_CNT] = [0u16; CONTROL_CNT]])]
|
|
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
|
|
|
let mut flash = cx.device.FLASH.constrain();
|
|
let rcc = cx.device.RCC.constrain();
|
|
|
|
let clocks = rcc
|
|
.cfgr
|
|
.use_hse(8.MHz())
|
|
.sysclk(48.MHz())
|
|
.adcclk(2.MHz())
|
|
.freeze(&mut flash.acr);
|
|
|
|
// Timer with 1 ms resolution
|
|
let mono = cx.device.TIM2.monotonic::<1_000>(&clocks);
|
|
|
|
let mut afio = cx.device.AFIO.constrain();
|
|
|
|
let mut gpioa = cx.device.GPIOA.split();
|
|
let tx = gpioa.pa9.into_alternate_open_drain(&mut gpioa.crh);
|
|
let rx = gpioa.pa10;
|
|
|
|
let usart = Serial::new(
|
|
cx.device.USART1,
|
|
(tx, rx),
|
|
&mut afio.mapr,
|
|
Config::default().baudrate(31250.bps()).parity_none(),
|
|
&clocks,
|
|
);
|
|
|
|
let mut gpiob = cx.device.GPIOB.split();
|
|
|
|
// SPI 2, PB13, PB15
|
|
let adc_pins = AdcPins(
|
|
gpioa.pa0.into_analog(&mut gpioa.crl),
|
|
gpioa.pa1.into_analog(&mut gpioa.crl),
|
|
gpioa.pa2.into_analog(&mut gpioa.crl),
|
|
gpioa.pa3.into_analog(&mut gpioa.crl),
|
|
gpioa.pa4.into_analog(&mut gpioa.crl),
|
|
gpioa.pa5.into_analog(&mut gpioa.crl),
|
|
gpioa.pa6.into_analog(&mut gpioa.crl),
|
|
gpioa.pa7.into_analog(&mut gpioa.crl),
|
|
gpiob.pb0.into_analog(&mut gpiob.crl),
|
|
);
|
|
let adc1 = adc::Adc::adc1(cx.device.ADC1, clocks);
|
|
let dma_ch1 = cx.device.DMA1.split().1;
|
|
|
|
let scan_dma_rescources = ScanDmaResources(adc1, adc_pins, dma_ch1);
|
|
|
|
let (tx, _rx) = usart.split();
|
|
let midi_out = MidiOut::new(tx);
|
|
|
|
read_adc::spawn().unwrap();
|
|
|
|
(
|
|
Shared {
|
|
controls: [
|
|
MidiControl { value: 0, update: false };
|
|
CONTROL_CNT
|
|
],
|
|
},
|
|
Local {
|
|
dma_buffer: Some(cx.local.buffer),
|
|
scan_dma_rescources: Some(scan_dma_rescources),
|
|
midi_out,
|
|
},
|
|
init::Monotonics(mono)
|
|
)
|
|
}
|
|
|
|
// This can be implemented non-blocking (use the DMA1 interrupt)
|
|
// Is it worth the effort?
|
|
#[task(local = [scan_dma_rescources, dma_buffer], shared = [controls])]
|
|
fn read_adc(mut cx: read_adc::Context) {
|
|
let resources = cx.local.scan_dma_rescources;
|
|
let ScanDmaResources(adc, pins, dma) = resources.take().unwrap();
|
|
let adc_scan = adc.with_scan_dma(pins, dma);
|
|
// Get DMA buffer
|
|
let buffer = cx.local.dma_buffer.take().unwrap();
|
|
// Perform read
|
|
let (buffer, adc_scan) = adc_scan.read(buffer).wait();
|
|
// Put the recources back in place
|
|
let (adc, pins, dma) = adc_scan.split();
|
|
resources.replace(ScanDmaResources(adc, pins, dma));
|
|
// Convert adc values to midi control values and check if they changed
|
|
cx.shared.controls.lock(|controls| {
|
|
for i in 0..controls.len() {
|
|
let value = adc_to_midi(&buffer[i]);
|
|
if controls[i].value != value {
|
|
controls[i].value = value;
|
|
controls[i].update = true;
|
|
}
|
|
}
|
|
});
|
|
// Put the buffer back in place
|
|
cx.local.dma_buffer.replace(buffer);
|
|
|
|
send_control_changes::spawn().unwrap();
|
|
}
|
|
|
|
#[task(local = [midi_out], shared = [controls])]
|
|
fn send_control_changes(mut cx: send_control_changes::Context) {
|
|
cx.shared.controls.lock(|controls| {
|
|
for i in 0..controls.len() {
|
|
if controls[i].update {
|
|
let event = MidiMessage::ControlChange(
|
|
0u8.into(),
|
|
(i as u8).into(),
|
|
controls[i].value.into()
|
|
);
|
|
cx.local.midi_out.write(&event).unwrap();
|
|
controls[i].update = false;
|
|
}
|
|
}
|
|
});
|
|
read_adc::spawn_after(100.millis()).unwrap();
|
|
}
|
|
}
|
|
|
|
// Converts a 12 bit adc value to a 7 bit midi control value
|
|
fn adc_to_midi(adc_val: &u16) -> u8{
|
|
((adc_val & 0x0FFF) >> 5) as u8
|
|
}
|
|
|