Does the space need a clock?


(ian) #41

ok, thanks,


(Steve Kennedy) #42

@ian Is there plans to put an led panel in the center of the clock, if there is i have either a dmd red led panel (32x64) or a 32x32 rgb panel that would run on a pi zero.
I can donate one to the cause.


(ian) #43

Hi @crashman39,

All contributions welcome,
If you would like to add a date or something in the middle feel free to do so.

@eyal, I have updated the program, but note I haven’t tested it yet. Code below.

#define FastLED

#if NeoPixel
# include <Adafruit_NeoPixel.h>
#elif FastLED
# include <FastLED.h>
# include "RTClib.h"
#endif

const int LedDataPin = 5;
const int LedCount = 180;

#if NeoPixel
Adafruit_NeoPixel leds = Adafruit_NeoPixel(180, LedDataPin, NEO_GRB + NEO_KHZ800);
#elif FastLED
static CRGB[] leds = new CRGB[LedCount];
#endif

static RTC_DS3231 rtc;

const float pulsePeriod = 6.0f;

const float MilliSecondDisplayWidth = 7.5f / LedCount;
const int MilliSecondRed = 16;
const int MilliSecondGreen = 16;
const int MilliSecondBlue = 16;

const float SecondDisplayWidth = 15.0f / LedCount;
const int SecondRed = 131;
const int SecondGreen = 215;
const int SecondBlue = 32;
const float SecondPulseOffset = pulsePeriod / 4.0f;

const float MinuteDisplayWidth = 30.0f / LedCount;
const int MinuteRed = 222;
const int MinuteGreen = 24;
const int MinuteBlue = 121;
const float MinutePulseOffset = pulsePeriod / 2.0f;

const float HourDisplayWidth = 45.0f / LedCount;
const int HourRed = 12;
const int HourGreen = 68;
const int HourBlue = 245;
const float HourPulseOffset = 3.0f * pulsePeriod / 4.0f;

int hour;
int minute;
int second;
int milliSecond;
long lastTime;

int red;
int green;
int blue;
int ledIndex;

void setup()
{
#if NeoPixel
	leds.begin();
#elif FastLED
	FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, LedCount).setCorrection(TypicalLEDStrip);
	FastLED.setBrightness(255);
#endif

	minute = 0;
	second = 0;
}

void loop()
{
	if (minute == 0 && second == 0)
	{
		ReadExternalTime();
	}

	ProgressInternalTime();
	TrailingFadeHandDisplay();

#if NeoPixel
	leds.show();
#elif FastLED
	FastLED.show();
#endif
}

void ReadExternalTime()
{
	DateTime previousTime = rtc.now();
	uint8_t previousSecond = previousTime.second();

	DateTime nextTime;
	while (true)
	{
		nextTime = rtc.now();

		uint8_t nextSecond = nextTime.second();

		if (nextSecond != previousSecond)
		{
			break;
		}
	}

	hour = nextTime.hour() % 12;
	minute = nextTime.minute();
	second = nextTime.second();
	milliSecond = 0;
	lastTime = millis();
}

void ProgressInternalTime()
{
	long currentTime = millis();
	int timeDifference = (int)(currentTime - lastTime);
	lastTime = currentTime;

	if (timeDifference > 100)
	{
		return;
	}

	milliSecond += timeDifference;
	while (milliSecond >= 1000)
	{
		milliSecond -= 1000;
		second++;
		if (second >= 60)
		{
			second -= 60;
			minute++;
			if (minute >= 60)
			{
				minute -= 60;
				hour++;
				if (hour >= 12)
				{
					hour -= 12;
				}
			}
		}
	}
}

void TrailingFadeHandDisplay()
{
	float milliSecondEnd = milliSecond / 1000.0f;
	float secondEnd = (second + milliSecond / 1000.0f) / 60.0f;
	float minuteEnd = (minute + secondEnd) / 60.0f;
	float hourEnd = (hour + minuteEnd) / 12.0f;

	for (ledIndex = 0; ledIndex < LedCount; ledIndex++)
	{
		ClearColour();

		//TrailingFadeHandAddColour(milliSecondEnd, MilliSecondDisplayWidth, MilliSecondRed, MilliSecondGreen, MilliSecondBlue, 0);
		TrailingFadeHandAddColour(secondEnd, SecondDisplayWidth, SecondRed, SecondGreen, SecondBlue, SecondPulseOffset);
		TrailingFadeHandAddColour(minuteEnd, MinuteDisplayWidth, MinuteRed, MinuteGreen, MinuteBlue, MinutePulseOffset);
		TrailingFadeHandAddColour(hourEnd, HourDisplayWidth, HourRed, HourGreen, HourBlue, HourPulseOffset);

		PulsingFiveMinuteMarker();

		ApplyColourToLed();
	}
}

void ClearColour()
{
	red = 0;
	green = 0;
	blue = 0;
}

void AddColour(int sectionRed, int sectionGreen, int sectionBlue, float percent)
{
	percent = percent * percent;
	red = min((int)255, red + (int)(percent * sectionRed));
	green = min((int)255, green + (int)(percent * sectionGreen));
	blue = min((int)255, blue + (int)(percent * sectionBlue));
}

void ApplyColourToLed()
{
	int ledPosition = IndexToLed(ledIndex);
#if NeoPixel
	leds.setPixelColor(ledPosition, 0
				| (long)red << 16
				| (long)green << 8
				| (long)blue
				);
#elif FastLED

	leds[ledPosition].red = red;
	leds[ledPosition].green = green;
	leds[ledPosition].blue = blue;
#endif
}

int IndexToLed(int ledPosition)
{
	return (2 * LedCount - 1 - ledPosition) % LedCount;
}

void TrailingFadeHandAddColour(float sectionEnd, float sectionWidth, int sectionRed, int sectionGreen, int sectionBlue, float pulseOffset)
{
	const float LedWidth = 1.0f / LedCount;
	float sectionStart = sectionEnd - sectionWidth;

	float ledMin = (float)ledIndex / LedCount;
	ledMin = RoundToComparable(sectionStart, ledMin);
	float ledMax = ledMin + LedWidth;

	if (ledMin <= sectionEnd && sectionEnd < ledMax)
	{
		float percent = (sectionEnd - ledMin) / LedWidth + 0.25f * PulsePercent(sectionEnd, ledMin, ledMax, pulseOffset);
		AddColour(sectionRed, sectionGreen, sectionBlue, percent);
	} else if (sectionStart <= ledMin && ledMax < sectionEnd)
	{
		float percent = 1.0f - (sectionEnd - ledMax) / sectionWidth + 0.25f * PulsePercent(sectionEnd, ledMin, ledMax, pulseOffset);
		AddColour(sectionRed, sectionGreen, sectionBlue, percent);
	} else if (ledMin < sectionStart && sectionStart < ledMax)
	{
		float percent = 1.0f - (sectionEnd - ledMax) / sectionWidth + 0.25f * PulsePercent(sectionEnd, ledMin, ledMax, pulseOffset);
		AddColour(sectionRed, sectionGreen, sectionBlue, percent);
	}
}

float PulsePercent(float sectionEnd, float ledMin, float ledMax, float pulseOffset)
{
	float pulseSeconds = second + milliSecond / 1000.0f + pulseOffset;
	float pulsePosition = sectionEnd + SquareWave(pulseSeconds, pulsePeriod);
	pulsePosition = RoundToComparable(ledMax, pulsePosition);

	float pulsePercent;
	if (pulsePosition > ledMax)
	{
		pulsePercent = 1.0f - 2.0f * (pulsePosition - ledMax);
	} else if (pulsePosition < ledMin)
	{
		pulsePercent = 1.0f - 2.0f * (ledMin - pulsePosition);
	} else
	{
		pulsePercent = 1.0f;
	}

	pulsePercent = pow(pulsePercent, 16.0f);
	return pulsePercent;
}

float SquareWave(float seconds, float period)
{
	return (seconds - period * (int)(seconds / period)) / period;
}

float RoundToComparable(float baseValue, float value)
{
	while (value - baseValue >= 0.5f)
	{
		value -= 1;
	}
	while (baseValue - value >= 0.5f)
	{
		value += 1;
	}

	return value;
}

void PulsingFiveMinuteMarker()
{
	const float pulseMin = 0.5f;
	const float pulseMax = 1.0f;
	const float pulseRange = pulseMax - pulseMin;

	if ((ledIndex % (LedCount / 12)) == 0)
	{
		float pulseSeconds = second + milliSecond / 1000.0f;
		float percent = 2 * SquareWave(pulseSeconds, pulsePeriod) - 1;
		if (percent < 0)
		{
			percent = -percent;
		}
		percent = pulseMin + percent * pulseRange;
		percent = pow(percent, 7);
		AddColour(0xff, 0xff, 0xff, percent);
	}
}

(Eyal Lebedinsky) #44

@ian, Looks like a major surgery to me. Would it be better to just have two programs, one for each library? Should be much easier to read/maintain.

I only read the first few lines. Two comments:

1) Use #if defined(NeoPixel) etc.

2) Replace the #endif with

#else
#error No library selected
#endif

Maybe check that only one library is selected?

You can see why mixing the two is difficult and error prone.

See you next Wed?


(ian) #45

I think I should be able to make next Wednesday,


(ian) #46

Thanks @eyal for fixing the code with me last night.
I’m happy it eventually worked.

I know the code can be optimised, so I will have a go at some stage.
but I also found this which may be worth trying


(Eyal Lebedinsky) #47

I now checked in the version from last night.

As for the speedup: the improvement depends very much of what the code does, and may not be enough to remove the slight flicker.

Since the LED configuration changes only once every 1/3 second, I wonder if reducing the update rate to only repaint when the LED position changes will hide the flicker.


(ian) #48

Before I forget, latest code.

Changes around optimising performance

  • fixed point math

  • equation approximations,

  • use the existing millis() time rather than managing it our self

  • reduced number of leds used and processed

    #define fastLED 1
    //#define NeoPixel 0

    #if NeoPixel
    #include <Adafruit_NeoPixel.h>
    #elif fastLED
    #include <FastLED.h>
    #else
    #error Must select a LED library
    #endif

    #include “RTClib.h”

    #define LedDataPin 5
    #define LedCount 180

    const int SecondRed = 131;
    const int SecondGreen = 215;
    const int SecondBlue = 32;

    const int MinuteRed = 222;
    const int MinuteGreen = 24;
    const int MinuteBlue = 121;

    const int HourRed = 12;
    const int HourGreen = 68;
    const int HourBlue = 245;

    const unsigned long MinutesPerHour = 60;
    const unsigned long SecondsPerMinute = 60;
    const unsigned long MilliSecondsPerSecond = 1000;

    const unsigned long SyncTimeStart = (((1 * MinutesPerHour) + 23 * SecondsPerMinute + 45) * MilliSecondsPerSecond + 678);
    const unsigned long SyncTimeEnd = (SyncTimeStart + 321);
    const unsigned long MaxTime = (((12 * MinutesPerHour + 0) * SecondsPerMinute + 0) * MilliSecondsPerSecond + 0);

    #define IntScale 1000

    #if NeoPixel
    Adafruit_NeoPixel leds = Adafruit_NeoPixel(LedCount, LedDataPin, NEO_GRB + NEO_KHZ800);
    #elif fastLED

    static CRGB leds[LedCount];
    //static CRGB[] leds = new CRGB[LedCount];
    #endif

    static RTC_DS3231 rtc;

    void setup()
    {
    Serial.begin(115200);
    Serial.println(“setup”);

    if (!rtc.begin())
    {
    Serial.println(“Couldn’t find RTC”);
    while (true)
    ;
    }

    #if NeoPixel
    leds.begin();
    #elif fastLED
    FastLED.addLeds<WS2812B, LedDataPin, GRB>(leds, LedCount).setCorrection(TypicalLEDStrip);
    FastLED.setBrightness(255);
    #endif

    SetMillis(SyncTimeStart);
    }

    void loop()
    {
    long milliSeconds = millis();

    if (SyncTimeStart <= milliSeconds && milliSeconds <= SyncTimeEnd)
    {
    ReadExternalTime();
    }

    if (milliSeconds > MaxTime)
    {
    SetMillis(0);
    milliSeconds = 0;
    }

    ClearLeds();

    long highlightIndex = LedCount - 1 - milliSeconds % (4000) * LedCount / 4000;

    long secondPosition = 100 * (milliSeconds / 1000 % 60 * IntScale + (milliSeconds % 1000)) / 60;
    WriteHand(secondPosition, 5, SecondRed, SecondGreen, SecondBlue, highlightIndex);

    long minutePosition = 100 * (milliSeconds / 60000 % 60 * IntScale + (milliSeconds / 1000 % 60 * IntScale / 60)) / 60;
    WriteHand(minutePosition, 10, MinuteRed, MinuteGreen, MinuteBlue, (highlightIndex + 45) % LedCount);

    long hourPosition = 100 * (milliSeconds / 3600000 * IntScale + (milliSeconds / 60000 % 60 * IntScale / 60)) / 12;
    WriteHand(hourPosition, 15, HourRed, HourGreen, HourBlue, (highlightIndex + 90) % LedCount);

    WriteHourMarkers((highlightIndex + 135) % LedCount);

    #if NeoPixel
    leds.show();
    #elif fastLED
    FastLED.show();
    #endif
    }

    void ReadExternalTime()
    {
    Serial.println(“ReadExternalTime”);

    DateTime previousTime = rtc.now();
    uint8_t previousSecond = previousTime.second();

    DateTime nextTime;
    while (true)
    {
    nextTime = rtc.now();

    uint8_t nextSecond = nextTime.second();

    if (nextSecond != previousSecond)
    {
    break;
    }
    }

    long hour = nextTime.hour() % 12;
    long minute = nextTime.minute();
    long second = nextTime.second();
    long milliSecond = 0;

    long time = ((hour * MinutesPerHour + minute) * SecondsPerMinute + second) * MilliSecondsPerSecond + milliSecond;
    SetMillis(time);

    Serial.print(“ReadExternalTime finished”);
    }

    void ClearLeds()
    {
    for (long ledIndex = 0; ledIndex < LedCount; ledIndex++)
    {
    #if NeoPixel
    leds.setPixelColor(ledIndex, 0 );
    #elif fastLED
    leds[ledIndex].red = 0;
    leds[ledIndex].green = 0;
    leds[ledIndex].blue = 0;
    #endif
    }
    }

    void WriteHand(long handPosition, long width, long red, long green, long blue, long highlightIndex)
    {
    long ledEndIndex = LedCount * handPosition / 100;
    long ledIndexDecimals = ledEndIndex % IntScale;
    ledEndIndex /= IntScale;

    long ledIndex = (ledEndIndex + LedCount - width + 1) % LedCount;
    for (long index = 0; index < width; index++)
    {
    long ledValue = ((index + 1) * IntScale - ledIndexDecimals) / width;
    ledValue = IntCurve20(ledValue);

    AddColour(ledIndex, red * ledValue / IntScale, green * ledValue / IntScale, blue * ledValue / IntScale);

    ledIndex = (ledIndex + 1) % LedCount;
    }

    AddColour(ledIndex, red * ledIndexDecimals / IntScale, green * ledIndexDecimals / IntScale, blue * ledIndexDecimals / IntScale);

    if (highlightIndex < width)
    {
    ledIndex = (ledEndIndex + LedCount + LedCount - highlightIndex - 1) % LedCount;
    AddColour(ledIndex, red / 8, green / 8, blue / 4);
    ledIndex = (ledEndIndex + LedCount - highlightIndex) % LedCount;
    AddColour(ledIndex, red / 4, green / 4, blue / 4);
    ledIndex = (ledEndIndex + LedCount - highlightIndex + 1) % LedCount;
    AddColour(ledIndex, red / 8, green / 8, blue / 4);
    }
    }

    void WriteHourMarkers(long highlightIndex)
    {
    if (highlightIndex > 60)
    {
    return;
    }

    uint8_t value = 255 * (IntScale * (30 - abs(highlightIndex - 30)) / 30) / IntScale;

    for (long ledIndex = 0; ledIndex < LedCount; ledIndex += 15)
    {
    AddColour(ledIndex, value, value, value);
    }
    }

    long IndexToLed(long ledIndex)
    {
    return (2 * LedCount - 1 - ledIndex) % LedCount;
    //return ledIndex;
    }

    void AddColour(long ledIndex, long red, long green, long blue)
    {
    long ledPosition = IndexToLed(ledIndex);
    #if NeoPixel
    #error not implemented

    leds.setPixelColor(ledIndex, 0
    | (long)red << 16
    | (long)green << 8
    | (long)blue
    );

    #elif fastLED
    leds[ledPosition].red = min(max(0, leds[ledPosition].red + red), 255);
    leds[ledPosition].green = min(max(0, leds[ledPosition].green + green), 255);
    leds[ledPosition].blue = min(max(0, leds[ledPosition].blue + blue), 255);
    #endif
    }

    long IntCurve20(long x)
    {
    if (x <= 0)
    {
    return 0;
    }

    if (x >= IntScale)
    {
    return IntScale;
    }

    if (x < 740)
    {
    if (x < 0466)
    {
    return x * 206 / IntScale;
    } else
    {
    return (x - 466) * 719 / IntScale + 96;
    }
    } else
    {
    if (x < 939)
    {
    return (x - 740) * 1747 / IntScale + 293;
    } else
    {
    return (x - 939) * 5902 / IntScale + 640;
    }
    }
    }

    long IntCurve05(long x)
    {
    if (x <= 0)
    {
    return 0;
    }

    if (x >= 1000)
    {
    return 1000;
    }

    if (x < 894)
    {
    if (x < 721)
    {
    if (x < 451)
    {
    return x * 34 / IntScale;
    } else
    {
    return (x - 451) * 66 / IntScale + 16;
    }
    } else
    {
    if (x < 831)
    {
    return (x - 721) * 236 / IntScale + 33;
    } else
    {
    return (x - 831) * 887 / IntScale + 59;
    }
    }
    } else
    {
    if (x < 975)
    {
    if (x < 940)
    {
    return (x - 894) * 2344 / IntScale + 116;
    } else
    {
    return (x - 940) * 4936 / IntScale + 222;
    }
    } else
    {
    if (x < 998)
    {
    return (x - 975) * 9602 / IntScale + 395;
    } else
    {
    return (x - 998) * 175249 / IntScale + 617;
    }
    }
    }
    }

    extern volatile unsigned long timer0_millis;

    void SetMillis(unsigned long time)
    {
    Serial.println(“SetMillis”);
    uint8_t oldSREG = SREG;

    // disable longerrupts while we read timer0_millis or we might get an
    // inconsistent value (e.g. in the middle of a write to timer0_millis)
    cli();
    timer0_millis = time;
    SREG = oldSREG;
    }


(Eyal Lebedinsky) #49

Thanks @ian.

Enclosing the code inside a code block (delimited by three back-quotes) will keep the formatting and allow people to copy it properly.

Even better if you can place it in a github repository. Do you have permission for MHV github? Ask the committee if not.