Controlling On/Off Speed, PWM, and Weight.

I'm trying to make a pseudo granular on/off tremolo effect for some audio signal.

At the moment I'm using a NPN transistor as a switch, and an LED to indicate status.

I should mention that I'm very new to Arduino-ing, so this is just using some example code and trying to mess about with things.

I managed to get a pot control the rate of on/off and managed to scale the values to useful range, but I'm stuck on how to implement the other features I want.

I'd like to be able to control the PWM(duty cycle?) of the LED. So if the analogread is 1023 the LED is always on. And at 0, it would flash a tiny tiny blip at the rate set by the.... rate.

Not sure if PWM is the right term in this application.

What kind of changes would I have to make to the code below to implement that? Do I define a new variable, then make the on/off times dependent on it?

Eventually I want to implement a random control, where it doesn't turn on/off regularly, but rather, based on the value read from a pot, it either:
max clockwise : LED always on
middle : on/off roughly 50% of the time
counterclock : LED always off.

But based on random on/off. So a 'weight' type value. The higher the 'weight' the more the LED will be on (though still random), and the lower the weight, the less the LED will be on.

I know these are a lot of questions, and very specific/particular ones. I don't want someone to just give me code that will work, as I enjoy figuring things out. But I'm stuck as to how such things would be done at all in code.

int sensorPin = 2;
int ledPin = 0;
int sensorValue = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
}
 void loop() {
   sensorValue = analogRead(sensorPin);
   sensorValue = map(sensorValue, 0, 1023, 20, 500);
   digitalWrite(ledPin, HIGH);
   delay(sensorValue);
   digitalWrite(ledPin, LOW);
   delay(sensorValue);
 }

I would say you probably intend to change the frequency of the output, however going from completely off, to pulsing at say a 10% ON duty cycle, as you ramp up the frequency and bring those 10% ON pulses closer together, you need to also increase the ON time so as to approach 100% ON. This is a complex transfer function with a few different states to it.

It's all possible, but everything works better with interrupts when you are trying to read something in, and output something that is time domain based. Especially if it is audio, because it's easy to audibly hear the glitches and errors in the code timing.

If you can define the input to output transfer function I can try to help you.

Like this:

Analog input = 0-1%, Digital output = 100% OFF (deadband)
Analog input = 0-20%, Digital output = 100% OFF to 10% ON at 1kHz
Analog input = 20-80%, Digital output = 10% ON from 1 to 10kHz
Analog input = 80-99%, Digital output = 10% ON to 100% ON at 10kHz
Analog input = 99-100%, Digital output = 100% ON (deadband)

Again, code is not my strong point, but wouldn't those two things be independently controllable?

Like the rate at which the periods happen (which would be the speed/rate control I guess)

. . . . . . .

then the 'duty cycle', which determines how long the period is.


If I understand you correctly, are you saying that those two things are one thing? In order to make something slow and short, or fast and short, I need to give values that would ramp up in between those?

The audio portion of this will be a simple 'killswitch' at the start, just shorting the audio to ground. So the audio will literally be on or off. Eventually it would be nice to mess with waveform and depth, but one thing (or 4) at a time.

I guess I was taking your example too literally... I figured you had one potentiometer that you wanted to do everything with, but that was just your starting point.

But you are correct, it would be much easier to have two different controls for the frequency and duty cycle. If you did that, then 100% on or off would be pretty easy to control.

So what does a tremelo have to do exactly?

Ah I see the confusion. I plan on having separate controls for the 3 things mentioned so far (speed, duty cycle, and weight).

Normally a tremolo turns the audio on/off with a selectable sine, triangle, squarewave, sawtooth etc...

That would be nice, but since I'm mainly going for really fast/random on/off, squarewave is good enough for me. Which means I can bypass a lot of circuitry and just ground the signal. As mentioned, I'd eventually like to dabble with waveshape, but I figure all the 'control' features, happening in software, and waveshape happening with actual components, would be the way to go.

So, how would I go about controlling the duty cycle?

Duty cycle alone is dead simple using one of the hardware PWM outputs.

val = analogRead(0); //reads in 0-1023
analogWrite(val/4); //scales 0-1023 to 0-255 for PWM out

The only problem is the PWM is fixed at 400Hz or so with the built in Arduino libraries.

We would most likely want to bit-bang the PWM output... which would require a interrupt routine. To get the higher frequencies, I need to figure out how to take my 1ms interrupt code and make it a shorter delay. I'm guessing more is involved with the prescaler, but I haven't looked yet.

I was looking through the book (getting start with arduino) and was going to try using one of the PWM outputs but then realized that I would have no way of controlling the 'rate', which would leave me, again, in the same boat.

You lost me at bit-bang. I'm guessing it has something to do with redefining the PWM rate.

Wouldn't using a mathematical formula to manipulate the numbers between on and off time be easier? Like, if my rate is 500ms. In my example code it would be 500ms on, 500ms off.
If I split that into 2 variables, both being defined by the same thing (analogRead from the pot), then use the analogRead from the 'duty cycle' pot to change those numbers to say 600ms on, 400ms off, or 750/250 etc.. I guess like an active remapping/scaling of the values?

Would using the built in PWM control, and then manipulating the rate be easier than using the built in rate control, and manipulating the PWM?

bit-banging is basically directly manipulating digital I/O through software. So that's exactly what you are thinking with the mathematical approach.

What frequencies are you thinking of sweeping between?

Rate of speed I take it?

At the moment my mockup is going from about 1sec at the longest setting (500ms on 500ms off) to 20ms(10/10) at the fastest. It could probably go faster than that.

Not bad, fairly slow... I think that will be easy to do. Let me work on it a little bit. I'll try to post something later.

Yeah, definitely slow as far as control rates go.

Man, thanks a ton for all your help in this!

Ok, I've got a the code done for the rate adjustment from 1Hz to 50Hz (1sec to 20ms period at 50% duty cycle).

Next thing to do is hook up another pot to control the duty cycle.

Nice!

I think I have another breadboard pot or 2 laying around. I should really order a handful of breadboard layout pots.

Ok Kriista, here ya go! Happy Valentine's Day ... haha

/*
 *  AudioMod.pde v1.0 - Audio Modulator for Arduino
 *  02-14-2010
 *
 *  Copyright (c) 2010 Brett Walach <emailbrett @ gmail dot com>
 *  All rights reserved.
 *
 *  LICENSE
 *  -------
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  EXPLANATION
 *  -----------
 *  This code requires two potentiometers, and a speaker or LED output. Pot0 is a Rate
 *  pot, and adjusts the period of the output.  Pot1 is a Duty pot, which adj. the positive
 *  duty cycle of the output. The output can be a speaker or LED or fed into some other 
 *  audio circuitry, or strobing circuit.
 *
 *  An interrupt fires every 1ms, and a timer_tick flag is set if the timer has expired.  
 *  In doing it this way, it is very easy to increment to 5 or 10 for a 5ms or 10ms interrupt.
 *  The potentiometers are sampled every 1ms.  The period_ctr is decremented once per 1ms.
 *  period_ctr is updated when it decrements to zero to create as smooth of a fade as
 *  possible.  As the period approaches the low end (high frequency), there are more noticable
 *  graduations to the fading of the duty cycle.  This is due to low period being a mere multiple
 *  of 10 of our interrupt routine, so the duty can only slice the period up into 10 pieces.
 * 
 *  If the duty cycle is adjusted to 0% or 100%, the period has no effect on the output.
 *  If there is too much noise on the A/D ground return from the potentiometers, specifically
 *  the duty cycle pot, the input will not reach 0 and it will be impossible to completely 
 *  reach 0% on time for the output.  To solve this, don't drive large speakers and if you do, 
 *  don't have them on the same breadboard that your potentiometers are on.
 *
 */

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

//---------- Constants ----------
#define rateinputPin 0           // Pot0 pin
#define dutyinputPin 1           // Pot1 pin
#define speakPin 7               // Output pin
#define HALF_PERIOD_LOW_MS 10    // Half of the period in ms, low
#define HALF_PERIOD_HIGH_MS 500  // Half of the period in ms, high


//---------- Status variables ----------
boolean speakState = false;     // Speaker output on/off

//---------- Variables -----------
unsigned long rateValue = 0;    // rate value, defines the period of the output
unsigned long dutyValue = 0;    // duty value, defines the duty cycle on time of period
unsigned long period_ctr = 0;   // the actual period counter that gets decremented
unsigned long old_period = 0;   // a snapshot of the new_period, to compare period_ctr to
unsigned long new_period = 0;   // the newest period calculation

//---------- Interrupt Defs / Vars ----------
#define TIMER_DELAY_MS 1  // Interrupt timer delay in milliseconds, timer_tick will be set every TIMER_DELAY_MS ms.
int int_counter = 0;  
volatile boolean timer_tick = false;

// INTERRUPT ROUTINE ----------
// Arduino runs at 16 Mhz, so we have 1000 Overflows per second.  
// 1 / ((16000000 / 64) / 256) = 1 / 1000 (once per millisecond)
ISR(TIMER2_OVF_vect) {  
  int_counter += 1;  
  if (int_counter == TIMER_DELAY_MS) {  
    timer_tick = true;
    int_counter = 0;  
  }   
}; 

void setup()
{
  // SETUP INTERRUPT HANDLING
  TCCR2B |= (1<<CS22);                  // turn on CS22 bit  
  TCCR2B &= ~((1<<CS21) | (1<<CS20));    // turn off CS21 and CS20 bits     
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));   // turn off WGM21 and WGM20 bits   
  TCCR2B &= ~(1<<WGM22);                  // turn off WGM22  
  // Use internal clock - external clock not used in Arduino  
  ASSR |= (0<<AS2);  
  TIMSK2 |= (1<<TOIE2) | (0<<OCIE2A);        //Timer2 Overflow Interrupt Enable    
  sei();      //not needed, but works with it in... don't forget about cli(); to disable
  
  // Uncomment to debug
  //Serial.begin(57600); 
   
  pinMode(speakPin, OUTPUT);  
  pinMode(rateinputPin, INPUT);  
  pinMode(dutyinputPin, INPUT);  
}

void loop()
{
  if (timer_tick) {  // Timer has expired, time to process some stuff once per timer_tick
                     // timer_tick fires every 1ms based on TIMER_DELAY_MS constant.  Must run
                     // this fast enough to oversample and catch the shortest switch input duration
                     
    timer_tick = false;  // Reset timer tick

    // DON'T TAKE UP MORE THAN 1MS BELOW OR YOU WILL SACRIFICE REAL-TIME
    updateTimers();  // Update the timers to know when to take action

    readInputs();    // Read the inputs

    updateOutputs(); // Update our outputs based on our inputs
  }

  // MAIN LOOP
  // -- add code here that you want to run in between the timer ticks (i.e. FAST!)

} // MAIN LOOP END


//----------
// Update Timers
//
// Updates timers based on INTERRUPT routine
//----------
void updateTimers()
{
  //rateValue scaled to 10-500 and stored in new_period
  new_period = map(rateValue,0,1024,HALF_PERIOD_LOW_MS,HALF_PERIOD_HIGH_MS+1); 
    
  if(period_ctr == 0) // Update period counter on 0 to reduce jitter
    period_ctr = old_period = new_period;  // Store period value in old_period too
  
  //Uncomment for debug
  //Serial.print(old_period);
  //Serial.print(" ");
  //Serial.print(dutyValue);
  //Serial.print(" ");
  //Serial.println(old_period*dutyValue/1023);
    
  if(period_ctr > (old_period*dutyValue/1023))  // Turn on output during first half of period
    speakState = HIGH;
  else
    speakState = LOW;
  
  if(--period_ctr < 0) period_ctr = 0;  // Just safety net, should NEVER decrement past zero
}


//----------
// Read Inputs
//
// Reads analog inputs
//----------
void readInputs()
{
  rateValue = analogRead(rateinputPin);
  dutyValue = analogRead(dutyinputPin);
}


//----------
// Update Outputs
//
// Updates outputs based on debounced inputs
//----------
void updateOutputs()
{
  digitalWrite(speakPin,speakState);
}

Let me know how that sounds... seems to work pretty good on my end.

Yay!

Man, that code looks like serious business. I look forward to trying to fish through it and trying to make sense of it.

So many lines that are completely alien to me.

Thanks again.

Ok, given that I hardly understood what you posted, I sat down with some friends today to try to see if I could express what needed to happen mathematically.

I think we figured it out.

If C = rate and D = DutyCycle
with A being ON time, and B being OFF time.

I have A + B = C
with the ratio of A to B being defined by D.

A = C(D/1000)
B = C(1-D/1000)

C(D/1000) + C(1-D/1000) = C

I'm using 1000 as that's what the analog pin spits out.
Doing the math on paper works right. If my rate is set to 1000 and duty cycle is set to 850, then A = 850 and B = 150.

I tried putting it in code and I'm running into some problems. For one, the math isn't working right.

At the moment I'm pre-defining dutyCycle as I don't have a pot handle to read for it.

Here is the code I have so far.

int ratePot = 2;
int audioPin = 0;
int rateValue = 0;
int dutyCycle = 500;
int cycleOn;
int cycleOff;

void setup() {
  pinMode(audioPin, OUTPUT);
  //Serial.begin(9600);
}

 void loop() {
   rateValue = analogRead(ratePot);
   rateValue = map(rateValue, 0, 1023, 40, 1000);
   // dutyCycle = map(dutyCycle, 0, 1023, 1, 1000);
   cycleOn = rateValue * (dutyCycle / 1000);
   cycleOff = rateValue * (1 - (dutyCycle / 1000));
   //Serial.println(cycleOn);
   //Serial.println(cycleOff);
   digitalWrite(audioPin, HIGH);
   delay(cycleOn);
   digitalWrite(audioPin, LOW);
   delay(cycleOff);
 }

There's a pretty glaring problem that the delay is built into the loop, so the longer/slower the rate, the slower the read value. I'm assuming this can be circumvented by having the pins and math separated from the delay. But as it is, my cycleOn is spitting out 0 and I can't figure out why.

Now if this code can be fixed so the math works, and the pin reading and math functions can be separated from the on/off loop. Is the longer code you posted still preferable?

As mentioned in the original post, I want to incorporate all sorts of other bits, and using the code you posted as a starting point, I'm already lost, so being able to expand on it is ever further away. I can sort of understand math, and the little bit of code I have going so far, so it would make an ideal place to build from.

Kriista, I'm glad you are getting into this and trying to understand it. Maybe later I can go through my code better and explain exactly how it works step by step. Last night I tried to incorporate that into the code header explanation, but was too tired to do it properly. Right now I'm installing an over-the-range microwave, yay me!

As for the math computing zero... I had the same problem last night. It's because we are dealing with a microcontroller, that is performing binary math on various data types. It does this math in a specific order, and the result of each operation is used as an argument for the next operation.

Look at your data types and make sure that when the operation is executed, the result can fit into one of the variables used. I'm not sure, but I think a result would most likely be temporarily stored in a similar data type.

First we have to see if the variable types are correct to hold the results of our math operations.

For instance, 1023 (max rateValue) * 1023 (max dutyCycle if you had a pot) = 1046529, which is way larger than int which is only -32,768 to +32,767. Once you exceed 32,767, it will wrap into the negative portion of the variable (read up on signed int to understand why... it's a binary thing). So we will need to use larger variables!

Unsigned int actually only gives us 65,535... not big enough. Long is double the RAM allocation of int, but that makes it MUCH more capable of holding large numbers. It's -2,147,483,648 to 2,147,483,647!

However, we want to use unsigned variables instead of signed, otherwise you will get negative values when you don't want them (here in your code you never want to be negative). unsigned will give you a little more overhead as well for bigger multiplies.

So use unsigned long :slight_smile: 0 to 4,294,967,295!

Now that the variables can hold the data... let's look at what's happening.

When you do cycleOn = rateValue * (dutyCycle / 1000);, it's actually dividing dutyCycle by 1000 first, as it should because of the parenthesis. However, that result is going to be at max 1.0, and most likely 0.something. It has to temporarily store that in cycleOn's memory space which even if it was unsigned long is only capable of storing WHOLE numbers, or integers. So it stores 1 or 0 and truncates the rest. Then it multiplies by rateValue and it's going to be ZERO or rateValue that's stored in cycleOn.

So what we need to do it divide LAST, and if we can we need to temporarily store that divided number in a float variable.

We can just divide last though, like so:

cycleOn = rateValue * dutyCycle / 1000;

It will operate from left to right, multiply first... make a large number, and then divide a large number by a relatively smaller number. The result will have the positive whole number we expected. There is some decimal place part usually, but it just gets truncated away.

This one is a little harder: cycleOff = rateValue * (1 - (dutyCycle / 1000));

But just unsimplify it...

cycleOff = rateValue - rateValue * dutyCycle / 1000;

which is really just:

cycleOff = rateValue - cycleOn; (use this instead)

Try that and see how it works :slight_smile:

Thanks for the thorough explanation there. I do plan on looking at the code and trying to make it out, but there's tons of stuff in there.

After wrestling with what you suggested for about 30 minutes I figured it out. I defined stuff as unsigned long, but was still having problems. It was giving me 4billion as a value for cycleOn, and other stuff wasn't working right. Primarily I found another breadboard pot and hooked it up to take live values for dutyCycle, but that wasn't working right either.

It turns out it was just the 4billion number freaking things out. I hadn't redefined rateValue and dutyValue as unsigned long too.

So at the moment, it's working right.

There is the problem that it reads the pins at the rate that it's turning on/off, which makes changing speed quickly impossible (from max slow to max fast, there is no ramping, it just jumps up).
I tried just nesting another "void loop" in there, but it wasn't into it.

Is the approach I've gone here (simple math) limited by this, or is there a workaround to this?
I'm assuming there will often be times where you are reading pins, and making other things go on/off at the same time, so there's got to be something I'm missing there.

Also, I noticed that when I did the serial.println thing, that the code didn't function. Is that normal? It would spit out the values to the serial monitor, but the LED wouldn't turn on/off.

Ooops, guess I should have mentioned "make them ALL unsigned long". You got it though.

The delayed reading of the potentiometer can be solved by using interrupts, like I did in my code. That's the reason my code looks in depth. It's actually really minimalistic, to me. The interrupt timer is always running in the background, and when it overflows the set counter, the code execution jumps to the interrupt routine. There it sets the timer_tick flag to true and exits. When code execution returns to the main loop, timer_tick == true, so all of the routines run. This happens once per 1ms, and the code execution needs to happen in less than 1ms so we can be waiting for the interrupt to fire again. That insures that the code runs, "on the dot" every time the interrupt fires.

Then in the readInputs() routine, it reads our two analog inputs.

Then the updateTimers() routine runs and all of the math is done here. The period_ctr keeps track of where in the period we are. This gets decremented every 1ms, so the period is composed of little 1ms divisions, that are counted. If the period_ctr indicates we are in the ON part of the waveform, we set the speakState to HIGH, else we set it LOW.

Lastly, in the updateOutputs() routine, the state of speakState is used to set the speaker/LED output.

Your output won't work because you have it set to pin #0. Pin zero is used for the RX line and should not be used again as a digital I/O. Switch your pin definition to pin 2 and move your LED over to there. That should fix it.

STILL haven't got this microwave installed... what a pain in the arse this is! GOtta get back to it and put the kitchen back together.

BTW, if you haven't tried my code out yet... give it a whirl and see how it sounds and how it updates quick. Then you will know where you want it to be when you are done :wink: