Skip to content

Instantly share code, notes, and snippets.

@rubberduck203
Last active February 3, 2021 03:22
Show Gist options
  • Save rubberduck203/f82305738879ae37b003079164c49fbc to your computer and use it in GitHub Desktop.
Save rubberduck203/f82305738879ae37b003079164c49fbc to your computer and use it in GitHub Desktop.
Sagemath/Jupyter notebook explaining the math for calculating microcontroller timers
Display the source blob
Display the rendered blob
Raw
{
"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