Compare commits
31 commits
Author | SHA1 | Date | |
---|---|---|---|
fruchti | 87b734cd93 | ||
fruchti | 6b55403334 | ||
fruchti | 72a55fb026 | ||
fruchti | 76f69d33f9 | ||
fruchti | f3edcd1c26 | ||
fruchti | 3a6c0a6391 | ||
fruchti | 6d1481fb95 | ||
fruchti | ad433e2452 | ||
fruchti | e13d522f4e | ||
fruchti | 9f230125d8 | ||
fruchti | ea2049efb2 | ||
fruchti | 8ac36022e0 | ||
fruchti | a3799b02a0 | ||
fruchti | 10a5973361 | ||
fruchti | 3fd2881ba2 | ||
fruchti | 5e4b80343f | ||
fruchti | 99b3d6a36c | ||
fruchti | 1e8da90156 | ||
fruchti | 2a3db7b9fb | ||
fruchti | d6a5ee1689 | ||
fruchti | a3da7c3803 | ||
fruchti | 5d1ef2263a | ||
fruchti | 53aa19d91f | ||
fruchti | e81daf59fe | ||
fruchti | bf50402ce2 | ||
fruchti | 38d3a3846b | ||
fruchti | ee5a5b9f6c | ||
fruchti | 5a45524af3 | ||
fruchti | 187c915799 | ||
fruchti | 3121557afd | ||
fruchti | 659fdb9004 |
16
LICENCE_ISC.md
Normal file
16
LICENCE_ISC.md
Normal 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
9
README.md
Normal 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
20
animation_lut.py
Executable 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('};')
|
|
@ -1 +1 @@
|
|||
298
|
||||
515
|
||||
|
|
BIN
laurelin.jpg
Normal file
BIN
laurelin.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
|
@ -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 :
|
||||
{
|
|
@ -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
|
2
makefile
2
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
|
||||
|
|
115
src/animation.c
115
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
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1 +1,25 @@
|
|||
#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
261
src/animation_lut.c
Normal 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
5
src/animation_lut.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "led.h"
|
||||
|
||||
extern const LED_Colour_t Animation_ColourLUT[256];
|
106
src/led.c
106
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
|
||||
|
|
21
src/led.h
21
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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
67
src/main.c
67
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "stm32f030x6.h"
|
||||
#include "buildid.h"
|
||||
#include "led.h"
|
||||
#include "light_sensor.h"
|
||||
#include "animation.h"
|
||||
#include "nvs.h"
|
||||
|
||||
int main(void);
|
||||
|
||||
|
|
204
src/nvs.c
Normal file
204
src/nvs.c
Normal 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*)¤t_block->marker, 0x0000))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NVS_FlashData = next_block;
|
||||
|
||||
return true;
|
||||
}
|
26
src/nvs.h
Normal file
26
src/nvs.h
Normal 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);
|
||||
|
||||
|
|
@ -6,4 +6,3 @@ void SystemInit(void)
|
|||
// Disable all interrupts
|
||||
RCC->CIR = 0x00000000;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue