Initial commit

This commit is contained in:
jaseg 2018-05-12 12:43:39 +02:00
commit 4a98183253
29 changed files with 667 additions and 0 deletions

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "themes/after-dark"]
path = themes/after-dark
url = https://github.com/comfusion/after-dark
[submodule "themes/ananke"]
path = themes/ananke
url = https://github.com/budparr/gohugo-theme-ananke.git

69
Untitled.ipynb Normal file

File diff suppressed because one or more lines are too long

6
archetypes/default.md Normal file
View file

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

95
cccie31_locus.csv Normal file
View file

@ -0,0 +1,95 @@
360, 0.175560, 0.005294, 0.819146
365, 0.175161, 0.005256, 0.819582
370, 0.174821, 0.005221, 0.819959
375, 0.174510, 0.005182, 0.820309
380, 0.174112, 0.004964, 0.820924
385, 0.174008, 0.004981, 0.821012
390, 0.173801, 0.004915, 0.821284
395, 0.173560, 0.004923, 0.821517
400, 0.173337, 0.004797, 0.821866
405, 0.173021, 0.004775, 0.822204
410, 0.172577, 0.004799, 0.822624
415, 0.172087, 0.004833, 0.823081
420, 0.171407, 0.005102, 0.823490
425, 0.170301, 0.005789, 0.823911
430, 0.168878, 0.006900, 0.824222
435, 0.166895, 0.008556, 0.824549
440, 0.164412, 0.010858, 0.824731
445, 0.161105, 0.013793, 0.825102
450, 0.156641, 0.017705, 0.825654
455, 0.150985, 0.022740, 0.826274
460, 0.143960, 0.029703, 0.826337
465, 0.135503, 0.039879, 0.824618
470, 0.124118, 0.057803, 0.818079
475, 0.109594, 0.086843, 0.803563
480, 0.091294, 0.132702, 0.776004
485, 0.068706, 0.200723, 0.730571
490, 0.045391, 0.294976, 0.659633
495, 0.023460, 0.412703, 0.563837
500, 0.008168, 0.538423, 0.453409
505, 0.003859, 0.654823, 0.341318
510, 0.013870, 0.750186, 0.235943
515, 0.038852, 0.812016, 0.149132
520, 0.074302, 0.833803, 0.091894
525, 0.114161, 0.826207, 0.059632
530, 0.154722, 0.805864, 0.039414
535, 0.192876, 0.781629, 0.025495
540, 0.229620, 0.754329, 0.016051
545, 0.265775, 0.724324, 0.009901
550, 0.301604, 0.692308, 0.006088
555, 0.337363, 0.658848, 0.003788
560, 0.373102, 0.624451, 0.002448
565, 0.408736, 0.589607, 0.001657
570, 0.444062, 0.554714, 0.001224
575, 0.478775, 0.520202, 0.001023
580, 0.512486, 0.486591, 0.000923
585, 0.544787, 0.454434, 0.000779
590, 0.575151, 0.424232, 0.000616
595, 0.602933, 0.396497, 0.000571
600, 0.627037, 0.372491, 0.000472
605, 0.648233, 0.351395, 0.000372
610, 0.665764, 0.334011, 0.000226
615, 0.680079, 0.319747, 0.000174
620, 0.691504, 0.308342, 0.000154
625, 0.700606, 0.299301, 0.000093
630, 0.707918, 0.292027, 0.000055
635, 0.714032, 0.285929, 0.000040
640, 0.719033, 0.280935, 0.000032
645, 0.723032, 0.276948, 0.000020
650, 0.725992, 0.274008, 0.000000
655, 0.728272, 0.271728, 0.000000
660, 0.729969, 0.270031, 0.000000
665, 0.731089, 0.268911, 0.000000
670, 0.731993, 0.268007, 0.000000
675, 0.732719, 0.267281, 0.000000
680, 0.733417, 0.266583, 0.000000
685, 0.734047, 0.265953, 0.000000
690, 0.734390, 0.265610, 0.000000
695, 0.734592, 0.265408, 0.000000
700, 0.734690, 0.265310, 0.000000
705, 0.734690, 0.265310, 0.000000
710, 0.734690, 0.265310, 0.000000
715, 0.734548, 0.265452, 0.000000
720, 0.734690, 0.265310, 0.000000
725, 0.734690, 0.265310, 0.000000
730, 0.734690, 0.265310, 0.000000
735, 0.734690, 0.265310, 0.000000
740, 0.734690, 0.265310, 0.000000
745, 0.734690, 0.265310, 0.000000
750, 0.734690, 0.265310, 0.000000
755, 0.734690, 0.265310, 0.000000
760, 0.734690, 0.265310, 0.000000
765, 0.734690, 0.265310, 0.000000
770, 0.734690, 0.265310, 0.000000
775, 0.734690, 0.265310, 0.000000
780, 0.734690, 0.265310, 0.000000
785, 0.734690, 0.265310, 0.000000
790, 0.734690, 0.265310, 0.000000
795, 0.734690, 0.265310, 0.000000
800, 0.734690, 0.265310, 0.000000
805, 0.734690, 0.265310, 0.000000
810, 0.734690, 0.265310, 0.000000
815, 0.734690, 0.265310, 0.000000
820, 0.734690, 0.265310, 0.000000
825, 0.734690, 0.265310, 0.000000
830, 0.734690, 0.265310, 0.000000
1 360 0.175560 0.005294 0.819146
2 365 0.175161 0.005256 0.819582
3 370 0.174821 0.005221 0.819959
4 375 0.174510 0.005182 0.820309
5 380 0.174112 0.004964 0.820924
6 385 0.174008 0.004981 0.821012
7 390 0.173801 0.004915 0.821284
8 395 0.173560 0.004923 0.821517
9 400 0.173337 0.004797 0.821866
10 405 0.173021 0.004775 0.822204
11 410 0.172577 0.004799 0.822624
12 415 0.172087 0.004833 0.823081
13 420 0.171407 0.005102 0.823490
14 425 0.170301 0.005789 0.823911
15 430 0.168878 0.006900 0.824222
16 435 0.166895 0.008556 0.824549
17 440 0.164412 0.010858 0.824731
18 445 0.161105 0.013793 0.825102
19 450 0.156641 0.017705 0.825654
20 455 0.150985 0.022740 0.826274
21 460 0.143960 0.029703 0.826337
22 465 0.135503 0.039879 0.824618
23 470 0.124118 0.057803 0.818079
24 475 0.109594 0.086843 0.803563
25 480 0.091294 0.132702 0.776004
26 485 0.068706 0.200723 0.730571
27 490 0.045391 0.294976 0.659633
28 495 0.023460 0.412703 0.563837
29 500 0.008168 0.538423 0.453409
30 505 0.003859 0.654823 0.341318
31 510 0.013870 0.750186 0.235943
32 515 0.038852 0.812016 0.149132
33 520 0.074302 0.833803 0.091894
34 525 0.114161 0.826207 0.059632
35 530 0.154722 0.805864 0.039414
36 535 0.192876 0.781629 0.025495
37 540 0.229620 0.754329 0.016051
38 545 0.265775 0.724324 0.009901
39 550 0.301604 0.692308 0.006088
40 555 0.337363 0.658848 0.003788
41 560 0.373102 0.624451 0.002448
42 565 0.408736 0.589607 0.001657
43 570 0.444062 0.554714 0.001224
44 575 0.478775 0.520202 0.001023
45 580 0.512486 0.486591 0.000923
46 585 0.544787 0.454434 0.000779
47 590 0.575151 0.424232 0.000616
48 595 0.602933 0.396497 0.000571
49 600 0.627037 0.372491 0.000472
50 605 0.648233 0.351395 0.000372
51 610 0.665764 0.334011 0.000226
52 615 0.680079 0.319747 0.000174
53 620 0.691504 0.308342 0.000154
54 625 0.700606 0.299301 0.000093
55 630 0.707918 0.292027 0.000055
56 635 0.714032 0.285929 0.000040
57 640 0.719033 0.280935 0.000032
58 645 0.723032 0.276948 0.000020
59 650 0.725992 0.274008 0.000000
60 655 0.728272 0.271728 0.000000
61 660 0.729969 0.270031 0.000000
62 665 0.731089 0.268911 0.000000
63 670 0.731993 0.268007 0.000000
64 675 0.732719 0.267281 0.000000
65 680 0.733417 0.266583 0.000000
66 685 0.734047 0.265953 0.000000
67 690 0.734390 0.265610 0.000000
68 695 0.734592 0.265408 0.000000
69 700 0.734690 0.265310 0.000000
70 705 0.734690 0.265310 0.000000
71 710 0.734690 0.265310 0.000000
72 715 0.734548 0.265452 0.000000
73 720 0.734690 0.265310 0.000000
74 725 0.734690 0.265310 0.000000
75 730 0.734690 0.265310 0.000000
76 735 0.734690 0.265310 0.000000
77 740 0.734690 0.265310 0.000000
78 745 0.734690 0.265310 0.000000
79 750 0.734690 0.265310 0.000000
80 755 0.734690 0.265310 0.000000
81 760 0.734690 0.265310 0.000000
82 765 0.734690 0.265310 0.000000
83 770 0.734690 0.265310 0.000000
84 775 0.734690 0.265310 0.000000
85 780 0.734690 0.265310 0.000000
86 785 0.734690 0.265310 0.000000
87 790 0.734690 0.265310 0.000000
88 795 0.734690 0.265310 0.000000
89 800 0.734690 0.265310 0.000000
90 805 0.734690 0.265310 0.000000
91 810 0.734690 0.265310 0.000000
92 815 0.734690 0.265310 0.000000
93 820 0.734690 0.265310 0.000000
94 825 0.734690 0.265310 0.000000
95 830 0.734690 0.265310 0.000000

19
cccie31_locus.svg Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="200mm"
height="200mm"
viewBox="0 0 200 200"
id="svg2">
<g id="layer1">
<path id="path2991"
style="fill:none;stroke:#000000;stroke-width:0.1mm;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 0,0 L 35.112000 1.058800 35.032200 1.051200 34.964200 1.044200 34.902000 1.036400 34.822400 0.992800 34.801600 0.996200 34.760200 0.983000 34.712000 0.984600 34.667400 0.959400 34.604200 0.955000 34.515400 0.959800 34.417400 0.966600 34.281400 1.020400 34.060200 1.157800 33.775600 1.380000 33.379000 1.711200 32.882400 2.171600 32.221000 2.758600 31.328200 3.541000 30.197000 4.548000 28.792000 5.940600 27.100600 7.975800 24.823600 11.560600 21.918800 17.368600 18.258800 26.540400 13.741200 40.144600 9.078200 58.995200 4.692000 82.540600 1.633600 107.684600 0.771800 130.964600 2.774000 150.037200 7.770400 162.403200 14.860400 166.760600 22.832200 165.241400 30.944400 161.172800 38.575200 156.325800 45.924000 150.865800 53.155000 144.864800 60.320800 138.461600 67.472600 131.769600 74.620400 124.890200 81.747200 117.921400 88.812400 110.942800 95.755000 104.040400 102.497200 97.318200 108.957400 90.886800 115.030200 84.846400 120.586600 79.299400 125.407400 74.498200 129.646600 70.279000 133.152800 66.802200 136.015800 63.949400 138.300800 61.668400 140.121200 59.860200 141.583600 58.405400 142.806400 57.185800 143.806600 56.187000 144.606400 55.389600 145.198400 54.801600 145.654400 54.345600 145.993800 54.006200 146.217800 53.782200 146.398600 53.601400 146.543800 53.456200 146.683400 53.316600 146.809400 53.190600 146.878000 53.122000 146.918400 53.081600 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.909600 53.090400 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000 146.938000 53.062000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
cccie31_locus_edited.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

64
cccie31_locus_edited.svg Normal file
View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="200mm"
height="200mm"
viewBox="0 0 200 200"
id="svg2"
sodipodi:docname="cccie31_locus_edited.svg"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
inkscape:export-filename="/home/user/admin/site/jaseg.net/cccie31_locus_edited.png"
inkscape:export-xdpi="304.79999"
inkscape:export-ydpi="304.79999">
<metadata
id="metadata853">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs851" />
<sodipodi:namedview
pagecolor="#757575"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1394"
id="namedview849"
showgrid="false"
inkscape:zoom="1.2488333"
inkscape:cx="360.76879"
inkscape:cy="388.84643"
inkscape:window-x="0"
inkscape:window-y="46"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<g
id="layer1"
style="stroke:#ffffff;stroke-width:1.99999999;stroke-miterlimit:4;stroke-dasharray:none">
<path
id="path2991"
style="fill:none;stroke:#ffffff;stroke-width:1.99999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 0,0 35.112,1.0588 35.0322,1.0512 34.9642,1.0442 34.902,1.0364 34.8224,0.9928 34.8016,0.9962 34.7602,0.983 34.712,0.9846 34.6674,0.9594 34.6042,0.955 34.5154,0.9598 34.4174,0.9666 34.2814,1.0204 34.0602,1.1578 33.7756,1.38 c 0,0 -0.267304,0.2174127 -0.3966,0.3312 -0.169453,0.1491275 -0.32941,0.3087398 -0.4966,0.4604 -0.218329,0.1980491 -0.44032,0.3920267 -0.6614,0.587 -0.296779,0.2617334 -0.596218,0.5204434 -0.8928,0.7824 -0.378371,0.3341961 -0.763262,0.6613518 -1.1312,1.007 -0.480602,0.4514862 -0.959583,0.906368 -1.405,1.3926 -0.59584,0.6504392 -1.171969,1.3222562 -1.6914,2.0352 -0.833592,1.1441462 -1.579982,2.352683 -2.277,3.5848 -1.065823,1.884053 -2.018746,3.83302 -2.9048,5.808 -1.347399,3.003298 -2.530815,6.079839 -3.66,9.1718 -1.639123,4.488286 -3.18996,9.014122 -4.5176,13.6042 -1.798515,6.218045 -3.292537,12.524418 -4.663,18.8506 -1.6902833,7.8025 -3.1725772,15.654697 -4.3862,23.5454 -1.2834927,8.344981 -2.3919081,16.72724 -3.0584,25.144 -0.6129876,7.74108 -1.12418942,15.51912 -0.8618,23.28 0.21600103,6.38882 0.456064,12.86993 2.0022,19.0726 1.0752855,4.31375 2.1260697,8.97102 4.9964,12.366 1.7909835,2.11834 4.361518,3.85701 7.09,4.3574 2.660714,0.48796 5.409703,-0.65135 7.9718,-1.5192 2.865196,-0.97052 5.480049,-2.57761 8.1122,-4.0686 2.621924,-1.48519 5.147938,-3.13947 7.6308,-4.847 2.51447,-1.72927 4.948959,-3.57487 7.3488,-5.46 2.463182,-1.93488 4.857621,-3.95696 7.231,-6.001 2.427203,-2.0904 4.80184,-4.24154 7.1658,-6.4032 2.409363,-2.20318 4.783617,-4.44462 7.1518,-6.692 2.398672,-2.27632 4.774303,-4.57685 7.1478,-6.8794 2.384784,-2.3135 4.757064,-4.63988 7.1268,-6.9688 2.360924,-2.32026 4.713931,-4.64856 7.0652,-6.9786 2.31795,-2.29702 4.630032,-4.59996 6.9426,-6.9024 2.248988,-2.23914 4.49396,-4.482308 6.7422,-6.7222 2.1526,-2.144606 4.30633,-4.288075 6.4602,-6.4314 2.02382,-2.013913 4.05037,-4.025084 6.0728,-6.0404 1.85382,-1.847306 3.70317,-3.699103 5.5564,-5.547 1.60598,-1.601356 3.2136,-3.201063 4.8208,-4.8012 1.41284,-1.406631 2.82484,-2.814105 4.2392,-4.2192 1.16766,-1.160011 2.33887,-2.31645 3.5062,-3.4768 0.95549,-0.949775 1.9091,-1.901433 2.863,-2.8528 0.76201,-0.759987 1.52239,-1.521614 2.285,-2.281 0.60605,-0.603487 1.21383,-1.205237 1.8204,-1.8082 0.48765,-0.484749 0.97525,-0.969551 1.4624,-1.4548 0.40786,-0.406269 0.81532,-0.812942 1.2228,-1.2196 0.3335,-0.332832 0.66667,-0.666 1.0002,-0.9988 0.26649,-0.265907 0.53295,-0.531852 0.7998,-0.7974 0.19715,-0.196186 0.395,-0.391666 0.592,-0.588 0.15226,-0.151742 0.304,-0.304 0.456,-0.456 0.11313,-0.113133 0.22627,-0.226267 0.3394,-0.3394 0.0747,-0.07467 0.14933,-0.149333 0.224,-0.224 0.0603,-0.06027 0.12053,-0.120533 0.1808,-0.1808 0.0484,-0.0484 0.0968,-0.0968 0.1452,-0.1452 0.0465,-0.04653 0.0931,-0.09307 0.1396,-0.1396 0.042,-0.042 0.084,-0.084 0.126,-0.126 0.0229,-0.02287 0.0457,-0.04573 0.0686,-0.0686 0.0135,-0.01347 0.0269,-0.02693 0.0404,-0.0404 0.007,-0.0065 0.0196,-0.0196 0.0196,-0.0196 v 0 0 l -0.0284,0.0284 0.0284,-0.0284 v 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccccsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5 KiB

BIN
colorsys_led_mapping.blend Normal file

Binary file not shown.

4
config.toml Normal file
View file

@ -0,0 +1,4 @@
baseURL = "https://jaseg.net/"
languageCode = "en-us"
title = "jaseg.net"
theme = "ananke"

View file

@ -0,0 +1,304 @@
---
title: "Led Characterization"
date: 2018-05-02T11:18:38+02:00
draft: true
---
Preface
-------
Recently, I have been working on a `small driver`_ for ambient lighting using 12V LED strips like you can get
inexpensively from China. I wanted to be able to just throw one of these somewhere, stick down some LED tape, hook it up
to a small transformer and be able to control it through Wifi. When I was writing the firmware, I noticed that when
fading between different colors, the colors look *all wrong*! This observation led me down a rabbit hole of color
perception and LED peculiarities.
The idea of the LED driver was that it can be used either with up to eight single-color LED tapes or, much more
interesting, with up to two RGB or RGBW (red-green-blue-white) LED tapes. For ambient lighting high color resolution was
really important so you could dim it down a lot without flickering. I ended up using the same driver stage I used in the
`multichannel LED driver`_ project for its great color resolution and low hardware requirements.
.. figure:: https://upload.wikimedia.org/wikipedia/commons/d/d6/RGB_color_cube.svg
:width: 100%
:alt: An illustration of the RGB color cube
An illustration of the RGB color cube. `Picture by Maklaan
<https://commons.wikimedia.org/wiki/File:RGB_color_cube.svg>`_ (`CC BY-SA 3.0`_), from Wikimedia Commons.
To make setting colors over Wifi more intuitive I implemented support for HSV colors. RGB is fine for communication
between computers, but I think HSV is easier to work with when manually inputting colors from the command line. RGB is
close to how most monitors, cameras and the human visual apparatus work on a very low level but doesn't match
higher-level human color perception very well. When we describe a color we tend to think in terms of "hue" or
"brightness", and computing a measure of those from RGB values is not easy.
Colors and Color Spaces
-----------------------
`Color spaces`_ are a mathematical abstraction of the concept of color. When we say "RGB", most of the time we actually
mean `sRGB`_, a standardized notion of how to map three numbers labelled "red", "green" and "blue" onto a perceived
color. `HSV`_ is an early attempt to more closely align these numbers with our perception. After HSV, a number of other
*perceptual* color spaces such as `XYZ (CIE 1931)`_ and `CIE Lab/LCh`_ were born, further improving this alignment. In
this mathematical model, mapping a color from one color space into another color space is just a coordinate
transformation.
.. figure:: https://upload.wikimedia.org/wikipedia/commons/4/4e/HSV_color_solid_cylinder.png
:width: 50%
:align: center
:alt: An illustration of the HSV color space as a cylinder
An illustration of the HSV color space as a cylinder. `Picture by SharkD
<https://commons.wikimedia.org/wiki/File:HSV_color_solid_cylinder.png>`_ (`CC BY-SA 3.0`_), from Wikimedia Commons.
The wrong colors I got when fading between colors were caused by this coordinate transformation being askew. Thinking
over the problem, there are several sources for imperfections:
* The LED driver may not be entirely linear. For most modulations such as PWM the brightness will be linear starting
from a certain value, but there is probably an offset caused by imperfect edges of the LED current. This offset can be
compensated with software calibration. I built a calibration setup for driver linearity in the `multichannel LED
driver`_ project.
.. FIXME picture with ringing on edges
* The red, green and blue channels of the LEDs used on the LED tape are not matched. This skews the RGB color space.
In practice, the blue channel of my RGB tape to me *looks* much brighter than the red channel.
* The precise colors of the red, green and blue channels of the LEDs are unknown. Though the red channel *looks* red, it
may be of a slightly different hue compared to the reference red used in `sRGB`_ which would also skew the RGB color
space.
These last two errors are tricky to compensate. What I needed for that was basically a model of the *perceived* colors
of the LED tape's color channels. A way of doing his is to record the spectra of all color channels and then evaluate
their respective XYZ coordinates. If all three channels are measured in one go with the same setup the relative
magnitudes of the channels in XYZ will be accurate.
To map any color to the LEDs, the color's XYZ coordinates simply have to be mapped onto the linear coordinate system
produced by these three points within XYZ. LEDs are extremely linear in their luminous flux vs. current characteristic
so this model will be adequate. The spectral integrals mapping the channels' measured responses to XYZ need only be
calculated once and their results can be used as scaling factors thereafter.
.. FIXME: Add led/current graph here
Measuring the spectrum
----------------------
In order to compensate for the cheap LED tape's non-ideal performance I had to measure the LED's red, green and blue
channels' spectra. The obvious thing would be to go out and buy a `spectrograph`_, or ask someone to borrow theirs. The
former is kind of expensive, and I did not want to wait two weeks for the thing to arrive. The latter I could probably
not do every time I got new LED tape. Thus the only choice was to build my own.
Luckily, building your own spectrometer is really easy. The first thing you need is something that splits incident light
into its constituent wavelengths. In professional devices this is called the *`monochromator`_*, since it allows extraction
of small color bands from the spectrum. The second thing is some sort of optics that project the incident light onto a
screen behind the monochromator. In professional devices lenses or curved mirrors are used. In a simple homebrew job a
pinhole as you would use in a `camera obscura`_ does a remarkably nice job.
For the monochromator component several things could be used. A prism would work, but I did not have any. The
alternative is a `diffraction grating`_. Professional gratings are quite specialized pieces of equipment and thus
rather expensive. Luckily, there is a common household item that works almost as well: A regular CD or DVD. The
microscopic grooves that are used to record data in a CD or DVD work the same as the grooves in a professional
diffraction grating.
Household spectra
-----------------
From this starting point, a few seconds on my favorite search engine yielded an `article by two researchers from the
National Science Museum in Tokyo`_ providing a nice blueprint for a simple cardboard-and-DVD construction for use in
classrooms. I replicated their device using a DVD and it worked beautifully. Daylight and several types of small LEDs I
had around did show the expected spectra. Small red, yellow, green, and blue LEDs showed narrow spectra, daylight one
continuous broad one, and white LEDs a continuous broad one with a distinct bright spot in the blue part. The
single-color LED spectra are quite narrow since they are determined by the LED's semiconductor's band gap, which is
specific to the semiconductor used and is quite precise. White LEDs are in fact a blue LED chip covered with a so-called
*phosphor*. This phosphor is not elementary phosphorus but an anorganic compound that absorbs the LED chip's blue light
and re-emits a broader spectrum of more yellow-ish wavelengths instead. The final LED spectrum is a superposition of
both spectra, with some of the original blue light leaking through the phosphor mixing with the broadband yellow
spectrum of the phosphor.
.. FIXME: Cardboard spectrograph pictures
Now that I had a spectrograph, I needed a somewhat predictable way of measuring the spectrum it gave me.
Measuring a spectrum
--------------------
Pointing a camera at the spectrograph would be the obvious thing to do. This produces pretty images but has one critical
flaw: I wanted to acquire quantitative measurements of brightness across the spectrum. Since I don't have a precise
technical datasheet specifying the spectral response of any of my cameras I can't compare the absolute brightness of
different colors on their pictures. Some other sensor was needed.
.. FIXME: Spectrum picture
Measuring light intensity
~~~~~~~~~~~~~~~~~~~~~~~~~
Looking around my lab, I found a bag of `SFH2701`_ visible-light photodiodes. Their
datasheet includes their spectral response so I can compensate for that, allowing precise-ish absolute intensity
measurements. Just like LEDs, photodiodes are extremely linear across several orders of magnitude. The datasheet of the
classic `BPW34`_ photodiode shows that this photodiode's light current is exactly proportional to illuminance over at
least three orders of magnitude. The `SFH2701`_ datasheet does not include a similar graph but its performance will be
similar. The `SFH2701`_ photodiodes I had at hand were perfect for the job compared to the vintage `BPW34`_ since their
active sensing area is really small (0.6mm by 0.6mm) compared to the BPW34 (a whopping 3mm by 3mm). If I were to use a
`BPW34`_ I would have to insert some small apterture in front of it so it does not catch too broad a part of the
spectrum at once. The `SFH2701`_ is small enough that if I just point it at the projected spectrum directly I will
already get only a small part of the spectrum inside its 0.6mm active area.
To convert the photodiode's tiny photocurrent into a measurable voltage I built another copy of the `transimpedance
amplifier`_ circuit I already used in the `multichannel LED driver`_. A `transimpedance amplifier`_ is an
amplifiert that produces a large voltage from a small current. The weird name comes from the fact that it works kind of
like an amplified resistor (which can be generalized as an *impedance* electrically). Apply a current to a resistor and
you get a voltage. A transimpedance amplifiert does the same with the difference that its input always stays at 0V,
making it look like an ideal current sink to the connected current source.
Transimpedance amplifiers are common in optoelectronics to convert small photocurrents to voltages. In this instance I
built a very simple circuit with a dampened transimpedance amplifier stage followed by a simple RC filter for noise
rejection and a regular non-inverting amplifier using another op-amp from the same chip to further boost the filtered
transimpedance amplifier output. I put all the passives setting amplifier response (the gain-setting resistors and the
filter resistor and capacitors) on a small removable adapter so I could easily change them if necessary. I put a small
trimpot on the virtual ground both amplifers use as a reference so I could trim that if necessary.
.. FIXME: Add transimp amp schematic and build pics
Given a way to measure intensity what remains missing is a way to scan a single photodiode across the spectrum.
Scanning the projection
~~~~~~~~~~~~~~~~~~~~~~~
A cheap linear stage can be found in any old CD or DVD drive. These drives use a small linear stage based on a
stepper-driven screw to move the laser unit radially. Removing the laser unit and connecting a leftover stepper driver
module I was left with a small linear stage with about 45 steps per cm without microstepping enabled. The driver I used
was an `A4988`_ module that required at least 8V motor drive voltage. I used a small micro USB-input boost converter
module to generate a stable 10V supply for the motor driver, with the USB's 5V rail used as a logic supply for the motor
driver.
.. FIXME: Add picture of photodiode stage here
The `SFH2701`_ can easily be mounted to the linear stage using a small SMD breakout board glued in place with thin wires
connecting it to the transimpedance amplifier. The DVD drive linear stage is not very strong so it is important that
this wire does not put too much strain on it.
Above the photodiode, I mounted a small piece of paper on the linear stage to be used as a projection screen to align
the linear stage in front of the spectrometer viewing window. A line on the screen paper points to the photodiode die in
parallel to the linear stage allowing precise alignment.
The whole unit with photodiode preamplifier, linear stage, photodiode and stepper motor driver finally looks like this:
.. FIXME: pics of linear stage unit electronics
The projection of the spectrum can be adjusted by moving the light source relative to the entry slot and by moving
around the grating DVD.
The capture process
~~~~~~~~~~~~~~~~~~~
To capture a spectrum, first the light source has to be mounted near the spectrograph's entry slot. The LED tape I
tested I just taped face-down directly into it. Next, the grating DVD has to be adjusted to make sure the spectrum
covers a sensible part of the photodiode's path. Mostly, this boils down to adjusting the photodiode distance and height
to match the vertical extent and wiggling the grating DVD to adjust the projection's horizontal position.
After the optics are set-up, the photodiode preamplifier has to be adjusted. In my experiments, most LED tape at 5GΩ
required a high-ish amplification. The goal in this step is to maximize the peak response of the preamp to be just
shy of its VCC rail to make best use of its dynamic range. To adjust the pre-amp, I took several very coarsely-spaced
measurements to give me an estimate of the peak while I did not yet know its precise location.
Since stray daylight totally swamped out the weak projection of the LED's spectrum I shielded the entire setup with a
small box made of black cardboard and two black t-shirts on top. This shielding proved adequate for all my measurements
but I had to be careful not to accidentially move the DVD that was stuck into the spectrograph with the shielding
t-shirts.
For capturing a single spectrum I wrote a small python script that will automatically move the stepper in adjustable
intervals and take two measurements at each point, one with the LED tape off that can be used for offset calibration and
one with the LED tape on. All measurements are stored in a sqlite database that can then be accesssed from other
scripts.
I built a small script that shows the progress of the current run and an jupyter notebook for data analysis. The jupyter
notebook is capable of live-updating a graph with the in-progress spectrum's data. This was quite useful as a sanity
check for when I made some mistake easy to spot in the resulting data.
After one color channel is captured, the LED tape has to be manually set to the next color and the next measurement can
begin.
Data analysis
~~~~~~~~~~~~~
Data analysis consists of three major steps: Offset- and stray light removal, wavelength and amplitude calibration and
color space mapping.
Offset removal
**************
The first task is to remove the offset caused by dark current as well as stray light of the LED's bright primary
reflection on the DVD. The LED is very bright and only a small part of its light gets reflected by the grating towards
the photodiode screen. The remaining part of the light is reflected onto the table in front of the DVD spectrograph.
Though I covered all of this with black cardboard, some of that light ultimately gets reflected onto the photodiode.
This causes a large offset, in particular in the blue part of the spectrum since in this part the photodiode is closest
to the spectrograph's opening.
The composite offset can be approximated with a second-order polynomial that is fitted to all the data outside of the
main peak's area. Since at this point the wavelength of each data point is still unknown this is done with a rough first
estimate of the three colors' peaks' locations and widths.
Wavelength- and amplitude calibration
*************************************
The photodiode's response is strongly wavelength-dependent. In particular in the blue band, the photodiode's sensitivity
gets very poor down to about 20% at the edge to ultraviolet. This effect is strong enough to move the apparent location
of the blue peak towards red.
The problem is that in order to remove this non-linearity, we would already have to know the wavelength of the measured
light. Since I don't, I settled for a two-step process. First, a coarse wavelength calibration is done relative to the
red peak and the short-wavelength edge of the blue peak. The photodiode measurements are then sensitivity-corrected
using this coarse measurement. Then all three channel peaks are measured in the resulting data and a fine wavelength
estimate is produced by a least-squares fit of a linear function. This fine estimate is then used for a second
sensitivity correction of all original measurements and the scale is changed from stepper motor step count to
wavelength in nanometers.
.. FIXME: calibration for brightness imbalance due to wedge-shaped projection of spectrum
Color space mapping
*******************
Finally, to achieve the objective of measuring the LED tape's channels' precise color coordinates the measured spetra
have to be matched against the color spaces' *color matching functions*. The color matching functions describe how
strong the color space's idealized *standard observer* would react to light at a particular wavelength. Going from a
measured spectrum to color coordinates XYZ works by integrating over the product of the measurement and each color
coordinate's color matching function.
The result are three color coordinates X, Y and Z for each channel R, G and B yielding nine coordinates in total. When
written as a matrix conversion between XYZ color space and LED-RGB color space is as simple as multiplying that matrix
(or its inverse) and a vector from one of the color spaces.
If you view the three channels' color coordinates as vectors in XYZ space, the set of colors that can be produced with
this LED tape is described by the `parallelepiped`_ spanned by the three channel vectors.
The last task is to decide on a scaling factor to map XYZ space to RGB space. Both are limited to values between 0.0 and
1.0. The LEDs cannot go below off or above fully on. For any LED tape there will be a set of colors that are outside
the range that this tape can produce.
A scaling factor can be used to increase the number of XYZ coordinates that can be mapped to RGB colors the tape *can*
produce by stretching the RGB parallelepiped along its major axis. Up to a point the number of possible colors (the
gamut) increases at expense of maximum brightness. When the parallelepiped is stretched far enought for all three
channel vectors to be outside the 1,1,1 XYZ-cube, maximum brightness continues to decrease but the gamut stays constant.
Firmware implementation
-----------------------
In the end, the above measurements yield two matrices: One for mapping XYZ to RGB, and one for mapping RGB to XYZ. I
chose the CIE 1931 XYZ color space as a basis for the firmware because it is most popular. Mapping a color coordinate in
one color space to the other is as simple as performing nine floating-point multiplications and six additions. Mapping
Lab or Lch to RGB is done by first mapping Lab/Lch to XYZ, then XYZ to RGB. Lab to XYZ is somewhat complex since it
requires a floating-point power for gamma correction, but any self-respecting libc will have one of those so this is
still no problem. Lch also requires floating-point sine and cosine functions, but these should still be no problem on
most hardware.
My implementation of these conversions in the ESP8266 firmware of my `Wifi LED driver`_ can be found `on Github`_.
.. _`on Github`: https://github.com/jaseg/esp_led_drv/blob/master/user/led_controller.c
.. _`Wifi LED driver`: {{<ref "posts/wifi-led-driver.rst">}}
.. _`small driver`: {{<ref "posts/wifi-led-driver.rst">}}
.. _`multichannel LED driver`: {{<ref "posts/multichannel-led-driver.rst">}}
.. _`sRGB`: https://en.wikipedia.org/wiki/SRGB
.. _`CC BY-SA 3.0`: https://creativecommons.org/licenses/by-sa/3.0
.. _`Color spaces`: https://en.wikipedia.org/wiki/Color_space
.. _`HSV`: https://en.wikipedia.org/wiki/HSL_and_HSV
.. _`CIE Lab/LCh`: https://en.wikipedia.org/wiki/Lab_color_space
.. _`XYZ (CIE 1931)`: https://en.wikipedia.org/wiki/CIE_1931_color_space
.. _`camera obscura`: https://en.wikipedia.org/wiki/Pinhole_camera
.. _`article by two researchers from the National Science Museum in Tokyo`: http://www.candac.ca/candacweb/sites/default/files/BuildaSpectroscope.pdf
.. _`spectrograph`: https://en.wikipedia.org/wiki/Ultraviolet%E2%80%93visible_spectroscopy
.. _`monochromator`: https://en.wikipedia.org/wiki/Monochromator
.. _`diffraction grating`: https://en.wikipedia.org/wiki/Diffraction_grating
.. _`SFH2701`: https://dammedia.osram.info/media/resource/hires/osram-dam-2495903/SFH%202701.pdf
.. _`BPW34`: http://www.vishay.com/docs/81521/bpw34.pdf
.. _`transimpedance amplifier`: https://en.wikipedia.org/wiki/Transimpedance_amplifier
.. _`A4988`: https://www.pololu.com/file/0J450/A4988.pdf
.. _`parallelepiped`: https://en.wikipedia.org/wiki/Parallelepiped

View file

@ -0,0 +1,6 @@
---
title: "Multichannel Led Driver"
date: 2018-05-02T11:31:14+02:00
draft: true
---

View file

@ -0,0 +1,6 @@
---
title: "Wifi Led Driver"
date: 2018-05-02T11:31:03+02:00
draft: true
---

View file

@ -0,0 +1,43 @@
---
title: "Zeus Hammer"
date: 2018-05-03T11:59:37+02:00
draft: true
---
In case you were having an inferiority complex because your friends' IBM Model M keyboards are so much louder than the
shitty rubber dome freebie you got with your pc... Here's the solution: Zeus Hammer, a simple typing cadence enhancer
for `PS/2`_ keyboards.
.. FIXME: add demo video
The connects to the keyboard's PS/2 clock line and briefly actuates a large solenoid on each key press. An interesting
fact about PS/2 is that the clock line is only active as long as either the host computer or the input device actually
want to send data. In case of a keyboard that's the case when a key is pressed or when the host changes the keyboard's
LED state, otherwise the clock line is silent. We ignore the LED activity for now as it's generally coupled to key
presses. By just triggering an NE555 configured as astable flipflop we can stretch each train of clock pulses to a
pulse a few tens of milliseconds long that is enough to actuate the solenoid.
.. image:: /images/zeus_hammer_schematic.jpg
Since PS/2 sends each key press and key release separately this circuit will pulse twice per keystroke. It would be
possible to ignore one of them but I figure the added noise just adds to the experience.
Built on a breadboard, the circuit looks like this.
.. image:: /images/zeus_hammer_breadboard.jpg
The completed system looks like this.
.. FIXME: add image of completed system
Since my solenoid did not have a tensioning spring I used a rubber band and some vinyl tape to make an adjustable
tensioner. The small orange USB hub serves as an end-stop because I had nothing else of the right shape. The sound and
resonance of the thing can be adjusted to taste by moving the end stop, adjusting the tensioning rubber and tuning the
excitation duration using the potentiometer. My particular solenoid was a bit slow so I added some pieces of circuit
board as shims between the plunger and the case to limit the plunger's travel inside the solenoid core. Here is another
video of the thing in action in which I tune and de-tune the mechanical resonance using the potentiometer.
.. FIXME: add video w/ tune/detune
.. _`PS/2`: https://en.wikipedia.org/wiki/PS/2_port

43
csv_to_svg_path.py Executable file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env python3
import textwrap
def svg_path_from_points(points, r):
points_joined = ' '.join(f'{x:.6f} {y:.6f}' for x, y in points)
return textwrap.dedent(f'''
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="{r*2}mm"
height="{r*2}mm"
viewBox="0 0 {r*2} {r*2}"
id="svg2">
<g id="layer1">
<path id="path2991"
style="fill:none;stroke:#000000;stroke-width:0.1mm;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 0,0 L {points_joined}" />
</g>
</svg>
''').strip()
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('locus_csv', help='CSV file containing locus coordinates. Format: lambda, x, y, z.')
parser.add_argument('-r', '--radius', type=float, default=100, help='Radius of plot area in mm')
args = parser.parse_args()
import csv, ast
points = []
with open(args.locus_csv, newline='') as f:
for row in csv.reader(f):
# use literal_eval to handle entries like "1.153E-5"
λ, x, y, z = (ast.literal_eval(e.strip()) for e in row)
points.append((x*args.radius*2, y*args.radius*2))
print(svg_path_from_points(points, args.radius))

BIN
foo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sRGB.mkv Normal file

Binary file not shown.

BIN
scale=1.mkv Normal file

Binary file not shown.

BIN
scale=2.5.mkv Normal file

Binary file not shown.

BIN
scale=5.mkv Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

1
themes/after-dark Submodule

@ -0,0 +1 @@
Subproject commit 7b4ddbf697db366c53fb3279aefa8bf36daad991

1
themes/ananke Submodule

@ -0,0 +1 @@
Subproject commit 41727a62e2afa9cdb1c828ddeafc2928d5566c3b