Skip to content

Commit b5900fa

Browse files
Jude MerrittJude Merritt
authored andcommitted
Complete ADC V1
1 parent 5da3675 commit b5900fa

3 files changed

Lines changed: 447 additions & 2 deletions

File tree

src/devices/adc.c

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/**
2+
* This file is part of the Titan Flight Computer Project
3+
* Copyright (c) 2026 UW SARP
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, version 3.
8+
*
9+
* This program is distributed in the hope that it will be useful, but
10+
* WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
* @file devices/adc.h
18+
* @authors Jude Merritt
19+
* @brief ADS124S0x ADC driver
20+
*/
21+
22+
#include <stdint.h>
23+
#include "devices/adc.h"
24+
#include "peripheral/spi.h"
25+
#include "peripheral/systick.h"
26+
#include "internal/mmio.h"
27+
#include "peripheral/errc.h"
28+
29+
#define RESET 0x06
30+
#define START 0x08
31+
#define STOP 0x0A
32+
#define RDATA 0x12
33+
#define STATUS_REG 0x01
34+
#define INPMUX_REG 0x02
35+
#define PGA_REG 0x03
36+
#define REF_REG 0x05
37+
#define IDACMAG_REG 0x06
38+
#define IDACMUX_REG 0x07
39+
#define GPIODAT_REG 0x10
40+
#define GPIOCON_REG 0x11
41+
#define RDY_FLAG 0x40
42+
#define READ_BIT 0x20
43+
#define WRITE_BIT 0x40
44+
#define MAX_RREG_SIZE 6
45+
46+
struct adc_spi_dev DEV;
47+
48+
static int spi_rreg(uint8_t reg_addr, uint8_t data_size, enum ti_errc_t* errc) {
49+
if (data_size > (MAX_RREG_SIZE - 2) || data_size == 0) {
50+
*errc = TI_ERRC_INVALID_ARG;
51+
return -1;
52+
}
53+
54+
uint8_t src[MAX_RREG_SIZE] = {0};
55+
uint8_t dst[MAX_RREG_SIZE] = {0};
56+
57+
src[0] = READ_BIT | reg_addr;
58+
src[1] = data_size - 1;
59+
60+
// Two command bytes + the number of registers to read
61+
uint8_t tot_size = 2 + data_size;
62+
spi_transfer_sync(DEV.inst, DEV.ss_pin, src, dst, tot_size, errc); // TODO: Make sure that SPI is returning an actual error code
63+
64+
if (*errc != TI_ERRC_NONE) {
65+
return -1;
66+
}
67+
68+
uint32_t result = 0;
69+
for (int i = 0; i < data_size; i++) {
70+
result = (result << 8) | dst[2 + i];
71+
}
72+
73+
return result;
74+
}
75+
76+
static void spi_wreg(uint8_t reg_addr, uint16_t data_size, uint32_t data, enum ti_errc_t* errc) {
77+
if (data_size > (MAX_RREG_SIZE - 2) || data_size == 0) {
78+
*errc = TI_ERRC_INVALID_ARG;
79+
return;
80+
}
81+
82+
uint8_t src[MAX_RREG_SIZE] = {0};
83+
uint8_t dst[MAX_RREG_SIZE] = {0};
84+
85+
src[0] = WRITE_BIT | reg_addr;
86+
src[1] = data_size - 1;
87+
88+
for (int i = 0; i < data_size; i++) {
89+
// Move data into src while accounting for MSB formatting
90+
src[2 + i] = (uint8_t)(data >> (8 * (data_size - 1 - i)));
91+
}
92+
93+
uint8_t tot_size = 2 + data_size;
94+
spi_transfer_sync(DEV.inst, DEV.ss_pin, src, dst, tot_size, errc);
95+
}
96+
97+
static int32_t spi_single_command(uint8_t cmd, uint8_t transfer_size, enum ti_errc_t* errc) {
98+
if (transfer_size < 1 || transfer_size > 4) {
99+
*errc = TI_ERRC_INVALID_ARG;
100+
101+
return -1;
102+
}
103+
104+
uint8_t src[4] = {cmd, 0, 0, 0};
105+
uint8_t dst[4] = {0, 0, 0, 0};
106+
107+
spi_transfer_sync(DEV.inst, DEV.ss_pin, src, dst, transfer_size, errc);
108+
109+
if (*errc != TI_ERRC_NONE) {
110+
return -1;
111+
}
112+
113+
if (transfer_size == 1) {
114+
return dst[0];
115+
} else {
116+
int32_t result = (dst[1] << 16) | (dst[2] << 8) | dst[3];
117+
return result;
118+
}
119+
}
120+
121+
void adc_init(struct adc_spi_dev *dev, enum ti_errc_t* errc) {
122+
if (dev->inst < 1 || dev->inst > 6) {
123+
*errc = TI_ERRC_INVALID_ARG;
124+
return;
125+
}
126+
127+
*errc = TI_ERRC_NONE;
128+
DEV = *dev;
129+
130+
// Reset ADC
131+
uint8_t err = spi_single_command(RESET, 1, errc);
132+
if (err == -1 || *errc != TI_ERRC_NONE) {
133+
return;
134+
}
135+
136+
// Delay recommended by datasheet after RESET
137+
systick_delay(5);
138+
139+
// Wait until ADC is ready for communication
140+
bool is_ready = false;
141+
int timeout = 100000;
142+
while (!is_ready) {
143+
uint8_t status_reg = spi_rreg(STATUS_REG, 1, errc);
144+
145+
if ((status_reg & RDY_FLAG) == 0 && *errc == TI_ERRC_NONE) {
146+
is_ready = true;
147+
} else if (timeout == 0) {
148+
*errc = TI_ERRC_TIMEOUT;
149+
return;
150+
}
151+
152+
timeout--;
153+
}
154+
155+
// Enable internal reference
156+
spi_wreg(REF_REG, 1, 0x12, errc);
157+
}
158+
159+
int adc_read_voltage(const struct adc_channel* channel, enum ti_errc_t* errc) {
160+
if (DEV.inst < 1 || DEV.inst > 6 || !channel) {
161+
*errc = TI_ERRC_INVALID_ARG;
162+
return -1;
163+
}
164+
165+
*errc = TI_ERRC_NONE;
166+
167+
// Configure Input Multiplexer
168+
uint8_t mux_val = (channel->pos_pin << 4) | (channel->neg_pin & 0x0F);
169+
spi_wreg(INPMUX_REG, 1, mux_val, errc); // Returns -1 if unexpected return, otherwise 1
170+
171+
// Set gain
172+
uint8_t pga_val = 0x08 | (channel->gain & 0x07);
173+
spi_wreg(PGA_REG, 1, pga_val, errc);
174+
175+
// Set reference voltage
176+
uint8_t ref_val = 0x12 | ((channel->source & 0x03) << 2);
177+
spi_wreg(REF_REG, 1, ref_val, errc);
178+
179+
if (*errc != TI_ERRC_NONE) {
180+
return -1;
181+
}
182+
183+
// Wait for device ready flag
184+
int timeout = 100;
185+
while ((spi_rreg(STATUS_REG, 1, errc) & RDY_FLAG) != 0 && timeout > 0) {
186+
timeout--;
187+
}
188+
189+
// Request data
190+
int32_t result = spi_single_command(RDATA, 4, errc);
191+
192+
// If result is negative, ensure that top eight bit are flipped to 1
193+
if (result & 0x800000) {
194+
result |= 0xFF000000;
195+
}
196+
197+
// Voltage conversion math
198+
float divisor = (float)((1 << 23) - 1);
199+
200+
// Convert the 3-bit gain code (0-7) into the actual multiplier (1, 2, 4... 128)
201+
float actual_gain = (float)(1 << (channel->gain & 0x07));
202+
203+
float final_voltage = ((float)result * channel->ref_voltage) / (actual_gain * divisor);
204+
205+
return (int32_t)(final_voltage * 1000);
206+
}
207+
208+
int adc_read_voltage_diff(struct adc_channel channel1, struct adc_channel channel2, enum ti_errc_t* errc) {
209+
int32_t voltage1 = adc_read_voltage(&channel1, errc);
210+
int32_t voltage2 = adc_read_voltage(&channel2, errc);
211+
212+
return voltage1 - voltage2;
213+
}
214+
215+
// You don't need to disconnect a pin to change the idac pins
216+
void adc_set_idac(enum idac_mag magnitude, enum adc_pin pin1, enum adc_pin pin2, enum ti_errc_t* errc) {
217+
if (DEV.inst < 1 || DEV.inst > 6) {
218+
*errc = TI_ERRC_INVALID_ARG;
219+
return;
220+
}
221+
222+
// Set IDAC magnitude
223+
spi_wreg(IDACMAG_REG, 1, magnitude, errc);
224+
225+
if (*errc != TI_ERRC_NONE) {
226+
return;
227+
}
228+
229+
uint8_t mux_pins = ((pin2 & 0x0F) << 4) | (pin1 & 0x0F);
230+
spi_wreg(IDACMUX_REG, 1, mux_pins, errc);
231+
}
232+
233+
void adc_set_gpio(enum adc_pin pin, bool default_high, bool input, enum ti_errc_t* errc) {
234+
if (DEV.inst < 1 || DEV.inst > 6) {
235+
*errc = TI_ERRC_INVALID_ARG;
236+
return;
237+
}
238+
239+
// Only ADC pins 8 - 11 work
240+
uint8_t gpio_idx = pin - 0x08;
241+
242+
// Enable GPIO function
243+
uint8_t gpiocon_val;
244+
gpiocon_val |= (1 << gpio_idx);
245+
spi_wreg(GPIOCON_REG, 1, gpiocon_val, errc);
246+
if (*errc != TI_ERRC_NONE) {
247+
return;
248+
}
249+
250+
uint8_t gpiodat_val;
251+
if (default_high && input) {
252+
gpiodat_val = 1 << (gpio_idx + 4) | gpio_idx;
253+
} else if (default_high && !input) {
254+
gpiodat_val = 1 << gpio_idx;
255+
} else if (!default_high && input) {
256+
gpiodat_val = 1 << (gpio_idx + 4);
257+
}
258+
259+
// Set as output/input and default high/low
260+
spi_wreg(GPIODAT_REG, 1, gpiodat_val, errc);
261+
}
262+
263+
char* adc_get_channel_name(struct adc_channel channel) {
264+
return channel.name;
265+
}
266+
267+
268+
/**
269+
* Notes:
270+
* 1. Start and reset pins are perminently tied to high, clk is tied to low, and data ready is left hanging.
271+
* Only standard spi pins are used.
272+
*
273+
* 2. TODO: Reduce magic numbers. For example STATUS_REG & 0x40 is RDY_FLAG.
274+
*
275+
* 4. If errc is not TI_ERRC_NONE the return value has no meaning **
276+
*/

0 commit comments

Comments
 (0)