My problem with PWM from TimerThree Library

In my current project I am developing I recently moved up from using a Freetronics Ether-Ten(328) to a Freetronics Ether-Mega(2560) board.

In my software I make use of a timer library to do two things;

  • Generate a 100mSec interrupt for calling a routine,
  • and also to generate a PWM signal from the output of a PID control loop.

I use the library to generate a PWM output that is far slower than the standard core PWM as I use it to control a SSR (Solid State Relay).
The SSR is a 240VAC 25 Amp unit and will not operate with the fast built-in PWM.
As part of the 100mSec interrupt routine, I set the PWM value, so in essence, this is as fast as I switch it, PWM with a 100mSec period.

With the 328 board I used the TimerOne Library, and all appeared to be problem free.
Using the 2560 Mega board I shifted to using the TimerThree Library, essentially the same, just catering for the 2560.
TimerOne and TimerThree library Arduino Playground - Timer1

What I am experiencing is the following;
The 100mSec interrupt generated by the timer library works fine, no problems there at all.
But, I do have a problem with the generation of PWM in that after a period, typically a few days it appears to cease generating any PWM output.
This is a real problem for me as the PID control loop that drives the PWM which in turn operates the SSR which is dumping excess energy from my micro-hydro turbine.
My PID setpoint is 27.6Vdc for the battery bank, so when the PWM sigal stops and the PID loop continues trying to drive it, of course my battery volts go up, potentially too high.

I have searched for anyone else having such a problem using the TimerOne / TimerThree libraries, but haven't come across anything.

Below is my main program, main.cpp which is the section that initialises the TimerThree interrupt and attaches a routine as well as sets up the PWM.
Specifically, in the setup section, see the following code;

	Timer3.initialize(100000);									// initialize timer1, and set a 100 milli second period:
	Timer3.pwm(pin_TurbineDump, 0);									// pre-initialise the pwm on pin 2 and set for 0% duty cycle:
	Timer3.attachInterrupt(timerInterrupt);								// attach interrupt to timerInterrupt routine, used for PID and other timing control:

And then near the endof my code where I use the PID value to set it as the PWM value.
The PID output value has already been scaled to fall in the range of 0 to 1023 which is the range of the PWM input.

	Timer3.setPwmDuty(pin_TurbineDump, hydroPID.cvPID);						// set the pwm with the output of the pid output to control the SSR:

I apologise that the neatness of my original code is destroyed when pasting here, it is all niceand neat in my editor.

/*-------------------------------------------------------------------------
	main.cpp

	Created on: Sep 19, 2012
	Author: Paul Alting van Geusau

 */
#define WEBDUINO_FAVICON_DATA ""
#define serialDebug true

#include	"main.h"
#include	"Ethernet.h"
#include	"EthernetUdp.h"
#include	"TimerThree.h"
#include	"Local_files/sensor.h"
#include	"Local_files/logData.h"
#include	"Local_files/ntpClient.h"
#include	"Local_files/pidControl.h"
#include	"Local_files/serverCmds.h"

//	const	type		variable			=	value;		comment
	const	uint8_t		pin_vTurbine		=	0;			// analog 0
	const	uint8_t		pin_iTurbine		=	1;			// analog 1
	const	uint8_t		pin_vBattery		=	2;			// analog 2
	const	uint8_t		pin_iBattery		=	3;			// analog 3
	const	uint8_t		pin_TurbineDump		=	2;			// PWM from OCR3B
	const	uint8_t		pin_sdSelect		=	4;
	const	uint8_t		pin_TurbineDumpLed	=	8;			// LED to signal PID driving PWM
	const	uint8_t		pin_CSether			=	10;
	const	uint8_t		pin_SPI_CS			=	53;

			boolean		firstScan			=	true;

			uint32_t	sdLogCount			=	0;			// increental count of records written to sdRecordLog:
			int16_t		rtcTimeTrigger		=	9;			// incremented by TimerOne interrupt:
			boolean		ntpPacketSent		=	false;

			uint8_t		mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE};			// MAC address, default is {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}:
			uint8_t		ip[] = {192,168,1,9};								// IP network address:

//-------------------------------------------------------------------------
//	void setup()
//
void setup() {
	Serial.begin(115200);

	pinMode(pin_vTurbine, INPUT);
	pinMode(pin_iTurbine, INPUT);
	pinMode(pin_vBattery, INPUT);
	pinMode(pin_iBattery, INPUT);
	pinMode(pin_TurbineDumpLed, OUTPUT);
	pinMode(pin_TurbineDump, OUTPUT);
	pinMode(pin_CSether, OUTPUT);											// set ethernet CS as output:
	pinMode(pin_SPI_CS, OUTPUT);											// set ATMega-2560, pin 53 hardware cs pin output, even if un-used:

	digitalWrite(pin_CSether, HIGH);										// set CS high to disable ethernet spi until sd has been initialised:

	sdBegin();																// call external begin to initialise SD card ready for use:
	delay(250);																// allow time for the ethernet cpu to come alive out of reset:
	Ethernet.begin(mac, ip);												// initialise the ethernet library:
	webserverBegin();														// call exteranl begin to start webserver:
	serverAddCommands();													// add webserver URL commands:

	udpStart();																// start UDP service for getting NTP formatted time for software RTC:

	Timer3.initialize(100000);												// initialize timer1, and set a 100 milli second period:
	Timer3.pwm(pin_TurbineDump, 0);											// pre-initialise the pwm on pin 2 and set for 0% duty cycle:
	Timer3.attachInterrupt(timerInterrupt);									// attach interrupt to timerInterrupt routine, used for PID and other timing control:
}

//-------------------------------------------------------------------------
//	void loop()
//
void loop() {
	if (!ntpPacketSent) {
		if (firstScan || rtcDayOns) {										// if firstScan or if '00:00:00':
			ntpPacketSend();												// send an NTP packet to a time server:
			ntpPacketSent = true;
		}
	}

	if (ntpPacketSent && ntpGetRequest()) {
		rtcTime = ntpTime;
		Serial.print(F("Time :"));
		Serial.println(rtcTime);
		ntpPacketSent = false;
		// TODO write file system log that rtcTime was update successfully
	}

	if (rtcHourOns) {														// check for hour rollover:
		sdRecordLog();
		sdLogCount ++;

		vStatSensor(vacHydro, true, rtcDayOns);								// reset and calculate hourly stats:
		iStatSensor(idcHydro, true, rtcDayOns, rtcWeekOns);

		vStatSensor(vdcSolar, true, rtcDayOns);
		iStatSensor(idcSolar, true, rtcDayOns, rtcWeekOns);

		vStatSensor(vdcBattery, true, rtcDayOns);
		iStatSensor(idcBusInv, true, rtcDayOns, rtcWeekOns);
		iStatSensor(idcBusAux, true, rtcDayOns, rtcWeekOns);

		rtcHourOns = false;
		rtcDayOns = false;
		rtcWeekOns = false;
		// TODO write to system log the sdRecordLog what was reset
	}

	processConnection();													// process any incoming Ethernet connection request:
	firstScan = false;
}

//-------------------------------------------------------------------------
//	void timerInterrupt()
//
void timerInterrupt() {
	if (rtcTimeTrigger > 0) {												// take care on software rtc:
		--rtcTimeTrigger;
	}
	else {
		++rtcTime;
		rtcTimeTrigger = 9;
		rtcGetDateTime(rtcTime);											// update rtc date and time:
		vStatSensor(vacHydro, false, false);								// calculate maximum and minimum stats, no reset:
		iStatSensor(idcHydro, false, false, false);

		vStatSensor(vdcSolar, false, false);
		iStatSensor(idcSolar, false, false, false);

		vStatSensor(vdcBattery, false, false);								// calculate maximum and minimum stats, no reset:
		iStatSensor(idcBusInv, false, false, false);
		iStatSensor(idcBusAux, false, false, false);
	}

	readSensor(vacHydro);													// read analog sensors:
	idcHydro.pv = 300;				 		// XXX test 20 / 1023 * 300 = 5.865 Amps
//	readSensor(idcHydro);

	vdcSolar.pv = 0;						// XXX test
	idcSolar.pv = 0; 						// XXX test
//	readSensor(vdcSolar);
//	readSensor(idcSolar);

	readSensor(vdcBattery);
	readSensor(idcBusInv);
	idcBusInv.pv = 0;						// XXX test
	readSensor(idcBusAux);
	idcBusAux.pv = 0;						// XXX test

	hydroPID.pvPID = vdcBattery.pv;											// pass the battery value into the PID data block:
	controlPID(hydroPID);													// call PID loop for dumpload of AC from turbine into heating element:

 	if (hydroPID.cvPID > 0) {
 		loadStateHydro = true;												// code for displaying load state on web and on led:
 	}
	else {
		loadStateHydro = false;
	}
	digitalWrite(pin_TurbineDumpLed, loadStateHydro);
	Timer3.setPwmDuty(pin_TurbineDump, hydroPID.cvPID);						// set the pwm with the output of the pid output to control the SSR: *** problem appears to be here***
}

//------------------------------------------------------------------------------------------------------
//
//void softReset() {
//	asm volatile ("  jmp 0");
//}

I would appreciate any advice or clues that can help me sort this one out?

Paul

rockwallaby:
I apologise that the neatness of my original code is destroyed when pasting here, it is all niceand neat in my editor.

Sounds like a tab issue. Convert tabs to spaces before posting and it should look OK.

Hi Nick,
Yes, I know it's a tab issue, I use tabs exclusively as part of my way of writing code.
I guess it would be nice that using the code section button would take this into account.

For me to exchange the tabs for spaces in the code I am posting becomes overly compilcated being time comsuming.

I wonder if the forum software can be made to handle correctly tabulated code?

Paul

I wonder if the forum software can be made to handle correctly tabulated code?

Don't bet on it. FWIW when cut and pasted into one of my editors it looks fine.

Unfortunately that hasn't helped my spot any errors :frowning:

EDIT:
It seems to me that it's either in the timer PWM code or the PID code. Can you verify one way or the other by checking that the timer is getting a correct value, say printing the value.

I guess that means waiting a few days.

Another option is to apply a sanity test to the PWM value, might be difficult though of the range is 0 to 100%.


Rob

There are a few places in the library where the wrong bit names are used, for example:

void TimerThree::stop()
{
  TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));          // clears all clock selects bits
}

Should be:

void TimerThree::stop()
{
  TCCR3B &= ~(_BV(CS30) | _BV(CS31) | _BV(CS32));          // clears all clock selects bits
}

This happens in quite a few places, it's obviously copy/paste from the Timer 1 code.

Having said that though, the bits are probably the same so it is probably not the issue. However to be neat the library should be corrected so that the Timer 3 bit names are used for Timer 3 and not the Timer 1 ones.

But, I do have a problem with the generation of PWM in that after a period, typically a few days it appears to cease generating any PWM output.

This sounds to me like an overflow issue in your code. How many days exactly?

rockwallaby:
I wonder if the forum software can be made to handle correctly tabulated code?

The tabs are in the actual data (as I found when I copied it into my text editor). It's just that the tab stops in web browsers are not necessarily the ones in your editor.

It's only a few seconds work to do a "convert tabs to spaces" before posting a copy, so it's not a big issue. Of course, the tabs are not your "real" problem.

I still suspect some integer overflow somewhere, or the way you are comparing times (if you are comparing them).

Do any of those libraries, or other files, use the String class?

Nick,

No string class is used anymore, after experiencing the often spoke of problems with string class, I threw them all out.

I'm uncertain that it is an overflow, which was my original thought, which is what typically happens with issues that pop up after a time period.
Quite simply, I set the PWM with a value between 0 and 1023 which is what it requires.
I don't see anywhere where I am accumulating to lead to overflow.

But, what I have noticed, is a difference between Timer1 and Timer3 libraries apart from the differences you have noted Nick.

In Timer1 we have the following section of code

void TimerOne::setPeriod(long microseconds)		// AR modified for atomic access
{
  long cycles = (F_CPU / 2000000) * microseconds;                                // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2
  if(cycles < RESOLUTION)              clockSelectBits = _BV(CS10);              // no prescale, full xtal
  else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11);              // prescale by /8
  else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10);  // prescale by /64
  else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12);              // prescale by /256
  else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10);  // prescale by /1024
  else        cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10);  // request was out of bounds, set as maximum
  
  oldSREG = SREG;				
  cli();							// Disable interrupts for 16 bit register access
  ICR1 = pwmPeriod = cycles;                                          // ICR1 is TOP in p & f correct pwm mode
  SREG = oldSREG;
  
  TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
  TCCR1B |= clockSelectBits;                                          // reset clock select register, and starts the clock
}

And then in Timer3 we have

void TimerThree::setPeriod(long microseconds)
{
  long cycles = (F_CPU * microseconds) / 2000000;                                // the counter runs backwards after TOP, interrupt is at BOTTOM so divide microseconds by 2
  if(cycles < RESOLUTION)              clockSelectBits = _BV(CS10);              // no prescale, full xtal
  else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11);              // prescale by /8
  else if((cycles >>= 3) < RESOLUTION) clockSelectBits = _BV(CS11) | _BV(CS10);  // prescale by /64
  else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12);              // prescale by /256
  else if((cycles >>= 2) < RESOLUTION) clockSelectBits = _BV(CS12) | _BV(CS10);  // prescale by /1024
  else        cycles = RESOLUTION - 1, clockSelectBits = _BV(CS12) | _BV(CS10);  // request was out of bounds, set as maximum
  ICR3 = pwmPeriod = cycles;                                                     // ICR1 is TOP in p & f correct pwm mode
  TCCR3B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
  TCCR3B |= clockSelectBits;                                                     // reset clock select register
}

Spot the diffeence near the top, timer3 has "(F_CPU * microseconds) / 2000000" which I spoted a while back as I could see that that would lead to overflow conditions.
In fact, initially having Timer3 in original form, I noticed that the things were not working, which lead me to make that change.
So I changed it to how it was in Timer1 where I do the division before multiplication.

So, with this change, things started to work, but the problem I posted about exists.

I haven't noticed that the problem occurs at precise periods as yet or due to any observable event, just that I get no PWM output.
When the issue occurs, I do know that the 100mSec interrupt routine continues to be called as I can still see updated real-time data via my web page as I read sensor data in that interrupt period, so I am thinking that all other functions are performing normally.
Even the LED output on digital pin 8, "pin_TurbineDumpLed" called just before I set the PWM operates and tells me that I should be getting PWM output.

Regards,
Paul

Call me old-fashioned but I prefer to work with the actual values in situations like this.

In this particular case 16000000 / 2000000 will be 8, which is nice and even, so no problems there.

  Timer3.pwm(pin_TurbineDump, 0);                     // pre-initialise the pwm on pin 2 and set for 0% duty cycle:

Why do you want a 0% duty cycle?

I'm a bit skeptical about this:

void TimerThree::setPwmDuty(char pin, int duty)
{
  unsigned long dutyCycle = pwmPeriod;
  dutyCycle *= duty;
  dutyCycle >>= 10;
  if(pin == 5) OCR3A = dutyCycle;
  if(pin == 2) OCR3B = dutyCycle;
  if(pin == 3) OCR3C = dutyCycle;
}

First, the multiplication might overflow. Second, after shifting dutyCycle (a 32 bit field) right 10 bits we have 22 bits left. But OCR3B is a 16 bit field. Now maybe the numbers are OK. Can you provide actual figures here? Eg. from debug output?

Call me old-fashioned but I prefer to work with the actual values in situations like this.

In this particular case 16000000 / 2000000 will be 8, which is nice and even, so no problems there.

Yes I agree Nick, though if we use Timer3 with how it is originally we will not be doing the division first, but multiplying 16000000 by the value of microseconds and then dividing by 2000000.
In my setup function I do the following;

	Timer3.initialize(100000);

Thereby setting the value of the variable in Timer3 to 100000 microseconds being the 100 milliseconds for my interrupt period.
I am guessing that if we were to take the original Timer3 code where it would multiply 16000000 by 100000 giving 1.6 x 10^12 which in this case does not fit withing the declared data 'long' type of microseconds.
Long data type is 2^32 which is only 2 and a wee bit x 10^9, so we are out by a long shot on the original code.
Changing it so that effectively we have as you say 8 x 100000 does fit and does work, and I have seen this to be the case.
This is just an error in the 'Timer3' library that others should be aware of.

But for other people following, remember the above is nothing to do with my existing problem, whereby the PWM signal from OCR3B simply ceases after some period of days of good operation.

	Timer3.pwm(pin_TurbineDump, 0);

Why do you want a 0% duty cycle?

I do this so as be certain that on startup I am not presenting a value to the PWM output.
You may well be correct in thinking that it is not needed, as the PID control loop will feed the required value to the PWM function.
I guess belt and braces here a little bit doesn't hurt, and for me it is definite.

Rob,

It seems to me that it's either in the timer PWM code or the PID code. Can you verify one way or the other by checking that the timer is getting a correct value, say printing the value.
I guess that means waiting a few days.
Another option is to apply a sanity test to the PWM value, might be difficult though of the range is 0 to 100%.

You mean like the following?

/*-------------------------------------------------------------------------
    pidControl.cpp
    
    Created on: Sep 23, 2012
        Author: Paul Alting van Geusau

 */

#include "pidControl.h"

//    const    type        variable            =    value;        comment
//                                                  pv  sp   err i  d lst kp  ki kd min max cv
            PID            hydroPID            =    {0, 2760, 0, 0, 0, 0, 5,  1, 0, 0, 1023, 0};
            PID            solarPID            =    {0, 2760, 0, 0, 0, 0, 10, 1, 0, 0, 1023, 0};
            boolean        loadStateHydro      =    false;
            boolean        loadStateSolar      =    false;

//-------------------------------------------------------------------------
//    PID Load control of single phase AC from turbine:
//
//    http://brettbeauregard.com/blog/category/pid/coding/
//    https://groups.google.com/forum/?fromgroups=#!forum/diy-pid-control
//
void controlPID(PID& PID) {
/*            data structure of PID block
             int16_t        pvPID;
             int16_t        spPID;
             int16_t        errPID;
             int16_t        intPID;
             int16_t        derPID;
             int16_t        pvLast;
    const    int16_t        kp;                // gain constant for proportional term:
    const    int16_t        ki;                // gain constant for integral term:
    const    int16_t        kd;                // gain constant for derivative term:
    const    int16_t        outMin;            // pid output minimum clamp value:
    const    int16_t        outMax;            // pid output maximum clamp value:
             int16_t        cvPID;
*/

    PID.errPID = PID.pvPID - PID.spPID;                                                   // calculate setpoint error:
    PID.intPID = PID.intPID + PID.ki * PID.errPID;                                        // calculate integral term:
//  constrain(PID.intPID, PID.outMin, PID.outMax);                                        // clamp integral term:
    if (PID.intPID > PID.outMax) {                                                        // clamp integral term:
        PID.intPID = PID.outMax;
    }
    else if (PID.intPID < PID.outMin) {
        PID.intPID = PID.outMin;
    }

    PID.derPID = PID.pvPID - PID.pvLast;                                                  // calculate derivative term:

    PID.cvPID = PID.kp * PID.errPID + PID.intPID - PID.kd * PID.derPID;                   // calculate pid output:
//  constrain(PID.cvPID, PID.outMin, PID.outMax);                                         // clamp pid output value to 0 - 1023 for pwm:
    if (PID.cvPID <= PID.outMin) {                                                        // clamp pid output value to 0 - 1023 for pwm:
        PID.cvPID = PID.outMin;
    }
    else if (PID.cvPID > PID.outMax) {
        PID.cvPID = PID.outMax;
    }

    PID.pvLast = PID.pvPID;                                                                // remember input value for next time:
}

Where I do perform range checks in the PID function.
You can see that my data structure holds the range, min of 0 and max of 1023 which is used in the last part of the above code.
So the PID output value, 'PID.cvPID' will then be constrained to those limits.
On the web page you see it as 0 - 100% on that dial guage you see, where as in the Arduino it is processed as 0 to 1023.

To wait a few days will mean I will need to cut that trace on the pcb that resets the cpu everytime I connect with the terminal program.
That might be a good thing anyhow for me, as it's generally a nuisance to have the Arduino reset everytime I want serial stuff.
More long term, I am looking more to developing debugs through the Ethernet connection.

Nick,

Second, after shifting dutyCycle (a 32 bit field) right 10 bits we have 22 bits left. But OCR3B is a 16 bit field. Now maybe the numbers are OK. Can you provide actual figures here?

I don't have any numbers to provide from the 'Timer3' library functions at present,
I will look at putting some debug points in the Timer3 function to get closer to the problem area.

I do appreciate the help.

Paul

Where I do perform range checks in the PID function.

Good question, I'm afraid all that PID code is beyond me, in fact I was planning to pick your brains about it when the time comes :slight_smile:

I was thinking of applying a sanity test to the value sent to the PWM which I assume is the yellow bit

Timer3.setPwmDuty(pin_TurbineDump, hydroPID.cvPID);

Your problem is that it turns full on and stays that way, (or doesn't turn on when it should) is that the case?

So you can't test for 1023 because that's valid. How about limiting the valid values to say 1000 and trapping anything over that. At least that will tell you that the PID code has a problem.

Maybe test for 1023 over a long period when the voltage is already at 30.

NOTE: Maybe the numbers are the other way around as you are controlling a load dump, so limit to say 20-1023 and test for < 20.

In other words general defensive coding, but despite being entertained by the graphs for the last couple of weeks I don't know your system well enough offer very targeted advice. You and Nick seem to have well analysed the actual timer code.


Rob

Hey Rob,
I think you know my system pretty well actually, you keeping a midnigh eye on the trend graphs, much like watching the glowing embers of the late night fire eh?

Your suggestions are good and I will think more about putting that conditional test in to see if that helps, maybe this evening when the solar fire dims.

Edit:

applying a sanity test to the value sent to the PWM which I assume is the yellow bit

Yes, you are correct, that is what I do do in the PID section of code, which I pasted a few posts up.
The value of hydroPID.cvPID is clamped to be within 0 to 1023.

Your problem is that it turns full on and stays that way, (or doesn't turn on when it should) is that the case?

Yes, the problem is that it does not turn on when it is supposed to turn on, thus my battery bank voltage goes up.
If the PID ouput is close to zero, then I dump less energy, when it approaches the max of 1023, then I am putting max energy into the dumpload element.
The PID works quite well, I will look at tweeking the tuning parameters later on if need be.
So, my dump-load LED or watching the web page for turbine PID dumpload % which tells me that there should be PWM being applied to the dump load when there is no PWM is my problem.

Presently it is still dumping energy to the load, around 10% as the sun sets on the Australian bush horizon.

Paul

I find that debugging displays usually help. Also in a case like this, isolate it down to a simple sketch with just the Timer3 library in it. Now either that fails after a few days or not. If it fails, there is an issue with the timer (library) or your use of it. If not, the issue is elsewhere, and it just looks like the timer is to blame.

Ok, good thoughts Nick and Rob,

What you say Nick is perfectly true,and my brain tends to think that way when in diagnostic mode.
My preference is to debug the real system as from my experience finding tricky problems on half baked systems designed to trap the error usually does not trap the error.

What I have done is to bring some of the values of Timer3, specifically function "setPwmDuty(char pin, int duty)" to be viewable on the web.
The debug code now looks like this;

void TimerThree::setPwmDuty(char pin, int duty)
{
  unsigned long dutyCycle = pwmPeriod;
  dutyCycle *= duty;
  dutyCycle >>= 10;
  dutyCycleDebug = dutyCycle;			// XXX test
  if(pin == 5) OCR3A = dutyCycle;
  if(pin == 2) OCR3B = dutyCycle;
  if(pin == 3) OCR3C = dutyCycle;
  OCR3BDebug = OCR3B;					// XXX test
}

T3 PWM Period should stay as a constant at 12500.
The value for T3 Duty Cycle should always equate tothe same value of T3 OCR3B, if not then we have a problem.

Also, I took on your suggestion Nick and modified those values you mentioned in post #4 and right away I noticed no PWM and the PID max'd out at 1023 trying to bring down the battery volts back to setpoint.

Then restoring the back to original and it works again, the PID settles back as the PWM does its thing

So, seemingly no go there. I am reading the datasheet on OCR3 but it is a bit to wade through,especially at 01h15.

At least I can now see the actual OCR3B register value in next to real-time on the web so I can keep an eye on it if it decides to fall over.

Paul

I'm just suspicious of all those calculations in the library. Fundamentally the timers are very simple. You have two numbers:

The "count" which gives the period (and thus, the frequency) of the PWM output. Depending on the mode of the timer this is either fixed or in OCRxA.

The duty cycle, which gives the number of "ticks" within the "count" for which the output is high rather than low. The duty cycle has to be lower than the count. Depending on the mode, again, this duty cycle could be in OCRxB.

Example:

Count = 199. Prescaler = 1.

Therefore the period is 12.5 uS (200 * 0.0625 uS). I say 200 because the count is zero-relative.

That being the period the frequency is: 80 KHz. That is: 1 / (200 * 62.5e-9)

(Caveat: this is fast-PWM mode. Phase-correct PWM mode has twice the period and therefore half the frequency).

So far so good. Now valid duty cycles in this example are 0 to 199 where 0 would be "all off" and 199 would be "all on". If, for example, the duty cycle was 220 the timer would not behave as expected because it will never reach the duty cycle count.

However if the duty cycle was 99 then you have a 50% duty cycle (you get 100 out of 200 counts in the "on" state).

Well, I finnaly caught my Arduino out not sending PWM from the TimerThree library again, and have captured from the web page the values I was watching for TimerThree.
What is different is the value of TCCR3A, which normally has a value of 32 when the PWM is working.
This is the first time I have noticed it to be zero while also noticing no PWM output when there is supposed to be.

So, even though I am seeing the counter clocking over, TCNT3 and the values in the output compare register OCR3B being updated, the problem is that somehow the value of TCCR3A has changed and is now zero rather than 32.

Now, every 100mSec I make a call to a TimerThree function, the PWM function, and this is where TCCR3A is able to be changed, as follows;

void TimerThree::pwm(char pin, int duty, long microseconds)					// expects duty cycle to be 10 bit (1024)
{
  if(microseconds > 0) setPeriod(microseconds);
  
	// sets data direction register for pwm output pin
	// activates the output pin
  if(pin == 5) { DDRE |= _BV(PORTE3); TCCR3A |= _BV(COM3A1); }
  if(pin == 2) { DDRE |= _BV(PORTE4); TCCR3A |= _BV(COM3B1); }  // I use this pin
  if(pin == 3) { DDRE |= _BV(PORTE5); TCCR3A |= _BV(COM3C1); }
  setPwmDuty(pin, duty);
  start();
}

As you can see, I use pin 2, and TCCR3A is being bitwised OR'd with _BV(COM3B1)
And I believe this may well be the source of the problem.

Below is a screen capture of the Timer values as I see them in real-time on my web page .

I don't know what is causing the this problem yet, though I am certain if I can follow TCCR3A and _BV(COM3B1) back further I may find the route cause.
Just to note: when I initialise the TimerThree at the start using the built-in initialise function, TCCR3A is set to zero, like thus;

void TimerThree::initialize(long microseconds)
{
  TCCR3A = 0;																// clear control register A
  TCCR3B = _BV(WGM13);														// set mode as phase and frequency correct pwm, stop the timer
  setPeriod(microseconds);
}

So, I am guessing the problem may well be to do with _BV(COM3B1), which I don't yet know what that is or does until I dig deeper.

Any thoughts or suggestions?

Paul

TCCR3A is being bitwised OR'd with _BV(COM3B1)

I bitwise OR can never force something to 0, so both those expressions must already have been 0.

But how can _BV(COM3B1) ever be 0? What is the value of COM3B1, if it's > 7 the bit will be off the end and not used.


Rob

Hey Rob and others,

so both those expressions must already have been 0.

OK, right, so if I see that the value of TCCR3A is 32 and all that is done to it is a compound bitwise OR with _BV(COM3B1) as on the following line;

 if(pin == 2) { DDRE |= _BV(PORTE4); TCCR3A |= _BV(COM3B1); }

Then how does it get back to being zero and stopping the PWM output on pin 2?

I don't yet know what _BV(COM3B1) is?
I know that when I hover over it in Eclipse IDE it tells me it is a 2 step macro expression;

  1. #define COM3B1 5
  2. (1 << (5))

Would someone be able to explain this to me, or help me find some resource for it.
Would or could this be the source of the problem or do I need to look elsewhere?

Paul

In the datasheet for the Atmega2560:

You can see that for register TCCR3A the "bit" called COM3B1 is bit 5 (the 6th one across, it is bit 5 if you start counting at zero).

So your expression: 1 << 5

... gives you that particular bit. If you "or" that into the register you have set that bit.

_BV is just a macro as you've found, so the end result of that is

TCCR3A |= 32;

because COM3B1 is 5 and 1<<5 = 32. Therefore TCCR3B was either 0 or 32 before the operation.

If it was 0 it becomes 32, if it was 32 it stays 32.

Any other value and it becomes some value > 32, but it can never be 0 after that OR.

So how does it become 0? Something must be screwing with it elsewhere, or maybe your reporting it at the wrong time/place.


Rob