Repository navigation
Expand file tree
/
Copy pathCrowPanelRoundDisplay.cpp
More file actions
146 lines (132 loc) · 4.25 KB
/
Copy pathCrowPanelRoundDisplay.cpp
File metadata and controls
146 lines (132 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include "CrowPanelRoundDisplay.h"
#ifdef WITH_ROUNDLCD_DISPLAY
#include <Wire.h>
#include <math.h>
#define I2C_SDA 4
#define I2C_SCL 5
#define EXPANDER_PIN_TOUCH_RESET 3
#define EXPANDER_PIN_LCD_RESET 4
#define EXPANDER_PIN_BACKLIGHT 2
// Physical layout (see readme.md): 12 radial 5-LED strips ("spokes"), one per
// clock-hour position, index 0 at 12 o'clock going clockwise. Within a spoke,
// LED 0 (data-in) is outermost and LED 4 (data-out) is innermost -- deduced
// from readme.md ("the last LED being the innermost at the 11 o'clock
// position", plus the data-in/data-out chaining description).
#define LEDS_PER_SPOKE 5
#define OUTER_RADIUS 105
#define INNER_RADIUS 25
#define DOT_RADIUS 4
CrowPanelRoundDisplay::LGFX::LGFX() {
{
auto cfg = bus.config();
cfg.spi_host = SPI2_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 80000000;
cfg.freq_read = 20000000;
cfg.spi_3wire = true;
cfg.use_lock = true;
cfg.dma_channel = SPI_DMA_CH_AUTO;
cfg.pin_sclk = 6;
cfg.pin_mosi = 7;
cfg.pin_miso = -1;
cfg.pin_dc = 2;
bus.config(cfg);
panel.setBus(&bus);
}
{
auto cfg = panel.config();
cfg.pin_cs = 10;
cfg.pin_rst = -1; // reset is via the IO expander, not a direct GPIO
cfg.pin_busy = -1;
cfg.memory_width = 240;
cfg.memory_height = 240;
cfg.panel_width = 240;
cfg.panel_height = 240;
cfg.offset_x = 0;
cfg.offset_y = 0;
cfg.offset_rotation = 0;
cfg.dummy_read_pixel = 8;
cfg.dummy_read_bits = 1;
cfg.readable = false;
cfg.invert = true;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = false;
panel.config(cfg);
}
setPanel(&panel);
}
CrowPanelRoundDisplay::CrowPanelRoundDisplay(int numLeds_)
: numLeds(numLeds_)
{
pending = new uint32_t[numLeds];
lastShown = new uint32_t[numLeds];
for (int i=0; i<numLeds; i++) {
pending[i] = 0;
// Force the first show() to actually draw every dot, even the ones that
// are still black -- lastShown must not start out equal to pending.
lastShown[i] = 0xffffffff;
}
}
void CrowPanelRoundDisplay::begin() {
Wire.begin(I2C_SDA, I2C_SCL);
expander.begin();
expander.setPin(EXPANDER_PIN_TOUCH_RESET, true);
expander.setPin(EXPANDER_PIN_LCD_RESET, true);
tft.init();
tft.initDMA();
tft.startWrite();
tft.fillScreen(TFT_BLACK);
tft.endWrite();
// Backlight on last, mirroring the factory firmware's own sequencing, so
// nothing garbled is ever visible on power-up.
expander.setPin(EXPANDER_PIN_BACKLIGHT, true);
}
void CrowPanelRoundDisplay::clear() {
tft.startWrite();
tft.fillScreen(TFT_BLACK);
tft.endWrite();
for (int i=0; i<numLeds; i++) {
pending[i] = 0;
lastShown[i] = 0;
}
}
void CrowPanelRoundDisplay::setPixel(int index, uint32_t rgb) {
pending[index] = rgb;
}
uint32_t CrowPanelRoundDisplay::getPixel(int index) {
return pending[index];
}
// color565() truncates each channel to 5/6/5 bits, so any raw value below
// ~8 rounds straight down to zero -- unlike a WS2812's 8-bit PWM, which
// stays visibly nonzero much further down. Gamma-correct so dim colors
// (e.g. the tail of a temporal-status comet trail) fade out instead of
// vanishing well before the NeoPixel backend would.
static uint8_t gammaCorrect(uint8_t v) {
return (uint8_t)(powf(v / 255.0f, 1.0f / 2.2f) * 255.0f + 0.5f);
}
void CrowPanelRoundDisplay::show() {
tft.startWrite();
for (int i=0; i<numLeds; i++) {
if (pending[i] != lastShown[i]) {
int x, y;
dotPosition(i, x, y);
uint8_t r = gammaCorrect((pending[i] >> 16) & 0xff);
uint8_t g = gammaCorrect((pending[i] >> 8) & 0xff);
uint8_t b = gammaCorrect(pending[i] & 0xff);
tft.fillCircle(x, y, DOT_RADIUS, tft.color565(r, g, b));
lastShown[i] = pending[i];
}
}
tft.endWrite();
}
void CrowPanelRoundDisplay::dotPosition(int index, int &x, int &y) {
int spoke = index / LEDS_PER_SPOKE;
int posInSpoke = index % LEDS_PER_SPOKE;
int numSpokes = numLeds / LEDS_PER_SPOKE;
float angle = 2*PI*spoke/numSpokes; // spoke 0 = 12 o'clock, increasing clockwise
float radius = OUTER_RADIUS - posInSpoke * ((OUTER_RADIUS - INNER_RADIUS) / (float)(LEDS_PER_SPOKE - 1));
x = 120 + (int)(radius*sin(angle));
y = 120 - (int)(radius*cos(angle));
}
#endif // WITH_ROUNDLCD_DISPLAY