Timer TCA PWM latency/synchronization with TCB on ATtiny 2 series.

Thread Starter

farm_tinker

Joined Nov 20, 2025
11
I've found something a little weird and I can't figure out the cause. I may be missing something in the data sheets. The TCB errata for the TCB_SYNCUPD doesn't apply to this series.

In my example, TCB has a period much longer than TCA, but sync is working and truncates the long period. When TCB is synced to TCA, TCB output is high 1 cycle before TCA. I.E. they sync, but TCA has a cycle of latency that TCB doesn't have.

Microcontroller is an ATtiny 3224. Channel 1 is output from TCA with Channel 2 being TCB. Clock is 20mHz. Programmed in Arduino IDE using MegaTinyCore. Basically, when you sync them, TCB goes high a clock cycle faster than TCA. While this doesn't really affect my application, I don't know why it's doing this. Cursor measurement in the picture is from the rise on TCB to the rise on TCA.

Minimum code follows. TimerB1 option for millis() has to be set in the IDE. Code basically just changes the duties around so you can see that TCB is preceding TCA and it's not TCB lagging.

I've messed with slew rate, inversion, restarting TCA, nothing makes it go away. I may be missing something very simple here. I just can't figure it out. It also won't affect my application, just curious.

ATtiny 3224 TCB precedes TCA.jpg

Code:
#ifndef MILLIS_USE_TIMERB1
  #error "This sketch is written for use with TCB1 as the millis timing source"
#endif


void setup() {
  pinMode(PIN_PB1, OUTPUT);   //PB1 TCA0 WO1
  pinMode(PIN_PA5, OUTPUT);   //PA5 TCB0 WO0

//  PORTB.PORTCTRL |= PORT_SRL_bm; // Turns on slew rate limiting
//  PORTA.PORTCTRL |= PORT_SRL_bm; // Turns on slew rate limiting
    TCB0.CCMPL = 255; // Timer B period of 256
    TCB0.CCMPH = 2;  // starting pulse width of 2
    TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_PWM8_gc; //TCB0 8 bit PWM
 
  takeOverTCA0();                               // this replaces disabling and resettng the timer, required previously.
  TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP1EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc; // Single slope PWM mode, PWM on WO1
  TCA0.SINGLE.PER   = 31;                   // Timer A period of 31
  TCA0.SINGLE.CMP1 = 1; // starting pulse width of 1
  TCA0.SINGLE.CTRLA = TCA_SINGLE_ENABLE_bm; // enable the timer with no prescaler
  TCB0.CTRLA = TCB_CLKSEL_CLKTCA_gc | TCB_SYNCUPD_bm | TCB_ENABLE_bm; //Select TCA clock and set it to sync
  TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESTART_gc | 0x02; // Restarts TCA0, but TCB0 still precedes TCA0's pulse by one cycle
  TCB0.CCMPH = 1; // this line and the following in setup are just for messing with the pulse widths to see what happening at startup
  TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESTART_gc | 0x02;
  TCB0.CCMPH = 4;
  TCA0.SINGLE.CMP1 = 5;
}

void loop() {
 
  TCA0.SINGLE.CMP1 = 10;
  TCB0.CCMPH = 10;
  delay(1);
  TCA0.SINGLE.CMP1 = 1;
  TCB0.CCMPH = 1;
  delay(1);
    
}
 

Thread Starter

farm_tinker

Joined Nov 20, 2025
11
https://github.com/SpenceKonde/megaTinyCore/discussions/1285

Looks like it is a documented latency:

  1. The ATtiny3224 TCA description explicitly says that an interrupt/event/waveform output change triggered by CNT reaching BOTTOM, TOP, or CMPn occurs on the next CLK_TCA cycle. With DIV1 at 20 MHz, that is exactly 50 ns.
  2. TCB 8-bit PWM defines its output as being set at BOTTOM and cleared at CCMPH. Its output path is therefore one TCA clock ahead of the delayed TCA WO update even though both counters reach BOTTOM together.
 
Top