Compare commits
72 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c33ff22666 | ||
|
|
aad0f12ad3 | ||
|
|
fc2a1bc89b | ||
|
|
2d3e33ac9c | ||
|
|
0de9720e78 | ||
|
|
41844f5fd6 | ||
| c93f1fc417 | |||
|
|
27edec7280 | ||
|
|
3d7a8c8426 | ||
|
|
33355392b3 | ||
|
|
52ca906be0 | ||
|
|
ec5c86ef26 | ||
|
|
28f931609e | ||
|
|
8a6a572f49 | ||
|
|
aeac173e08 | ||
|
|
8f7d9af4c1 | ||
|
|
511276be50 | ||
|
|
ff3a338155 | ||
|
|
0b7be17886 | ||
|
|
7910e26c6d | ||
|
|
f66a28c7c4 | ||
|
|
d006e19bb9 | ||
|
|
97bbc9d3f2 | ||
|
|
7eee74266a | ||
|
|
1c4bbae801 | ||
|
|
c5334f2975 | ||
|
|
f7ce41e04e | ||
|
|
d03b6fb4cd | ||
|
|
2858950c55 | ||
|
|
474ecf171f | ||
|
|
eb000eabdb | ||
|
|
382363a876 | ||
|
|
40e15367fe | ||
|
|
fe9e8298b5 | ||
|
|
278ff38b3c | ||
|
|
ea20cddab5 | ||
|
|
a5c14a480d | ||
|
|
98e8b73ec6 | ||
|
|
10795d8fa3 | ||
|
|
1f8a21094a | ||
|
|
75896fc27d | ||
|
|
8a77cb318e | ||
|
|
a4a97b3244 | ||
|
|
9c99e4246d | ||
|
|
6d00c648a0 | ||
|
|
0a16847855 | ||
|
|
2486500a14 | ||
|
|
837a2f97bd | ||
|
|
248763ddf9 | ||
|
|
b306fc38ca | ||
|
|
f603f4d8bd | ||
|
|
2e91831cc9 | ||
|
|
a7b4dad24d | ||
|
|
a8e4ddc095 | ||
|
|
bd5cfef670 | ||
|
|
18119d4d88 | ||
|
|
b4f9815316 | ||
|
|
fd9ad27be0 | ||
|
|
c35a3a3ed9 | ||
|
|
85a8bd1df1 | ||
|
|
96a866fc2c | ||
|
|
c41d52cf08 | ||
|
|
c09bab4baa | ||
|
|
bf7c3c2b19 | ||
|
|
e2bc0d6514 | ||
|
|
f9fe03db61 | ||
|
|
314d52624e | ||
|
|
4a8d322dd9 | ||
|
|
e77c6fedad | ||
|
|
86a383d4d6 | ||
|
|
14542a5187 | ||
|
|
692510b391 |
62 changed files with 124402 additions and 37 deletions
46
.github/ISSUE_TEMPLATE.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE.md
vendored
Normal 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
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
32
.github/workflows/githubci.yml
vendored
Normal 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
3
.gitignore
vendored
|
|
@ -1,2 +1,5 @@
|
||||||
|
Doxyfile*
|
||||||
|
doxygen_sqlite3.db
|
||||||
|
html
|
||||||
.vscode
|
.vscode
|
||||||
build
|
build
|
||||||
|
|
|
||||||
55
Readme.md
55
Readme.md
|
|
@ -1,33 +1,48 @@
|
||||||
RP2350 PicoDVI Preview
|
Special Fork of PicoDVI for RP2040/RP2350 Arduino Philhower Core + Adafruit_GFX
|
||||||
======================
|
========================================================================
|
||||||
|
|
||||||
Changes from the public GitHub version:
|
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.
|
||||||
|
|
||||||
* All Arm assembly in `libdvi` has been ported to RISC-V and tuned for Hazard3
|
WARNING: all video modes require overclocking (performed automatically at
|
||||||
* Some of the existing Arm assembly in `libdvi` has been tweaked for better performance on Cortex-M33
|
run-time, nothing to select), occasionally over-volting (optionally selected
|
||||||
* RGB encode now uses the SIO TMDS encoders by default on RP2350 (can be disabled by defining `DVI_USE_SIO_TMDS_ENCODE=0` -- see `software/libdvi/dvi_config_defs.h`)
|
in sketch code), and higher resolutions may require reducing the QSPI clock
|
||||||
* Much of the Arm assembly in `libsprite` has been ported to RISC-V -- enough to run the stock demos
|
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.
|
||||||
|
|
||||||
Build instructions:
|
Requires Earle Philhower III RP2040 Arduino core (not the "official" Arduino
|
||||||
|
RP2040 core).
|
||||||
|
|
||||||
```bash
|
Changes vs main PicoDVI repo:
|
||||||
cd software
|
- Add library.properties file, src and examples directories per Arduino
|
||||||
mkdir build
|
requirements.
|
||||||
# PICO_PLATFORM can also be rp2350-riscv
|
- 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!
|
||||||
# List of DVI configs is in software/include/common_dvi_pin_configs.h
|
- The file dvi_serialiser.pio.h, normally not part of the distribution and
|
||||||
cmake -DPICO_SDK_PATH=/path/to/sdk -DPICO_PLATFORM=rp2350 -DPICO_COPY_TO_RAM=1 -DDVI_DEFAULT_SERIAL_CONFIG=pico_sock_cfg ..
|
generated during the Pico SDK build process, is provided here for Arduino
|
||||||
make -j$(nproc)
|
build to work. If any changes are made in dvi_serialiser.pio (either here
|
||||||
# Then flash a binary, e.g.:
|
or by bringing in updates from upstream code), this header will need to be
|
||||||
cp apps/tiles_and_sprites/tiles_and_sprites.uf2
|
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.
|
||||||
|
|
||||||
If you plan to run the `vista` demo, then note that there are now two UF2 data files, `software/assets/vista_data_rp2040.uf2` and `software/assets/vista_data_rp2350.uf2`. The only difference is the family IDs: the first can be dragged on RP2040 and on RP2350 A0, and the second can be dragged on RP2350 A1 and later.
|
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.
|
||||||
|
|
||||||
The following is the original RP2040 writeup:
|
Original Readme content:
|
||||||
|
|
||||||
Bitbanged DVI on the RP2040 Microcontroller
|
Bitbanged DVI on the RP2040 Microcontroller
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
*640x480 RGB565 image, 640x480p 60 Hz DVI mode. 264 kB SRAM, 2x Cortex-M0+, system clock 252 MHz*
|
*640x480 RGB565 image, 640x480p 60 Hz DVI mode. 264 kB SRAM, 2x Cortex-M0+, system clock 252 MHz*
|
||||||
|
|
|
||||||
619
examples/16bit_hello/16bit_hello.ino
Normal file
619
examples/16bit_hello/16bit_hello.ino
Normal 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
|
||||||
|
|
||||||
|
const 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
|
||||||
58
examples/1bit_double_buffer/1bit_double_buffer.ino
Normal file
58
examples/1bit_double_buffer/1bit_double_buffer.ino
Normal 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();
|
||||||
|
}
|
||||||
31
examples/1bit_single_buffer/1bit_single_buffer.ino
Normal file
31
examples/1bit_single_buffer/1bit_single_buffer.ino
Normal 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)
|
||||||
|
}
|
||||||
35
examples/1bit_text/1bit_text.ino
Normal file
35
examples/1bit_text/1bit_text.ino
Normal 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);
|
||||||
|
}
|
||||||
61
examples/8bit_double_buffer/8bit_double_buffer.ino
Normal file
61
examples/8bit_double_buffer/8bit_double_buffer.ino
Normal 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();
|
||||||
|
}
|
||||||
33
examples/8bit_single_buffer/8bit_single_buffer.ino
Normal file
33
examples/8bit_single_buffer/8bit_single_buffer.ino
Normal 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
5
examples/Readme.md
Normal 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.
|
||||||
0
examples/file_access/.pico_rp2040_tinyusb.test.only
Normal file
0
examples/file_access/.pico_rp2040_tinyusb.test.only
Normal file
83
examples/file_access/file_access.ino
Normal file
83
examples/file_access/file_access.ino
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
153
examples/screensavers/aquarium/aquarium.ino
Normal file
153
examples/screensavers/aquarium/aquarium.ino
Normal 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.
|
||||||
|
}
|
||||||
29409
examples/screensavers/aquarium/sprites.h
Normal file
29409
examples/screensavers/aquarium/sprites.h
Normal file
File diff suppressed because it is too large
Load diff
121
examples/screensavers/boing/boing.ino
Normal file
121
examples/screensavers/boing/boing.ino
Normal 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
|
||||||
|
}
|
||||||
1376
examples/screensavers/boing/graphics.h
Normal file
1376
examples/screensavers/boing/graphics.h
Normal file
File diff suppressed because it is too large
Load diff
41
examples/screensavers/logobounce/logobounce.ino
Normal file
41
examples/screensavers/logobounce/logobounce.ino
Normal 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;
|
||||||
|
}
|
||||||
386
examples/screensavers/logobounce/sprite.h
Normal file
386
examples/screensavers/logobounce/sprite.h
Normal 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
|
||||||
|
};
|
||||||
2668
examples/screensavers/toasters/sprites.h
Normal file
2668
examples/screensavers/toasters/sprites.h
Normal file
File diff suppressed because it is too large
Load diff
138
examples/screensavers/toasters/toasters.ino
Normal file
138
examples/screensavers/toasters/toasters.ino
Normal 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).
|
||||||
|
*/
|
||||||
84589
examples/screensavers/tvhost/sprites.h
Normal file
84589
examples/screensavers/tvhost/sprites.h
Normal file
File diff suppressed because it is too large
Load diff
343
examples/screensavers/tvhost/tvhost.ino
Normal file
343
examples/screensavers/tvhost/tvhost.ino
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
examples/virtual_spitft/fourwire.pio
Normal file
24
examples/virtual_spitft/fourwire.pio
Normal 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
|
||||||
251
examples/virtual_spitft/virtual_spitft.ino
Normal file
251
examples/virtual_spitft/virtual_spitft.ino
Normal 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
10
library.properties
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name=PicoDVI - Adafruit Fork
|
||||||
|
version=1.1.0
|
||||||
|
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
|
||||||
|
|
@ -23,6 +23,7 @@ target_compile_definitions(bad_apple PRIVATE
|
||||||
DVI_VERTICAL_REPEAT=1
|
DVI_VERTICAL_REPEAT=1
|
||||||
DVI_N_TMDS_BUFFERS=3
|
DVI_N_TMDS_BUFFERS=3
|
||||||
DVI_MONOCHROME_TMDS
|
DVI_MONOCHROME_TMDS
|
||||||
|
DVI_1BPP_BIT_REVERSE=0
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(bad_apple
|
target_link_libraries(bad_apple
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@
|
||||||
|
|
||||||
#include "pico/types.h"
|
#include "pico/types.h"
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
// Render characters using an 8px-wide font and a per-character 2bpp
|
// Render characters using an 8px-wide font and a per-character 2bpp
|
||||||
// foreground/background colour. This function is fast enough to run 3 times
|
// 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
|
// 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,
|
void tmds_encode_font_2bpp(const uint8_t *charbuf, const uint32_t *colourbuf,
|
||||||
uint32_t *tmdsbuf, uint n_pix, const uint8_t *font_line);
|
uint32_t *tmdsbuf, uint n_pix, const uint8_t *font_line);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ target_compile_definitions(dht_logging PRIVATE
|
||||||
DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG}
|
DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG}
|
||||||
DVI_VERTICAL_REPEAT=1
|
DVI_VERTICAL_REPEAT=1
|
||||||
DVI_N_TMDS_BUFFERS=3
|
DVI_N_TMDS_BUFFERS=3
|
||||||
|
DVI_1BPP_BIT_REVERSE=0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ target_compile_definitions(moon PRIVATE
|
||||||
DVI_VERTICAL_REPEAT=1
|
DVI_VERTICAL_REPEAT=1
|
||||||
DVI_N_TMDS_BUFFERS=3
|
DVI_N_TMDS_BUFFERS=3
|
||||||
DVI_MONOCHROME_TMDS
|
DVI_MONOCHROME_TMDS
|
||||||
|
DVI_1BPP_BIT_REVERSE=0
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(moon
|
target_link_libraries(moon
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ target_compile_definitions(terminal PRIVATE
|
||||||
DVI_VERTICAL_REPEAT=1
|
DVI_VERTICAL_REPEAT=1
|
||||||
DVI_N_TMDS_BUFFERS=3
|
DVI_N_TMDS_BUFFERS=3
|
||||||
DVI_MONOCHROME_TMDS=1
|
DVI_MONOCHROME_TMDS=1
|
||||||
|
DVI_1BPP_BIT_REVERSE=0
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(terminal
|
target_link_libraries(terminal
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
// #define MODE_720x480_60Hz
|
// #define MODE_720x480_60Hz
|
||||||
// #define MODE_800x600_60Hz
|
// #define MODE_800x600_60Hz
|
||||||
// #define MODE_960x540p_60Hz
|
// #define MODE_960x540p_60Hz
|
||||||
// #define MODE_1280x720_30Hz
|
//#define MODE_1280x720_30Hz
|
||||||
|
|
||||||
#if defined(MODE_640x480_60Hz)
|
#if defined(MODE_640x480_60Hz)
|
||||||
// DVDD 1.2V (1.1V seems ok too)
|
// DVDD 1.2V (1.1V seems ok too)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,11 @@
|
||||||
// developing on. It's not a particularly important file -- just saves some
|
// developing on. It's not a particularly important file -- just saves some
|
||||||
// copy + paste.
|
// copy + paste.
|
||||||
|
|
||||||
|
#if defined(ARDUINO)
|
||||||
|
#include "../libdvi/dvi_serialiser.h"
|
||||||
|
#else
|
||||||
#include "dvi_serialiser.h"
|
#include "dvi_serialiser.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef DVI_DEFAULT_SERIAL_CONFIG
|
#ifndef DVI_DEFAULT_SERIAL_CONFIG
|
||||||
#define DVI_DEFAULT_SERIAL_CONFIG pico_sock_cfg
|
#define DVI_DEFAULT_SERIAL_CONFIG pico_sock_cfg
|
||||||
|
|
@ -101,7 +105,7 @@ static const struct dvi_serialiser_cfg not_hdmi_featherwing_cfg = {
|
||||||
|
|
||||||
// Adafruit Feather RP2040 DVI
|
// Adafruit Feather RP2040 DVI
|
||||||
static const struct dvi_serialiser_cfg adafruit_feather_dvi_cfg = {
|
static const struct dvi_serialiser_cfg adafruit_feather_dvi_cfg = {
|
||||||
.pio = pio0,
|
.pio = DVI_DEFAULT_PIO_INST,
|
||||||
.sm_tmds = {0, 1, 2},
|
.sm_tmds = {0, 1, 2},
|
||||||
.pins_tmds = {18, 20, 22},
|
.pins_tmds = {18, 20, 22},
|
||||||
.pins_clk = 16,
|
.pins_clk = 16,
|
||||||
|
|
@ -115,6 +119,13 @@ static const struct dvi_serialiser_cfg waveshare_rp2040_pizero = {
|
||||||
.pins_tmds = {26, 24, 22},
|
.pins_tmds = {26, 24, 22},
|
||||||
.pins_clk = 28,
|
.pins_clk = 28,
|
||||||
.invert_diffpairs = false
|
.invert_diffpairs = false
|
||||||
|
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
#include "dvi_serialiser.h"
|
#include "dvi_serialiser.h"
|
||||||
#include "tmds_encode.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
|
// Time-critical functions pulled into RAM but each in a unique section to
|
||||||
// allow garbage collection
|
// allow garbage collection
|
||||||
#define __dvi_func(f) __not_in_flash_func(f)
|
#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, true, &inst->dma_list_vblank_sync);
|
||||||
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, false, &inst->dma_list_vblank_nosync);
|
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);
|
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);
|
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) {
|
for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) {
|
||||||
void *tmdsbuf;
|
void *tmdsbuf;
|
||||||
#if DVI_MONOCHROME_TMDS
|
if (dvi_monochrome_tmds)
|
||||||
tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
|
tmdsbuf = malloc(inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
|
||||||
#else
|
else
|
||||||
tmdsbuf = malloc(3 * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
|
tmdsbuf = malloc(3 * inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD * sizeof(uint32_t));
|
||||||
#endif
|
|
||||||
if (!tmdsbuf)
|
if (!tmdsbuf)
|
||||||
panic("TMDS buffer allocation failed");
|
panic("TMDS buffer allocation failed");
|
||||||
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
|
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;
|
tmdsbuf = NULL;
|
||||||
}
|
}
|
||||||
else if (queue_try_peek_u32(&inst->q_tmds_valid, &tmdsbuf)) {
|
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);
|
queue_remove_blocking_u32(&inst->q_tmds_valid, &tmdsbuf);
|
||||||
inst->tmds_buf_release_next = tmdsbuf;
|
inst->tmds_buf_release_next = tmdsbuf;
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +216,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
|
||||||
else {
|
else {
|
||||||
// No valid scanline was ready (generates solid red scanline)
|
// No valid scanline was ready (generates solid red scanline)
|
||||||
tmdsbuf = NULL;
|
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;
|
++inst->late_scanline_ctr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,7 +229,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
|
||||||
else {
|
else {
|
||||||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_error);
|
_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();
|
inst->scanline_callback();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ struct dvi_inst {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
// Set up data structures and hardware for DVI.
|
// Set up data structures and hardware for DVI.
|
||||||
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue);
|
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_8bpp(struct dvi_inst *inst);
|
||||||
void dvi_framebuf_main_16bpp(struct dvi_inst *inst);
|
void dvi_framebuf_main_16bpp(struct dvi_inst *inst);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#if defined(__cplusplus)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
// If 1, reverse the order of pixels within each byte. Order of bytes within
|
// If 1, reverse the order of pixels within each byte. Order of bytes within
|
||||||
// each word is still little-endian.
|
// each word is still little-endian.
|
||||||
#ifndef DVI_1BPP_BIT_REVERSE
|
#ifndef DVI_1BPP_BIT_REVERSE
|
||||||
#define DVI_1BPP_BIT_REVERSE 0
|
#define DVI_1BPP_BIT_REVERSE 1 // Adafruit_GFX GFXcanvas1 requires this 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
101
software/libdvi/dvi_serialiser.pio.h
Normal file
101
software/libdvi/dvi_serialiser.pio.h
Normal 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
|
||||||
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
// achievable bit clock from 12 MHz crystal)
|
// achievable bit clock from 12 MHz crystal)
|
||||||
// - Helper functions for generating DMA lists based on these timings
|
// - 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
|
// Pull into RAM but apply unique section suffix to allow linker GC
|
||||||
#define __dvi_func(x) __not_in_flash_func(x)
|
#define __dvi_func(x) __not_in_flash_func(x)
|
||||||
#define __dvi_const(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) {
|
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) {
|
for (int i = 0; i < N_TMDS_LANES; ++i) {
|
||||||
#if DVI_MONOCHROME_TMDS
|
const uint32_t *lane_tmdsbuf = dvi_monochrome_tmds ? tmdsbuf : tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
|
||||||
const uint32_t *lane_tmdsbuf = tmdsbuf;
|
|
||||||
#else
|
|
||||||
const uint32_t *lane_tmdsbuf = tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
|
|
||||||
#endif
|
|
||||||
if (i == TMDS_SYNC_LANE)
|
if (i == TMDS_SYNC_LANE)
|
||||||
dvi_lane_from_list(l, i)[3].read_addr = lane_tmdsbuf;
|
dvi_lane_from_list(l, i)[3].read_addr = lane_tmdsbuf;
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@
|
||||||
#include "hardware/interp.h"
|
#include "hardware/interp.h"
|
||||||
#include "dvi_config_defs.h"
|
#include "dvi_config_defs.h"
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
// Functions from tmds_encode.c
|
// 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_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_8bpp(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix, uint channel_msb, uint channel_lsb);
|
||||||
|
|
@ -51,6 +56,9 @@ void tmds_encode_sio_loop_peekpop_ratio8(const uint32_t *pixbuf, uint32_t *symbu
|
||||||
void tmds_encode_sio_loop_peekpop_ratio16(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
|
void tmds_encode_sio_loop_peekpop_ratio16(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
|
||||||
void tmds_encode_sio_loop_peekpop_ratio32(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
|
void tmds_encode_sio_loop_peekpop_ratio32(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
|
||||||
void tmds_encode_sio_loop_peekpop_ratio64(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
|
void tmds_encode_sio_loop_peekpop_ratio64(const uint32_t *pixbuf, uint32_t *symbuf, size_t n_pix);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
457
src/PicoDVI.cpp
Normal file
457
src/PicoDVI.cpp
Normal 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
346
src/PicoDVI.h
Normal 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
5
src/Readme.md
Normal 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
177
src/font_8x8.h
Normal 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
33
src/libdvi/CMakeLists.txt
Normal 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)
|
||||||
255
src/libdvi/dvi.c
Normal file
255
src/libdvi/dvi.c
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
#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) {
|
||||||
|
while (dma_debug_hw->ch[inst->dma_cfg[i].chan_data].tcr != inst->timing->h_active_pixels / DVI_SYMBOLS_PER_WORD)
|
||||||
|
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
81
src/libdvi/dvi.h
Normal 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
|
||||||
151
src/libdvi/dvi_config_defs.h
Normal file
151
src/libdvi/dvi_config_defs.h
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
#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.
|
||||||
|
#include "hardware/platform_defs.h"
|
||||||
|
#include "pico/config.h"
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// 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
|
||||||
73
src/libdvi/dvi_serialiser.c
Normal file
73
src/libdvi/dvi_serialiser.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/libdvi/dvi_serialiser.h
Normal file
22
src/libdvi/dvi_serialiser.h
Normal 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
|
||||||
53
src/libdvi/dvi_serialiser.pio
Normal file
53
src/libdvi/dvi_serialiser.pio
Normal 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);
|
||||||
|
}
|
||||||
|
%}
|
||||||
101
src/libdvi/dvi_serialiser.pio.h
Normal file
101
src/libdvi/dvi_serialiser.pio.h
Normal 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
344
src/libdvi/dvi_timing.c
Normal 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
100
src/libdvi/dvi_timing.h
Normal 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
623
src/libdvi/tmds_encode.S
Normal 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
305
src/libdvi/tmds_encode.c
Normal 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
46
src/libdvi/tmds_encode.h
Normal 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
|
||||||
46
src/libdvi/tmds_encode_1bpp.pio
Normal file
46
src/libdvi/tmds_encode_1bpp.pio
Normal 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
76
src/libdvi/tmds_table.h
Normal 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,
|
||||||
139
src/libdvi/tmds_table_fullres.h
Normal file
139
src/libdvi/tmds_table_fullres.h
Normal 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
150
src/libdvi/tmds_table_gen.py
Executable 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}")
|
||||||
83
src/libdvi/util_queue_u32_inline.h
Normal file
83
src/libdvi/util_queue_u32_inline.h
Normal 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
|
||||||
Loading…
Reference in a new issue