ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ad9238.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ad9238.h
6 * @brief SPI configuration-port codec for the AD9238 (and the shared ADI high-speed-ADC SPI
7 * map it belongs to) - PROTOCORE_ENABLE_AD9238.
8 *
9 * The AD9238 (12-bit, 20/40/65 MSPS dual ADC) has TWO interfaces that must not be confused:
10 * - The **sample data path**: a parallel CMOS/LVDS bus (12 data lines + DCO/output clock per
11 * channel) run far beyond what a microcontroller can bit-bang at 20-65 MSPS. That path is
12 * NOT this file - it is out of scope for direct MCU capture; see reverse_engineering/README.md
13 * for the FPGA/CPLD-buffered burst-drain architecture this project actually uses.
14 * - The **SPI configuration port** (SCLK / SDIO / CSB, 3-wire, MSB first): a low-speed,
15 * low-throughput control channel for power-down, output data format, output test patterns,
16 * and offset trim - register writes only, never the sample stream. This file is that codec:
17 * it builds the 16-bit instruction word (R/W + 2-bit byte-count + 13-bit address) and the
18 * shadow-register "device update" transfer that the whole ADI high-speed-ADC generation of
19 * this era (AD9238 and its close siblings) share, per AN-877's SPI register map. Pure codec
20 * (builds/parses byte sequences); the SPI clocking is the app's - same contract as every other
21 * codec in this library (see services/instrumentation/scpi, services/instrumentation/gpib).
22 *
23 * **Confidence note.** The instruction-word framing, the transfer-register mechanism and every
24 * register address in @ref Ad9238Reg are transcribed from AN-877 Rev. A, which defines the map
25 * this whole ADI ADC generation shares. AN-877 says which registers exist and where; it does not
26 * say which of them a given part implements, so **confirm every address and bit position against
27 * your part's datasheet revision before writing to real silicon** - per this project's
28 * hardware-verification policy (docs/KNOWN_LIMITATIONS.md), nothing here has been validated
29 * against a physical AD9238 yet.
30 *
31 * Reference: Analog Devices AN-877 Rev. A, "Interfacing to High Speed ADCs via SPI", the FORMAT
32 * and CHIP PROGRAMMING sections. Cached at docs/learn/datasheets/an877.pdf.
33 *
34 * @author Douglas Quigg (dstroy0)
35 * @date 2026
36 */
37
38#ifndef PROTOCORE_AD9238_H
39#define PROTOCORE_AD9238_H
40
41#include "protocore_config.h" // the entry point: protocore_types.h for the widths
42
43#if PROTOCORE_ENABLE_AD9238
44
46
47// This module holds nothing between calls, so it carves no borrow and states none. An entry
48// takes one all the same, and never reads it, so every namespace in the tree is invoked the
49// same way.
50
51/**
52 * @brief SPI register addresses (13-bit address field), from AN-877 "CHIP PROGRAMMING".
53 *
54 * Every address below is the one AN-877 Rev. A names for that register, section by section:
55 * Configuration Register (0x000), Chip ID (0x001), Chip Grade (0x002), Device Indexing (0x004 and
56 * 0x005), Modes (0x008), Output Test Modes (0x00D), Analog Input (0x00F), Offset Adjust (0x010),
57 * Output Mode (0x014), Clock Divider Phase (0x016), Output Delay Adjust (0x017), Reference Adjust
58 * (0x018) and the Transfer Register (0x0FF). A device may not implement all of them - check the
59 * part's own datasheet for which are present and what the fields mean.
60 */
61typedef enum PROTO_ENUM_PACKED
62{
63 AD9238_REG_CHIP_PORT_CONFIG = 0x000, ///< SDIO active, LSB first, soft reset (mirrored nibbles)
64 AD9238_REG_CHIP_ID = 0x001, ///< read-only device id
65 AD9238_REG_CHIP_GRADE = 0x002, ///< read-only speed-grade id
66 AD9238_REG_CHANNEL_INDEX = 0x005, ///< device indexing, ADC0..ADC3 (0x004 indexes ADC4..ADC7)
67 AD9238_REG_POWER_DOWN = 0x008, ///< modes: bits 2:0 the internal power-down mode
68 AD9238_REG_TEST_IO = 0x00D, ///< output test modes: bits 3:0 select the pattern (see Ad9238TestPattern)
69 AD9238_REG_ANALOG_INPUT = 0x00F, ///< analog input: low-pass corner, disconnect, single-ended
70 AD9238_REG_OFFSET_ADJUST = 0x010, ///< digital offset trim, twos complement about midscale
71 AD9238_REG_OUTPUT_MODE = 0x014, ///< output logic type, invert, and bits 1:0 the data format
72 AD9238_REG_OUTPUT_PHASE = 0x016, ///< clock divider phase: which phase latches the data
73 AD9238_REG_OUTPUT_DELAY = 0x017, ///< fine delay on the output latch
74 AD9238_REG_VREF = 0x018, ///< reference adjust: bits 7:6 select VREF, bits 5:0 trim it
75 AD9238_REG_DEVICE_UPDATE = 0x0FF, ///< transfer: bit0 latches every shadowed write into effect
76} Ad9238Reg;
77
78/** @brief AD9238_REG_TEST_IO[3:0] - the output test pattern (AN-877 Table 8, register 0x00D). */
79typedef enum PROTO_ENUM_PACKED
80{
81 AD9238_TEST_OFF = 0x00, ///< normal operation
82 AD9238_TEST_MIDSCALE_SHORT = 0x01,
83 AD9238_TEST_POS_FULLSCALE = 0x02,
84 AD9238_TEST_NEG_FULLSCALE = 0x03,
85 AD9238_TEST_CHECKERBOARD = 0x04, ///< alternating 0xAAA/0x555 - the pipeline's self-test pattern
86 AD9238_TEST_PN23 = 0x05,
87 AD9238_TEST_PN9 = 0x06,
88 AD9238_TEST_ONE_ZERO_TOGGLE = 0x07,
89} Ad9238TestPattern;
90
91/** @brief AD9238_REG_OUTPUT_MODE[1:0] - the output data format (AN-877 Table 11, register 0x014). */
92typedef enum PROTO_ENUM_PACKED
93{
94 AD9238_FORMAT_OFFSET_BINARY = 0x00,
95 AD9238_FORMAT_TWOS_COMPLEMENT = 0x01,
96 AD9238_FORMAT_GRAY_CODE = 0x02,
97} Ad9238OutputFormat;
98
99/** @brief Which channel a per-channel register write targets (AD9238_REG_CHANNEL_INDEX bits). */
100typedef enum PROTO_ENUM_PACKED
101{
102 AD9238_CHAN_A = 0x01,
103 AD9238_CHAN_B = 0x02,
104 AD9238_CHAN_BOTH = 0x03,
105} Ad9238Channel;
106
107/** @brief What build_instruction takes: read, reg_addr, nbytes, out2. */
108typedef struct
109{
110 proto_bool read; ///< true for a read transaction, false for a write
111 uint16_t reg_addr; ///< 13-bit register address (Ad9238Reg or a raw value)
112 uint8_t nbytes; ///< number of data bytes to follow (1-4; encoded as W1:W0 = nbytes-1, so 4 means "streaming" ...
113 uint8_t *out2; ///< receives the 2-byte instruction word
114} Ad9238BuildInstructionArgs;
115
116/** @brief What build_write takes: reg_addr, value, out, cap. */
117typedef struct
118{
119 uint16_t reg_addr;
120 uint8_t value;
121 uint8_t *out;
122 size_t cap;
123} Ad9238BuildWriteArgs;
124
125/** @brief What build_read takes: reg_addr, out, cap. */
126typedef struct
127{
128 uint16_t reg_addr;
129 uint8_t *out;
130 size_t cap;
131} Ad9238BuildReadArgs;
132
133/** @brief What build_transfer takes: out, cap. */
134typedef struct
135{
136 uint8_t *out;
137 size_t cap;
138} Ad9238BuildTransferArgs;
139
140/**
141 * @brief SPI configuration-port codec for the AD9238 (and the shared ADI high-speed-ADC SPI map it belongs to) - ...
142 *
143 * A caller sets the members a call takes, invokes it through ::Ad9238 with the bytes it runs
144 * out of, and reads the outcome off the same handle.
145 *
146 * Ad9238.build_instruction_args.read = ...;
147 * Ad9238.build_instruction_args.reg_addr = ...;
148 * Ad9238.build_instruction_args.nbytes = ...;
149 * Ad9238.build_instruction_args.out2 = ...;
150 * Ad9238.build_instruction(work);
151 * // Ad9238.ok is what the call reports
152 *
153 * @var Ad9238Ns::build_instruction_args what build_instruction takes: read, reg_addr, nbytes, out2
154 * @var Ad9238Ns::build_write_args what build_write takes: reg_addr, value, out, cap
155 * @var Ad9238Ns::build_read_args what build_read takes: reg_addr, out, cap
156 * @var Ad9238Ns::build_transfer_args what build_transfer takes: out, cap
157 * @var Ad9238Ns::ok true; false only if out2 is null or nbytes is 0 or > 4
158 * @var Ad9238Ns::n 3 (bytes written to out), or 0 if out is null / cap < 3
159 * @var Ad9238Ns::build_instruction build the 16-bit SPI instruction word (MSB first on the wire: high ...
160 * @var Ad9238Ns::build_write build a complete single-register write transaction (instruction ...
161 * @var Ad9238Ns::build_read build a single-register read instruction (the 2-byte header; the ...
162 * @var Ad9238Ns::build_transfer build the "device update" transfer transaction (write 0x01 to ...
163 *
164 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
165 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
166 * a caller drives every namespace the same way.
167 */
168typedef struct
169{
170 Ad9238BuildInstructionArgs build_instruction_args;
171 Ad9238BuildWriteArgs build_write_args;
172 Ad9238BuildReadArgs build_read_args;
173 Ad9238BuildTransferArgs build_transfer_args;
174 proto_bool ok;
175 size_t n;
176} Ad9238Vars;
177
178/** @brief The operands and the outcome. */
179extern Ad9238Vars Ad9238V;
180
181/** @brief The entries. */
182typedef struct
183{
184 void (*const build_instruction)(uint8_t *work);
185 void (*const build_write)(uint8_t *work);
186 void (*const build_read)(uint8_t *work);
187 void (*const build_transfer)(uint8_t *work);
188} Ad9238Ns;
189
190// What the table binds, defined once in the .c and taking one parameter each: everything
191// else an entry needs is an operand in Ad9238V or a region of the borrow at a fixed offset.
192void protocore_ad9238_build_instruction(uint8_t *work);
193void protocore_ad9238_build_write(uint8_t *work);
194void protocore_ad9238_build_read(uint8_t *work);
195void protocore_ad9238_build_transfer(uint8_t *work);
196
197// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
198// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
199// `Ad9238.build_instruction(work)` resolves to a named function and becomes a DIRECT call. An extern table
200// leaves the call indirect and the symbol live at every level, -O2 -flto included.
201static const Ad9238Ns Ad9238 __attribute__((unused)) = {
202 .build_instruction = protocore_ad9238_build_instruction,
203 .build_write = protocore_ad9238_build_write,
204 .build_read = protocore_ad9238_build_read,
205 .build_transfer = protocore_ad9238_build_transfer,
206};
207
209
210#endif // PROTOCORE_ENABLE_AD9238
211
212#endif // PROTOCORE_AD9238_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97