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
|
||||
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
|
||||
* Some of the existing Arm assembly in `libdvi` has been tweaked for better performance on Cortex-M33
|
||||
* 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`)
|
||||
* Much of the Arm assembly in `libsprite` has been ported to RISC-V -- enough to run the stock demos
|
||||
WARNING: all video modes require overclocking (performed automatically at
|
||||
run-time, nothing to select), occasionally over-volting (optionally selected
|
||||
in sketch code), and higher resolutions may require reducing the QSPI clock
|
||||
rate (Tools menu in future arduino-pico release). The RP2040 microcontroller
|
||||
is being run WAY beyond spec and there is a VERY SMALL BUT NONZERO
|
||||
POSSIBILITY OF PERMANENT DAMAGE. Please see LICENSE file; usual software
|
||||
disclaimers about liability apply, user accepts risk.
|
||||
|
||||
Build instructions:
|
||||
Requires Earle Philhower III RP2040 Arduino core (not the "official" Arduino
|
||||
RP2040 core).
|
||||
|
||||
```bash
|
||||
cd software
|
||||
mkdir build
|
||||
# PICO_PLATFORM can also be rp2350-riscv
|
||||
# List of DVI configs is in software/include/common_dvi_pin_configs.h
|
||||
cmake -DPICO_SDK_PATH=/path/to/sdk -DPICO_PLATFORM=rp2350 -DPICO_COPY_TO_RAM=1 -DDVI_DEFAULT_SERIAL_CONFIG=pico_sock_cfg ..
|
||||
make -j$(nproc)
|
||||
# Then flash a binary, e.g.:
|
||||
cp apps/tiles_and_sprites/tiles_and_sprites.uf2
|
||||
```
|
||||
Changes vs main PicoDVI repo:
|
||||
- Add library.properties file, src and examples directories per Arduino
|
||||
requirements.
|
||||
- A full copy of software/libdvi is made in src (originally was soft-linked but Arduino Library Manager does not approve). If any updates are made in the original PicoDVI libdvi directory, copy them here!
|
||||
- The file dvi_serialiser.pio.h, normally not part of the distribution and
|
||||
generated during the Pico SDK build process, is provided here for Arduino
|
||||
build to work. If any changes are made in dvi_serialiser.pio (either here
|
||||
or by bringing in updates from upstream code), this header will need to be
|
||||
regenerated.
|
||||
- extern "C" around most function defs, to be linkable with C++ (Arduino).
|
||||
- A couple compile-time constants have been changed to run-time configurable
|
||||
because some color and resolution things aren't known until a constructor is
|
||||
called: dvi_vertical_repeat and dvi_monochrome_tmds.
|
||||
- DVI_1BPP_BIT_REVERSE is switched '1' by default (to work with bit order
|
||||
used by GFXcanvas1 object). Pico SDK-built examples using 1-bit mode are
|
||||
fixed by changing the corresponding CMakeLists.txt files to set this to 0.
|
||||
Font data has had bit order reversed to match this layout.
|
||||
|
||||
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
|
||||
===========================================
|
||||
|
||||
|
||||

|
||||
|
||||
*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_N_TMDS_BUFFERS=3
|
||||
DVI_MONOCHROME_TMDS
|
||||
DVI_1BPP_BIT_REVERSE=0
|
||||
)
|
||||
|
||||
target_link_libraries(bad_apple
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
|
||||
#include "pico/types.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// Render characters using an 8px-wide font and a per-character 2bpp
|
||||
// foreground/background colour. This function is fast enough to run 3 times
|
||||
// per scanline on one core, so RGB222 coloured text can be rendered (with
|
||||
|
|
@ -20,4 +25,8 @@
|
|||
void tmds_encode_font_2bpp(const uint8_t *charbuf, const uint32_t *colourbuf,
|
||||
uint32_t *tmdsbuf, uint n_pix, const uint8_t *font_line);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ target_compile_definitions(dht_logging PRIVATE
|
|||
DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG}
|
||||
DVI_VERTICAL_REPEAT=1
|
||||
DVI_N_TMDS_BUFFERS=3
|
||||
DVI_1BPP_BIT_REVERSE=0
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ target_compile_definitions(moon PRIVATE
|
|||
DVI_VERTICAL_REPEAT=1
|
||||
DVI_N_TMDS_BUFFERS=3
|
||||
DVI_MONOCHROME_TMDS
|
||||
DVI_1BPP_BIT_REVERSE=0
|
||||
)
|
||||
|
||||
target_link_libraries(moon
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ target_compile_definitions(terminal PRIVATE
|
|||
DVI_VERTICAL_REPEAT=1
|
||||
DVI_N_TMDS_BUFFERS=3
|
||||
DVI_MONOCHROME_TMDS=1
|
||||
DVI_1BPP_BIT_REVERSE=0
|
||||
)
|
||||
|
||||
target_link_libraries(terminal
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
// #define MODE_720x480_60Hz
|
||||
// #define MODE_800x600_60Hz
|
||||
// #define MODE_960x540p_60Hz
|
||||
// #define MODE_1280x720_30Hz
|
||||
//#define MODE_1280x720_30Hz
|
||||
|
||||
#if defined(MODE_640x480_60Hz)
|
||||
// DVDD 1.2V (1.1V seems ok too)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@
|
|||
// developing on. It's not a particularly important file -- just saves some
|
||||
// copy + paste.
|
||||
|
||||
#if defined(ARDUINO)
|
||||
#include "../libdvi/dvi_serialiser.h"
|
||||
#else
|
||||
#include "dvi_serialiser.h"
|
||||
#endif
|
||||
|
||||
#ifndef DVI_DEFAULT_SERIAL_CONFIG
|
||||
#define DVI_DEFAULT_SERIAL_CONFIG pico_sock_cfg
|
||||
|
|
@ -101,7 +105,7 @@ static const struct dvi_serialiser_cfg not_hdmi_featherwing_cfg = {
|
|||
|
||||
// Adafruit Feather RP2040 DVI
|
||||
static const struct dvi_serialiser_cfg adafruit_feather_dvi_cfg = {
|
||||
.pio = pio0,
|
||||
.pio = DVI_DEFAULT_PIO_INST,
|
||||
.sm_tmds = {0, 1, 2},
|
||||
.pins_tmds = {18, 20, 22},
|
||||
.pins_clk = 16,
|
||||
|
|
@ -115,6 +119,13 @@ static const struct dvi_serialiser_cfg waveshare_rp2040_pizero = {
|
|||
.pins_tmds = {26, 24, 22},
|
||||
.pins_clk = 28,
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
#include "dvi_serialiser.h"
|
||||
#include "tmds_encode.h"
|
||||
|
||||
// Adafruit PicoDVI fork requires a couple global items run-time configurable:
|
||||
uint8_t dvi_vertical_repeat = DVI_VERTICAL_REPEAT;
|
||||
bool dvi_monochrome_tmds = DVI_MONOCHROME_TMDS;
|
||||
|
||||
// Time-critical functions pulled into RAM but each in a unique section to
|
||||
// allow garbage collection
|
||||
#define __dvi_func(f) __not_in_flash_func(f)
|
||||
|
|
@ -37,16 +41,19 @@ void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_col
|
|||
|
||||
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, true, &inst->dma_list_vblank_sync);
|
||||
dvi_setup_scanline_for_vblank(inst->timing, inst->dma_cfg, false, &inst->dma_list_vblank_nosync);
|
||||
#if defined(ARDUINO)
|
||||
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (uint32_t*)SRAM_BASE, &inst->dma_list_active);
|
||||
#else
|
||||
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, (void*)SRAM_BASE, &inst->dma_list_active);
|
||||
#endif
|
||||
dvi_setup_scanline_for_active(inst->timing, inst->dma_cfg, NULL, &inst->dma_list_error);
|
||||
|
||||
for (int i = 0; i < DVI_N_TMDS_BUFFERS; ++i) {
|
||||
void *tmdsbuf;
|
||||
#if DVI_MONOCHROME_TMDS
|
||||
if (dvi_monochrome_tmds)
|
||||
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));
|
||||
#endif
|
||||
if (!tmdsbuf)
|
||||
panic("TMDS buffer allocation failed");
|
||||
queue_add_blocking_u32(&inst->q_tmds_free, &tmdsbuf);
|
||||
|
|
@ -201,7 +208,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
|
|||
tmdsbuf = NULL;
|
||||
}
|
||||
else if (queue_try_peek_u32(&inst->q_tmds_valid, &tmdsbuf)) {
|
||||
if (inst->timing_state.v_ctr % DVI_VERTICAL_REPEAT == DVI_VERTICAL_REPEAT - 1) {
|
||||
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) {
|
||||
queue_remove_blocking_u32(&inst->q_tmds_valid, &tmdsbuf);
|
||||
inst->tmds_buf_release_next = tmdsbuf;
|
||||
}
|
||||
|
|
@ -209,7 +216,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
|
|||
else {
|
||||
// No valid scanline was ready (generates solid red scanline)
|
||||
tmdsbuf = NULL;
|
||||
if (inst->timing_state.v_ctr % DVI_VERTICAL_REPEAT == DVI_VERTICAL_REPEAT - 1)
|
||||
if (inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1)
|
||||
++inst->late_scanline_ctr;
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +229,7 @@ static void __dvi_func(dvi_dma_irq_handler)(struct dvi_inst *inst) {
|
|||
else {
|
||||
_dvi_load_dma_op(inst->dma_cfg, &inst->dma_list_error);
|
||||
}
|
||||
if (inst->scanline_callback && inst->timing_state.v_ctr % DVI_VERTICAL_REPEAT == DVI_VERTICAL_REPEAT - 1) {
|
||||
if (inst->scanline_callback && inst->timing_state.v_ctr % dvi_vertical_repeat == dvi_vertical_repeat - 1) {
|
||||
inst->scanline_callback();
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ struct dvi_inst {
|
|||
|
||||
};
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// Set up data structures and hardware for DVI.
|
||||
void dvi_init(struct dvi_inst *inst, uint spinlock_tmds_queue, uint spinlock_colour_queue);
|
||||
|
||||
|
|
@ -73,7 +78,7 @@ void dvi_scanbuf_main_16bpp(struct dvi_inst *inst);
|
|||
void dvi_framebuf_main_8bpp(struct dvi_inst *inst);
|
||||
void dvi_framebuf_main_16bpp(struct dvi_inst *inst);
|
||||
|
||||
#ifdef __cplusplus
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@
|
|||
// If 1, reverse the order of pixels within each byte. Order of bytes within
|
||||
// each word is still little-endian.
|
||||
#ifndef DVI_1BPP_BIT_REVERSE
|
||||
#define DVI_1BPP_BIT_REVERSE 0
|
||||
#define DVI_1BPP_BIT_REVERSE 1 // Adafruit_GFX GFXcanvas1 requires this 1
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
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)
|
||||
// - Helper functions for generating DMA lists based on these timings
|
||||
|
||||
extern bool dvi_monochrome_tmds; // In dvi.c
|
||||
|
||||
// Pull into RAM but apply unique section suffix to allow linker GC
|
||||
#define __dvi_func(x) __not_in_flash_func(x)
|
||||
#define __dvi_const(x) __not_in_flash_func(x)
|
||||
|
|
@ -330,11 +332,7 @@ void dvi_setup_scanline_for_active(const struct dvi_timing *t, const struct dvi_
|
|||
|
||||
void __dvi_func(dvi_update_scanline_data_dma)(const struct dvi_timing *t, const uint32_t *tmdsbuf, struct dvi_scanline_dma_list *l) {
|
||||
for (int i = 0; i < N_TMDS_LANES; ++i) {
|
||||
#if DVI_MONOCHROME_TMDS
|
||||
const uint32_t *lane_tmdsbuf = tmdsbuf;
|
||||
#else
|
||||
const uint32_t *lane_tmdsbuf = tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
|
||||
#endif
|
||||
const uint32_t *lane_tmdsbuf = dvi_monochrome_tmds ? tmdsbuf : tmdsbuf + i * t->h_active_pixels / DVI_SYMBOLS_PER_WORD;
|
||||
if (i == TMDS_SYNC_LANE)
|
||||
dvi_lane_from_list(l, i)[3].read_addr = lane_tmdsbuf;
|
||||
else
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
#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);
|
||||
|
|
@ -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_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);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#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