Trying create pulse generator with Arduino

I'm trying to create a pulse generator with an Atmega 328. I want to be able to modify frequency and pulse width up to 2KHz. I have a standalone pulse generator that works fine for this purpose, but I want to miniaturize it since the pulse generator I have is rather large. I currently have pots connected to the Arduino at A0 and A1. I want the Arduino to power my strobe light through my solid-state relay. The code below almost does the job but it isn't smooth. At certain points the light looses the pattern and seems to jump all over the place. On my oscilloscope, when I compare the Arduino signal and my Systron pulse generator signal the Arduino signal seems to be erratic. I have been told that I may need capacitors to use in conjunction with the Arduino. I would be open to using buttons instead of pots to adjust the freq/PW
Any comments about the code? Thank You...

// PINS
int pinTotal = A0; // pin for total cycle width. Analog
int pinPulse = A1; // pin for pulse width. Analog If pulse > total - ON all the time
int pinLED = 13; // pin for LED. Output

// data
int nTotalWidth = 0; // current total cycle width value 0 - 1023
int nPulseWidth = 0; // current pulse width value 0 - 1023

// 1. we should not change values if deviation is too small.
int nMinTriggerDelta = 10; // roughly 1% change is enough to trigger

void setup()
{
pinMode (pinLED, OUTPUT); // declare the ledPin as an OUTPUT
}

void loop()
{
// let's put another loop inside not to exit/enter extra functions
while (true)
{
int nNewTotal = analogRead (pinTotal);
int nNewPulse = analogRead (pinPulse);

// this block should have roughly the same execution time on every iteration
int delta = abs(nNewTotal-nTotalWidth) + abs(nNewPulse-nPulseWidth);
if (delta > nMinTriggerDelta)
{
nTotalWidth = nNewTotal;
nPulseWidth = nNewPulse;
}
else
{
nTotalWidth = nTotalWidth;
nPulseWidth = nPulseWidth;
}

// this block has different exec time, but the same time in on/off scenario
if (nPulseWidth >= nTotalWidth)
{
digitalWrite(pinLED, HIGH);
}
else
{
int delayOnMks = (nPulseWidth+1) * 500; // ~ 500mks - 500ms
int delayOffMks = (nTotalWidth-nPulseWidth+1) * 500; // ~ 500mks - 500ms
digitalWrite(pinLED, HIGH); // on
delayMicroseconds(delayOnMks);
digitalWrite(pinLED, LOW); // off
delayMicroseconds(delayOffMks);
}
}
}

Could it be, that the interrupt for millis() is interfering in unfortunate ways at certain settings? As you don't really use millis() in any way, you could try to disable it to check if things get better.

Korman.

At certain points the light looses the pattern and seems to jump all over the place.

Exactly what points, do you know?

I've certainly seen the "millis" interrupt disrupt program flow before. I would not expect the disruption to be significant at 2khz but maybe at faster speeds.


Rob

If those pulses are used for a stroboscope, small deviations can become visible. I agree, more information about the problem would be of interest.

Another thing to check is whether your input values are stable. If you don't touch the potentiometers, they shouldn't change by more than 1.

Korman

If you don't touch the potentiometers, they shouldn't change by more than 1.

Or hard code some input values to isolate the possible variable input from a possible variable output.


Rob

pulse_widTH:
...Arduino signal seems to be erratic....
Any comments about the code? ...

Hmmm...Let's do some math:

1. analogRead() returns an integer value zero through 1023.

2. The parameter of delayMicroseconds() is an unsigned int (value 0 through 65535).

3. You feed delayMicroseconds() an argument equal to (the analogRead() value plus 1) times 500

4. 132 * 500 = 66000, and the result can not be represented as an unsigned int, no matter how hard you try.

Therefore

5. Values from analogRead() greater than 130 will yield a value that can't give you the number of microseconds that your program seems to need.

Bottom line: Try scaling the analogRead() value to a maximim value of 130 so that the maximum value fed to delayMicroseconds() is 131*500.

Then an analogRead() value of 0 will give close to 500 microseconds delay. This will be (approximately) the minimum period, for an approximate maximum frequency of 2 kHz.
An analogRead() value of 1023 (scaled to 130) will give close to 65500 microseconds delay. This will be (approximately) the maximum period, for an approximate minimum frequency of about 15.27 Hz.

Better yet, instead of scaling the analogRead() value and multiplying that (plus 1) by 500, just use the Arduino map() function to convert the analogRead() value to something between 500 and 65535 (or whatever values you need for the range of periods for your pulse generator).

Regards,

Dave

Footnote:

Of course, as has been pointed out, noise on the analogRead() values can cause problems (obviously), and you should try to see whether that contributes to unacceptable behavior. Unless the noise is really gross, a simple software filtering algorithm can smooth out the readings but the time consumed by the filter will diminish, somewhat, the maximum frequency. (It's probably not a bad idea to put something like this in all programs of this type. You could tweak the scale factor to give something closer to your desired maximum frequency.)

However...

If you want a jitter-free pulse generator, you simply can't do it with software unless you disable the Timer 0 interrupt (and I doubt that you really want to do that). I mean, with Timer 0 interrupt enabled and with software toggling the output pin, it may be that you can (with care) get something that averages out to what you want, but, it won't be clean.

Now, that's not the same as "jumping all over the place," so it might be acceptable to you, but I thought I would mention it, since it (probably) wouldn't be acceptable to me if I were going to use this as a lab instrument.

With all interrupts disabled, it may be possible to get a clean output, but that requires a detailed counting of instruction cycles to get a constant-delay-through-the-loop design. And it requires knowledge, with absolute moral certitude, of what will or not be optimized away by the compiler to get it "just right."

On the other hand...

It is possible to use Timer with its output enabled to one of its ATmega OC pins to get a 1Hz to 100 kHz programmable frequency, programmable duty cycle jitter-free output. Takes a little study of the timer section(s) of the ATmega data sheet, but it is possible.

Hi. I am in the process of assembling my first project with Arduino. It is a digital flip-flop mosfet pulse generator with a frequency counter. I am using 4046 PLL IC (a Voltage Controlled Oscillator) as a clocking input for Arduino, triggering a hardware interrupt to generate a precise pulse of 1 to 500 us of duration and frequency in 0 up to 30 kHz range. As of now it is controlled with one analog pot and 3 buttons. The value from the pot is used to set 2 digital potentiometers (MCP41010 and MCP41050) controlling the 4046 IC frequency and range for finetunning and pulse width as well. Output from VCO is going to pin nr 2 (triggering a hardware interrupt) and to pin 5 for hardware measuring of number of pulses from VCO to determine the frequency by checking the counter every second using a software interrupt. Output goes directly to a dual mosfet driver (ILC7667) and then to logic level mosfets (5V gate drive). Hope my code helps. Greetings from Poland... :smiley:

#include <LiquidCrystal.h>
#include <MsTimer2.h>

// Initialize the library with the numbers of the interface pins
LiquidCrystal lcd(0, 1, 3, 4, 6, 7);      // LiquidCrystal lcd(rs, enable, d4, d5, d6, d7);

volatile boolean fetToggle = false;       // mosfet flip flop flag , a varible changed from within the interrupt function
unsigned int count;
unsigned int pulses;
unsigned long duration = 0;               //
unsigned int frequency = 0;               // frequency of operation
unsigned int pulseWidth = 0;               // initial minimal value
unsigned int pulseTime = 0;               // duration of a single pulse in microseconds
unsigned long past = 0;
const int period = 1000;                  // timer interrupt set to 1 second 

int bL;                                   // states of buttons connected to analog inputs
int bM;
int bR;
const int abLPin = 3;                     // analog input pin numbers for 3 buttons
const int abMPin = 4;
const int abRPin = 5;
const int analogPot0 = 0;                 // analog input pin number for pot

int plsValue = 0;
int frqValue = 127;
int rngValue = 127;

const int ftAPin = 9;                     // OUTPUT: mosfet driver pin
const int ftBPin = 10;                    // OUTPUT: mosfet driver pin
const int intPin = 2;                     // interrupt enabled input pin triggered with VCO 4046 OUTPUT 
const int plsPin = 5;                     // pin cannot be changed - using hardware counter on pin 5

const int frqPin = 8;                     // 10k digital pot VCO frequency controlling pin (CS)
const int rngPin = 11;                    // 50k digital pot VCO range controlling pin (CS)
const int clkPin = 12;                    // SPI clock pin (SCK)
const int spiPin = 13;                    // SPI data pin (SI)
const byte cmd_Write = B00010001;         // Binary values for sending WRITE command to single IC pot

///////////////////////// INITIALISATION /////////////////////////
void setup() {

  pinMode(ftAPin, OUTPUT);
  pinMode(ftBPin, OUTPUT);
  digitalWrite(ftAPin, HIGH);             // set pins to HIGH because of inverting mosfet driver
  digitalWrite(ftBPin, HIGH);
  pinMode(frqPin, OUTPUT);
  digitalWrite(frqPin, HIGH);             // puts digital pot on hold
  pinMode(rngPin, OUTPUT);
  digitalWrite(rngPin, HIGH);             // puts digital pot on hold
  spi_out(frqPin, cmd_Write, 127);        // set default values
  spi_out(rngPin, cmd_Write, 127);
  pinMode(clkPin, OUTPUT);
  pinMode(spiPin, OUTPUT);
  pinMode(intPin, INPUT);                 // interrupting input from VCO
  pinMode(plsPin, INPUT);                 // input from VCO
  digitalWrite(plsPin, HIGH);             // hardware counter setup for counting input pulses

  lcd.begin(16, 2);                       // Set the display to 16 columns and 2 rows
  lcd.clear();                            // Clears the LCD display
  lcd.setCursor(0, 0);
  lcd.print("R(   )");
  lcd.setCursor(2, 0);
  lcd.print(rngValue);
  lcd.setCursor(7, 0);
  lcd.print("P:     us");
  lcd.setCursor(10, 0);
  lcd.print(pulseWidth + 1);
  lcd.setCursor(0, 1);
  lcd.print("F(   )");
  lcd.setCursor(2, 1);
  lcd.print(frqValue);
  lcd.setCursor(7, 1);
  lcd.print("       Hz");
  lcd.setCursor(7, 1);
  lcd.print(frequency);
  
  // timer2 used to measure pulses to determine frequency
  MsTimer2::set(period, update);          
  MsTimer2::start();
  
  // reset timer/counter control register & starting the clock counting pulses from pin 5 input
  TCCR1A=0;                              
  getCount();                        
  
  // running analog pot input with high speed clock (set prescale to 16)
  bitClear(ADCSRA,ADPS0);
  bitClear(ADCSRA,ADPS1);
  bitSet(ADCSRA,ADPS2);

  // enables INT0 interrupt on Pin 2 input to execute mosfet turn ON/OFF cycle
  attachInterrupt(0, trigger, CHANGE);    
}

/////////////////////////// MAIN  LOOP ///////////////////////////
void loop() {
  
  // button state reading
  bL = analogRead(abLPin);
  bM = analogRead(abMPin);
  bR = analogRead(abRPin);

  
  // frequency range setting
  if (bL >= 1000) {
    rngValue = analogRead(analogPot0) / 4;
    spi_out(rngPin, cmd_Write, rngValue);
    lcd.setCursor(0, 0);
    lcd.print("R(   )");
    lcd.setCursor(2, 0);
    lcd.print(rngValue);
  }
  
  // pulse width setting
  if (bM >= 1000) {
    plsValue = analogRead(analogPot0);
    if (plsValue >= pulseTime) {
      pulseWidth = (pulseTime / 2) - 1;
    }
    else {
      pulseWidth = plsValue / 2;
    }
    lcd.setCursor(7, 0);
    lcd.print("P:     us");
    lcd.setCursor(10, 0);
    lcd.print(pulseWidth + 1);          // 1 us is the minimal pulse value at 0 setting
  }

  // frequency setting
  if (bR >= 1000) {
    frqValue = analogRead(analogPot0) / 4;
    spi_out(frqPin, cmd_Write, frqValue);
    lcd.setCursor(0, 1);
    lcd.print("F(   )");
    lcd.setCursor(2, 1);
    lcd.print(frqValue);
  }
  
  // Display actual frequency on LCD every second
  if ((millis() - past) >= 1000) {
    pulseTime = (1000000 / frequency);
    lcd.setCursor(7, 1);
    lcd.print("       Hz");
    lcd.setCursor(7, 1);
    lcd.print(frequency);
    past = millis();
  }
  
  delay(100);
  
} ////////////////////// End of the MAIN LOOP //////////////////////


// flip-flop mosfet pulsing, routine executed on interrupt 0 triggered by VCO output
void trigger() {
  if (fetToggle) {
    bitClear(PORTB, ftAPin - 8);                 // quickly pulse channel A directly using hardware registers
    if (pulseWidth != 0) {
      for (int i=1; i <= pulseWidth; i++) {
        bitClear(PORTB, ftAPin - 8); 
        bitClear(PORTB, ftAPin - 8); 
      }
    }
    bitSet(PORTB, ftAPin - 8);
    fetToggle = false;
  }                 
  else {
    bitClear(PORTB, ftBPin - 8);                 // quickly pulse channel B directly using hardware registers
    if (pulseWidth != 0) {
      for (int i=1; i <= pulseWidth; i++) {
        bitClear(PORTB, ftBPin - 8); 
        bitClear(PORTB, ftBPin - 8); 
      }
    }
    bitSet(PORTB, ftBPin - 8);
    fetToggle = true;
  }
}

// executed every second with a timer interrupt for accuracy of frequency measurement
void update() {  
  frequency = getCount();                         // frequency measurement by pulse counting
}

// returns the current count of pulses from pin 5, resets the count, and starts counting again
unsigned long getCount()  {              
  TCCR1B = 0;                                     // Gate Off / Counter Tn stopped
  count = TCNT1;
  TCNT1 = 0;
  bitSet(TCCR1B ,CS12);                           // Counter Clock source is external pin
  bitSet(TCCR1B ,CS11);                           // Clock on rising edge
  bitSet(TCCR1B ,CS10);                           // you can clear this bit for falling edge
  return count;
}

// tranfers analog pot setting values to digital pot via bit banging technique
void spi_out(int CS, byte cmd_byte, byte data_byte) {
  digitalWrite(CS, LOW);                          // Set the passed ChipSelect pin to low to start programming
  spi_transfer(cmd_byte);                         // Send the passed COMMAND BYTE
  delay(2);
  spi_transfer(data_byte);                        // Send the passed DATA BYTE
  delay(2);
  digitalWrite(CS, HIGH);                         // Set the passed ChipSelect pin to high to end programming
}

// bit banging technique
void spi_transfer(byte working) {
  for (int i = 1; i <= 8; i++) {                  // Set up a loop of 8 iterations (8 bits in a byte)
    if (working > 127) {
      digitalWrite (spiPin, HIGH);                // If the MSB is a 1 then set MOSI high
    }
    else {
      digitalWrite (spiPin, LOW);                 // If the MSB is a 0 then set MOSI low
    }
  digitalWrite(clkPin, HIGH);                     // Pulse the CLKdpot high
  working = working <<= 1 ;                       // Bit-shift the working byte
  digitalWrite(clkPin, LOW);                      // Pulse the CLKdpot low
  }
}

Hi
I used your code for other needs.

but my code works with MCP41050 if I change the last line of your code.

...
 digitalWrite(clk, LOW); // Pulse clock
  working = working <<= 1 ;    // Bit-shift 
  digitalWrite(clk, HIGH);   // Pulse clock
...

thanks

555 timer, anyone?