AVR Tiny84 makes a Flanger

Started by anotherjim, April 17, 2015, 03:39:51 PM

Previous topic - Next topic

anotherjim

Don't have a catchy name for this one.

Scheme...


Soundcloud...
https://soundcloud.com/ashdalestudio/sets/avr-flanger-demoes

Some specs...
CPU= Atmel ATtiny84-20pu. Internal clock 8Mhz.
Delay Sweep= 0-4ms
LFO= Triangle
Bit depth =10. Internal ADC and 2 channel 8bit PWM.
Sample Rate 31.25Khz
Regen= Off, Positive Feedback, Negative Feedback, Negative Feedback and Negative Play Mix.
Sweep= Full, Half with offset from zero, Quarter with offset from zero, Half spread over full range (sample skip)
9VDC
25mA

Digital Larry

Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

Ice-9

Pretty cool sounding flanger Jim :) I suppose you could mod the code to make a nice chorus quite easily  as well  ???
www.stanleyfx.co.uk

Sanity: doing the same thing over and over again and expecting the same result. Mick Taylor

Please at least have 1 forum post before sending me a PM demanding something.

electricco

Cool ! FV1 into ATTINY chip !!  :P
Well done.

Is it possible to have the code too ? 
PWM A is replicating input and PWM B is doin the magic sound ?

thanks in advance

Drum track has really cool sound !


lars-musik

Great! I am a really a big flanger fan. Would you consider sharing the code?


anotherjim

Thanks for positive comments. Sounds pretty good considering it's all mostly happening in the MCU. Going digital means the wet/dry mixing is exactly 50:50 so the notches are always 100% cuts.

I took "Not more complex and an Electric Mistress" as my design guide. That means there is next to no protection against heterodyne whine caused by any HF getting in.

Chorus would need more memory. The 512bytes in the 84 are not nicely page aligned (starts at $0060), so to make it easier on myself I'm only using the middle 256 bytes so I can just spin the low byte of a pointer register and don't have to check it's bounds. That said, there turned out to be enough cycles free to do the 16bit maths check on the pointer and therefore use all of the ram, which would extend maximum delay to 8ms. My code is Assembly, there are are no interrupts, calls or stack operations, so ALL of the ram is free for sampling. For guitar, you could halve the sampling rate and double the maximum delay, but that means needing severe low pass filtering  in and out at about 6Khz. This design assumes there is little HF coming in to worry it in most conditions -  just like the Electric Mistress.

The 2 PWM channels are handling the high and low bytes of the 10bit samples. At 8Mhz clock (or even at 16Khz), the PWM isn't fast enough for more than 8bit. So the PWM-A is carrying the 8bit MSB and the PWM-B is carrying the 2bits (right justified) of the LSB. It's the TC0 counter using both output compare pins. The LSB voltage is divided by 4 by the mixer IC2b input resistor ratio to correct it's weighting.
To simplify the audio circuits, the PWM is in inverted mode to correct the inversion of the output mixer - so it is phase correct.

10bit is night&day better than 8bit so well worth the modest extra hardware. The PWM counter overflow automatically starts an ADC conversion. The ADC is clocked at 500Khz to be within the PWM cycle. The data sheet recommends no faster than 200Khz for 10bit accuracy, but I've found you can happily clock the ADC up to 1Mhz if you want.

The LFO RC timer is running at audio frequency - so careful routing needed. Charge discharge cycle is software controlled but automatically (LFO resistor drive pin is also external clock for TC1) clocks TC1 in 8bit up/down mode so divides the RC timer rate by 512. The counter value is simply subtracted from the buffer read pointer to control the delay sweep. An output compare fixed at half value ($80) toggles the LFO rate indicator pin.

The Sweep options are mainly to control pitch mod depth at fast LFO rates (it can do Leslie type warbling), but a small fixed offset is also added so delay never reaches zero. The 4th Regen mode (inverse polarity of record and play mixes), achieves total null at zero delay, which is interesting, but can be reduced with the offset. I've not settled on the best value - it's in an equate definition in the code to make it easy to experiment with.

The 4th Sweep option is an experiment, the sweep skips alternate samples. This is responsible for a "tinkling" effect particularly noticeable  on treble content. The is no interpolation done, it simply skips. I haven't tried to see if I can get away with bigger skips yet.

For the audio circuits, I needed an amp that can swing to ground so selected the ancient and cheap LM358. It's poor bandwidth is a help to counter HF input. IC1a, like in the EM, is a treble pre-emphasis booster. This means the PWM RC filters can be quite severe which helps improve the overall SNR a lot. I've tried some more modern amp types - LCM662 & TLC272 and can't say I heard much difference.

What's the best way to post code here?
Just paste it in?




cloudscapes

hugely impressive work for an attiny!
~~~~~~~~~~~~~~~~~~~~~~
{DIY blog}
{www.dronecloud.org}

g_u_e_s_t

that is really cool.  the use of the timer as an input for the control knob is really clever.  for my stuff i used a rotary encoder (as the ADC was booked doing audio work), but i really dont like rotary encoders.

why an ATTINY?  i realize that for DIP packages, you cant get an ATMEGA as small, but the price isnt that much more, and you get a hardware multiplier, which makes a world of difference.

electricco

code is only text (unless you use lot of custom libraries...) so pasting it will be nice and enough, I think.
as suggested, changing from attiny to atmega or better a NANO (even easier to programming it .. and you can make and usb pedal as well ) will expand even more the capabilities

Please have some sample of Leslie sound !!!
Wow !! I never thought one can do different from fuzz with such chip !!



anotherjim

#9
The "code" option here destroys some of my formatting, but should all be there. I'm self taught at programming so this will probably give coding professionals a nightmare.

;************************************************************************************************
;Designer. J.W.Yale. Ashdale Studio V9
;
;For Atmel ATtiny84-20PU using internal 8Mhz clock at 8Mhz.
;
;10bit PWM resolution using 2 8bit PWM outputs. MSB (top 8 bits of 10bit conversion) on OC0A
;LSB on OC0B. Signals are mixed together externally and LSB attenuated by 4
;8bit PWM gives sample rate over 30Khz with 8Mhz clock, simplifying external filtering.
;PWM is inverted to simplify external PWM mixer and maintain true polarity.
;
;LFO speed control using external RC timer with Pot adjust.
;Normal Regen or Negative regen or Negative regen with inverted wet/dry mix) using 2 switch bits.
;4 Sweep range options (Full, Half, Quarter or Stepped) using 2 switch bits.
;
;Pin usage.
; 1=Vcc +5Volts
; 2=PB0:INPUT:Switch:Regen mode A A:B|A:B|A:B|A:B
; 3=PB1:INPUT:Switch:Regen mode B 1:1|0:1 |1:0|0:0
;       OFF¦ON¦INV¦INV with INV play mix.
; 4=PB3:INPUT:Reset pin. Active low.
; 5=PB2:OUTPUT:OC0A PWM audio MSB
; 6=PA7:OUTPUT:OC0B PWM audio LSB
; 7=PA6:OUTPUT:OC1A LFO sweep rate indicator
; 8=PA5:INPUT:LFO timing capacitor
; 9=PA4:OUTPUT:LFO timing RC resistance
;10=PA3:INPUT:Switch:Sweep mode B   A:B A:B A:B A:B
;11=PA2:INPUT:Switch:Sweep mode A   1:1 0:1 1:0 0:0
;  ALL¦1/2¦1/4¦Step
;12=PA1:ADC1:INPUT:Audio input.
;13=PA0:Aref:DC input. ADC reference voltage.
;14=GND:0Volts
;
;************************************************************************************************

.INCLUDE "tn84def.inc"
.DEF SwStateA=R6 ;Memory of current switch state PortA
.DEF SwStateB=R7 ;Memory of current switch state PortB
.DEF SwCntHi=R8 ;Location of switch debounce timing high count.
.DEF RegenLo=R9 ;Playback sample pair for Regen Mix.
.DEF RegenHi=R10
.DEF LSBbits=R11 ;Will contain XOR value to invert the 2 valid LSB sample bits.
.DEF SwitchLo=R13 ;Low counter for switch scan time
.DEF SwitchHi=R14 ;High counter for switch scan time
.DEF Temp=R16 ;General scratch register
.DEF SampleLo=R17 ;Hi byte of record sample from ADC
.DEF SampleHi=R18
.DEF MixHi=R19 ;Hi byte of play sample for PWM
.DEF MixLo=R20
.DEF LoTemp=R22 ;Temporary register for any maths interim result
.DEF HiTemp=R23 ;Another temporary for 16bit math MSB
.EQU SwCnt=$06 ;High count of 16bit switch read delay
.EQU LSBmask=$C0 ;Bit mask to isolate bits 6 and 7 of sample LSB
.EQU SwpOff=$06 ;Amount sweep is offset to prevent null in inverted mix modes
;Z register pair is sample recording pointer
;Y register pair is sample playback pointer
;
;*******************************Initialise and set up******************************************
;
.cseg ;CODE segment
.org 0
rjmp INIT ;Reset vector
INIT: cli ;Disable interrupts - none are used.
;Set Stack Pointer
ldi Temp,$00 ;Low stack position, delay buffer will be $0100 to $01FF
out SPH,Temp ;Just to be safe. Stack isn't used.
ldi Temp,$FF
out SPL,Temp
;Set ADC. Left justified, 500Khz clock. Sync to PWM (TC0) by auto trigger from TC0 count overflow.
ldi Temp,$41 ;Set external reference voltage and ADC channel1
out ADMUX,Temp
ldi Temp,$94 ;Enable ADC and set clock divider
out ADCSRA,Temp
ldi Temp,$14 ;Left Justify result & set auto trigger source TC0 overflow
out ADCSRB,Temp
ldi Temp,$02 ;Disable digital input on ADC pin
out DIDR0,Temp
ldi Temp,$F4 ;Start a conversion to warm it up
out ADCSRA,Temp
;Set timer/counter 0 Fast PWM, no prescale. OCR0A & B outputs enabled. This sets the sample rate.
ldi Temp,$F3 ;Set up Fast PWM with OC0A & OC0B active & inverting
out TCCR0A,Temp
ldi Temp,$01 ;Enable -  no prescale
out TCCR0B,Temp
ldi Temp,$00
out TIMSK0,Temp ;No interrupt
;Set timer/counter 1. Used as auto up/down (triangle) counter for LFO sweep of delay time
ldi Temp,$C1 ;Dual slope PWM, OC1A active pin 7 for LFO LED
out TCCR1A,Temp
ldi Temp,$07 ;External clock via pin 9. Clock is from the external RC timer
out TCCR1B,Temp
ldi Temp,$80 ;Mid count for LFO rate indicator toggle
out OCR1AL,Temp
;Set up pointers & fill sample buffer with audio silence (zero crossing level)
ldi SampleHi,$02 ;High byte  of zero crossing of 10bit value.
clr SampleLo ;Low byte
ldi ZH,$01 ;Base adress high byte of delay buffer ram
ldi YH,$01
clr ZL ;Low byte of buffer.
clr YL
ClnLoop:st Z,SampleLo ;Write Low byte of sample first
inc ZL ;Increment low byte of Z pointer only. High byte must remain.
st Z,SampleHi ;Ditto for High byte of sample
inc ZL
tst ZL
brne Clnloop ;256 byte buffer - all done when ZL pointer back to zero.
;Send initial sample to PWM. High byte to OCR0A, Low byte to OCR0B.
out OCR0A,SampleHi ;Update both TC0 PWM output compare registers
out OCR0B,SampleLo
;Clear LFO & switch scan and clock counters. Preload registers.
clr SwitchLo
clr SwitchHi
ldi Temp,LSBmask ;Bit mask to invert sample LSB bits.
mov LSBbits,Temp
ldi Temp,SwCnt
mov SwCntHi,Temp
;Set ports
ldi Temp,$0C ;Enable pull-ups for switch inputs PA2,3
out PORTA,Temp
ldi Temp,$D0 ;PA7 out OC0B,PA6 out Rate indicator,PA4 TC1 clock connect.
out DDRA,Temp
ldi Temp,$03 ;Enable pull-ups for switch inputs PB0,1
out PORTB,Temp
ldi Temp,$04 ;PB2 output OC0A
out DDRB,Temp
;Initialise switch memory and precharge LFO RC timer.
ldi Temp,$0C
in SwStateA,PINA
and SwStateA,Temp
ldi Temp,$03
in SwStateB,PINB
and SwStateB,Temp
sbi PORTA,4 ;Set LFO timing cap to charge up.
;
;*****************IT BEGINS!!!*****************************************************************
;
LOOP:
;Wait for ADC complete flag set and save result, bit 4 of ADCSRA will be high
sbis ADCSRA,4
rjmp LOOP
in SampleLo,ADCL ;Get the result. ADCL is LSB 10xxxxxx
in SampleHi,ADCH ;ADCH is MSB of 10bits, 98765432
sbi ADCSRA,4 ;Clear the complete flag by setting it 1
ldi Temp,$01
out TIFR0,Temp ;Clear TC0 overflow flag to re-enable auto ADC trigger
;Regen mix if switch set
sbrc SwStateB,2 ;Test switches for both (PB0&PB1=0)
rjmp Invert ;Which forces Negative Regen
sbrc SwStateB,0 ;Test Switch for Positive Regen (PB0=0)
rjmp NegRgn
rjmp PosRgn
NegRgn: sbrc SwStateB,1 ;Test for Negative Regen (PB1=0)
rjmp UpLFO ;No regen so jump to next function
Invert: eor RegenLo,LSBbits ;Invert regen sample.
com RegenHi
PosRgn: add SampleLo,RegenLo ;Add to current recording sample.
adc SampleHi,RegenHi
ror SampleHi ;Divide addition by 2 and preserve carry from addition
ror SampleLo
and SampleLo,LSBbits ;Clear all but bits 6,7 of LSB
;Update the LFO RC timer. Toggles PA4 which clocks TC1 up/down counter
UpLFO: sbis PINA,4 ;RC timer charging test (PA4=1)
rjmp UpLFOa ;Not being charged so test discharge.
sbis PINA,5 ;Has it charged yet? (PA5=1)
rjmp UpCnt ;Not yet so skip to next function
cbi PORTA,4 ;It is charged so discharge (PA4=0)
rjmp UpCnt ;Done until next loop
UpLFOa: sbic PINA,5 ;Is commanded to discharge-has it? (PA5=0)
rjmp UpCnt ;Not discharged, skip to next function
sbi PORTA,4 ;Is discharged so charge it again (PA4=1)
;Update the LFO sweep counter - Play pointer offset behind Record by TC1 count value
UpCnt:  in Temp,TCNT1L ;Get the current count from TC1
sbrc SwStateA,0 ;Always full sweep for Step option
rjmp Step
ldi LoTemp,SwpOff
sbrc SwStateA,2 ;Test if Sweep range switch on for Half Sweep (PA2=0)
rjmp SweepQ ;Else check for quarter sweep
lsr Temp ;Halve count for Half Sweep
add Temp,LoTemp
rjmp Sweep ;Go to Sweep
SweepQ: sbrc SwStateA,3 ;Test if Sweep range switch on for Quarter Sweep (PA3=0)
rjmp Sweep ;Else full sweep
lsr Temp ;Halve sweep
lsr Temp ;Again for quarter
add Temp,LoTemp ;Add an offset to move frequency range down slightly
rjmp Sweep
Step: andi Temp,$FC ;Clear bit 1 for step sweep effect
Sweep: andi Temp,$FE ;Clear bit 0 - don't want odd numbered counts
mov YL,ZL ;Playback pointer=Record pointer
sub YL,Temp ;Move playback pointer back by TC1 LFO value
;Manage record and playback
GoMix: st Z,SampleLo ;Save new sample to current Record pointer
inc ZL
st Z,SampleHi
inc ZL ;Record pointer advanced one sample.
MixReg: ld MixLo,Y ;Get playback sample from current Y pointer.
inc YL
ld MixHi,Y
mov RegenLo,MixLo ;Copy playback for next loop regen mix.
mov RegenHi,MixHi
;Determine mix polarity from mode switch state.
sbrs SwStateB,2
rjmp PosMix ;
eor MixLo,LSBbits ;Invert play sample LSB by exclusive OR with 1's
com MixHi ;Invert play MSB
PosMix: and Mixlo,LSBbits ;Clear all but LSB bits 7,6
add MixLo,SampleLo ;16bit addition of input sample and playback sample.
adc MixHi,SampleHi
ror MixHi ;Divide addition by 2
rol MixLo ;Right justify LSB of playback mix by rotating thru carry
rol MixLo ;Remainder from division (1bit) left in carry
out OCR0A,MixHi ;Send MSB and LSB of mix to PWM
out OCR0B,MixLo
;Manage switch scan. Test switches about once per 50ms
inc SwitchLo ;Increment 8bit count registers until switch delay time.
brne NotTime
inc SwitchHi
cp SwitchHi,SwCntHi ;Delay met when low count=0 and high count=SwCnt See .EQU
brne NotTime
clr SwitchHi
in Temp,PINA ;Store current mode switches port A
andi Temp,$0C ;Mask unwanted bits
brne SwitchB
sbr Temp,$01 ;Bit 0 set=Offset thru Zero LFO Sweep.
SwitchB:mov SwStateA,Temp
in Temp,PINB
andi Temp,$03
brne StoreB
sbr Temp,$04 ;Bit 2 set=negative play mix
StoreB: mov SwStateB,Temp
NotTime:rjmp LOOP ;Loop forever
;*********************IT ENDS!!!***********************************************************
.Exit



No multiplication done, so hardware multiplier isn't missed - an ATMega doesn't really bring much extra to the table. Atmel shot themselves in the foot not making a few XMega in DIP package. They have 12bitADC&DAC, more RAM and more direct peripheral connections. If you don't need a proper UART and multiplier - Mega's don't offer much over the Tiny.
Then again, I suspect the 28pin Mega328 could make a nifty multitap delay with it's 2K ram. I have some.

I should have built mine with jumpers to enable easy ISP connection - unplugging the chip to change the code gets old soon ;)


g_u_e_s_t

are you using a fixed LFO frequency?  for non-integer frequencies, interpolation helps a lot, and you need multiplication for that.  the 328 is a great chip, and is the cheapest thing they have per bit of SRAM.  i think the arduino market really brought the price down for it.  i have a bunch of audio stuff written for the 328 up on my site (openmusiclabs.com/wiki), and some could be ported for the ATTINY series.  the XMEGA stuff is great on paper, but ive found it isnt as easy to work with in practice.

anotherjim

LFO is variable due to the pot and cap switch - the counter clocked by the RC osc gets read every PWM cycle. Everything gets done within that time, so 256 instruction cycles to do it all with - the current code uses about half of that.

I was trying to figure out a way to use a half wave sine table LFO but keep the maths simple. I have an idea that the sine value determines how fast the sweep counter clocks (the sine value itself is a count time), that way there should be no jumps in the delay sweep. At the sine zero crossing, the sweep counter would be clocked rapidly (but never faster than the audio sample rate) and at the peak the sweep rate would slow down. Once back to the zero crossing the sweep count direction is reversed and the same half-sine table run through again. The RC pot/cap would determine the clock rate of the sine value counter and so control the LFO rate.

Shall have a look at openmusiclabs. My own searches found very little on MCU's as FX - so I've been making it up as I go along!


electricco

thanks for the code Jim, I'll read it next week.

I was mentioning Mega just for having more pins to expand the pedal... like external ram, LCD, digital pot (?!), silent switches  etc....




anotherjim

Ah, I think Guest was getting at the LFO depth being fixed. To begin with, I was only after full slow flange. Depth of sweep is always maximum in that case - at least enough to create notches throughout the audio range. At only 4ms max delay I was always going to need the sweep to cover all of that. That changes when the LFO rate increases into vibrato territory and the full sweep becomes a ridiculous warble so I took the simple option to halve or quarter the sweep counter value. The available pins and functions of the Tiny84 pretty much led the design by the nose.
I wanted it to be simple. Think of it as like a PT2399 - is what it is, does what it does. If anyone wanted to, it could be a one knob pedal (rate), the timing cap fixed for slow sweep range and  a single switch to turn positive regen on. The switch inputs use internal pull ups so you simply don't connect the pins you don't need.


anotherjim

It's just popped into my head (don't know why, I wasn't even thinking about it!), that this bit of code for the LFO RC timer is overcomplicated...
UpLFO:   sbis PINA,4            ;RC timer charging test (PA4=1)
      rjmp UpLFOa            ;Not being charged so test discharge.
      sbis PINA,5            ;Has it charged yet? (PA5=1)
      rjmp UpCnt            ;Not yet so skip to next function
      cbi   PORTA,4            ;It is charged so discharge (PA4=0)
      rjmp UpCnt            ;Done until next loop
UpLFOa:   sbic PINA,5            ;Is commanded to discharge-has it? (PA5=0)
      rjmp UpCnt            ;Not discharged, skip to next function
      sbi PORTA,4            ;Is discharged so charge it again (PA4=1)

Port A pins 4 & 5 are acting as a Schmitt trigger inverter oscillator. The AVR digital inputs are Schmitt trigger with similar thresholds to CMOS logic parts. All it needs to do is to read the input level of the pin to the timing capacitor and output the inverse level to the timing resistor.

So...
UpLFO: sbis PINA,5                                  ;If timing cap low, then drive resistor high
            sbi PORTA,4                                ;Set resistor output high
            sbic PINA,5                                 ;If timing cap high, then drive resistor low
            cbi PORTA,4                                ;Clear resistor output low
...should be all that's needed. I haven't tested it and probably won't implement it unless I make any other changes.

The original code works of course and is more sophisticated as it reads back the timing resistor output pin to ensure correct charge/discharge sequence -  but I don't think that's necessary, it only needs to act like an inverter.


anotherjim

Some development to report...

Have got it working using all 512bytes of ram in the Tiny84 for the delay buffer. Sweep range now doubled to 0-8ms.
Made my brain hurt somewhat. Trouble is with the RAM running from address $0060 up to $025F.
Anyway, it turned out I could use a sample count register as the cyclic pointer covering 256 samples, then make an index from that by multiplying by 2 (so address 256 words) and add $0060 to align with the ram addresses. More code in the loop of course, but the LFO management mod I mention in the last post works which took a chunk of code out.

This has implications for Sweep range options, which I haven't altered yet. I'll play with it some more before making any more changes and posting clips and details. But if anyone else is working on this and would like the new code as is, let me know.

anotherjim

#16
Ok, the new code version seems good.
With the extended delay, I've made the Half sweep mode more like a Chorus. It sweeps between 4-8ms. Will have to find some time to record demo's soon. Of course, full sweep is now longer with 256 sample in 256 steps compared to 128 before.

;************************************************************************************************
;MCU digital audio effect. Flanger/Chorus
;Author. J.W.Yale. Ashdale Studio V1
;
;For Atmel ATtiny84-20PU using internal 8Mhz clock at 8Mhz.
;
;10bit PWM resolution using 2 8bit PWM outputs. MSB (top 8 bits of 10bit conversion) on OC0A
;LSB on OC0B. Signals are mixed together externally and LSB attenuated by 4
;8bit PWM gives sample rate over 30Khz with 8Mhz clock, simplifying external filtering.
;PWM is inverted to simplify external PWM mixer and maintain true polarity.
;
;LFO speed control using external RC timer with Pot adjust.
;Normal Regen or Negative regen or Negative regen with inverted wet/dry mix) using 2 switch bits.
;4 Sweep range options (Full, Chorus, Quarter or Stepped) using 2 switch bits.
;Full sweep range = 0-8ms. Chorus sweep=4-8ms
;
;Pin usage.
; 1=Vcc +5Volts
; 2=PB0:INPUT:Switch:Regen mode A A:B A:B A:B A:B
; 3=PB1:INPUT:Switch:Regen mode B 1:1 0:1 1:0 0:0
; OFF¦ON ¦INV¦INV with INV play mix.
; 4=PB3:INPUT:Reset pin. Active low.
; 5=PB2:OUTPUT:OC0A PWM audio MSB
; 6=PA7:OUTPUT:OC0B PWM audio LSB
; 7=PA6:OUTPUT:OC1A LFO sweep rate indicator
; 8=PA5:INPUT:LFO timing capacitor
; 9=PA4:OUTPUT:LFO timing RC resistance
;10=PA3:INPUT:Switch:Sweep mode B   A:B A:B A:B A:B
;11=PA2:INPUT:Switch:Sweep mode A   1:1 0:1 1:0 0:0
; ALL¦Cho¦1/4¦Step
;12=PA1:ADC1:INPUT:Audio input.
;13=PA0:Aref:DC input. ADC reference voltage.
;14=GND:0Volts
;
;************************************************************************************************

.INCLUDE "tn84def.inc"
.DEF BufferHi=R5 ;To hold buffer pointer compare value
.DEF SwStateA=R6 ;Memory of current switch state PortA
.DEF SwStateB=R7 ;Memory of current switch state PortB
.DEF SwCntHi=R8 ;Location of switch debounce timing high count.
.DEF RegenLo=R9 ;Playback sample pair for Regen Mix.
.DEF RegenHi=R10
.DEF LSBbits=R11 ;Will contain XOR value to invert the 2 valid LSB sample bits.
.DEF SwitchLo=R13 ;Low counter for switch scan time
.DEF SwitchHi=R14 ;High counter for switch scan time
.DEF Temp=R16 ;General scratch register
.DEF SampleLo=R17 ;Hi byte of record sample from ADC
.DEF SampleHi=R18
.DEF MixHi=R19 ;Hi byte of play sample for PWM
.DEF MixLo=R20
.DEF LoTemp=R22 ;Temporary register for any maths interim result
.DEF HiTemp=R23 ;Another temporary for 16bit math MSB
.EQU SwCnt=$06 ;High count of 16bit switch read delay
.EQU LSBmask=$C0 ;Bit mask to isolate bits 6 and 7 of sample LSB
.EQU SwpOff=$06 ;Amount sweep is offset to prevent null in inverted mix modes
.EQU BufferTopH=$02 ;High and Low bytes of maximum buffer address
.EQU BufferTopL=$60 ;This is also the Low byte of Buffer bottom(with high byte clear)
.EQU ChorusOff=$80 ;Used as Chorus mode sweep pointer offset (4-8ms delay)
;
;XH register is record sample position XL register is play sample position
;Y register pair is sample playback pointer
;Z register pair is sample recording pointer
;
;
;*******************************Initialise and set up******************************************
;
.cseg ;CODE segment
.org 0
rjmp INIT ;Reset vector
INIT: cli ;Disable interrupts - none are used.
;Set Stack Pointer
ldi Temp,BufferTopH   ;Stack pointer will be unused, delay buffer is $0100 to $01FF
out SPH,Temp
ldi Temp,BufferTopL-1
out SPL,Temp
;Set ADC. Left justified, 500Khz clock. Sync to PWM (TC0) by auto trigger from TC0 count overflow.
ldi Temp,$41 ;Set external reference voltage and ADC channel1
out ADMUX,Temp
ldi Temp,$94 ;Enable ADC and set clock divider
out ADCSRA,Temp
ldi Temp,$14 ;Left Justify result & set auto trigger source TC0 overflow
out ADCSRB,Temp
ldi Temp,$02 ;Disable digital input on ADC pin
out DIDR0,Temp
ldi Temp,$F4 ;Start a conversion to warm it up
out ADCSRA,Temp
;Set timer/counter 0 Fast PWM, no prescale. OCR0A & B outputs enabled. This sets the sample rate.
ldi Temp,$F3 ;Set up Fast PWM with OC0A & OC0B active & inverting
out TCCR0A,Temp
ldi Temp,$01 ;Enable -  no prescale
out TCCR0B,Temp
ldi Temp,$00
out TIMSK0,Temp ;No interrupt
;Set timer/counter 1. Used as auto up/down (triangle) counter for LFO sweep of delay time
ldi Temp,$C1 ;Dual slope PWM, OC1A active pin 7 for LFO LED
out TCCR1A,Temp
ldi Temp,$07 ;External clock via pin 9. Clock is from the external RC timer
out TCCR1B,Temp
ldi Temp,$80 ;Mid count for LFO rate indicator toggle
out OCR1AL,Temp
;Set up pointers & fill sample buffer with audio silence (zero crossing level)
ldi SampleHi,$02 ;High byte  of zero crossing of 10bit value.
clr SampleLo ;Low byte
clr ZH   ;Base adress high byte of delay buffer ram
ldi ZL,BufferTopL ;Low byte of buffer.
ldi Temp,BufferTopH
mov BufferHi,Temp    ;For buffer compare instruction
ClnLoop:st Z+,SampleLo ;Write Low byte of sample first
st Z+,SampleHi ;Ditto for High byte of sample
cpi ZL,BufferTopL ;Check for Z pointer past top
cpc ZH,BufferHi
brne Clnloop ;512 byte buffer - all done when ZL pointer past top.
ldi XL,BufferTopL ;Used to offset buffer pointers due to ram start @$0060
;Send initial sample to PWM. High byte to OCR0A, Low byte to OCR0B.
out OCR0A,SampleHi ;Update both TC0 PWM output compare registers
out OCR0B,SampleLo
;Clear LFO & switch scan and clock counters. Preload registers.
clr SwitchLo
clr SwitchHi
ldi Temp,LSBmask ;Bit mask to invert sample LSB bits.
mov LSBbits,Temp
ldi Temp,SwCnt
mov SwCntHi,Temp
;Set ports
ldi Temp,$0C ;Enable pull-ups for switch inputs PA2,3
out PORTA,Temp
ldi Temp,$D0 ;PA7 out OC0B,PA6 out Rate indicator,PA4 TC1 clock connect.
out DDRA,Temp
ldi Temp,$03 ;Enable pull-ups for switch inputs PB0,1
out PORTB,Temp
ldi Temp,$04 ;PB2 output OC0A
out DDRB,Temp
;Initialise switch memory and precharge LFO RC timer.
ldi Temp,$0C
mov SwStateA,Temp ;Initially full sweep until first switch read
ldi Temp,$03
mov SwStateB,Temp ;Initially No Regen until first switch read
sbi PORTA,4 ;Set LFO timing cap to charge up.
;
;*****************IT BEGINS!!!*****************************************************************
;
LOOP:
;Wait for ADC complete flag set and save result, bit 4 of ADCSRA will be high
sbis ADCSRA,4
rjmp LOOP
in SampleLo,ADCL ;Get the result. ADCL is LSB 10xxxxxx
in SampleHi,ADCH ;ADCH is MSB of 10bits, 98765432
sbi ADCSRA,4 ;Clear the complete flag by setting it 1
ldi Temp,$01
out TIFR0,Temp ;Clear TC0 overflow flag to re-enable auto ADC trigger
;Regen mix if switch set
sbrc SwStateB,2 ;Test switches for both (PB0&PB1=0)
rjmp Invert ;Which forces Negative Regen
sbrc SwStateB,0 ;Test Switch for Positive Regen (PB0=0)
rjmp NegRgn
rjmp PosRgn
NegRgn: sbrc SwStateB,1 ;Test for Negative Regen (PB1=0)
rjmp UpLFO ;No regen so jump to next function
Invert: eor RegenLo,LSBbits ;Invert regen sample.
com RegenHi
PosRgn: add SampleLo,RegenLo ;Add to current recording sample.
adc SampleHi,RegenHi
ror SampleHi ;Divide addition by 2 and preserve carry from addition
ror SampleLo
and SampleLo,LSBbits ;Clear all but bits 6,7 of LSB
;Manage the LFO RC timer. Toggles PA4 which clocks TC1 up/down counter
;Input PA5 and Output PA4 act as inverter of RC oscillator.
UpLFO:  sbis PINA,5             ;If timing cap low, then set resistor high
       sbi PORTA,4             ;Set resistor output high
       sbic PINA,5             ;If timing cap high, then clear resistor low
       cbi PORTA,4             ;Clear resistor output low
;Update the LFO sweep counter - Play pointer offset behind Record by TC1 count value
UpCnt:  in Temp,TCNT1L ;Get the current count from TC1
sbrc SwStateA,0 ;Always full sweep for Step option
rjmp Step
ldi LoTemp,SwpOff
sbrc SwStateA,2 ;Test if Sweep range switch on for Half Sweep (PA2=0)
rjmp SweepQ ;Else check for quarter sweep
lsr Temp ;Halve count for Half Sweep
ori Temp,ChorusOff ;Set bit 7 to move sweep delay back for chorus effect
rjmp Sweep ;Go to Sweep
SweepQ: sbrc SwStateA,3 ;Test if Sweep range switch on for Quarter Sweep (PA3=0)
rjmp Sweep ;Else full sweep
lsr Temp ;Halve sweep
lsr Temp ;Again for quarter
add Temp,LoTemp ;Add an offset to offset sweep away from zero delay
rjmp Sweep
Step: andi Temp,$FC ;Clear bit 0 & 1 for step sweep effect
Sweep: mov ZL,XH ;Initialize record pointer
clr ZH ;XH is the sample counter 0-255
clr LoTemp
lsl ZL ;Multiply by 2 and convert to 16bit value in Z pointer
rol ZH ;This done to cover 512byte buffer of 256 10bit samples
add ZL,XL ;Add offset to shift pointer relative to ram start ($0060)
adc ZH,LoTemp
mov YL,XH ;Initialize play pointer. Initally same as sample counter.
sub YL,Temp ;Apply sweep offset to Y pointer. Will wrap around on underflow
clr YH ;Convert to 16bit & add offset as above.
lsl YL
rol YH
add YL,XL
adc YH,LoTemp
;Manage record and playback
GoMix: st Z+,SampleLo      ;Save new sample to current Record pointer
st Z,SampleHi
inc XH ;Increment the sample counter.It will wrap around on overflow.
MixReg: ld MixLo,Y+    ;Get playback sample from current Y pointer.
ld MixHi,Y
mov RegenLo,MixLo ;Copy playback for next loop regen mix.
mov RegenHi,MixHi
;Determine mix polarity from mode switch state.
sbrs SwStateB,2
rjmp PosMix ;
eor MixLo,LSBbits ;Invert play sample LSB by exclusive OR with 1's
com MixHi ;Invert play MSB
PosMix: and Mixlo,LSBbits ;Clear all but LSB bits 7,6
add MixLo,SampleLo ;16bit addition of input sample and playback sample.
adc MixHi,SampleHi
ror MixHi ;Divide addition by 2
rol MixLo ;Right justify LSB of playback mix by rotating thru carry
rol MixLo ;Remainder from division (1bit) left in carry
out OCR0A,MixHi ;Send MSB and LSB of mix to PWM
out OCR0B,MixLo
;Manage switch scan. Test switches about once per 50ms
inc SwitchLo ;Increment 8bit count registers until switch delay time.
brne NotTime
inc SwitchHi
cp SwitchHi,SwCntHi ;Delay met when low count=0 and high count=SwCnt See .EQU
brne NotTime
clr SwitchHi
in Temp,PINA ;Store current mode switches port A
andi Temp,$0C ;Mask unwanted bits
brne SwitchB ;Test for both switches=0
sbr Temp,$01 ;Bit 0 set=Stepped full LFO Sweep.
SwitchB:mov SwStateA,Temp ;Save switch state bits
in Temp,PINB            ;Store current mode switches port A
andi Temp,$03 ;Mask unwanted bits
brne StoreB ;Test for both switches=0
sbr Temp,$04 ;Bit 2 set=negative play mix
StoreB: mov SwStateB,Temp ;Save switch state bits
NotTime:rjmp LOOP ;Loop forever
;*********************IT ENDS!!!***********************************************************
.Exit


electricco

Hi Jim, thanks for shering !

...I had a look to previous program but do not recognize which language are you using for the program ...is it C+ ?
I use arduino and compiling it like standard Sketches it returns several error...

can you pls explain the way you upload the code into the chip (not need in detail... just the method)

Thanks !

anotherjim

It's Assembler  - you need an assembler such as that included in AVR Studio to generate the machine code .hex file.
This is the hex file data...
:020000020000FC
:1000000000C0F89402E00EBF0FE50DBF01E407B990
:1000100004E906B904E103B902E001B904EF06B945
:1000200003EF00BF01E003BF00E009BF01EC0FBD1B
:1000300007E00EBD00E80ABD22E01127FF27E0E639
:1000400002E0502E11932193E036F505D9F7A0E692
:1000500026BF1CBFDD24EE2400ECB02E06E0802E6F
:100060000CE00BBB00ED0ABB03E008BB04E007BBE0
:100070000CE0602E03E0702EDC9A349BFECF14B1AE
:1000800025B1349A01E008BF72FC05C070FC01C0C4
:1000900004C071FC07C09B24A094190D2A1D27954C
:1000A00017951B21CD9BDC9ACD99DC980CB560FC93
:1000B0000CC066E062FC03C00695006807C063FCE4
:1000C00005C006950695060F01C00C7FEB2FFF2794
:1000D0006627EE0FFF1FEA0FF61FCB2FC01BDD2791
:1000E000CC0FDD1FCA0FD61F11932083B395499102
:1000F0003881942EA32E72FE02C04B2530954B21E1
:10010000410F321F3795441F441F36BF4CBFD39455
:1001100071F4E394E81459F4EE2409B30C7009F473
:100120000160602E06B3037009F40460702EA5CF41
:00000001FF


Looks really small like that doesn't it? Takes 304 bytes of program flash. I only write code in Assembler and consider this a big program ;)

Arduino Sketch is a Java based pseudo-code that does in the end generate AVR machine code HEX, but I don't know if you can use it to program the chip, even given the Hex file. I believe you can get an Arduino sketch that turns a board into a programmer that can program the Hex into an ordinary (no boot-loader) AVR.

I do mention it in the code comments, but do make sure you have the correct fuse settings. The chip already comes set to use the internal RC 8Mhz clock, but with the Div8 fuse set so it runs slower at 1Mhz. You only need to clear that fuse so it runs at 8Mhz if you have a blank chip.


anotherjim