altosui: Add config and pyro tabs to graph widget
[fw/altos] / src / samd21 / ao_dac_samd21.c
1 /*
2  * Copyright © 2020 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
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 along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18
19 #include <snek.h>
20 #include <ao.h>
21 #include <ao_dac-samd21.h>
22 #include <ao_tcc-samd21.h>
23
24 /* Max DAC output value. We're using left-adjusted values */
25 #define SNEK_DAC_MAX            65535
26
27 #ifdef SNEK_SAMD21_DAC_TIMER
28 /*
29  * If there's a timer available, we can use that
30  * to implement the 'tone' function
31  */
32
33 #include "sine.h"
34
35 #define NSINE   (sizeof(sine) / sizeof(sine[0]))
36
37 static uint16_t current_power;
38 static uint16_t power;
39 static uint32_t phase;
40 static uint32_t phase_step;
41 static volatile bool dac_running;
42
43 #define _paste2(x,y)    x ## y
44 #define _paste3(x,y,z)  x ## y ## z
45 #define paste2(x,y)     _paste2(x,y)
46 #define paste3(x,y,z)   _paste3(x,y,z)
47 #define SAMD21_TCC      paste2(samd21_tcc, SNEK_SAMD21_DAC_TIMER)
48 #define SAMD21_TCC_ISR  paste3(samd21_tcc, SNEK_SAMD21_DAC_TIMER, _isr)
49
50 #define AO_DAC_RATE     24000
51
52 #define UINT_TO_FIXED(u)        ((uint32_t) (u) << 16)
53 #define FIXED_TO_UINT(u)        ((u) >> 16)
54
55 void
56 SAMD21_TCC_ISR(void)
57 {
58         uint32_t intflag = SAMD21_TCC.intflag;
59         SAMD21_TCC.intflag = intflag;
60         if (intflag & (1 << SAMD21_TCC_INTFLAG_OVF)) {
61                 if (phase_step) {
62                         samd21_dac.data = ((uint32_t) sine[FIXED_TO_UINT(phase)] * current_power) >> 16;
63                         if ((phase += phase_step) >= UINT_TO_FIXED(NSINE)) {
64                                 phase -= UINT_TO_FIXED(NSINE);
65
66                                 current_power = power;
67
68                                 /* Stop output at zero crossing when no longer outputing tone */
69                                 if (!dac_running) {
70                                         phase_step = 0;
71                                         phase = 0;
72                                         SAMD21_TCC.intenclr = (1 << SAMD21_TCC_INTFLAG_OVF);
73                                 }
74                         }
75                 }
76         }
77 }
78
79 void
80 ao_dac_set_hz(float hz)
81 {
82         /* samples/second = AC_DAC_RATE
83          *
84          * cycles/second = hz
85          *
86          * samples/cycle = AC_DAC_RATE / hz
87          *
88          * step/cycle = 256
89          *
90          * step/sample = step/cycle * cycle/samples
91          *             = TWO_PI * hz / AC_DAC_RATE;
92          */
93         uint32_t new_phase_step = (float) UINT_TO_FIXED(NSINE) * hz / (float) AO_DAC_RATE;
94         ao_arch_critical(
95                 if (new_phase_step) {
96                         dac_running = true;
97                         phase_step = new_phase_step;
98                         SAMD21_TCC.intenset = (1 << SAMD21_TCC_INTFLAG_OVF);
99                 } else {
100                         dac_running = false;
101                 });
102 }
103
104 static void
105 ao_dac_timer_init(void)
106 {
107         /* Adjust timer to interrupt once per sample period */
108         SAMD21_TCC.per = AO_HCLK / AO_DAC_RATE;
109
110         /* Enable timer interrupts */
111         samd21_nvic_set_enable(paste3(SAMD21_NVIC_ISR_TCC, SNEK_SAMD21_DAC_TIMER, _POS));
112         samd21_nvic_set_priority(paste3(SAMD21_NVIC_ISR_TCC, SNEK_SAMD21_DAC_TIMER, _POS), 3);
113 }
114 #else
115 #define ao_dac_timer_init()
116 #endif
117
118 static void
119 ao_dac_sync(void)
120 {
121         while (samd21_dac.status & (1 << SAMD21_DAC_STATUS_SYNCBUSY))
122                 ;
123 }
124
125 void
126 ao_dac_set(uint16_t new_power)
127 {
128 #if SNEK_DAC_MAX != SNEK_PWM_MAX
129         new_power = (uint16_t) ((uint32_t) new_power * SNEK_DAC_MAX) / SNEK_PWM_MAX;
130 #endif
131
132         ao_arch_critical(
133 #ifdef SNEK_SAMD21_DAC_TIMER
134                 power = new_power;
135                 /*
136                  * When not generating a tone, just set the DAC
137                  * output to the requested level
138                  */
139                 if (!phase_step) {
140                         current_power = new_power;
141                         samd21_dac.data = new_power;
142                 }
143 #else
144                 samd21_dac.data = new_power;
145 #endif
146                 );
147 }
148
149 void
150 ao_dac_init(void)
151 {
152         /* supply a clock */
153         samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_DAC);
154
155         /* enable the device */
156         samd21_pm.apbcmask |= (1 << SAMD21_PM_APBCMASK_DAC);
157
158         /* reset */
159         samd21_dac.ctrla = (1 << SAMD21_DAC_CTRLA_SWRST);
160
161         while ((samd21_dac.ctrla & (1 << SAMD21_DAC_CTRLA_SWRST)) != 0 ||
162                (samd21_dac.status & (1 << SAMD21_DAC_STATUS_SYNCBUSY)) != 0)
163                 ao_arch_nop();
164
165         /* Configure using VDD as reference */
166         samd21_dac.ctrlb = ((1 << SAMD21_DAC_CTRLB_EOEN) |
167                             (0 << SAMD21_DAC_CTRLB_IOEN) |
168                             (1 << SAMD21_DAC_CTRLB_LEFTADJ) |
169                             (0 << SAMD21_DAC_CTRLB_VPD) |
170                             (1 << SAMD21_DAC_CTRLB_BDWP) |
171                             (SAMD21_DAC_CTRLB_REFSEL_VDDANA << SAMD21_DAC_CTRLB_REFSEL));
172
173         ao_dac_sync();
174
175         samd21_dac.ctrla = (1 << SAMD21_DAC_CTRLA_ENABLE);
176
177         ao_dac_sync();
178
179         ao_dac_timer_init();
180 }