diff --git a/glowbit/glowbit.py b/glowbit/glowbit.py index e3f58ad..68a8a69 100644 --- a/glowbit/glowbit.py +++ b/glowbit/glowbit.py @@ -5,8 +5,21 @@ from machine import Pin import micropython import rp2 +elif _SYSNAME == 'esp32': + from machine import Pin + import micropython + import neopixel -if _SYSNAME == 'Linux': + # Dummy class for @rp2 decorator + class rp2(): + class PIO(): + OUT_LOW = None + SHIFT_LEFT = None + def asm_pio(sideset_init, out_shiftdir, autopull, pull_thresh): + def wrapper(*argx, **kwargs): + return None + return wrapper +elif _SYSNAME == 'Linux': import rpi_ws281x as ws # Dummy ptr32() for within micropython.viper @@ -18,7 +31,7 @@ def viper(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper - + # Dummy class for @rp2 decorator class rp2(): class PIO(): @@ -27,7 +40,7 @@ class PIO(): def asm_pio(sideset_init, out_shiftdir, autopull, pull_thresh): def wrapper(*argx, **kwargs): return None - return wrapper + return wrapper from petme128 import petme128 @@ -41,7 +54,7 @@ def wrapper(*argx, **kwargs): # Methods for transforming colours to 32-bit packed GlowBit colour values # # A packed 32-bit GlowBit colour is an integer with 8-bits per colour channel data encoded in hexadecimal as follows: -# +# # 0x00RRGGBB # # where RR, GG, and BB are hexadecimal values (decimal [0,255]) and the most significant 8 bits are reserved and left as zero. @@ -67,7 +80,7 @@ def wheel(self,pos: int) -> int: return ((255 - pos * 3)<<8 | (pos * 3)) pos -= 170 return ((pos * 3)<<16) | (255 - pos * 3) - + ## @brief Converts the r, g, and b integer arguments to a packed 32-bit RGB GlowBit colour value # # All arguments are required as this is a micropython viper function. @@ -80,7 +93,7 @@ def wheel(self,pos: int) -> int: @micropython.viper def rgbColour(self, r: int, g: int, b: int) -> int: return ( (r<<16) | (g<<8) | b ) - + ## @brief Converts a 32-bit GlowBit colour value to an (R,G,B) tuple. # # \param colour A 32-bit GlowBit colour value @@ -137,14 +150,14 @@ class colourMaps(): def colourMapSolid(self, index, minIndex, maxIndex): return self.colour - + ## @brief Maps the pure hue colour wheel between minIndex and maxIndex # # \param index The value to be mapped # \param minIndex The value of index mapped to a colour wheel angle of 0 degrees # \param maxIndex The value of index mapped to a colour wheel angle of 360 degrees - # \return The 32-bit packed GlowBit colour value + # \return The 32-bit packed GlowBit colour value def colourMapRainbow(self, index, minIndex, maxIndex): return self.wheel(int(((index-minIndex)*255)/(maxIndex-minIndex))) @@ -176,9 +189,21 @@ def _pixelsShowPico(self): r = int((((int(c) >> 16) & 0xFF) * br) >> 8) g = int((((int(c) >> 8) & 0xFF) * br) >> 8) b = int(((int(c) & 0xFF) * br) >> 8) - ar[i] = (g<<16) | (r<<8) | b + ar[i] = (g<<16) | (r<<8) | b self.sm.put(ar[i], 8) + @micropython.viper + def _pixelsShowEsp32(self): + self.__syncWait() + gc.collect() + br = int(self.brightness) + for i,c in enumerate(self.ar): + r = int((((int(c) >> 16) & 0xFF) * br) >> 8) + g = int((((int(c) >> 8) & 0xFF) * br) >> 8) + b = int(((int(c) & 0xFF) * br) >> 8) + self.neopixels[i] = (r, g, b) + self.neopixels.write() + def _pixelsShowRPi(self): self.__syncWait() br = self.brightness @@ -188,26 +213,26 @@ def _pixelsShowRPi(self): b = int(((int(c) & 0xFF) * br) >> 8) self.strip.setPixelColor(i, (r<<16) | (g<<8) | b) self.strip.show() - + def __syncWait(self): while self.ticks_ms() < (self.lastFrame_ms + 1000 // self.rateLimit): continue self.lastFrame_ms = self.ticks_ms() - + def _ticks_ms_Linux(self): return time.time()*1000 - + ## @brief Pushes the internal pixel data buffer to the physical GlowBit LEDs - # + # # This function must be called before the connected GlowBit LEDs will change colour. - # + # # Note that several GlowBit library methods call this method unconditionally (eg: glowbit.blankDisplay ) or optionally (eg: by passing the update = True parameter to stick.graph1D() ) def pixelsShow(self): return ## @brief Sets the i'th GlowBit LED to a 32-bit GlowBit colour value. - # + # # NB: For efficiency, this method does not do any bounds checking. If the value of the parameter i is larger than the number of LEDs it will cause an IndexError exception. # # \param i An LED's index @@ -216,9 +241,9 @@ def pixelsShow(self): @micropython.viper def pixelSet(self, i: int, colour: int): self.ar[i] = colour - + ## @brief Sets the i'th GlowBit LED to a 32-bit GlowBit colour value and updates the physical LEDs. - # + # # NB: For efficiency, this method does not do any index bounds checking. If the value of the parameter i is larger than the number of LEDs it will cause an IndexError exception. # # \param i An LED's index @@ -228,7 +253,7 @@ def pixelSet(self, i: int, colour: int): def pixelSetNow(self, i: int, colour: int): self.ar[i] = colour self.pixelsShow() - + ## @brief Adds a 32-bit GlowBit colour value to the i'th LED in the internal buffer only. # # Data colour corruption will occur if the sum result of any RGB value exceeds 255. Care must be taken to avoid this manually. eg: if the blue channel's resulting intensity value is 256 it will be set to zero and the red channel incremented by 1. See the colourFunctions class documentation for the 32-bit GlowBit colour specification. @@ -237,20 +262,20 @@ def pixelSetNow(self, i: int, colour: int): # # \param i An LED's index # \param colour The 32-bit GlowBit colour value - # + # @micropython.viper def pixelAdd(self, i: int, colour: int): tmp = int(self.ar[i]) + colour self.ar[i] = tmp - + ## @brief Adds a 32-bit GlowBit colour value to the i'th LED in the internal buffer. This function performs "saturating" arithmetic. It is much slower than pixelAdd but will saturate at 255 to avoid data corruption. # # NB: For efficiency, this method does not do any index bounds checking. If the value of the parameter i is larger than the number of LEDs it will cause an IndexError exception. # # \param i An LED's index # \param colour The 32-bit GlowBit colour value - # + # @micropython.viper def pixelSaturatingAdd(self, i: int, colour: int): @@ -258,7 +283,7 @@ def pixelSaturatingAdd(self, i: int, colour: int): r = (colour & 0xFF0000) >> 16 g = (colour & 0x00FF00) >> 8 b = (colour & 0x0000FF) - + r2 = (tmp & 0xFF0000) >> 16 g2 = (tmp & 0x00FF00) >> 8 b2 = (tmp & 0x0000FF) @@ -275,7 +300,7 @@ def pixelSaturatingAdd(self, i: int, colour: int): b3 = 255 self.ar[i] = (r3 << 16) + (g3 << 8) + b3 - + ## @brief Fills all pixels with a solid colour value # # \param colour The 32-bit GlowBit colour value @@ -285,7 +310,7 @@ def pixelsFill(self, colour: int): ar = self.ar for i in range(int(len(self.ar))): ar[i] = colour - + ## @brief Fills all pixels with a solid colour value and updates the physical LEDs. # # \param colour The 32-bit GlowBit colour value @@ -296,7 +321,7 @@ def pixelsFillNow(self, colour: int): for i in range(int(len(self.ar))): ar[i] = colour self.pixelsShow() - + ## @brief Blanks the entire GlowBit display. ie: sets the colour value of all GlowBit LEDs to zero in the internal buffer and updates the physical LEDs. # # @@ -307,7 +332,7 @@ def blankDisplay(self): for i in range(int(len(self.ar))): ar[i] = 0 self.pixelsShow() - + ## @brief Returns the 32-bit GlowBit colour value of the i'th LED # @@ -323,7 +348,7 @@ def getPixel(self, N): def updateRateLimitFPS(self, rateLimitFPS): self.rateLimit = rateLimitFPS - + ## @brief Set a new brightness value # # \param brightness The relative brightness of the LEDs. Colours drawn to the internal buffer should be in the range [0,255] and the brightness parameter scales this value before drawing to the physical display. If brightness is an integer it should be in the range [0,255]. If brightness is floating point it is assumed to be in the range [0,1.0]. @@ -385,13 +410,13 @@ def pixelSetXY(self, x: int, y: int, colour: int): x = x % int(self.numLEDsX) y = y % int(self.numLEDsY) self.ar[int(self.remap(x,y))] = colour - + ## @brief Sets the colour value of the GlowBit LED at a given x-y coordinate and immediately calls pixelsShow() to update the physical LEDs. # # The coordinate assumes an origin in the upper left of the display with x increasing to the right and y increasing downwards. # # If the x-y coordinate falls outside the display's boundary this function will "wrap-around". For example, A dot placed just off the right edge will appear along the left edge. - # + # # Advanced: If seeking maximum speed consider modifying the ar[] array directly # # \param x The x coordinate of the GlowBit LED. x must be an integer. @@ -405,17 +430,17 @@ def pixelSetXYNow(self, x: int, y: int, colour: int): i = int(self.remap(x,y)) self.ar[i % int(self.numLEDs)] = colour self.pixelsShow() - + ## @brief Sets the colour value of the GlowBit LED at a given x-y coordinate # # The coordinate assumes an origin in the upper left of the display with x increasing to the right and y increasing downwards. # - # If the x-y coordinate falls outside the display's boundary the display's internal buffer will not be modified. + # If the x-y coordinate falls outside the display's boundary the display's internal buffer will not be modified. # # \param x The x coordinate of the GlowBit LED. x must be an integer. # \param y The y coordinate of the GlowBit LED. y must be an integer. # \param colour A packed 32-bit GlowBit colour value - + @micropython.viper def pixelSetXYClip(self, x: int, y: int, colour: int): if x >= 0 and y >= 0 and x < int(self.numLEDsX) and y < int(self.numLEDsY): @@ -444,7 +469,7 @@ def pixelAddXY(self, x: int, y: int, colour: int): # # The coordinate assumes an origin in the upper left of the display with x increasing to the right and y increasing downwards. # - # If the x-y coordinate falls outside the display's boundary the display's internal buffer will not be modified. + # If the x-y coordinate falls outside the display's boundary the display's internal buffer will not be modified. # # Data colour corruption will occur if the sum result of any RGB value exceeds 255. Care must be taken to avoid this manually. eg: if the blue channel's resulting intensity value is 256 it will be set to zero and the red channel incremented by 1. See the colourFunctions class documentation for the 32-bit GlowBit colour specification. # @@ -456,7 +481,7 @@ def pixelAddXY(self, x: int, y: int, colour: int): def pixelAddXYClip(self, x: int, y: int, colour: int): if x >= 0 and y >= 0 and x < int(self.numLEDsX) and y < int(self.numLEDsY): self.ar[int(self.remap(x,y))] = colour + int(self.ar[int(self.remap(x,y))]) - + ## @brief Returns the 32-bit GlowBit colour value of the LED at a given (x,y) coordinate # # If the (x,y) coordinate falls outside of the display's boundary an IndexError exception may be thrown or the GlowBit colour value of an undefined pixel may be returned. @@ -480,17 +505,17 @@ def getPixelXY(self, x, y): @micropython.viper def drawLine(self, x0: int, y0: int, x1: int, y1: int, colour: int): steep = abs(y1-y0) > abs(x1-x0) - + if steep: # Swap x/y tmp = x0 x0 = y0 y0 = tmp - + tmp = y1 y1 = x1 x1 = tmp - + if x0 > x1: # Swap start/end tmp = x0 @@ -499,17 +524,17 @@ def drawLine(self, x0: int, y0: int, x1: int, y1: int, colour: int): tmp = y0 y0 = y1 y1 = tmp - + dx = x1 - x0; dy = int(abs(y1-y0)) - + err = dx >> 1 # Divide by 2 - + if(y0 < y1): ystep = 1 else: ystep = -1 - + while x0 <= x1: if steep: self.pixelSetXYClip(y0, x0, colour) @@ -520,7 +545,7 @@ def drawLine(self, x0: int, y0: int, x1: int, y1: int, colour: int): y0 += ystep err += dx x0 += 1 - + ## @brief Draws a triangle with vertices (corners) at (x0,y0), (x1, y1), and (x2,y2). All lines are drawn with the specified colour. # # If pixel is drawn off the screen a "clipping" effect will be inherited from the behaviour of pixelSetXYClip(). ie: Pixels landing off the screen will not be drawn. @@ -537,9 +562,9 @@ def drawTriangle(self, x0,y0, x1, y1, x2, y2, colour): self.drawLine(x0, y0, x1, y1, colour) self.drawLine(x1, y1, x2, y2, colour) self.drawLine(x2, y2, x0, y0, colour) - + ## @brief Draws a rectangle with upper-left corner (x0,y0) and lower right corner (x1, y1). All edge lines are drawn with the specified colour. - # + # # Pixels inside the rectangle are left unmodified. # # If pixel is drawn off the screen a "clipping" effect will be inherited from the behaviour of pixelSetXYClip(). ie: Pixels landing off the screen will not be drawn. @@ -555,7 +580,7 @@ def drawRectangle(self, x0, y0, x1, y1, colour): self.drawLine(x1, y0, x1, y1, colour) self.drawLine(x1, y1, x0, y1, colour) self.drawLine(x0, y1, x0, y0, colour) - + ## @brief Draws a rectangle with upper-left corner (x0,y0) and lower right corner (x1, y1). The rectangle is then filled to form a solid block of the specified colour. # # This method overwrites pixel data with the colour value. @@ -593,7 +618,7 @@ def drawRectangleFillAdd(self, x0: int, y0: int, x1: int, y1: int, colour): self.pixelAddXY(x,y,colour) ## @brief Draws a circle with center (x0,y0) and radius r. The circle's outline is drawn in the specified colour. Pixels inside the circle are not modified. - # + # # \param x0 The x coordinate of the circle's center # \param y0 The y coordinate of the circle's center # \param colour A packed 32-bit GlowBit colour value @@ -608,9 +633,9 @@ def drawCircle(self, x0, y0, r, colour): self.pixelSetXYClip(x0, y0 - r, colour) self.pixelSetXYClip(x0 + r, y0, colour) self.pixelSetXYClip(x0 - r, y0, colour) - + while x < y: - if f >= 0: + if f >= 0: y -= 1 ddf_y += 2 f += ddf_y @@ -625,7 +650,7 @@ def drawCircle(self, x0, y0, r, colour): self.pixelSetXYClip(x0 - y, y0 + x, colour) self.pixelSetXYClip(x0 + y, y0 - x, colour) self.pixelSetXYClip(x0 - y, y0 - x, colour) - + ## @brief One dimensional graph object for graph bars on GlowBit Matrix displays. class graph1D(colourFunctions, colourMaps): @@ -661,7 +686,7 @@ def __init__(self, originX = 0, originY = 7, length = 8, direction = "Up", minVa if direction == "Right": self.orientation = 0 self.inc = 1 - + if self.orientation == -1 or self.inc == 0: print("Invalid direction \"", direction, "\".") print("Valid options: Up, Down, Left, Right") @@ -679,7 +704,7 @@ def __init__(self, originX = 0, originY = 7, length = 8, direction = "Up", minVa self.colourMap = self.colourMapSolid elif colourMap == "Rainbow": self.colourMap = self.colourMapRainbow - + ## @brief Wrapper method to create a graph1D object # # Calling matrix.graph1D() directly is recommended - this is only here so that it appears more clearly in the Doxygen. @@ -753,18 +778,18 @@ def __init__(self, originX = 0, originY = 7, width = 8, height = 8, minValue=0, self.m = (-height)/(maxValue-minValue) self.offset = originY+self.m*minValue self.bars = bars - + self.data = [] - + if callable(colourMap) == True: self.colourMap = colourMap elif colourMap == "Solid": self.colourMap = self.colourMapSolid elif colourMap == "Rainbow": self.colourMap = self.colourMapRainbow - + ## @brief Updates a 2D graph with a new value. - # + # # \param graph A graph2D object created graph2D # \param value A new value to draw to the graph. This value will be drawn on the right edge and the oldest value will be deleted. @@ -803,7 +828,7 @@ def lineDemo(self, iters = 10): self.pixelsShow() iters -= 1 self.blankDisplay() - + ## @brief Demonstrate drawing randomly placed, randomly coloured, expanding circles. # # Note that pixelsFill(0) is only called after drawing an expanding circle, simulating a mostly filled circle. Gaps are an artefact of the circle drawing algorithm, not a bug. @@ -823,11 +848,11 @@ def fireworks(self, iters = 10): self.drawCircle(Cx, Cy, r, 0) self.pixelsShow() iters -= 1 - + ## @brief Demonstration of a rainbow effect is pseudo-polar coordinates. # # This function intentionally uses integer arithmetic for performance reasons. As such, it is drawn half a pixel off center. - # + # # The function animates 255 frames then returns. @micropython.viper @@ -860,11 +885,11 @@ def __init__(self, x, speed): self.x = x self.speed = speed self.y = 0 - + def update(self): self.y += self.speed return (self.x, (self.y//10)) - + def getY(self): return (self.y//10) @@ -910,7 +935,7 @@ def rain(self, iters = 200, density=1): drops.remove(drop) iters -= 1 - self.pixelsShow(); + self.pixelsShow() ## @brief Demonstrates creation of non-blocking scrolling text. Only compatible with the GlowBit Matrix 8x8 and tiled arrangements thereof. # @@ -971,7 +996,7 @@ def demo(self): class stick(glowbit): ## @brief Initialisation routine for GlowBit stick modules and tiled arrays thereof. - # + # # \param numLEDs The total number of LEDs. Should be set to 8 * (the number of tiled modules). # \param pin The GPIO pin connected to the GlowBit stick module. Defaults to 18 as that pin is compatible with the Raspberry Pi and Raspberry Pi Pico. Any pin can be used on the Raspberry Pi Pico, only pins 18 and 12 are valid on the Raspberry Pi. # \param brightness The relative brightness of the LEDs. Colours drawn to the internal buffer should be in the range [0,255] and the brightness parameter scales this value before drawing to the physical display. If brightness is an integer it should be in the range [0,255]. If brightness is floating point it is assumed to be in the range [0,1.0]. @@ -979,16 +1004,19 @@ class stick(glowbit): # \param sm (Raspberry Pi Pico only) The PIO state machine to generate the GlowBit data stream. Each connected GlowBit display chain requires a unique state machine. Valid values are in the range [0,7]. def __init__(self, numLEDs = 8, pin = 18, brightness = 20, rateLimitFPS = 30, sm = 0): + self.numLEDs = numLEDs + if _SYSNAME == 'rp2': self.sm = rp2.StateMachine(sm, self._ws2812, freq=8_000_000, sideset_base=Pin(pin)) self.sm.active(1) self.pixelsShow = self._pixelsShowPico self.ticks_ms = time.ticks_ms - - self.numLEDs = numLEDs - - if _SYSNAME == 'Linux': - self.strip = ws.PixelStrip(numLEDs, pin) + elif _SYSNAME == 'esp32': + self.neopixels = neopixel.NeoPixel(Pin(pin), self.numLEDs) + self.pixelsShow = self._pixelsShowEsp32 + self.ticks_ms = time.ticks_ms + elif _SYSNAME == 'Linux': + self.strip = ws.PixelStrip(self.numLEDs, pin) self.strip.begin() self.pixelsShow = self._pixelsShowRPi self.ticks_ms = self._ticks_ms_Linux @@ -997,20 +1025,20 @@ def __init__(self, numLEDs = 8, pin = 18, brightness = 20, rateLimitFPS = 30, sm self.ar = array.array("I", [0 for _ in range(self.numLEDs)]) self.dimmer_ar = array.array("I", [0 for _ in range(self.numLEDs)]) - if rateLimitFPS > 0: + if rateLimitFPS > 0: self.rateLimit = rateLimitFPS else: self.rateLimit = 100 - + if brightness <= 1.0 and isinstance(brightness, float): self.brightness = int(brightness*255) else: self.brightness = int(brightness) - + self.pixelsFill(0) self.pixelsShow() - - # The list of pulses which are drawn upon a call + + # The list of pulses which are drawn upon a call self.pulses = [] ## @brief A class for animating "pulses" which move down a GlowBit stick. @@ -1019,7 +1047,7 @@ class pulse(colourFunctions, colourMaps): ## @brief Initialisation routine for the GlowBit Stick pulse object. # - # This function uses the pixelSaturatingAdd() method so multiple pulses can be drawn without colour values corrupting due to addition overflow. + # This function uses the pixelSaturatingAdd() method so multiple pulses can be drawn without colour values corrupting due to addition overflow. # # \param speed The speed of the pulse in units of (pixels moved per frame) * 100. A value of 100 means the pulse will move 1 pixels per frame. A speed of 1 will move a pulse 1 pixel every 100 frames. Speed can be positive or negative to allow pulses to move in either direction. # \param colour A list of 32-bit GlowBit colours for the pulse. The pulse will have a width equal to the number of elements in this list. A list entry of -1 will have the colour set by a colour map function. @@ -1028,11 +1056,11 @@ class pulse(colourFunctions, colourMaps): def __init__(self, speed = 100, colour = [0xFFFFFF], index = 0, colourMap = None): ## Speed of the pulse - self.speed = speed + self.speed = speed ## Initial index of the pulse self.index = index self._position = self.index*100 # index * 100 - + if type(colour) is list: ## A list of 32-bit GlowBit colour values. Each one is drawn to a pixel; a -1 indicates the use of the colourMap function self.colour = colour @@ -1048,12 +1076,12 @@ def __init__(self, speed = 100, colour = [0xFFFFFF], index = 0, colourMap = None self.colourMap = self.colourMapRainbow else: self.colourMap = None - + def _update(self): self._position += self.speed self.index = self._position//100 - ## @brief Add a pulse to the list of pulses + ## @brief Add a pulse to the list of pulses # # \param speed The speed of the pulse in units of (pixels moved per frame) * 100. A value of 100 means the pulse will move 1 pixels per frame. A speed of 1 will move a pulse 1 pixel every 100 frames. Speed can be positive or negative to allow pulses to move in either direction. # \param colour A list of 32-bit GlowBit colours for the pulse. The pulse will have a width equal to the number of elements in this list. A list entry of -1 will have the colour set by a colour map function. @@ -1062,7 +1090,7 @@ def _update(self): def addPulse(self, speed = 100, colour = [0xFFFFFF], index = 0, colourMap = None): self.pulses.append(self.pulse(speed, colour, index, colourMap)) - + ## @brief Update the position of all pulses in self.pulses[] and draw them to the internal buffer. # @@ -1081,19 +1109,19 @@ def updatePulses(self): self.pixelSaturatingAdd(i, c) i -= 1 p._update() - + for p in reversed(self.pulses): if p.index - len(p.colour) >= self.numLEDs: self.pulses.remove(p) if p.index + len(p.colour) < 0: self.pulses.remove(p) - + ## @brief One dimensional graph ofject for drawing a graph bar on a GlowBit Stick display class graph1D(colourFunctions, colourMaps): ## @brief Initialisation routine for the glowbit.stick.graph1D object. This object is drawn to the display with glowbit.stick.updateGraph1D. - # + # # \param minIndex The pixel index for the start of the graph # \param maxIndex The pixel index for the end of the graph # \param minValue The numerical value of the start of the graph @@ -1121,11 +1149,11 @@ def __init__(self, minIndex = 0, maxIndex = 7, minValue=0, maxValue=255, colour self.colourMap = self.colourMapSolid elif colourMap == "Rainbow": self.colourMap = self.colourMapRainbow - + ## @brief Wrapper function to create graph1D objects. Returns a new stick.graph1D() object. # # See also stick.graph1D() - # + # # \param minIndex The pixel index for the start of the graph # \param maxIndex The pixel index for the end of the graph # \param minValue The numerical value of the start of the graph @@ -1138,7 +1166,7 @@ def newGraph1D(self, minIndex = 0, maxIndex = 7, minValue = 0, maxValue = 255, c return self.graph1D(minIndex, maxIndex, minValue, maxValue, colour, colourMap, update) ## @brief Updates a graph1D object, drawing it to the display. - # + # # If the graph1D object was created with "update = True" this function will call pixelsShow() to update the physical display before returning. # # \param graph A graph1D object as returned by stick.graph1D @@ -1153,11 +1181,11 @@ def updateGraph1D(self, graph, value): self.pixelSet(idx, 0) if graph.update == True: self.pixelsShow() - + ## @brief Fill a "slice" of the GlowBit stick's pixels with a solid colour. - # + # # By default it will fill the entire display with a solid colour. - # + # # \param i The minimum index to fill # \param j The maximum index to fill # \param colour A 32-bit GlowBit colour value @@ -1169,11 +1197,11 @@ def fillSlice(self, i=0, j=-1, colour = 0xFFFFFF): self.pixelSet(k, colour) ## @brief A demonstration of the use of "pulse" objects - # + # # The pulse traveling "up" the stick is drawn with default arguments: a single white pixel - # + # # The pulse returning "down" the stick is drawn with a 3-pixel list of colours. The first and last are coloured with the "Rainbow" colour map, changing colour with pixel index, while the middle is white. - # + # # \param iters The number of frames which are drawn before returning. def pulseDemo(self, iters = 100): while iters > 0: @@ -1256,7 +1284,7 @@ def demo(self): class rainbow(stick): ## @brief Initialisation routine for GlowBit rainbow modules. - # + # # \param numLEDs The total number of LEDs. Should be set to 13 * (the number of tiled modules). # \param pin The GPIO pin connected to the GlowBit Rainbow module. Defaults to 18 as that pin is compatible with the Raspberry Pi and Raspberry Pi Pico. Any pin can be used on the Raspberry Pi Pico, only pins 18 and 12 are valid on the Raspberry Pi. # \param brightness The relative brightness of the LEDs. Colours drawn to the internal buffer should be in the range [0,255] and the brightness parameter scales this value before drawing to the physical display. If brightness is an integer it should be in the range [0,255]. If brightness is floating point it is assumed to be in the range [0,1.0]. @@ -1287,7 +1315,7 @@ def drawRainbow(self, offset = 0): self.pixelSet(i, self.wheel(colPhase%255)) colPhase += 17 # "True" rainbow, red to purple self.pixelsShow() - + ## @brief Displays a rainbow animation in an infinite loop. This method demonstrates the use of drawRainbow(). def demo(self): @@ -1301,7 +1329,7 @@ def demo(self): class triangle(glowbit): ## @brief Initialisation routine for triangular GlowBit modules and tiled arrays thereof. - # + # # \param numTris The number of triangle modules in the tiled array. # \param LEDsPerTri The number of LEDs on each triangular module. # \param pin The GPIO pin connected to the GlowBit stick module. Defaults to 18 as that pin is compatible with the Raspberry Pi and Raspberry Pi Pico. Any pin can be used on the Raspberry Pi Pico, only pins 18 and 12 are valid on the Raspberry Pi. @@ -1310,17 +1338,20 @@ class triangle(glowbit): # \param sm (Raspberry Pi Pico only) The PIO state machine to generate the GlowBit data stream. Each connected GlowBit display chain requires a unique state machine. Valid values are in the range [0,7]. def __init__(self, numTris = 1, LEDsPerTri = 6, pin = 18, brightness = 20, rateLimitFPS = 20, sm = 0): + self.LEDsPerTri = LEDsPerTri + self.numLEDs = numTris*LEDsPerTri + self.numTris = numTris + if _SYSNAME == 'rp2': self.sm = rp2.StateMachine(sm, self._ws2812, freq=8_000_000, sideset_base=Pin(pin)) self.sm.active(1) self.pixelsShow = self._pixelsShowPico self.ticks_ms = time.ticks_ms - - self.LEDsPerTri = LEDsPerTri - self.numLEDs = numTris*LEDsPerTri - self.numTris = numTris - - if _SYSNAME == 'Linux': + elif _SYSNAME == 'esp32': + self.neopixels = neopixel.NeoPixel(Pin(pin), self.numLEDs) + self.pixelsShow = self._pixelsShowEsp32 + self.ticks_ms = time.ticks_ms + elif _SYSNAME == 'Linux': self.strip = ws.PixelStrip(self.numLEDs, pin) self.strip.begin() self.pixelsShow = self._pixelsShowRPi @@ -1328,21 +1359,21 @@ def __init__(self, numTris = 1, LEDsPerTri = 6, pin = 18, brightness = 20, rateL self.ar = array.array("I", [0 for _ in range(self.numLEDs)]) self.dimmer_ar = array.array("I", [0 for _ in range(self.numLEDs)]) - - if rateLimitFPS > 0: + + if rateLimitFPS > 0: self.rateLimit = rateLimitFPS else: self.rateLimit = 100 - + if brightness <= 1.0 and isinstance(brightness, float): self.brightness = int(brightness*255) else: self.brightness = int(brightness) - + self.pixelsFill(0) self.lastFrame_ms = self.ticks_ms() self.pixelsShow() - + ## @brief Fills all LEDs on a given triangle with the same colour. # # \param tri The triangle to fill. The first triangle is addressed with 0. @@ -1356,14 +1387,14 @@ def fillTri(self, tri, colour): ## @brief Displays a simple demo pattern def demo(self): - import random + import random for i in range(2): for j in range(self.numTris): self.fillTri(j, self.wheel(random.randint(0,255))) ## @brief Class for driving GlowBit Matrix 4x4 modules and horizontally tiled arrangements thereof. # -# NB: The 4x4 matrix is designed to only tile horizontally, making an Nx4 pixel display. +# NB: The 4x4 matrix is designed to only tile horizontally, making an Nx4 pixel display. # # If manually tiling horizontally and vertically a custom remapping function will need to be written. # @@ -1374,7 +1405,7 @@ def demo(self): class matrix4x4(glowbitMatrix): ## @brief Initialisation routine for GlowBit stick modules and tiled arrays thereof. - # + # # \param tiles The number of tiled GlowBit Matrix 4x4 modules. # \param pin The GPIO pin connected to the GlowBit stick module. Defaults to 18 as that pin is compatible with the Raspberry Pi and Raspberry Pi Pico. Any pin can be used on the Raspberry Pi Pico, only pins 18 and 12 are valid on the Raspberry Pi. # \param brightness The relative brightness of the LEDs. Colours drawn to the internal buffer should be in the range [0,255] and the brightness parameter scales this value before drawing to the physical display. If brightness is an integer it should be in the range [0,255]. If brightness is floating point it is assumed to be in the range [0,1.0]. @@ -1384,12 +1415,6 @@ class matrix4x4(glowbitMatrix): def __init__(self, tiles = 1, pin = 18, brightness = 20, mapFunction = None, rateLimitFPS = 30, sm = 0): - if _SYSNAME == 'rp2': - self.sm = rp2.StateMachine(sm, self._ws2812, freq=8_000_000, sideset_base=Pin(pin)) - self.sm.active(1) - self.pixelsShow = self._pixelsShowPico - self.ticks_ms = time.ticks_ms - self.tiles = tiles self.numLEDs = tiles*16 self.numLEDsX = tiles*4 @@ -1399,7 +1424,16 @@ def __init__(self, tiles = 1, pin = 18, brightness = 20, mapFunction = None, rat # Convenience variable; equal to numLEDsY self.numRows = self.numLEDsY - if _SYSNAME == 'Linux': + if _SYSNAME == 'rp2': + self.sm = rp2.StateMachine(sm, self._ws2812, freq=8_000_000, sideset_base=Pin(pin)) + self.sm.active(1) + self.pixelsShow = self._pixelsShowPico + self.ticks_ms = time.ticks_ms + elif _SYSNAME == 'esp32': + self.neopixels = neopixel.NeoPixel(Pin(pin), self.numLEDs) + self.pixelsShow = self._pixelsShowEsp32 + self.ticks_ms = time.ticks_ms + elif _SYSNAME == 'Linux': self.strip = ws.PixelStrip(self.numLEDs, pin) self.strip.begin() self.pixelsShow = self._pixelsShowRPi @@ -1409,7 +1443,7 @@ def __init__(self, tiles = 1, pin = 18, brightness = 20, mapFunction = None, rat self.dimmer_ar = array.array("I", [0 for _ in range(self.numLEDs)]) self.lastFrame_ms = self.ticks_ms() self.scrollingText = False # Only required because the self.pixelsShow() function is shared with the 8x8 - + if brightness <= 1.0 and isinstance(brightness, float): self.brightness = int(brightness*255) else: @@ -1419,12 +1453,12 @@ def __init__(self, tiles = 1, pin = 18, brightness = 20, mapFunction = None, rat self.remap = mapFunction else: self.remap = self.remap4x4 - - if rateLimitFPS > 0: + + if rateLimitFPS > 0: self.rateLimit = rateLimitFPS else: self.rateLimit = 100 - + # Blank display self.pixelsFill(0) self.pixelsShow() @@ -1435,7 +1469,7 @@ def __init__(self, tiles = 1, pin = 18, brightness = 20, mapFunction = None, rat # It is recommended to use pixelSetXY() (and variants) instead of this function. # # The return value can be passed to pixelSet(i, colour) (and its variants pixelSetNow() etc) in place of the paramter "i". - # + # # The (x,y) coordinates assume (0,0) in the upper left corner of the display with x increasing to the right and y increasing down # # This function does not do boundary checking and may return a value which is outside the array, causing an IndexError exception to be raised. @@ -1450,7 +1484,7 @@ def remap4x4(self, x: int,y: int) -> int: TopLeftIndex = mc * 16 # ASSUMES 4X4 MODULES LEDsBefore = 4*y + x - 4*mc # LEDs before in a module return TopLeftIndex + LEDsBefore - + ## @brief Class for driving GlowBit Matrix 8x8 modules and tiled arrangements thereof. @@ -1468,9 +1502,9 @@ def remap4x4(self, x: int,y: int) -> int: # class matrix8x8(glowbitMatrix): - + ## @brief Initialisation routine for GlowBit stick modules and tiled arrays thereof. - # + # # \param tileRows The number of tiled GlowBit Matrix 8x8 module rows. # \param tileCols The number of tiled GlowBit Matrix 8x8 module columns. # \param pin The GPIO pin connected to the GlowBit stick module. Defaults to 18 as that pin is compatible with the Raspberry Pi and Raspberry Pi Pico. Any pin can be used on the Raspberry Pi Pico, only pins 18 and 12 are valid on the Raspberry Pi. @@ -1481,7 +1515,7 @@ class matrix8x8(glowbitMatrix): # \param sm (Raspberry Pi Pico only) The PIO state machine to generate the GlowBit data stream. Each connected GlowBit display chain requires a unique state machine. Valid values are in the range [0,7]. def __init__(self, tileRows = 1, tileCols = 1, pin = 18, brightness = 20, mapFunction = None, rateLimitFPS = -1, rateLimitCharactersPerSecond = -1, sm = 0): - + self.tileRows = tileRows self.tileCols = tileCols self.numLEDs = tileRows*tileCols*64 @@ -1491,14 +1525,17 @@ def __init__(self, tileRows = 1, tileCols = 1, pin = 18, brightness = 20, mapFun self.numCols = self.numLEDsX # Convenience variable; equal to numLEDsY self.numRows = self.numLEDsY - + if _SYSNAME == 'rp2': self.sm = rp2.StateMachine(sm, self._ws2812, freq=8_000_000, sideset_base=Pin(pin)) self.sm.active(1) self.pixelsShow = self._pixelsShowPico self.ticks_ms = time.ticks_ms - - if _SYSNAME == 'Linux': + elif _SYSNAME == 'esp32': + self.neopixels = neopixel.NeoPixel(Pin(pin), self.numLEDs) + self.pixelsShow = self._pixelsShowEsp32 + self.ticks_ms = time.ticks_ms + elif _SYSNAME == 'Linux': self.strip = ws.PixelStrip(self.numLEDs, pin) self.strip.begin() self.pixelsShow = self._pixelsShowRPi @@ -1506,32 +1543,32 @@ def __init__(self, tileRows = 1, tileCols = 1, pin = 18, brightness = 20, mapFun self.ar = array.array("I", [0 for _ in range(self.numLEDs)]) self.dimmer_ar = array.array("I", [0 for _ in range(self.numLEDs)]) - + if brightness <= 1.0 and isinstance(brightness, float): self.brightness = int(brightness*255) else: self.brightness = int(brightness) - + # Set to True while a scrolling text object is available to be drawn. self.scrollingText = False - + self.lastFrame_ms = self.ticks_ms() - - if rateLimitFPS > 0: + + if rateLimitFPS > 0: self.rateLimit = rateLimitFPS elif rateLimitCharactersPerSecond > 0: self.rateLimit = rateLimitCharactersPerSecond * 8 else: self.rateLimit = 30 - + self.scrollingTextList = [] - + if callable(mapFunction) is True: self.remap = mapFunction print(self.remap) else: self.remap = self.remap8x8 - + # Blank display self.blankDisplay() self.blankDisplay() @@ -1565,7 +1602,7 @@ def __init__(self, string, y = 0, x = 0, colour = 0xFFFFFF, bgColour = 0): self.colour = colour self.bgColour = bgColour self.string = string - + ## @brief Adds a line of scrolling text to the display. # # This method can be blocking or non-blocking. @@ -1593,7 +1630,7 @@ def addTextScroll(self, string, y = 0, x = 0, colour = 0xFFFFFF, bgColour = 0x00 self.updateText = True while self.scrollingText > 0: self.updateTextScroll() - + ## @brief Update a scrolling text animation # # addTextScroll() must be called at least once for scrolling text to be drawn to the display. @@ -1606,11 +1643,11 @@ def updateTextScroll(self): self.drawChar(c, -textLine.x+8*x, textLine.y, textLine.colour) x += 1 textLine.x += 1 - + for textLine in reversed(self.scrollingTextList): if textLine.x == 8*len(textLine.string)+1: self.scrollingTextList.remove(textLine) - + if self.updateText == True: self.pixelsShow() if len(self.scrollingTextList) == 0: @@ -1619,13 +1656,13 @@ def updateTextScroll(self): ## @brief Maps an (x,y) coordinate on a tiled GlowBit Matrix 8x8 array to an internal buffer array index. # # It is recommended to use pixelSetXY() (and variants) instead of this function. - # + # # The return value can be passed to pixelSet(i, colour) (and its variants pixelSetNow() etc) in place of the paramter "i". - # + # # The (x,y) coordinates assume (0,0) in the upper left corner of the display with x increasing to the right and y increasing down # # This function does not do boundary checking and may return a value which is outside the array, causing an IndexError exception to be raised. - # + # # \param x The x coordinate of the pixel to index # \param y The y coordinate of the pixel to index @@ -1684,7 +1721,7 @@ def drawChar(self, char, Px: int, Py: int, colour: int): ar[int(remap(x,y+6))] += ((dat>>6)&1)*colour ar[int(remap(x,y+7))] += ((dat>>7)&1)*colour x += 1 - + ## @brief Changes the 8x8 matrix display's update rate in units of "characters of scrolling text per second". # # For example, a value of 2 would scroll 2 charcters per second; leaving each character at least partly visible for 0.5 seconds.