diff --git a/stereo_mix/drivers/adc.cc b/stereo_mix/drivers/adc.cc index b47e9ea..6d2f609 100755 --- a/stereo_mix/drivers/adc.cc +++ b/stereo_mix/drivers/adc.cc @@ -2,10 +2,6 @@ #include "peripherals.h" #include -extern "C" { -void DMA_TransferComplete(DMA_HandleTypeDef* dma); // declared in stereo_mix.cc -} - namespace stereo_mix { Adc::Adc() diff --git a/stereo_mix/drivers/adc.h b/stereo_mix/drivers/adc.h index 9c91074..87c17b5 100644 --- a/stereo_mix/drivers/adc.h +++ b/stereo_mix/drivers/adc.h @@ -35,7 +35,21 @@ class Adc { void OnDMATransferComplete(); inline const uint16_t* values() { return &values_[0]; } - inline int32_t value(int32_t channel) const + + inline int16_t cv_value(AdcChannel channel) + { +#ifdef NEW_HARDWARE + return this->values_[ADC_GROUP_CV + channel] - 32768; +#else + if (channel >= ADC_CHANNEL_PAN_1) { + return this->values_[ADC_GROUP_CV + channel] - 32768; + } else { + return this->values_[ADC_GROUP_CV + channel] >> 1; + } +#endif + } + + inline uint16_t value(int32_t channel) const { return static_cast(values_[channel]); } diff --git a/stereo_mix/drivers/dac.h b/stereo_mix/drivers/dac.h index c74a1b1..c0d83d8 100644 --- a/stereo_mix/drivers/dac.h +++ b/stereo_mix/drivers/dac.h @@ -13,11 +13,12 @@ class Dac { // MCP4xx2 dac implementation : ssGpioPort(ssGpioPort_) , ssGpioPin(ssGpioPin_) { + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_SPI1_CLK_ENABLE(); + ssGpioPort = ssGpioPort_; ssGpioPin = ssGpioPin_; // init SS/CS/RST GPIO - __HAL_RCC_GPIOA_CLK_ENABLE(); - __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef gpio_init; gpio_init.Mode = GPIO_MODE_OUTPUT_PP; @@ -38,10 +39,6 @@ class Dac { // MCP4xx2 dac implementation HAL_GPIO_Init(GPIOB, &gpio_init); // init SPI - __HAL_RCC_SPI1_CLK_ENABLE(); - // HAL_SPI_DeInit(&spi); - - // Initialize SPI TODO: check which config we need hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.DataSize = SPI_DATASIZE_16BIT; diff --git a/stereo_mix/drivers/leds.h b/stereo_mix/drivers/leds.h index 51795ec..7fd2cfe 100644 --- a/stereo_mix/drivers/leds.h +++ b/stereo_mix/drivers/leds.h @@ -22,18 +22,23 @@ class Leds { gpioInit.Pull = GPIO_NOPULL; gpioInit.Speed = GPIO_SPEED_FREQ_MEDIUM; HAL_GPIO_Init(kGpioPorts[i], &gpioInit); + + intensities[i] = 0; + blinking[i] = false; } } void Write() { pwm_counter++; pwm_counter &= 0x1ff; // equals to if(pwm_counter > 512) pwm_counter = 0; - + blink_counter++; + blink_counter &= 0x7FFF; for (size_t i = 0; i < kNumChannels; i++) { - if (intensities[0] && lut_led_gamma[intensities[i]] >= pwm_counter) { - HAL_GPIO_WritePin(kGpioPorts[i], kGpioPins[i], GPIO_PIN_SET); + bool in_blink_phase = blink_counter < 16383 || !blinking[i]; + if (intensities[0] && lut_led_gamma[intensities[i]] >= pwm_counter && in_blink_phase) { + kGpioPorts[i]->BSRR |= kGpioPins[i]; } else { - HAL_GPIO_WritePin(kGpioPorts[i], kGpioPins[i], GPIO_PIN_RESET); + kGpioPorts[i]->BRR |= kGpioPins[i]; } } } @@ -43,8 +48,16 @@ class Leds { return; intensities[channel] = intensity; } + void set_blinking(uint8_t channel, bool blink) + { + if (channel >= kNumChannels) + return; + blinking[channel] = blink; + } private: uint16_t pwm_counter; + uint16_t blink_counter; uint8_t intensities[kNumChannels]; + bool blinking[kNumChannels]; }; diff --git a/stereo_mix/pot_controller.h b/stereo_mix/pot_controller.h new file mode 100644 index 0000000..d64d324 --- /dev/null +++ b/stereo_mix/pot_controller.h @@ -0,0 +1,170 @@ +// Copyright 2015 Emilie Gillet. +// +// Author: Emilie Gillet (emilie.o.gillet@gmail.com) +// modified for ints by Jan-Henrik Bruhn (hi@jhbruhn.de) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Handles the "hold button to tweak a hidden parameter" behaviour of pots. +// A pot can be in 4 states: +// - POT_STATE_TRACKING: the main parameter tracks the position of the pot. +// - POT_STATE_LOCKING: the main parameter no longer tracks the position of +// the pot. We wait for the pot to move further from its original position +// to start modifying the hidden parameter. +// - POT_STATE_HIDDEN_PARAMETER: the hidden parameter tracks the position of +// the pot. +// - POT_STATE_CATCHING_UP: the pot adjusts the main parameter in a relative +// way, until the position of the pot and the value of the parameter match +// again. + +#pragma once + +#include "stmlib/stmlib.h" +#include "stmlib/dsp/dsp.h" + + +enum PotState { + POT_STATE_TRACKING, + POT_STATE_LOCKING, + POT_STATE_HIDDEN_PARAMETER, + POT_STATE_CATCHING_UP +}; + +class PotController { + public: + PotController() { } + ~PotController() { } + + inline void Init( + uint16_t* main_parameter, + uint16_t* hidden_parameter // this pot controller always works on values between 0 and 65535 + ) { + state_ = POT_STATE_TRACKING; + was_catching_up_ = false; + + main_parameter_ = main_parameter; + hidden_parameter_ = hidden_parameter; + + value_ = 0; + stored_value_ = 0; + previous_value_ = 0; + } + + inline void Lock() { + if (state_ == POT_STATE_LOCKING || state_ == POT_STATE_HIDDEN_PARAMETER) { + return; + } + if (hidden_parameter_) { + was_catching_up_ = state_ == POT_STATE_CATCHING_UP; + state_ = POT_STATE_LOCKING; + } + } + + + inline bool editing_hidden_parameter() const { + return state_ == POT_STATE_HIDDEN_PARAMETER; + } + + inline void Unlock() { + if (state_ == POT_STATE_HIDDEN_PARAMETER || was_catching_up_) { + state_ = POT_STATE_CATCHING_UP; + } else { + state_ = POT_STATE_TRACKING; + } + } + + inline void Realign() { + state_ = POT_STATE_TRACKING; + } + + inline void ProcessControlRate(uint16_t adc_value) { + value_ += (adc_value - value_) >> 6; + CONSTRAIN(value_, 0, 65535); + // approximately this: + // ONE_POLE(value_, adc_value, 0.01f); + + if (state_ == POT_STATE_TRACKING) { + *main_parameter_ = value_; + } + } + + inline void ProcessUIRate() { + switch (state_) { + case POT_STATE_TRACKING: + previous_value_ = value_; + break; + + case POT_STATE_LOCKING: + if (abs(value_ - previous_value_) > 1966) { + stored_value_ = previous_value_; + CONSTRAIN(value_, 0, 65535); + *hidden_parameter_ = value_; + state_ = POT_STATE_HIDDEN_PARAMETER; + previous_value_ = value_; + } + break; + + case POT_STATE_HIDDEN_PARAMETER: + CONSTRAIN(value_, 0, 65535); + *hidden_parameter_ = value_; + previous_value_ = value_; + break; + + case POT_STATE_CATCHING_UP: + { + if (abs(value_ - previous_value_) > 327) { + int32_t delta = value_ - previous_value_; + + int32_t skew_ratio = delta > 0 + ? (65600 - stored_value_) / (65535 - previous_value_) + : (66 + stored_value_) / (66 + previous_value_); + CONSTRAIN(skew_ratio, 6553, 655350); + + stored_value_ += (skew_ratio * delta) >> 11; + CONSTRAIN(stored_value_, 0, 65535); + + if (abs(stored_value_ - value_) < 327) { + state_ = POT_STATE_TRACKING; + } + previous_value_ = value_; + *main_parameter_ = stored_value_; + } + } + break; + } + } + + private: + PotState state_; + bool was_catching_up_; + + uint16_t* main_parameter_; + uint16_t* hidden_parameter_; + int32_t value_; + int32_t stored_value_; + int32_t previous_value_; + + DISALLOW_COPY_AND_ASSIGN(PotController); +}; + + diff --git a/stereo_mix/stereo_mix.cc b/stereo_mix/stereo_mix.cc index 0cfa75b..0006bb7 100644 --- a/stereo_mix/stereo_mix.cc +++ b/stereo_mix/stereo_mix.cc @@ -5,6 +5,7 @@ #include "drivers/switches.h" #include "resources.h" #include "stmlib/ui/event_queue.h" +#include "ui.h" #include using namespace stereo_mix; @@ -14,14 +15,11 @@ Dac dacs[8] = { { GPIOB, GPIO_PIN_8 }, { GPIOB, GPIO_PIN_9 }, { GPIOB, GPIO_PIN_10 }, { GPIOB, GPIO_PIN_11 }, { GPIOA, GPIO_PIN_8 }, { GPIOA, GPIO_PIN_9 }, { GPIOA, GPIO_PIN_10 }, { GPIOA, GPIO_PIN_11 } }; + Adc adc; Leds leds; Switches switches; - -const int32_t kLongPressDuration = 2000; -EventQueue<> queue; -uint32_t press_time[SWITCH_COUNT]; -bool ignore_release[SWITCH_COUNT]; +UI ui(&adc, &switches, &leds); bool mute[4]; @@ -65,28 +63,7 @@ void SysTick_Handler() system_clock.Tick(); switches.Debounce(); - - for (size_t i = 0; i < SWITCH_COUNT; i++) { - if (switches.just_pressed(Switch(i))) { - queue.AddEvent(CONTROL_SWITCH, i, 0); - press_time[i] = HAL_GetTick(); - ignore_release[i] = false; - } - if (switches.pressed(Switch(i)) && !ignore_release[i]) { - int32_t pressed_time = HAL_GetTick() - press_time[i]; - if (pressed_time > kLongPressDuration) { - queue.AddEvent(CONTROL_SWITCH, i, pressed_time); - ignore_release[i] = true; - } - } - if (switches.released(Switch(i)) && !ignore_release[i]) { - queue.AddEvent( - CONTROL_SWITCH, - i, - system_clock.milliseconds() - press_time[i] + 1); - ignore_release[i] = true; - } - } + ui.Poll(); } void SystemClock_Config(void) @@ -124,7 +101,8 @@ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { adc.OnDMATransferComplete(); } -void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) // called with 1kHz (OPTIMIZE!) the display should get its own spi bus + +void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) { if (htim != &htim3) { return; @@ -135,18 +113,25 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) // called with 1kHz void Init(void) { + __HAL_RCC_SYSCFG_CLK_ENABLE(); + __HAL_RCC_PWR_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_TIM3_CLK_ENABLE(); + __HAL_RCC_SPI1_CLK_ENABLE(); + HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM3_IRQn); - __HAL_RCC_TIM3_CLK_ENABLE(); - htim3.Init.Prescaler = 14; + htim3.Init.Prescaler = 10; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 128; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.RepetitionCounter = 0; HAL_TIM_Base_Init(&htim3); HAL_TIM_Base_Start_IT(&htim3); + system_clock.Init(); - queue.Init(); } void WriteOutputs(void) @@ -157,8 +142,8 @@ void WriteOutputs(void) uint16_t pan_pot = adc.value(ADC_GROUP_POT + ADC_CHANNEL_PAN_1 + i) >> (16 - 12); // adc is only 12 bit anyways uint16_t vol_pot = adc.value(ADC_GROUP_POT + ADC_CHANNEL_VOL_1 + i) >> (16 - 12); - int16_t pan_cv = (adc.value(ADC_GROUP_CV + ADC_CHANNEL_PAN_1 + i) - 32768) >> (16 - 12); - uint16_t vol_cv = adc.value(ADC_GROUP_CV + ADC_CHANNEL_VOL_1 + i) >> (16 - 12); + int16_t pan_cv = adc.cv_value(AdcChannel(ADC_CHANNEL_PAN_1 + i)) >> (16 - 12); + int16_t vol_cv = adc.cv_value(AdcChannel(ADC_CHANNEL_VOL_1 + i)) >> (16 - 12); int32_t pan = pan_pot + pan_cv; int32_t vol = vol_pot + vol_cv; @@ -167,7 +152,7 @@ void WriteOutputs(void) CONSTRAIN(pan, 0, (1 << 12) - 1); CONSTRAIN(vol, 0, (1 << 12) - 1); - leds.set_intensity(i, vol >> 4); + // leds.set_intensity(i, vol >> 4); value_l = (lut_left_sin_pan[pan] * lut_linear_to_exp[vol]) >> 16; value_r = (lut_right_cos_pan[pan] * lut_linear_to_exp[vol]) >> 16; @@ -178,36 +163,16 @@ void WriteOutputs(void) } } -void HandleEvent(void) -{ - while (queue.available()) { - Event ev = queue.PullEvent(); - switch (ev.control_type) { - case CONTROL_SWITCH: - if (ev.data != 0) { // switch released - mute[ev.control_id] = !mute[ev.control_id]; - } - break; - default: - break; - } - } -} - int main(void) { HAL_Init(); SystemClock_Config(); - __HAL_RCC_SYSCFG_CLK_ENABLE(); - __HAL_RCC_PWR_CLK_ENABLE(); - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); Init(); while (true) { - HandleEvent(); + ui.DoEvents(); WriteOutputs(); } diff --git a/stereo_mix/ui.cc b/stereo_mix/ui.cc new file mode 100644 index 0000000..34cd3a1 --- /dev/null +++ b/stereo_mix/ui.cc @@ -0,0 +1,84 @@ +#include "ui.h" +#include "pot_controller.h" +#include + +const int kLongPressDuration = 2000; + +void UI::Poll() +{ + for (size_t i = 0; i < SWITCH_COUNT; i++) { + if (switches->just_pressed(Switch(i))) { + queue.AddEvent(CONTROL_SWITCH, i, 0); + press_time[i] = HAL_GetTick(); + ignore_release[i] = false; + potControllers[0].Lock(); + } + if (switches->pressed(Switch(i)) && !ignore_release[i]) { + bool suppress_release_hidden_parameters = false; + for (size_t j = 0; j < kNumChannels * 2; j++) { + suppress_release_hidden_parameters |= potControllers[j].editing_hidden_parameter(); + } + int32_t pressed_time = HAL_GetTick() - press_time[i]; + if (!suppress_release_hidden_parameters && pressed_time > kLongPressDuration) { + queue.AddEvent(CONTROL_SWITCH, i, pressed_time); + ignore_release[i] = true; + } + } + if (switches->released(Switch(i))) { + bool suppress_release_hidden_parameters = false; + for (size_t j = 0; j < kNumChannels * 2; j++) { + suppress_release_hidden_parameters |= potControllers[j].editing_hidden_parameter(); + } + if (!suppress_release_hidden_parameters && !ignore_release[i]) { + queue.AddEvent( + CONTROL_SWITCH, + i, + system_clock.milliseconds() - press_time[i] + 1); + ignore_release[i] = true; + } + potControllers[0].Unlock(); + } + } + + for (size_t i = 0; i < kNumChannels * 2; i++) { + potControllers[i].ProcessControlRate(adc->value(ADC_GROUP_POT + i)); + } +} + +void UI::OnSwitchPressed(const Event& e) +{ +} + +void UI::OnSwitchReleased(const Event& e) +{ + mute[e.control_id] = !mute[e.control_id]; +} + +void UI::DoEvents() +{ + for (size_t i = 0; i < kNumChannels * 2; i++) { + potControllers[i].ProcessUIRate(); + + if (potControllers[i].editing_hidden_parameter()) { + leds->set_intensity(i, abs(volume_att_pots[i] - 32767) >> 7); + leds->set_blinking(i, volume_att_pots[i] - 32767 < 0); + } else { + leds->set_intensity(i, mute[i] ? 0 : volume_pots[i] >> 8); + leds->set_blinking(i, false); + } + } + while (queue.available()) { + Event ev = queue.PullEvent(); + switch (ev.control_type) { + case CONTROL_SWITCH: + if (ev.data != 0) { + OnSwitchReleased(ev); + } else { + OnSwitchPressed(ev); + } + break; + default: + break; + } + } +} diff --git a/stereo_mix/ui.h b/stereo_mix/ui.h new file mode 100644 index 0000000..2cd64f8 --- /dev/null +++ b/stereo_mix/ui.h @@ -0,0 +1,50 @@ +#pragma once + +#include "drivers/adc.h" +#include "drivers/leds.h" +#include "drivers/switches.h" +#include "pot_controller.h" +#include "stmlib/ui/event_queue.h" + +using namespace stmlib; + +extern const uint8_t kNumChannels; // TODO + +class UI { + public: + UI(Adc* adc_, Switches* switches_, Leds* leds_) + : adc(adc_) + , switches(switches_) + , leds(leds_) + { + queue.Init(); + + for (size_t i = 0; i < kNumChannels; i++) { + potControllers[i].Init(&volume_pots[i], &volume_att_pots[i]); + potControllers[i + kNumChannels].Init(&pan_pots[i], &pan_att_pots[i]); + + volume_att_pots[i] = pan_att_pots[i] = 65535; + } + }; + void Poll(); + void DoEvents(); + + private: + void OnSwitchPressed(const Event&); + void OnSwitchReleased(const Event&); + + EventQueue<> queue; + uint32_t press_time[SWITCH_COUNT]; + bool ignore_release[SWITCH_COUNT]; + Adc* adc; + Switches* switches; + Leds* leds; + + uint16_t volume_pots[kNumChannels]; + uint16_t pan_pots[kNumChannels]; + uint16_t volume_att_pots[kNumChannels]; + uint16_t pan_att_pots[kNumChannels]; + bool mute[kNumChannels]; + + PotController potControllers[kNumChannels * 2]; // todo: count +};