aboutsummaryrefslogtreecommitdiff
path: root/arch/attiny1616.c
blob: c5499ddc53a4b96b97273eb0b8cf7e06ebac4bed (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
// arch/attiny1616.c: attiny1616 support functions
// Copyright (C) 2023 Selene ToyKeeper
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include "arch/attiny1616.h"

////////// clock speed / delay stuff //////////

inline void mcu_clock_speed() {
    // TODO: allow hwdef to define a base clock speed
    // set up the system clock to run at 10 MHz instead of the default 3.33 MHz
    _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 //////////

inline void mcu_set_admux_therm() {
    // put the ADC in temperature mode
    // attiny1616 datasheet section 30.3.2.6
    mcu_set_adc0_vref(VREF_ADC0REFSEL_1V1_gc);  // Set Vbg ref to 1.1V
    ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc;  // read temperature
    ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc;  // 10-bit result + 4x oversampling
    ADC0.CTRLC = ADC_SAMPCAP_bm
               | ADC_PRESC_DIV16_gc
               | ADC_REFSEL_INTREF_gc; // Internal ADC reference
}

inline void mcu_set_admux_voltage() {
    // Enabled, free-running (aka, auto-retrigger), run in standby
    ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm | ADC_RUNSTBY_bm;
    // set a INITDLY value because the AVR manual says so (section 30.3.5)
    // (delay 1st reading until Vref is stable)
    ADC0.CTRLD |= ADC_INITDLY_DLY16_gc;
    #ifdef USE_VOLTAGE_DIVIDER  // measure an arbitrary pin
        // result = resolution * Vdiv / 1.1V
        mcu_set_adc0_vref(VREF_ADC0REFSEL_1V1_gc);  // Set Vbg ref to 1.1V
        ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER;  // read the requested ADC pin
        ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc;  // 12-bit result, 4x oversampling
        ADC0.CTRLC = ADC_SAMPCAP_bm
                   | ADC_PRESC_DIV16_gc
                   | ADC_REFSEL_INTREF_gc; // Use internal ADC reference
    #else  // measure VDD pin
        // result = resolution * 1.5V / Vbat
        mcu_set_adc0_vref(VREF_ADC0REFSEL_1V5_gc);  // Set Vbg ref to 1.5V
        ADC0.MUXPOS = ADC_MUXPOS_INTREF_gc;  // read internal reference
        ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc;  // 12-bit result, 4x oversampling
        ADC0.CTRLC = ADC_SAMPCAP_bm
                   | ADC_PRESC_DIV16_gc
                   | ADC_REFSEL_VDDREF_gc; // Vdd (Vcc) be ADC reference
    #endif
}

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
}

/*
inline void mcu_adc_on() {
    VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc; // Set Vbg ref to 1.1V
    // Enabled, free-running (aka, auto-retrigger), run in standby
    ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm | ADC_RUNSTBY_bm;
    // set a INITDLY value because the AVR manual says so (section 30.3.5)
    // (delay 1st reading until Vref is stable)
    ADC0.CTRLD |= ADC_INITDLY_DLY16_gc;
    hwdef_set_admux_voltage();
}
*/

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_temp() {
    // just return left-aligned ADC result, don't convert to calibrated units
    //return ADC0.RES << 6;
    return ADC0.RES << 4;
}

inline uint16_t mcu_adc_result_volts() {
    // ADC has no left-aligned mode, so left-align it manually
    return ADC0.RES << 4;
}

inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
    // In : 65535 * 1.5 / Vbat
    // Out: uint8_t: Vbat * 40
    // 1.5 = ADC Vref
    #if 0
    // 1024 = how much ADC resolution we're using (10 bits)
    // (12 bits available, but it costs an extra 84 bytes of ROM to calculate)
    uint8_t vbat40 = (uint16_t)(40 * 1.5 * 1024) / (measurement >> 6);
    #else
    // ... spend the extra 84 bytes of ROM for better precision
    // 4096 = how much ADC resolution we're using (12 bits)
    uint8_t vbat40 = (uint32_t)(40 * 1.5 * 4096) / (measurement >> 4);
    #endif
    return vbat40;
}

#if 0  // fine voltage, 0 to 10.24V in 1/6400th V steps
inline uint16_t mcu_vdd_raw2fine(uint16_t measurement) {
    // In : 65535 * 1.5 / Vbat
    // Out: 65535 * (Vbat / 10) / 1.024V
    uint16_t voltage = ((uint32_t)(1.5 * 4096 * 100 * 64 * 16) / measurement;
    return voltage;
}
#endif

#ifdef USE_VOLTAGE_DIVIDER
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
    // In : 4095 * Vdiv / 1.1V
    // Out: uint8_t: Vbat * 40
    // Vdiv = Vbat / 4.3  (typically)
    // 1.1 = ADC Vref
    const uint16_t adc_per_volt =
            (((uint16_t)ADC_44 << 4) - ((uint16_t)ADC_22 << 4))
            / (4 * (44-22));
    uint8_t result = measurement / adc_per_volt;
    return result;
}
#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)
    // attiny1616 datasheet section 30.3.2.6
    uint8_t sigrow_gain  = SIGROW.TEMPSENSE0;  // factory calibration data
    int8_t sigrow_offset = SIGROW.TEMPSENSE1;
    const uint32_t scaling_factor = 65536;  // use all 16 bits of ADC data
    uint32_t temp = measurement - (sigrow_offset << 6);
    temp *= sigrow_gain;  // 24-bit result
    temp += scaling_factor / 8;  // Add 1/8th K to get correct rounding on later divisions
    temp = temp >> 8;  // change (K << 14) to (K << 6)
    return temp;  // left-aligned uint16_t, 0 to 1023.98 Kelvin
}

inline uint8_t mcu_adc_lsb() {
    //return (ADCL >> 6) + (ADCH << 2);
    return ADC0.RESL; // right aligned, not left... so should be equivalent?
}


////////// WDT //////////

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;
}

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 = (1<<6) | (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() {
    // put the WDT in hard reset mode, then trigger it
    cli();
    // Enable, timeout 8ms
    _PROTECTED_WRITE(WDT.CTRLA, WDT_PERIOD_8CLK_gc);
    sei();
    wdt_reset();
    while (1) {}
}

inline void prevent_reboot_loop() {
    // prevent WDT from rebooting MCU again
    RSTCTRL.RSTFR &= ~(RSTCTRL_WDRF_bm);  // reset status flag
    wdt_disable();  // from avr/wdt.h
}