Compare commits

...

86 commits

Author SHA1 Message Date
Tyeth Gundry
93c013dde4
Update library.properties - bump to 1.3.2 2025-07-22 15:32:33 +01:00
foamyguy
ef98363996
Merge pull request #17 from FoamyGuy/hstx_dvi_cowbell
adding pin cfg for hstx dvi cowbell
2025-07-17 15:58:07 -05:00
foamyguy
476845da90 adding pin cfg for hstx dvi cowbell 2025-07-17 09:03:26 -05:00
Liz
72f5f044d8
bump for release 2025-06-23 11:02:08 -04:00
Limor "Ladyada" Fried
a8248beef2
Merge pull request #16 from adafruit/rp2040_4-6_fix
update for philhower 4.6
2025-06-23 10:55:20 -04:00
Liz
004d92849c update for philhowever 4.6 2025-06-23 08:45:25 -04:00
Tyeth Gundry
1b05077f39
Update library.properties - bump version to 1.3.0 2025-02-12 00:07:03 +00:00
Liz
a9edaddc25
Merge pull request #13 from adafruit/Wren6991-master2
Update & add working RP2350 def for fruitjam
2025-02-11 07:08:45 -05:00
ladyada
b6791acb11 add fruitjam 2025-02-10 22:03:20 -05:00
ladyada
ad85aaddbc Merge branch 'master' of https://github.com/Wren6991/PicoDVI into Wren6991-master2 2025-02-10 21:41:33 -05:00
Peter Recktenwald
7af20b2742 Add pin definitions for Olimex RP2040pc, RP2040-PICO-PC, Neo6502 Neo6502pc 2025-02-07 09:35:46 +00:00
Liz
6b93a88ecc
Merge pull request #9 from tannewt/fix_sdk2
Work with pico-sdk 2
2024-10-08 16:17:44 -04:00
Scott Shawcroft
47b99f0772
Work with pico-sdk 2
This is used in the philhower Arduino core version 4+.

Fixes #8
2024-10-08 13:04:42 -07:00
Phillip Burgess
d3945b6339 Add test.only file for file_access example (tinyusb) 2023-06-20 19:18:17 -07:00
Phillip Burgess
07e3b62582 Avert some warnings 2023-06-20 19:14:11 -07:00
Phillip Burgess
297908dd27 file_access example: add begin() arg for CPFS 1.1.0 2023-06-20 19:05:23 -07:00
Phillip Burgess
40fdf7a97f Adafruit_CPFS compatibility (example included) 2023-06-20 17:27:10 -07:00
Eva Herrada
971b104f04
Bumped to 1.0.7 2023-05-30 15:20:11 -04:00
Paint Your Dragon
78eea0b5c1
Merge pull request #4 from jepler/800x480p30
Add 800x480p30 mode
2023-05-30 08:46:18 -07:00
e0470b1960
Add 800x480p30 mode
this requires less OC than 800x480p60 and is verified to work with
https://www.adafruit.com/product/2232
2023-05-26 10:32:58 -05:00
dherrada
b96cd39e44 Update CI action versions 2023-05-15 10:36:56 -04:00
Phillip Burgess
6973daf22e Update tvhost example 2023-05-12 15:42:10 -07:00
Phillip Burgess
a5b837d006 Add boing example 2023-05-10 17:24:03 -07:00
Paint Your Dragon
68ddfe4f73
Merge pull request #3 from adafruit/pb-screensavers
Add four “screensaver” examples
2023-04-17 22:53:31 -07:00
Phillip Burgess
a136f53add Bump version # for new examples 2023-04-17 22:18:23 -07:00
Phillip Burgess
55d7fce972 Add 4 more examples in "screensavers" subdir 2023-04-17 22:17:59 -07:00
lady ada
53dd278b72 new bell 2023-04-01 22:58:04 -04:00
Eva Herrada
37d4f465b1
Bumped to 1.0.2 2023-03-14 15:50:17 -04:00
Eva Herrada
f6bd5f62f4
Bump to 1.0.1 2023-03-14 15:49:59 -04:00
lady ada
93e110890e fix code to match comment 2023-03-11 14:13:56 -05:00
Phillip Burgess
bb7dc7c20d Remove soft link to libdvi (copy full directory instead)
For Arduino Library Manager compliance
2023-03-09 15:00:54 -08:00
Phillip Burgess
506fca674a Update examples for Feather DVI pinout, fix comments 2023-03-09 14:18:33 -08:00
Phillip Burgess
8fb694adaf Mostly Doxygened 2023-03-09 12:27:02 -08:00
Phillip Burgess
5c47c170cd Update .gitignore 2023-03-08 17:09:54 -08:00
Phillip Burgess
339644e2da Update githubci.yml 2023-03-08 16:57:01 -08:00
Phillip Burgess
e6928c641b Update githubci.yml 2023-03-08 16:56:21 -08:00
Phillip Burgess
dc6a02c82d Update githubci.yml 2023-03-08 16:54:34 -08:00
Phillip Burgess
d72266e833 Disable clang-format part of CI because most code is from elsewhere 2023-03-08 16:53:52 -08:00
Phillip Burgess
44028a4645 Add Github workflows 2023-03-08 16:49:49 -08:00
Phillip Burgess
63b6b70b7c Add Adafruit Feather DVI to pin configs 2023-03-08 16:41:06 -08:00
Phillip Burgess
b0e9e5af8b Some notes on text mode 2023-02-22 13:35:36 -08:00
Phillip Burgess
74e7dae8d0 Rename DVIterm1 class to DVItext1, because "term" Implies a Lot 2023-02-15 08:10:56 -08:00
Phillip Burgess
1acf442ffa Remove separate 8x2 class; double-buffered is a constructor arg 2023-02-15 08:05:07 -08:00
Phillip Burgess
e36890e453 Terminal: vert scroll fix 2023-02-14 14:12:50 -08:00
Phillip Burgess
a329cc7f1a Seems to address startup hang issue? 2023-02-14 13:53:26 -08:00
Phillip Burgess
8c338e42c1 Fix comment 2023-02-14 08:33:47 -08:00
Phillip Burgess
24657de3e0 clang-format 2023-02-13 13:58:08 -08:00
Phillip Burgess
18d5152faf Move wait_begin into PicoDVI class so subclasses in other source files can access 2023-02-13 13:57:35 -08:00
Phillip Burgess
648d90c8fc Replace font with canonical IBM VGA 8x8 CP437 2023-02-13 12:14:03 -08:00
Phillip Burgess
f2d0455f65 Add skinny pixel modes, ability to print() to DVIterm1 2023-02-13 11:16:24 -08:00
Phillip Burgess
361c988dd9 Simplify resolution spec and switch some constructor args around 2023-02-13 09:53:35 -08:00
Phillip Burgess
8d0223bfff Some text progress 2023-02-11 19:23:56 -08:00
Phillip Burgess
7e67e8b175 Minor text work 2023-02-10 19:42:28 -08:00
Phillip Burgess
95fc12845d Text mode stuff WIP 2023-02-10 16:49:48 -08:00
Phillip Burgess
b381918c80 Add comments in examples 2023-01-18 16:00:18 -08:00
Phillip Burgess
e2f30c125c Mono double-buffered working 2023-01-18 14:57:17 -08:00
Phillip Burgess
0e1065c066 Simplify virtual_spitft a bit 2023-01-18 13:40:17 -08:00
Phillip Burgess
8c38416baa virtual_spitft example: some cleanup 2023-01-18 12:25:14 -08:00
Phillip Burgess
6a3b85828f virtual_spitft: handle rotation, malformed requests 2023-01-18 11:54:08 -08:00
Phillip Burgess
dad3d14664 Quick port of the spitft2hdmi code into Arduino 2023-01-17 14:22:18 -08:00
Phillip Burgess
9d0e8acc40 Fix 1-bit examples built w/Pico SDK 2023-01-17 13:01:08 -08:00
Phillip Burgess
86fd338d7c 640x480 and 800x480 monochrome now working
Required diverging libdvi a little further from the original, sorry
2023-01-17 09:42:09 -08:00
Phillip Burgess
e85c2d39b0 1bpp WIP 2023-01-16 19:04:34 -08:00
Phillip Burgess
a59149edbe 1bpp WIP 2023-01-16 18:43:36 -08:00
Phillip Burgess
7ae84fc393 1bpp WIP 2023-01-16 17:11:51 -08:00
Phillip Burgess
c192ac459f Add double-buffered 8-bit framebuf (requires GFX 1.11.4) 2023-01-16 16:00:14 -08:00
Phillip Burgess
e12f1a3d91 DVIGFX8 cleanup 2023-01-15 14:05:10 -08:00
Phillip Burgess
ca42c96c63 Simpler dealing with the framebuf size 2023-01-15 11:59:49 -08:00
Phillip Burgess
9ec735f6b2 8bpp WIP 2023-01-15 10:16:15 -08:00
Phillip Burgess
4f68592b8a Can remove AVR mentions from example (bc RP2040 only) 2023-01-13 15:20:59 -08:00
Phillip Burgess
97d2f362ce Various Readme & properties updates, add virtual_spitft boilerplate 2023-01-13 11:01:42 -08:00
Phillip Burgess
66560ef717 Decouple PicoDVI class allowing use as base for more framebuffer types 2023-01-12 20:42:08 -08:00
Phillip Burgess
2c2cc4859c More Readme notes 2023-01-12 12:15:54 -08:00
Phillip Burgess
6ee060c0d0 README notes 2023-01-12 11:57:53 -08:00
Phillip Burgess
d07537c3b8 400x240 working (needs higher VREG voltage) 2023-01-12 11:12:48 -08:00
Phillip Burgess
c6be564f6d Update library.properties 2023-01-12 11:04:34 -08:00
Phillip Burgess
a66d598337 Tweaklets 2023-01-12 10:50:21 -08:00
Phillip Burgess
49efd6ff02 proto 2023-01-11 18:22:59 -08:00
Phillip Burgess
5b07f7c0d5 Some cleanup, clang-format 2023-01-11 17:56:53 -08:00
Phillip Burgess
2913b73d37 Simpler org, soft link libdvi folder instead of each file 2023-01-11 17:08:19 -08:00
Phillip Burgess
da60f46c79 Starting to work as a GFX subclass, still WIP 2023-01-11 14:38:30 -08:00
Phillip Burgess
6403dc2239 Starting to work 2023-01-11 13:47:37 -08:00
Phillip Burgess
44fa0c45ec More WIP, trying different approach (soft links rather than include .c's) 2023-01-11 13:26:38 -08:00
Phillip Burgess
b28c878c9b Moar wip 2023-01-11 12:05:09 -08:00
Phillip Burgess
a434bf4cdd Moar WIP 2023-01-11 11:17:28 -08:00
Phillip Burgess
fba38ea47a Initial WIP 2023-01-11 10:52:46 -08:00
61 changed files with 124441 additions and 18 deletions

46
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,46 @@
Thank you for opening an issue on an Adafruit Arduino library repository. To
improve the speed of resolution please review the following guidelines and
common troubleshooting steps below before creating the issue:
- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use
the forums at http://forums.adafruit.com to ask questions and troubleshoot why
something isn't working as expected. In many cases the problem is a common issue
that you will more quickly receive help from the forum community. GitHub issues
are meant for known defects in the code. If you don't know if there is a defect
in the code then start with troubleshooting on the forum first.
- **If following a tutorial or guide be sure you didn't miss a step.** Carefully
check all of the steps and commands to run have been followed. Consult the
forum if you're unsure or have questions about steps in a guide/tutorial.
- **For Arduino projects check these very common issues to ensure they don't apply**:
- For uploading sketches or communicating with the board make sure you're using
a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes
very hard to tell the difference between a data and charge cable! Try using the
cable with other devices or swapping to another cable to confirm it is not
the problem.
- **Be sure you are supplying adequate power to the board.** Check the specs of
your board and plug in an external power supply. In many cases just
plugging a board into your computer is not enough to power it and other
peripherals.
- **Double check all soldering joints and connections.** Flakey connections
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.
- **Ensure you are using an official Arduino or Adafruit board.** We can't
guarantee a clone board will have the same functionality and work as expected
with this code and don't support them.
If you're sure this issue is a defect in the code and checked the steps above
please fill in the following fields to provide enough troubleshooting information.
You may delete the guideline and text above to just leave the following details:
- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE**
- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO
VERSION HERE**
- List the steps to reproduce the problem below (if possible attach a sketch or
copy the sketch code in too): **LIST REPRO STEPS BELOW**

26
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,26 @@
Thank you for creating a pull request to contribute to Adafruit's GitHub code!
Before you open the request please review the following guidelines and tips to
help it be more easily integrated:
- **Describe the scope of your change--i.e. what the change does and what parts
of the code were modified.** This will help us understand any risks of integrating
the code.
- **Describe any known limitations with your change.** For example if the change
doesn't apply to a supported platform of the library please mention it.
- **Please run any tests or examples that can exercise your modified code.** We
strive to not break users of the code and running tests/examples helps with this
process.
Thank you again for contributing! We will try to test and integrate the change
as soon as we can, but be aware we have many GitHub repositories to manage and
can't immediately respond to every request. There is no need to bump or check in
on a pull request (it will clutter the discussion of the request).
Also don't be worried if the request is closed or not integrated--sometimes the
priorities of Adafruit's GitHub code (education, ease of use) might not match the
priorities of the pull request. Don't fret, the open source community thrives on
forks and GitHub makes it easy to keep your changes in a forked repo.
After reviewing the guidelines above you can delete this text from the pull request.

32
.github/workflows/githubci.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: Arduino Library CI
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- uses: actions/checkout@v3
- uses: actions/checkout@v3
with:
repository: adafruit/ci-arduino
path: ci
- name: pre-install
run: bash ci/actions_install.sh
- name: test platforms
run: python3 ci/build_platform.py pico_rp2040
# - name: clang
# run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r .
- name: doxygen
env:
GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }}
PRETTYNAME : "PicoDVI - Adafruit Fork"
run: bash ci/doxy_gen_and_deploy.sh

3
.gitignore vendored
View file

@ -1,2 +1,5 @@
Doxyfile*
doxygen_sqlite3.db
html
.vscode
build

View file

@ -1,3 +1,45 @@
PicoDVI - Adafruit Fork for Arduino IDE + Adafruit_GFX compatibility
====================================================================
(Original Readme content follows later)
Implements a few framebuffer types to which Adafruit_GFX drawing operations
can be made; not every permutation PicoDVI is capable of, but a useful subset.
WARNING: all video modes require overclocking (performed automatically at
run-time, nothing to select), occasionally over-volting (optionally selected
in sketch code), and higher resolutions may require reducing the QSPI clock
rate (Tools menu in future arduino-pico release). The RP2040 microcontroller
is being run WAY beyond spec and there is a VERY SMALL BUT NONZERO
POSSIBILITY OF PERMANENT DAMAGE. Please see LICENSE file; usual software
disclaimers about liability apply, user accepts risk.
Requires Earle Philhower III RP2040 Arduino core (not the "official" Arduino
RP2040 core).
Changes vs main PicoDVI repo:
- Add library.properties file, src and examples directories per Arduino
requirements.
- A full copy of software/libdvi is made in src (originally was soft-linked but Arduino Library Manager does not approve). If any updates are made in the original PicoDVI libdvi directory, copy them here!
- The file dvi_serialiser.pio.h, normally not part of the distribution and
generated during the Pico SDK build process, is provided here for Arduino
build to work. If any changes are made in dvi_serialiser.pio (either here
or by bringing in updates from upstream code), this header will need to be
regenerated.
- extern "C" around most function defs, to be linkable with C++ (Arduino).
- A couple compile-time constants have been changed to run-time configurable
because some color and resolution things aren't known until a constructor is
called: dvi_vertical_repeat and dvi_monochrome_tmds.
- DVI_1BPP_BIT_REVERSE is switched '1' by default (to work with bit order
used by GFXcanvas1 object). Pico SDK-built examples using 1-bit mode are
fixed by changing the corresponding CMakeLists.txt files to set this to 0.
Font data has had bit order reversed to match this layout.
All files from the PicoDVI repo are kept even if not used in this build
(e.g. apps and assets directories, among others) so same repo can still be
used via Pico SDK if desired.
Original Readme content:
=======
RP2350 PicoDVI Preview
======================

View file

@ -0,0 +1,619 @@
// 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 <PicoDVI.h> // Core display & graphics library
#include <Fonts/FreeSansBold18pt7b.h> // 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 -- examples are written for Adafruit Feather RP2040 DVI, 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_feather_dvi_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<display.width(); x++) { // Each column of screen...
// Note the inverted Y axis here (cy-value rather than cy+value)
// because GFX, like most graphics libraries, has +Y heading down,
// vs. classic Cartesian coords which have +Y heading up.
int16_t y = cy - (int16_t)(sin((x - cx) * 0.05) * (float)minor * 0.5);
display.drawPixel(x, y, 0xFFFF);
}
delay(PAUSE);
// Next, let's draw some charts...
// NOTE: some other examples in this code take extra steps to avoid placing
// anything off in the rounded corners of certain displays. The charts do
// not. It's *possible* but would introduce a lot of complexity into code
// that's trying to show the basics. We'll leave the clipped charts here as
// a teachable moment: not all content suits all displays.
// A list of data to plot. These are Y values only; X assumed equidistant.
const uint8_t data[] = { 31, 42, 36, 58, 67, 88 }; // Percentages, 0-100
const uint8_t num_points = sizeof data / sizeof data[0]; // Length of data[] list
display.fillScreen(0); // Clear screen
display.setFont(); // Use default (built-in) font
display.setTextSize(2); // and 2X size for chart label
// Chart label is centered manually; 144 is the width in pixels of
// "Widget Sales" at 2X scale (12 chars * 6 px * 2 = 144). A later example
// shows automated centering based on string.
display.setCursor((display.width() - 144) / 2, 0);
display.print(F("Widget Sales")); // F("string") is in program memory, not RAM
// The chart-drawing code is then written to skip the top 20 rows where
// this label is located.
// First, a line chart, connecting the values point-to-point:
// Draw a grid of lines to provide scale & an interesting background.
for (uint8_t i=0; i<11; i++) {
int16_t x = map(i, 0, 10, 0, display.width() - 1); // Scale grid X to screen
display.drawFastVLine(x, 20, display.height(), 0x001F);
int16_t y = map(i, 0, 10, 20, display.height() - 1); // Scale grid Y to screen
display.drawFastHLine(0, y, display.width(), 0x001F);
}
// And then draw lines connecting data points. Load up the first point...
int16_t prev_x = 0;
int16_t prev_y = map(data[0], 0, 100, display.height() - 1, 20);
// Then connect lines to each subsequent point...
for (uint8_t i=1; i<num_points; i++) {
int16_t new_x = map(i, 0, num_points - 1, 0, display.width() - 1);
int16_t new_y = map(data[i], 0, 100, display.height() - 1, 20);
display.drawLine(prev_x, prev_y, new_x, new_y, 0x07FF);
prev_x = new_x;
prev_y = new_y;
}
// For visual interest, let's add a circle around each data point. This is
// done in a second pass so the circles are always drawn "on top" of lines.
for (uint8_t i=0; i<num_points; i++) {
int16_t x = map(i, 0, num_points - 1, 0, display.width() - 1);
int16_t y = map(data[i], 0, 100, display.height() - 1, 20);
display.drawCircle(x, y, 5, 0xFFFF);
}
delay(PAUSE);
// Then a bar chart of the same data...
// Erase the old chart but keep the label at top.
display.fillRect(0, 20, display.width(), display.height() - 20, 0);
// Just draw the Y axis lines; bar chart doesn't really need X lines.
for (uint8_t i=0; i<11; i++) {
int16_t y = map(i, 0, 10, 20, display.height() - 1);
display.drawFastHLine(0, y, display.width(), 0x001F);
}
int bar_width = display.width() / num_points - 4; // 2px pad to either side
for (uint8_t i=0; i<num_points; i++) {
int16_t x = map(i, 0, num_points, 0, display.width()) + 2; // Left edge of bar
int16_t height = map(data[i], 0, 100, 0, display.height() - 20);
// Some GFX functions (rects, H/V lines and similar) can accept negative
// width/height values. What this does is anchor the shape at the right or
// bottom coordinate (rather than the usual left/top) and draw back from
// there, hence the -height here (bar is anchored at bottom of screen):
display.fillRect(x, display.height() - 1, bar_width, -height, 0xFFE0);
}
delay(PAUSE);
} // END CHART EXAMPLES
// TEXT ALIGN FUNCTIONS ----------------------------------------------------
// Adafruit_GFX only handles left-aligned text. This is normal and by design;
// it's a rare need that would further strain AVR by incurring a ton of extra
// code to properly handle, and some details would confuse. If needed, these
// functions give a fair approximation, with the "gotchas" that multi-line
// input won't work, and this operates only as a println(), not print()
// (though, unlike println(), cursor X does not reset to column 0, instead
// returning to initial column and downward by font's line spacing). If you
// can work with those constraints, it's a modest amount of code to copy
// into a project. Or, if your project only needs one or two aligned strings,
// simply use getTextBounds() for a bounding box and work from there.
// DO NOT ATTEMPT TO MAKE THIS A GFX-NATIVE FEATURE, EVERYTHING WILL BREAK.
typedef enum { // Alignment options passed to functions below
GFX_ALIGN_LEFT,
GFX_ALIGN_CENTER,
GFX_ALIGN_RIGHT
} GFXalign;
// Draw text aligned relative to current cursor position. Arguments:
// gfx : An Adafruit_GFX-derived type (e.g. display or canvas object).
// str : String to print (as a char *).
// align : One of the GFXalign values declared above.
// GFX_ALIGN_LEFT is normal left-aligned println() behavior.
// GFX_ALIGN_CENTER prints centered on cursor pos.
// GFX_ALIGN_RIGHT prints right-aligned to cursor pos.
// Cursor advances down one line a la println(). Column is unchanged.
void print_aligned(Adafruit_GFX &gfx, const char *str,
GFXalign align = GFX_ALIGN_LEFT) {
uint16_t w, h;
int16_t x, y, cursor_x, cursor_x_save;
cursor_x = cursor_x_save = gfx.getCursorX();
gfx.getTextBounds(str, 0, gfx.getCursorY(), &x, &y, &w, &h);
if (align == GFX_ALIGN_RIGHT) cursor_x -= w;
else if (align == GFX_ALIGN_CENTER) cursor_x -= w / 2;
//gfx.drawRect(cursor_x, y, w, h, 0xF800); // Debug rect
gfx.setCursor(cursor_x - x, gfx.getCursorY()); // Center/right align
gfx.println(str);
gfx.setCursor(cursor_x_save, gfx.getCursorY()); // Restore cursor X
}
// Equivalent function for strings in flash memory (e.g. F("Foo")). Body
// appears identical to above function, but with C++ overloading it it works
// from flash instead of RAM. Any changes should be made in both places.
void print_aligned(Adafruit_GFX &gfx, const __FlashStringHelper *str,
GFXalign align = GFX_ALIGN_LEFT) {
uint16_t w, h;
int16_t x, y, cursor_x, cursor_x_save;
cursor_x = cursor_x_save = gfx.getCursorX();
gfx.getTextBounds(str, 0, gfx.getCursorY(), &x, &y, &w, &h);
if (align == GFX_ALIGN_RIGHT) cursor_x -= w;
else if (align == GFX_ALIGN_CENTER) cursor_x -= w / 2;
//gfx.drawRect(cursor_x, y, w, h, 0xF800); // Debug rect
gfx.setCursor(cursor_x - x, gfx.getCursorY()); // Center/right align
gfx.println(str);
gfx.setCursor(cursor_x_save, gfx.getCursorY()); // Restore cursor X
}
// Equivalent function for Arduino Strings; converts to C string (char *)
// and calls corresponding print_aligned() implementation.
void print_aligned(Adafruit_GFX &gfx, const String &str,
GFXalign align = GFX_ALIGN_LEFT) {
print_aligned(gfx, const_cast<char *>(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

View file

@ -0,0 +1,58 @@
// Double-buffered 1-bit Adafruit_GFX-compatible framebuffer for PicoDVI.
// Animates without redraw flicker. Requires Adafruit_GFX >= 1.11.5
#include <PicoDVI.h>
// Here's how a 640x480 1-bit (black, white) framebuffer is declared.
// Second argument ('true' here) enables double-buffering for flicker-free
// animation. Third argument is a hardware configuration -- examples are
// written for Adafruit Feather RP2040 DVI, 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').
DVIGFX1 display(DVI_RES_640x480p60, true, adafruit_feather_dvi_cfg);
// An 800x480 mode is possible but pushes overclocking even higher than
// 640x480 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.
//DVIGFX1 display(DVI_RES_800x480p60, true, adafruit_feather_dvi_cfg);
#define N_BALLS 100 // Number of bouncy balls to draw
struct {
int16_t pos[2]; // Ball position (X,Y)
int8_t vel[2]; // Ball velocity (X,Y)
} ball[N_BALLS];
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);
}
// Randomize initial ball positions and velocities
for (int i=0; i<N_BALLS; i++) {
ball[i].pos[0] = 10 + random(display.width() - 20);
ball[i].pos[1] = 10 + random(display.height() - 20);
do {
ball[i].vel[0] = 4 - random(9);
ball[i].vel[1] = 4 - random(9);
} while ((ball[i].vel[0] == 0) && (ball[i].vel[1] == 0));
}
}
void loop() {
display.fillScreen(0); // Clear back framebuffer...
// And draw bouncy balls (circles) there
for (int i=0; i<N_BALLS; i++) {
display.drawCircle(ball[i].pos[0], ball[i].pos[1], 40, 1);
// After drawing each one, update positions, bounce off edges.
ball[i].pos[0] += ball[i].vel[0];
if ((ball[i].pos[0] <= 0) || (ball[i].pos[0] >= display.width())) ball[i].vel[0] *= -1;
ball[i].pos[1] += ball[i].vel[1];
if ((ball[i].pos[1] <= 0) || (ball[i].pos[1] >= display.height())) ball[i].vel[1] *= -1;
}
// Swap front/back buffers, do not duplicate current screen state to next frame,
// we'll draw it new from scratch each time.
display.swap();
}

View file

@ -0,0 +1,31 @@
// Simple 1-bit Adafruit_GFX-compatible framebuffer for PicoDVI.
#include <PicoDVI.h>
// Here's how a 640x480 1-bit (black, white) framebuffer is declared.
// Second argument ('false' here) means NO double-buffering; all drawing
// operations are shown as they occur. Third argument is a hardware
// configuration -- examples are written for Adafruit Feather RP2040 DVI,
// 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').
DVIGFX1 display(DVI_RES_640x480p60, false, adafruit_feather_dvi_cfg);
// An 800x480 mode is possible but pushes overclocking even higher than
// 640x480 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.
//DVIGFX1 display(DVI_RES_800x480p60, false, 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);
}
}
void loop() {
// Draw random lines
display.drawLine(random(display.width()), random(display.height()), // Start X,Y
random(display.width()), random(display.height()), // End X,Y
random(2)); // Color (0 or 1)
}

View file

@ -0,0 +1,35 @@
// 1-bit (black, white) text mode for PicoDVI.
#include <PicoDVI.h>
// Here's how an 80x30 character display is declared. First argument,
// resolution, is full display pixel count...character cells are 8x8 pixels,
// yielding the 80x30 result. 640x240 uses "tall" pixels, the result of all
// this is very reminiscent of IBM VGA mode. Second argument is a hardware
// configuration -- examples are written for Adafruit Feather RP2040 DVI,
// 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').
DVItext1 display(DVI_RES_640x240p60, adafruit_feather_dvi_cfg);
// Wider and taller modes are possible. Wider pushes overclocking even
// higher than 640x480 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 over-volting the CPU to 1.25 or 1.3 V.
// Here's how a 100x60 char display might be declared:
//DVItext1 display(DVI_RES_800x480p60, adafruit_feather_dvi_cfg);
// A reduced refresh rate display doesn't as aggressive an over-clock
// This timing is verified to work on https://www.adafruit.com/product/2232
//DVItext1 display(DVI_RES_800x240p30, 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);
}
}
void loop() {
display.print("Hello World! ");
delay(50);
}

View file

@ -0,0 +1,61 @@
// Double-buffered 8-bit Adafruit_GFX-compatible framebuffer for PicoDVI.
// Animates without redraw flicker. Requires Adafruit_GFX >= 1.11.4
#include <PicoDVI.h>
// Here's how a 320x240 8-bit (color-paletted) framebuffer is declared.
// Second argument ('true' here) enables double-buffering for flicker-free
// animation. Third argument is a hardware configuration -- examples are
// written for Adafruit Feather RP2040 DVI, 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').
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_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.
//DVIGFX8 display(DVI_RES_400x240p60, true, adafruit_feather_dvi_cfg);
#define N_BALLS 100 // Number of bouncy balls to draw, 1-254 (not 255)
struct {
int16_t pos[2]; // Ball position (X,Y)
int8_t vel[2]; // Ball velocity (X,Y)
} ball[N_BALLS];
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);
}
// Randomize initial ball positions, velocities and colors
for (int i=0; i<N_BALLS; i++) {
display.setColor(i+1, 64 + random(192), 64 + random(192), 64 + random(192));
ball[i].pos[0] = 10 + random(display.width() - 20);
ball[i].pos[1] = 10 + random(display.height() - 20);
do {
ball[i].vel[0] = 2 - random(5);
ball[i].vel[1] = 2 - random(5);
} while ((ball[i].vel[0] == 0) && (ball[i].vel[1] == 0));
}
display.setColor(255, 0xFFFF); // Last palette entry = white
display.swap(false, true); // Duplicate same palette into front & back buffers
}
void loop() {
// Clear back framebuffer and draw balls (circles) there.
display.fillScreen(0);
for (int i=0; i<N_BALLS; i++) {
display.fillCircle(ball[i].pos[0], ball[i].pos[1], 20, i + 1);
// After drawing each one, update positions, bounce off edges.
ball[i].pos[0] += ball[i].vel[0];
if ((ball[i].pos[0] <= 0) || (ball[i].pos[0] >= display.width())) ball[i].vel[0] *= -1;
ball[i].pos[1] += ball[i].vel[1];
if ((ball[i].pos[1] <= 0) || (ball[i].pos[1] >= display.height())) ball[i].vel[1] *= -1;
}
// Swap front/back buffers, do not duplicate current screen state to next frame,
// we'll draw it new from scratch each time.
display.swap();
}

View file

@ -0,0 +1,33 @@
// 8-bit Adafruit_GFX-compatible framebuffer for PicoDVI.
#include <PicoDVI.h>
// Here's how a 320x240 8-bit (color-paletted) framebuffer is declared.
// Second argument ('false' here) means NO double-buffering; all drawing
// operations are shown as they occur. Third argument is a hardware
// configuration -- examples are written for Adafruit Feather RP2040 DVI,
// 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').
DVIGFX8 display(DVI_RES_320x240p60, false, adafruit_feather_dvi_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.
//DVIGFX8 display(DVI_RES_400x240p60, false, 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);
}
// Randomize color palette. First entry is left black, last is set white.
for (int i=1; i<255; i++) display.setColor(i, random(65536));
display.setColor(255, 0xFFFF);
}
void loop() {
// Draw random lines
display.drawLine(random(display.width()), random(display.height()), random(display.width()), random(display.height()), random(256));
}

5
examples/Readme.md Normal file
View file

@ -0,0 +1,5 @@
Arduino Examples for PicoDVI Library
====================================
This directory does not exist in the original PicoDVI repository.
It contains examples compatible with the Arduino IDE.

View file

@ -0,0 +1,83 @@
/*
This example shows how to use PicoDVI and a board's flash filesystem (i.e.
CIRCUITPY drive) simultaneously. This can be useful for loading graphics
files, saving game state in emulators, etc. To keep this simple, it's just
a mash-up of the existing PicoDVI '1bit_text' and Adafruit_CPFS 'simple'
examples, but the same principles certainly apply to other modes.
To start, you first need to temporarily install CircuitPython on the board
to initialize the flash filesystem, which can then be loaded up with the
files you need. https://circuitpython.org/downloads
With the filesystem prepared, one can then work with the Arduino code...
SUPER IMPORTANT: you MUST have current versions of several libraries and
the Earle Philhower arduino-pico core installed. Failure to have all the
right pieces will wipe out any data stored on the drive!
arduino-pico (via Arduino board manager) 3.3.0 or later
Adafruit_CPFS (via Library manager) 1.1.0 or later
Adafruit_SPIFlash (") 4.2.0 or later
It is wise and STRONGLY RECOMMENDED to keep a backup of any data you install
on the board. These libraries combined are asking a LOT of the RP2040 chip,
and despite best efforts there's still the occasional hiccup that can wipe
the filesystem, making you start over with the CircuitPython install and
drive setup. See notes below about perhaps adding a boot switch to USB-mount
CIRCUITPY only when needed; it's more stable if left unmounted.
*/
#include <PicoDVI.h> // For DVI video out
#include <Adafruit_CPFS.h> // For accessing the CIRCUITPY drive
FatVolume *fs = NULL; // CIRCUITPY flash filesystem, as a FAT pointer
// This example uses 80x30 monochrome text mode. See other PicoDVI examples
// for color, bitmapped graphics, widescreen, alternate boards, etc.
DVItext1 display(DVI_RES_640x240p60, adafruit_feather_dvi_cfg);
void setup() { // Runs once on startup
pinMode(LED_BUILTIN, OUTPUT);
// Start the CIRCUITPY flash filesystem. SUPER IMPORTANT: NOTICE THE
// EXTRA PARAMETERS HERE. This is REQUIRED when using PicoDVI and
// Adafruit_CPFS together.
fs = Adafruit_CPFS::begin(true, -1, NULL, false);
// The initial 'true' argument tells CPFS to make the flash filesystem
// available to a USB-connected host computer. Passing 'false' makes it
// only available to the Arduino sketch. Given the tenuous stability of
// handling so much at once (DVI, flash, USB), one might want to add a
// boot-time button or switch to select whether CIRCUITPY is mounted on
// host, or is just using USB for power.
// Next two arguments are ignored on RP2040; they're specifically for
// some 'Haxpress' dev boards with the CPFS library. Last argument should
// ALWAYS be set 'false' on RP2040, or there will be...trouble.
if (!display.begin()) { // Start DVI, slow blink LED if insufficient RAM
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
if (fs == NULL) { // If CIRCUITPY filesystem is missing or malformed...
// Show error message & fast blink LED to indicate problem. Full stop.
display.println("Can't access board's CIRCUITPY drive.");
display.println("Has CircuitPython been previously installed?");
for (;;) digitalWrite(LED_BUILTIN, (millis() / 250) & 1);
} // else valid CIRCUITPY drive, proceed...
// As in Adafruit_CPFS 'simple' example, allow USB events to settle...
delay(2500);
Adafruit_CPFS::change_ack();
// Then access files and directories using any SdFat calls (open(), etc.)
// Because fs is a pointer, we use "->" indirection rather than "." access.
// display pointer is cast to print_t so ls() treats it just like Serial.
fs->ls((print_t *)&display, LS_R | LS_SIZE); // List initial drive contents
}
void loop() {
if (Adafruit_CPFS::changed()) { // Anything changed on CIRCUITPY drive?
Adafruit_CPFS::change_ack(); // Got it, thanks.
display.println("CIRCUITPY drive contents changed.");
fs->ls((print_t *)&display, LS_R | LS_SIZE); // List updated drive contents
}
}

View file

@ -0,0 +1,153 @@
// "Aquarium" example for PicoDVI library. If just starting out,
// see the 8bit_double_buffer which explains the PicoDVI groundwork.
// Comments in THIS file are mostly distinct & new concepts.
// The flying toasters example also goes into more detail.
// IF NO OUTPUT OR RED FLICKER SCANLINES: try Tools->Optimize->(-O3)
#include <PicoDVI.h>
#include "sprites.h" // Graphics data
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);
// See notes in 8bit_double_buffer regarding 400x240 mode.
//DVIGFX8 display(DVI_RES_400x240p60, true, adafruit_feather_dvi_cfg);
// Also requires -O3 setting.
// This structure holds pointers to sprite graphics and masks in sprites.h.
const struct {
const uint8_t *sprite[2][2]; // L/R directions and A/B frames
const uint8_t *mask[2][2]; // Same
} spritedata[] = {
// There are FOUR sprites/masks for each fish (and kelp):
// two left-facing, two right-facing, and A/B frames for each.
{ sprite0LA , sprite0LB , sprite0RA , sprite0RB , mask0LA , mask0LB , mask0RA , mask0RB },
{ sprite1LA , sprite1LB , sprite1RA , sprite1RB , mask1LA , mask1LB , mask1RA , mask1RB },
{ sprite2LA , sprite2LB , sprite2RA , sprite2RB , mask2LA , mask2LB , mask2RA , mask2RB },
{ sprite3LA , sprite3LB , sprite3RA , sprite3RB, mask3LA , mask3LB , mask3RA , mask3RB },
{ sprite4LA , sprite4LB , sprite4RA , sprite4RB , mask4LA , mask4LB , mask4RA , mask4RB },
{ sprite5LA , sprite5LB , sprite5RA , sprite5RB , mask5LA , mask5LB , mask5RA , mask5RB },
{ sprite6LA , sprite6LB , sprite6RA , sprite6RB , mask6LA , mask6LB , mask6RA , mask6RB },
{ sprite7LA , sprite7LB , sprite7RA , sprite7RB , mask7LA , mask7LB , mask7RA , mask7RB },
{ sprite8LA , sprite8LB , sprite8RA , sprite8RB , mask8LA , mask8LB , mask8RA , mask8RB },
{ sprite9LA , sprite9LB , sprite9RA , sprite9RB , mask9LA , mask9LB , mask9RA , mask9RB },
{ sprite10LA, sprite10LB, sprite10RA, sprite10RB, mask10LA, mask10LB, mask10RA, mask10RB },
{ sprite11LA, sprite11LB, sprite11RA, sprite11RB, mask11LA, mask11LB, mask11RA, mask11RB },
{ sprite12LA, sprite12LB, sprite12RA, sprite12RB, mask12LA, mask12LB, mask12RA, mask12RB },
{ sprite13LA, sprite13LB, sprite13RA, sprite13RB, mask13LA, mask13LB, mask13RA, mask13RB },
{ sprite14LA, sprite14LB, sprite14RA, sprite14RB, mask14LA, mask14LB, mask14RA, mask14RB },
{ sprite15LA, sprite15LB, sprite15RA, sprite15RB, mask15LA, mask15LB, mask15RA, mask15RB },
{ sprite16LA, sprite16LB, sprite16RA, sprite16RB, mask16LA, mask16LB, mask16RA, mask16RB },
{ sprite17LA, sprite17LB, sprite17RA, sprite17RB, mask17LA, mask17LB, mask17RA, mask17RB },
// Bubbles are a special case. No right/left versions, but A/B frames.
// To use same struct (not a special case), just duplicate each 2X.
{ sprite18A , sprite18B , sprite18A , sprite18B , mask18A , mask18B , mask18A , mask18B },
};
#define N_SPRITES 12 // MUST be >= 6
// This structure contains positions and other data for the sprites
// in motion (notice it's not "const", because contents change).
struct {
int16_t pos[2]; // sprite position (X,Y) * 16
int8_t speed; // sprite speed (-16 to -8 or +8 to +16)
uint8_t index; // which index (in spritedata) to use
uint8_t offset; // Timer offset to de-sync each sprite's animation
} sprite[N_SPRITES];
// Initialize one sprite (index passed to function) to a random offscreen
// position, also randomizing speed and sprite (fish) type.
void randomsprite(uint8_t i) {
// To move the sprites at slightly different speeds, coordinates are
// stored in 1/16 pixel units. When drawing, the stored values get
// divided by 16 to yield final pixel coordinates.
sprite[i].speed = random(8, 17); // 1/2 to 1 pixel per frame
if (random(2)) { // 50/50 random chance...
sprite[i].speed *= -1; // Fish moves right-to-left
sprite[i].pos[0] = (display.width() + random(64)) * 16; // Start off right edge
} else { // Fish moves left-to-right
sprite[i].pos[0] = random(64, 128) * -16; // Start off left edge
}
// WHEEL. OF. FISH. -2 here is to ignore last 2 sprites (kelp, bubbles)
sprite[i].index = random(sizeof spritedata / sizeof spritedata[0] - 2);
if (sprite[i].index == 8) { // Sprite #8 is crab, keep close to ground
sprite[i].pos[1] = random(display.height() - 96, display.height() - 64) * 16;
} else { // Is a fish, upper part of screen
sprite[i].pos[1] = random(display.height() - 96) * 16;
}
sprite[i].offset = random(256); // De-synchronize sprite animation
}
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);
}
// Initialize color palette from table in sprites.h. Rather than
// calling setColor() for each one, we can just dump it directly...
memcpy(display.getPalette(), palette, sizeof palette);
display.swap(false, true); // Duplicate same palette to front & back buffers
// Randomize initial sprite states
randomSeed(analogRead(A0)); // Seed randomness from unused analog in
int range = display.width() + 64;
for (int i=0; i<3; i++) { // FIRST THREE sprites...
sprite[i].index = 17; // Are always kelp
sprite[i].speed = random(2) ? 1 : -1; // 50/50 left/right flip
sprite[i].pos[0] = (random(range * i / 3, range * (i + 1) / 3 - 64) - 32) * 16;
sprite[i].pos[1] = random(display.height() - 120, display.height() - 100) * 16;
sprite[i].offset = random(256);
}
for (int i=3; i<6; i++) { // NEXT THREE sprites...
sprite[i].index = 18; // Are always bubbles
sprite[i].speed = 0;
sprite[i].pos[0] = display.width() * 16; // Start them all offscreen
sprite[i].pos[1] = random(display.height()) * 8;
sprite[i].offset = random(256);
}
for (int i=6; i<N_SPRITES; i++) randomsprite(i); // Rest are fish
}
uint8_t frame = 0; // Counter for animation
void loop() { // Runs once every frame
display.fillScreen(0); // Clear back framebuffer,
for (int x=0; x<display.width(); x += 192) { // Tile background sprite
// Although DVIGFX8 is a COLOR display type, we leverage GFX's
// drawGrayscaleBitmap() function to draw the sprites...it saves us
// writing a ton of code this way.
display.drawGrayscaleBitmap(x, display.height() - 64, gravel, 192, 64);
}
for (int i=0; i<N_SPRITES; i++) { // and then the rest of the sprites...
uint8_t dir = sprite[i].speed > 0; // Left/right
uint8_t fr = ((frame + sprite[i].offset) >> 4) & 1; // A/B frame
if (sprite[i].speed) { // FISH or KELP; 64x64 sprite
display.drawGrayscaleBitmap(sprite[i].pos[0] / 16, sprite[i].pos[1] / 16,
spritedata[sprite[i].index].sprite[dir][fr],
spritedata[sprite[i].index].mask[dir][fr], 64, 64);
if (abs(sprite[i].speed) > 1) { // Not kelp...
sprite[i].pos[0] += sprite[i].speed; // Update position, check if offscreen...
if (((sprite[i].speed > 0) && (sprite[i].pos[0] > (display.width() * 16))) ||
((sprite[i].speed < 0) && (sprite[i].pos[0] < -64 * 16)))
randomsprite(i); // Replace with a new fish
}
} else { // Is BUBBLES
display.drawGrayscaleBitmap(sprite[i].pos[0] / 16, sprite[i].pos[1] / 16,
spritedata[sprite[i].index].sprite[0][fr],
spritedata[sprite[i].index].mask[0][fr], 64, 16);
sprite[i].pos[1] -= 16; // Move up by 1 pixel
if (sprite[i].pos[1] < -256) { // Off top of screen?
int j = random(6, N_SPRITES); // Pick a random fish,
sprite[i].pos[0] = sprite[j].pos[0]; // and move bubbles there
sprite[i].pos[1] = sprite[j].pos[1] + 384;
}
}
}
// Swap front/back buffers, do not duplicate current screen state
// to next frame, we'll draw it new from scratch each time.
display.swap();
frame++; // Increment animation counter; "rolls over" 0-255 automatically.
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,121 @@
// "Boing" ball example for PicoDVI library, If just starting out,
// see the 8bit_double_buffer which explains the PicoDVI groundwork.
// Comments in THIS file are mostly distinct & new concepts.
// Adapted from PyPortal example in Adafruit_ILI9341 library.
#include <PicoDVI.h>
#include "graphics.h" // Graphics data
// 4:3 aspect for that Amiga flavour:
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);
#define YBOTTOM 123 // Ball Y coord at bottom
#define YBOUNCE -3.5 // Upward velocity on ball bounce
// Ball coordinates are stored floating-point because screen refresh
// is so quick, whole-pixel movements are just too fast!
float ballx = 20.0, bally = YBOTTOM, // Current ball position
ballvx = 0.8, ballvy = YBOUNCE, // Ball velocity
ballframe = 3; // Ball animation frame #
int balloldx = ballx, balloldy = bally; // Prior ball position
void setup() {
if (!display.begin()) { // Blink LED if insufficient RAM
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
// Set up color palette
display.setColor(0, 0xAD75); // #0 = Background color
display.setColor(1, 0xA815); // #1 = Grid color
display.setColor(2, 0x5285); // #2 = Background in shadow
display.setColor(3, 0x600C); // #3 = Grid in shadow
// Draw initial framebuffer contents (grid, no shadow):
display.drawBitmap(0, 0, (uint8_t *)background, 320, 240, 1, 0);
display.swap(true, true); // Duplicate same bg & palette into both buffers
}
void loop() {
balloldx = (int16_t)ballx; // Save prior position
balloldy = (int16_t)bally;
ballx += ballvx; // Update position
bally += ballvy;
ballvy += 0.06; // Update Y velocity
if((ballx <= 15) || (ballx >= display.width() - BALLWIDTH))
ballvx *= -1; // Left/right bounce
if(bally >= YBOTTOM) { // Hit ground?
bally = YBOTTOM; // Clip and
ballvy = YBOUNCE; // bounce up
}
// Determine screen area to update. This is the bounds of the ball's
// prior and current positions, so the old ball is fully erased and new
// ball is fully drawn.
int16_t minx, miny, maxx, maxy, width, height;
// Determine bounds of prior and new positions
minx = ballx;
if(balloldx < minx) minx = balloldx;
miny = bally;
if(balloldy < miny) miny = balloldy;
maxx = ballx + BALLWIDTH - 1;
if((balloldx + BALLWIDTH - 1) > maxx) maxx = balloldx + BALLWIDTH - 1;
maxy = bally + BALLHEIGHT - 1;
if((balloldy + BALLHEIGHT - 1) > maxy) maxy = balloldy + BALLHEIGHT - 1;
width = maxx - minx + 1;
height = maxy - miny + 1;
// Ball animation frame # is incremented opposite the ball's X velocity
ballframe -= ballvx * 0.5;
if(ballframe < 0) ballframe += 14; // Constrain from 0 to 13
else if(ballframe >= 14) ballframe -= 14;
// Set 7 palette entries to white, 7 to red, based on frame number.
// This makes the ball spin.
for(uint8_t i=0; i<14; i++) {
display.setColor(i + 4, ((((int)ballframe + i) % 14) < 7) ? 0xFFFF : 0xF800);
}
// Only the changed rectangle is drawn into the 'renderbuf' array...
uint8_t c, *destPtr;
int16_t bx = minx - (int)ballx, // X relative to ball bitmap (can be negative)
by = miny - (int)bally, // Y relative to ball bitmap (can be negative)
bgx = minx, // X relative to background bitmap (>= 0)
bgy = miny, // Y relative to background bitmap (>= 0)
x, y, bx1, bgx1; // Loop counters and working vars
uint8_t p; // 'packed' value of 2 ball pixels
int8_t bufIdx = 0;
uint8_t *buf = display.getBuffer(); // -> back buffer
for(y=0; y<height; y++) { // For each row...
destPtr = &buf[display.width() * (miny + y) + minx];
bx1 = bx; // Need to keep the original bx and bgx values,
bgx1 = bgx; // so copies of them are made here (and changed in loop below)
for(x=0; x<width; x++) {
if((bx1 >= 0) && (bx1 < BALLWIDTH) && // Is current pixel row/column
(by >= 0) && (by < BALLHEIGHT)) { // inside the ball bitmap area?
// Yes, do ball compositing math...
p = ball[by][bx1 / 2]; // Get packed value (2 pixels)
c = (bx1 & 1) ? (p & 0xF) : (p >> 4); // Unpack high or low nybble
if(c == 0) { // Outside ball - just draw grid
c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? 1 : 0;
} else if(c > 1) { // In ball area...
c += 2; // Convert to color index >= 4
} else { // In shadow area, draw shaded grid...
c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? 3 : 2;
}
} else { // Outside ball bitmap, just draw background bitmap...
c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? 1 : 0;
}
*destPtr++ = c; // Store pixel color
bx1++; // Increment bitmap position counters (X axis)
bgx1++;
}
by++; // Increment bitmap position counters (Y axis)
bgy++;
}
display.swap(true, false); // Show & copy current background buffer to next
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
// "Logo bounce" example for PicoDVI library. If just starting out,
// see the 1bit_double_buffer which explains the PicoDVI groundwork.
// Comments in THIS file are mostly distinct & new concepts.
// IF NO OUTPUT OR RED FLICKER SCANLINES: try Tools->Optimize->(-O3)
#include <PicoDVI.h>
#include "sprite.h" // Graphics data
DVIGFX1 display(DVI_RES_640x480p60, true, adafruit_feather_dvi_cfg);
// See notes in 1bit_double_buffer regarding 800x480 mode.
//DVIGFX1 display(DVI_RES_800x480p60, true, adafruit_feather_dvi_cfg);
// May also require -O3 setting.
int x = 0; // Start logo at
int y = 0; // top left corner,
int vx = 1; // moving right
int vy = 1; // and down
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);
}
}
void loop() { // Runs once every frame
display.fillScreen(0); // Clear back framebuffer, then draw sprite:
display.drawBitmap(x, y, sprite, SPRITE_WIDTH, SPRITE_HEIGHT, 1);
// Swap front/back buffers, do not duplicate current screen state
// to next frame, we'll draw it new from scratch each time.
display.swap();
// Update sprite position, bouncing off all 4 sides
x += vx; // Horizontal
if ((x == 0) || (x == (display.width() - SPRITE_WIDTH))) vx *= -1;
y += vy; // Vertical
if ((y == 0) || (y == (display.height() - SPRITE_HEIGHT))) vy *= -1;
}

View file

@ -0,0 +1,386 @@
// "DVI video" logo sprite. Generated from a 1-bit PNG using ImageMagick:
// magick sprite.png -define h:format=gray -depth 1 sprite.h
// (then edited a bit for this program)
// The sprite was intentionally planned with dimensions that are both
// prime numbers, and then starts from the upper-left corner. This should
// make the bounce SUPER annoying, taking hours or days to ever hit an
// exact corner again!
#define SPRITE_WIDTH 239
#define SPRITE_HEIGHT 149
static const uint8_t sprite[] = {
0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00,
0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x7F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xEF,
0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x80,
0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x9F,
0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xFF,
0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x1F,
0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xE0,
0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xE0, 0x00, 0x3F, 0xFF, 0xFF, 0xF8, 0x3F,
0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xE0,
0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x3F,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF, 0xF0,
0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xF0, 0x01, 0xFF, 0xFF, 0xFF, 0xC0, 0x7F,
0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xF8,
0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFF, 0xFF, 0x00, 0x7F,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xF8,
0x07, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0xFF,
0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFC,
0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF,
0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xF8, 0x00, 0xFF,
0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0xFC,
0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x0F, 0xFF,
0xFF, 0xFE, 0x01, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xE0, 0x01, 0xFF,
0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF,
0xF8, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFE,
0xFF, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFF, 0xFC, 0x01, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF,
0xF8, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0xF8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF,
0xF0, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF,
0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xF8, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x3F, 0xFF, 0xFF,
0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x07, 0xFF,
0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0xFF,
0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF,
0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x07, 0xFF,
0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xE0, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x1F, 0xFF, 0xFF,
0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF,
0xFF, 0xFE, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x0F, 0xFF, 0xFF,
0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x1F, 0xFF,
0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF,
0xF8, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x7F, 0xFF,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00,
0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0,
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFC, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC,
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x80, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0,
0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00,
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7F, 0x80, 0x01, 0xFE, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFF, 0xFE, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x0F, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xC0, 0x01,
0xFE, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F,
0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3F, 0xC0, 0x03, 0xFC, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xE0, 0x03,
0xFC, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F,
0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1F, 0xE0, 0x07, 0xF8, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x07,
0xF8, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x07, 0xFF, 0x00, 0x00, 0x3F,
0x80, 0x00, 0x00, 0x07, 0xFF, 0x00, 0x7F, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFE, 0x01, 0xFF, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x07, 0xFC, 0x00,
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF8, 0x0F,
0xE0, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x00, 0xFF, 0x80, 0x00, 0x3F,
0x80, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xFC, 0x1F, 0xE0, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFE, 0x00, 0x7F, 0x80, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x0F, 0xF0, 0x00,
0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFC, 0x1F,
0xC0, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x00, 0x7F, 0x80, 0x00, 0x3F,
0xFF, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFE, 0x3F, 0xC0, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFE, 0x00, 0x3F, 0x80, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x1F, 0xE0, 0x00,
0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFE, 0x3F,
0x80, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x00, 0x3F, 0x80, 0x00, 0x3F,
0xFF, 0xE0, 0x00, 0x1F, 0xE0, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0x7F, 0x80, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFE, 0x00, 0x3F, 0x80, 0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x0F, 0xF0, 0x00,
0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7F,
0x00, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x00, 0x7F, 0x80, 0x00, 0x3F,
0x80, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFE, 0x00, 0x7F, 0x80, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x0F, 0xF8, 0x00,
0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE,
0x00, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x00, 0xFF, 0x80, 0x00, 0x3F,
0x80, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFE, 0x01, 0xFF, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0x07, 0xFE, 0x00,
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFC,
0x00, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFE, 0x0F, 0xFE, 0x00, 0x00, 0x3F,
0x80, 0x00, 0x00, 0x07, 0xFF, 0x80, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xFF,
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8,
0x00, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x3F,
0xFF, 0xF0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x7F, 0xFF,
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF0,
0x00, 0x00, 0x03, 0xF8, 0x00, 0x01, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x3F,
0xFF, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x01,
0xFF, 0xFC, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,138 @@
// "Flying toasters" example for PicoDVI library. If just starting out,
// see the 8bit_double_buffer which explains the PicoDVI groundwork.
// Comments in THIS file are mostly distinct & new concepts.
// IF NO OUTPUT OR RED FLICKER SCANLINES: try Tools->Optimize->(-O3)
#include <PicoDVI.h>
#include "sprites.h" // Graphics data
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);
// See notes in 8bit_double_buffer regarding 400x240 mode.
//DVIGFX8 display(DVI_RES_400x240p60, true, adafruit_feather_dvi_cfg);
// Also requires -O3 setting.
// This structure holds pointers to sprite graphics and masks in sprites.h.
// First 8 are toasters, last 3 are toast.
const struct {
const uint8_t *sprite;
const uint8_t *mask;
} spritedata[] = {
{ toaster0, toaster0_mask}, // There are really only 4 unique frames
{ toaster1, toaster1_mask}, // of toaster flapping. Instead of code to
{ toaster2, toaster2_mask}, // handle the back-and-forth motion, just
{ toaster3, toaster3_mask}, // refer to frames in the desired sequence.
{ toaster3, toaster3_mask}, // This also doubles up the first and last
{ toaster2, toaster2_mask}, // frames to create a small pause at those
{ toaster1, toaster1_mask}, // points in the loop.
{ toaster0, toaster0_mask},
{ toast0 , toast_mask}, // Light,
{ toast1 , toast_mask}, // medium and
{ toast2 , toast_mask}, // dark toast - all use the same mask.
};
// 12 sprites seems to be about the limit for this code to maintain
// consistent 60 frames/sec updates. Double-buffered graphics synchronize
// to display refresh, and if something can't complete in one frame,
// everything is stalled until the next. It's especially annoying in
// edge cases with some frames take 1/60 sec but others take 1/30.
// See notes at end of file regarding potential improvements.
#define N_SPRITES 12
// This structure contains positions and other data for the sprites
// in motion (notice it's not "const", because contents change).
struct {
int16_t pos[2]; // sprite position (X,Y) * 16
int8_t speed; // sprite speed
uint8_t frame; // for animation
} sprite[N_SPRITES];
// Initialize one sprite (index passed to function) to a random offscreen
// position, also randomizing speed and sprite type (toaster or toast).
void randomsprite(uint8_t i) {
// To move the sprites at slightly different speeds, coordinates are
// stored in 1/16 pixel units. When drawing, the stored values get
// divided by 16 to yield final pixel coordinates.
sprite[i].pos[0] = display.width() * 16; // Off right edge
sprite[i].pos[1] = random(-display.width() / 2, display.height()) * 16;
sprite[i].speed = random(8, 17); // Move 1/2 to 1 pixel per frame
// The spritedata array has 8 toaster frames and 3 toasts; just picking
// one randomly gives us 8/11 odds of a toaster (with a random initial
// wing position) and 3/11 of toast (with random done-ness), good mix.
sprite[i].frame = random(sizeof spritedata / sizeof spritedata[0]);
}
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);
}
// Initialize color palette from table in sprites.h. Rather than
// calling setColor() for each one, we can just dump it directly...
memcpy(display.getPalette(), palette, sizeof palette);
display.swap(false, true); // Duplicate same palette to front & back buffers
// Randomize initial sprite states
randomSeed(analogRead(A0)); // Seed randomness from unused analog in
for (int i=0; i<N_SPRITES; i++) randomsprite(i);
}
uint8_t flap = 0; // Counter for flapping animation
void loop() { // Runs once every frame
display.fillScreen(0); // Clear back framebuffer,
for (int i=0; i<N_SPRITES; i++) { // and draw each sprite there...
// Although DVIGFX8 is a COLOR display type, we leverage GFX's
// drawGrayscaleBitmap() function to draw the sprites...it saves us
// writing a ton of code this way. See notes at end of this file.
// Also here's the "divide position by 16" mentioned earlier:
display.drawGrayscaleBitmap(sprite[i].pos[0] / 16, sprite[i].pos[1] / 16,
spritedata[sprite[i].frame].sprite, spritedata[sprite[i].frame].mask,
64, 64); // All sprites are 64x64 to simplify this code
// After drawing each sprite, update its position
sprite[i].pos[0] -= sprite[i].speed; // Move left
sprite[i].pos[1] += sprite[i].speed / 2; // Move down (X:Y 2:1 ratio)
// Every fourth video frame, IF this sprite is a toaster (frame is 0-7),
// increment the sprite index and wrap around back to 0 if necessary...
if ((!(flap & 3)) && (sprite[i].frame < 8)) {
sprite[i].frame = (sprite[i].frame + 1) & 7; // Loop 0-7
} // else is video frame 1-3 or is toast (not animated)
// If the sprite has moved off the left or bottom edges, reassign it:
if ((sprite[i].pos[0] < -64 * 16) || (sprite[i].pos[1] > display.height() * 16))
randomsprite(i);
}
// Swap front/back buffers, do not duplicate current screen state
// to next frame, we'll draw it new from scratch each time.
display.swap();
flap++; // Increment animation counter; "rolls over" 0-255 automatically.
}
/*
NOTES ON EFFICIENCY
This was written to be a silly and somewhat simple example. No care is
taken to minimize program size or speed, so it's NOT a good role model
for complex situations, but it was quick to produce. Given time and effort,
what could be improved?
- As written, every sprite is 64x64 pixels, period. Makes it very easy to
draw. Flash space could be saved by cropping each sprite to a minimum
bounding rectangle...tradeoff being there would need to be a table of
sizes and X/Y offsets to maintain consistent motion when animating.
Demo's using ~230K -- just a fraction of available flash -- and life is
short so it's just not a priority here.
- The GFX library's drawGrayscaleBitmap() function is used because it's
there and works for our needs, but is not especially efficient (testing
clipping on every pixel) and expects 1 byte/pixel data. Since this demo's
sprites are all 16-color, "packing" 2 pixels/byte is possible, using half
as much flash. A function could be written both to handle clipping more
efficiently and to de-pack and draw sprite data straight to framebuffer,
but would likely increase source code 2-3X and confuse novices.
- The sprite data is all stored in flash memory, which is slower to access
than RAM. RAM is scarce (perhaps ~64K after PicoDVI claims a framebuffer),
but the sprites might still fit, especially if packed 2 pixels/byte. It's
not a bottleneck now AS WRITTEN (due to drawGrayscaleBitmap() itself being
a bit pokey), but if sprite-drawing were handled as mentioned above, could
be possible to increase the sprite count while maintaining frame rate by
adding the __not_in_flash attribute to these tables (see Pico SDK docs).
*/

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,343 @@
// "TV host" example for PicoDVI library. If just starting out,
// see the 8bit_double_buffer which explains the PicoDVI groundwork.
// Comments in THIS file are mostly distinct & new concepts.
// IF NO OUTPUT OR RED FLICKER SCANLINES: try Tools->Optimize->(-O3)
#include <PicoDVI.h>
#include "sprites.h" // Graphics data
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);
// See notes in 8bit_double_buffer regarding 400x240 mode.
//DVIGFX8 display(DVI_RES_400x240p60, true, adafruit_feather_dvi_cfg);
// Also requires -O3 setting.
#define SPRITE_WIDTH 256
#define SPRITE_HEIGHT 207
// This structure holds pointers to sprite graphics and masks in sprites.h.
const struct {
const uint8_t *sprite;
const uint8_t *mask;
} spritedata[] = {
{ sprite0 , mask0 },
{ sprite1 , mask1 },
{ sprite2 , mask2 },
{ sprite3 , mask3 },
{ sprite4 , mask4 },
{ sprite5 , mask5 },
{ sprite6 , mask6 },
{ sprite7 , mask7 },
{ sprite8 , mask8 },
{ sprite9 , mask9 },
{ sprite10, mask10 },
{ sprite11, mask11 },
{ sprite12, mask12 },
{ sprite13, mask13 },
{ sprite14, mask14 },
{ sprite15, mask15 },
{ sprite16, mask16 }
};
#define NUM_SPRITES (sizeof spritedata / sizeof spritedata[0])
// Spinning cube implements 3D rotation & projection, but takes every
// mathematical shortcut; not recommended as a starting point for proper
// 3D drawing. Check out Wikipedia, Foley & Van Dam, etc.
#define STEPS 20 // Number of lines between cube edges
#define CAM_Z 1.8 // Camera position along Z axis; approx sqrt(3) * 1.05
float scale; // Calc'd once per frame for in/out bounce effect
float min_scale = sqrt((display.width() * display.width() + display.height() * display.height()) / 4) / M_SQRT2 * 2.0;
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);
}
// Initialize color palette from table in sprites.h. Rather than
// calling setColor() for each one, we can just dump it directly...
memcpy(display.getPalette(), palette, sizeof palette);
// And then override the last few entries for our background cube...
display.setColor(0, 0, 0, 0); // Black background
display.setColor(253, 255, 0, 255); // Index 253 = magenta
display.setColor(254, 0, 255, 255); // Index 254 = cyan
display.setColor(255, 255, 255, 0); // Index 255 = yellow
display.swap(false, true); // Duplicate same palette into front & back buffers
}
const float corners[8][3] = { // Cube vertices in original coord space
{ -1, -1, -1 },
{ 1, -1, -1 },
{ -1, 1, -1 },
{ 1, 1, -1 },
{ -1, -1, 1 },
{ 1, -1, 1 },
{ -1, 1, 1 },
{ 1, 1, 1 },
};
float rotated[8][3]; // Cube vertices after rotation applied
// (Cam is stationary, cube is transformed, rather than other way around)
// Draw one face of the cube, if it's visible (not back-facing).
// Some points & doubled-up lines could be avoided with more care,
// but code would be somewhat longer. Not a bottleneck here, so...
void face(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4, uint8_t p5, uint8_t color) {
// p1-p4 are indices of 4 corners, p5 is a perpendicular point (surface normal)
float vx = rotated[p1][0]; // Vector from cam to first point
float vy = rotated[p1][1]; // (not normalized)
float vz = rotated[p1][2] - CAM_Z;
float nx = rotated[p5][0] - rotated[p1][0]; // Surface normal
float ny = rotated[p5][1] - rotated[p1][1]; // (not normalized)
float nz = rotated[p5][2] - rotated[p1][2];
// Dot product of vectors. Since only the sign of the result is needed
// below, the two vectors do not require normalization first.
float dot = vx * nx + vy * ny + vz * nz;
if (dot < 0) { // Facing camera?
for(int i=0; i<STEPS; i++) { // Interpolate parallel lines...
float s1 = (float)i / (float)(STEPS - 1); // Weighting of p1, p3
float s2 = 1.0 - s1; // Weighting of p2, p4
// Interpolate between p1, p2
float t1x = rotated[p1][0] * s1 + rotated[p2][0] * s2;
float t1y = rotated[p1][1] * s1 + rotated[p2][1] * s2;
float t1z = rotated[p1][2] * s1 + rotated[p2][2] * s2;
// Interpolate between p3, p4
float t2x = rotated[p3][0] * s1 + rotated[p4][0] * s2;
float t2y = rotated[p3][1] * s1 + rotated[p4][1] * s2;
float t2z = rotated[p3][2] * s1 + rotated[p4][2] * s2;
// Project interpolated endpoints to image plane @ Z=0
float dz = t1z - CAM_Z; // Camera to point
float x1 = (float)(display.width() / 2) + t1x / dz * scale + 0.5;
float y1 = (float)(display.height() / 2) + t1y / dz * scale + 0.5;
dz = t2z - CAM_Z; // Camera to point
float x2 = (float)(display.width() / 2) + t2x / dz * scale + 0.5;
float y2 = (float)(display.height() / 2) + t2y / dz * scale + 0.5;
line(x1, y1, x2, y2, color);
}
}
}
uint8_t frame = 0; // For animation timing
bool stutter = false; // If true, doing A/B stutter animation
uint32_t duration = 3000; // Time to hold current mode, in milliseconds
uint32_t lastModeSwitchTime = 0;
uint8_t var[2] = { 4, 4 }; // Mode-specific variables
uint8_t spritenum = 0; // Active sprite index
void loop() { // Runs once every frame
display.fillScreen(0); // Clear back framebuffer, will do full redraw...
// This does the in & out bounce effect (bit of a cheat but less math
// than moving camera in & out. Cam stays in one position just far
// enough that it's never inside cube, need not handle Z clipping.)
scale = min_scale * (1.0 + (float)abs((long)(millis() & 4095) - 2048) / 8192.0); // 1.0-1.25X
// Rotate cube vertices from corners[] to rotated[]
float now = millis() / 1000.0; // Elapsed time in seconds
float rx = now * -0.19; // X, Y, Z rotation
float ry = now * 0.23;
float rz = now * 0.13;
float cx = cos(rx);
float cy = cos(ry);
float cz = cos(rz);
float sx = sin(rx);
float sy = sin(ry);
float sz = sin(rz);
for (int i=0; i<8; i++) {
float x1 = corners[i][0];
float y1 = corners[i][1];
float z1 = corners[i][2];
float y2 = y1 * cx - z1 * sx;
float z2 = y1 * sx + z1 * cx;
float x2 = x1 * cy - z2 * sy;
float z3 = x1 * sy + z2 * cy;
float x3 = x2 * cz - y2 * sz;
float y3 = x2 * sz + y2 * cz;
rotated[i][0] = x3;
rotated[i][1] = y3;
rotated[i][2] = z3;
}
face(0, 1, 4, 5, 2, 253); // Test & draw 6 faces of cube
face(0, 2, 4, 6, 1, 254);
face(2, 3, 6, 7, 0, 253);
face(1, 3, 5, 7, 0, 254);
face(0, 1, 2, 3, 4, 255);
face(4, 5, 6, 7, 0, 255);
// Check if switching in/out of stutter mode
if ((millis() - lastModeSwitchTime) >= duration) { // Time to switch?
lastModeSwitchTime = millis(); // Note time,
stutter = !stutter; // and make the switch
if (stutter) { // If entering stutter mode...
duration = random(250, 500); // Go for 1/4 to 1/2 second
var[0] = random(NUM_SPRITES); // Pick 2 different sprites
var[1] = (var[0] + 1 + random(NUM_SPRITES - 1)) % NUM_SPRITES;
} else { // Returning to regular mode...
duration = random(2000, 5000); // Go for 2-5 seconds
var[0] = random(3, 6); // Hold each pose for 3-5 frames
}
}
// Choose sprite to display based on current mode...
if (stutter) {
// Every second frame, alternate between two sprites
spritenum = var[((frame >> 1) & 1)];
} else {
// Pick among random frames (may repeat, is OK)
if (!(frame % var[0])) {
spritenum = random(NUM_SPRITES);
}
}
// Overlay big sprite. Although DVIGFX8 is a COLOR display type,
// we leverage GFX's drawGrayscaleBitmap() function to draw it...
// saves us writing a ton of code this way.
// Comment this out if you want ONLY the background cube.
display.drawGrayscaleBitmap((display.width() - SPRITE_WIDTH) / 2,
display.height() - SPRITE_HEIGHT, spritedata[spritenum].sprite,
spritedata[spritenum].mask, SPRITE_WIDTH, SPRITE_HEIGHT);
// Swap front/back buffers, do not duplicate current screen state
// to next frame, we'll draw it new from scratch each time.
display.swap();
frame++; // For animation timing; wraps around 0-255
}
// A somewhat smooth(er) line drawing function. Not antialiased, but with
// subpixel positioning that makes the background animation less "jumpy"
// than with GFX, which is all integer end points.
// Returns bitmask of which edge(s) a point exceeds
uint8_t xymask(float x, float y) {
return ( (x < 0.0 ) | ((x >= (float)display.width() ) << 1) |
((y < 0.0) << 2) | ((y >= (float)display.height()) << 3));
}
// Returns bitmask of which X edge(s) a point exceeds
uint8_t xmask(float x) {
return (x < 0.0) | ((x >= (float)display.width()) << 1);
}
void line(float x1, float y1, float x2, float y2, uint8_t color) {
float ox1 = x1, oy1 = y1, ox2 = x2, oy2 = y2;
uint8_t mask1 = xymask(x1, y1); // If both endpoints are
uint8_t mask2 = xymask(x2, y2); // off same edge(s),
if (mask1 & mask2) return; // line won't overlap screen
float dx = x2 - x1;
float dy = y2 - y1;
// Clip top
if (mask1 & 4) {
x1 -= y1 * dx / dy;
y1 = 0.0;
} else if (mask2 & 4) {
x2 -= y2 * dx / dy;
y2 = 0.0;
}
float maxy = (float)(display.height() - 1);
// Clip bottom
if (mask1 & 8) {
x1 -= (y1 - maxy) * dx / dy;
y1 = maxy;
} else if (mask2 & 8) {
x2 -= (y2 - maxy) * dx / dy;
y2 = maxy;
}
mask1 = xmask(x1); // Repeat the offscreen check after Y clip
mask2 = xmask(x2);
if (mask1 & mask2) return;
dx = x2 - x1;
dy = y2 - y1;
// Clip left
if (mask1 & 1) {
y1 -= x1 * dy / dx;
x1 = 0.0;
} else if (mask2 & 1) {
y2 -= x2 * dy / dx;
x2 = 0.0;
}
float maxx = (float)(display.width() - 1);
// Clip right
if (mask1 & 2) {
y1 -= (x1 - maxx) * dy / dx;
x1 = maxx;
} else if (mask2 & 2) {
y2 -= (x2 - maxx) * dy / dx;
x2 = maxx;
}
// (x1, y1) to (x2, y2) is known to be on-screen and in-bounds now.
// Handle a couple special cases (horizontal, vertical lines) first,
// GFX takes care of these fine and it avoids some divide-by-zero
// checks in the diagonal code later.
if ((int)y1 == (int)y2) { // Horizontal or single point
int16_t x, w;
if (x2 >= x1) {
x = (int)x1;
w = (int)x2 - (int)x1 + 1;
} else {
x = (int)x2;
w = (int)x1 - (int)x2 + 1;
}
display.drawFastHLine(x, (int)y1, w, color);
} else if ((int)x1 == (int)x2) { // Vertical
int16_t y, h;
if (y2 >= y1) {
y = (int)y1;
h = (int)y2 - (int)y1 + 1;
} else {
y = (int)y2;
h = (int)y1 - (int)y2 + 1;
}
display.drawFastVLine((int)x1, y, h, color);
} else { // Diagonal
dx = x2 - x1;
dy = y2 - y1;
uint8_t *ptr = display.getBuffer();
// This is a bit ugly in that it uses floating-point math in the line
// drawing loop. There are more optimal Bresenham-esque fixed-point
// approaches to do this (initializing the error term based on subpixel
// endpoints and slopes), but A) I'm out of spoons today, and B) we're
// drawing just a few dozen lines and it's simply not a bottleneck in
// this demo. Just saying this won't scale up to thousands of lines.
if (fabs(dx) >= fabs(dy)) { // "Wide" line
if (x1 > x2) { // Put (x1, y1) at left
float t = x1; x1 = x2; x2 = t;
t = y1; y1 = y2; y2 = t;
}
uint16_t ix1 = (uint16_t)x1;
uint16_t ix2 = (uint16_t)x2;
float slope = dy / dx;
for (uint16_t x=ix1; x <= ix2; x++) {
uint16_t iy = (uint16_t)(y1 + slope * (float)(x - ix1));
if (iy < display.height()) ptr[iy * display.width() + x] = color;
}
} else { // "Tall" line
if (y1 > y2) { // Put (x1, y1) at top
float t = x1; x1 = x2; x2 = t;
t = y1; y1 = y2; y2 = t;
}
uint16_t iy1 = (uint16_t)y1;
uint16_t iy2 = (uint16_t)y2;
float slope = dx / dy;
for (uint16_t y=iy1; y <= iy2; y++) {
uint16_t ix = (uint16_t)(x1 + slope * (float)(y - iy1));
if (ix < display.width()) ptr[y * display.width() + ix] = color;
}
}
}
}

View file

@ -0,0 +1,24 @@
.program fourwire
; Sample bits using an external clock, and push groups of bits into the RX FIFO.
; - IN pin 0 is the data pin (GPIO18)
; - IN pin 1 is the dc pin (GPIO19)
; - IN pin 2 is the clock pin (GPIO20)
; - JMP pin is the chip select (GPIO21)
; - Autopush is enabled, threshold 8
;
; This program waits for chip select to be asserted (low) before it begins
; clocking in data. Whilst chip select is low, data is clocked continuously. If
; chip select is deasserted part way through a data byte, the partial data is
; discarded. This makes use of the fact a mov to isr clears the input shift
; counter.
flush:
mov isr, null ; Clear ISR and input shift counter
jmp check_chip_select ; Poll chip select again
.wrap_target
do_bit:
wait 0 pin 2 ; Detect rising edge and sample input data
wait 1 pin 2 ; (autopush takes care of moving each complete
in pins, 2 ; data word to the FIFO)
check_chip_select:
jmp pin, flush ; Bail out if we see chip select high
.wrap

View file

@ -0,0 +1,251 @@
// PicoDVI-based "virtual SPITFT" display. Receives graphics commands/data
// over 4-wire SPI interface, mimicking functionality of displays such as
// ST7789 or ILI9341, but shown on an HDMI monitor instead.
#include <PicoDVI.h> // Core display & graphics library
// Configurables ----
// GPIO connected to (or shared with) TFT control.
// Careful not to overlap the DVI pins.
#define PIN_DATA 9 // 3 contiguous pins start here: data, DC, clk
#define PIN_CS 6 // Chip-select need not be contiguous
// 320x240 16-bit color display (to match common TFT display resolution):
DVIGFX16 display(DVI_RES_320x240p60, adafruit_feather_dvi_cfg);
// Output of pioasm ----
#define fourwire_wrap_target 2
#define fourwire_wrap 5
static const uint16_t fourwire_program_instructions[] = {
0xa0c3, // 0: mov isr, null
0x0005, // 1: jmp 5
// .wrap_target
0x2022, // 2: wait 0 pin, 2
0x20a2, // 3: wait 1 pin, 2
0x4002, // 4: in pins, 2
0x00c0, // 5: jmp pin, 0
// .wrap
};
static const struct pio_program fourwire_program = {
.instructions = fourwire_program_instructions,
.length = 6,
.origin = -1,
};
static inline pio_sm_config fourwire_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + fourwire_wrap_target, offset + fourwire_wrap);
return c;
}
// end pioasm output ----
PIO pio = pio1; // libdvi uses pio0 (but has 1 avail state machine if you want to use it)
uint sm;
uint16_t *framebuf = display.getBuffer();
uint8_t decode[256];
#define DECODE(w) (decode[(w & 0x55) | ((w >> 7) & 0xaa)])
#define BIT_DEPOSIT(b, i) ((b) ? (1<<(i)) : 0)
#define BIT_EXTRACT(b, i) (((b) >> (i)) & 1)
#define BIT_MOVE(b, src, dest) BIT_DEPOSIT(BIT_EXTRACT(b, src), dest)
#define ENCODED_COMMAND(x) ( \
(BIT_MOVE(x, 0, 0)) | \
(BIT_MOVE(x, 1, 2)) | \
(BIT_MOVE(x, 2, 4)) | \
(BIT_MOVE(x, 3, 6)) | \
(BIT_MOVE(x, 4, 8)) | \
(BIT_MOVE(x, 5, 10)) | \
(BIT_MOVE(x, 6, 12)) | \
(BIT_MOVE(x, 7, 14)) \
)
#define COMMAND_NOP (0x00)
#define COMMAND_SWRESET (0x01)
#define COMMAND_CASET (0x2a)
#define COMMAND_PASET (0x2b)
#define COMMAND_RAMWR (0x2c)
#define COMMAND_MADCTL (0x36)
#define MADCTL_MY 0x80
#define MADCTL_MX 0x40
#define MADCTL_MV 0x20
#define MADCTL_ML 0x10
void setup() {
Serial.begin(115200);
//while(!Serial);
if (!display.begin()) { // Blink LED if insufficient RAM
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
for(int i=0; i<256; i++) {
int j = (BIT_MOVE(i, 0, 0)) |
(BIT_MOVE(i, 2, 1)) |
(BIT_MOVE(i, 4, 2)) |
(BIT_MOVE(i, 6, 3)) |
(BIT_MOVE(i, 1, 4)) |
(BIT_MOVE(i, 3, 5)) |
(BIT_MOVE(i, 5, 6)) |
(BIT_MOVE(i, 7, 7));
decode[i] = j;
}
uint offset = pio_add_program(pio, &fourwire_program);
sm = pio_claim_unused_sm(pio, true);
pio_sm_config c = fourwire_program_get_default_config(offset);
// Set the IN base pin to the provided PIN_DATA parameter. This is the data
// pin, and the next-numbered GPIO is used as the clock pin.
sm_config_set_in_pins(&c, PIN_DATA);
sm_config_set_jmp_pin(&c, PIN_CS);
// Set the pin directions to input at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, PIN_DATA, 3, false);
pio_sm_set_consecutive_pindirs(pio, sm, PIN_CS, 1, false);
// Connect GPIOs to PIO block, set pulls
for (uint8_t i=0; i<3; i++) {
pio_gpio_init(pio, PIN_DATA + i);
gpio_set_pulls(PIN_DATA + i, true, false);
}
pio_gpio_init(pio, PIN_CS);
gpio_set_pulls(PIN_CS, true, false);
// Shifting to left matches the customary MSB-first ordering of SPI.
sm_config_set_in_shift(
&c,
false, // Shift-to-right = false (i.e. shift to left)
true, // Autopush enabled
16 // Autopush threshold
);
// We only receive, so disable the TX FIFO to make the RX FIFO deeper.
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
// Load our configuration, and start the program from the beginning
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
// State machine should handle malformed requests somewhat,
// e.g. RAMWR writes that wrap around or drop mid-data.
uint8_t cmd = COMMAND_NOP; // Last received command
uint16_t X0 = 0, X1 = display.width() - 1; // Address window X
uint16_t Y0 = 0, Y1 = display.height() - 1; // Address window Y
uint16_t x = 0, y = 0; // Current pixel pos.
union { // Data receive buffer sufficient for implemented commands
uint8_t b[4];
uint16_t w[2];
uint32_t l;
} buf;
int8_t bufidx = -1; // Current pos. in buf.b[] array (or -1 = full)
for (;;) {
uint16_t ww = pio_sm_get_blocking(pio, sm); // Read next word (data & DC interleaved)
if ((ww & 0x2)) { // DC bit is set, that means it's data (most common case, hence 1st)
if (bufidx >= 0) { // Decode & process only if recv buffer isn't full
buf.b[bufidx] = DECODE(ww);
// Buffer is filled in reverse so byte swaps aren't needed on uint16_t values
if (--bufidx < 0) { // Receive threshold reached?
switch (cmd) {
case COMMAND_CASET:
// Clipping is not performed here because framebuffer
// may be a different size than implied SPI device.
// That occurs in the RAMWR condition later.
X0 = buf.w[1]; // [sic.] 1 because buffer is loaded in reverse
X1 = buf.w[0];
if (X0 > X1) {
uint16_t tmp = X0;
X0 = X1;
X1 = tmp;
}
break;
case COMMAND_PASET:
Y0 = buf.w[1]; // [sic.] 1 because buffer is loaded in reverse
Y1 = buf.w[0];
if (Y0 > Y1) {
uint16_t tmp = Y0;
Y0 = Y1;
Y1 = tmp;
}
break;
case COMMAND_RAMWR:
// Write pixel to screen, increment X/Y, wrap around as needed.
// drawPixel() is used as it handles both clipping & rotation,
// saves a lot of bother here. However, this only handles rotation,
// NOT full MADCTL mapping, but the latter is super rare, I think
// it's only used in some eye code to mirror one of two screens.
// If it's required, then rotation, mirroring and clipping will
// all need to be handled in this code...but, can write direct to
// framebuffer then, might save some cycles.
display.drawPixel(x, y, buf.w[0]);
if (++x > X1) {
x = X0;
if (++y > Y1) {
y = Y0;
}
}
bufidx = 1; // Reset buffer counter for next pixel
// Buflen is left as-is, so more pixels can be processed
break;
case COMMAND_MADCTL:
switch (buf.b[0] & 0xF0) {
case MADCTL_MX | MADCTL_MV: // ST77XX
case MADCTL_MX | MADCTL_MY | MADCTL_MV: // ILI9341
display.setRotation(0);
break;
case MADCTL_MX | MADCTL_MY: // ST77XX
case MADCTL_MX: // ILI9341
display.setRotation(1);
break;
case MADCTL_MY | MADCTL_MV: // ST77XX
case MADCTL_MV: // ILI9341
display.setRotation(2);
break;
case 0: // ST77XX
case MADCTL_MY: // ILI9341
display.setRotation(3);
break;
}
break;
}
}
}
} else { // Is command
cmd = DECODE(ww);
switch (cmd) {
case COMMAND_SWRESET:
display.setRotation(0);
x = y = X0 = Y0 = 0;
X1 = display.width() - 1;
Y1 = display.height() - 1;
break;
case COMMAND_CASET:
bufidx = 3; // Expecting two 16-bit values (X0, X1)
break;
case COMMAND_PASET:
bufidx = 3; // Expecting two 16-bit values (Y0, Y1)
break;
case COMMAND_RAMWR:
bufidx = 1; // Expecting one 16-bit value (or more)
x = X0; // Start at UL of address window
y = Y0;
break;
case COMMAND_MADCTL:
bufidx = 0; // Expecting one 8-bit value
break;
default:
// Unknown or unimplemented command, discard any data that follows
bufidx = -1;
}
}
}
}
void loop() {
}

10
library.properties Normal file
View file

@ -0,0 +1,10 @@
name=PicoDVI - Adafruit Fork
version=1.3.2
author=Luke Wren (Wren6991)
maintainer=Adafruit <info@adafruit.com>
sentence=Arduino library for RP2040 DVI output, based on PicoDVI
paragraph=Arduino library for RP2040 DVI output, based on PicoDVI
category=Display
url=https://github.com/adafruit/PicoDVI
architectures=rp2040
depends=Adafruit GFX Library, Adafruit CPFS

View file

@ -23,6 +23,7 @@ target_compile_definitions(bad_apple PRIVATE
DVI_VERTICAL_REPEAT=1
DVI_N_TMDS_BUFFERS=3
DVI_MONOCHROME_TMDS
DVI_1BPP_BIT_REVERSE=0
)
target_link_libraries(bad_apple

View file

@ -3,6 +3,11 @@
#include "pico/types.h"
#if defined(__cplusplus)
extern "C"
{
#endif
// Render characters using an 8px-wide font and a per-character 2bpp
// foreground/background colour. This function is fast enough to run 3 times
// per scanline on one core, so RGB222 coloured text can be rendered (with
@ -20,4 +25,8 @@
void tmds_encode_font_2bpp(const uint8_t *charbuf, const uint32_t *colourbuf,
uint32_t *tmdsbuf, uint n_pix, const uint8_t *font_line);
#if defined(__cplusplus)
}
#endif
#endif

View file

@ -13,6 +13,7 @@ target_compile_definitions(dht_logging PRIVATE
DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG}
DVI_VERTICAL_REPEAT=1
DVI_N_TMDS_BUFFERS=3
DVI_1BPP_BIT_REVERSE=0
)

View file

@ -11,6 +11,7 @@ target_compile_definitions(moon PRIVATE
DVI_VERTICAL_REPEAT=1
DVI_N_TMDS_BUFFERS=3
DVI_MONOCHROME_TMDS
DVI_1BPP_BIT_REVERSE=0
)
target_link_libraries(moon

View file

@ -14,6 +14,7 @@ target_compile_definitions(terminal PRIVATE
DVI_VERTICAL_REPEAT=1
DVI_N_TMDS_BUFFERS=3
DVI_MONOCHROME_TMDS=1
DVI_1BPP_BIT_REVERSE=0
)
target_link_libraries(terminal

View file

@ -24,11 +24,12 @@
// Pick one:
#define MODE_640x480_60Hz
//#define MODE_640x480_60Hz
// #define MODE_720x480_60Hz
// #define MODE_800x600_60Hz
// #define MODE_960x540p_60Hz
// #define MODE_1280x720_30Hz
#define MODE_1280x720_30Hz
#if defined(MODE_640x480_60Hz)
// DVDD 1.2V (1.1V seems ok too)

View file

@ -5,7 +5,11 @@
// developing on. It's not a particularly important file -- just saves some
// copy + paste.
#if defined(ARDUINO)
#include "../libdvi/dvi_serialiser.h"
#else
#include "dvi_serialiser.h"
#endif
#ifndef DVI_DEFAULT_SERIAL_CONFIG
#define DVI_DEFAULT_SERIAL_CONFIG pico_sock_cfg
@ -101,13 +105,42 @@ static const struct dvi_serialiser_cfg not_hdmi_featherwing_cfg = {
// Adafruit Feather RP2040 DVI
static const struct dvi_serialiser_cfg adafruit_feather_dvi_cfg = {
.pio = pio0,
.pio = DVI_DEFAULT_PIO_INST,
.sm_tmds = {0, 1, 2},
.pins_tmds = {18, 20, 22},
.pins_clk = 16,
.invert_diffpairs = true
};
static const struct dvi_serialiser_cfg adafruit_dvibell_cfg = {
.pio = DVI_DEFAULT_PIO_INST,
.sm_tmds = {0, 1, 2},
.pins_tmds = {10, 8, 6},
.pins_clk = 12,
.invert_diffpairs = false,
};
// hstx dvi cowbell
static const struct dvi_serialiser_cfg adafruit_hstxdvibell_cfg = {
.pio = DVI_DEFAULT_PIO_INST,
.sm_tmds = {0, 1, 2},
.pins_tmds = {12, 18, 16},
.pins_clk = 14,
.invert_diffpairs = false,
};
// Adafruit 'Fruit Jam' computer board
static const struct dvi_serialiser_cfg adafruit_fruitjam_cfg = {
.pio = DVI_DEFAULT_PIO_INST,
.sm_tmds = {0, 1, 2},
.pins_tmds = {14, 16, 18},
.pins_clk = 12,
.invert_diffpairs = true,
};
// Waveshare RP2040-PiZero
static const struct dvi_serialiser_cfg waveshare_rp2040_pizero = {
.pio = DVI_DEFAULT_PIO_INST,
@ -117,4 +150,13 @@ static const struct dvi_serialiser_cfg waveshare_rp2040_pizero = {
.invert_diffpairs = false
};
// Olimex RP2040PC, RP2040-Pico-PC, Neo6502,
static struct dvi_serialiser_cfg olimex_rp2040_cfg = {
.pio = DVI_DEFAULT_PIO_INST,
.sm_tmds = {0, 1, 2},
.pins_tmds = {14, 18, 16},
.pins_clk = 12,
.invert_diffpairs = true
};
#endif

View file

@ -7,6 +7,10 @@
#include "dvi_serialiser.h"
#include "tmds_encode.h"
// Adafruit PicoDVI fork requires a couple global items run-time configurable:
uint8_t dvi_vertical_repeat = DVI_VERTICAL_REPEAT;
bool dvi_monochrome_tmds = DVI_MONOCHROME_TMDS;
// Time-critical functions pulled into RAM but each in a unique section to
// allow garbage collection
#define __dvi_func(f) __not_in_flash_func(f)
@ -37,16 +41,19 @@ void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_col
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, true, &inst->dma_list_vblank_sync);
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, false, &inst->dma_list_vblank_nosync);
#if defined(ARDUINO)
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (uint32_t*)SRAM_BASE, &inst->dma_list_active);
#else
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active);
#endif
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_error);
for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) {
void *tmdsbuf;
#if DVI_MONOCHROME_TMDS
tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
#else
tmdsbuf = malloc(3 * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
#endif
if (dvi_monochrome_tmds)
tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
else
tmdsbuf = malloc(3 * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
if (!tmdsbuf)
panic("TMDS buffer allocation failed");
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
@ -201,7 +208,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
tmdsbuf = NULL;
}
else if (queue_try_peek_u32(&inst->q_tmds_valid, &tmdsbuf)) {
if (inst->timing_state.v_ctr % DVI_VERTICAL_REPEAT == DVI_VERTICAL_REPEAT - 1) {
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) {
queue_remove_blocking_u32(&inst->q_tmds_valid, &tmdsbuf);
inst->tmds_buf_release_next = tmdsbuf;
}
@ -209,7 +216,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
else {
// No valid scanline was ready (generates solid red scanline)
tmdsbuf = NULL;
if (inst->timing_state.v_ctr % DVI_VERTICAL_REPEAT == DVI_VERTICAL_REPEAT - 1)
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1)
++inst->late_scanline_ctr;
}
@ -222,7 +229,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
else {
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_error);
}
if (inst->scanline_callback && inst->timing_state.v_ctr % DVI_VERTICAL_REPEAT == DVI_VERTICAL_REPEAT - 1) {
if (inst->scanline_callback && inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) {
inst->scanline_callback();
}
break;

View file

@ -52,6 +52,11 @@ struct dvi_inst {
};
#if defined(__cplusplus)
extern "C"
{
#endif
// Set up data structures and hardware for DVI.
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue);
@ -73,7 +78,7 @@ void dvi_scanbuf_main_16bpp(struct dvi_inst *inst);
void dvi_framebuf_main_8bpp(struct dvi_inst *inst);
void dvi_framebuf_main_16bpp(struct dvi_inst *inst);
#ifdef __cplusplus
#if defined(__cplusplus)
}
#endif

View file

@ -126,7 +126,7 @@
// If 1, reverse the order of pixels within each byte. Order of bytes within
// each word is still little-endian.
#ifndef DVI_1BPP_BIT_REVERSE
#define DVI_1BPP_BIT_REVERSE 0
#define DVI_1BPP_BIT_REVERSE 1 // Adafruit_GFX GFXcanvas1 requires this 1
#endif
// ----------------------------------------------------------------------------

View file

@ -0,0 +1,101 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// -------------- //
// dvi_serialiser //
// -------------- //
#define dvi_serialiser_wrap_target 0
#define dvi_serialiser_wrap 1
static const uint16_t dvi_serialiser_program_instructions[] = {
// .wrap_target
0x70a1, // 0: out pc, 1 side 2
0x68a1, // 1: out pc, 1 side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program dvi_serialiser_program = {
.instructions = dvi_serialiser_program_instructions,
.length = 2,
.origin = 0,
};
static inline pio_sm_config dvi_serialiser_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + dvi_serialiser_wrap_target, offset + dvi_serialiser_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
#endif
// -------------------- //
// dvi_serialiser_debug //
// -------------------- //
#define dvi_serialiser_debug_wrap_target 0
#define dvi_serialiser_debug_wrap 11
static const uint16_t dvi_serialiser_debug_program_instructions[] = {
// .wrap_target
0x98e0, // 0: pull ifempty block side 1
0xb042, // 1: nop side 0
0x6001, // 2: out pins, 1
0x6001, // 3: out pins, 1
0x6001, // 4: out pins, 1
0x6001, // 5: out pins, 1
0x6001, // 6: out pins, 1
0x6001, // 7: out pins, 1
0x6001, // 8: out pins, 1
0x6001, // 9: out pins, 1
0x6001, // 10: out pins, 1
0x6001, // 11: out pins, 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program dvi_serialiser_debug_program = {
.instructions = dvi_serialiser_debug_program_instructions,
.length = 12,
.origin = -1,
};
static inline pio_sm_config dvi_serialiser_debug_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + dvi_serialiser_debug_wrap_target, offset + dvi_serialiser_debug_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
#include "dvi_config_defs.h"
static inline void dvi_serialiser_program_init(PIO pio, uint sm, uint offset, uint data_pins, bool debug) {
pio_sm_set_pins_with_mask(pio, sm, 2u << data_pins, 3u << data_pins);
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, 3u << data_pins);
pio_gpio_init(pio, data_pins);
pio_gpio_init(pio, data_pins + 1);
pio_sm_config c;
if (debug) {
c = dvi_serialiser_debug_program_get_default_config(offset);
}
else {
c = dvi_serialiser_program_get_default_config(offset);
}
sm_config_set_sideset_pins(&c, data_pins);
if (debug)
sm_config_set_out_pins(&c, data_pins, 1);
sm_config_set_out_shift(&c, true, !debug, 10 * DVI_SYMBOLS_PER_WORD);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, false);
}
#endif

View file

@ -7,6 +7,8 @@
// achievable bit clock from 12 MHz crystal)
// - Helper functions for generating DMA lists based on these timings
extern bool dvi_monochrome_tmds; // In dvi.c
// Pull into RAM but apply unique section suffix to allow linker GC
#define __dvi_func(x) __not_in_flash_func(x)
#define __dvi_const(x) __not_in_flash_func(x)
@ -330,11 +332,7 @@ void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_
void __dvi_func(dvi_update_scanline_data_dma)(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l) {
for (int i = 0; i < N_TMDS_LANES; ++i) {
#if DVI_MONOCHROME_TMDS
const uint32_t *lane_tmdsbuf = tmdsbuf;
#else
const uint32_t *lane_tmdsbuf = tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
#endif
const uint32_t *lane_tmdsbuf = dvi_monochrome_tmds ? tmdsbuf : tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
if (i == TMDS_SYNC_LANE)
dvi_lane_from_list(l, i)[3].read_addr = lane_tmdsbuf;
else

457
src/PicoDVI.cpp Normal file
View file

@ -0,0 +1,457 @@
// SPDX-FileCopyrightText: 2023 P Burgess for Adafruit Industries
//
// SPDX-License-Identifier: BSD-3-Clause
/*!
* @file PicoDVI.cpp
*
* Arduino-and-Adafruit-GFX wrapper around Luke Wren's PicoDVI library.
*/
#include "PicoDVI.h"
#include "libdvi/tmds_encode.h"
// PicoDVI class encapsulates some of the libdvi functionality -------------
// Subclasses then implement specific display types.
static struct {
const dvi_timing &timing;
vreg_voltage v;
uint16_t width;
uint16_t height;
uint8_t v_rep;
} dvispec[] = {
{dvi_timing_640x480p_60hz, VREG_VOLTAGE_1_20, 320, 240, 2},
{dvi_timing_800x480p_60hz, VREG_VOLTAGE_1_20, 400, 240, 2},
{dvi_timing_800x480p_30hz, VREG_VOLTAGE_1_20, 400, 240, 2},
{dvi_timing_640x480p_60hz, VREG_VOLTAGE_1_20, 640, 480, 1},
{dvi_timing_800x480p_60hz, VREG_VOLTAGE_1_20, 800, 480, 1},
{dvi_timing_800x480p_30hz, VREG_VOLTAGE_1_20, 800, 480, 1},
{dvi_timing_640x480p_60hz, VREG_VOLTAGE_1_20, 640, 240, 2},
{dvi_timing_800x480p_60hz, VREG_VOLTAGE_1_20, 800, 240, 2},
{dvi_timing_800x480p_30hz, VREG_VOLTAGE_1_20, 800, 240, 2},
// Additional resolutions might get added here if the overclock issue can
// be sorted out. Regardless, always keep this list 1:1 in sync with the
// DVIresolution enum in PicoDVI.h.
{dvi_timing_1280x720p_30hz, VREG_VOLTAGE_1_30, 1280, 720, 1},
};
static PicoDVI *dviptr = NULL; // For C access to active C++ object
/*!
@brief Runs on core 1 on startup; this is how Philhower RP2040 handles
multiprocessing.
*/
void setup1(void) {
while (dviptr == NULL) // Wait for PicoDVI::begin() to start on core 0
yield();
dviptr->_setup();
}
// Runs on core 1 after dviptr set
void PicoDVI::_setup(void) {
while (wait_begin)
; // Wait for DVIGFX*::begin() to set this
dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
dvi_start(&dvi0);
// This is disabled for now, for a transitional period while folks
// might be mixing in older versions of arduino-pico, Adafruit_CPFS
// and/or Adafruit_SPIFlash. Once is seems pretty solid that everyone's
// using current versions that correctly locate PicoDVI functions in
// RAM, this can be activated. Not urgent but a nice-to-have.
#if 0
// Borrowed from CircuitPython: turn off core 1 flash access. Any attempt
// after this will hard fault (not good, but preferable to the alternative
// which would corrupt the CIRCUITPY flash filesystem if mounted). Linker
// file MUST place the following files in RAM or library won't work:
// *interp.c.obj *divider.S.obj *PicoDVI.cpp.o *dvi.c.o
// (Earle Philhower arduino-pico >= 3.3.0 does this now.)
MPU->CTRL = MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
MPU->RNR = 6;
MPU->RBAR = XIP_MAIN_BASE | MPU_RBAR_VALID_Msk;
MPU->RASR = MPU_RASR_XN_Msk | MPU_RASR_ENABLE_Msk |
(0x1b << MPU_RASR_SIZE_Pos);
MPU->RNR = 7;
#endif
(*mainloop)(&dvi0);
}
PicoDVI::PicoDVI(const struct dvi_timing &t, const struct dvi_serialiser_cfg &c,
vreg_voltage v)
: voltage(v) {
dvi0.timing = &t;
memcpy(&dvi0.ser_cfg, &c, sizeof dvi0.ser_cfg);
};
PicoDVI::~PicoDVI(void) { dviptr = NULL; }
void PicoDVI::begin(void) {
dviptr = this;
vreg_set_voltage(voltage);
delay(10);
set_sys_clock_khz(dvi0.timing->bit_clk_khz, true); // Run at TMDS bit clock
dvi_init(&dvi0, next_striped_spin_lock_num(), next_striped_spin_lock_num());
}
// DVIGFX16 class provides GFX-compatible 16-bit color framebuffer ---------
static void *gfxptr = NULL; // For C access to active C++ object
DVIGFX16::DVIGFX16(const DVIresolution r, const struct dvi_serialiser_cfg &c,
vreg_voltage v)
: PicoDVI(dvispec[r].timing, c, v),
GFXcanvas16(dvispec[r].width, dvispec[r].height) {
dvi_vertical_repeat = dvispec[r].v_rep;
dvi_monochrome_tmds = false;
}
DVIGFX16::~DVIGFX16(void) { gfxptr = NULL; }
static void scanline_callback_GFX16(void) {
((DVIGFX16 *)gfxptr)->_scanline_callback();
}
void DVIGFX16::_scanline_callback(void) {
// Discard any scanline pointers passed back
uint16_t *bufptr;
while (queue_try_remove_u32(&dvi0.q_colour_free, &bufptr))
;
bufptr = &getBuffer()[WIDTH * scanline];
queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);
scanline = (scanline + 1) % HEIGHT;
}
bool DVIGFX16::begin(void) {
uint16_t *bufptr = getBuffer();
if ((bufptr)) {
gfxptr = this;
mainloop = dvi_scanbuf_main_16bpp; // in libdvi
dvi0.scanline_callback = scanline_callback_GFX16;
PicoDVI::begin();
queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);
bufptr += WIDTH;
queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);
wait_begin = false; // Set core 1 in motion
return true;
}
return false;
}
// DVIGFX8 (8-bit, color-indexed framebuffer) is all manner of dirty pool.
// PicoDVI seems to have some palette support but I couldn't grasp the DMA
// stuff going on, so just doing a brute force thing here for now: in
// addition to the 8-bit framebuffer, two 16-bit (RGB565) scanlines are
// allocated...then, in the scanline callback, pixels are mapped from the
// 8-bit framebuffer through the palette into one of these buffers, allowing
// use of the same dvi_scanbuf_main_16bpp handler as DVIGFX16 above. Not
// optimal, sure...but not pessimal either. The allocation of those 16-bit
// scanlines is weird(tm) though. Rather than a separate malloc (which
// creates a nasty can of worms if that fails after a successful framebuffer
// allocation...unlikely but not impossible), the framebuffer size is
// tweaked so that W*H is always an even number, plus 4 extra rows are
// added: thus two 16-bit scanlines, word-aligned. That extra memory is for
// us, but allocated by GFX as part of the framebuffer all at once. The
// HEIGHT value is de-tweaked to the original value so clipping won't allow
// any drawing operations to spill into the 16-bit scanlines.
// This requires latest Adafruit_GFX as double-buffered mode plays games with
// the canvas pointer, wasn't possible until that was made protected (vs
// private). Drawing and palette-setting operations ONLY apply to the "back"
// state. Call swap() to switch the front/back buffers at the next vertical
// sync, for flicker-free and tear-free animation.
DVIGFX8::DVIGFX8(const DVIresolution r, const bool d,
const struct dvi_serialiser_cfg &c, vreg_voltage v)
: PicoDVI(dvispec[r].timing, c, v),
GFXcanvas8(
dvispec[r].width,
(d ? (dvispec[r].height * 2) : ((dvispec[r].height + 1) & ~1)) + 4),
dbuf(d) {
HEIGHT = _height = dvispec[r].height;
buffer_save = buffer;
dvi_vertical_repeat = dvispec[r].v_rep;
dvi_monochrome_tmds = false;
}
DVIGFX8::~DVIGFX8(void) {
buffer = buffer_save; // Restore pointer so canvas destructor works
gfxptr = NULL;
}
static void __not_in_flash_func(scanline_callback_GFX8)(void) {
((DVIGFX8 *)gfxptr)->_scanline_callback();
}
void __not_in_flash_func(DVIGFX8::_scanline_callback)(void) {
uint16_t *b16;
while (queue_try_remove_u32(&dvi0.q_colour_free, &b16))
; // Discard returned pointer(s)
b16 = row565[rowidx]; // Next row to send
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16); // Send it
if (++scanline >= HEIGHT) { // Next scanline...end of screen reached?
if (swap_wait) { // Swap buffers?
back_index = 1 - back_index; // Yes plz
buffer = buffer_save + WIDTH * HEIGHT * back_index;
swap_wait = 0;
}
scanline = 0;
}
// Refresh from front buffer
uint8_t *b8 = buffer_save + WIDTH * scanline; // New src
if (dbuf)
b8 += WIDTH * HEIGHT * (1 - back_index);
rowidx = (rowidx + 1) & 1; // Swap row565[] bufs
b16 = row565[rowidx]; // New dest
uint16_t *p16 = dbuf ? palette[1 - back_index] : palette[0];
for (int i = 0; i < WIDTH; i++)
b16[i] = p16[b8[i]];
}
bool DVIGFX8::begin(void) {
uint8_t *bufptr = getBuffer();
if ((bufptr)) {
gfxptr = this;
row565[0] = (uint16_t *)&bufptr[dbuf ? WIDTH * HEIGHT * 2
: (WIDTH * HEIGHT + 1) & ~1];
row565[1] = row565[0] + WIDTH;
memset(palette, 0, sizeof palette);
// mainloop = mainloop8;
mainloop = dvi_scanbuf_main_16bpp; // in libdvi
dvi0.scanline_callback = scanline_callback_GFX8;
PicoDVI::begin();
// No need to initialize the row565 buffer contents as that memory is
// cleared on canvas alloc, and the initial palette state is also all 0.
uint16_t *b16 = row565[0];
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16);
b16 = row565[1];
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16);
wait_begin = false; // Set core 1 in motion
return true;
}
return false;
}
void DVIGFX8::swap(bool copy_framebuffer, bool copy_palette) {
if (dbuf) {
// Request buffer swap at next frame end, wait for it to happen.
for (swap_wait = 1; swap_wait;)
;
if ((copy_framebuffer)) {
uint32_t bufsize = WIDTH * HEIGHT;
memcpy(buffer_save + bufsize * back_index,
buffer_save + bufsize * (1 - back_index), bufsize);
}
if ((copy_palette)) {
memcpy(palette[back_index], palette[1 - back_index], sizeof(palette[0]));
}
}
}
DVIGFX1::DVIGFX1(const DVIresolution r, const bool d,
const struct dvi_serialiser_cfg &c, vreg_voltage v)
: PicoDVI(dvispec[r].timing, c, v),
GFXcanvas1(dvispec[r].width,
d ? (dvispec[r].height * 2) : dvispec[r].height),
dbuf(d) {
dvi_vertical_repeat = dvispec[r].v_rep;
dvi_monochrome_tmds = true;
HEIGHT = _height = dvispec[r].height;
buffer_save = buffer;
}
DVIGFX1::~DVIGFX1(void) {
buffer = buffer_save; // Restore pointer so canvas destructor works
gfxptr = NULL;
}
static void mainloop1(struct dvi_inst *inst) {
(void)inst; // Avoid compiler warning
((DVIGFX1 *)gfxptr)->_mainloop();
}
void __not_in_flash_func(DVIGFX1::_mainloop)(void) {
for (;;) {
uint8_t *b8 = buffer_save;
if (dbuf)
b8 += ((WIDTH + 7) / 8) * HEIGHT * (1 - back_index);
for (int y = 0; y < HEIGHT; y++) {
const uint32_t *colourbuf =
(const uint32_t *)(b8 + y * ((WIDTH + 7) / 8));
uint32_t *tmdsbuf;
queue_remove_blocking_u32(&dvi0.q_tmds_free, &tmdsbuf);
tmds_encode_1bpp(colourbuf, tmdsbuf, WIDTH);
queue_add_blocking_u32(&dvi0.q_tmds_valid, &tmdsbuf);
}
if (swap_wait) { // Swap buffers?
back_index = 1 - back_index; // Yes plz
buffer = buffer_save + ((WIDTH + 7) / 8) * HEIGHT * back_index;
swap_wait = 0;
}
}
}
bool DVIGFX1::begin(void) {
if ((getBuffer())) {
gfxptr = this;
mainloop = mainloop1;
PicoDVI::begin();
wait_begin = false; // Set core 1 in motion
return true;
}
return false;
}
void DVIGFX1::swap(bool copy_framebuffer) {
if (dbuf) {
// Request buffer swap at next frame end, wait for it to happen.
for (swap_wait = 1; swap_wait;)
;
if ((copy_framebuffer)) {
uint32_t bufsize = ((WIDTH + 7) / 8) * HEIGHT;
memcpy(buffer_save + bufsize * back_index,
buffer_save + bufsize * (1 - back_index), bufsize);
}
}
}
// TEXT MODE IS A WORK IN PROGRESS
#define FONT_CHAR_WIDTH 8 ///< Character block width, pixels
#define FONT_CHAR_HEIGHT 8 ///< Character block height, pixels
#define FONT_N_CHARS 256 ///< Number of symbols in font
#define FONT_FIRST_ASCII 0 ///< Full CP437 is there, start at 0
#include "font_8x8.h"
DVItext1::DVItext1(const DVIresolution r, const struct dvi_serialiser_cfg &c,
vreg_voltage v)
: PicoDVI(dvispec[r].timing, c, v),
GFXcanvas16(dvispec[r].width / 8, dvispec[r].height / 8) {
dvi_vertical_repeat = dvispec[r].v_rep;
dvi_monochrome_tmds = true;
}
DVItext1::~DVItext1(void) { gfxptr = NULL; }
// Character framebuffer is actually a small GFXcanvas16, so...
size_t DVItext1::write(uint8_t c) {
if (c == '\r') { // Carriage return
cursor_x = 0;
} else if ((c == '\n') || (cursor_x >= WIDTH)) { // Newline OR right edge
cursor_x = 0;
if (cursor_y >= (HEIGHT - 1)) { // Vert scroll?
memmove(getBuffer(), getBuffer() + WIDTH, WIDTH * (HEIGHT - 1) * 2);
drawFastHLine(0, HEIGHT - 1, WIDTH, ' '); // Clear bottom line
cursor_y = HEIGHT - 1;
} else {
cursor_y++;
}
}
if ((c != '\r') && (c != '\n')) {
drawPixel(cursor_x, cursor_y, c);
cursor_x++;
}
return 1;
}
// TO DO: alloc this dynamically as part of object (maybe part of canvas)
static uint8_t scanbuf[1280 / 8] __attribute__((aligned(4)));
#ifdef TERM_USE_INTERRUPT
void inline __not_in_flash_func(DVItext1::_prepare_scanline)(uint16_t y) {
uint16_t *row = getBuffer() + (y / FONT_CHAR_HEIGHT) * WIDTH;
uint32_t offset = (y & 7) * FONT_N_CHARS;
// Blit font into 1bpp scanline buffer, then encode scanbuf into tmdsbuf
for (uint16_t x = 0; x < WIDTH; x++) {
// Though this only handles 8-bit character output (e.g. ASCII), the
// character buffer uses 16-bit cells. The LOWER BYTE contains the 8-bit
// character value, while (for now) the UPPER BYTE is either 0 (display
// normally) or 255 (inverse). Eventually the upper byte might be
// switched to bitfields, e.g. bit 0 = inverse, bit 1 = blink, maybe an
// underscore cursor or something, etc. This is slightly wasteful, but
// greatly simplifies keeping character values and attributes always
// moving together (and the display buffer really isn't THAT big).
uint8_t mask = row[x] >> 8;
uint8_t c = (row[x] & 255) - FONT_FIRST_ASCII;
scanbuf[x] = font_8x8[offset + c] ^ mask;
}
uint32_t *tmdsbuf;
queue_remove_blocking_u32(&dvi0.q_tmds_free, &tmdsbuf);
tmds_encode_1bpp((const uint32_t *)scanbuf, tmdsbuf, WIDTH * 8);
queue_add_blocking_u32(&dvi0.q_tmds_valid, &tmdsbuf);
}
void __not_in_flash_func(text1_scanline_callback)(void) {
static uint y = 1;
((DVItext1 *)gfxptr)->_prepare_scanline(y);
y = (y + 1) % (((DVItext1 *)gfxptr)->height() * 8);
}
static void mainlooptext1(struct dvi_inst *inst) {
// Idle func, everything happens in interrupt
for (;;)
delay(1000);
}
#else
// Old way, without interrupt
// This is a little simpler and might stick with it
// since nothing important to do in idle func above.
static void __not_in_flash_func(mainlooptext1)(struct dvi_inst *inst) {
(void)inst; // Avoid compiler warning
((DVItext1 *)gfxptr)->_mainloop();
}
void __not_in_flash_func(DVItext1::_mainloop)(void) {
for (;;) {
for (uint16_t y = 0; y < HEIGHT; y++) {
uint16_t *row = getBuffer() + y * WIDTH;
for (uint8_t y1 = 0; y1 < 8; y1++) {
uint32_t offset = y1 * FONT_N_CHARS;
for (uint16_t x = 0; x < WIDTH; x++) {
// See notes above
uint8_t mask = row[x] >> 8;
uint8_t c = (row[x] & 255) - FONT_FIRST_ASCII;
scanbuf[x] = font_8x8[offset + c] ^ mask;
}
uint32_t *tmdsbuf;
queue_remove_blocking_u32(&dvi0.q_tmds_free, &tmdsbuf);
tmds_encode_1bpp((const uint32_t *)scanbuf, tmdsbuf, WIDTH * 8);
queue_add_blocking_u32(&dvi0.q_tmds_valid, &tmdsbuf);
}
}
}
}
#endif // end TERM_USE_INTERRUPT
bool DVItext1::begin(void) {
if ((getBuffer())) {
fillScreen(' ');
gfxptr = this;
#ifdef TERM_USE_INTERRUPT
dvi0.scanline_callback = text1_scanline_callback;
#endif
mainloop = mainlooptext1;
PicoDVI::begin();
// Must do this AFTER begin because tmdsbuf (accessed in func)
// doesn't exist yet until dvi_init (in begin) is called.
#ifdef TERM_USE_INTERRUPT
_prepare_scanline(0);
#endif
wait_begin = false; // Set core 1 in motion
return true;
}
return false;
}

346
src/PicoDVI.h Normal file
View file

@ -0,0 +1,346 @@
// SPDX-FileCopyrightText: 2023 P Burgess for Adafruit Industries
//
// SPDX-License-Identifier: BSD-3-Clause
/*!
* @file PicoDVI.h
*
* Arduino-and-Adafruit-GFX wrapper around Luke Wren's PicoDVI library.
*/
#pragma once
#include "../software/include/common_dvi_pin_configs.h"
#include "hardware/vreg.h" // In Pico SDK
#include "libdvi/dvi.h"
#include "pico/stdlib.h" // In Pico SDK
#include <Adafruit_GFX.h>
/** A set list of resolutions keeps things manageable for users,
avoids some possibly-incompatible argument combos to constructors. */
enum DVIresolution {
DVI_RES_320x240p60 = 0,
DVI_RES_400x240p60,
DVI_RES_400x240p30, // Reduced refresh rate, less overclock required
DVI_RES_640x480p60,
DVI_RES_800x480p60,
DVI_RES_800x480p30, // Reduced refresh rate, less overclock required
DVI_RES_640x240p60, // "Tall" pixels, e.g. for 80-column text mode
DVI_RES_800x240p60, // Same, 100-column
DVI_RES_800x240p30, // Reduced refresh rate, less overclock required
DVI_RES_1280x720p30 // Experimenting, not working, plz don't use
};
extern uint8_t dvi_vertical_repeat; ///< In libdvi/dvi.c
extern bool dvi_monochrome_tmds; ///< In libdvi/dvi.c
/*!
@brief A base class used by some of the framebuffer classes that follow.
Wraps essential functionality of the underlying PicoDVI library.
Maybe only rarely (if ever) needed in Arduino sketch code, but
it's here if a project requires working at a lower level to
provide features not present in the framebuffer modes.
*/
class PicoDVI {
public:
/*!
@brief PicoDVI constructor. Instantiates a new PicoDVI object (must
follow with a begin() call to alloc buffers and init hardware).
@param t Pointer to dvi_timing struct, one of the supported
resolutions declared in libdvi/dvi_timing.c. Default if not
specified is 640x480p60.
@param c Pointer to dvi_serialiser_cfg struct, which defines pinout.
These are in ../software/include/common_dvi_pin_configs.h.
Default if not specified is adafruit_feather_dvi_cfg.
@param v Core voltage; higher resolution modes require faster
overclocking and potentially higher voltages. This is an
enumeration declared in Pico SDK hardware/vreg.h. Normal
system operation would be 1.1V, default here if not
specified is 1.2V.
*/
PicoDVI(const struct dvi_timing &t = dvi_timing_640x480p_60hz,
const struct dvi_serialiser_cfg &c = adafruit_feather_dvi_cfg,
vreg_voltage v = VREG_VOLTAGE_1_20);
~PicoDVI(void);
/*!
@brief Internal function used during DVI initialization. User code
should not touch this, but it does need to remain public for
C init code to access protected elements.
*/
void _setup(void);
protected:
/*!
@brief Initialize hardware and start video thread.
*/
void begin(void);
vreg_voltage voltage; ///< Core voltage
struct dvi_inst dvi0; ///< DVI instance
void (*mainloop)(dvi_inst *) = NULL; ///< Video mode loop handler
volatile bool wait_begin = true; ///< For synchronizing cores
};
/*!
@brief DVIGFX16 provides a 16-bit color (RGB565) Adafruit_GFX-compatible
framebuffer. Double-buffering is NOT provided even as an option,
there simply isn't adequate RAM.
*/
class DVIGFX16 : public PicoDVI, public GFXcanvas16 {
public:
/*!
@brief DVIGFX16 constructor.
@param res Video mode, from the DVIresolution enumeration. This will
usually be 320x240 or 400x240, there just isn't RAM to
handle anything larger as a pixel-accessible framebuffer.
@param c Pointer to dvi_serialiser_cfg struct, which defines pinout.
These are in ../software/include/common_dvi_pin_configs.h.
Default if not specified is adafruit_feather_dvi_cfg.
@param v Core voltage; higher resolution modes require faster
overclocking and potentially higher voltages. This is an
enumeration declared in Pico SDK hardware/vreg.h. Normal
system operation would be 1.1V, default here if not
specified is 1.2V.
*/
DVIGFX16(const DVIresolution res = DVI_RES_320x240p60,
const struct dvi_serialiser_cfg &c = adafruit_feather_dvi_cfg,
vreg_voltage v = VREG_VOLTAGE_1_20);
~DVIGFX16(void);
/*!
@brief Initialize DVI output for 16-bit color display.
@return true on success, false otherwise (buffer allocation usually).
*/
bool begin(void);
/*!
@brief Convert a 24-bit RGB color (as three 8-bit values) to a packed
16-bit "RGB565" color, as native to PicoDVI and Adafruit_GFX.
@param red Red component, 0-255.
@param green Green component, 0-255.
@param blue Blue component, 0-255.
@return Nearest 16-bit RGB565 equivalent.
*/
uint16_t color565(uint8_t red, uint8_t green, uint8_t blue) {
return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
/*!
@brief Scanline-processing function used internally during video
generation. User sketch code should never call this, but it
needs to be public so that C code can access it.
*/
void _scanline_callback(void);
protected:
uint16_t scanline = 2; ///< First 2 scanlines are set up before DVI start
};
/*!
@brief DVIGFX8 provides a 8-bit color-mapped Adafruit_GFX-compatible
framebuffer, with optional double-buffering.
*/
class DVIGFX8 : public PicoDVI, public GFXcanvas8 {
public:
/*!
@brief DVIGFX8 constructor.
@param res Video mode, from the DVIresolution enumeration.
@param dbuf Double-buffered flag. If true, two 8-bit framebuffers
(each with associated color palette) are allocated, code
can alternate between these for flicker-free animation.
@param c Pointer to dvi_serialiser_cfg struct, which defines pinout.
These are in ../software/include/common_dvi_pin_configs.h.
Default if not specified is adafruit_feather_dvi_cfg.
@param v Core voltage; higher resolution modes require faster
overclocking and potentially higher voltages. This is an
enumeration declared in Pico SDK hardware/vreg.h. Normal
system operation would be 1.1V, default here if not
specified is 1.2V.
*/
DVIGFX8(const DVIresolution res = DVI_RES_320x240p60, const bool dbuf = false,
const struct dvi_serialiser_cfg &c = adafruit_feather_dvi_cfg,
vreg_voltage v = VREG_VOLTAGE_1_20);
~DVIGFX8(void);
/*!
@brief Initialize DVI output for 8-bit colormapped display.
@return true on success, false otherwise (buffer allocation usually).
*/
bool begin(void);
/*!
@brief Get base address of 'back' palette (or single palette in
single-buffered mode), for code that wants to access this.
directly rather than through setColor() and getColor().
@return uint16_t* pointer to 256-entry palette (RGB565 colors).
*/
uint16_t *getPalette(void) { return palette[back_index]; }
/*!
@brief Set one color in the 'back' color palette (or single palette in
single-buffered mode).
@param idx Color index to set, 0-255.
@param color 16-bit "RGB565" color.
*/
void setColor(uint8_t idx, uint16_t color) {
palette[back_index][idx] = color;
}
/*!
@brief Set one color in the 'back' color palette (or single palette in
single-buffered mode). Accepts three 8-bit values, but COLOR
WILL BE QUANTIZED TO 16-BIT RGB565 because that's what PicoDVI
uses. Original 24-bit value is LOST and cannot be accurately
read back by getColor().
@param idx Color index to set, 0-255.
@param red Red component, 0-255.
@param green Green component, 0-255.
@param blue Blue component, 0-255.
*/
void setColor(uint8_t idx, uint8_t red, uint8_t green, uint8_t blue) {
palette[back_index][idx] =
((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
/*!
@brief Read one color from the 'back' color palette (or single palette
in single-buffered mode).
@param idx Color index to read, 0-255.
@return 16-bit "RGB565" color.
*/
uint16_t getColor(uint8_t idx) { return palette[back_index][idx]; }
/*!
@brief Swap front/back framebuffers on next vertical sync.
@param copy_framebuffer If true, contents of new 'front' framebuffer
will be duplicated into new 'back' buffer for
updates (useful for code that modifies screen
frame-to-frame, rather than complete regen).
@param copy_palette If true, copies 'front' palette to 'back'.
This is obscure; most code will set up a global
palette once, swap-with-copy once (so front
and back are both using same palette), and not
touch this...but it's there if palette-
animating code needs it.
*/
void swap(bool copy_framebuffer = false, bool copy_palette = false);
/*!
@brief Scanline-processing function used internally during video
generation. User sketch code should never call this, but it
needs to be public so that C code can access it.
*/
void _scanline_callback(void);
protected:
uint16_t palette[2][256]; ///< [2] for double-buffering
uint16_t *row565[2]; ///< 2 scanlines of 16-bit RGB565 data
uint16_t scanline = 2; ///< 1st 2 scanlines loaded before DVI start
uint8_t rowidx = 1; ///< Alternate 0/1 for which row565[] is active
bool dbuf = false; ///< True if double-buffered
uint8_t *buffer_save; ///< Original canvas buffer pointer
uint8_t back_index = 0; ///< Which of 2 buffers receives draw ops
volatile bool swap_wait = 0; ///< For synchronizing fromt/back buffer swap
};
/*!
@brief DVIGFX1 provides a 1-bit monochrome (black, white) Adafruit_GFX-
compatible framebuffer, with optional double-buffering.
*/
class DVIGFX1 : public PicoDVI, public GFXcanvas1 {
public:
/*!
@brief DVIGFX1 constructor.
@param res Video mode, from the DVIresolution enumeration. This will
usually be 640x480 or 800x480. Higher settings are not
currently supported.
@param dbuf Double-buffered flag. If true, two 1-bit framebuffers are
allocated, code can alternate between these for flicker-
free animation.
@param c Pointer to dvi_serialiser_cfg struct, which defines pinout.
These are in ../software/include/common_dvi_pin_configs.h.
Default if not specified is adafruit_feather_dvi_cfg.
@param v Core voltage; higher resolution modes require faster
overclocking and potentially higher voltages. This is an
enumeration declared in Pico SDK hardware/vreg.h. Normal
system operation would be 1.1V, default here if not
specified is 1.2V.
*/
DVIGFX1(const DVIresolution res = DVI_RES_640x480p60, const bool dbuf = false,
const struct dvi_serialiser_cfg &c = adafruit_feather_dvi_cfg,
vreg_voltage v = VREG_VOLTAGE_1_20);
~DVIGFX1(void);
/*!
@brief Initialize DVI output for 1-bit monochrome graphics display.
@return true on success, false otherwise (buffer allocation usually).
*/
bool begin(void);
/*!
@brief Swap front/back framebuffers on next vertical sync.
@param copy_framebuffer If true, contents of new 'front' framebuffer
will be duplicated into new 'back' buffer for
updates (useful for code that modifies screen
frame-to-frame, rather than complete regen).
*/
void swap(bool copy_framebuffer = false);
/*!
@brief Video main loop handler invoked by underlying PicoDVI code.
User sketch code should never call this, but it needs to be
public so that C code can access it.
*/
void _mainloop(void);
private:
bool dbuf = false; // True if double-buffered
uint8_t *buffer_save; // Original canvas buffer pointer
uint8_t back_index = 0; // Which of 2 buffers receives draw ops
volatile bool swap_wait = 0; // For syncronizing front/back buffer swap
};
/*!
@brief DVItext1 provides a 1-bit monochrome (black, white) text-only
display. print() and related calls to this display will vertical
scroll when needed. It's very basic and does not provide any ANSI-
like terminal functionality.
*/
class DVItext1 : public PicoDVI, public GFXcanvas16 {
public:
/*!
@brief DVItext1 constructor.
@param res Video mode, from the DVIresolution enumeration. This is the
pixel resolution; text cells are 8x8 pixels and thus the
number of rows and columns will be less (e.g. 640x240 mode
yields 80x30 characters).
@param c Pointer to dvi_serialiser_cfg struct, which defines pinout.
These are in ../software/include/common_dvi_pin_configs.h.
Default if not specified is adafruit_feather_dvi_cfg.
@param v Core voltage; higher resolution modes require faster
overclocking and potentially higher voltages. This is an
enumeration declared in Pico SDK hardware/vreg.h. Normal
system operation would be 1.1V, default here if not
specified is 1.2V.
*/
DVItext1(const DVIresolution res = DVI_RES_640x240p60,
const struct dvi_serialiser_cfg &c = adafruit_feather_dvi_cfg,
vreg_voltage v = VREG_VOLTAGE_1_20);
~DVItext1(void);
/*!
@brief Initialize DVI output for text display.
@return true on success, false otherwise (buffer allocation usually).
*/
bool begin(void);
/*!
@brief Base character-writing function, allows Arduino print() and
println() calls to work with display. Text will scroll upward
as needed.
@param c ASCII character to write.
@return Number of characters written; always 1.
*/
size_t write(uint8_t c);
/*!
@brief Scanline-processing function used internally during video
generation. User sketch code should never call this, but it
needs to be public so that C code can access it.
@param y Scanline index; 0 to height-1.
*/
void _prepare_scanline(uint16_t y);
/*!
@brief Video main loop handler invoked by underlying PicoDVI code.
User sketch code should never call this, but it needs to be
public so that C code can access it.
*/
void _mainloop(void);
protected:
};

5
src/Readme.md Normal file
View file

@ -0,0 +1,5 @@
Arduino Wrapper for PicoDVI Library
===================================
This directory does not exist in the original PicoDVI repository.
It contains files and soft link(s) to provide Arduino-like functionality.

177
src/font_8x8.h Normal file
View file

@ -0,0 +1,177 @@
// Adapted from IBM VGA 8x8 font, thx to Stec Dose github.com/spacerace/romfont
// To simplify the scanline conversion function, font data has been reordered
// so all the row 0 bytes for each of 256 chars appear first, then all row 1
// bytes for each, row 2, etc. Individual character data is not contiguous.
uint8_t __not_in_flash_func(font_8x8)[] = {
0x00, 0x7E, 0x7E, 0x6C, 0x10, 0x38, 0x10, 0x00, 0xFF, 0x00, 0xFF, 0x0F,
0x3C, 0x3F, 0x7F, 0x99, 0x80, 0x02, 0x18, 0x66, 0x7F, 0x3E, 0x00, 0x18,
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x6C, 0x6C,
0x30, 0x00, 0x38, 0x60, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
0x7C, 0x30, 0x78, 0x78, 0x1C, 0xFC, 0x38, 0xFC, 0x78, 0x78, 0x00, 0x00,
0x18, 0x00, 0x60, 0x78, 0x7C, 0x30, 0xFC, 0x3C, 0xF8, 0xFE, 0xFE, 0x3C,
0xCC, 0x78, 0x1E, 0xE6, 0xF0, 0xC6, 0xC6, 0x38, 0xFC, 0x78, 0xFC, 0x78,
0xFC, 0xCC, 0xCC, 0xC6, 0xC6, 0xCC, 0xFE, 0x78, 0xC0, 0x78, 0x10, 0x00,
0x30, 0x00, 0xE0, 0x00, 0x1C, 0x00, 0x38, 0x00, 0xE0, 0x30, 0x0C, 0xE0,
0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1C, 0x18, 0xE0, 0x76, 0x00, 0x78, 0x00, 0x1C, 0x7E,
0xCC, 0xE0, 0x30, 0x00, 0x7E, 0xCC, 0xE0, 0xCC, 0x7C, 0xE0, 0xC6, 0x30,
0x1C, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x78, 0x00, 0x00, 0xC3, 0xCC, 0x18,
0x38, 0xCC, 0xF8, 0x0E, 0x1C, 0x38, 0x00, 0x00, 0x00, 0xFC, 0x3C, 0x38,
0x30, 0x00, 0x00, 0xC3, 0xC3, 0x18, 0x00, 0x00, 0x22, 0x55, 0xDB, 0x18,
0x18, 0x18, 0x36, 0x00, 0x00, 0x36, 0x36, 0x00, 0x36, 0x36, 0x18, 0x00,
0x18, 0x18, 0x00, 0x18, 0x00, 0x18, 0x18, 0x36, 0x36, 0x00, 0x36, 0x00,
0x36, 0x00, 0x36, 0x18, 0x36, 0x00, 0x00, 0x36, 0x18, 0x00, 0x00, 0x36,
0x18, 0x18, 0x00, 0xFF, 0x00, 0xF0, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFC, 0x00, 0x00, 0x00, 0xFC, 0x38, 0x38, 0x1C, 0x00, 0x06, 0x38, 0x78,
0x00, 0x30, 0x60, 0x18, 0x0E, 0x18, 0x30, 0x00, 0x38, 0x00, 0x00, 0x0F,
0x78, 0x70, 0x00, 0x00, 0x00, 0x81, 0xFF, 0xFE, 0x38, 0x7C, 0x10, 0x00,
0xFF, 0x3C, 0xC3, 0x07, 0x66, 0x33, 0x63, 0x5A, 0xE0, 0x0E, 0x3C, 0x66,
0xDB, 0x63, 0x00, 0x3C, 0x3C, 0x18, 0x18, 0x30, 0x00, 0x24, 0x18, 0xFF,
0x00, 0x78, 0x6C, 0x6C, 0x7C, 0xC6, 0x6C, 0x60, 0x30, 0x30, 0x66, 0x30,
0x00, 0x00, 0x00, 0x0C, 0xC6, 0x70, 0xCC, 0xCC, 0x3C, 0xC0, 0x60, 0xCC,
0xCC, 0xCC, 0x30, 0x30, 0x30, 0x00, 0x30, 0xCC, 0xC6, 0x78, 0x66, 0x66,
0x6C, 0x62, 0x62, 0x66, 0xCC, 0x30, 0x0C, 0x66, 0x60, 0xEE, 0xE6, 0x6C,
0x66, 0xCC, 0x66, 0xCC, 0xB4, 0xCC, 0xCC, 0xC6, 0xC6, 0xCC, 0xC6, 0x60,
0x60, 0x18, 0x38, 0x00, 0x30, 0x00, 0x60, 0x00, 0x0C, 0x00, 0x6C, 0x00,
0x60, 0x00, 0x00, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x30, 0xDC, 0x10,
0xCC, 0xCC, 0x00, 0xC3, 0x00, 0x00, 0x30, 0x00, 0xC3, 0x00, 0x00, 0x00,
0xC6, 0x00, 0x38, 0x30, 0x00, 0x00, 0x6C, 0xCC, 0xCC, 0xE0, 0xCC, 0xE0,
0xCC, 0x18, 0x00, 0x18, 0x6C, 0xCC, 0xCC, 0x1B, 0x00, 0x00, 0x1C, 0x1C,
0xF8, 0x00, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0x18, 0x33, 0xCC,
0x88, 0xAA, 0x77, 0x18, 0x18, 0x18, 0x36, 0x00, 0x00, 0x36, 0x36, 0x00,
0x36, 0x36, 0x18, 0x00, 0x18, 0x18, 0x00, 0x18, 0x00, 0x18, 0x18, 0x36,
0x36, 0x00, 0x36, 0x00, 0x36, 0x00, 0x36, 0x18, 0x36, 0x00, 0x00, 0x36,
0x18, 0x00, 0x00, 0x36, 0x18, 0x18, 0x00, 0xFF, 0x00, 0xF0, 0x0F, 0xFF,
0x00, 0x78, 0xFC, 0xFE, 0xCC, 0x00, 0x66, 0x76, 0x30, 0x6C, 0x6C, 0x30,
0x00, 0x0C, 0x60, 0xCC, 0xFC, 0x30, 0x30, 0x30, 0x1B, 0x18, 0x30, 0x76,
0x6C, 0x00, 0x00, 0x0C, 0x6C, 0x18, 0x00, 0x00, 0x00, 0xA5, 0xDB, 0xFE,
0x7C, 0x38, 0x38, 0x18, 0xE7, 0x66, 0x99, 0x0F, 0x66, 0x3F, 0x7F, 0x3C,
0xF8, 0x3E, 0x7E, 0x66, 0xDB, 0x38, 0x00, 0x7E, 0x7E, 0x18, 0x0C, 0x60,
0xC0, 0x66, 0x3C, 0xFF, 0x00, 0x78, 0x6C, 0xFE, 0xC0, 0xCC, 0x38, 0xC0,
0x60, 0x18, 0x3C, 0x30, 0x00, 0x00, 0x00, 0x18, 0xCE, 0x30, 0x0C, 0x0C,
0x6C, 0xF8, 0xC0, 0x0C, 0xCC, 0xCC, 0x30, 0x30, 0x60, 0xFC, 0x18, 0x0C,
0xDE, 0xCC, 0x66, 0xC0, 0x66, 0x68, 0x68, 0xC0, 0xCC, 0x30, 0x0C, 0x6C,
0x60, 0xFE, 0xF6, 0xC6, 0x66, 0xCC, 0x66, 0xE0, 0x30, 0xCC, 0xCC, 0xC6,
0x6C, 0xCC, 0x8C, 0x60, 0x30, 0x18, 0x6C, 0x00, 0x18, 0x78, 0x60, 0x78,
0x0C, 0x78, 0x60, 0x76, 0x6C, 0x70, 0x0C, 0x66, 0x30, 0xCC, 0xF8, 0x78,
0xDC, 0x76, 0xDC, 0x7C, 0x7C, 0xCC, 0xCC, 0xC6, 0xC6, 0xCC, 0xFC, 0x30,
0x18, 0x30, 0x00, 0x38, 0xC0, 0x00, 0x78, 0x3C, 0x78, 0x78, 0x78, 0x78,
0x3C, 0x78, 0x78, 0x70, 0x38, 0x70, 0x6C, 0x00, 0xFC, 0x7F, 0xCC, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xCC, 0x7E, 0x64, 0x78, 0xCC, 0x18,
0x78, 0x70, 0x00, 0x00, 0x00, 0xCC, 0x6C, 0x6C, 0x30, 0x00, 0x00, 0xCC,
0xCC, 0x00, 0x66, 0x66, 0x22, 0x55, 0xDB, 0x18, 0x18, 0xF8, 0x36, 0x00,
0xF8, 0xF6, 0x36, 0xFE, 0xF6, 0x36, 0xF8, 0x00, 0x18, 0x18, 0x00, 0x18,
0x00, 0x18, 0x1F, 0x36, 0x37, 0x3F, 0xF7, 0xFF, 0x37, 0xFF, 0xF7, 0xFF,
0x36, 0xFF, 0x00, 0x36, 0x1F, 0x1F, 0x00, 0x36, 0xFF, 0x18, 0x00, 0xFF,
0x00, 0xF0, 0x0F, 0xFF, 0x76, 0xCC, 0xCC, 0x6C, 0x60, 0x7E, 0x66, 0xDC,
0x78, 0xC6, 0xC6, 0x18, 0x7E, 0x7E, 0xC0, 0xCC, 0x00, 0xFC, 0x18, 0x60,
0x1B, 0x18, 0x00, 0xDC, 0x6C, 0x00, 0x00, 0x0C, 0x6C, 0x30, 0x3C, 0x00,
0x00, 0x81, 0xFF, 0xFE, 0xFE, 0xFE, 0x7C, 0x3C, 0xC3, 0x42, 0xBD, 0x7D,
0x66, 0x30, 0x63, 0xE7, 0xFE, 0xFE, 0x18, 0x66, 0x7B, 0x6C, 0x00, 0x18,
0x18, 0x18, 0xFE, 0xFE, 0xC0, 0xFF, 0x7E, 0x7E, 0x00, 0x30, 0x00, 0x6C,
0x78, 0x18, 0x76, 0x00, 0x60, 0x18, 0xFF, 0xFC, 0x00, 0xFC, 0x00, 0x30,
0xDE, 0x30, 0x38, 0x38, 0xCC, 0x0C, 0xF8, 0x18, 0x78, 0x7C, 0x00, 0x00,
0xC0, 0x00, 0x0C, 0x18, 0xDE, 0xCC, 0x7C, 0xC0, 0x66, 0x78, 0x78, 0xC0,
0xFC, 0x30, 0x0C, 0x78, 0x60, 0xFE, 0xDE, 0xC6, 0x7C, 0xCC, 0x7C, 0x70,
0x30, 0xCC, 0xCC, 0xD6, 0x38, 0x78, 0x18, 0x60, 0x18, 0x18, 0xC6, 0x00,
0x00, 0x0C, 0x7C, 0xCC, 0x7C, 0xCC, 0xF0, 0xCC, 0x76, 0x30, 0x0C, 0x6C,
0x30, 0xFE, 0xCC, 0xCC, 0x66, 0xCC, 0x76, 0xC0, 0x30, 0xCC, 0xCC, 0xD6,
0x6C, 0xCC, 0x98, 0xE0, 0x00, 0x1C, 0x00, 0x6C, 0xCC, 0xCC, 0xCC, 0x06,
0x0C, 0x0C, 0x0C, 0xC0, 0x66, 0xCC, 0xCC, 0x30, 0x18, 0x30, 0xC6, 0x78,
0x60, 0x0C, 0xFE, 0x78, 0x78, 0x78, 0xCC, 0xCC, 0xCC, 0x66, 0xCC, 0xC0,
0xF0, 0xFC, 0xFA, 0x3C, 0x0C, 0x30, 0x78, 0xCC, 0xF8, 0xEC, 0x3E, 0x38,
0x60, 0xFC, 0xFC, 0xDE, 0xDB, 0x18, 0xCC, 0x33, 0x88, 0xAA, 0xEE, 0x18,
0x18, 0x18, 0x36, 0x00, 0x18, 0x06, 0x36, 0x06, 0x06, 0x36, 0x18, 0x00,
0x18, 0x18, 0x00, 0x18, 0x00, 0x18, 0x18, 0x36, 0x30, 0x30, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x36, 0x18, 0x18, 0x00, 0x36,
0x18, 0x18, 0x00, 0xFF, 0x00, 0xF0, 0x0F, 0xFF, 0xDC, 0xF8, 0xC0, 0x6C,
0x30, 0xD8, 0x66, 0x18, 0xCC, 0xFE, 0xC6, 0x7C, 0xDB, 0xDB, 0xF8, 0xCC,
0xFC, 0x30, 0x30, 0x30, 0x18, 0x18, 0xFC, 0x00, 0x38, 0x18, 0x00, 0x0C,
0x6C, 0x60, 0x3C, 0x00, 0x00, 0xBD, 0xC3, 0x7C, 0x7C, 0xFE, 0xFE, 0x3C,
0xC3, 0x42, 0xBD, 0xCC, 0x3C, 0x30, 0x63, 0xE7, 0xF8, 0x3E, 0x18, 0x66,
0x1B, 0x6C, 0x7E, 0x7E, 0x18, 0x7E, 0x0C, 0x60, 0xC0, 0x66, 0xFF, 0x3C,
0x00, 0x30, 0x00, 0xFE, 0x0C, 0x30, 0xDC, 0x00, 0x60, 0x18, 0x3C, 0x30,
0x00, 0x00, 0x00, 0x60, 0xF6, 0x30, 0x60, 0x0C, 0xFE, 0x0C, 0xCC, 0x30,
0xCC, 0x0C, 0x00, 0x00, 0x60, 0x00, 0x18, 0x30, 0xDE, 0xFC, 0x66, 0xC0,
0x66, 0x68, 0x68, 0xCE, 0xCC, 0x30, 0xCC, 0x6C, 0x62, 0xD6, 0xCE, 0xC6,
0x60, 0xDC, 0x6C, 0x1C, 0x30, 0xCC, 0xCC, 0xFE, 0x38, 0x30, 0x32, 0x60,
0x0C, 0x18, 0x00, 0x00, 0x00, 0x7C, 0x66, 0xC0, 0xCC, 0xFC, 0x60, 0xCC,
0x66, 0x30, 0x0C, 0x78, 0x30, 0xFE, 0xCC, 0xCC, 0x66, 0xCC, 0x66, 0x78,
0x30, 0xCC, 0xCC, 0xFE, 0x38, 0xCC, 0x30, 0x30, 0x18, 0x30, 0x00, 0xC6,
0x78, 0xCC, 0xFC, 0x3E, 0x7C, 0x7C, 0x7C, 0xC0, 0x7E, 0xFC, 0xFC, 0x30,
0x18, 0x30, 0xFE, 0xCC, 0x78, 0x7F, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
0xCC, 0x66, 0xCC, 0xC0, 0x60, 0x30, 0xC6, 0x18, 0x7C, 0x30, 0xCC, 0xCC,
0xCC, 0xFC, 0x00, 0x00, 0xC0, 0xC0, 0x0C, 0x33, 0x37, 0x18, 0x66, 0x66,
0x22, 0x55, 0xDB, 0x18, 0xF8, 0xF8, 0xF6, 0xFE, 0xF8, 0xF6, 0x36, 0xF6,
0xFE, 0xFE, 0xF8, 0xF8, 0x1F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0x1F, 0x37,
0x3F, 0x37, 0xFF, 0xF7, 0x37, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F,
0x1F, 0x1F, 0x3F, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xF0, 0x0F, 0x00,
0xC8, 0xCC, 0xC0, 0x6C, 0x60, 0xD8, 0x66, 0x18, 0xCC, 0xC6, 0x6C, 0xCC,
0xDB, 0xDB, 0xC0, 0xCC, 0x00, 0x30, 0x60, 0x18, 0x18, 0x18, 0x00, 0x76,
0x00, 0x18, 0x18, 0xEC, 0x6C, 0x78, 0x3C, 0x00, 0x00, 0x99, 0xE7, 0x38,
0x38, 0x7C, 0x7C, 0x18, 0xE7, 0x66, 0x99, 0xCC, 0x18, 0x70, 0x67, 0x3C,
0xE0, 0x0E, 0x7E, 0x00, 0x1B, 0x38, 0x7E, 0x3C, 0x18, 0x3C, 0x18, 0x30,
0xFE, 0x24, 0xFF, 0x18, 0x00, 0x00, 0x00, 0x6C, 0xF8, 0x66, 0xCC, 0x00,
0x30, 0x30, 0x66, 0x30, 0x30, 0x00, 0x30, 0xC0, 0xE6, 0x30, 0xCC, 0xCC,
0x0C, 0xCC, 0xCC, 0x30, 0xCC, 0x18, 0x30, 0x30, 0x30, 0xFC, 0x30, 0x00,
0xC0, 0xCC, 0x66, 0x66, 0x6C, 0x62, 0x60, 0x66, 0xCC, 0x30, 0xCC, 0x66,
0x66, 0xC6, 0xC6, 0x6C, 0x60, 0x78, 0x66, 0xCC, 0x30, 0xCC, 0x78, 0xEE,
0x6C, 0x30, 0x66, 0x60, 0x06, 0x18, 0x00, 0x00, 0x00, 0xCC, 0x66, 0xCC,
0xCC, 0xC0, 0x60, 0x7C, 0x66, 0x30, 0xCC, 0x6C, 0x30, 0xD6, 0xCC, 0xCC,
0x7C, 0x7C, 0x60, 0x0C, 0x34, 0xCC, 0x78, 0xFE, 0x6C, 0x7C, 0x64, 0x30,
0x18, 0x30, 0x00, 0xC6, 0x18, 0xCC, 0xC0, 0x66, 0xCC, 0xCC, 0xCC, 0x78,
0x60, 0xC0, 0xC0, 0x30, 0x18, 0x30, 0xC6, 0xFC, 0x60, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xCC, 0x7C, 0x3C, 0xCC, 0x7E, 0xE6, 0xFC, 0xCF, 0x18,
0xCC, 0x30, 0xCC, 0xCC, 0xCC, 0xDC, 0x7E, 0x7C, 0xCC, 0xC0, 0x0C, 0x66,
0x6F, 0x18, 0x33, 0xCC, 0x88, 0xAA, 0x77, 0x18, 0x18, 0x18, 0x36, 0x36,
0x18, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18,
0x00, 0x18, 0x18, 0x36, 0x00, 0x36, 0x00, 0x36, 0x36, 0x00, 0x36, 0x00,
0x00, 0x18, 0x36, 0x00, 0x00, 0x18, 0x36, 0x36, 0x18, 0x00, 0x18, 0xFF,
0xFF, 0xF0, 0x0F, 0x00, 0xDC, 0xF8, 0xC0, 0x6C, 0xCC, 0xD8, 0x7C, 0x18,
0x78, 0x6C, 0x6C, 0xCC, 0x7E, 0x7E, 0x60, 0xCC, 0xFC, 0x00, 0x00, 0x00,
0x18, 0xD8, 0x30, 0xDC, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x3C, 0x00,
0x00, 0x81, 0xFF, 0x10, 0x10, 0x38, 0x38, 0x00, 0xFF, 0x3C, 0xC3, 0xCC,
0x7E, 0xF0, 0xE6, 0x5A, 0x80, 0x02, 0x3C, 0x66, 0x1B, 0xCC, 0x7E, 0x18,
0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x6C,
0x30, 0xC6, 0x76, 0x00, 0x18, 0x60, 0x00, 0x00, 0x30, 0x00, 0x30, 0x80,
0x7C, 0xFC, 0xFC, 0x78, 0x1E, 0x78, 0x78, 0x30, 0x78, 0x70, 0x30, 0x30,
0x18, 0x00, 0x60, 0x30, 0x78, 0xCC, 0xFC, 0x3C, 0xF8, 0xFE, 0xF0, 0x3E,
0xCC, 0x78, 0x78, 0xE6, 0xFE, 0xC6, 0xC6, 0x38, 0xF0, 0x1C, 0xE6, 0x78,
0x78, 0xFC, 0x30, 0xC6, 0xC6, 0x78, 0xFE, 0x78, 0x02, 0x78, 0x00, 0x00,
0x00, 0x76, 0xDC, 0x78, 0x76, 0x78, 0xF0, 0x0C, 0xE6, 0x78, 0xCC, 0xE6,
0x78, 0xC6, 0xCC, 0x78, 0x60, 0x0C, 0xF0, 0xF8, 0x18, 0x76, 0x30, 0x6C,
0xC6, 0x0C, 0xFC, 0x1C, 0x18, 0xE0, 0x00, 0xFE, 0x0C, 0x7E, 0x78, 0x3F,
0x7E, 0x7E, 0x7E, 0x0C, 0x3C, 0x78, 0x78, 0x78, 0x3C, 0x78, 0xC6, 0xCC,
0xFC, 0x7F, 0xCE, 0x78, 0x78, 0x78, 0x7E, 0x7E, 0x0C, 0x18, 0x78, 0x18,
0xFC, 0x30, 0xC6, 0xD8, 0x7E, 0x78, 0x78, 0x7E, 0xCC, 0xCC, 0x00, 0x00,
0x78, 0x00, 0x00, 0xCC, 0xCF, 0x18, 0x00, 0x00, 0x22, 0x55, 0xDB, 0x18,
0x18, 0x18, 0x36, 0x36, 0x18, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x18,
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x36, 0x00, 0x36, 0x00, 0x36,
0x36, 0x00, 0x36, 0x00, 0x00, 0x18, 0x36, 0x00, 0x00, 0x18, 0x36, 0x36,
0x18, 0x00, 0x18, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0x76, 0xC0, 0xC0, 0x6C,
0xFC, 0x70, 0x60, 0x18, 0x30, 0x38, 0xEE, 0x78, 0x00, 0x60, 0x38, 0xCC,
0x00, 0xFC, 0xFC, 0xFC, 0x18, 0xD8, 0x30, 0x00, 0x00, 0x00, 0x00, 0x3C,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x7C, 0x7C, 0x00,
0xFF, 0x00, 0xFF, 0x78, 0x18, 0xE0, 0xC0, 0x99, 0x00, 0x00, 0x18, 0x00,
0x00, 0x78, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF8, 0x00, 0x00, 0x18, 0x00, 0x30, 0xC7, 0x70, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00, 0x00, 0x00,
0x88, 0xAA, 0xEE, 0x18, 0x18, 0x18, 0x36, 0x36, 0x18, 0x36, 0x36, 0x36,
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x36,
0x00, 0x36, 0x00, 0x36, 0x36, 0x00, 0x36, 0x00, 0x00, 0x18, 0x36, 0x00,
0x00, 0x18, 0x36, 0x36, 0x18, 0x00, 0x18, 0xFF, 0xFF, 0xF0, 0x0F, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xFC, 0x00, 0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x70, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00};

33
src/libdvi/CMakeLists.txt Normal file
View file

@ -0,0 +1,33 @@
# Note we are using INTERFACE so that the library can be configured per-app
# with compile-time defines
add_library(libdvi INTERFACE)
target_sources(libdvi INTERFACE
${CMAKE_CURRENT_LIST_DIR}/dvi.c
${CMAKE_CURRENT_LIST_DIR}/dvi.h
${CMAKE_CURRENT_LIST_DIR}/dvi_config_defs.h
${CMAKE_CURRENT_LIST_DIR}/dvi_serialiser.c
${CMAKE_CURRENT_LIST_DIR}/dvi_serialiser.h
${CMAKE_CURRENT_LIST_DIR}/dvi_timing.c
${CMAKE_CURRENT_LIST_DIR}/dvi_timing.h
${CMAKE_CURRENT_LIST_DIR}/tmds_encode.S
${CMAKE_CURRENT_LIST_DIR}/tmds_encode.c
${CMAKE_CURRENT_LIST_DIR}/tmds_encode.h
${CMAKE_CURRENT_LIST_DIR}/tmds_table.h
${CMAKE_CURRENT_LIST_DIR}/tmds_table_fullres.h
${CMAKE_CURRENT_LIST_DIR}/util_queue_u32_inline.h
)
target_include_directories(libdvi INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(libdvi INTERFACE
pico_base_headers
pico_util
hardware_dma
hardware_interp
hardware_pio
hardware_pwm
)
pico_generate_pio_header(libdvi ${CMAKE_CURRENT_LIST_DIR}/dvi_serialiser.pio)
pico_generate_pio_header(libdvi ${CMAKE_CURRENT_LIST_DIR}/tmds_encode_1bpp.pio)

259
src/libdvi/dvi.c Normal file
View file

@ -0,0 +1,259 @@
#include <stdlib.h>
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "dvi.h"
#include "dvi_timing.h"
#include "dvi_serialiser.h"
#include "tmds_encode.h"
// Adafruit PicoDVI fork requires a couple global items run-time configurable:
uint8_t dvi_vertical_repeat = DVI_VERTICAL_REPEAT;
bool dvi_monochrome_tmds = DVI_MONOCHROME_TMDS;
// Time-critical functions pulled into RAM but each in a unique section to
// allow garbage collection
#define __dvi_func(f) __not_in_flash_func(f)
#define __dvi_func_x(f) __scratch_x(__STRING(f)) f
// We require exclusive use of a DMA IRQ line. (you wouldn't want to share
// anyway). It's possible in theory to hook both IRQs and have two DVI outs.
static struct dvi_inst *dma_irq_privdata[2];
static void dvi_dma0_irq();
static void dvi_dma1_irq();
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue) {
dvi_timing_state_init(&inst->timing_state);
dvi_serialiser_init(&inst->ser_cfg);
for (int i = 0; i < N_TMDS_LANES; ++i) {
inst->dma_cfg[i].chan_ctrl = dma_claim_unused_channel(true);
inst->dma_cfg[i].chan_data = dma_claim_unused_channel(true);
inst->dma_cfg[i].tx_fifo = (void*)&inst->ser_cfg.pio->txf[inst->ser_cfg.sm_tmds[i]];
inst->dma_cfg[i].dreq = pio_get_dreq(inst->ser_cfg.pio, inst->ser_cfg.sm_tmds[i], true);
}
inst->late_scanline_ctr = 0;
inst->tmds_buf_release_next = NULL;
inst->tmds_buf_release = NULL;
queue_init_with_spinlock(&inst->q_tmds_valid, sizeof(void*), 8, spinlock_tmds_queue);
queue_init_with_spinlock(&inst->q_tmds_free, sizeof(void*), 8, spinlock_tmds_queue);
queue_init_with_spinlock(&inst->q_colour_valid, sizeof(void*), 8, spinlock_colour_queue);
queue_init_with_spinlock(&inst->q_colour_free, sizeof(void*), 8, spinlock_colour_queue);
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, true, &inst->dma_list_vblank_sync);
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, false, &inst->dma_list_vblank_nosync);
#if defined(ARDUINO)
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (uint32_t*)SRAM_BASE, &inst->dma_list_active);
#else
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active);
#endif
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_error);
for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) {
void *tmdsbuf;
if (dvi_monochrome_tmds)
tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
else
tmdsbuf = malloc(3 * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
if (!tmdsbuf)
panic("TMDS buffer allocation failed");
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
}
}
// The IRQs will run on whichever core calls this function (this is why it's
// called separately from dvi_init)
void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num) {
uint32_t mask_sync_channel = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data;
uint32_t mask_all_channels = 0;
for (int i = 0; i < N_TMDS_LANES; ++i)
mask_all_channels |= 1u << inst->dma_cfg[i].chan_ctrl | 1u << inst->dma_cfg[i].chan_data;
dma_hw->ints0 = mask_sync_channel;
if (irq_num == DMA_IRQ_0) {
hw_write_masked(&dma_hw->inte0, mask_sync_channel, mask_all_channels);
dma_irq_privdata[0] = inst;
irq_set_exclusive_handler(DMA_IRQ_0, dvi_dma0_irq);
}
else {
hw_write_masked(&dma_hw->inte1, mask_sync_channel, mask_all_channels);
dma_irq_privdata[1] = inst;
irq_set_exclusive_handler(DMA_IRQ_1, dvi_dma1_irq);
}
irq_set_enabled(irq_num, true);
}
// Set up control channels to make transfers to data channels' control
// registers (but don't trigger the control channels -- this is done either by
// data channel CHAIN_TO or an initial write to MULTI_CHAN_TRIGGER)
static inline void __attribute__((always_inline)) _dvi_load_dma_op(const struct dvi_lane_dma_cfg dma_cfg[], struct dvi_scanline_dma_list *l) {
for (int i = 0; i < N_TMDS_LANES; ++i) {
dma_channel_config cfg = dma_channel_get_default_config(dma_cfg[i].chan_ctrl);
channel_config_set_ring(&cfg, true, 4); // 16-byte write wrap
channel_config_set_read_increment(&cfg, true);
channel_config_set_write_increment(&cfg, true);
dma_channel_configure(
dma_cfg[i].chan_ctrl,
&cfg,
&dma_hw->ch[dma_cfg[i].chan_data],
dvi_lane_from_list(l, i),
4, // Configure all 4 registers then halt until next CHAIN_TO
false
);
}
}
// Setup first set of control block lists, configure the control channels, and
// trigger them. Control channels will subsequently be triggered only by DMA
// CHAIN_TO on data channel completion. IRQ handler *must* be prepared before
// calling this. (Hooked to DMA IRQ0)
void dvi_start(struct dvi_inst *inst) {
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync);
dma_start_channel_mask(
(1u << inst->dma_cfg[0].chan_ctrl) |
(1u << inst->dma_cfg[1].chan_ctrl) |
(1u << inst->dma_cfg[2].chan_ctrl));
// We really don't want the FIFOs to bottom out, so wait for full before
// starting the shift-out.
for (int i = 0; i < N_TMDS_LANES; ++i)
while (!pio_sm_is_tx_fifo_full(inst->ser_cfg.pio, inst->ser_cfg.sm_tmds[i]))
tight_loop_contents();
dvi_serialiser_enable(&inst->ser_cfg, true);
}
static inline void __dvi_func_x(_dvi_prepare_scanline_8bpp)(struct dvi_inst *inst, uint32_t *scanbuf) {
uint32_t *tmdsbuf;
queue_remove_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
uint pixwidth = inst->timing->h_active_pixels;
uint words_per_channel = pixwidth / DVI_SYMBOLS_PER_WORD;
// Scanline buffers are half-resolution; the functions take the number of *input* pixels as parameter.
tmds_encode_data_channel_8bpp(scanbuf, tmdsbuf + 0 * words_per_channel, pixwidth / 2, DVI_8BPP_BLUE_MSB, DVI_8BPP_BLUE_LSB );
tmds_encode_data_channel_8bpp(scanbuf, tmdsbuf + 1 * words_per_channel, pixwidth / 2, DVI_8BPP_GREEN_MSB, DVI_8BPP_GREEN_LSB);
tmds_encode_data_channel_8bpp(scanbuf, tmdsbuf + 2 * words_per_channel, pixwidth / 2, DVI_8BPP_RED_MSB, DVI_8BPP_RED_LSB );
queue_add_blocking_u32(&inst->q_tmds_valid, &tmdsbuf);
}
static inline void __dvi_func_x(_dvi_prepare_scanline_16bpp)(struct dvi_inst *inst, uint32_t *scanbuf) {
uint32_t *tmdsbuf;
queue_remove_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
uint pixwidth = inst->timing->h_active_pixels;
uint words_per_channel = pixwidth / DVI_SYMBOLS_PER_WORD;
tmds_encode_data_channel_16bpp(scanbuf, tmdsbuf + 0 * words_per_channel, pixwidth / 2, DVI_16BPP_BLUE_MSB, DVI_16BPP_BLUE_LSB );
tmds_encode_data_channel_16bpp(scanbuf, tmdsbuf + 1 * words_per_channel, pixwidth / 2, DVI_16BPP_GREEN_MSB, DVI_16BPP_GREEN_LSB);
tmds_encode_data_channel_16bpp(scanbuf, tmdsbuf + 2 * words_per_channel, pixwidth / 2, DVI_16BPP_RED_MSB, DVI_16BPP_RED_LSB );
queue_add_blocking_u32(&inst->q_tmds_valid, &tmdsbuf);
}
// "Worker threads" for TMDS encoding (core enters and never returns, but still handles IRQs)
// Version where each record in q_colour_valid is one scanline:
void __dvi_func(dvi_scanbuf_main_8bpp)(struct dvi_inst *inst) {
uint y = 0;
while (1) {
uint32_t *scanbuf;
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf);
_dvi_prepare_scanline_8bpp(inst, scanbuf);
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf);
++y;
if (y == inst->timing->v_active_lines) {
y = 0;
}
}
__builtin_unreachable();
}
// Ugh copy/paste but it lets us garbage collect the TMDS stuff that is not being used from .scratch_x
void __dvi_func(dvi_scanbuf_main_16bpp)(struct dvi_inst *inst) {
uint y = 0;
while (1) {
uint32_t *scanbuf;
queue_remove_blocking_u32(&inst->q_colour_valid, &scanbuf);
_dvi_prepare_scanline_16bpp(inst, scanbuf);
queue_add_blocking_u32(&inst->q_colour_free, &scanbuf);
++y;
if (y == inst->timing->v_active_lines) {
y = 0;
}
}
__builtin_unreachable();
}
static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
// Every fourth interrupt marks the start of the horizontal active region. We
// now have until the end of this region to generate DMA blocklist for next
// scanline.
dvi_timing_state_advance(inst->timing, &inst->timing_state);
if (inst->tmds_buf_release && !queue_try_add_u32(&inst->q_tmds_free, &inst->tmds_buf_release))
panic("TMDS free queue full in IRQ!");
inst->tmds_buf_release = inst->tmds_buf_release_next;
inst->tmds_buf_release_next = NULL;
// Make sure all three channels have definitely loaded their last block
// (should be within a few cycles of one another)
for (int i = 0; i < N_TMDS_LANES; ++i) {
#if PICO_SDK_VERSION_MAJOR == 2
while (dma_debug_hw->ch[inst->dma_cfg[i].chan_data].dbg_tcr != inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD)
#else
while (dma_debug_hw->ch[inst->dma_cfg[i].chan_data].tcr != inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD)
#endif
tight_loop_contents();
}
uint32_t *tmdsbuf;
while (inst->late_scanline_ctr > 0 && queue_try_remove_u32(&inst->q_tmds_valid, &tmdsbuf)) {
// If we displayed this buffer then it would be in the wrong vertical
// position on-screen. Just pass it back.
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
--inst->late_scanline_ctr;
}
if (inst->timing_state.v_state != DVI_STATE_ACTIVE) {
// Don't care
tmdsbuf = NULL;
}
else if (queue_try_peek_u32(&inst->q_tmds_valid, &tmdsbuf)) {
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) {
queue_remove_blocking_u32(&inst->q_tmds_valid, &tmdsbuf);
inst->tmds_buf_release_next = tmdsbuf;
}
}
else {
// No valid scanline was ready (generates solid red scanline)
tmdsbuf = NULL;
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1)
++inst->late_scanline_ctr;
}
switch (inst->timing_state.v_state) {
case DVI_STATE_ACTIVE:
if (tmdsbuf) {
dvi_update_scanline_data_dma(inst->timing, tmdsbuf, &inst->dma_list_active);
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_active);
}
else {
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_error);
}
if (inst->scanline_callback && inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) {
inst->scanline_callback();
}
break;
case DVI_STATE_SYNC:
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_sync);
break;
default:
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_vblank_nosync);
break;
}
}
static void __dvi_func(dvi_dma0_irq)() {
struct dvi_inst *inst = dma_irq_privdata[0];
dma_hw->ints0 = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data;
dvi_dma_irq_handler(inst);
}
static void __dvi_func(dvi_dma1_irq)() {
struct dvi_inst *inst = dma_irq_privdata[1];
dma_hw->ints1 = 1u << inst->dma_cfg[TMDS_SYNC_LANE].chan_data;
dvi_dma_irq_handler(inst);
}

81
src/libdvi/dvi.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef _DVI_H
#define _DVI_H
#define N_TMDS_LANES 3
#define TMDS_SYNC_LANE 0 // blue!
#include "pico/util/queue.h"
#include "dvi_config_defs.h"
#include "dvi_timing.h"
#include "dvi_serialiser.h"
#include "util_queue_u32_inline.h"
typedef void (*dvi_callback_t)(void);
struct dvi_inst {
// Config ---
const struct dvi_timing *timing;
struct dvi_lane_dma_cfg dma_cfg[N_TMDS_LANES];
struct dvi_timing_state timing_state;
struct dvi_serialiser_cfg ser_cfg;
// Called in the DMA IRQ once per scanline -- careful with the run time!
dvi_callback_t scanline_callback;
// State ---
struct dvi_scanline_dma_list dma_list_vblank_sync;
struct dvi_scanline_dma_list dma_list_vblank_nosync;
struct dvi_scanline_dma_list dma_list_active;
struct dvi_scanline_dma_list dma_list_error;
// After a TMDS buffer has been enqueue via a control block for the last
// time, two IRQs must go by before freeing. The first indicates the control
// block for this buf has been loaded, and the second occurs some time after
// the actual data DMA transfer has completed.
uint32_t *tmds_buf_release_next;
uint32_t *tmds_buf_release;
// Remember how far behind the source is on TMDS scanlines, so we can output
// solid colour until they catch up (rather than dying spectacularly)
uint late_scanline_ctr;
// Encoded scanlines:
queue_t q_tmds_valid;
queue_t q_tmds_free;
// Either scanline buffers or frame buffers:
queue_t q_colour_valid;
queue_t q_colour_free;
};
#if defined(__cplusplus)
extern "C"
{
#endif
// Set up data structures and hardware for DVI.
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue);
// Call this after calling dvi_init(). DVI DMA interrupts will be routed to
// whichever core called this function. Registers an exclusive IRQ handler.
void dvi_register_irqs_this_core(struct dvi_inst *inst, uint irq_num);
// Start actually wiggling TMDS pairs. Call this once you have initialised the
// DVI, have registered the IRQs, and are producing rendered scanlines.
void dvi_start(struct dvi_inst *inst);
// TMDS encode worker function: core enters and doesn't leave, but still
// responds to IRQs. Repeatedly pop a scanline buffer from q_colour_valid,
// TMDS encode it, and pass it to the tmds valid queue.
void dvi_scanbuf_main_8bpp(struct dvi_inst *inst);
void dvi_scanbuf_main_16bpp(struct dvi_inst *inst);
// Same as above, but each q_colour_valid entry is a framebuffer
void dvi_framebuf_main_8bpp(struct dvi_inst *inst);
void dvi_framebuf_main_16bpp(struct dvi_inst *inst);
#if defined(__cplusplus)
}
#endif
#endif

View file

@ -0,0 +1,154 @@
#ifndef _DVI_CONFIG_DEFS_H
#define _DVI_CONFIG_DEFS_H
// Compile-time configuration definitions for libdvi. This file provides
// defaults -- you can override using a board header, or setting compile
// definitions directly from the commandline (e.g. using CMake
// target_compile_definitions())
// Pull in base headers to make sure board definitions override the
// definitions provided here. Note this file is included in asm and C.
// added ifndef after Philhower 4.6.0 release to fix compilation issue
#ifndef __ASSEMBLER__
#include "hardware/platform_defs.h"
#include "pico/config.h"
#endif
// ----------------------------------------------------------------------------
// General DVI defines
// How many times to output the same TMDS buffer before recyling it onto the
// free queue. Pixels are repeated vertically if this is >1.
#ifndef DVI_VERTICAL_REPEAT
#define DVI_VERTICAL_REPEAT 2
#endif
// Number of TMDS buffers to allocate (malloc()) in DVI init. You can set this
// to 0 if you want to allocate your own (e.g. if you want static buffers)
#ifndef DVI_N_TMDS_BUFFERS
#define DVI_N_TMDS_BUFFERS 3
#endif
// If 1, replace the DVI serialiser with a 10n1 UART (1 start bit, 10 data
// bits, 1 stop bit) so the stream can be dumped and analysed easily.
#ifndef DVI_SERIAL_DEBUG
#define DVI_SERIAL_DEBUG 0
#endif
// If 1, the same TMDS symbols are sent to all 3 lanes during the horizontal
// active period. This means only monochrome colour is available, but the TMDS
// buffers are 3 times smaller as a result, and the performance requirements
// for encode are also cut by 3.
#ifndef DVI_MONOCHROME_TMDS
#define DVI_MONOCHROME_TMDS 0
#endif
// By default, we assume each 32-bit word written to a PIO FIFO contains 2x
// 10-bit TMDS symbols, concatenated into the lower 20 bits, least-significant
// first. This is convenient if you are generating two or more pixels at once,
// e.g. using the pixel-doubling TMDS encode. You can change this value to 1
// (so each word contains 1 symbol) for e.g. full resolution RGB encode. Note
// that this value needs to divide the DVI horizontal timings, so is limited
// to 1 or 2.
#ifndef DVI_SYMBOLS_PER_WORD
#define DVI_SYMBOLS_PER_WORD 2
#endif
#if DVI_SYMBOLS_PER_WORD != 1 && DVI_SYMBOLS_PER_WORD !=2
#error "Unsupported value for DVI_SYMBOLS_PER_WORD"
#endif
// ----------------------------------------------------------------------------
// Pixel component layout
// By default we go R, G, B from MSB -> LSB. Override to e.g. swap RGB <-> BGR
// Default 8bpp layout: RGB332, {r[1:0], g[2:0], b[1:0]}
#ifndef DVI_8BPP_RED_MSB
#define DVI_8BPP_RED_MSB 7
#endif
#ifndef DVI_8BPP_RED_LSB
#define DVI_8BPP_RED_LSB 5
#endif
#ifndef DVI_8BPP_GREEN_MSB
#define DVI_8BPP_GREEN_MSB 4
#endif
#ifndef DVI_8BPP_GREEN_LSB
#define DVI_8BPP_GREEN_LSB 2
#endif
#ifndef DVI_8BPP_BLUE_MSB
#define DVI_8BPP_BLUE_MSB 1
#endif
#ifndef DVI_8BPP_BLUE_LSB
#define DVI_8BPP_BLUE_LSB 0
#endif
// Default 16bpp layout: RGB565, {r[4:0], g[5:0], b[4:0]}
#ifndef DVI_16BPP_RED_MSB
#define DVI_16BPP_RED_MSB 15
#endif
#ifndef DVI_16BPP_RED_LSB
#define DVI_16BPP_RED_LSB 11
#endif
#ifndef DVI_16BPP_GREEN_MSB
#define DVI_16BPP_GREEN_MSB 10
#endif
#ifndef DVI_16BPP_GREEN_LSB
#define DVI_16BPP_GREEN_LSB 5
#endif
#ifndef DVI_16BPP_BLUE_MSB
#define DVI_16BPP_BLUE_MSB 4
#endif
#ifndef DVI_16BPP_BLUE_LSB
#define DVI_16BPP_BLUE_LSB 0
#endif
// Default 1bpp layout: bitwise little-endian, i.e. least significant bit of
// each word is the first (leftmost) of a block of 32 pixels.
// If 1, reverse the order of pixels within each byte. Order of bytes within
// each word is still little-endian.
#ifndef DVI_1BPP_BIT_REVERSE
#define DVI_1BPP_BIT_REVERSE 1 // Adafruit_GFX GFXcanvas1 requires this 1
#endif
// ----------------------------------------------------------------------------
// TMDS encode controls
// Number of TMDS loop bodies between branches. cmp + branch costs 3 cycles,
// so you can easily save 10% of encode time by bumping this. Note that body
// will *already* produce multiple pixels, and total symbols per iteration
// must cleanly divide symbols per scanline, else the loop won't terminate.
// Point gun away from foot.
#ifndef TMDS_ENCODE_UNROLL
#define TMDS_ENCODE_UNROLL 1
#endif
// If 1, don't save/restore the interpolators on full-resolution TMDS encode.
// Speed hack. The TMDS code uses both interpolators, for each of the 3 data
// channels, so this define avoids 6 save/restores per scanline.
#ifndef TMDS_FULLRES_NO_INTERP_SAVE
#define TMDS_FULLRES_NO_INTERP_SAVE 0
#endif
// If 1, don't DC-balance the output of full resolution encode. Hilariously
// noncompliant, but Dell Ultrasharp -- the honey badger of computer monitors
// -- does not seem to mind (it helps that we DC-couple). Another speed hack,
// useful when you are trying to get everything else up to speed.
#ifndef TMDS_FULLRES_NO_DC_BALANCE
#define TMDS_FULLRES_NO_DC_BALANCE 0
#endif
#endif

View file

@ -0,0 +1,73 @@
#include "pico.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/pwm.h"
#include "hardware/structs/padsbank0.h"
#include "dvi.h"
#include "dvi_serialiser.h"
#include "dvi_serialiser.pio.h"
static void dvi_configure_pad(uint gpio, bool invert) {
// 2 mA drive, enable slew rate limiting (this seems fine even at 720p30, and
// the 3V3 LDO doesn't get warm like when turning all the GPIOs up to 11).
// Also disable digital receiver.
hw_write_masked(
&padsbank0_hw->io[gpio],
(0 << PADS_BANK0_GPIO0_DRIVE_LSB),
PADS_BANK0_GPIO0_DRIVE_BITS | PADS_BANK0_GPIO0_SLEWFAST_BITS | PADS_BANK0_GPIO0_IE_BITS
);
gpio_set_outover(gpio, invert ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
}
void dvi_serialiser_init(struct dvi_serialiser_cfg *cfg) {
#if DVI_SERIAL_DEBUG
uint offset = pio_add_program(cfg->pio, &dvi_serialiser_debug_program);
#else
uint offset = pio_add_program(cfg->pio, &dvi_serialiser_program);
#endif
cfg->prog_offs = offset;
for (int i = 0; i < N_TMDS_LANES; ++i) {
pio_sm_claim(cfg->pio, cfg->sm_tmds[i]);
dvi_serialiser_program_init(
cfg->pio,
cfg->sm_tmds[i],
offset,
cfg->pins_tmds[i],
DVI_SERIAL_DEBUG
);
dvi_configure_pad(cfg->pins_tmds[i], cfg->invert_diffpairs);
dvi_configure_pad(cfg->pins_tmds[i] + 1, cfg->invert_diffpairs);
}
// Use a PWM slice to drive the pixel clock. Both GPIOs must be on the same
// slice (lower-numbered GPIO must be even).
assert(cfg->pins_clk % 2 == 0);
uint slice = pwm_gpio_to_slice_num(cfg->pins_clk);
// 5 cycles high, 5 low. Invert one channel so that we get complementary outputs.
pwm_config pwm_cfg = pwm_get_default_config();
pwm_config_set_output_polarity(&pwm_cfg, true, false);
pwm_config_set_wrap(&pwm_cfg, 9);
pwm_init(slice, &pwm_cfg, false);
pwm_set_both_levels(slice, 5, 5);
for (uint i = cfg->pins_clk; i <= cfg->pins_clk + 1; ++i) {
gpio_set_function(i, GPIO_FUNC_PWM);
dvi_configure_pad(i, cfg->invert_diffpairs);
}
}
void dvi_serialiser_enable(struct dvi_serialiser_cfg *cfg, bool enable) {
uint mask = 0;
for (int i = 0; i < N_TMDS_LANES; ++i)
mask |= 1u << (cfg->sm_tmds[i] + PIO_CTRL_SM_ENABLE_LSB);
if (enable) {
hw_set_bits(&cfg->pio->ctrl, mask);
pwm_set_enabled(pwm_gpio_to_slice_num(cfg->pins_clk), true);
}
else {
hw_clear_bits(&cfg->pio->ctrl, mask);
pwm_set_enabled(pwm_gpio_to_slice_num(cfg->pins_clk), false);
}
}

View file

@ -0,0 +1,22 @@
#ifndef _DVI_SERIALISER_H
#define _DVI_SERIALISER_H
#include "hardware/pio.h"
#include "dvi_config_defs.h"
#define N_TMDS_LANES 3
struct dvi_serialiser_cfg {
PIO pio;
uint sm_tmds[N_TMDS_LANES];
uint pins_tmds[N_TMDS_LANES];
uint pins_clk;
bool invert_diffpairs;
uint prog_offs;
};
void dvi_serialiser_init(struct dvi_serialiser_cfg *cfg);
void dvi_serialiser_enable(struct dvi_serialiser_cfg *cfg, bool enable);
uint32_t dvi_single_to_diff(uint32_t in);
#endif

View file

@ -0,0 +1,53 @@
.program dvi_serialiser
.side_set 2
.origin 0
; Single-ended -> differential serial
out pc, 1 side 0b10
out pc, 1 side 0b01
.program dvi_serialiser_debug
.side_set 1 opt
; The debug variant behaves as a UART with 1 start bit, 10 data bits, 1 stop
; bit, and 5/6ths the data throughput of the TMDS version.
pull ifempty side 1 ; Extend stop bit with FIFO stall
nop side 0
out pins, 1 ; Unrolled because we require 1 bit / clk
out pins, 1
out pins, 1
out pins, 1
out pins, 1
out pins, 1
out pins, 1
out pins, 1
out pins, 1
out pins, 1
% c-sdk {
#include "dvi_config_defs.h"
static inline void dvi_serialiser_program_init(PIO pio, uint sm, uint offset, uint data_pins, bool debug) {
pio_sm_set_pins_with_mask(pio, sm, 2u << data_pins, 3u << data_pins);
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, 3u << data_pins);
pio_gpio_init(pio, data_pins);
pio_gpio_init(pio, data_pins + 1);
pio_sm_config c;
if (debug) {
c = dvi_serialiser_debug_program_get_default_config(offset);
}
else {
c = dvi_serialiser_program_get_default_config(offset);
}
sm_config_set_sideset_pins(&c, data_pins);
if (debug)
sm_config_set_out_pins(&c, data_pins, 1);
sm_config_set_out_shift(&c, true, !debug, 10 * DVI_SYMBOLS_PER_WORD);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, false);
}
%}

View file

@ -0,0 +1,101 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// -------------- //
// dvi_serialiser //
// -------------- //
#define dvi_serialiser_wrap_target 0
#define dvi_serialiser_wrap 1
static const uint16_t dvi_serialiser_program_instructions[] = {
// .wrap_target
0x70a1, // 0: out pc, 1 side 2
0x68a1, // 1: out pc, 1 side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program dvi_serialiser_program = {
.instructions = dvi_serialiser_program_instructions,
.length = 2,
.origin = 0,
};
static inline pio_sm_config dvi_serialiser_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + dvi_serialiser_wrap_target, offset + dvi_serialiser_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
#endif
// -------------------- //
// dvi_serialiser_debug //
// -------------------- //
#define dvi_serialiser_debug_wrap_target 0
#define dvi_serialiser_debug_wrap 11
static const uint16_t dvi_serialiser_debug_program_instructions[] = {
// .wrap_target
0x98e0, // 0: pull ifempty block side 1
0xb042, // 1: nop side 0
0x6001, // 2: out pins, 1
0x6001, // 3: out pins, 1
0x6001, // 4: out pins, 1
0x6001, // 5: out pins, 1
0x6001, // 6: out pins, 1
0x6001, // 7: out pins, 1
0x6001, // 8: out pins, 1
0x6001, // 9: out pins, 1
0x6001, // 10: out pins, 1
0x6001, // 11: out pins, 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program dvi_serialiser_debug_program = {
.instructions = dvi_serialiser_debug_program_instructions,
.length = 12,
.origin = -1,
};
static inline pio_sm_config dvi_serialiser_debug_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + dvi_serialiser_debug_wrap_target, offset + dvi_serialiser_debug_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
#include "dvi_config_defs.h"
static inline void dvi_serialiser_program_init(PIO pio, uint sm, uint offset, uint data_pins, bool debug) {
pio_sm_set_pins_with_mask(pio, sm, 2u << data_pins, 3u << data_pins);
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, 3u << data_pins);
pio_gpio_init(pio, data_pins);
pio_gpio_init(pio, data_pins + 1);
pio_sm_config c;
if (debug) {
c = dvi_serialiser_debug_program_get_default_config(offset);
}
else {
c = dvi_serialiser_program_get_default_config(offset);
}
sm_config_set_sideset_pins(&c, data_pins);
if (debug)
sm_config_set_out_pins(&c, data_pins, 1);
sm_config_set_out_shift(&c, true, !debug, 10 * DVI_SYMBOLS_PER_WORD);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, false);
}
#endif

344
src/libdvi/dvi_timing.c Normal file
View file

@ -0,0 +1,344 @@
#include "dvi.h"
#include "dvi_timing.h"
#include "hardware/dma.h"
// This file contains:
// - Timing parameters for DVI modes (horizontal + vertical counts, best
// achievable bit clock from 12 MHz crystal)
// - Helper functions for generating DMA lists based on these timings
extern bool dvi_monochrome_tmds; // In dvi.c
// Pull into RAM but apply unique section suffix to allow linker GC
#define __dvi_func(x) __not_in_flash_func(x)
#define __dvi_const(x) __not_in_flash_func(x)
// VGA -- we do this mode properly, with a pretty comfortable clk_sys (252 MHz)
const struct dvi_timing __dvi_const(dvi_timing_640x480p_60hz) = {
.h_sync_polarity = false,
.h_front_porch = 16,
.h_sync_width = 96,
.h_back_porch = 48,
.h_active_pixels = 640,
.v_sync_polarity = false,
.v_front_porch = 10,
.v_sync_width = 2,
.v_back_porch = 33,
.v_active_lines = 480,
.bit_clk_khz = 252000
};
// SVGA -- completely by-the-book but requires 400 MHz clk_sys
const struct dvi_timing __dvi_const(dvi_timing_800x600p_60hz) = {
.h_sync_polarity = false,
.h_front_porch = 44,
.h_sync_width = 128,
.h_back_porch = 88,
.h_active_pixels = 800,
.v_sync_polarity = false,
.v_front_porch = 1,
.v_sync_width = 4,
.v_back_porch = 23,
.v_active_lines = 600,
.bit_clk_khz = 400000
};
// 800x480p 60 Hz (note this doesn't seem to be a CEA mode, I just used the
// output of `cvt 800 480 60`), 295 MHz bit clock
const struct dvi_timing __dvi_const(dvi_timing_800x480p_60hz) = {
.h_sync_polarity = false,
.h_front_porch = 24,
.h_sync_width = 72,
.h_back_porch = 96,
.h_active_pixels = 800,
.v_sync_polarity = true,
.v_front_porch = 3,
.v_sync_width = 10,
.v_back_porch = 7,
.v_active_lines = 480,
.bit_clk_khz = 295200
};
// 800x480p 30 Hz, 148 MHz bit clock. This is not a CEA mode either , but needs
// lower overclock compared to dvi_timing_800x480p_60hz and the timing is
// verified to work on https://www.adafruit.com/product/2232
const struct dvi_timing __dvi_const(dvi_timing_800x480p_30hz) = {
.h_sync_polarity = false,
.h_front_porch = 24,
.h_sync_width = 72,
.h_back_porch = 96,
.h_active_pixels = 800,
.v_sync_polarity = true,
.v_front_porch = 3,
.v_sync_width = 10,
.v_back_porch = 4,
.v_active_lines = 480,
.bit_clk_khz = 147600
};
// SVGA reduced blanking (355 MHz bit clock) -- valid CVT mode, less common
// than fully-blanked SVGA, but doesn't require such a high system clock
const struct dvi_timing __dvi_const(dvi_timing_800x600p_reduced_60hz) = {
.h_sync_polarity = true,
.h_front_porch = 48,
.h_sync_width = 32,
.h_back_porch = 80,
.h_active_pixels = 800,
.v_sync_polarity = false,
.v_front_porch = 3,
.v_sync_width = 4,
.v_back_porch = 11,
.v_active_lines = 600,
.bit_clk_khz = 354000
};
// Also known as qHD, bit uncommon, but it's a nice modest-resolution 16:9
// aspect mode. Pixel clock 37.3 MHz
const struct dvi_timing __dvi_const(dvi_timing_960x540p_60hz) = {
.h_sync_polarity = true,
.h_front_porch = 16,
.h_sync_width = 32,
.h_back_porch = 96,
.h_active_pixels = 960,
.v_sync_polarity = true,
.v_front_porch = 2,
.v_sync_width = 6,
.v_back_porch = 15,
.v_active_lines = 540,
.bit_clk_khz = 372000
};
// Note this is NOT the correct 720p30 CEA mode, but rather 720p60 run at half
// pixel clock. Seems to be commonly accepted (and is a valid CVT mode). The
// actual CEA mode is the same pixel clock as 720p60 but with >50% blanking,
// which would require a clk_sys of 742 MHz!
const struct dvi_timing __dvi_const(dvi_timing_1280x720p_30hz) = {
.h_sync_polarity = true,
.h_front_porch = 110,
.h_sync_width = 40,
.h_back_porch = 220,
.h_active_pixels = 1280,
.v_sync_polarity = true,
.v_front_porch = 5,
.v_sync_width = 5,
.v_back_porch = 20,
.v_active_lines = 720,
.bit_clk_khz = 372000
};
// Reduced-blanking (CVT) 720p. You aren't supposed to use reduced blanking
// modes below 60 Hz, but I won't tell anyone (and it works on the monitors
// I've tried). This nets a lower system clock than regular 720p30 (319 MHz)
const struct dvi_timing __dvi_const(dvi_timing_1280x720p_reduced_30hz) = {
.h_sync_polarity = true,
.h_front_porch = 48,
.h_sync_width = 32,
.h_back_porch = 80,
.h_active_pixels = 1280,
.v_sync_polarity = false,
.v_front_porch = 3,
.v_sync_width = 5,
.v_back_porch = 13,
.v_active_lines = 720,
.bit_clk_khz = 319200
};
// This requires a spicy 488 MHz system clock and is illegal in most countries
// (you need to have a very lucky piece of silicon to run this at 1.3 V, or
// connect an external supply and give it a bit more juice)
const struct dvi_timing __dvi_const(dvi_timing_1600x900p_reduced_30hz) = {
.h_sync_polarity = true,
.h_front_porch = 48,
.h_sync_width = 32,
.h_back_porch = 80,
.h_active_pixels = 1600,
.v_sync_polarity = false,
.v_front_porch = 3,
.v_sync_width = 5,
.v_back_porch = 18,
.v_active_lines = 900,
.bit_clk_khz = 488000
};
// ----------------------------------------------------------------------------
// The DMA scheme is:
//
// - One channel transferring data to each of the three PIO state machines
// performing TMDS serialisation
//
// - One channel programming the registers of each of these data channels,
// triggered (CHAIN_TO) each time the corresponding data channel completes
//
// - Lanes 1 and 2 have one block for blanking and one for video data
//
// - Lane 0 has one block for each horizontal region (front porch, hsync, back
// porch, active)
//
// - The IRQ_QUIET flag is used to select which data block on the sync lane is
// allowed to generate an IRQ upon completion. This is the block immediately
// before the horizontal active region. The IRQ is entered at ~the same time
// as the last data transfer starts
//
// - The IRQ points the control channels at new blocklists for next scanline.
// The DMA starts the new list automatically at end-of-scanline, via
// CHAIN_TO.
//
// The horizontal active region is the longest continuous transfer, so this
// gives the most time to handle the IRQ and load new blocklists.
//
// Note a null trigger IRQ is not suitable because we get that *after* the
// last data transfer finishes, and the FIFOs bottom out very shortly
// afterward. For pure DVI (four blocks per scanline), it works ok to take
// four regular IRQs per scanline and return early from 3 of them, but this
// breaks down when you have very short scanline sections like guard bands.
// Each symbol appears twice, concatenated in one word. Note these must be in
// RAM because they see a lot of DMA traffic
const uint32_t __dvi_const(dvi_ctrl_syms)[4] = {
0xd5354,
0x2acab,
0x55154,
0xaaeab
};
// Output solid red scanline if we are given NULL for tmdsbuff
#if DVI_SYMBOLS_PER_WORD == 2
static uint32_t __dvi_const(empty_scanline_tmds)[3] = {
0x7fd00u, // 0x00, 0x00
0x7fd00u, // 0x00, 0x00
0xbfa01u // 0xfc, 0xfc
};
#else
static uint32_t __attribute__((aligned(8))) __dvi_const(empty_scanline_tmds)[6] = {
0x100u, 0x1ffu, // 0x00, 0x00
0x100u, 0x1ffu, // 0x00, 0x00
0x201u, 0x2feu // 0xfc, 0xfc
};
#endif
void dvi_timing_state_init(struct dvi_timing_state *t) {
t->v_ctr = 0;
t->v_state = DVI_STATE_FRONT_PORCH;
};
void __dvi_func(dvi_timing_state_advance)(const struct dvi_timing *t, struct dvi_timing_state *s) {
s->v_ctr++;
if ((s->v_state == DVI_STATE_FRONT_PORCH && s->v_ctr == t->v_front_porch) ||
(s->v_state == DVI_STATE_SYNC && s->v_ctr == t->v_sync_width) ||
(s->v_state == DVI_STATE_BACK_PORCH && s->v_ctr == t->v_back_porch) ||
(s->v_state == DVI_STATE_ACTIVE && s->v_ctr == t->v_active_lines)) {
s->v_state = (s->v_state + 1) % DVI_STATE_COUNT;
s->v_ctr = 0;
}
}
void dvi_scanline_dma_list_init(struct dvi_scanline_dma_list *dma_list) {
*dma_list = (struct dvi_scanline_dma_list){};
}
static const uint32_t *get_ctrl_sym(bool vsync, bool hsync) {
return &dvi_ctrl_syms[!!vsync << 1 | !!hsync];
}
// Make a sequence of paced transfers to the relevant FIFO
static void _set_data_cb(dma_cb_t *cb, const struct dvi_lane_dma_cfg *dma_cfg,
const void *read_addr, uint transfer_count, uint read_ring, bool irq_on_finish) {
cb->read_addr = read_addr;
cb->write_addr = dma_cfg->tx_fifo;
cb->transfer_count = transfer_count;
cb->c = dma_channel_get_default_config(dma_cfg->chan_data);
channel_config_set_ring(&cb->c, false, read_ring);
channel_config_set_dreq(&cb->c, dma_cfg->dreq);
// Call back to control channel for reconfiguration:
channel_config_set_chain_to(&cb->c, dma_cfg->chan_ctrl);
// Note we never send a null trigger, so IRQ_QUIET is an IRQ suppression flag
channel_config_set_irq_quiet(&cb->c, !irq_on_finish);
};
void dvi_setup_scanline_for_vblank(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[],
bool vsync_asserted, struct dvi_scanline_dma_list *l) {
bool vsync = t->v_sync_polarity == vsync_asserted;
const uint32_t *sym_hsync_off = get_ctrl_sym(vsync, !t->h_sync_polarity);
const uint32_t *sym_hsync_on = get_ctrl_sym(vsync, t->h_sync_polarity);
const uint32_t *sym_no_sync = get_ctrl_sym(false, false );
dma_cb_t *synclist = dvi_lane_from_list(l, TMDS_SYNC_LANE);
// The symbol table contains each control symbol *twice*, concatenated into 20 LSBs of table word, so we can always do word-repeat.
_set_data_cb(&synclist[0], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_off, t->h_front_porch / DVI_SYMBOLS_PER_WORD, 2, false);
_set_data_cb(&synclist[1], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_on, t->h_sync_width / DVI_SYMBOLS_PER_WORD, 2, false);
_set_data_cb(&synclist[2], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_off, t->h_back_porch / DVI_SYMBOLS_PER_WORD, 2, true);
_set_data_cb(&synclist[3], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_off, t->h_active_pixels / DVI_SYMBOLS_PER_WORD, 2, false);
for (int i = 0; i < N_TMDS_LANES; ++i) {
if (i == TMDS_SYNC_LANE)
continue;
dma_cb_t *cblist = dvi_lane_from_list(l, i);
_set_data_cb(&cblist[0], &dma_cfg[i], sym_no_sync,(t->h_front_porch + t->h_sync_width + t->h_back_porch) / DVI_SYMBOLS_PER_WORD, 2, false);
_set_data_cb(&cblist[1], &dma_cfg[i], sym_no_sync, t->h_active_pixels / DVI_SYMBOLS_PER_WORD, 2, false);
}
}
void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[],
uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l) {
const uint32_t *sym_hsync_off = get_ctrl_sym(!t->v_sync_polarity, !t->h_sync_polarity);
const uint32_t *sym_hsync_on = get_ctrl_sym(!t->v_sync_polarity, t->h_sync_polarity);
const uint32_t *sym_no_sync = get_ctrl_sym(false, false );
dma_cb_t *synclist = dvi_lane_from_list(l, TMDS_SYNC_LANE);
_set_data_cb(&synclist[0], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_off, t->h_front_porch / DVI_SYMBOLS_PER_WORD, 2, false);
_set_data_cb(&synclist[1], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_on, t->h_sync_width / DVI_SYMBOLS_PER_WORD, 2, false);
_set_data_cb(&synclist[2], &dma_cfg[TMDS_SYNC_LANE], sym_hsync_off, t->h_back_porch / DVI_SYMBOLS_PER_WORD, 2, true);
for (int i = 0; i < N_TMDS_LANES; ++i) {
dma_cb_t *cblist = dvi_lane_from_list(l, i);
if (i != TMDS_SYNC_LANE) {
_set_data_cb(&cblist[0], &dma_cfg[i], sym_no_sync,
(t->h_front_porch + t->h_sync_width + t->h_back_porch) / DVI_SYMBOLS_PER_WORD, 2, false);
}
int target_block = i == TMDS_SYNC_LANE ? DVI_SYNC_LANE_CHUNKS - 1 : DVI_NOSYNC_LANE_CHUNKS - 1;
if (tmdsbuf) {
// Non-repeating DMA for the freshly-encoded TMDS buffer
_set_data_cb(&cblist[target_block], &dma_cfg[i], tmdsbuf + i * (t->h_active_pixels / DVI_SYMBOLS_PER_WORD),
t->h_active_pixels / DVI_SYMBOLS_PER_WORD, 0, false);
}
else {
// Use read ring to repeat the correct DC-balanced symbol pair on blank scanlines (4 or 8 byte period)
_set_data_cb(&cblist[target_block], &dma_cfg[i], &empty_scanline_tmds[2 * i / DVI_SYMBOLS_PER_WORD],
t->h_active_pixels / DVI_SYMBOLS_PER_WORD, DVI_SYMBOLS_PER_WORD == 2 ? 2 : 3, false);
}
}
}
void __dvi_func(dvi_update_scanline_data_dma)(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l) {
for (int i = 0; i < N_TMDS_LANES; ++i) {
const uint32_t *lane_tmdsbuf = dvi_monochrome_tmds ? tmdsbuf : tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
if (i == TMDS_SYNC_LANE)
dvi_lane_from_list(l, i)[3].read_addr = lane_tmdsbuf;
else
dvi_lane_from_list(l, i)[1].read_addr = lane_tmdsbuf;
}
}

100
src/libdvi/dvi_timing.h Normal file
View file

@ -0,0 +1,100 @@
#ifndef _DVI_TIMING_H
#define _DVI_TIMING_H
#include "hardware/dma.h"
#include "pico/util/queue.h"
#include "dvi.h"
struct dvi_timing {
bool h_sync_polarity;
uint h_front_porch;
uint h_sync_width;
uint h_back_porch;
uint h_active_pixels;
bool v_sync_polarity;
uint v_front_porch;
uint v_sync_width;
uint v_back_porch;
uint v_active_lines;
uint bit_clk_khz;
};
enum dvi_line_state {
DVI_STATE_FRONT_PORCH = 0,
DVI_STATE_SYNC,
DVI_STATE_BACK_PORCH,
DVI_STATE_ACTIVE,
DVI_STATE_COUNT
};
struct dvi_timing_state {
uint v_ctr;
enum dvi_line_state v_state;
};
// This should map directly to DMA register layout, but more convenient types
// (also this really shouldn't be here... we don't have a dma_cb in the SDK
// because there are many valid formats due to aliases)
typedef struct dma_cb {
const void *read_addr;
void *write_addr;
uint32_t transfer_count;
dma_channel_config c;
} dma_cb_t;
static_assert(sizeof(dma_cb_t) == 4 * sizeof(uint32_t), "bad dma layout");
static_assert(__builtin_offsetof(dma_cb_t, c.ctrl) == __builtin_offsetof(dma_channel_hw_t, ctrl_trig), "bad dma layout");
#define DVI_SYNC_LANE_CHUNKS DVI_STATE_COUNT
#define DVI_NOSYNC_LANE_CHUNKS 2
struct dvi_scanline_dma_list {
dma_cb_t l0[DVI_SYNC_LANE_CHUNKS];
dma_cb_t l1[DVI_NOSYNC_LANE_CHUNKS];
dma_cb_t l2[DVI_NOSYNC_LANE_CHUNKS];
};
static inline dma_cb_t* dvi_lane_from_list(struct dvi_scanline_dma_list *l, int i) {
return i == 0 ? l->l0 : i == 1 ? l->l1 : l->l2;
}
// Each TMDS lane uses one DMA channel to transfer data to a PIO state
// machine, and another channel to load control blocks into this channel.
struct dvi_lane_dma_cfg {
uint chan_ctrl;
uint chan_data;
void *tx_fifo;
uint dreq;
};
// Note these are already converted to pseudo-differential representation
extern const uint32_t dvi_ctrl_syms[4];
extern const struct dvi_timing dvi_timing_640x480p_60hz;
extern const struct dvi_timing dvi_timing_800x480p_60hz;
extern const struct dvi_timing dvi_timing_800x480p_30hz;
extern const struct dvi_timing dvi_timing_800x600p_60hz;
extern const struct dvi_timing dvi_timing_960x540p_60hz;
extern const struct dvi_timing dvi_timing_1280x720p_30hz;
extern const struct dvi_timing dvi_timing_800x600p_reduced_60hz;
extern const struct dvi_timing dvi_timing_1280x720p_reduced_30hz;
void dvi_timing_state_init(struct dvi_timing_state *t);
void dvi_timing_state_advance(const struct dvi_timing *t, struct dvi_timing_state *s);
void dvi_scanline_dma_list_init(struct dvi_scanline_dma_list *dma_list);
void dvi_setup_scanline_for_vblank(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[],
bool vsync_asserted, struct dvi_scanline_dma_list *l);
void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_lane_dma_cfg dma_cfg[],
uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l);
void dvi_update_scanline_data_dma(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l);
#endif

623
src/libdvi/tmds_encode.S Normal file
View file

@ -0,0 +1,623 @@
#include "hardware/regs/addressmap.h"
#include "hardware/regs/sio.h"
#include "dvi_config_defs.h"
// Offsets suitable for ldr/str (must be <= 0x7c):
#define ACCUM0_OFFS (SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define ACCUM1_OFFS (SIO_INTERP0_ACCUM1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define ACCUM1_ADD_OFFS (SIO_INTERP0_ACCUM1_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define PEEK0_OFFS (SIO_INTERP0_PEEK_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define PEEK1_OFFS (SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define PEEK2_OFFS (SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP1 (SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
// Note the entirety of INTERP0 and INTERP1 fits inside this 5-bit
// word-addressed space... almost as though it were intentional! :)
.syntax unified
.cpu cortex-m0plus
.thumb
.macro decl_func_x name
.section .scratch_x.\name, "ax"
.global \name
.type \name,%function
.thumb_func
\name:
.endm
.macro decl_func_y name
.section .scratch_y.\name, "ax"
.global \name
.type \name,%function
.thumb_func
\name:
.endm
#define decl_func decl_func_x
// ----------------------------------------------------------------------------
// Pixel-doubling encoders for RGB
// r0: Input buffer (word-aligned)
// r1: Output buffer (word-aligned)
// r2: Input size (pixels)
.macro do_channel_16bpp r_ibase r_inout0 r_out1
str \r_inout0, [\r_ibase, #ACCUM0_OFFS]
ldr \r_inout0, [\r_ibase, #PEEK0_OFFS]
ldr \r_inout0, [\r_inout0]
ldr \r_out1, [\r_ibase, #PEEK1_OFFS]
ldr \r_out1, [\r_out1]
.endm
decl_func tmds_encode_loop_16bpp
push {r4, r5, r6, r7, lr}
lsls r2, #2
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
b 2f
.align 2
1:
.rept TMDS_ENCODE_UNROLL
ldmia r0!, {r4, r6}
do_channel_16bpp r2, r4, r5
do_channel_16bpp r2, r6, r7
stmia r1!, {r4, r5, r6, r7}
.endr
2:
cmp r1, ip
bne 1b
pop {r4, r5, r6, r7, pc}
// Same as above, but scale data to make up for lack of left shift
// in interpolator (costs 1 cycle per 2 pixels)
//
// r0: Input buffer (word-aligned)
// r1: Output buffer (word-aligned)
// r2: Input size (pixels)
// r3: Left shift amount
decl_func tmds_encode_loop_16bpp_leftshift
push {r4, r5, r6, r7, lr}
lsls r2, #2
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
b 2f
.align 2
1:
.rept TMDS_ENCODE_UNROLL
ldmia r0!, {r4, r6}
lsls r4, r3
do_channel_16bpp r2, r4, r5
lsls r6, r3
do_channel_16bpp r2, r6, r7
stmia r1!, {r4, r5, r6, r7}
.endr
2:
cmp r1, ip
bne 1b
pop {r4, r5, r6, r7, pc}
// r0: Input buffer (word-aligned)
// r1: Output buffer (word-aligned)
// r2: Input size (pixels)
decl_func tmds_encode_loop_8bpp
push {r4, r5, r6, r7, lr}
lsls r2, #2
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
b 2f
.align 2
1:
.rept TMDS_ENCODE_UNROLL
ldmia r0!, {r4}
str r4, [r2, #ACCUM0_OFFS + INTERP1]
str r4, [r2, #ACCUM0_OFFS]
ldr r4, [r2, #PEEK0_OFFS]
ldr r4, [r4]
ldr r5, [r2, #PEEK1_OFFS]
ldr r5, [r5]
ldr r6, [r2, #PEEK0_OFFS + INTERP1]
ldr r6, [r6]
ldr r7, [r2, #PEEK1_OFFS + INTERP1]
ldr r7, [r7]
stmia r1!, {r4, r5, r6, r7}
.endr
2:
cmp r1, ip
bne 1b
pop {r4, r5, r6, r7, pc}
// r0: Input buffer (word-aligned)
// r1: Output buffer (word-aligned)
// r2: Input size (pixels)
// r3: Left shift amount
//
// Note that only the data written to interp0 (pixel 0, 1) is leftshifted, not
// the data written to interp1 (pixel 2, 3). Otherwise we always lose MSBs, as
// the LUT offset MSB is at bit 8, so pixel 0 always requires some left shift,
// since its channel MSBs are no greater than 7.
decl_func tmds_encode_loop_8bpp_leftshift
push {r4, r5, r6, r7, lr}
lsls r2, #3
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
b 2f
.align 2
1:
.rept TMDS_ENCODE_UNROLL
ldmia r0!, {r4}
str r4, [r2, #ACCUM0_OFFS + INTERP1]
lsls r4, r3
str r4, [r2, #ACCUM0_OFFS]
ldr r4, [r2, #PEEK0_OFFS]
ldr r4, [r4]
ldr r5, [r2, #PEEK1_OFFS]
ldr r5, [r5]
ldr r6, [r2, #PEEK0_OFFS + INTERP1]
ldr r6, [r6]
ldr r7, [r2, #PEEK1_OFFS + INTERP1]
ldr r7, [r7]
stmia r1!, {r4, r5, r6, r7}
.endr
2:
cmp r1, ip
bne 1b
pop {r4, r5, r6, r7, pc}
// ----------------------------------------------------------------------------
// Fast 1bpp black/white encoder (full res)
// Taking the encoder from DVI spec, with initial balance 0:
//
// - Encoding either 0x00 or 0xff will produce a running balance of -8, with
// output symbol of 0x100 or 0x200
//
// - Subsequently encoding either 0x01 or 0xfe will return the balance to 0, with
// output symbol of 0x1ff or 0x2ff
//
// So we can do 1bpp encode with a lookup of x coordinate LSB, and input
// colour bit. If we process pixels in even-sized blocks, only the colour
// lookup is needed.
// Encode 8 pixels @ 1bpp (using two table lookups)
// r3 contains lookup mask (preshifted)
// r8 contains pointer to encode table
// 2.125 cyc/pix
.macro tmds_encode_1bpp_body shift_instr0 shamt0 shift_instr1 shamt1
\shift_instr0 r4, r2, #\shamt0
ands r4, r3
add r4, r8
ldmia r4, {r4, r5}
\shift_instr1 r6, r2, #\shamt1
ands r6, r3
add r6, r8
ldmia r6, {r6, r7}
stmia r1!, {r4, r5, r6, r7}
.endm
// r0: input buffer (word-aligned)
// r1: output buffer (word-aligned)
// r2: output pixel count
decl_func tmds_encode_1bpp
push {r4-r7, lr}
mov r7, r8
push {r7}
lsls r2, #1
add r2, r1
mov ip, r2
adr r4, tmds_1bpp_table
mov r8, r4
// Mask: 4 bit index, 8 bytes per entry
movs r3, #0x78
b 2f
1:
ldmia r0!, {r2}
#if !DVI_1BPP_BIT_REVERSE
tmds_encode_1bpp_body lsls 3 lsrs 1
tmds_encode_1bpp_body lsrs 5 lsrs 9
tmds_encode_1bpp_body lsrs 13 lsrs 17
tmds_encode_1bpp_body lsrs 21 lsrs 25
#else
tmds_encode_1bpp_body lsrs 1 lsls 3
tmds_encode_1bpp_body lsrs 9 lsrs 5
tmds_encode_1bpp_body lsrs 17 lsrs 13
tmds_encode_1bpp_body lsrs 25 lsrs 21
#endif
2:
cmp r1, ip
blo 1b
pop {r7}
mov r8, r7
pop {r4-r7, pc}
.align 2
tmds_1bpp_table:
#if !DVI_1BPP_BIT_REVERSE
.word 0x7fd00, 0x7fd00 // 0000
.word 0x7fe00, 0x7fd00 // 0001
.word 0xbfd00, 0x7fd00 // 0010
.word 0xbfe00, 0x7fd00 // 0011
.word 0x7fd00, 0x7fe00 // 0100
.word 0x7fe00, 0x7fe00 // 0101
.word 0xbfd00, 0x7fe00 // 0110
.word 0xbfe00, 0x7fe00 // 0111
.word 0x7fd00, 0xbfd00 // 1000
.word 0x7fe00, 0xbfd00 // 1001
.word 0xbfd00, 0xbfd00 // 1010
.word 0xbfe00, 0xbfd00 // 1011
.word 0x7fd00, 0xbfe00 // 1100
.word 0x7fe00, 0xbfe00 // 1101
.word 0xbfd00, 0xbfe00 // 1110
.word 0xbfe00, 0xbfe00 // 1111
#else
.word 0x7fd00, 0x7fd00 // 0000
.word 0x7fd00, 0xbfd00 // 1000
.word 0x7fd00, 0x7fe00 // 0100
.word 0x7fd00, 0xbfe00 // 1100
.word 0xbfd00, 0x7fd00 // 0010
.word 0xbfd00, 0xbfd00 // 1010
.word 0xbfd00, 0x7fe00 // 0110
.word 0xbfd00, 0xbfe00 // 1110
.word 0x7fe00, 0x7fd00 // 0001
.word 0x7fe00, 0xbfd00 // 1001
.word 0x7fe00, 0x7fe00 // 0101
.word 0x7fe00, 0xbfe00 // 1101
.word 0xbfe00, 0x7fd00 // 0011
.word 0xbfe00, 0xbfd00 // 1011
.word 0xbfe00, 0x7fe00 // 0111
.word 0xbfe00, 0xbfe00 // 1111
#endif
// ----------------------------------------------------------------------------
// Full-resolution 2bpp encode (for 2bpp grayscale, or bitplaned RGB222)
// Even-x-position pixels are encoded as symbols with imbalance -4, and odd
// pixels with +4, so that we can mix-and-match our even/odd codewords and
// always get a properly balanced sequence:
//
// level 0: (05 -> 103), then (04 -> 1fc) (decimal 5, 4)
// level 1: (50 -> 130), then (51 -> 1cf) (decimal 80, 81)
// level 2: (af -> 230), then (ae -> 2cf) (decimal 175, 174)
// level 3: (fa -> 203), then (fb -> 2fc) (decimal 250, 251)
//
// These correspond to roughly 255 times (0, 1/3, 2/3, 1).
//
// Alternatively we could use symbols with 0 balance, which results in lower
// contrast but avoids the LSB bobble:
//
// level 0: (10 -> 1f0) always
// level 1: (5a -> 263) always
// level 2: (a5 -> 163) always
// level 3: (ef -> 2f0) always
// Table base pointer in r0. Input pixels in r2.
.macro encode_2bpp_body shift_instr shamt rd
\shift_instr \rd, r2, #\shamt
ands \rd, r3
ldr \rd, [r0, \rd]
.endm
// r0: input buffer (word-aligned)
// r1: output buffer (word-aligned)
// r2: output pixel count
decl_func tmds_encode_2bpp
push {r4-r7, lr}
mov r7, r8
push {r7}
mov r8, r0
adr r0, tmds_2bpp_table
// Mask: 4-bit index into 4-byte entries.
movs r3, #0x3c
// Limit pointer: 1 word per 2 pixels
lsls r2, #1
add r2, r1
mov ip, r2
b 2f
1:
mov r4, r8
ldmia r4!, {r2}
mov r8, r4
encode_2bpp_body lsls 2 r4
encode_2bpp_body lsrs 2 r5
encode_2bpp_body lsrs 6 r6
encode_2bpp_body lsrs 10 r7
stmia r1!, {r4-r7}
encode_2bpp_body lsrs 14 r4
encode_2bpp_body lsrs 18 r5
encode_2bpp_body lsrs 22 r6
encode_2bpp_body lsrs 26 r7
stmia r1!, {r4-r7}
2:
cmp r1, ip
blo 1b
pop {r7}
mov r8, r7
pop {r4-r7, pc}
.align 2
tmds_2bpp_table:
.word 0x7f103 // 00, 00
.word 0x7f130 // 01, 00
.word 0x7f230 // 10, 00
.word 0x7f203 // 11, 00
.word 0x73d03 // 00, 01
.word 0x73d30 // 01, 01
.word 0x73e30 // 10, 01
.word 0x73e03 // 11, 01
.word 0xb3d03 // 00, 10
.word 0xb3d30 // 01, 10
.word 0xb3e30 // 10, 10
.word 0xb3e03 // 11, 10
.word 0xbf103 // 00, 11
.word 0xbf130 // 01, 11
.word 0xbf230 // 10, 11
.word 0xbf203 // 11, 11
// ----------------------------------------------------------------------------
// Full-resolution RGB encode (not very practical)
// Non-doubled TMDS encode. 8.333 cycles per pixel, no exceptions. (This is
// taking horizontal blanking (at VGA) and dual core into account, and
// assuming the 3 channels are encoded individually.)
//
// Here is an idea
// Have a table with a 7 bit lookup. The lookup is the 6 colour data bits (in
// ACCUM0), concatenated with the sign bit of our running disparity (from
// ACCUM1). Each table entry is a 20-bit TMDS symbol (pseudodifferential),
// with the symbol's disparity stored left-justified in the upper 12 bits, as
// e.g. a 6 bit signed integer.
//
// - Load pixel data. cyc: 0.75 (ldmia 2 words, every 4 pixels)
// - Write pixel to ACCUM0. cyc: 1
// - Read address from PEEK2. cyc: 1
// - Load encoded pixel from address. cyc: 2
// - Write disparity data to ACCUM1_ADD cyc: 1
// - Write encoded data to output buffer. cyc: 1.25 (stmia 4 words, every 4 pixels)
//
// With decent register allocation we may be able to load 4 pixels at
// once (2 words), and write 4 at once (4 words). This gives 7 cyc/pix.
//
// One issue is that the TMDS data in the bottom of ACCUM1 will eventually
// overflow and affect the running disparity, but with 16 zeroes in between,
// this would take much longer than one scanline, so everything is fine if
// we clear the accumulator at the start of the scanline.
//
// Note that we need to use two interpolators to get the bits from both pixels
// -- we are not outputting a single DC-balanced stream, but rather two
// interleaved streams which are each DC-balanced. This is fine electrically,
// but our output here will *NOT* match the TMDS encoder given in the DVI
// spec.
// You can define TMDS_FULLRES_NO_DC_BALANCE to disable the running balance
// feedback. With the feedback enabled (default), the output is DC balanced,
// but there are just barely enough CPU cycles to do all the encode, so it's
// essentially a party trick. If you disable DC balancing, the performance is
// much better, and many monitors will still accept the signals as long as you
// DC couple your DVI signals.
.macro tmds_fullres_encode_loop_body ra rb
str \ra, [r2, #ACCUM0_OFFS + INTERP1]
str \ra, [r2, #ACCUM0_OFFS]
ldr \ra, [r2, #PEEK2_OFFS]
ldr \ra, [\ra]
#if !TMDS_FULLRES_NO_DC_BALANCE
str \ra, [r2, #ACCUM1_ADD_OFFS]
#endif
ldr \rb, [r2, #PEEK2_OFFS + INTERP1]
ldr \rb, [\rb]
#if !TMDS_FULLRES_NO_DC_BALANCE
str \rb, [r2, #ACCUM1_ADD_OFFS + INTERP1]
#endif
.endm
// r0: Input buffer (word-aligned)
// r1: Output buffer (word-aligned)
// r2: Pixel count
.macro tmds_fullres_encode_loop_16bpp
push {r4-r7, lr}
mov r4, r8
push {r4}
lsls r2, #2
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
// DC balance defined to be 0 at start of scanline:
movs r4, #0
str r4, [r2, #ACCUM1_OFFS]
#if TMDS_FULLRES_NO_DC_BALANCE
// Alternate parity between odd/even symbols if no feedback
mvns r4, r4
#endif
str r4, [r2, #ACCUM1_OFFS + INTERP1]
// Keep loop start pointer in r8 so we can get a longer backward branch
adr r4, 1f
adds r4, #1 // god damn thumb bit why is this a thing
mov r8, r4
b 2f
.align 2
1:
.rept 16
ldmia r0!, {r4, r6}
tmds_fullres_encode_loop_body r4 r5
tmds_fullres_encode_loop_body r6 r7
stmia r1!, {r4, r5, r6, r7}
.endr
2:
cmp r1, ip
beq 1f
bx r8
1:
pop {r4}
mov r8, r4
pop {r4-r7, pc}
.endm
// One copy each in X and Y, so the two cores don't step on each other
decl_func_x tmds_fullres_encode_loop_16bpp_x
tmds_fullres_encode_loop_16bpp
decl_func_y tmds_fullres_encode_loop_16bpp_y
tmds_fullres_encode_loop_16bpp
.macro tmds_fullres_encode_loop_body_leftshift ra rb
// Note we apply the leftshift for INTERP0 only
str \ra, [r2, #ACCUM0_OFFS + INTERP1]
lsls \ra, r3
str \ra, [r2, #ACCUM0_OFFS]
ldr \ra, [r2, #PEEK2_OFFS]
ldr \ra, [\ra]
#if !TMDS_FULLRES_NO_DC_BALANCE
str \ra, [r2, #ACCUM1_ADD_OFFS]
#endif
ldr \rb, [r2, #PEEK2_OFFS + INTERP1]
ldr \rb, [\rb]
#if !TMDS_FULLRES_NO_DC_BALANCE
str \rb, [r2, #ACCUM1_ADD_OFFS + INTERP1]
#endif
.endm
// r0: Input buffer (word-aligned)
// r1: Output buffer (word-aligned)
// r2: Pixel count
// r3: Left shift amount
.macro tmds_fullres_encode_loop_16bpp_leftshift
push {r4-r7, lr}
mov r4, r8
mov r5, r9
push {r4-r5}
lsls r2, #2
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
// DC balance defined to be 0 at start of scanline:
movs r4, #0
str r4, [r2, #ACCUM1_OFFS]
#if TMDS_FULLRES_NO_DC_BALANCE
// Alternate parity between odd/even symbols if there's no balance feedback
mvns r4, r4
#endif
str r4, [r2, #ACCUM1_OFFS + INTERP1]
adr r4, 1f
adds r4, #1
mov r8, r4
b 2f
.align 2
1:
.rept 16 // 64 pixels per iteration
ldmia r0!, {r4, r6}
tmds_fullres_encode_loop_body_leftshift r4 r5
tmds_fullres_encode_loop_body_leftshift r6 r7
stmia r1!, {r4, r5, r6, r7}
.endr
2:
cmp r1, ip
beq 1f
bx r8
1:
pop {r4-r5}
mov r8, r4
mov r9, r5
pop {r4-r7, pc}
.endm
decl_func_x tmds_fullres_encode_loop_16bpp_leftshift_x
tmds_fullres_encode_loop_16bpp_leftshift
decl_func_y tmds_fullres_encode_loop_16bpp_leftshift_y
tmds_fullres_encode_loop_16bpp_leftshift
// ----------------------------------------------------------------------------
// Full-resolution 8bpp paletted encode
// Variant of tmds_fullres_encode_loop_16bpp that reads
// 8-bit wide pixels packed 4 per word. The interpolator
// base is set to a reordered list of TMDS symbols based
// on a user colour palette.
// Two pixels input in rd[17:2]. Two symbols output in rd[19:0]. r2 contains
// interp base pointer. r7 used as temporary.
.macro tmds_palette_encode_loop_body rd
str \rd, [r2, #ACCUM0_OFFS]
str \rd, [r2, #ACCUM0_OFFS + INTERP1]
ldr \rd, [r2, #PEEK2_OFFS]
ldr \rd, [\rd]
#if !TMDS_FULLRES_NO_DC_BALANCE
str \rd, [r2, #ACCUM1_ADD_OFFS]
#endif
ldr r7, [r2, #PEEK2_OFFS + INTERP1]
ldr r7, [r7]
#if !TMDS_FULLRES_NO_DC_BALANCE
str r7, [r2, #ACCUM1_ADD_OFFS + INTERP1]
#endif
lsls r7, #10
orrs \rd, r7
.endm
.macro tmds_palette_encode_loop
push {r4-r7, lr}
mov r4, r8
push {r4}
lsls r2, #1
add r2, r1
mov ip, r2
ldr r2, =(SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET)
// DC balance defined to be 0 at start of scanline:
movs r4, #0
str r4, [r2, #ACCUM1_OFFS]
#if TMDS_FULLRES_NO_DC_BALANCE
// Alternate parity between odd/even symbols if there's no balance feedback
mvns r4, r4
#endif
str r4, [r2, #ACCUM1_OFFS + INTERP1]
// Keep loop start pointer in r8 so we can get a longer backward branch
adr r4, 1f
adds r4, #1 // god damn thumb bit why is this a thing
mov r8, r4
b 2f
.align 2
1:
.rept 10
ldmia r0!, {r3, r5}
lsrs r4, r3, #14
lsls r3, #2
lsrs r6, r5, #14
lsls r5, #2
tmds_palette_encode_loop_body r3
tmds_palette_encode_loop_body r4
tmds_palette_encode_loop_body r5
tmds_palette_encode_loop_body r6
stmia r1!, {r3, r4, r5, r6}
.endr
2:
cmp r1, ip
beq 1f
bx r8
1:
pop {r4}
mov r8, r4
pop {r4-r7, pc}
.endm
decl_func_x tmds_palette_encode_loop_x
tmds_palette_encode_loop
decl_func_y tmds_palette_encode_loop_y
tmds_palette_encode_loop

305
src/libdvi/tmds_encode.c Normal file
View file

@ -0,0 +1,305 @@
#include "hardware/interp.h"
#include "tmds_encode.h"
#include "hardware/gpio.h"
#include "hardware/sync.h"
static const uint32_t __scratch_x("tmds_table") tmds_table[] = {
#include "tmds_table.h"
};
// Fullres table is bandwidth-critical, so gets one copy for each scratch
// memory. There is a third copy which can go in flash, because it's just used
// to generate palette LUTs. The ones we don't use will get garbage collected
// during linking.
const uint32_t __scratch_x("tmds_table_fullres_x") tmds_table_fullres_x[] = {
#include "tmds_table_fullres.h"
};
const uint32_t __scratch_y("tmds_table_fullres_y") tmds_table_fullres_y[] = {
#include "tmds_table_fullres.h"
};
// Configure an interpolator to extract a single colour channel from each of a pair
// of pixels, with the first pixel's lsb at pixel_lsb, and the pixels being
// pixel_width wide. Produce a LUT address for the first pixel's colour data on
// LANE0, and the second pixel's colour data on LANE1.
//
// Returns nonzero if the *_leftshift variant of the encoder loop must be used
// (needed for blue channel because I was a stubborn idiot and didn't put
// signed/bidirectional shift on interpolator, very slightly slower). The
// return value is the size of left shift required.
static int __not_in_flash_func(configure_interp_for_addrgen)(interp_hw_t *interp, uint channel_msb, uint channel_lsb, uint pixel_lsb, uint pixel_width, uint lut_index_width, const uint32_t *lutbase) {
interp_config c;
const uint index_shift = 2; // scaled lookup for 4-byte LUT entries
int shift_channel_to_index = pixel_lsb + channel_msb - (lut_index_width - 1) - index_shift;
int oops = 0;
if (shift_channel_to_index < 0) {
// "It's ok we'll fix it in software"
oops = -shift_channel_to_index;
shift_channel_to_index = 0;
}
uint index_msb = index_shift + lut_index_width - 1;
c = interp_default_config();
interp_config_set_shift(&c, shift_channel_to_index);
interp_config_set_mask(&c, index_msb - (channel_msb - channel_lsb), index_msb);
interp_set_config(interp, 0, &c);
c = interp_default_config();
interp_config_set_shift(&c, pixel_width + shift_channel_to_index);
interp_config_set_mask(&c, index_msb - (channel_msb - channel_lsb), index_msb);
interp_config_set_cross_input(&c, true);
interp_set_config(interp, 1, &c);
interp->base[0] = (uint32_t)lutbase;
interp->base[1] = (uint32_t)lutbase;
return oops;
}
// Extract up to 6 bits from a buffer of 16 bit pixels, and produce a buffer
// of TMDS symbols from this colour channel. Number of pixels must be even,
// pixel buffer must be word-aligned.
void __not_in_flash_func(tmds_encode_data_channel_16bpp)(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb) {
interp_hw_save_t interp0_save;
interp_save(interp0_hw, &interp0_save);
int require_lshift = configure_interp_for_addrgen(interp0_hw, channel_msb, channel_lsb, 0, 16, 6, tmds_table);
if (require_lshift)
tmds_encode_loop_16bpp_leftshift(pixbuf, symbuf, n_pix, require_lshift);
else
tmds_encode_loop_16bpp(pixbuf, symbuf, n_pix);
interp_restore(interp0_hw, &interp0_save);
}
// As above, but 8 bits per pixel, multiple of 4 pixels, and still word-aligned.
void __not_in_flash_func(tmds_encode_data_channel_8bpp)(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb) {
interp_hw_save_t interp0_save, interp1_save;
interp_save(interp0_hw, &interp0_save);
interp_save(interp1_hw, &interp1_save);
// Note that for 8bpp, some left shift is always required for pixel 0 (any
// channel), which destroys some MSBs of pixel 3. To get around this, pixel
// data sent to interp1 is *not left-shifted*
int require_lshift = configure_interp_for_addrgen(interp0_hw, channel_msb, channel_lsb, 0, 8, 6, tmds_table);
int lshift_upper = configure_interp_for_addrgen(interp1_hw, channel_msb, channel_lsb, 16, 8, 6, tmds_table);
assert(!lshift_upper); (void)lshift_upper;
if (require_lshift)
tmds_encode_loop_8bpp_leftshift(pixbuf, symbuf, n_pix, require_lshift);
else
tmds_encode_loop_8bpp(pixbuf, symbuf, n_pix);
interp_restore(interp0_hw, &interp0_save);
interp_restore(interp1_hw, &interp1_save);
}
// ----------------------------------------------------------------------------
// Code for full-resolution TMDS encode (barely possible, utterly impractical):
// Different scheme used for full res as the fun pixel-doubling DC balance
// trick doesn't work, so we need to actually do running disparity. ACCUM0 has
// pixel data, ACCUM1 has running disparity. INTERP0 is used to process even
// pixels, and INTERP1 for odd pixels. Note this means that even and odd
// symbols have their DC balance handled separately, which is not to spec.
static int __not_in_flash_func(configure_interp_for_addrgen_fullres)(interp_hw_t *interp, uint channel_msb, uint channel_lsb, uint lut_index_width, const uint32_t *lutbase) {
const uint index_shift = 2; // scaled lookup for 4-byte LUT entries
int shift_channel_to_index = channel_msb - (lut_index_width - 1) - index_shift;
int oops = 0;
if (shift_channel_to_index < 0) {
// "It's ok we'll fix it in software"
oops = -shift_channel_to_index;
shift_channel_to_index = 0;
}
uint index_msb = index_shift + lut_index_width - 1;
interp_config c;
// Shift and mask colour channel to lower 6 bits of LUT index (note lut_index_width excludes disparity sign)
c = interp_default_config();
interp_config_set_shift(&c, shift_channel_to_index);
interp_config_set_mask(&c, index_msb - (channel_msb - channel_lsb), index_msb);
interp_set_config(interp, 0, &c);
// Concatenate disparity (ACCUM1) sign onto the LUT index
c = interp_default_config();
interp_config_set_shift(&c, 30 - index_msb);
interp_config_set_mask(&c, index_msb + 1, index_msb + 1);
interp_set_config(interp, 1, &c);
interp->base[2] = (uint32_t)lutbase;
return oops;
}
void __not_in_flash_func(tmds_encode_data_channel_fullres_16bpp)(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb) {
uint core = get_core_num();
#if !TMDS_FULLRES_NO_INTERP_SAVE
interp_hw_save_t interp0_save, interp1_save;
interp_save(interp0_hw, &interp0_save);
interp_save(interp1_hw, &interp1_save);
#endif
// There is a copy of the inner loop and the LUT in both scratch X and
// scratch Y memories. Use X on core 1 and Y on core 0 so the cores don't
// tread on each other's toes too much.
const uint32_t *lutbase = core ? tmds_table_fullres_x : tmds_table_fullres_y;
int lshift_lower = configure_interp_for_addrgen_fullres(interp0_hw, channel_msb, channel_lsb, 6, lutbase);
int lshift_upper = configure_interp_for_addrgen_fullres(interp1_hw, channel_msb + 16, channel_lsb + 16, 6, lutbase);
assert(!lshift_upper); (void)lshift_upper;
if (lshift_lower) {
(core ?
tmds_fullres_encode_loop_16bpp_leftshift_x :
tmds_fullres_encode_loop_16bpp_leftshift_y
)(pixbuf, symbuf, n_pix, lshift_lower);
}
else {
(core ?
tmds_fullres_encode_loop_16bpp_x :
tmds_fullres_encode_loop_16bpp_y
)(pixbuf, symbuf, n_pix);
}
#if !TMDS_FULLRES_NO_INTERP_SAVE
interp_restore(interp0_hw, &interp0_save);
interp_restore(interp1_hw, &interp1_save);
#endif
}
static const int8_t imbalance_lookup[16] = { -4, -2, -2, 0, -2, 0, 0, 2, -2, 0, 0, 2, 0, 2, 2, 4 };
static inline int byte_imbalance(uint32_t x)
{
return imbalance_lookup[x >> 4] + imbalance_lookup[x & 0xF];
}
static void tmds_encode_symbols(uint8_t pixel, uint32_t* negative_balance_sym, uint32_t* positive_balance_sym)
{
int pixel_imbalance = byte_imbalance(pixel);
uint32_t sym = pixel & 1;
if (pixel_imbalance > 0 || (pixel_imbalance == 0 && sym == 0)) {
for (int i = 0; i < 7; ++i) {
sym |= (~((sym >> i) ^ (pixel >> (i + 1))) & 1) << (i + 1);
}
}
else {
for (int i = 0; i < 7; ++i) {
sym |= ( ((sym >> i) ^ (pixel >> (i + 1))) & 1) << (i + 1);
}
sym |= 0x100;
}
int imbalance = byte_imbalance(sym & 0xFF);
if (imbalance == 0) {
if ((sym & 0x100) == 0) sym ^= 0x2ff;
*positive_balance_sym = sym;
*negative_balance_sym = sym;
return;
}
else if (imbalance > 0) {
*negative_balance_sym = (sym ^ 0x2ff) | (((-imbalance + imbalance_lookup[2 ^ (sym >> 8)] + 2) & 0x3F) << 26);
*positive_balance_sym = sym | ((imbalance + imbalance_lookup[sym >> 8] + 2) << 26);
}
else {
*negative_balance_sym = sym | (((imbalance + imbalance_lookup[sym >> 8] + 2) & 0x3F) << 26);
*positive_balance_sym = (sym ^ 0x2ff) | ((-imbalance + imbalance_lookup[2 ^ (sym >> 8)] + 2) << 26);
}
}
// This takes a 16-bit (RGB 565) colour palette and makes palettes of TMDS symbols suitable
// for performing fullres encode.
// The TMDS palette buffer should be 6 * n_palette words long.
// n_palette must be a power of 2 <= 256.
void tmds_setup_palette_symbols(const uint16_t *palette, uint32_t *tmds_palette, size_t n_palette) {
uint32_t* tmds_palette_blue = tmds_palette;
uint32_t* tmds_palette_green = tmds_palette + 2 * n_palette;
uint32_t* tmds_palette_red = tmds_palette + 4 * n_palette;
for (int i = 0; i < n_palette; ++i) {
uint16_t blue = (palette[i] << 3) & 0xf8;
uint16_t green = (palette[i] >> 3) & 0xfc;
uint16_t red = (palette[i] >> 8) & 0xf8;
tmds_encode_symbols(blue, &tmds_palette_blue[i], &tmds_palette_blue[i + n_palette]);
tmds_encode_symbols(green, &tmds_palette_green[i], &tmds_palette_green[i + n_palette]);
tmds_encode_symbols(red, &tmds_palette_red[i], &tmds_palette_red[i + n_palette]);
}
}
// This takes a 24-bit (RGB 888) colour palette and makes palettes of TMDS symbols suitable
// for performing fullres encode.
// The TMDS palette buffer should be 6 * n_palette words long.
// n_palette must be a power of 2 <= 256.
void tmds_setup_palette24_symbols(const uint32_t *palette, uint32_t *tmds_palette, size_t n_palette) {
uint32_t* tmds_palette_blue = tmds_palette;
uint32_t* tmds_palette_green = tmds_palette + 2 * n_palette;
uint32_t* tmds_palette_red = tmds_palette + 4 * n_palette;
for (int i = 0; i < n_palette; ++i) {
uint16_t blue = palette[i] & 0xff;
uint16_t green = (palette[i] >> 8) & 0xff;
uint16_t red = (palette[i] >> 16) & 0xff;
tmds_encode_symbols(blue, &tmds_palette_blue[i], &tmds_palette_blue[i + n_palette]);
tmds_encode_symbols(green, &tmds_palette_green[i], &tmds_palette_green[i + n_palette]);
tmds_encode_symbols(red, &tmds_palette_red[i], &tmds_palette_red[i + n_palette]);
}
}
// Encode palette data for all 3 channels.
// pixbuf is an array of n_pix 8-bit wide pixels containing palette values (32-bit word aligned)
// tmds_palette is a palette of TMDS symbols produced by tmds_setup_palette_symbols
// symbuf is 3*n_pix 32-bit words, this function writes the symbol values for each of the channels to it.
void __not_in_flash_func(tmds_encode_palette_data)(const uint32_t *pixbuf, const uint32_t *tmds_palette, uint32_t *symbuf, size_t n_pix, uint32_t palette_bits) {
uint core = get_core_num();
#if !TMDS_FULLRES_NO_INTERP_SAVE
interp_hw_save_t interp0_save, interp1_save;
interp_save(interp0_hw, &interp0_save);
interp_save(interp1_hw, &interp1_save);
#endif
interp0_hw->base[2] = (uint32_t)tmds_palette;
interp1_hw->base[2] = (uint32_t)tmds_palette;
// Lane 0 on both interpolators masks the palette bits, starting at bit 2,
// The second interpolator also shifts to read the 2nd or 4th byte of the word.
interp0_hw->ctrl[0] =
(2 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) |
((palette_bits + 1) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB);
interp1_hw->ctrl[0] =
(8 << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) |
(2 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) |
((palette_bits + 1) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB);
// Lane 1 shifts and masks the sign bit into the right position to add to the symbol
// table index to choose the negative disparity symbols if the sign is negative.
const uint32_t ctrl_lane_1 =
((31 - (palette_bits + 2)) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) |
(palette_bits + 2) * ((1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB));
interp0_hw->ctrl[1] = ctrl_lane_1;
interp1_hw->ctrl[1] = ctrl_lane_1;
if (core) {
tmds_palette_encode_loop_x(pixbuf, symbuf, n_pix);
interp0_hw->base[2] = (uint32_t)(tmds_palette + (2 << palette_bits));
interp1_hw->base[2] = (uint32_t)(tmds_palette + (2 << palette_bits));
tmds_palette_encode_loop_x(pixbuf, symbuf + (n_pix >> 1), n_pix);
interp0_hw->base[2] = (uint32_t)(tmds_palette + (4 << palette_bits));
interp1_hw->base[2] = (uint32_t)(tmds_palette + (4 << palette_bits));
tmds_palette_encode_loop_x(pixbuf, symbuf + n_pix, n_pix);
} else {
tmds_palette_encode_loop_y(pixbuf, symbuf, n_pix);
interp0_hw->base[2] = (uint32_t)(tmds_palette + (2 << palette_bits));
interp1_hw->base[2] = (uint32_t)(tmds_palette + (2 << palette_bits));
tmds_palette_encode_loop_y(pixbuf, symbuf + (n_pix >> 1), n_pix);
interp0_hw->base[2] = (uint32_t)(tmds_palette + (4 << palette_bits));
interp1_hw->base[2] = (uint32_t)(tmds_palette + (4 << palette_bits));
tmds_palette_encode_loop_y(pixbuf, symbuf + n_pix, n_pix);
}
#if !TMDS_FULLRES_NO_INTERP_SAVE
interp_restore(interp0_hw, &interp0_save);
interp_restore(interp1_hw, &interp1_save);
#endif
}

46
src/libdvi/tmds_encode.h Normal file
View file

@ -0,0 +1,46 @@
#ifndef _TMDS_ENCODE_H_
#define _TMDS_ENCODE_H_
#include "hardware/interp.h"
#include "dvi_config_defs.h"
#if defined(__cplusplus)
extern "C"
{
#endif
// Functions from tmds_encode.c
void tmds_encode_data_channel_16bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb);
void tmds_encode_data_channel_8bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb);
void tmds_encode_data_channel_fullres_16bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb);
void tmds_setup_palette_symbols(const uint16_t *palette, uint32_t *symbuf, size_t n_palette);
void tmds_setup_palette24_symbols(const uint32_t *palette, uint32_t *symbuf, size_t n_palette);
void tmds_encode_palette_data(const uint32_t *pixbuf, const uint32_t *tmds_palette, uint32_t *symbuf, size_t n_pix, uint32_t palette_bits);
// Functions from tmds_encode.S
void tmds_encode_1bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
void tmds_encode_2bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
// Uses interp0:
void tmds_encode_loop_16bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
void tmds_encode_loop_16bpp_leftshift(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint leftshift);
// Uses interp0 and interp1:
void tmds_encode_loop_8bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
void tmds_encode_loop_8bpp_leftshift(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint leftshift);
// Uses interp0 and interp1:
// (Note a copy is provided in scratch memories X and Y)
void tmds_fullres_encode_loop_16bpp_x(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
void tmds_fullres_encode_loop_16bpp_y(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
void tmds_fullres_encode_loop_16bpp_leftshift_x(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint leftshift);
void tmds_fullres_encode_loop_16bpp_leftshift_y(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint leftshift);
void tmds_palette_encode_loop_x(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
void tmds_palette_encode_loop_y(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
#if defined(__cplusplus)
}
#endif
#endif

View file

@ -0,0 +1,46 @@
.program tmds_encode_1bpp
; 1bpp black/white pixels go in, TMDS symbols come out.
; Each output word contains two output symbols, each 10 bits in size,
; right-justified. The least-significant symbol is displayed first.
;
; We can encode using the following LUT: (yes this is compliant)
;
; x % 2 | colour | symbol
; ------+--------+-------
; 0 | 0 | 0x100
; 0 | 1 | 0x200
; 1 | 0 | 0x1ff
; 1 | 1 | 0x2ff
;
; OSR: shift to right, autopull, threshold 32
; ISR: shift to right, autopush, threshold 24
;
; Note the ISR needs to be shifted to *right* so that we can get the first
; pixel in the less-significant position. Threshold 24 so we can get 8x 0-bits
; at the LSBs for free :)
even_pixel:
out x, 1
mov y, ~x
in y, 1
in x, 1
odd_pixel:
mov x, ~null
in x, 8
out x, 1
mov y, ~x
in y, 1
in x, 13 ; Bring total shift to 24, triggering push.
% c-sdk {
static inline void tmds_encode_1bpp_init(PIO pio, uint sm) {
uint offset = pio_add_program(pio, &tmds_encode_1bpp_program);
pio_sm_config c = tmds_encode_1bpp_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_in_shift(&c, true, true, 24);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

76
src/libdvi/tmds_table.h Normal file
View file

@ -0,0 +1,76 @@
// Generated from tmds_table_gen.py
//
// This table converts a 6 bit data input into a pair of TMDS data symbols
// with data content *almost* equal (1 LSB off) to input value left shifted by
// two. The pairs of symbols have a net DC balance of 0.
//
// The two symbols are concatenated in the 20 LSBs of a data word, with the
// first symbol in least-significant position.
//
// Note the declaration isn't included here, just the table body. This is in
// case you want multiple copies of the table in different SRAMs (particularly
// scratch X/Y).
0x7fd00u,
0x40dfcu,
0x41df8u,
0x7ed04u,
0x43df0u,
0x7cd0cu,
0x7dd08u,
0x42df4u,
0x47de0u,
0x78d1cu,
0x79d18u,
0x46de4u,
0x7bd10u,
0x44decu,
0x45de8u,
0xafa41u,
0x4fdc0u,
0x70d3cu,
0x71d38u,
0x4edc4u,
0x73d30u,
0x4cdccu,
0x4ddc8u,
0xa7a61u,
0x77d20u,
0x48ddcu,
0x49dd8u,
0xa3a71u,
0x4bdd0u,
0xa1a79u,
0xa0a7du,
0x9fa81u,
0x5fd80u,
0x60d7cu,
0x61d78u,
0x5ed84u,
0x63d70u,
0x5cd8cu,
0x5dd88u,
0xb7a21u,
0x67d60u,
0x58d9cu,
0x59d98u,
0xb3a31u,
0x5bd90u,
0xb1a39u,
0xb0a3du,
0x8fac1u,
0x6fd40u,
0x50dbcu,
0x51db8u,
0xbba11u,
0x53db0u,
0xb9a19u,
0xb8a1du,
0x87ae1u,
0x57da0u,
0xbda09u,
0xbca0du,
0x83af1u,
0xbea05u,
0x81af9u,
0x80afdu,
0xbfa01u,

View file

@ -0,0 +1,139 @@
// Each entry consists of a 10 bit TMDS symbol in pseudo-differential format
// (10 LSBs) and the symbol's disparity as a 6 bit signed integer (the 6
// MSBs). There is a 16 bit gap in between them, which is actually vital for
// the way the TMDS encode works!
//
// There are 128 1-word entries. The lookup index should be the concatenation
// of the sign bit of current running disparity, with 6 bits of colour channel
// data.
// Non-negative running disparity:
0xe0000100,
0xf8000303,
0x00000307,
0xe8000104,
0x000001f0,
0xf000010c,
0xe8000108,
0x0000030b,
0xf80001e0,
0xf800011c,
0xf0000118,
0x000001e4,
0xe8000110,
0x00000313,
0x000001e8,
0xf0000241,
0xf00001c0,
0x0000013c,
0xf8000138,
0xf80001c4,
0xf0000130,
0x000001cc,
0xf80001c8,
0xf8000261,
0xe8000120,
0x00000323,
0x000001d8,
0x00000271,
0xf80001d0,
0xf0000086,
0xe8000082,
0xf0000281,
0xe8000180,
0x00000383,
0x00000178,
0xf0000184,
0xf8000170,
0xf800018c,
0xf0000188,
0xf0000221,
0xf0000160,
0x0000019c,
0xf8000198,
0xf8000231,
0xf0000190,
0x00000239,
0xf00000c2,
0xf80002c1,
0xe8000140,
0x00000343,
0x000001b8,
0xf0000211,
0xf80001b0,
0xf8000219,
0x0000021d,
0x000002e1,
0xf00001a0,
0xf0000209,
0xf800020d,
0xf000000e,
0xf0000205,
0xe8000006,
0xe0000002,
0xe8000201,
// Negative running disparity:
0x280003ff,
0x100001fc,
0x080001f8,
0x200003fb,
0x000001f0,
0x180003f3,
0x200003f7,
0x080001f4,
0x1000031f,
0x100003e3,
0x180003e7,
0x000001e4,
0x200003ef,
0x080001ec,
0x000001e8,
0x080000be,
0x1800033f,
0x0000013c,
0x100003c7,
0x1000033b,
0x180003cf,
0x000001cc,
0x10000337,
0x0000009e,
0x200003df,
0x080001dc,
0x000001d8,
0x00000271,
0x1000032f,
0x08000279,
0x1000027d,
0x0800007e,
0x2000037f,
0x0800017c,
0x00000178,
0x1800037b,
0x1000038f,
0x10000373,
0x18000377,
0x080000de,
0x1800039f,
0x0000019c,
0x10000367,
0x000000ce,
0x1800036f,
0x00000239,
0x0800023d,
0x0000003e,
0x200003bf,
0x080001bc,
0x000001b8,
0x080000ee,
0x1000034f,
0x000000e6,
0x0000021d,
0x000002e1,
0x1800035f,
0x080000f6,
0x000000f2,
0x080002f1,
0x080000fa,
0x100002f9,
0x180002fd,
0x100000fe,

150
src/libdvi/tmds_table_gen.py Executable file
View file

@ -0,0 +1,150 @@
#!/usr/bin/env python3
# The key fact is that, if x is even, and the encoder currently has a running
# imbalance of 0, encoding x followed by x + 1 produces a symbol pair with a
# net balance of 0.
#
# This is a reasonable constraint, because we only want RGB565 (so 6 valid
# channel data bits -> data is multiple of 4), and can probably tolerate
# 0.25LSB of noise :)
#
# This means that encoding a half-horizontal-resolution scanline buffer is a
# simple LUT operation for each colour channel, because we have made the
# encoding process stateless by guaranteeing 0 balance.
def popcount(x):
n = 0
while x:
n += 1
x = x & (x - 1)
return n
# Equivalent to N1(q) - N0(q) in the DVI spec
def byteimbalance(x):
return 2 * popcount(x) - 8
# This is a direct translation of "Figure 3-5. T.M.D.S. Encode Algorithm" on
# page 29 of DVI 1.0 spec
class TMDSEncode:
ctrl_syms = {
0b00: 0b1101010100,
0b01: 0b0010101011,
0b10: 0b0101010100,
0b11: 0b1010101011
}
def __init__(self):
self.imbalance = 0
def encode(self, d, c, de):
if not de:
self.imbalance = 0
return self.ctrl_syms[c]
# Minimise transitions
q_m = d & 0x1
if popcount(d) > 4 or (popcount(d) == 4 and not d & 0x1):
for i in range(7):
q_m = q_m | (~(q_m >> i ^ d >> i + 1) & 0x1) << i + 1
else:
for i in range(7):
q_m = q_m | ( (q_m >> i ^ d >> i + 1) & 0x1) << i + 1
q_m = q_m | 0x100
# Correct DC balance
inversion_mask = 0x2ff
q_out = 0
if self.imbalance == 0 or byteimbalance(q_m & 0xff) == 0:
q_out = q_m ^ (0 if q_m & 0x100 else inversion_mask)
if q_m & 0x100:
self.imbalance += byteimbalance(q_m & 0xff)
else:
self.imbalance -= byteimbalance(q_m & 0xff)
elif (self.imbalance > 0) == (byteimbalance(q_m & 0xff) > 0):
q_out = q_m ^ inversion_mask
self.imbalance += ((q_m & 0x100) >> 7) - byteimbalance(q_m & 0xff)
else:
q_out = q_m
self.imbalance += byteimbalance(q_m & 0xff) - ((~q_m & 0x100) >> 7)
return q_out
# Turn a bitmap of width n into n pairs of pseudo-differential bits
def differentialise(x, n):
accum = 0
for i in range(n):
accum <<= 2
if x & (1 << (n - 1)):
accum |= 0b01
else:
accum |= 0b10
x <<= 1
return accum
enc = TMDSEncode()
###
# Pixel-doubled table:
# for i in range(0, 256, 4):
# sym0 = enc.encode(i, 0, 1)
# sym1 = enc.encode(i ^ 1, 0, 1)
# assert(enc.imbalance == 0)
# print(f"0x{sym0 | (sym1 << 10):05x}u,")
###
# Fullres 1bpp table: (each entry is 2 words, 4 pixels)
# (note trick here is that encoding 0x00 or 0xff sets imbalance to -8, and
# (encoding 0x01 or 0xfe returns imbalance to 0, so we alternate between these
# (two pairs of dark/light colours. Creates some fairly subtle vertical
# (banding, but it's cheap.
# for i in range(1 << 4):
# syms = list(enc.encode((0xff if i & 1 << j else 0) ^ j & 0x01, 0, 1) for j in range(4))
# print(f"0x{syms[0] | syms[1] << 10:05x}, 0x{syms[2] | syms[3] << 10:05x}")
# assert(enc.imbalance == 0)
###
# Fullres table stuff:
# def disptable_format(sym):
# return sym | ((popcount(sym) * 2 - 10 & 0x3f) << 26)
# print("// Non-negative running disparity:")
# for i in range(0, 256, 4):
# enc.imbalance = 1
# print("0x{:08x},".format(disptable_format(enc.encode(i, 0, 1))))
# print("// Negative running disparity:")
# for i in range(0, 256, 4):
# enc.imbalance = -1
# print("0x{:08x},".format(disptable_format(enc.encode(i, 0, 1))))
###
# Control symbols:
# for i in range(4):
# sym = enc.encode(0, i, 0)
# print(f"0x{sym << 10 | sym:05x},")
###
# Find zero-balance symbols:
# for i in range(256):
# enc.imbalance = 0
# sym = enc.encode(i, 0, 1)
# if enc.imbalance == 0:
# print(f"{i:02x}: {sym:03x}")
###
# Generate 2bpp table based on above experiment:
levels_2bpp_even = [0x05, 0x50, 0xaf, 0xfa]
levels_2bpp_odd = [0x04, 0x51, 0xae, 0xfb]
for i1, p1 in enumerate(levels_2bpp_odd):
for i0, p0 in enumerate(levels_2bpp_even):
sym0 = enc.encode(p0, 0, 1)
sym1 = enc.encode(p1, 0, 1)
assert(enc.imbalance == 0)
print(f".word 0x{sym1 << 10 | sym0:05x} // {i0:02b}, {i1:02b}")

View file

@ -0,0 +1,83 @@
#ifndef _UTIL_QUEUE_U32_INLINE_H
#define _UTIL_QUEUE_U32_INLINE_H
// Faster versions of the functions found in pico/util/queue.h, for the common
// case of 32-bit-sized elements. Can be used on the same queue data
// structure, and mixed freely with the generic access methods, as long as
// element_size == 4.
#include "pico/util/queue.h"
#include "hardware/sync.h"
static inline uint16_t _queue_inc_index_u32(queue_t *q, uint16_t index) {
if (++index > q->element_count) { // > because we have element_count + 1 elements
index = 0;
}
return index;
}
static inline bool queue_try_add_u32(queue_t *q, void *data) {
bool success = false;
uint32_t flags = spin_lock_blocking(q->core.spin_lock);
if (queue_get_level_unsafe(q) != q->element_count) {
((uint32_t*)q->data)[q->wptr] = *(uint32_t*)data;
q->wptr = _queue_inc_index_u32(q, q->wptr);
success = true;
}
spin_unlock(q->core.spin_lock, flags);
if (success) __sev();
return success;
}
static inline bool queue_try_remove_u32(queue_t *q, void *data) {
bool success = false;
uint32_t flags = spin_lock_blocking(q->core.spin_lock);
if (queue_get_level_unsafe(q) != 0) {
*(uint32_t*)data = ((uint32_t*)q->data)[q->rptr];
q->rptr = _queue_inc_index_u32(q, q->rptr);
success = true;
}
spin_unlock(q->core.spin_lock, flags);
if (success) __sev();
return success;
}
static inline bool queue_try_peek_u32(queue_t *q, void *data) {
bool success = false;
uint32_t flags = spin_lock_blocking(q->core.spin_lock);
if (queue_get_level_unsafe(q) != 0) {
*(uint32_t*)data = ((uint32_t*)q->data)[q->rptr];
success = true;
}
spin_unlock(q->core.spin_lock, flags);
return success;
}
static inline void queue_add_blocking_u32(queue_t *q, void *data) {
bool done;
do {
done = queue_try_add_u32(q, data);
if (done) break;
__wfe();
} while (true);
}
static inline void queue_remove_blocking_u32(queue_t *q, void *data) {
bool done;
do {
done = queue_try_remove_u32(q, data);
if (done) break;
__wfe();
} while (true);
}
static inline void queue_peek_blocking_u32(queue_t *q, void *data) {
bool done;
do {
done = queue_try_peek_u32(q, data);
if (done) break;
__wfe();
} while (true);
}
#endif