Download the .UF2 file below by clicking the green box. Save the file to your computer. Plug your Metro RP2350 into your computer via a known good USB data+power cable (data + power, not a charge only cable).
Hold down the BOOT/BOOTSEL button (highlighted in red above), and while continuing to hold it (don't let go!), press and release the reset button (highlighted in blue above). Continue to hold the BOOT/BOOTSEL button until the RP2350 drive appears on your computer. Copy the .UF2 file you saved previously to the RP2350 drive and The Matrix should appear if you have the display connected.
Download the .UF2 file below by clicking the green box. Save the file to your computer. Plug your Fruit Jam into your computer via a known good USB data+power cable.
To enter the bootloader, hold down the BOOT/BOOTSEL button (highlighted in red above labeled Button #1), and while continuing to hold it (don't let go!), press and release the reset button (highlighted in blue above). Continue to hold the BOOT/BOOTSEL button until the RP2350 drive appears!
If the drive does not appear, release all the buttons, and then repeat the process above.
You can also start with your board unplugged from USB, press and hold the BOOTSEL button (highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive to appear before releasing the button.
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called RP2350.
Drag the matrix_fruitjam.uf2 file to RP2350. The program should immediately start.
Arduino Code (if you want)
Please refer to the Arduino IDE setup in the Adafruit Metro RP2350 guide or Fruit Jam Guide, depending on your board.
You will want to add the library Adafruit DVI HSTX (version 1.10 or later, likely the latest version) to your Arduino environment. Select Sketch - > Include Library -> Manage Libraries... Search for "Adafruit DVI HSTX" by Jeff Epler. You will want version 1.1.0 or later, likely the newest version. Click Install and accept installing libraries that Adafruit DVI HSTX is dependent on. Those are the only libraries which need to be loaded.
For Metro RP2350
For the main program, get the code by clicking the "Download File" button below. Extract the file Metro_HSTX_Matrix.ino from the zip archive. Load it into the Arduino IDE.
// SPDX-FileCopyrightText: 2021 Anne Barela for Adafruit Industries
//
// SPDX-License-Identifier: MIT
//
// Based on Adafruit-DVI-HSTX library code written by Jeff Epler
// and use of Claude 3.7 Sonnet on 3/2/2025
// https://claude.site/artifacts/cf022b66-50c3-43eb-b334-17fbf0ed791c
#include <Adafruit_dvhstx.h>
// Display configuration for text mode in Adafruit-DVI-HSTX
const int SCREEN_WIDTH = 91;
const int SCREEN_HEIGHT = 30;
// Animation speed (lower = faster)
// Adjust this value to change the speed of the animation
const int ANIMATION_SPEED = 70; // milliseconds between updates
// Initialize display for Adafruit Metro RP2350
DVHSTXText display({14, 18, 16, 12}); // Adafruit Metro HSTX Pinout
// Define structures for character streams
struct CharStream {
int x; // X position
int y; // Y position (head of the stream)
int length; // Length of the stream
int speed; // How many frames to wait before moving
int countdown; // Counter for movement
bool active; // Whether this stream is currently active
char chars[30]; // Characters in the stream
};
// Array of character streams - increased for higher density
// To fill 60-75% of the screen width (91 chars), we need around 55-68 active streams
CharStream streams[250]; // Allow for decent density
// Stream creation rate (higher = more frequent new streams)
const int STREAM_CREATION_CHANCE = 65; // % chance per frame to create new stream
// Initial streams to create at startup
const int INITIAL_STREAMS = 30;
// Random characters that appear in the streams
const char matrixChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;:,.<>?/\\";
const int numMatrixChars = sizeof(matrixChars) - 1;
// Function declarations
void initStreams();
void updateStreams();
void drawStream(CharStream &stream);
void createNewStream();
char getRandomChar();
void setup() {
// Initialize the display
display.begin();
display.clear();
// Seed the random number generator
randomSeed(analogRead(A0));
// Initialize all streams
initStreams();
}
void loop() {
// Update and draw all streams
updateStreams();
// Randomly create new streams at a higher rate
if (random(100) < STREAM_CREATION_CHANCE) {
createNewStream();
}
// Control animation speed
delay(ANIMATION_SPEED);
}
void initStreams() {
// Initialize all streams as inactive
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
streams[i].active = false;
}
// Create more initial streams for immediate visual impact
for (int i = 0; i < INITIAL_STREAMS; i++) {
createNewStream();
}
}
void createNewStream() {
// Find an inactive stream
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
if (!streams[i].active) {
// Initialize the stream
streams[i].x = random(SCREEN_WIDTH);
streams[i].y = random(5) - 5; // Start above the screen
streams[i].length = random(5, 20);
streams[i].speed = random(1, 4);
streams[i].countdown = streams[i].speed;
streams[i].active = true;
// Fill with random characters
for (int j = 0; j < streams[i].length; j++) {
streams[i].chars[j] = getRandomChar();
}
return;
}
}
}
void updateStreams() {
display.clear();
// Count active streams (for debugging if needed)
int activeCount = 0;
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
if (streams[i].active) {
activeCount++;
streams[i].countdown--;
// Time to move the stream down
if (streams[i].countdown <= 0) {
streams[i].y++;
streams[i].countdown = streams[i].speed;
// Change a random character in the stream
int randomIndex = random(streams[i].length);
streams[i].chars[randomIndex] = getRandomChar();
}
// Draw the stream
drawStream(streams[i]);
// Check if the stream has moved completely off the screen
if (streams[i].y - streams[i].length > SCREEN_HEIGHT) {
streams[i].active = false;
}
}
}
}
void drawStream(CharStream &stream) {
for (int i = 0; i < stream.length; i++) {
int y = stream.y - i;
// Only draw if the character is on screen
if (y >= 0 && y < SCREEN_HEIGHT) {
display.setCursor(stream.x, y);
// Set different colors/intensities based on position in the stream
if (i == 0) {
// Head of the stream is white (brightest)
display.setColor(TextColor::TEXT_WHITE, TextColor::BG_BLACK, TextColor::ATTR_NORMAL_INTEN);
} else if (i < 3) {
// First few characters are bright green
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK, TextColor::ATTR_NORMAL_INTEN);
} else if (i < 6) {
// Next few are medium green
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK, TextColor::ATTR_LOW_INTEN);
} else {
// The rest are dim green
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK, TextColor::ATTR_V_LOW_INTEN);
}
// Draw the character
display.write(stream.chars[i]);
}
}
// Occasionally change a character in the stream
if (random(100) < 25) { // 25% chance
int idx = random(stream.length);
stream.chars[idx] = getRandomChar();
}
}
char getRandomChar() {
return matrixChars[random(numMatrixChars)];
}
Select Adafruit Metro RP2350 as the board in the box in the toolbar.
Plug your Metro into your computer via a known good USB data + power cable (not the tiny power-only cables that come with battery packs). The Metro should show up as a new serial port. Select that serial port under Tools -> Port.
Click the arrow key -> on the toolbar to compile the program and upload it to the Metro.
For the main program, get the code by clicking the "Download File" button below. Extract the file matrix_fruitjam.ino from the zip archive. Load it into the Arduino IDE.
// SPDX-FileCopyrightText: 2025 Anne Barela for Adafruit Industries
//
// SPDX-License-Identifier: MIT
//
// Based on Adafruit-DVI-HSTX library code written by Jeff Epler
// Version for the Adafruit Fruit Jam
#include <Adafruit_dvhstx.h>
// Display configuration for text mode in Adafruit-DVI-HSTX
const int SCREEN_WIDTH = 91;
const int SCREEN_HEIGHT = 30;
// Animation speed (lower = faster)
// Adjust this value to change the speed of the animation
const int ANIMATION_SPEED = 120; // milliseconds between updates
#if defined(ADAFRUIT_FEATHER_RP2350_HSTX)
DVHSTXPinout pinConfig = ADAFRUIT_FEATHER_RP2350_CFG;
#elif defined(ADAFRUIT_METRO_RP2350)
DVHSTXPinout pinConfig = ADAFRUIT_METRO_RP2350_CFG;
#elif defined(ARDUINO_ADAFRUIT_FRUITJAM_RP2350)
DVHSTXPinout pinConfig = ADAFRUIT_FRUIT_JAM_CFG;
#elif (defined(ARDUINO_RASPBERRY_PI_PICO_2) || defined(ARDUINO_RASPBERRY_PI_PICO_2W))
DVHSTXPinout pinConfig = ADAFRUIT_HSTXDVIBELL_CFG;
#else
// If your board definition has PIN_CKP and related defines,
// DVHSTX_PINOUT_DEFAULT is available
DVHSTXPinout pinConfig = DVHSTX_PINOUT_DEFAULT;
#endif
DVHSTXText display(pinConfig);
// If you get the message "error: 'DVHSTX_PINOUT_DEFAULTx' was not declared"
// then you need to give the pins numbers explicitly, like the example below.
// The order is: {CKP, D0P, D1P, D2P}.
//
// DVHSTXText display({12, 14, 16, 18});
// Define structures for character streams
struct CharStream {
int x; // X position
int y; // Y position (head of the stream)
int length; // Length of the stream
int speed; // How many frames to wait before moving
int countdown; // Counter for movement
bool active; // Whether this stream is currently active
char chars[30]; // Characters in the stream
};
const static TextColor colors[] = {
TextColor::TEXT_BLACK, TextColor::TEXT_RED, TextColor::TEXT_GREEN,
TextColor::TEXT_BLUE, TextColor::TEXT_YELLOW, TextColor::TEXT_MAGENTA,
TextColor::TEXT_CYAN, TextColor::TEXT_WHITE,
};
const static TextColor background_colors[] = {
TextColor::BG_BLACK, TextColor::BG_RED, TextColor::BG_GREEN,
TextColor::BG_BLUE, TextColor::BG_YELLOW, TextColor::BG_MAGENTA,
TextColor::BG_CYAN, TextColor::BG_WHITE,
};
// Array of character streams - increased for higher density
// To fill 60-75% of the screen width (91 chars), we need around 55-68 active streams
CharStream streams[250]; // Allow for decent density
// Stream creation rate (higher = more frequent new streams)
const int STREAM_CREATION_CHANCE = 80; // % chance per frame to create new stream
// Initial streams to create at startup
const int INITIAL_STREAMS = 30;
// Random characters that appear in the streams
const char matrixChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;:,.<>?/\\";
const int numMatrixChars = sizeof(matrixChars) - 1;
// Function declarations
void initStreams();
void updateStreams();
void drawStream(CharStream &stream);
void createNewStream();
char getRandomChar();
void setup() {
// Initialize the display
display.begin();
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK);
display.clear();
// Seed the random number generator
randomSeed(analogRead(A0));
// Initialize all streams
initStreams();
}
void loop() {
// Update and draw all streams
updateStreams();
// Randomly create new streams at a higher rate
if (random(100) < STREAM_CREATION_CHANCE) {
createNewStream();
}
// Control animation speed
delay(ANIMATION_SPEED);
}
void initStreams() {
// Initialize all streams as inactive
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
streams[i].active = false;
}
// Create more initial streams for immediate visual impact
for (int i = 0; i < INITIAL_STREAMS; i++) {
createNewStream();
}
}
void createNewStream() {
// Find an inactive stream
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
if (!streams[i].active) {
// Initialize the stream
streams[i].x = random(SCREEN_WIDTH);
streams[i].y = random(5) - 5; // Start above the screen
streams[i].length = random(5, 20);
streams[i].speed = random(1, 4);
streams[i].countdown = streams[i].speed;
streams[i].active = true;
// Fill with random characters
for (int j = 0; j < streams[i].length; j++) {
streams[i].chars[j] = getRandomChar();
}
return;
}
}
}
void updateStreams() {
display.clear();
// Count active streams (for debugging if needed)
int activeCount = 0;
for (int i = 0; i < sizeof(streams) / sizeof(streams[0]); i++) {
if (streams[i].active) {
activeCount++;
streams[i].countdown--;
// Time to move the stream down
if (streams[i].countdown <= 0) {
streams[i].y++;
streams[i].countdown = streams[i].speed;
// Change a random character in the stream
int randomIndex = random(streams[i].length);
streams[i].chars[randomIndex] = getRandomChar();
}
// Draw the stream
drawStream(streams[i]);
// Check if the stream has moved completely off the screen
if (streams[i].y - streams[i].length > SCREEN_HEIGHT) {
streams[i].active = false;
}
}
}
}
void drawStream(CharStream &stream) {
for (int i = 0; i < stream.length; i++) {
int y = stream.y - i;
// Only draw if the character is on screen
if (y >= 0 && y < SCREEN_HEIGHT) {
display.setCursor(stream.x, y);
// Set different colors/intensities based on position in the stream
if (i == 0) {
// Head of the stream is white (brightest)
display.setColor(TextColor::TEXT_WHITE, TextColor::BG_BLACK, TextColor::ATTR_NORMAL_INTEN);
} else if (i < 3) {
// First few characters are bright green
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK, TextColor::ATTR_NORMAL_INTEN);
} else if (i < 6) {
// Next few are medium green
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK, TextColor::ATTR_LOW_INTEN);
} else {
// The rest are dim green
display.setColor(TextColor::TEXT_GREEN, TextColor::BG_BLACK, TextColor::ATTR_V_LOW_INTEN);
}
// Draw the character
display.write(stream.chars[i]);
}
}
// Occasionally change a character in the stream
if (random(100) < 25) { // 25% chance
int idx = random(stream.length);
stream.chars[idx] = getRandomChar();
}
}
char getRandomChar() {
return matrixChars[random(numMatrixChars)];
}
Select Adafruit Fruit Jam as the board in the box in the toolbar.
Plug your Fruit Jam into your computer via a known good USB data + power cable (not the tiny power-only cables that come with battery packs). The Fruit Jam should show up as a new serial port. Select that serial port under Tools -> Port.
Click the arrow key -> on the toolbar to compile the program and upload it to the Fruit Jam.
Page last edited December 10, 2025
Text editor powered by tinymce.