190 lines
7.1 KiB
Markdown
190 lines
7.1 KiB
Markdown
# TIMDAC
|
|
|
|
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
|
|
shown.
|
|
|
|
### 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
|
|
meson.build
|
|
timdac_config.h
|
|
|
|
$ 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
|
|
[`timdac_config_template_ch32v103.h`](inc/timdac_config_template_ch32v103.h).
|
|
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.
|
|
[`timdac_hw_ch32v103.c`](src/timdac_hw_ch32v103.c)).
|
|
|
|
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:
|
|
|
|
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
|
|
RCC_APB2PeriphClockCmd(
|
|
RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC,
|
|
ENABLE
|
|
);
|
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
|
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
|
|
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
|
|
|
|
timdac_init();
|
|
timdac_start();
|
|
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).
|