Moving away from AnalogWrite() to directly control registers for 16 bit PWM

Hi there,
I am doing a project involving an LED strip slowly fading up to full brightness and back to zero.

After prototyping with an Arduino Uno I designed a circuit using an ATTINY to reduce cost and size and it worked but I decided the fade was not smooth enough at low levels.

My solution was to get a chip with a 16 bit timer so I could increase the resolution of the PWM.

I am currently using an ATTINY84 and my code works, I have accessed the 16 bit timer but now I am limited by the fact that the analogwrite() function I am using in my code only goes up to 255.

I tried using analogWriteResolution() but this only apparently works on a couple of arduino boards, is this correct?

It seems from the research that I have done that I need to abandon analogwrite() and control the timer registers directly. My only understanding of coding has come through working on this project so my knowledge is at a very basic level.
How do I do this?
Or is there another way to increase the resolution of analogwrite()?

My code is as follows:

const int transistorPin = 5;    // connected to the base of the transistor

                                // The number of Steps between the output being on and off

const int pwmIntervals = 800;  

// The R value in the graph equation
float R;

 void setup() {
   pinMode(transistorPin, OUTPUT);

noInterrupts();  
  TCNT1 = 0;
  ICR1 = 0x1FFF;

    // Calculate the R variable (only needs to be done once at setup)
  R = (pwmIntervals * log10(2))/(log10(65535));
 }
 


 void loop() {
    unsigned int brightness = 0;

unsigned int interval = 0; interval <= pwmIntervals; interval++) {
      // Calculate the required PWM value for this interval step
      brightness = pow (2, (interval / R)) - 1;
      // Set the LED output to the calculated brightness
     
      analogWrite(transistorPin, brightness);
      delay(1);                                   // delay (n) where n is time to full brightness in milliseconds
    }

    delay(100);                                   //n is time at full brightness  

    for (int interval = 0; interval <= pwmIntervals; interval++ ) {
      // Calculate the required PWM value for this interval step
     brightness = pow (2, (-(interval-pwmIntervals)/ R)) - 1;
      // Set the LED output to the calculated brightness
  analogWrite(transistorPin, brightness);
     
    delay(1);                      //n is time to zero brightness
    }

    delay(20);             //n is time at zero brighness
}

Here is my implementation of 16-bit PWM on the UNO:

// Set 'TOP' for PWM resolution.  Assumes 16 MHz clock.
// const unsigned int TOP = 0xFFFF; // 16-bit resolution.   244 Hz PWM
// const unsigned int TOP = 0x7FFF; // 15-bit resolution.   488 Hz PWM
// const unsigned int TOP = 0x3FFF; // 14-bit resolution.   976 Hz PWM
// const unsigned int TOP = 0x1FFF; // 13-bit resolution.  1953 Hz PWM
// const unsigned int TOP = 0x0FFF; // 12-bit resolution.  3906 Hz PWM
// const unsigned int TOP = 0x07FF; // 11-bit resolution.  7812 Hz PWM
   const unsigned int TOP = 0x03FF; // 10-bit resolution. 15624 Hz PWM

void PWM16Begin() {
  // Stop Timer/Counter1
  TCCR1A = 0;  // Timer/Counter1 Control Register A
  TCCR1B = 0;  // Timer/Counter1 Control Register B
  TIMSK1 = 0;   // Timer/Counter1 Interrupt Mask Register
  TIFR1 = 0;   // Timer/Counter1 Interrupt Flag Register
  ICR1 = TOP;
  OCR1A = 0;  // Default to 0% PWM
  OCR1B = 0;  // Default to 0% PWM

  // Set clock prescale to 1 for maximum PWM frequency
  TCCR1B |= (1 << CS10);

  // Set to Timer/Counter1 to Waveform Generation Mode 14: Fast PWM with TOP set by ICR1
  TCCR1A |= (1 << WGM11);
  TCCR1B |= (1 << WGM13) | (1 << WGM12) ;
}

void PWM16EnableA() {
  // Enable Fast PWM on Pin 9: Set OC1A at BOTTOM and clear OC1A on OCR1A compare
  TCCR1A |= (1 << COM1A1);
  pinMode(9, OUTPUT);
}

void PWM16EnableB() {
  // Enable Fast PWM on Pin 10: Set OC1B at BOTTOM and clear OC1B on OCR1B compare
  TCCR1A |= (1 << COM1B1);
  pinMode(10, OUTPUT);
}

inline void PWM16A(unsigned int PWMValue) {
  OCR1A = constrain(PWMValue, 0, TOP);
}

inline void PWM16B(unsigned int PWMValue) {
  OCR1B = constrain(PWMValue, 0, TOP);
}


void setup() {
  Serial.begin(9600);
  PWM16Begin();
  
  // On the Arduino UNO T1A is Pin 9 and T1B is Pin 10
  
//  PWM16A(0);  // Set initial PWM value for Pin 9
//  PWM16EnableA();  // Turn PWM on for Pin 9

  PWM16B(0);  // Set initial PWM value for Pin 10
  PWM16EnableB();  // Turn PWM on for Pin 10
}

void loop() {
}

Thanks very much John, that's a big help. I'm not quite sure how to implement this for my application.
How do I input the PMW value that I need written - before the syntax was analogWrite(pin, value)

With this code do I need to input the value in the PWM16enableB() command?
What is the syntax for that?

Thanks again for sharing your code

See the examples in the setup() function.

PWM16Begin(): Set up Timer1 for PWM.
PWM16EnableA(): Start the PWM output on Pin 9
PWM16EnableB(): Start the PWM output on Pin 10
PWM16A(unsigned int value): Set the PWM value for Pin 9.
PWM16B(unsigned int value): Set the PWM value for Pin 10.

You can use either or both Pin 9 and Pin 10 for 16-bit PWM but you can't use one for 16-bit PWM and the other for analogWrite() because they use the same timer.