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