1 - Display
The display module is available on platforms which have the framebuffer driver enabled. It allows for controlling the display of your device.
Available on: ✅ CampZone 2020 ✅ Disobey 2020 ✅ CampZone 2019 ✅ HackerHotel 2019
✅ Disobey 2019 ✅ SHA2017
Reference
Command | Parameters | Description |
---|
flush | [flags] | Flush the contents of the framebuffer to the display. Optionally you may provide flags (see the table down below) |
size | [window] | Get the size (width, height) of the framebuffer or a window as a tuple |
width | [window] | Get the width of the framebuffer or a window as an integer |
height | [window] | Get the height of the framebuffer or a window as an integer |
orientation | [window], [angle] | Get or set the orientation of the framebuffer or a window |
getPixel | [window], x, y | Get the color of a pixel in the framebuffer or a window |
drawRaw | [window], x, y, width, height, data | Copy a raw bytes buffer directly to the framebuffer or the current frame of a window. The length of the bytes buffer must match the formula width*height*(bitsPerPixel//8). This is a direct copy: color format (bitsPerPixel) must match the specific display of the badge this command is used on. |
drawPixel | [window], x, y, color | Draw a pixel in the framebuffer or a window |
drawFill | [window], color | Fill the framebuffer or a window |
drawLine | [window], x0, y0, x1, y1, color | Draw a line from (x0, y0) to (x1, y1) |
drawTri(angle) | [window], x0, y0, x1, y1, x2, y2, color | Draws a filled triangle |
drawRect | [window], x, y, width, height, filled, color | Draw a rectangle at (x, y) with size (width, height). Set the filled parameter to False to draw only the border, or set it to True to draw a filled rectangle. |
drawQuad* | [window], x0, y0, x1, y1, x2, y2, x3, y3, color | Draws a four-pointed shape between (x0, y0), (x1, y1), (x2, y2) and (x3, y3), always filled |
drawCircle | [window], x0, y0, radius, a0, a1, fill, color | Draw a circle with center point (x0, y0) with the provided radius from angle a0 to angle a1, optionally filled (boolean) |
drawText | [window], x, y, text, [color], [font], [x-scale], [y-scale] | Draw text at (x, y) with a certain color and font. Can be scaled (drawn with rects instead of pixels) in both the x and y direction |
drawPng | [window], x, y, [data or filename] | Draw a PNG image at (x, y) from either a bytes buffer or a file |
getTextWidth | text, [font] | Get the width a string would take if drawn with a certain font |
getTextHeight | text, [font] | Get the height a string would take if drawn with a certain font |
pngInfo | [data or filename] | Get information about a PNG image |
windowCreate | name, width, height | |
windowRemove | name | |
windowMove | name, x, y | |
windowResize | name, width, height | |
windowVisibility | name, [visible] | |
windowShow | name | |
windowHide | name | |
windowFocus | name | |
windowList | - | |
translate* | [window], x, y | Move the canvas of the window by (x, y) |
rotate* | [window], angle | Rotate the canvas of the window by an angle (in randians) |
scale* | [window], x, y | Scale the canvas of the window by (x, y) |
pushMatrix* | [window] | Save the current transformation for later, may be more than one |
popMatrix* | [window] | Restore the transformation pushed earlier, may be more than one |
clearMatrix* | [window], [keep-stack] | Clears the current matrix, and also the matrix stack unless keep-stack is true |
getMatrix* | [window] | Returns an array representing the current matrix for the window |
setMatrix* | [window], [matrix] | Sets the current matrix to the array representing it |
matrixSize* | [window] | Returns the current size of the matrix stack for the window |
* This command is only available if you run a firmware with graphics acceleration, and the respective part enabled in the component config under Driver: framebuffer.
Currently, badges have these features disabled by default.
flag | platform | description |
---|
FLAG_FORCE | All | Force flushing the entire screen. |
FLAG_FULL | All | Force flushing the entire screen. |
FLAG_LUT_GREYSCALE | All with greyscale: SHA2017 | Simulate greyscale. |
FLAG_LUT_NORMAL | All with e-ink | Normal speed flush. |
FLAG_LUT_FAST | All with e-ink | Faster flush. |
FLAG_LUT_FASTEST | All with e-ink | Much faster flush. |
Color representation
Colors are always represented in 24-bit from within Python, in the 0xRRGGBB format. This matches HTML/CSS colors which are #RRGGBB as well.
Devices with a smaller colorspace will not actually store the exact color information provided.
For displays with a color depth of less than 24-bit the display driver will automatically mix down the colors to the available color depth.
This means that even if you have a black and white display 0x000000 is black and 0xFFFFFF is white.
Examples
Setting one pixel
import display
x = 2
y = 3
display.drawPixel(x, y, 0x00FF00) # Set one pixel to 100% green
display.flush() # Write the contents of the buffer to the display
Drawing a line
import display
display.drawFill(0x000000) # Fill the screen with black
display.drawLine(10, 10, 20, 20, 0xFFFFFF) # Draw a white line from (10,10) to (20,20)
display.flush() # Write the contents of the buffer to the display
Drawing a line using pixels:
import display, time
display.drawFill(display.BLACK) # Fill the screen with black before drawing the line
displau.flush() # Write the color to the screen before drawing the line
for i in range(80): # Loop for the X axis
display.drawPixel(i, 1, 0x00FF00) # Set 1 pixel on the X axis i, and the Y axis 1 to 100% green
display.flush() # Write the pixel output to the screen
time.sleep(0.050) # Sleep for 50 milliseconds as to show the line being drawn
Drawing text
import display
display.drawFill(0x000000) # Fill the screen with black
display.drawText(10, 10, "Hello world!", 0xFFFFFF, "permanentmarker22") # Draw the text "Hello world!" at (10,10) in white with the PermanentMarker font with size 22
display.flush() # Write the contents of the buffer to the display
Drawing a rectangle
import display
display.drawFill(0x000000) # Fill the screen with black
display.drawRect(10, 10, 10, 10, False, 0xFFFFFF) # Draw the border of a 10x10 rectangle at (10,10) in white
display.drawRect(30, 30, 10, 10, True, 0xFFFFFF) # Draw a filled 10x10 rectangle at (30,30) in white
display.flush() # Write the contents of the buffer to the display
Spinning a box
Note: as described earlier, matrix transformations are not enabled in the firmware by default
import display, math
# Note: radians are an angle unit where PI (math.pi) is half a rotation
display.clearMatrix() # Clear the matrix stack, just in case it wasn't already
display.translate(display.width() / 2, display.height() / 2) # Go to the middle of the screen
# Everything is now offset as if the middle of the screen is X/Y (0, 0)
while True:
display.drawFill(0xffffff) # Fill the screen with white
display.rotate(math.pi * 0.1) # This will continually rotate the screen by a small amount
display.drawRect(-20, -20, 40, 40, True, 0x000000) # Simply draw a rectangle, which will then spin
display.flush() # Flush, show everything
Spinning text
Note: as described earlier, matrix transformations are not enabled in the firmware by default
Similarly to spinning a box, you can also spin text this way.
import display, math
# Note: radians are an angle unit where PI (math.pi) is half a rotation
text = "Well hello there!" # Whatever you want to show
font = "7x5" # Pick a font
scale = 2 # You can scale text, too!
display.clearMatrix() # Clear the matrix stack, just in case it wasn't already
display.translate(display.width() / 2, display.height() / 2) # Go to the middle of the screen
# Everything is now offset as if the middle of the screen is X/Y (0, 0)
while True:
display.drawFill(0xffffff) # Fill the screen with white
display.rotate(math.pi * 0.1) # This will continually rotate the screen by a small amount
textWidth = display.getTextWidth(text, font) # Get the size so we can center the text
textHeight = display.getTextHeight(text, font)
display.pushMatrix() # Save the transformation for later
display.scale(scale, scale) # Scale the text
display.translate(-textWidth / 2, -textHeight / 2) # Move the canvas so the text is centered
# It is important you scale first, then translate
display.drawText(0, 0, text, 0x000000, font) # Spinny texts
display.popMatrix() # Restore the transformation
display.flush() # Flush, show everything
More complex graphics
Note: as described earlier, matrix transformations are not enabled in the firmware by default
Now you’ve spun a box and some text, what about something more complicated?
Let’s say we draw a boat on a wave!
First, we draw the boat using some shapes:
import display, math
def drawBoat():
display.pushMatrix()
drawBoatBottom(0x000000)
display.translate(-4, 0) # Move just a little so the mast lines up nicely
drawBoatMast(0x000000, 0x000000)
display.popMatrix()
def drawBoatMast(mastColor, flagColor):
# The points drawn, by place:
# 0--1
# | |
# | 6
# | |\
# | 5-4
# | |
# 3--2
x0, y0 = 0, -23
x1, y1 = 3, -23
x2, y2 = 3, 0
x3, y3 = 0, 0
x4, y4 = 12, -10
x5, y5 = 3, -10
x6, y6 = 3, -20
display.drawQuad(x0, y0, x1, y1, x2, y2, x3, y3, mastColor) # This is the mast: points 0, 1, 2, 3
display.drawTri(x4, y4, x5, y5, x6, y6, flagColor) # This is the flag: points 4, 5, 6
def drawBoatBottom(color):
# The points drawn, by place:
# 0--------1
# \ /
# 3----2
x0, y0 = -20, 0
x1, y1 = 20, 0
x2, y2 = 16, 8
x3, y3 = -16, 8
display.drawQuad(x0, y0, x1, y1, x2, y2, x3, y3, color)
Now, to test your boat drawing action:
import display, math
# Put the boat drawing functions here
display.clearMatrix() # Don't forget
display.translate(30, 30) # Move to where you want to draw the boat
display.drawFill(0xffffff) # Clear the screen once more
drawBoat() # Draw the boat of course
display.flush() # Flush display; boat is now visible
Then, we’ll draw a wave and a sun:
import display, math
def drawSun(color): # Draws the sun with a circle and some lines
display.pushMatrix()
display.translate(-3, -3 - display.height()) # This is where the sun will orbit around
# We do - display.height() here because we set the origin to be at the bottom of the screen earlier
display.drawCircle(0, 0, 30, 0, 360, True, color) # The sun
display.rotate(sunOffset)
for i in range(20): # Draw lines as the sun's rays
display.rotate(math.pi / 10)
display.drawLine(0, 35, 0, 45, color)
display.popMatrix()
# For good measure.
display.clearMatrix()
display.translate(0, display.height())
sunOffset = 0
offset = 0
boatX = display.width() / 6
boatAngle = 0
boatY = 0
while True:
display.drawFill(0xffffff)
drawSun(0x000000) # Draw the sun
for i in range(display.width()): # Draw the waves by plotting points
wave = math.sin((i + offset) * math.pi / 35) * 8 - 35
display.drawPixel(i, wave, 0x000000)
if i & 1:
for j in range(round(wave - 1) | 1, 0, 2):
display.drawPixel(i, j + ((i >> 1) & 1) + 1, 0x000000)
offset += 8 # Move the waves over by a bit
sunOffset += math.pi * 0.025 # Spin the sun by a bit
display.flush()
Finally, you can draw the boat on the wave, by adding some code:
while True:
display.drawFill(0xffffff)
drawSun(0x000000)
for i in range(display.width()):
wave = math.sin((i + offset) * math.pi / 35) * 8 - 35
display.drawPixel(i, wave, 0x000000)
if i & 1:
for j in range(round(wave - 1) | 1, 0, 2):
display.drawPixel(i, j + ((i >> 1) & 1) + 1, 0x000000)
# vvvv HERE vvvv
display.pushMatrix() # Save the transformation, we're going to mess with it
waterLevelBeforeBoat = math.sin((boatX + 2 + offset) * math.pi / 35) * 8 - 35
boatY = math.sin((boatX + offset) * math.pi / 35) * 8 - 35
# Calculate the two water levels, one at and one before the boat
# By doing this, we know how and where to position the boat
boatAngle = math.atan2(waterLevelBeforeBoat - boatY, 2) # Using atan2 to find the angle required to rock the boat with the wave
display.translate(boatX, boatY - 6) # Now, position the boat
display.rotate(boatAngle)
drawBoat() # And draw the boat
display.popMatrix() # Undo our changes to the transformation
# ^^^^ HERE ^^^^
offset += 8
sunOffset += math.pi * 0.025
display.flush()
The source code for the boat can be found here:
gist: boat.py
Available fonts:
The fonts in the latest firmware can be obtained from the sourcecode.
Known problems
- Rotation of the contents of windows does not work correctly in combination with rotation of the screen itself
- There is no method available to list the fonts available on your platform
- There is no method for providing a custom font
- There is no anti-aliassing support
2 - Buttons
The buttons API allows you to read the state of the buttons on a badge.
This API encapsulates the drivers for different button types.
Badge support
This API is currently supported on the following badges:
- SHA2017
- Hackerhotel 2019
- Disobey 2019
- CampZone 2019
- Disobey 2020
- Fri3dcamp 2018
Support for GPIO buttons and touch-buttons via the MPR121 touch controller IC are supported. Touch buttons using the touch button features of the ESP32 can not be used (yet).
Reference
Command | Parameters | Description |
---|
attach | button, callback function | Attach a callback to a button |
detach | button | Detach a callback from a button |
value | button | Get the current value of a button |
getCallback | button | Get the current callback of a button |
pushMapping | [mapping] | Switch to a new button mapping |
popMapping | none | Switch back to the previous button mapping |
rotate | degrees | Adapt the button layout to an orientation. Accepts 0, 90, 180 and 270 as values. |
Name | SHA2017 | Hackerhotel 2019 | Disobey 2019 | CampZone 2019 | Disobey 2020 | MCH2022 |
---|
A | Yes | Yes | Yes | Yes | Yes | Yes |
B | Yes | Yes | Yes | Yes | Yes | Yes |
SELECT | Yes | Yes | No | No | Yes | Yes |
START | Yes | Yes | No | No | Yes | Yes |
UP | Yes | Yes | Yes | Yes | Yes | Yes |
DOWN | Yes | Yes | Yes | Yes | Yes | Yes |
LEFT | Yes | Yes | Yes | Yes | Yes | Yes |
RIGHT | Yes | Yes | Yes | Yes | Yes | Yes |
HOME | No | No | No | No | No | Yes |
MENU | No | No | No | No | No | Yes |
Name | SHA2017 | Hackerhotel 2019 | Disobey 2019 | CampZone 2019 | Disobey 2020 | MCH2022 |
---|
A | | | | | | |
B | | | Exit app | Exit app | | |
SELECT | | | | | | |
START | Exit app | Exit app | | | Exit app | |
UP | | | | | | |
DOWN | | | | | | |
LEFT | | | | | | |
RIGHT | | | | | | |
HOME | | | | | | Exit app |
MENU | | | | | | |
Callback implementation:
to use the buttons, you need to implement a callback function:
import buttons, display # Imports 2 libraries to use the buttons, the display library, as well as the buttons library
def on_action_btn(pressed): # Defines a function on_action_btn with the required parameter pressed
if pressed: # Uses an if statement to check if the button has been pressed
display.drawFill(display.BLACK) # If the button is pressed, sets the screen to black
display.drawText(10,10,"Hack The Planet!!!", display.GREEN, "roboto_regular18") # Draws text if the button is pressed
display.flush() # Flushes the screen to draw the text and color onto the screen
buttons.attach(buttons.BTN_A, on_action_btn) # Assigns the function on_action_btn to the A button
3 - mch22
Warning
This module is only available on the MCH2022 badge, only use this module if you plan to use hardware features specific to the MCH2022 badge.FPGA
The ICE40UP5K FPGA on the MCH2022 badge can be controlled using these APIs.
Loading a bitstream
A bitstream can be loaded into the FPGA by calling mch22.fpga_load(x)
with x
being a bytes object containing the bitstream binary. Loading a bitstream automatically enables the FPGA.
Disabling the FPGA
To disable the FPGA after a bitstream was loaded call mch22.fpga_disable()
. Note that to enable the FPGA a bitstream has to be loaded in again.
Communicating with the FPGA
The FPGA is connected to the ESP32 via an SPI bus. Communication over SPI can be done in full-duplex mode (sending while receiving) or in half-duplex mode (only sending or receiving).
The transmitting functions require a bytes object, the receiving functions return a bytes object.
Function | Direction | Bus speed |
---|
transaction | Full-duplex | 26.7MHz |
receive | Read from FPGA | 40MHz |
send | Write to FPGA | 40MHz |
send_turbo | Write to FPGA | 40MHz |
In case you get communication errors please check the timing constraints of your bitstream.
LCD mode
To connect the LCD to the FPGA call mch22.lcd_mode(True)
, to connect the LCD to the ESP32 call mch22.lcd_mode(False)
. To send the contents of the framebuffer to the LCD call display.flush(True)
, the True
argument forces the flush to happen even though the contents of the framebuffer haven’t changed.
GPIO
You can set the direction of a GPIO pin using mch22.set_gpio_dir(pin, direction)
, where pin is one of mch22.SAO_IO0_PIN
, mch22.SAO_IO1_PIN
, mch22.PROTO_0_PIN
or mch2022.PROTO_1_PIN
and direction
is False
for input or True
for output.
Inputs can be read using value = mch22.get_gpio_value(pin)
and outputs can be set using mch22.set_gpio_value(pin, value)
.
Deprecated functions
These functions have been moved to other, more generic APIs. Please use those APIs instead.
The mch22
module exposes a direct interface to the button handler code via the buttons
and set_handler
functions. Please do not use these, use the buttons API instead. Using these APIs directly can cause crashes to occur.
Brightness control
Please use the display API instead, brightness can be set using display.brightness(x)
with x
being a value from 0 to 255.