Compare commits

..

No commits in common. "develop" and "led_order" have entirely different histories.

21 changed files with 127 additions and 842 deletions

View file

@ -1,16 +0,0 @@
# 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.

View file

@ -1,9 +0,0 @@
# 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.

View file

@ -1,20 +0,0 @@
#!/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 @@
515
298

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

View file

@ -12,15 +12,6 @@ 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 :
{

View file

@ -1,8 +1,7 @@
MEMORY
{
flash (rx) : ORIGIN = 0x08000000, LENGTH = 15K
flash_nvstore (rw) : ORIGIN = 0x08003c00, LENGTH = 1K
flash (rx) : ORIGIN = 0x08000000, LENGTH = 16K
sram (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
}
INCLUDE ld/common_nvs.ld
INCLUDE ld/common.ld

View file

@ -10,7 +10,7 @@ DEBUG := yes
H_DEVICE = STM32F030x6
STARTUP_SOURCE_DIR = src
STARTUP_SOURCES = $(STARTUP_SOURCE_DIR)/startup.S
LD_SCRIPT = ld/stm32f030f4_15k_flash_1k_nvs.ld
LD_SCRIPT = ld/stm32f030f4_flash.ld
ifeq ($(DEBUG),yes)
DEBUG_FLAGS = -DDEBUG -g3

View file

@ -1,107 +1,18 @@
#include "animation.h"
#include "animation_lut.h"
#include "light_sensor.h"
#include "nvs.h"
#include "led.h"
volatile bool Animation_FrameFlag = false;
#if LED_COLUMNS == 12
const unsigned int Animation_LEDOrder[LED_COUNT] =
{
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;
}
// 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
};

View file

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

View file

@ -1,261 +0,0 @@
#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 },
};

View file

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

106
src/led.c
View file

@ -3,10 +3,7 @@
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) \
@ -27,27 +24,6 @@ bool LED_Suspended = 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
@ -57,7 +33,6 @@ 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,
@ -65,14 +40,6 @@ 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
@ -91,11 +58,6 @@ 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.
@ -105,13 +67,15 @@ void LED_Commit(void)
{
for(int i = 0; i < LED_COLUMNS; i++)
{
// 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];
// 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;
for(int j = 0; j < LED_BITS; j++)
{
if(colour_value & (1 << j))
if(gamma_corrected & (1 << j))
{
LED_BackBuffer[r * (LED_BITS + 1) + j] &=
~(1 << LED_Pins[i]);
@ -144,7 +108,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 event
// DMA channel 3: Output data to port a on TIM3 update
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.
@ -153,7 +117,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 4: update TIM3 ARR on TIM3 compare 1 match
// DMA channel 3: Output data to port a on TIM3 update
// 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]);
@ -251,7 +215,6 @@ void LED_Init(void)
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// Fill both DMA buffers
LED_QueuePageFlip = false;
LED_Commit();
LED_PageFlip();
LED_Commit();
@ -281,53 +244,6 @@ 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
@ -352,12 +268,6 @@ 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

View file

@ -5,19 +5,17 @@
#include "stm32f030x6.h"
#include "pinning.h"
#define LED_BITS 12 // BCM resolution in bits
#define LED_BITS 12
#define LED_ROWS 8 // Rows are driven by a shift register
#define LED_COLUMNS 12 // Columns are driven by the MCU directly.
// Set to 9 to free the SWD pins and enable
// debugging.
#define LED_COLUMNS 12 // Columns are driven by the MCU directly
#define LED_COUNT (LED_ROWS * LED_COLUMNS / 3)
typedef struct
{
uint16_t r;
uint16_t g;
uint16_t b;
} __attribute__((packed, aligned(2))) LED_Colour_t;
uint8_t r;
uint8_t g;
uint8_t b;
} __attribute__((packed)) LED_Colour_t;
// Pixel data, not displayed until LED_Commit() is called
extern LED_Colour_t LED_PixelData[LED_COUNT];
@ -29,11 +27,6 @@ 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);

View file

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

View file

@ -6,21 +6,17 @@
#include "pinning.h"
// ADC polling interval in milliseconds
#define LIGHTSENSOR_INTERVAL 500
// Resolution of the brightness output
#define LIGHTSENSOR_BITS 12
#define LIGHTSENSOR_MAX ((1 << LIGHTSENSOR_BITS) - 1)
#define LIGHTSENSOR_INTERVAL 250
// 'Forgetting factor' of the rolling brightness average
#define LIGHTSENSOR_LAMBDA_BITS 2
#define LIGHTSENSOR_LAMBDA 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))
// 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
extern int LightSensor_RelativeBrightness;
extern float LightSensor_RelativeBrightness;
void LightSensor_Init(void);
void LightSensor_Poll(void);

View file

@ -5,37 +5,74 @@ 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(LightSensor_RelativeBrightness == 0 && !powered_down)
if(LED_FrameFlag)
{
LED_Suspend();
NVS_Save(true);
powered_down = true;
}
if(powered_down && LightSensor_RelativeBrightness > 0)
{
powered_down = false;
NVS_Save(true);
LED_WakeUp();
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();
}
}
return 0;
}

View file

@ -1,13 +1,11 @@
#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
View file

@ -1,204 +0,0 @@
#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;
}

View file

@ -1,26 +0,0 @@
#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,3 +6,4 @@ void SystemInit(void)
// Disable all interrupts
RCC->CIR = 0x00000000;
}