Merge lp:~i-dan-3/flashlight-firmware/pwm-strobe into lp:flashlight-firmware

Proposed by Dan Stahlke
Status: Needs review
Proposed branch: lp:~i-dan-3/flashlight-firmware/pwm-strobe
Merge into: lp:flashlight-firmware
Diff against target: 303 lines (+190/-4)
2 files modified
ToyKeeper/hwdef-Emisar_D4v2.h (+56/-0)
ToyKeeper/spaghetti-monster/anduril/anduril.c (+134/-4)
To merge this branch: bzr merge lp:~i-dan-3/flashlight-firmware/pwm-strobe
Reviewer Review Type Date Requested Status
Selene ToyKeeper Pending
Review via email: mp+375470@code.launchpad.net

Commit message

Added accurate 16-bit PWM strobe modes

Disabled by default. Only supported on CPUs having a 16-bit PWM, which means only attiny1634, which means only Emisar D4v2 and D4Sv2. I don't have a D4Sv2 to test, so currently it's only implemented on D4v2. The relevant functions could probably be directly copied from hwdef-Emisar_D4v2.h to hwdef-Emisar_D4Sv2.h.

Strobe rate can be continuously varied from 1Hz to 500Hz. Theoretically it could run faster, but this is already enough to freeze something that's spinning at 30000 RPM. Uses full brightness (1x7135 plus FET) with a 1:256 duty cycle (party strobe) or 1:4 duty cycle (tactical strobe). Rate adjustment has acceleration, going twice as fast after 1 second and 4x as fast after 2 seconds. It still takes about 10 seconds to traverse the whole range from 1Hz to 500Hz.

With this I was able to freeze my Proxon drill (similar to Dremel) running at 20000 RPM. Unfortunately the flash rate fluctuates by about 0.1%, probably due to the CPU's RC oscillator.

To post a comment you must log in.

Unmerged revisions

238. By Dan Stahlke <dstahlke@psi> on 2019-11-13

Added accurate 16-bit PWM strobe modes

Disabled by default. Only supported on CPUs having a 16-bit PWM, which means only attiny1634, which means only Emisar D4v2 and D4Sv2. I don't have a D4Sv2 to test, so currently it's only implemented on D4v2. The relevant functions could probably be directly copied from hwdef-Emisar_D4v2.h to hwdef-Emisar_D4Sv2.h.

Strobe rate can be continuously varied from 1Hz to 500Hz. Theoretically it could run faster, but this is already enough to freeze something that's spinning at 30000 RPM. Uses full brightness (1x7135 plus FET) with a 1:256 duty cycle (party strobe) or 1:4 duty cycle (tactical strobe). Rate adjustment has acceleration, going twice as fast after 1 second and 4x as fast after 2 seconds. It still takes about 10 seconds to traverse the whole range from 1Hz to 500Hz.

With this I was able to freeze my Proxon drill (similar to Dremel) running at 20000 RPM. Unfortunately the flash rate fluctuates by about 0.1%, probably due to the CPU's RC oscillator.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ToyKeeper/hwdef-Emisar_D4v2.h'
2--- ToyKeeper/hwdef-Emisar_D4v2.h 2019-08-05 07:40:20 +0000
3+++ ToyKeeper/hwdef-Emisar_D4v2.h 2019-11-13 06:37:16 +0000
4@@ -95,6 +95,62 @@
5 SWITCH_PCMSK = (1 << SWITCH_PCINT); // enable pin change interrupt
6 }
7
8+#if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
9+void exit_pwm_strobe() {
10+ // copied from hwdef_setup
11+ TCCR1A = (0<<WGM11) | (1<<WGM10) // 8-bit (TOP=0xFF) (DS table 12-5)
12+ | (1<<COM1A1) | (0<<COM1A0) // PWM 1A in normal direction (DS table 12-4)
13+ | (1<<COM1B1) | (0<<COM1B0) // PWM 1B in normal direction (DS table 12-4)
14+ ;
15+ TCCR1B = (0<<CS12) | (0<<CS11) | (1<<CS10) // clk/1 (no prescaling) (DS table 12-6)
16+ | (0<<WGM13) | (0<<WGM12) // phase-correct PWM (DS table 12-5)
17+ ;
18+ PWM1_LVL = 0;
19+ PWM2_LVL = 0;
20+}
21+
22+void init_pwm_strobe(uint32_t delay32, uint8_t tactical) {
23+ uint16_t delay16;
24+ uint8_t cs;
25+ if (delay32 <= 0xffff) {
26+ delay16 = (uint16_t)delay32;
27+ cs = (1<<CS10); // clk/1
28+ } else if ((delay32 >> 3) <= 0xffff) {
29+ delay16 = (uint16_t)(delay32 >> 3);
30+ cs = (1<<CS11); // clk/8
31+ } else if ((delay32 >> 6) <= 0xffff) {
32+ delay16 = (uint16_t)(delay32 >> 6);
33+ cs = (1<<CS11) | (1<<CS10); // clk/64
34+ } else {
35+ delay16 = (uint16_t)(delay32 >> 10);
36+ cs = (1<<CS12) | (1<<CS10); // clk/1024
37+ }
38+
39+ TCCR1A = (1<<WGM11) | (0<<WGM10)
40+ | (1<<COM1A1) | (0<<COM1A0) // PWM 1A in normal direction (DS table 12-4)
41+ | (1<<COM1B1) | (0<<COM1B0) // PWM 1B in normal direction (DS table 12-4)
42+ ;
43+ TCCR1B = (0<<CS12) | (0<<CS11) | (0<<CS10) // clock turned off
44+ | (1<<WGM13) | (1<<WGM12) // WGM=1110 (fast PWM, TOP=ICR1) (DS table 12-5)
45+ ;
46+
47+ if (tactical) {
48+ PWM1_LVL = delay16 >> 8;
49+ PWM2_LVL = delay16 >> 8;
50+ } else {
51+ PWM1_LVL = delay16 >> 2;
52+ PWM2_LVL = delay16 >> 2;
53+ }
54+ ICR1 = delay16;
55+ if (TCNT1 >= delay16)
56+ TCNT1 = delay16 - 1;
57+
58+ TCCR1B = cs
59+ | (1<<WGM13) | (1<<WGM12) // WGM=1110 (fast PWM, TOP=ICR1) (DS table 12-5)
60+ ;
61+}
62+#endif
63+
64 #define LAYOUT_DEFINED
65
66 #endif
67
68=== modified file 'ToyKeeper/spaghetti-monster/anduril/anduril.c'
69--- ToyKeeper/spaghetti-monster/anduril/anduril.c 2019-08-05 07:40:20 +0000
70+++ ToyKeeper/spaghetti-monster/anduril/anduril.c 2019-11-13 06:37:16 +0000
71@@ -66,6 +66,9 @@
72 #define USE_BIKE_FLASHER_MODE
73 #define USE_PARTY_STROBE_MODE
74 #define USE_TACTICAL_STROBE_MODE
75+// PWM strobe modes require 16-bit timer, and only attiny1634 has that.
76+//#define USE_PWM_PARTY_STROBE_MODE
77+//#define USE_PWM_TACTICAL_STROBE_MODE
78 #define USE_LIGHTNING_MODE
79 #define USE_CANDLE_MODE
80
81@@ -140,7 +143,7 @@
82 #endif
83 #endif
84
85-#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
86+#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
87 #define USE_STROBE_STATE
88 #endif
89
90@@ -172,6 +175,11 @@
91 strobe_delays_0_e,
92 strobe_delays_1_e,
93 #endif
94+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
95+ strobe_delays_l_e,
96+ strobe_delays_m_e,
97+ strobe_delays_h_e,
98+ #endif
99 #ifdef USE_BIKE_FLASHER_MODE
100 bike_flasher_brightness_e,
101 #endif
102@@ -438,6 +446,12 @@
103 #ifdef USE_TACTICAL_STROBE_MODE
104 tactical_strobe_e,
105 #endif
106+ #ifdef USE_PWM_PARTY_STROBE_MODE
107+ pwm_party_strobe_e,
108+ #endif
109+ #ifdef USE_PWM_TACTICAL_STROBE_MODE
110+ pwm_tactical_strobe_e,
111+ #endif
112 #ifdef USE_LIGHTNING_MODE
113 lightning_storm_e,
114 #endif
115@@ -465,6 +479,15 @@
116 volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe
117 #endif
118
119+#if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
120+// party / tactical strobe timing, using 16-bit pwm
121+// Note: if pwm_strobe_delay_max is more than 24 bits, then load_config and
122+// save_config will need to be modified (they only save the lower 24 bits).
123+static const uint32_t pwm_strobe_delay_max = F_CPU; // min freq 1Hz
124+static const uint32_t pwm_strobe_delay_min = F_CPU / 500; // max freq 500Hz
125+volatile uint32_t pwm_strobe_delay = F_CPU / 10; // default freq 10Hz
126+#endif
127+
128 // bike mode config options
129 #ifdef USE_BIKE_FLASHER_MODE
130 volatile uint8_t bike_flasher_brightness = MAX_1x7135;
131@@ -1093,6 +1116,53 @@
132
133
134 #ifdef USE_STROBE_STATE
135+
136+#if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
137+// Factoring out this common code saves 72 bytes. Returning uint16_t instead of
138+// uint32_t saves another 48 bytes, but that optimization is only valid if
139+// pwm_strobe_delay_max >> 7 is a 16-bit value. That's true (just barely) with
140+// an 8MHz clock and 1Hz min blink rate, but the optimization probably isn't
141+// worth the risk in case these numbers change.
142+uint32_t pwm_strobe_step_size(uint16_t ticks) {
143+ if (ticks >= TICKS_PER_SECOND*2) {
144+ return pwm_strobe_delay >> 7;
145+ } else if (ticks >= TICKS_PER_SECOND) {
146+ return pwm_strobe_delay >> 8;
147+ } else {
148+ return pwm_strobe_delay >> 9;
149+ }
150+}
151+
152+void pwm_strobe_rate_faster(uint16_t ticks) {
153+ pwm_strobe_delay -= pwm_strobe_step_size(ticks);
154+ if (pwm_strobe_delay < pwm_strobe_delay_min)
155+ pwm_strobe_delay = pwm_strobe_delay_min;
156+}
157+
158+void pwm_strobe_rate_slower(uint16_t ticks) {
159+ pwm_strobe_delay += pwm_strobe_step_size(ticks);
160+ if (pwm_strobe_delay > pwm_strobe_delay_max)
161+ pwm_strobe_delay = pwm_strobe_delay_max;
162+}
163+
164+uint8_t is_pwm_strobe_mode(strobe_mode_te st) {
165+ #ifdef USE_PWM_TACTICAL_STROBE_MODE
166+ if (st == pwm_tactical_strobe_e) return 1;
167+ #endif
168+ #ifdef USE_PWM_PARTY_STROBE_MODE
169+ if (st == pwm_party_strobe_e) return 1;
170+ #endif
171+ return 0;
172+}
173+
174+uint8_t is_pwm_tactical_strobe_mode(strobe_mode_te st) {
175+ #ifdef USE_PWM_TACTICAL_STROBE_MODE
176+ if (st == pwm_tactical_strobe_e) return 1;
177+ #endif
178+ return 0;
179+}
180+#endif
181+
182 uint8_t strobe_state(Event event, uint16_t arg) {
183 static int8_t ramp_direction = 1;
184
185@@ -1114,17 +1184,43 @@
186 // init anything which needs to be initialized
187 else if (event == EV_enter_state) {
188 ramp_direction = 1;
189+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
190+ if (is_pwm_strobe_mode(st)) {
191+ #ifdef USE_DYNAMIC_UNDERCLOCKING
192+ clock_prescale_set(clock_div_1);
193+ #endif // ifdef USE_DYNAMIC_UNDERCLOCKING
194+ init_pwm_strobe(pwm_strobe_delay, is_pwm_tactical_strobe_mode(st));
195+ }
196+ #endif
197 return MISCHIEF_MANAGED;
198 }
199 // 1 click: off
200 else if (event == EV_1click) {
201+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
202+ if (is_pwm_strobe_mode(st)) {
203+ exit_pwm_strobe();
204+ }
205+ #endif
206 set_state(off_state, 0);
207 return MISCHIEF_MANAGED;
208 }
209 // 2 clicks: rotate through strobe/flasher modes
210 else if (event == EV_2clicks) {
211- strobe_type = (st + 1) % NUM_STROBES;
212+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
213+ if (is_pwm_strobe_mode(st)) {
214+ exit_pwm_strobe();
215+ }
216+ #endif
217+ strobe_type = st = (st + 1) % NUM_STROBES;
218 save_config();
219+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
220+ if (is_pwm_strobe_mode(st)) {
221+ #ifdef USE_DYNAMIC_UNDERCLOCKING
222+ clock_prescale_set(clock_div_1);
223+ #endif // ifdef USE_DYNAMIC_UNDERCLOCKING
224+ init_pwm_strobe(pwm_strobe_delay, is_pwm_tactical_strobe_mode(st));
225+ }
226+ #endif
227 return MISCHIEF_MANAGED;
228 }
229 // hold: change speed (go faster)
230@@ -1149,6 +1245,17 @@
231 }
232 #endif
233
234+ // party / tactical strobe faster/slower
235+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
236+ else if (is_pwm_strobe_mode(st)) {
237+ if (ramp_direction > 0)
238+ pwm_strobe_rate_faster(arg);
239+ else
240+ pwm_strobe_rate_slower(arg);
241+ init_pwm_strobe(pwm_strobe_delay, is_pwm_tactical_strobe_mode(st));
242+ }
243+ #endif
244+
245 // lightning has no adjustments
246 //else if (st == lightning_storm_e) {}
247
248@@ -1191,6 +1298,14 @@
249 }
250 #endif
251
252+ // party / tactical strobe faster
253+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
254+ else if (is_pwm_strobe_mode(st)) {
255+ pwm_strobe_rate_faster(arg);
256+ init_pwm_strobe(pwm_strobe_delay, is_pwm_tactical_strobe_mode(st));
257+ }
258+ #endif
259+
260 // lightning has no adjustments
261 //else if (st == lightning_storm_e) {}
262
263@@ -2387,11 +2502,19 @@
264 #ifdef USE_TINT_RAMPING
265 tint = eeprom[tint_e];
266 #endif
267- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
268+ #ifdef USE_STROBE_STATE
269 strobe_type = eeprom[strobe_type_e]; // TODO: move this to eeprom_wl?
270+ #endif
271+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
272 strobe_delays[0] = eeprom[strobe_delays_0_e];
273 strobe_delays[1] = eeprom[strobe_delays_1_e];
274 #endif
275+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
276+ pwm_strobe_delay =
277+ ((uint32_t)eeprom[strobe_delays_h_e] << 16) |
278+ ((uint32_t)eeprom[strobe_delays_m_e] << 8) |
279+ ((uint32_t)eeprom[strobe_delays_l_e]);
280+ #endif
281 #ifdef USE_BIKE_FLASHER_MODE
282 bike_flasher_brightness = eeprom[bike_flasher_brightness_e];
283 #endif
284@@ -2435,11 +2558,18 @@
285 #ifdef USE_TINT_RAMPING
286 eeprom[tint_e] = tint;
287 #endif
288- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
289+ #ifdef USE_STROBE_STATE
290 eeprom[strobe_type_e] = strobe_type; // TODO: move this to eeprom_wl?
291+ #endif
292+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
293 eeprom[strobe_delays_0_e] = strobe_delays[0];
294 eeprom[strobe_delays_1_e] = strobe_delays[1];
295 #endif
296+ #if defined(USE_PWM_PARTY_STROBE_MODE) || defined(USE_PWM_TACTICAL_STROBE_MODE)
297+ eeprom[strobe_delays_h_e] = (uint8_t)(pwm_strobe_delay >> 16);
298+ eeprom[strobe_delays_m_e] = (uint8_t)(pwm_strobe_delay >> 8);
299+ eeprom[strobe_delays_l_e] = (uint8_t)(pwm_strobe_delay);
300+ #endif
301 #ifdef USE_BIKE_FLASHER_MODE
302 eeprom[bike_flasher_brightness_e] = bike_flasher_brightness;
303 #endif