Last active
February 3, 2021 03:22
-
-
Save rubberduck203/f82305738879ae37b003079164c49fbc to your computer and use it in GitHub Desktop.
Sagemath/Jupyter notebook explaining the math for calculating microcontroller timers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Timers\n", | |
"\n", | |
"Every time I need to program a timer on a micro, I have to spend time looking up [this timer tutorial from the AVR Freaks forum](https://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-timers), so I want to take a moment to distill it into some simple, yet practical examples.\n", | |
"\n", | |
"## Resolution\n", | |
"\n", | |
"$$ Timer Resolution = (1 / Input Frequency) $$\n", | |
"\n", | |
"$$ Resolution = \\frac{1}{F_{cpu}} $$\n", | |
"\n", | |
"Let's assume the 16MHz clock of an Arduino UNO board." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"MHz = 1000000 * units.frequency.hertz\n", | |
"Fcpu = 16*MHz" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"Resolution = 1/Fcpu" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1/16000000/hertz" | |
] | |
}, | |
"execution_count": 4, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"Resolution" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1/16000000*second" | |
] | |
}, | |
"execution_count": 5, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(Resolution, units.time.second)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1/16*(micro*second)" | |
] | |
}, | |
"execution_count": 6, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"us = units.time.second * units.si_prefixes.micro\n", | |
"sage.symbolic.units.convert(Resolution, us)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"$$ \\frac{1}{16000000sec} * \\frac{1000ms}{1sec} * \\frac{1000us}{1ms} = \\frac{1}{16}us $$\n", | |
"\n", | |
"This means the default timer resolution of a micro with a 16MHz clock is 1/16th of a microsecond." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Calculating Target Ticks\n", | |
"\n", | |
"$$ Target Timer Count = (1 / Target Frequency) / (1 / Timer Clock Frequency) - 1 $$\n", | |
"\n", | |
"\n", | |
"$$ Ticks_{target} = \\frac{\\frac{1}{F_{target}}}{\\frac{1}{F_{cpu}}} - 1$$\n", | |
"$$ Ticks_{target} = \\left(\\frac{1}{F_{target}}\\right)\\left(\\frac{F_{cpu}}{1}\\right) -1 $$\n", | |
"$$ Ticks_{target} = \\frac{F_{cpu}}{F_{target}} - 1 $$\n", | |
"\n", | |
"Let's say we want a 1 sec period (1Hz frequency)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"Fcpu = 16 * units.frequency.hertz * units.si_prefixes.mega\n", | |
"Ftarget = 1 * units.frequency.hertz" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"16*mega" | |
] | |
}, | |
"execution_count": 8, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"Fcpu / Ftarget" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"16000000" | |
] | |
}, | |
"execution_count": 9, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"16*(10 ^ 6)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Resulting in a, rather obvious, 16,000,000 ticks per second." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"For a more interesting example, let's calculate a 100ms delay." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"10*hertz" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"ms = units.time.second * units.si_prefixes.milli\n", | |
"Ftarget = 1/(100*ms)\n", | |
"Ftarget = sage.symbolic.units.convert(Ftarget, units.frequency.hertz)\n", | |
"Ftarget" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"8/5*mega" | |
] | |
}, | |
"execution_count": 11, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"Fcpu / Ftarget" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1600000" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"8/5*(10 ^ 6)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Resulting in 1,600,000 ticks per 100ms." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Prescaling\n", | |
"\n", | |
"In an 8 bit timer, we only get 255 ticks before we overflow.\n", | |
"In a 16 bit timer, we get significantly more, 65,535 ticks before overflow,\n", | |
"but this still isn't enough to hold our 16,000,000 ticks for a 1Hz frequency.\n", | |
"\n", | |
"For these longer timeframes, we can use prescaling.\n", | |
"\n", | |
"The Atmega 328p on the UNO has several prescalers on it's 16 bit Timer1.\n", | |
"\n", | |
"| CS12 | CS11 | CS10 | Description |\n", | |
"| ---- | ---- | ---- | ---------------------------------------- |\n", | |
"|0 |0 |0 | No clock source (Timer/Counter stopped). |\n", | |
"|0 |0 |1 | clkI/O/1 (no prescaling) |\n", | |
"|0 |1 |0 | clkI/O/8 (from prescaler) |\n", | |
"|0 |1 |1 | clkI/O/64 (from prescaler) |\n", | |
"|1 |0 |0 | clkI/O/256 (from prescaler) |\n", | |
"|1 |0 |1 | clkI/O/1024 (from prescaler) |\n", | |
"\n", | |
"[Atmega 328p datasheet table 15-6 pg. 110](http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf)\n", | |
"\n", | |
"As you can see, having no prescaling is the same as dividing the clock source by `1`, so the formulas we used in the above sections were actually just simplified special cases of the full formulas that we'll cover now." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Prescaled Timer Resolution\n", | |
"\n", | |
"$$ Resolution = \\frac{1}{\\frac{F_{cpu}}{prescaler}} $$\n", | |
"\n", | |
"$$ Resolution = \\frac{prescaler}{F_{cpu}}$$\n", | |
"\n", | |
"Let's calculate the different possible resolutions at 16MHz." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1/16*(micro*second)" | |
] | |
}, | |
"execution_count": 13, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(1/Fcpu, us)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1/2*(micro*second)" | |
] | |
}, | |
"execution_count": 14, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(8/Fcpu, us)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"4*(micro*second)" | |
] | |
}, | |
"execution_count": 15, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(64/Fcpu, us)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"16*(micro*second)" | |
] | |
}, | |
"execution_count": 16, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(256/Fcpu, us)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"64*(micro*second)" | |
] | |
}, | |
"execution_count": 17, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(1024/Fcpu, us)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"### Calculating Target Ticks with a Prescaler\n", | |
"\n", | |
"$$ Ticks_{target} = \\frac{\\frac{1}{F_{target}}}{\\frac{Prescaler}{F_{cpu}}} - 1$$\n", | |
"\n", | |
"\n", | |
"$$ Ticks_{target} = \\frac{\\frac{F_{cpu}}{Prescaler}}{F_{target}} - 1$$\n", | |
"\n", | |
"Going back to our 1 second timer example, that wasn't actually possible because our 16bit timer would overflow before reaching the required 16,000,000 ticks that make up a second.\n", | |
"Now, let's use a prescaler to achieve it.\n", | |
"Remembering that the maximum value of a 16 bit register is 65,535, we can calculate a timer that _will_ work." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"Fcpu = 16 * units.frequency.hertz * units.si_prefixes.mega\n", | |
"Ftarget = 1 * units.frequency.hertz\n", | |
"Prescaler = 256" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1/16*mega - 1" | |
] | |
}, | |
"execution_count": 19, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"Fcpu/Prescaler/Ftarget - 1" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"62499" | |
] | |
}, | |
"execution_count": 20, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"1/16*(10^6) - 1" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"So, to achieve a 1Hz frequency (1 second period) with a 16MHz clock, we need to use a 1/256 prescaler and count to `62499`.\n", | |
"\n", | |
"This can be achieved precisely by using by using a `Compare Match` timer in `CTC` (clear timer counter) mode." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Overflow\n", | |
"\n", | |
"Sometimes you don't need a ton of precision for your counter, and it's easier to set up an overflow counter.\n", | |
"An overflow counter triggers the ISR (interupt service routine) whenever the register overflows.\n", | |
"For a 16 bit timer, this means the ISR is triggered when the count exceeds 65,535.\n", | |
"\n", | |
"The time to overflow can be calculated as follows.\n", | |
"\n", | |
"$$ Period = (OverflowValue)(Resolution) $$\n", | |
"\n", | |
"$$ Period = 65535 \\left(\\frac{Prescaler}{F_{cpu}}\\right) $$" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"16*(micro*second)" | |
] | |
}, | |
"execution_count": 21, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"Resolution = sage.symbolic.units.convert(256/Fcpu, us)\n", | |
"Resolution" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 26, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1048560*(micro*second)" | |
] | |
}, | |
"execution_count": 26, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"period = 65535 * Resolution\n", | |
"period" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"1.04856000000000*second" | |
] | |
}, | |
"execution_count": 27, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"sage.symbolic.units.convert(period, units.time.second) * 1.0" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "SageMath 8.2", | |
"language": "", | |
"name": "sagemath" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 2 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython2", | |
"version": "2.7.14" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment