Is there a SMALLER digital LFO chip than the TAPLFO?

Started by midwayfair, October 24, 2014, 02:50:08 PM

Previous topic - Next topic

slacker

Quote from: midwayfair on November 17, 2014, 04:13:30 PM
This is awesome! Thanks so much!

p.s. What are the unused pins? Or what CAN they be?

Cheers Jon, hopefully a few folks will find it useful.

One of the unused pins is a digital in/out and the other is a digital in only, so both could have a switch to select something. I was thinking one of them could be used to select between how it works now and a second mode with different wave shapes. Or could do a half/double speed switch or freeze or reset the waveform. If anyone's got any other ideas let me know.

Quote from: jubal81 on November 17, 2014, 05:36:48 PM
Can't wait to put this to use. Would it be possible to do one with a second LFO output, 180 degrees out of phase?

I don't think so but I'll have a look, if you want to use it to drive LEDs you can invert the phase using a transistor.

midwayfair

Quote from: slacker on November 18, 2014, 12:49:18 PM
if you want to use it to drive LEDs you can invert the phase using a transistor.

This is what I'd do. You'd still want an NPN to buffer the PWM, so it doesn't save a part over just using a PNP to invert it.

I'm having a hard time understanding the shape control, mostly because I'm not sure what a trapezoidal wave sounds like ... is that like the duty cycle? When you say that it clips the size to make the square, does that mean that the PWM amplitude is limited, or is it more like it's pushing the wave up until it squares off?

I do kind of like the idea of using one of the pins to set what that control does for the wave shape control (like one mode would be pre-set waveforms like the TAPLFO and the other would be your more analog control over the wave); that adds some versatility for builders without clutter.

By the way, are the Brownout functions what set whether it defaults to the knob positions on power-up, or is that something beyond the capabilities of this chip? I know there's two TAPLFOs, one of which defaults to the knobs and the other doesn't.

I really know too little of this stuff to be helpful with suggestions for the last pin, and I'm really just happy as long as a true sine and square are available, but there are some things I can imagine an LFO control doing:

1. Half speed, as you suggested. People seem to like this in stuff like the Fulltone Supatrem, but it doesn't work perfectly in an analog design. Definitely one case where the digital chip would have a big advantage, and it's also really easy to leave it off.

2. Not sure about this, but speaking of waveforms, would it free up an analog pin (if you have cooler ideas for that) if you have one of the digital pins set the waveform? Like just center = sine, left connects to G = square, right connects to +5V = triangle?

3. LFO brake ... I really doubt this is possible, though. I'm thinking of something that gradually reduces the speed as long as it's held and winds back up when it's not, like in a Leslie. Someone could probably figure out a way to implement the chip in the ROG Tri-Vibe as a Leslie simulator if that existed. Again, though, I don't see this one being plausible in such a simple chip. Maybe at absolute best it would switch what the shape control does, so instead of someone having a wave shape pot, it would default to a sine wave and then that pin is controlled by a switch.

Not sure what anyone else would find useful.
My band, Midway Fair: www.midwayfair.org. Myself's music and things I make: www.jonpattonmusic.com. DIY pedal demos: www.youtube.com/jonspatton. PCBs of my Bearhug Compressor and Cardinal Harmonic Tremolo are available from http://www.1776effects.com!

slacker

#22
Quote from: midwayfair on November 18, 2014, 01:33:40 PM
I'm having a hard time understanding the shape control, mostly because I'm not sure what a trapezoidal wave sounds like

The shape control works basically the same as hard diode clipping, think of the shape control as increasing the gain. At minimum you get a sine with the peak at the clipping level, as you increase the shape the top and bottom of the wave get clipped off, the amplitude stays the same but the peaks flatten off.             _     _
This gives a trapezoid shape, sloping sides and a flat top _/  \_/   \_ the more you clip it the straighter the sides become until you get a square wave. This actually needs a bit of work, the square isn't quite square enough, it sounds square at higher speeds but at slower ones the sides still have a noticeable slope.

Quote
I do kind of like the idea of using one of the pins to set what that control does for the wave shape control (like one mode would be pre-set waveforms like the TAPLFO and the other would be your more analog control over the wave); that adds some versatility for builders without clutter.

Yeah that's what I thought, two for the price of one, assuming I can fit it all in :)

Quote
By the way, are the Brownout functions what set whether it defaults to the knob positions on power-up, or is that something beyond the capabilities of this chip? I know there's two TAPLFOs, one of which defaults to the knobs and the other doesn't.

Short answer, it defaults to the knob positions at power up.
Long answer, at power up all the settings are at zero. It reads a pot every 20ms, cycling through each one in turn, so after 60ms it will have read all 3 pots and be using their settings.

Quote
2. Not sure about this, but speaking of waveforms, would it free up an analog pin (if you have cooler ideas for that) if you have one of the digital pins set the waveform? Like just center = sine, left connects to G = square, right connects to +5V = triangle?

Nice idea. Digital inputs can only be high or low though, so only two options per pin, could use both to get four wave forms.

Quote
3. LFO brake ... I really doubt this is possible, though. I'm thinking of something that gradually reduces the speed as long as it's held and winds back up when it's not, like in a Leslie.

Unfortunately probably not possible. You might be able to add it externally though, little circuit that ramps the voltage from the speed pot up and down.  

midwayfair

Quote from: slacker on November 18, 2014, 03:49:31 PM

Unfortunately probably not possible. You might be able to add it externally though, little circuit that ramps the voltage from the speed pot up and down.  

Good idea. Fortunately, Josh McClaren at 1776 already has a circuit that does that in the Multiplex I can nick!

Just an idea on the wave shape idea:

What if it changes the slope of the wave instead of clipping the top of the wave? At one side, the rise and fall is uniform (sine). As the pot is turned, the rise becomes more and more immediate, resulting in a square wave at the far end. I can't figure out how to get a triangle out of that, but maybe that's what the function switch idea is for.
My band, Midway Fair: www.midwayfair.org. Myself's music and things I make: www.jonpattonmusic.com. DIY pedal demos: www.youtube.com/jonspatton. PCBs of my Bearhug Compressor and Cardinal Harmonic Tremolo are available from http://www.1776effects.com!

samhay

Nice job slacker, and the 12F1822 looks like a very cool little chip.

I guess you would be stepping on the TAPFLO's toes, a little, but do you have enough resources to use one of the unused pins as an optional tap tempo input?
I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

slacker

#25
Cheers Sam. Yeah the 12F1882 is a nice bit of kit, only issue with it as far as I'm concerned is that MPLABX doesn't support programming it with a PICkit2 programmer, they want you to buy a PICkit3 or an ICD3. Luckily there's some unofficial updates to the standalone PICkit2 software that support it.

I'd have to have a think about how to implement it but I think the chip has enough resources left to do a tap tempo, it's not something I'm planning on doing for now but I might have a look in the future.

samhay

^it's not something I'm planning on doing for now but I might have a look in the futureFair enough.

Fair enough.

Thanks for the posting the code - and very nice to see you wrote it in c rather than assembly. If/when I next get the need for a digital LFO, I might take a closer look.
In the mean time, I must add a few of these chips to my next Farnell order.
I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

ElectricDruid

Quote from: slacker on November 17, 2014, 04:07:48 PM
I've come up with a first attempt at this. I started off trying to do it with a PIC 12F683 running a 8Mhz, which limited me to an 8 bit PWM, I soon found that, probably due in part to my mediocre programming skills, this was really too slow for the job so I had a look for some faster chips.
I settled on the PIC 12F1822 which can run at 32Mhz and has all the peripherals I needed. It's a pretty cool beast for a an 8 pin chip and as a bonus it's cheaper than the 12F683, or it is round here anyway.

The current version has a 10 bit PWM output and produces a sine wave of between 0.03Hz and 20Hz. It also has depth and shape controls, the shape clips the sine giving trapezoid and square waves. I might add a second mode that changes the shape control's function to switch between different wave forms, triangle, saw etc.
Thanks go to Tom Wiltshire for his excellent TAPLFO project, studying his code to see how he did it was a great help. The speed pot scaling and the hysteresis for the shape pot were copied from him.

The code is here if anyone wants to see it https://github.com/slackDSP/slackLFO/blob/master/slack_lfo_main.c it still needs tidying up and commenting properly.

I've tested it using the tap tempo trem audio circuit and it seems to do the job.

I was going to suggest using one of the newer 12F1xxx or 16F1xxx "enhanced" chips, but it sounds like Slacker's right there already. They remove the need for the crystal which is a major help, and the higher clock rate is the icing on the cake. Plus the new instructions should lead to some more efficient code. There's also no need to run the sample update at the same rate as the PWM output if time gets tight. I've done some projects using the Timer postscaler to divide-down the interrupt rate. Like this, you can have a PWM frequency of 31.25KHz and a sample interrupt of half that. Essentially, it automates just outputting each sample twice.

So, having stepped in to tell you what you all already know, I'll just say "Good work! Carry On!".

Tom

rustypinto

#28
Quote from: jubal81 on November 17, 2014, 05:36:48 PM
Can't wait to put this to use. Would it be possible to do one with a second LFO output, 180 degrees out of phase?
Quote
I don't think so but I'll have a look, if you want to use it to drive LEDs you can invert the phase using a transistor.

Assuming you use the ECCP for PWM: using the half-bridge mode with P1B inverted can generate a complimentary LFO (or P1A inverted, P1B normal, doesn't matter). I've done this with 12F1822. This chip also offers two pin-muxing options if P1B gets in the way of an ADC or something else.
  • SUPPORTER

samhay

Figured it was best to bring this post back from the dead than start another.
I find myself in the need (well want) of a PIC that can do 2 LFOs.

Quote from: rustypinto on December 06, 2014, 11:30:28 AM
Assuming you use the ECCP for PWM: using the half-bridge mode with P1B inverted can generate a complimentary LFO (or P1A inverted, P1B normal, doesn't matter). I've done this with 12F1822. This chip also offers two pin-muxing options if P1B gets in the way of an ADC or something else.

I got quite excited about this for a while, but realised that I don't understand how this would work if the depth was also determined by the duty cycle?

So, any suggestions for a small(ish) PIC that can run at 32MHz(ish) and has 2 CCP modules?
I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

slacker

#30
I've not used any of them but look at the bigger enhanced midrange 8 bit jobbies, 16F1824 to 16F1829 have two PWM modules. Some of the through hole dsPICs have 4 PWMs and there's a couple with 16bit DACs, might be overkill for what you want.

samhay

Have spent some time the the microchip MAPS and found the 16F1824/5. These should work, but are 14 pin. Nice features (including DIP) and price, so will add a couple to the basket.

As I don't need ECCP, just 2xCCP, the 12F1622 might just do the trick with 3 10bit ADC to spare (don't need wave shape, but could have 2 switchable options via the last digital IO pin

http://www.microchip.com/wwwproducts/Devices.aspx?product=PIC12F1612
I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

samhay

After a few teething problems with the 8 pin pic12F1612 - new chip and a few errors in the datasheet and some mostly undocumented new features in the CCP modules - I have it running and generating a dual LFO. Currently have 3 pots - depth, speed and phase between LFOs (0-359 degrees). I currently have the last pin disabling the LFO when grounded, but may alternatively use this to switch between 2 waveforms.

I will tidy up the code and post it along with a hex file. If there is any interest, I could easily also do a second version where the second LFO is fixed 180 out of phase (could be switchable e.g. between 0 or 90 vs 180o) and have the 3rd pot do something else - probably different waveforms, but open to suggestions...

slacker - your slackLFO code was very helpful in getting started. Thanks.
I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

samhay

#33
Before I get distracted again, I figured I should post the code for the dual LFO I mentioned in the last post.
It works quite nicely, but at some stage I will tweak the speed range/spread and to make the way the depth pot scales the LFO a little more elegant.

At the moment, pin 4 switches between a sine and square wave LFO.
I see that the code is censored too. The 'c0cked wah' mode kicks in when the speed pot is fully ccw. In this case, the LFO stops osciallating and the 2 outputs are controlled separatedly by the depth and phase pots.



/*
* File:   DUAL_LFO_main.c
* Author: Sam Hay
* Created on August 15, 2015, 2:12 PM
*
* based in part on slackLFO:
* https://github.com/slackDSP/slackLFO/blob/master/slack_lfo_main.c
* which is based in part on TAPLFO:
* http://www.electricdruid.net/index.php?page=projects.taplfo
*
*
*                              __________
*                VDD (+5V) ---|         |--- VSS
*           CPP1, PWM out1 ---| 12F1612 |--- CPP2, PWM out2 (ICSPDAT)
*           AN3, phase pot ---|         |--- AN1, speed pot (ICSPCLK)
*  (MCLR off) RA3, wave_sw ---|_________|--- AN2, depth pot
*
*/

#include <xc.h>                 // pic12F1612 is new and needs xc8 v1.35(ish)
#include <stdint.h>

// CONFIG1
#pragma config BOREN = ON       // Brown-out Reset Enable->Brown-out Reset enabled
#pragma config PWRTE = OFF      // Power-up Timer Enable->PWRT disabled
#pragma config FOSC = INTOSC    // Oscillator Selection Bits->INTOSC oscillator: I/O function on CLKIN pin
#pragma config MCLRE = OFF      // Disable MCLR so pin 4 (RA3) can be used as input
#pragma config CP = OFF         // Flash Program Memory Code Protection->Program memory code protection is disabled
#pragma config CLKOUTEN = OFF   // Clock Out Enable->CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection->Write protection off
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset->Low-Power BOR is disabled
#pragma config LVP = OFF        // Low-Voltage Programming Enable->High-voltage on MCLR/VPP must be used for programming
#pragma config ZCD = OFF        // Zero Cross Detect Disable Bit->ZCD disable.  ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable->Stack Overflow or Underflow will cause a Reset
#pragma config PLLEN = ON       // PLL Enable Bit->4x PLL is always enabled
#pragma config BORV = LO        // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (Vbor), low trip point selected.

// CONFIG3
#pragma config WDTCWS = WDTCWSSW // WDT Window Select->Software WDT window size control (WDTWS bits)
#pragma config WDTCCS = SWC      // WDT Input Clock Selector->Software control, controlled by WDTCS bits
#pragma config WDTE = OFF        // Watchdog Timer Enable->WDT disabled
#pragma config WDTCPS = WDTCPS1F // WDT Period Select->Software Control (WDTPS)

// GLOBAL DEFINITIONS
#define _XTAL_FREQ 32000000     // processor speed for delay macro
#define speed_pot pots[0]       // these are 8bit values populated from ADRESH (8 MSB) in main
#define depth_pot pots[1]
#define phase_pot pots[2]
#define wave_sw PORTAbits.RA3   // select between sine (RA3 = 1) and alternative (RA£ = 0) waveforms in interrupt routine

// GLOBAL ARRAYS
// 256 8bit values that cover 2pi rad of a sine wave, starting from 0
volatile const unsigned int sine[256] = {
0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35,37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76,79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124,128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173,176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244,245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246,245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179,176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82,79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40,37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0
};
// '0' + 255 speed values log spaced from 54-13366 with speed_pot = 0 for '%^&*ed wah' mode;
// LFO freq calculation given below in interrupt routine
volatile const unsigned int speed[256] = {
0,54,56,57,59,60,61,62,64,65,67,68,70,71,73,74,76,78,79,81,83,85,86,88,90,92,94,96,98,101,103,105,107,110,112,115,117,120,122,125,128,130,133,136,139,142,145,148,152,155,158,162,165,169,173,177,180,184,188,192,197,201,205,210,214,219,224,229,234,239,244,250,255,261,266,272,278,284,290,297,303,310,317,324,331,338,345,353,360,368,376,385,393,402,410,419,429,438,448,457,467,478,488,499,510,521,532,544,556,568,580,593,606,619,633,646,661,675,690,705,720,736,752,769,785,803,820,838,856,875,894,914,934,954,975,996,1018,1041,1063,1087,1110,1135,1159,1185,1211,1237,1264,1292,1320,1349,1379,1409,1439,1471,1503,1536,1570,1604,1639,1675,1711,1749,1787,1826,1866,1907,1949,1991,2035,2079,2125,2171,2219,2267,2317,2368,2419,2472,2526,2582,2638,2696,2755,2815,2877,2940,3004,3069,3137,3205,3275,3347,3420,3495,3571,3650,3729,3811,3894,3979,4066,4155,4246,4339,4434,4531,4630,4731,4835,4941,5049,5159,5272,5387,5505,5625,5748,5874,6003,6134,6268,6405,6545,6688,6835,6984,7137,7293,7453,7616,7782,7952,8126,8304,8486,8671,8861,9055,9253,9455,9662,9873,10089,10310,10535,10766,11001,11242,11488,11739,11996,12258,12526,12800,13080,13366
};
// used in main to update _pots values
volatile unsigned char pots[3] = {0,0,0};

// INITIATION GUBBINS
void OSCILLATOR_Init(void) { 
    OSCCONbits.IRCF = 14;       // 8Mhz clock + 4 x PLL from PLLEN   
    OSCSTAT = 0x00;             // HFIOFR disabled; HFIOFS not 0.5percent_acc; PLLR disabled; MFIOFR disabled; HFIOFL not2percent_acc; LFIOFR disabled;
    OSCTUNE = 0x00;             // TUN 0x0;
}

void TIMERS_Init(void) {
    INTCONbits.GIE = 1;         // set the Global Interrupt Enable
    INTCONbits.PEIE = 1;        // set the Peripheral Interrupt Enable
    INTCONbits.T0IF = 0;        // Timer0 overflow interrupt flag bit
    INTCONbits.T0IE = 1;        // Timer0 overflow interrupt enable
    OPTION_REGbits.T0CS = 0;    // Timer0 Clock Source Select - Internal instruction cycle clock (FOSC/4)
    OPTION_REGbits.T0SE = 0;    // Timer0 Source Edge Select bit - Increment on low-to-high transition on T0CKI pin
    OPTION_REGbits.PSA = 0;     // Prescaler is assigned to the Timer0 module
    OPTION_REGbits.PS = 0;      // Prescaler Rate Select = 1 : 2
    TMR0 = 0;                   // load a value of 0 into the timer
    T2CONbits.TMR2ON = 1;       // Timer 2 on
    T2CONbits.T2CKPS = 0;       // Timer 2 prescaler 1
    PR2 = 255;                  // Timer 2 period ~31Khz PWM freq   
}

void PIN_Init(void) {
    LATA = 0x00;                // clear all pins to 0 
    TRISAbits.TRISA0 = 0;       // RA0 (pin 7) CCP1 PWM out - output 1
    TRISAbits.TRISA5 = 0;       // RA5 (pin 2) CCP2 PWM out - output 2
    TRISAbits.TRISA1 = 1;       // RA1 (pin 6) input
    TRISAbits.TRISA2 = 1;       // RA2 (pin 5) input
    TRISAbits.TRISA3 = 1;       // RA3 (pin 4) input - wave_sw
    TRISAbits.TRISA4 = 1;       // RA4 (pin 3) input
    ANSELAbits.ANSA1 = 1;       // AN1 (pin 6) analogue input - speed pot
    ANSELAbits.ANSA2 = 1;       // AN2 (pin 5) analogue input - depth pot
    ANSELAbits.ANSA4 = 1;       // AN3 (pin 3) analogue input - phase pot
    OPTION_REGbits.nWPUEN = 0x01; // All weak pull-ups are disabled (except MCLR, if it is enabled)
    WPUAbits.WPUA3 = 1;         // enable weak pullup on RA3/MCLR - wave_sw should default to sine with no connection to pin 4
}

void PWM_Init(void) {           // initialise both PWM modules: CCP1 on RA5/pin 2 and CCP2 on RA1/pin 7
    APFCONbits.CCP1SEL = 1;     // CCP1 functions on RA5 / pin 2     
    CCP1CON = 0xCC;             // EN=1, module enabled; OE=1, output enabled; FMT=0, duty right-aligned; MODE PWM
    CCP2CON = 0xCC;             // (0b11001100)
    CCPR1H = 0x00;              // to start, lets set duty cycle to 0
    CCPR2H = 0x00;
    CCPR1L = 0x00;              //
    CCPR2L = 0x00;
    CCPTMRSbits.CCP1TSEL = 0x0; // Select Timer2
    CCPTMRSbits.CCP2TSEL = 0x0; 
}

void ADC_Init(void) {
    ADCON1bits.ADCS = 3;        // DFRC - ADC clock supplied from an internal RC oscillator
    ADCON1bits.ADFM = 0;        // ADC register pair left justified, ADRESH contains 8 MSB, ADRESL <7:8> contains 2 LSB
    ADCON0bits.ADON = 1;        // ADC on
}

// SET PWN DUTY CYCLE ROUTINE
void PWM1_DutyValueSet(unsigned int duty1) {
    duty1 &= 1023;              // duty cycle value is 10bit
    CCPR1H = duty1 >> 8;        // FMT=0 so CCPRxH <1:0> contains 2 MSB,
    CCPR1L = duty1;
}

void PWM2_DutyValueSet(unsigned int duty2) {
    duty2 &= 1023;
    CCPR2H = duty2 >> 8;
    CCPR2L = duty2;
}

// MAIN - INITIALISE EVERYTHING AND THEN POLL ADC AND UPDATE _pots VALUES
void main() {
   
    // local variables
    const unsigned char adc_channel[3] = {1, 2, 3};
    unsigned char count = 0;
   
    // initialise everything using above gubbins
    OSCILLATOR_Init();          //
    TIMERS_Init();              //
    PIN_Init();                 //
    PWM_Init();                 //
    ADC_Init();                 //

    // taken straight from slackLFO with ADC channels updated
    for (;;) {                                 
        count++;                                // read 1 pot per cycle, runs every ~20 ms
        if (count == 3)                         // count = 0,1,2,0...;
            count = 0;                          // could alter this to sample some pots more than others

        ADCON0bits.CHS = adc_channel[count];    // select analog input - AN1, AN2, AN3 in turn
        __delay_ms(20);                         // could make this shorter

        GO_nDONE = 1;                           // make ADC read
        while (GO_nDONE);                       // wait while reading takes place
        pots[count] = ADRESH;                   // ADC Result High Register = 8 MSB
    }  // end for
}  // end main


// INTERRUPT - CALCULATE & WRITE PWM DUTY CYCLE
void interrupt isr(void) {                      // ISR runs every 64us

    INTCONbits.T0IF = 0;                        // clear Timer0

    // local variables
    static unsigned int step = 0;
    static unsigned int pos1 = 0; 
    unsigned int pos2 = 0; 
    unsigned int output1 = 0;
    unsigned int output2 = 0;
    unsigned int output1_duty = 0;
    unsigned int output2_duty = 0;
   
    // '%^&*ED WAH' mode - also useful for calibration/trimming the LED current to set e.g. min LDR resistance

    if (speed_pot == 0) {                       // speed pot at minimum = manual mode with no LFO

        PWM1_DutyValueSet(depth_pot << 2);      // PWM output1 is determined only by depth pot
        PWM2_DutyValueSet(phase_pot << 2);      // PWM output2 is determined only by phase pot
       
    } else {                                    // LFO frequency is determined by interrupt rate (64 us) and speed[]   
        step = step + speed[speed_pot];
        if (step > 32767) {
            step = step & 32767;
            pos1 = (pos1 + 1) & 255;
            pos2 = (pos1 + phase_pot) & 255;
           
            // 1/freq = (32767/speed_pot) * 64eE-6 * 255
            // LFO freq = 0.1 - 25 Hz if min/max speed_pot = 54/13366
           
            if (wave_sw == 1) {                         // pin 4 is high, LFO has sine waveform
            output1 = sine[pos1];                       // 8 bit unsigned
            output2 = sine[pos2];                       // 8 bit unsigned
            } else {                                    // pin 4 is low
                output1 = (pos1 < 128)? 255 : 0;        // alternatively pin 4 is low, LFO has square waveform
                output2 = (pos2 < 128)? 255 : 0;
            }  // end wave_sw else
           
            output1_duty = (output1 * depth_pot) >> 6;  // 8bit unsigned * 8bit unsigned >> 6 = 10 bit unsigned
            output2_duty = (output2 * depth_pot) >> 6;

            PWM1_DutyValueSet(output1_duty);            // set PWM duty cycle
            PWM2_DutyValueSet(output2_duty);

        }  // end step if
    }  // end speed else
   
}  // end interrupt

I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

idiot savant

GREAT job. Thanks for sharing!

I did something similar recently using an AVR to do a simple LFO for a project.

http://www.diystompboxes.com/smfforum/index.php?topic=110823.0

I only posted code for sine wave, but I have a setup for triangle, ramp up and down and square as well. 2 outputs at 180deg.

The variable phasing you implemented is a neat idea, What kind of applications were you thinking of using it for?

--At some point, representing the full 360deg cycle (in complementary form)seems a little weird to me, are you really going to need multiple outputs a few degrees apart, in practice you'd never even notice phase shifts of less than a certain difference...

Speed wise, you have a HUGE range of 0.1-25Hz. How does it feel on pot travel?

When I dealt with mine, I decided to limit the range, and provide code functions for doubling or halving speed ranges eternally...


I'm not a PIC guy, so some hardware config stuff looks kinda funny to me. Like why bother running the chip at 32MHz when the fastest thing happening is your 31kHz PWM. You'd save a ton of power running at the slowest possible speed that maintains your fastest desired peripheral. Also, hard coded _delay_ms calls like in the ADC read function are sloppy and may cause timing errors on the important stuff like the phase accumulator and PWM, why not round-robin and discard the extra samples(you can only turn a pot so fast).

Again this all might just be differences in PIC's vs. the AVR's that I'm used to.


Anyways, not griping, just interested as I'm a novice programmer.

Thanks for posting!!

-Morgan

samhay

#35
Thanks Morgan, I hadn't seen your project but I like it - there is something wonderful about combining a valve and a uC.

I figured I would play with arbitary phase shifting as, AFAIK, it is essentially impossible to do (reasonably) in the analogue domain.
The first thing I will use the code for is this: http://www.diystompboxes.com/smfforum/index.php?topic=111802.5
I just have a couple of tweaks to make and should post the V2 with the pic quite soon.

I haven't quite figured whether having 180 or 360 degrees of phase variation is better, so I figured I would keep it like this for a while and do some real world experiments to see if I ever turn it past noon/180 degrees.
In any case, it is trivial to change the code to give only 180 degrees:
pos2 = pos1 + (phase_pot >> 2) & 255;

The other reason for doing this is that I have a 'kitchen sink' version in the works using a pic16F1825 with a second LFO that modulates the phase and in this case, it makes sense (I think) to be able to swing the full 360 degrees.

I took the approximate speed range from the TAPLFO. It is mostly quite workable, but I might change the taper a little to give more play around the sweet spot, once I work out where that is. I did think about a range switch, but there isn't enough pins with this chip.

^I'm not a PIC guy...
I do not have a huge ammount of experience with PICs, so some of the design choices may be suboptimal. That said, most operations take 4 clock cycles, so you can think of it as running at 8MHz. In any case, I will have to check, but I'm fairly sure that the clock has to be running this fast to get the 10bit PWM output at ~31kHz
Actually, that must be right as 31.25kHz * 1024 = 32 MHz.
Also, remember this is 8bit architecture, so you need to spend a fair few cycles doing most of the math.

The ADC read code came straight from slacker's code and as it works well enough, I haven't bothered changing it. I am guessing that the 20ms delay is essentially debouncing, but perhaps he can elaborate.

I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

idiot savant

Lol, yeah 10-bit makes more sense now... I saw the 8-bit wave table and ass-umed 8-bit. :icon_redface:

samhay

Your confusion is quite understandable.
I wanted to keep the ADC 8bit, so made the wave table entries 8bit so that ADC*wave table is 16bit. As this is then divided down to 10bit, there are other ways to get there, and I have been thinking about getting better resolution by using a 10bit wave table and then a more elaborate duty cycle calculation that doesn't require operations on >16bit integers.
I'm a refugee of the great dropbox purge of '17.
Project details (schematics, layouts, etc) are slowly being added here: http://samdump.wordpress.com

slacker

Quote from: samhay on September 17, 2015, 09:24:43 AM
The ADC read code came straight from slacker's code and as it works well enough, I haven't bothered changing it. I am guessing that the 20ms delay is essentially debouncing, but perhaps he can elaborate.

The delay is there for two reasons, first when you change ADC channels you need to wait a certain amount of time for the ADC to settle and you start getting valid readings from it, can't remember the exact value it's in the datasheet somewhere, it's a few u seconds. Second there's no point reading the pots faster than you can turn them so I slowed it down, can't remember why I chose 20ms you could probably slow it down even more and the pots would still feel nice and responsive.
Morgan, not sure what you meant by "sloppy" if you meant the timing is sloppy, then yeah it is but it doesn't matter if the pots aren't read exactly every 20ms so not an issue. If you meant it's sloppy coding then yeah it can be, the delay makes the program sit there doing nothing and often it's better to poll things periodically use an interrupt to trigger something or do something useful to cause a delay instead. In this case though all the main loop does is read the pots so it doesn't matter if it sits there doing nothing useful most of the time. It doesn't affect the timing of the phase accumulator because this is running in an interrupt triggered every 64us by a timer, you actually have the opposite problem, the interrupt messes with the timing of the main loop. The longer the interrupt takes doing its thing the longer the main loop is interrupted for.
The PWM is a hardware peripheral running off the min clock its speed isn't influenced by what the processor is doing.

idiot savant

Quote from: slacker on September 17, 2015, 01:27:19 PM
Quote from: samhay on September 17, 2015, 09:24:43 AM
The ADC read code came straight from slacker's code and as it works well enough, I haven't bothered changing it. I am guessing that the 20ms delay is essentially debouncing, but perhaps he can elaborate.

If you meant it's sloppy coding then yeah it can be, the delay makes the program sit there doing nothing and often it's better to poll things periodically use an interrupt to trigger something or do something useful to cause a delay instead.

This is what I was getting at. Though as you said it doesn't really matter. I've been chewed out for using busy wait delays too much, so now I try to avoid them.

Sorry for making it sound confrontational in my other post, I didn't understand the program flow fully before I opened my mouth. :icon_redface: