焚き火の炎は予測不能な変化があって飽きることなく見ていられますね。予測不能でも完全な乱数とは違って、滑らかな変化の中に期待を裏切る変化が混じる点が飽きさせないのだろうと思います。8×8シリアルLEDが比較的安価に手に入るようになったのでMicroPythonで焚き火風な卓上ランプを作ることにしました。

使用するマイコンボードはRaspberry Pi Picoと同じRP2040マイコンを使っているWaveshare RP2040-zeroです。後継のRP2350マイコンが出てきたので探せばさらに安価に入手できるようになりました。
パーリンノイズは、コンピュータグラフィックスで炎や煙や雲を表現するのによく使われていると、wikipediaに書かれていて、MicroPython用のライブラリ “perlin.py” が公開されています。このライブラリをダウンロードしてマイコンのメモリに保存します。
シリアルLED用のライブラリ “neopixel.py”もダウンロードしてマイコンのメモリに保存します。
作成したプログラムではパーリンノイズで8×8のLEDの場所の温度が2次元的に変化するのですが、 (1)温度が上がるにつれて赤→黄→白に変化する256色のカラーパレット(heat_color)と、(2)8×8 LEDの中心ほど熱くなる(led_base)、(3)時間的にも明るさが変化する(intensity)という細工を入れています。
なお、今回使用した8×8 LEDは左上が0番で右上が7番、一行下が8番から15番というようにシンプルに配線されていて、X列Y行のLED番号は(Y x 8) + X で指定できます。LEDの信号ピンは29pinです。
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 |
import perlin from neopixel import Neopixel import time, random numpix = 64 led_pin = 29 led = Neopixel(numpix, 0, led_pin, "GRB") led.brightness(100) x,y,z = 0.5, 0.5, 0.7 octave = 3 perlin.reseed(time.ticks_ms()) intensity = [0.0]*100 led_heat = [0.0]*64 led_base = [10, 10, 10, 20, 20, 10, 10, 10, 10, 15, 20, 35, 35, 20, 15, 10, 10, 20, 40, 40, 40, 40, 20, 10, 15, 35, 40, 60, 60, 40, 35, 15, 15, 35, 40, 60, 60, 40, 35, 15, 10, 20, 40, 40, 40, 40, 20, 10, 10, 15, 20, 35, 35, 20, 15, 10, 10, 10, 10, 20, 20, 10, 10, 10,] def in_range(x): if x < 0: x = 0 elif x > 255: x = 255 return(x) def heatmap(n): #n 0-255 red = in_range(3 * n) green = in_range(3 * (n - 85)) blue = in_range(3 * (n - 170)) return((red, green, blue)) def heat_tuple(): palette = [] for i in range (256): palette.append(heatmap(i)) heat_color = tuple(palette) del palette return(heat_color) def generate_intensity(): #generate intensity coefficients between 0.6-1.0 for i in range (100): x = i intensity[i] = perlin.octave_perlin(x,y,z,octave) min_intensity = min(intensity) max_intensity = max(intensity) for i in range (100): intensity[i] = ((intensity[i] - min_intensity) / (max_intensity - min_intensity)) * 0.45 + 0.55 color = heat_tuple() generate_intensity() intensity_index = 0 while True: for y in range (8): for x in range (8): count = y * 8 + x heat_noise = perlin.octave_perlin(x,y,z,octave) led_heat[count] = heat_noise * (led_base[count] /100) minimum = min(led_heat) maximum = max(led_heat) for count in range (64): heat_index = int((led_heat[count] - minimum) * 200 * intensity[intensity_index] / (maximum - minimum)) led.set_pixel(count, color[heat_index]) led.show() perlin.reseed(time.ticks_ms()) intensity_index = intensity_index + 1 if intensity_index >=100: generate_intensity() intensity_index = 0 |