Compare commits
No commits in common. "develop" and "led_order" have entirely different histories.
|
@ -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.
|
|
||||||
|
|
|
@ -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.
|
|
|
@ -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('};')
|
|
|
@ -1 +1 @@
|
||||||
515
|
298
|
||||||
|
|
BIN
laurelin.jpg
BIN
laurelin.jpg
Binary file not shown.
Before Width: | Height: | Size: 64 KiB |
|
@ -12,15 +12,6 @@ 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 :
|
||||||
{
|
{
|
|
@ -1,8 +1,7 @@
|
||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
flash (rx) : ORIGIN = 0x08000000, LENGTH = 15K
|
flash (rx) : ORIGIN = 0x08000000, LENGTH = 16K
|
||||||
flash_nvstore (rw) : ORIGIN = 0x08003c00, LENGTH = 1K
|
|
||||||
sram (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
|
sram (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
|
||||||
}
|
}
|
||||||
|
|
||||||
INCLUDE ld/common_nvs.ld
|
INCLUDE ld/common.ld
|
2
makefile
2
makefile
|
@ -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_15k_flash_1k_nvs.ld
|
LD_SCRIPT = ld/stm32f030f4_flash.ld
|
||||||
|
|
||||||
ifeq ($(DEBUG),yes)
|
ifeq ($(DEBUG),yes)
|
||||||
DEBUG_FLAGS = -DDEBUG -g3
|
DEBUG_FLAGS = -DDEBUG -g3
|
||||||
|
|
117
src/animation.c
117
src/animation.c
|
@ -1,107 +1,18 @@
|
||||||
#include "animation.h"
|
#include "animation.h"
|
||||||
#include "animation_lut.h"
|
#include "led.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] =
|
||||||
{
|
{
|
||||||
19, 27, 21, 13, 0, 4, 24, 8, 12, 15, 6, 5, 28,
|
// Red
|
||||||
29, 17, 3, 18, 26, 22, 10, 16, 20, 30, 1,
|
0, 1, 2, 3, 4, 5,
|
||||||
25, 2, 14, 31, 7, 11, 9, 23
|
// Green
|
||||||
};
|
6, 7, 8, 9, 10, 11,
|
||||||
#elif LED_COLUMNS == 9
|
// Blue
|
||||||
const unsigned int Animation_LEDOrder[LED_COUNT] =
|
12, 13, 14, 15, 16, 17,
|
||||||
{
|
// Yellow
|
||||||
19, 21, 13, 0, 4, 8, 12, 15, 6, 5,
|
18, 19, 20, 21, 22, 23,
|
||||||
17, 3, 18, 22, 10, 16, 20, 1,
|
// Cyan
|
||||||
2, 14, 7, 11, 9, 23
|
24, 25, 26, 27, 28, 29,
|
||||||
};
|
// Fuchsia
|
||||||
#endif
|
30, 31
|
||||||
|
};
|
||||||
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,25 +1 @@
|
||||||
#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);
|
|
|
@ -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 },
|
|
||||||
};
|
|
|
@ -1,5 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "led.h"
|
|
||||||
|
|
||||||
extern const LED_Colour_t Animation_ColourLUT[256];
|
|
106
src/led.c
106
src/led.c
|
@ -3,10 +3,7 @@
|
||||||
|
|
||||||
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) \
|
||||||
|
@ -27,27 +24,6 @@ bool LED_Suspended = 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
|
||||||
|
@ -57,7 +33,6 @@ 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,
|
||||||
|
@ -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_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
|
||||||
|
@ -91,11 +58,6 @@ 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.
|
||||||
|
@ -105,13 +67,15 @@ 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 word buffer to get R, G, B in order
|
// Use pixel data as a raw byte buffer to get R, G, B in order
|
||||||
uint16_t colour_value =
|
uint8_t colour_value = ((uint8_t*)LED_PixelData)[r * LED_COLUMNS + i];
|
||||||
((uint16_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++)
|
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] &=
|
LED_BackBuffer[r * (LED_BITS + 1) + j] &=
|
||||||
~(1 << LED_Pins[i]);
|
~(1 << LED_Pins[i]);
|
||||||
|
@ -144,7 +108,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 event
|
// DMA channel 3: Output data to port a on TIM3 update
|
||||||
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.
|
||||||
|
@ -153,7 +117,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 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 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]);
|
||||||
|
@ -251,7 +215,6 @@ 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();
|
||||||
|
@ -281,53 +244,6 @@ 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
|
||||||
|
@ -352,12 +268,6 @@ 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
|
||||||
|
|
21
src/led.h
21
src/led.h
|
@ -5,19 +5,17 @@
|
||||||
#include "stm32f030x6.h"
|
#include "stm32f030x6.h"
|
||||||
#include "pinning.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_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
|
||||||
{
|
{
|
||||||
uint16_t r;
|
uint8_t r;
|
||||||
uint16_t g;
|
uint8_t g;
|
||||||
uint16_t b;
|
uint8_t b;
|
||||||
} __attribute__((packed, aligned(2))) LED_Colour_t;
|
} __attribute__((packed)) 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];
|
||||||
|
@ -29,11 +27,6 @@ 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);
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,13 @@ 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
|
||||||
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)
|
static void LightSensor_Measure(void)
|
||||||
{
|
{
|
||||||
|
@ -62,25 +66,35 @@ void LightSensor_Poll(void)
|
||||||
{
|
{
|
||||||
unsigned int measurement = LightSensor_Measurement;
|
unsigned int measurement = LightSensor_Measurement;
|
||||||
LightSensor_NewMeasurement = false;
|
LightSensor_NewMeasurement = false;
|
||||||
unsigned int brightness = ((1 << 31)
|
float brightness = 65535.0f / measurement;
|
||||||
/ (measurement + 1)) >> (31 - 16);
|
LightSensor_AbsoluteBrightness = LIGHTSENSOR_LAMBDA * LightSensor_AbsoluteBrightness
|
||||||
LightSensor_AbsoluteBrightness -= LightSensor_AbsoluteBrightness
|
+ (1.0f - LIGHTSENSOR_LAMBDA) * brightness;
|
||||||
>> LIGHTSENSOR_LAMBDA_BITS;
|
|
||||||
LightSensor_AbsoluteBrightness += brightness
|
|
||||||
>> LIGHTSENSOR_LAMBDA_BITS;
|
|
||||||
|
|
||||||
LightSensor_RelativeBrightness =
|
if(LightSensor_AbsoluteBrightness < LightSensor_MinimumBrightness)
|
||||||
((int)LightSensor_AbsoluteBrightness - LIGHTSENSOR_LOW_BOUND)
|
|
||||||
* LIGHTSENSOR_MAX
|
|
||||||
/ (LIGHTSENSOR_HIGH_BOUND - LIGHTSENSOR_LOW_BOUND);
|
|
||||||
|
|
||||||
if(LightSensor_RelativeBrightness < 0)
|
|
||||||
{
|
{
|
||||||
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_NewMeasurement = true;
|
||||||
|
|
||||||
LightSensor_Measure();
|
LightSensor_Measure();
|
||||||
}
|
}
|
|
@ -6,21 +6,17 @@
|
||||||
#include "pinning.h"
|
#include "pinning.h"
|
||||||
|
|
||||||
// ADC polling interval in milliseconds
|
// ADC polling interval in milliseconds
|
||||||
#define LIGHTSENSOR_INTERVAL 500
|
#define LIGHTSENSOR_INTERVAL 250
|
||||||
|
|
||||||
// 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_BITS 2
|
#define LIGHTSENSOR_LAMBDA 0.9f
|
||||||
|
|
||||||
// Bounds for converting absolute to relative brightness (empirically
|
// Bounds for converting absolute to relative brightness: Consider everything
|
||||||
// determined)
|
// near the minimum or maximum 0.0 or 1.0, respectively
|
||||||
#define LIGHTSENSOR_LOW_BOUND 5
|
#define LIGHTSENSOR_LOW_BOUND 0.005f
|
||||||
#define LIGHTSENSOR_HIGH_BOUND ((int)(0.85 * LIGHTSENSOR_MAX))
|
#define LIGHTSENSOR_HIGH_BOUND 0.9f
|
||||||
|
|
||||||
extern int LightSensor_RelativeBrightness;
|
extern float LightSensor_RelativeBrightness;
|
||||||
|
|
||||||
void LightSensor_Init(void);
|
void LightSensor_Init(void);
|
||||||
void LightSensor_Poll(void);
|
void LightSensor_Poll(void);
|
||||||
|
|
67
src/main.c
67
src/main.c
|
@ -5,37 +5,74 @@ 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(LightSensor_RelativeBrightness == 0 && !powered_down)
|
if(LED_FrameFlag)
|
||||||
{
|
{
|
||||||
LED_Suspend();
|
memset(LED_PixelData, 0, sizeof(LED_PixelData));
|
||||||
NVS_Save(true);
|
|
||||||
powered_down = true;
|
for(unsigned int i = 0; i < LED_COUNT; i++)
|
||||||
}
|
{
|
||||||
if(powered_down && LightSensor_RelativeBrightness > 0)
|
uint8_t brightness = 0;
|
||||||
{
|
if((counter / blink_period) <= (i % group_size))
|
||||||
powered_down = false;
|
{
|
||||||
NVS_Save(true);
|
if(counter / (blink_period / 2) % 2)
|
||||||
LED_WakeUp();
|
{
|
||||||
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
#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
204
src/nvs.c
|
@ -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*)¤t_block->marker, 0x0000))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NVS_FlashData = next_block;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
26
src/nvs.h
26
src/nvs.h
|
@ -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);
|
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,4 @@ void SystemInit(void)
|
||||||
// Disable all interrupts
|
// Disable all interrupts
|
||||||
RCC->CIR = 0x00000000;
|
RCC->CIR = 0x00000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue