Compare commits

...

31 commits

Author SHA1 Message Date
fruchti 87b734cd93 Add larger photo for Gitea 2023-03-12 13:37:43 +01:00
fruchti 6b55403334 Add animation LUT generation script 2020-10-04 22:43:42 +02:00
fruchti 72a55fb026 Add tree photo 2020-10-04 22:40:06 +02:00
fruchti 76f69d33f9 Add project description, licence information 2020-10-03 20:19:31 +02:00
fruchti f3edcd1c26 Fix BCM comments 2020-09-27 23:53:08 +02:00
fruchti 3a6c0a6391 Fix dimming overflow 2020-09-21 10:36:50 +02:00
fruchti 6d1481fb95 Tune light sensor parameters 2020-09-21 00:34:32 +02:00
fruchti ad433e2452 Move gamma correction from software to LUT 2020-09-21 00:33:44 +02:00
fruchti e13d522f4e Remove light sensor auto-normalisation 2020-09-20 19:00:55 +02:00
fruchti 9f230125d8 Fix NVS erase return typo 2020-09-20 18:59:59 +02:00
fruchti ea2049efb2 Make NVS saving more resilient 2020-09-20 18:17:37 +02:00
fruchti 8ac36022e0 Also save to NVS from time to time 2020-09-20 18:00:35 +02:00
fruchti a3799b02a0 Save to NVS on suspend and wake-up 2020-09-20 17:40:13 +02:00
fruchti 10a5973361 Load NVS data on power-on 2020-09-20 17:36:58 +02:00
fruchti 3fd2881ba2 Fix NVS erase by properly resetting FLASH_CR 2020-09-20 17:29:15 +02:00
fruchti 5e4b80343f Fix warnings 2020-09-20 16:45:35 +02:00
fruchti 99b3d6a36c Move animation steps to NVS data 2020-09-20 16:44:52 +02:00
fruchti 1e8da90156 Allow using 9 columns to enable debugging 2020-09-20 16:40:55 +02:00
fruchti 2a3db7b9fb Fix EOF newlines 2020-09-02 00:12:14 +02:00
fruchti d6a5ee1689 Tune light sensor parameters 2020-07-18 00:21:11 +02:00
fruchti a3da7c3803 Define animation based on cycle time 2020-07-17 23:38:02 +02:00
fruchti 5d1ef2263a Gradually reset maximum/minimum brightness 2020-07-17 23:31:57 +02:00
fruchti 53aa19d91f Externalise colour LUT 2020-07-17 16:38:09 +02:00
fruchti e81daf59fe Use dedicated timer for animation 2020-07-17 00:17:51 +02:00
fruchti bf50402ce2 Track suspend status in LED driver 2020-07-17 00:17:35 +02:00
fruchti 38d3a3846b Add power-down mode for LED driver 2020-07-16 23:48:48 +02:00
fruchti ee5a5b9f6c Fix light sensor underflow 2020-07-16 23:19:28 +02:00
fruchti 5a45524af3 Add simple smooth animation 2020-07-16 23:11:09 +02:00
fruchti 187c915799 Use integer arithmetic for light sensor 2020-07-16 22:43:39 +02:00
fruchti 3121557afd Add basic gradient output 2020-07-16 22:04:54 +02:00
fruchti 659fdb9004 Add LED order 2020-07-16 20:45:17 +02:00
21 changed files with 841 additions and 126 deletions

16
LICENCE_ISC.md Normal file
View file

@ -0,0 +1,16 @@
# ISC licence
Copyright (c) 2020, fruchti<fruchti@gvfr.de>
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.

9
README.md Normal file
View file

@ -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.

20
animation_lut.py Executable file
View file

@ -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('};')

View file

@ -1 +1 @@
298 515

BIN
laurelin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -12,6 +12,15 @@ SECTIONS
_end_text = .; _end_text = .;
} >flash } >flash
/* Non-volatile storage area in flash */
.nvstore (NOLOAD) :
{
. = ALIGN(1K);
*(.nvstore*)
. = ALIGN(1K);
_end_nvstore = .;
} > flash_nvstore
/* C++ initialiser code segments */ /* C++ initialiser code segments */
.preinit_array : .preinit_array :
{ {

View file

@ -1,7 +1,8 @@
MEMORY 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 sram (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
} }
INCLUDE ld/common.ld INCLUDE ld/common_nvs.ld

View file

@ -10,7 +10,7 @@ DEBUG := yes
H_DEVICE = STM32F030x6 H_DEVICE = STM32F030x6
STARTUP_SOURCE_DIR = src STARTUP_SOURCE_DIR = src
STARTUP_SOURCES = $(STARTUP_SOURCE_DIR)/startup.S 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) ifeq ($(DEBUG),yes)
DEBUG_FLAGS = -DDEBUG -g3 DEBUG_FLAGS = -DDEBUG -g3

View file

@ -1,18 +1,107 @@
#include "animation.h" #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] = const unsigned int Animation_LEDOrder[LED_COUNT] =
{ {
// Red 19, 27, 21, 13, 0, 4, 24, 8, 12, 15, 6, 5, 28,
0, 1, 2, 3, 4, 5, 29, 17, 3, 18, 26, 22, 10, 16, 20, 30, 1,
// Green 25, 2, 14, 31, 7, 11, 9, 23
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
}; };
#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;
}

View file

@ -1 +1,25 @@
#pragma once #pragma once
#include <stdbool.h>
#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);

261
src/animation_lut.c Normal file
View file

@ -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 },
};

5
src/animation_lut.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include "led.h"
extern const LED_Colour_t Animation_ColourLUT[256];

106
src/led.c
View file

@ -3,7 +3,10 @@
LED_Colour_t LED_PixelData[LED_COUNT] = {{0}}; LED_Colour_t LED_PixelData[LED_COUNT] = {{0}};
volatile bool LED_FrameFlag = false; 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) \ #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_B_0) | (1 << PIN_LED_R_1) \
| (1 << PIN_LED_G_1) | (1 << PIN_LED_B_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_R_2 * 2) | (1 << PIN_LED_G_2 * 2) \
| (1 << PIN_LED_B_2 * 2) | (1 << PIN_LED_R_3 * 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)) | (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 // 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 // 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 16, 32, 64, 128, 256, 512, 1024, 2048
}; };
#if LED_COLUMNS == 12
static const int LED_Pins[LED_COLUMNS] = static const int LED_Pins[LED_COLUMNS] =
{ {
PIN_LED_R_0, PIN_LED_G_0, PIN_LED_B_0, 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_2, PIN_LED_G_2, PIN_LED_B_2,
PIN_LED_R_3, PIN_LED_G_3, PIN_LED_B_3 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 // 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 // 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) void LED_Commit(void)
{ {
if(LED_Suspended)
{
return;
}
// Wait for the current data to be displayed in case LED_Commit was called // 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 // more than one time during a single frame. Otherwise, a race condition
// might occur. // might occur.
@ -67,15 +105,13 @@ void LED_Commit(void)
{ {
for(int i = 0; i < LED_COLUMNS; i++) for(int i = 0; i < LED_COLUMNS; i++)
{ {
// Use pixel data as a raw byte buffer to get R, G, B in order // Use pixel data as a raw word buffer to get R, G, B in order
uint8_t colour_value = ((uint8_t*)LED_PixelData)[r * LED_COLUMNS + i]; uint16_t colour_value =
uint16_t gamma_corrected = (uint16_t)colour_value; ((uint16_t*)LED_PixelData)[r * LED_COLUMNS + i];
gamma_corrected *= gamma_corrected;
gamma_corrected >>= 16 - LED_BITS;
for(int j = 0; j < LED_BITS; j++) 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] &= LED_BackBuffer[r * (LED_BITS + 1) + j] &=
~(1 << LED_Pins[i]); ~(1 << LED_Pins[i]);
@ -108,7 +144,7 @@ static void LED_StartBCM(int row)
TIM3->ARR = LED_BitLengths[0]; TIM3->ARR = LED_BitLengths[0];
TIM3->DIER = TIM_DIER_UDE | TIM_DIER_CC1DE; 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]); 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. // 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. // 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 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_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 bit lengths table is offset because the first value is already in
// the timer's ARR shadow register. // the timer's ARR shadow register.
DMA1_Channel4->CMAR = (uint32_t)&(LED_BitLengths[1]); DMA1_Channel4->CMAR = (uint32_t)&(LED_BitLengths[1]);
@ -215,6 +251,7 @@ void LED_Init(void)
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// Fill both DMA buffers // Fill both DMA buffers
LED_QueuePageFlip = false;
LED_Commit(); LED_Commit();
LED_PageFlip(); LED_PageFlip();
LED_Commit(); LED_Commit();
@ -244,6 +281,53 @@ void LED_Init(void)
LED_StartBCM(0); 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) void DMA1_Channel2_3_IRQHandler(void)
{ {
// Interrupt when all bits have been sent // Interrupt when all bits have been sent
@ -268,6 +352,12 @@ void DMA1_Channel2_3_IRQHandler(void)
LED_PulseRowClock(); LED_PulseRowClock();
} }
if(LED_SuspendFlag)
{
LED_SuspendFlag = false;
return;
}
// Start sending bits out again. The row offset caused by the shift // Start sending bits out again. The row offset caused by the shift
// register "lagging" one clock cycle behind because RCK and SCK are // 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 // connected to the same signal doesn't matter here: we're not paying much

View file

@ -5,17 +5,19 @@
#include "stm32f030x6.h" #include "stm32f030x6.h"
#include "pinning.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_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) #define LED_COUNT (LED_ROWS * LED_COLUMNS / 3)
typedef struct typedef struct
{ {
uint8_t r; uint16_t r;
uint8_t g; uint16_t g;
uint8_t b; uint16_t b;
} __attribute__((packed)) LED_Colour_t; } __attribute__((packed, aligned(2))) LED_Colour_t;
// Pixel data, not displayed until LED_Commit() is called // Pixel data, not displayed until LED_Commit() is called
extern LED_Colour_t LED_PixelData[LED_COUNT]; extern LED_Colour_t LED_PixelData[LED_COUNT];
@ -27,6 +29,11 @@ extern volatile bool LED_FrameFlag;
void LED_InitShiftRegister(void); void LED_InitShiftRegister(void);
void LED_Init(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 // Display LED_PixelData, starting with the next frame
void LED_Commit(void); void LED_Commit(void);

View file

@ -4,13 +4,9 @@ volatile unsigned int LightSensor_Measurement;
volatile bool LightSensor_NewMeasurement = false; volatile bool LightSensor_NewMeasurement = false;
// Rolling average of the brightness measurement // Rolling average of the brightness measurement
float LightSensor_AbsoluteBrightness = 0.5f; unsigned int LightSensor_AbsoluteBrightness = 0;
// Maximum and minimum encountered so far int LightSensor_RelativeBrightness;
float LightSensor_MinimumBrightness = 1.0f;
float LightSensor_MaximumBrightness = 0.0f;
float LightSensor_RelativeBrightness;
static void LightSensor_Measure(void) static void LightSensor_Measure(void)
{ {
@ -66,35 +62,25 @@ void LightSensor_Poll(void)
{ {
unsigned int measurement = LightSensor_Measurement; unsigned int measurement = LightSensor_Measurement;
LightSensor_NewMeasurement = false; LightSensor_NewMeasurement = false;
float brightness = 65535.0f / measurement; unsigned int brightness = ((1 << 31)
LightSensor_AbsoluteBrightness = LIGHTSENSOR_LAMBDA * LightSensor_AbsoluteBrightness / (measurement + 1)) >> (31 - 16);
+ (1.0f - LIGHTSENSOR_LAMBDA) * brightness; LightSensor_AbsoluteBrightness -= LightSensor_AbsoluteBrightness
>> LIGHTSENSOR_LAMBDA_BITS;
LightSensor_AbsoluteBrightness += brightness
>> LIGHTSENSOR_LAMBDA_BITS;
if(LightSensor_AbsoluteBrightness < LightSensor_MinimumBrightness) LightSensor_RelativeBrightness =
{ ((int)LightSensor_AbsoluteBrightness - LIGHTSENSOR_LOW_BOUND)
LightSensor_MinimumBrightness = LightSensor_AbsoluteBrightness; * LIGHTSENSOR_MAX
} / (LIGHTSENSOR_HIGH_BOUND - LIGHTSENSOR_LOW_BOUND);
if(LightSensor_AbsoluteBrightness > LightSensor_MaximumBrightness)
{
LightSensor_MaximumBrightness = LightSensor_AbsoluteBrightness;
}
// Scale and saturate to get relative brightness value if(LightSensor_RelativeBrightness < 0)
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)
{ {
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;
} }
} }
} }

View file

@ -6,17 +6,21 @@
#include "pinning.h" #include "pinning.h"
// ADC polling interval in milliseconds // 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 // '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 // Bounds for converting absolute to relative brightness (empirically
// near the minimum or maximum 0.0 or 1.0, respectively // determined)
#define LIGHTSENSOR_LOW_BOUND 0.005f #define LIGHTSENSOR_LOW_BOUND 5
#define LIGHTSENSOR_HIGH_BOUND 0.9f #define LIGHTSENSOR_HIGH_BOUND ((int)(0.85 * LIGHTSENSOR_MAX))
extern float LightSensor_RelativeBrightness; extern int LightSensor_RelativeBrightness;
void LightSensor_Init(void); void LightSensor_Init(void);
void LightSensor_Poll(void); void LightSensor_Poll(void);

View file

@ -5,74 +5,37 @@ int main(void)
LED_InitShiftRegister(); LED_InitShiftRegister();
LightSensor_Init(); LightSensor_Init();
bool powered_down = false;
// Delay a bit to make programming easier // Delay a bit to make programming easier
for(unsigned int i = 0; i < 1000; i++) for(unsigned int i = 0; i < 1000; i++)
{ {
LightSensor_Poll(); LightSensor_Poll();
} }
NVS_Load();
LED_Init(); LED_Init();
Animation_Init();
unsigned int counter = 0;
const unsigned int blink_period = 1000;
const unsigned int group_size = 6;
while(1) while(1)
{ {
__WFI(); __WFI();
LightSensor_Poll(); LightSensor_Poll();
Animation_Poll();
if(LED_FrameFlag) if(LightSensor_RelativeBrightness == 0 && !powered_down)
{ {
memset(LED_PixelData, 0, sizeof(LED_PixelData)); LED_Suspend();
NVS_Save(true);
for(unsigned int i = 0; i < LED_COUNT; i++) powered_down = true;
{
uint8_t brightness = 0;
if((counter / blink_period) <= (i % group_size))
{
if(counter / (blink_period / 2) % 2)
{
brightness = 30;
} }
} if(powered_down && LightSensor_RelativeBrightness > 0)
switch(i / group_size)
{ {
case 0: powered_down = false;
LED_PixelData[i].r = brightness; NVS_Save(true);
break; LED_WakeUp();
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();
} }
} }
return 0; return 0;
} }

View file

@ -1,11 +1,13 @@
#pragma once #pragma once
#include <string.h> #include <string.h>
#include <stdbool.h>
#include "stm32f030x6.h" #include "stm32f030x6.h"
#include "buildid.h" #include "buildid.h"
#include "led.h" #include "led.h"
#include "light_sensor.h" #include "light_sensor.h"
#include "animation.h"
#include "nvs.h"
int main(void); int main(void);

204
src/nvs.c Normal file
View file

@ -0,0 +1,204 @@
#include <stddef.h>
#include <string.h>
#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*)&current_block->marker, 0x0000))
{
return false;
}
}
NVS_FlashData = next_block;
return true;
}

26
src/nvs.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <stdbool.h>
#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);

View file

@ -6,4 +6,3 @@ void SystemInit(void)
// Disable all interrupts // Disable all interrupts
RCC->CIR = 0x00000000; RCC->CIR = 0x00000000;
} }