how to convert from pot to encoder

Hi all,
After some great help some time ago from cattledog, I have been improving my tester and think it would be easier to use if I could adjust for exact RPM to match the spreadsheet I use for programming PICs. I have read several articles on this but it leaves me just with a hurting brain! So anyone want to show me the way would be great!
The variable for RPM is what I want to change to encoder rather than pot.
Thanks, Tom

// update 7/14/2017 added correct math and setting for position of pickup
// revision 10 10/28/2016 adds charging pulse with Timer 2
// 3/2016 added wire to 2 for future pulse width (not needed as first pulse is all that is needed regardless of width)
// lowest RPM = 615 with charge pulse to keep OCR2A <= 255
// CDI Tester Pulse Generator Serial Output Arduino Code

int pot1 = A3; // select the input pin for the pot for rpm
int pot2 = A4; // select the input pin for the pot for pickup location
int potValue = 0; // variable to store the value coming from the sensor
int pickupValue = 0;
int timerTopValue = 12500; // changed from timerTopValue = 0
int outputPin = 4; // select the pin for the output for trigger pulse, changed from 8
int chargePin = 7; // select the pin for the output for charge pulses

volatile boolean trigger = false;
volatile unsigned long delayPeriod;
unsigned long copy_delayPeriod;
volatile unsigned long delayPeriodStart;
float delayDegrees; // changed from int to float for decimal place display
int RPM;
int pickup;                                            
volatile boolean interruptFlag;
unsigned long analogReadInterval = 1000; //read pots and map
unsigned long lastAnalogRead;

const byte setFallingSwitch = 5;
char risefall = 'R'; //default rising mode

const byte setChargePulseSwitch = 6;
boolean chargePulse = false ;  //default dc powered mode

volatile byte timeSliceCount = 0; //TDC 0 degrees

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// set the LCD address to 0x3f for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x3f, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

void setup() {
  //Serial.begin(115200);
  //Serial.println("starting...");
  lcd.begin(20, 4); // initialize the lcd for 20 chars 4 lines, turn on backlight
  lcd.setCursor(1, 0); // lcd display setup of unchanging headings
  lcd.print("RPM:"); // print fixed characters
  lcd.setCursor(11, 0);
  lcd.print("Mode:"); //to display code choice R or F
  lcd.setCursor(1, 1);
  lcd.print("Pickup Position:");
  lcd.setCursor(1, 2);
  lcd.print("Us Delay:");
  lcd.setCursor(1, 3);
  lcd.print("Deg Advance:");

  pinMode(outputPin, OUTPUT); // declare the outputPin as an OUTPUT
  pinMode(chargePin, OUTPUT); // declare the chargePin as an OUTPUT
  pinMode (setFallingSwitch, INPUT_PULLUP);//check for a LOW input to indicate switch is closed to ground
  pinMode(setChargePulseSwitch, INPUT_PULLUP);

  if (digitalRead(setChargePulseSwitch) == LOW)
    chargePulse = true; //AC CDI

  //Timer1 default set up .5ms trigger pulse every 50 ms
  TCCR1A = 0;
  TCCR1B = (1 << WGM12); //CTC mode to OCR1A
  OCR1A = timerTopValue;
  TIMSK1 |= (1 << OCIE1A); //interrupt enable compareA
  TIMSK1 |= (1 << OCIE1B); //interrupt enable compareB
  OCR1B = 125; //sets trigger pulse width, 500 = 2Ms, 250 = 1Ms, 125 = .5Ms, set to 1250 for 5013
  TCCR1B |= (1 << CS11) | (1 << CS10); //prescaler 64 4us/tick

  //Timer2 default setup charge pulse 12 periods 30 degrees each; only 5 get turned on
  //charge pulse timing interval = Timer1 trigger period/12 = timerTopValue/96
  TCCR2A = 0;
  TCCR2B = 0;
  TCCR2A = 1 << WGM20; //lsb of mode 7 pwm to OCR2A
  TCCR2B = 1 << WGM22; //msb of mode 7 pwm to OCR2A;
  OCR2A = timerTopValue / 96; //96 = 12*4*2 (#periods, prescaler difference, up/down timer mode )
  TIMSK2 = 0;
  TIMSK2 = 1 << TOIE2; //enable overflow interrupt
  // actually start timer in ISR(TIMER1_COMPA_vect
  // to prevent out of sync charge pulse, no prescaler set here

  attachInterrupt(digitalPinToInterrupt(3), delayPeriodTiming, FALLING);

}

void loop()
{
  if (millis() - lastAnalogRead >= analogReadInterval)
  {
    lastAnalogRead += analogReadInterval;
    potValue = analogRead(pot1);
    pickupValue = analogRead(pot2);
   
    //Timer2 OCR2A requires max value 255 => 615 lowest RPM
    if (chargePulse)
    {
      RPM = map(potValue, 0, 1023, 615, 8000); // want this value for RPM to be set with encoder
      pickup = 60; //map(pickupValue, 0, 1023, 0, 65); //to set position of pickup so advance will read based on delay
      //RPM = 615;//for my serial test purposes 615-3800
    }
    else
    {
      RPM = map(potValue, 0, 1023, 500, 3800);
      pickup = 25; //map(pickupValue, 0, 1023, 0, 50); //to set position of pickup so advance will read
     // RPM = 500;//for my serial test purposes 500-3800
    }

    timerTopValue = 15000000UL / RPM;

    if (digitalRead(setFallingSwitch) == LOW) //set Falling
    {
      risefall = 'F';
    }
    else
    {
      risefall = 'R';
    }

    lcd.setCursor(6, 0);
    lcd.print("    "); // print blank spaces to clear old data
    lcd.setCursor(6, 0);
    lcd.print(RPM);

    lcd.setCursor(17, 0);
    lcd.print("  ");
    lcd.setCursor(17, 0);
    lcd.print(risefall); //print R or F at upper right side

    lcd.setCursor(18, 1);
    lcd.print("   ");
    lcd.setCursor(18, 1);
    lcd.print(pickup);

    lcd.setCursor(11, 2);
    lcd.print("       ");
    lcd.setCursor(11, 2);
    lcd.print(copy_delayPeriod);
   
    lcd.setCursor(14, 3);
    lcd.print("      ");
    lcd.setCursor(14, 3);
    lcd.print(delayDegrees, 1);   //delayDegrees, 1);
  }
 
  if (trigger == true && interruptFlag == true )
  {
    trigger = false;
    interruptFlag = false;
    noInterrupts();
    copy_delayPeriod = delayPeriod;
    interrupts();

    delayDegrees = pickup - (360.0 * (copy_delayPeriod) / (timerTopValue * 4.0)); //for decimal place in deg display.
    }
}

ISR(TIMER1_COMPA_vect) {
  OCR1A = (timerTopValue); // value to set delay between pulses to trigger cdi
  digitalWrite(chargePin, LOW); //guarantee off charge pin at trigger
  digitalWrite(outputPin, HIGH); //turn on pin  trigger

  if (risefall == 'R') //switch not set; default Rising trigger
  {
    delayPeriodStart = micros(); //start looking for response as pulse rises
    trigger = true;
  }
  //start Timer 2 for charge pulses
  if (chargePulse)
  {
    timeSliceCount = 0;
    TCNT2 = 0;
    OCR2A = timerTopValue/96; //set 12 periods
    TCCR2B |=  1 << CS22 | 1 << CS21; //prescaleer 256 16us/tick
  }
}

ISR(TIMER1_COMPB_vect) {
  digitalWrite(outputPin, LOW);

  if (risefall == 'F') //switch set for Falling trigger
  {
    delayPeriodStart = micros(); //start looking for response as pulse falls
    trigger = true;
  }
}

void delayPeriodTiming()
{
  delayPeriod = micros() - delayPeriodStart;
  interruptFlag = true;
}

ISR(TIMER2_OVF_vect)
//5 pulses of 30 degrees starting at 60 degrees
//ON at 60,120,180,240,300 = 2,4,6,8,10
//OFF at 90,150,210,270,330 = 3,5,7,9,11
{
  if (timeSliceCount != 0 && timeSliceCount % 2 == 0)
  {
    digitalWrite (chargePin, HIGH);
  }
  else //if (timeSliceCount == 0 || timeSliceCount % 2 == 1)
  {
    digitalWrite(chargePin, LOW);
  }
  timeSliceCount++;

  if (timeSliceCount == 12)
  {
    timeSliceCount = 0;
    //stop Timer2 by clearing prescaler bits
    TCCR2B &= ~1<< CS22;
    TCCR2B &= ~1<< CS21;
  }
}

First, if you'd rather avoid the nitty-gritty of an encoder there are libraries out there for that. Second, I would offer that 615 to 8000 is a long way spinning a rotary encoder. I'd include a switched feature that adds/subtracts 50 or 100, or whatever, at a time (think of it as a coarse adjustment) in addition to the standard one pulse per detent. Also consider that the physical pot 'remembers' where it is when power goes away. The encoder version won't so you might want to look into saving that value, among others, in EEPROM. Oh, you'll also have to build in the limit values somehow to keep the pot from going out of RPM range.

OK good points but I see that some of the libs have provision for the encoder to increment faster if turned faster so that may solve the wide range issue. As for remembering where it was I don't see that as a problem if there was a way to limit it to never going below 615. The push switch on the encoder could be used as a 'reset' to some pre defined RPM perhaps? What I don't know or understand is how to have the result of the encoder represent as a variable such as 'RPM'

tombauer:
What I don't know or understand is how to have the result of the encoder represent as a variable such as 'RPM'

The encoder will have two outputs derived from turning it - left & right. At its heart it's nothing more than:

if (encoderLeftPulse) RPMval++;

else if (encoderRightPulse) RPMval--;

You can think of it as pressing an UP button multiple times to increment, and a separate DN button to decrement.

OK, thanks i understand that part of it now. What I am hesitant to do is try to integrate this into the code I have now having little experience with coding! I usually just learn enough to get by and then promptly forget all I learned. What I need now is to find an example close to what I want to do.

I agree with dougp that the range of 615 to 8000 is not really suited to an encoder, and the issues of coarse and fine adjustment responding to turn rate is not trivial. Do you have an encoder that you want to use?

Have you thought about whether a membrane keypad or other keypad device might be a more suitable way to enter an exact number.

A ten turn pot and a 12 bit ADC might get you to an adjustment of +- 2rpm. With the potentiometer you are currently using with analogRead() are you adjusting the rpm on the fly and sweeping across values to simulate acceleration or decceleration.

You say that there is a spreadsheet of rpm values you would like to enter. How many values are there? Can you attach this table to a post. It might be possible to select an index for an array table rather than to increment and decrement a large number.

Before heading down the road of the encoder give some thought to alternatives.

OK, I may put this off for a while and think about it. I only use this approach when I need to extract the curve info from an unknown CDI which is not that often.

A final thought - They're not as common as they used to be but, a BCD thumbwheel may work for you. It'd take more on the hardware side to interface (sixteen bits) but it's doable.

It'd take more on the hardware side to interface (sixteen bits) but it's doable.

I suppose that you could use one bcd rotary selector and use another four position switch(maybe with a resistor chain so it could be read with an analog input) to cycle through the four digits. It will still take a minimum of 5 pins. I think it would be more intuitive to have one knob for each digit, but that is 16 pins.

cattledog:
I think it would be more intuitive to have one knob for each digit, but that is 16 pins.

https://www.sparkfun.com/datasheets/Components/Buttons/B-21-24.pdf

You could cut that down to two pins by using a port expander and external pullups for the switches.

You could cut that down to two pins by using a port expander and external pullups for the switches.

Good idea. One benefit of that chip is according to the data sheet

There's even the ability to get an interrupt via an external pin when any of the inputs change so you don't have to keep polling the chip.

If I recall Tom's system correctly the constant analogReads() create a jitter problem for some of his timing.

I missed a little detail in the port expander description, namely:

"You can set each of 16 pins to be input, output, or input with a pullup".

So there'd a bit less hardware to deal with.

cattledog, this is what I ended up with and it has been very accurate. changed some details and added some stuff, but it does exactly what it should. thanks for your help. I think I am going to wait and do more research before I move with any more changes.

it has been very accurate. changed some details and added some stuff, but it does exactly what it should

It's great that the CDI tester is working well. It's nice to get feed back after a project has been in service for awhile.

For anyone who is interested in the painful history of this project, and the complicated hardware issues that Tom had to solve as well as digesting some unfamiliar software, here's a link to the original thread.

CDI tester project

I am going to wait and do more research before I move with any more changes.

Given the difficulties in the past, I can fully understand why you want to be conservative with any changes. It's also probably the high season for broken lawn mowers right now. 8)

cattledog,

Hi, I have a request since you were familiar with this code. One thing that is not quite right is that the time duration of the trigger pulse does not change with the rpm. In real world as rpm goes up the time length of that pulse would shorten. In the code it is set as 'OCR1B = 125;' and is fixed.

Some older CDI with a time delay IC use the time between the + and - of that short sine wave signal to control the delay of the spark. So the tester does not properly accommodate them. How can this be addressed?

Thanks, Tom

Hi Tom--

In real world as rpm goes up the time length of that pulse would shorten.

Changing the length of the pulse (which is determined by the value of OCR1B) is pretty straightforward. There is a place in the code where you set the OCR1A timerTopValue(15000000/rpm) from rpm, and that would be the place to change the OCR1B value.

How do you want to change the pulse length with rpm? You will have to define the function or mapping which changes OCR1B with rpm.

Some older CDI with a time delay IC use the time between the + and - of that short sine wave signal to control the delay of the spark. So the tester does not properly accommodate them. How can this be addressed?

I'm not sure I understand the second issue. In the current code, the delay is the measured response from the cdi unit, and it can be timed from either the lead or trailing edge of the trigger pulse. Whether the response from the unit matches the specifications is something you verify. How do you currently do that? I would think that these variable pulse width units would just have their own method of verification.

Can all units tolerate a varying pulse width? If not, you could define a mode for these units(like you do for the charging or not charging units) which changes the pulse width with rpm only for them.

Hi,
Explanation of this is on the flywheel there is a bar or wide pin that can be from just a few degrees wide up to 30 degrees wide. The leading edge and trailing edge, as they go past a pickup which is narrow maybe 1/16" metal with coil, make a + or a - pulse. CDI's use this signal in different ways. So I need to put in place a pot and a display reading in degrees and have the time unit of the pulse vary accordingly. The pot and the display and making it a variable I think I can do and have started with the code, fairly simple. So say I have a variable called 'Width' how to use it with the timer? Sorry, timer part has always been struggle for me!!

The measurement part of the code is great and now after I finally got good noise free signal for feedback it is very accurate so all it needs is to have the pulse out mimic real thing.

Tom

// update 7/14/2017 added correct math and setting for position of pickup
// revision 10 10/28/2016 adds charging pulse with Timer 2
// 3/2016 added wire to 2 for future pulse width (not needed as first pulse is all that is needed regardless of width)
// lowest RPM = 615 with charge pulse to keep OCR2A <= 255
// CDI Tester Pulse Generator Serial Output Arduino Code

int pot1 = A3; // select the input pin for the pot for rpm
int pot2 = A4; // select the input pin for the pot for pickup location
int potValue = 0; // variable to store the value coming from the sensor
int potWidth = 0; // width of flywheel bar
int pickupValue = 0; // position of pickup in degreed
int timerTopValue = 12500; // changed from timerTopValue = 0
int outputPin = 4; // select the pin for the output for trigger pulse, changed from 8
int chargePin = 7; // select the pin for the output for charge pulses

volatile boolean trigger = false;
volatile unsigned long delayPeriod;
unsigned long copy_delayPeriod;
volatile unsigned long delayPeriodStart;
float delayDegrees; // changed from int to float for decimal place display
int RPM;
int pickup;
int Width;                                            
volatile boolean interruptFlag;
unsigned long analogReadInterval = 1000; //read pots and map
unsigned long lastAnalogRead;

const byte setFallingSwitch = 5;
char risefall = 'R'; //default rising mode

const byte setChargePulseSwitch = 6;
boolean chargePulse = false ;  //default dc powered mode

volatile byte timeSliceCount = 0; //TDC 0 degrees

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// set the LCD address to 0x3f for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x3f, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

void setup() {
  //Serial.begin(115200);
  //Serial.println("starting...");
  lcd.begin(20, 4); // initialize the lcd for 20 chars 4 lines, turn on backlight
  lcd.setCursor(1, 0); // lcd display setup of unchanging headings
  lcd.print("RPM:"); // print fixed characters
  lcd.setCursor(12, 0);
  lcd.print("Mode:"); //to display code choice R or F
  lcd.setCursor(1, 1);
  lcd.print("Pickup Position:");
  lcd.setCursor(1, 2);
  lcd.print("Us Delay:");
  lcd.setCursor(1, 3);
  lcd.print("Deg Advance:");

  pinMode(outputPin, OUTPUT); // declare the outputPin as an OUTPUT
  pinMode(chargePin, OUTPUT); // declare the chargePin as an OUTPUT
  pinMode (setFallingSwitch, INPUT_PULLUP);//check for a LOW input to indicate switch is closed to ground
  pinMode(setChargePulseSwitch, INPUT_PULLUP);

  if (digitalRead(setChargePulseSwitch) == LOW)
    chargePulse = true; //AC CDI

  //Timer1 default set up .5ms trigger pulse every 50 ms
  TCCR1A = 0;
  TCCR1B = (1 << WGM12); //CTC mode to OCR1A
  OCR1A = timerTopValue;
  TIMSK1 |= (1 << OCIE1A); //interrupt enable compareA
  TIMSK1 |= (1 << OCIE1B); //interrupt enable compareB
  OCR1B = 1250; //sets trigger pulse width, 500 = 2Ms, 250 = 1Ms, 125 = .5Ms, set to 1250 for 5013
  TCCR1B |= (1 << CS11) | (1 << CS10); //prescaler 64 4us/tick

  //Timer2 default setup charge pulse 12 periods 30 degrees each; only 5 get turned on
  //charge pulse timing interval = Timer1 trigger period/12 = timerTopValue/96
  TCCR2A = 0;
  TCCR2B = 0;
  TCCR2A = 1 << WGM20; //lsb of mode 7 pwm to OCR2A
  TCCR2B = 1 << WGM22; //msb of mode 7 pwm to OCR2A;
  OCR2A = timerTopValue / 96; //96 = 12*4*2 (#periods, prescaler difference, up/down timer mode )
  TIMSK2 = 0;
  TIMSK2 = 1 << TOIE2; //enable overflow interrupt
  // actually start timer in ISR(TIMER1_COMPA_vect
  // to prevent out of sync charge pulse, no prescaler set here

  attachInterrupt(digitalPinToInterrupt(3), delayPeriodTiming, FALLING);

}

void loop()
{
  if (millis() - lastAnalogRead >= analogReadInterval)
  {
    lastAnalogRead += analogReadInterval;
    potValue = analogRead(pot1);
    potWidth = analogRead(pot1);
    pickupValue = analogRead(pot2);
   
    //Timer2 OCR2A requires max value 255 => 615 lowest RPM
    if (chargePulse)
    {
      RPM = map(potValue, 0, 1023, 615, 3800); 
      Width = map(potWidth, 0, 1023, 271, 44);
      pickup = 30; //map(pickupValue, 0, 1023, 0, 65); //to set position of pickup so advance will read based on delay
      //RPM = 615;//for my serial test purposes 615-3800
    }
    else
    {
      RPM = map(potValue, 0, 1023, 500, 3800);
      pickup = 25; //map(pickupValue, 0, 1023, 0, 50); //to set position of pickup so advance will read
     // RPM = 500;//for my serial test purposes 500-3800
    }

    timerTopValue = 15000000UL / RPM;

    if (digitalRead(setFallingSwitch) == LOW) //set Falling
    {
      risefall = 'F';
    }
    else
    {
      risefall = 'R';
    }

    lcd.setCursor(6, 0);
    lcd.print("     "); // print blank spaces to clear old data
    lcd.setCursor(6, 0);
    lcd.print(RPM);

    lcd.setCursor(18, 0);
    lcd.print("  ");
    lcd.setCursor(18, 0);
    lcd.print(risefall); //print R or F at upper right side

    lcd.setCursor(18, 1);
    lcd.print("   ");
    lcd.setCursor(18, 1);
    lcd.print(pickup);

    lcd.setCursor(11, 2);
    lcd.print("       ");
    lcd.setCursor(11, 2);
    lcd.print(copy_delayPeriod);
   
    lcd.setCursor(14, 3);
    lcd.print("      ");
    lcd.setCursor(14, 3);
    lcd.print(delayDegrees, 1);   //delayDegrees, 1);
  }
 
  if (trigger == true && interruptFlag == true )
  {
    trigger = false;
    interruptFlag = false;
    noInterrupts();
    copy_delayPeriod = delayPeriod;
    interrupts();

    delayDegrees = pickup - (360.0 * (copy_delayPeriod) / (timerTopValue * 4.0)); //for decimal place in deg display.
    }
}

ISR(TIMER1_COMPA_vect) {
  OCR1A = (timerTopValue); // value to set delay between pulses to trigger cdi
  digitalWrite(chargePin, LOW); //guarantee off charge pin at trigger
  digitalWrite(outputPin, HIGH); //turn on pin  trigger

  if (risefall == 'R') //switch not set; default Rising trigger
  {
    delayPeriodStart = micros(); //start looking for response as pulse rises
    trigger = true;
  }
  //start Timer 2 for charge pulses
  if (chargePulse)
  {
    timeSliceCount = 0;
    TCNT2 = 0;
    OCR2A = timerTopValue/96; //set 12 periods
    TCCR2B |=  1 << CS22 | 1 << CS21; //prescaleer 256 16us/tick
  }
}

ISR(TIMER1_COMPB_vect) {
  digitalWrite(outputPin, LOW);

  if (risefall == 'F') //switch set for Falling trigger
  {
    delayPeriodStart = micros(); //start looking for response as pulse falls
    trigger = true;
  }
}

void delayPeriodTiming()
{
  delayPeriod = micros() - delayPeriodStart;
  interruptFlag = true;
}

ISR(TIMER2_OVF_vect)
//5 pulses of 30 degrees starting at 60 degrees
//ON at 60,120,180,240,300 = 2,4,6,8,10
//OFF at 90,150,210,270,330 = 3,5,7,9,11
{
  if (timeSliceCount != 0 && timeSliceCount % 2 == 0)
  {
    digitalWrite (chargePin, HIGH);
  }
  else //if (timeSliceCount == 0 || timeSliceCount % 2 == 1)
  {
    digitalWrite(chargePin, LOW);
  }
  timeSliceCount++;

  if (timeSliceCount == 12)
  {
    timeSliceCount = 0;
    //stop Timer2 by clearing prescaler bits
    TCCR2B &= ~1<< CS22;
    TCCR2B &= ~1<< CS21;
  }
}

The pot and the display and making it a variable I think I can do and have started with the code, fairly simple. So say I have a variable called 'Width' how to use it with the timer?

You need to add a new pot for the Width

int pot1 = A3; // select the input pin for the pot for rpm
int pot2 = A4; // select the input pin for the pot for pickup location
int pot3 = ??; // need to add new pot

Read it

potValue = analogRead(pot1);
//potWidth = analogRead(pot1);
potWidth = analogRead(pot3);
Width = map(potWidth, 0, 1023, 271, 44);

Do you you need to apply this to both cases with and without the charge pulses. If so, you need to add it to the else case.

OCR1B = 1250; //sets trigger pulse width, 500 = 2Ms, 250 = 1Ms, 125 = .5Ms, set to 1250 for 5013

Do you really want the starting default value of 1250 = 5 ms when the OCR1B range you are going to use is 271 - 44?

Here's my revisions of what you have

[code]if (millis() - lastAnalogRead >= analogReadInterval)
  {
    lastAnalogRead += analogReadInterval;
    potValue = analogRead(pot1);
    potWidth = analogRead(pot3);//new pot number
    pickupValue = analogRead(pot2);
   
    //Timer2 OCR2A requires max value 255 => 615 lowest RPM
    if (chargePulse)
    {
      RPM = map(potValue, 0, 1023, 615, 3800); 
      Width = map(potWidth, 0, 1023, 271, 44);
      pickup = 30; //map(pickupValue, 0, 1023, 0, 65); //to set position of pickup so advance will read based on delay
      //RPM = 615;//for my serial test purposes 615-3800
    }
    else
    {
      RPM = map(potValue, 0, 1023, 500, 3800);
      Width = map(potWidth, 0, 1023, 271, 44);//add Width to non charging case
      pickup = 25; //map(pickupValue, 0, 1023, 0, 50); //to set position of pickup so advance will read
     // RPM = 500;//for my serial test purposes 500-3800
    }

    timerTopValue = 15000000UL / RPM;
    OCR1B = Width;

cattledog, thanks, no, I had been changing the OCR1B = 125; to adjust the width, not sure what it should be set to now. Here is the current code, but it does not change the width! Maybe I made a mistake? I read it over carefully and used cut and paste. I also added the display code. I have the pickup and Width commented out with hard numbers replacing them for now until I change the hardware side.

// update 7/14/2017 added correct math and setting for position of pickup
// revision 10 10/28/2016 adds charging pulse with Timer 2
// lowest RPM = 615 with charge pulse to keep OCR2A <= 255
// CDI Tester Pulse Generator Serial Output Arduino Code
// 8/17 added code for pulse out width

int pot1 = A3; // select the input pin for the pot for rpm
int pot2 = A4; // select the input pin for the pot for pickup location in degrees
int pot3 = A5; // select the input pin for the pot for width of bar on flywheel in degrees
int potValue = 0; // variable to store the value coming from the sensor
int potWidth = 0; // width of flywheel bar in degrees
int pickupValue = 0; // position of pickup in degreed
int timerTopValue = 12500; // changed from timerTopValue = 0
int outputPin = 4; // select the pin for the output for trigger pulse, changed from 8
int chargePin = 7; // select the pin for the output for charge pulses

volatile boolean trigger = false;
volatile unsigned long delayPeriod;
unsigned long copy_delayPeriod;
volatile unsigned long delayPeriodStart;
float delayDegrees; // changed from int to float for decimal place display
int RPM;
int pickup;
int Width;                                            
volatile boolean interruptFlag;
unsigned long analogReadInterval = 1000; //read pots and map
unsigned long lastAnalogRead;

const byte setFallingSwitch = 5;
char risefall = 'R'; //default rising mode

const byte setChargePulseSwitch = 6;
boolean chargePulse = false ;  //default dc powered mode

volatile byte timeSliceCount = 0; //TDC 0 degrees

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// set the LCD address to 0x3f for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x3f, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

void setup() {
  //Serial.begin(115200);
  //Serial.println("starting...");
  lcd.begin(20, 4); // initialize the lcd for 20 chars 4 lines, turn on backlight
  lcd.setCursor(1, 0); // lcd display setup of unchanging headings
  lcd.print("RPM:"); // print fixed characters
  lcd.setCursor(12, 0);
  lcd.print("Mode:"); //to display code choice R or F
  lcd.setCursor(1, 1);
  lcd.print("Pos:");
  lcd.setCursor(9, 1);
  lcd.print("Width:");
  lcd.setCursor(1, 2);
  lcd.print("Us Delay:");
  lcd.setCursor(1, 3);
  lcd.print("Deg Advance:");

  pinMode(outputPin, OUTPUT); // declare the outputPin as an OUTPUT
  pinMode(chargePin, OUTPUT); // declare the chargePin as an OUTPUT
  pinMode (setFallingSwitch, INPUT_PULLUP);//check for a LOW input to indicate switch is closed to ground
  pinMode(setChargePulseSwitch, INPUT_PULLUP);

  if (digitalRead(setChargePulseSwitch) == LOW)
    chargePulse = true; //AC CDI

  //Timer1 default set up .5ms trigger pulse every 50 ms
  TCCR1A = 0;
  TCCR1B = (1 << WGM12); //CTC mode to OCR1A
  OCR1A = timerTopValue;
  TIMSK1 |= (1 << OCIE1A); //interrupt enable compareA
  TIMSK1 |= (1 << OCIE1B); //interrupt enable compareB
  OCR1B = 125; //sets trigger pulse width, 500 = 2Ms, 250 = 1Ms, 125 = .5Ms, set to 1250 for 5013
  TCCR1B |= (1 << CS11) | (1 << CS10); //prescaler 64 4us/tick

  //Timer2 default setup charge pulse 12 periods 30 degrees each; only 5 get turned on
  //charge pulse timing interval = Timer1 trigger period/12 = timerTopValue/96
  TCCR2A = 0;
  TCCR2B = 0;
  TCCR2A = 1 << WGM20; //lsb of mode 7 pwm to OCR2A
  TCCR2B = 1 << WGM22; //msb of mode 7 pwm to OCR2A;
  OCR2A = timerTopValue / 96; //96 = 12*4*2 (#periods, prescaler difference, up/down timer mode )
  TIMSK2 = 0;
  TIMSK2 = 1 << TOIE2; //enable overflow interrupt
  // actually start timer in ISR(TIMER1_COMPA_vect
  // to prevent out of sync charge pulse, no prescaler set here

  attachInterrupt(digitalPinToInterrupt(3), delayPeriodTiming, FALLING);

}

void loop()
{
  if (millis() - lastAnalogRead >= analogReadInterval)
  {
    lastAnalogRead += analogReadInterval;
    potValue = analogRead(pot1);  //rpm
    pickupValue = analogRead(pot2);  // pickup position
    potWidth = analogRead(pot3);  //width of bar on flywheel
  
    //Timer2 OCR2A requires max value 255 => 615 lowest RPM
    if (chargePulse)
    {
      RPM = map(potValue, 0, 1023, 615, 3800);
      pickup = 30; //map(pickupValue, 0, 1023, 0, 65); //to set position of pickup so advance will read based on delay
      Width = 25; //map(potWidth, 0, 1023, 271, 44);
      //RPM = 615;//for my serial test purposes 615-3800
    }
    else
    {
      RPM = map(potValue, 0, 1023, 500, 3800);
      pickup = 25; //map(pickupValue, 0, 1023, 0, 50); //to set position of pickup so advance will read
      Width = map(potWidth, 0, 1023, 271, 44);//add Width to non charging case
     // RPM = 500;//for my serial test purposes 500-3800
    }

    timerTopValue = 15000000UL / RPM;
    OCR1B = Width;

    if (digitalRead(setFallingSwitch) == LOW) //set Falling
    {
      risefall = 'F';
    }
    else
    {
      risefall = 'R';
    }

    lcd.setCursor(6, 0);
    lcd.print("     "); // print blank spaces to clear old data
    lcd.setCursor(6, 0);
    lcd.print(RPM);

    lcd.setCursor(18, 0);
    lcd.print("  ");
    lcd.setCursor(18, 0);
    lcd.print(risefall); //print R or F at upper right side

    lcd.setCursor(6, 1);
    lcd.print("   ");
    lcd.setCursor(6, 1);
    lcd.print(pickup);

    lcd.setCursor(16, 1);
    lcd.print("   ");
    lcd.setCursor(16, 1);
    lcd.print(Width);

    lcd.setCursor(11, 2);
    lcd.print("       ");
    lcd.setCursor(11, 2);
    lcd.print(copy_delayPeriod);
   
    lcd.setCursor(14, 3);
    lcd.print("      ");
    lcd.setCursor(14, 3);
    lcd.print(delayDegrees, 1);   //delayDegrees, 1);
  }
 
  if (trigger == true && interruptFlag == true )
  {
    trigger = false;
    interruptFlag = false;
    noInterrupts();
    copy_delayPeriod = delayPeriod;
    interrupts();

    delayDegrees = pickup - (360.0 * (copy_delayPeriod) / (timerTopValue * 4.0)); //for decimal place in deg display.
    }
}

ISR(TIMER1_COMPA_vect) {
  OCR1A = (timerTopValue); // value to set delay between pulses to trigger cdi
  digitalWrite(chargePin, LOW); //guarantee off charge pin at trigger
  digitalWrite(outputPin, HIGH); //turn on pin  trigger

  if (risefall == 'R') //switch not set; default Rising trigger
  {
    delayPeriodStart = micros(); //start looking for response as pulse rises
    trigger = true;
  }
  //start Timer 2 for charge pulses
  if (chargePulse)
  {
    timeSliceCount = 0;
    TCNT2 = 0;
    OCR2A = timerTopValue/96; //set 12 periods
    TCCR2B |=  1 << CS22 | 1 << CS21; //prescaleer 256 16us/tick
  }
}

ISR(TIMER1_COMPB_vect) {
  digitalWrite(outputPin, LOW);

  if (risefall == 'F') //switch set for Falling trigger
  {
    delayPeriodStart = micros(); //start looking for response as pulse falls
    trigger = true;
  }
}

void delayPeriodTiming()
{
  delayPeriod = micros() - delayPeriodStart;
  interruptFlag = true;
}

ISR(TIMER2_OVF_vect)
//5 pulses of 30 degrees starting at 60 degrees
//ON at 60,120,180,240,300 = 2,4,6,8,10
//OFF at 90,150,210,270,330 = 3,5,7,9,11
{
  if (timeSliceCount != 0 && timeSliceCount % 2 == 0)
  {
    digitalWrite (chargePin, HIGH);
  }
  else //if (timeSliceCount == 0 || timeSliceCount % 2 == 1)
  {
    digitalWrite(chargePin, LOW);
  }
  timeSliceCount++;

  if (timeSliceCount == 12)
  {
    timeSliceCount = 0;
    //stop Timer2 by clearing prescaler bits
    TCCR2B &= ~1<< CS22;
    TCCR2B &= ~1<< CS21;
  }
}

Ok, I got confused here. The pulse width changes with the rpm but also has a basis on how wide the bar or pin on flywheel is. So if the bar is 23 degrees wide the pulse is wider across the range compared to the bar being 5 degrees wide. Not as simple as just one thing!

So I changed the code from map( ) to 'pulseWidth = (60000000/RPM)/360' this gives the width in degrees for any rpm then that is a basis for further use?

Thanks, Tom (in a slight mental fog based on age!!)