commit 4ade3a5f00d9bb13e470df834e9cf6e38f296218 Author: Ellen Arvidsson Date: Fri Jun 13 12:51:54 2025 +0200 initcommit osclsk prototype diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23a49de --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +**/build/ + +tags + +*.swp +*.swo diff --git a/daisy/Drum/Drum.cpp b/daisy/Drum/Drum.cpp new file mode 100644 index 0000000..4c3f9f8 --- /dev/null +++ b/daisy/Drum/Drum.cpp @@ -0,0 +1,120 @@ +#include "daisy_seed.h" +#include "daisysp.h" + +// Use the daisy namespace to prevent having to type +// daisy:: before all libdaisy functions +using namespace daisy; +using namespace daisysp; + +// Declare a DaisySeed object called hardware +DaisySeed hardware; + +Oscillator osc; +WhiteNoise noise; + +AdEnv kickVolEnv, kickPitchEnv, snareEnv; + +Switch kick, snare; + +void AudioCallback(AudioHandle::InterleavingInputBuffer in, + AudioHandle::InterleavingOutputBuffer out, + size_t size) +{ + float osc_out, noise_out, snr_env_out, kck_env_out, sig; + //Get rid of any bouncing + snare.Debounce(); + kick.Debounce(); + + //If you press the kick button... + if(kick.RisingEdge()) + { + //Trigger both envelopes! + kickVolEnv.Trigger(); + kickPitchEnv.Trigger(); + } + + //If press the snare button trigger its envelope + if(snare.RisingEdge()) + { + snareEnv.Trigger(); + } + + //Prepare the audio block + for(size_t i = 0; i < size; i += 2) + { + //Get the next volume samples + snr_env_out = snareEnv.Process(); + kck_env_out = kickVolEnv.Process(); + + //Apply the pitch envelope to the kick + osc.SetFreq(kickPitchEnv.Process()); + //Set the kick volume to the envelope's output + osc.SetAmp(kck_env_out); + //Process the next oscillator sample + osc_out = osc.Process(); + + //Get the next snare sample + noise_out = noise.Process(); + //Set the sample to the correct volume + noise_out *= snr_env_out; + + //Mix the two signals at half volume + sig = .5 * noise_out + .5 * osc_out; + + //Set the left and right outputs to the mixed signals + out[i] = sig; + out[i + 1] = sig; + } +} + +int main(void) +{ + // Configure and Initialize the Daisy Seed + // These are separate to allow reconfiguration of any of the internal + // components before initialization. + hardware.Configure(); + hardware.Init(); + hardware.SetAudioBlockSize(4); + float samplerate = hardware.AudioSampleRate(); + + //Initialize oscillator for kickdrum + osc.Init(samplerate); + osc.SetWaveform(Oscillator::WAVE_TRI); + osc.SetAmp(1); + + //Initialize noise + noise.Init(); + + //Initialize envelopes, this one's for the snare amplitude + snareEnv.Init(samplerate); + snareEnv.SetTime(ADENV_SEG_ATTACK, .01); + snareEnv.SetTime(ADENV_SEG_DECAY, .2); + snareEnv.SetMax(1); + snareEnv.SetMin(0); + + //This envelope will control the kick oscillator's pitch + //Note that this envelope is much faster than the volume + kickPitchEnv.Init(samplerate); + kickPitchEnv.SetTime(ADENV_SEG_ATTACK, .01); + kickPitchEnv.SetTime(ADENV_SEG_DECAY, .05); + kickPitchEnv.SetMax(400); + kickPitchEnv.SetMin(50); + + //This one will control the kick's volume + kickVolEnv.Init(samplerate); + kickVolEnv.SetTime(ADENV_SEG_ATTACK, .01); + kickVolEnv.SetTime(ADENV_SEG_DECAY, 1); + kickVolEnv.SetMax(1); + kickVolEnv.SetMin(0); + + //Initialize the kick and snare buttons on pins 27 and 28 + //The callback rate is samplerate / blocksize (48) + snare.Init(hardware.GetPin(27), samplerate / 48.f); + kick.Init(hardware.GetPin(28), samplerate / 48.f); + + //Start calling the callback function + hardware.StartAudio(AudioCallback); + + // Loop forever + for(;;) {} +} diff --git a/daisy/Drum/Makefile b/daisy/Drum/Makefile new file mode 100644 index 0000000..040238a --- /dev/null +++ b/daisy/Drum/Makefile @@ -0,0 +1,7 @@ +# Project Name +TARGET = Drum + +# Sources +CPP_SOURCES = Drum.cpp + +include ../../make/daisy.mk diff --git a/daisy/Drum/README.md b/daisy/Drum/README.md new file mode 100644 index 0000000..9cceb14 --- /dev/null +++ b/daisy/Drum/README.md @@ -0,0 +1,25 @@ +# Drum + +## Author + +Ben Sergentanis + + +## Description + +Simple drumset with whitenoise snare and oscillator kick. + +[Source Code](https://github.com/electro-smith/DaisyExamples/tree/master/seed/Drum) + +## Controls +Press one button to trigger the kick, and the other to trigger the snare. + +## Breadboard + +[Drum_bb.png](https://raw.githubusercontent.com/electro-smith/DaisyExamples/master/seed/Drum/resources/Drum_bb.png) + + +## Schematic + +[Drum_schem.png](https://raw.githubusercontent.com/electro-smith/DaisyExamples/master/seed/Drum/resources/Drum_schem.png) + diff --git a/daisy/Drum/resources/Drum.fzz b/daisy/Drum/resources/Drum.fzz new file mode 100644 index 0000000..735e17d Binary files /dev/null and b/daisy/Drum/resources/Drum.fzz differ diff --git a/daisy/Drum/resources/Drum_bb.png b/daisy/Drum/resources/Drum_bb.png new file mode 100644 index 0000000..aa983fa Binary files /dev/null and b/daisy/Drum/resources/Drum_bb.png differ diff --git a/daisy/Drum/resources/Drum_schem.png b/daisy/Drum/resources/Drum_schem.png new file mode 100644 index 0000000..860ced8 Binary files /dev/null and b/daisy/Drum/resources/Drum_schem.png differ diff --git a/daisy/blnk/Blink.cpp b/daisy/blnk/Blink.cpp new file mode 100644 index 0000000..8fd5e80 --- /dev/null +++ b/daisy/blnk/Blink.cpp @@ -0,0 +1,34 @@ +#include "daisy_seed.h" + +// Use the daisy namespace to prevent having to type +// daisy:: before all libdaisy functions +using namespace daisy; + +// Declare a DaisySeed object called hardware +DaisySeed hardware; + +int main(void) +{ + // Declare a variable to store the state we want to set for the LED. + bool led_state; + led_state = true; + + // Configure and Initialize the Daisy Seed + // These are separate to allow reconfiguration of any of the internal + // components before initialization. + hardware.Configure(); + hardware.Init(); + + // Loop forever + for(;;) + { + // Set the onboard LED + hardware.SetLed(led_state); + + // Toggle the LED state for the next time around. + led_state = !led_state; + + // Wait 500ms + System::Delay(500); + } +} diff --git a/daisy/blnk/Makefile b/daisy/blnk/Makefile new file mode 100644 index 0000000..ab1d078 --- /dev/null +++ b/daisy/blnk/Makefile @@ -0,0 +1,7 @@ +# Project Name +TARGET = Blink + +# Sources +CPP_SOURCES = Blink.cpp + +include ../../make/daisy.mk diff --git a/daisy/blnk/README.md b/daisy/blnk/README.md new file mode 100644 index 0000000..79f0c96 --- /dev/null +++ b/daisy/blnk/README.md @@ -0,0 +1,11 @@ +# Blink + +## Author + +Shensley + +## Description + +Blinks the Seed's onboard LED at a constant rate. + +[Source Code](https://github.com/electro-smith/DaisyExamples/tree/master/seed/Blink) diff --git a/daisy/scope/Makefile b/daisy/scope/Makefile new file mode 100644 index 0000000..3d3435d --- /dev/null +++ b/daisy/scope/Makefile @@ -0,0 +1,7 @@ +# Project Name +TARGET = scope + +# Sources +CPP_SOURCES = scope.cpp tft.cpp osclsk.cpp + +include ../../make/daisy.mk diff --git a/daisy/scope/audioc.hpp b/daisy/scope/audioc.hpp new file mode 100644 index 0000000..c55b5ec --- /dev/null +++ b/daisy/scope/audioc.hpp @@ -0,0 +1,41 @@ +#ifndef _AUDIOC_HPP +#define _AUDIOC_HPP + +#define AUDIOC_DEBUG + +#ifdef AUDIOC_DEBUG + +#define AUDIOC_PRECOND(cond) \ + do { \ + if (! (cond)) { \ + AUDIOC_LOG("AC_PRECOND(" #cond ") @ %s: line %u failed\n", \ + __FILE__, __LINE__); \ + } \ + } while (0) +#define AUDIOC_POSTCOND(cond) \ + do { \ + if (! (cond)) { \ + AUDIOC_LOG("AC_POSTCOND(" #cond ") @ %s: line %u failed\n", \ + __FILE__, __LINE__); \ + } \ + } while (0) +#define AUDIOC_ASSERT(cond) \ + do { \ + if (! (cond)) { \ + AUDIOC_LOG("AC_ASSERT(" #cond ") @ %s: line %u failed\n", \ + __FILE__, __LINE__); \ + } \ + } while (0) +#else + +#define AUDIOC_ASSERT(cond) + +#endif /* ifdef AUDIOC_DEBUG */ + +#define AC_ASSERT AUDIOC_ASSERT +#define AC_PRECOND AUDIOC_PRECOND +#define AC_POSTCOND AUDIOC_POSTCOND + +#define AC_LOG AUDIOC_LOG + +#endif /* ifndef _AUDIOC_HPP */ diff --git a/daisy/scope/osclsk.cpp b/daisy/scope/osclsk.cpp new file mode 100644 index 0000000..cf8570f --- /dev/null +++ b/daisy/scope/osclsk.cpp @@ -0,0 +1,368 @@ +#include "audioc.hpp" +#include "scope.hpp" +#include "osclsk.hpp" + +#define _HUUUGE_FLOAT (std::numeric_limits::infinity()) + +int +osclsk_scope::init(uint8_t *dma_buf, size_t dma_sz) +{ + AC_PRECOND(dma_buf != NULL && dma_sz > 0); + + if (tft.init(dma_buf, dma_sz) != 0) + return (-1); + + + fg = tft_color(0, 0xff, 0); + bg = tft_color(0, 0, 0); + + if (init_block() != 0) + return (-1); + if (init_screen() != 0) + return (-1); + + AC_ASSERT(st == osc_state::RENDER || st == osc_state::RENDER_DONE); + + return (0); +} + +#define OSCLSK_WAIT_READY_INTERVAL 100 +int +osclsk_scope::wait_ready(int ms) +{ + int time; + + switch (state()) { + case osclsk_scope::osc_state::INIT: + AC_LOG("scope.state == INIT"); + break; + case osclsk_scope::osc_state::TRIGGER: + AC_LOG("scope.state == TRIGGER"); + break; + case osclsk_scope::osc_state::SAMPLE: + AC_LOG("scope.state == SAMPLE"); + break; + case osclsk_scope::osc_state::SAMPLE_DONE: + AC_LOG("scope.state == SAMPLE_DONE"); + break; + case osclsk_scope::osc_state::RENDER: + AC_LOG("scope.state == RENDER"); + break; + case osclsk_scope::osc_state::RENDER_DONE: + AC_LOG("scope.state == RENDER_DONE"); + break; + case osclsk_scope::osc_state::READY: + AC_LOG("scope.state == READY"); + break; + default: + AC_LOG("scope.state invalid"); + break; + } + + time = 0; + while (true) { + if (prep() == 0 || time > ms) + break; + System::Delay(OSCLSK_WAIT_READY_INTERVAL); + time += OSCLSK_WAIT_READY_INTERVAL; + } + + if (st != osc_state::READY) + return (-1); + + return (0); +} + +int +osclsk_scope::prep(void) +{ + switch (st) { + case osc_state::READY: + return (0); + case osc_state::RENDER_DONE: + block_fill = 0; + block_fill_nsamp = 0; + st = osc_state::READY; + + return (0); + default: + return (-1); + } +} + +int +osclsk_scope::trig(void) +{ + switch (st) { + case osc_state::READY: + trig_lkb_idx = 0; + trig_lkb_idx_nsamp = 0; + + trig_num = 0; + + trig_state = osc_trig_state::READY; + st = osc_state::TRIGGER; + + return (0); + default: + return (-1); + } +} + +int +osclsk_scope::init_block(void) +{ + block_fill = 0; + block_fill_nsamp = 0; + + st = osc_state::SAMPLE; + + for (block_fill = 0; block_fill < OSCLSK_BLOCK_LEN; block_fill++) { + block_y_max[block_fill] = 0.f; + block_y_min[block_fill] = 0.f; + } + + st = osc_state::SAMPLE_DONE; + + AUDIOC_ASSERT(block_fill == OSCLSK_BLOCK_LEN); + + return (0); +} + +int +osclsk_scope::init_screen(void) +{ + return (render_block()); +} + +void +osclsk_scope::sample(const float *sig, size_t num) +{ + size_t i; + + switch (st) { + case osc_state::SAMPLE: + for (i = 0; i < num && block_fill < OSCLSK_BLOCK_LEN; i++) { + if (block_fill_nsamp == 0) { + block_y_max[block_fill] = -_HUUUGE_FLOAT; + block_y_min[block_fill] = _HUUUGE_FLOAT; + } + + block_y_max[block_fill] = std::max( + block_y_max[block_fill], sig[i]); + block_y_min[block_fill] = std::min( + block_y_min[block_fill], sig[i]); + + /* block_fill_nsamp increments and wraps. + * wrapping increments block_fill */ + block_fill_nsamp++; + if (block_fill_nsamp >= OSCLSK_RATE_DIV) { + block_fill++; + block_fill_nsamp = 0; + } + } + + if (block_fill >= OSCLSK_BLOCK_LEN) + st = osc_state::SAMPLE_DONE; + + break; + case osc_state::TRIGGER: + sample_trigger(sig, num); + break; + default: + break; + } +} + + +bool +osclsk_scope::is_triggering(float sample) +{ + switch (trig_mode) { + case osc_trig_mode::RISING_EDGE: + return (sample > trig_margin); + default: + return (false); + } +} + +void +osclsk_scope::trig_lkb_wr_block(void) +{ + size_t lkb_i, n; + + lkb_i = trig_lkb_idx > trig_lkb_amount ? + trig_lkb_idx - trig_lkb_amount : + OSCLSK_TRIG_LOOKBACK + trig_lkb_idx - trig_lkb_amount; + + for (n = 0; n < trig_lkb_amount; n++) { + block_y_max[block_fill] = trig_lkb_y_max[lkb_i]; + block_y_min[block_fill] = trig_lkb_y_min[lkb_i]; + + block_fill++; + lkb_i = (lkb_i + 1) & (OSCLSK_TRIG_LOOKBACK - 1); + } + + /* we wanna resume sampling from right after the lookback data, + * so we write partial data points to block as well */ + block_y_max[block_fill] = trig_lkb_y_max[trig_lkb_idx]; + block_y_min[block_fill] = trig_lkb_y_min[trig_lkb_idx]; + block_fill_nsamp = trig_lkb_idx_nsamp; +} + + +void +osclsk_scope::sample_trigger(const float *sig, size_t num) +{ + size_t i; + + AC_PRECOND(st == osc_state::TRIGGER); + + /* sample data in lookback buffers */ + for (i = 0; i < num; i++) { + AC_ASSERT(trig_lkb_idx < OSCLSK_TRIG_LOOKBACK); + + if (trig_lkb_idx_nsamp == 0) { + trig_lkb_y_max[trig_lkb_idx] = -_HUUUGE_FLOAT; + trig_lkb_y_min[trig_lkb_idx] = _HUUUGE_FLOAT; + } + + trig_lkb_y_max[trig_lkb_idx] = std::max( + trig_lkb_y_max[trig_lkb_idx], sig[i]); + trig_lkb_y_min[trig_lkb_idx] = std::min( + trig_lkb_y_min[trig_lkb_idx], sig[i]); + + trig_lkb_idx_nsamp++; + if (trig_lkb_idx_nsamp >= OSCLSK_RATE_DIV) { + trig_lkb_idx++; + trig_lkb_idx_nsamp = 0; + } + + trig_lkb_idx = + trig_lkb_idx & (OSCLSK_TRIG_LOOKBACK - 1); + } + + /* do triggering checks */ + for (i = 0; i < num; i++) { + switch (trig_state) { + case osc_trig_state::READY: + if (! is_triggering(sig[i])) { + trig_state = osc_trig_state::NO_TRIG; + } + break; + case osc_trig_state::NO_TRIG: + if (is_triggering(sig[i])) { + trig_num = 0; + trig_state = osc_trig_state::TRIG; + } + break; + case osc_trig_state::TRIG: + if (is_triggering(sig[i])) { + if (++trig_num >= trig_num_req) { + trig_state = + osc_trig_state::TRIGGERED; + } + } else { + trig_state = osc_trig_state::NO_TRIG; + } + break; + default: + break; + } + + } + + /* transfer lookback to actual block if triggered */ + switch (trig_state) { + case osc_trig_state::TRIGGERED: + trig_lkb_wr_block(); + st = osc_state::SAMPLE; + break; + default: + break; + } +} + + +osclsk_scope::osc_state +osclsk_scope::state(void) +{ + return (st); +} + +#define OSCLSK_Y_MAX_PX (OSCLSK_SCREEN_YSZ - 1) + +void +osclsk_scope::render_finish_tft_cb(void *ctx) +{ + osclsk_scope *osc; + + osc = (osclsk_scope *)ctx; + + if (osc->st != osc_state::RENDER) + return; + + osc->st = osc_state::RENDER_DONE; +} + +int +osclsk_scope::render_block(void) +{ + int res; + size_t render_idx; + + float block_norm = 1.f; + + if (st != osc_state::SAMPLE_DONE) + return (-1); + + st = osc_state::RENDER; + + for (render_idx = 0; render_idx < OSCLSK_BLOCK_LEN; render_idx++) { + block_norm = std::max( std::abs(block_y_max[render_idx]), + block_norm); + block_norm = std::max( std::abs(block_y_min[render_idx]), + block_norm); + } + + for (render_idx = 0; render_idx < OSCLSK_BLOCK_LEN; render_idx++) { + block_y_max[render_idx] = block_y_max[render_idx] / block_norm; + block_y_min[render_idx] = block_y_min[render_idx] / block_norm; + } + + for (render_idx = 0; render_idx < OSCLSK_BLOCK_LEN; render_idx++) { + screen_px_y_max[render_idx] = 1u + (uint16_t)( + OSCLSK_Y_MAX_PX * (.5f + block_y_max[render_idx])); + screen_px_y_min[render_idx] = (uint16_t)( + OSCLSK_Y_MAX_PX * (.5f + block_y_min[render_idx])); + } + + res = -1; + tft.async_on_finish(render_finish_tft_cb, this); + tft.async_start(); + for (render_idx = 0; render_idx < OSCLSK_BLOCK_LEN; render_idx++) { + res = tft.async_hline_row_bg_fill(&bg, &fg, + screen_px_y_min[render_idx], screen_px_y_max[render_idx], + render_idx); + if (res == -1) + break; + } + tft.async_end(); + + return (res); +} + +void +osclsk_scope::set_color(osc_cc which, const tft_color *what) +{ + switch (which) { + case osc_cc::FOREGROUND: + fg = *what; + break; + case osc_cc::BACKGROUND: + bg = *what; + break; + default: + break; + } +} diff --git a/daisy/scope/osclsk.hpp b/daisy/scope/osclsk.hpp new file mode 100644 index 0000000..ce0c37f --- /dev/null +++ b/daisy/scope/osclsk.hpp @@ -0,0 +1,115 @@ +#ifndef _OSCILLOSK_HPP +#define _OSCILLOSK_HPP + +#include "daisy.h" + +#include "tft.hpp" + +using namespace daisy; + + +#define OSCLSK_SCREEN_XSZ ILI9341_TFTHEIGHT +#define OSCLSK_SCREEN_YSZ ILI9341_TFTWIDTH + + +#define OSCLSK_RATE_DIV 32 + +#define OSCLSK_BLOCK_LEN OSCLSK_SCREEN_XSZ +#define OSCLSK_TRIG_LOOKBACK 0x100 + +#define OSCLSK_TFT_CMD_BUF_NSPLIT 4 + +#define OSCLSK_CMDS_LEN 8 * OSCLSK_SCREEN_XSZ +struct osclsk_scope { + enum class osc_cc { + FOREGROUND, + BACKGROUND + }; + + enum class osc_state { + INIT, + TRIGGER, + SAMPLE, + SAMPLE_DONE, + RENDER, + RENDER_DONE, + READY + }; + + enum class osc_trig_mode { + RISING_EDGE, + FALLING_EDGE, + ABS_THRESHOLD, + }; + + enum class osc_trig_state { + INIT, + READY, + NO_TRIG, + TRIG, + TRIGGERED + }; + + volatile osc_state st; + + float block_y_max[OSCLSK_BLOCK_LEN]; + float block_y_min[OSCLSK_BLOCK_LEN]; + size_t block_fill; + int block_fill_nsamp; + + float trig_lkb_y_max[OSCLSK_TRIG_LOOKBACK]; + float trig_lkb_y_min[OSCLSK_TRIG_LOOKBACK]; + size_t trig_lkb_idx; + int trig_lkb_idx_nsamp; + size_t trig_lkb_amount; + + float trig_margin; + size_t trig_num; + size_t trig_num_req; + osc_trig_state trig_state; + osc_trig_mode trig_mode; + + + uint16_t rate_div; + + + + tft_color fg, bg; + tft_driver_ili9341 tft; + + uint16_t screen_px_y_max[OSCLSK_SCREEN_XSZ]; + uint16_t screen_px_y_min[OSCLSK_SCREEN_XSZ]; + + osclsk_scope(void) : st(osc_state::INIT), + block_fill(0), block_fill_nsamp(0), + trig_lkb_idx(0), trig_lkb_idx_nsamp(0), + trig_lkb_amount(2), + trig_margin(.05f), trig_num(0), + trig_num_req(2), + trig_state(osc_trig_state::INIT), + trig_mode(osc_trig_mode::RISING_EDGE) {}; + + int init(uint8_t *dma_buf, size_t dma_sz); + int init_block(void); + int init_screen(void); + + osc_state state(void); + + void set_color(osc_cc which, const tft_color *to_what); + + void sample(const float *sig, size_t num); + void sample_trigger(const float *sig, size_t num); + + int wait_ready(int ms); + int prep(void); + int trig(void); + + bool is_triggering(float sample); + void trig_lkb_wr_block(void); + + int render_block(void); + + static void render_finish_tft_cb(void *ctx); +}; + +#endif /* _OSCILLOSK_HPP */ diff --git a/daisy/scope/scope.cpp b/daisy/scope/scope.cpp new file mode 100644 index 0000000..e4c779e --- /dev/null +++ b/daisy/scope/scope.cpp @@ -0,0 +1,202 @@ +#include "daisy_seed.h" +#include "daisysp.h" + +#include "scope.hpp" +#include "osclsk.hpp" + +using namespace daisy; +using namespace daisysp; + +#define DMA_AREA_SIZE (3*(1 << 16)) +#define SCOPE_RING_BUF_SIZE 1024 +#define AUDIO_BLOCK_SIZE 2 + + +/* declarations */ +DaisySeed daisy_hw; +CpuLoadMeter load_meter; + +osclsk_scope scope; + +uint8_t DMA_BUFFER_MEM_SECTION dma_area[DMA_AREA_SIZE]; + +static void audio_cb(AudioHandle::InputBuffer in, + AudioHandle::OutputBuffer out, size_t sz); + +Oscillator osc; +WhiteNoise noise; + +AdEnv kickVolEnv, kickPitchEnv, snareEnv; + +Switch kick, snare; + +RingBuffer scope_in; + +static void +audio_cb(AudioHandle::InputBuffer in, + AudioHandle::OutputBuffer out, size_t sz) +{ + float osc_out, noise_out, snr_env_out, kck_env_out; + float sig[AUDIO_BLOCK_SIZE]; + + load_meter.OnBlockStart(); + //Get rid of any bouncing + snare.Debounce(); + kick.Debounce(); + + //If you press the kick button... + if(kick.RisingEdge()) + { + //Trigger both envelopes! + kickVolEnv.Trigger(); + kickPitchEnv.Trigger(); + } + + //If press the snare button trigger its envelope + if(snare.RisingEdge()) + { + snareEnv.Trigger(); + } + + //Prepare the audio block + for(size_t i = 0; i < sz; i++) + { + //Get the next volume samples + snr_env_out = snareEnv.Process(); + kck_env_out = kickVolEnv.Process(); + + //Apply the pitch envelope to the kick + osc.SetFreq(kickPitchEnv.Process()); + //Set the kick volume to the envelope's output + osc.SetAmp(kck_env_out); + //Process the next oscillator sample + osc_out = osc.Process(); + + //Get the next snare sample + noise_out = noise.Process(); + //Set the sample to the correct volume + noise_out *= snr_env_out; + + //Mix the two signals at half volume + sig[i] = .5 * noise_out + .5 * osc_out; + } + + for (size_t i = 0; i < sz; i++) { + out[0][i] = sig[i]; + out[1][i] = sig[i]; + + } + + scope_in.Overwrite(sig, sz); + + load_meter.OnBlockEnd(); +} + +static void +setup_drums(void) +{ + float samplerate; + + /* kick n snare */ + samplerate = daisy_hw.AudioSampleRate(); + + //Initialize oscillator for kickdrum + osc.Init(samplerate); + osc.SetWaveform(Oscillator::WAVE_TRI); + osc.SetAmp(1); + + //Initialize noise + noise.Init(); + + //Initialize envelopes, this one's for the snare amplitude + snareEnv.Init(samplerate); + snareEnv.SetTime(ADENV_SEG_ATTACK, .01); + snareEnv.SetTime(ADENV_SEG_DECAY, .2); + snareEnv.SetMax(1); + snareEnv.SetMin(0); + + //This envelope will control the kick oscillator's pitch + //Note that this envelope is much faster than the volume + kickPitchEnv.Init(samplerate); + kickPitchEnv.SetTime(ADENV_SEG_ATTACK, .01); + kickPitchEnv.SetTime(ADENV_SEG_DECAY, .05); + kickPitchEnv.SetMax(400); + kickPitchEnv.SetMin(50); + + //This one will control the kick's volume + kickVolEnv.Init(samplerate); + kickVolEnv.SetTime(ADENV_SEG_ATTACK, .01); + kickVolEnv.SetTime(ADENV_SEG_DECAY, 1); + kickVolEnv.SetMax(1); + kickVolEnv.SetMin(0); + + //Initialize the kick and snare buttons on pins 27 and 28 + //The callback rate is samplerate / blocksize (48) + snare.Init(daisy_hw.GetPin(27), samplerate / (float)AUDIO_BLOCK_SIZE); + kick.Init(daisy_hw.GetPin(28), samplerate / (float)AUDIO_BLOCK_SIZE); +} + + +#define LOAD_METER_TICKS (1 << 16) +int +main(void) +{ + size_t read; + float s[AUDIO_BLOCK_SIZE]; + int load_tick; + daisy_hw.Configure(); + daisy_hw.Init(); + daisy_hw.StartLog(false); /* true = wait for usb connection */ + daisy_hw.SetAudioBlockSize(AUDIO_BLOCK_SIZE); + + load_meter.Init(daisy_hw.AudioSampleRate(), daisy_hw.AudioBlockSize()); + load_tick = LOAD_METER_TICKS; + + if (scope.init(dma_area, sizeof(dma_area)) == -1) + daisy_hw.PrintLine("scope.init failed"); + + scope_in.Init(); + + setup_drums(); + + daisy_hw.StartAudio(audio_cb); + + while (true) { + + read = scope_in.readable(); + read = (read <= sizeof(s)/sizeof(*s)) ? read : + sizeof(s)/sizeof(*s); + + scope_in.ImmediateRead(s, read); + + scope.sample(s, read); + + switch (scope.state()) { + case osclsk_scope::osc_state::SAMPLE_DONE: + scope.render_block(); + break; + case osclsk_scope::osc_state::RENDER_DONE: + scope.prep(); + break; + case osclsk_scope::osc_state::READY: + scope.trig(); + break; + default: + break; + } + + if (--load_tick == 0) { + load_tick = LOAD_METER_TICKS; + // get the current load (smoothed value and peak values) + const float avgLoad = load_meter.GetAvgCpuLoad(); + const float maxLoad = load_meter.GetMaxCpuLoad(); + const float minLoad = load_meter.GetMinCpuLoad(); + // print it to the serial connection (as percentages) + daisy_hw.PrintLine("Processing Load %:"); + daisy_hw.PrintLine("Max: " FLT_FMT3, FLT_VAR3(maxLoad * 100.0f)); + daisy_hw.PrintLine("Avg: " FLT_FMT3, FLT_VAR3(avgLoad * 100.0f)); + daisy_hw.PrintLine("Min: " FLT_FMT3, FLT_VAR3(minLoad * 100.0f)); + } + } +} + diff --git a/daisy/scope/scope.hpp b/daisy/scope/scope.hpp new file mode 100644 index 0000000..dcb7e30 --- /dev/null +++ b/daisy/scope/scope.hpp @@ -0,0 +1,11 @@ +#ifndef _SCOPE_HPP +#define _SCOPE_HPP + +#include "daisy_seed.h" + +extern daisy::DaisySeed daisy_hw; + +#define AUDIOC_LOG (daisy_hw.PrintLine) +#include "audioc.hpp" + +#endif /* _SCOPE_HPP */ diff --git a/daisy/scope/tft.cpp b/daisy/scope/tft.cpp new file mode 100644 index 0000000..ab05de4 --- /dev/null +++ b/daisy/scope/tft.cpp @@ -0,0 +1,528 @@ +#include "daisy.h" + +#include "tft.hpp" + +using namespace daisy; + + +static tft_cmd init_cmds[] = { + tft_cmd(0xEF, {0x03, 0x80, 0x02}), + tft_cmd(0xCF, {0x00, 0xC1, 0x30}), + tft_cmd(0xED, {0x64, 0x03, 0x12, 0x81}), + tft_cmd(0xE8, {0x85, 0x00, 0x78}), + tft_cmd(0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}), + tft_cmd(0xF7, {0x20}), + tft_cmd(0xEA, {0x00, 0x00}), + tft_cmd(ILI9341_PWCTR1, {0x23}), // Power control VRH[5:0] + tft_cmd(ILI9341_PWCTR2, {0x10}), // Power control SAP[2:0];BT[3:0] + tft_cmd(ILI9341_VMCTR1, {0x3e, 0x28}), // VCM control + tft_cmd(ILI9341_VMCTR2, {0x86}), // VCM control2 + tft_cmd(ILI9341_MADCTL, {0x08}), // Memory Access Control + tft_cmd(ILI9341_VSCRSADD, {0x00}), // Vertical scroll zero + tft_cmd(ILI9341_PIXFMT, {0x55}), // 16 bit pixel fmt + tft_cmd(ILI9341_FRMCTR1, {0x00, 0x18}), + tft_cmd(ILI9341_DFUNCTR, {0x08, 0x82, 0x27}), // Display Function Control + tft_cmd(0xF2, {0x00}), // 3Gamma Function Disable + tft_cmd(ILI9341_GAMMASET, {0x01}), // Gamma curve selected + tft_cmd(ILI9341_GMCTRP1, {0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00}), + tft_cmd(ILI9341_GMCTRN1, {0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F}), + tft_cmd(ILI9341_SLPOUT, 150), // Exit Sleep + tft_cmd(ILI9341_DISPON, 150), // Display on +}; + +static tft_cmd RESET_CMD(ILI9341_SWRESET, 150); + +int +tft_driver_ili9341::init(uint8_t *dma_buf, size_t dma_sz) +{ + + AC_PRECOND(_async_stat == async_status::INIT); + + SpiHandle::Result res; + + dcx_pin = daisy::seed::D12; // Pin(PORTB, 8); + dcx.Init(dcx_pin, GPIO::Mode::OUTPUT, GPIO::Pull::PULLUP, + GPIO::Speed::VERY_HIGH); + + spi_conf.datasize = 8; + + spi_conf.mode = SpiHandle::Config::Mode::MASTER; + spi_conf.direction = SpiHandle::Config::Direction::TWO_LINES; + + spi_conf.clock_polarity = SpiHandle::Config::ClockPolarity::LOW; + spi_conf.clock_phase = SpiHandle::Config::ClockPhase::ONE_EDGE; + + spi_conf.baud_prescaler = SpiHandle::Config::BaudPrescaler::PS_2; + + spi_conf.periph = SpiHandle::Config::Peripheral::SPI_1; + spi_conf.nss = SpiHandle::Config::NSS::HARD_OUTPUT; + + spi_conf.pin_config.nss = daisy::seed::D7; //Pin(PORTG, 10); // D7 + spi_conf.pin_config.sclk = daisy::seed::D8; //Pin(PORTG, 11); // D8 + spi_conf.pin_config.miso = daisy::seed::D9; // Pin(PORTB, 4); // D9 + spi_conf.pin_config.mosi = daisy::seed::D10; //Pin(PORTB, 5); // D10 + + res = spi.Init(spi_conf); + + if (res == SpiHandle::Result::ERR) + return (-1); + + daisy_hw.PrintLine("resetting tft"); + send_sync(&RESET_CMD); + + for (auto cmd : init_cmds) { + daisy_hw.PrintLine("sending tft init command"); + send_sync(&cmd); + } + + _async_dma_buf = dma_buf; + _async_dma_sz = dma_sz; + _async_stat = async_status::READY; + + return (0); +} + +void +tft_driver_ili9341::set_addr_window(uint16_t col_st, uint16_t width, + uint16_t row_st, uint16_t height) +{ + uint16_t start, end; + + start = col_st; end = col_st + width; + tft_cmd col_set(ILI9341_CASET, + { (uint8_t)((start >> 8) & 0xff), (uint8_t)(start & 0xff), + (uint8_t)((end >> 8) & 0xff), (uint8_t)(end & 0xff) }); + + start = row_st; end = row_st + height; + tft_cmd row_set(ILI9341_PASET, + { (uint8_t)((start >> 8) & 0xff), (uint8_t)(start & 0xff), + (uint8_t)((end >> 8) & 0xff), (uint8_t)(end & 0xff) }); + + send_sync(&col_set); + send_sync(&row_set); +} + + +void +tft_driver_ili9341::async_on_finish(async_on_finish_f cb, void *ctx) +{ + _async_cb_fun = cb; + _async_cb_ctx = ctx; +} + +int +tft_driver_ili9341::async_start(void) +{ + if (_async_stat != async_status::READY && + _async_stat != async_status::DONE) + return (-1); + + if (_async_cb_fun == NULL) + return (-1); + + _async_curr_wr = (tft_dma_cmd *)_async_dma_buf; + _async_curr_rd = _async_curr_wr; + + _async_curr_wr->size = 0; + + _async_wr = async_wr::START; + _async_stat = async_status::NO_TX; + + return (0); +} + +int +tft_driver_ili9341::async_end(void) +{ + _async_wr = async_wr::END; + async_flush(); + return (0); +} + +tft_driver_ili9341::async_status +tft_driver_ili9341::async_stat(void) +{ + return (_async_stat); +} + +void +tft_driver_ili9341::async_flush(void) +{ + async_tx(); +} + +int +tft_driver_ili9341::async_hline_row_bg_fill(tft_color *bg, tft_color *fg, + uint16_t hline_idx_st, uint16_t hline_idx_end, uint16_t row_idx) +{ + if (render_dma_buf_hline_row_bg_fill(bg, fg, hline_idx_st, + hline_idx_end, row_idx) != 0) { + return (-1); + } + + async_tx_try(); + + return (0); +} + +#define TFT_ASYNC_TRY_TX_MIN 4096 +void +tft_driver_ili9341::async_tx_try(void) +{ + if (dma_buf_sz_tx_pend() > TFT_ASYNC_TRY_TX_MIN) + return; + + async_tx(); +} + +void +tft_driver_ili9341::async_tx(void) +{ + if (_async_wr == async_wr::END && _async_stat == async_status::NO_TX && + dma_buf_sz_tx_pend() == 0) { + _async_stat = async_status::DONE; + _async_cb_fun((void *)_async_cb_ctx); + return; + } + + if (_async_stat != async_status::NO_TX) + return; + + AC_ASSERT(_async_curr_rd->size > 0); + + _async_stat = async_status::TX; + + async_send_raw((const uint8_t *)_async_curr_rd->buf, 1, + async_tx_cmd_scb, async_tx_cmd_ecb, this); +} + +void +tft_driver_ili9341::async_tx_cmd_scb(void *ctx) +{ + tft_driver_ili9341 *tft; + + tft = (tft_driver_ili9341 *)ctx; + + AC_ASSERT(tft->_async_stat == async_status::TX); + + tft->dcx_low(); +} + +void +tft_driver_ili9341::async_tx_cmd_ecb(void *ctx, SpiHandle::Result res) +{ + tft_driver_ili9341 *tft; + const tft_dma_cmd *cmd; + + tft = (tft_driver_ili9341 *)ctx; + cmd = tft->dma_buf_curr_rd(); + + tft->async_send_raw(&cmd->buf[1], cmd->size, async_tx_param_scb, + async_tx_param_ecb, tft); +} + +void +tft_driver_ili9341::async_tx_param_scb(void *ctx) +{ + tft_driver_ili9341 *tft; + + tft = (tft_driver_ili9341 *)ctx; + tft->dcx_high(); +} + +void +tft_driver_ili9341::async_tx_param_ecb(void *ctx, SpiHandle::Result res) +{ + tft_driver_ili9341 *tft; + const tft_dma_cmd *cmd; + + + tft = (tft_driver_ili9341 *)ctx; + cmd = tft->dma_buf_next_rd(); + + AUDIOC_ASSERT(tft->async_stat() == async_status::TX); + + if (cmd->size == 0) { + tft->_async_stat = async_status::NO_TX; + if (tft->dma_buf_sz_tx_pend() == 0 && + tft->_async_wr == async_wr::END) { + tft->_async_stat = async_status::DONE; + tft->_async_cb_fun((void *)tft->_async_cb_ctx); + } + return; + } + + tft->async_send_raw(cmd->buf, 1, async_tx_cmd_scb, async_tx_cmd_ecb, + tft); +} + +size_t +tft_driver_ili9341::dma_buf_sz_tx_pend(void) +{ + return ((size_t) + ((uint8_t *)_async_curr_wr - (uint8_t *)_async_curr_rd)); +} + +size_t +tft_driver_ili9341::dma_buf_sz_used(void) +{ + size_t buf_fill; + + buf_fill = (size_t)((uint8_t *)_async_curr_wr - _async_dma_buf); + if (_async_curr_wr->size > 0) + buf_fill += sizeof(tft_dma_cmd) + _async_curr_wr->size; + + return (buf_fill); +} + +tft_dma_cmd * +tft_driver_ili9341::dma_buf_curr_wr(void) +{ + return ((tft_dma_cmd *)_async_curr_wr); +} + +tft_dma_cmd * +tft_driver_ili9341::dma_buf_next_wr(void) +{ + size_t sz; + uint8_t *bytes; + tft_dma_cmd *tmp; + + sz = sizeof(tft_dma_cmd) + _async_curr_wr->size; + bytes = (uint8_t *)_async_curr_wr; + + tmp = (tft_dma_cmd *)&bytes[sz]; + tmp->size = 0; + + return ((tft_dma_cmd *)(_async_curr_wr = tmp)); +} + +const tft_dma_cmd * +tft_driver_ili9341::dma_buf_curr_rd(void) +{ + return ((const tft_dma_cmd *)_async_curr_rd); +} + +const tft_dma_cmd * +tft_driver_ili9341::dma_buf_next_rd(void) +{ + size_t sz; + uint8_t *bytes; + + sz = sizeof(tft_dma_cmd) + _async_curr_rd->size; + bytes = (uint8_t *)_async_curr_rd; + + return ((const tft_dma_cmd *) + (_async_curr_rd = (tft_dma_cmd *)&bytes[sz])); +} + + + + +int +tft_driver_ili9341::render_dma_buf_set_addr_window(uint16_t col_st, + uint16_t col_end, uint16_t row_st, uint16_t row_end) +{ + size_t n; + tft_dma_cmd *cmd; + + if (_async_dma_sz - dma_buf_sz_used() < + 2 * (sizeof(tft_dma_cmd) + sizeof(uint8_t) + 2 * sizeof(uint16_t))) + return (-1); + + cmd = dma_buf_curr_wr(); + + n = 0; + cmd->buf[n++] = ILI9341_CASET; + cmd->buf[n++] = (uint8_t)((col_st >> 8) & 0xff); + cmd->buf[n++] = (uint8_t)(col_st & 0xff); + cmd->buf[n++] = (uint8_t)((col_end >> 8) & 0xff); + cmd->buf[n++] = (uint8_t)(col_end & 0xff); + + cmd->size = n; + + cmd = dma_buf_next_wr(); + + n = 0; + cmd->buf[n++] = ILI9341_PASET; + cmd->buf[n++] = (uint8_t)((row_st >> 8) & 0xff); + cmd->buf[n++] = (uint8_t)(row_st & 0xff); + cmd->buf[n++] = (uint8_t)((row_end >> 8) & 0xff); + cmd->buf[n++] = (uint8_t)(row_end & 0xff); + + cmd->size = n; + + dma_buf_next_wr(); + + return (0); +} + +int +tft_driver_ili9341::render_dma_buf_memwr(const tft_color *col, size_t num) +{ + size_t i, n; + uint16_t c16; + tft_dma_cmd *cmd; + + if (_async_dma_sz - dma_buf_sz_used() < + sizeof(tft_dma_cmd) + sizeof(uint8_t) + num * sizeof(uint16_t)) + return (-1); + + cmd = dma_buf_curr_wr(); + + c16 = col->u16(); + i = 0; + cmd->buf[i++] = ILI9341_RAMWR; + for (n = 0; n < num; n++) { + cmd->buf[i++] = (uint8_t)((c16 >> 8) & 0xff); + cmd->buf[i++] = (uint8_t)(c16 & 0xff); + } + + cmd->size = i; + + dma_buf_next_wr(); + + return (0); +} + +int +tft_driver_ili9341::render_dma_buf_memwr_cont(const tft_color *col, size_t num) +{ + size_t i, n; + uint16_t c16; + tft_dma_cmd *cmd; + + if (_async_dma_sz - dma_buf_sz_used() < + sizeof(tft_dma_cmd) + sizeof(uint8_t) + num * sizeof(uint16_t)) + return (-1); + + cmd = dma_buf_curr_wr(); + + c16 = col->u16(); + i = 0; + cmd->buf[i++] = ILI9341_WRCONT; + for (n = 0; n < num; n++) { + cmd->buf[i++] = (uint8_t)((c16 >> 8) & 0xff); + cmd->buf[i++] = (uint8_t)(c16 & 0xff); + } + + cmd->size = i; + + dma_buf_next_wr(); + + return (0); +} +int +tft_driver_ili9341::render_dma_buf_hline(const tft_color *c, uint16_t col_s, + uint16_t col_e, uint16_t row) +{ + if (render_dma_buf_set_addr_window(col_s, col_e, row, row + 1) == -1) + return (-1); + + if (render_dma_buf_memwr(c, (size_t)(col_e - col_s)) == -1) + return (-1); + + return (0); +} + +int +tft_driver_ili9341::render_dma_buf_hline_row_bg_fill(const tft_color *bg, + const tft_color *fg, uint16_t col_s, uint16_t col_e, uint16_t row) +{ + if (col_e < col_s || width() < col_e) + return (-1); + + if (render_dma_buf_set_addr_window(0, width(), row, row+1) == -1) + return (-1); + + if (render_dma_buf_memwr(bg, (size_t)col_s) == -1) + return (-1); + if (render_dma_buf_memwr_cont(fg, (size_t)(col_e - col_s)) == -1) + return (-1); + if (render_dma_buf_memwr_cont(bg, (size_t)(width() - col_e)) == -1) + return (-1); + + return (0); +} + +#define TFT_N_PIXELS (ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT) +#define TFT_BLOCK_SZ ((size_t)16386) +int +tft_driver_ili9341::fill_screen(const tft_color *c) +{ + uint8_t buf[TFT_BLOCK_SZ]; + size_t n, i; + uint16_t c16; + + set_addr_window(0, width(), 0, height()); + if (send_sync(ILI9341_RAMWR, NULL, 0) == -1) + return (-1); + + i = 0; + c16 = c->u16(); + for (n = 0; n < sizeof(buf) / sizeof(uint16_t); n++) { + buf[i++] = (uint8_t)((c16 >> 8) & 0xff); + buf[i++] = (uint8_t)(c16 & 0xff); + } + + for (n = 0; + n < TFT_N_PIXELS * sizeof(uint16_t); + n += i) { + if (send_raw_sync(buf, + std::min(i, (TFT_N_PIXELS * sizeof(uint16_t)) - n)) == -1) + return (-1); + } + + return (0); +} + +int +tft_driver_ili9341::send_sync(const tft_cmd *cmd) +{ + if (send_sync(cmd->cmd, cmd->param_bytes(), cmd->param_len) == -1) + return (-1); + + if (cmd->post_delay > 0) + System::Delay(cmd->post_delay); + + return (0); +} + +int +tft_driver_ili9341::send_sync(uint8_t cmd, const uint8_t *buf, size_t len) +{ + SpiHandle::Result res; + + dcx_low(); + res = spi.BlockingTransmit(&cmd, 1); + dcx_high(); + + if (res == SpiHandle::Result::ERR) + return (-1); + + if (len == 0) + return (0); + + res = spi.BlockingTransmit((uint8_t *)buf, len); + + return (res == SpiHandle::Result::ERR ? -1 : 0); +} + +int +tft_driver_ili9341::send_raw_sync(const uint8_t *buf, size_t len) +{ + size_t block_st, transfer_sz; + SpiHandle::Result res; + + res = SpiHandle::Result::OK; + for (block_st = 0; block_st < len; block_st += block_sz()) { + transfer_sz = std::min(block_sz(), len - block_st); + res = spi.BlockingTransmit((uint8_t *)&buf[block_st], + transfer_sz); + } + + return (res == SpiHandle::Result::ERR ? -1 : 0); +} + diff --git a/daisy/scope/tft.hpp b/daisy/scope/tft.hpp new file mode 100644 index 0000000..a7ed2b0 --- /dev/null +++ b/daisy/scope/tft.hpp @@ -0,0 +1,288 @@ +#ifndef _TFT_HPP +#define _TFT_HPP + +#include "daisy.h" + +#include "scope.hpp" +#include "audioc.hpp" + +using namespace daisy; + +#define ILI9341_TFTWIDTH 240 ///< ILI9341 max TFT width +#define ILI9341_TFTHEIGHT 320 ///< ILI9341 max TFT height + +#define ILI9341_NOP 0x00 ///< No-op register +#define ILI9341_SWRESET 0x01 ///< Software reset register +#define ILI9341_RDDID 0x04 ///< Read display identification information +#define ILI9341_RDDST 0x09 ///< Read Display Status + +#define ILI9341_SLPIN 0x10 ///< Enter Sleep Mode +#define ILI9341_SLPOUT 0x11 ///< Sleep Out +#define ILI9341_PTLON 0x12 ///< Partial Mode ON +#define ILI9341_NORON 0x13 ///< Normal Display Mode ON + +#define ILI9341_RDMODE 0x0A ///< Read Display Power Mode +#define ILI9341_RDMADCTL 0x0B ///< Read Display MADCTL +#define ILI9341_RDPIXFMT 0x0C ///< Read Display Pixel Format +#define ILI9341_RDIMGFMT 0x0D ///< Read Display Image Format +#define ILI9341_RDSELFDIAG 0x0F ///< Read Display Self-Diagnostic Result + +#define ILI9341_INVOFF 0x20 ///< Display Inversion OFF +#define ILI9341_INVON 0x21 ///< Display Inversion ON +#define ILI9341_GAMMASET 0x26 ///< Gamma Set +#define ILI9341_DISPOFF 0x28 ///< Display OFF +#define ILI9341_DISPON 0x29 ///< Display ON + +#define ILI9341_CASET 0x2A ///< Column Address Set +#define ILI9341_PASET 0x2B ///< Page Address Set +#define ILI9341_RAMWR 0x2C ///< Memory Write +#define ILI9341_RAMRD 0x2E ///< Memory Read + +#define ILI9341_PTLAR 0x30 ///< Partial Area +#define ILI9341_VSCRDEF 0x33 ///< Vertical Scrolling Definition +#define ILI9341_MADCTL 0x36 ///< Memory Access Control +#define ILI9341_VSCRSADD 0x37 ///< Vertical Scrolling Start Address +#define ILI9341_PIXFMT 0x3A ///< COLMOD: Pixel Format Set +#define ILI9341_WRCONT 0x3C ///< Continue Memory Write + +#define ILI9341_DBRIGHT 0x51 + +#define ILI9341_FRMCTR1 \ + 0xB1 ///< Frame Rate Control (In Normal Mode/Full Colors) +#define ILI9341_FRMCTR2 0xB2 ///< Frame Rate Control (In Idle Mode/8 colors) +#define ILI9341_FRMCTR3 \ + 0xB3 ///< Frame Rate control (In Partial Mode/Full Colors) +#define ILI9341_INVCTR 0xB4 ///< Display Inversion Control +#define ILI9341_DFUNCTR 0xB6 ///< Display Function Control + +#define ILI9341_PWCTR1 0xC0 ///< Power Control 1 +#define ILI9341_PWCTR2 0xC1 ///< Power Control 2 +#define ILI9341_PWCTR3 0xC2 ///< Power Control 3 +#define ILI9341_PWCTR4 0xC3 ///< Power Control 4 +#define ILI9341_PWCTR5 0xC4 ///< Power Control 5 +#define ILI9341_VMCTR1 0xC5 ///< VCOM Control 1 +#define ILI9341_VMCTR2 0xC7 ///< VCOM Control 2 + +#define ILI9341_RDID1 0xDA ///< Read ID 1 +#define ILI9341_RDID2 0xDB ///< Read ID 2 +#define ILI9341_RDID3 0xDC ///< Read ID 3 +#define ILI9341_RDID4 0xDD ///< Read ID 4 + +#define ILI9341_GMCTRP1 0xE0 ///< Positive Gamma Correction +#define ILI9341_GMCTRN1 0xE1 ///< Negative Gamma Correction +//#define ILI9341_PWCTR6 0xFC + +// Color definitions +#define ILI9341_BLACK 0x0000 ///< 0, 0, 0 +#define ILI9341_NAVY 0x000F ///< 0, 0, 123 +#define ILI9341_DARKGREEN 0x03E0 ///< 0, 125, 0 +#define ILI9341_DARKCYAN 0x03EF ///< 0, 125, 123 +#define ILI9341_MAROON 0x7800 ///< 123, 0, 0 +#define ILI9341_PURPLE 0x780F ///< 123, 0, 123 +#define ILI9341_OLIVE 0x7BE0 ///< 123, 125, 0 +#define ILI9341_LIGHTGREY 0xC618 ///< 198, 195, 198 +#define ILI9341_DARKGREY 0x7BEF ///< 123, 125, 123 +#define ILI9341_BLUE 0x001F ///< 0, 0, 255 +#define ILI9341_GREEN 0x07E0 ///< 0, 255, 0 +#define ILI9341_CYAN 0x07FF ///< 0, 255, 255 +#define ILI9341_RED 0xF800 ///< 255, 0, 0 +#define ILI9341_MAGENTA 0xF81F ///< 255, 0, 255 +#define ILI9341_YELLOW 0xFFE0 ///< 255, 255, 0 +#define ILI9341_WHITE 0xFFFF ///< 255, 255, 255 +#define ILI9341_ORANGE 0xFD20 ///< 255, 165, 0 +#define ILI9341_GREENYELLOW 0xAFE5 ///< 173, 255, 41 +#define ILI9341_PINK 0xFC18 ///< 255, 130, 198 + + +struct __attribute__((packed)) tft_color { + uint8_t red; + uint8_t green; + uint8_t blue; + // uint16_t c16; + + tft_color() : red(0x00), green(0), blue(0x00) {}; + tft_color(uint8_t r, uint8_t g, uint8_t b) : + red(r), green(g), blue(b) {}; + + inline uint16_t u16(void) const + { + uint16_t ret = 0; + ret |= ((uint16_t)(red & 0xf8) << 8); + ret |= ((uint16_t)(green & 0xfc)) << 5; + ret |= ((uint16_t)blue) >> 3; + return (ret); + } +}; + +struct tft_dma_cmd { + size_t size; + uint8_t buf[]; +}; + +#define TFT_CMD_PARAMS_MAXLEN 16 +struct tft_cmd { + uint8_t cmd; + uint8_t _param_bytes[TFT_CMD_PARAMS_MAXLEN]; + size_t param_len; + uint8_t *param_bytes_ext; + int post_delay; + + tft_cmd(uint8_t c, std::initializer_list params, + int dly = -1) + : cmd(c), param_len(params.size()), post_delay(dly) + { + size_t i; + + AUDIOC_ASSERT(param_len <= sizeof(_param_bytes)); + + i = 0; + for (auto par : params) { + _param_bytes[i++] = par; + } + } + tft_cmd(uint8_t c, int dly = -1) + : cmd(c), param_len(0), post_delay(dly) {}; + + inline const uint8_t *param_bytes(void) const + { + return (_param_bytes); + } +}; + +#define TFT_DRIVER_ILI9341_NBLOCKS 8 + +struct tft_driver_ili9341 { + typedef void (*async_on_finish_f)(void *); + + enum class async_status { + INIT, + READY, + TX, + NO_TX, + DONE + }; + + enum class async_wr { + START, + END + }; + + enum class async_hint { + NONE, + FULL_UPDATE_RENDER, + PARTIAL_UPDATE_RENDER + }; + + SpiHandle spi; + SpiHandle::Config spi_conf; + + Logger *lg; + + Pin dcx_pin; + GPIO dcx; + + async_hint _async_hint; + volatile async_status _async_stat; + volatile async_wr _async_wr; + volatile async_on_finish_f _async_cb_fun; + volatile void *_async_cb_ctx; + + uint8_t *_async_dma_buf; + size_t _async_dma_sz; + + volatile tft_dma_cmd *_async_curr_wr; + volatile const tft_dma_cmd *_async_curr_rd; + + /* general */ + inline uint16_t width(void) { return (ILI9341_TFTWIDTH); } + inline uint16_t height(void) { return (ILI9341_TFTHEIGHT); } + inline size_t block_rows(void) { + return (sizeof(tft_color) * height() / + TFT_DRIVER_ILI9341_NBLOCKS); + } + inline size_t block_sz(void) { + return (sizeof(tft_color) * width() * height() / + TFT_DRIVER_ILI9341_NBLOCKS); + } + + tft_driver_ili9341(void) : _async_hint(async_hint::NONE), + _async_stat(async_status::INIT), _async_wr(async_wr::END), + _async_dma_buf(NULL), _async_dma_sz(0), + _async_curr_wr(NULL), _async_curr_rd(NULL) {}; + + int init(uint8_t *dma_buf, size_t dma_sz); + + /* syncronous */ + int fill_screen(const tft_color *c); + void set_addr_window(uint16_t col_st, uint16_t width, uint16_t row_st, + uint16_t height); + + int send_sync(const tft_cmd *cmd); + int send_sync(uint8_t cmd, const uint8_t *buf, size_t len); + int send_raw_sync(const uint8_t *buf, size_t len); + ssize_t recv_raw_sync(const uint8_t *buf, size_t len); + + /* asyncronous */ + void async_on_finish(async_on_finish_f cb, void *ctx); + + int async_start(void); + int async_end(void); + + async_status async_stat(void); + + void async_flush(void); + void async_tx(void); + void async_tx_try(void); + + int async_cmd_hint(async_hint hint); + int async_hline_row_bg_fill(tft_color *bg, tft_color *fg, + uint16_t hline_idx_st, uint16_t hline_idx_end, uint16_t row_idx); + + static void async_tx_cmd_scb(void *ctx); + static void async_tx_cmd_ecb(void *ctx, SpiHandle::Result res); + static void async_tx_param_scb(void *ctx); + static void async_tx_param_ecb(void *ctx, SpiHandle::Result res); + + inline int + async_send_raw(const uint8_t *dma_buf, size_t dma_bufsz, + SpiHandle::StartCallbackFunctionPtr scb, + SpiHandle::EndCallbackFunctionPtr cb, + void *ctx) { + return (spi.DmaTransmit((uint8_t *)dma_buf, dma_bufsz, scb, + cb, ctx) == SpiHandle::Result::OK ? 0 : -1); + } + + size_t dma_buf_sz_tx_pend(void); + size_t dma_buf_sz_used(void); + + tft_dma_cmd *dma_buf_curr_wr(void); + tft_dma_cmd *dma_buf_next_wr(void); + + const tft_dma_cmd *dma_buf_curr_rd(void); + const tft_dma_cmd *dma_buf_next_rd(void); + + /* rendering */ + int render_dma_buf_hline(const tft_color *c, uint16_t col_s, + uint16_t col_e, uint16_t row); + int render_dma_buf_memwr(const tft_color *col, size_t num); + int render_dma_buf_memwr_cont(const tft_color *col, size_t num); + int render_dma_buf_set_addr_window(uint16_t col_st, + uint16_t col_end, uint16_t row_st, uint16_t row_end); + int render_dma_buf_hline_row_bg_fill(const tft_color *bg, + const tft_color *fg, uint16_t col_s, uint16_t col_e, uint16_t row); + + /* gpio control */ + inline void + dcx_low(void) + { + dcx.Write(0); + } + + inline void + dcx_high(void) + { + dcx.Write(1); + } +}; + +#endif diff --git a/make/daisy.mk b/make/daisy.mk new file mode 100644 index 0000000..844938c --- /dev/null +++ b/make/daisy.mk @@ -0,0 +1,10 @@ +PROJECT_DIR = /Users/ellen/code/daisy + +# Path to libDaisy and DaisySP +LIBDAISY_DIR = $(PROJECT_DIR)/libDaisy +DAISYSP_DIR = $(PROJECT_DIR)/DaisySP + +# Core location, and generic makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +include $(SYSTEM_FILES_DIR)/Makefile