├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── githubci.yml ├── .gitignore ├── README.md ├── examples ├── animated_gif │ ├── .matrixportal.test.only │ └── animated_gif.ino ├── doublebuffer_scrolltext │ └── doublebuffer_scrolltext.ino ├── pixeldust │ └── pixeldust.ino ├── simple │ └── simple.ino └── tiled │ └── tiled.ino ├── library.properties └── src ├── Adafruit_Protomatter.cpp ├── Adafruit_Protomatter.h ├── arch ├── arch.h ├── esp32-c3.h ├── esp32-c6.h ├── esp32-common.h ├── esp32-s2.h ├── esp32-s3.h ├── esp32.h ├── nrf52.h ├── rp2040.h ├── samd-common.h ├── samd21.h ├── samd51.h ├── stm32.h └── teensy4.h ├── core.c └── core.h /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for opening an issue on an Adafruit Arduino library repository. To 2 | improve the speed of resolution please review the following guidelines and 3 | common troubleshooting steps below before creating the issue: 4 | 5 | - **Do not use GitHub issues for troubleshooting projects and issues.** Instead use 6 | the forums at http://forums.adafruit.com to ask questions and troubleshoot why 7 | something isn't working as expected. In many cases the problem is a common issue 8 | that you will more quickly receive help from the forum community. GitHub issues 9 | are meant for known defects in the code. If you don't know if there is a defect 10 | in the code then start with troubleshooting on the forum first. 11 | 12 | - **If following a tutorial or guide be sure you didn't miss a step.** Carefully 13 | check all of the steps and commands to run have been followed. Consult the 14 | forum if you're unsure or have questions about steps in a guide/tutorial. 15 | 16 | - **For Arduino projects check these very common issues to ensure they don't apply**: 17 | 18 | - For uploading sketches or communicating with the board make sure you're using 19 | a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes 20 | very hard to tell the difference between a data and charge cable! Try using the 21 | cable with other devices or swapping to another cable to confirm it is not 22 | the problem. 23 | 24 | - **Be sure you are supplying adequate power to the board.** Check the specs of 25 | your board and plug in an external power supply. In many cases just 26 | plugging a board into your computer is not enough to power it and other 27 | peripherals. 28 | 29 | - **Double check all soldering joints and connections.** Flakey connections 30 | cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. 31 | 32 | - **Ensure you are using an official Arduino or Adafruit board.** We can't 33 | guarantee a clone board will have the same functionality and work as expected 34 | with this code and don't support them. 35 | 36 | If you're sure this issue is a defect in the code and checked the steps above 37 | please fill in the following fields to provide enough troubleshooting information. 38 | You may delete the guideline and text above to just leave the following details: 39 | 40 | - Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** 41 | 42 | - Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO 43 | VERSION HERE** 44 | 45 | - List the steps to reproduce the problem below (if possible attach a sketch or 46 | copy the sketch code in too): **LIST REPRO STEPS BELOW** 47 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for creating a pull request to contribute to Adafruit's GitHub code! 2 | Before you open the request please review the following guidelines and tips to 3 | help it be more easily integrated: 4 | 5 | - **Describe the scope of your change--i.e. what the change does and what parts 6 | of the code were modified.** This will help us understand any risks of integrating 7 | the code. 8 | 9 | - **Describe any known limitations with your change.** For example if the change 10 | doesn't apply to a supported platform of the library please mention it. 11 | 12 | - **Please run any tests or examples that can exercise your modified code.** We 13 | strive to not break users of the code and running tests/examples helps with this 14 | process. 15 | 16 | Thank you again for contributing! We will try to test and integrate the change 17 | as soon as we can, but be aware we have many GitHub repositories to manage and 18 | can't immediately respond to every request. There is no need to bump or check in 19 | on a pull request (it will clutter the discussion of the request). 20 | 21 | Also don't be worried if the request is closed or not integrated--sometimes the 22 | priorities of Adafruit's GitHub code (education, ease of use) might not match the 23 | priorities of the pull request. Don't fret, the open source community thrives on 24 | forks and GitHub makes it easy to keep your changes in a forked repo. 25 | 26 | After reviewing the guidelines above you can delete this text from the pull request. 27 | -------------------------------------------------------------------------------- /.github/workflows/githubci.yml: -------------------------------------------------------------------------------- 1 | name: Arduino Library CI 2 | 3 | on: [pull_request, push, repository_dispatch] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | arduino-platform: ["metro_m0", "metro_m4", "metroesp32s2", "feather_esp32s3", "feather_rp2040", "nrf52840", "esp32"] 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.8' 18 | - uses: actions/checkout@v4 19 | - uses: actions/checkout@v4 20 | with: 21 | repository: adafruit/ci-arduino 22 | path: ci 23 | 24 | - name: pre-install 25 | run: bash ci/actions_install.sh 26 | 27 | - name: test platforms 28 | run: python3 ci/build_platform.py ${{ matrix.arduino-platform }} 29 | 30 | - name: clang 31 | run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . 32 | 33 | - name: doxygen 34 | env: 35 | GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} 36 | PRETTYNAME : "Adafruit Protomatter" 37 | run: bash ci/doxy_gen_and_deploy.sh 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Our handy .gitignore for automation ease 2 | Doxyfile* 3 | doxygen_sqlite3.db 4 | html 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adafruit_Protomatter [![Build Status](https://github.com/adafruit/Adafruit_Protomatter/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_Protomatter/actions) 2 | 3 | "I used protomatter in the Genesis matrix." - David Marcus, Star Trek III 4 | 5 | Code for driving HUB75-style RGB LED matrices, targeted at 32-bit MCUs 6 | using brute-force GPIO (that is, not relying on DMA or other specialized 7 | peripherals beyond a timer interrupt, goal being portability). 8 | 9 | # Matrix Concepts and Jargon 10 | 11 | HUB75 RGB LED matrices are basically a set of six concurrent shift register 12 | chains, each with one output bit per column, the six chains being red, green 13 | and blue bits for two non-adjacent rows, plus a set of row drivers (each 14 | driving the aforementioned two rows) selected by a combination of address 15 | lines. The number of address lines determines the overall matrix height 16 | (3 to 5 bits is common...as an example, 3 address lines = 2^3 = 8 distinct 17 | address line combinations, each driving two rows = 16 pixels high). Address 18 | 0 enables rows 0 and height/2, address 1 enables rows 1 and height/2+1, etc. 19 | Shift register chain length determines matrix width...32 and 64 pixels are 20 | common...matrices can be chained to increase width, a 64-pixel wide matrix 21 | is equivalent to two 32-pixel chained matrices, and so forth. 22 | 23 | These matrices render only ONE BIT each for red, green and blue, they DO NOT 24 | natively display full color and must be quickly refreshed by the driving 25 | microcontroller, basically PWM-ing the intermediate shades (this in addition 26 | to the row scanning that must be performed). 27 | 28 | There are a few peculiar RGB LED matrices that have the same physical 29 | connection but work a bit differently -- they might have only have three 30 | shift register chains rather than six, or might use a shift register for 31 | the row selection rather than a set of address lines. The code presented 32 | here DOES NOT support these matrix variants. Aim is to provide support for 33 | all HUB75 matrices in the Adafruit shop. Please don't submit pull requests 34 | for these other matrices as we have no means to test them. If you require 35 | this functionality, it's OK to create a fork of the code, which Git can 36 | help keep up-to-date with any future changes here! 37 | 38 | # Hardware Requirements and Jargon 39 | 40 | The common ground for architectures to support this library: 41 | 42 | * 32-bit device (e.g. ARM core, ESP32 and others) 43 | * One or more 32-bit GPIO PORTs with atomic (single-cycle, not 44 | read-modify-write) bitmask SET and CLEAR registers. A bitmask TOGGLE 45 | register, if present, may improve performance but is NOT required. 46 | * There may be performance or storage benefits if the architecture tolerates 47 | 8-bit or word-aligned 16-bit accesses within the 32-bit PORT registers 48 | (e.g. writing just one of four bytes, rather than the whole 32 bits), but 49 | this is NOT a hardware requirement. Also, the library does not use any 50 | unaligned accesses (i.e. "middle word" of a 32-bit register), even if a 51 | device tolerates such. 52 | 53 | # Software Components 54 | 55 | This repository currently consists of: 56 | 57 | * An Arduino C++ library (files Adafruit_Protomatter.cpp and 58 | Adafruit_Protomatter.h, plus the "examples" directory). The Arduino code 59 | is dependent on the Adafruit_GFX library. 60 | 61 | * An underlying C library (files core.c, core.h and headers in the arch 62 | directory) that might be adaptable to other runtime environments (e.g. 63 | CircuitPython). 64 | 65 | # Arduino Library 66 | 67 | This supersedes the RGBmatrixPanel library on non-AVR devices, as the older 68 | library has painted itself into a few corners. The newer library uses a 69 | single constructor for all matrix setups, potentially handling parallel 70 | chains (not yet fully implemented), various matrix sizes and chain lengths, 71 | and variable bit depths from 1 to 6 (refresh rate is a function of all of 72 | these). Note however that it is NOT A DROP-IN REPLACEMENT for RGBmatrixPanel. 73 | The constructor is entirely different, and there are several changes in the 74 | available functions. Also, all colors in the new library are specified as 75 | 5/6/5-bit RGB (as this is what the GFX library GFXcanvas16 type uses, being 76 | aimed at low-cost color LCD displays), even if the matrix is configured for 77 | a lower bit depth (colors will be decimated/quantized in this case). 78 | 79 | It does have some new limitations, mostly significant RAM overhead (hence 80 | no plans for AVR port) and (with a few exceptions) that all RGB data pins 81 | and the clock pin MUST be on the same PORT register (e.g. all PORTA or PORTB 82 | ,can't intermix). RAM overhead is somewhat reduced (but still large) if 83 | those pins are all in a single 8-bit byte within the PORT (they do not need 84 | to be contiguous or sequential within this byte, if for instance it makes 85 | PCB routing easier, but they should all aim for a single byte). Other pins 86 | (matrix address lines, latch and output enable) can reside on any PORT or bit. 87 | 88 | # C Library 89 | 90 | The underlying C library is focused on *driving* the matrix and does not 91 | provide any drawing operations of its own. That must be handled by 92 | higher-level code, as in the Arduino wrapper which uses the Adafruit_GFX 93 | drawing functions. 94 | 95 | The C code has the same limitations as the Arduino library: all RGB data 96 | pins and the clock pin MUST be on the same PORT register, and it's most 97 | memory efficient (though still slightly gluttonous) if those pins are all 98 | within the same 8-bit byte within the PORT (they do not need to be 99 | contiguous or sequential within that byte). Other pins (matrix address lines, 100 | latch and output enable) can reside on any PORT or bit. 101 | 102 | When adapting this code to new devices (e.g. iMX) or new runtime environments 103 | (e.g. CircuitPython), goal is to put all the device- or platform-specific 104 | code into a new header file in the arch directory (or completely separate 105 | source files, as in the Arduino library .cpp and .h). core.c contains only 106 | the device-neutral bitbang code and should not have any "#ifdef DEVICE"- or 107 | "#ifdef ENVIRONMENT"-like lines (exception for the 565 color conversion 108 | functions, since the internal representation is common to both Arduino and 109 | CircuitPython). Macros for things like getting a PORT register address from 110 | a pin, or setting up a timer peripheral, all occur in the arch header files, 111 | which are ONLY #included by core.c (to prevent problems like multiple 112 | instances of ISR functions, which must be singularly declared at 113 | compile-time). 114 | 115 | Most macros and functions begin with the prefix **\_PM\_** in order to 116 | avoid naming collisions with other code (exception being static functions, 117 | which can't be seen outside their source file). 118 | 119 | # Pull Requests 120 | 121 | If you encounter artifacts (noise, sparkles, dropouts and other issues) and 122 | it seems to resolve by adjusting the NOP counts, please do not submit this 123 | as a PR claiming a fix. Quite often what improves stability for one matrix 124 | type can make things worse for other types. Instead, open an issue and 125 | describe the hardware (both microcontroller and RGB matrix) and what worked 126 | for you. A general solution working across all matrix types typically 127 | involves monitoring the signals on a logic analyzer and aiming for a 50% 128 | duty cycle on the CLK signal, 20 MHz or less, and then testing across a 129 | wide variety of different matrix types to confirm; trial and error on just 130 | a single matrix type is problematic. Maintainers: this goes for you too. 131 | Don't merge a "fix" unless you've checked it out on a 'scope and on tested 132 | across a broad range of matrices. 133 | -------------------------------------------------------------------------------- /examples/animated_gif/.matrixportal.test.only: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_Protomatter/d55e3bf83306b19651c1905bfb984f3768c2569e/examples/animated_gif/.matrixportal.test.only -------------------------------------------------------------------------------- /examples/animated_gif/animated_gif.ino: -------------------------------------------------------------------------------- 1 | // Play GIFs from CIRCUITPY drive (USB-accessible filesystem) to LED matrix. 2 | // ***DESIGNED FOR ADAFRUIT MATRIXPORTAL***, but may run on some other M4, 3 | // M0, ESP32S3 and nRF52 boards (relies on TinyUSB stack). As written, runs 4 | // on 64x32 pixel matrix, this can be changed by editing the WIDTH and HEIGHT 5 | // definitions. See the "simple" example for a run-down on matrix config. 6 | // Adapted from examples from Larry Bank's AnimatedGIF library and 7 | // msc_external_flash example in Adafruit_TinyUSB_Arduino. 8 | // Prerequisite libraries: 9 | // - Adafruit_Protomatter 10 | // - Adafruit_SPIFlash 11 | // - Adafruit_TinyUSB 12 | // - SdFat (Adafruit fork) 13 | // - AnimatedGIF 14 | // Set ENABLE_EXTENDED_TRANSFER_CLASS and FAT12_SUPPORT in SdFatConfig.h. 15 | // Select Tools->USB Stack->TinyUSB before compiling. 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // CONFIGURABLE SETTINGS --------------------------------------------------- 25 | 26 | char GIFpath[] = "/gifs"; // Absolute path to GIFs on CIRCUITPY drive 27 | uint16_t GIFminimumTime = 10; // Min. repeat time (seconds) until next GIF 28 | #define WIDTH 64 // Matrix width in pixels 29 | #define HEIGHT 32 // Matrix height in pixels 30 | // Maximim matrix height is 32px on most boards, 64 on MatrixPortal if the 31 | // 'E' jumper is set. 32 | 33 | // FLASH FILESYSTEM STUFF -------------------------------------------------- 34 | 35 | // External flash macros for QSPI or SPI are defined in board variant file. 36 | #if defined(ARDUINO_ARCH_ESP32) 37 | static Adafruit_FlashTransport_ESP32 flashTransport; 38 | #elif defined(EXTERNAL_FLASH_USE_QSPI) 39 | Adafruit_FlashTransport_QSPI flashTransport; 40 | #elif defined(EXTERNAL_FLASH_USE_SPI) 41 | Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, 42 | EXTERNAL_FLASH_USE_SPI); 43 | #else 44 | #error No QSPI/SPI flash are defined in your board variant.h! 45 | #endif 46 | 47 | Adafruit_SPIFlash flash(&flashTransport); 48 | FatFileSystem filesys; // Filesystem object from SdFat 49 | Adafruit_USBD_MSC usb_msc; // USB mass storage object 50 | 51 | // RGB MATRIX (PROTOMATTER) LIBRARY STUFF ---------------------------------- 52 | 53 | #if defined(_VARIANT_MATRIXPORTAL_M4_) 54 | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 55 | uint8_t addrPins[] = {17, 18, 19, 20, 21}; // 16/32/64 pixels tall 56 | uint8_t clockPin = 14; 57 | uint8_t latchPin = 15; 58 | uint8_t oePin = 16; 59 | #define BACK_BUTTON 2 60 | #define NEXT_BUTTON 3 61 | #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) 62 | uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; 63 | uint8_t addrPins[] = {45, 36, 48, 35, 21}; // 16/32/64 pixels tall 64 | uint8_t clockPin = 2; 65 | uint8_t latchPin = 47; 66 | uint8_t oePin = 14; 67 | #define BACK_BUTTON 6 68 | #define NEXT_BUTTON 7 69 | #elif defined(_VARIANT_METRO_M4_) 70 | uint8_t rgbPins[] = {2, 3, 4, 5, 6, 7}; 71 | uint8_t addrPins[] = {A0, A1, A2, A3}; // 16 or 32 pixels tall 72 | uint8_t clockPin = A4; 73 | uint8_t latchPin = 10; 74 | uint8_t oePin = 9; 75 | #elif defined(_VARIANT_FEATHER_M4_) 76 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 77 | uint8_t addrPins[] = {A5, A4, A3, A2}; // 16 or 32 pixels tall 78 | uint8_t clockPin = 13; 79 | uint8_t latchPin = 0; 80 | uint8_t oePin = 1; 81 | #endif 82 | #if HEIGHT == 16 83 | #define NUM_ADDR_PINS 3 84 | #elif HEIGHT == 32 85 | #define NUM_ADDR_PINS 4 86 | #elif HEIGHT == 64 87 | #define NUM_ADDR_PINS 5 88 | #endif 89 | 90 | Adafruit_Protomatter matrix(WIDTH, 6, 1, rgbPins, NUM_ADDR_PINS, addrPins, 91 | clockPin, latchPin, oePin, true); 92 | 93 | // ANIMATEDGIF LIBRARY STUFF ----------------------------------------------- 94 | 95 | AnimatedGIF GIF; 96 | File GIFfile; 97 | int16_t xPos = 0, yPos = 0; // Top-left pixel coord of GIF in matrix space 98 | 99 | // FILE ACCESS FUNCTIONS REQUIRED BY ANIMATED GIF LIB ---------------------- 100 | 101 | // Pass in ABSOLUTE PATH of GIF file to open 102 | void *GIFOpenFile(const char *filename, int32_t *pSize) { 103 | GIFfile = filesys.open(filename); 104 | if (GIFfile) { 105 | *pSize = GIFfile.size(); 106 | return (void *)&GIFfile; 107 | } 108 | return NULL; 109 | } 110 | 111 | void GIFCloseFile(void *pHandle) { 112 | File *f = static_cast(pHandle); 113 | if (f) f->close(); 114 | } 115 | 116 | int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { 117 | int32_t iBytesRead = iLen; 118 | File *f = static_cast(pFile->fHandle); 119 | // If a file is read all the way to last byte, seek() stops working 120 | if ((pFile->iSize - pFile->iPos) < iLen) 121 | iBytesRead = pFile->iSize - pFile->iPos - 1; // ugly work-around 122 | if (iBytesRead <= 0) return 0; 123 | iBytesRead = (int32_t)f->read(pBuf, iBytesRead); 124 | pFile->iPos = f->position(); 125 | return iBytesRead; 126 | } 127 | 128 | int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) { 129 | File *f = static_cast(pFile->fHandle); 130 | f->seek(iPosition); 131 | pFile->iPos = (int32_t)f->position(); 132 | return pFile->iPos; 133 | } 134 | 135 | // Draw one line of image to matrix back buffer 136 | void GIFDraw(GIFDRAW *pDraw) { 137 | uint8_t *s; 138 | uint16_t *d, *usPalette, usTemp[320]; 139 | int x, y; 140 | 141 | y = pDraw->iY + pDraw->y; // current line in image 142 | 143 | // Vertical clip 144 | int16_t screenY = yPos + y; // current row on matrix 145 | if ((screenY < 0) || (screenY >= matrix.height())) return; 146 | 147 | usPalette = pDraw->pPalette; 148 | 149 | s = pDraw->pPixels; 150 | // Apply the new pixels to the main image 151 | if (pDraw->ucHasTransparency) { // if transparency used 152 | uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; 153 | int x, iCount; 154 | pEnd = s + pDraw->iWidth; 155 | x = 0; 156 | iCount = 0; // count non-transparent pixels 157 | while (x < pDraw->iWidth) { 158 | c = ucTransparent - 1; 159 | d = usTemp; 160 | while (c != ucTransparent && s < pEnd) { 161 | c = *s++; 162 | if (c == ucTransparent) { // done, stop 163 | s--; // back up to treat it like transparent 164 | } else { // opaque 165 | *d++ = usPalette[c]; 166 | iCount++; 167 | } 168 | } // while looking for opaque pixels 169 | if (iCount) { // any opaque pixels? 170 | span(usTemp, xPos + pDraw->iX + x, screenY, iCount); 171 | x += iCount; 172 | iCount = 0; 173 | } 174 | // no, look for a run of transparent pixels 175 | c = ucTransparent; 176 | while (c == ucTransparent && s < pEnd) { 177 | c = *s++; 178 | if (c == ucTransparent) 179 | iCount++; 180 | else 181 | s--; 182 | } 183 | if (iCount) { 184 | x += iCount; // skip these 185 | iCount = 0; 186 | } 187 | } 188 | } else { 189 | s = pDraw->pPixels; 190 | // Translate 8-bit pixels through RGB565 palette (already byte reversed) 191 | for (x = 0; x < pDraw->iWidth; x++) 192 | usTemp[x] = usPalette[*s++]; 193 | span(usTemp, xPos + pDraw->iX, screenY, pDraw->iWidth); 194 | } 195 | } 196 | 197 | // Copy a horizontal span of pixels from a source buffer to an X,Y position 198 | // in matrix back buffer, applying horizontal clipping. Vertical clipping is 199 | // handled in GIFDraw() above -- y can safely be assumed valid here. 200 | void span(uint16_t *src, int16_t x, int16_t y, int16_t width) { 201 | if (x >= matrix.width()) return; // Span entirely off right of matrix 202 | int16_t x2 = x + width - 1; // Rightmost pixel 203 | if (x2 < 0) return; // Span entirely off left of matrix 204 | if (x < 0) { // Span partially off left of matrix 205 | width += x; // Decrease span width 206 | src -= x; // Increment source pointer to new start 207 | x = 0; // Leftmost pixel is first column 208 | } 209 | if (x2 >= matrix.width()) { // Span partially off right of matrix 210 | width -= (x2 - matrix.width() + 1); 211 | } 212 | if(matrix.getRotation() == 0) { 213 | memcpy(matrix.getBuffer() + y * matrix.width() + x, src, width * 2); 214 | } else { 215 | while(x <= x2) { 216 | matrix.drawPixel(x++, y, *src++); 217 | } 218 | } 219 | } 220 | 221 | // FUNCTIONS REQUIRED FOR USB MASS STORAGE --------------------------------- 222 | 223 | static bool msc_changed = true; // Is set true on filesystem changes 224 | 225 | // Callback on READ10 command. 226 | int32_t msc_read_cb(uint32_t lba, void *buffer, uint32_t bufsize) { 227 | return flash.readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize : -1; 228 | } 229 | 230 | // Callback on WRITE10 command. 231 | int32_t msc_write_cb(uint32_t lba, uint8_t *buffer, uint32_t bufsize) { 232 | digitalWrite(LED_BUILTIN, HIGH); 233 | return flash.writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1; 234 | } 235 | 236 | // Callback on WRITE10 completion. 237 | void msc_flush_cb(void) { 238 | flash.syncBlocks(); // Sync with flash 239 | filesys.cacheClear(); // Clear filesystem cache to force refresh 240 | digitalWrite(LED_BUILTIN, LOW); 241 | msc_changed = true; 242 | } 243 | 244 | // Get number of files in a specified path that match extension ('filter'). 245 | // Pass in absolute path (e.g. "/" or "/gifs") and extension WITHOUT period 246 | // (e.g. "gif", NOT ".gif"). 247 | int16_t numFiles(const char *path, const char *filter) { 248 | File dir = filesys.open(path); 249 | if (!dir) return -1; 250 | char filename[256]; 251 | for(int16_t num_files = 0;;) { 252 | File entry = dir.openNextFile(); 253 | if (!entry) return num_files; // No more files 254 | entry.getName(filename, sizeof(filename) - 1); 255 | entry.close(); 256 | if (!entry.isDirectory() && // Skip directories 257 | strncmp(filename, "._", 2)) { // and Mac junk files 258 | char *extension = strrchr(filename, '.'); 259 | if (extension && !strcasecmp(&extension[1], filter)) num_files++; 260 | } 261 | } 262 | return -1; 263 | } 264 | 265 | // Return name of file (matching extension) by index (0 to numFiles()-1) 266 | char *filenameByIndex(const char *path, const char *filter, int16_t index) { 267 | static char filename[256]; // Must be static, we return a pointer to this! 268 | File entry, dir = filesys.open(path); 269 | if (!dir) return NULL; 270 | while(entry = dir.openNextFile()) { 271 | entry.getName(filename, sizeof(filename) - 1); 272 | entry.close(); 273 | if(!entry.isDirectory() && // Skip directories 274 | strncmp(filename, "._", 2)) { // and Mac junk files 275 | char *extension = strrchr(filename, '.'); 276 | if (extension && !strcasecmp(&extension[1], filter)) { 277 | if(!index--) { 278 | return filename; 279 | } 280 | } 281 | } 282 | } 283 | return NULL; 284 | } 285 | 286 | // SETUP FUNCTION - RUNS ONCE AT STARTUP ----------------------------------- 287 | 288 | void setup() { 289 | pinMode(LED_BUILTIN, OUTPUT); 290 | #if defined(BACK_BUTTON) 291 | pinMode(BACK_BUTTON, INPUT_PULLUP); 292 | #endif 293 | #if defined(NEXT_BUTTON) 294 | pinMode(NEXT_BUTTON, INPUT_PULLUP); 295 | #endif 296 | 297 | // USB mass storage / filesystem setup (do BEFORE Serial init) 298 | flash.begin(); 299 | // Set disk vendor id, product id and revision 300 | usb_msc.setID("Adafruit", "External Flash", "1.0"); 301 | // Set disk size, block size is 512 regardless of spi flash page size 302 | usb_msc.setCapacity(flash.pageSize() * flash.numPages() / 512, 512); 303 | usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); 304 | usb_msc.setUnitReady(true); // MSC is ready for read/write 305 | usb_msc.begin(); 306 | filesys.begin(&flash); // Start filesystem on the flash 307 | 308 | Serial.begin(115200); 309 | //while (!Serial); 310 | 311 | // Protomatter (RGB matrix) setup 312 | ProtomatterStatus status = matrix.begin(); 313 | Serial.print("Protomatter begin() status: "); 314 | Serial.println((int)status); 315 | matrix.fillScreen(0); 316 | matrix.show(); 317 | 318 | // GIF setup 319 | GIF.begin(LITTLE_ENDIAN_PIXELS); 320 | } 321 | 322 | // LOOP FUNCTION - RUNS REPEATEDLY UNTIL RESET / POWER OFF ----------------- 323 | 324 | int16_t GIFindex = -1; // Current file index in GIFpath 325 | int8_t GIFincrement = 1; // +1 = next GIF, -1 = prev, 0 = same 326 | uint32_t GIFstartTime = 0; // When current GIF started playing 327 | bool GIFisOpen = false; // True if GIF is currently open 328 | 329 | void loop() { 330 | if (msc_changed) { // If filesystem has changed... 331 | msc_changed = false; // Clear flag 332 | GIFincrement = 1; // Set index to next file when we resume here 333 | return; // Prioritize USB, handled in calling func 334 | } 335 | 336 | #if defined(BACK_BUTTON) 337 | if(!digitalRead(BACK_BUTTON)) { 338 | GIFincrement = -1; // Back 339 | while(!digitalRead(BACK_BUTTON)); // Wait for release 340 | } 341 | #endif 342 | #if defined(NEXT_BUTTON) 343 | if(!digitalRead(NEXT_BUTTON)) { 344 | GIFincrement = 1; // Forward 345 | while(!digitalRead(NEXT_BUTTON)); // Wait for release 346 | } 347 | #endif 348 | 349 | if (GIFincrement) { // Change file? 350 | if (GIFisOpen) { // If currently playing, 351 | GIF.close(); // stop it 352 | GIFisOpen = false; 353 | } 354 | GIFindex += GIFincrement; // Fwd or back 1 file 355 | int num_files = numFiles(GIFpath, "GIF"); 356 | if(GIFindex >= num_files) GIFindex = 0; // 'Wrap around' file index 357 | else if(GIFindex < 0) GIFindex = num_files - 1; // both directions 358 | 359 | char *filename = filenameByIndex(GIFpath, "GIF", GIFindex); 360 | if (filename) { 361 | char fullname[sizeof GIFpath + 256]; 362 | sprintf(fullname, "%s/%s", GIFpath, filename); // Absolute path to GIF 363 | Serial.printf("Opening file '%s'\n", fullname); 364 | if (GIF.open(fullname, GIFOpenFile, GIFCloseFile, 365 | GIFReadFile, GIFSeekFile, GIFDraw)) { 366 | matrix.fillScreen(0); 367 | Serial.printf("GIF dimensions: %d x %d\n", 368 | GIF.getCanvasWidth(), GIF.getCanvasHeight()); 369 | xPos = (matrix.width() - GIF.getCanvasWidth()) / 2; // Center on matrix 370 | yPos = (matrix.height() - GIF.getCanvasHeight()) / 2; 371 | GIFisOpen = true; 372 | GIFstartTime = millis(); 373 | GIFincrement = 0; // Reset increment flag 374 | } 375 | } 376 | } else if(GIFisOpen) { 377 | if (GIF.playFrame(true, NULL) >= 0) { // Auto resets to start if needed 378 | matrix.show(); 379 | if ((millis() - GIFstartTime) >= (GIFminimumTime * 1000)) { 380 | GIFincrement = 1; // Minimum time has elapsed, proceed to next GIF 381 | } 382 | } else { 383 | GIFincrement = 1; // Decode error, proceed to next GIF 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /examples/doublebuffer_scrolltext/doublebuffer_scrolltext.ino: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------- 2 | Double-buffering (smooth animation) Protomatter library example. 3 | PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH. 4 | Comments here pare down many of the basics and focus on the new concepts. 5 | 6 | This example is written for a 64x32 matrix but can be adapted to others. 7 | ------------------------------------------------------------------------- */ 8 | 9 | #include 10 | #include // Large friendly font 11 | 12 | /* ---------------------------------------------------------------------- 13 | The RGB matrix must be wired to VERY SPECIFIC pins, different for each 14 | microcontroller board. This first section sets that up for a number of 15 | supported boards. 16 | ------------------------------------------------------------------------- */ 17 | 18 | #if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4 19 | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 20 | uint8_t addrPins[] = {17, 18, 19, 20, 21}; 21 | uint8_t clockPin = 14; 22 | uint8_t latchPin = 15; 23 | uint8_t oePin = 16; 24 | #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 25 | uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; 26 | uint8_t addrPins[] = {45, 36, 48, 35, 21}; 27 | uint8_t clockPin = 2; 28 | uint8_t latchPin = 47; 29 | uint8_t oePin = 14; 30 | #elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing 31 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 32 | uint8_t addrPins[] = {A5, A4, A3, A2}; 33 | uint8_t clockPin = 13; 34 | uint8_t latchPin = 0; 35 | uint8_t oePin = 1; 36 | #elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift) 37 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 38 | uint8_t addrPins[] = {A5, A4, A3, A2}; 39 | uint8_t clockPin = 13; 40 | uint8_t latchPin = 0; 41 | uint8_t oePin = 1; 42 | #elif defined(_SAMD21_) // Feather M0 variants 43 | uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13}; 44 | uint8_t addrPins[] = {0, 1, 2, 3}; 45 | uint8_t clockPin = SDA; 46 | uint8_t latchPin = 4; 47 | uint8_t oePin = 5; 48 | #elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout 49 | uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11}; 50 | uint8_t addrPins[] = {10, 5, 13, 9}; 51 | uint8_t clockPin = 12; 52 | uint8_t latchPin = PIN_SERIAL1_RX; 53 | uint8_t oePin = PIN_SERIAL1_TX; 54 | #elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM 55 | // M0/M4/RP2040 Matrix FeatherWing compatible: 56 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 57 | uint8_t addrPins[] = {A5, A4, A3, A2}; 58 | uint8_t clockPin = 13; // Must be on same port as rgbPins 59 | uint8_t latchPin = RX; 60 | uint8_t oePin = TX; 61 | #elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2 62 | // M0/M4/RP2040 Matrix FeatherWing compatible: 63 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 64 | uint8_t addrPins[] = {A5, A4, A3, A2}; 65 | uint8_t clockPin = 13; // Must be on same port as rgbPins 66 | uint8_t latchPin = RX; 67 | uint8_t oePin = TX; 68 | #elif defined(ESP32) 69 | // 'Safe' pins, not overlapping any peripherals: 70 | // GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33 71 | // Peripheral-overlapping pins, sorted from 'most expendible': 72 | // 16, 17 (RX, TX) 73 | // 25, 26 (A0, A1) 74 | // 18, 5, 9 (MOSI, SCK, MISO) 75 | // 22, 23 (SCL, SDA) 76 | uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21}; 77 | uint8_t addrPins[] = {16, 17, 25, 26}; 78 | uint8_t clockPin = 27; // Must be on same port as rgbPins 79 | uint8_t latchPin = 32; 80 | uint8_t oePin = 33; 81 | #elif defined(ARDUINO_TEENSY40) 82 | uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL 83 | uint8_t addrPins[] = {2, 3, 4, 5}; 84 | uint8_t clockPin = 23; // A9 85 | uint8_t latchPin = 6; 86 | uint8_t oePin = 9; 87 | #elif defined(ARDUINO_TEENSY41) 88 | uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8 89 | uint8_t addrPins[] = {2, 3, 4, 5}; 90 | uint8_t clockPin = 23; // A9 91 | uint8_t latchPin = 6; 92 | uint8_t oePin = 9; 93 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) 94 | // RP2040 support requires the Earle Philhower board support package; 95 | // will not compile with the Arduino Mbed OS board package. 96 | // The following pinout works with the Adafruit Feather RP2040 and 97 | // original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version). 98 | // Pin numbers here are GP## numbers, which may be different than 99 | // the pins printed on some boards' top silkscreen. 100 | uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12}; 101 | uint8_t addrPins[] = {25, 24, 29, 28}; 102 | uint8_t clockPin = 13; 103 | uint8_t latchPin = 1; 104 | uint8_t oePin = 0; 105 | #endif 106 | 107 | /* ---------------------------------------------------------------------- 108 | Matrix initialization is explained EXTENSIVELY in "simple" example sketch! 109 | It's very similar here, but we're passing "true" for the last argument, 110 | enabling double-buffering -- this permits smooth animation by having us 111 | draw in a second "off screen" buffer while the other is being shown. 112 | ------------------------------------------------------------------------- */ 113 | 114 | Adafruit_Protomatter matrix( 115 | 64, // Matrix width in pixels 116 | 6, // Bit depth -- 6 here provides maximum color options 117 | 1, rgbPins, // # of matrix chains, array of 6 RGB pins for each 118 | 4, addrPins, // # of address pins (height is inferred), array of pins 119 | clockPin, latchPin, oePin, // Other matrix control pins 120 | true); // HERE IS THE MAGIC FOR DOUBLE-BUFFERING! 121 | 122 | // Sundry globals used for animation --------------------------------------- 123 | 124 | int16_t textX; // Current text position (X) 125 | int16_t textY; // Current text position (Y) 126 | int16_t textMin; // Text pos. (X) when scrolled off left edge 127 | char str[64]; // Buffer to hold scrolling message text 128 | int16_t ball[3][4] = { 129 | { 3, 0, 1, 1 }, // Initial X,Y pos+velocity of 3 bouncy balls 130 | { 17, 15, 1, -1 }, 131 | { 27, 4, -1, 1 } 132 | }; 133 | uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup()) 134 | 135 | // SETUP - RUNS ONCE AT PROGRAM START -------------------------------------- 136 | 137 | void setup(void) { 138 | Serial.begin(9600); 139 | 140 | // Initialize matrix... 141 | ProtomatterStatus status = matrix.begin(); 142 | Serial.print("Protomatter begin() status: "); 143 | Serial.println((int)status); 144 | if(status != PROTOMATTER_OK) { 145 | // DO NOT CONTINUE if matrix setup encountered an error. 146 | for(;;); 147 | } 148 | 149 | // Unlike the "simple" example, we don't do any drawing in setup(). 150 | // But we DO initialize some things we plan to animate... 151 | 152 | // Set up the scrolling message... 153 | sprintf(str, "Adafruit %dx%d RGB LED Matrix", 154 | matrix.width(), matrix.height()); 155 | matrix.setFont(&FreeSansBold18pt7b); // Use nice bitmap font 156 | matrix.setTextWrap(false); // Allow text off edge 157 | matrix.setTextColor(0xFFFF); // White 158 | int16_t x1, y1; 159 | uint16_t w, h; 160 | matrix.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); // How big is it? 161 | textMin = -w; // All text is off left edge when it reaches this point 162 | textX = matrix.width(); // Start off right edge 163 | textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically 164 | // Note: when making scrolling text like this, the setTextWrap(false) 165 | // call is REQUIRED (to allow text to go off the edge of the matrix), 166 | // AND it must be BEFORE the getTextBounds() call (or else that will 167 | // return the bounds of "wrapped" text). 168 | 169 | // Set up the colors of the bouncy balls. 170 | ballcolor[0] = matrix.color565(0, 20, 0); // Dark green 171 | ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue 172 | ballcolor[2] = matrix.color565(20, 0, 0); // Dark red 173 | } 174 | 175 | // LOOP - RUNS REPEATEDLY AFTER SETUP -------------------------------------- 176 | 177 | void loop(void) { 178 | // Every frame, we clear the background and draw everything anew. 179 | // This happens "in the background" with double buffering, that's 180 | // why you don't see everything flicker. It requires double the RAM, 181 | // so it's not practical for every situation. 182 | 183 | matrix.fillScreen(0); // Fill background black 184 | 185 | // Draw the big scrolly text 186 | matrix.setCursor(textX, textY); 187 | matrix.print(str); 188 | 189 | // Update text position for next frame. If text goes off the 190 | // left edge, reset its position to be off the right edge. 191 | if((--textX) < textMin) textX = matrix.width(); 192 | 193 | // Draw the three bouncy balls on top of the text... 194 | for(byte i=0; i<3; i++) { 195 | // Draw 'ball' 196 | matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]); 197 | // Update ball's X,Y position for next frame 198 | ball[i][0] += ball[i][2]; 199 | ball[i][1] += ball[i][3]; 200 | // Bounce off edges 201 | if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1))) 202 | ball[i][2] *= -1; 203 | if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1))) 204 | ball[i][3] *= -1; 205 | } 206 | 207 | // AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX! 208 | 209 | matrix.show(); 210 | 211 | delay(20); // 20 milliseconds = ~50 frames/second 212 | } 213 | -------------------------------------------------------------------------------- /examples/pixeldust/pixeldust.ino: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------- 2 | "Pixel dust" Protomatter library example. As written, this is 3 | SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix. 4 | Change "HEIGHT" below for 64x64 matrix. Could also be adapted to other 5 | Protomatter-capable boards with an attached LIS3DH accelerometer. 6 | 7 | PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH, 8 | or "doublebuffer" for animation basics. 9 | ------------------------------------------------------------------------- */ 10 | 11 | #include // For I2C communication 12 | #include // For accelerometer 13 | #include // For sand simulation 14 | #include // For RGB matrix 15 | 16 | #define HEIGHT 32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX! 17 | #define WIDTH 64 // Matrix width (pixels) 18 | #define MAX_FPS 45 // Maximum redraw rate, frames/second 19 | 20 | #if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4 21 | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 22 | uint8_t addrPins[] = {17, 18, 19, 20, 21}; 23 | uint8_t clockPin = 14; 24 | uint8_t latchPin = 15; 25 | uint8_t oePin = 16; 26 | #else // MatrixPortal ESP32-S3 27 | uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; 28 | uint8_t addrPins[] = {45, 36, 48, 35, 21}; 29 | uint8_t clockPin = 2; 30 | uint8_t latchPin = 47; 31 | uint8_t oePin = 14; 32 | #endif 33 | 34 | #if HEIGHT == 16 35 | #define NUM_ADDR_PINS 3 36 | #elif HEIGHT == 32 37 | #define NUM_ADDR_PINS 4 38 | #elif HEIGHT == 64 39 | #define NUM_ADDR_PINS 5 40 | #endif 41 | 42 | Adafruit_Protomatter matrix( 43 | WIDTH, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins, 44 | clockPin, latchPin, oePin, true); 45 | 46 | Adafruit_LIS3DH accel = Adafruit_LIS3DH(); 47 | 48 | #define N_COLORS 8 49 | #define BOX_HEIGHT 8 50 | #define N_GRAINS (BOX_HEIGHT*N_COLORS*8) 51 | uint16_t colors[N_COLORS]; 52 | 53 | Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false); 54 | 55 | uint32_t prevTime = 0; // Used for frames-per-second throttle 56 | 57 | // SETUP - RUNS ONCE AT PROGRAM START -------------------------------------- 58 | 59 | void err(int x) { 60 | uint8_t i; 61 | pinMode(LED_BUILTIN, OUTPUT); // Using onboard LED 62 | for(i=1;;i++) { // Loop forever... 63 | digitalWrite(LED_BUILTIN, i & 1); // LED on/off blink to alert user 64 | delay(x); 65 | } 66 | } 67 | 68 | void setup(void) { 69 | Serial.begin(115200); 70 | //while (!Serial) delay(10); 71 | 72 | ProtomatterStatus status = matrix.begin(); 73 | Serial.printf("Protomatter begin() status: %d\n", status); 74 | 75 | if (!sand.begin()) { 76 | Serial.println("Couldn't start sand"); 77 | err(1000); // Slow blink = malloc error 78 | } 79 | 80 | if (!accel.begin(0x19)) { 81 | Serial.println("Couldn't find accelerometer"); 82 | err(250); // Fast bink = I2C error 83 | } 84 | accel.setRange(LIS3DH_RANGE_4_G); // 2, 4, 8 or 16 G! 85 | 86 | //sand.randomize(); // Initialize random sand positions 87 | 88 | // Set up initial sand coordinates, in 8x8 blocks 89 | int n = 0; 90 | for(int i=0; i (%d, %d)\n", n, xx + x, yy + y); 96 | sand.setPosition(n++, xx + x, yy + y); 97 | } 98 | } 99 | } 100 | Serial.printf("%d total pixels\n", n); 101 | 102 | colors[0] = matrix.color565(64, 64, 64); // Dark Gray 103 | colors[1] = matrix.color565(120, 79, 23); // Brown 104 | colors[2] = matrix.color565(228, 3, 3); // Red 105 | colors[3] = matrix.color565(255,140, 0); // Orange 106 | colors[4] = matrix.color565(255,237, 0); // Yellow 107 | colors[5] = matrix.color565( 0,128, 38); // Green 108 | colors[6] = matrix.color565( 0, 77,255); // Blue 109 | colors[7] = matrix.color565(117, 7,135); // Purple 110 | } 111 | 112 | // MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ---------------------------- 113 | 114 | void loop() { 115 | // Limit the animation frame rate to MAX_FPS. Because the subsequent sand 116 | // calculations are non-deterministic (don't always take the same amount 117 | // of time, depending on their current states), this helps ensure that 118 | // things like gravity appear constant in the simulation. 119 | uint32_t t; 120 | while(((t = micros()) - prevTime) < (1000000L / MAX_FPS)); 121 | prevTime = t; 122 | 123 | // Read accelerometer... 124 | sensors_event_t event; 125 | accel.getEvent(&event); 126 | //Serial.printf("(%0.1f, %0.1f, %0.1f)\n", event.acceleration.x, event.acceleration.y, event.acceleration.z); 127 | 128 | double xx, yy, zz; 129 | xx = event.acceleration.x * 1000; 130 | yy = event.acceleration.y * 1000; 131 | zz = event.acceleration.z * 1000; 132 | 133 | // Run one frame of the simulation 134 | sand.iterate(xx, yy, zz); 135 | 136 | //sand.iterate(-accel.y, accel.x, accel.z); 137 | 138 | // Update pixel data in LED driver 139 | dimension_t x, y; 140 | matrix.fillScreen(0x0); 141 | for(int i=0; i 16 | 17 | /* ---------------------------------------------------------------------- 18 | The RGB matrix must be wired to VERY SPECIFIC pins, different for each 19 | microcontroller board. This first section sets that up for a number of 20 | supported boards. Notes have been moved to the bottom of the code. 21 | ------------------------------------------------------------------------- */ 22 | 23 | #if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4 24 | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 25 | uint8_t addrPins[] = {17, 18, 19, 20, 21}; 26 | uint8_t clockPin = 14; 27 | uint8_t latchPin = 15; 28 | uint8_t oePin = 16; 29 | #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 30 | uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; 31 | uint8_t addrPins[] = {45, 36, 48, 35, 21}; 32 | uint8_t clockPin = 2; 33 | uint8_t latchPin = 47; 34 | uint8_t oePin = 14; 35 | #elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing 36 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 37 | uint8_t addrPins[] = {A5, A4, A3, A2}; 38 | uint8_t clockPin = 13; 39 | uint8_t latchPin = 0; 40 | uint8_t oePin = 1; 41 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) // Feather ESP32-C6 42 | // not featherwing compatible, but can 'hand wire' if desired 43 | uint8_t rgbPins[] = {6, A3, A1, A0, A2, 0}; 44 | uint8_t addrPins[] = {8, 5, 15, 7}; 45 | uint8_t clockPin = 14; 46 | uint8_t latchPin = RX; 47 | uint8_t oePin = TX; 48 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) // Feather ESP32-S2 49 | // M0/M4/RP2040 Matrix FeatherWing compatible: 50 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 51 | uint8_t addrPins[] = {A5, A4, A3, A2}; 52 | uint8_t clockPin = 13; // Must be on same port as rgbPins 53 | uint8_t latchPin = RX; 54 | uint8_t oePin = TX; 55 | #elif defined(ARDUINO_METRO_ESP32S2) // Metro ESP32-S2 56 | // Matrix Shield compatible: 57 | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 58 | uint8_t addrPins[] = {A0, A1, A2, A3}; 59 | uint8_t clockPin = 13; // Must be on same port as rgbPins 60 | uint8_t latchPin = 15; 61 | uint8_t oePin = 14; 62 | #elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift) 63 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 64 | uint8_t addrPins[] = {A5, A4, A3, A2}; 65 | uint8_t clockPin = 13; 66 | uint8_t latchPin = 0; 67 | uint8_t oePin = 1; 68 | #elif defined(_SAMD21_) // Feather M0 variants 69 | uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13}; 70 | uint8_t addrPins[] = {0, 1, 2, 3}; 71 | uint8_t clockPin = SDA; 72 | uint8_t latchPin = 4; 73 | uint8_t oePin = 5; 74 | #elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout 75 | uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11}; 76 | uint8_t addrPins[] = {10, 5, 13, 9}; 77 | uint8_t clockPin = 12; 78 | uint8_t latchPin = PIN_SERIAL1_RX; 79 | uint8_t oePin = PIN_SERIAL1_TX; 80 | #elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM 81 | // M0/M4/RP2040 Matrix FeatherWing compatible: 82 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 83 | uint8_t addrPins[] = {A5, A4, A3, A2}; 84 | uint8_t clockPin = 13; // Must be on same port as rgbPins 85 | uint8_t latchPin = RX; 86 | uint8_t oePin = TX; 87 | #elif defined(ESP32) 88 | // 'Safe' pins, not overlapping any peripherals: 89 | // GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33 90 | // Peripheral-overlapping pins, sorted from 'most expendible': 91 | // 16, 17 (RX, TX) 92 | // 25, 26 (A0, A1) 93 | // 18, 5, 9 (MOSI, SCK, MISO) 94 | // 22, 23 (SCL, SDA) 95 | uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21}; 96 | uint8_t addrPins[] = {16, 17, 25, 26}; 97 | uint8_t clockPin = 27; // Must be on same port as rgbPins 98 | uint8_t latchPin = 32; 99 | uint8_t oePin = 33; 100 | #elif defined(ARDUINO_TEENSY40) 101 | uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL 102 | uint8_t addrPins[] = {2, 3, 4, 5}; 103 | uint8_t clockPin = 23; // A9 104 | uint8_t latchPin = 6; 105 | uint8_t oePin = 9; 106 | #elif defined(ARDUINO_TEENSY41) 107 | uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8 108 | uint8_t addrPins[] = {2, 3, 4, 5}; 109 | uint8_t clockPin = 23; // A9 110 | uint8_t latchPin = 6; 111 | uint8_t oePin = 9; 112 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) 113 | // RP2040 support requires the Earle Philhower board support package; 114 | // will not compile with the Arduino Mbed OS board package. 115 | // The following pinout works with the Adafruit Feather RP2040 and 116 | // original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version). 117 | // Pin numbers here are GP## numbers, which may be different than 118 | // the pins printed on some boards' top silkscreen. 119 | uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12}; 120 | uint8_t addrPins[] = {25, 24, 29, 28}; 121 | uint8_t clockPin = 13; 122 | uint8_t latchPin = 1; 123 | uint8_t oePin = 0; 124 | #endif 125 | 126 | /* ---------------------------------------------------------------------- 127 | Okay, here's where the RGB LED matrix is actually declared... 128 | 129 | First argument is the matrix width, in pixels. Usually 32 or 130 | 64, but might go larger if you're chaining multiple matrices. 131 | 132 | Second argument is the "bit depth," which determines color 133 | fidelity, applied to red, green and blue (e.g. "4" here means 134 | 4 bits red, 4 green, 4 blue = 2^4 x 2^4 x 2^4 = 4096 colors). 135 | There is a trade-off between bit depth and RAM usage. Most 136 | programs will tend to use either 1 (R,G,B on/off, 8 colors, 137 | best for text, LED sand, etc.) or the maximum of 6 (best for 138 | shaded images...though, because the GFX library was designed 139 | for LCDs, only 5 of those bits are available for red and blue. 140 | 141 | Third argument is the number of concurrent (parallel) matrix 142 | outputs. THIS SHOULD ALWAYS BE "1" FOR NOW. Fourth is a uint8_t 143 | array listing six pins: red, green and blue data out for the 144 | top half of the display, and same for bottom half. There are 145 | hard constraints as to which pins can be used -- they must all 146 | be on the same PORT register, ideally all within the same byte 147 | of that PORT. 148 | 149 | Fifth argument is the number of "address" (aka row select) pins, 150 | from which the matrix height is inferred. "4" here means four 151 | address lines, matrix height is then (2 x 2^4) = 32 pixels. 152 | 16-pixel-tall matrices will have 3 pins here, 32-pixel will have 153 | 4, 64-pixel will have 5. Sixth argument is a uint8_t array 154 | listing those pin numbers. No PORT constraints here. 155 | 156 | Next three arguments are pin numbers for other RGB matrix 157 | control lines: clock, latch and output enable (active low). 158 | Clock pin MUST be on the same PORT register as RGB data pins 159 | (and ideally in same byte). Other pins have no special rules. 160 | 161 | Last argument is a boolean (true/false) to enable double- 162 | buffering for smooth animation (requires 2X the RAM). See the 163 | "doublebuffer" example for a demonstration. 164 | ------------------------------------------------------------------------- */ 165 | 166 | Adafruit_Protomatter matrix( 167 | 64, // Width of matrix (or matrix chain) in pixels 168 | 4, // Bit depth, 1-6 169 | 1, rgbPins, // # of matrix chains, array of 6 RGB pins for each 170 | 4, addrPins, // # of address pins (height is inferred), array of pins 171 | clockPin, latchPin, oePin, // Other matrix control pins 172 | false); // No double-buffering here (see "doublebuffer" example) 173 | 174 | // SETUP - RUNS ONCE AT PROGRAM START -------------------------------------- 175 | 176 | void setup(void) { 177 | Serial.begin(9600); 178 | 179 | // Initialize matrix... 180 | ProtomatterStatus status = matrix.begin(); 181 | Serial.print("Protomatter begin() status: "); 182 | Serial.println((int)status); 183 | if(status != PROTOMATTER_OK) { 184 | // DO NOT CONTINUE if matrix setup encountered an error. 185 | for(;;); 186 | } 187 | 188 | // Since this is a simple program with no animation, all the 189 | // drawing can be done here in setup() rather than loop(): 190 | 191 | // Make four color bars (red, green, blue, white) with brightness ramp: 192 | for(int x=0; x 32) { 209 | matrix.setCursor(0, 32); 210 | matrix.println("64 pixel"); // Default text color is white 211 | matrix.println("matrix"); // Default text color is white 212 | } 213 | 214 | // AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX! 215 | 216 | matrix.show(); // Copy data to matrix buffers 217 | } 218 | 219 | // LOOP - RUNS REPEATEDLY AFTER SETUP -------------------------------------- 220 | 221 | void loop(void) { 222 | // Since there's nothing more to be drawn, this loop() function just 223 | // shows the approximate refresh rate of the matrix at current settings. 224 | Serial.print("Refresh FPS = ~"); 225 | Serial.println(matrix.getFrameCount()); 226 | delay(1000); 227 | } 228 | 229 | // MORE NOTES -------------------------------------------------------------- 230 | 231 | /* 232 | The "RGB and clock bits on same PORT register" constraint requires 233 | considerable planning and knowledge of the underlying microcontroller 234 | hardware. These are some earlier notes on various devices' PORT registers 235 | and bits and their corresponding Arduino pin numbers. You probably won't 236 | need this -- it's all codified in the #if defined() sections at the top 237 | of this sketch now -- but keeping it around for reference if needed. 238 | 239 | METRO M0 PORT-TO-PIN ASSIGNMENTS BY BYTE: 240 | PA00 PA08 D4 PA16 D11 PB00 PB08 A1 241 | PA01 PA09 D3 PA17 D13 PB01 PB09 A2 242 | PA02 A0 PA10 D1 PA18 D10 PB02 A5 PB10 MOSI 243 | PA03 PA11 D0 PA19 D12 PB03 PB11 SCK 244 | PA04 A3 PA12 MISO PA20 D6 PB04 PB12 245 | PA05 A4 PA13 PA21 D7 PB05 PB13 246 | PA06 D8 PA14 D2 PA22 SDA PB06 PB14 247 | PA07 D9 PA15 D5 PA23 SCL PB07 PB15 248 | 249 | SAME, METRO M4: 250 | PA00 PA08 PA16 D13 PB00 PB08 A4 PB16 D3 251 | PA01 PA09 PA17 D12 PB01 PB09 A5 PB17 D2 252 | PA02 A0 PA10 PA18 D10 PB02 SDA PB10 PB18 253 | PA03 PA11 PA19 D11 PB03 SCL PB11 PB19 254 | PA04 A3 PA12 MISO PA20 D9 PB04 PB12 D7 PB20 255 | PA05 A1 PA13 SCK PA21 D8 PB05 PB13 D4 PB21 256 | PA06 A2 PA14 MISO PA22 D1 PB06 PB14 D5 PB22 257 | PA07 PA15 PA23 D0 PB07 PB15 D6 PB23 258 | 259 | FEATHER M4: 260 | PA00 PA08 PA16 D5 PB08 A2 PB16 D1/TX 261 | PA01 PA09 PA17 SCK PB09 A3 PB17 D0/RX 262 | PA02 A0 PA10 PA18 D6 PB10 PB18 263 | PA03 PA11 PA19 D9 PB11 PB19 264 | PA04 A4 PA12 SDA PA20 D10 PB12 PB20 265 | PA05 A1 PA13 SCL PA21 D11 PB13 PB21 266 | PA06 A5 PA14 D4 PA22 D12 PB14 PB22 MISO 267 | PA07 PA15 PA23 D13 PB15 PB23 MOSI 268 | 269 | FEATHER M0: 270 | PA00 PA08 PA16 D11 PB00 PB08 A1 271 | PA01 PA09 PA17 D13 PB01 PB09 A2 272 | PA02 A0 PA10 TX/D1 PA18 D10 PB02 A5 PB10 MOSI 273 | PA03 PA11 RX/D0 PA19 D12 PB03 PB11 SCK 274 | PA04 A3 PA12 MISO PA20 D6 PB04 PB12 275 | PA05 A4 PA13 PA21 D7 PB05 PB13 276 | PA06 PA14 PA22 SDA PB06 PB14 277 | PA07 D9 PA15 D5 PA23 SCL PB07 PB15 278 | 279 | FEATHER nRF52840: 280 | P0.00 P0.08 D12 P0.24 RXD P1.08 D5 281 | P0.01 P0.09 P0.25 TXD P1.09 D13 282 | P0.02 A4 P0.10 D2 (NFC) P0.26 D9 P1.10 283 | P0.03 A5 P0.11 SCL P0.27 D10 P1.11 284 | P0.04 A0 P0.12 SDA P0.28 A3 P1.12 285 | P0.05 A1 P0.13 MOSI P0.29 P1.13 286 | P0.06 D11 P0.14 SCK P0.30 A2 P1.14 287 | P0.07 D6 P0.15 MISO P0.31 P1.15 288 | 289 | FEATHER ESP32: 290 | P0.00 P0.08 P0.16 16/RX P0.24 P1.00 32/A7 291 | P0.01 P0.09 P0.17 17/TX P0.25 25/A1 P1.01 33/A9/SS 292 | P0.02 P0.10 P0.18 18/MOSI P0.26 26/A0 P1.02 34/A2 (in) 293 | P0.03 P0.11 P0.19 19/MISO P0.27 27/A10 P1.03 294 | P0.04 4/A5 P0.12 12/A11 P0.20 P0.28 P1.04 36/A4 (in) 295 | P0.05 5/SCK P0.13 13/A12 P0.21 21 P0.29 P1.05 296 | P0.06 P0.14 14/A6 P0.22 22/SCL P0.30 P1.06 297 | P0.07 P0.15 15/A8 P0.23 23/SDA P0.31 P1.07 39/A3 (in) 298 | 299 | GRAND CENTRAL M4: (___ = byte boundaries) 300 | PA00 PB00 D12 PC00 A3 PD00 301 | PA01 PB01 D13 (LED) PC01 A4 PD01 302 | PA02 A0 PB02 D9 PC02 A5 PD02 303 | PA03 84 (AREF) PB03 A2 PC03 A6 PD03 304 | PA04 A13 PB04 A7 PC04 D48 PD04 305 | PA05 A1 PB05 A8 PC05 D49 PD05 306 | PA06 A14 PB06 A9 PC06 D46 PD06 307 | PA07 A15 ______ PB07 A10 ______ PC07 D47 _____ PD07 __________ 308 | PA08 PB08 A11 PC08 PD08 D51 (SCK) 309 | PA09 PB09 A12 PC09 PD09 D52 (MOSI) 310 | PA10 PB10 PC10 D45 PD10 D53 311 | PA11 PB11 PC11 D44 PD11 D50 (MISO) 312 | PA12 D26 PB12 D18 PC12 D41 PD12 D22 313 | PA13 D27 PB13 D19 PC13 D40 PD13 314 | PA14 D28 PB14 D39 PC14 D43 PD14 315 | PA15 D23 ______ PB15 D38 ______ PC15 D42 _____ PD15 __________ 316 | PA16 D37 PB16 D14 PC16 D25 PD16 317 | PA17 D36 PB17 D15 PC17 D24 PD17 318 | PA18 D35 PB18 D8 PC18 D2 PD18 319 | PA19 D34 PB19 D29 PC19 D3 PD19 320 | PA20 D33 PB20 D20 (SDA) PC20 D4 PD20 D6 321 | PA21 D32 PB21 D21 (SCL) PC21 D5 PD21 D7 322 | PA22 D31 PB22 D10 PC22 D16 PD22 323 | PA23 D30 ______ PB23 D11 ______ PC23 D17 _____ PD23 __________ 324 | PA24 PB24 D1 325 | PA25 PB25 D0 326 | PA26 PB26 327 | PA27 PB27 328 | PA28 PB28 329 | PA29 PB29 330 | PA30 PB30 96 (SWO) 331 | PA31 __________ PB31 95 (SD CD) ______________________________ 332 | 333 | RGB MATRIX FEATHERWING NOTES: 334 | R1 D6 A A5 335 | G1 D5 B A4 336 | B1 D9 C A3 337 | R2 D11 D A2 338 | G2 D10 LAT D0/RX 339 | B2 D12 OE D1/TX 340 | CLK D13 341 | RGB+clock fit in one PORT byte on Feather M4! 342 | RGB+clock are on same PORT but not within same byte on Feather M0 -- 343 | the code could run there, but would be super RAM-inefficient. Avoid. 344 | Should be fine on other M0 devices like a Metro, if wiring manually 345 | so one can pick a contiguous byte of PORT bits. 346 | Original RGB Matrix FeatherWing will NOT work on Feather nRF52840 347 | because RGB+clock are on different PORTs. This was resolved by making 348 | a unique version of the FeatherWing that works with that board! 349 | */ 350 | -------------------------------------------------------------------------------- /examples/tiled/tiled.ino: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------- 2 | "Tiled" Protomatter library example sketch. Demonstrates use of multiple 3 | RGB LED matrices as a single larger drawing surface. This example is 4 | written for two 64x32 matrices (tiled into a 64x64 display) but can be 5 | adapted to others. If using MatrixPortal, larger multi-panel tilings like 6 | this should be powered from a separate 5V DC supply, not the USB port 7 | (this example works OK because the graphics are very minimal). 8 | 9 | PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH. 10 | ------------------------------------------------------------------------- */ 11 | 12 | #include 13 | 14 | /* ---------------------------------------------------------------------- 15 | The RGB matrix must be wired to VERY SPECIFIC pins, different for each 16 | microcontroller board. This first section sets that up for a number of 17 | supported boards. 18 | ------------------------------------------------------------------------- */ 19 | 20 | #if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4 21 | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 22 | uint8_t addrPins[] = {17, 18, 19, 20, 21}; 23 | uint8_t clockPin = 14; 24 | uint8_t latchPin = 15; 25 | uint8_t oePin = 16; 26 | #elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3 27 | uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; 28 | uint8_t addrPins[] = {45, 36, 48, 35, 21}; 29 | uint8_t clockPin = 2; 30 | uint8_t latchPin = 47; 31 | uint8_t oePin = 14; 32 | #elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing 33 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 34 | uint8_t addrPins[] = {A5, A4, A3, A2}; 35 | uint8_t clockPin = 13; 36 | uint8_t latchPin = 0; 37 | uint8_t oePin = 1; 38 | #elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift) 39 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 40 | uint8_t addrPins[] = {A5, A4, A3, A2}; 41 | uint8_t clockPin = 13; 42 | uint8_t latchPin = 0; 43 | uint8_t oePin = 1; 44 | #elif defined(_SAMD21_) // Feather M0 variants 45 | uint8_t rgbPins[] = {6, 7, 10, 11, 12, 13}; 46 | uint8_t addrPins[] = {0, 1, 2, 3}; 47 | uint8_t clockPin = SDA; 48 | uint8_t latchPin = 4; 49 | uint8_t oePin = 5; 50 | #elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout 51 | uint8_t rgbPins[] = {6, A5, A1, A0, A4, 11}; 52 | uint8_t addrPins[] = {10, 5, 13, 9}; 53 | uint8_t clockPin = 12; 54 | uint8_t latchPin = PIN_SERIAL1_RX; 55 | uint8_t oePin = PIN_SERIAL1_TX; 56 | #elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM 57 | // M0/M4/RP2040 Matrix FeatherWing compatible: 58 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 59 | uint8_t addrPins[] = {A5, A4, A3, A2}; 60 | uint8_t clockPin = 13; // Must be on same port as rgbPins 61 | uint8_t latchPin = RX; 62 | uint8_t oePin = TX; 63 | #elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2 64 | // M0/M4/RP2040 Matrix FeatherWing compatible: 65 | uint8_t rgbPins[] = {6, 5, 9, 11, 10, 12}; 66 | uint8_t addrPins[] = {A5, A4, A3, A2}; 67 | uint8_t clockPin = 13; // Must be on same port as rgbPins 68 | uint8_t latchPin = RX; 69 | uint8_t oePin = TX; 70 | #elif defined(ESP32) 71 | // 'Safe' pins, not overlapping any peripherals: 72 | // GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33 73 | // Peripheral-overlapping pins, sorted from 'most expendible': 74 | // 16, 17 (RX, TX) 75 | // 25, 26 (A0, A1) 76 | // 18, 5, 9 (MOSI, SCK, MISO) 77 | // 22, 23 (SCL, SDA) 78 | uint8_t rgbPins[] = {4, 12, 13, 14, 15, 21}; 79 | uint8_t addrPins[] = {16, 17, 25, 26}; 80 | uint8_t clockPin = 27; // Must be on same port as rgbPins 81 | uint8_t latchPin = 32; 82 | uint8_t oePin = 33; 83 | #elif defined(ARDUINO_TEENSY40) 84 | uint8_t rgbPins[] = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL 85 | uint8_t addrPins[] = {2, 3, 4, 5}; 86 | uint8_t clockPin = 23; // A9 87 | uint8_t latchPin = 6; 88 | uint8_t oePin = 9; 89 | #elif defined(ARDUINO_TEENSY41) 90 | uint8_t rgbPins[] = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8 91 | uint8_t addrPins[] = {2, 3, 4, 5}; 92 | uint8_t clockPin = 23; // A9 93 | uint8_t latchPin = 6; 94 | uint8_t oePin = 9; 95 | #elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) 96 | // RP2040 support requires the Earle Philhower board support package; 97 | // will not compile with the Arduino Mbed OS board package. 98 | // The following pinout works with the Adafruit Feather RP2040 and 99 | // original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version). 100 | // Pin numbers here are GP## numbers, which may be different than 101 | // the pins printed on some boards' top silkscreen. 102 | uint8_t rgbPins[] = {8, 7, 9, 11, 10, 12}; 103 | uint8_t addrPins[] = {25, 24, 29, 28}; 104 | uint8_t clockPin = 13; 105 | uint8_t latchPin = 1; 106 | uint8_t oePin = 0; 107 | #endif 108 | 109 | /* ---------------------------------------------------------------------- 110 | Matrix initialization is explained EXTENSIVELY in "simple" example sketch! 111 | It's very similar here, but we're passing an extra argument to define the 112 | matrix tiling along the vertical axis: -2 means there are two matrices 113 | (or rows of matrices) arranged in a "serpentine" path (the second matrix 114 | is rotated 180 degrees relative to the first, and positioned below). 115 | A positive 2 would indicate a "progressive" path (both matrices are 116 | oriented the same way), but usually requires longer cables. 117 | ------------------------------------------------------------------------- */ 118 | 119 | Adafruit_Protomatter matrix( 120 | 64, // Width of matrix (or matrices, if tiled horizontally) 121 | 6, // Bit depth, 1-6 122 | 1, rgbPins, // # of matrix chains, array of 6 RGB pins for each 123 | 4, addrPins, // # of address pins (height is inferred), array of pins 124 | clockPin, latchPin, oePin, // Other matrix control pins 125 | false, // No double-buffering here (see "doublebuffer" example) 126 | -2); // Row tiling: two rows in "serpentine" path 127 | 128 | // SETUP - RUNS ONCE AT PROGRAM START -------------------------------------- 129 | 130 | void setup(void) { 131 | Serial.begin(9600); 132 | 133 | // Initialize matrix... 134 | ProtomatterStatus status = matrix.begin(); 135 | Serial.print("Protomatter begin() status: "); 136 | Serial.println((int)status); 137 | if(status != PROTOMATTER_OK) { 138 | // DO NOT CONTINUE if matrix setup encountered an error. 139 | for(;;); 140 | } 141 | 142 | // Since this program has no animation, all the drawing can be done 143 | // here in setup() rather than loop(). It's just a few basic shapes 144 | // that span across the matrices...nothing showy, the goal of this 145 | // sketch is just to demonstrate tiling basics. 146 | 147 | matrix.drawLine(0, 0, matrix.width() - 1, matrix.height() - 1, 148 | matrix.color565(255, 0, 0)); // Red line 149 | matrix.drawLine(matrix.width() - 1, 0, 0, matrix.height() - 1, 150 | matrix.color565(0, 0, 255)); // Blue line 151 | int radius = min(matrix.width(), matrix.height()) / 2; 152 | matrix.drawCircle(matrix.width() / 2, matrix.height() / 2, radius, 153 | matrix.color565(0, 255, 0)); // Green circle 154 | 155 | // AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX! 156 | 157 | matrix.show(); // Copy data to matrix buffers 158 | } 159 | 160 | // LOOP - RUNS REPEATEDLY AFTER SETUP -------------------------------------- 161 | 162 | void loop(void) { 163 | // Since there's nothing more to be drawn, this loop() function just 164 | // prints the approximate refresh rate of the matrix at current settings. 165 | Serial.print("Refresh FPS = ~"); 166 | Serial.println(matrix.getFrameCount()); 167 | delay(1000); 168 | } 169 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Adafruit Protomatter 2 | version=1.7.0 3 | author=Adafruit 4 | maintainer=Adafruit 5 | sentence=A library for Adafruit RGB LED matrices. 6 | paragraph=RGB LED matrix. 7 | category=Display 8 | url=https://github.com/adafruit/Adafruit_protomatter 9 | architectures=samd,nrf52,stm32,esp32,rp2040 10 | depends=Adafruit GFX Library, Adafruit LIS3DH, Adafruit PixelDust, AnimatedGIF, Adafruit SPIFlash, Adafruit TinyUSB Library 11 | -------------------------------------------------------------------------------- /src/Adafruit_Protomatter.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file Adafruit_Protomatter.cpp 3 | * 4 | * @mainpage Adafruit Protomatter RGB LED matrix library. 5 | * 6 | * @section intro_sec Introduction 7 | * 8 | * This is documentation for Adafruit's protomatter library for HUB75-style 9 | * RGB LED matrices. It is designed to work with various matrices sold by 10 | * Adafruit ("HUB75" is a vague term and other similar matrices are not 11 | * guaranteed to work). This file is the Arduino-specific calls; the 12 | * underlying C code is more platform-neutral. 13 | * 14 | * Adafruit invests time and resources providing this open source code, 15 | * please support Adafruit and open-source hardware by purchasing products 16 | * from Adafruit! 17 | * 18 | * @section dependencies Dependencies 19 | * 20 | * This library depends on 21 | * Adafruit_GFX 22 | * being present on your system. Please make sure you have installed the 23 | * latest version before using this library. 24 | * 25 | * @section author Author 26 | * 27 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 28 | * Adafruit Industries, with contributions from the open source community. 29 | * 30 | * @section license License 31 | * 32 | * BSD license, all text here must be included in any redistribution. 33 | * 34 | */ 35 | 36 | // Arduino-specific wrapper for the Protomatter C library (provides 37 | // constructor and so forth, builds on Adafruit_GFX). There should 38 | // not be any device-specific #ifdefs here. See notes in core.c and 39 | // arch/arch.h regarding portability. 40 | 41 | #include "Adafruit_Protomatter.h" // Also includes core.h & Adafruit_GFX.h 42 | 43 | extern Protomatter_core *_PM_protoPtr; ///< In core.c (via arch.h) 44 | 45 | Adafruit_Protomatter::Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, 46 | uint8_t rgbCount, uint8_t *rgbList, 47 | uint8_t addrCount, uint8_t *addrList, 48 | uint8_t clockPin, uint8_t latchPin, 49 | uint8_t oePin, bool doubleBuffer, 50 | int8_t tile, void *timer) 51 | : GFXcanvas16(bitWidth, (2 << min((int)addrCount, 5)) * 52 | min((int)rgbCount, 5) * 53 | (tile ? abs(tile) : 1)) { 54 | if (bitDepth > 6) 55 | bitDepth = 6; // GFXcanvas16 color limit (565) 56 | 57 | // Arguments are passed through to the C _PM_init() function which does 58 | // some input validation and minor allocation. Return value is ignored 59 | // because we can't really do anything about it in a C++ constructor. 60 | // The class begin() function checks rgbPins for NULL to determine 61 | // whether to proceed or indicate an error. 62 | (void)_PM_init(&core, bitWidth, bitDepth, rgbCount, rgbList, addrCount, 63 | addrList, clockPin, latchPin, oePin, doubleBuffer, tile, 64 | timer); 65 | } 66 | 67 | Adafruit_Protomatter::~Adafruit_Protomatter(void) { 68 | _PM_deallocate(&core); 69 | _PM_protoPtr = NULL; 70 | } 71 | 72 | ProtomatterStatus Adafruit_Protomatter::begin(void) { 73 | _PM_protoPtr = &core; 74 | return _PM_begin(&core); 75 | } 76 | 77 | // Transfer data from GFXcanvas16 to the matrix framebuffer's weird 78 | // internal format. The actual conversion functions referenced below 79 | // are in core.c, reasoning is explained there. 80 | void Adafruit_Protomatter::show(void) { 81 | _PM_convert_565(&core, getBuffer(), WIDTH); 82 | _PM_swapbuffer_maybe(&core); 83 | } 84 | 85 | // Returns current value of frame counter and resets its value to zero. 86 | // Two calls to this, timed one second apart (or use math with other 87 | // intervals), can be used to get a rough frames-per-second value for 88 | // the matrix (since this is difficult to estimate beforehand). 89 | uint32_t Adafruit_Protomatter::getFrameCount(void) { 90 | return _PM_getFrameCount(_PM_protoPtr); 91 | } 92 | 93 | // This is based on the HSV function in Adafruit_NeoPixel.cpp, but with 94 | // 16-bit RGB565 output for GFX lib rather than 24-bit. See that code for 95 | // an explanation of the math, this is stripped of comments for brevity. 96 | uint16_t Adafruit_Protomatter::colorHSV(uint16_t hue, uint8_t sat, 97 | uint8_t val) { 98 | uint8_t r, g, b; 99 | 100 | hue = (hue * 1530L + 32768) / 65536; 101 | 102 | if (hue < 510) { // Red to Green-1 103 | b = 0; 104 | if (hue < 255) { // Red to Yellow-1 105 | r = 255; 106 | g = hue; // g = 0 to 254 107 | } else { // Yellow to Green-1 108 | r = 510 - hue; // r = 255 to 1 109 | g = 255; 110 | } 111 | } else if (hue < 1020) { // Green to Blue-1 112 | r = 0; 113 | if (hue < 765) { // Green to Cyan-1 114 | g = 255; 115 | b = hue - 510; // b = 0 to 254 116 | } else { // Cyan to Blue-1 117 | g = 1020 - hue; // g = 255 to 1 118 | b = 255; 119 | } 120 | } else if (hue < 1530) { // Blue to Red-1 121 | g = 0; 122 | if (hue < 1275) { // Blue to Magenta-1 123 | r = hue - 1020; // r = 0 to 254 124 | b = 255; 125 | } else { // Magenta to Red-1 126 | r = 255; 127 | b = 1530 - hue; // b = 255 to 1 128 | } 129 | } else { // Last 0.5 Red (quicker than % operator) 130 | r = 255; 131 | g = b = 0; 132 | } 133 | 134 | // Apply saturation and value to R,G,B, pack into 16-bit 'RGB565' result: 135 | uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 136 | uint16_t s1 = 1 + sat; // 1 to 256; same reason 137 | uint8_t s2 = 255 - sat; // 255 to 0 138 | return (((((r * s1) >> 8) + s2) * v1) & 0xF800) | 139 | ((((((g * s1) >> 8) + s2) * v1) & 0xFC00) >> 5) | 140 | (((((b * s1) >> 8) + s2) * v1) >> 11); 141 | } 142 | -------------------------------------------------------------------------------- /src/Adafruit_Protomatter.h: -------------------------------------------------------------------------------- 1 | // Arduino-specific header, accompanies Adafruit_Protomatter.cpp. 2 | // There should not be any device-specific #ifdefs here. 3 | 4 | #pragma once 5 | 6 | #include "core.h" 7 | #include 8 | 9 | /*! 10 | @brief Class representing the Arduino-facing side of the Protomatter 11 | library. Subclass of Adafruit_GFX's GFXcanvas16 to allow all 12 | the drawing operations. 13 | */ 14 | class Adafruit_Protomatter : public GFXcanvas16 { 15 | public: 16 | /*! 17 | @brief Adafruit_Protomatter constructor. 18 | @param bitWidth Total width of RGB matrix chain, in pixels. 19 | Usu. some multiple of 32, but maybe exceptions. 20 | @param bitDepth Color "depth" in bitplanes, determines range of 21 | shades of red, green and blue. e.g. passing 4 22 | bits = 16 shades ea. R,G,B = 16x16x16 = 4096 23 | colors. Max is 6, since the GFX library works 24 | with "565" RGB colors (6 bits green, 5 red/blue). 25 | @param rgbCount Number of "sets" of RGB data pins, each set 26 | containing 6 pins (2 ea. R,G,B). Typically 1, 27 | indicating a single matrix (or matrix chain). 28 | In theory (but not yet extensively tested), 29 | multiple sets of pins can be driven in parallel, 30 | up to 5 on some devices (if the hardware design 31 | provides all those bits on one PORT). 32 | @param rgbList A uint8_t array of pins (Arduino pin numbering), 33 | 6X the prior rgbCount value, corresponding to 34 | the 6 output color bits for a matrix (or chain). 35 | Order is upper-half red, green, blue, lower-half 36 | red, green blue (repeat for each add'l chain). 37 | All the RGB pins (plus the clock pin below on 38 | some architectures) MUST be on the same PORT 39 | register. It's recommended (but not required) 40 | that all RGB pins (and clock depending on arch) 41 | be within the same byte of a PORT (but do not 42 | need to be sequential or contiguous within that 43 | byte) for more efficient RAM utilization. For 44 | two concurrent chains, same principle but 16-bit 45 | word instead of byte. 46 | @param addrCount Number of row address lines required of matrix. 47 | Total pixel height is then 2 x 2^addrCount, e.g. 48 | 32-pixel-tall matrices have 4 row address lines. 49 | @param addrList A uint8_t array of pins (Arduino pin numbering), 50 | one per row address line. 51 | @param clockPin RGB clock pin (Arduino pin #). 52 | @param latchPin RGB data latch pin (Arduino pin #). 53 | @param oePin Output enable pin (Arduino pin #), active low. 54 | @param doubleBuffer If true, two matrix buffers are allocated, 55 | so changing display contents doesn't introduce 56 | artifacts mid-conversion. Requires ~2X RAM. 57 | @param tile If multiple matrices are chained and stacked 58 | vertically (rather than or in addition to 59 | horizontally), the number of vertical tiles is 60 | specified here. Positive values indicate a 61 | "progressive" arrangement (always left-to-right), 62 | negative for a "serpentine" arrangement (alternating 63 | 180 degree orientation). Horizontal tiles are implied 64 | in the 'bitWidth' argument. 65 | @param timer Pointer to timer peripheral or timer-related 66 | struct (architecture-dependent), or NULL to 67 | use a default timer ID (also arch-dependent). 68 | */ 69 | Adafruit_Protomatter(uint16_t bitWidth, uint8_t bitDepth, uint8_t rgbCount, 70 | uint8_t *rgbList, uint8_t addrCount, uint8_t *addrList, 71 | uint8_t clockPin, uint8_t latchPin, uint8_t oePin, 72 | bool doubleBuffer, int8_t tile = 1, void *timer = NULL); 73 | ~Adafruit_Protomatter(void); 74 | 75 | /*! 76 | @brief Start a Protomatter matrix display running -- initialize 77 | pins, timer and interrupt into existence. 78 | @return A ProtomatterStatus status, one of: 79 | PROTOMATTER_OK if everything is good. 80 | PROTOMATTER_ERR_PINS if data and/or clock pins are split 81 | across different PORTs. 82 | PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate 83 | display memory. 84 | PROTOMATTER_ERR_ARG if a bad value was passed to the 85 | constructor. 86 | */ 87 | ProtomatterStatus begin(void); 88 | 89 | /*! 90 | @brief Process data from GFXcanvas16 to the matrix framebuffer's 91 | internal format for display. 92 | */ 93 | void show(void); 94 | 95 | /*! 96 | @brief Disable (but do not deallocate) a Protomatter matrix. 97 | */ 98 | void stop(void) { _PM_stop(&core); } 99 | 100 | /*! 101 | @brief Resume a previously-stopped matrix. 102 | */ 103 | void resume(void) { _PM_resume(&core); } 104 | 105 | /*! 106 | @brief Returns current value of frame counter and resets its value 107 | to zero. Two calls to this, timed one second apart (or use 108 | math with other intervals), can be used to get a rough 109 | frames-per-second value for the matrix (since this is 110 | difficult to estimate beforehand). 111 | @return Frame count since previous call to function, as a uint32_t. 112 | */ 113 | uint32_t getFrameCount(void); 114 | 115 | /*! 116 | @brief Converts 24-bit color (8 bits red, green, blue) used in a lot 117 | a lot of existing graphics code down to the "565" color format 118 | used by Adafruit_GFX. Might get further quantized by matrix if 119 | using less than 6-bit depth. 120 | @param red Red brightness, 0 (min) to 255 (max). 121 | @param green Green brightness, 0 (min) to 255 (max). 122 | @param blue Blue brightness, 0 (min) to 255 (max). 123 | @return Packed 16-bit (uint16_t) color value suitable for GFX drawing 124 | functions. 125 | */ 126 | uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) { 127 | return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3); 128 | } 129 | 130 | /*! 131 | @brief Convert hue, saturation and value into a packed 16-bit RGB color 132 | that can be passed to GFX drawing functions. 133 | @param hue An unsigned 16-bit value, 0 to 65535, representing one full 134 | loop of the color wheel, which allows 16-bit hues to "roll 135 | over" while still doing the expected thing (and allowing 136 | more precision than the wheel() function that was common to 137 | older graphics examples). 138 | @param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255 139 | (max or pure hue). Default of 255 if unspecified. 140 | @param val Value (brightness), 8-bit value, 0 (min / black / off) to 141 | 255 (max or full brightness). Default of 255 if unspecified. 142 | @return Packed 16-bit '565' RGB color. Result is linearly but not 143 | perceptually correct (no gamma correction). 144 | */ 145 | uint16_t colorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255); 146 | 147 | /*! 148 | @brief Adjust HUB clock signal duty cycle on architectures that support 149 | this (currently SAMD51 only) (else ignored). 150 | @param Duty setting, 0 minimum. Increasing values generate higher clock 151 | duty cycles at the same frequency. Arbitrary granular units, max 152 | varies by architecture and CPU speed, if supported at all. 153 | e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%). 154 | */ 155 | void setDuty(uint8_t d) { _PM_setDuty(d); }; 156 | 157 | private: 158 | Protomatter_core core; // Underlying C struct 159 | void convert_byte(uint8_t *dest); // GFXcanvas16-to-matrix 160 | void convert_word(uint16_t *dest); // conversion functions 161 | void convert_long(uint32_t *dest); // for 8/16/32 bit bufs 162 | }; 163 | -------------------------------------------------------------------------------- /src/arch/arch.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file arch.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file establishes some very low-level things and includes headers 6 | * specific to each supported device. This should ONLY be included by 7 | * core.c, nowhere else. Ever. 8 | * 9 | * Adafruit invests time and resources providing this open source code, 10 | * please support Adafruit and open-source hardware by purchasing 11 | * products from Adafruit! 12 | * 13 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 14 | * Adafruit Industries, with contributions from the open source community. 15 | * 16 | * BSD license, all text here must be included in any redistribution. 17 | * 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | /* 25 | Common ground for architectures to support this library: 26 | 27 | - 32-bit device (e.g. ARM core, ESP32, potentially others in the future) 28 | - One or more 32-bit GPIO PORTs with atomic bitmask SET and CLEAR registers. 29 | A TOGGLE register, if present, may improve performance but is NOT required. 30 | - Tolerate 8-bit or word-aligned 16-bit accesses within the 32-bit PORT 31 | registers (e.g. writing just one of four bytes, rather than the whole 32 | 32 bits). The library does not use any unaligned accesses (i.e. the 33 | "middle word" of a 32-bit register), even if a device tolerates such. 34 | 35 | "Pin" as used in this code is always a uint8_t value, but the semantics 36 | of what it means may vary between Arduino and non-Arduino situations. 37 | In Arduino, it's the pin index one would pass to functions such as 38 | digitalWrite(), and doesn't necessarily correspond to physical hardware 39 | pins or any other arrangement. Some may have names like 'A0' that really 40 | just map to higher indices. 41 | In non-Arduino settings (CircuitPython, other languages, etc.), how a 42 | pin index relates to hardware is entirely implementation dependent, and 43 | how to get from one to the other is what must be implemented in this file. 44 | Quite often an environment will follow the Arduino pin designations 45 | (since the numbers are on a board's silkscreen) and will have an internal 46 | table mapping those indices to registers and bitmasks...but probably not 47 | an identically-named and -structured table to the Arduino code, hence the 48 | reason for many "else" situations in this code. 49 | 50 | Each architecture defines the following macros and/or functions (the _PM_ 51 | prefix on each is to reduce likelihood of naming collisions...especially 52 | on ESP32, which has some similarly-named timer functions: 53 | 54 | GPIO-related macros/functions: 55 | 56 | _PM_portOutRegister(pin): Get address of PORT out register. Code calling 57 | this can cast it to whatever type's needed. 58 | _PM_portSetRegister(pin): Get address of PORT set-bits register. 59 | _PM_portClearRegister(pin): Get address of PORT clear-bits register. 60 | _PM_portToggleRegister(pin): Get address of PORT toggle-bits register. 61 | Not all devices support this, in which case 62 | it must be left undefined. 63 | _PM_portBitMask(pin): Get bit mask within PORT register corresponding 64 | to a pin number. When compiling for Arduino, 65 | this just maps to digitalPinToBitMask(), other 66 | environments will need an equivalent. 67 | _PM_byteOffset(pin): Get index of byte (0 to 3) within 32-bit PORT 68 | corresponding to a pin number. 69 | _PM_wordOffset(pin): Get index of word (0 or 1) within 32-bit PORT 70 | corresponding to a pin number. 71 | _PM_pinOutput(pin): Set a pin to output mode. In Arduino this maps 72 | to pinMode(pin, OUTPUT). Other environments 73 | will need an equivalent. 74 | _PM_pinInput(pin): Set a pin to input mode, no pullup. In Arduino 75 | this maps to pinMode(pin, INPUT). 76 | _PM_pinHigh(pin): Set an output pin to a high or 1 state. In 77 | Arduino this maps to digitalWrite(pin, HIGH). 78 | _PM_pinLow(pin): Set an output pin to a low or 0 state. In 79 | Arduino this maps to digitalWrite(pin, LOW). 80 | 81 | Timer-related macros/functions: 82 | 83 | _PM_timerFreq: A numerical constant - the source clock rate 84 | (in Hz) that's fed to the timer peripheral. 85 | _PM_timerInit(Protomatter_core*): Initialize (but do not start) timer. 86 | _PM_timerStart(Protomatter_core*,count): (Re)start timer for a given 87 | timer-tick interval. 88 | _PM_timerStop(Protomatter_core*): Stop timer, return current timer 89 | counter value. 90 | _PM_timerGetCount(Protomatter_core*): Get current timer counter value 91 | (whether timer is running or stopped). 92 | A timer interrupt service routine is also required, syntax for which varies 93 | between architectures. 94 | The void* argument passed to the timer functions is some indeterminate type 95 | used to uniquely identify a timer peripheral within a given environment. For 96 | example, in the Arduino wrapper for this library, compiling for SAMD chips, 97 | it's just a pointer directly to a timer/counter peripheral base address. If 98 | an implementation needs more data associated alongside a peripheral, this 99 | could instead be a pointer to a struct, or an integer index. 100 | 101 | Other macros/functions: 102 | 103 | _PM_chunkSize: Matrix bitmap width (both in RAM and as issued 104 | to the device) is rounded up (if necessary) to 105 | a multiple of this value as a way of explicitly 106 | unrolling the innermost data-stuffing loops. 107 | So far all HUB75 displays I've encountered are 108 | a multiple of 32 pixels wide, but in case 109 | something new comes along, or if a larger 110 | unroll actually decreases performance due to 111 | cache size, this can be set to whatever works 112 | best (any additional data is simply shifted 113 | out the other end of the matrix). Default if 114 | unspecified is 8 (e.g. four loop passes on a 115 | 32-pixel matrix, eight if 64-pixel). Only 116 | certain chunkSizes are actually implemented, 117 | see .cpp code (avoiding GCC-specific tricks 118 | that would handle arbitrary chunk sizes). 119 | _PM_delayMicroseconds(us): Function or macro to delay some number of 120 | microseconds. For Arduino, this just maps to 121 | delayMicroseconds(). Other environments will 122 | need to provide their own or map to an 123 | an equivalent function. 124 | _PM_clockHoldHigh: Additional code (typically some number of NOPs) 125 | needed to delay the clock fall after RGB data is 126 | written to PORT. Only required on fast devices. 127 | If left undefined, no delay happens. 128 | _PM_clockHoldLow: Additional code (e.g. NOPs) needed to delay 129 | clock rise after writing RGB data to PORT. 130 | No delay if left undefined. 131 | _PM_minMinPeriod: Mininum value for the "minPeriod" class member, 132 | so bit-angle-modulation time always doubles with 133 | each bitplane (else lower bits may be the same). 134 | _PM_allocate: Memory allocation function, should return a 135 | pointer to a buffer of requested size, aligned 136 | to the architecture's largest native type. 137 | If not defined, malloc() is used. 138 | _PM_free: Corresponding deallocator for _PM_allocate(). 139 | If not defined, free() is used. 140 | _PM_bytesPerElement If defined, this allows an arch-specific source 141 | file to override core's data size that's based 142 | on pin selections. Reasonable values would be 1, 143 | 2 or 4. This came about during ESP32-S2 144 | development; GPIO or I2S/LCD peripherals there 145 | allows super flexible pin MUXing, so one byte 146 | could be used even w/pins spread all over. 147 | _PM_USE_TOGGLE_FORMAT If defined, this instructs the core code to 148 | format pixel data for GPIO bit-toggling, even 149 | if _PM_portToggleRegister is not defined. 150 | _PM_CUSTOM_BLAST If defined, instructs core code to not compile 151 | the blast_byte(), blast_word() or blast_long() 152 | functions; these will be declared in the arch- 153 | specific file instead. This might benefit 154 | architectures, where DMA, PIO or other 155 | specialized peripherals could be set up to 156 | issue data independent of the CPU. This goes 157 | against's Protomatter's normal design of using 158 | the most baseline peripherals for a given 159 | architecture, but time marches on, y'know? 160 | */ 161 | 162 | // ENVIRONMENT-SPECIFIC DECLARATIONS --------------------------------------- 163 | 164 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 165 | 166 | #include // Pull in all that stuff. 167 | 168 | #define _PM_delayMicroseconds(us) delayMicroseconds(us) 169 | #define _PM_pinOutput(pin) pinMode(pin, OUTPUT) 170 | #define _PM_pinInput(pin) pinMode(pin, INPUT) 171 | #define _PM_pinHigh(pin) digitalWrite(pin, HIGH) 172 | #define _PM_pinLow(pin) digitalWrite(pin, LOW) 173 | 174 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 175 | 176 | #include "py/mphal.h" 177 | #include "shared-bindings/microcontroller/Pin.h" 178 | 179 | #define _PM_delayMicroseconds(us) mp_hal_delay_us(us) 180 | 181 | // No #else here. In non-Arduino case, declare things in the arch-specific 182 | // files below...unless other environments provide device-neutral functions 183 | // as above, in which case those could go here (w/#elif). 184 | 185 | #endif // END CIRCUITPYTHON ------------------------------------------------ 186 | 187 | // ARCHITECTURE-SPECIFIC HEADERS ------------------------------------------- 188 | 189 | // clang-format off 190 | #include "esp32-common.h" 191 | #include "esp32.h" // Original ESP32 192 | #include "esp32-s2.h" 193 | #include "esp32-s3.h" 194 | #include "esp32-c3.h" 195 | #include "esp32-c6.h" 196 | #include "nrf52.h" 197 | #include "rp2040.h" 198 | #include "samd-common.h" 199 | #include "samd21.h" 200 | #include "samd51.h" 201 | #include "stm32.h" 202 | #include "teensy4.h" 203 | // clang-format on 204 | 205 | // DEFAULTS IF NOT DEFINED ABOVE ------------------------------------------- 206 | 207 | #if defined(_PM_portToggleRegister) 208 | #define _PM_USE_TOGGLE_FORMAT 209 | #endif 210 | 211 | #if !defined(_PM_portBitMask) 212 | #define _PM_portBitMask(pin) digitalPinToBitMask(pin) 213 | #endif 214 | 215 | #if !defined(_PM_chunkSize) 216 | #define _PM_chunkSize 8 ///< Unroll data-stuffing loop to this size 217 | #endif 218 | 219 | #if !defined(_PM_clockHoldHigh) 220 | #define _PM_clockHoldHigh ///< Extra cycles (if any) on clock HIGH signal 221 | #endif 222 | 223 | #if !defined(_PM_clockHoldLow) 224 | #define _PM_clockHoldLow ///< Extra cycles (if any) on clock LOW signal 225 | #endif 226 | 227 | #if !defined(_PM_minMinPeriod) 228 | #define _PM_minMinPeriod 100 ///< Minimum timer interval for least bit 229 | #endif 230 | 231 | #if !defined(_PM_allocate) 232 | #define _PM_allocate(x) (malloc((x))) ///< Memory alloc call 233 | #endif 234 | 235 | #if !defined(_PM_free) 236 | #define _PM_free(x) (free((x))) ///< Corresponding memory free call 237 | #endif 238 | 239 | #if !defined(IRAM_ATTR) 240 | #define IRAM_ATTR ///< Neutralize ESP32-specific attribute in core.c 241 | #endif 242 | 243 | #if !defined(_PM_PORT_TYPE) 244 | #define _PM_PORT_TYPE uint32_t ///< PORT register size/type 245 | #endif 246 | 247 | #if !defined(_PM_maxDuty) 248 | #define _PM_maxDuty 0 ///< Max duty cycle setting (where supported) 249 | #endif 250 | 251 | #if !defined(_PM_defaultDuty) 252 | #define _PM_defaultDuty 0 ///< Default duty cycle setting (where supported) 253 | #endif 254 | -------------------------------------------------------------------------------- /src/arch/esp32-c3.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file esp32-c3.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains ESP32-C3-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | // NOTE: there is some intentional repetition in the macros and functions 21 | // for some ESP32 variants. Previously they were all one file, but complex 22 | // preprocessor directives were turning into spaghetti. THEREFORE, if making 23 | // a change or bugfix in one variant-specific header, check the others to 24 | // see if the same should be applied! 25 | 26 | #if defined(CONFIG_IDF_TARGET_ESP32C3) 27 | 28 | #define _PM_portOutRegister(pin) (volatile uint32_t *)&GPIO.out 29 | #define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts 30 | #define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc 31 | 32 | #define _PM_portBitMask(pin) (1U << ((pin)&31)) 33 | 34 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 35 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 36 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 37 | #else 38 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 39 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 40 | #endif 41 | 42 | // No special peripheral setup on ESP32C3, just use common timer init... 43 | #define _PM_timerInit(core) _PM_esp32commonTimerInit(core); 44 | 45 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 46 | // Return current count value (timer enabled or not). 47 | // Timer must be previously initialized. 48 | // This function is the same on all ESP32 parts EXCEPT S3. 49 | IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 50 | return (uint32_t)timerRead((hw_timer_t *)core->timer); 51 | } 52 | 53 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 54 | 55 | #endif // END CIRCUITPYTHON ------------------------------------------------ 56 | 57 | #endif // END ESP32C3 58 | -------------------------------------------------------------------------------- /src/arch/esp32-c6.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file esp32-c3.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains ESP32-C3-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | // NOTE: there is some intentional repetition in the macros and functions 21 | // for some ESP32 variants. Previously they were all one file, but complex 22 | // preprocessor directives were turning into spaghetti. THEREFORE, if making 23 | // a change or bugfix in one variant-specific header, check the others to 24 | // see if the same should be applied! 25 | 26 | #if defined(CONFIG_IDF_TARGET_ESP32C6) 27 | 28 | #define _PM_portOutRegister(pin) (volatile uint32_t *)&GPIO.out 29 | #define _PM_portSetRegister(pin) (volatile uint32_t *)&GPIO.out_w1ts 30 | #define _PM_portClearRegister(pin) (volatile uint32_t *)&GPIO.out_w1tc 31 | 32 | #define _PM_portBitMask(pin) (1U << ((pin)&31)) 33 | 34 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 35 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 36 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 37 | #else 38 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 39 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 40 | #endif 41 | 42 | // No special peripheral setup on ESP32C3, just use common timer init... 43 | #define _PM_timerInit(core) _PM_esp32commonTimerInit(core); 44 | 45 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 46 | // Return current count value (timer enabled or not). 47 | // Timer must be previously initialized. 48 | // This function is the same on all ESP32 parts EXCEPT S3. 49 | IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 50 | return (uint32_t)timerRead((hw_timer_t *)core->timer); 51 | } 52 | 53 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 54 | 55 | #endif // END CIRCUITPYTHON ------------------------------------------------ 56 | 57 | #endif // END ESP32C3 58 | -------------------------------------------------------------------------------- /src/arch/esp32-common.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file esp32-common.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains ESP32-SPECIFIC CODE (common to all ESP variants). 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(ESP32) || \ 21 | defined(ESP_PLATFORM) // *All* ESP32 variants (OG, S2, S3, etc.) 22 | 23 | #include 24 | 25 | #include "esp_idf_version.h" 26 | 27 | // NOTE: there is some intentional repetition in the macros and functions 28 | // for some ESP32 variants. Previously they were all one file, but complex 29 | // preprocessor directives were turning into spaghetti. THEREFORE, if making 30 | // a change or bugfix in one variant-specific header, check the others to 31 | // see if the same should be applied! 32 | 33 | #include "soc/gpio_periph.h" 34 | 35 | // As currently written, only one instance of the Protomatter_core struct 36 | // is allowed, set up when calling begin()...so it's just a global here: 37 | Protomatter_core *_PM_protoPtr; 38 | 39 | #define _PM_timerFreq 40000000 // 40 MHz (1:2 prescale) 40 | 41 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 42 | 43 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) 44 | #define _PM_timerNum 0 // Timer #0 (can be 0-3) 45 | static hw_timer_t *_PM_esp32timer = NULL; 46 | #define _PM_TIMER_DEFAULT &_PM_esp32timer 47 | #else 48 | #define _PM_TIMER_DEFAULT ((void *)-1) // some non-NULL but non valid pointer 49 | #endif 50 | 51 | // The following defines and functions are common to all ESP32 variants in 52 | // the Arduino platform. Anything unique to one variant (or a subset of 53 | // variants) is declared in the corresponding esp32-*.h header(s); please 54 | // no #if defined(CONFIG_IDF_TARGET_*) action here...if you find yourself 55 | // started down that path, it's okay, but move the code out of here and 56 | // into the variant-specific headers. 57 | 58 | extern void _PM_row_handler(Protomatter_core *core); // In core.c 59 | 60 | // Timer interrupt handler. This, _PM_row_handler() and any functions 61 | // called by _PM_row_handler() should all have the IRAM_ATTR attribute 62 | // (RAM-resident functions). This isn't really the ISR itself, but a 63 | // callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c) 64 | // which takes care of interrupt status bits & such. 65 | IRAM_ATTR static void _PM_esp32timerCallback(void) { 66 | _PM_row_handler(_PM_protoPtr); // In core.c 67 | } 68 | 69 | // Set timer period, initialize count value to zero, enable timer. 70 | IRAM_ATTR inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 71 | hw_timer_t *timer = (hw_timer_t *)core->timer; 72 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) 73 | timerAlarmWrite(timer, period, true); 74 | timerAlarmEnable(timer); 75 | timerStart(timer); 76 | #else 77 | timerWrite(timer, 0); 78 | timerAlarm(timer, period ? period : 1, true, 0); 79 | timerStart(timer); 80 | #endif 81 | } 82 | 83 | // Disable timer and return current count value. 84 | // Timer must be previously initialized. 85 | IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) { 86 | timerStop((hw_timer_t *)core->timer); 87 | return _PM_timerGetCount(core); 88 | } 89 | 90 | // Initialize, but do not start, timer. This function contains timer setup 91 | // that's common to all ESP32 variants; code in variant-specific files might 92 | // set up its own special peripherals, then call this. 93 | void _PM_esp32commonTimerInit(Protomatter_core *core) { 94 | hw_timer_t *timer_in = (hw_timer_t *)core->timer; 95 | if (!timer_in || timer_in == _PM_TIMER_DEFAULT) { 96 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) 97 | core->timer = timerBegin(_PM_timerNum, 2, true); // 1:2 prescale, count up 98 | #else 99 | core->timer = timerBegin(_PM_timerFreq); 100 | #endif 101 | } 102 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) 103 | timerAttachInterrupt(core->timer, &_PM_esp32timerCallback, true); 104 | #else 105 | timerAttachInterrupt(core->timer, _PM_esp32timerCallback); 106 | #endif 107 | } 108 | 109 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 110 | 111 | // The following defines and functions are common to all ESP32 variants in 112 | // the CircuitPython platform. Anything unique to one variant (or a subset 113 | // of variants) is declared in the corresponding esp32-*.h header(s); 114 | // please no #if defined(CONFIG_IDF_TARGET_*) action here...if you find 115 | // yourself started down that path, it's okay, but move the code out of 116 | // here and into the variant-specific headers. 117 | 118 | #include "driver/gpio.h" 119 | #include "esp_idf_version.h" 120 | #include "hal/timer_ll.h" 121 | #if ESP_IDF_VERSION_MAJOR == 5 122 | #include "driver/gptimer.h" 123 | #include "esp_memory_utils.h" 124 | #else 125 | #include "driver/timer.h" 126 | #endif 127 | 128 | #define _PM_TIMER_DEFAULT NULL 129 | #define _PM_pinOutput(pin) gpio_set_direction((pin), GPIO_MODE_OUTPUT) 130 | #define _PM_pinLow(pin) gpio_set_level((pin), false) 131 | #define _PM_pinHigh(pin) gpio_set_level((pin), true) 132 | 133 | // Timer interrupt handler. This, _PM_row_handler() and any functions 134 | // called by _PM_row_handler() should all have the IRAM_ATTR attribute 135 | // (RAM-resident functions). This isn't really the ISR itself, but a 136 | // callback invoked by the real ISR (in arduino-esp32's esp32-hal-timer.c) 137 | // which takes care of interrupt status bits & such. 138 | #if ESP_IDF_VERSION_MAJOR == 5 139 | // This is "private" for now. We link to it anyway because there isn't a more 140 | // public method yet. 141 | extern bool spi_flash_cache_enabled(void); 142 | static IRAM_ATTR bool 143 | _PM_esp32timerCallback(gptimer_handle_t timer, 144 | const gptimer_alarm_event_data_t *event, void *unused) { 145 | #else 146 | static IRAM_ATTR bool _PM_esp32timerCallback(void *unused) { 147 | #endif 148 | #if ESP_IDF_VERSION_MAJOR == 5 149 | // Some functions and data used by _PM_row_handler may exist in external flash 150 | // or PSRAM so we can't run them when their access is disabled (through the 151 | // flash cache.) 152 | if (_PM_protoPtr && spi_flash_cache_enabled()) { 153 | #else 154 | if (_PM_protoPtr) { 155 | #endif 156 | _PM_row_handler(_PM_protoPtr); // In core.c 157 | } 158 | return false; 159 | }; 160 | 161 | // Set timer period, initialize count value to zero, enable timer. 162 | #if (ESP_IDF_VERSION_MAJOR == 5) 163 | IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) { 164 | gptimer_handle_t timer = (gptimer_handle_t)core->timer; 165 | 166 | gptimer_alarm_config_t alarm_config = { 167 | .reload_count = 0, // counter will reload with 0 on alarm event 168 | .alarm_count = period, // period in ms 169 | .flags.auto_reload_on_alarm = true, // enable auto-reload 170 | }; 171 | gptimer_set_alarm_action(timer, &alarm_config); 172 | gptimer_start(timer); 173 | } 174 | #else 175 | IRAM_ATTR void _PM_timerStart(Protomatter_core *core, uint32_t period) { 176 | timer_index_t *timer = (timer_index_t *)core->timer; 177 | timer_ll_set_counter_enable(timer->hw, timer->idx, false); 178 | timer_ll_set_counter_value(timer->hw, timer->idx, 0); 179 | timer_ll_set_alarm_value(timer->hw, timer->idx, period); 180 | timer_ll_set_alarm_enable(timer->hw, timer->idx, true); 181 | timer_ll_set_counter_enable(timer->hw, timer->idx, true); 182 | } 183 | #endif 184 | 185 | // Disable timer and return current count value. 186 | // Timer must be previously initialized. 187 | IRAM_ATTR uint32_t _PM_timerStop(Protomatter_core *core) { 188 | #if (ESP_IDF_VERSION_MAJOR == 5) 189 | gptimer_handle_t timer = (gptimer_handle_t)core->timer; 190 | gptimer_stop(timer); 191 | #else 192 | timer_index_t *timer = (timer_index_t *)core->timer; 193 | timer_ll_set_counter_enable(timer->hw, timer->idx, false); 194 | #endif 195 | return _PM_timerGetCount(core); 196 | } 197 | 198 | #if !defined(CONFIG_IDF_TARGET_ESP32S3) 199 | IRAM_ATTR uint32_t _PM_timerGetCount(Protomatter_core *core) { 200 | #if (ESP_IDF_VERSION_MAJOR == 5) 201 | gptimer_handle_t timer = (gptimer_handle_t)core->timer; 202 | uint64_t raw_count; 203 | gptimer_get_raw_count(timer, &raw_count); 204 | return (uint32_t)raw_count; 205 | #else 206 | timer_index_t *timer = (timer_index_t *)core->timer; 207 | uint64_t result; 208 | timer_ll_get_counter_value(timer->hw, timer->idx, &result); 209 | return (uint32_t)result; 210 | #endif 211 | } 212 | #endif 213 | 214 | // Initialize, but do not start, timer. This function contains timer setup 215 | // that's common to all ESP32 variants; code in variant-specific files might 216 | // set up its own special peripherals, then call this. 217 | static void _PM_esp32commonTimerInit(Protomatter_core *core) { 218 | 219 | #if (ESP_IDF_VERSION_MAJOR == 5) 220 | gptimer_handle_t timer = (gptimer_handle_t)core->timer; 221 | gptimer_event_callbacks_t cbs = { 222 | .on_alarm = _PM_esp32timerCallback, // register user callback 223 | }; 224 | gptimer_register_event_callbacks(timer, &cbs, NULL); 225 | 226 | gptimer_enable(timer); 227 | #else 228 | timer_index_t *timer = (timer_index_t *)core->timer; 229 | const timer_config_t config = { 230 | .alarm_en = false, 231 | .counter_en = false, 232 | .intr_type = TIMER_INTR_LEVEL, 233 | .counter_dir = TIMER_COUNT_UP, 234 | .auto_reload = true, 235 | .divider = 2 // 40MHz 236 | }; 237 | 238 | timer_init(timer->group, timer->idx, &config); 239 | timer_isr_callback_add(timer->group, timer->idx, _PM_esp32timerCallback, NULL, 240 | 0); 241 | timer_enable_intr(timer->group, timer->idx); 242 | #endif 243 | } 244 | 245 | #endif // END CIRCUITPYTHON ------------------------------------------------ 246 | 247 | #endif // END ESP32 (all variants) 248 | -------------------------------------------------------------------------------- /src/arch/esp32-s2.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file esp32-s2.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains ESP32-S2-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | // NOTE: there is some intentional repetition in the macros and functions 21 | // for some ESP32 variants. Previously they were all one file, but complex 22 | // preprocessor directives were turning into spaghetti. THEREFORE, if making 23 | // a change or bugfix in one variant-specific header, check the others to 24 | // see if the same should be applied! 25 | 26 | #if defined(CONFIG_IDF_TARGET_ESP32S2) 27 | 28 | #define _PM_portOutRegister(pin) \ 29 | (volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val) 30 | #define _PM_portSetRegister(pin) \ 31 | (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val) 32 | #define _PM_portClearRegister(pin) \ 33 | (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val) 34 | 35 | // On ESP32-S2, use the Dedicated GPIO peripheral, which allows faster bit- 36 | // toggling than the conventional GPIO registers. Unfortunately NOT present 37 | // on S3 or other ESP32 devices. Normal GPIO has a bottleneck where toggling 38 | // a pin is limited to 8 MHz max. Dedicated GPIO can work around this and 39 | // get over twice this rate, but requires some very weird hoops! 40 | // Dedicated GPIO only supports 8-bit output, so parallel output isn't 41 | // supported, but positives include that there's very flexible pin MUXing, 42 | // so matrix data in RAM can ALWAYS be stored in byte format regardless how 43 | // the RGB+clock bits are distributed among pins. 44 | #define _PM_bytesPerElement 1 45 | #define _PM_byteOffset(pin) 0 46 | #define _PM_wordOffset(pin) 0 47 | 48 | // Odd thing with Dedicated GPIO is that the bit-toggle operation is faster 49 | // than bit set or clear (perhaps some underlying operation is atomic rather 50 | // than read-modify-write). So, instruct core.c to format the matrix data in 51 | // RAM as if we're using a port toggle register, even though 52 | // _PM_portToggleRegister is NOT defined because ESP32 doesn't have that in 53 | // conventional GPIO. Good times. 54 | #define _PM_USE_TOGGLE_FORMAT 55 | 56 | // This table is used to remap 7-bit (RGB+RGB+clock) data to the 2-bits-per 57 | // GPIO format used by Dedicated GPIO. Bits corresponding to clock output 58 | // are always set here, as we want that bit toggled low at the same time new 59 | // RGB data is set up. 60 | static uint16_t _bit_toggle[128] = { 61 | 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, 0x30C0, 62 | 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300, 0x3303, 63 | 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3, 0x33CC, 64 | 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C, 0x3C0F, 65 | 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 0x3CF0, 66 | 0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 0x3F33, 67 | 0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 0x3FFC, 68 | 0x3FFF, 0x3000, 0x3003, 0x300C, 0x300F, 0x3030, 0x3033, 0x303C, 0x303F, 69 | 0x30C0, 0x30C3, 0x30CC, 0x30CF, 0x30F0, 0x30F3, 0x30FC, 0x30FF, 0x3300, 70 | 0x3303, 0x330C, 0x330F, 0x3330, 0x3333, 0x333C, 0x333F, 0x33C0, 0x33C3, 71 | 0x33CC, 0x33CF, 0x33F0, 0x33F3, 0x33FC, 0x33FF, 0x3C00, 0x3C03, 0x3C0C, 72 | 0x3C0F, 0x3C30, 0x3C33, 0x3C3C, 0x3C3F, 0x3CC0, 0x3CC3, 0x3CCC, 0x3CCF, 73 | 0x3CF0, 0x3CF3, 0x3CFC, 0x3CFF, 0x3F00, 0x3F03, 0x3F0C, 0x3F0F, 0x3F30, 74 | 0x3F33, 0x3F3C, 0x3F3F, 0x3FC0, 0x3FC3, 0x3FCC, 0x3FCF, 0x3FF0, 0x3FF3, 75 | 0x3FFC, 0x3FFF, 76 | }; 77 | 78 | #include 79 | #include 80 | #include 81 | 82 | // Override the behavior of _PM_portBitMask macro so instead of returning 83 | // a 32-bit mask for a pin within its corresponding GPIO register, it instead 84 | // returns a 7-bit mask for the pin within the Direct GPIO register *IF* it's 85 | // one of the RGB bits or the clock bit...this requires comparing against pin 86 | // numbers in the core struct. 87 | static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) { 88 | if (pin == core->clockPin) 89 | return 1 << 6; 90 | for (uint8_t i = 0; i < 6; i++) { 91 | if (pin == core->rgbPins[i]) 92 | return 1 << i; 93 | } 94 | // Else return the bit that would normally be used for regular GPIO 95 | return (1U << (pin & 31)); 96 | } 97 | // Thankfully, at present, any core code which calls _PM_portBitMask() 98 | // currently has a 'core' variable, so we can safely do this... 99 | #define _PM_portBitMask(pin) _PM_directBitMask(core, pin) 100 | 101 | // Dedicated GPIO requires a complete replacement of the "blast" functions 102 | // in order to get sufficient speed. 103 | #define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c 104 | IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) { 105 | volatile uint32_t *gpio = &DEDIC_GPIO.gpio_out_idv.val; 106 | 107 | // GPIO has already been initialized with RGB data + clock bits 108 | // all LOW, so we don't need to initialize that state here. 109 | 110 | for (uint32_t bits = core->chainBits / 8; bits--;) { 111 | *gpio = _bit_toggle[*data++]; // Toggle in new data + toggle clock low 112 | *gpio = 0b11000000000000; // Toggle clock high 113 | *gpio = _bit_toggle[*data++]; 114 | *gpio = 0b11000000000000; 115 | *gpio = _bit_toggle[*data++]; 116 | *gpio = 0b11000000000000; 117 | *gpio = _bit_toggle[*data++]; 118 | *gpio = 0b11000000000000; 119 | *gpio = _bit_toggle[*data++]; 120 | *gpio = 0b11000000000000; 121 | *gpio = _bit_toggle[*data++]; 122 | *gpio = 0b11000000000000; 123 | *gpio = _bit_toggle[*data++]; 124 | *gpio = 0b11000000000000; 125 | *gpio = _bit_toggle[*data++]; 126 | *gpio = 0b11000000000000; 127 | } 128 | 129 | // Want the pins left with RGB data and clock LOW on function exit 130 | // (so it's easier to see on 'scope, and to prime it for the next call). 131 | // This is implicit in the no-toggle case (due to how the PEW macro 132 | // works), but toggle case requires explicitly clearing those bits. 133 | // rgbAndClockMask is an 8-bit value when toggling, hence offset here. 134 | *gpio = 0b10101010101010; // Clear RGB + clock bits 135 | } 136 | 137 | // If using custom "blast" function(s), all three must be declared. 138 | // Unused ones can be empty, that's fine, just need to exist. 139 | IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {} 140 | IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {} 141 | 142 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 143 | 144 | void _PM_timerInit(Protomatter_core *core) { 145 | 146 | // On S2, initialize the Dedicated GPIO peripheral using the RGB pin list 147 | // list from the core struct, plus the clock pin (7 pins total). Unsure if 148 | // these structs & arrays need to be persistent. Declaring static just in 149 | // case...could experiment with removing one by one. 150 | static int pins[7]; 151 | for (uint8_t i = 0; i < 6; i++) 152 | pins[i] = core->rgbPins[i]; 153 | pins[6] = core->clockPin; 154 | static dedic_gpio_bundle_config_t config_in = { 155 | .gpio_array = pins, // Array of GPIO numbers 156 | .array_size = 7, // RGB pins + clock pin 157 | .flags = { 158 | .in_en = 0, // Disable input 159 | .out_en = 1, // Enable output 160 | .out_invert = 0, // Non-inverted 161 | }}; 162 | static dedic_gpio_bundle_handle_t bundle; 163 | (void)dedic_gpio_new_bundle(&config_in, &bundle); 164 | dedic_gpio_bundle_write(bundle, config_in.array_size, 1); 165 | DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions 166 | 167 | _PM_esp32commonTimerInit(core); // In esp32-common.h 168 | } 169 | 170 | // Return current count value (timer enabled or not). 171 | // Timer must be previously initialized. 172 | // This function is the same on all ESP32 parts EXCEPT S3. 173 | IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 174 | return (uint32_t)timerRead((hw_timer_t *)core->timer); 175 | } 176 | 177 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 178 | 179 | void _PM_timerInit(Protomatter_core *core) { 180 | 181 | // TO DO: adapt this function for any CircuitPython-specific changes. 182 | // If none are required, this function can be deleted and the version 183 | // above can be moved before the ARDUIO/CIRCUITPY checks. If minimal 184 | // changes, consider a single _PM_timerInit() implementation with 185 | // ARDUINO/CIRCUITPY checks inside. 186 | 187 | // On S2, initialize the Dedicated GPIO peripheral using the RGB pin list 188 | // list from the core struct, plus the clock pin (7 pins total). Unsure if 189 | // these structs & arrays need to be persistent. Declaring static just in 190 | // case...could experiment with removing one by one. 191 | static int pins[7]; 192 | for (uint8_t i = 0; i < 6; i++) 193 | pins[i] = core->rgbPins[i]; 194 | pins[6] = core->clockPin; 195 | static dedic_gpio_bundle_config_t config_in = { 196 | .gpio_array = pins, // Array of GPIO numbers 197 | .array_size = 7, // RGB pins + clock pin 198 | .flags = { 199 | .in_en = 0, // Disable input 200 | .out_en = 1, // Enable output 201 | .out_invert = 0, // Non-inverted 202 | }}; 203 | static dedic_gpio_bundle_handle_t bundle; 204 | (void)dedic_gpio_new_bundle(&config_in, &bundle); 205 | dedic_gpio_bundle_write(bundle, config_in.array_size, 1); 206 | DEDIC_GPIO.gpio_out_cpu.val = 0; // Use GPIO registers, not CPU instructions 207 | 208 | _PM_esp32commonTimerInit(core); // In esp32-common.h 209 | } 210 | 211 | #endif // END CIRCUITPYTHON ------------------------------------------------ 212 | 213 | #endif // END ESP32S2 214 | -------------------------------------------------------------------------------- /src/arch/esp32-s3.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file esp32-s3.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains ESP32-S3-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | // NOTE: there is some intentional repetition in the macros and functions 21 | // for some ESP32 variants. Previously they were all one file, but complex 22 | // preprocessor directives were turning into spaghetti. THEREFORE, if making 23 | // a change or bugfix in one variant-specific header, check the others to 24 | // see if the same should be applied! 25 | 26 | #if defined(CONFIG_IDF_TARGET_ESP32S3) 27 | 28 | #define GPIO_DRIVE_STRENGTH GPIO_DRIVE_CAP_3 29 | #define LCD_CLK_PRESCALE 9 // 8, 9, 10 allowed. Bit clock = 160 MHz / this. 30 | 31 | #if defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 32 | #include "components/esp_rom/include/esp_rom_sys.h" 33 | #include "components/heap/include/esp_heap_caps.h" 34 | #endif 35 | 36 | // Use DMA-capable RAM (not PSRAM) for framebuffer: 37 | #define _PM_allocate(x) heap_caps_malloc(x, MALLOC_CAP_DMA | MALLOC_CAP_8BIT) 38 | #define _PM_free(x) heap_caps_free(x) 39 | 40 | #define _PM_portOutRegister(pin) \ 41 | (volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val) 42 | #define _PM_portSetRegister(pin) \ 43 | (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val) 44 | #define _PM_portClearRegister(pin) \ 45 | (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val) 46 | 47 | // On ESP32-S3, use the LCD_CAM peripheral for fast parallel output. 48 | // Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in 49 | // byte format regardless how RGB+clock bits are distributed among pins. 50 | #define _PM_bytesPerElement 1 51 | #define _PM_byteOffset(pin) 0 52 | #define _PM_wordOffset(pin) 0 53 | 54 | // On ESP32-S3, use the LCD_CAM peripheral for fast parallel output. 55 | // Thanks to ESP's pin MUXing, matrix data in RAM can ALWAYS be stored in 56 | // byte format regardless how RGB+clock bits are distributed among pins. 57 | #define _PM_bytesPerElement 1 58 | #define _PM_byteOffset(pin) 0 59 | #define _PM_wordOffset(pin) 0 60 | 61 | // On most architectures, _PM_timerGetCount() is used to measure bitbang 62 | // speed for one scanline, which is then used for bitplane 0 time, and each 63 | // subsequent plane doubles that. Since ESP32-S3 uses DMA and we don't have 64 | // an end-of-transfer interrupt, we make an informed approximation. 65 | // dmaSetupTime (measured in blast_byte()) measures the number of timer 66 | // cycles to set up and trigger the DMA transfer... 67 | static uint32_t dmaSetupTime = 100; 68 | // ...then, the version of _PM_timerGetCount() here uses that as a starting 69 | // point, plus the known constant DMA xfer speed (160/LCD_CLK_PRESCALE MHz) 70 | // and timer frequency (40 MHz), to return an estimate of the one-scanline 71 | // transfer time, from which everything is extrapolated: 72 | IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 73 | // Time estimate seems to come in a little high, so the -10 here is an 74 | // empirically-derived fudge factor that may yield ever-so-slightly better 75 | // refresh in some edge cases. If visual glitches are encountered, might 76 | // need to dial back this number a bit or remove it. 77 | return dmaSetupTime + core->chainBits * 40 * LCD_CLK_PRESCALE / 160 - 10; 78 | } 79 | // Note that dmaSetupTime can vary from line to line, potentially influenced 80 | // by interrupts, nondeterministic DMA channel clearing times, etc., which is 81 | // why we don't just use a constant value. Each scanline might show for a 82 | // slightly different length of time, but duty cycle scales with this so it's 83 | // perceptually consistent; don't see bright or dark rows. 84 | 85 | #define _PM_minMinPeriod \ 86 | (200 + (uint32_t)core->chainBits * 40 * LCD_CLK_PRESCALE / 160) 87 | 88 | #if (ESP_IDF_VERSION_MAJOR == 5) 89 | #include 90 | #else 91 | #include 92 | #endif 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | #include 100 | #include 101 | 102 | // Override the behavior of _PM_portBitMask macro so instead of returning 103 | // a 32-bit mask for a pin within its corresponding GPIO register, it instead 104 | // returns a 7-bit mask for the pin within the LCD_CAM data order *IF* it's 105 | // one of the RGB bits or the clock bit...this requires comparing against pin 106 | // numbers in the core struct. 107 | static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) { 108 | if (pin == core->clockPin) 109 | return 1 << 6; 110 | for (uint8_t i = 0; i < 6; i++) { 111 | if (pin == core->rgbPins[i]) 112 | return 1 << i; 113 | } 114 | // Else return the bit that would normally be used for regular GPIO 115 | return (1U << (pin & 31)); 116 | } 117 | 118 | // Thankfully, at present, any core code which calls _PM_portBitMask() 119 | // currently has a 'core' variable, so we can safely do this... 120 | #define _PM_portBitMask(pin) _PM_directBitMask(core, pin) 121 | 122 | static dma_descriptor_t desc; 123 | static gdma_channel_handle_t dma_chan; 124 | 125 | // If using custom "blast" function(s), all three must be declared. 126 | // Unused ones can be empty, that's fine, just need to exist. 127 | IRAM_ATTR static void blast_word(Protomatter_core *core, uint16_t *data) {} 128 | IRAM_ATTR static void blast_long(Protomatter_core *core, uint32_t *data) {} 129 | 130 | static void pinmux(int8_t pin, uint8_t signal) { 131 | esp_rom_gpio_connect_out_signal(pin, signal, false, false); 132 | gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO); 133 | gpio_set_drive_capability((gpio_num_t)pin, GPIO_DRIVE_STRENGTH); 134 | } 135 | 136 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 137 | 138 | // LCD_CAM requires a complete replacement of the "blast" functions in order 139 | // to use the DMA-based peripheral. 140 | #define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c 141 | IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) { 142 | // Reset LCD DOUT parameters each time (required). 143 | // IN PRINCIPLE, cyclelen should be chainBits-1 (resulting in chainBits 144 | // cycles). But due to the required dummy phases at start of transfer, 145 | // extend by 1; set to chainBits, issue chainBits+1 cycles. 146 | LCD_CAM.lcd_user.lcd_dout_cyclelen = core->chainBits; 147 | LCD_CAM.lcd_user.lcd_dout = 1; 148 | LCD_CAM.lcd_user.lcd_update = 1; 149 | 150 | // Reset LCD TX FIFO each time, else we see old data. When doing this, 151 | // it's REQUIRED in the setup code to enable at least one dummy pulse, 152 | // else the PCLK & data are randomly misaligned by 1-2 clocks! 153 | LCD_CAM.lcd_misc.lcd_afifo_reset = 1; 154 | 155 | // Partially re-init descriptor each time (required) 156 | desc.dw0.size = desc.dw0.length = core->chainBits; 157 | desc.buffer = data; 158 | gdma_start(dma_chan, (intptr_t)&desc); 159 | esp_rom_delay_us(1); // Necessary before starting xfer 160 | 161 | LCD_CAM.lcd_user.lcd_start = 1; // Begin LCD DMA xfer 162 | 163 | // Timer was cleared to 0 before calling blast_byte(), so this 164 | // is the state of the timer immediately after DMA started: 165 | dmaSetupTime = (uint32_t)timerRead((hw_timer_t *)core->timer); 166 | // See notes near top of this file for what's done with this info. 167 | } 168 | 169 | void _PM_timerInit(Protomatter_core *core) { 170 | // On S3, initialize the LCD_CAM peripheral and DMA. 171 | 172 | // LCD_CAM isn't enabled by default -- MUST begin with this: 173 | periph_module_enable(PERIPH_LCD_CAM_MODULE); 174 | periph_module_reset(PERIPH_LCD_CAM_MODULE); 175 | 176 | // Reset LCD bus 177 | LCD_CAM.lcd_user.lcd_reset = 1; 178 | esp_rom_delay_us(100); 179 | 180 | // Configure LCD clock 181 | LCD_CAM.lcd_clock.clk_en = 1; // Enable clock 182 | LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source 183 | LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide, 184 | LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus prescale below yields... 185 | #if LCD_CLK_PRESCALE == 8 186 | LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK) 187 | #elif LCD_CLK_PRESCALE == 9 188 | LCD_CAM.lcd_clock.lcd_clkm_div_num = 8; // 1:9 prescale (17.8 MHz CLK) 189 | #else 190 | LCD_CAM.lcd_clock.lcd_clkm_div_num = 9; // 1:10 prescale (16 MHz CLK) 191 | #endif 192 | LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle 193 | LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle 194 | LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N) 195 | 196 | // Configure frame format. Some of these could probably be skipped and 197 | // use defaults, but being verbose for posterity... 198 | LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB) 199 | LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter 200 | LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame 201 | LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays 202 | LCD_CAM.lcd_user.lcd_always_out_en = 0; // Only when requested 203 | LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes 204 | LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order 205 | LCD_CAM.lcd_user.lcd_2byte_en = 0; // 8-bit data mode 206 | // MUST enable at least one dummy phase at start of output, else clock and 207 | // data are randomly misaligned by 1-2 cycles following required TX FIFO 208 | // reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit 209 | // (as in still very occasionally misaligned by 1 cycle). Two seems ideal; 210 | // no sparkle. Since HUB75 is just a shift register, the extra clock ticks 211 | // are harmless and the zero-data shifts off end of the chain. 212 | LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start 213 | LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases 214 | LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start 215 | LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0; 216 | LCD_CAM.lcd_user.lcd_update = 1; 217 | 218 | // Configure signal pins. IN THEORY this could be expanded to support 219 | // 2 parallel chains, but the rest of the LCD & DMA setup is not currently 220 | // written for that, so it's limited to a single chain for now. 221 | const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX, 222 | LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX, 223 | LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX}; 224 | for (int i = 0; i < 6; i++) 225 | pinmux(core->rgbPins[i], signal[i]); 226 | pinmux(core->clockPin, LCD_PCLK_IDX); 227 | gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_STRENGTH); 228 | gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH); 229 | for (uint8_t i = 0; i < core->numAddressLines; i++) { 230 | gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_STRENGTH); 231 | } 232 | 233 | // Disable LCD_CAM interrupts, clear any pending interrupt 234 | LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE; 235 | LCD_CAM.lc_dma_int_clr.val = 0x03; 236 | 237 | // Set up DMA TX descriptor 238 | desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; 239 | desc.dw0.suc_eof = 1; 240 | desc.dw0.size = desc.dw0.length = core->chainBits; 241 | desc.buffer = core->screenData; 242 | desc.next = NULL; 243 | 244 | // Alloc DMA channel & connect it to LCD periph 245 | gdma_channel_alloc_config_t dma_chan_config = { 246 | .sibling_chan = NULL, 247 | .direction = GDMA_CHANNEL_DIRECTION_TX, 248 | .flags = {.reserve_sibling = 0}}; 249 | esp_err_t ret = gdma_new_channel(&dma_chan_config, &dma_chan); 250 | (void)ret; 251 | gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); 252 | gdma_strategy_config_t strategy_config = {.owner_check = false, 253 | .auto_update_desc = false}; 254 | gdma_apply_strategy(dma_chan, &strategy_config); 255 | gdma_transfer_ability_t ability = { 256 | .sram_trans_align = 0, 257 | .psram_trans_align = 0, 258 | }; 259 | gdma_set_transfer_ability(dma_chan, &ability); 260 | gdma_start(dma_chan, (intptr_t)&desc); 261 | 262 | // Enable TRANS_DONE interrupt. Note that we do NOT require nor install 263 | // an interrupt service routine, but DO need to enable the TRANS_DONE 264 | // flag to make the LCD DMA transfer work. 265 | LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03; 266 | 267 | _PM_esp32commonTimerInit(core); // In esp32-common.h 268 | } 269 | 270 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 271 | 272 | // LCD_CAM requires a complete replacement of the "blast" functions in order 273 | // to use the DMA-based peripheral. 274 | #define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c 275 | IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) { 276 | // Reset LCD DOUT parameters each time (required). 277 | // IN PRINCIPLE, cyclelen should be chainBits-1 (resulting in chainBits 278 | // cycles). But due to the required dummy phases at start of transfer, 279 | // extend by 1; set to chainBits, issue chainBits+1 cycles. 280 | LCD_CAM.lcd_user.lcd_dout_cyclelen = core->chainBits; 281 | LCD_CAM.lcd_user.lcd_dout = 1; 282 | LCD_CAM.lcd_user.lcd_update = 1; 283 | 284 | // Reset LCD TX FIFO each time, else we see old data. When doing this, 285 | // it's REQUIRED in the setup code to enable at least one dummy pulse, 286 | // else the PCLK & data are randomly misaligned by 1-2 clocks! 287 | LCD_CAM.lcd_misc.lcd_afifo_reset = 1; 288 | 289 | // Partially re-init descriptor each time (required) 290 | desc.dw0.size = desc.dw0.length = core->chainBits; 291 | desc.buffer = data; 292 | gdma_start(dma_chan, (intptr_t)&desc); 293 | esp_rom_delay_us(1); // Necessary before starting xfer 294 | 295 | LCD_CAM.lcd_user.lcd_start = 1; // Begin LCD DMA xfer 296 | 297 | // Timer was cleared to 0 before calling blast_byte(), so this 298 | // is the state of the timer immediately after DMA started: 299 | uint64_t value; 300 | 301 | #if (ESP_IDF_VERSION_MAJOR == 5) 302 | gptimer_handle_t timer = (gptimer_handle_t)core->timer; 303 | gptimer_get_raw_count(timer, &value); 304 | #else 305 | timer_index_t *timer = (timer_index_t *)core->timer; 306 | timer_get_counter_value(timer->group, timer->idx, &value); 307 | #endif 308 | dmaSetupTime = (uint32_t)value; 309 | // See notes near top of this file for what's done with this info. 310 | } 311 | 312 | static void _PM_timerInit(Protomatter_core *core) { 313 | 314 | // TO DO: adapt this function for any CircuitPython-specific changes. 315 | // If none are required, this function can be deleted and the version 316 | // above can be moved before the ARDUIO/CIRCUITPY checks. If minimal 317 | // changes, consider a single _PM_timerInit() implementation with 318 | // ARDUINO/CIRCUITPY checks inside. It's all good. 319 | 320 | // On S3, initialize the LCD_CAM peripheral and DMA. 321 | 322 | // LCD_CAM isn't enabled by default -- MUST begin with this: 323 | periph_module_enable(PERIPH_LCD_CAM_MODULE); 324 | periph_module_reset(PERIPH_LCD_CAM_MODULE); 325 | 326 | // Reset LCD bus 327 | LCD_CAM.lcd_user.lcd_reset = 1; 328 | esp_rom_delay_us(100); 329 | 330 | // Configure LCD clock 331 | LCD_CAM.lcd_clock.clk_en = 1; // Enable clock 332 | LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source 333 | LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide, 334 | LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus prescale below yields... 335 | #if LCD_CLK_PRESCALE == 8 336 | LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK) 337 | #elif LCD_CLK_PRESCALE == 9 338 | LCD_CAM.lcd_clock.lcd_clkm_div_num = 8; // 1:9 prescale (17.8 MHz CLK) 339 | #else 340 | LCD_CAM.lcd_clock.lcd_clkm_div_num = 9; // 1:10 prescale (16 MHz CLK) 341 | #endif 342 | LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle 343 | LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle 344 | LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N) 345 | 346 | // Configure frame format. Some of these could probably be skipped and 347 | // use defaults, but being verbose for posterity... 348 | LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB) 349 | LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter 350 | LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame 351 | LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays 352 | LCD_CAM.lcd_user.lcd_always_out_en = 0; // Only when requested 353 | LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes 354 | LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order 355 | LCD_CAM.lcd_user.lcd_2byte_en = 0; // 8-bit data mode 356 | // MUST enable at least one dummy phase at start of output, else clock and 357 | // data are randomly misaligned by 1-2 cycles following required TX FIFO 358 | // reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit 359 | // (as in still very occasionally misaligned by 1 cycle). Two seems ideal; 360 | // no sparkle. Since HUB75 is just a shift register, the extra clock ticks 361 | // are harmless and the zero-data shifts off end of the chain. 362 | LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start 363 | LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases 364 | LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start 365 | LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0; 366 | LCD_CAM.lcd_user.lcd_update = 1; 367 | 368 | // Configure signal pins. IN THEORY this could be expanded to support 369 | // 2 parallel chains, but the rest of the LCD & DMA setup is not currently 370 | // written for that, so it's limited to a single chain for now. 371 | const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX, 372 | LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX, 373 | LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX}; 374 | for (int i = 0; i < 6; i++) 375 | pinmux(core->rgbPins[i], signal[i]); 376 | pinmux(core->clockPin, LCD_PCLK_IDX); 377 | gpio_set_drive_capability(core->latch.pin, GPIO_DRIVE_STRENGTH); 378 | gpio_set_drive_capability(core->oe.pin, GPIO_DRIVE_STRENGTH); 379 | for (uint8_t i = 0; i < core->numAddressLines; i++) { 380 | gpio_set_drive_capability(core->addr[i].pin, GPIO_DRIVE_STRENGTH); 381 | } 382 | 383 | // Disable LCD_CAM interrupts, clear any pending interrupt 384 | LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE; 385 | LCD_CAM.lc_dma_int_clr.val = 0x03; 386 | 387 | // Set up DMA TX descriptor 388 | desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; 389 | desc.dw0.suc_eof = 1; 390 | desc.dw0.size = desc.dw0.length = core->chainBits; 391 | desc.buffer = core->screenData; 392 | desc.next = NULL; 393 | 394 | // Alloc DMA channel & connect it to LCD periph 395 | if (dma_chan == NULL) { 396 | gdma_channel_alloc_config_t dma_chan_config = { 397 | .sibling_chan = NULL, 398 | .direction = GDMA_CHANNEL_DIRECTION_TX, 399 | .flags = {.reserve_sibling = 0}}; 400 | gdma_new_channel(&dma_chan_config, &dma_chan); 401 | gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); 402 | gdma_strategy_config_t strategy_config = {.owner_check = false, 403 | .auto_update_desc = false}; 404 | gdma_apply_strategy(dma_chan, &strategy_config); 405 | gdma_transfer_ability_t ability = { 406 | .sram_trans_align = 0, 407 | .psram_trans_align = 0, 408 | }; 409 | gdma_set_transfer_ability(dma_chan, &ability); 410 | } 411 | gdma_start(dma_chan, (intptr_t)&desc); 412 | 413 | // Enable TRANS_DONE interrupt. Note that we do NOT require nor install 414 | // an interrupt service routine, but DO need to enable the TRANS_DONE 415 | // flag to make the LCD DMA transfer work. 416 | LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03; 417 | 418 | _PM_esp32commonTimerInit(core); // In esp32-common.h 419 | } 420 | 421 | #endif // END CIRCUITPYTHON ------------------------------------------------ 422 | 423 | #endif // END ESP32S3 424 | -------------------------------------------------------------------------------- /src/arch/esp32.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file esp32.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains ORIGINAL-ESP32-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | // NOTE: there is some intentional repetition in the macros and functions 21 | // for some ESP32 variants. Previously they were all one file, but complex 22 | // preprocessor directives were turning into spaghetti. THEREFORE, if making 23 | // a change or bugfix in one variant-specific header, check the others to 24 | // see if the same should be applied! 25 | 26 | #if defined(CONFIG_IDF_TARGET_ESP32) // ORIGINAL ESP32, NOT S2/S3/etc. 27 | 28 | #define _PM_portOutRegister(pin) \ 29 | (volatile uint32_t *)((pin < 32) ? &GPIO.out : &GPIO.out1.val) 30 | #define _PM_portSetRegister(pin) \ 31 | (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val) 32 | #define _PM_portClearRegister(pin) \ 33 | (volatile uint32_t *)((pin < 32) ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val) 34 | 35 | #define _PM_portBitMask(pin) (1U << ((pin)&31)) 36 | 37 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 38 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 39 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 40 | #else 41 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 42 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 43 | #endif 44 | 45 | // No special peripheral setup on OG ESP32, just use common timer init... 46 | #define _PM_timerInit(core) _PM_esp32commonTimerInit(core); 47 | 48 | // Return current count value (timer enabled or not). 49 | // Timer must be previously initialized. 50 | // This function is the same on all ESP32 parts EXCEPT S3. 51 | IRAM_ATTR inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 52 | return (uint32_t)timerRead((hw_timer_t *)core->timer); 53 | } 54 | 55 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 56 | 57 | // ESP32 requires a custom PEW declaration (issues one set of RGB color bits 58 | // followed by clock pulse). Turns out the bit set/clear registers are not 59 | // actually atomic. If two writes are made in quick succession, the second 60 | // has no effect. One option is NOPs, other is to write a 0 (no effect) to 61 | // the opposing register (set vs clear) to synchronize the next write. 62 | // S2, S3 replace the whole "blast" functions and don't use PEW. C3 can use 63 | // the default PEW. 64 | #if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) 65 | #define PEW \ 66 | *set = *data++; /* Set RGB data high */ \ 67 | *clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \ 68 | *set_full = clock; /* Set clock high */ \ 69 | *clear_full = rgbclock; /* Clear RGB data + clock */ \ 70 | ///< Bitbang one set of RGB data bits to matrix 71 | #endif // end !ESP32S3/S2 72 | 73 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 74 | 75 | #define _PM_STRICT_32BIT_IO (1) 76 | 77 | // ESP32 requires a custom PEW declaration (issues one set of RGB color bits 78 | // followed by clock pulse). Turns out the bit set/clear registers are not 79 | // actually atomic. If two writes are made in quick succession, the second 80 | // has no effect. One option is NOPs, other is to write a 0 (no effect) to 81 | // the opposing register (set vs clear) to synchronize the next write. 82 | #define PEW \ 83 | *set = (*data++) << shift; /* Set RGB data high */ \ 84 | *clear_full = 0; /* ESP32 MUST sync before 2nd 'set' */ \ 85 | *set = clock; /* Set clock high */ \ 86 | *clear_full = rgbclock; /* Clear RGB data + clock */ \ 87 | ///< Bitbang one set of RGB data bits to matrix 88 | 89 | #endif // END CIRCUITPYTHON ------------------------------------------------ 90 | 91 | #endif // END ESP32 92 | -------------------------------------------------------------------------------- /src/arch/nrf52.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file nrf52.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains NRF52-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(NRF52_SERIES) 21 | 22 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 23 | 24 | // digitalPinToPort, g_ADigitalPinMap[] are Arduino specific: 25 | 26 | void *_PM_portOutRegister(uint32_t pin) { 27 | NRF_GPIO_Type *port = digitalPinToPort(pin); 28 | return &port->OUT; 29 | } 30 | 31 | void *_PM_portSetRegister(uint32_t pin) { 32 | NRF_GPIO_Type *port = digitalPinToPort(pin); 33 | return &port->OUTSET; 34 | } 35 | 36 | void *_PM_portClearRegister(uint32_t pin) { 37 | NRF_GPIO_Type *port = digitalPinToPort(pin); 38 | return &port->OUTCLR; 39 | } 40 | 41 | // Leave _PM_portToggleRegister(pin) undefined on nRF! 42 | 43 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 44 | #define _PM_byteOffset(pin) ((g_ADigitalPinMap[pin] & 0x1F) / 8) 45 | #define _PM_wordOffset(pin) ((g_ADigitalPinMap[pin] & 0x1F) / 16) 46 | #else 47 | #define _PM_byteOffset(pin) (3 - ((g_ADigitalPinMap[pin] & 0x1F) / 8)) 48 | #define _PM_wordOffset(pin) (1 - ((g_ADigitalPinMap[pin] & 0x1F) / 16)) 49 | #endif 50 | 51 | // Because it's tied to a specific timer right now, there can be only 52 | // one instance of the Protomatter_core struct. The Arduino library 53 | // sets up this pointer when calling begin(). 54 | void *_PM_protoPtr = NULL; 55 | 56 | // Arduino implementation is tied to a specific timer/counter, 57 | // Partly because IRQs must be declared at compile-time. 58 | #define _PM_IRQ_HANDLER TIMER4_IRQHandler 59 | #define _PM_timerFreq 16000000 60 | #define _PM_TIMER_DEFAULT NRF_TIMER4 61 | 62 | #ifdef __cplusplus 63 | extern "C" { 64 | #endif 65 | 66 | // Timer interrupt service routine 67 | void _PM_IRQ_HANDLER(void) { 68 | if (_PM_TIMER_DEFAULT->EVENTS_COMPARE[0]) { 69 | _PM_TIMER_DEFAULT->EVENTS_COMPARE[0] = 0; 70 | } 71 | _PM_row_handler(_PM_protoPtr); // In core.c 72 | } 73 | 74 | #ifdef __cplusplus 75 | } 76 | #endif 77 | 78 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 79 | 80 | #include "nrf_gpio.h" 81 | 82 | volatile uint32_t *_PM_portOutRegister(uint32_t pin) { 83 | NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin); 84 | return &port->OUT; 85 | } 86 | 87 | volatile uint32_t *_PM_portSetRegister(uint32_t pin) { 88 | NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin); 89 | return &port->OUTSET; 90 | } 91 | 92 | volatile uint32_t *_PM_portClearRegister(uint32_t pin) { 93 | NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&pin); 94 | return &port->OUTCLR; 95 | } 96 | #define _PM_pinOutput(pin) \ 97 | nrf_gpio_cfg(pin, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, \ 98 | NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE) 99 | #define _PM_pinInput(pin) nrf_gpio_cfg_input(pin) 100 | #define _PM_pinHigh(pin) nrf_gpio_pin_set(pin) 101 | #define _PM_pinLow(pin) nrf_gpio_pin_clear(pin) 102 | #define _PM_portBitMask(pin) (1u << ((pin)&31)) 103 | 104 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 105 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 106 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 107 | #else 108 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 109 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 110 | #endif 111 | 112 | // CircuitPython implementation is tied to a specific freq (but the counter 113 | // is dynamically allocated): 114 | #define _PM_timerFreq 16000000 115 | 116 | // Because it's tied to a specific timer right now, there can be only 117 | // one instance of the Protomatter_core struct. The Arduino library 118 | // sets up this pointer when calling begin(). 119 | void *_PM_protoPtr = NULL; 120 | 121 | // Timer interrupt service routine 122 | void _PM_IRQ_HANDLER(void) { 123 | NRF_TIMER_Type *timer = (((Protomatter_core *)_PM_protoPtr)->timer); 124 | if (timer->EVENTS_COMPARE[0]) { 125 | timer->EVENTS_COMPARE[0] = 0; 126 | } 127 | 128 | _PM_row_handler(_PM_protoPtr); // In core.c 129 | } 130 | 131 | #else // END CIRCUITPYTHON ------------------------------------------------- 132 | 133 | // Byte offset macros, timer and ISR work for other environments go here. 134 | 135 | #endif 136 | 137 | // CODE COMMON TO ALL ENVIRONMENTS ----------------------------------------- 138 | 139 | void _PM_timerInit(Protomatter_core *core) { 140 | static const struct { 141 | NRF_TIMER_Type *tc; // -> Timer peripheral base address 142 | IRQn_Type IRQn; // Interrupt number 143 | } timer[] = { 144 | #if defined(NRF_TIMER0) 145 | {NRF_TIMER0, TIMER0_IRQn}, 146 | #endif 147 | #if defined(NRF_TIMER1) 148 | {NRF_TIMER1, TIMER1_IRQn}, 149 | #endif 150 | #if defined(NRF_TIMER2) 151 | {NRF_TIMER2, TIMER2_IRQn}, 152 | #endif 153 | #if defined(NRF_TIMER3) 154 | {NRF_TIMER3, TIMER3_IRQn}, 155 | #endif 156 | #if defined(NRF_TIMER4) 157 | {NRF_TIMER4, TIMER4_IRQn}, 158 | #endif 159 | }; 160 | #define NUM_TIMERS (sizeof timer / sizeof timer[0]) 161 | 162 | // Determine IRQn from timer address 163 | uint8_t timerNum = 0; 164 | while ((timerNum < NUM_TIMERS) && 165 | (timer[timerNum].tc != (NRF_TIMER_Type *)core->timer)) { 166 | timerNum++; 167 | } 168 | if (timerNum >= NUM_TIMERS) 169 | return; 170 | 171 | NRF_TIMER_Type *tc = timer[timerNum].tc; 172 | 173 | tc->TASKS_STOP = 1; // Stop timer 174 | tc->MODE = TIMER_MODE_MODE_Timer; // Timer (not counter) mode 175 | tc->TASKS_CLEAR = 1; 176 | tc->BITMODE = TIMER_BITMODE_BITMODE_16Bit 177 | << TIMER_BITMODE_BITMODE_Pos; // 16-bit timer res 178 | tc->PRESCALER = 0; // 1:1 prescale (16 MHz) 179 | tc->INTENSET = TIMER_INTENSET_COMPARE0_Enabled 180 | << TIMER_INTENSET_COMPARE0_Pos; // Event 0 interrupt 181 | // NVIC_DisableIRQ(timer[timerNum].IRQn); 182 | // NVIC_ClearPendingIRQ(timer[timerNum].IRQn); 183 | // NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority 184 | NVIC_EnableIRQ(timer[timerNum].IRQn); 185 | } 186 | 187 | inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 188 | volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer; 189 | tc->TASKS_STOP = 1; // Stop timer 190 | tc->TASKS_CLEAR = 1; // Reset to 0 191 | tc->CC[0] = period; 192 | tc->TASKS_START = 1; // Start timer 193 | } 194 | 195 | inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 196 | volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer; 197 | tc->TASKS_CAPTURE[1] = 1; // Capture timer to CC[1] register 198 | return tc->CC[1]; // (don't clobber value in CC[0]) 199 | } 200 | 201 | uint32_t _PM_timerStop(Protomatter_core *core) { 202 | volatile NRF_TIMER_Type *tc = (volatile NRF_TIMER_Type *)core->timer; 203 | tc->TASKS_STOP = 1; // Stop timer 204 | __attribute__((unused)) uint32_t count = _PM_timerGetCount(core); 205 | return count; 206 | } 207 | 208 | #define _PM_clockHoldHigh asm("nop; nop"); 209 | 210 | #define _PM_minMinPeriod 100 211 | 212 | #endif // END NRF52_SERIES 213 | -------------------------------------------------------------------------------- /src/arch/rp2040.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file rp2040.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains RP2040 (Raspberry Pi Pico, etc.) SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | * RP2040 NOTES: This initial implementation does NOT use PIO. That's normal 17 | * for Protomatter, which was written for simple GPIO + timer interrupt for 18 | * broadest portability. While not entirely optimal, it's not pessimal 19 | * either...no worse than any other platform where we're not taking 20 | * advantage of device-specific DMA or peripherals. Would require changes to 21 | * the 'blast' functions or possibly the whole _PM_row_handler() (both 22 | * currently in core.c). CPU load is just a few percent for a 64x32 23 | * matrix @ 6-bit depth, so I'm not losing sleep over this. 24 | * 25 | */ 26 | 27 | #pragma once 28 | 29 | #if defined(ARDUINO_ARCH_RP2040) || defined(PICO_BOARD) || \ 30 | defined(__RP2040__) || defined(__RP2350__) 31 | 32 | #include "../../hardware_pwm/include/hardware/pwm.h" 33 | #include "hardware/irq.h" 34 | #include "hardware/timer.h" 35 | #include "pico/stdlib.h" // For sio_hw, etc. 36 | 37 | // RP2040 only allows full 32-bit aligned writes to GPIO. 38 | #define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only 39 | 40 | // Enable this to use PWM for bitplane timing, else a timer alarm is used. 41 | // PWM has finer resolution, but alarm is adequate -- this is more about 42 | // which peripheral we'd rather use, as both are finite resources. 43 | #ifndef _PM_CLOCK_PWM 44 | #define _PM_CLOCK_PWM (1) 45 | #endif 46 | 47 | #if _PM_CLOCK_PWM // Use PWM for timing 48 | static void _PM_PWM_ISR(void); 49 | #else // Use timer alarm for timing 50 | static void _PM_timerISR(void); 51 | #endif 52 | 53 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 54 | 55 | // THIS CURRENTLY ONLY WORKS WITH THE PHILHOWER RP2040 CORE. 56 | // mbed Arduino RP2040 core won't compile due to missing stdio.h. 57 | // Also, much of this currently assumes GPXX pin numbers with no remapping. 58 | 59 | // 'pin' here is GPXX # (might change if pin remapping gets added in core) 60 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 61 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 62 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 63 | #else 64 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 65 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 66 | #endif 67 | 68 | #if _PM_CLOCK_PWM // Use PWM for timing 69 | 70 | // Arduino implementation is tied to a specific PWM slice & frequency 71 | #define _PM_PWM_SLICE 0 72 | #define _PM_PWM_DIV ((F_CPU + 20000000) / 40000000) // 125 MHz->3->~41.6 MHz 73 | #define _PM_timerFreq (F_CPU / _PM_PWM_DIV) 74 | #define _PM_TIMER_DEFAULT NULL 75 | 76 | #else // Use alarm for timing 77 | 78 | // Arduino implementation is tied to a specific timer alarm & frequency 79 | #define _PM_ALARM_NUM 1 80 | #define _PM_IRQ_HANDLER TIMER_IRQ_1 81 | #define _PM_timerFreq 1000000 82 | #define _PM_TIMER_DEFAULT NULL 83 | 84 | #endif 85 | 86 | // Initialize, but do not start, timer. 87 | void _PM_timerInit(Protomatter_core *core) { 88 | #if _PM_CLOCK_PWM 89 | // Enable PWM wrap interrupt 90 | pwm_clear_irq(_PM_PWM_SLICE); 91 | pwm_set_irq_enabled(_PM_PWM_SLICE, true); 92 | irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR); 93 | irq_set_enabled(PWM_IRQ_WRAP, true); 94 | 95 | // Config but do not start PWM 96 | pwm_config config = pwm_get_default_config(); 97 | pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV); 98 | pwm_init(_PM_PWM_SLICE, &config, true); 99 | #else 100 | timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer 101 | hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM); 102 | irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler 103 | #endif 104 | } 105 | 106 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 107 | 108 | #if !defined(F_CPU) // Not sure if CircuitPython build defines this 109 | #ifdef __RP2040__ 110 | #define F_CPU 125000000 // Standard RP2040 clock speed 111 | #endif 112 | #ifdef __RP2350__ 113 | #define F_CPU 150000000 // Standard RP2350 clock speed 114 | #endif 115 | #endif 116 | 117 | // 'pin' here is GPXX # 118 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 119 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 120 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 121 | #else 122 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 123 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 124 | #endif 125 | 126 | #if _PM_CLOCK_PWM 127 | 128 | int _PM_pwm_slice; 129 | #define _PM_PWM_SLICE (_PM_pwm_slice & 0xff) 130 | #define _PM_PWM_DIV 3 // ~41.6 MHz, similar to SAMD 131 | #define _PM_timerFreq (F_CPU / _PM_PWM_DIV) 132 | #define _PM_TIMER_DEFAULT NULL 133 | 134 | #else // Use alarm for timing 135 | 136 | // Currently tied to a specific timer alarm & frequency 137 | #define _PM_ALARM_NUM 1 138 | #define _PM_IRQ_HANDLER TIMER_IRQ_1 139 | #define _PM_timerFreq 1000000 140 | #define _PM_TIMER_DEFAULT NULL 141 | 142 | #endif // end PWM/alarm 143 | 144 | // Initialize, but do not start, timer. 145 | void _PM_timerInit(Protomatter_core *core) { 146 | #if _PM_CLOCK_PWM 147 | _PM_pwm_slice = (int)core->timer & 0xff; 148 | // Enable PWM wrap interrupt 149 | pwm_clear_irq(_PM_PWM_SLICE); 150 | pwm_set_irq_enabled(_PM_PWM_SLICE, true); 151 | irq_set_exclusive_handler(PWM_IRQ_WRAP, _PM_PWM_ISR); 152 | irq_set_enabled(PWM_IRQ_WRAP, true); 153 | 154 | // Config but do not start PWM 155 | pwm_config config = pwm_get_default_config(); 156 | pwm_config_set_clkdiv_int(&config, _PM_PWM_DIV); 157 | pwm_init(_PM_PWM_SLICE, &config, true); 158 | #else 159 | timer_hw->alarm[_PM_ALARM_NUM] = timer_hw->timerawl; // Clear any timer 160 | hw_set_bits(&timer_hw->inte, 1u << _PM_ALARM_NUM); 161 | irq_set_exclusive_handler(_PM_IRQ_HANDLER, _PM_timerISR); // Set IRQ handler 162 | #endif 163 | } 164 | 165 | // 'pin' here is GPXX # 166 | #define _PM_portBitMask(pin) (1UL << pin) 167 | // Same for these -- using GPXX # 168 | #define _PM_pinOutput(pin) \ 169 | { \ 170 | gpio_init(pin); \ 171 | gpio_set_dir(pin, GPIO_OUT); \ 172 | } 173 | #define _PM_pinLow(pin) gpio_clr_mask(1UL << pin) 174 | #define _PM_pinHigh(pin) gpio_set_mask(1UL << pin) 175 | 176 | #ifndef _PM_delayMicroseconds 177 | #define _PM_delayMicroseconds(n) sleep_us(n) 178 | #endif 179 | 180 | #endif // end CIRCUITPY 181 | 182 | #define _PM_portOutRegister(pin) ((void *)&sio_hw->gpio_out) 183 | #define _PM_portSetRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_set) 184 | #define _PM_portClearRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_clr) 185 | #define _PM_portToggleRegister(pin) ((volatile uint32_t *)&sio_hw->gpio_togl) 186 | 187 | #if !_PM_CLOCK_PWM 188 | // Unlike timers on other devices, on RP2040 you don't reset a counter to 189 | // zero at the start of a cycle. To emulate that behavior (for determining 190 | // elapsed times), the timer start time must be saved somewhere... 191 | static volatile uint32_t _PM_timerSave; 192 | #endif 193 | 194 | // Because it's tied to a specific timer right now, there can be only 195 | // one instance of the Protomatter_core struct. The Arduino library 196 | // sets up this pointer when calling begin(). 197 | void *_PM_protoPtr = NULL; 198 | 199 | #if _PM_CLOCK_PWM // Use PWM for timing 200 | static void _PM_PWM_ISR(void) { 201 | pwm_clear_irq(_PM_PWM_SLICE); // Reset PWM wrap interrupt 202 | _PM_row_handler(_PM_protoPtr); // In core.c 203 | } 204 | #else // Use timer alarm for timing 205 | static void _PM_timerISR(void) { 206 | hw_clear_bits(&timer_hw->intr, 1u << _PM_ALARM_NUM); // Clear alarm flag 207 | _PM_row_handler(_PM_protoPtr); // In core.c 208 | } 209 | #endif 210 | 211 | // Set timer period and enable timer. 212 | inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 213 | #if _PM_CLOCK_PWM 214 | pwm_set_counter(_PM_PWM_SLICE, 0); 215 | pwm_set_wrap(_PM_PWM_SLICE, period); 216 | pwm_set_enabled(_PM_PWM_SLICE, true); 217 | #else 218 | irq_set_enabled(_PM_IRQ_HANDLER, true); // Enable alarm IRQ 219 | _PM_timerSave = timer_hw->timerawl; // Time at start 220 | timer_hw->alarm[_PM_ALARM_NUM] = _PM_timerSave + period; // Time at end 221 | #endif 222 | } 223 | 224 | // Return current count value (timer enabled or not). 225 | // Timer must be previously initialized. 226 | inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 227 | #if _PM_CLOCK_PWM 228 | return pwm_get_counter(_PM_PWM_SLICE); 229 | #else 230 | return timer_hw->timerawl - _PM_timerSave; 231 | #endif 232 | } 233 | 234 | // Disable timer and return current count value. 235 | // Timer must be previously initialized. 236 | uint32_t _PM_timerStop(Protomatter_core *core) { 237 | #if _PM_CLOCK_PWM 238 | pwm_set_enabled(_PM_PWM_SLICE, false); 239 | #else 240 | irq_set_enabled(_PM_IRQ_HANDLER, false); // Disable alarm IRQ 241 | #endif 242 | return _PM_timerGetCount(core); 243 | } 244 | 245 | #if (F_CPU >= 250000000) 246 | #define _PM_clockHoldLow asm("nop; nop; nop;"); 247 | #define _PM_clockHoldHigh asm("nop; nop; nop;"); 248 | #elif (F_CPU >= 175000000) 249 | #define _PM_clockHoldLow asm("nop; nop; nop;"); 250 | #define _PM_clockHoldHigh asm("nop;"); 251 | #elif (F_CPU >= 125000000) 252 | #define _PM_clockHoldLow asm("nop; nop; nop;"); 253 | #define _PM_clockHoldHigh asm("nop;"); 254 | #elif (F_CPU >= 100000000) 255 | #define _PM_clockHoldLow asm("nop;"); 256 | #endif // No NOPs needed at lower speeds 257 | 258 | #define _PM_chunkSize 8 259 | #if _PM_CLOCK_PWM 260 | #define _PM_minMinPeriod 100 261 | #else 262 | #define _PM_minMinPeriod 8 263 | #endif 264 | 265 | #endif // END RP2040 266 | -------------------------------------------------------------------------------- /src/arch/samd-common.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file samd-common.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains SAMD-SPECIFIC CODE (SAMD51 & SAMD21). 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(__SAMD51__) || defined(SAM_D5X_E5X) || defined(_SAMD21_) || \ 21 | defined(SAMD21) 22 | 23 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 24 | 25 | // g_APinDescription[] table and pin indices are Arduino specific: 26 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 27 | #define _PM_byteOffset(pin) (g_APinDescription[pin].ulPin / 8) 28 | #define _PM_wordOffset(pin) (g_APinDescription[pin].ulPin / 16) 29 | #else 30 | #define _PM_byteOffset(pin) (3 - (g_APinDescription[pin].ulPin / 8)) 31 | #define _PM_wordOffset(pin) (1 - (g_APinDescription[pin].ulPin / 16)) 32 | #endif 33 | 34 | // Arduino implementation is tied to a specific timer/counter & freq: 35 | #if defined(TC4) 36 | #define _PM_TIMER_DEFAULT TC4 37 | #define _PM_IRQ_HANDLER TC4_Handler 38 | #else // No TC4 on some M4's 39 | #define _PM_TIMER_DEFAULT TC3 40 | #define _PM_IRQ_HANDLER TC3_Handler 41 | #endif 42 | #define _PM_timerFreq 48000000 43 | // Partly because IRQs must be declared at compile-time, and partly 44 | // because we know Arduino's already set up one of the GCLK sources 45 | // for 48 MHz. 46 | 47 | // Because it's tied to a specific timer right now, there can be only 48 | // one instance of the Protomatter_core struct. The Arduino library 49 | // sets up this pointer when calling begin(). 50 | void *_PM_protoPtr = NULL; 51 | 52 | // Timer interrupt service routine 53 | void _PM_IRQ_HANDLER(void) { 54 | // Clear overflow flag: 55 | _PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF; 56 | _PM_row_handler(_PM_protoPtr); // In core.c 57 | } 58 | 59 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 60 | 61 | #include "hal_gpio.h" 62 | 63 | #define _PM_pinOutput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_OUT) 64 | #define _PM_pinInput(pin) gpio_set_pin_direction(pin, GPIO_DIRECTION_IN) 65 | #define _PM_pinHigh(pin) gpio_set_pin_level(pin, 1) 66 | #define _PM_pinLow(pin) gpio_set_pin_level(pin, 0) 67 | #define _PM_portBitMask(pin) (1u << ((pin)&31)) 68 | 69 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 70 | #define _PM_byteOffset(pin) ((pin & 31) / 8) 71 | #define _PM_wordOffset(pin) ((pin & 31) / 16) 72 | #else 73 | #define _PM_byteOffset(pin) (3 - ((pin & 31) / 8)) 74 | #define _PM_wordOffset(pin) (1 - ((pin & 31) / 16)) 75 | #endif 76 | 77 | // CircuitPython implementation is tied to a specific freq (but the counter 78 | // is dynamically allocated): 79 | #define _PM_timerFreq 48000000 80 | 81 | // As currently implemented, there can be only one instance of the 82 | // Protomatter_core struct. This pointer is set up when starting the matrix. 83 | void *_PM_protoPtr = NULL; 84 | 85 | // Timer interrupt service routine 86 | void _PM_IRQ_HANDLER(void) { 87 | ((Tc *)(((Protomatter_core *)_PM_protoPtr)->timer))->COUNT16.INTFLAG.reg = 88 | TC_INTFLAG_OVF; 89 | _PM_row_handler(_PM_protoPtr); // In core.c 90 | } 91 | 92 | #else // END CIRCUITPYTHON ------------------------------------------------- 93 | 94 | // Byte offset macros, timer and ISR work for other environments go here. 95 | 96 | #endif 97 | 98 | #endif // END SAMD5x/SAME5x/SAMD21 99 | -------------------------------------------------------------------------------- /src/arch/samd21.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file samd21.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains SAMD21-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(_SAMD21_) || defined(SAMD21) // Arduino, Circuitpy SAMD21 defs 21 | 22 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 23 | 24 | // g_APinDescription[] table and pin indices are Arduino specific: 25 | #define _PM_portOutRegister(pin) \ 26 | &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUT.reg 27 | 28 | #define _PM_portSetRegister(pin) \ 29 | &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTSET.reg 30 | 31 | #define _PM_portClearRegister(pin) \ 32 | &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTCLR.reg 33 | 34 | #define _PM_portToggleRegister(pin) \ 35 | &PORT_IOBUS->Group[g_APinDescription[pin].ulPort].OUTTGL.reg 36 | 37 | #else // END ARDUINO ------------------------------------------------------- 38 | 39 | // Non-Arduino port register lookups go here, if not already declared 40 | // in samd-common.h. 41 | 42 | #endif 43 | 44 | // CODE COMMON TO ALL ENVIRONMENTS ----------------------------------------- 45 | 46 | // Initialize, but do not start, timer 47 | void _PM_timerInit(Protomatter_core *core) { 48 | static const struct { 49 | Tc *tc; // -> Timer/counter peripheral base address 50 | IRQn_Type IRQn; // Interrupt number 51 | uint8_t GCM_ID; // GCLK selection ID 52 | } timer[] = { 53 | #if defined(TC0) 54 | {TC0, TC0_IRQn, GCM_TCC0_TCC1}, 55 | #endif 56 | #if defined(TC1) 57 | {TC1, TC1_IRQn, GCM_TCC0_TCC1}, 58 | #endif 59 | #if defined(TC2) 60 | {TC2, TC2_IRQn, GCM_TCC2_TC3}, 61 | #endif 62 | #if defined(TC3) 63 | {TC3, TC3_IRQn, GCM_TCC2_TC3}, 64 | #endif 65 | #if defined(TC4) 66 | {TC4, TC4_IRQn, GCM_TC4_TC5}, 67 | #endif 68 | }; 69 | #define NUM_TIMERS (sizeof timer / sizeof timer[0]) 70 | 71 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 72 | 73 | uint8_t timerNum = 0; 74 | while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) { 75 | timerNum++; 76 | } 77 | if (timerNum >= NUM_TIMERS) 78 | return; 79 | 80 | // Enable GCLK for timer/counter 81 | GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | 82 | GCLK_CLKCTRL_ID(timer[timerNum].GCM_ID)); 83 | while (GCLK->STATUS.bit.SYNCBUSY == 1) 84 | ; 85 | 86 | // Counter must first be disabled to configure it 87 | tc->COUNT16.CTRLA.bit.ENABLE = 0; 88 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 89 | ; 90 | 91 | tc->COUNT16.CTRLA.reg = // Configure timer counter 92 | TC_CTRLA_PRESCALER_DIV1 | // 1:1 Prescale 93 | TC_CTRLA_WAVEGEN_MFRQ | // Match frequency generation mode (MFRQ) 94 | TC_CTRLA_MODE_COUNT16; // 16-bit counter mode 95 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 96 | ; 97 | 98 | tc->COUNT16.CTRLBCLR.reg = TCC_CTRLBCLR_DIR; // Count up 99 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 100 | ; 101 | 102 | // Overflow interrupt 103 | tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF; 104 | 105 | NVIC_DisableIRQ(timer[timerNum].IRQn); 106 | NVIC_ClearPendingIRQ(timer[timerNum].IRQn); 107 | NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority 108 | NVIC_EnableIRQ(timer[timerNum].IRQn); 109 | 110 | // Timer is configured but NOT enabled by default 111 | } 112 | 113 | // Set timer period, initialize count value to zero, enable timer. 114 | // Timer must be initialized to 16-bit mode using the init function 115 | // above, but must be inactive before calling this. 116 | inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 117 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 118 | tc->COUNT16.COUNT.reg = 0; 119 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 120 | ; 121 | tc->COUNT16.CC[0].reg = period; 122 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 123 | ; 124 | tc->COUNT16.CTRLA.bit.ENABLE = 1; 125 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 126 | ; 127 | } 128 | 129 | // Return current count value (timer enabled or not). 130 | // Timer must be previously initialized. 131 | inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 132 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 133 | tc->COUNT16.READREQ.reg = TC_READREQ_RCONT | TC_READREQ_ADDR(0x10); 134 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 135 | ; 136 | return tc->COUNT16.COUNT.reg; 137 | } 138 | 139 | // Disable timer and return current count value. 140 | // Timer must be previously initialized. 141 | inline uint32_t _PM_timerStop(Protomatter_core *core) { 142 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 143 | uint32_t count = _PM_timerGetCount(core); 144 | tc->COUNT16.CTRLA.bit.ENABLE = 0; 145 | while (tc->COUNT16.STATUS.bit.SYNCBUSY) 146 | ; 147 | return count; 148 | } 149 | 150 | #endif // END _SAMD21_ || SAMD21 151 | -------------------------------------------------------------------------------- /src/arch/samd51.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file samd51.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains SAMD51-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(__SAMD51__) || \ 21 | defined(SAM_D5X_E5X) // Arduino, Circuitpy SAMD5x / E5x defs 22 | 23 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 24 | 25 | // g_APinDescription[] table and pin indices are Arduino specific: 26 | #define _PM_portOutRegister(pin) \ 27 | &PORT->Group[g_APinDescription[pin].ulPort].OUT.reg 28 | 29 | #define _PM_portSetRegister(pin) \ 30 | &PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg 31 | 32 | #define _PM_portClearRegister(pin) \ 33 | &PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg 34 | 35 | #define _PM_portToggleRegister(pin) \ 36 | &PORT->Group[g_APinDescription[pin].ulPort].OUTTGL.reg 37 | 38 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 39 | 40 | #define _PM_portOutRegister(pin) (&PORT->Group[(pin / 32)].OUT.reg) 41 | 42 | #define _PM_portSetRegister(pin) (&PORT->Group[(pin / 32)].OUTSET.reg) 43 | 44 | #define _PM_portClearRegister(pin) (&PORT->Group[(pin / 32)].OUTCLR.reg) 45 | 46 | #define _PM_portToggleRegister(pin) (&PORT->Group[(pin / 32)].OUTTGL.reg) 47 | 48 | #define F_CPU (120000000) 49 | 50 | // Enable high output driver strength on one pin. Arduino does this by 51 | // default on pinMode(OUTPUT), but CircuitPython requires the motions... 52 | static void _hi_drive(uint8_t pin) { 53 | // For Arduino testing only: 54 | // pin = g_APinDescription[pin].ulPort * 32 + g_APinDescription[pin].ulPin; 55 | 56 | // Input, pull-up and peripheral MUX are disabled as we're only using 57 | // vanilla PORT writes on Protomatter GPIO. 58 | PORT->Group[pin / 32].WRCONFIG.reg = 59 | (pin & 16) 60 | ? PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR | 61 | PORT_WRCONFIG_HWSEL | (1 << (pin & 15)) 62 | : PORT_WRCONFIG_WRPINCFG | PORT_WRCONFIG_DRVSTR | (1 << (pin & 15)); 63 | } 64 | 65 | #else 66 | 67 | // Other port register lookups go here 68 | 69 | #endif 70 | 71 | // CODE COMMON TO ALL ENVIRONMENTS ----------------------------------------- 72 | 73 | // Initialize, but do not start, timer 74 | void _PM_timerInit(Protomatter_core *core) { 75 | static const struct { 76 | Tc *tc; // -> Timer/counter peripheral base address 77 | IRQn_Type IRQn; // Interrupt number 78 | uint8_t GCLK_ID; // Peripheral channel # for clock source 79 | } timer[] = { 80 | #if defined(TC0) 81 | {TC0, TC0_IRQn, TC0_GCLK_ID}, 82 | #endif 83 | #if defined(TC1) 84 | {TC1, TC1_IRQn, TC1_GCLK_ID}, 85 | #endif 86 | #if defined(TC2) 87 | {TC2, TC2_IRQn, TC2_GCLK_ID}, 88 | #endif 89 | #if defined(TC3) 90 | {TC3, TC3_IRQn, TC3_GCLK_ID}, 91 | #endif 92 | #if defined(TC4) 93 | {TC4, TC4_IRQn, TC4_GCLK_ID}, 94 | #endif 95 | #if defined(TC5) 96 | {TC5, TC5_IRQn, TC5_GCLK_ID}, 97 | #endif 98 | #if defined(TC6) 99 | {TC6, TC6_IRQn, TC6_GCLK_ID}, 100 | #endif 101 | #if defined(TC7) 102 | {TC7, TC7_IRQn, TC7_GCLK_ID}, 103 | #endif 104 | #if defined(TC8) 105 | {TC8, TC8_IRQn, TC8_GCLK_ID}, 106 | #endif 107 | #if defined(TC9) 108 | {TC9, TC9_IRQn, TC9_GCLK_ID}, 109 | #endif 110 | #if defined(TC10) 111 | {TC10, TC10_IRQn, TC10_GCLK_ID}, 112 | #endif 113 | #if defined(TC11) 114 | {TC11, TC11_IRQn, TC11_GCLK_ID}, 115 | #endif 116 | #if defined(TC12) 117 | {TC12, TC12_IRQn, TC12_GCLK_ID}, 118 | #endif 119 | }; 120 | #define NUM_TIMERS (sizeof timer / sizeof timer[0]) 121 | 122 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 123 | 124 | uint8_t timerNum = 0; 125 | while ((timerNum < NUM_TIMERS) && (timer[timerNum].tc != tc)) { 126 | timerNum++; 127 | } 128 | if (timerNum >= NUM_TIMERS) 129 | return; 130 | 131 | // Feed timer/counter off GCLK1 (already set 48 MHz by Arduino core). 132 | // Sure, SAMD51 can run timers up to F_CPU (e.g. 120 MHz or up to 133 | // 200 MHz with overclocking), but on higher bitplanes (which have 134 | // progressively longer timer periods) I could see this possibly 135 | // exceeding a 16-bit timer, and would have to switch prescalers. 136 | // We don't actually need atomic precision on the timer -- point is 137 | // simply that the period doubles with each bitplane, and this can 138 | // work fine at 48 MHz. 139 | GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN = 0; // Disable 140 | while (GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN) 141 | ; // Wait for it 142 | GCLK_PCHCTRL_Type pchctrl; // Read-modify-store 143 | pchctrl.reg = GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg; 144 | pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK1_Val; 145 | pchctrl.bit.CHEN = 1; 146 | GCLK->PCHCTRL[timer[timerNum].GCLK_ID].reg = pchctrl.reg; 147 | while (!GCLK->PCHCTRL[timer[timerNum].GCLK_ID].bit.CHEN) 148 | ; 149 | 150 | // Disable timer before configuring it 151 | tc->COUNT16.CTRLA.bit.ENABLE = 0; 152 | while (tc->COUNT16.SYNCBUSY.bit.ENABLE) 153 | ; 154 | 155 | // 16-bit counter mode, 1:1 prescale 156 | tc->COUNT16.CTRLA.bit.MODE = TC_CTRLA_MODE_COUNT16; 157 | tc->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_DIV1_Val; 158 | 159 | tc->COUNT16.WAVE.bit.WAVEGEN = 160 | TC_WAVE_WAVEGEN_MFRQ_Val; // Match frequency generation mode (MFRQ) 161 | 162 | tc->COUNT16.CTRLBCLR.reg = TC_CTRLBCLR_DIR; // Count up 163 | while (tc->COUNT16.SYNCBUSY.bit.CTRLB) 164 | ; 165 | 166 | // Overflow interrupt 167 | tc->COUNT16.INTENSET.reg = TC_INTENSET_OVF; 168 | 169 | NVIC_DisableIRQ(timer[timerNum].IRQn); 170 | NVIC_ClearPendingIRQ(timer[timerNum].IRQn); 171 | NVIC_SetPriority(timer[timerNum].IRQn, 0); // Top priority 172 | NVIC_EnableIRQ(timer[timerNum].IRQn); 173 | 174 | // Timer is configured but NOT enabled by default 175 | 176 | #if defined(CIRCUITPY) // See notes earlier; Arduino doesn't need this. 177 | // Enable high drive strength on all Protomatter pins. TBH this is kind 178 | // of a jerky place to do this (it's not actually related to the timer 179 | // peripheral) but Protomatter doesn't really have a spot for it. 180 | uint8_t i; 181 | for (i = 0; i < core->parallel * 6; i++) 182 | _hi_drive(core->rgbPins[i]); 183 | for (i = 0; i < core->numAddressLines; i++) 184 | _hi_drive(core->addr[i].pin); 185 | _hi_drive(core->clockPin); 186 | _hi_drive(core->latch.pin); 187 | _hi_drive(core->oe.pin); 188 | #endif 189 | } 190 | 191 | // Set timer period, initialize count value to zero, enable timer. 192 | // Timer must be initialized to 16-bit mode using the init function 193 | // above, but must be inactive before calling this. 194 | inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 195 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 196 | tc->COUNT16.COUNT.reg = 0; 197 | while (tc->COUNT16.SYNCBUSY.bit.COUNT) 198 | ; 199 | tc->COUNT16.CC[0].reg = period; 200 | while (tc->COUNT16.SYNCBUSY.bit.CC0) 201 | ; 202 | tc->COUNT16.CTRLA.bit.ENABLE = 1; 203 | while (tc->COUNT16.SYNCBUSY.bit.STATUS) 204 | ; 205 | } 206 | 207 | // Return current count value (timer enabled or not). 208 | // Timer must be previously initialized. 209 | inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 210 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 211 | tc->COUNT16.CTRLBSET.bit.CMD = 0x4; // Sync COUNT 212 | while (tc->COUNT16.CTRLBSET.bit.CMD) 213 | ; // Wait for command 214 | return tc->COUNT16.COUNT.reg; 215 | } 216 | 217 | // Disable timer and return current count value. 218 | // Timer must be previously initialized. 219 | uint32_t _PM_timerStop(Protomatter_core *core) { 220 | Tc *tc = (Tc *)core->timer; // Cast peripheral address passed in 221 | uint32_t count = _PM_timerGetCount(core); 222 | tc->COUNT16.CTRLA.bit.ENABLE = 0; 223 | while (tc->COUNT16.SYNCBUSY.bit.STATUS) 224 | ; 225 | return count; 226 | } 227 | 228 | // SAMD51 takes a WEIRD TURN here, in an attempt to make the HUB75 clock 229 | // waveform slightly adjustable. Old vs new matrices seem to have different 230 | // preferences, and this tries to address that. If this works well then the 231 | // approach might be applied to other architectures (which are all fixed 232 | // duty cycle right now). THE CHALLENGE is that Protomatter works in a bit- 233 | // bangingly way (this is on purpose and by design, avoiding peripherals 234 | // that might work only on certain pins, for better compatibility with 235 | // existing shields and wings from the AVR era), we're aiming for nearly a 236 | // 20 MHz signal, and the SAMD51 cycle clock is ostensibly 120 MHz. With 237 | // just a few cycles to toggle the data and clock lines, that doesn't even 238 | // leave enough time for a counter loop. 239 | 240 | #define _PM_CUSTOM_BLAST ///< Disable blast_*() functions in core.c 241 | 242 | #define _PM_chunkSize 8 ///< Data-stuffing loop is unrolled to this size 243 | 244 | extern uint8_t _PM_duty; // In core.c 245 | 246 | // The approach is to use a small list of pointers, with a clock-toggling 247 | // value written to each one in succession. Most of the pointers are aimed 248 | // on a nonsense "bit bucket" variable, effectively becoming NOPs, and just 249 | // one is set to the PORT toggle register, raising the clock. A couple of 250 | // actual traditional NOPs are also present because concurrent PORT writes 251 | // on SAMD51 incur a 1-cycle delay, so the NOPs keep the clock frequency 252 | // constant (tradeoff is that the clock is now 7 rather than 6 cycles -- 253 | // ~17.1 MHz rather than 20 with F_CPU at 120 MHz). The NOPs could be 254 | // removed and duty range increased by one, but the tradeoff then is 255 | // inconsistent timing at different duty settings. That 1-cycle delay is 256 | // also why this uses a list of pointers with a common value, rather than 257 | // a common pointer (the PORT reg) with a list of values -- because those 258 | // writes would all take 2 cycles, way too slow. A counter loop would also 259 | // take 2 cycles/count, because of the branch. 260 | 261 | #if F_CPU >= 200000000 // 200 MHz; 10 cycles/bit; 20 MHz, 6 duty settings 262 | 263 | #define _PM_maxDuty 5 ///< Allow duty settings 0-5 264 | #define _PM_defaultDuty 2 ///< ~60% 265 | 266 | #define PEW \ 267 | asm("nop"); \ 268 | *toggle = *data++; \ 269 | asm("nop"); \ 270 | *ptr0 = clock; \ 271 | *ptr1 = clock; \ 272 | *ptr2 = clock; \ 273 | *ptr3 = clock; \ 274 | *ptr4 = clock; \ 275 | *ptr5 = clock; 276 | 277 | #elif F_CPU >= 180000000 // 180 MHz; 9 cycles/bit; 20 MHz, 5 duty settings 278 | 279 | #define _PM_maxDuty 4 ///< Allow duty settings 0-4 280 | #define _PM_defaultDuty 1 ///< ~50% 281 | 282 | #define PEW \ 283 | asm("nop"); \ 284 | *toggle = *data++; \ 285 | asm("nop"); \ 286 | *ptr0 = clock; \ 287 | *ptr1 = clock; \ 288 | *ptr2 = clock; \ 289 | *ptr3 = clock; \ 290 | *ptr4 = clock; 291 | 292 | #elif F_CPU >= 150000000 // 150 MHz; 8 cycles/bit; 18.75 MHz, 4 duty settings 293 | 294 | #define _PM_maxDuty 3 ///< Allow duty settings 0-3 295 | #define _PM_defaultDuty 1 ///< ~55% 296 | 297 | #define PEW \ 298 | asm("nop"); \ 299 | *toggle = *data++; \ 300 | asm("nop"); \ 301 | *ptr0 = clock; \ 302 | *ptr1 = clock; \ 303 | *ptr2 = clock; \ 304 | *ptr3 = clock; 305 | 306 | #else // 120 MHz; 7 cycles/bit; 17.1 MHz, 3 duty settings 307 | 308 | #define _PM_maxDuty 2 ///< Allow duty settings 0-2 309 | #define _PM_defaultDuty 0 ///< ~50% 310 | 311 | #define PEW \ 312 | asm("nop"); \ 313 | *toggle = *data++; \ 314 | asm("nop"); \ 315 | *ptr0 = clock; \ 316 | *ptr1 = clock; \ 317 | *ptr2 = clock; 318 | 319 | #endif 320 | 321 | static void blast_byte(Protomatter_core *core, uint8_t *data) { 322 | // If here, it was established in begin() that the RGB data bits and 323 | // clock are all within the same byte of a PORT register, else we'd be 324 | // in the word- or long-blasting functions now. So we just need an 325 | // 8-bit pointer to the PORT: 326 | volatile uint8_t *toggle = 327 | (volatile uint8_t *)core->toggleReg + core->portOffset; 328 | uint8_t bucket, clock = core->clockMask; 329 | // Pointer list must be distinct vars, not an array, else slow. 330 | volatile uint8_t *ptr0 = 331 | (_PM_duty == _PM_maxDuty) ? toggle : (volatile uint8_t *)&bucket; 332 | volatile uint8_t *ptr1 = 333 | (_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint8_t *)&bucket; 334 | volatile uint8_t *ptr2 = 335 | (_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint8_t *)&bucket; 336 | #if _PM_maxDuty >= 3 337 | volatile uint8_t *ptr3 = 338 | (_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint8_t *)&bucket; 339 | #endif 340 | #if _PM_maxDuty >= 4 341 | volatile uint8_t *ptr4 = 342 | (_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint8_t *)&bucket; 343 | #endif 344 | #if _PM_maxDuty >= 5 345 | volatile uint8_t *ptr5 = 346 | (_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint8_t *)&bucket; 347 | #endif 348 | uint16_t chunks = core->chainBits / 8; 349 | 350 | // PORT has already been initialized with RGB data + clock bits 351 | // all LOW, so we don't need to initialize that state here. 352 | 353 | do { 354 | PEW PEW PEW PEW PEW PEW PEW PEW 355 | } while (--chunks); 356 | 357 | // Want the PORT left with RGB data and clock LOW on function exit 358 | // (so it's easier to see on 'scope, and to prime it for the next call). 359 | // This is implicit in the no-toggle case (due to how the PEW macro 360 | // works), but toggle case requires explicitly clearing those bits. 361 | // rgbAndClockMask is an 8-bit value when toggling, hence offset here. 362 | *((volatile uint8_t *)core->clearReg + core->portOffset) = 363 | core->rgbAndClockMask; 364 | } 365 | 366 | // This is a copypasta of blast_byte() with types changed to uint16_t. 367 | static void blast_word(Protomatter_core *core, uint16_t *data) { 368 | volatile uint16_t *toggle = (uint16_t *)core->toggleReg + core->portOffset; 369 | uint16_t bucket, clock = core->clockMask; 370 | volatile uint16_t *ptr0 = 371 | (_PM_duty == _PM_maxDuty) ? toggle : (volatile uint16_t *)&bucket; 372 | volatile uint16_t *ptr1 = 373 | (_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint16_t *)&bucket; 374 | volatile uint16_t *ptr2 = 375 | (_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint16_t *)&bucket; 376 | #if _PM_maxDuty >= 3 377 | volatile uint16_t *ptr3 = 378 | (_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint16_t *)&bucket; 379 | #endif 380 | #if _PM_maxDuty >= 4 381 | volatile uint16_t *ptr4 = 382 | (_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint16_t *)&bucket; 383 | #endif 384 | #if _PM_maxDuty >= 5 385 | volatile uint16_t *ptr5 = 386 | (_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint16_t *)&bucket; 387 | #endif 388 | uint16_t chunks = core->chainBits / 8; 389 | do { 390 | PEW PEW PEW PEW PEW PEW PEW PEW 391 | } while (--chunks); 392 | *((volatile uint16_t *)core->clearReg + core->portOffset) = 393 | core->rgbAndClockMask; 394 | } 395 | 396 | // This is a copypasta of blast_byte() with types changed to uint32_t. 397 | static void blast_long(Protomatter_core *core, uint32_t *data) { 398 | volatile uint32_t *toggle = (uint32_t *)core->toggleReg; 399 | uint32_t bucket, clock = core->clockMask; 400 | volatile uint32_t *ptr0 = 401 | (_PM_duty == _PM_maxDuty) ? toggle : (volatile uint32_t *)&bucket; 402 | volatile uint32_t *ptr1 = 403 | (_PM_duty == (_PM_maxDuty - 1)) ? toggle : (volatile uint32_t *)&bucket; 404 | volatile uint32_t *ptr2 = 405 | (_PM_duty == (_PM_maxDuty - 2)) ? toggle : (volatile uint32_t *)&bucket; 406 | #if _PM_maxDuty >= 3 407 | volatile uint32_t *ptr3 = 408 | (_PM_duty == (_PM_maxDuty - 3)) ? toggle : (volatile uint32_t *)&bucket; 409 | #endif 410 | #if _PM_maxDuty >= 4 411 | volatile uint32_t *ptr4 = 412 | (_PM_duty == (_PM_maxDuty - 4)) ? toggle : (volatile uint32_t *)&bucket; 413 | #endif 414 | #if _PM_maxDuty >= 5 415 | volatile uint32_t *ptr5 = 416 | (_PM_duty == (_PM_maxDuty - 5)) ? toggle : (volatile uint32_t *)&bucket; 417 | #endif 418 | uint16_t chunks = core->chainBits / 8; 419 | do { 420 | PEW PEW PEW PEW PEW PEW PEW PEW 421 | } while (--chunks); 422 | *((volatile uint32_t *)core->clearReg + core->portOffset) = 423 | core->rgbAndClockMask; 424 | } 425 | 426 | #define _PM_minMinPeriod 160 427 | 428 | #endif // END __SAMD51__ || SAM_D5X_E5X 429 | -------------------------------------------------------------------------------- /src/arch/stm32.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file stm32.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains STM32-SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(STM32F4_SERIES) || defined(STM32F405xx) // Arduino, CircuitPy 21 | 22 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 23 | 24 | // Arduino port register lookups go here, else ones in arch.h are used. 25 | 26 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 27 | 28 | #include "timers.h" 29 | 30 | #undef _PM_portBitMask 31 | #define _PM_portBitMask(pin) (1u << ((pin)&15)) 32 | #define _PM_byteOffset(pin) ((pin & 15) / 8) 33 | #define _PM_wordOffset(pin) ((pin & 15) / 16) 34 | 35 | #define _PM_pinOutput(pin_) \ 36 | do { \ 37 | int8_t pin = (pin_); \ 38 | GPIO_InitTypeDef GPIO_InitStruct = {0}; \ 39 | GPIO_InitStruct.Pin = 1 << (pin & 15); \ 40 | GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; \ 41 | GPIO_InitStruct.Pull = GPIO_NOPULL; \ 42 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; \ 43 | HAL_GPIO_Init(pin_port(pin / 16), &GPIO_InitStruct); \ 44 | } while (0) 45 | #define _PM_pinInput(pin_) \ 46 | do { \ 47 | int8_t pin = (pin_); \ 48 | GPIO_InitTypeDef GPIO_InitStruct = {0}; \ 49 | GPIO_InitStruct.Pin = 1 << (pin & 15); \ 50 | GPIO_InitStruct.Mode = GPIO_MODE_INPUT; \ 51 | GPIO_InitStruct.Pull = GPIO_NOPULL; \ 52 | GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; \ 53 | HAL_GPIO_Init(pin_port(pin / 16), &GPIO_InitStruct); \ 54 | } while (0) 55 | #define _PM_pinHigh(pin) \ 56 | HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin & 15), GPIO_PIN_SET) 57 | #define _PM_pinLow(pin) \ 58 | HAL_GPIO_WritePin(pin_port(pin / 16), 1 << (pin & 15), GPIO_PIN_RESET) 59 | 60 | #define _PM_PORT_TYPE uint16_t 61 | 62 | volatile uint16_t *_PM_portOutRegister(uint32_t pin) { 63 | return (uint16_t *)&pin_port(pin / 16)->ODR; 64 | } 65 | 66 | volatile uint16_t *_PM_portSetRegister(uint32_t pin) { 67 | return (uint16_t *)&pin_port(pin / 16)->BSRR; 68 | } 69 | 70 | // To make things interesting, STM32F4xx places the set and clear 71 | // GPIO bits within a single register. The "clear" bits are upper, so 72 | // offset by 1 in uint16_ts 73 | volatile uint16_t *_PM_portClearRegister(uint32_t pin) { 74 | return 1 + (uint16_t *)&pin_port(pin / 16)->BSRR; 75 | } 76 | 77 | // TODO: was this somehow specific to TIM6? 78 | #define _PM_timerFreq 42000000 79 | 80 | // Because it's tied to a specific timer right now, there can be only 81 | // one instance of the Protomatter_core struct. The Arduino library 82 | // sets up this pointer when calling begin(). 83 | // TODO: this is no longer true, should it change? 84 | void *_PM_protoPtr = NULL; 85 | 86 | static TIM_HandleTypeDef tim_handle; 87 | 88 | // Timer interrupt service routine 89 | void _PM_IRQ_HANDLER(void) { 90 | // Clear overflow flag: 91 | //_PM_TIMER_DEFAULT->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF; 92 | _PM_row_handler(_PM_protoPtr); // In core.c 93 | } 94 | 95 | // Initialize, but do not start, timer 96 | void _PM_timerInit(Protomatter_core *core) { 97 | TIM_TypeDef *tim_instance = (TIM_TypeDef *)core->timer; 98 | stm_peripherals_timer_reserve(tim_instance); 99 | // Set IRQs at max priority and start clock 100 | stm_peripherals_timer_preinit(tim_instance, 0, _PM_IRQ_HANDLER); 101 | 102 | tim_handle.Instance = tim_instance; 103 | tim_handle.Init.Period = 1000; // immediately replaced. 104 | tim_handle.Init.Prescaler = 0; 105 | tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; 106 | tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP; 107 | tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; 108 | 109 | HAL_TIM_Base_Init(&tim_handle); 110 | 111 | size_t tim_irq = stm_peripherals_timer_get_irqnum(tim_instance); 112 | HAL_NVIC_DisableIRQ(tim_irq); 113 | NVIC_ClearPendingIRQ(tim_irq); 114 | NVIC_SetPriority(tim_irq, 0); // Top priority 115 | } 116 | 117 | inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 118 | TIM_TypeDef *tim = core->timer; 119 | tim->SR = 0; 120 | tim->ARR = period; 121 | tim->CR1 |= TIM_CR1_CEN; 122 | tim->DIER |= TIM_DIER_UIE; 123 | HAL_NVIC_EnableIRQ(stm_peripherals_timer_get_irqnum(tim)); 124 | } 125 | 126 | inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 127 | TIM_TypeDef *tim = core->timer; 128 | return tim->CNT; 129 | } 130 | 131 | uint32_t _PM_timerStop(Protomatter_core *core) { 132 | TIM_TypeDef *tim = core->timer; 133 | HAL_NVIC_DisableIRQ(stm_peripherals_timer_get_irqnum(tim)); 134 | tim->CR1 &= ~TIM_CR1_CEN; 135 | tim->DIER &= ~TIM_DIER_UIE; 136 | return tim->CNT; 137 | } 138 | // settings from M4 for >= 150MHz, we use this part at 168MHz 139 | #define _PM_clockHoldHigh asm("nop; nop; nop"); 140 | #define _PM_clockHoldLow asm("nop"); 141 | 142 | #define _PM_minMinPeriod 140 143 | 144 | #endif // END CIRCUITPYTHON ------------------------------------------------ 145 | 146 | #endif // END STM32F4_SERIES || STM32F405xx 147 | -------------------------------------------------------------------------------- /src/arch/teensy4.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file teensy4.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * This file contains i.MX 1062 (Teensy 4.x) SPECIFIC CODE. 6 | * 7 | * Adafruit invests time and resources providing this open source code, 8 | * please support Adafruit and open-source hardware by purchasing 9 | * products from Adafruit! 10 | * 11 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 12 | * Adafruit Industries, with contributions from the open source community. 13 | * 14 | * BSD license, all text here must be included in any redistribution. 15 | * 16 | */ 17 | 18 | #pragma once 19 | 20 | #if defined(__IMXRT1062__) 21 | 22 | // i.MX only allows full 32-bit aligned writes to GPIO. 23 | #define _PM_STRICT_32BIT_IO ///< Change core.c behavior for long accesses only 24 | 25 | #if defined(ARDUINO) // COMPILING FOR ARDUINO ------------------------------ 26 | 27 | static const struct { 28 | volatile uint32_t *base; ///< GPIO base address for pin 29 | uint8_t bit; ///< GPIO bit number for pin (0-31) 30 | } _PM_teensyPins[] = { 31 | {&CORE_PIN0_PORTREG, CORE_PIN0_BIT}, 32 | {&CORE_PIN1_PORTREG, CORE_PIN1_BIT}, 33 | {&CORE_PIN2_PORTREG, CORE_PIN2_BIT}, 34 | {&CORE_PIN3_PORTREG, CORE_PIN3_BIT}, 35 | {&CORE_PIN4_PORTREG, CORE_PIN4_BIT}, 36 | {&CORE_PIN5_PORTREG, CORE_PIN5_BIT}, 37 | {&CORE_PIN6_PORTREG, CORE_PIN6_BIT}, 38 | {&CORE_PIN7_PORTREG, CORE_PIN7_BIT}, 39 | {&CORE_PIN8_PORTREG, CORE_PIN8_BIT}, 40 | {&CORE_PIN9_PORTREG, CORE_PIN9_BIT}, 41 | {&CORE_PIN10_PORTREG, CORE_PIN10_BIT}, 42 | {&CORE_PIN11_PORTREG, CORE_PIN11_BIT}, 43 | {&CORE_PIN12_PORTREG, CORE_PIN12_BIT}, 44 | {&CORE_PIN13_PORTREG, CORE_PIN13_BIT}, 45 | {&CORE_PIN14_PORTREG, CORE_PIN14_BIT}, 46 | {&CORE_PIN15_PORTREG, CORE_PIN15_BIT}, 47 | {&CORE_PIN16_PORTREG, CORE_PIN16_BIT}, 48 | {&CORE_PIN17_PORTREG, CORE_PIN17_BIT}, 49 | {&CORE_PIN18_PORTREG, CORE_PIN18_BIT}, 50 | {&CORE_PIN19_PORTREG, CORE_PIN19_BIT}, 51 | {&CORE_PIN20_PORTREG, CORE_PIN20_BIT}, 52 | {&CORE_PIN21_PORTREG, CORE_PIN21_BIT}, 53 | {&CORE_PIN22_PORTREG, CORE_PIN22_BIT}, 54 | {&CORE_PIN23_PORTREG, CORE_PIN23_BIT}, 55 | {&CORE_PIN24_PORTREG, CORE_PIN24_BIT}, 56 | {&CORE_PIN25_PORTREG, CORE_PIN25_BIT}, 57 | {&CORE_PIN26_PORTREG, CORE_PIN26_BIT}, 58 | {&CORE_PIN27_PORTREG, CORE_PIN27_BIT}, 59 | {&CORE_PIN28_PORTREG, CORE_PIN28_BIT}, 60 | {&CORE_PIN29_PORTREG, CORE_PIN29_BIT}, 61 | {&CORE_PIN30_PORTREG, CORE_PIN30_BIT}, 62 | {&CORE_PIN31_PORTREG, CORE_PIN31_BIT}, 63 | {&CORE_PIN32_PORTREG, CORE_PIN32_BIT}, 64 | {&CORE_PIN33_PORTREG, CORE_PIN33_BIT}, 65 | {&CORE_PIN34_PORTREG, CORE_PIN34_BIT}, 66 | {&CORE_PIN35_PORTREG, CORE_PIN35_BIT}, 67 | {&CORE_PIN36_PORTREG, CORE_PIN36_BIT}, 68 | {&CORE_PIN37_PORTREG, CORE_PIN37_BIT}, 69 | {&CORE_PIN38_PORTREG, CORE_PIN38_BIT}, 70 | {&CORE_PIN39_PORTREG, CORE_PIN39_BIT}, 71 | }; 72 | 73 | #define _PM_SET_OFFSET 33 ///< 0x84 byte offset = 33 longs 74 | #define _PM_CLEAR_OFFSET 34 ///< 0x88 byte offset = 34 longs 75 | #define _PM_TOGGLE_OFFSET 35 ///< 0x8C byte offset = 35 longs 76 | 77 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 78 | #define _PM_byteOffset(pin) (_PM_teensyPins[pin].bit / 8) 79 | #define _PM_wordOffset(pin) (_PM_teensyPins[pin].bit / 16) 80 | #else 81 | #define _PM_byteOffset(pin) (3 - (_PM_teensyPins[pin].bit / 8)) 82 | #define _PM_wordOffset(pin) (1 - (_PM_teensyPins[pin].bit / 16)) 83 | #endif 84 | 85 | #define _PM_portOutRegister(pin) (void *)_PM_teensyPins[pin].base 86 | 87 | #define _PM_portSetRegister(pin) \ 88 | ((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_SET_OFFSET) 89 | 90 | #define _PM_portClearRegister(pin) \ 91 | ((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_CLEAR_OFFSET) 92 | 93 | #define _PM_portToggleRegister(pin) \ 94 | ((volatile uint32_t *)_PM_teensyPins[pin].base + _PM_TOGGLE_OFFSET) 95 | 96 | // As written, because it's tied to a specific timer right now, the 97 | // Arduino lib only permits one instance of the Protomatter_core struct, 98 | // which it sets up when calling begin(). 99 | void *_PM_protoPtr = NULL; 100 | 101 | // Code as written works with the Periodic Interrupt Timer directly, 102 | // rather than using the Teensy IntervalTimer library, reason being we 103 | // need to be able to poll the current timer value in _PM_timerGetCount(), 104 | // but that's not available from IntervalTimer, and the timer base address 105 | // it keeps is a private member (possible alternative is to do dirty pool 106 | // and access the pointer directly, knowing it's the first element in the 107 | // IntervalTimer object, but this is fraught with peril). 108 | 109 | #define _PM_timerFreq 24000000 // 24 MHz 110 | #define _PM_timerNum 0 // PIT timer #0 (can be 0-3) 111 | #define _PM_TIMER_DEFAULT (IMXRT_PIT_CHANNELS + _PM_timerNum) // PIT channel * 112 | 113 | // Interrupt service routine for Periodic Interrupt Timer 114 | static void _PM_timerISR(void) { 115 | IMXRT_PIT_CHANNEL_t *timer = _PM_TIMER_DEFAULT; 116 | _PM_row_handler(_PM_protoPtr); // In core.c 117 | timer->TFLG = 1; // Clear timer interrupt 118 | } 119 | 120 | // Initialize, but do not start, timer. 121 | void _PM_timerInit(Protomatter_core *core) { 122 | IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer; 123 | CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); // Enable clock signal to PIT 124 | PIT_MCR = 1; // Enable PIT 125 | timer->TCTRL = 0; // Disable timer and interrupt 126 | timer->LDVAL = 100000; // Timer initial load value 127 | // Interrupt is attached but not enabled yet 128 | attachInterruptVector(IRQ_PIT, &_PM_timerISR); 129 | NVIC_ENABLE_IRQ(IRQ_PIT); 130 | } 131 | 132 | // Set timer period, initialize count value to zero, enable timer. 133 | inline void _PM_timerStart(Protomatter_core *core, uint32_t period) { 134 | IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer; 135 | timer->TCTRL = 0; // Disable timer and interrupt 136 | timer->LDVAL = period; // Set load value 137 | // timer->CVAL = period; // And current value (just in case?) 138 | timer->TFLG = 1; // Clear timer interrupt 139 | timer->TCTRL = 3; // Enable timer and interrupt 140 | } 141 | 142 | // Return current count value (timer enabled or not). 143 | // Timer must be previously initialized. 144 | inline uint32_t _PM_timerGetCount(Protomatter_core *core) { 145 | IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer; 146 | return (timer->LDVAL - timer->CVAL); 147 | } 148 | 149 | // Disable timer and return current count value. 150 | // Timer must be previously initialized. 151 | uint32_t _PM_timerStop(Protomatter_core *core) { 152 | IMXRT_PIT_CHANNEL_t *timer = (IMXRT_PIT_CHANNEL_t *)core->timer; 153 | timer->TCTRL = 0; // Disable timer and interrupt 154 | return _PM_timerGetCount(core); 155 | } 156 | 157 | #define _PM_clockHoldHigh \ 158 | asm("nop; nop; nop; nop; nop; nop; nop;"); \ 159 | asm("nop; nop; nop; nop; nop; nop; nop;"); 160 | #define _PM_clockHoldLow \ 161 | asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); \ 162 | asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); 163 | 164 | #define _PM_chunkSize 1 ///< DON'T unroll loop, Teensy 4 is SO FAST 165 | 166 | #elif defined(CIRCUITPY) // COMPILING FOR CIRCUITPYTHON -------------------- 167 | 168 | // Teensy 4 CircuitPython magic goes here. 169 | 170 | #endif // END CIRCUITPYTHON ------------------------------------------------ 171 | 172 | #endif // END __IMXRT1062__ (Teensy 4) 173 | -------------------------------------------------------------------------------- /src/core.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file core.h 3 | * 4 | * Part of Adafruit's Protomatter library for HUB75-style RGB LED matrices. 5 | * 6 | * Adafruit invests time and resources providing this open source code, 7 | * please support Adafruit and open-source hardware by purchasing 8 | * products from Adafruit! 9 | * 10 | * Written by Phil "Paint Your Dragon" Burgess and Jeff Epler for 11 | * Adafruit Industries, with contributions from the open source community. 12 | * 13 | * BSD license, all text here must be included in any redistribution. 14 | * 15 | */ 16 | 17 | #pragma once 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include 24 | #include 25 | 26 | /** Status type returned by some functions. */ 27 | typedef enum { 28 | PROTOMATTER_OK, // Everything is hunky-dory! 29 | PROTOMATTER_ERR_PINS, // Clock and/or data pins on different PORTs 30 | PROTOMATTER_ERR_MALLOC, // Couldn't allocate memory for display 31 | PROTOMATTER_ERR_ARG, // Bad input to function 32 | } ProtomatterStatus; 33 | 34 | /** Struct for matrix control lines NOT related to RGB data or clock, i.e. 35 | latch, OE and address lines. RGB data and clock ("RGBC") are handled 36 | differently as they have specific requirements (and might use a toggle 37 | register if present). The data conversion functions need bitmasks for 38 | RGB data but do NOT need the set or clear registers, so those items are 39 | also declared as separate things in the core structure that follows. */ 40 | typedef struct { 41 | volatile void *setReg; ///< GPIO bit set register 42 | volatile void *clearReg; ///< GPIO bit clear register 43 | uint32_t bit; ///< GPIO bitmask 44 | uint8_t pin; ///< Some unique ID, e.g. Arduino pin # 45 | } _PM_pin; 46 | 47 | /** Struct with info about an RGB matrix chain and lots of state and buffer 48 | details for the library. Toggle-related items in this structure MUST be 49 | declared even if the device lacks GPIO bit-toggle registers (i.e. don't 50 | do an ifdef check around these). All hardware-specific details (including 51 | the presence or lack of toggle registers) are isolated to a single 52 | file -- arch.h -- which should ONLY be included by core.c, and ifdef'ing 53 | them would result in differing representations of this structure which 54 | must be shared between the library and calling code. (An alternative is 55 | to put any toggle-specific stuff at the end of the struct with an ifdef 56 | check, but that's just dirty pool and asking for trouble.) */ 57 | typedef struct { 58 | void *timer; ///< Arch-specific timer/counter info 59 | void *setReg; ///< RGBC bit set register (cast to use) 60 | void *clearReg; ///< RGBC bit clear register " 61 | void *toggleReg; ///< RGBC bit toggle register " 62 | uint8_t *rgbPins; ///< Array of RGB data pins (mult of 6) 63 | void *rgbMask; ///< PORT bit mask for each RGB pin 64 | uint32_t clockMask; ///< PORT bit mask for RGB clock 65 | uint32_t rgbAndClockMask; ///< PORT bit mask for RGB data + clock 66 | volatile void *addrPortToggle; ///< See singleAddrPort below 67 | void *screenData; ///< Per-bitplane RGB data for matrix 68 | _PM_pin latch; ///< RGB data latch 69 | _PM_pin oe; ///< !OE (LOW out enable) 70 | _PM_pin *addr; ///< Array of address pins 71 | uint32_t bufferSize; ///< Bytes per matrix buffer 72 | uint32_t bitZeroPeriod; ///< Bitplane 0 timer period 73 | uint32_t minPeriod; ///< Plane 0 timer period for ~250Hz 74 | volatile uint32_t frameCount; ///< For estimating refresh rate 75 | uint16_t width; ///< Matrix chain width only in bits 76 | uint16_t chainBits; ///< Matrix chain width*tiling in bits 77 | uint8_t bytesPerElement; ///< Using 8, 16 or 32 bits of PORT? 78 | uint8_t clockPin; ///< RGB clock pin identifier 79 | uint8_t parallel; ///< Number of concurrent matrix outs 80 | uint8_t numAddressLines; ///< Number of address line pins 81 | uint8_t portOffset; ///< Active 8- or 16-bit pos. in PORT 82 | uint8_t numPlanes; ///< Display bitplanes (1 to 6) 83 | uint8_t numRowPairs; ///< Addressable row pairs 84 | int8_t tile; ///< Vertical tiling repetitions 85 | bool doubleBuffer; ///< 2X buffers for clean switchover 86 | bool singleAddrPort; ///< If 1, all addr lines on same PORT 87 | volatile uint8_t activeBuffer; ///< Index of currently-displayed buf 88 | volatile uint8_t plane; ///< Current bitplane (changes in ISR) 89 | volatile uint8_t row; ///< Current scanline (changes in ISR) 90 | volatile uint8_t prevRow; ///< Scanline from prior ISR 91 | volatile bool swapBuffers; ///< If 1, awaiting double-buf switch 92 | } Protomatter_core; 93 | 94 | // Protomatter core function prototypes. Environment-specific code (like the 95 | // Adafruit_Protomatter class for Arduino) calls on these underlying things, 96 | // and has to provide a few extras of its own (interrupt handlers and such). 97 | // User code shouldn't need to invoke any of them directly. 98 | 99 | /*! 100 | @brief Initialize values in Protomatter_core structure. 101 | @param core Pointer to Protomatter_core structure. 102 | @param bitWidth Total width of RGB matrix chain, in pixels. 103 | Usu. some multiple of 32, but maybe exceptions. 104 | @param bitDepth Color "depth" in bitplanes, determines range of 105 | shades of red, green and blue. e.g. passing 4 106 | bits = 16 shades ea. R,G,B = 16x16x16 = 4096 107 | colors. 108 | @param rgbCount Number of "sets" of RGB data pins, each set 109 | containing 6 pins (2 ea. R,G,B). Typically 1, 110 | indicating a single matrix (or matrix chain). 111 | In theory (but not yet extensively tested), 112 | multiple sets of pins can be driven in parallel, 113 | up to 5 on some devices (if the hardware design 114 | provides all those bits on one PORT). 115 | @param rgbList A uint8_t array of pins (values are platform- 116 | dependent), 6X the prior rgbCount value, 117 | corresponding to the 6 output color bits for a 118 | matrix (or chain). Order is upper-half red, green, 119 | blue, lower-half red, green blue (repeat for each 120 | add'l chain). All the RGB pins (plus the clock pin 121 | below on some architectures) MUST be on the same 122 | PORT register. It's recommended (but not required) 123 | that all RGB pins (and clock depending on arch) be 124 | within the same byte of a PORT (but do not need to 125 | be sequential or contiguous within that byte) for 126 | more efficient RAM utilization. For two concurrent 127 | chains, same principle but 16-bit word. 128 | @param addrCount Number of row address lines required of matrix. 129 | Total pixel height is then 2 x 2^addrCount, e.g. 130 | 32-pixel-tall matrices have 4 row address lines. 131 | @param addrList A uint8_t array of pins (platform-dependent pin 132 | numbering), one per row address line. 133 | @param clockPin RGB clock pin (platform-dependent pin #). 134 | @param latchPin RGB data latch pin (platform-dependent pin #). 135 | @param oePin Output enable pin (platform-dependent pin #), 136 | active low. 137 | @param doubleBuffer If true, two matrix buffers are allocated, 138 | so changing display contents doesn't introduce 139 | artifacts mid-conversion. Requires ~2X RAM. 140 | @param tile If multiple matrices are chained and stacked 141 | vertically (rather than or in addition to 142 | horizontally), the number of vertical tiles is 143 | specified here. Positive values indicate a 144 | "progressive" arrangement (always left-to-right), 145 | negative for a "serpentine" arrangement (alternating 146 | 180 degree orientation). Horizontal tiles are implied 147 | in the 'bitWidth' argument. 148 | @param timer Pointer to timer peripheral or timer-related 149 | struct (architecture-dependent), or NULL to 150 | use a default timer ID (also arch-dependent). 151 | @return A ProtomatterStatus status, one of: 152 | PROTOMATTER_OK if everything is good. 153 | PROTOMATTER_ERR_PINS if data and/or clock pins are split across 154 | different PORTs. 155 | PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate display 156 | memory. 157 | PROTOMATTER_ERR_ARG if a bad value (core or timer pointer) was 158 | passed in. 159 | */ 160 | extern ProtomatterStatus _PM_init(Protomatter_core *core, uint16_t bitWidth, 161 | uint8_t bitDepth, uint8_t rgbCount, 162 | uint8_t *rgbList, uint8_t addrCount, 163 | uint8_t *addrList, uint8_t clockPin, 164 | uint8_t latchPin, uint8_t oePin, 165 | bool doubleBuffer, int8_t tile, void *timer); 166 | 167 | /*! 168 | @brief Allocate display buffers and populate additional elements of a 169 | Protomatter matrix. 170 | @param core Pointer to Protomatter_core structure. 171 | @return A ProtomatterStatus status, one of: 172 | PROTOMATTER_OK if everything is good. 173 | PROTOMATTER_ERR_PINS if data and/or clock pins are split across 174 | different PORTs. 175 | PROTOMATTER_ERR_MALLOC if insufficient RAM to allocate display 176 | memory. 177 | PROTOMATTER_ERR_ARG if a bad value. 178 | */ 179 | extern ProtomatterStatus _PM_begin(Protomatter_core *core); 180 | 181 | /*! 182 | @brief Disable (but do not deallocate) a Protomatter matrix. Disables 183 | matrix by setting OE pin HIGH and writing all-zero data to 184 | matrix shift registers, so it won't halt with lit LEDs. 185 | @param core Pointer to Protomatter_core structure. 186 | */ 187 | extern void _PM_stop(Protomatter_core *core); 188 | 189 | /*! 190 | @brief Start or restart a matrix. Initialize counters, configure and 191 | start timer. 192 | @param core Pointer to Protomatter_core structure. 193 | */ 194 | extern void _PM_resume(Protomatter_core *core); 195 | 196 | /*! 197 | @brief Deallocate memory associated with Protomatter_core structure 198 | (e.g. screen data, pin lists for data and rows). Does not 199 | deallocate the structure itself. 200 | @param core Pointer to Protomatter_core structure. 201 | */ 202 | extern void _PM_deallocate(Protomatter_core *core); 203 | 204 | /*! 205 | @brief Matrix "row handler" that's called by the timer interrupt. 206 | Handles row address lines and issuing data to matrix. 207 | @param core Pointer to Protomatter_core structure. 208 | */ 209 | extern void _PM_row_handler(Protomatter_core *core); 210 | 211 | // ********************************************************************* 212 | // NOTE: AS OF 1.3.0, TIMER-RELATED FUNCTIONS REQUIRE A Protomatter_core 213 | // STRUCT POINTER, RATHER THAN A void* TIMER-RELATED POINTER. 214 | // ********************************************************************* 215 | 216 | /*! 217 | @brief Returns current value of frame counter and resets its value to 218 | zero. Two calls to this, timed one second apart (or use math with 219 | other intervals), can be used to get a rough frames-per-second 220 | value for the matrix (since this is difficult to estimate 221 | beforehand). 222 | @param core Pointer to Protomatter_core structure. 223 | @return Frame count since previous call to function, as a uint32_t. 224 | */ 225 | extern uint32_t _PM_getFrameCount(Protomatter_core *core); 226 | 227 | /*! 228 | @brief Start (or restart) a timer/counter peripheral. 229 | @param core Pointer to Protomatter core structure, from which timer 230 | details can be derived. 231 | @param period Timer 'top' / rollover value. 232 | */ 233 | extern void _PM_timerStart(Protomatter_core *core, uint32_t period); 234 | 235 | /*! 236 | @brief Stop timer/counter peripheral. 237 | @param core Pointer to Protomatter core structure, from which timer 238 | details can be derived. 239 | @return Counter value when timer was stopped. 240 | */ 241 | extern uint32_t _PM_timerStop(Protomatter_core *core); 242 | 243 | /*! 244 | @brief Query a timer/counter peripheral's current count. 245 | @param core Pointer to Protomatter core structure, from which timer 246 | details can be derived. 247 | @return Counter value. 248 | */ 249 | extern uint32_t _PM_timerGetCount(Protomatter_core *core); 250 | 251 | /*! 252 | @brief Pauses until the next vertical blank to avoid 'tearing' animation 253 | (if display is double-buffered). If single-buffered, has no effect. 254 | @param core Pointer to Protomatter_core structure. 255 | */ 256 | extern void _PM_swapbuffer_maybe(Protomatter_core *core); 257 | 258 | /*! 259 | @brief Adjust duty cycle of HUB75 clock signal. This is not supported on 260 | all architectures. 261 | @param d Duty setting, 0 minimum. Increasing values generate higher clock 262 | duty cycles at the same frequency. Arbitrary granular units, max 263 | varies by architecture and CPU speed, if supported at all. 264 | e.g. SAMD51 @ 120 MHz supports 0 (~50% duty) through 2 (~75%). 265 | */ 266 | extern void _PM_setDuty(uint8_t d); 267 | 268 | #if defined(ARDUINO) || defined(CIRCUITPY) 269 | 270 | /*! 271 | @brief Converts image data from GFX16 canvas to the matrices weird 272 | internal format. 273 | @param core Pointer to Protomatter_core structure. 274 | @param source Pointer to source image data (see Adafruit_GFX 16-bit 275 | canvas type for format). 276 | @param width Width of canvas in pixels, as this may be different than 277 | the matrix pixel width due to row padding. 278 | */ 279 | extern void _PM_convert_565(Protomatter_core *core, uint16_t *source, 280 | uint16_t width); 281 | 282 | #endif // END ARDUINO || CIRCUITPY 283 | 284 | #ifdef __cplusplus 285 | } // extern "C" 286 | #endif 287 | --------------------------------------------------------------------------------