Hi, my sketch has menu items in a list, one below the next, but each items "y" position is "pos * 9 + 1"
how does that work?
shouldn't they each be 0-240 (size of display)
fvduino.ino (37.4 KB)
Hi, my sketch has menu items in a list, one below the next, but each items "y" position is "pos * 9 + 1"
how does that work?
shouldn't they each be 0-240 (size of display)
fvduino.ino (37.4 KB)
Please follow the advice given in the link below when posting code , use code tags and post the code here to make it easier to read and copy for examination
It looks like your controls are the full width of the screen, but only a few pixels (9?) high.
#include <Arduino.h>
//#include <Bounce2.h>
#include <EEPROM.h>
#include <ButtonEvents.h>
#include <avr/power.h>
typedef unsigned char PROGMEM prog_uchar;
#include "HW_I2CMaster.h"
#include "font.h"
const uint16_t MARKED = 0x3000; //0x0020
const uint16_t NONE = 0xFFFF; // this is used for rectangle inner color when not used
#define BypassFSW A0
////////////////////////////////////////////////////////////////////////////////////////////////
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Arduino_ST7789_Fast.h>
#define TFT_DC A2
#define TFT_RST 1
#define SCR_WD 240
#define SCR_HT 240
Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST);
////////////////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------NEOPIXEL---------------------------------------------
#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN 7
#define PIXEL_COUNT 1
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
// Yellow Preset 1
int r0 = 255;
int g0 = 255;
int b0 = 0;
// Green Preset 2
int r1 = 0;
int g1 = 255;
int b1 = 0;
// Red Preset 3
int r2 = 255;
int g2 = 0;
int b2 = 0;
// Grey Preset 4
int r3 = 128;
int g3 = 128;
int b3 = 128;
// Blue Preset 5
int r4 = 0;
int g4 = 0;
int b4 = 255;
// Magenta Preset 6
int r5 = 255;
int g5 = 0;
int b5 = 255;
// Orange Preset 7
int r6 = 230;
int g6 = 80;
int b6 = 0;
// Cyan Preset 8
int r7 = 0;
int g7 = 255;
int b7 = 255;
// ----------------------------------------NEOPIXEL---------------------------------------------
// Pin configuration
// Digital potentiometer uses hardware I2C (TWI), pins A4 and A5
const uint8_t POT_ENABLE_PIN = A3;
//const uint8_t POT_SDA_PIN = A4;
//const uint8_t POT_SCL_PIN = A5;
// Three PWM outputs
const uint8_t PAR1_PWM_PIN = 3;
const uint8_t PAR2_PWM_PIN = 9;
const uint8_t PAR3_PWM_PIN = 10;
// Two buttons.
const uint8_t PresetFSW = 8; // Preset Footswitch
const uint8_t buttonPin = 0; // Select/Save Encoder Button
//ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button
// Encoder
const uint8_t ENC1_PIN = 2;
const uint8_t ENC2_PIN = A1;
// EEPROM emulation and FV-1 program switching
const uint8_t PC_NOTIFY_PIN = 4;
const uint8_t I2C_EEPROM_EMUL_SDA_PIN = 5;
const uint8_t I2C_EEPROM_EMUL_SCL_PIN = 6;
const uint8_t EEPROM_EMUL_ADDR = 0x50;
// OLED display commununication
// For reference here, oled code uses port and bit banging
//const uint8_t OLED_DATA_PIN = 13;
//const uint8_t OLED_CS_PIN = 12;
//const uint8_t OLED_CLK_PIN = 11;
// Digipot address
const uint8_t DS1881_BASE_I2C_ADDR = 0x28;
const uint8_t DS1881_write_address = DS1881_BASE_I2C_ADDR << 1;
const uint8_t DS1881_read_address = (DS1881_BASE_I2C_ADDR << 1) | _BV(0);
/*********** FV-1 algorithm data structures ***********/
// The eeprom data is in 16 * 32 byte banks (total 512 bytes)
// They are compressed by LZ algorithm (compress.py) to approx.
// half the size. Include the algorithm data blocks here.
#include "spinsemi.h"
// Strings for describing the algorithm and parameters
typedef struct {
char algoname [14];
char par1name [6];
char par2name [6];
char par3name [6];
} Description;
// Sensible default settings for each algorithm
typedef struct {
uint8_t vol;
uint8_t mix;
uint8_t par1;
uint8_t par2;
uint8_t par3;
} Default;
// Keep everything together in a single array
typedef struct {
Description desc;
Default def;
uint8_t* const prog_addr;
} AlgoDatum;
const AlgoDatum algodata [] PROGMEM = {
{{"Hall", "Pre", "Time", "Damp" }, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_0_Hall},
{{"Room", "Pre", "Time", "Damp" }, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_1_Room},
{{"Plate Reverb", "PreD", "Time", "Damp" }, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_2_PLATE},
{{"Rep Echo/Rev", "Delay", "Rep", "Revrb"}, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_4_ECHO_REV},
{{"Chorus+Revrb", "Width", "Sweep", "Revrb"}, {15, 20, 128, 128, 128}, (uint8_t *)K3_V1_6_CHOR_REV},
{{"Echo/Revrb2", "Revrb", "Time", "Level"}, {15, 10, 128, 128, 128}, (uint8_t *)GA_DEMO_ECHO},
{{"Flanger+Rev", "Revrb", "Rate", "Depth"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_FLANGE},
{{"Phaser+Rev", "Revrb", "Rate", "Width"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_PHASE},
{{"VibraVerb", "Revrb", "Speed", "Depth"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_VIBRATO},
{{"Filter Verb", "Revrb", "Sens", "Depth"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_WAH},
{{"Rom Rev 1", "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)rom_rev1},
{{"Rom Rev 2", "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)rom_rev2},
{{"Tremolo+Rev", "Revrb", "Rate", "Trem" }, {15, 10, 128, 128, 128}, (uint8_t *)rom_trem_rev},
{{"Spring+Trem", "Revrb", "Rate", "Depth"}, {15, 10, 128, 128, 128}, (uint8_t *)spring_verb},
{{"Reverse Dly", "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)afx},
{{"Abbey Reverb", "Par 1", "Par 2", "Par 3"}, {15, 20, 128, 128, 128}, (uint8_t *)abbeyverb},
{{"Choirsaw", "Delay", "Width", "Trem"}, {15, 10, 128, 128, 128}, (uint8_t *)choirsaw},
{{"Blue Nebula", "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)bluenebula},
{{"Shimmer", "Dwell", "Pitch", "Mix"}, {15, 10, 128, 128, 128}, (uint8_t *)glimmer},
{{"Flanger", "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)flanger},
};
// The space for printing algo number on screen is two characters
// In practice, about 80 algorithms is the max that fits on atmega328 32k memory
const uint8_t NALGO = min(99, sizeof(algodata) / sizeof(AlgoDatum));
// Algorithm will be copied to RAM before sending because PROGMEM is too slow
uint8_t algo_buffer[512];
// The strings of the currently visible algorithm are copied to ram
Description current_algo_strings;
/************** Patch data structures *****************/
typedef struct {
uint8_t algorithm; // index
uint8_t vol;
uint8_t mix;
uint8_t par1;
uint8_t par2;
uint8_t par3;
} Patch;
// 5 RGB 565 colours for each patch, from dark to light
const uint16_t patch_colors [][5] = {
{0x5940, 0xD1A0, 0xFC20, 0xFD80, 0xFE85}, // Amber
{0x0220, 0x0C80, 0x3640, 0x4F29, 0xA7D4}, // Green
{0x7820, 0xE060, 0xFA8A, 0xFBCF, 0xFDD7}, // Red
{0x31E8, 0x4ACC, 0x638F, 0xA577, 0xC639}, // Gray
{0x00C9, 0x0230, 0x0457, 0x965B, 0xDF5E}, // Blue
{0x3807, 0x8811, 0xF81F, 0xFC5F, 0xFEDF}, // Mag
{0x8285, 0x9307, 0xBBE9, 0xD52D, 0xFFFF}, // Brown
{0x1112, 0x73FD, 0xBD5F, 0x36BC, 0xAFDF}, // Cyan
};
const uint8_t NPATCH = sizeof(patch_colors) / sizeof(patch_colors[0]);
Patch patch_data[NPATCH]; // Keep all patch data in memory, store and restore in EEPROM
const uint16_t P_IN_USE_EE_ADDR = 0;
const uint16_t PATCH_EE_ADDR = 1;
const uint16_t ALGO_EE_ADDR = 512;
uint8_t changed[NPATCH]; // to keep track of patch edit
// State machine for user interface
enum EditState {None, Select, Change};
EditState es = None;
uint8_t selection = 5; // default start from line 5
uint8_t patches_in_use = 2;
uint8_t current_patch = 0;
uint16_t bg_col = BLACK; // bg_col will be different if patch is edited but not saved
/************** Digipot code ****************/
/* Calculate actual pot positions from vol and mix values
vol goes from 0 to 20
17 is unity gain (corresponding to pot volume 60)
mix goes from 0 to 20
10 is 1:1 mix max wet - max dry
below 10 is less wet - max dry
above 10 is max wet - less dry
*/
uint8_t dry_from(uint8_t vol, uint8_t mix)
{
if (mix > 20)
mix = 20;
if (vol > 20)
vol = 20;
if (vol == 0 || mix == 20)
return 0; // dry is muted
uint8_t nominal_vol = 43 + vol; // Max 63
if (mix <= 10)
return nominal_vol; // dry is maxed
uint8_t vol_adjustment = mix - 10;
if (vol_adjustment > nominal_vol)
return 0; // dry is muted
return nominal_vol - vol_adjustment;
}
uint8_t wet_from(uint8_t vol, uint8_t mix)
{
if (mix > 20)
mix = 20;
if (vol > 20)
vol = 20;
if (vol == 0 || mix == 0)
return 0; // wet is muted
uint8_t nominal_vol = 43 + vol; // Max 63
if (mix >= 10)
return nominal_vol; // wet is maxed
uint8_t vol_adjustment = 10 - mix;
if (vol_adjustment > nominal_vol)
return 0; // wet is muted
return nominal_vol - vol_adjustment;
}
/* Run only once during factory default setup.
*/
bool pot_init()
{
digitalWrite(POT_ENABLE_PIN, LOW); // enable pot communication
if (!i2c_start(DS1881_write_address))
return false; // no reply from device
i2c_write(0b10000010); // Nonvolatile writes: Zero crossing, 63 values + mute
i2c_stop();
//delay(20); // wait for EEPROM write to finish
delay(10); // half delay when clocking nano to 8 MHz
i2c_start(DS1881_write_address);
i2c_write(0b10000110); // Go back to volatile writes
i2c_stop();
//delay(20); // wait for EEPROM write to finish
delay(10); // half delay when clocking nano to 8 MHz
digitalWrite(POT_ENABLE_PIN, HIGH);
return true;
}
/* Set the digipot to given value.
Min volume 0: -80db attenuation,
Max volume 63: 0db attenuation
*/
bool pot_set(uint8_t pot, uint8_t val)
{
val &= 0b00111111; // only the low 6 bits matter
val = 63 - val; // invert the value.
// pot acts as attenuator: 0 - no attenuation (max volume)
// we want larger number to mean louder
if (pot)
val |= 0b01000000; // set the pot bit;
digitalWrite(POT_ENABLE_PIN, LOW); // enable pot communication
if (!i2c_start(DS1881_write_address))
return false; // no reply from device
bool res = i2c_write(val);
i2c_stop();
digitalWrite(POT_ENABLE_PIN, HIGH);
return res;
}
/*************** User Interface code **********************/
using Bounce2::Button;
Button footswitch = Button();
ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button
void draw_selection(const uint16_t* colors, uint8_t pos, bool emph)
{
uint8_t y = pos * 9;
uint16_t col = emph ? colors[4] : colors[2];
draw_rect(0 , y, SCR_WD , 10, col, NONE, false); // Selection box
}
void clear_selection(uint8_t pos)
{
uint8_t y = pos * 9;
draw_rect(0 , y, SCR_WD , 10, bg_col, NONE, false);
}
void draw_patch ()
{
tft.fillRect(0, 54, SCR_WD, 10, bg_col); // clear algorithm name area with background color
char buff[3]; // 3 chars + \0
buff[0] = ' '; // put spaces in tens and hundreds position
byte algo = patch_data[current_patch].algorithm + 1;
btoa(algo, buff + 3); // give pointer to just after buffer (end)
// Print algo number
//plot_text(83, 6 * 9 + 1, buff, patch_colors[current_patch][4]);
tft.setCursor(220, 6 * 9 + 1);
tft.setTextColor(patch_colors[current_patch][4]);
tft.setTextSize(1);
tft.print(buff); // Algo number
// Print algo name
// plot_text(2, 6 * 9 + 1, current_algo_strings.algoname, patch_colors[current_patch][4]);
tft.setCursor(2, 6 * 9 + 1);
tft.setTextColor(patch_colors[current_patch][4]);
tft.setTextSize(1);
tft.print(current_algo_strings.algoname); // Algo name
const uint16_t * palette = patch_colors[current_patch];
clear_selection(5);
draw_volume(palette, bg_col, 5, patch_data[current_patch].vol);
clear_selection(4);
draw_mix(palette, bg_col, 4, patch_data[current_patch].mix);
clear_selection(3);
draw_parameter(palette, bg_col, current_algo_strings.par1name, 3, patch_data[current_patch].par1);
clear_selection(2);
draw_parameter(palette, bg_col, current_algo_strings.par2name, 2, patch_data[current_patch].par2);
clear_selection(1);
draw_parameter(palette, bg_col, current_algo_strings.par3name, 1, patch_data[current_patch].par3);
}
void draw_panel()
{
uint8_t boxlen = SCR_WD / NPATCH; // Preset boxes
tft.drawRect(1, 1, 238, 8, bg_col);
tft.fillRect(1, 1, 238, 8, bg_col);
for (uint8_t i = 0; i < patches_in_use; ++i) {
if (i == current_patch) {
tft.drawRect(boxlen * i , 1, boxlen , 8, patch_colors[i][2]);
tft.fillRect(boxlen * i , 1, boxlen , 8, patch_colors[i][3]);
} else {
tft.drawRect(boxlen * i , 1, boxlen , 8, bg_col);
tft.fillRect(boxlen * i , 1, boxlen , 8, patch_colors[i][0]);
}
}
}
void change_selection(const uint16_t * palette, int8_t dir)
{
if (dir < -1) dir = -1;
if (dir > 1) dir = 1;
uint8_t oldsel = selection;
selection += dir + 7;
selection %= 7;
clear_selection(oldsel);
draw_selection(palette, selection, false);
}
void set_patch()
{
// Turn digipots
uint8_t dry = dry_from(patch_data[current_patch].vol, patch_data[current_patch].mix);
uint8_t wet = wet_from(patch_data[current_patch].vol, patch_data[current_patch].mix);
pot_set(0, dry);
pot_set(1, wet);
// Set analog signal to FV-1
analogWrite(PAR1_PWM_PIN, patch_data[current_patch].par1);
analogWrite(PAR2_PWM_PIN, patch_data[current_patch].par2);
analogWrite(PAR3_PWM_PIN, patch_data[current_patch].par3);
}
void change_patch(int8_t dir)
{
current_patch += dir + patches_in_use;
current_patch %= patches_in_use;
bg_col = changed[current_patch] ? MARKED : BLACK;
// Save active patch to EEPROM?
// 100000 writes promised -> 1000 days 100 patch changes per day
// Maybe not worth it...
send_algo();
set_patch();
// copy current patch strings for ui
uint8_t algo = patch_data[current_patch].algorithm;
memcpy_P(¤t_algo_strings, &(algodata[algo].desc), sizeof current_algo_strings);
// print changed gui
draw_panel();
draw_patch();
}
// Valid values for dir are -1 and 1
void edit_algo(int8_t dir)
{
patch_data[current_patch].algorithm += dir + NALGO;
patch_data[current_patch].algorithm %= NALGO;
uint8_t algo = patch_data[current_patch].algorithm;
// Copy current patch strings for ui
memcpy_P(¤t_algo_strings, &(algodata[algo].desc), sizeof current_algo_strings);
// Set params to default values. Copy 5 bytes from EEPROM
Default tmp;
EEPROM.get(ALGO_EE_ADDR + algo * sizeof(Default), tmp);
patch_data[current_patch].vol = tmp.vol;
patch_data[current_patch].mix = tmp.mix;
patch_data[current_patch].par1 = tmp.par1;
patch_data[current_patch].par2 = tmp.par2;
patch_data[current_patch].par3 = tmp.par3;
changed[current_patch] = true;
send_algo();
set_patch();
// print updated elements
draw_patch();
const uint16_t * palette = patch_colors[current_patch];
draw_selection(palette, selection, true);
}
void edit_vol(int8_t dir, const uint8_t pos)
{
uint8_t vol = patch_data[current_patch].vol;
if (vol == 0 && dir < 0)
return; // At minimum
if (vol == 20 && dir > 0)
return; // Maxed out
vol += dir;
// Change the patch
patch_data[current_patch].vol = vol;
changed[current_patch] = true;
// Turn the digipots.
uint8_t dry = dry_from(vol, patch_data[current_patch].mix);
uint8_t wet = wet_from(vol, patch_data[current_patch].mix);
pot_set(0, dry);
pot_set(1, wet);
// Draw the change
const uint16_t * palette = patch_colors[current_patch];
draw_volume(palette, bg_col, pos, vol);
}
void edit_mix(int8_t dir, const uint8_t pos)
{
uint8_t mix = patch_data[current_patch].mix;
if (mix == 0 && dir < 0)
return; // At minimum
if (mix == 20 && dir > 0)
return; // At maximum
mix += dir;
// Change the patch
patch_data[current_patch].mix = mix;
changed[current_patch] = true;
// Turn the digipots.
uint8_t dry = dry_from(patch_data[current_patch].vol, mix);
uint8_t wet = wet_from(patch_data[current_patch].vol, mix);
pot_set(0, dry);
pot_set(1, wet);
// Draw the change
const uint16_t * palette = patch_colors[current_patch];
draw_mix(palette, bg_col, pos, mix);
}
// Valid values for dir are -1 and 1
//void edit_par(int8_t dir, bool fast, uint8_t pin, uint8_t &val, const char *parname, const uint8_t pos)
void edit_par(int8_t dir, uint8_t pin, uint8_t &val, const char *parname, const uint8_t pos)
{
// if (fast)
dir *= 10;
if (dir < 0 && val < -dir)
val = 0;
else if (dir > 0 && (255 - val) < dir)
val = 255;
else
val += dir;
const uint16_t * palette = patch_colors[current_patch];
changed[current_patch] = true;
analogWrite(pin, val);
draw_parameter(palette, bg_col, parname, pos, val);
}
void edit_menubar(int8_t dir)
{
if (dir < -1) dir = -1;
if (dir > 1) dir = 1;
// patches_in_use must be in [2, NPATCH]
if (patches_in_use <= 2 && dir < 0)
return;
if (patches_in_use >= NPATCH && dir > 0)
return;
const uint16_t * palette = patch_colors[current_patch];
patches_in_use += dir;
if (current_patch >= patches_in_use) {
change_patch(-1); // move to previous patch since current is not in use
draw_selection(palette, selection, true);
} else {
draw_panel();
draw_selection(palette, selection, true);
}
// write number of patches in use to eeprom
EEPROM.put(P_IN_USE_EE_ADDR, patches_in_use);
}
//void edit_patch(uint8_t sel, int8_t dir, bool fast)
void edit_patch(uint8_t sel, int8_t dir)
{
switch (sel) {
case 6 : edit_algo(dir); break;
case 5 : edit_vol(dir, sel); break;
case 4 : edit_mix(dir, sel); break;
case 3 : edit_par(dir, PAR1_PWM_PIN, patch_data[current_patch].par1, current_algo_strings.par1name, sel); break;
case 2 : edit_par(dir, PAR2_PWM_PIN, patch_data[current_patch].par2, current_algo_strings.par2name, sel); break;
case 1 : edit_par(dir, PAR3_PWM_PIN, patch_data[current_patch].par3, current_algo_strings.par3name, sel); break;
case 0 : edit_menubar(dir); break;
}
}
void save_current_patch()
{
uint16_t eeAddress = PATCH_EE_ADDR + current_patch * sizeof(Patch);
Patch curr = patch_data[current_patch];
EEPROM.put(eeAddress, curr);
// Save to algo defaults also so that next time they can be used
eeAddress = ALGO_EE_ADDR + curr.algorithm * sizeof(Default);
Default tmp = {curr.vol, curr.mix, curr.par1, curr.par2, curr.par3};
EEPROM.put(eeAddress, tmp);
changed[current_patch] = false;
bg_col = BLACK;
draw_panel();
draw_patch(); // Redraw the whole screen
}
/********** 24LC32A EEPROM emulation */
/* Compression algorithm based on
http://excamera.com/sphinx/article-compression.html
With small modifications.
*/
class Flashbits {
public:
void begin(prog_uchar *s)
{
src = s;
mask = curr_byte = 0x00;
}
uint8_t get1(void)
{
if (!mask) {
mask = 0x80;
curr_byte = pgm_read_byte_near(src++);
}
uint8_t r = (curr_byte & mask) != 0;
mask >>= 1;
return r;
}
uint16_t getn(uint8_t n)
{
uint16_t r = 0;
while (n--) {
r <<= 1;
r |= get1();
}
return r;
}
private:
prog_uchar *src;
uint8_t mask;
uint8_t curr_byte;
};
Flashbits bits;
void decompress(uint8_t *dst, prog_uchar *src)
{
bits.begin(src);
uint8_t O = bits.getn(4);
uint8_t L = bits.getn(4);
uint8_t M = bits.getn(2);
uint8_t *end = dst + bits.getn(16);
while (dst != end) {
if (bits.get1() == 0) {
*dst++ = bits.getn(8);
} else {
int offset = -bits.getn(O) - 1;
int length = bits.getn(L) + M;
while (length--) {
*dst = *(dst + offset);
dst++;
}
}
}
}
void send_algo()
{
// Copy FV-1 algorithm from PROGMEM to ram buffer.
// Progmem is too slow in the send loop.
// Since we will anyway have to copy from PROGMEM,
// it makes sense to compress the algorithms and get
// space savings (almost 2x).
uint8_t algo = patch_data[current_patch].algorithm;
decompress(algo_buffer, (prog_uchar *)pgm_read_word(&(algodata[algo].prog_addr)));
// Set up everything to be ready
const uint8_t sda_mask = (1 << PIND5);
const uint8_t clk_mask = (1 << PIND6);
uint8_t prev_clk = clk_mask;
DDRD &= ~(clk_mask); // clk pin as input
DDRD &= ~(sda_mask); // sda pin as input
PORTD &= ~(sda_mask); // SDA pin is pulled low or floated up
// -> we need to set pin low only once.
// Toggling is done by setting pin to
// input (let float high) or output (pull low).
// FV-1 internal pullups are ok (tested).
uint16_t pos = 0;
uint8_t curr_byte = algo_buffer[pos];
uint8_t bit_mask = 0b10000000;
uint8_t clk_count = 0;
// Undivided attention for FV-1 requests
noInterrupts();
// Notify FV-1 of patch change by toggling the notify pin
PIND = _BV(4); // Toggle pin 4
while (clk_count < 37) // Handle the header
{
uint8_t clk = PIND & clk_mask;
if (!clk && prev_clk) { // scl went down
switch (clk_count)
{
case 8:
case 17:
case 26:
case 36:
DDRD |= sda_mask; // send ACK - pull sda pin low
break;
default:
DDRD &= ~(sda_mask); // Release
break;
}
clk_count++;
}
prev_clk = clk;
}
clk_count = 0;
while (pos < 512) // Send the data
{
uint8_t clk = PIND & clk_mask;
if (!clk && prev_clk) { // scl went down
if (clk_count != 8) { // Sending byte
if (curr_byte & bit_mask) {
DDRD &= ~(sda_mask); // Send 1 = Let High
} else {
DDRD |= sda_mask; // Send 0 = Pull Low
}
bit_mask >>= 1;
clk_count++;
} else { // Let reciever ACK
DDRD &= ~(sda_mask); // Release
clk_count = 0;
bit_mask = 0b10000000;
pos++;
curr_byte = algo_buffer[pos];
}
}
prev_clk = clk;
}
interrupts();
}
void setup()
{
// ----------------------------------------NEOPIXEL---------------------------------------------
pixels.begin();
pixels.setPixelColor(0, 255, 255, 0);
pixels.show();
// ----------------------------------------NEOPIXEL---------------------------------------------
// set up pins
// Use high frequency PWM
TCCR1A = (TCCR1A & 0b11111100) | 0b00000001; // set timer 1 to 8 bit mode
TCCR1B = (TCCR1B & 0b11100000) | 0b00001001; // set timer 1 to fast PWM, divisor to 1 for PWM frequency of 31372.55 Hz
TCCR2A = (TCCR2A & 0b11111100) | 0b00000011; // set timer 2 to fast PWM
TCCR2B = (TCCR2B & 0b11111000) | 0b00000001; // set timer 2 divisor to 1 for PWM frequency of 31372.55 Hz
// PWM outputs
pinMode(PAR1_PWM_PIN, OUTPUT);
pinMode(PAR2_PWM_PIN, OUTPUT);
pinMode(PAR3_PWM_PIN, OUTPUT);
// 24LC32 EEPROM emulation
pinMode(PC_NOTIFY_PIN, OUTPUT);
pinMode(I2C_EEPROM_EMUL_SDA_PIN, INPUT);
pinMode(I2C_EEPROM_EMUL_SCL_PIN, INPUT);
// Buttons and encoder
pinMode(buttonPin, INPUT_PULLUP);
myButton.attach(buttonPin);
myButton.activeHigh();
myButton.debounceTime(15); //apply 5ms debounce
myButton.holdTime(750); // require button to be held for 1000ms before triggering a hold event
footswitch.attach(PresetFSW, INPUT);
footswitch.interval(5);
pinMode(ENC1_PIN, INPUT_PULLUP);
pinMode(ENC2_PIN, INPUT_PULLUP);
pinMode(BypassFSW, INPUT_PULLUP);
// SoftI2C is used for digipot control
pinMode(POT_ENABLE_PIN, OUTPUT);
i2c_init(); // Uses hardware I2C (pins A4 and A5)
// TFT Display setup
tft.init();
tft.setRotation(1);
tft.fillScreen(BLACK); // Black
// Write factory defaults to EEPROM if requested
// Footswitch pressed at boot for 2 sec
unsigned long startTime = millis();
unsigned long heldTime = 0;
while (digitalRead(PresetFSW) == HIGH)
{
heldTime = millis() - startTime;
if (heldTime > 1000)
break;
}
if (heldTime > 1000) {
// Digipot initial settings, saved in its EEPROM.
// Depends on i2c_init(). Hangs setup if no pot present...
pot_init();
// Write algorithm default settings
Default tmp_d;
for (uint8_t i = 0; i < NALGO; ++i) {
memcpy_P(&tmp_d, &(algodata[i].def), sizeof(Default));
EEPROM.put(ALGO_EE_ADDR + i * sizeof(Default), tmp_d);
delay(10);
rainbow(2);
pixels.show();
break;
}
// Write empty patches using the first algorithm and default settings
memcpy_P(&tmp_d, &(algodata[0].def), sizeof(Default));
Patch tmp_p = {0, tmp_d.vol, tmp_d.mix, tmp_d.par1, tmp_d.par2, tmp_d.par3};
for (uint8_t i = 0; i < NPATCH; ++i) {
EEPROM.put(PATCH_EE_ADDR + i * sizeof(Patch), tmp_p);
delay(10);
}
// Set 8 patches as default
EEPROM.put(P_IN_USE_EE_ADDR, (uint8_t)8);
}
// load patch settings from EEPROM
uint16_t eeAdress = PATCH_EE_ADDR;
for (uint8_t i = 0; i < NPATCH; ++i) {
EEPROM.get(eeAdress, patch_data[i]);
if (patch_data[i].algorithm >= NALGO) {
patch_data[i].algorithm = 0; // something wrong with algo index, set to zero
changed[i] = true; // user will have to edit and save
}
if (patch_data[i].vol > 20)
patch_data[i].vol = 20;
if (patch_data[i].mix > 100)
patch_data[i].mix = 100;
eeAdress += sizeof(Patch);
}
current_patch = 0;
// get patches_in_use from EEPROM
EEPROM.get(P_IN_USE_EE_ADDR, patches_in_use);
if (patches_in_use < 2)
patches_in_use = 2;
if (patches_in_use >= NPATCH)
patches_in_use = NPATCH;
// copy current patch strings for ui
uint8_t algo = patch_data[current_patch].algorithm;
memcpy_P(¤t_algo_strings, &(algodata[algo].desc), sizeof current_algo_strings);
// send current patch algorithm to FV-1
send_algo();
set_patch();
// print initial gui
draw_panel();
draw_patch();
}
/* Rotary encoder handling code from:
https://www.best-microcontroller-projects.com/rotary-encoder.html
*/
uint8_t prevNextCode = 0;
uint16_t store = 0;
// A valid CW or CCW move returns 1 or -1, invalid returns 0.
int8_t read_rotary() {
const int8_t rot_enc_table[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
prevNextCode <<= 2;
if (digitalRead(ENC1_PIN)) prevNextCode |= 0x02;
if (digitalRead(ENC2_PIN)) prevNextCode |= 0x01;
prevNextCode &= 0x0f;
// If valid then store as 16 bit data.
if (rot_enc_table[prevNextCode] ) {
store <<= 4;
store |= prevNextCode;
if ((store & 0xff) == 0x2b) return -1;
if ((store & 0xff) == 0x17) return 1;
}
return 0;
}
void loop()
{
if (digitalRead(BypassFSW) == HIGH)
{
if (current_patch == 0) {
pixels.setPixelColor(0, r0, g0, b0); // Yellow
pixels.show();
}
if (current_patch == 1) {
pixels.setPixelColor(0, r1, g1, b1); // Green
pixels.show();
}
if (current_patch == 2) {
pixels.setPixelColor(0, r2, g2, b2); // Red
pixels.show();
}
if (current_patch == 3) {
pixels.setPixelColor(0, r3, g3, b3); // Grey
pixels.show();
}
if (current_patch == 4) {
pixels.setPixelColor(0, r4, g4, b4); // Blue
pixels.show();
}
if (current_patch == 5) {
pixels.setPixelColor(0, r5, g5, b5); // Magenta
pixels.show();
}
if (current_patch == 6) {
pixels.setPixelColor(0, r6, g6, b6); // Orange
pixels.show();
}
if (current_patch == 7) {
pixels.setPixelColor(0, r7, g7, b7); // Cyan
pixels.show();
}
}
if (digitalRead(BypassFSW) == LOW)
{
pixels.setPixelColor(0, 0, 0, 0); // Off
pixels.show();
}
const uint16_t * palette = patch_colors[current_patch];
if (myButton.rose() == true) {
if (es == None) {
es = Select;
draw_selection(palette, selection, false);
} else if (es == Select) {
es = Change;
// emphasize bar that is being edited
draw_selection(palette, selection, true);
} else if (es == Change) {
es = None;
if (changed[current_patch]) {
bg_col = MARKED;
draw_panel();
draw_patch(); // Redraw the whole screen
}
clear_selection(selection);
}
}
int8_t dir = read_rotary();
if (myButton.update() == true) {
switch (myButton.event()) {
// things to do if the button was held (Hold = Save)
case (hold) : {
if (changed[current_patch]) {
save_current_patch();
es = None; // Go to default mode
clear_selection(selection);
rainbow(1);
pixels.show();
}
break;
}
}
}
footswitch.update();
// Encoder turned
if (dir) {
if (es == None) { // first movement activates Select mode
es = Select;
draw_selection(palette, selection, false);
} else if (es == Select) {
change_selection(palette, dir); // Move selection
} else if (es == Change) {
// bool fast = (fast_button.read() == HIGH);
edit_patch(selection, -dir); // Change parameter
}
}
// Footswitch pressed
if (footswitch.pressed()) {
if (es == Select) {
clear_selection(selection);
es = None;
}
if (es != Change) { // Patch change disabled during editing
change_patch(1);
}
}
}
void rainbow(int wait) {
for (long firstPixelHue = 0; firstPixelHue < 1 * 65536; firstPixelHue += 256) {
for (int i = 0; i < pixels.numPixels(); i++) {
int pixelHue = firstPixelHue + (i * 65536L / pixels.numPixels());
pixels.setPixelColor(i, pixels.gamma32(pixels.ColorHSV(pixelHue)));
}
pixels.show(); // Update strip with new contents
delay(wait); // Pause for a moment
pixels.setPixelColor(0, 0, 0, 0);
pixels.show(); // Update strip with new contents
}
}
// - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / -
// Draw a rectangle in foreground colour optionally filled another colour
void draw_rect (uint8_t x, uint8_t y, uint8_t w, uint8_t h,
uint16_t edgecol, uint16_t fillcol, boolean filled)
{
if (w == 0 || h == 0)
return;
uint8_t e_red = (edgecol & 0xf800) >> 10;
uint8_t e_green = (edgecol & 0x07e0) >> 5;
uint8_t e_blue = (edgecol & 0x001f) << 1;
uint8_t f_red = (fillcol & 0xf800) >> 10;
uint8_t f_green = (fillcol & 0x07e0) >> 5;
uint8_t f_blue = (fillcol & 0x001f) << 1;
tft.drawRect(x, y, w, h, edgecol);
if (filled) // filled rectangles take time to draw
tft.fillRect(x, y, w, h, edgecol);
}
// Send a byte to the display
//void Send(uint8_t d)
//{
// for (uint8_t bit = 0x80; bit; bit >>= 1) {
// PINB = 1 << clk; // clk low
// if (d & bit) PORTB = PORTB | (1 << data); else PORTB = PORTB & ~(1 << data);
// PINB = 1 << clk; // clk high
// }
//}
void init_display()
{
}
// Display off = 0, on = 1
void display_on(uint8_t on)
{
}
// Graphics **********************************************
// Clear display
void clear_display()
{
tft.clearScreen();
// PINB = 1 << cs; // cs low
// Send(0x25); // Clear Window
// Send(0); Send(0); Send(95); Send(63);
// PINB = 1 << cs; // cs high
// delayMicroseconds(900);
//delayMicroseconds(450); // half delay when clocking nano to 8 MHz
}
// Draw line to (x,y) in given colour
void draw_line (uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint16_t col)
{
uint8_t red = (col & 0xf800) >> 10;
uint8_t green = (col & 0x07e0) >> 5;
uint8_t blue = (col & 0x001f) << 1;
tft.drawLine(x0, y0, x1, y1, col);
// PINB = 1 << cs; // cs low
// Send(0x21); // Draw Line
// Send(x0); Send(y0); Send(x1); Send(y1);
// Send(red); Send(green); Send(blue);
// PINB = 1 << cs; // cs high
}
// Plot a point at (x,y)
void plot_point (uint8_t x, uint8_t y, uint16_t col)
{
draw_line(x, y, x, y, col);
}
// Plot character in given colour
void plot_char (uint8_t x, uint8_t y, uint8_t ch, uint16_t col, uint8_t scale)
{
uint8_t red = (col & 0xf800) >> 10;
uint8_t green = (col & 0x07e0) >> 5;
uint8_t blue = (col & 0x001f) << 1;
// PINB = 1 << cs; // cs low
for (uint8_t c = 0 ; c < 5; c++) { // Column range
uint8_t bits = pgm_read_byte(&font[ch - 32][c]);
uint8_t r = 0;
while (bits) {
while ((bits & 1) == 0) {
r++;
bits = bits >> 1;
}
uint8_t on = (7 - r) * scale;
while ((bits & 1) != 0) {
r++;
bits = bits >> 1;
}
uint8_t off = (7 - r) * scale + 1;
for (int i = 0; i < scale; i++) {
uint8_t h = x + c * scale + i;
// Send(0x21); // Draw line
// Send(h); Send(y + on); Send(h); Send(y + off);
// Send(red); Send(green); Send(blue);
}
}
}
// PINB = 1 << cs; // cs high
}
// Plot text
void plot_text(uint8_t x, uint8_t y, const char* text, uint16_t col, uint8_t scale)
{
while (1) {
char c = *(text++);
if (c == 0) return;
plot_char(x, y, c, col, scale);
x += 6;
}
}
// Higher level functions for drawing ui elements
// convert unsigned byte to char array
// *s points to end() of buffer
char *btoa(uint8_t x, char *s)
{
*--s = 0;
if (!x) *--s = '0';
for (; x; x /= 10)
* --s = '0' + x % 10;
return s;
}
// Master volume: -10 db - +4 db
// Mix: 0 - 50% - 100% (Not all values are possible, map to pot values)
// Valid values for dir are -1 and 1
// Draw volume -15 ------ 0 --- +4db / Mute
// Values given are 0-20.
void draw_volume(const uint16_t* palette, const uint16_t bg, const uint8_t pos, const uint8_t val)
{
uint8_t start = pos * 9 + 1;
uint8_t len = map(val, 0, 20, 0, 238);
uint16_t barcol = palette[0];
uint16_t textcol = palette[3];
tft.fillRect(1 , start, 238, 8, bg); // Erase old bar
tft.fillRect(1 , start, len, 8, barcol); // Draw new bar
// plot_text(2, start, "Vol", textcol);
tft.setCursor(2, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print("Vol");
// The code for constructing these on the fly
// takes as much space, but the array is simpler.
static const char db[21][4] = {"-80",
"-16", "-15", "-14", "-13",
"-12", "-11", "-10", " -9",
" -8", " -7", " -6", " -5",
" -4", " -3", " -2", " -1",
" +0", " +1", " +2", " +3"
};
//plot_text(31, start, db[val], textcol);
tft.setCursor(220, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print(db[val]);
// plot_text(49, start, "db", textcol);
tft.setCursor(115, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print("db");
}
// Draw mix as Dry --- 50% ---- Wet
// Values given are 0-20.
void draw_mix(const uint16_t* palette, const uint16_t bg, const uint8_t pos, const uint8_t val)
{
uint8_t start = pos * 9 + 1;
uint8_t len = map(val, 0, 20, 0, 238);
uint16_t barcol = palette[0];
uint16_t textcol = palette[3];
tft.fillRect(1 , start, 238, 8, bg); // Erase old bar
tft.fillRect(1 , start, len, 8, barcol); // Draw new bar
//plot_text(2, start, "Dry", textcol);
tft.setCursor(2, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print("Dry");
// plot_text(77, start, "Wet", textcol);
tft.setCursor(220, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print("Wet");
char buff[4]; // 3 chars + \0
buff[0] = buff[1] = ' '; // put spaces in tens and hundreds position
btoa(val * 5, buff + 4); // give pointer to just after buffer (end)
//plot_text(37, start, buff, textcol);
tft.setCursor(110, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print(buff);
}
// Draws bar with name and value
// Parameter values shown as 0-100%
void draw_parameter(const uint16_t* palette, const uint16_t bg, const char* text, const uint8_t pos, const uint8_t val)
{
uint8_t start = pos * 9 + 1;
uint8_t len = map(val, 0, 255, 0, 238);
uint8_t perc = map(val, 0, 255, 0, 100);
uint16_t barcol = palette[0];
uint16_t textcol = palette[3];
tft.fillRect(1 , start, 238, 8, bg); // Erase old bar
tft.fillRect(1 , start, len, 8, barcol); // Draw new bar
//plot_text(2, start, text, textcol);
tft.setCursor(2, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print(text);
char buff[4]; // 3 chars + \0 // add percent mark?
buff[0] = buff[1] = ' '; // put spaces in tens and hundreds position
btoa(perc, buff + 4); // give pointer to just after buffer (end)
// we want right justified, give buff instead of res
// plot_text(77, start, buff, textcol);
tft.setCursor(220, start);
tft.setTextColor(textcol);
tft.setTextSize(1);
tft.print(buff);
}
Do you have a question?
9 represents the number of pixels moved in the y direction. The font takes up 7 pixels and in addition there are two blank lines. The size of the pixels are changed by the setTextSize command.
G
Looks like the library reduces your coordinates modulo 240.
Pixel height is normally one.
Sorry, “THE pixel height” can’t be the 9
I’m trying to figure out how each menu item has the same y position, yet they are spaced apart
maybe this will help you figure it out:
Nope
Then maybe you should create a very simple sketch to play with...
start = pos * 9 + 1;
tft.fillRect(1 , start, 238, 8, someColor);
...using various values of pos (say, from 0 to 5)
I meant each item has the same x and y position, but they’re in a list rather than on top of one another, I’m past it now anyway, thanks for the suggestions
This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.