summaryrefslogtreecommitdiff
path: root/gm_platform/fw/adc.c
blob: e3aba5be34260dc7c180d2cfa476e9e287bf01a9 (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
/* 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 "serial.h"

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

static struct __attribute__((__packed__)) hl_adc_pkt {
    struct ll_pkt ll;
    uint16_t seq;
    volatile uint16_t data[32];
} adc_pkt[2];
static uint16_t current_seq = 0;
static int current_buf = 0;

static void adc_dma_init(void);
static void adc_timer_init(int psc, int ivl);
static void adc_dma_launch(void);


/* Mode that can be used for debugging */
void adc_configure_scope_mode(int sampling_interval_ns) {
	adc_dma_init();

    /* 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 239.5 ADC clock cycles -> total conversion time 38.5us*/
    ADC1->SMPR  = (7<<ADC_SMPR_SMP_Pos);

	/* Setup DMA and triggering */
	/* Trigger from TIM1 TRGO */
    ADC1->CFGR1 = ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | (2<<ADC_CFGR1_EXTEN_Pos) | (1<<ADC_CFGR1_EXTSEL_Pos);
    ADC1->CHSELR = ADC_CHSELR_CHSEL2;
	/* 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;

	/* 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);
}

static void adc_dma_init() {
    /* Configure DMA 1 Channel 1 to get rid of all the data */
    DMA1_Channel1->CPAR = (unsigned int)&ADC1->DR;
    DMA1_Channel1->CCR = (0<<DMA_CCR_PL_Pos);
    DMA1_Channel1->CCR |=
          (1<<DMA_CCR_MSIZE_Pos) /* 16 bit */
        | (1<<DMA_CCR_PSIZE_Pos) /* 16 bit */
        | DMA_CCR_MINC
        | DMA_CCR_TCIE; /* Enable transfer complete interrupt. */

    /* triggered on half-transfer and on transfer completion. We use this to send out the ADC data and to trap into GDB. */
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    NVIC_SetPriority(DMA1_Channel1_IRQn, 2<<5);

    adc_dma_launch();
}

void adc_dma_launch() {
    DMA1_Channel1->CCR &= ~DMA_CCR_EN; /* Disable channel */
    current_buf = !current_buf;
    DMA1_Channel1->CMAR = (unsigned int)&(adc_pkt[current_buf].data);
    DMA1_Channel1->CNDTR = ARRAY_LEN(adc_pkt[current_buf].data);
    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) {
}

void DMA1_Channel1_IRQHandler(void) {
    uint32_t isr = DMA1->ISR;
    /* Clear the interrupt flag */
    DMA1->IFCR |= DMA_IFCR_CGIF1;
    adc_dma_launch();

    gdb_dump();

    adc_pkt[!current_buf].seq = current_seq++;
    /* Ignore return value since we can't do anything here. Overruns are logged in serial.c. */
    usart_send_packet_nonblocking(&adc_pkt[!current_buf].ll, sizeof(adc_pkt[!current_buf]));
    
    /*
    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;
            }
        }
    }
    */
}