Hi guys, I am experiencing some really weird behavior with a project of mine that uses the ATtiny1614 and I would really appreciate your help.
This project is an analog Wah-Wah guitar effect that is controlled by a digital potentiometer.
Basically here is how it works. The effect has 3 modes that are controlled using an optocoupler.
Mode 1 is Autowah and the coupler is OFF, removing the Digipot from the circuit.
Mode 2 is normal wah with a expression pedal connected to an analog input of the ATtiny, and controls the Digipot via SPI. In this mode the coupler is ON and inserts the Digipot in the analog circuit.
Mode 3 is a mix of both modes and the coupler is OFF when the pedal all back, but as soon as it moves a bit it turns ON and the Digipot reacts to it.
There is a button that cycle between modes with a short press and saves the current mode to the eeprom with a long press.
When cycling the modes the LED blinks as many times as the mode number, and when the mode is saved, it blinks a few times but faster.
There is also a general settings menu that can be called by pressing this button long at power on. It has 4 options (1 to 4). A short press cycles between options and the LED blinks the option number. When the button is pressed long, the option function is executed:
Option 1 - Control Type, Expression Pedal or Midi. Press Button long to change and save to eeprom.
Option2 - Pedal Reversed or Normal. Press Button long to change and save to eeprom.
Option3 - Pedal Calibration. Press Button long to initiate calibration, LED Blinks fast, Move pedal to both ends, after a few seconds calibration is finished and values are saved to the eeprom, LED blinks fast a few times again to indicate end of calibration.
Option4 - Press the button long to Exit General settings and resume normal operation.
Also, at Power ON, still in Setup and after reading the eeprom, the Code should blink the LED as many times as the Mode number saved on the EEprom, and then go into the main loop.
The Problem - After I flash it, it works fine for a few days and then suddenly it stops reacting to the expression pedal, it doesn't indicate the Mode number by Blinking the LED at power ON, and when I save a new mode it returns to the previous mode after cycling the power, meaning that it either didn't save the new mode number to the eeprom, or it is not reading it at power ON. It behaves like the pedal got stuck at the lower end, so for some reason is not reading the ADC, or not updating the digipot. But why would it also stop blinking the mode number at power ON? Weird
Normally to fix this I need to flash the code to the chip again, but today I managed to fix it by just changing it from mode 3 to mode 2 and saving it. It didn't work when I changed from mode 3 to Mode 1 and saved though.
as I said this is a very weird problem and it doesn't happen all the time. I still couldn't find out what triggers this behavior. Here is the entire code and thanks in advance for your help:
#include <SPI.h>
#include <EEPROM.h>
#include "HystFilter.h"
HystFilter potA(1024, 128, 4); // Input 12 bit ADC = 4096, 128 discrete output values required, margin = 8 units (of 4096)
// Which pin is connected to CS
const uint8_t CS_PIN = 7;
const int PotPin = 0; // Analog input pin that the potentiometer is attached to
int Led = 2;
int Opto = 6;
bool OptoState = LOW;
bool Old_OptoState;
int Trigger = 3;
int External_Input = 0;
int Reverse = 0;
int ControlType = 0;
int PotValue = 0; // value read from the pot
int OutputValue = 0; // value output to the PWM (analog out)
int Last_OutputValue = 0;
int Last_PotValue = 0;
int sensorMin = 127; // minimum sensor value
int sensorMax = 0; // maximum sensor value
int last_val_scaled;
int val_scaled;
const unsigned long calib_time = 6000;
int MuteDelayON = 30;
int MuteDelayOFF = 0;
int MidiChannel;
bool Bypass = false;
byte state = 0;
byte statusByte;
byte dataByte1;
byte dataByte2;
byte channel;
byte incomingByte;
byte number;
byte value;
byte velocity = 0;
int old_velocity = -1;
byte program;
int Reset_Eeprom;
long onDuration = 100; // ON time for the Led (in milliseconds)
long offDuration = 500; // OFF time for the Led (in milliseconds)
bool ledState = LOW; // Initial state of the Led
long rememberTime = 0; // Used by the code
int blinkCount = 5; // Number of times to blink the Led
bool BlinkActive = false;
int Counter = 0;
const int numberButtons = 1;
const int button[numberButtons] = { 1};
const int buttons[] = { 1};
int pressed[numberButtons], justReleased[numberButtons], justpressed[numberButtons], ShortPress[numberButtons], LongPress[numberButtons], XtraLongPress[numberButtons];
static long Timestart;
boolean ButtonWasPressed = false;
const int debounceDelay = 3; // the debounce time in milliseconds
unsigned long lastDebounceTime[numberButtons]; // the last time the button was debounced
bool buttonState[numberButtons];
bool lastButtonState[numberButtons];
int Byp = 1;
int AutoButton = 0;
void setup() {
SPI.begin();
for (int x = 0; x < numberButtons; x++) {
pinMode(button[x], INPUT_PULLUP);
}
for (int i = 0; i < numberButtons; i++) {
buttonState[numberButtons] = LOW;
lastButtonState[numberButtons] = LOW;
}
pinMode(Led, OUTPUT);
pinMode(Opto, OUTPUT);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
digitalWrite(Led, LOW);
digitalWrite(Opto, LOW);
External_Input = EEPROM.read(0);
Reverse = EEPROM.read(1);
ControlType = EEPROM.read(2);
sensorMax = EEPROM.read(3);
sensorMin = EEPROM.read(4);
MuteDelayON = EEPROM.read(5);
MuteDelayOFF = EEPROM.read(6);
Reset_Eeprom = EEPROM.read(10);
if (Reset_Eeprom != 69) { // If EEprom is not valid, format it
for (int i = 0; i < 256; i++) {
EEPROM.write(i, 0);
delay(2);
}
EEPROM.write(10, 69);
EEPROM.write(3, 127);
EEPROM.write(4, 0);
Blink_LED(12, 40, 100); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
if (digitalRead(button[AutoButton]) == LOW) {
StartupModes();
}
Blink_LED(ControlType + 1, 40, 250); // (Nr of Blinks, ON Time ms, OFF Time ms)
Serial.begin(31250);
if(External_Input == 0) {
PotValue = potA.getOutputLevel(analogRead(PotPin));
Send_PotValue();
}
if (ControlType == 0) { // Auto Wah Mode
OptoState = LOW;
}
if (ControlType == 1) { // Pedal Wah Mode
OptoState = HIGH;
}
HandleOpto();
// digitalWrite(MutePin, LOW);
}
void loop() {
MidiIn();
check_switches();
if (External_Input == 0) {
PotValue = potA.getOutputLevel(analogRead(PotPin));
if (PotValue != Last_PotValue) {
Send_PotValue();
}
Last_PotValue = PotValue;
}
if (ShortPress[AutoButton]) {
ControlType++;
if (ControlType > 2) ControlType = 0;
if (ControlType == 0) { // Auto Wah Mode
OptoState = LOW;
}
if (ControlType == 1) { // Pedal Wah Mode
OptoState = HIGH;
}
if ((ControlType == 2) and (OutputValue < Trigger)) { // Pedal Wah Mode
OptoState = LOW;
}
HandleOpto();
Blink_LED(ControlType + 1, 40, 250); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
if (XtraLongPress[AutoButton]) {
EEPROM.write(2, ControlType);
Blink_LED(5, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
if (ControlType == 2) {
if (OutputValue >= Trigger) {
OptoState = HIGH;
// if ((OptoState == LOW) && (OptoState != Old_OptoState)) {
if (OptoState != Old_OptoState) {
HandleOpto();
Old_OptoState = OptoState;
}
}
if (OutputValue < Trigger) {
OptoState = LOW;
// if ((OptoState == HIGH) && (OptoState != Old_OptoState)) {
if (OptoState != Old_OptoState) {
HandleOpto();
Old_OptoState = OptoState;
}
}
}
if (BlinkActive) {
// THIS ONE STARTS AND ENDS WITH LED HIGH
if (blinkCount > 0) {
if (ledState == LOW) {
if ((millis() - rememberTime) >= offDuration) {
ledState = HIGH; // Change the state of the LED
rememberTime = millis(); // Remember the current millis() time
}
} else {
if ((millis() - rememberTime) >= onDuration) {
ledState = LOW; // Change the state of the LED
rememberTime = millis(); // Remember the current millis() time
blinkCount--; // Decrease the blink count
}
}
digitalWrite(Led, ledState); // Turn the LED ON or OFF
} else {
BlinkActive = false;
}
}
}
void HandleOpto() {
if (OptoState == LOW) { // Auto Wah Mode
digitalWrite(Opto, OptoState);
// Serial.println("Auto");
}
if (OptoState == HIGH) { // Pedal Wah Mode
digitalWrite(Opto, OptoState);
// Serial.println("Pedal");
}
}
void Send_MIDI_Value() {
// Reverse = EEPROM.read(0);
if (Reverse == 0) {
// map it to the range of the analog out:
OutputValue = map(velocity, 0, 127, 0, 127);
} else if (Reverse == 1) {
OutputValue = map(velocity, 0, 127, 127, 0);
}
OutputValue = constrain(OutputValue, 0, 127);
MCP41HV_Write(CS_PIN, OutputValue);
}
void Send_PotValue() {
if (Reverse == 0) {
// map it to the range of the analog out:
OutputValue = map(PotValue, sensorMin, sensorMax, 0, 127);
} else if (Reverse == 1) {
OutputValue = map(PotValue, sensorMin, sensorMax, 127, 0);
}
OutputValue = constrain(OutputValue, 0, 127);
// Serial.println(OutputValue);
MCP41HV_Write(CS_PIN, OutputValue);
}
int MCP41HV_Write(int pin, int value) {
digitalWrite(pin, LOW); //When Slave is LOW, the MCP listens to the master
SPI.transfer(0x00);
SPI.transfer(value);
digitalWrite(pin, HIGH); //When Slave is HIGH, the MCP does not listen to the master
}
void StartupModes() {
bool StayingInside = true;
Blink_LED(6, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
while (digitalRead(button[AutoButton]) == LOW) {
}
delay(500);
Blink_LED(Counter + 1, 70, 300); // (Nr of Blinks, ON Time ms, OFF Time ms)
while (StayingInside) {
check_switches();
Do_The_Blink();
for (int tmp = 0; tmp < numberButtons; tmp++) {
if (ShortPress[tmp]) {
if (tmp == AutoButton) {
Counter++;
if (Counter > 3) Counter = 0;
Blink_LED(Counter + 1, 70, 300); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
}
if (XtraLongPress[tmp]) {
if (tmp == AutoButton) {
if (Counter == 0) { // Cofigure PowerON State
External_Input = !External_Input;
EEPROM.write(0, External_Input);
Blink_LED(6, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
if (Counter == 1) { // Cofigure PowerON State
Reverse = !Reverse;
EEPROM.write(1, Reverse);
Blink_LED(6, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
if (Counter == 2) { // Cofigure MuteDelayON
Pedal_Calibration();
Blink_LED(6, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
if (Counter == 3) { // Exit
StayingInside = false;
Blink_LED(6, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
}
}
// StayingInside = false;
}
}
}
External_Input = EEPROM.read(0);
Reverse = EEPROM.read(1);
sensorMax = EEPROM.read(3);
sensorMin = EEPROM.read(4);
}
void Pedal_Calibration() {
sensorMax = 0;
sensorMin = 127;
Blink_LED(3, 60, 80); // (Nr of Blinks, ON Time ms, OFF Time ms)
while (BlinkActive) {
Do_The_Blink();
}
uint16_t startMillis = millis();
uint16_t currentMillis = millis();
// calibrate during the first five seconds
while (currentMillis - startMillis < calib_time) {
int Pot_val = potA.getOutputLevel(analogRead(PotPin));
// record the maximum sensor value
if (Pot_val > sensorMax) {
sensorMax = Pot_val;
}
// record the minimum sensor value
if (Pot_val < sensorMin) {
sensorMin = Pot_val;
}
currentMillis = millis();
// delay(2);
}
// signal the end of the calibration period
delay(20);
EEPROM.write(3, sensorMax);
EEPROM.write(4, sensorMin);
// Blink_LED(2);
}
void Blink_LED(byte Count, const long On, const long Off) {
blinkCount = Count;
onDuration = On;
offDuration = Off;
BlinkActive = true;
}
void Do_The_Blink() {
if (BlinkActive) {
// THIS ONE STARTS AND ENDS WITH LED LOW
if (blinkCount > 0) {
if (ledState == LOW) {
if ((millis() - rememberTime) >= offDuration) {
ledState = HIGH; // Change the state of the LED
rememberTime = millis(); // Remember the current millis() time
}
} else {
if ((millis() - rememberTime) >= onDuration) {
ledState = LOW; // Change the state of the LED
rememberTime = millis(); // Remember the current millis() time
blinkCount--; // Decrease the blink count
}
}
digitalWrite(Led, ledState); // Turn the Led ON or OFF
} else {
BlinkActive = false;
}
}
}
void check_switches() {
static byte previousstate[numberButtons];
static byte currentstate[numberButtons];
static long lasttime;
static long Timelapse = 500;
static long TimelapseLong = 500;
// static long Timestart;
int index;
for (index = 0; index < numberButtons; index++) {
justReleased[index] = 0;
justpressed[index] = 0; //when we start, we clear out the "just" indicators
ShortPress[index] = 0;
LongPress[index] = 0;
XtraLongPress[index] = 0;
}
for (index = 0; index < numberButtons; index++) {
int reading = digitalRead(buttons[index]);
// check if the button state has changed
if (reading != lastButtonState[index]) {
lastDebounceTime[index] = millis();
}
if ((millis() - lastDebounceTime[index]) > debounceDelay) {
// if the button state has been stable for the debounce delay
if (reading != buttonState[index]) {
buttonState[index] = reading;
currentstate[index] = buttonState[index]; //read the button
pressed[index] = !currentstate[index]; // If CurrenState is LOW, Pressed is 1 (active)
if (pressed[index]) ButtonWasPressed = true;
if (currentstate[index] != previousstate[index]) {
if ((!pressed[index]) && (ButtonWasPressed)) {
justReleased[index] = 1;
}
if ((pressed[index]) && (previousstate[index] == HIGH)) { // just pressed
justpressed[index] = 1;
Timestart = millis();
}
if ((!pressed[index]) && (ButtonWasPressed) && (millis() - Timestart < Timelapse)) {
justpressed[index] = 0;
ShortPress[index] = 1; // just released ShortPress
ButtonWasPressed = false;
} else if ((!pressed[index] && ButtonWasPressed) && (millis() - Timestart >= Timelapse) && (millis() - Timestart < TimelapseLong)) {
justpressed[index] = 0;
ShortPress[index] = 0;
LongPress[index] = 1; // just released LongPress
ButtonWasPressed = false;
} else if ((!pressed[index] && ButtonWasPressed) && millis() - Timestart >= TimelapseLong) {
justpressed[index] = 0;
ShortPress[index] = 0;
LongPress[index] = 0;
XtraLongPress[index] = 1; // just released XtraLongPress
ButtonWasPressed = false;
}
}
previousstate[index] = currentstate[index]; //keep a running tally of the buttons
}
}
lastButtonState[index] = reading;
}
}
void MidiIn() {
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
switch (state) {
case 0:
channel = (incomingByte & B00001111) + 1;
if (incomingByte == 0xF9) {
statusByte = incomingByte;
} else {
statusByte = incomingByte & B11110000;
}
if (((statusByte > 0x7F) && (incomingByte < 0xF0)) || (incomingByte == 0xF9)) { // this reads all statusbytes, realtime stuff is ignored
state = 1;
}
case 1:
// get first databyte
if (incomingByte < 0x80) {
dataByte1 = incomingByte;
// Serial.println(dataByte1);
//check for two byte messages...
if ((statusByte & 0xF0) == 0xC0 | (statusByte & 0xF0) == 0xD0) {
midiParser();
state = 0; //reset state machine
} else state = 2;
}
break;
case 2:
// get second databyte
if (incomingByte < 0x80) {
dataByte2 = incomingByte;
// Serial.println(dataByte2);
}
midiParser();
state = 0; // reset state machine to start
break;
//stay here for sysex, wait for it to end...
case 3:
if (incomingByte == 0xF7) state = 0;
if (((statusByte > 0x7F) && (incomingByte < 0xF0)) || (incomingByte == 0xF9)) {
statusByte = incomingByte;
state = 1;
}
break;
}
}
}
void midiParser() {
if (channel == 16) {
switch (statusByte) {
case 0xC0: //program change
program = dataByte1;
break; // End Of Program Change
case 0xB0: //cc messages
number = dataByte1;
velocity = dataByte2;
if ((number == 8) and (External_Input == 1)) {
Send_MIDI_Value();
}
break;
}
}
}