[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/100/original/thumb.jpg?1723576569","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n        \u003cp\u003eThe other day I decided to level up my understanding of how these popular \"HUB75\" style \u003ca href=\"https://www.adafruit.com/category/327\"\u003eRGB LED matrices\u003c/a\u003e work. So I sat down with the intent to write the most basic bit bang example I could come up with. Here I share the resulting code, written in CircuitPython.\u003c/p\u003e\n\u003cp\u003eThere are plenty of Google discoverable write ups on these that go into lots more detail, discussing the shift registers, scan lines, etc. It's worth reading a few of those first. We found these to be useful:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://www.sparkfun.com/news/2650\"\u003eEverything You Didn't Want to Know About RGB Matrix Panels\u003c/a\u003e - A good write up with some nice animations by Nick Poole at Sparkfun who needed to also understand how these work to modify an Arduino library for an unsupported panel type.\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://bikerglen.com/projects/lighting/led-panel-1up/\"\u003eRGB LED Panel Driver Tutorial\u003c/a\u003e - read the \"Theory of Operation\" section.\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://justanotherelectronicsblog.com/?p=636\"\u003eDriving a 64*64 RGB LED panel with an FPGA\u003c/a\u003e - read everything up to \"Driving the matrix\".\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThere's also a brief description in the Adafruit Learn System here:\u003c/p\u003e\n      \n\n\n\n","metadata":{}},{"element_type":"button","content":"https://learn.adafruit.com/32x16-32x32-rgb-led-matrix/how-the-matrix-works","metadata":{"class":"btn btn-large btn-block btn-primary","button_type":"primary","target":"_blank","zip_folder":"false"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThese discussions can quickly get a bit complex, since there are a lot of pieces that work together in concert. However, there are a lot of things that are redundant. For example, how each red/green/blue LED is controlled is the same. Also, the matrices are typically split into upper and lower halves, but each is updated in the same way - R1/G1/B1 for the upper half, R2/G2/B2 for the lower half. So to keep things as simple as possible, this example does the following:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eOnly use a single color\u003c/li\u003e\n\u003cli\u003eOnly work with the upper half of the matrix\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis greatly reduces the total number of pins needed. It also really helps focus on the basic way in which data is moved into the matrices. By using just the upper half, the R2, G2, B2 pins can be left unused. And by focusing on just one color, we can use any \u003cstrong\u003eone\u003c/strong\u003e of R1, G1, or B1.\u003c/p\u003e\n\u003cp\u003eA Feather RP2040 was used with the following pin connections (Feather to Matrix):\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e13 to R1 (or G1 or B1 - your choice!)\u003c/li\u003e\n\u003cli\u003eSCK to CLK\u003c/li\u003e\n\u003cli\u003eMOSI to LAT\u003c/li\u003e\n\u003cli\u003eMISO to OE\u003c/li\u003e\n\u003cli\u003eA0 to A\u003c/li\u003e\n\u003cli\u003eA1 to B\u003c/li\u003e\n\u003cli\u003eA2 to C\u003c/li\u003e\n\u003cli\u003eA3 to D\u003c/li\u003e\n\u003cli\u003eGND to GND\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThere's nothing special about these pin choices - \u003cstrong\u003eany digital pins could be used\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThe matrix used was an \u003ca href=\"https://www.adafruit.com/product/607\" target=\"_blank\"\u003eAdafruit 32x32 4mm pitch panel (PID 607)\u003c/a\u003e.\u003c/p\u003e\n      \n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/101/original/wiring.png?1723576595","metadata":{"caption":""}},{"element_type":"alert","content":"\u003cp\u003e5V power was also connected to the matrix separately (not shown).\u003c/p\u003e","metadata":{"class":"element row-fluid alert-element build-alert alert-info","markdown":"5V power was also connected to the matrix separately (not shown).","alert_type":"info"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eAnd here's the CircuitPython code example. It's very crude. It's not even PEP8'd.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"import board\nimport digitalio\n\n#--| PIN DEFINITIONS |-----------------------------------------------\nR1 = digitalio.DigitalInOut(board.D13); R1.switch_to_output(False)\n\nAA = digitalio.DigitalInOut(board.A0); AA.switch_to_output(False)\nAB = digitalio.DigitalInOut(board.A1); AB.switch_to_output(False)\nAC = digitalio.DigitalInOut(board.A2); AC.switch_to_output(False)\nAD = digitalio.DigitalInOut(board.A3); AD.switch_to_output(False)\n\nCLK = digitalio.DigitalInOut(board.SCK); CLK.switch_to_output(False)\nLAT = digitalio.DigitalInOut(board.MOSI); LAT.switch_to_output(False)\nOE = digitalio.DigitalInOut(board.MISO); OE.switch_to_output(False)\n#--------------------------------------------------------------------\n\n# define a bitmap\n# 16 rows of 32 columns\n# 0=OFF, 1=ON\nbmp = (\n  (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),\n  (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),\n  (0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0),\n  (0,0,0,1,0,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0),\n  (0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0),\n  (0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0),\n  (0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0,0,0,1,1,0,1,0,0),\n  (0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,1,0,0,0,1,1,0,0,1,0),\n  (0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0),\n  (0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0),\n  (0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0),\n  (0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0,0),\n  (0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0),\n  (0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0),\n  (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0),\n  (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),\n)\n\nCOLS = len(bmp[0])\nROWS = len(bmp)\nprint(f\"ROWS={ROWS}, COLS={COLS}\")\n\nwhile True:\n  for row in range(ROWS):\n\n    # clock row data out\n    for col in range(COLS):\n      # set color bit\n      R1.value = bmp[row][col]\n\n      # clock it out\n      CLK.value = True\n      CLK.value = False\n\n    # disable output\n    OE.value = True\n\n    # set row address bits\n    AA.value = (row \u003e\u003e 0) \u0026 0x01\n    AB.value = (row \u003e\u003e 1) \u0026 0x01\n    AC.value = (row \u003e\u003e 2) \u0026 0x01\n    AD.value = (row \u003e\u003e 3) \u0026 0x01\n\n    # latch it out\n    LAT.value = True\n    LAT.value = False\n\n    # enable output\n    OE.value = False","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n        \u003cp\u003eRunning that code should produce the output shown in the banner image at the top of this Playground Note. \u003cstrong\u003eThere's a good chance the output will be glitchy.\u003c/strong\u003e Nothing special is being done to control any of the timings. And bit banging this from CircuitPython is much slower than a real matrix driver. But this bare bones example shows the basic way in which the RGB matrices are controlled.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eTry moving the matrix connection from R1 to G1 or B1 and running the code again. It should show the same thing, just in that different color. Could even connect to one of R2, G2, or B2 and the result should show up on the lower half of the matrix.\u003c/p\u003e\n      ","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n        \u003ch2\u003eWhat About RGB Colors?\u003c/h2\u003e\n\u003cp\u003eThat would require more code and connecting to more pins. However, it would be the exact same logic used for each color channel as shown above for one channel. For example, if you connected to R1, G1, and B1 and then added code to clock out R1=1, G1=0, B1=1, that pixel would end up being red + blue = magenta.\u003c/p\u003e\n\u003ch2\u003eWhat About Controlling The Full Matrix?\u003c/h2\u003e\n\u003cp\u003eAgain, just more code and wiring connections. Upper half is controlled by R1, G1, and B1. Lower half is controlled by R2, G2, and B2. The basic code logic would be the same though.\u003c/p\u003e\n\u003ch2\u003eWhat About N-Bit Depth Colors?\u003c/h2\u003e\n\u003cp\u003eThat's where things get interesting and complex. It basically requires some form of PWM control of each and every LED. Doing that efficiently and quickly across the entire LED matrix is non-trivial. But regardless - all that work is done by the host controller. The RGB LED matrix itself is a very simple device.\u003c/p\u003e\n      \n\n\n","metadata":{}}]