PWM and Register Counter?

PWM and Register Counter?
UNO
I couldn’t find any examples of bit banging PWM without delay and this is what I have so far.
I’m reading Two frequencies and outputting a PWM signal. My first attempt used Interrupts to read an optical disk of a motor, software to read the control frequency, and analogWrite() for the PWM.
Motor optical disk is 0 to 1.6kHz. I’m including zero in the frequency range since the input will be zero when nothing is moving and the code has to handle that state.
The control frequency was low (0 to 60Hz) and I had issues with resolution. I decided to use a higher frequency range(0 to 3.2kHz). The higher frequencies were taxing and there were conflicts between the software and the interrupts.
I implemented Register Counters to read the control frequency. That was accurate. Using the registers for frequency counting disabled millis. I simply used the register timer for timing needed for the interrupt counter. Both counters are very accurate with no conflicts while testing on the bench with a function generator. Moving on to attaching the motor I found the PWM output is also disabled when using registers for counting. This is where I am now, trying to Bit bang without a delay.
I’m able to use Timer2 for the timing, but it increments each 1mS. I set the PWM time to 200mS but isn’t fast enough for smooth motor at slow speeds, needs to be uS. I left the PID portion out of this example, but it ran the motor at a crawl before I tried to use register counting. I tried to use Timer1 for timing, but when I added code to that section it broke it.
I have tried a few variations such as the attached example where the HIGH time follows the input frequency 1 to 200Hz, and the LOW time is the remaining time up to 200mS. I tried HIGH for 1mS and LOW time 200mS to 1mS, and then HIGH time 1mS to 200mS with LOW time for 1mS. And a hybrid of those two to get almost 700 points, but the motor is too jerky at slower speeds.
Does anyone know how to generate a PWM output while using the registers for frequency counting?
Is there a way to tap into the Timer1 without effecting the frequency counting, or is there a different reqister counter that frees up a register for PWM, or is there another way of bit banging PWM than what I am doing?

/*
 * 
 * Use pin 2 for optical interrupt int. 0 (Motor optical disk)input pullUp
 * Use pin 6 for input for register counter(128Kppm)
 *
 */


const int optPin = 2; //Input Pin Interrupt for optical disk
int mover = 8;//Pin 8 pwm to motor
float frqIn = 0;//Input Frequency
float frq2 = 0;//Frequency of optical disk
float PWMtime = 0;
float driveCal = 0;//CALIBRATION*******************
float optical_speed = 0;//actual speed= frequency of the optical disk
#define driveCal (frqIn)//CALIBRATION********************************200 MAX
#define delayHi (PWMtime==driveCal)//PWM on time
unsigned long flag2=0;//iterates each optical disk pulse
int flag3=0;//flag for delayHi
float flag4=0;//1 second timer for serial output
float flag5=0;//mS timer for bit banging PWM

// set sampling period here (in milliseconds):
unsigned int samplingPeriod = 1000;

// Timer 1 overflows counter
volatile unsigned long overflow1;

void init_Timer1() {
  overflow1 = 0; // reset overflow counter
  // Set control registers (see datasheet)
  TCCR1A = 0; // normal mode of operation
  TCCR1B = bit(CS12) | bit(CS11) | bit(CS10); // use external clock source
  TCNT1 = 0; // set current timer value to 0
  TIMSK1 = bit(TOIE1); // enable interrupt on overflow
}

ISR(TIMER1_OVF_vect) {
  overflow1++; // increment overflow counter
}

// Timer 2 overflows counter
volatile unsigned int overflow2;
void init_Timer2() {
  overflow2 = 0; // reset overflow counter
  GTCCR = bit(PSRASY); // reset prescalers

  // Set control registers (see datasheet)
  TCCR2A = bit(WGM21); // CTC mode
  TCCR2B = bit(CS22) | bit(CS20); // prescaler set to 1/128, "ticks" at 125 kHz
  OCR2A = 124; // counts from 0 to 124, then fire interrupt and reset;
  TCNT2 = 0; // set current timer value to 0
  TIMSK2 = bit(OCIE2A); // enable interrupt
}

// interrupt happens at each 125 counts / 125 kHz = 0.001 seconds = 1 ms
ISR(TIMER2_COMPA_vect) {
  ++PWMtime;//increments up to 1000 then resets to 0
  ++flag5;//Increment 200mS counter each 1mS timer for bit banging output
    if (delayHi)// PWM Hi delay When PWMtime matches input frequency value
     flag3 = PWMtime;// Capture PWMtime value when it matches input value
   if (++overflow2 < samplingPeriod) // add an overflow and check if it's ready
    return; // still sampling
    
  unsigned long totalSamples = (overflow1 << 16) + TCNT1;  
  float freqHz = totalSamples * 1000.0 / samplingPeriod;
  frqIn = freqHz;
  frq2 = (flag2);//set frq2 to number of holes in 1000 ms and convert to 1 second
  optical_speed = ((frq2/64)*60);//set optical_speed to number of revs per second based on 64 hole disk convert to Hz
  
  flag2=0;//Resets flag  each 1 second for next Optical disk pulse count
  ++flag4;//Increment second counter each 1S for serial output
      
  // reset timers
  TCNT1 = 0; overflow1 = 0;
  TCNT2 = 0; overflow2 = 0;
  PWMtime = 0;
  
}

void setup()
{

  
  Serial.begin(115200);//enable serial monitor
  pinMode(optPin, INPUT_PULLUP); //Declared Pin 2 as Input
  pinMode(8, OUTPUT);//
  attachInterrupt(0, N2, RISING);//pin3 Optical disk
  // Disable Timer0; millis() will no longer work
  TCCR0A = 0;//Pin 6 
  TCCR0B = 0;//Pin 5 Timer 1 input

  // start timer 1 (count frequency)
  init_Timer1();
  init_Timer2();
  
}

void loop()
{
  //Bit Banging PWM
    if (flag5 <= flag3)// PWM Hi delay
     {
     digitalWrite(mover,HIGH);//
     } 
     else
     {
     digitalWrite(mover,LOW);//
     }
     if (flag5 >= 200)//Set bit banging Time here
     {
     flag5 = 0;//Reset bit banging timer
     }
     
     
    if (flag4 == 1)//1 second
    {
     Serial.print("  Optical Disk is ");// CALIBRATION**********
     Serial.print(frq2);//CALIBRATION***************************
     Serial.print("  driveCal is ");//CALIBRATION*******************
     Serial.println(driveCal);//CALIBRATION***********************
     flag4 = 0;//reset second counter
    }
   
 }//end of void loop


void N2() 
{
    flag2++;//increment flag for Optical disk Routine
}




  

The less of experience and knowledge, the more words....
It's an art to write in a way that the reader stays interested.
Looking at Your text, I'm not interested.
Where are the schematics? They could show what's it all about.

1 Like

I'm not sure schematics would be helpful.

It sounds like you may want to use:

  • one timer to monitor (via counting) optical disk frequency
  • another timer to monitor (via counting) some other frequency ("control"?)
  • another timer to do PWM for motor control.

If that's true, then maybe you should consider a MEGA, which has six timers (and FWIW, its Timer3, 4, and 5 are all 16 bit timers).

Some good info here: Arduino 101: Timers and Interrupts - Tutorials - RobotShop Community

DaveEvans, Thanks for the reply. I was thinking about using a MEGA. I played around with different bit banging over the year end break and asked this question after two weeks. When I saw your suggestion I downloaded the MEGA pin diagram and saw that I had the microprocessor datasheet open on Adobe reader. Over the last week I have been reading the datasheet and have it working. I was unable to adapt the register counter in the code above, but I did find one that works on MEGA on Nick Gammon’s website. Each time I look through the datasheet I get a greater understanding of what is going on. I’m starting to know what I’m looking at, but I can’t design a register counter on a cocktail napkin. Although there are six timers on the MEGA, and on the microprocessor, 5 have the Tn pin for external clock source. Of the 5 Tn pins only two are wired out to the MEGA board T0 (pin 38), and T5 (pin 47). The register counter requires an external clock source, and also disables Timer0 so interrupts don’t affect timing. This would limit the number of register counters on the MEGA to one unless you attach a wire to the uP pin 27 to access T4. Not a problem for me as I have the MEGA working as I need it.
It’s true that I want to complete this project and I’ll use the MEGA, but I also want to learn along the way and I was hoping to engage in a discussion about different methods of generating a PWM signal. I had to place the counter inside of Timer2. Four lines of code, enough to capture a count, but without affecting the timer. Timer1 is too fast to add anything, I thought of adding another counter next to overflow1++, but reading it externally of counter1. By the time it can be read it will have enumerated beyond a reasonable value. That’s where I gave up. If you’ve made it this far you might want to evaluate your life choices, or not. Suggestions welcome, critique, criticism, insults, free advice. Like Railroader, my 9 year old son is intimidated by wordy books, he too prefers to look at pictures…

I read it all but don't follow most of it, sorry.

A shame that only 2 of 5 MEGA Tn pins are broken out. Didn't know that.

For the two frequency monitoring jobs, I figured you could use pin Tx and pin Ty with external "clocks" from the optical disk and the other thing, and track the time between Output Compare Match interrupts. Or let the counters run and periodically check them. That would use two timers.

A third timer would be used for PWM.

If this were an XY problem, I’d say the use of a MEGA is the solution.

I found a couple websites that describe some methods of bit banging.

Some research I did with some code on the UNO and an oscilloscope:

analogWrite command, the typical PWM output minimum pulse is 8uS.

a digitalWrite(HIGH) command followed by a digitalWrite(LOW) outputs a 4uS pulse.

10 digitalWrite(HIGH) statements give a pulse of 40uS.

An if statement, a digitalWrite(HIGH), and an increment (++) followed by a similar setup with the digitalWrite(LOW).

The if command and the increment line add a considerable amount of time to the pulse for a total of 40uS. I tried a few different counts. 64 counts yields a PWM frequency of 490Hz, 255 counts for 128Hz, and 512 counts at 64Hz. The analogWrite works o.k. with the MEGA, but the cost of the MEGA is twice as much as the UNO. The duration of the digitalWrite in this code is longer than analogWrite, but still a lot faster than my original solution of 1mS, 25x faster to be precise.

I also found that the pin state can be switched with Register Control for a pulse of about 1.2uS, but with the if statement and the count(++) the period is the same as digitalWrite.

I rewired my daughter board for the UNO and implemented this routine in my code. It works well. I’m using the count of 512 so I can have greater precision than the typical PWM of 255. I am able to run a DC motor at incredibly slow speeds.

I’m curious if there is a way to achieve even faster bit banging. Separate the if statement and the increment(++) from the pulse command? Is there code that can say execute this next command for a determined number of times?

/*
 * 
 * Use pin 2 for optical interrupt int. 0 (Motor optical disk)input pullUp
 * Use pin 5 for input for register counter(128Kppm)
 * Jumper pins 3 and 4
 * Use pin 12 for PWM output
 *
 */


const int optPin = 2; //Input Pin Interrupt for optical disk
int mover = 12;//Pin 8 pwm to motor
float frqIn = 0;//Input Frequency
float frq2 = 0;//Frequency of optical disk
//int val = 0;//variable for optical disk pass through
float PWMtime = 0;
float driveCal = 0;//CALIBRATION*******************
float optical_speed = 0;//actual speed= frequency of the optical disk
#define driveCal (frqIn)//CALIBRATION********************************200 MAX
#define delayHi (PWMtime==driveCal)//PWM on time
//#define delayLow (1000-driveCal)//PWM off time
int flag1=0;//flag for serial monitor output
unsigned long flag2=0;//iterates each optical disk pulse
int flag3=0;//flag for delayHi
float flag4=0;//1 second timer for serial output
//float flag5=0;//mS timer for bit banging PWM
float HiCount=0;//counter for high time
float LoCount=0;//counter for low time
float LoTime=0;//duration of low time
#define LoTime (512-flag3)//set period count here
// set sampling period here (in milliseconds):
unsigned int samplingPeriod = 1000;

// Timer 1 overflows counter
volatile unsigned long overflow1;

void init_Timer1() {
  overflow1 = 0; // reset overflow counter
  // Set control registers (see datasheet)
  TCCR1A = 0; // normal mode of operation
  TCCR1B = bit(CS12) | bit(CS11) | bit(CS10); // use external clock source
  TCNT1 = 0; // set current timer value to 0
  TIMSK1 = bit(TOIE1); // enable interrupt on overflow
}

ISR(TIMER1_OVF_vect) {
  overflow1++; // increment overflow counter
}

// Timer 2 overflows counter
volatile unsigned int overflow2;
void init_Timer2() {
  overflow2 = 0; // reset overflow counter
  GTCCR = bit(PSRASY); // reset prescalers

  // Set control registers (see datasheet)
  TCCR2A = bit(WGM21); // CTC mode
  TCCR2B = bit(CS22) | bit(CS20); // prescaler set to 1/128, "ticks" at 125 kHz
  OCR2A = 124; // counts from 0 to 124, then fire interrupt and reset;
  TCNT2 = 0; // set current timer value to 0
  TIMSK2 = bit(OCIE2A); // enable interrupt
}

// interrupt happens at each 125 counts / 125 kHz = 0.001 seconds = 1 ms
ISR(TIMER2_COMPA_vect) {
  ++PWMtime;//increments up to 1000 then resets to 0
  //++flag5;//Increment 200mS counter each 1mS timer for bit banging output
    if (delayHi)// PWM Hi delay When PWMtime matches input frequency value
     flag3 = PWMtime;// Capture PWMtime value when it matches input value
   if (++overflow2 < samplingPeriod) // add an overflow and check if it's ready
    return; // still sampling
    
  unsigned long totalSamples = (overflow1 << 16) + TCNT1;  
  float freqHz = totalSamples * 1000.0 / samplingPeriod;
  frqIn = freqHz;
  frq2 = (flag2);//set frq2 to number of holes in 1000 ms and convert to 1 second
  optical_speed = ((frq2/64)*60);//set optical_speed to number of revs per second based on 64 hole disk convert to Hz
  
  flag2=0;//Resets flag  each 1 second for next Optical disk pulse count
  ++flag4;//Increment second counter each 1S, flag4 = 5 at 1S timer for serial output
      
  // reset timers
  TCNT1 = 0; overflow1 = 0;
  TCNT2 = 0; overflow2 = 0;
  PWMtime = 0;// flag3 = 0;
  
}

void setup()
{

  
  Serial.begin(115200);//enable serial monitor
  pinMode(optPin, INPUT_PULLUP); //Declared Pin 2 as Input
  pinMode(12, OUTPUT);//
  attachInterrupt(0, N2, RISING);//pin3 Optical disk
  // Disable Timer0; millis() will no longer work
  TCCR0A = 0;//Pin 6 
  TCCR0B = 0;//Pin 5 Timer 1 input

  // start timer 1 (count frequency)
  init_Timer1();
  init_Timer2();
  
}

void loop()
{
  //Bit Banging PWM
     if (HiCount <= flag3)// PWM Hi delay
     {
     digitalWrite(mover,HIGH);//
     HiCount++;
     } 
     else
     {
     digitalWrite(mover,LOW);//
     LoCount++;//
     }
     if (LoCount>=LoTime)//(200-flag3)
     {
     HiCount = 0;//Reset Hi counter
     LoCount = 0;//Reset Lo counter
     }
    /*if (flag5 <= flag3)// PWM Hi delay
     {
     digitalWrite(mover,HIGH);//
     } else {
     digitalWrite(mover,LOW);//
     }
     if (flag5 >= 200)//Set bit banging duty cycle here
     {
     flag5 = 0;//Reset bit banging timer
     }
     */
     
    if (flag4 == 1)//1 second
    {
     Serial.print("  Optical Disk is ");// CALIBRATION**********
     Serial.print(frq2);//CALIBRATION***************************
     Serial.print("  driveCal is ");//CALIBRATION*******************
     Serial.println(driveCal);//CALIBRATION***********************
     flag4 = 0;//reset second counter
    }
   
 }//end of void loop


void N2() 
{
    flag2++;//increment flag for Optical disk Routine
}




  

If the PWM part is just to be used as a signal generator for testing your code, then there's a new way this could be done in Wokwi. Basically, a PWM chip could be created (done) which is totally separate from the Arduino board simulation.

Try experimenting with this project, which seems similar to yours. The default settings here should let you accurately simulate using very high frequencies below 10,000Hz. The PWM generator is very accurate and has 1μs resolution and frequency range of (0-100,000 Hz) and duty cycle range of (0-100%). Note that with higher frequencies, the pulse period becomes smaller, therefore the RPM resolution gets worse (higher steps).

Run the simulation then click on the "PWM breakout" to change its settings.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.