large update

This commit is contained in:
2024-02-04 09:59:54 -06:00
parent a3aec45452
commit 266537ff33
14 changed files with 580 additions and 4 deletions

File diff suppressed because one or more lines are too long

14
src/ActionId.hpp Normal file
View File

@@ -0,0 +1,14 @@
#ifndef H_C91B532FD0D0486EAE4B48A326F9CA8C
#define H_C91B532FD0D0486EAE4B48A326F9CA8C
#include <cstdint>
namespace Configuration
{
struct ActionId
{
uint32_t value;
};
}
#endif //H_C91B532FD0D0486EAE4B48A326F9CA8C

30
src/Audio.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include "Audio.hpp"
//extern "C"
void audio_interrupt_handler()
{
//api seems to require I check channels 1 at a time
for (int i = 0; i < 12; i++)
{
if (dma_irqn_get_channel_status(_BaseAudio::IRQ_NUM, i))
{
auto aud = _BaseAudio::audio_callback_items[i];
if (aud)
aud->perform_interrupt();
dma_irqn_acknowledge_channel(_BaseAudio::IRQ_NUM, i);
}
}
}
void _BaseAudio::enable_handler()
{
static bool handler_enabled = false;
if (!handler_enabled)
{
handler_enabled = true;
irq_add_shared_handler(IRQ_NUM, audio_interrupt_handler, 200);
irq_set_enabled(IRQ_NUM, true);
}
}
std::array<_BaseAudio*, 12> _BaseAudio::audio_callback_items;

165
src/Audio.hpp Normal file
View File

@@ -0,0 +1,165 @@
#ifndef H_DB5EAFBD40EE4ACDADB973F02E1A4AF1
#define H_DB5EAFBD40EE4ACDADB973F02E1A4AF1
#include <array>
#include <cstdint>
#include <hardware/dma.h>
#include <hardware/irq.h>
//extern "C"
void audio_interrupt_handler();
//allows audio classes of different sizes to share interrupt
class _BaseAudio
{
public:
using interrupt_t = void(*)(uint8_t* write_to, uint8_t* dont_write_to, size_t size);
static constexpr float sys_tick_freq = 133'000'000;
static constexpr unsigned int IRQ_NUM = DMA_IRQ_1;
virtual void perform_interrupt() = 0;
static std::array<_BaseAudio*, 12> audio_callback_items;
protected:
friend void audio_interrupt_handler();
static void enable_handler();
};
template <std::size_t buffer_size>
class Audio : public _BaseAudio
{
public:
//claims and configures dma resources
bool init(void* write_address)
{
deinit();
dma_channel_a = dma_claim_unused_channel(false);
if (dma_channel_a < 0)
return false;
audio_callback_items[dma_channel_a] = this;
dma_channel_b = dma_claim_unused_channel(false);
if (dma_channel_b < 0)
{
dma_channel_unclaim(dma_channel_a);
audio_callback_items[dma_channel_a] = nullptr;
dma_channel_a = -1;
return false;
}
audio_callback_items[dma_channel_b] = this;
dma_channel_config dconf;
channel_config_set_read_increment(&dconf, true);
channel_config_set_write_increment(&dconf, false);
timer = dma_claim_unused_timer(false);
if (timer < 0)
{
dma_channel_unclaim(dma_channel_a);
dma_channel_unclaim(dma_channel_b);
audio_callback_items[dma_channel_a] = audio_callback_items[dma_channel_b] = nullptr;
dma_channel_a = dma_channel_b = -1;
return false;
}
channel_config_set_dreq(&dconf, dma_get_timer_dreq(timer));
channel_config_set_transfer_data_size(&dconf, DMA_SIZE_8);
//create a second config so the triggers can point to each other
dma_channel_config dconf_b = dconf;
channel_config_set_chain_to(&dconf, dma_channel_b);
channel_config_set_chain_to(&dconf, dma_channel_a);
dma_channel_configure(dma_channel_a, &dconf, write_address, bankA.data(), buffer_size, false);
dma_channel_configure(dma_channel_b, &dconf_b, write_address, bankB.data(), buffer_size, false);
enable_handler();
dma_irqn_set_channel_enabled(IRQ_NUM, dma_channel_a, true);
dma_irqn_set_channel_enabled(IRQ_NUM, dma_channel_b, true);
return true;
}
void deinit()
{
if (dma_channel_a >= 0)
{
dma_channel_cleanup(dma_channel_a);
dma_channel_unclaim(dma_channel_a);
dma_irqn_set_channel_enabled(IRQ_NUM, dma_channel_a, false);
audio_callback_items[dma_channel_a] = nullptr;
dma_channel_a = -1;
}
if (dma_channel_b >= 0)
{
dma_channel_cleanup(dma_channel_b);
dma_channel_unclaim(dma_channel_b);
dma_irqn_set_channel_enabled(IRQ_NUM, dma_channel_a, false);
audio_callback_items[dma_channel_b] = nullptr;
dma_channel_b = -1;
}
if (timer >= 0)
{
dma_timer_unclaim(timer);
timer = -1;
}
}
bool is_initialized() const
{
return dma_channel_a >= 0 && dma_channel_b >= 0;
}
std::array<uint8_t, buffer_size>& get_inactive_buffer()
{
return bankA_active ? bankB : bankA;
}
std::array<uint8_t, buffer_size>& get_active_buffer()
{
return bankA_active ? bankA : bankB;
}
//this callback is responsible for loading data into the unused buffer
//(or triggering it to happen outside of interrupt context)
void set_callback(interrupt_t new_callback)
{
callback = new_callback;
}
//need an algorithm for this
// void set_sample_rate(float sample_rate)
// {
// float ratio = sys_tick_freq / sample_rate;
// }
void set_sample_fraction(uint16_t num, uint16_t den)
{
if (timer >= 0)
dma_timer_set_fraction(timer, num, den);
}
void start()
{
dma_channel_start(dma_channel_a);
}
protected:
int get_active_channel() const
{
return bankA_active ? dma_channel_a : dma_channel_b;
}
int get_inactive_channel() const
{
return bankA_active ? dma_channel_b : dma_channel_a;
}
void perform_interrupt() override
{
bankA_active = !bankA_active;
dma_channel_set_read_addr(get_inactive_channel(), get_inactive_buffer().data(), false);
auto& just_finished = get_inactive_buffer();
auto& now_active = get_active_buffer();
if (callback)
callback(just_finished.data(), now_active.data(), buffer_size);
}
private:
std::array<uint8_t, buffer_size> bankA;
std::array<uint8_t, buffer_size> bankB;
int dma_channel_a = -1;
int dma_channel_b = -1;
int timer = -1;
bool bankA_active = true;
interrupt_t callback;
};
#endif //H_DB5EAFBD40EE4ACDADB973F02E1A4AF1

66
src/Button.hpp Normal file
View File

@@ -0,0 +1,66 @@
#ifndef H_B4D7EE1A3D57466AA444F3FC912641B9
#define H_B4D7EE1A3D57466AA444F3FC912641B9
#include <string>
#include <vector>
#include <cstdint>
#include "Pixel.hpp"
#include "ActionId.hpp"
#include "ConfigError.hpp"
namespace Configuration
{
class Page;
class Button
{
friend class Page;
//text on screen when holding the button
std::string hold_text;
//series of keycodes pressed in order then all released
std::vector<uint8_t> keycodes;
//color of this button
Pixel color;
//if set, make this page active after press
Page *goto_page;
//for custom actions, this tells the host what to do
ActionId action_id;
public:
inline Button()
{
}
inline Button(const Button& other)
{
hold_text = other.hold_text;
keycodes = other.keycodes;
color = other.color;
goto_page = other.goto_page;
action_id = other.action_id;
}
inline Button& operator=(const Button& other)
{
hold_text = other.hold_text;
keycodes = other.keycodes;
color = other.color;
goto_page = other.goto_page;
action_id = other.action_id;
return *this;
}
inline Button(Button&& other)
{
hold_text = std::move(other.hold_text);
keycodes = std::move(other.keycodes);
color = other.color;
goto_page = other.goto_page;
action_id = other.action_id;
}
inline ConfigError load()
{
return ConfigError::None;
}
};
}
#endif //H_B4D7EE1A3D57466AA444F3FC912641B9

9
src/Config.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include "Config.hpp"
namespace Configuration
{
void Config::append(const Page& new_page)
{
pages.push_back(new_page);
}
}

26
src/Config.hpp Normal file
View File

@@ -0,0 +1,26 @@
#ifndef H_AE095F749CEA4FC29AC38902BC5FD5D1
#define H_AE095F749CEA4FC29AC38902BC5FD5D1
#include <vector>
#include <ArduinoJson.h>
#include "Page.hpp"
#include "ConfigError.hpp"
namespace Configuration
{
class Config
{
std::vector<Page> pages;
public:
Page& page(size_t index)
{
return pages[index];
}
void append(const Page& new_page);
ConfigError load(JsonDocument);
};
}
#endif //H_AE095F749CEA4FC29AC38902BC5FD5D1

16
src/ConfigError.hpp Normal file
View File

@@ -0,0 +1,16 @@
#ifndef H_BA0C3ED91D9B4CAAB2D8C6FBCD1A1C07
#define H_BA0C3ED91D9B4CAAB2D8C6FBCD1A1C07
namespace Configuration
{
enum class ConfigError
{
None = 0,
deserialization_error = 1,
serialization_error = 2,
};
//prefered over using error = ConfigError::None directly in case other non-error values are added
bool is_error(ConfigError error);
}
#endif //H_BA0C3ED91D9B4CAAB2D8C6FBCD1A1C07

12
src/Page.cpp Normal file
View File

@@ -0,0 +1,12 @@
#include "Page.hpp"
namespace Configuration
{
std::array<Pixel, 12> Page::led_rgb;
void Page::update_leds()
{
for (int i = 0; i < 12; i++)
led_rgb[i] = buttons[i].color;
}
}

32
src/Page.hpp Normal file
View File

@@ -0,0 +1,32 @@
#ifndef H_1AAB801F01BA49D0BB2AAB917C307E50
#define H_1AAB801F01BA49D0BB2AAB917C307E50
#include <array>
#include "Button.hpp"
#include "ConfigError.hpp"
namespace Configuration
{
class Page
{
static std::array<Pixel, 12> led_rgb;
std::array<Button, 12> buttons;
std::string display_text;
public:
inline Page() { }
inline Page(const Page& other)
{
buttons = other.buttons;
display_text = other.display_text;
}
inline Page(Page&& other) : buttons(std::move(other.buttons)) { }
ConfigError load();
void update_leds();
inline Button& button(std::size_t index)
{
return buttons[index];
}
};
}
#endif //H_1AAB801F01BA49D0BB2AAB917C307E50

27
src/Pixel.hpp Normal file
View File

@@ -0,0 +1,27 @@
#ifndef H_FF2FF9A6BC114C719F44441B6FD238C2
#define H_FF2FF9A6BC114C719F44441B6FD238C2
#include <cstdint>
namespace Configuration
{
struct Pixel
{
uint32_t value;
//should check for endianness I think
//but there is no fully compliant way to test at compile time
#if defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
uint8_t& green() { return *(reinterpret_cast<uint8_t*>(value)+0); }
uint8_t& red() { return *(reinterpret_cast<uint8_t*>(value)+1); }
uint8_t& blue() { return *(reinterpret_cast<uint8_t*>(value)+2); }
uint8_t& white() { return *(reinterpret_cast<uint8_t*>(value)+3); }
#else
uint8_t& green() { return *(reinterpret_cast<uint8_t*>(value)+3); }
uint8_t& red() { return *(reinterpret_cast<uint8_t*>(value)+2); }
uint8_t& blue() { return *(reinterpret_cast<uint8_t*>(value)+1); }
uint8_t& white() { return *(reinterpret_cast<uint8_t*>(value)+0); }
#endif
};
}
#endif //H_FF2FF9A6BC114C719F44441B6FD238C2

31
src/pwm.pio Normal file
View File

@@ -0,0 +1,31 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Side-set pin 0 is used for PWM output
.program pwm
.side_set 1 opt
pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
mov x, osr ; Copy most-recently-pulled value back to scratch X
mov y, isr ; ISR contains PWM period. Y used as counter.
countloop:
jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched
jmp skip side 1
noset:
nop ; Single dummy cycle to keep the two paths the same length
skip:
jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO
% c-sdk {
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = pwm_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
pio_sm_init(pio, sm, offset, &c);
}
%}

3
src/sine_8khz.hpp Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,150 @@
#include <FreeRTOS.h>
#include <task.h>
#include <event_groups.h>
#include <Adafruit_SH110X.h>
#include <Adafruit_NeoPixel.h>
#include <RotaryEncoder.h>
#include <hardware/pwm.h>
#include <hardware/gpio.h>
#include <hardware/regs/pwm.h>
#include "src/Config.hpp"
#include "src/Audio.hpp"
#include <limits>
void setup() {
// put your setup code here, to run once:
#include "src/sine_8khz.hpp"
#include "src/200hz300hz_sr10khz_dur8sec.hpp"
constexpr float MIDDLE_C = 261.63;
inline int modulo(int a, int b) {
if (b < 0)
return modulo(-a, -b);
const int result = a % b;
return result >= 0 ? result : result + b;
}
void loop() {
// put your main code here, to run repeatedly:
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM_NEOPIXEL, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &SPI1, OLED_DC, OLED_RST, OLED_CS);
StaticSemaphore_t encoder_semaphore_buffer;
SemaphoreHandle_t encoder_semaphore;
// Create the rotary encoder
RotaryEncoder encoder(PIN_ROTA, PIN_ROTB, RotaryEncoder::LatchMode::FOUR3);
void checkPosition()
{
encoder.tick();
xSemaphoreGiveFromISR(encoder_semaphore, nullptr);
} // just call tick() to check the state.
// our encoder position state
int encoder_pos = 0;
// Audio<2000> audioPlayer;
// struct audio_task_params
// {
// size_t size;
// const uint8_t* data;
// };
// audio_task_params params;
// TaskHandle_t audio_task_handle;
// void audio_interrupt_func(uint8_t* inactive, uint8_t* active, size_t buffer_size)
// {
// vTaskNotifyGiveFromISR(audio_task_handle, nullptr);
// }
// void audio_task_func(void* task_parameters)
// {
// auto& params = *reinterpret_cast<audio_task_params*>(task_parameters);
// int offset = 0;
// const uint8_t* end = params.data + params.size;
// auto& first = audioPlayer.get_active_buffer();
// auto& second = audioPlayer.get_inactive_buffer();
// audioPlayer.set_callback(audio_interrupt_func);
// memcpy(first.data(), params.data, first.size());
// params.data += first.size();
// memcpy(second.data(), params.data, second.size());
// params.data += second.size();
// pwm_set_enabled(pwm_gpio_to_slice_num(PIN_SPEAKER), true);
// audioPlayer.start();
// while(params.data + first.size() < end)
// {
// ulTaskNotifyTake(pdTRUE, std::numeric_limits<TickType_t>::max());
// auto& inactive = audioPlayer.get_inactive_buffer();
// memcpy(inactive.data(), params.data, inactive.size());
// params.data += first.size();
// }
// while (1);
// }
void setup()
{
encoder_semaphore = xSemaphoreCreateBinaryStatic(&encoder_semaphore_buffer);
Serial.begin(115200);
pixels.begin();
pixels.setBrightness(255);
pixels.show();
// pixels.setPixelColor(3, 0xFFBF00);
// pixels.show();
pinMode(PIN_LED, OUTPUT);
digitalWrite(PIN_LED, LOW);
// set rotary encoder inputs and interrupts
pinMode(PIN_ROTA, INPUT_PULLUP);
pinMode(PIN_ROTB, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_ROTA), checkPosition, CHANGE);
attachInterrupt(digitalPinToInterrupt(PIN_ROTB), checkPosition, CHANGE);
// Start OLED
display.begin(0, true); // we dont use the i2c address but we will reset!
//display.display();
display.clearDisplay();
// text display tests
display.setTextSize(1);
display.setTextWrap(false);
display.setTextColor(SH110X_WHITE, SH110X_BLACK); // white text, black background
// pinMode(PIN_SPEAKER_ENABLE, OUTPUT);
// digitalWrite(PIN_SPEAKER_ENABLE, HIGH);
// gpio_set_function(PIN_SPEAKER, GPIO_FUNC_PWM);
// auto slice = pwm_gpio_to_slice_num(PIN_SPEAKER);
// auto channel = pwm_gpio_to_channel(PIN_SPEAKER);
// pwm_config conf;
// pwm_config_set_output_polarity(&conf, false, false);
// pwm_config_set_phase_correct(&conf, false);
// pwm_config_set_clkdiv_mode(&conf, PWM_DIV_FREE_RUNNING);
// pwm_config_set_wrap(&conf, 255);
// pwm_init(slice, &conf, false);
// pwm_set_chan_level(slice, channel, 128);
// // if (!audioPlayer.init((void*)
// // (PWM_BASE + (slice * (PWM_CH1_CTR_OFFSET - PWM_CH0_CTR_OFFSET)
// // + PWM_CH0_CC_OFFSET + (channel ? 2 : 0)))))
// if (!audioPlayer.init((void*)PWM_BASE + PWM_CH0_CC_OFFSET))
// digitalWrite(PIN_LED, HIGH);
// audioPlayer.set_sample_fraction(1, 13300);
// params.data = wave_200hz300hz_sr10khz_dur8sec;
// params.size = sizeof(wave_200hz300hz_sr10khz_dur8sec);
// auto result = xTaskCreate(audio_task_func, "Audio Task",
// 1024, &params, 10, &audio_task_handle);
// tone(PIN_SPEAKER, 400, 500);
// pwm_set_enabled(pwm_gpio_to_slice_num(PIN_SPEAKER), true);
// display.drawCircle(50, 50, 20, SH110X_WHITE);
// display.display();
}
void loop()
{
// display.printf("%d", random(9));
// display.display();
// delay(100);
for (int i = 0; i < 12; i++)
pixels.setPixelColor(i, (modulo(encoder.getPosition(), 12) == i) ? 0xFF0000 : 0);
pixels.show();
delay(50);
}