diff --git a/LICENCE_ISC.md b/LICENCE_ISC.md new file mode 100644 index 0000000..b785b6e --- /dev/null +++ b/LICENCE_ISC.md @@ -0,0 +1,16 @@ +# ISC licence + +Copyright (c) 2020, fruchti + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..428acea --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# STM32F030F4P6 LED Tree + +![Photo](laurelin.jpg) + +This repository contains the code for a 8-by-6 RGB LED matrix driver with software 12-bit BCM output based on a STM32F030F4P6 and some additional code for brightness control and a pretty animation. More information about the project can be found [here](https://25120.org/post/laurelin/). + +## Licence + +The header files in the third_party directory are provided by ARM Limited and ST Microelectronics and contain their own licence information. Everything else is ISC licenced. diff --git a/animation_lut.py b/animation_lut.py new file mode 100755 index 0000000..0ad7c04 --- /dev/null +++ b/animation_lut.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +from colorsys import hsv_to_rgb + +LUT_SIZE = 256 +SATURATION = 0.8 +VALUE = 1 +RESOLUTION = 12 # in bits + +print('const LED_Colour_t Animation_ColourLUT[] =') +print('{') +for i in range(LUT_SIZE): + h = i / LUT_SIZE + rgb = hsv_to_rgb(h, SATURATION, VALUE) + # Gamma-correct + rgb = tuple(pow(x, 2.2) for x in rgb) + # Scale to BCM resolution + (r, g, b) = tuple(round(2 ** RESOLUTION * x) for x in rgb) + print(' {{ .r = {}, .g = {}, .b = {} }},'.format(r, g, b)) +print('};') diff --git a/build-number.txt b/build-number.txt index a1f7f63..3cda32f 100644 --- a/build-number.txt +++ b/build-number.txt @@ -1 +1 @@ -298 +515 diff --git a/laurelin.jpg b/laurelin.jpg new file mode 100644 index 0000000..a295deb Binary files /dev/null and b/laurelin.jpg differ diff --git a/ld/common.ld b/ld/common_nvs.ld similarity index 87% rename from ld/common.ld rename to ld/common_nvs.ld index 9d9835a..91ea21c 100644 --- a/ld/common.ld +++ b/ld/common_nvs.ld @@ -12,6 +12,15 @@ SECTIONS _end_text = .; } >flash + /* Non-volatile storage area in flash */ + .nvstore (NOLOAD) : + { + . = ALIGN(1K); + *(.nvstore*) + . = ALIGN(1K); + _end_nvstore = .; + } > flash_nvstore + /* C++ initialiser code segments */ .preinit_array : { diff --git a/ld/stm32f030f4_flash.ld b/ld/stm32f030f4_15k_flash_1k_nvs.ld similarity index 59% rename from ld/stm32f030f4_flash.ld rename to ld/stm32f030f4_15k_flash_1k_nvs.ld index 9fecb90..93724af 100644 --- a/ld/stm32f030f4_flash.ld +++ b/ld/stm32f030f4_15k_flash_1k_nvs.ld @@ -1,7 +1,8 @@ MEMORY { - flash (rx) : ORIGIN = 0x08000000, LENGTH = 16K + flash (rx) : ORIGIN = 0x08000000, LENGTH = 15K + flash_nvstore (rw) : ORIGIN = 0x08003c00, LENGTH = 1K sram (xrw) : ORIGIN = 0x20000000, LENGTH = 4K } -INCLUDE ld/common.ld +INCLUDE ld/common_nvs.ld diff --git a/makefile b/makefile index f0849c5..6583cf1 100644 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ DEBUG := yes H_DEVICE = STM32F030x6 STARTUP_SOURCE_DIR = src STARTUP_SOURCES = $(STARTUP_SOURCE_DIR)/startup.S -LD_SCRIPT = ld/stm32f030f4_flash.ld +LD_SCRIPT = ld/stm32f030f4_15k_flash_1k_nvs.ld ifeq ($(DEBUG),yes) DEBUG_FLAGS = -DDEBUG -g3 diff --git a/src/animation.c b/src/animation.c index fc3657f..72dbb8b 100644 --- a/src/animation.c +++ b/src/animation.c @@ -1,18 +1,107 @@ #include "animation.h" -#include "led.h" +#include "animation_lut.h" +#include "light_sensor.h" +#include "nvs.h" +volatile bool Animation_FrameFlag = false; + +#if LED_COLUMNS == 12 const unsigned int Animation_LEDOrder[LED_COUNT] = { - // Red - 0, 1, 2, 3, 4, 5, - // Green - 6, 7, 8, 9, 10, 11, - // Blue - 12, 13, 14, 15, 16, 17, - // Yellow - 18, 19, 20, 21, 22, 23, - // Cyan - 24, 25, 26, 27, 28, 29, - // Fuchsia - 30, 31 -}; \ No newline at end of file + 19, 27, 21, 13, 0, 4, 24, 8, 12, 15, 6, 5, 28, + 29, 17, 3, 18, 26, 22, 10, 16, 20, 30, 1, + 25, 2, 14, 31, 7, 11, 9, 23 +}; +#elif LED_COLUMNS == 9 +const unsigned int Animation_LEDOrder[LED_COUNT] = +{ + 19, 21, 13, 0, 4, 8, 12, 15, 6, 5, + 17, 3, 18, 22, 10, 16, 20, 1, + 2, 14, 7, 11, 9, 23 +}; +#endif + +LED_Colour_t Animation_GetColour(unsigned int step, unsigned int brightness) +{ + const unsigned int lut_size = sizeof(Animation_ColourLUT) + / sizeof(LED_Colour_t); + + unsigned int index = step * lut_size / ANIMATION_STEPS; + + LED_Colour_t colour = Animation_ColourLUT[index]; + + colour.r = ((unsigned int)colour.r * brightness) >> LIGHTSENSOR_BITS; + colour.g = ((unsigned int)colour.g * brightness) >> LIGHTSENSOR_BITS; + colour.b = ((unsigned int)colour.b * brightness) >> LIGHTSENSOR_BITS; + + return colour; +} + +void Animation_DrawGradient(unsigned int step_bottom, unsigned int step_top, + unsigned int brightness) +{ + if(step_bottom >= ANIMATION_STEPS) + { + step_bottom = 2 * ANIMATION_STEPS - step_bottom; + } + if(step_top >= ANIMATION_STEPS) + { + step_top = 2 * ANIMATION_STEPS - step_top; + } + for(int i = 0; i < LED_COUNT; i++) + { + unsigned int step = ((LED_COUNT - 1 - i) * step_bottom + i * step_top) + / (LED_COUNT - 1); + LED_PixelData[Animation_LEDOrder[i]] = Animation_GetColour(step, + brightness); + } +} + +void Animation_Init(void) +{ + RCC->APB2ENR |= RCC_APB2ENR_TIM17EN; + + TIM17->PSC = 8000 - 1; + TIM17->ARR = 1000 / ANIMATION_REFRESH_RATE; + TIM17->DIER = TIM_DIER_UIE; + TIM17->CR1 = TIM_CR1_CEN; + NVIC_EnableIRQ(TIM17_IRQn); +} + +void Animation_Poll(void) +{ + if(!Animation_FrameFlag) + { + return; + } + Animation_FrameFlag = false; + + NVS_Data->animation_step_bottom += ANIMATION_STEPS + / ANIMATION_CYCLE_TIME_BOTTOM + / ANIMATION_REFRESH_RATE; + NVS_Data->animation_step_top += ANIMATION_STEPS + / ANIMATION_CYCLE_TIME_TOP + / ANIMATION_REFRESH_RATE; + + NVS_Data->animation_step_bottom %= 2 * ANIMATION_STEPS; + NVS_Data->animation_step_top %= 2 * ANIMATION_STEPS; + + Animation_DrawGradient(NVS_Data->animation_step_bottom, + NVS_Data->animation_step_top, + LightSensor_RelativeBrightness); + LED_Commit(); + + static unsigned int store_counter = 0; + store_counter++; + if(store_counter >= ANIMATION_NVS_STORE_INTERVAL * ANIMATION_REFRESH_RATE) + { + store_counter = 0; + NVS_Save(false); + } +} + +void TIM17_IRQHandler(void) +{ + Animation_FrameFlag = true; + TIM17->SR &= ~TIM_SR_UIF; +} diff --git a/src/animation.h b/src/animation.h index 7b9637e..04e2a73 100644 --- a/src/animation.h +++ b/src/animation.h @@ -1 +1,25 @@ -#pragma once \ No newline at end of file +#pragma once + +#include + +#include "led.h" + +#define ANIMATION_STEPS (1 << 24) + +#define ANIMATION_REFRESH_RATE 10 + +// Cycle time of the animation for the bottom end of the gradient (in seconds) +#define ANIMATION_CYCLE_TIME_BOTTOM \ + (6 * 24 * 60 * 60) +// Cycle time of the animation for the top end of the gradient (in seconds) +#define ANIMATION_CYCLE_TIME_TOP \ + (8 * 24 * 60 * 60) + +// Interval for saving the current animation step to NVS (in seconds) +#define ANIMATION_NVS_STORE_INTERVAL \ + (60 * 60) + +extern const unsigned int Animation_LEDOrder[LED_COUNT]; + +void Animation_Init(void); +void Animation_Poll(void); diff --git a/src/animation_lut.c b/src/animation_lut.c new file mode 100644 index 0000000..04132a5 --- /dev/null +++ b/src/animation_lut.c @@ -0,0 +1,261 @@ +#include "animation_lut.h" + +const LED_Colour_t Animation_ColourLUT[] = +{ + { .r = 4096, .g = 119, .b = 119 }, + { .r = 4096, .g = 145, .b = 119 }, + { .r = 4096, .g = 173, .b = 119 }, + { .r = 4096, .g = 205, .b = 119 }, + { .r = 4096, .g = 239, .b = 119 }, + { .r = 4096, .g = 277, .b = 119 }, + { .r = 4096, .g = 317, .b = 119 }, + { .r = 4096, .g = 360, .b = 119 }, + { .r = 4096, .g = 407, .b = 119 }, + { .r = 4096, .g = 456, .b = 119 }, + { .r = 4096, .g = 509, .b = 119 }, + { .r = 4096, .g = 565, .b = 119 }, + { .r = 4096, .g = 623, .b = 119 }, + { .r = 4096, .g = 686, .b = 119 }, + { .r = 4096, .g = 751, .b = 119 }, + { .r = 4096, .g = 820, .b = 119 }, + { .r = 4096, .g = 891, .b = 119 }, + { .r = 4096, .g = 967, .b = 119 }, + { .r = 4096, .g = 1045, .b = 119 }, + { .r = 4096, .g = 1127, .b = 119 }, + { .r = 4096, .g = 1212, .b = 119 }, + { .r = 4096, .g = 1301, .b = 119 }, + { .r = 4096, .g = 1393, .b = 119 }, + { .r = 4096, .g = 1489, .b = 119 }, + { .r = 4096, .g = 1588, .b = 119 }, + { .r = 4096, .g = 1690, .b = 119 }, + { .r = 4096, .g = 1796, .b = 119 }, + { .r = 4096, .g = 1906, .b = 119 }, + { .r = 4096, .g = 2019, .b = 119 }, + { .r = 4096, .g = 2135, .b = 119 }, + { .r = 4096, .g = 2256, .b = 119 }, + { .r = 4096, .g = 2380, .b = 119 }, + { .r = 4096, .g = 2507, .b = 119 }, + { .r = 4096, .g = 2638, .b = 119 }, + { .r = 4096, .g = 2773, .b = 119 }, + { .r = 4096, .g = 2911, .b = 119 }, + { .r = 4096, .g = 3053, .b = 119 }, + { .r = 4096, .g = 3199, .b = 119 }, + { .r = 4096, .g = 3349, .b = 119 }, + { .r = 4096, .g = 3502, .b = 119 }, + { .r = 4096, .g = 3659, .b = 119 }, + { .r = 4096, .g = 3820, .b = 119 }, + { .r = 4096, .g = 3984, .b = 119 }, + { .r = 4040, .g = 4096, .b = 119 }, + { .r = 3874, .g = 4096, .b = 119 }, + { .r = 3712, .g = 4096, .b = 119 }, + { .r = 3554, .g = 4096, .b = 119 }, + { .r = 3399, .g = 4096, .b = 119 }, + { .r = 3249, .g = 4096, .b = 119 }, + { .r = 3102, .g = 4096, .b = 119 }, + { .r = 2958, .g = 4096, .b = 119 }, + { .r = 2819, .g = 4096, .b = 119 }, + { .r = 2683, .g = 4096, .b = 119 }, + { .r = 2550, .g = 4096, .b = 119 }, + { .r = 2422, .g = 4096, .b = 119 }, + { .r = 2297, .g = 4096, .b = 119 }, + { .r = 2175, .g = 4096, .b = 119 }, + { .r = 2057, .g = 4096, .b = 119 }, + { .r = 1943, .g = 4096, .b = 119 }, + { .r = 1832, .g = 4096, .b = 119 }, + { .r = 1725, .g = 4096, .b = 119 }, + { .r = 1621, .g = 4096, .b = 119 }, + { .r = 1521, .g = 4096, .b = 119 }, + { .r = 1425, .g = 4096, .b = 119 }, + { .r = 1331, .g = 4096, .b = 119 }, + { .r = 1242, .g = 4096, .b = 119 }, + { .r = 1155, .g = 4096, .b = 119 }, + { .r = 1072, .g = 4096, .b = 119 }, + { .r = 992, .g = 4096, .b = 119 }, + { .r = 916, .g = 4096, .b = 119 }, + { .r = 843, .g = 4096, .b = 119 }, + { .r = 773, .g = 4096, .b = 119 }, + { .r = 707, .g = 4096, .b = 119 }, + { .r = 644, .g = 4096, .b = 119 }, + { .r = 584, .g = 4096, .b = 119 }, + { .r = 527, .g = 4096, .b = 119 }, + { .r = 473, .g = 4096, .b = 119 }, + { .r = 423, .g = 4096, .b = 119 }, + { .r = 375, .g = 4096, .b = 119 }, + { .r = 331, .g = 4096, .b = 119 }, + { .r = 290, .g = 4096, .b = 119 }, + { .r = 251, .g = 4096, .b = 119 }, + { .r = 216, .g = 4096, .b = 119 }, + { .r = 184, .g = 4096, .b = 119 }, + { .r = 154, .g = 4096, .b = 119 }, + { .r = 127, .g = 4096, .b = 119 }, + { .r = 119, .g = 4096, .b = 136 }, + { .r = 119, .g = 4096, .b = 163 }, + { .r = 119, .g = 4096, .b = 194 }, + { .r = 119, .g = 4096, .b = 227 }, + { .r = 119, .g = 4096, .b = 264 }, + { .r = 119, .g = 4096, .b = 303 }, + { .r = 119, .g = 4096, .b = 346 }, + { .r = 119, .g = 4096, .b = 391 }, + { .r = 119, .g = 4096, .b = 439 }, + { .r = 119, .g = 4096, .b = 491 }, + { .r = 119, .g = 4096, .b = 546 }, + { .r = 119, .g = 4096, .b = 603 }, + { .r = 119, .g = 4096, .b = 665 }, + { .r = 119, .g = 4096, .b = 729 }, + { .r = 119, .g = 4096, .b = 796 }, + { .r = 119, .g = 4096, .b = 867 }, + { .r = 119, .g = 4096, .b = 941 }, + { .r = 119, .g = 4096, .b = 1019 }, + { .r = 119, .g = 4096, .b = 1099 }, + { .r = 119, .g = 4096, .b = 1184 }, + { .r = 119, .g = 4096, .b = 1271 }, + { .r = 119, .g = 4096, .b = 1362 }, + { .r = 119, .g = 4096, .b = 1456 }, + { .r = 119, .g = 4096, .b = 1554 }, + { .r = 119, .g = 4096, .b = 1656 }, + { .r = 119, .g = 4096, .b = 1760 }, + { .r = 119, .g = 4096, .b = 1869 }, + { .r = 119, .g = 4096, .b = 1981 }, + { .r = 119, .g = 4096, .b = 2096 }, + { .r = 119, .g = 4096, .b = 2215 }, + { .r = 119, .g = 4096, .b = 2338 }, + { .r = 119, .g = 4096, .b = 2464 }, + { .r = 119, .g = 4096, .b = 2594 }, + { .r = 119, .g = 4096, .b = 2728 }, + { .r = 119, .g = 4096, .b = 2865 }, + { .r = 119, .g = 4096, .b = 3006 }, + { .r = 119, .g = 4096, .b = 3150 }, + { .r = 119, .g = 4096, .b = 3298 }, + { .r = 119, .g = 4096, .b = 3450 }, + { .r = 119, .g = 4096, .b = 3606 }, + { .r = 119, .g = 4096, .b = 3766 }, + { .r = 119, .g = 4096, .b = 3929 }, + { .r = 119, .g = 4096, .b = 4096 }, + { .r = 119, .g = 3929, .b = 4096 }, + { .r = 119, .g = 3766, .b = 4096 }, + { .r = 119, .g = 3606, .b = 4096 }, + { .r = 119, .g = 3450, .b = 4096 }, + { .r = 119, .g = 3298, .b = 4096 }, + { .r = 119, .g = 3150, .b = 4096 }, + { .r = 119, .g = 3006, .b = 4096 }, + { .r = 119, .g = 2865, .b = 4096 }, + { .r = 119, .g = 2728, .b = 4096 }, + { .r = 119, .g = 2594, .b = 4096 }, + { .r = 119, .g = 2464, .b = 4096 }, + { .r = 119, .g = 2338, .b = 4096 }, + { .r = 119, .g = 2215, .b = 4096 }, + { .r = 119, .g = 2096, .b = 4096 }, + { .r = 119, .g = 1981, .b = 4096 }, + { .r = 119, .g = 1869, .b = 4096 }, + { .r = 119, .g = 1760, .b = 4096 }, + { .r = 119, .g = 1656, .b = 4096 }, + { .r = 119, .g = 1554, .b = 4096 }, + { .r = 119, .g = 1456, .b = 4096 }, + { .r = 119, .g = 1362, .b = 4096 }, + { .r = 119, .g = 1271, .b = 4096 }, + { .r = 119, .g = 1184, .b = 4096 }, + { .r = 119, .g = 1099, .b = 4096 }, + { .r = 119, .g = 1019, .b = 4096 }, + { .r = 119, .g = 941, .b = 4096 }, + { .r = 119, .g = 867, .b = 4096 }, + { .r = 119, .g = 796, .b = 4096 }, + { .r = 119, .g = 729, .b = 4096 }, + { .r = 119, .g = 665, .b = 4096 }, + { .r = 119, .g = 603, .b = 4096 }, + { .r = 119, .g = 546, .b = 4096 }, + { .r = 119, .g = 491, .b = 4096 }, + { .r = 119, .g = 439, .b = 4096 }, + { .r = 119, .g = 391, .b = 4096 }, + { .r = 119, .g = 346, .b = 4096 }, + { .r = 119, .g = 303, .b = 4096 }, + { .r = 119, .g = 264, .b = 4096 }, + { .r = 119, .g = 227, .b = 4096 }, + { .r = 119, .g = 194, .b = 4096 }, + { .r = 119, .g = 163, .b = 4096 }, + { .r = 119, .g = 136, .b = 4096 }, + { .r = 127, .g = 119, .b = 4096 }, + { .r = 154, .g = 119, .b = 4096 }, + { .r = 184, .g = 119, .b = 4096 }, + { .r = 216, .g = 119, .b = 4096 }, + { .r = 251, .g = 119, .b = 4096 }, + { .r = 290, .g = 119, .b = 4096 }, + { .r = 331, .g = 119, .b = 4096 }, + { .r = 375, .g = 119, .b = 4096 }, + { .r = 423, .g = 119, .b = 4096 }, + { .r = 473, .g = 119, .b = 4096 }, + { .r = 527, .g = 119, .b = 4096 }, + { .r = 584, .g = 119, .b = 4096 }, + { .r = 644, .g = 119, .b = 4096 }, + { .r = 707, .g = 119, .b = 4096 }, + { .r = 773, .g = 119, .b = 4096 }, + { .r = 843, .g = 119, .b = 4096 }, + { .r = 916, .g = 119, .b = 4096 }, + { .r = 992, .g = 119, .b = 4096 }, + { .r = 1072, .g = 119, .b = 4096 }, + { .r = 1155, .g = 119, .b = 4096 }, + { .r = 1242, .g = 119, .b = 4096 }, + { .r = 1331, .g = 119, .b = 4096 }, + { .r = 1425, .g = 119, .b = 4096 }, + { .r = 1521, .g = 119, .b = 4096 }, + { .r = 1621, .g = 119, .b = 4096 }, + { .r = 1725, .g = 119, .b = 4096 }, + { .r = 1832, .g = 119, .b = 4096 }, + { .r = 1943, .g = 119, .b = 4096 }, + { .r = 2057, .g = 119, .b = 4096 }, + { .r = 2175, .g = 119, .b = 4096 }, + { .r = 2297, .g = 119, .b = 4096 }, + { .r = 2422, .g = 119, .b = 4096 }, + { .r = 2550, .g = 119, .b = 4096 }, + { .r = 2683, .g = 119, .b = 4096 }, + { .r = 2819, .g = 119, .b = 4096 }, + { .r = 2958, .g = 119, .b = 4096 }, + { .r = 3102, .g = 119, .b = 4096 }, + { .r = 3249, .g = 119, .b = 4096 }, + { .r = 3399, .g = 119, .b = 4096 }, + { .r = 3554, .g = 119, .b = 4096 }, + { .r = 3712, .g = 119, .b = 4096 }, + { .r = 3874, .g = 119, .b = 4096 }, + { .r = 4040, .g = 119, .b = 4096 }, + { .r = 4096, .g = 119, .b = 3984 }, + { .r = 4096, .g = 119, .b = 3820 }, + { .r = 4096, .g = 119, .b = 3659 }, + { .r = 4096, .g = 119, .b = 3502 }, + { .r = 4096, .g = 119, .b = 3349 }, + { .r = 4096, .g = 119, .b = 3199 }, + { .r = 4096, .g = 119, .b = 3053 }, + { .r = 4096, .g = 119, .b = 2911 }, + { .r = 4096, .g = 119, .b = 2773 }, + { .r = 4096, .g = 119, .b = 2638 }, + { .r = 4096, .g = 119, .b = 2507 }, + { .r = 4096, .g = 119, .b = 2380 }, + { .r = 4096, .g = 119, .b = 2256 }, + { .r = 4096, .g = 119, .b = 2135 }, + { .r = 4096, .g = 119, .b = 2019 }, + { .r = 4096, .g = 119, .b = 1906 }, + { .r = 4096, .g = 119, .b = 1796 }, + { .r = 4096, .g = 119, .b = 1690 }, + { .r = 4096, .g = 119, .b = 1588 }, + { .r = 4096, .g = 119, .b = 1489 }, + { .r = 4096, .g = 119, .b = 1393 }, + { .r = 4096, .g = 119, .b = 1301 }, + { .r = 4096, .g = 119, .b = 1212 }, + { .r = 4096, .g = 119, .b = 1127 }, + { .r = 4096, .g = 119, .b = 1045 }, + { .r = 4096, .g = 119, .b = 967 }, + { .r = 4096, .g = 119, .b = 891 }, + { .r = 4096, .g = 119, .b = 820 }, + { .r = 4096, .g = 119, .b = 751 }, + { .r = 4096, .g = 119, .b = 686 }, + { .r = 4096, .g = 119, .b = 623 }, + { .r = 4096, .g = 119, .b = 565 }, + { .r = 4096, .g = 119, .b = 509 }, + { .r = 4096, .g = 119, .b = 456 }, + { .r = 4096, .g = 119, .b = 407 }, + { .r = 4096, .g = 119, .b = 360 }, + { .r = 4096, .g = 119, .b = 317 }, + { .r = 4096, .g = 119, .b = 277 }, + { .r = 4096, .g = 119, .b = 239 }, + { .r = 4096, .g = 119, .b = 205 }, + { .r = 4096, .g = 119, .b = 173 }, + { .r = 4096, .g = 119, .b = 145 }, +}; diff --git a/src/animation_lut.h b/src/animation_lut.h new file mode 100644 index 0000000..79bb8dc --- /dev/null +++ b/src/animation_lut.h @@ -0,0 +1,5 @@ +#pragma once + +#include "led.h" + +extern const LED_Colour_t Animation_ColourLUT[256]; diff --git a/src/led.c b/src/led.c index bad20ff..f644614 100644 --- a/src/led.c +++ b/src/led.c @@ -3,7 +3,10 @@ LED_Colour_t LED_PixelData[LED_COUNT] = {{0}}; volatile bool LED_FrameFlag = false; +volatile bool LED_SuspendFlag = false; +bool LED_Suspended = false; +#if LED_COLUMNS == 12 #define LED_ODR_MASK ((1 << PIN_LED_R_0) | (1 << PIN_LED_G_0) \ | (1 << PIN_LED_B_0) | (1 << PIN_LED_R_1) \ | (1 << PIN_LED_G_1) | (1 << PIN_LED_B_1) \ @@ -24,6 +27,27 @@ volatile bool LED_FrameFlag = false; | (1 << PIN_LED_R_2 * 2) | (1 << PIN_LED_G_2 * 2) \ | (1 << PIN_LED_B_2 * 2) | (1 << PIN_LED_R_3 * 2) \ | (1 << PIN_LED_G_3 * 2) | (1 << PIN_LED_B_3 * 2)) +#elif LED_COLUMNS == 9 +#define LED_ODR_MASK ((1 << PIN_LED_R_0) | (1 << PIN_LED_G_0) \ + | (1 << PIN_LED_B_0) | (1 << PIN_LED_R_1) \ + | (1 << PIN_LED_G_1) | (1 << PIN_LED_B_1) \ + | (1 << PIN_LED_R_2) | (1 << PIN_LED_G_2) \ + | (1 << PIN_LED_B_2)) + +#define LED_MODER_MASK ((3 << PIN_LED_R_0 * 2) | (3 << PIN_LED_G_0 * 2) \ + | (3 << PIN_LED_B_0 * 2) | (3 << PIN_LED_R_1 * 2) \ + | (3 << PIN_LED_G_1 * 2) | (3 << PIN_LED_B_1 * 2) \ + | (3 << PIN_LED_R_2 * 2) | (3 << PIN_LED_G_2 * 2) \ + | (3 << PIN_LED_B_2 * 2)) + +#define LED_MODER ((1 << PIN_LED_R_0 * 2) | (1 << PIN_LED_G_0 * 2) \ + | (1 << PIN_LED_B_0 * 2) | (1 << PIN_LED_R_1 * 2) \ + | (1 << PIN_LED_G_1 * 2) | (1 << PIN_LED_B_1 * 2) \ + | (1 << PIN_LED_R_2 * 2) | (1 << PIN_LED_G_2 * 2) \ + | (1 << PIN_LED_B_2 * 2)) +#else +#error Unsupported LED column count +#endif // TIM3 is clocked by APB1 and thus receives only half the system clock. The 4 // LSBs have bit lengths 2, 4, 8, and 16 cycles and are generated blocking from @@ -33,6 +57,7 @@ static const uint16_t LED_BitLengths[LED_BITS - 4] = 16, 32, 64, 128, 256, 512, 1024, 2048 }; +#if LED_COLUMNS == 12 static const int LED_Pins[LED_COLUMNS] = { PIN_LED_R_0, PIN_LED_G_0, PIN_LED_B_0, @@ -40,6 +65,14 @@ static const int LED_Pins[LED_COLUMNS] = PIN_LED_R_2, PIN_LED_G_2, PIN_LED_B_2, PIN_LED_R_3, PIN_LED_G_3, PIN_LED_B_3 }; +#elif LED_COLUMNS == 9 +static const int LED_Pins[LED_COLUMNS] = +{ + PIN_LED_R_0, PIN_LED_G_0, PIN_LED_B_0, + PIN_LED_R_1, PIN_LED_G_1, PIN_LED_B_1, + PIN_LED_R_2, PIN_LED_G_2, PIN_LED_B_2 +}; +#endif // Number of 16-bit values to be transferred to the GPIO's output register via // DMA. Each value contains output values for all columns. For each row, a @@ -58,6 +91,11 @@ static volatile bool LED_QueuePageFlip = false; void LED_Commit(void) { + if(LED_Suspended) + { + return; + } + // Wait for the current data to be displayed in case LED_Commit was called // more than one time during a single frame. Otherwise, a race condition // might occur. @@ -67,15 +105,13 @@ void LED_Commit(void) { for(int i = 0; i < LED_COLUMNS; i++) { - // Use pixel data as a raw byte buffer to get R, G, B in order - uint8_t colour_value = ((uint8_t*)LED_PixelData)[r * LED_COLUMNS + i]; - uint16_t gamma_corrected = (uint16_t)colour_value; - gamma_corrected *= gamma_corrected; - gamma_corrected >>= 16 - LED_BITS; + // Use pixel data as a raw word buffer to get R, G, B in order + uint16_t colour_value = + ((uint16_t*)LED_PixelData)[r * LED_COLUMNS + i]; for(int j = 0; j < LED_BITS; j++) { - if(gamma_corrected & (1 << j)) + if(colour_value & (1 << j)) { LED_BackBuffer[r * (LED_BITS + 1) + j] &= ~(1 << LED_Pins[i]); @@ -108,7 +144,7 @@ static void LED_StartBCM(int row) TIM3->ARR = LED_BitLengths[0]; TIM3->DIER = TIM_DIER_UDE | TIM_DIER_CC1DE; - // DMA channel 3: Output data to port a on TIM3 update + // DMA channel 3: output data to port a on TIM3 update event DMA1_Channel3->CMAR = (uint32_t)&(LED_FrontBuffer[row * (LED_BITS + 1) + 4]); // One transfer for each bit plus one to set the outputs to zero again. // The first 4 are sent out with assembly before the first DMA transfer. @@ -117,7 +153,7 @@ static void LED_StartBCM(int row) DMA1_Channel3->CCR = DMA_CCR_PL | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_EN | DMA_CCR_TCIE; - // DMA channel 3: Output data to port a on TIM3 update + // DMA channel 4: update TIM3 ARR on TIM3 compare 1 match // The bit lengths table is offset because the first value is already in // the timer's ARR shadow register. DMA1_Channel4->CMAR = (uint32_t)&(LED_BitLengths[1]); @@ -215,6 +251,7 @@ void LED_Init(void) RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // Fill both DMA buffers + LED_QueuePageFlip = false; LED_Commit(); LED_PageFlip(); LED_Commit(); @@ -244,6 +281,53 @@ void LED_Init(void) LED_StartBCM(0); } +void LED_Suspend(void) +{ + if(LED_Suspended) + { + return; + } + + LED_SuspendFlag = true; + while(LED_SuspendFlag); + + // Disable timer and DMA channels + TIM3->CR1 = 0x0000; + TIM3->DIER = 0x0000; + TIM3->CNT = 0; + DMA1->IFCR = DMA_IFCR_CTCIF3; + DMA1_Channel3->CCR = 0x0000; + DMA1_Channel4->CCR = 0x0000; + NVIC_DisableIRQ(DMA1_Channel2_3_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel2_3_IRQn); + + // Deactivate all rows and columns + GPIOA->BSRR = LED_ODR_MASK; + GPIOF->BSRR = (1 << PIN_ROW_DATA); + for(int i = 0; i < LED_ROWS + 1; i++) + { + LED_PulseRowClock(); + } + + // Disable timer and DMA clocks + RCC->AHBENR &= ~RCC_AHBENR_DMA1EN; + RCC->APB1ENR &= ~RCC_APB1ENR_TIM3EN; + + LED_Suspended = true; +} + +void LED_WakeUp(void) +{ + if(!LED_Suspended) + { + return; + } + + LED_Init(); + + LED_Suspended = false; +} + void DMA1_Channel2_3_IRQHandler(void) { // Interrupt when all bits have been sent @@ -268,6 +352,12 @@ void DMA1_Channel2_3_IRQHandler(void) LED_PulseRowClock(); } + if(LED_SuspendFlag) + { + LED_SuspendFlag = false; + return; + } + // Start sending bits out again. The row offset caused by the shift // register "lagging" one clock cycle behind because RCK and SCK are // connected to the same signal doesn't matter here: we're not paying much diff --git a/src/led.h b/src/led.h index 83833c2..083bc1d 100644 --- a/src/led.h +++ b/src/led.h @@ -5,17 +5,19 @@ #include "stm32f030x6.h" #include "pinning.h" -#define LED_BITS 12 +#define LED_BITS 12 // BCM resolution in bits #define LED_ROWS 8 // Rows are driven by a shift register -#define LED_COLUMNS 12 // Columns are driven by the MCU directly +#define LED_COLUMNS 12 // Columns are driven by the MCU directly. + // Set to 9 to free the SWD pins and enable + // debugging. #define LED_COUNT (LED_ROWS * LED_COLUMNS / 3) typedef struct { - uint8_t r; - uint8_t g; - uint8_t b; -} __attribute__((packed)) LED_Colour_t; + uint16_t r; + uint16_t g; + uint16_t b; +} __attribute__((packed, aligned(2))) LED_Colour_t; // Pixel data, not displayed until LED_Commit() is called extern LED_Colour_t LED_PixelData[LED_COUNT]; @@ -27,6 +29,11 @@ extern volatile bool LED_FrameFlag; void LED_InitShiftRegister(void); void LED_Init(void); +// Enter power-saving mode (LEDs off) +void LED_Suspend(void); + +// Leave power-saving mode +void LED_WakeUp(void); + // Display LED_PixelData, starting with the next frame void LED_Commit(void); - diff --git a/src/light_sensor.c b/src/light_sensor.c index 6f0d8e1..ebdfb95 100644 --- a/src/light_sensor.c +++ b/src/light_sensor.c @@ -4,13 +4,9 @@ volatile unsigned int LightSensor_Measurement; volatile bool LightSensor_NewMeasurement = false; // Rolling average of the brightness measurement -float LightSensor_AbsoluteBrightness = 0.5f; +unsigned int LightSensor_AbsoluteBrightness = 0; -// Maximum and minimum encountered so far -float LightSensor_MinimumBrightness = 1.0f; -float LightSensor_MaximumBrightness = 0.0f; - -float LightSensor_RelativeBrightness; +int LightSensor_RelativeBrightness; static void LightSensor_Measure(void) { @@ -66,35 +62,25 @@ void LightSensor_Poll(void) { unsigned int measurement = LightSensor_Measurement; LightSensor_NewMeasurement = false; - float brightness = 65535.0f / measurement; - LightSensor_AbsoluteBrightness = LIGHTSENSOR_LAMBDA * LightSensor_AbsoluteBrightness - + (1.0f - LIGHTSENSOR_LAMBDA) * brightness; + unsigned int brightness = ((1 << 31) + / (measurement + 1)) >> (31 - 16); + LightSensor_AbsoluteBrightness -= LightSensor_AbsoluteBrightness + >> LIGHTSENSOR_LAMBDA_BITS; + LightSensor_AbsoluteBrightness += brightness + >> LIGHTSENSOR_LAMBDA_BITS; - if(LightSensor_AbsoluteBrightness < LightSensor_MinimumBrightness) - { - LightSensor_MinimumBrightness = LightSensor_AbsoluteBrightness; - } - if(LightSensor_AbsoluteBrightness > LightSensor_MaximumBrightness) - { - LightSensor_MaximumBrightness = LightSensor_AbsoluteBrightness; - } + LightSensor_RelativeBrightness = + ((int)LightSensor_AbsoluteBrightness - LIGHTSENSOR_LOW_BOUND) + * LIGHTSENSOR_MAX + / (LIGHTSENSOR_HIGH_BOUND - LIGHTSENSOR_LOW_BOUND); - // Scale and saturate to get relative brightness value - float range = LightSensor_MaximumBrightness - - LightSensor_MinimumBrightness; - float low = LightSensor_MinimumBrightness - + range * LIGHTSENSOR_LOW_BOUND; - float high = LightSensor_MinimumBrightness - + range * LIGHTSENSOR_HIGH_BOUND; - LightSensor_RelativeBrightness = (LightSensor_AbsoluteBrightness - low) - / (high - low); - if(LightSensor_RelativeBrightness < 0.0f) + if(LightSensor_RelativeBrightness < 0) { - LightSensor_RelativeBrightness = 0.0f; + LightSensor_RelativeBrightness = 0; } - if(LightSensor_RelativeBrightness > 1.0f) + if(LightSensor_RelativeBrightness > LIGHTSENSOR_MAX) { - LightSensor_RelativeBrightness = 1.0f; + LightSensor_RelativeBrightness = LIGHTSENSOR_MAX; } } } @@ -112,4 +98,4 @@ void TIM14_IRQHandler(void) LightSensor_NewMeasurement = true; LightSensor_Measure(); -} \ No newline at end of file +} diff --git a/src/light_sensor.h b/src/light_sensor.h index 4ae31cc..cd56d74 100644 --- a/src/light_sensor.h +++ b/src/light_sensor.h @@ -6,17 +6,21 @@ #include "pinning.h" // ADC polling interval in milliseconds -#define LIGHTSENSOR_INTERVAL 250 +#define LIGHTSENSOR_INTERVAL 500 + +// Resolution of the brightness output +#define LIGHTSENSOR_BITS 12 +#define LIGHTSENSOR_MAX ((1 << LIGHTSENSOR_BITS) - 1) // 'Forgetting factor' of the rolling brightness average -#define LIGHTSENSOR_LAMBDA 0.9f +#define LIGHTSENSOR_LAMBDA_BITS 2 -// Bounds for converting absolute to relative brightness: Consider everything -// near the minimum or maximum 0.0 or 1.0, respectively -#define LIGHTSENSOR_LOW_BOUND 0.005f -#define LIGHTSENSOR_HIGH_BOUND 0.9f +// Bounds for converting absolute to relative brightness (empirically +// determined) +#define LIGHTSENSOR_LOW_BOUND 5 +#define LIGHTSENSOR_HIGH_BOUND ((int)(0.85 * LIGHTSENSOR_MAX)) -extern float LightSensor_RelativeBrightness; +extern int LightSensor_RelativeBrightness; void LightSensor_Init(void); void LightSensor_Poll(void); diff --git a/src/main.c b/src/main.c index 781c397..d921b37 100644 --- a/src/main.c +++ b/src/main.c @@ -5,74 +5,37 @@ int main(void) LED_InitShiftRegister(); LightSensor_Init(); + bool powered_down = false; + // Delay a bit to make programming easier for(unsigned int i = 0; i < 1000; i++) { LightSensor_Poll(); } + NVS_Load(); LED_Init(); + Animation_Init(); - unsigned int counter = 0; - const unsigned int blink_period = 1000; - const unsigned int group_size = 6; while(1) { __WFI(); LightSensor_Poll(); + Animation_Poll(); - if(LED_FrameFlag) + if(LightSensor_RelativeBrightness == 0 && !powered_down) { - memset(LED_PixelData, 0, sizeof(LED_PixelData)); - - for(unsigned int i = 0; i < LED_COUNT; i++) - { - uint8_t brightness = 0; - if((counter / blink_period) <= (i % group_size)) - { - if(counter / (blink_period / 2) % 2) - { - brightness = 30; - } - } - - switch(i / group_size) - { - case 0: - LED_PixelData[i].r = brightness; - break; - case 1: - LED_PixelData[i].g = brightness; - break; - case 2: - LED_PixelData[i].b = brightness; - break; - case 3: - LED_PixelData[i].r = brightness; - LED_PixelData[i].g = brightness; - break; - case 4: - LED_PixelData[i].g = brightness; - LED_PixelData[i].b = brightness; - break; - case 5: - LED_PixelData[i].b = brightness; - LED_PixelData[i].r = brightness; - break; - } - - counter++; - if(counter > blink_period * (group_size + 2)) - { - counter = 0; - } - } - - LED_FrameFlag = false; - LED_Commit(); + LED_Suspend(); + NVS_Save(true); + powered_down = true; + } + if(powered_down && LightSensor_RelativeBrightness > 0) + { + powered_down = false; + NVS_Save(true); + LED_WakeUp(); } } return 0; } - diff --git a/src/main.h b/src/main.h index 48ce191..48e0d25 100644 --- a/src/main.h +++ b/src/main.h @@ -1,11 +1,13 @@ #pragma once #include +#include #include "stm32f030x6.h" #include "buildid.h" #include "led.h" #include "light_sensor.h" +#include "animation.h" +#include "nvs.h" int main(void); - diff --git a/src/nvs.c b/src/nvs.c new file mode 100644 index 0000000..7adcd73 --- /dev/null +++ b/src/nvs.c @@ -0,0 +1,204 @@ +#include +#include + +#include "nvs.h" + +// Note that the area used for storage must be aligned to the flash's pages. If +// you want to use a larger area, be sure to also change the size and location +// in the linker script. +#define NVS_AREA_SIZE 1024 +#define NVS_BLOCK_COUNT (NVS_AREA_SIZE / sizeof(NVS_Block_t)) +#define NVS_VALID_BLOCK_MARKER 0xab34 + +typedef struct +__attribute__((packed, aligned(2))) +{ + // Actual block data + NVS_Data_t data; + + // Ensure struct size is divisible by two + uint8_t padding[sizeof(NVS_Data_t) % 2]; + + // Block marker for detecting if the block is the current or an old block + uint16_t marker; + + uint32_t crc; +} NVS_Block_t; + +__attribute__((used, section(".nvstore"))) +NVS_Block_t NVS_Area[NVS_BLOCK_COUNT]; + +NVS_Block_t *NVS_FlashData; +__attribute__((used)) +NVS_Block_t NVS_RAMData; + +NVS_Data_t *const NVS_Data = &NVS_RAMData.data; + +static uint32_t NVS_CalculateCRC(NVS_Data_t *data) +{ + CRC->CR = CRC_CR_RESET; + for(unsigned int i = 0; i < sizeof(NVS_Data_t); i++) + { + CRC->DR = ((uint8_t*)(data))[i]; + } + return CRC->DR; +} + +static bool NVS_ProgramHalfWord(uint16_t *dest, uint16_t value) +{ + FLASH->CR = FLASH_CR_PG; + *(uint16_t*)dest = value; + while(FLASH->SR & FLASH_SR_BSY); + if(*dest != value) + { + // Write failed + FLASH->CR = 0x00000000; + return false; + } + FLASH->CR = 0x00000000; + return true; +} + +static void NVS_UnlockFlash(void) +{ + while(FLASH->SR & FLASH_SR_BSY); + if(FLASH->CR & FLASH_CR_LOCK) + { + FLASH->KEYR = 0x45670123; + FLASH->KEYR = 0xcdef89ab; + } +} + +static bool NVS_EraseArea(void) +{ + for(unsigned int i = 0; i < NVS_AREA_SIZE; i += 1024) + { + while(FLASH->SR & FLASH_SR_BSY); + FLASH->CR = FLASH_CR_PER; + FLASH->AR = (uint32_t)NVS_Area + i; + FLASH->CR |= FLASH_CR_STRT; + while(FLASH->SR & FLASH_SR_BSY); + if(FLASH->SR & FLASH_SR_EOP) + { + // Clear EOP flag + FLASH->SR = FLASH_SR_EOP; + } + else + { + // Erase failed + FLASH->CR = 0x00000000; + return false; + } + FLASH->CR = 0x00000000; + } + return true; +} + +static bool NVS_BlockEmpty(NVS_Block_t *block) +{ + for(unsigned int i = 0; i < sizeof(NVS_Block_t) / 2; i++) + { + if(*((uint16_t*)block + i) != 0xffff) + { + return false; + } + } + return true; +} + +static void NVS_LoadDefaults(void) +{ + NVS_Data->animation_step_bottom = 0; + NVS_Data->animation_step_top = 0; +} + +bool NVS_Load(void) +{ + RCC->AHBENR |= RCC_AHBENR_CRCEN; + + NVS_Block_t *block = NULL; + + // Find valid block + for(unsigned int i = 0; i < NVS_BLOCK_COUNT; i++) + { + block = &NVS_Area[i]; + if(block->marker == NVS_VALID_BLOCK_MARKER) + { + // Valid block found, next up is the CRC check + break; + } + else if(block->marker == 0xffff) + { + // This block was erased and no block before was valid, so we can + // assume there is no valid block + block = NULL; + break; + } + } + + if(block == NULL || block->marker != NVS_VALID_BLOCK_MARKER + || block->crc != NVS_CalculateCRC(&block->data)) + { + // No valid block found + NVS_LoadDefaults(); + return false; + } + else + { + // Valid block and CRC check successful + NVS_FlashData = block; + memcpy(&NVS_RAMData, (void*)block, sizeof(NVS_Block_t)); + return true; + } +} + +bool NVS_Save(bool erase_if_needed) +{ + NVS_UnlockFlash(); + + // Currently loaded block. Is NULL if the defaults were loaded in NVS_Load() + // instead of some flash contents. + NVS_Block_t *current_block = NVS_FlashData; + NVS_Block_t *next_block = NVS_FlashData + 1; + + if(current_block == NULL || next_block > NVS_Area + NVS_BLOCK_COUNT + || !NVS_BlockEmpty(next_block)) + { + if(!erase_if_needed) + { + return false; + } + + if(!NVS_EraseArea()) + { + return false; + } + next_block = &NVS_Area[0]; + current_block = NULL; + } + + NVS_RAMData.crc = NVS_CalculateCRC(NVS_Data); + NVS_RAMData.marker = NVS_VALID_BLOCK_MARKER; + + // The block length is guaranteed to be divisible by 2 + for(unsigned int i = 0; i < sizeof(NVS_Block_t) / 2; i++) + { + if(!NVS_ProgramHalfWord((uint16_t*)next_block + i, + *((uint16_t*)&NVS_RAMData + i))) + { + return false; + } + } + + if(current_block != NULL) + { + if(!NVS_ProgramHalfWord((uint16_t*)¤t_block->marker, 0x0000)) + { + return false; + } + } + + NVS_FlashData = next_block; + + return true; +} diff --git a/src/nvs.h b/src/nvs.h new file mode 100644 index 0000000..a26f8e6 --- /dev/null +++ b/src/nvs.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "stm32f030x6.h" + +typedef struct +__attribute__((packed, aligned(2))) +{ + unsigned int animation_step_bottom; + unsigned int animation_step_top; +} NVS_Data_t; + +extern NVS_Data_t *const NVS_Data; + +// Returns true if the data was successfully loaded from flash and false if the +// defaults were restored instead +bool NVS_Load(void); + +// Stores the current contents of NVS_Data to flash. Pass false as a parameter +// to skip saving unless it can be done without a flash page erase. Returns true +// if the operation succeeds and false if there is was an error or erasing was +// disallowed but would have been necessary. +bool NVS_Save(bool erase_if_needed); + + diff --git a/src/system.c b/src/system.c index 7ca3df8..0a64ee1 100644 --- a/src/system.c +++ b/src/system.c @@ -6,4 +6,3 @@ void SystemInit(void) // Disable all interrupts RCC->CIR = 0x00000000; } -