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
|
// arch/avr32dd20.h: avr32dd20 support functions
// Copyright (C) 2023 Selene ToyKeeper
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "arch/avr32dd20.h"
////////// clock speed / delay stuff //////////
inline void mcu_clock_speed() {
// TODO: allow hwdef to define a base clock speed
// run the internal clock at 12 MHz, not the default 4 MHz
_PROTECTED_WRITE( CLKCTRL.OSCHFCTRLA,
CLKCTRL_FRQSEL_12M_gc | CLKCTRL_AUTOTUNE_bm );
// (another option is to use 20 MHz / 2, like attiny1616 does)
// divide 20 MHz to run at 10 MHz to match attiny1616
//_PROTECTED_WRITE( CLKCTRL.MCLKCTRLB,
// CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm );
}
///// clock dividers
// this should work, but needs further validation
inline void clock_prescale_set(uint8_t n) {
cli();
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, n); // Set the prescaler
while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm) {} // wait for clock change to finish
sei();
}
////////// ADC voltage / temperature //////////
// ADC0.CTRLA bits:
// RUNSTDBY, -, CONVMODE, LEFTADJ, RESSEL[1:0], FREERUN, ENABLE
// CTRLB bits: -, -, -, -, -, SAMPNUM[2:0]
// CTRLC bits: -, -, -, -, PRESC[3:0]
// CTRLD bits: INITDLY[2:0], -, SAMPDLY[3:0]
// CTRLE bits: -, -, -, -, -, WINCM[2:0]
// SAMPCTRL: 8 bits
// MUXPOS, MUXNEG: 7 bits each
// COMMAND: -, -, -, -, -, -, SPCONV, STCONV
inline void mcu_set_admux_therm() {
// ADC init: Datasheet section 33.3.2
// Temperature mode: Datasheet section 33.3.3.8
// measurements should be 12-bit right-adjusted single-ended conversion
// set Vref to 2.048V for temperature mode
VREF.ADC0REF = VREF_REFSEL_2V048_gc;
// set temp sensor as input
ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc;
// configure init delay to >= 25 us * Fclk_adc (no sample delay needed)
ADC0.CTRLD = ADC_INITDLY_DLY32_gc | ADC_SAMPDLY_DLY0_gc;
// configure ADC sample length to >= 28 us * Fclk_adc
ADC0.SAMPCTRL = 32;
// set single-ended or differential
// set resolution to 12 bits
// set left- or right-adjust
// set free-running mode or not (yes)
ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc
| ADC_RESSEL_12BIT_gc
| ADC_LEFTADJ_bm
| ADC_FREERUN_bm;
// set number of samples (requires adjustment in formula too)
ADC0.CTRLB = ADC_SAMPNUM_NONE_gc;
// accumulate more samples for more resolution
ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result
// set a clock prescaler
//ADC0.CTRLC = ADC_PRESC_DIV64_gc; // use this when no accumulation
ADC0.CTRLC = ADC_PRESC_DIV4_gc; // measure faster when oversampling
// enable the ADC
ADC0.CTRLA |= ADC_ENABLE_bm;
// actually start measuring (happens in another function)
//ADC0.COMMAND |= ADC_STCONV_bm;
// for each measurement:
// process according to sigrow data + formula
}
inline void mcu_set_admux_voltage() {
// ADC init: Datasheet section 33.3.2
// set Vref
VREF.ADC0REF = VREF_REFSEL_1V024_gc;
// set single-ended or differential
// set resolution to 12 bits
// set left- or right-adjust (right)
// set free-running mode or not (yes)
ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc
| ADC_RESSEL_12BIT_gc
| ADC_LEFTADJ_bm // has no effect when 16+ samples taken
| ADC_FREERUN_bm
| ADC_RUNSTBY_bm // allow voltage sense in standby mode
;
// set number of samples
ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result
// set a clock prescaler
ADC0.CTRLC = ADC_PRESC_DIV4_gc; // measure faster when oversampling
// select the positive ADC input with MUXPOS
#ifdef USE_VOLTAGE_DIVIDER // external voltage divider
// ADC input pin / Vref
ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER; // external pin
#elif defined (USE_VOLTAGE_VDDIO2) // internal voltage divider
// (Vbat / 10) / Vref
ADC0.MUXPOS = ADC_MUXPOS_VDDIO2DIV10_gc;
#else // measure directly on VDD/VCC pin
// (Vbat / 10) / Vref
ADC0.MUXPOS = ADC_MUXPOS_VDDDIV10_gc;
#endif
// enable the ADC
ADC0.CTRLA |= ADC_ENABLE_bm;
// actually start measuring (happens in another function)
//ADC0.COMMAND |= ADC_STCONV_bm;
}
inline void mcu_adc_sleep_mode() {
set_sleep_mode(SLEEP_MODE_STANDBY);
}
inline void mcu_adc_start_measurement() {
ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt
ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring
}
/*
void mcu_adc_on() {
hwdef_set_admux_voltage();
mcu_adc_start_measurement();
}
*/
inline void mcu_adc_off() {
ADC0.CTRLA &= ~(ADC_ENABLE_bm); // disable the ADC
}
inline void mcu_adc_vect_clear() {
ADC0.INTFLAGS = ADC_RESRDY_bm; // clear the interrupt
}
inline uint16_t mcu_adc_result() {
// value is 12-bit left-aligned + 16x oversampling = 16 bits total
return ADC0.RES;
}
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
// In : 65535 * (Vbat / 10) / 1.024V
// Out: uint8_t: Vbat * 40
// (add 80 to round up near a boundary)
uint8_t vbat40 = (uint16_t)(measurement + 80) / 160;
return vbat40;
}
#if 0
inline uint16_t mcu_vdd_raw2fine(uint16_t measurement) {
// In : 65535 * (Vbat / 10) / 1.024V
// Out: 65535 * (Vbat / 10) / 1.024V
// This MCU's native format is already correct
return measurement;
}
#endif
inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) {
// convert raw ADC values to calibrated temperature
// In: ADC raw temperature (16-bit, or 12-bit left-aligned)
// Out: Kelvin << 6
// Precision: 1/64th Kelvin (but noisy)
// AVR DD datasheet section 33.3.3.8
uint16_t sigrow_slope = SIGROW.TEMPSENSE0; // factory calibration data
uint16_t sigrow_offset = SIGROW.TEMPSENSE1; // 12-bit value
//const uint32_t scaling_factor = 4096; // use top 12 bits of ADC data
//uint32_t temp = sigrow_offset - (measurement >> 4);
const uint32_t scaling_factor = 65536; // use all 16 bits of ADC data
uint32_t temp = (sigrow_offset << 4) - measurement;
temp *= sigrow_slope; // 24-bit result
temp += scaling_factor / 8; // Add 1/8th K to get correct rounding on later divisions
//temp = temp >> 6; // change (K << 12) to (K << 6)
temp = temp >> 10; // change (K << 16) to (K << 6)
return temp; // left-aligned uint16_t, 0 to 1023.98 Kelvin
}
inline uint8_t mcu_adc_lsb() {
// volts and temp are both 16-bit, so the LSB is useful as-is
return ADC0_RESL;
}
////////// WDT //////////
// this uses the RTC PIT interrupt instead of WDT,
// as recommended on AVR 0/1-series and later:
// https://github.com/SpenceKonde/DxCore/blob/master/megaavr/extras/PowerSave.md#using-the-real-time-counter-rtcpit
// The PIT runs even in power-down mode, unlike RTC,
// and its cycles are relative to the AVR's internal 32768 Hz ULP oscillator
// AVR datasheet sections 26, 26.5, 26.9, 26.12, 26.13.12
// PIT tick speeds:
// 0 (none)
// 1 8192 Hz (CYC4)
// 2 4096 Hz
// 3 2048 Hz
// 4 1024 Hz (CYC32)
// 5 512 Hz
// 6 256 Hz
// 7 128 Hz
// 8 64 Hz (default) (CYC512)
inline void mcu_wdt_active() {
RTC.PITINTCTRL = RTC_PI_bm; // enable the Periodic Interrupt
while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated
// Period = 16ms (64 Hz), enable the PI Timer
RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm;
}
// STANDBY_TICK_SPEED:
// 0 64 Hz
// 1 32 Hz
// 2 16 Hz
// 3 8 Hz (default)
// 4 4 Hz
// 5 2 Hz
// 6 1 Hz
inline void mcu_wdt_standby() {
RTC.PITINTCTRL = RTC_PI_bm; // enable the Periodic Interrupt
while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated
// Set period (64 Hz / STANDBY_TICK_SPEED = 8 Hz), enable the PI Timer
RTC.PITCTRLA = ((8+STANDBY_TICK_SPEED)<<3) | RTC_PITEN_bm;
}
inline void mcu_wdt_stop() {
while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated
RTC.PITCTRLA = 0; // Disable the PI Timer
}
inline void mcu_wdt_vect_clear() {
RTC.PITINTFLAGS = RTC_PI_bm; // clear the PIT interrupt flag
}
////////// PCINT - pin change interrupt (e-switch) //////////
inline void mcu_switch_vect_clear() {
// Write a '1' to clear the interrupt flag
SWITCH_INTFLG |= (1 << SWITCH_PIN);
}
inline void mcu_pcint_on() {
SWITCH_ISC_REG |= PORT_ISC_BOTHEDGES_gc;
}
inline void mcu_pcint_off() {
SWITCH_ISC_REG &= ~(PORT_ISC_gm);
}
////////// misc //////////
void reboot() {
// request a reboot (software reset)
_PROTECTED_WRITE(RSTCTRL.SWRR, RSTCTRL_SWRST_bm);
}
inline void prevent_reboot_loop() {
// if previous reset was a crash (WDT time-out),
// prevent it from happening again immediately
//RSTCTRL.RSTFR &= ~(RSTCTRL_WDRF_bm); // reset wdt flag only
RSTCTRL.RSTFR = 0; // reset all reset status flags (maybe unneeded?)
wdt_disable(); // from avr/wdt.h
}
|