Multiple Timer SYNC problem on Mega board

Hello,

Please find an extract from a global sketch aiming at full control of multiple timers. The structure CFG contains all the parameters used to configure each timer, after initialization the subroutine FrameFast is called to update each change of CFG.

I've connected my oscilloscope Tek2445 to the PWM outputs but discovered that the ouptuts are not TCNT's delayed correctly for some reason but all OCR's are correct. In other words, the internal registers OCR's once uploaded with CFG's update produce correct signals (frequency and duty cycle) but for some reason, the TCNT's register do not time delay correctly. I do get a time delay of occurence between timer4 and timer3 but this delay is always incorrect.

There must be either a bug in my code or TCNT register timer special procedure I'm missing !

Thank you in advance for any help, Albert

#define CPU_CLOCK 16000000.0

typedef unsigned char percentage_t;

// current frequency of timer1, timer 3, timer4 and timer5
static unsigned int freqPWM=800; // multiply by 10 to obtain value in Hz
static unsigned int localfreqPWM;

struct timerConfig {
	percentage_t pulse;
	percentage_t extract;
	percentage_t delay;
} __attribute__((__packed__));

struct allConfig {
	timerConfig timer1;
	timerConfig timer3;
	timerConfig timer4;
	timerConfig timer5;
} __attribute__((__packed__));

static struct allConfig cfg = {
	{1, 1, 0}, // Timer 1,
	{20, 20, 80}, // Timer 3,
	{100, 100, 0}, // Timer 4,
	{1, 1, 0}, // Timer 5,
};

// current frequency pulse extract delay of timer1
static unsigned int localpulsePWM1;
static unsigned int localextractPWM1;
static unsigned int localdelayPWM1;

// current frequency pulse extract delay of timer3
static unsigned int localpulsePWM3;
static unsigned int localextractPWM3;
static unsigned int localdelayPWM3;

// current frequency pulse extract delay of timer4
static unsigned int localpulsePWM4;
static unsigned int localextractPWM4;
static unsigned int localdelayPWM4;

// current frequency pulse extract delay of timer5
static unsigned int localpulsePWM5;
static unsigned int localextractPWM5;
static unsigned int localdelayPWM5;

// timer1 used for ZPE pump
int outputPin1B = 12;
int outputPin1C = 13;

// timer3 used for ZPE pump
int outputPin3B = 2;
int outputPin3C = 3;

// timer4 used for ZPE pump
int outputPin4B = 7;
int outputPin4C = 8;

// timer5 used for ZPE pump
int outputPin5B = 45;
int outputPin5C = 44;

void setup()
{
// select output pins & initialize timer1
//  pinMode(outputPin1B, OUTPUT);
//  pinMode(outputPin1C, OUTPUT);
//  TCCR1A = B00101011; // Fast PWM change at OCR1A
//  TCCR1B = B11001;  // prescaling by 1 the system clock
  
// select output pins & initialize timer3
  pinMode(outputPin3B, OUTPUT); // Fast PWM change at OCR3A
  pinMode(outputPin3C, OUTPUT); // prescaling by 1 the system clock
  TCCR3A = B00101011;
  TCCR3B = B11001;
  
// select output pins & initialize timer4
  pinMode(outputPin4B, OUTPUT); // Fast PWM change at OCR4A
  pinMode(outputPin4C, OUTPUT); // prescaling by 1 the system clock
  TCCR4A = B00101011;
  TCCR4B = B11001;

// select output pins & initialize timer5
//  pinMode(outputPin5B, OUTPUT);
//  pinMode(outputPin5C, OUTPUT);
//  TCCR5A = B00101011; // Fast PWM change at OCR5A
//  TCCR5B = B11001;    // prescaling by 1 the system clock

  FrameFast();
}

void loop()
{
}


void FrameFast()
{
  float fperiod;

  fperiod=CPU_CLOCK/10.0/float(freqPWM);
  localfreqPWM=(unsigned int) (fperiod-0.5);

  fperiod=fperiod/1000.0;  // millièmes

//  localpulsePWM1=(unsigned int) (float(cfg.timer1.pulse)*fperiod-0.5);
//  localextractPWM1=(unsigned int) (float(cfg.timer1.extract)*fperiod-0.5);
//  localdelayPWM1=(unsigned int) (float(cfg.timer1.delay)*fperiod-0.5);
  localpulsePWM3=(unsigned int) (float(cfg.timer3.pulse)*fperiod-0.5);
  localextractPWM3=(unsigned int) (float(cfg.timer3.extract)*fperiod-0.5);
  localdelayPWM3=(unsigned int) (float(cfg.timer3.delay)*fperiod-0.5);
  localpulsePWM4=(unsigned int) (float(cfg.timer4.pulse)*fperiod-0.5);
  localextractPWM4=(unsigned int) (float(cfg.timer4.extract)*fperiod-0.5);
  localdelayPWM4=(unsigned int) (float(cfg.timer4.delay)*fperiod-0.5);
//  localpulsePWM5=(unsigned int) (float(cfg.timer5.pulse)*fperiod-0.5);
//  localextractPWM5=(unsigned int) (float(cfg.timer5.extract)*fperiod-0.5);
//  localdelayPWM5=(unsigned int) (float(cfg.timer5.delay)*fperiod-0.5);

  cli();
//  OCR1A=localfreqPWM;
  OCR3A=localfreqPWM;
  OCR4A=localfreqPWM;
//  OCR5A=localfreqPWM;
  
//  OCR1B=localpulsePWM1;
//  OCR1C=localpulsePWM1;
//  OCR1C=localextractPWM1;
  
  OCR3B=localpulsePWM3;
  OCR3C=localpulsePWM3;
//  OCR3C=localextractPWM3;

  OCR4B=localpulsePWM4;
  OCR4C=localpulsePWM4;
//  OCR4C=localextractPWM4;

//  OCR5B=localpulsePWM5;
//  OCR5C=localpulsePWM5;
//  OCR5C=localextractPWM5;

//  TCNT1=localdelayPWM1;
  TCNT3=localdelayPWM3;
  TCNT4=localdelayPWM4;
//  TCNT5=localdelayPWM5;
  sei();
}

P.S.1. If I software hard code both TCNT register, it will produce a time delay output of 29us between PWM3 and PWM4 whereas the delay in this case should be 1/16MHz = 62,5ns

  TCNT3=1;
  TCNT4=0;

P.S.2. The crazy thing is that OCR's register produces correct outputs PWM parameters and my understanding is that loading TCNT's, OCR's,.. timer register works the same way given you disable any interrupts

P.S.3. Unless there is special way to initialize TCNT's...

Well... it is even worse than I thought because even this very simple sketch will corrupt the time delay between PWM3 and PWM4 introducing 29us instead of 62,5ns =(

What is the correct way of initializing 2 TCNT's registers in order to have each respective PWM outputs time delayed ?

// timer3 used for ZPE pump
int outputPin3B = 2;
int outputPin3C = 3;

// timer4 used for ZPE pump
int outputPin4B = 7;
int outputPin4C = 8;

void setup()
{

// select output pins & initialize timer3
  pinMode(outputPin3B, OUTPUT); // Fast PWM change at OCR3A
  pinMode(outputPin3C, OUTPUT); // prescaling by 1 the system clock
  TCCR3A = B00101011;
  TCCR3B = B11001;

// select output pins & initialize timer4
  pinMode(outputPin4B, OUTPUT); // Fast PWM change at OCR4A
  pinMode(outputPin4C, OUTPUT); // prescaling by 1 the system clock
  TCCR4A = B00101011;
  TCCR4B = B11001;
  cli();
  OCR3A=2000;
  OCR4A=2000;
  OCR3B=20;
  OCR3C=20;
  OCR4B=200;
  OCR4C=200;
  TCNT3=1;
  TCNT4=0;
  sei();
}

void loop()
{
}

Ok, I've finally found a method proving it is possible properly set TCNT's values per Atemga1280 datasheet but this might hide a compiler issue or something else !

Only problem as you'll see below in sketch, it requires receiving a serial data from either Serial Monitor of arduino IDE or from another Serial Protocol (i.e. SerPro linking a PC or iMac with arduino board developed by Alavaro).

Normally, calling FrameFast in order to set TCNT's should NOT require any Serial event unless TCNT's counter have build-in hardware relations through Atmega1280.

If I never send any data via serial monitor, the TCNT's are never updated correctly.

P.S. As I've explained before, this problem does not affect the update of OCR's which always work fine even in stand alone mode with serial communication implemented.

// timer3 used for ZPE pump
int outputPin3B = 2;
int outputPin3C = 3;

// timer4 used for ZPE pump
int outputPin4B = 7;
int outputPin4C = 8;

void setup()
{
 Serial.begin(9600);

// select output pins & initialize timer3
 pinMode(outputPin3B, OUTPUT); // Fast PWM change at OCR3A
 pinMode(outputPin3C, OUTPUT); // prescaling by 1 the system clock
 TCCR3A = B00101011;
 TCCR3B = B11001;

// select output pins & initialize timer4
 pinMode(outputPin4B, OUTPUT); // Fast PWM change at OCR4A
 pinMode(outputPin4C, OUTPUT); // prescaling by 1 the system clock
 TCCR4A = B00101011;
 TCCR4B = B11001;

 FrameFast();
}

void loop()
{
 int bIn;

 while (Serial.available()>0) {
   bIn =  Serial.read();
   FrameFast();
 }
}

void FrameFast()
{
 cli();
 OCR3A=2000;
 OCR4A=2000;
 OCR3B=20;
 OCR3C=20;
 OCR4B=200;
 OCR4C=200;
 TCNT3=100;
 TCNT4=0;
 sei();
}

Not ignoring you selfonlypath, just trying to get my head around the problem :slight_smile:

Are you saying that if you call FrameFast when a serial char is available it works?


Rob

Yes Rob, FrameFast() will only set correct values on TCNT's register if serial char is available. If not, TCNT's will get corrupted values from FrameFast(). I do know that each 16bits TCNT's registers share same common temporary 8bit register so modifying is tricky per Atmega1280 datasheet. Nevertheless, each TCNTx hence each associated timer (x=1, 3, 4 and 5) should run independently from any serial module so there should be NO correlation between serial char reading and timer (x=1, 3, 4 and 5) register management. What is even more strange is that each OCR's do get correct values from FrameFast() without any serial protocol running so the problem affects only TCNT's writing.

Please do you confirm one can set any time delay between each TCNT timer clock ?

Albert

From the data sheet

Modifying the counter (TCNTn) while the counter is running introduces a risk of missing a compare
match between TCNTn and one of the OCRnx Registers.

Maybe you should stop the counters, do the updates, then restart. Then there's this

Writing to the TCNTn Register blocks (removes) the compare match on the following timer clock
for all compare units.

I'm actually not sure what that means exactly :~, maybe do the TCNT write before the OCRn writes (although you said that they seem good after the first run)


Rob

About missing a compare match, this could indeed happen but only during one PWM cycle while writing to TCNT so this does not explain the incorrect time delay shift observed with my oscilloscope. In other words, you can have a transient update & local register conflicts (TCNT's, OCR's) but then, thsi disappear on steady state mode.

OK, I've finally found the root cause of the problem :wink:

I can't say if it is compiler bug or Atmega1280 bug or arduino mega board bug or serial read function bug, maybe some experts could tell here.

In all cases, in order to trigger or time synchronize any 16bit timer, you need to write a NON-zero value into each TCNT's you want to use. This takes care of the corruption if serial char reading or stand alone issue in arduino mega board. For example (see below sketch), I intentionnaly wrote 1 to TCNT4 in order to align them with an offset of 1 with TCNT3 being set to 2 so we think in differential mode (2-1). If I would have written TCNT4=0 and TCNT3=1 with still same difference, the result is erratic, incorrect and dependent on wether serial char is invoked or not.

P.S. Writing in a row two TCNT's introduces an additional 4 cycles delay (250 ns) due to execution time so you need to add extra time shift if you really want 0 ns between each timer.

// timer3 used for ZPE pump
int outputPin3B = 2;
int outputPin3C = 3;

// timer4 used for ZPE pump
int outputPin4B = 7;
int outputPin4C = 8;

void setup()
{
  Serial.begin(9600);

// select output pins & initialize timer3
  pinMode(outputPin3B, OUTPUT); // Fast PWM change at OCR3A
  pinMode(outputPin3C, OUTPUT); // prescaling by 1 the system clock
  TCCR3A = B00101011;
  TCCR3B = B11001;

// select output pins & initialize timer4
  pinMode(outputPin4B, OUTPUT); // Fast PWM change at OCR4A
  pinMode(outputPin4C, OUTPUT); // prescaling by 1 the system clock
  TCCR4A = B00101011;
  TCCR4B = B11001;

  FrameFast();
}

void loop()
{
  int bIn;

  while (Serial.available()>0) {
    bIn =  Serial.read();
    FrameFast();
  }
}

void FrameFast()
{
  cli();
  OCR3A=2000;
  OCR4A=2000;
  OCR3B=40;
  OCR3C=40;
  OCR4B=200;
  OCR4C=200;
  TCNT3=2;
  TCNT4=1;
  sei();
}