diff --git a/examples/drivers/neomatrix/assets/heart.png b/examples/drivers/neomatrix/assets/heart.png new file mode 100644 index 0000000000..9ec03139f0 Binary files /dev/null and b/examples/drivers/neomatrix/assets/heart.png differ diff --git a/examples/drivers/neomatrix/main.js b/examples/drivers/neomatrix/main.js new file mode 100644 index 0000000000..614720b0fb --- /dev/null +++ b/examples/drivers/neomatrix/main.js @@ -0,0 +1,30 @@ +import parseBMP from 'commodetto/parseBMP' +import Poco from 'commodetto/Poco' +import Resource from 'Resource' +import Timer from 'timer' + +const heart = parseBMP(new Resource('heart-alpha.bmp')) + +const render = new Poco(global.screen, { + pixels: 256 +}) + +function getTick (heartRate, fps) { + return heartRate * 6 / fps +} + +const FPS = 20 +const black = render.makeColor(0, 0, 0) + +let heartRate = 60 +let deg = 0 + +Timer.repeat(() => { + deg = (deg + getTick(heartRate, FPS)) % 360 + const strength = 255 * Math.sin(Math.PI * deg / 360) + const color = render.makeColor(strength, 0, 0) + render.begin() + render.fillRectangle(black, 0, 0, 16, 16) + render.drawGray(heart, color, 0, 0) + render.end() +}, 1000 / FPS) diff --git a/examples/drivers/neomatrix/manifest.json b/examples/drivers/neomatrix/manifest.json new file mode 100644 index 0000000000..1d0fb71b0a --- /dev/null +++ b/examples/drivers/neomatrix/manifest.json @@ -0,0 +1,22 @@ +{ + "include": [ + "$(MODDABLE)/examples/manifest_base.json", + "$(MODDABLE)/examples/manifest_commodetto.json", + "$(MODULES)/drivers/neomatrix/manifest.json" + ], + "config": { + "screen": "neomatrix-render", + "touch": "" + }, + "modules": { + "commodetto/parseBMP": "$(COMMODETTO)/commodettoParseBMP", + "commodetto/Bitmap": "$(COMMODETTO)/commodettoBitmap", + "commodetto/Poco": "$(COMMODETTO)/commodettoPoco", + "commodetto/*": "$(COMMODETTO)/commodettoPocoBlit", + "*": "./main" + }, + "resources": { + "*": "./assets/*" + }, + "preload": ["commodetto/*"] +} diff --git a/modules/drivers/neomatrix/manifest.json b/modules/drivers/neomatrix/manifest.json new file mode 100644 index 0000000000..89e98486a6 --- /dev/null +++ b/modules/drivers/neomatrix/manifest.json @@ -0,0 +1,33 @@ +{ + "config": { + "screen": "neomatrix-render", + "touch": "" + }, + "defines": { + "neomatrix": { + "pin": 21, + "width": 16, + "height": 16, + "brightness": 128 + } + }, + "modules": { + "*": [ + "$(MODULES)/drivers/neopixel/*", + "./neomatrix", + "./neomatrix-render" + ], + "commodetto/Bitmap": "$(COMMODETTO)/commodettoBitmap" + }, + "preload": ["commodetto/*", "neopixel", "neomatrix", "neomatrix-render"], + "platforms": { + "esp32": { + "modules": { + "*": "$(MODULES)/drivers/neopixel/esp32/*" + } + }, + "...": { + "error": "unsupported platform" + } + } +} diff --git a/modules/drivers/neomatrix/neomatrix-render.c b/modules/drivers/neomatrix/neomatrix-render.c new file mode 100644 index 0000000000..7e493dfc18 --- /dev/null +++ b/modules/drivers/neomatrix/neomatrix-render.c @@ -0,0 +1,34 @@ +#include "xsmc.h" +#include "mc.defines.h" + +#ifndef MODDEF_NEOMATRIX_PIN + #define MODDEF_NEOMATRIX_PIN (21) +#endif + +#ifndef MODDEF_NEOMATRIX_WIDTH + #define MODDEF_NEOMATRIX_WIDTH (16) +#endif + +#ifndef MODDEF_NEOMATRIX_HEIGHT + #define MODDEF_NEOMATRIX_HEIGHT (16) +#endif + +#ifndef MODDEF_NEOMATRIX_BRIGHTNESS + #define MODDEF_NEOMATRIX_BRIGHTNESS (32) +#endif + +void xs_NeoMatrix_get_configure_pin(xsMachine *the) { + xsmcSetInteger(xsResult, MODDEF_NEOMATRIX_PIN); +} + +void xs_NeoMatrix_get_configure_width(xsMachine *the) { + xsmcSetInteger(xsResult, MODDEF_NEOMATRIX_WIDTH); +} + +void xs_NeoMatrix_get_configure_height(xsMachine *the) { + xsmcSetInteger(xsResult, MODDEF_NEOMATRIX_HEIGHT); +} + +void xs_NeoMatrix_get_configure_brightness(xsMachine *the) { + xsmcSetInteger(xsResult, MODDEF_NEOMATRIX_BRIGHTNESS); +} diff --git a/modules/drivers/neomatrix/neomatrix-render.js b/modules/drivers/neomatrix/neomatrix-render.js new file mode 100644 index 0000000000..c568eafd9d --- /dev/null +++ b/modules/drivers/neomatrix/neomatrix-render.js @@ -0,0 +1,123 @@ +/* eslint-disable camelcase */ + +import Bitmap from 'commodetto/Bitmap' +import { NeoMatrix } from 'neomatrix' +import CONFIG from "mc/config"; + +export default class NeoMatrixRender { + #matrix + #x + #y + #w + #h + #idx + #pin + #width + #height + #pixelFormat + constructor (dictionary = {}) { + this.#width = dictionary.width ? dictionary.width : this.#getConfigureWidth() + this.#height = dictionary.height ? dictionary.height : this.#getConfigureHeight() + this.#pixelFormat = dictionary.pixelFormat + ? dictionary.pixelFormat + : Bitmap.RGB565LE + this.#pin = dictionary.pin ? dictionary.pin : this.#getConfigurePin() + this.#matrix = new NeoMatrix({ + width: this.#width, + height: this.#height, + pin: this.#pin, + order: 'GRB', + brightness: this.#getConfigureBrightness(), + rotation: CONFIG.rotation + }) + this.#idx = 0 + } + + begin (x, y, width, height) { + this.#x = x + this.#y = y + this.#w = width + this.#h = height + this.#idx = 0 + } + + send (pixels, offset, count) { + if (this.#x == null) { + throw new Error('begin should be called first') + } + const m = this.#matrix + const u16a = new Uint16Array(pixels) + const len = u16a.length + for (let i = this.#idx; i < this.#idx + len; i++) { + const pixel = u16a[i] + const r = (pixel & 0b1111100000000000) >> 8 + const g = (pixel & 0b0000011111100000) >> 3 + const b = (pixel & 0b0000000000011111) << 3 + const x = (i % this.#w) + this.#x + const y = Math.floor(i / this.#w) + this.#y + m.setPixel(x, y, m.makeRGB(r, g, b)) + } + this.#idx += len + } + + end () { + this.#matrix.update() + this.#x = this.#y = this.#w = this.#h = this.#idx = null + } + + adaptInvalid (r) { + r.x = 0 + r.y = 0 + r.w = this.#width + r.h = this.#height + } + + continue (x, y, width, height) { + this.#x = x + this.#y = y + this.#w = width + this.#h = height + this.#idx = 0 + } + + pixelsToBytes (count) { + const bytes = (count * Bitmap.depth(this.#pixelFormat)) >> 3 + return bytes + } + + get async () { + return false + } + + get clut () {} + + set clut (clut) {} + + get c_dispatch () {} + + get width () { + return this.#width + } + + get height () { + return this.#height + } + + get pixelFormat () { + return this.#pixelFormat + } + + set brightness (b) { + this.#matrix.brightness = b + } + + #getConfigurePin() @ "xs_NeoMatrix_get_configure_pin"; + + #getConfigureWidth() @ "xs_NeoMatrix_get_configure_width"; + + #getConfigureHeight() @ "xs_NeoMatrix_get_configure_height"; + + #getConfigureBrightness() @"xs_NeoMatrix_get_configure_brightness"; +} + +Object.freeze(NeoMatrixRender.prototype) diff --git a/modules/drivers/neomatrix/neomatrix.js b/modules/drivers/neomatrix/neomatrix.js new file mode 100644 index 0000000000..f772f1750f --- /dev/null +++ b/modules/drivers/neomatrix/neomatrix.js @@ -0,0 +1,59 @@ +import NeoPixel from 'neopixel' + +const TIMING_WS2812B = { + mark: { level0: 1, duration0: 900, level1: 0, duration1: 350 }, + space: { level0: 1, duration0: 350, level1: 0, duration1: 900 }, + reset: { level0: 0, duration0: 100, level1: 0, duration1: 100 } +} +Object.freeze(TIMING_WS2812B) +Object.freeze(TIMING_WS2812B.mark) +Object.freeze(TIMING_WS2812B.space) +Object.freeze(TIMING_WS2812B.reset) + +export class NeoMatrix { + constructor ({ height, width, pin, timing, order, brightness }) { + this.length = height * width + this.height = height + this.width = width + this.timing = timing || TIMING_WS2812B + this.neoPixel = new NeoPixel({ + length: this.length, + pin, + timing: this.timing, + order + }) + this.neoPixel.brightness = brightness || 32 + } + setPixel (x, y, color) { + const a = x * this.width + const b = x & 1 ? this.height - y - 1 : y + const i = a + b + this.neoPixel.setPixel(i, color) + } + fill (color, index, count) { + if (index == null) { + this.neoPixel.fill(color) + } else if (count == null) { + this.neoPixel.fill(color, index) + } else { + this.neoPixel.fill(color, index, count) + } + } + update () { + this.neoPixel.update() + } + set brightness (b) { + this.neoPixel.brightness = b + } + makeRGB (r, g, b, w) { + return this.neoPixel.makeRGB(r, g, b, w) + } + makeHSB (h, s, b, w) { + return this.neoPixel.makeHSB(h, s, b, w) + } + close () { + this.neoPixel.close() + } +} + +Object.freeze(NeoMatrix.prototype)