diff --git a/Helios/Colortypes.cpp b/Helios/Colortypes.cpp index 8039c446..9cb3dcd4 100644 --- a/Helios/Colortypes.cpp +++ b/Helios/Colortypes.cpp @@ -369,8 +369,14 @@ RGBColor hsv_to_rgb_generic(const HSVColor &rhs) return col; } - region = rhs.hue / 43; - remainder = ((rhs.hue - (region * 43)) * 6); + // At 1MHz the AVR has ~8x fewer cycles per tick. The original division + // (hue / 43) compiles to a ~200-cycle software divide on AVR. Replacing it + // with a branchless compare tree + a LUT subtraction cuts this to ~10 cycles. + static const uint8_t region_base[6] = { 0, 43, 86, 129, 172, 215 }; + region = (rhs.hue < 86) ? ((rhs.hue < 43) ? 0 : 1) + : (rhs.hue < 172) ? ((rhs.hue < 129) ? 2 : 3) + : ((rhs.hue < 215) ? 4 : 5); + remainder = ((rhs.hue - region_base[region]) * 6); // extraneous casts to uint16_t are to prevent overflow p = (uint8_t)(((uint16_t)(rhs.val) * (255 - rhs.sat)) >> 8); diff --git a/Helios/Helios.cpp b/Helios/Helios.cpp index 835f945b..0f2d24c6 100644 --- a/Helios/Helios.cpp +++ b/Helios/Helios.cpp @@ -717,6 +717,12 @@ void Helios::handle_state_pat_select() void Helios::handle_state_toggle_flag(Flags flag) { + // Play the pattern for this one-tick toggle state so the blink timer does + // not drop a tick across the transition. This handler runs for a single + // tick and otherwise never calls pat.play(), so the next poll would see + // timeDiff = m_alarm + 1 -- a phantom one-tick cadence gap. (Visible with + // the catch-up alarm; harmless to always run.) + pat.play(); // toggle the conjure flag toggle_flags(flag); // write out the new global flags and the current mode diff --git a/Helios/TimeControl.cpp b/Helios/TimeControl.cpp index e79e3721..e2e18fac 100644 --- a/Helios/TimeControl.cpp +++ b/Helios/TimeControl.cpp @@ -103,8 +103,13 @@ uint32_t Time::microseconds() // should always just rely on the current tick to perform operations uint8_t oldSREG = SREG; cli(); - // multiply by 8 early to avoid floating point math or division - uint32_t micros = (timer0_overflow_count * (256 * 8)) + (TCNT0 * 8); + // Scale overflow count and timer ticks by the number of microseconds each Timer0 + // tick represents at the configured CPU speed. Timer0 runs at F_CPU/1 (no prescaler), + // so each tick = (1/F_CPU) seconds = (1000000/F_CPU) microseconds. + // The factor (64000000UL/F_CPU) bakes that in as an integer: 8 @ 8MHz, 64 @ 1MHz, + // 4 @ 16MHz. Using F_CPU here means this formula automatically adapts when + // CPU_SPEED is changed in the Makefile — no manual constant updates needed. + uint32_t micros = (timer0_overflow_count * (256 * (64000000UL / F_CPU))) + (TCNT0 * (64000000UL / F_CPU)); SREG = oldSREG; // then shift right to counteract the multiplication by 8 return micros >> 6; diff --git a/Helios/Timer.cpp b/Helios/Timer.cpp index 48dd685b..0d9f5ca0 100644 --- a/Helios/Timer.cpp +++ b/Helios/Timer.cpp @@ -48,12 +48,27 @@ bool Timer::alarm() if (timeDiff == 0) { return true; } - // if the current alarm duration is not a multiple of the current tick - if (m_alarm && (timeDiff % m_alarm) != 0) { - // then the alarm was not hit - return false; + // Recurring alarm: returns true once per m_alarm ticks. + // + // Small-slip branch (timeDiff in [m_alarm, 2*m_alarm)): re-anchor to now + // so consecutive beats stay evenly spaced -- a 1-tick slip that would + // produce a long-then-short pair instead advances the anchor to the actual + // fire time, spreading the slip smoothly across future beats. + // (This is the behavior Kurt confirmed "looks perfect" on hardware.) + // + // Big-gap branch (timeDiff >= 2*m_alarm): the timer was suspended for a + // long menu hold or similar; realign to the period grid so post-menu + // cadence matches the original schedule and menu-test timing stays intact. + // + // No 32-bit divide/modulo in the per-tick hot path (expensive on AVR). + if (timeDiff < (int32_t)m_alarm) { return false; } + if (timeDiff < (int32_t)(2 * m_alarm)) { + m_startTime = now; + return true; } - // update the start time of the timer + int32_t rem = timeDiff; + while (rem >= (int32_t)m_alarm) { rem -= (int32_t)m_alarm; } + if (rem != 0) { return false; } m_startTime = now; return true; } diff --git a/HeliosEmbedded/Makefile b/HeliosEmbedded/Makefile index 02dee584..02e0bd87 100644 --- a/HeliosEmbedded/Makefile +++ b/HeliosEmbedded/Makefile @@ -84,7 +84,9 @@ AVRDUDE_FLAGS = $(AVRDUDE_CONFIG_FLAG) \ -P$(AVRDUDE_PORT) \ -b$(AVRDUDE_BAUDRATE) \ -v \ - -B1 + -B10 # ISP bit-clock period in microseconds. Was -B1 (1µs = 1MHz ISP clock). + # At 1MHz CPU the target can only accept an ISP clock up to F_CPU/4 = 250kHz, + # so -B1 overclocked the ISP bus and caused upload failures. -B10 = 100kHz ISP. # -v -- Verbose output - display detailed progress # -B1 -- Bit clock period (in microseconds) - sets programming speed @@ -96,7 +98,10 @@ AVRDUDE_FLAGS = $(AVRDUDE_CONFIG_FLAG) \ ### COMPILER FLAGS #### ####################### -CPU_SPEED = 8000000L +# 1MHz internal oscillator. Drops power draw significantly vs 8MHz. +# Requires fuse H:0xDF L:0x62 (CKDIV8 enabled, SUT_CKSEL=internal 8MHz / 8). +# Run `make set_fuses` after changing this value. +CPU_SPEED = 1000000L # the port for serial upload SERIAL_PORT = COM11 diff --git a/tests/tests/0125_From_First_Mode_Enter_Conjure_Mode.test b/tests/tests/0125_From_First_Mode_Enter_Conjure_Mode.test index 26e28281..bf71aff5 100644 --- a/tests/tests/0125_From_First_Mode_Enter_Conjure_Mode.test +++ b/tests/tests/0125_From_First_Mode_Enter_Conjure_Mode.test @@ -3804,8 +3804,6 @@ D200FF 3C1C00 3C1C00 000000 -000000 -000000 00FFD1 00FFD1 0000FF @@ -4105,3 +4103,5 @@ D200FF 000000 000000 000000 +000000 +000000 diff --git a/tests/tests/0152_Enter_Glow_Lock.test b/tests/tests/0152_Enter_Glow_Lock.test index fc3a54b0..a0c8bead 100644 --- a/tests/tests/0152_Enter_Glow_Lock.test +++ b/tests/tests/0152_Enter_Glow_Lock.test @@ -2605,4 +2605,4 @@ D200FF 3C0000 3C0000 000000 -000000 +D200FF diff --git a/tests/tests/0160_Exit_Glow_Lock.test b/tests/tests/0160_Exit_Glow_Lock.test index 74a0a69c..fe764c7b 100644 --- a/tests/tests/0160_Exit_Glow_Lock.test +++ b/tests/tests/0160_Exit_Glow_Lock.test @@ -2605,8 +2605,8 @@ D200FF 3C0000 3C0000 000000 -000000 -000000 +D200FF +D200FF 3C0000 3C0000 3C0000 diff --git a/tests/tests/0376_From_First_Mode_Enter_Conjure_Mode_Force_Enter_Sleep.test b/tests/tests/0376_From_First_Mode_Enter_Conjure_Mode_Force_Enter_Sleep.test index 64a376aa..9a924b9b 100644 --- a/tests/tests/0376_From_First_Mode_Enter_Conjure_Mode_Force_Enter_Sleep.test +++ b/tests/tests/0376_From_First_Mode_Enter_Conjure_Mode_Force_Enter_Sleep.test @@ -3804,8 +3804,6 @@ D200FF 3C1C00 3C1C00 000000 -000000 -000000 00FFD1 00FFD1 0000FF @@ -5104,6 +5102,8 @@ D200FF 000000 000000 000000 +000000 +000000 003C31 003C31 003C31 diff --git a/tests/tests/0403_Enter_Glow_Lock_Force_Enter_Sleep.test b/tests/tests/0403_Enter_Glow_Lock_Force_Enter_Sleep.test index b5b9d8f7..41a5c029 100644 --- a/tests/tests/0403_Enter_Glow_Lock_Force_Enter_Sleep.test +++ b/tests/tests/0403_Enter_Glow_Lock_Force_Enter_Sleep.test @@ -2605,8 +2605,8 @@ D200FF 3C0000 3C0000 000000 -000000 -000000 +D200FF +D200FF 3C0000 3C0000 3C0000 diff --git a/tests/tests/0411_Exit_Glow_Lock_Force_Enter_Sleep.test b/tests/tests/0411_Exit_Glow_Lock_Force_Enter_Sleep.test index 71430ebb..3d67ac9a 100644 --- a/tests/tests/0411_Exit_Glow_Lock_Force_Enter_Sleep.test +++ b/tests/tests/0411_Exit_Glow_Lock_Force_Enter_Sleep.test @@ -2605,8 +2605,8 @@ D200FF 3C0000 3C0000 000000 -000000 -000000 +D200FF +D200FF 3C0000 3C0000 3C0000