-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuadraDAC_4Chan_Example.ino
More file actions
179 lines (148 loc) · 7.24 KB
/
Copy pathQuadraDAC_4Chan_Example.ino
File metadata and controls
179 lines (148 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/********************************************************************************************************
This example Arduino sketch is meant to work with Anabit's Quadra DAC 12/14/16 Bit 4 channel DAC open source
reference design
Product link: https://anabit.co/products/quadra-dac
This example sketch demonstrates how to output 4 different voltage patterns from the 14 bit DAC.
This portable version targets ANY Arduino board with hardware SPI and uses standard digitalWrite()
for the chip select (CS) pin. The DAC is driven over SPI using the Arduino board's default SPI pins.
The sketch does the following:
--> in the setup fucntion all 4 outputs are set to a voltage level defined by constants "VOUT_X"
--> In the main loop a dynamic signal is demonstrated, from channel 4 or D a sinewave is output
--> By default the sketch uses the max voltage set by verf gain of 1 (2.5V), you can change the
gain to 2 to get a max of ~V.
Note that this DAC with gain of 2 can do max 5V. This is done by the vref getting multipled by 2
but keep in mind this is done with internal op amps and no op amp is true rail to rail so when
the gain is set to 2 you can get close to 5V out but it will by more like 4.9V depending on load
conditions. To get more of a true 5V out set the input power to just above 5V like 5.2 or 5.3V
Please report any issue with the sketch to the Anagit forum: https://anabit.co/community/forum/digital-to-analog-converters-dacs
Example code developed by Your Anabit LLC © 2025
Licensed under the Apache License, Version 2.0.
********************************************************************************************************/
#include <SPI.h>
#include <math.h>
/* ================== User config ================== */
#define DAC_RESOLUTION 12 //12=AD5684R (default), 14=AD5685R, 16=AD5686R
constexpr float DAC_VREF = 2.5f; // internal reference voltage
constexpr float DAC_GAIN = 1.0f; // 1.0 if GAIN pin = GND (default), 2.0 if GAIN pin = VLOGIC
// Desired static outputs (used in setup), change these values to what you want
const float VOUT_A = 1.60f;
const float VOUT_B = 1.25f;
const float VOUT_C = 2.00f;
const float VOUT_D = 0.50f;
// Chip-Select (SYNC) pin — change to your wiring
const uint8_t PIN_CS = 10; // e.g., 10 on UNO/MEGA; any GPIO on others
// SPI speed: 4 MHz is a safe default across boards (raise if your wiring allows), DAC uses SPI mode1
SPISettings dacSPI(20000000, MSBFIRST, SPI_MODE1);
/* ===== Sine generator globals (edit these) ===== */
const uint16_t SINE_N = 64; // points per cycle
const uint32_t SINE_RATE_SPS = 0; // samples/sec; 0 = run as fast as possible
const bool EXECUTE_SINE = false; //set to false if you do not want to exceute sinewave in main loop
uint16_t idx = 0; //Index used for sinewave array tracking
uint32_t next_ts = 0; //used for sinewave timing
// Precomputed payloads for channel D (left-justified for the DAC)
uint16_t SINE_LUT[SINE_N];
/* ================================================= */
/* ===== AD568xR protocol commands ===== */
#define CMD_WRITE_INPUT_N_UPDATE_N 0x3
#define CMD_INTERNAL_REF_SETUP 0x7
//DAC channel addresses
#define ADDR_A 0x1
#define ADDR_B 0x2
#define ADDR_C 0x4
#define ADDR_D 0x8
#define ADDR_ALL 0xF
/* ================== Setup function ================== */
void setup() {
spiInit(); //start SPI communication
ref_on(); // keeps internal ref ON
// Static startup values
dac_write_update_volts(ADDR_A, VOUT_A);
dac_write_update_volts(ADDR_B, VOUT_B);
dac_write_update_volts(ADDR_C, VOUT_C);
dac_write_update_volts(ADDR_D, VOUT_D);
while(!EXECUTE_SINE); //loop forever and do not execute sivewave in main loop
build_sine_lut(); //build sinewave and store in global array
}
/* ======================================================= */
void loop() {
if (SINE_RATE_SPS == 0) { //run sinewave as fast as possible
// Fastest possible (bounded by SPI + loop overhead)
uint8_t b0; //declare variables for DAC data before sinewave loop to reduce overhead in loop
uint8_t b1;
uint8_t b2;
SPI.beginTransaction(dacSPI); //start SPI communication
while(1) {
//Build DAC command to set next sinewave value
b0 = (uint8_t)((CMD_WRITE_INPUT_N_UPDATE_N << 4) | (ADDR_D & 0x0F));
b1 = (uint8_t)(SINE_LUT[idx] >> 8);
b2 = (uint8_t)(SINE_LUT[idx]);
//send next sinewave point to DAC
digitalWrite(PIN_CS, LOW);
SPI.transfer(b0); SPI.transfer(b1); SPI.transfer(b2);
digitalWrite(PIN_CS, HIGH);
idx++; //update sinewave buffer index tracking variable
if (idx >= SINE_N) idx = 0;
}
} else {
// Timed update based on SINE_RATE_SPS
const uint32_t period_us = (SINE_RATE_SPS > 0) ? max(1ul, 1000000ul / SINE_RATE_SPS) : 1ul;
const uint32_t now = micros();
if ((int32_t)(now - next_ts) >= 0) {
dac_send24(CMD_WRITE_INPUT_N_UPDATE_N, ADDR_D, SINE_LUT[idx]);
idx = (uint16_t)(idx + 1);
if (idx >= SINE_N) idx = 0;
next_ts = now + period_us;
}
}
}
/* ===== Utilities ===== */
void spiInit() {
SPI.begin();
pinMode(PIN_CS, OUTPUT);
digitalWrite(PIN_CS, HIGH);
}
// Send one 24-bit frame: [4-bit cmd][4-bit addr][16-bit data]
//input arguments: DAC command, address / channel, data 12 / 14 / 16 bit
inline void dac_send24(uint8_t cmd, uint8_t addr, uint16_t data16) {
const uint8_t b0 = (uint8_t)((cmd << 4) | (addr & 0x0F));
const uint8_t b1 = (uint8_t)(data16 >> 8);
const uint8_t b2 = (uint8_t)(data16);
SPI.beginTransaction(dacSPI);
digitalWrite(PIN_CS, LOW);
SPI.transfer(b0); SPI.transfer(b1); SPI.transfer(b2);
digitalWrite(PIN_CS, HIGH);
SPI.endTransaction();
}
// Keep internal 2.5 V reference ON (default state is ON; DB0=0 keeps it on)
void ref_on() {
dac_send24(CMD_INTERNAL_REF_SETUP, 0x0, 0x0000); // address don't care
}
// Convert volts -> 16-bit payload (left-justified N-bit code)
//input arguments are voltage, ref volt, gain setting, resolution
static inline uint16_t volts_to_data16(float v, float vref, float gain, uint8_t nbits) {
float vfs = vref * gain;
if (v < 0) v = 0;
if (v > vfs) v = vfs;
const uint32_t max_code = (1u << nbits) - 1u; // 4095/16383/65535
uint32_t code = (uint32_t)((v / vfs) * max_code + 0.5f); // round to nearest
if (code > max_code) code = max_code;
return (uint16_t)(code << (16 - nbits)); // 12b:<<4, 14b:<<2, 16b:<<0
}
// Per-channel immediate update (no LDAC pin needed)
//set DAC output to specified voltage level
inline void dac_write_update_volts(uint8_t addrMask, float volts) {
const uint16_t data16 = volts_to_data16(volts, DAC_VREF, DAC_GAIN, DAC_RESOLUTION);
dac_send24(CMD_WRITE_INPUT_N_UPDATE_N, addrMask, data16);
}
//build sinewave using voltage values, then convert to DAC codes and store in array
//This function uses various global and constant values defined in the beginning of sketch
void build_sine_lut() {
const float vfs = DAC_VREF * DAC_GAIN;
const float amp = 0.5f * vfs; // amplitude
const float off = 0.5f * vfs; // offset (unipolar)
for (uint16_t i = 0; i < SINE_N; ++i) {
const float phase = (2.0f * 3.14159265358979323846f * i) / (float)SINE_N;
const float v = off + amp * sinf(phase); // spans 0..vfs
SINE_LUT[i] = volts_to_data16(v, DAC_VREF, DAC_GAIN, DAC_RESOLUTION);
}
}