LDR as digital control for analog effects

Started by Reinerterig, June 26, 2018, 12:35:49 AM

Previous topic - Next topic

Reinerterig

I recently got myself some NSL-32s ( they are light sensitive resistors with an LED in the same packet ).

The idea is that you can program a preset brightness for the LED resulting in a certain resistance or control the brightness via an encoder. I can use this in a ton of different ways to improve the functionality of my effects.

The NSL-32 has a huge range of resistance going into the M-ohms so I can dial in virtually any resistance I need. But the problem is because the range is so huge it is difficult for me to be precise.

I need a way to calibrate the NSL-32 and lock in the brightness of my LED to a specific range, say 1K when my encoder is fully closed and 500K when fully open.

I know I can limit the brightness of my LED using the resistor from the negative leg to ground and that will certainly help lock down one half of my problem.

At the moment I am using an Arduino to adjust the brightness using pwm. with a 220-ohm resistor on my LED I get about 45-ohm on the NSL-32 encoder fully open and when the encoder is fully closed it jumps around  from 1M-17M-overload and stays at OL when I pull it back a bit I can get the meter to hover at .470K (470-ohms) but even then it jumps around a little bit.

Does anyone have any experience with these LDRs? I would love to stability use them up to 1M.

Here is the code I am using it's really straightforward.

/*
  Analog input, analog output, serial output

  Reads an analog input pin, maps the result to a range from 0 to 255 and uses
  the result to set the pulse width modulation (PWM) of an output pin.
  Also prints the results to the Serial Monitor.

  The circuit:
  - potentiometer connected to analog pin 0.
    Center pin of the potentiometer goes to the analog pin.
    side pins of the potentiometer go to +5V and ground
  - LED connected from digital pin 9 to ground

  created 29 Dec. 2008
  modified 9 Apr 2012
  by Tom Igoe

  This example code is in the public domain.

  http://www.arduino.cc/en/Tutorial/AnalogInOutSerial
*/

// These constants won't change. They're used to give names to the pins used:
const int analogInPin = A0;  // Analog input pin that the potentiometer is attached to
const int analogOutPin = 13; // Analog output pin that the LED is attached to

int sensorValue = 0;        // value read from the pot
int outputValue = 0;        // value output to the PWM (analog out)

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
}

void loop() {
  // read the analog in value:
  sensorValue = analogRead(analogInPin);
  // map it to the range of the analog out:
  outputValue = map(sensorValue, 0, 1023, 0, 255);
  // change the analog out value:
  analogWrite(analogOutPin, outputValue);

  // print the results to the Serial Monitor:
  Serial.print("sensor = ");
  Serial.print(sensorValue);
  Serial.print("\t output = ");
  Serial.println(outputValue);

  // wait 2 milliseconds before the next loop for the analog-to-digital
  // converter to settle after the last reading:
  delay(2);
}

Digital Larry

I did this years ago with an LDR that had a center tap on the resistive element.  The assumption was that both sides were the same.  Ground the center tap and pull one side high through a resistor.  This supplies the feedback to an op-amp servo circuit.  You send the target voltage to the servo input and the op amp drives the LED to try to get it balanced. 

Couple of drawbacks:
a) Center tapped LDRs are rare if not non existent.
b) Requires the design to use a shunt-to-ground topology.

You could also use 2 "identical" LDRs with LEDs in series and hope for the best as regards matching.  Or you can calibrate each one somehow and put a table into your code which attempts to even things out.

All of this assumes that you want to get repeatable behavior over a number of different units that are likely to have an inherent spread in resistance for a given amount of LED current.  If you just want to get one unit working to your liking I'd skip the servo and just design it so you have a table to match the knob adjustment range to how you want it to sound.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

MetalGuy

#2
This topic has been discussed thoroughly at the MEF (old Ampage) in a thread called "LDRs as pots". I've played with that a lot when I was building my programmable tube preamp (code was written by a friend so I can't comment that part) so I'll try to summarize it for you:
1/ For a "real pot" with a taper you'll need a 6 pin LDR similar to Hamamatsu HTV P873-13 which is unobtainium (unless of course you kindly order from Hamamatsu several thousand of them). If you're lucky enough to find single pieces at Ebay or something be ready to dump some cash.
2/ Good news is you can custom make your own 6 pin LDRs for cheap combining two photocells and a LED in a shrink tube. Patch both ends of your newly made 6 pin LDR with a black silicon or similar to make sure it doesn't leak light. Select the photocells so than can go down to couple of hundred of Ohms and depending on where you put them up to 1M. At that time I experimented with VT935G-A, NSL-6112 and NSL-5162. I still have couple of dozen from them.
3/ Using Mesa's patented cervo circuit for stable operation is a good idea so check that out.
4/ Downside is LDRs would drift with temperature and over time but not as much as that to be a serious problem.

Later I completely abandoned this in favor of the completely analog "relays pot" approach which wouldn't be very suitable for a pedal design due to the space such a pot takes.

ElectricDruid

Are you sure the LDR drift is not caused by the LED/PWM?

If you're using a pot connected to the ADC to control the PWM output value, I'd expect to see a few bits of noise at the bottom. If you read sequential values on an ADC, you often see something like 134, 134, 133, 134,135, 135, 134, 133, 132, etc etc - it's close, but not perfect. If you feed that to the PWM, the LED brightness with vary too, and that won't help.
Ultimately though, what everyone has said is correct. You need to close the loop somehow, so that the processor can detect the result it is achieving and calibrate the output accordingly. That doesn't have to be using the LDR necessarily.

Example: One way to control the PT2399's delay time is to use an LDR hanging off pin 6 to ground. Imagine we connect up our Arduino to this thinking that we can build a tap tempo delay. The Arduino needs to know the delay time that the chip is providing. Luckily the PT2399 provides a clock output which we can feed back to the Arduino, so it can calibrate a specific output value with a specific clock frequency. For this, a single LDR is enough.

HTH,
Tom

Reinerterig

Before I got the LDRs I looked at a bunch of different options DIGI-Pots has a limit to the amount of voltage you can push through them --usually quite small- whereas these LDRs can take up to 60V.

I solved my problem!

first of all, I was using a 220-ohm resistor which gives me 22mA of current at 5V and 4mA at 1V resulting in a range of about 40-200 Ohms ( not impressive )

I swapped out the resistor on the LED to a 100K giving me 0.01mA at 1V and 0.05mA at 5V taking my range up into the K-ohms ( I can't remember the exact values but the datasheet has a graph and I remember it being pretty spot on)

Also, my code ranges from 0-255. 0 is a problem because that turns my LED off that's why my resistance read as an overload. So I changed it to range from 4-255 and things were way more stable. By stable I mean when applying a voltage the resistance drifts within 1ohm ( not bad at all ).

I was playing around with a gain circuit a couple of days ago and I pulled out the resistor in the feedback loop and replaced it with the LDR. Using the code above the LDR reacted quickly as I changed the value of the POT and it sounded pretty stable. I changed the code to an LFO I found online ( I'll post that below ). --BTW, If anyone can point me to a lesson that can show me how to create waveforms using PWM and change the Freq. That would be appreciated.-- All in all, it was a pretty good tremolo and a solid proof of concept.



// 3 phase PWM sine
// (c) 2016 C. Masenas
// Modified from original DDS from:
// KHM 2009 / Martin Nawrath

// table of 256 sine values / one sine period / stored in flash memory
PROGMEM const unsigned char sine256[] = {
127,130,133,136,139,143,146,149,152,155,158,161,16 4,167,170,173,176,178,181,184,187,190,192,195,198, 200,203,205,208,210,212,215,217,219,221,223,225,22 7,229,231,233,234,236,238,239,240,
242,243,244,245,247,248,249,249,250,251,252,252,25 3,253,253,254,254,254,254,254,254,254,253,253,253, 252,252,251,250,249,249,248,247,245,244,243,242,24 0,239,238,236,234,233,231,229,227,225,223,
221,219,217,215,212,210,208,205,203,200,198,195,19 2,190,187,184,181,178,176,173,170,167,164,161,158, 155,152,149,146,143,139,136,133,130,127,124,121,11 8,115,111,108,105,102,99,96,93,90,87,84,81,78,
76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35 ,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7, 6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5, 5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,
33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73 ,76,78,81,84,87,90,93,96,99,102,105,108,111,115,11 8,121,124

};
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

int testPin = 7;
int enablePin = 6 ;

volatile float freq=1;
const float refclk=122.549 ; // 16 MHz/510/256

// variables used inside interrupt service declared as voilatile
volatile unsigned long sigma; // phase accumulator
volatile unsigned long delta; // phase increment
byte phase0, phase1, phase2 ;

void setup()
{
Serial.begin(9600); // connect to the serial port
Serial.println("DDS Test");

pinMode(enablePin, OUTPUT); // sets the digital pin as output
pinMode(testPin, OUTPUT); // sets the digital pin as output
pinMode(12, OUTPUT); // pin9= PWM output / frequency output
pinMode(10, OUTPUT); // pin10= PWM output / frequency output
pinMode(11, OUTPUT); // pin11= PWM output / frequency output

Setup_timer2();
Setup_timer1();
digitalWrite(enablePin, HIGH);

// the waveform index is the highest 8 bits of sigma
// choose refclk as freq to increment the lsb of the 8 highest bits
// for every call to the ISR of timer2 overflow
// the lsb of the 8 highest bits is 1<<24 (1LL<<24 for long integer literal)
delta = (1LL<<24)*freq/refclk ;
}
void loop(){

changeFreq(3);


}

void changeFreq(float _freq){
cbi (TIMSK2,TOIE2); // disable timer2 overflow detect
freq = _freq;
delta=(1LL<<24)*freq/refclk; // update phase increment
sbi (TIMSK2,TOIE2); // enable timer2 overflow detect
}

//************************************************** ****************
// timer2 setup
// set prscaler to 1, fast PWM
void Setup_timer2() {

// Timer2 Clock Prescaler to : 1
sbi (TCCR2B, CS20); // set
cbi (TCCR2B, CS21); // clear
cbi (TCCR2B, CS22);

// Timer2 PWM Mode
cbi (TCCR2A, COM2A0); // clear OC2A on Compare Match, PWM pin 11
sbi (TCCR2A, COM2A1);

// set to fast PWM
sbi (TCCR2A, WGM20); // Mode 1, phase correct PWM
cbi (TCCR2A, WGM21);
cbi (TCCR2B, WGM22);

sbi (TIMSK2,TOIE2); // enable overflow detect

}
// timer1 setup (sets pins 9 and 10)
// set prscaler to 1, PWM mode to phase correct PWM, 16000000/510 = 31372.55 Hz clock
void Setup_timer1() {

// Timer1 Clock Prescaler to : 1
sbi (TCCR1B, CS10);
cbi (TCCR1B, CS11);
cbi (TCCR1B, CS12);

// Timer1 PWM Mode set to Phase Correct PWM
cbi (TCCR1A, COM1A0); // clear OC1A on Compare Match, PWM pin 9
sbi (TCCR1A, COM1A1);
cbi (TCCR1A, COM1B0); // clear OC1B on Compare Match, PWM pin 10
sbi (TCCR1A, COM1B1);

sbi (TCCR1A, WGM10); // Mode 1 / phase correct PWM
cbi (TCCR1A, WGM11);
cbi (TCCR1B, WGM12);
cbi (TCCR1B, WGM13);
}

//************************************************** ****************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// runtime : 8 microseconds ( inclusive push and pop)
// OC2A - pin 11
// OC1B - pin 10
// OC1A - pin 9
// https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
ISR(TIMER2_OVF_vect) {

sbi(PORTD,testPin);

sigma=sigma+delta; // soft DDS, phase accu with 32 bits
phase0=sigma >> 24; // use upper 8 bits for phase accu as frequency information
// read value fron ROM sine table and send to PWM DAC
phase1 = phase0 +85 ;
phase2 = phase0 +170 ;

OCR2A=pgm_read_byte_near(sine256 + phase0); // pwm pin 11
OCR1B=pgm_read_byte_near(sine256 + phase1); // pwm pin 10
OCR1A=pgm_read_byte_near(sine256 + phase2); // pwm pin 9

cbi(PORTD,testPin);

}
   

ElectricDruid

If you want to learn about how to change the frequency of your waveform, the search term you need is "Numerically controlled oscillator" (NCO)
I wrote a page about it:

https://electricdruid.net/direct-digital-synthesis/

Once you've got a phase accumulator that you can vary the frequency of, you're there. You can generate other waveforms either by manipulating the basic ramp wave that the phase accumulator gives you (for example, doubling it's value then flipping the second half makes a triangle wave) or you can just use the phase as an index into a waveform look-up table. That uses more ROM but you can have arbitrary waveforms - anything you can think up and generate data for!

HTH,
Tom