aboutsummaryrefslogtreecommitdiff
path: root/center_fw/adc.c
blob: 0cf70d1425a814eed265d15eb83c18c8170a9ab4 (plain)
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/* Megumin LED display firmware
 * Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "adc.h"

#include <stdbool.h>
#include <stdlib.h>

#define DETECTOR_CHANNEL a

volatile uint16_t adc_buf[ADC_BUFSIZE];
volatile struct adc_state adc_state = {0};
#define st adc_state
volatile struct adc_measurements adc_data;

static void adc_dma_init(int burstlen, bool enable_interrupt);
static void adc_timer_init(int psc, int ivl);


/* Mode that can be used for debugging */
void adc_configure_scope_mode(uint8_t channel_mask, int sampling_interval_ns) {
	/* The constant SAMPLE_FAST (0) when passed in as sampling_interval_ns is handled specially in that we turn the ADC
	to continuous mode to get the highest possible sampling rate. */

	/* First, disable trigger timer, DMA and ADC in case we're reconfiguring on the fly. */
    TIM1->CR1 &= ~TIM_CR1_CEN;
    ADC1->CR &= ~ADC_CR_ADSTART;
    DMA1_Channel1->CCR &= ~DMA_CCR_EN;

	/* keep track of current mode in global variable */
	st.adc_mode = ADC_SCOPE;

	adc_dma_init(sizeof(adc_buf)/sizeof(adc_buf[0]), true);

    /* Clock from PCLK/4 instead of the internal exclusive high-speed RC oscillator. */
    ADC1->CFGR2 = (2<<ADC_CFGR2_CKMODE_Pos); /* Use PCLK/4=12MHz */
    /* Sampling time 13.5 ADC clock cycles -> total conversion time 2.17us*/
    ADC1->SMPR  = (2<<ADC_SMPR_SMP_Pos);

	/* Setup DMA and triggering */
	if (sampling_interval_ns == SAMPLE_FAST) /* Continuous trigger */
		ADC1->CFGR1 = ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | ADC_CFGR1_CONT;
	else /* Trigger from timer 1 Channel 4 */
		ADC1->CFGR1 = ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | (2<<ADC_CFGR1_EXTEN_Pos) | (1<<ADC_CFGR1_EXTSEL_Pos);
    ADC1->CHSELR = channel_mask;
	/* Perform self-calibration */
    ADC1->CR |= ADC_CR_ADCAL;
    while (ADC1->CR & ADC_CR_ADCAL)
        ;
	/* Enable conversion */
    ADC1->CR |= ADC_CR_ADEN;
    ADC1->CR |= ADC_CR_ADSTART;

	if (sampling_interval_ns == SAMPLE_FAST)
		return; /* We don't need the timer to trigger in continuous mode. */

	/* An ADC conversion takes 1.1667us, so to be sure we don't get data overruns we limit sampling to every 1.5us.
	Since we don't have a spare PLL to generate the ADC sample clock and re-configuring the system clock just for this
	would be overkill we round to 250ns increments. The minimum sampling rate is about 60Hz due to timer resolution. */
	int cycles = sampling_interval_ns > 1500 ? sampling_interval_ns/250 : 6;
	if (cycles > 0xffff)
		cycles = 0xffff;
	adc_timer_init(12/*250ns/tick*/, cycles);
}

/* FIXME figure out the proper place to configure this. */
#define ADC_TIMER_INTERVAL_US 20

/* Regular operation receiver mode. */
void adc_configure_monitor_mode(const struct command_if_def *cmd_if) {
	/* First, disable trigger timer, DMA and ADC in case we're reconfiguring on the fly. */
    TIM1->CR1 &= ~TIM_CR1_CEN;
    ADC1->CR &= ~ADC_CR_ADSTART;
    DMA1_Channel1->CCR &= ~DMA_CCR_EN;

	/* keep track of current mode in global variable */
	st.adc_mode = ADC_MONITOR;

	for (int i=0; i<NCH; i++)
		st.adc_aggregate[i] = 0;
	st.mean_aggregator[0] = st.mean_aggregator[1] = st.mean_aggregator[2] = 0; 
	st.mean_aggregate_ctr = 0;

    st.det_st.hysteresis_mv = 6000;
    /* base_cycles * the ADC timer interval (20us) must match the driver's AC period. */ 
    st.det_st.base_interval_cycles = 40; /* 40 * 20us = 800us/1.25kHz */

	st.det_st.sync = 0;
	st.det_st.last_bit = 0;
	st.det_st.committed_len_ctr = st.det_st.len_ctr = 0;
	xfr_8b10b_reset((struct state_8b10b_dec *)&st.det_st.rx8b10b);
    reset_receiver((struct proto_rx_st *)&st.det_st.rx_st, cmd_if);

	adc_dma_init(NCH, true);

	/* Setup DMA and triggering: Trigger from Timer 1 Channel 4 */
    ADC1->CFGR1 = ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | (2<<ADC_CFGR1_EXTEN_Pos) | (1<<ADC_CFGR1_EXTSEL_Pos);
    /* Clock from PCLK/4 instead of the internal exclusive high-speed RC oscillator. */
    ADC1->CFGR2 = (2<<ADC_CFGR2_CKMODE_Pos); /* Use PCLK/4=12MHz */
    /* Sampling time 13.5 ADC clock cycles -> total conversion time 2.17us*/
    ADC1->SMPR  = (2<<ADC_SMPR_SMP_Pos);
    /* Internal VCC and temperature sensor channels */
    ADC1->CHSELR = ADC_CHSELR_CHSEL0 | ADC_CHSELR_CHSEL1 | ADC_CHSELR_CHSEL16 | ADC_CHSELR_CHSEL17;
    /* Enable internal voltage reference and temperature sensor */
    ADC->CCR = ADC_CCR_TSEN | ADC_CCR_VREFEN;
    /* Perform ADC calibration */
    ADC1->CR |= ADC_CR_ADCAL;
    while (ADC1->CR & ADC_CR_ADCAL)
        ;
    /* Enable ADC */
    ADC1->CR |= ADC_CR_ADEN;
    ADC1->CR |= ADC_CR_ADSTART;

    /* Initialize the timer. Set the divider to get a nice round microsecond tick. The interval must be long enough to
     * comfortably fit all conversions inside. There should be some margin since the ADC runs off its own internal RC
     * oscillator and will drift w.r.t. the system clock. 20us is a nice value when four channels are selected (A, B,
     * T and V).
     */
    adc_timer_init(SystemCoreClock/1000000/*1.0us/tick*/, 20/* us */);
}

static void adc_dma_init(int burstlen, bool enable_interrupt) {
    /* Configure DMA 1 Channel 1 to get rid of all the data */
    DMA1_Channel1->CPAR = (unsigned int)&ADC1->DR;
    DMA1_Channel1->CMAR = (unsigned int)&adc_buf;
    DMA1_Channel1->CNDTR = burstlen;
    DMA1_Channel1->CCR = (0<<DMA_CCR_PL_Pos);
    DMA1_Channel1->CCR |=
          DMA_CCR_CIRC /* circular mode so we can leave it running indefinitely */
        | (1<<DMA_CCR_MSIZE_Pos) /* 16 bit */
        | (1<<DMA_CCR_PSIZE_Pos) /* 16 bit */
        | DMA_CCR_MINC
        | (enable_interrupt ? DMA_CCR_TCIE : 0); /* Enable transfer complete interrupt. */

	if (enable_interrupt) {
		/* triggered on transfer completion. We use this to process the ADC data */
		NVIC_EnableIRQ(DMA1_Channel1_IRQn);
		NVIC_SetPriority(DMA1_Channel1_IRQn, 2<<5);
	} else {
		NVIC_DisableIRQ(DMA1_Channel1_IRQn);
		DMA1->IFCR |= DMA_IFCR_CGIF1;
	}

    DMA1_Channel1->CCR |= DMA_CCR_EN; /* Enable channel */
}

static void adc_timer_init(int psc, int ivl) {
    TIM1->BDTR  = TIM_BDTR_MOE; /* MOE is needed even though we only "output" a chip-internal signal TODO: Verify this. */
    TIM1->CCMR2 = (6<<TIM_CCMR2_OC4M_Pos); /* PWM Mode 1 to get a clean trigger signal */
    TIM1->CCER  = TIM_CCER_CC4E; /* Enable capture/compare unit 4 connected to ADC */
    TIM1->CCR4  = 1; /* Trigger at start of timer cycle */
	/* Set prescaler and interval */
    TIM1->PSC   = psc-1;
    TIM1->ARR   = ivl-1;
    /* Preload all values */
    TIM1->EGR  |= TIM_EGR_UG;
    TIM1->CR1   = TIM_CR1_ARPE;
    /* And... go! */
    TIM1->CR1  |= TIM_CR1_CEN;
}

/* This acts as a no-op that provides a convenient point to set a breakpoint for the debug scope logic */
static void gdb_dump(void) {
}

/* Called on reception of a bit. This feeds the bit to the 8b10b state machine. When the 8b10b state machine recognizes
 * a received symbol, this in turn calls receive_symbol. Since this is called at sampling time roughly halfway into a
 * bit being received, receive_symbol is called roughly half-way through the last bit of the symbol, just before the
 * symbol's end.
 */
void receive_bit(struct bit_detector_st *st, int bit) {
    int symbol = xfr_8b10b_feed_bit((struct state_8b10b_dec *)&st->rx8b10b, bit);
    if (symbol == -K28_1)
        st->sync = 1;

    if (symbol == -DECODING_IN_PROGRESS)
        return;

    if (symbol == -DECODING_ERROR)
        st->sync = 0;
        /* Fall through so we also pass the error to receive_symbol */

    receive_symbol(&st->rx_st, symbol);

    /* Exceedingly handy piece of debug code: The Debug Scope 2000 (TM) */
    /*
    static int debug_buf_pos = 0;
    if (st->sync) {
        if (debug_buf_pos < NCH) {
                debug_buf_pos = NCH;
        } else {
            adc_buf[debug_buf_pos++] = symbol;

            if (debug_buf_pos >= sizeof(adc_buf)/sizeof(adc_buf[0])) {
                debug_buf_pos = 0;
                st->sync = 0;
                gdb_dump();
                for (int i=0; i<sizeof(adc_buf)/sizeof(adc_buf[0]); i++)
                    adc_buf[i] = -255;
            }
        }
    }
    */
}

/* From a series of detected line levels, extract discrete bits. This self-synchronizes to signal transitions. This
 * expects base_interval_cycles to be set correctly. When a bit is detected, this calls receive_bit(st, bit). The call
 * to receive_bit happens at the sampling point about half-way through the bit being received.
 */
void bit_detector(struct bit_detector_st *st, int a) {
    int new_bit = st->last_bit;
    int diff = a-5500; /* FIXME extract constants */
    if (diff < - st->hysteresis_mv/2)
        new_bit = 0;
    else if (diff > st->hysteresis_mv/2)
        new_bit = 1;
    else
        blank(); /* Safety, in case we get an unexpected transition */

    st->len_ctr++;
    if (new_bit != st->last_bit) { /* On transition */
        st->last_bit = new_bit;
        st->len_ctr = 0;
        st->committed_len_ctr = st->base_interval_cycles>>1; /* Commit first half of bit */

    } else if (st->len_ctr >= st->committed_len_ctr) {
        /* The line stayed constant for a longer interval than the commited length. Interpret this as a transmitted bit.
         *
         *     +-- Master clock edges     -->| - - - - |<-- One bit period
         *     |                             |         |
         * 1   X         X         X         X         X         X         X         X
         * ____/^^^^*^^^^\_______________________________________/^^^^*^^^^^^^^^*^^^^\__________________________________
         * 0   v    ^                                                 v         ^
         *     |    |                                                 |         |
         *     |    +-------------------------------+                 +---------+
         *     |                                    |                 |        
         *     At this point, commit 1/2 bit (until here). This       When we arrive at the committed value, commit next
         *     happens in the block above.                            full bit as we're now right in the middle of the
         *                                                            first bit. This happens in the line below.
         */

        /* Commit second half of this and first half of possible next bit */
        st->committed_len_ctr += st->base_interval_cycles;
        receive_bit(st, st->last_bit);
    }
}

void DMA1_Channel1_IRQHandler(void) {
    /* ISR timing measurement for debugging */
    //int start = SysTick->VAL;

    /* Clear the interrupt flag */
    DMA1->IFCR |= DMA_IFCR_CGIF1;
    
    if (st.adc_mode == ADC_SCOPE)
        return;

    /* FIXME This code section currently is a mess since I left it as soon as it worked. Re-work this and try to get
     * back all the useful monitoring stuff, in particular temperature. */

    /* This has been copied from the code examples to section 12.9 ADC>"Temperature sensor and internal reference
     * voltage" in the reference manual with the extension that we actually measure the supply voltage instead of
     * hardcoding it. This is not strictly necessary since we're running off a bored little LDO but it's free and
     * the current supply voltage is a nice health value.
     */
    // FIXME DEBUG adc_data.vcc_mv = (3300 * VREFINT_CAL)/(st.adc_aggregate[VREF_CH]);

    int64_t vcc = 3300;
    /* FIXME debug
    int64_t vcc = adc_data.vcc_mv;
    int64_t read = st.adc_aggregate[TEMP_CH] * 10 * 10000;
    int64_t cal = TS_CAL1 * 10 * 10000;
    adc_data.temp_celsius_tenths = 300 + ((read/4096 * vcc) - (cal/4096 * 3300))/43000;
    */

    /* Calculate the line voltage from the measured ADC voltage and the used resistive divider ratio */
    const long vmeas_r_total = VMEAS_R_HIGH + VMEAS_R_LOW;
    //int a = adc_data.vmeas_a_mv = (st.adc_aggregate[VMEAS_A]*(vmeas_r_total * vcc / VMEAS_R_LOW)) >> 12;
    int a = adc_data.vmeas_a_mv = (adc_buf[VMEAS_A]*13300) >> 12;
    bit_detector((struct bit_detector_st *)&st.det_st, a);

    /* ISR timing measurement for debugging */
    /*
    int end = SysTick->VAL;
    int tdiff = start - end;
    if (tdiff < 0)
        tdiff += SysTick->LOAD;
    st.dma_isr_duration = tdiff;
    */
}