Atmega DSP - Trying to resurrect old project

I have been looking at this project that has very little documentation other than the code for Atmega and a video of it working.

The video: DSP on ATmega328 - YouTube
The code: DSPmega.zip - Google Drive

I have managed to get it working (audio comes through, effect work so-so). There is just one big problem: I get 3,84 kHz tone at output with the actual audio. It can’t be coicidence that the tone is exactly 10 times less than the sample rate…

I will try to share some schematics tomorrow.

Here is the ADC input code snippet:
void ADCSetup(){
ADCSRA |= (1<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS0); //freerunning ADC, prescale 64, samplerate 19230Hz
ADMUX |= (1<<MUX2)|(1<<MUX0)|(1 << ADLAR)|(1<<REFS0); //ADC5 (PC5) , 8bit adc, Vcc ref
ADCSRA |= (1 << ADEN)|(1 << ADSC); //enable ADC, enable interrupt
sei();
}

And here is the PWM output one:
void PWMSetup(){
DDRD |= (1<<PD5); //output PD5
TCCR0A |= (1<<WGM00)|(1<<WGM01); // select fast pwm mode 3
TCCR0A |= (1<<COM0B1); //Clear OC0B on Compare Match, set OCR0B at BOTTOM (non-inverting mode)
OCR0B = 128; //duty cycle
TCCR0B |= (1<<CS00);
}

Do you have an RC filter on the output?

Something like the low pass filter or notch filter shown here: Mozzi

General observation, as someone who played with DDS on the Arduino in the past, you should have a look at the esp32 and Raspberry Pico (RP2040) platforms. They are reasonably cheap & available and are much more powerful then these older Arduino chips, the esp32 even has a hardware floating point maths (fpu)

I did have a low pass filter on the output (around 8 kHz). I might try the notch filter soon. Seems just wrong that the chip makes that annoying oscillation right over the audio range.

Here is a picture that shows what pins are used.

Found the problem: I had configured the bootloader wrong, the chip was running at 1 MHz speed all the time.

Turns out the LED blinking program was quite useful for figuring out the clock speed :v

Update: Got the basic code working, moved some pins to allow using crystal. Might figure out some effect to place at PD4 pin, also a clipping indicator might be useful.


Also the code is here: dspmega.c

/*
 * DSPmega.c
 *
 * Created: 2012-10-07 12:28:13
 *  Author: Anders Skoog
 */ 
/********************************************************************
*Copyright (c) 2013 Anders Skoog									*		
*																	*
*Permission is hereby granted, free of charge, to any person		*
*obtaining a copy of this software and associated documentation		*
*files (the "Software"), to deal in the Software without			*
*restriction, including without limitation the rights to use,		*
*copy, modify, merge, publish, distribute, sublicense, and/or sell	*
*copies of the Software, and to permit persons to whom the			*
*Software is furnished to do so, subject to the following			*	
*conditions:														*
*																	*
*The above copyright notice and this permission notice shall be		*	
*included in all copies or substantial portions of the Software.	*
*																	*
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,	*
*EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES	*
*OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND			*
*NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT		*
*HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,		*
*WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING		*
*FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR		*	
*OTHER DEALINGS IN THE SOFTWARE.    							 	*
*********************************************************************/


#include "DSPmega.h"

volatile uint8_t effects= 0;

int main(void)
{	
	ADCSetup();
	PWMSetup();
	buttonSetup();
    while(1)
    {
		if (PIND & (1<<PD0)){
			 effects&= ~PITCHUP;
		}else{
			effects|=PITCHUP;
		}	

		if (PIND & (1<<PD1)){
			effects&= ~PITCHDOWN;
		}else{
			effects|=PITCHDOWN;
		}

    if (PIND & (1<<PD2)){
    effects &= ~PHASOR;
    } else {
      effects |= PHASOR;
    }

    if (PIND & (1<<PD3)){
    effects &= ~LOWPASS;
    } else {
      effects |= LOWPASS;
    }

    //Extra PD4 Program could be here

		if (PIND & (1<<PD6)){
			effects&= ~BITCRUSH;
		}else{
			effects|=BITCRUSH;
		}
		 
		if (PIND & (1<<PD7)){
			effects&= ~ECHO;
		}else{
			effects|=ECHO;
		} 
	
    }
}

void ADCSetup(){
	ADCSRA |= (1<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS0); //freerunning ADC, prescale 64, samplerate 19230Hz
	ADMUX |= (1<<MUX2)|(1<<MUX0)|(1 << ADLAR)|(1<<REFS0); //ADC5 (PC5) , 8bit adc, Vcc ref
	ADCSRA |= (1 << ADEN)|(1 << ADSC); //enable ADC, enable interrupt
	sei();	
}
void PWMSetup(){  
	DDRD |= (1<<PD5); //output PD5 
	TCCR0A |= (1<<WGM00)|(1<<WGM01); // select fast pwm mode 3
	TCCR0A |= (1<<COM0B1);          //Clear OC0B on Compare Match, set OCR0B at BOTTOM (non-inverting mode)
	OCR0B = 128;						//duty cycle
  TCCR0B |= (1<<CS00);
}
/* THE OLD BUTTON CODE
void buttonSetup(){
	//pull up resistors
	PORTB |= (1<<PB6)|(1<<PB7)|(1<<PB1)|(1<<PB0);
	PORTD |= (1<<PD6)|(1<<PD7);
}*/
void buttonSetup(){
    // Pull up resistors for Port D
    PORTD |= (1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3) | (1<<PD4) | (1<<PD5) | (1<<PD6) | (1<<PD7);
}

uint8_t bitcrush(uint8_t sample){

		sample=sample>>6;
        sample=sample<<6; 

        return sample;
}

uint8_t echo(uint8_t sample){
	static uint16_t indexWrite=0;
	static uint16_t indexRead=1;
	uint16_t temp=0;
	static uint8_t Buffer[BUFLENGTH]={0};
	static uint8_t downSample=1; //
		
	temp=sample  +  (Buffer[indexRead]>>1); //Reads sample and adds echo 
	
	Buffer[indexWrite]=(uint8_t)temp;	   

	if (downSample==1)
	{	
		indexRead++;
		if (indexRead==(BUFLENGTH-1)) indexRead=0;

		indexWrite++;
		if (indexWrite==(BUFLENGTH-1)) indexWrite=0;

		downSample=0;

	}else{
		downSample=1;
	}

	return  (uint8_t)temp;
}	



uint8_t phasor(uint8_t sample){
	static uint8_t Buffer[BUFPH]={0};	
	static uint16_t indexWrite=0;
	static uint16_t indexRead=1;
	static uint16_t temp=0;
	
	temp=(sample>>1)+(Buffer[indexRead]>>1); //Reads sample and adds flanger 
	
	Buffer[indexWrite]=sample;
	  
	indexRead++;
	if (indexRead>=BUFPH-1) indexRead=0;

	indexWrite++;
	if (indexWrite>=BUFPH) indexWrite=0;
	
	return  (uint8_t)temp;
}

uint8_t lowpass(uint8_t sample){
	static uint8_t filter[9]={0};
	
	filter[8]=filter[7];
	filter[7]=filter[6];
	filter[6]=filter[5];
	filter[5]=filter[4];
	filter[4]=filter[3];
	filter[3]=filter[2];
	filter[2]=filter[1];
	filter[1]=filter[0];
	filter[0]=sample;

	sample = (filter[0]*0 +filter[1]*0 + filter[2]*22 + filter[3]*75 +filter[4]*108 +filter[5]*75 + filter[6]*22 + filter[7]*0 + filter[8]*0)>>8; //fir order 7, window Kaiser (beta 2.5, fc 3000Hz)
	
	return  sample;
}


uint8_t pitch_up(uint8_t sample){
	static int indexWrite=0;
	static int indexRead=1;
	static uint8_t Buffer[BUFP]={0};
	
	Buffer[indexWrite]=sample;
	   
	indexRead+=2;

	if (indexRead>=(BUFP-1)) indexRead=0;

	indexWrite++;
	if (indexWrite>=(BUFP-1)) indexWrite=0;
	
	return  Buffer[indexRead];
}


uint8_t pitch_down(uint8_t sample){
	static int indexWrite=0;
	static int indexRead=1;
	static uint8_t half=0;
	static uint8_t Buffer[BUFP]={0};

	Buffer[indexWrite]=sample;
	  half++; 
	if(half==2){
		indexRead++;
		half=0;
	}
	
	if (indexRead>=(BUFP-1)) indexRead=0;

	indexWrite++;
	if (indexWrite>=(BUFP-1)) indexWrite=0;
		
	return  Buffer[indexRead];
}


ISR(ADC_vect) 
{ 
		uint8_t ADCdata=0;

        ADCdata=ADCH;

		if(effects&PITCHUP) ADCdata=pitch_up(ADCdata);
		if(effects&PITCHDOWN) ADCdata=pitch_down(ADCdata);
		if(effects&BITCRUSH) ADCdata=bitcrush(ADCdata);

		//only echo or phasor can be used at one time
		if(effects&ECHO)ADCdata=echo(ADCdata);
		if( effects&PHASOR) ADCdata=phasor(ADCdata);
		
		if(effects&LOWPASS) ADCdata=lowpass(ADCdata); //clean up the signal
		
      OCR0B = ADCdata;
} 

And the dspmega.h header file, mostly unchanged:

/*
 * DSPmega.h
 *
 * Created: 2012-10-07 13:04:22
 *  Author: Anders
 */ 
 /********************************************************************
*Copyright (c) 2013 Anders Skoog									*		
*																	*
*Permission is hereby granted, free of charge, to any person		*
*obtaining a copy of this software and associated documentation		*
*files (the "Software"), to deal in the Software without			*
*restriction, including without limitation the rights to use,		*
*copy, modify, merge, publish, distribute, sublicense, and/or sell	*
*copies of the Software, and to permit persons to whom the			*
*Software is furnished to do so, subject to the following			*	
*conditions:														*
*																	*
*The above copyright notice and this permission notice shall be		*	
*included in all copies or substantial portions of the Software.	*
*																	*
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,	*
*EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES	*
*OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND			*
*NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT		*
*HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,		*
*WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING		*
*FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR		*	
*OTHER DEALINGS IN THE SOFTWARE.    							 	*
*********************************************************************/
#ifndef F_CPU 
#define F_CPU 16000000UL 
#endif 

#ifndef DSPMEGA_H_
#define DSPMEGA_H_

#include <avr/interrupt.h> 
#include <avr/io.h>

#define BUFLENGTH 825
#define BUFP 360
#define BUFPH 150


#define LOWPASS 0x01
#define PITCHUP 0x2
#define PITCHDOWN 0x4
#define BITCRUSH 0x10
#define ECHO 0x20
#define PHASOR 0x80

typedef unsigned char uint8_t;
typedef unsigned int uint16_t;


void ADCSetup();
void PWMSetup();
uint8_t bitcrush(uint8_t sample);
uint8_t echo(uint8_t sample);
uint8_t phasor(uint8_t sample);
uint8_t pitch_down(uint8_t sample);
uint8_t pitch_up(uint8_t sample);
uint8_t lowpass(uint8_t sample);
#endif /* DSPMEGA_H_ */

Also I would recommend biasing the analog input pin to 2,5V. Does not seem necessary but will reduce clipping.

Project update: It works! Some hiss at the output but anyways it does do the DSP properly.

Made a simple layout for controlling signal levels, could be improved to reduce hiss.

Here is updated code; changed some pins to make wiring easier, added compressor effect (made with ChatGPT!)

/*
 * DSPmega.h
 *
 * Created: 2012-10-07 13:04:22
 *  Author: Anders
 */ 
 /********************************************************************
*Copyright (c) 2013 Anders Skoog									*		
*																	*
*Permission is hereby granted, free of charge, to any person		*
*obtaining a copy of this software and associated documentation		*
*files (the "Software"), to deal in the Software without			*
*restriction, including without limitation the rights to use,		*
*copy, modify, merge, publish, distribute, sublicense, and/or sell	*
*copies of the Software, and to permit persons to whom the			*
*Software is furnished to do so, subject to the following			*	
*conditions:														*
*																	*
*The above copyright notice and this permission notice shall be		*	
*included in all copies or substantial portions of the Software.	*
*																	*
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,	*
*EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES	*
*OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND			*
*NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT		*
*HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,		*
*WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING		*
*FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR		*	
*OTHER DEALINGS IN THE SOFTWARE.    							 	*
*********************************************************************/
#ifndef F_CPU 
#define F_CPU 1600000UL 
#endif 

#ifndef DSPMEGA_H_
#define DSPMEGA_H_

#include <avr/interrupt.h> 
#include <avr/io.h>

#define BUFLENGTH 825
#define BUFP 360
#define BUFPH 150


#define LOWPASS 0x01
#define PITCHUP 0x2
#define PITCHDOWN 0x4
#define BITCRUSH 0x10
#define ECHO 0x20
#define PHASOR 0x80

typedef unsigned char uint8_t;
typedef unsigned int uint16_t;


void ADCSetup();
void PWMSetup();
uint8_t bitcrush(uint8_t sample);
uint8_t echo(uint8_t sample);
uint8_t phasor(uint8_t sample);
uint8_t pitch_down(uint8_t sample);
uint8_t pitch_up(uint8_t sample);
uint8_t lowpass(uint8_t sample);
#endif /* DSPMEGA_H_ */
/*
 * DSPmega.c
 *
 * Created: 2012-10-07 12:28:13
 *  Author: Anders Skoog
 */ 
/********************************************************************
*Copyright (c) 2013 Anders Skoog									*		
*																	*
*Permission is hereby granted, free of charge, to any person		*
*obtaining a copy of this software and associated documentation		*
*files (the "Software"), to deal in the Software without			*
*restriction, including without limitation the rights to use,		*
*copy, modify, merge, publish, distribute, sublicense, and/or sell	*
*copies of the Software, and to permit persons to whom the			*
*Software is furnished to do so, subject to the following			*	
*conditions:														*
*																	*
*The above copyright notice and this permission notice shall be		*	
*included in all copies or substantial portions of the Software.	*
*																	*
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,	*
*EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES	*
*OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND			*
*NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT		*
*HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,		*
*WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING		*
*FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR		*	
*OTHER DEALINGS IN THE SOFTWARE.    							 	*
*********************************************************************/


#include "DSPmega.h"

volatile uint8_t effects= 0;

int main(void)
{	

	ADCSetup();
	PWMSetup();
	buttonSetup();
    while(1)
    {
		if (PINB & (1<<PB6)){
			 effects&= ~PITCHUP;
		}else{
			effects|=PITCHUP;
		}			 
		if (PINB & (1<<PB7)){
			effects&= ~PITCHDOWN;
		}else{
			effects|=PITCHDOWN;
		}
		if (PIND & (1<<PD6)){
			effects&= ~BITCRUSH;
		}else{
			effects|=BITCRUSH;
		}
		 
		if (PIND & (1<<PD7)){
			effects&= ~ECHO;
		}else{
			effects|=ECHO;
		} 
		if (PINB & (1<<PB0)){
			effects&= ~PHASOR;
		}else{
			effects|=PHASOR;
		} 
		if (PINB & (1<<PB1)){
			effects&= ~LOWPASS;
		}else{
			effects|=LOWPASS;
		} 	
    }
}

void ADCSetup(){

	ADCSRA |= (1<<ADATE)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS0); //freerunning ADC, prescale 64, samplerate 19230Hz
	
	ADMUX |= (1<<MUX2)|(1<<MUX0)|(1 << ADLAR)|(1<<REFS0); //ADC5 (PC5) , 8bit adc, Vcc ref
	ADCSRA |= (1 << ADEN)|(1 << ADSC); //enable ADC, enable interrupt
	sei();
	
}
void PWMSetup(){  
	DDRD |= 1<<PD5; //output PD5 
	 TCCR0A |= (1<<WGM00)|(1<<WGM01); // select fast pwm mode 3
	 TCCR0A |= (1<<COM0B1);          //Clear OC0B on Compare Match, set OCR0B at BOTTOM (non-inverting mode)
	 OCR0B = 128;						//duty cycle
	 TCCR0B |= (1<<CS00); 
}

void buttonSetup(){
	//pull up resistors
	PORTB |= (1<<PB6)|(1<<PB7)|(1<<PB1)|(1<<PB0);
	PORTD |= (1<<PD6)|(1<<PD7);
}

uint8_t bitcrush(uint8_t sample){
		
		sample=sample>>6;
        sample=sample<<6;         

        return sample;
}

uint8_t echo(uint8_t sample){
	static uint16_t indexWrite=0;
	static uint16_t indexRead=1;
	uint16_t temp=0;
	static uint8_t Buffer[BUFLENGTH]={0};
	static uint8_t downSample=1; //
		
	temp=sample  +  (Buffer[indexRead]>>1); //Reads sample and adds echo 
	
	Buffer[indexWrite]=(uint8_t)temp;	   

	if (downSample==1)
	{	
		indexRead++;
		if (indexRead==(BUFLENGTH-1)) indexRead=0;

		indexWrite++;
		if (indexWrite==(BUFLENGTH-1)) indexWrite=0;

		downSample=0;


	}else{
		downSample=1;
	}


	return  (uint8_t)temp;
}	



uint8_t phasor(uint8_t sample){
	static uint8_t Buffer[BUFPH]={0};	
	static uint16_t indexWrite=0;
	static uint16_t indexRead=1;
	static uint16_t temp=0;
	
	temp=(sample>>1)+(Buffer[indexRead]>>1); //Reads sample and adds flanger 
	
	Buffer[indexWrite]=sample;
	  

	indexRead++;
	if (indexRead>=BUFPH-1) indexRead=0;

	indexWrite++;
	if (indexWrite>=BUFPH) indexWrite=0;
	
	return  (uint8_t)temp;
}

uint8_t lowpass(uint8_t sample){
	static uint8_t filter[9]={0};
	
	filter[8]=filter[7];
	filter[7]=filter[6];
	filter[6]=filter[5];
	filter[5]=filter[4];
	filter[4]=filter[3];
	filter[3]=filter[2];
	filter[2]=filter[1];
	filter[1]=filter[0];
	filter[0]=sample;

	sample = (filter[0]*0 +filter[1]*0 + filter[2]*22 + filter[3]*75 +filter[4]*108 +filter[5]*75 + filter[6]*22 + filter[7]*0 + filter[8]*0)>>8; //fir order 7, window Kaiser (beta 2.5, fc 3000Hz)
	
	return  sample;
}


uint8_t pitch_up(uint8_t sample){
	static int indexWrite=0;
	static int indexRead=1;
	static uint8_t Buffer[BUFP]={0};
	
	Buffer[indexWrite]=sample;
	   
	indexRead+=2;

	if (indexRead>=(BUFP-1)) indexRead=0;

	indexWrite++;
	if (indexWrite>=(BUFP-1)) indexWrite=0;
	
		
	return  Buffer[indexRead];
}


uint8_t pitch_down(uint8_t sample){
	static int indexWrite=0;
	static int indexRead=1;
	static uint8_t half=0;
	static uint8_t Buffer[BUFP]={0};

	

	Buffer[indexWrite]=sample;
	  half++; 
	if(half==2){
		indexRead++;
		half=0;
	}
	

	if (indexRead>=(BUFP-1)) indexRead=0;

	indexWrite++;
	if (indexWrite>=(BUFP-1)) indexWrite=0;
	
	
		
	return  Buffer[indexRead];
}


ISR(ADC_vect) 
{ 
		uint8_t ADCdata=0;

        ADCdata=ADCH;

		if(effects&PITCHUP) ADCdata=pitch_up(ADCdata);
		if(effects&PITCHDOWN) ADCdata=pitch_down(ADCdata);
		if(effects&BITCRUSH) ADCdata=bitcrush(ADCdata);

				
		//only echo or phasor can be used at one time
		if(effects&ECHO)ADCdata=echo(ADCdata);
		if( effects&PHASOR) ADCdata=phasor(ADCdata);
		
		if(effects&LOWPASS) ADCdata=lowpass(ADCdata); //clean up the signal
		
        OCR0B = ADCdata;

} 

If I ever return to this project I might try to add clipping indicator and different effects with controls (as there are unused ADCs on the chip). Would be great to get some airwindows vst code working, some of them are such simple that they should run well.

2 Likes

Any chance of a demo? It’s quite an intriguing sounding project!

1 Like

The original video does show quite well everything it can do:

3 Likes

Right! That’s something else for me to build :smiley:

1 Like