-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystem.c
More file actions
147 lines (120 loc) · 5.26 KB
/
Copy pathsystem.c
File metadata and controls
147 lines (120 loc) · 5.26 KB
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
// system.c — see system.h for the module overview.
//
// *** Deviation from the original plan, flagged explicitly ***
// The project spec calls for Timer0 at a 64 prescaler firing every 1ms.
// At F_CPU=20MHz that's not physically achievable on an 8-bit timer: the
// timer clock at /64 is 312.5 kHz, so one period would need OCR0A=311,
// but OCR0A is only 8 bits wide (max 255) -- the maximum period available
// at /64 is 256/312500 s = ~0.819ms, and it can't be divided evenly into
// exactly 1ms in the first place (312.5 isn't an integer).
//
// Instead this uses prescaler /256 with OCR0A=77 (78 counts), which gives
// an interrupt every 78*256/20000000 s = 0.9984ms -- about 0.16% fast.
// That's ~1.6ms of drift per second of uptime, which is negligible for
// frame pacing / debounce timers / animation but not lab-grade accurate.
// If you need long-run wall-clock accuracy, either move the tick to
// Timer1/Timer2 (16-bit gives more achievable divisors) or add a
// fractional accumulator (Arduino's wiring.c does this for a similar
// reason) to average out the error over many ticks.
#include "system.h"
#include <avr/io.h>
#include <avr/interrupt.h>
static volatile uint32_t s_millis = 0;
static void timer0Init(void) {
TCCR0A = (1 << WGM01); // CTC mode, TOP = OCR0A
TCCR0B = (1 << CS02); // prescaler /256
OCR0A = 77; // ~0.9984 ms/tick -- see note above
TIMSK0 = (1 << OCIE0A);
}
ISR(TIMER0_COMPA_vect) {
s_millis++;
}
void systemInit(void) {
timer0Init();
}
uint32_t systemGetTimeMs(void) {
uint32_t ms;
uint8_t sreg = SREG;
cli();
ms = s_millis;
SREG = sreg;
return ms;
}
// ---- Backlight Hardware PWM (Timer1 / OC1B / PB2) -----------------------
static uint8_t s_backlightPercent = 100;
void systemBacklightSetBrightness(uint8_t percent) {
s_backlightPercent = (percent > 100) ? 100 : percent;
// Apply a gamma curve (x^2) to the brightness to linearize human perception
uint32_t gammaPercent = ((uint32_t)s_backlightPercent * s_backlightPercent) / 100;
// Active low backlight: 0% brightness = 100% duty cycle (always high)
// Non-inverting Fast PWM (COM1B1=1, COM1B0=0): clears on compare match, sets at BOTTOM.
uint32_t top = ICR1;
OCR1B = (uint16_t)(top * (100 - gammaPercent) / 100);
}
// ---- Audio Engine (Timer1 / OC1A / PB1) --------------------------------
static uint8_t s_audioVolume = 100;
void systemAudioSetVolume(uint8_t vol) {
if (vol < 50) {
s_audioVolume = 0;
} else {
s_audioVolume = (vol > 100) ? 100 : (uint8_t)((vol - 50) * 2);
}
if (s_audioVolume == 0) {
systemAudioStop();
} else {
// If audio is currently playing, update duty cycle immediately
if (ICR1 > 0 && OCR1A > 0) {
uint32_t volGamma = ((uint32_t)s_audioVolume * s_audioVolume) / 100;
OCR1A = (uint16_t)(((uint32_t)ICR1 / 8) * volGamma / 100);
TCCR1A |= (1 << COM1A1); // Re-connect if disconnected
}
}
}
void systemAudioInit(void) {
DDRB |= (1 << PB1) | (1 << PB2); // OC1A (Audio) and OC1B (Backlight) as outputs
// Fast PWM, mode 14 (WGM13:0 = 1110), TOP = ICR1, non-inverting on OC1A and OC1B.
TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10); // Start with /1 prescaler
ICR1 = 1999; // Default 10kHz frequency for backlight before audio plays
systemBacklightSetBrightness(100);
systemAudioStop();
}
void systemAudioSetFrequency(uint16_t freqHz) {
if (freqHz == 0 || s_audioVolume == 0) {
systemAudioStop();
return;
}
// Try prescalers smallest-first so we keep the most timing resolution
// while still fitting the 16-bit TOP value.
static const uint16_t prescalers[] = {1, 8, 64, 256, 1024};
static const uint8_t csBits[] = {
(1 << CS10),
(1 << CS11),
(1 << CS11) | (1 << CS10),
(1 << CS12),
(1 << CS12) | (1 << CS10),
};
uint8_t i;
uint32_t top = 0xFFFFFFUL;
for (i = 0; i < 5; i++) {
top = (F_CPU / ((uint32_t)prescalers[i] * freqHz)) - 1;
if (top <= 0xFFFF) break;
}
if (top > 0xFFFF) top = 0xFFFF; // clamp for very low, out-of-range tones
TCCR1B = (TCCR1B & (uint8_t)~((1 << CS12) | (1 << CS11) | (1 << CS10))) | csBits[i];
ICR1 = (uint16_t)top;
uint32_t volGamma = ((uint32_t)s_audioVolume * s_audioVolume) / 100;
// 12.5% duty cycle max, scaled by gamma volume curve. Explicit uint32_t cast.
OCR1A = (uint16_t)(((uint32_t)top / 8) * volGamma / 100);
TCCR1A |= (1 << COM1A1); // Ensure pin is connected to Timer
systemBacklightSetBrightness(s_backlightPercent); // Re-sync OCR1B against new ICR1 top
}
void systemAudioStop(void) {
OCR1A = 0;
TCCR1A &= ~(1 << COM1A1); // Disconnect OC1A
PORTB &= ~(1 << PB1); // Ensure pin is low
// Reset Timer1 to a high frequency (10kHz) for smooth backlight PWM when audio is idle
TCCR1B = (TCCR1B & (uint8_t)~((1 << CS12) | (1 << CS11) | (1 << CS10))) | (1 << CS10);
ICR1 = 1999;
systemBacklightSetBrightness(s_backlightPercent); // Re-sync OCR1B
}