Hi,
I managed to get EEPROM emulation to work on atmega328. It took
about 7 attempts with oscilloscope traces to finally get it. TWI did not work
because it is too slow. The interrupt from TWI peripheral takes too long
to arrive to be able to respond in time for the FV-1.
The approach that I took was to initiate patch change from atmega328 by
toggling the FV-1 pin S0 (pin 16) and then wait in a busy loop counting I2C
clock cycles and sending the data at correct moments. The code is attached
below (GPL v. 3.0).
I have been working on an Arduino based FV-1 multi effect pedal for guitar.
I recently got the second version of PCB from China and I'm in process of testing it.
I went with SMD this time because the board got very crowded for THT parts.
Here are a couple of pictures from the first PCB prototype:


Features:
- atmega328 with Arduino UNO bootloader, programming directly from Arduino IDE
- 80 FV-1 algorithms (29 reverbs, 21 delays, 30 modulation effects currently)
- 8 saved presets (algorithm + 3 parameters, Volume and Mix)
- Volume and mix are done by digital potentiometers, position saved on preset.
- OLED screen.
Hardware:
- Analog dry Audio path
- Current consumption approx. 80 mA
- Atmega328p processor at 8MHz.
- DS1881 digital potentiometer.
- SSD1331 OLED screen.
- True bypass + patch select footswitch.
- No EEPROM chip.
Software:
- Runs on atmega328p, programmed with Arduino IDE (FTDI programming header)
- FV-1 algorithms as header files, compressed with lightweight compression
method (
https://excamera.com/sphinx/article-compression.html), thus making
it possible to fit 80 algorithms to the 32k memory of atmega328p (8.5k code,
23.5k algorithms currently).
- No EEPROM chip, instead the atmega328p is used to emulate an EEPROM
and send the algorithm to FV-1.
- Parameter values from atmega328 to FV-1 communicated using filtered PWM.
- Custom UI with modified SSD1331 OLED screen library
(
http://www.technoblogy.com/show?2EA7) with each preset having a different
colour scheme.
/* Copyright 2020 Perttu Haimi
This file is part of FVduino.
FVduino is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FVduino is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FVduino. If not, see <https://www.gnu.org/licenses/>.
*
*
*/
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;
// Overclock (3.3V 16 MHz)
clock_prescale_set(clock_div_1);
// 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; // sda pin as output send ACK - pull sda pin low
break;
default:
DDRD &= ~(sda_mask); // sda pin as input
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 (sda pin as input)
} else {
DDRD |= sda_mask; // Send 0 = Pull Low (sda pin as output)
}
bit_mask >>= 1;
clk_count++;
} else { // Let reciever reply
DDRD &= ~(sda_mask); // Release (sda pin as input)
clk_count = 0;
bit_mask = 0b10000000;
pos++;
curr_byte = algo_buffer[pos];
}
}
prev_clk = clk;
}
interrupts();
// Restore 8 MHz
clock_prescale_set(clock_div_2);
}