190 lines
7.1 KiB

TIMDAC is a public-domain, cheap, precision DAC, comprising a hardware
reference design and a firmware module.
## Quick links
- [Reference hardware implementation, bipolar and up to 8 channels](doc/Ref-good/Ref-good.pdf)
- [Reference hardware implementation, unipolar single-channel](doc/Ref-cheap/Ref-cheap.pdf)
- [Theory of operation](doc/TOO.md)
- [Why you should use it](#why-you-should-use-it)
- [Why you shouldn't use it](#why-you-shouldnt-use-it)
- [Supported platforms](#supported-platforms)
- [Implementation guide](#implementation-guide)
- [Future versions roadmap](#future-versions-roadmap)
## Why you should use it
It boasts pretty good specs for precision control applications:
- 15 usable bits
- DNL ≤ ±0.7 LSB
- INL ≤ ±2.75 LSB
- Optional bipolar output
- Channel counts up to 254, practically limited by scan frequency
- Can natively output any voltage reachable in your supply rails
It uses an integrating sample-and-hold topology, where a microcontroller timer
is used to create a precise pulse, an integrator converts it into a level, and
a sample-and-hold amplifier captures and buffers the level. The scan rate is
about 770 Hz, and the typical update latency is from 1.3 to 2.6 ms for a single
channel, or worst-case, 1.3 ms times the number of channels if all channels are
updating frequently.
At a build quantity of 10, BOM cost is around $3.00 + $0.67 n, where n is the
number of output channels up to 8. This gets you a full 15 bits! Also, most
parts are quite generic and can be easily substituted if hit by part shortages.
Here, have some plots:
![Plot of DNL and INL vs DAC code](doc/2022-12-26_18.51.48-inldnl.png)
## Why you shouldn't use it
The design has some drawbacks, as does any DAC. Here are some reasons TIMDAC
might not be right for you:
- Requires a microcontroller actively driving it (note that CPU utilization is
typically about 0.9%)
- This microcontroller needs to be a 32-bit part that can run its timer at
around 72 MHz
- Update rate is limited by the scan frequency, so if you need to update
very fast or update several outputs simultaneously, you may find it limiting
## Supported platforms
The following microcontrollers are presently supported. TIMDAC is portable and
has relatively basic requirements for its microcontroller as long as the timer
is fast enough, so adding more ports is straightforward.
- WCH CH32V103
## Implementation guide
### Hardware
To implement TIMDAC, you will need a supported microcontroller with an
available timer peripheral. It's also easy to port; if you plan to port it,
the timer peripheral must run at around 72 MHz, count to 65535, support
emitting a single pulse of specified width and then stopping, and be able to
fire an interrupt when it stops.
First, look at the reference hardware implementation above. You will need to
build this circuit on your board, and connect it to the microcontroller as
### Firmware (using Meson)
TIMDAC provides a Meson build file, so if you use Meson you can include it
directly as a subproject. The following options should be defined:
- `timdac_hw` — set to the name of the hardware port. Options are `ch32v103`
and `none`. If you use `none`, TIMDAC can link against port functions you
provide externally.
- `bsp` — set to the name of a subproject containing your board support
pack. This is where the config header, `timdac_config.h`, should live.
This is a nice, clean way to pull hardware definitions from the root project
into subprojects in Meson. If you're not using this approach already, you
can configure a very small board support pack like this:
$ ls subprojects/bsp-foo
$ cat subprojects/bsp-foo/meson.build
project('bsp-timdac', 'c')
bsp_dep = declare_dependency(include_directories: include_directories('.'))
If you want to call the dependency something other than `bsp_dep`, you can set
the same-named option (`bsp_dep`) to its actual name.
### Firmware (not using Meson)
- Add the source files under `src` to your build (only add one of the
`timdac_hw` files, matching your target platform).
- Add the include directory `inc` to your build.
- When building TIMDAC itself, ensure that wherever `timdac_config.h` lives is
in TIMDAC's include path.
### Firmware configuration
TIMDAC needs to be given a build configuration with hardware and runtime
options as `timdac_config.h`.
A template configuration file can be found in
All of the configuration options for TIMDAC itself are documented in
[`timdac.h`](inc/timdac.h), and the configuration options for each hardware
driver are documented in that driver's source file (e.g.
Some of these options are labeled `[REFHW]` in the documentation. These options
pertain to the reference hardware implementation, and as long as you are
following the hardware suggestions, you should not change them. They can have
surprising and difficult-to-measure effects on the DAC performance. To get the
correct options for a reference design, you should `#include` that reference
design's sub-config in your config:
#include "timdac_config_ref1p0.h"
### Using TIMDAC
Once integrated into your codebase, it can be initialized and started very
simply, and then you can set output values:
// First, ensure all necessary GPIO and timer clocks are running, any pin
// remapping has been done, and interrupts are enabled:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
timdac_set(channel, value);
When you set a channel, it is jumped to the start of the scan queue so that it
will receive an output as fast as possible. If another channel is already in
the pending queue-jump slot when you write a channel, this will not be done
(there is only one queue-jump slot). You may wait for an available slot:
while (timdac_pending());
## Future versions roadmap
- 1.0.1 or 1.1: Improve the scan algorithm to support many queue-jumpers.
- 1.1: Minor tweaks to the reference design BOM for cheaper cost and slightly
improved performance. A comparator is replaced by a cheaper one, a diode
is replaced by a capacitor, and several capacitors are replaced with lower
dielectric absorption units.
- 2.0: Add 16-bit operation! This will require an additional $1.21 part — a
digital potentiometer. The 15-bit version of TIMDAC uses a "hidden" 16th bit
to perform software scaling of output values to tune itself as timing
parameters vary; by moving this to an external potentiometer the full 16-bit
range of the timer can be realized. This will be a substantial upgrade that
is going to require careful redesign and testing on my end to maintain usably
low DNL and INL.
## Contributing
Want to contribute something? Bugs? Ports? Improvements? Sure! Hopefully
gitea/forgejo gets federation someday, but until then, I can be emailed:
(this username) @ (this domain).