023 - ESP32 MicroPython: Binary Clock

Introduction

In this article, I would like to share to you on how to create this simple yet cool project using a single 8×8 dot matrix module to display a binary clock – a clock that is represented using binary numeric system.

Bill Of Materials

  1. An ESP32 development board (or any other development board with MicroPython firmware).
  2. An 8×8 Dot Matrix module with SPI interface (if you have I2C interface, you just need to use a different driver library and modify the source code a little bit).
  3. And some jumper wires.

Hardware Instruction

For the hardware part, it is very easy just follow the circuit diagram below which connects the dot matrix to ESP32 as follows:

  1. Dot matrix VCC to 3.3V.
  2. Dot matrix GND pin to ESP32 GND pin.
  3. Dot matrix DIN pin to ESP32 GPIO 23.
  4. Dot matrix CLK pin to ESP32 GPIO 19.
  5. Dot matrix CS pin to ESP32 GPIO 18.

Software Instruction

  1. Copy the max7219 from Jeff Brown github: https://github.com/jgbrown32/ESP8266_MAX7219 or you may copy it in the SOURCE CODE section below.
  2. And save it to ESP32 MicroPython device root directory by clicking the File menu and select Save As.
  3. Select MicroPython device and name it as max7219.py and click OK.
  4. Copy the example source code below. Please feel free to modify it to your liking.
  5. Enjoy.

Video Demonstration

Call To Action

For any concern, write your message in the comment section.

You might also like to support my journey on Youtube by Subscribing. Click this to Subscribe to TechToTinker.

Thank you and have a good days ahead.

See you, – George Bantique | tech.to.tinker

Source Code

1. Binary Clock:

  1# More details can be found in TechToTinker.blogspot.com 
  2# George Bantique | tech.to.tinker@gmail.com
  3
  4from machine import Pin, SPI
  5from micropython import const
  6import framebuf, utime
  7
  8_DIGIT_0 = const(0x1)
  9
 10_DECODE_MODE = const(0x9)
 11_NO_DECODE = const(0x0)
 12
 13_INTENSITY = const(0xa)
 14_INTENSITY_MIN = const(0x0)
 15
 16_SCAN_LIMIT = const(0xb)
 17_DISPLAY_ALL_DIGITS = const(0x7)
 18
 19_SHUTDOWN = const(0xc)
 20_SHUTDOWN_MODE = const(0x0)
 21_NORMAL_OPERATION = const(0x1)
 22
 23_DISPLAY_TEST = const(0xf)
 24_DISPLAY_TEST_NORMAL_OPERATION = const(0x0)
 25
 26_MATRIX_SIZE = const(8)
 27
 28# _SCROLL_SPEED_NORMAL is ms to delay (slow) scrolling text.
 29_SCROLL_SPEED_NORMAL = 100
 30
 31class Max7219(framebuf.FrameBuffer):
 32    """
 33    Driver for MAX7219 8x8 LED matrices
 34    https://github.com/vrialland/micropython-max7219
 35    Example for ESP8266 with 2x4 matrices (one on top, one on bottom),
 36    so we have a 32x16 display area:
 37    >>> from machine import Pin, SPI
 38    >>> from max7219 import Max7219
 39    >>> spi = SPI(1, baudrate=10000000)
 40    >>> screen = Max7219(32, 16, spi, Pin(15))
 41    >>> screen.rect(0, 0, 32, 16, 1)  # Draws a frame
 42    >>> screen.text('Hi!', 4, 4, 1)
 43    >>> screen.show()
 44    On some matrices, the display is inverted (rotated 180°), in this case
 45     you can use `rotate_180=True` in the class constructor.
 46    """
 47
 48    def __init__(self, width, height, spi, cs, rotate_180=False):
 49        # Pins setup
 50        self.spi = spi
 51        self.cs = cs
 52        self.cs.init(Pin.OUT, True)
 53
 54        # Dimensions
 55        self.width = width
 56        self.height = height
 57        # Guess matrices disposition
 58        self.cols = width // _MATRIX_SIZE
 59        self.rows = height // _MATRIX_SIZE
 60        self.nb_matrices = self.cols * self.rows
 61        self.rotate_180 = rotate_180
 62        # 1 bit per pixel (on / off) -> 8 bytes per matrix
 63        self.buffer = bytearray(width * height // 8)
 64        format = framebuf.MONO_HLSB if not self.rotate_180 else framebuf.MONO_HMSB
 65        super().__init__(self.buffer, width, height, format)
 66
 67        # Init display
 68        self.init_display()
 69
 70    def _write_command(self, command, data):
 71        """Write command on SPI"""
 72        cmd = bytearray([command, data])
 73        self.cs(0)
 74        for matrix in range(self.nb_matrices):
 75            self.spi.write(cmd)
 76        self.cs(1)
 77
 78    def init_display(self):
 79        """Init hardware"""
 80        for command, data in (
 81                (_SHUTDOWN, _SHUTDOWN_MODE),  # Prevent flash during init
 82                (_DECODE_MODE, _NO_DECODE),
 83                (_DISPLAY_TEST, _DISPLAY_TEST_NORMAL_OPERATION),
 84                (_INTENSITY, _INTENSITY_MIN),
 85                (_SCAN_LIMIT, _DISPLAY_ALL_DIGITS),
 86                (_SHUTDOWN, _NORMAL_OPERATION),  # Let's go
 87        ):
 88            self._write_command(command, data)
 89
 90        self.fill(0)
 91        self.show()
 92
 93    def brightness(self, value):
 94        # Set display brightness (0 to 15)
 95        if not 0 <= value < 16:
 96            raise ValueError('Brightness must be between 0 and 15')
 97        self._write_command(_INTENSITY, value)
 98
 99    def marquee(self, message):
100        start = 33
101        extent = 0 - (len(message) * 8) - 32
102        for i in range(start, extent, -1):
103            self.fill(0)
104            self.text(message, i, 0, 1)
105            self.show()
106            utime.sleep_ms(_SCROLL_SPEED_NORMAL)
107
108    def show(self):
109        """Update display"""
110        # Write line per line on the matrices
111        for line in range(8):
112            self.cs(0)
113
114            for matrix in range(self.nb_matrices):
115                # Guess where the matrix is placed
116                row, col = divmod(matrix, self.cols)
117                # Compute where the data starts
118                if not self.rotate_180:
119                    offset = row * 8 * self.cols
120                    index = col + line * self.cols + offset
121                else:
122                    offset = 8 * self.cols - row * (8 - line) * self.cols
123                    index = (7 - line) * self.cols + col - offset
124
125                self.spi.write(bytearray([_DIGIT_0 + line, self.buffer[index]]))
126
127            self.cs(1)

2. max7219.py driver library:

  1# Jeff Brown max7219 driver library
  2from machine import Pin, SPI
  3from micropython import const
  4import framebuf, utime
  5
  6_DIGIT_0 = const(0x1)
  7
  8_DECODE_MODE = const(0x9)
  9_NO_DECODE = const(0x0)
 10
 11_INTENSITY = const(0xa)
 12_INTENSITY_MIN = const(0x0)
 13
 14_SCAN_LIMIT = const(0xb)
 15_DISPLAY_ALL_DIGITS = const(0x7)
 16
 17_SHUTDOWN = const(0xc)
 18_SHUTDOWN_MODE = const(0x0)
 19_NORMAL_OPERATION = const(0x1)
 20
 21_DISPLAY_TEST = const(0xf)
 22_DISPLAY_TEST_NORMAL_OPERATION = const(0x0)
 23
 24_MATRIX_SIZE = const(8)
 25
 26# _SCROLL_SPEED_NORMAL is ms to delay (slow) scrolling text.
 27_SCROLL_SPEED_NORMAL = 100
 28
 29class Max7219(framebuf.FrameBuffer):
 30    """
 31    Driver for MAX7219 8x8 LED matrices
 32    https://github.com/vrialland/micropython-max7219
 33    Example for ESP8266 with 2x4 matrices (one on top, one on bottom),
 34    so we have a 32x16 display area:
 35    >>> from machine import Pin, SPI
 36    >>> from max7219 import Max7219
 37    >>> spi = SPI(1, baudrate=10000000)
 38    >>> screen = Max7219(32, 16, spi, Pin(15))
 39    >>> screen.rect(0, 0, 32, 16, 1)  # Draws a frame
 40    >>> screen.text('Hi!', 4, 4, 1)
 41    >>> screen.show()
 42    On some matrices, the display is inverted (rotated 180°), in this case
 43     you can use `rotate_180=True` in the class constructor.
 44    """
 45
 46    def __init__(self, width, height, spi, cs, rotate_180=False):
 47        # Pins setup
 48        self.spi = spi
 49        self.cs = cs
 50        self.cs.init(Pin.OUT, True)
 51
 52        # Dimensions
 53        self.width = width
 54        self.height = height
 55        # Guess matrices disposition
 56        self.cols = width // _MATRIX_SIZE
 57        self.rows = height // _MATRIX_SIZE
 58        self.nb_matrices = self.cols * self.rows
 59        self.rotate_180 = rotate_180
 60        # 1 bit per pixel (on / off) -> 8 bytes per matrix
 61        self.buffer = bytearray(width * height // 8)
 62        format = framebuf.MONO_HLSB if not self.rotate_180 else framebuf.MONO_HMSB
 63        super().__init__(self.buffer, width, height, format)
 64
 65        # Init display
 66        self.init_display()
 67
 68    def _write_command(self, command, data):
 69        """Write command on SPI"""
 70        cmd = bytearray([command, data])
 71        self.cs(0)
 72        for matrix in range(self.nb_matrices):
 73            self.spi.write(cmd)
 74        self.cs(1)
 75
 76    def init_display(self):
 77        """Init hardware"""
 78        for command, data in (
 79                (_SHUTDOWN, _SHUTDOWN_MODE),  # Prevent flash during init
 80                (_DECODE_MODE, _NO_DECODE),
 81                (_DISPLAY_TEST, _DISPLAY_TEST_NORMAL_OPERATION),
 82                (_INTENSITY, _INTENSITY_MIN),
 83                (_SCAN_LIMIT, _DISPLAY_ALL_DIGITS),
 84                (_SHUTDOWN, _NORMAL_OPERATION),  # Let's go
 85        ):
 86            self._write_command(command, data)
 87
 88        self.fill(0)
 89        self.show()
 90
 91    def brightness(self, value):
 92        # Set display brightness (0 to 15)
 93        if not 0 <= value < 16:
 94            raise ValueError('Brightness must be between 0 and 15')
 95        self._write_command(_INTENSITY, value)
 96
 97    def marquee(self, message):
 98        start = 33
 99        extent = 0 - (len(message) * 8) - 32
100        for i in range(start, extent, -1):
101            self.fill(0)
102            self.text(message, i, 0, 1)
103            self.show()
104            utime.sleep_ms(_SCROLL_SPEED_NORMAL)
105
106    def show(self):
107        """Update display"""
108        # Write line per line on the matrices
109        for line in range(8):
110            self.cs(0)
111
112            for matrix in range(self.nb_matrices):
113                # Guess where the matrix is placed
114                row, col = divmod(matrix, self.cols)
115                # Compute where the data starts
116                if not self.rotate_180:
117                    offset = row * 8 * self.cols
118                    index = col + line * self.cols + offset
119                else:
120                    offset = 8 * self.cols - row * (8 - line) * self.cols
121                    index = (7 - line) * self.cols + col - offset
122
123                self.spi.write(bytearray([_DIGIT_0 + line, self.buffer[index]]))
124
125            self.cs(1)

References And Credits

  1. Jeff Brown max7219 library: https://github.com/jgbrown32/ESP8266_MAX7219


Posts in this series



No comments yet!

GitHub-flavored Markdown & a sane subset of HTML is supported.