ATtiny85 Timer1 TCCR1 CS[13:10] Can't Control PWM Frequency

I have a confusion with clock sources / speeds and timers - I’m fairly new to PIO and Arduino.
I am using an ATtiny85.
I have the Arduino framework loaded so as to initialise stuff like millis() - I appreciate this uses Timer0. - I’ll need this for some debounce work later.
I want to use the SendOnlySoftwareSerial.h library for debug.
My problem is that I can only seem to get 2 PWM frequencies whatever the setting of the CS[13:10] bits in TCCR1 - either way too fast 245Hz or super slow at 1Hz.
I want to generate an inverted (I’ll be using P-MOSFET buffer) 50Hz PWM signal for a servo.
I can ‘fine tune’ the frequency by reducing the value of OCR1C down from 0xFF but starting at 1Hz, I can’t get an accurate 50Hz frequency and still have the necessary control of the pulse width using OCR1A.
Please see my code, PlatformIO.ini and some debug TTY output.

#ifndef F_CPU
#define F_CPU 8000000 
#endif

#include <Arduino.h>
#include <SendOnlySoftwareSerial.h>
#include <util/delay.h>
#include <avr/io.h>			    // pulls in (most of) the register and register bit names
#include <avr/interrupt.h>	// so we can use interrupt vector names
#include <avr/sleep.h>		  // useful macros for putting the unit into sleep mode
#include <avr/wdt.h>		    // I changed the WDT so I can use it for measuring user inactivity
#include <avr/common.h>		  // added so SREG is a recognized register name - seems to be mission from <avr/io.h>

#define BAUD 9600                      
#define OCR1A_open 0x0F                 // PWM signal at 2ms
#define OCR1A_closed 0x07               // PWM signal at 1ms
//#define T1_CS13_CS10 (1<<CS13)|(1<<CS12)|(1<<CS11)|(1<<CS10) // 1Hz PWM on OC1A
//#define T1_CS13_CS10 (1<<CS13)|(0<<CS12)|(0<<CS11)|(0<<CS10) // 1Hz PWM on OC1A
//#define T1_CS13_CS10 (0<<CS13)|(1<<CS12)|(1<<CS11)|(1<<CS10) // 245Hz PWM on OC1A
//#define T1_CS13_CS10 (0<<CS13)|(1<<CS12)|(0<<CS11)|(0<<CS10) // 245Hz PWM on OC1A
#define T1_CS13_CS10 (0<<CS13)|(0<<CS12)|(0<<CS11)|(1<<CS10) // 245Hz PWM on OC1A

/*                                ATtiny85
        PCINT5/-RESET/ACD0/dw PB5 1| u |8 VCC
 PCINT3/XTAL1/CLKI/-OC1B/ADC3 PB3 2|   |7 PB2 SCK/USCK/SCL/ADC1/T0/INT0/PCINT2
  PCINT4/XTAL2/CLKO/OC1B/ADC2 PB4 3|   |6 PB1 MISO/DO/AIN1/OC0B/OC1A/PCINT1
                              GND 4|___|5 PB0 MOSI/DI/SDA/AIN0/OC0A/-OC1A/AREF/PCINT0

 PB0 - TTY        (output)
 PB1 - PWM        (output)
 PB2 - KEY        (input)
 PB3 - LDR        (input)
 PB4 - ENA        (output)
 PB5 - RESET      (input)
*/
#define TTY PB0
#define PWM PB1
#define KEY PB2
#define LDR PB3
#define ENA PB4
#define RESET PB5

SendOnlySoftwareSerial mySerial (TTY);  // Tx pin

void setup() {
  // put your setup code here, to run once:
  
  DDRB |= (1<<PWM)|(1<<TTY)|(1<<ENA);       // define outputs
  mySerial.begin(BAUD*2);                   // why do I need to do this?
  mySerial.println(F("\n\r\n\rStarting"));
  mySerial.println(F_CPU);                  // just to check what it's set to
  
  // PWM using Timer0 on OC0B - but can't use this as I need millis() which uses Timer0
  //OCR0A = 0x9C;  //0x9C = 50Hz
  //OCR0B = 20;
  //TCCR0A |= (0<<COM0A1)|(0<<COM0A0)|(1<<COM0B1)|(1<<COM0B0)|(1<<WGM01)|(1<<WGM00);
  //TCCR0B |= (1<<WGM02)|(1<<CS02)|(0<<CS01)|(1<<CS00);

  // PWM using Timer1 (inverted only on OC1A - PB1)
  //PLLCSR &= ~(1<<PCKE); // clearing this bit to force Synch clocking makes no difference
  OCR1A = 0x09; // sets the duty cycle
  OCR1C = 0xFF; // sets the TOP value ie frequency of PWM signal
  TCCR1 |= (1<<PWM1A)|(1<<COM1A1)|(1<<COM1A0)|T1_CS13_CS10;
}

void loop() {
  // put your main code here, to run repeatedly:
  static unsigned long myMillis = millis();
  myMillis = millis();
  
  static unsigned long counter = 0;
  counter ++;
  
  mySerial.print((counter));
  mySerial.print(F(" "));
  mySerial.println((myMillis));
  
  //delay(500);         
  _delay_ms(500);
  PORTB ^= (1<<ENA); // debug toggle an LED
}

See commented lines of #define T1_CS13_CS10 showing what values I used and the (lack of) change they made to the base PWM output frequency (My scope shows an inverted PWM signal on PB1)

I tried clearing the PCKE bit in the PLLCSR register to force synch clocking PLLCSR &= ~(1<<PCKE); This had no effect.

PlatformIO.ini

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:attiny85]
platform = atmelavr
board = attiny85
framework = arduino

; device is Pololu USBAVR Programmert v2.1
;upload_protocol = custom
;upload_port = COM5
;upload_flags =
;    -C
;    ; use "tool-avrdude-megaavr" for the atmelmegaavr platform
;    ${platformio.packages_dir}/tool-avrdude/avrdude.conf
;    -p
;    $BOARD_MCU
;    -P
;    $UPLOAD_PORT
;    -c
;    stk500v2
;upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i

; device is USBASP V2.0 with updated firmware
upload_protocol = custom
upload_port = usb
upload_flags =
    -C
    ; use "tool-avrdude-megaavr" for the atmelmegaavr platform
    ${platformio.packages_dir}/tool-avrdude/avrdude.conf
    -p
    $BOARD_MCU
    -P
    $UPLOAD_PORT
    -c
    usbasp
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i

; monitor set up
; You still need a TTY to USB convertor board
monitor_port = COM[3]
monitor_speed = 9600

TTY debug output

Starting
8000000
1 12
2 518
3 1026
4 1533
5 2041
6 2549

This confirms F_CPU is at 8000000. It also indicates that _delay_ms(500); is accurately timed. When I used the Arduino delay(500); this also generated an accurately timed delay when I tested this in code.

Here’s the PIO Upload dialog

*  Executing task in folder MorseCode: C:\Users\Peter\.platformio\penv\Scripts\platformio.exe run --target upload 

Processing attiny85 (platform: atmelavr; board: attiny85; framework: arduino)
----------------------------------------------------------------------------------------------------------------------------------Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/attiny85.html
PLATFORM: Atmel AVR (3.4.0) > Generic ATtiny85
HARDWARE: ATTINY85 8MHz, 512B RAM, 8KB Flash
DEBUG: Current (simavr) On-board (simavr)
PACKAGES:
 - framework-arduino-avr-attiny @ 1.5.2
 - tool-avrdude @ 1.60300.200527 (6.3.0)
 - toolchain-atmelavr @ 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 10 compatible libraries
Scanning dependencies...
Dependency Graph
|-- SendOnlySoftwareSerial
Building in release mode
Compiling .pio\build\attiny85\src\main.cpp.o
Linking .pio\build\attiny85\firmware.elf
Checking size .pio\build\attiny85\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [=         ]  12.1% (used 62 bytes from 512 bytes)
Flash: [==        ]  16.0% (used 1314 bytes from 8192 bytes)
Building .pio\build\attiny85\firmware.hex
Configuring upload protocol...
AVAILABLE: custom
CURRENT: upload_protocol = custom
Uploading .pio\build\attiny85\firmware.hex

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e930b (probably t85)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file ".pio\build\attiny85\firmware.hex"
avrdude: writing flash (1314 bytes):

Writing | ################################################## | 100% 0.70s

avrdude: 1314 bytes of flash written
avrdude: verifying flash memory against .pio\build\attiny85\firmware.hex:
avrdude: load data flash data from input file .pio\build\attiny85\firmware.hex:
avrdude: input file .pio\build\attiny85\firmware.hex contains 1314 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.36s

avrdude: verifying ...
avrdude: 1314 bytes of flash verified

avrdude: safemode: Fuses OK (E:FE, H:DF, L:E2)

avrdude done.  Thank you.

================================================== [SUCCESS] Took 5.30 seconds ==================================================
 *  Terminal will be reused by tasks, press any key to close it. 

There must be something in the Arduino environment or something else I’m missing that is preventing me from generating a relatively slow PWM using Timer1 on OC1A.

I tried redefining the TCCR1 CS[13:10] bit values after every print statement just in case this was changing the timer1 settings - it made no difference.

I tried commenting out all the SendOnlySoftwareSerial statements just in case this library is changing Timer1 - this made no difference.

Are you able to aim me in the right direction?

Best regards

I have a solution - having read an Arduino blog at <(https://forum.arduino.cc/t/attiny85-fast-pwm-on-timer-1/993958/7)> someone had the same issue and resolved it by switching clock source to Asynchronous. It worked for me too but I’d still like to understand why I couldn’t use the synchronous clock. Any thoughts?

My modified (working) code is now:

#ifndef F_CPU
#define F_CPU 8000000 
#endif

#include <Arduino.h>
#include <SendOnlySoftwareSerial.h>
#include <util/delay.h>
#include <avr/io.h>			    // pulls in (most of) the register and register bit names
#include <avr/interrupt.h>	// so we can use interrupt vector names
#include <avr/sleep.h>		  // useful macros for putting the unit into sleep mode
#include <avr/wdt.h>		    // I changed the WDT so I can use it for measuring user inactivity
#include <avr/common.h>		  // added so SREG is a recognized register name - seems to be mission from <avr/io.h>

#define BAUD 9600                      
#define OCR1A_open 0x07                 // PWM signal at 2ms
#define OCR1A_closed 0x03               // PWM signal at 1ms

/*                                ATtiny85
        PCINT5/-RESET/ACD0/dw PB5 1| u |8 VCC
 PCINT3/XTAL1/CLKI/-OC1B/ADC3 PB3 2|   |7 PB2 SCK/USCK/SCL/ADC1/T0/INT0/PCINT2
  PCINT4/XTAL2/CLKO/OC1B/ADC2 PB4 3|   |6 PB1 MISO/DO/AIN1/OC0B/OC1A/PCINT1
                              GND 4|___|5 PB0 MOSI/DI/SDA/AIN0/OC0A/-OC1A/AREF/PCINT0

 PB0 - TTY        (output)
 PB1 - PWM        (output)
 PB2 - KEY        (input)
 PB3 - LDR        (input)
 PB4 - ENA        (output)
 PB5 - RESET      (input)
*/
#define TTY PB0
#define PWM PB1
#define KEY PB2
#define LDR PB3
#define ENA PB4
#define RESET PB5

SendOnlySoftwareSerial mySerial (TTY);  // Tx pin

void setup() {
  // put your setup code here, to run once:
  
  DDRB |= (1<<PWM)|(1<<TTY)|(1<<ENA);       // define outputs
  mySerial.begin(BAUD*2);                   // why do I need to do this?
  mySerial.println(F("\n\r\n\rStarting"));
  mySerial.println(F_CPU);                  // just to check what it's set to
  
    // PWM using Timer1 (inverted only on OC1A - PB1)
  PLLCSR |= (1<<PLLE);                  // enable PLL
  while ((PLLCSR & (1<<PLOCK)) == 0x00) // wait until PLL is locked
  PLLCSR |= (1<<PCKE);                  // enable asynchronous mode
  
  OCR1A = OCR1A_closed; // sets the duty cycle
  OCR1C = 0x4D; // sets the TOP value ie frequency of PWM signal at 50Hz
  TCCR1 |= (1<<PWM1A)|(1<<COM1A1)|(1<<COM1A0)|(1<<CS13)|(1<<CS12)|(1<<CS11)|(1<<CS10);
}

void loop() {
  // put your main code here, to run repeatedly:
  static unsigned long myMillis = millis();
  myMillis = millis();
  
  static unsigned long counter = 0;
  counter ++;
  
  mySerial.print((counter));
  mySerial.print(F(" "));
  mySerial.println((myMillis));
  
  //delay(500);         
  _delay_ms(500);
  PORTB ^= (1<<ENA); // debug toggle an LED
}

But I still don’t understand.
Now I get 50Hz PWM frequency with either of the following values for CS[13:10]

  // PWM using Timer1 (inverted only on OC1A - PB1)
  PLLCSR |= (1<<PLLE);                  // enable PLL
  while ((PLLCSR & (1<<PLOCK)) == 0x00) // wait until PLL is locked
  PLLCSR |= (1<<PCKE);                  // enable asynchrounous mode
  
  OCR1A = OCR1A_closed;                 // sets the duty cycle
  OCR1C = 0x4D;                         // sets the TOP value ie frequency of PWM signal at 50Hz
  //TCCR1 |= (1<<PWM1A)|(1<<COM1A1)|(1<<COM1A0)|(1<<CS13)|(1<<CS12)|(1<<CS11)|(1<<CS10);
  TCCR1 |= (1<<PWM1A)|(1<<COM1A1)|(1<<COM1A0)|(1<<CS13)|(0<<CS12)|(0<<CS11)|(0<<CS10);

Could you explain what is going on?

Ah Ha

The following lines make a difference.

TCCR1 = (1<<PWM1A)|(1<<COM1A1)|(1<<COM1A0)|(1<<CS13)|(1<<CS12)|(1<<CS11)|(1<<CS10);
  //TCCR1 = (1<<PWM1A)|(1<<COM1A1)|(1<<COM1A0)|(1<<CS13)|(0<<CS12)|(0<<CS11)|(0<<CS10);
}

So some other process is setting CS[13:10] values hence my ‘for comment purposes’ ORing in a 0 didn’t clear the 1 that was there. You live and learn - then forget.

I’ll revisit the synchronous timings next…

Yup - that’s it - some other Arduino process is setting bit values in CS[13:10] so assuming they are all zero and ORing in 1s (and 0s for commenting purposes) just won’t hack it.

I need to mask out the bits prior to setting (or not) or just explicitly set the entire register value.

1 Like