Hello!
I've been working on a program for an ATTINY85 that supplies a 20kHz pulse through the main loop by toggling a pin's state at a predefined time interval. That part works well.
This program also contains a modified SPI routine that was inspired by one of nickgammon's old posts (thank you for being awesome!). This is for sending two bytes over to a pair of series shift registers that controls 4 segment displays.
When the program is busy sending over the serial bits it holds up the pulse routine in the main loop causing my 20kHz signal to go down to ~6.5kHz.
So my question is, what is the best way for my code to pause what's it's doing in the SPI routine, toggle the pulse pin on time and then continue the SPI routine where it left off.
I can probably get around this by updating the timer variable and copying the if statement & toggle routine in between each command outside of the main loop but find this "are we there yet" method rather counterintuitive.
Figured I should post a topic as google tends to be a gauntlet of misdirection.
johnwasser,
First off thanks for chiming in. You've helped me a couple of times over the years posting here.
2nd, you tell me? lol
Can I set a pin as a stable clock source all the while running other code simply by using Timer1?
Right now it simply toggles a pin's state by updating a time variable in the main loop.
I found references to the compare match function for timer0 and read through the datasheet however the formula on page 72 was not providing predicable results - I must be missing something. I did adjust the prescaler and play with the OCR0A value and was able to get close to my target frequency.
Now I have a stable clock source while running the software SPI with no problem.
Warning: I expect that Timer0 is used by the Arduino code to maintain the millis() and micros() timers. If you muck with it then any Arduino code, like libraries, that use millis() or micros() for timing will not work properly. That is why I recommended using Timer1.
I appreciate the warning. I did consider that implication but changing the clock to a compare match routine I no longer need to track time - which was my previous method of toggling a pin state for a specified frequency.
Here's a simple version of my code - leaving out the parts for the intended circuit. This outputs a ~10kHz signal on PB0 while controlling shift registers (in series) to drive 4 common anode segment displays. I've included an interrupt to count the compare match actuations - which will be for the intended use of this project.
I know it's not helpful without the schematic of what's happening on the breadboard but figured it would help others searching the web.
This is my first time playing with the registers as well as utilizing interrupts. So far I'm quite impressed with what this ATTINY85 can do.
did you undestand: using timer0 for your own pruposes will DISable the function of millis() and micros()
There a lot of libraries that make use of millis() or micros() inside the library and these libraies would malfunction or not work anymore. What would be the problem to simply use timer1 or timer2??
StefanL38, yes... as John had already implied.
You may also see in my code that I'm not using any libraries - let alone anything that uses timer0 outside of the compare & match.
If I'm mistaken please let me know. I am still learning and may be unaware of things - I'm still confused on why the formula on page 72 of the datasheet isn't producing the expecting frequency on PB0 when I use the suggested values & prescaler.
In effort to better understanding choosing prescalers & a value for OCR0A I had searched around and found an answer by nickgammon where he provided this formula;
MCUfreq / prescaler / OCR0A / 2 = desired frequency. Or as he wrote it on this page; "8000000 / 1024 / 128 / 2 = 30.5"
So in that respect, at 16MHz with a prescaler of 256 and OCR0A set to 2
16000000 / 256 / 2 / 2 = 15,265.
I'm getting ~10.6kHz and do not know why.
Am I including a function in my code that is also using timer0?
I suppose I could try to switch it to timer1 and see what happens.
Also, there is no timer2 on the ATtiny85. But if you look at the datasheet you may see that timer1 is a bit different than timer0. I suppose that's why I didn't think to change it after finding a solution.
here is a demo-code that uses timer2 on unos
I don't know if the atTiny85-timer1 uses the same control-registers as an an Uno Timer1
anyway here it is
It calculates the register settings for a given frequency
// this demo-code belongs to the public domain
// this code demonstrates how to blink the onboard-LED
// of an Arduino-Uno (connected to IO-pin 13)
// in a nonblocking way using timing based on function millis()
// and creating a pwm-signal "in the backround"
// through setting up a timer-interrupt through configuring timer2
// A T T E N T I O N ! R E M A R K
// some other libraries that make use of timer2 may conflict with this
// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope
#define dbgi(myFixedText, variableName,timeInterval) \
do { \
static unsigned long intervalStartTime; \
if ( millis() - intervalStartTime >= timeInterval ){ \
intervalStartTime = millis(); \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName); \
} \
} while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
void PrintFileNameDateTime() {
Serial.println( F("Code running comes from file ") );
Serial.println( F(__FILE__));
Serial.print( F(" compiled ") );
Serial.print(F(__DATE__));
Serial.print( F(" ") );
Serial.println(F(__TIME__));
}
boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - periodStartTime >= TimePeriod )
{
periodStartTime = currentMillis; // set new expireTime
return true; // more time than TimePeriod) has elapsed since last time if-condition was true
}
else return false; // not expired
}
unsigned long MyTestTimer = 0; // variables MUST be of type unsigned long
const byte OnBoard_LED = 13;
void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
static unsigned long MyBlinkTimer;
pinMode(IO_Pin, OUTPUT);
if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
}
}
const unsigned long pwmBaseFrequency = 16800;
const unsigned long pulseFreq = 280;
volatile unsigned long pwmCounter = 0;
volatile unsigned long periodCount = pwmBaseFrequency / pulseFreq;
volatile unsigned long duty80 = (periodCount * 4) / 5;
volatile unsigned long duty25 = (periodCount * 1) / 4;
volatile unsigned long duty50 = (periodCount * 1) / 2 ;
void setupTimerInterrupt(unsigned long ISR_call_frequency) {
const byte Prescaler___8 = (1 << CS21);
const byte Prescaler__32 = (1 << CS21) + (1 << CS20);
const byte Prescaler__64 = (1 << CS22);
const byte Prescaler_128 = (1 << CS22) + (1 << CS20);
const byte Prescaler_256 = (1 << CS22) + (1 << CS21);
const byte Prescaler1024 = (1 << CS22) + (1 << CS21) + (1 << CS20);
const unsigned long CPU_Clock = 16000000;
const byte toggleFactor = 1;
unsigned long OCR2A_value;
cli();//stop interrupts
TCCR2A = 0;// set entire TCCR2A register to 0
TCCR2B = 0;// same for TCCR2B
TCNT2 = 0;//initialize counter value to 0
TCCR2A |= (1 << WGM21); // turn on CTC mode
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
// the prescaler must be setup to a value that the calculation
// of the value for OCR2A is below 256
TCCR2B = Prescaler___8;
OCR2A_value = (CPU_Clock / ( 8 * ISR_call_frequency * toggleFactor) ) - 1;
dbg("1 setup: timer ", OCR2A_value);
if (OCR2A_value > 256) { // if value too big
TCCR2B = Prescaler__32; // set higher prescaler
OCR2A_value = (CPU_Clock / ( 32 * ISR_call_frequency * toggleFactor) ) - 1;
dbg("setup: prescaler 32", OCR2A_value);
}
if (OCR2A_value > 256) { // if value too big
TCCR2B = Prescaler__64;// set higher prescaler
OCR2A_value = (CPU_Clock / ( 64 * ISR_call_frequency * toggleFactor) ) - 1;
dbg("setup: prescaler 64", OCR2A_value);
}
if (OCR2A_value > 256) { // if value too big
TCCR2B = Prescaler_128;// set higher prescaler
OCR2A_value = (CPU_Clock / ( 128 * ISR_call_frequency * toggleFactor) ) - 1;
dbg("setup: prescaler 128", OCR2A_value);
}
if (OCR2A_value > 256) { // if value too big
TCCR2B = Prescaler_256; // set higher prescaler
OCR2A_value = (CPU_Clock / ( 256 * ISR_call_frequency * toggleFactor) ) - 1;
dbg("setup: prescaler 256", OCR2A_value);
}
if (OCR2A_value > 256) { // if value too big
TCCR2B = Prescaler1024; // set higher prescaler
OCR2A_value = (CPU_Clock / ( 1024 * ISR_call_frequency * toggleFactor) ) - 1;
dbg("setup: prescaler 1024", OCR2A_value);
}
OCR2A = OCR2A_value; // finally set the value of OCR2A
sei();//allow interrupts
dbg("setup: timer done", OCR2A_value);
dbg("setup",periodCount);
dbg("setup",duty50);
dbg("setup",duty80);
dbg("setup",duty25);
}
unsigned long pwmCount = 0;
const byte PWM_Pin = 4;
const byte Clock_Pin = 5;
void setup() {
Serial.begin(115200);
Serial.println( F("Setup-Start") );
PrintFileNameDateTime();
pinMode(PWM_Pin, OUTPUT);
pinMode(Clock_Pin, OUTPUT);
setupTimerInterrupt(pwmBaseFrequency);
pwmCount = 0;
}
const byte sc_50percentDuty = 1;
const byte sc_80percentDuty = 2;
const byte sc_25percentDuty = 3;
byte dutyState = sc_50percentDuty;
byte pulseCount = 0;
volatile byte pwmState;
void flap() {
pwmCount++;
digitalWrite(Clock_Pin, !digitalRead(Clock_Pin) );
if (pwmCount == periodCount) {
pwmCount = 0;
digitalWrite(PWM_Pin, !digitalRead(PWM_Pin) );
}
switch (dutyState) {
case sc_50percentDuty:
if (pwmCount == duty50) { // if end of HIGH-time reached
digitalWrite(PWM_Pin, LOW); // set LOW
pulseCount++;
if (pulseCount == 11) {
pulseCount = 0;
dutyState = sc_80percentDuty;
}
}
break;
case sc_80percentDuty:
if (pwmCount == duty80) { // if end of HIGH-time reached
digitalWrite(PWM_Pin, LOW); // set LOW
pulseCount++;
if (pulseCount == 11) {
pulseCount = 0;
dutyState = sc_25percentDuty;
}
}
break;
case sc_25percentDuty:
if (pwmCount == duty25) { // if end of HIGH-time reached
digitalWrite(PWM_Pin, LOW); // set LOW
pulseCount++;
if (pulseCount == 10) {
pulseCount = 0;
dutyState = sc_50percentDuty;
}
}
break;
}
}
ISR(TIMER2_COMPA_vect) {
flap();
}
void loop() {
BlinkHeartBeatLED(OnBoard_LED, 200);
}
Why does your example seem overcomplicated?
I appreciate it anyways. I'll scan through it later - however at a quick glance it doesn't help my current situation at all.
I've updated the title of my thread according to the direction things are going.
it uses a timer-interrupt. it is setting up a timer-interrupt and with calling the isr at a frequency of 40 kHz and toggling an IO-pin would produce a 20 kHz clock-signal
As long as you don't disable interrupts the ISR will be called regardless of what the rest of the code is doing
Ah you're totally right, John. I forgot about the +1 for the OCRA0 value when using the formula - it was a late night.
Back at it looking at my scope & frequency counter(multimeter) now, I'm actually getting 10.85kHz.
I would assume it's simply a matter of a poorly tuned internal oscillator but when I was using the time data within the loop (before using compare match) I was getting more accurate results. So I'm wondering if something else is going on that is causing the discrepancy.
High Frequency PLL Clock
There is an internal PLL that provides nominally 64 MHz clock rate locked to the RC Oscillator for the use of the Peripheral Timer/Counter1 and for the system clock source. When selected as a system clock source, by programming the CKSEL fuses to ‘0001’, it is divided by four like shown in Table 6-2
I have the option of using a 1MHz, 8MHz or 16MHz internal oscillator. Out of curiousity I did burn the bootloader at 8MHz and it did as expected - halfing the frequency.