diff --git a/PiCowbell_HSTX_DVI_Examples/arduino_rp2040_hstx_dvi_cowbell/.pico_rp2040.test.only b/PiCowbell_HSTX_DVI_Examples/arduino_rp2040_hstx_dvi_cowbell/.pico_rp2040.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/PiCowbell_HSTX_DVI_Examples/arduino_rp2040_hstx_dvi_cowbell/arduino_rp2040_hstx_dvi_cowbell.ino b/PiCowbell_HSTX_DVI_Examples/arduino_rp2040_hstx_dvi_cowbell/arduino_rp2040_hstx_dvi_cowbell.ino new file mode 100644 index 000000000..fd16c0d27 --- /dev/null +++ b/PiCowbell_HSTX_DVI_Examples/arduino_rp2040_hstx_dvi_cowbell/arduino_rp2040_hstx_dvi_cowbell.ino @@ -0,0 +1,623 @@ +// SPDX-FileCopyrightText: 2023 Phil B. for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// Basic full-color PicoDVI test. Provides a 16-bit color video framebuffer to +// which Adafruit_GFX calls can be made. It's based on the EYESPI_Test.ino sketch. + +#include // Core display & graphics library +#include // A custom font + +// Here's how a 320x240 16-bit color framebuffer is declared. Double-buffering +// is not an option in 16-bit color mode, just not enough RAM; all drawing +// operations are shown as they occur. Second argument is a hardware +// configuration -- this example is written for Adafruit PiCowBell HSTX DVI, +// and Raspberry Pi RP2040, but that's easily switched out for boards +// like the Pimoroni Pico DV (use 'pimoroni_demo_hdmi_cfg') or Pico DVI Sock ('pico_sock_cfg'). +DVIGFX16 display(DVI_RES_320x240p60, adafruit_hstxdvibell_cfg); + +// A 400x240 mode is possible but pushes overclocking even higher than +// 320x240 mode. SOME BOARDS MIGHT SIMPLY NOT BE COMPATIBLE WITH THIS. +// May require selecting QSPI div4 clock (Tools menu) to slow down flash +// accesses, may require further over-volting the CPU to 1.25 or 1.3 V. +//DVIGFX16 display(DVI_RES_400x240p60, adafruit_feather_dvi_cfg); + +void setup() { // Runs once on startup + if (!display.begin()) { // Blink LED if insufficient RAM + pinMode(LED_BUILTIN, OUTPUT); + for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1); + } +} + +#define PAUSE 2000 // Delay (milliseconds) between examples +uint8_t rotate = 0; // Current screen orientation (0-3) +#define CORNER_RADIUS 0 + +void loop() { + // Each of these functions demonstrates a different Adafruit_GFX concept: + show_shapes(); + show_charts(); + show_basic_text(); + show_char_map(); + show_custom_text(); + show_bitmap(); + show_canvas(); + + if (++rotate > 3) rotate = 0; // Cycle through screen rotations 0-3 + display.setRotation(rotate); // Takes effect on next drawing command +} + +// BASIC SHAPES EXAMPLE ---------------------------------------------------- + +void show_shapes() { + // Draw outlined and filled shapes. This demonstrates: + // - Enclosed shapes supported by GFX (points & lines are shown later). + // - Adapting to different-sized displays, and to rounded corners. + + const int16_t cx = display.width() / 2; // Center of screen = + const int16_t cy = display.height() / 2; // half of width, height + int16_t minor = min(cx, cy); // Lesser of half width or height + // Shapes will be drawn in a square region centered on the screen. But one + // particular screen -- rounded 240x280 ST7789 -- has VERY rounded corners + // that would clip a couple of shapes if drawn full size. If using that + // screen type, reduce area by a few pixels to avoid drawing in corners. + if (CORNER_RADIUS > 40) minor -= 4; + const uint8_t pad = 5; // Space between shapes is 2X this + const int16_t size = minor - pad; // Shapes are this width & height + const int16_t half = size / 2; // 1/2 of shape size + + display.fillScreen(0); // Start by clearing the screen; color 0 = black + + // Draw outline version of basic shapes: rectangle, triangle, circle and + // rounded rectangle in different colors. Rather than hardcoded numbers + // for position and size, some arithmetic helps adapt to screen dimensions. + display.drawRect(cx - minor, cy - minor, size, size, 0xF800); + display.drawTriangle(cx + pad, cy - pad, cx + pad + half, cy - minor, + cx + minor - 1, cy - pad, 0x07E0); + display.drawCircle(cx - pad - half, cy + pad + half, half, 0x001F); + display.drawRoundRect(cx + pad, cy + pad, size, size, size / 5, 0xFFE0); + delay(PAUSE); + + // Draw same shapes, same positions, but filled this time. + display.fillRect(cx - minor, cy - minor, size, size, 0xF800); + display.fillTriangle(cx + pad, cy - pad, cx + pad + half, cy - minor, + cx + minor - 1, cy - pad, 0x07E0); + display.fillCircle(cx - pad - half, cy + pad + half, half, 0x001F); + display.fillRoundRect(cx + pad, cy + pad, size, size, size / 5, 0xFFE0); + delay(PAUSE); +} // END SHAPE EXAMPLE + +// CHART EXAMPLES ---------------------------------------------------------- + +void show_charts() { + // Draw some graphs and charts. GFX library doesn't handle these as native + // object types, but it only takes a little code to build them from simple + // shapes. This demonstrates: + // - Drawing points and horizontal, vertical and arbitrary lines. + // - Adapting to different-sized displays. + // - Graphics being clipped off edge. + // - Use of negative values to draw shapes "backward" from an anchor point. + // - C technique for finding array size at runtime (vs hardcoding). + + display.fillScreen(0); // Clear screen + + const int16_t cx = display.width() / 2; // Center of screen = + const int16_t cy = display.height() / 2; // half of width, height + const int16_t minor = min(cx, cy); // Lesser of half width or height + const int16_t major = max(cx, cy); // Greater of half width or height + + // Let's start with a relatively simple sine wave graph with axes. + // Draw graph axes centered on screen. drawFastHLine() and drawFastVLine() + // need fewer arguments than normal 2-point line drawing shown later. + display.drawFastHLine(0, cy, display.width(), 0x0210); // Dark blue + display.drawFastVLine(cx, 0, display.height(), 0x0210); + + // Then draw some tick marks along the axes. To keep this code simple, + // these aren't to any particular scale, but a real program may want that. + // The loop here draws them from the center outward and pays no mind + // whether the screen is rectangular; any ticks that go off-screen will + // be clipped by the library. + for (uint8_t i=1; i<=10; i++) { + // The Arduino map() function scales an input value (e.g. "i") from an + // input range (0-10 here) to an output range (0 to major-1 here). + // Very handy for making graphics adjust to different screens! + int16_t n = map(i, 0, 10, 0, major - 1); // Tick offset relative to center point + display.drawFastVLine(cx - n, cy - 5, 11, 0x210); + display.drawFastVLine(cx + n, cy - 5, 11, 0x210); + display.drawFastHLine(cx - 5, cy - n, 11, 0x210); + display.drawFastHLine(cx - 5, cy + n, 11, 0x210); + } + + // Then draw sine wave over this using GFX drawPixel() function. + for (int16_t x=0; x(str.c_str())); +} + +// TEXT EXAMPLES ----------------------------------------------------------- + +// This section demonstrates: +// - Using the default 5x7 built-in font, including scaling in each axis. +// - How to access all characters of this font, including symbols. +// - Using a custom font, including alignment techniques that aren't a normal +// part of the GFX library (uses functions above). + +void show_basic_text() { + // Show text scaling with built-in font. + display.fillScreen(0); + display.setFont(); // Use default font + display.setCursor(0, CORNER_RADIUS); // Initial cursor position + display.setTextSize(1); // Default size + display.println(F("Standard built-in font")); + display.setTextSize(2); + display.println(F("BIG TEXT")); + display.setTextSize(3); + // "BIGGER TEXT" won't fit on narrow screens, so abbreviate there. + display.println((display.width() >= 200) ? F("BIGGER TEXT") : F("BIGGER")); + display.setTextSize(2, 4); + display.println(F("TALL and")); + display.setTextSize(4, 2); + display.println(F("WIDE")); + + delay(PAUSE); +} // END BASIC TEXT EXAMPLE + +void show_char_map() { + // "Code Page 437" is a name given to the original IBM PC character set. + // Despite age and limited language support, still seen in small embedded + // settings as it has some useful symbols and accented characters. The + // default 5x7 pixel font of Adafruit_GFX is modeled after CP437. This + // function draws a table of all the characters & explains some issues. + + // There are 256 characters in all. Draw table as 16 rows of 16 columns, + // plus hexadecimal row & column labels. How big can each cell be drawn? + const int cell_size = min(display.width(), display.height()) / 17; + if (cell_size < 8) return; // Screen is too small for table, skip example. + const int total_size = cell_size * 17; // 16 cells + 1 row or column label + + // Set up for default 5x7 font at 1:1 scale. Custom fonts are NOT used + // here as most are only 128 characters to save space (the "7b" at the + // end of many GFX font names means "7 bits," i.e. 128 characters). + display.setFont(); + display.setTextSize(1); + + // Early Adafruit_GFX was missing one symbol, throwing off some indices! + // But fixing the library would break MANY existing sketches that relied + // on the degrees symbol and others. The default behavior is thus "broken" + // to keep older code working. New code can access the CORRECT full CP437 + // table by calling this function like so: + display.cp437(true); + + display.fillScreen(0); + + const int16_t x = (display.width() - total_size) / 2; // Upper left corner of + int16_t y = (display.height() - total_size) / 2; // table centered on screen + if (y >= 4) { // If there's a little extra space above & below, scoot table + y += 4; // down a few pixels and show a message centered at top. + display.setCursor((display.width() - 114) / 2, 0); // 114 = pixel width + display.print(F("CP437 Character Map")); // of this message + } + + const int16_t inset_x = (cell_size - 5) / 2; // To center each character within cell, + const int16_t inset_y = (cell_size - 8) / 2; // compute X & Y offset from corner. + + for (uint8_t row=0; row<16; row++) { // 16 down... + // Draw row and columm headings as hexadecimal single digits. To get the + // hex value for a specific character, combine the left & top labels, + // e.g. Pi symbol is row E, column 3, thus: display.print((char)0xE3); + display.setCursor(x + (row + 1) * cell_size + inset_x, y + inset_y); + display.print(row, HEX); // This actually draws column labels + display.setCursor(x + inset_x, y + (row + 1) * cell_size + inset_y); + display.print(row, HEX); // and THIS is the row labels + for (uint8_t col=0; col<16; col++) { // 16 across... + if ((row + col) & 1) { // Fill alternating cells w/gray + display.fillRect(x + (col + 1) * cell_size, y + (row + 1) * cell_size, + cell_size, cell_size, 0x630C); + } + // drawChar() bypasses usual cursor positioning to go direct to an X/Y + // location. If foreground & background match, it's drawn transparent. + display.drawChar(x + (col + 1) * cell_size + inset_x, + y + (row + 1) * cell_size + inset_y, row * 16 + col, + 0xFFFF, 0xFFFF, 1); + } + } + + delay(PAUSE * 2); +} // END CHAR MAP EXAMPLE + +void show_custom_text() { + // Show use of custom fonts, plus how to do center or right alignment + // using some additional functions provided earlier. + + display.fillScreen(0); + display.setFont(&FreeSansBold18pt7b); + display.setTextSize(1); + display.setTextWrap(false); // Allow text off edges + + // Get "M height" of custom font and move initial base line there: + uint16_t w, h; + int16_t x, y; + display.getTextBounds("M", 0, 0, &x, &y, &w, &h); + // On rounded 240x280 display in tall orientation, "Custom Font" gets + // clipped by top corners. Scoot text down a few pixels in that one case. + if (CORNER_RADIUS && (display.height() == 280)) h += 20; + display.setCursor(display.width() / 2, h); + + if (display.width() >= 200) { + print_aligned(display, F("Custom Font"), GFX_ALIGN_CENTER); + display.setCursor(0, display.getCursorY() + 10); + print_aligned(display, F("Align Left"), GFX_ALIGN_LEFT); + display.setCursor(display.width() / 2, display.getCursorY()); + print_aligned(display, F("Centered"), GFX_ALIGN_CENTER); + // Small rounded screen, when oriented the wide way, "Right" gets + // clipped by bottom right corner. Scoot left to compensate. + int16_t x_offset = (CORNER_RADIUS && (display.height() < 200)) ? 15 : 0; + display.setCursor(display.width() - x_offset, display.getCursorY()); + print_aligned(display, F("Align Right"), GFX_ALIGN_RIGHT); + } else { + // On narrow screens, use abbreviated messages + print_aligned(display, F("Font &"), GFX_ALIGN_CENTER); + print_aligned(display, F("Align"), GFX_ALIGN_CENTER); + display.setCursor(0, display.getCursorY() + 10); + print_aligned(display, F("Left"), GFX_ALIGN_LEFT); + display.setCursor(display.width() / 2, display.getCursorY()); + print_aligned(display, F("Center"), GFX_ALIGN_CENTER); + display.setCursor(display.width(), display.getCursorY()); + print_aligned(display, F("Right"), GFX_ALIGN_RIGHT); + } + + delay(PAUSE); +} // END CUSTOM FONT EXAMPLE + +// BITMAP EXAMPLE ---------------------------------------------------------- + +// This section demonstrates: +// - Embedding a small bitmap in the code (flash memory). +// - Drawing that bitmap in various colors, and transparently (only '1' bits +// are drawn; '0' bits are skipped, leaving screen contents in place). +// - Use of the color565() function to decimate 24-bit RGB to 16 bits. + +#define HEX_WIDTH 16 // Bitmap width in pixels +#define HEX_HEIGHT 16 // Bitmap height in pixels +// Bitmap data. PROGMEM ensures it's in flash memory (not RAM). And while +// it would be valid to leave the brackets empty here (i.e. hex_bitmap[]), +// having dimensions with a little math makes the compiler verify the +// correct number of bytes are present in the list. +PROGMEM const uint8_t hex_bitmap[(HEX_WIDTH + 7) / 8 * HEX_HEIGHT] = { + 0b00000001, 0b10000000, + 0b00000111, 0b11100000, + 0b00011111, 0b11111000, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b00011111, 0b11111000, + 0b00000111, 0b11100000, + 0b00000001, 0b10000000, +}; +#define Y_SPACING (HEX_HEIGHT - 2) // Used by code below for positioning + +void show_bitmap() { + display.fillScreen(0); + + // Not screen center, but UL coordinates of center hexagon bitmap + const int16_t center_x = (display.width() - HEX_WIDTH) / 2; + const int16_t center_y = (display.height() - HEX_HEIGHT) / 2; + const uint8_t steps = min((display.height() - HEX_HEIGHT) / Y_SPACING, + display.width() / HEX_WIDTH - 1) / 2; + + display.drawBitmap(center_x, center_y, hex_bitmap, HEX_WIDTH, HEX_HEIGHT, + 0xFFFF); // Draw center hexagon in white + + // Tile the hexagon bitmap repeatedly in a range of hues. Don't mind the + // bit of repetition in the math, the optimizer easily picks this up. + // Also, if math looks odd, keep in mind "PEMDAS" operator precedence; + // multiplication and division occur before addition and subtraction. + for (uint8_t a=0; a<=steps; a++) { + for (uint8_t b=1; b<=steps; b++) { + display.drawBitmap( // Right section centered red: a = green, b = blue + center_x + (a + b) * HEX_WIDTH / 2, + center_y + (a - b) * Y_SPACING, + hex_bitmap, HEX_WIDTH, HEX_HEIGHT, + display.color565(255, 255 - 255 * a / steps, 255 - 255 * b / steps)); + display.drawBitmap( // UL section centered green: a = blue, b = red + center_x - b * HEX_WIDTH + a * HEX_WIDTH / 2, + center_y - a * Y_SPACING, + hex_bitmap, HEX_WIDTH, HEX_HEIGHT, + display.color565(255 - 255 * b / steps, 255, 255 - 255 * a / steps)); + display.drawBitmap( // LL section centered blue: a = red, b = green + center_x - a * HEX_WIDTH + b * HEX_WIDTH / 2, + center_y + b * Y_SPACING, + hex_bitmap, HEX_WIDTH, HEX_HEIGHT, + display.color565(255 - 255 * a / steps, 255 - 255 * b / steps, 255)); + } + } + + delay(PAUSE); +} // END BITMAP EXAMPLE + +// CANVAS EXAMPLE ---------------------------------------------------------- + +// This section demonstrates: +// - How to refresh changing values onscreen without erase/redraw flicker. +// - Using an offscreen canvas. It's similar to a bitmap above, but rather +// than a fixed pattern in flash memory, it's drawable like the screen. +// - More tips on text alignment, and adapting to different screen sizes. + +#define PADDING 6 // Pixels between axis label and value + +void show_canvas() { + // For this example, let's suppose we want to display live readings from a + // sensor such as a three-axis accelerometer, something like: + // X: (number) + // Y: (number) + // Z: (number) + // To look extra classy, we want a custom font, and the labels for each + // axis are right-aligned so the ':' characters line up... + + display.setFont(&FreeSansBold18pt7b); // Use a custom font + display.setTextSize(1); // and reset to 1:1 scale + + char *label[] = { "X:", "Y:", "Z:" }; // Labels for each axis + const uint16_t color[] = { 0xF800, 0x07E0, 0x001F }; // Colors for each value + + // To get the labels right-aligned, one option would be simple trial and + // error to find a column that looks good and doesn't clip anything off. + // Let's do this dynamically though, so it adapts to any font or labels! + // Start by finding the widest of the label strings: + uint16_t w, h, max_w = 0; + int16_t x, y; + for (uint8_t i=0; i<3; i++) { // For each label... + display.getTextBounds(label[i], 0, 0, &x, &y, &w, &h); + if (w > max_w) max_w = w; // Keep track of widest label + } + + // Rounded corners throwing us a curve again. If needed, scoot everything + // to the right a bit on wide displays, down a bit on tall ones. + int16_t y_offset = 0; + if (display.width() > display.height()) max_w += CORNER_RADIUS; + else y_offset = CORNER_RADIUS; + + // Now we have max_w for right-aligning the labels. Before we draw them + // though...in order to perform flicker-free updates, the numbers we show + // will be rendered in either a GFXcanvas1 or GFXcanvas16 object; a 1-bit + // or 16-bit offscreen bitmap, RAM permitting. The correct size for this + // canvas could also be trial-and-errored, but again let's make this adapt + // automatically. The width of the canvas will span from max_w (plus a few + // pixels for padding) to the right edge. But the height? Looking at an + // uppercase 'M' can work in many situations, but some fonts have ascenders + // and descenders on digits, and in some locales a comma (extending below + // the baseline) is the decimal separator. Feed ALL the numeric chars into + // getTextBounds() for a cumulative height: + display.setTextWrap(false); // Keep on one line + display.getTextBounds(F("0123456789.,-"), 0, 0, &x, &y, &w, &h); + + // Now declare a GFXcanvas16 object based on the computed width & height: + GFXcanvas16 canvas16(display.width() - max_w - PADDING, h); + + // Small devices (e.g. ATmega328p) will almost certainly lack enough RAM + // for the canvas. Check if canvas buffer exists. If not, fall back on + // using a 1-bit (rather than 16-bit) canvas. Much more RAM friendly, but + // not as fast to draw. If a project doesn't require super interactive + // updates, consider just going straight for the more compact Canvas1. + if (canvas16.getBuffer()) { + // If here, 16-bit canvas allocated successfully! Point of interest, + // only one canvas is needed for this example, we can reuse it for all + // three numbers because the regions are the same size. + + // display and canvas are independent drawable objects; must explicitly + // set the same custom font to use on the canvas now: + canvas16.setFont(&FreeSansBold18pt7b); + + // Clear display and print labels. Once drawn, these remain untouched. + display.fillScreen(0); + display.setCursor(max_w, -y + y_offset); // Set baseline for first row + for (uint8_t i=0; i<3; i++) print_aligned(display, label[i], GFX_ALIGN_RIGHT); + + // Last part now is to print numbers on the canvas and copy the canvas to + // the display, repeating for several seconds... + uint32_t elapsed, startTime = millis(); + while ((elapsed = (millis() - startTime)) <= PAUSE * 2) { + for (uint8_t i=0; i<3; i++) { // For each label... + canvas16.fillScreen(0); // fillScreen() in this case clears canvas + canvas16.setCursor(0, -y); // Reset baseline for custom font + canvas16.setTextColor(color[i]); + // These aren't real accelerometer readings, just cool-looking numbers. + // Notice we print to the canvas, NOT the display: + canvas16.print(sin(elapsed / 200.0 + (float)i * M_PI * 2.0 / 3.0), 5); + // And HERE is the secret sauce to flicker-free updates. Canvas details + // can be passed to the drawRGBBitmap() function, which fully overwrites + // prior screen contents in that area. yAdvance is font line spacing. + display.drawRGBBitmap(max_w + PADDING, i * FreeSansBold18pt7b.yAdvance + + y_offset, canvas16.getBuffer(), canvas16.width(), + canvas16.height()); + } + } + } else { + // Insufficient RAM for Canvas16. Try declaring a 1-bit canvas instead... + GFXcanvas1 canvas1(display.width() - max_w - PADDING, h); + // If even this smaller object fails, can't proceed, cancel this example. + if (!canvas1.getBuffer()) return; + + // Remainder here is nearly identical to the code above, simply using a + // different canvas type. It's stripped of most comments for brevity. + canvas1.setFont(&FreeSansBold18pt7b); + display.fillScreen(0); + display.setCursor(max_w, -y + y_offset); + for (uint8_t i=0; i<3; i++) print_aligned(display, label[i], GFX_ALIGN_RIGHT); + uint32_t elapsed, startTime = millis(); + while ((elapsed = (millis() - startTime)) <= PAUSE * 2) { + for (uint8_t i=0; i<3; i++) { + canvas1.fillScreen(0); + canvas1.setCursor(0, -y); + canvas1.print(sin(elapsed / 200.0 + (float)i * M_PI * 2.0 / 3.0), 5); + // Here's the secret sauce to flicker-free updates with GFXcanvas1. + // Canvas details can be passed to the drawBitmap() function, and by + // specifying both a foreground AND BACKGROUND color (0), this will fully + // overwrite/erase prior screen contents in that area (vs transparent). + display.drawBitmap(max_w + PADDING, i * FreeSansBold18pt7b.yAdvance + + y_offset, canvas1.getBuffer(), canvas1.width(), + canvas1.height(), color[i], 0); + } + } + } + + // Because canvas object was declared locally to this function, it's freed + // automatically when the function returns; no explicit delete needed. +} // END CANVAS EXAMPLE diff --git a/PiCowbell_HSTX_DVI_Examples/Helvetica-Bold-16.pcf b/PiCowbell_HSTX_DVI_Examples/circuitpython/Helvetica-Bold-16.pcf similarity index 100% rename from PiCowbell_HSTX_DVI_Examples/Helvetica-Bold-16.pcf rename to PiCowbell_HSTX_DVI_Examples/circuitpython/Helvetica-Bold-16.pcf diff --git a/PiCowbell_HSTX_DVI_Examples/blinka_computer.bmp b/PiCowbell_HSTX_DVI_Examples/circuitpython/blinka_computer.bmp similarity index 100% rename from PiCowbell_HSTX_DVI_Examples/blinka_computer.bmp rename to PiCowbell_HSTX_DVI_Examples/circuitpython/blinka_computer.bmp diff --git a/PiCowbell_HSTX_DVI_Examples/code.py b/PiCowbell_HSTX_DVI_Examples/circuitpython/code.py similarity index 100% rename from PiCowbell_HSTX_DVI_Examples/code.py rename to PiCowbell_HSTX_DVI_Examples/circuitpython/code.py diff --git a/PiCowbell_HSTX_DVI_Examples/setup_display.py b/PiCowbell_HSTX_DVI_Examples/circuitpython/setup_display.py similarity index 100% rename from PiCowbell_HSTX_DVI_Examples/setup_display.py rename to PiCowbell_HSTX_DVI_Examples/circuitpython/setup_display.py