Using Servo8Bit with attiny13's internal clock or alternative option

Hey
Im trying to control a servo with an attiny13. After much looking around, i found the Servo8Bit library.

The problem is, the attiny doesnt seem to have an internal clock of 8Mhz, instead the Arduino IDE (microcore attiny13 board) shows values of 9.6Mhz, 4.8Mhz, 1.2Mhz. This is the code i have written.

#include <avr/io.h>
#include <Servo8Bit.h>
Servo8Bit myServo;
void setup() {
  
  myServo.attach(1); //attach the servo to pin PB1
  
  DDRB &= ~(1 << DDB3);    //set PB3 as input
 
  DDRB &= ~(1 << DDB4);    //set PB4 as input
  //the inputs switch from low to high, so i decided to use physical pulldown resistors
  //PORTB |= (1 << PORTB3);  //activate internal pull-up resistor for PB3
  //PORTB |= (1 << PORTB4);  //activate internal pull-up resistor for PB4


}

void loop() {
  if(3 == LOW && 4 == LOW){  //no input, hold servo in 100
    myServo.write(100);
  }
  else if(3 == HIGH && 4 == LOW){ //pb3 high, move servo to 40
    myServo.write(40);
  }
  else if(3 == LOW && 4 == HIGH){ //pb4 high, move servo to 140
    myServo.write(140);
  }
}

The degree values are constant, which means i wont be changing them on the go. So another option would be to calculate and create pwm pulses manually for the 3 specefic degrees ive chosen.

But the confusion is, The attiny13 has very limited memory, so which option would be more viable. And if it is option 2, how would i go on to do that? also i cant find any clear explanation on how the pulse value for a specific degree is calculated.
Im using a sg90 servo.

This is the error code thats returned when compiling.

Arduino: 1.8.12 (Windows 10), Board: "ATtiny13, 9.6 MHz internal osc., BOD 2.7V, Micros disabled"

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:536:10: error: #error Unsupported clock speed. This code will only operate at 8Mhz or 1Mhz

         #error Unsupported clock speed. This code will only operate at 8Mhz or 1Mhz

          ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp: In static member function 'static void ServoSequencer::servoTimerSetup()':

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:458:5: error: 'TIMSK' was not declared in this scope

     TIMSK |= (1 << OCIEnx);

     ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:458:5: note: suggested alternative: 'GIMSK'

     TIMSK |= (1 << OCIEnx);

     ^~~~~

     GIMSK

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:69:21: error: 'OCIE1A' was not declared in this scope

     #define OCIEnx  OCIE1A

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:458:20: note: in expansion of macro 'OCIEnx'

     TIMSK |= (1 << OCIEnx);

                    ^~~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:69:21: note: suggested alternative: 'OCIE0A'

     #define OCIEnx  OCIE1A

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:458:20: note: in expansion of macro 'OCIEnx'

     TIMSK |= (1 << OCIEnx);

                    ^~~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:66:21: error: 'TCNT1' was not declared in this scope

     #define TCNTn   TCNT1

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:461:5: note: in expansion of macro 'TCNTn'

     TCNTn  = 0;

     ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:66:21: note: suggested alternative: 'TCNT0'

     #define TCNTn   TCNT1

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:461:5: note: in expansion of macro 'TCNTn'

     TCNTn  = 0;

     ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:67:21: error: 'OCR1A' was not declared in this scope

     #define OCRnx   OCR1A

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:463:5: note: in expansion of macro 'OCRnx'

     OCRnx = 255;

     ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:67:21: note: suggested alternative: 'OCR0A'

     #define OCRnx   OCR1A

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:463:5: note: in expansion of macro 'OCRnx'

     OCRnx = 255;

     ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp: In static member function 'static void ServoSequencer::setupTimerPrescaler()':

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:517:9: error: 'TCCR1' was not declared in this scope

         TCCR1 = 0;

         ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:517:9: note: suggested alternative: 'TCCR0A'

         TCCR1 = 0;

         ^~~~~

         TCCR0A

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp: In static member function 'static void ServoSequencer::timerCompareMatchISR()':

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:66:21: error: 'TCNT1' was not declared in this scope

     #define TCNTn   TCNT1

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:605:9: note: in expansion of macro 'TCNTn'

         TCNTn  = 0;

         ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:66:21: note: suggested alternative: 'TCNT0'

     #define TCNTn   TCNT1

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:605:9: note: in expansion of macro 'TCNTn'

         TCNTn  = 0;

         ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:67:21: error: 'OCR1A' was not declared in this scope

     #define OCRnx   OCR1A

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:607:9: note: in expansion of macro 'OCRnx'

         OCRnx = 64 - TRIM_DURATION; //trim off 4 ticks (32us), this is about the total combined time we spent inside this ISR;

         ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:67:21: note: suggested alternative: 'OCR0A'

     #define OCRnx   OCR1A

                     ^

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:607:9: note: in expansion of macro 'OCRnx'

         OCRnx = 64 - TRIM_DURATION; //trim off 4 ticks (32us), this is about the total combined time we spent inside this ISR;

         ^~~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:636:13: error: 'TIFR' was not declared in this scope

             TIFR = (1 << OCF0A);  // write logical 1 to the OCF0A flag to clear it

             ^~~~

C:\Program Files (x86)\Arduino\libraries\Servo8Bit\Servo8Bit.cpp:636:13: note: suggested alternative: 'GIFR'

             TIFR = (1 << OCF0A);  // write logical 1 to the OCF0A flag to clear it

             ^~~~

             GIFR

exit status 1
Error compiling for board ATtiny13.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

cant this part be modified so that “each timer tick be 8 microseconds” at 9.6 mhz?

#ifdef USE_TIMER0
        //reset the Timer Counter Control Register to its reset value
        TCCR0B = 0;

        #if F_CPU == 8000000L
            //set counter0 prescaler to 64
            //our FCLK is 8mhz so this makes each timer tick be 8 microseconds long
            TCCR0B &= ~(1<< CS02); //clear
            TCCR0B |=  (1<< CS01); //set
            TCCR0B |=  (1<< CS00); //set

        #elif F_CPU == 1000000L
            //set counter0 prescaler to 8
            //our F_CPU is 1mhz so this makes each timer tick be 8 microseconds long
            TCCR0B &= ~(1<< CS02); //clear
            TCCR0B |=  (1<< CS01); //set
            TCCR0B &= ~(1<< CS00); //clear
        #else
            //unsupported clock speed
        #error Unsupported clock speed. This code will only operate at 8Mhz or 1Mhz
        #endif
    #endif

Servo8Bit.cpp (34.8 KB)

anyone?

kaseftamjid:
also i cant find any clear explanation on how the pulse value for a specific degree is calculated.

I would think that this is easily found using google.
Servos expect a pulse of a specific duration. Typically, 1500ms correlates to a centered position. 1000 and 2000ms pulses correlate to full travel one way and the other.
Some servos can accept a pulse range wider than 1000-2000ms.
Some servos exhibit unexpected behavior when the pulse range exceeds what they expect.
Most servos have a total travel range of a bit less than 180 degrees.

Write a simple sketch that sends various duration pulses to the servo and see what it does.

Okay i did a lot more frustrated research and this is where i am right now.

#include <avr/io.h>
void setup(){
DDRB |= (1 << PB0);      //replaces pinMode(PB0, OUTPUT);

DDRB &= ~(1 << DDB3);    //set PB3 as input
 //[edit] The inputs are pulled low with 10Kohm resistors, due to the nature of the switch, pullup //resistors cant be used
DDRB &= ~(1 << DDB4);    //set PB4 as input
}
void loop()
{
 if(3 == LOW && 4 == LOW)
 {  //no input, hold servo in 100
  PORTB |= (1 << PB0); //digitalWrite High
  delayMicroseconds(1600);
  PORTB &= ~(1 << PB0);//digitalWrite Low
  delay(50); 
  }
  else if(3 == HIGH && 4 == LOW)
  { //pb3 high, move servo to 40
  PORTB |= (1 << PB0);//digitalWrite High
  delayMicroseconds(940);
  PORTB &= ~(1 << PB0);//digitalWrite Low
  delay(50);
  }
  else if(3 == LOW && 4 == HIGH)
  { //pb4 high, move servo to 140
  PORTB |= (1 << PB0);//dg.wr.High
  delayMicroseconds(2040);
  PORTB &= ~(1 << PB0);//dg.wr.Low
  delay(50);
  }
}

I tried this out on tinkercad but doesnt work at all…
TinkerCad link

I calculated the pulses for my sg90 using (angle*11)+500, i tested it out and it seems to be slightly off, but im okay with it.

But again, the attiny13 has so little memory. Should i?

You would be amazed at how much 1kb really is, i don’t see any issue with using millis(). Also there is as far as i know, no actual benefit for your memory usage in using direct port writes on an ATtiny (i did some experimentation and the conclusion was the difference almost nothing and not always positive, the compiler/optimizer does a really great job with them) only if you want to change a few pins at exactly the same time there is a use for it.
can you explain what this is supposed to do ?

if(3 == LOW && 4 == LOW)

neither of these conditions can ever be true (LOW == 0)
If you use notepad++ (or another editor, but notepad++ is really good) to open the Servo.h library that is built in with the IDE for atmega boards, and look up the write() function, you will see that it calculates for 180 degrees a pulse with a length between 544 - 2500 us and for the sg90 that is quite accurate, you may find that because the ATtiny13 clock is a bit quicker than it should be, you may not make the pulse as short as 544us (when the pulse is to short, the servo responds unpredictably).
My advice is anyway to use micros() to send the pulse but in a blocking way, just test micros() all the time, but only use it 16-bit (that does save space)

uint16_t moment = micros();
while ((uint16_t) micros() - moment < timespan) moment = micros();

OOps i made a giant goof here ! the second line should be just while ((uint16_t) micros() - moment < timespan) ;
Actually if you don’t want your delayMicroseconds() to be known at compile time you don’t have another choice, delayMicroseconds() only takes literals) and use 16-bit millis() for the (20ms - pulse minimum) latch but during that time also ‘poll’ the buttons.

Deva_Rishi:
You would be amazed at how much 1kb really is, i don’t see any issue with using millis(). Also there is as far as i know, no actual benefit for your memory usage in using direct port writes on an ATtiny (i did some experimentation and the conclusion was the difference almost nothing and not always positive, the compiler/optimizer does a really great job with them) only if you want to change a few pins at exactly the same time there is a use for it.
can you explain what this is supposed to do ? if(3 == LOW && 4 == LOW) neither of these conditions can ever be true (LOW == 0)
If you use notepad++ (or another editor, but notepad++ is really good) to open the Servo.h library that is built in with the IDE for atmega boards, and look up the write() function, you will see that it calculates for 180 degrees a pulse with a length between 544 - 2500 us and for the sg90 that is quite accurate, you may find that because the ATtiny13 clock is a bit quicker than it should be, you may not make the pulse as short as 544us (when the pulse is to short, the servo responds unpredictably).
My advice is anyway to use micros() to send the pulse but in a blocking way, just test micros() all the time, but only use it 16-bit (that does save space)

uint16_t moment = micros();

while ((uint16_t) micros() - moment < timespan) moment = micros();


Actually if you don't want your delayMicroseconds() to be known at compile time you don't have another choice, delayMicroseconds() only takes literals) and use 16-bit millis() for the (20ms - pulse minimum) latch but during that time also 'poll' the buttons.

Im sorry i may have edited the previous post without thinking much. Yes, i am surprised! The current code shows to be just 46bytes! 46-frickin-bytes!
I decided to erase the previous post because i remembered," A servo needs a pulse every 20ms or so, with the pulse width determining the position of the head" so in that case switching to another function/angle before that 20ms gap makes no sense, the servo would understand nothing( i guess).

Deva_Rishi:
can you explain what this is supposed to do ? if(3 == LOW && 4 == LOW) neither of these conditions can ever be true (LOW == 0)

Why? the pins are pulled to ground via 10Kohm resistors… I think i also forgot to add this.

I tested out the microsecond pulses with the servo.h library over serial, and they seem to work fine, i didnt see any unpredictable behaviour. there is also a possibility because i just checked them three to four times.

I saw that the attiny13 clock is a bit awkward at 9.6Mhz, which basically blocks me from using any library i know of. The prescaler cant help and im locked down at home without any access to buying a 8mhz oscillator.

Im really really REALLY new to this coding stuff. This short mess of a code took me 4 straight hours to put together. So micros and the non blocking delay stuff seems like a beast to handle.

forgive my insolence, but doesnt the attiny13 have only one 8 bit timer? is it related to a 16 bit milis?

lastly, i feel like i should mention this, I tested the servo with the timings with a nodemcu, not the attiny itself. I would prefer to program the attiny when the code ‘seems’ to have no errors.

Why? the pins are pulled to ground via 10Kohm resistors… I think i also forgot to add this.

You are not checking the pins ! you need digitalRead() for that (or a direct portread but as i said they don’t give you any much in the way of memory, though for a double digitalRead() there might actually be a significant advantage, though combining both methods is using excess memory.
I referred to the write() function only for the sake of showing the calculation, nothing else, i advocate just calculation the length and then sending the pulse manually. Set the pin high for anything between (544 & 2500us) and then keeping it low for about 20ms. I would not use the timer on the ATtiny.

attiny13 clock is a bit awkward at 9.6Mhz,

i think mine run at approx 12Mhx i have no idea where you get the 9.6hz from.

, I tested the servo with the timings with a nodemcu,

Yes well i meant the inaccuraciy of the ATtint clock may result in the pulse being less than 544us even if you define the pulse for that length (the clock may be as much as 5% off, though usually not, and all the timing functions are counted clockcycles based on the assumed clock speed (which btw you can redefine to tune the timing of the chip) so if the CPU runs quicker than assumed, the delay() and delayMicroseconds() would than hold the program up for less time in reality.

So micros and the non blocking delay stuff seems like a beast to handle.

The concept is not hugely complex, you should be able to work it out. The direct-port writes are in a way harder (and probably the reason why it took so long) if(3 == LOW && 4 == LOW)actually the compiler would have removed this and any code that came after, you code will grow beyond those 46 bytes, anything that is not used gets optimized away. So looking at all 3 if statements, none of them could be true ! 3 iis just not equal to 0, and so none of the code gets compiled, other than the direct port writes. If you would have used pinMode they would have been optimized out as well. Your code doesn’t actually do anything. just use digitalRead(3) if you want the pinState.
Actually just read pin 3 like this and store it in a variable and the same for pin 4. Then compare those variables.
Create a function for sending the pulse.

I have a version that compiles to 604 bytes, but it still uses the map function so it could be done with less, but it is for sure an easy fit.

Deva_Rishi:
I have a version that compiles to 604 bytes, but it still uses the map function so it could be done with less, but it is for sure an easy fit.

Could you please post it here?

Deva_Rishi:
You are not checking the pins ! you need digitalRead() for that (or a direct portread but as i said they don't give you any much in the way of memory, though for a double digitalRead() there might actually be a significant advantage, though combining both methods is using excess memory.
I referred to the write() function only for the sake of showing the calculation, nothing else, i advocate just calculation the length and then sending the pulse manually. Set the pin high for anything between (544 & 2500us) and then keeping it low for about 20ms. I would not use the timer on the ATtiny.i think mine run at approx 12Mhx i have no idea where you get the 9.6hz from.Yes well i meant the inaccuraciy of the ATtint clock may result in the pulse being less than 544us even if you define the pulse for that length (the clock may be as much as 5% off, though usually not.

I use the microcore and also the datasheet refers that the attiny13 runs at 9.6Mhz with prescaler set to 1. Even in the ide, there is only options for non integer clock speeds without extra component.

Should i try out the digital write function? I saw that its significantly slower than port manipulation.
Can i use a library for this? Digitalwritefast seemed an option, but i have my doubts.

Should i try out the digital write function? I saw that its significantly slower than port manipulation.

As a beginner i suggest you keep things simple. Use pinMode(), digitalRead() & digitalWrite()
Speed is not your issue and with that core the differences are marginal. Only if you write or read to more pins at once (which you actually do) there is significant difference, in space and/or time, but it is an easy fit don’t bother about it.

This seems to work on tinkercad, time for the hardware!! ill get back with how it went.

#define F_CPU 9600000
#include <avr/io.h>
void setup(){
pinMode(PB0, OUTPUT);
pinMode(PB3, INPUT);
pinMode(PB4, INPUT);

}
void loop()
{
 if ((digitalRead(PB3) == LOW) && (digitalRead(PB4) == LOW))
 
 //if(digitalRead ,PB3) && (digitalRead ,PB4 == LOW)
 {  //no input, hold servo in 100
  digitalWrite(PB0 ,HIGH);
  delayMicroseconds(1600);
  digitalWrite(PB0 ,LOW);
  delay(20); 
  }
  else if((digitalRead(PB3) == HIGH) && (digitalRead(PB4) == LOW))
  { //pb3 high, move servo to 40
  digitalWrite(PB0 ,HIGH);
  delayMicroseconds(940);
  digitalWrite(PB0 ,LOW);
  delay(20);
  }
  else if((digitalRead(PB3) == LOW) && (digitalRead(PB4) == HIGH))
  { //pb4 high, move servo to 140
  digitalWrite(PB0 ,HIGH);
  delayMicroseconds(2040);
  digitalWrite(PB0 ,LOW);
  delay(20);
  }
}

This seems to work

yep it should do !#include <avr/io.h>is no longer required.