adafruit-circuitpython-lt8912b 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/METADATA +202 -0
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/RECORD +8 -0
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/WHEEL +5 -0
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/LICENSE +21 -0
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/NOTICE +26 -0
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/NOTICE.license +2 -0
- adafruit_circuitpython_lt8912b-1.0.0.dist-info/top_level.txt +1 -0
- adafruit_lt8912b.py +369 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adafruit-circuitpython-lt8912b
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CircuitPython driver for the LT8912B MIPI DSI to HDMI/DVI bridge.
|
|
5
|
+
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/adafruit/Adafruit_CircuitPython_LT8912B
|
|
8
|
+
Keywords: adafruit,blinka,circuitpython,micropython,lt8912b,lt8912b,mipi,dsi,hdmi,dvi,bridge,display
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
12
|
+
Classifier: Topic :: System :: Hardware
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Description-Content-Type: text/x-rst
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
License-File: NOTICE
|
|
17
|
+
License-File: NOTICE.license
|
|
18
|
+
Requires-Dist: Adafruit-Blinka
|
|
19
|
+
Requires-Dist: adafruit-circuitpython-busdevice
|
|
20
|
+
Provides-Extra: optional
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
Introduction
|
|
24
|
+
============
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
.. image:: https://readthedocs.org/projects/adafruit-circuitpython-lt8912b/badge/?version=latest
|
|
28
|
+
:target: https://docs.circuitpython.org/projects/lt8912b/en/latest/
|
|
29
|
+
:alt: Documentation Status
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg
|
|
33
|
+
:target: https://adafru.it/discord
|
|
34
|
+
:alt: Discord
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
.. image:: https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/workflows/Build%20CI/badge.svg
|
|
38
|
+
:target: https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/actions
|
|
39
|
+
:alt: Build Status
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
43
|
+
:target: https://github.com/astral-sh/ruff
|
|
44
|
+
:alt: Code Style: Ruff
|
|
45
|
+
|
|
46
|
+
CircuitPython driver for the LT8912B MIPI DSI to HDMI/DVI bridge. The bridge is
|
|
47
|
+
configured over I2C and then fed video through a MIPI DSI host, such as the
|
|
48
|
+
ESP32-P4. Pixel data is sent by the ``mipidsi`` module; the LT8912B cannot send
|
|
49
|
+
pixels over I2C.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Dependencies
|
|
53
|
+
=============
|
|
54
|
+
This driver depends on:
|
|
55
|
+
|
|
56
|
+
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_ with
|
|
57
|
+
``mipidsi.Display.pixel_clock_frequency`` and ``low_power_blanking``, and
|
|
58
|
+
``mipidsi.Bus.minimum_frequency``, ``frequency`` and ``num_lanes``
|
|
59
|
+
* `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_
|
|
60
|
+
|
|
61
|
+
Please ensure all dependencies are available on the CircuitPython filesystem.
|
|
62
|
+
This is easily achieved by downloading
|
|
63
|
+
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
|
|
64
|
+
or individual libraries can be installed using
|
|
65
|
+
`circup <https://github.com/adafruit/circup>`_.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
This driver works with Adafruit's LT8912B MIPI DSI to HDMI/DVI adapter.
|
|
69
|
+
|
|
70
|
+
`Purchase one from the Adafruit shop <https://www.adafruit.com/>`_
|
|
71
|
+
|
|
72
|
+
Installing from PyPI
|
|
73
|
+
=====================
|
|
74
|
+
|
|
75
|
+
On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
|
|
76
|
+
PyPI <https://pypi.org/project/adafruit-circuitpython-lt8912b/>`_.
|
|
77
|
+
To install for current user:
|
|
78
|
+
|
|
79
|
+
.. code-block:: shell
|
|
80
|
+
|
|
81
|
+
pip3 install adafruit-circuitpython-lt8912b
|
|
82
|
+
|
|
83
|
+
To install system-wide (this may be required in some cases):
|
|
84
|
+
|
|
85
|
+
.. code-block:: shell
|
|
86
|
+
|
|
87
|
+
sudo pip3 install adafruit-circuitpython-lt8912b
|
|
88
|
+
|
|
89
|
+
To install in a virtual environment in your current project:
|
|
90
|
+
|
|
91
|
+
.. code-block:: shell
|
|
92
|
+
|
|
93
|
+
mkdir project-name && cd project-name
|
|
94
|
+
python3 -m venv .venv
|
|
95
|
+
source .env/bin/activate
|
|
96
|
+
pip3 install adafruit-circuitpython-lt8912b
|
|
97
|
+
|
|
98
|
+
Installing to a Connected CircuitPython Device with Circup
|
|
99
|
+
==========================================================
|
|
100
|
+
|
|
101
|
+
Make sure that you have ``circup`` installed in your Python environment.
|
|
102
|
+
Install it with the following command if necessary:
|
|
103
|
+
|
|
104
|
+
.. code-block:: shell
|
|
105
|
+
|
|
106
|
+
pip3 install circup
|
|
107
|
+
|
|
108
|
+
With ``circup`` installed and your CircuitPython device connected use the
|
|
109
|
+
following command to install:
|
|
110
|
+
|
|
111
|
+
.. code-block:: shell
|
|
112
|
+
|
|
113
|
+
circup install adafruit_lt8912b
|
|
114
|
+
|
|
115
|
+
Or the following command to update an existing version:
|
|
116
|
+
|
|
117
|
+
.. code-block:: shell
|
|
118
|
+
|
|
119
|
+
circup update
|
|
120
|
+
|
|
121
|
+
Usage Example
|
|
122
|
+
=============
|
|
123
|
+
|
|
124
|
+
``LT8912B`` is a ``mipidsi.Display``. It configures the bridge over I2C and
|
|
125
|
+
then starts RGB888 video on the DSI bus with the same timing. The constructor
|
|
126
|
+
raises ``ValueError`` if the bridge does not respond on I2C. Create the bus
|
|
127
|
+
with a ``minimum_frequency`` of at least 24 times the pixel clock divided by
|
|
128
|
+
the number of lanes. The PHY may run faster than that minimum, so the bridge
|
|
129
|
+
checks the rate the bus actually uses, available as ``bus.frequency``.
|
|
130
|
+
|
|
131
|
+
.. code-block:: python
|
|
132
|
+
|
|
133
|
+
import board
|
|
134
|
+
import busio
|
|
135
|
+
import framebufferio
|
|
136
|
+
import mipidsi
|
|
137
|
+
import adafruit_lt8912b
|
|
138
|
+
|
|
139
|
+
i2c = busio.I2C(board.SCL, board.SDA)
|
|
140
|
+
bus = mipidsi.Bus(minimum_frequency=480_000_000, num_lanes=2)
|
|
141
|
+
|
|
142
|
+
bridge = adafruit_lt8912b.LT8912B(
|
|
143
|
+
bus,
|
|
144
|
+
i2c,
|
|
145
|
+
width=800,
|
|
146
|
+
height=480,
|
|
147
|
+
hsync_pulse_width=48,
|
|
148
|
+
hsync_back_porch=310,
|
|
149
|
+
hsync_front_porch=112,
|
|
150
|
+
vsync_pulse_width=3,
|
|
151
|
+
vsync_back_porch=29,
|
|
152
|
+
vsync_front_porch=13,
|
|
153
|
+
pixel_clock_frequency=40_000_000,
|
|
154
|
+
)
|
|
155
|
+
display = framebufferio.FramebufferDisplay(bridge)
|
|
156
|
+
|
|
157
|
+
See ``examples/lt8912b_simpletest.py`` for a fixed-timing color-bar example,
|
|
158
|
+
``examples/lt8912b_720p.py`` and ``examples/lt8912b_1080p30.py`` for standard
|
|
159
|
+
CEA-861 modes, ``examples/lt8912b_640x480.py`` for a low-bandwidth mode, and
|
|
160
|
+
``examples/lt8912b_edid.py`` for one that uses the monitor's preferred EDID
|
|
161
|
+
timing.
|
|
162
|
+
|
|
163
|
+
Bridge Control
|
|
164
|
+
==============
|
|
165
|
+
|
|
166
|
+
The constructor resets and configures the bridge. It pulses the ``reset`` pin
|
|
167
|
+
when one is given and otherwise sends a software reset.
|
|
168
|
+
|
|
169
|
+
``hot_plug_detected`` is True when a monitor is connected.
|
|
170
|
+
|
|
171
|
+
EDID Timing
|
|
172
|
+
===========
|
|
173
|
+
|
|
174
|
+
``examples/lt8912b_edid.py`` reads the monitor's preferred timing from its
|
|
175
|
+
EDID EEPROM at 0x50 and passes it to ``mipidsi.Display`` and ``LT8912B``, with
|
|
176
|
+
an 800x480 fallback. If the adapter has a DDC switch, turn it on before reading
|
|
177
|
+
EDID. The example also folds the DPI clock rounding into the horizontal back
|
|
178
|
+
porch so the monitor's refresh rate survives the 240/N divider.
|
|
179
|
+
|
|
180
|
+
Documentation
|
|
181
|
+
=============
|
|
182
|
+
API documentation for this library can be found on `Read the Docs <https://docs.circuitpython.org/projects/lt8912b/en/latest/>`_.
|
|
183
|
+
|
|
184
|
+
For information on building library documentation, please check out
|
|
185
|
+
`this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
|
|
186
|
+
|
|
187
|
+
Credits and License
|
|
188
|
+
===================
|
|
189
|
+
|
|
190
|
+
The register initialization tables are adapted from `Espressif's
|
|
191
|
+
esp_lcd_lt8912b driver <https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_lt8912b>`_
|
|
192
|
+
and Adafruit's `Arduino LT8912B library <https://github.com/adafruit/Adafruit_LT8912B>`_.
|
|
193
|
+
Those portions remain under the Apache License, Version 2.0. See `NOTICE <https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/blob/main/NOTICE>`_ and
|
|
194
|
+
`LICENSES/Apache-2.0.txt <https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/blob/main/LICENSES/Apache-2.0.txt>`_. The Python interface is MIT licensed. Dependencies
|
|
195
|
+
retain their own licenses.
|
|
196
|
+
|
|
197
|
+
Contributing
|
|
198
|
+
============
|
|
199
|
+
|
|
200
|
+
Contributions are welcome! Please read our `Code of Conduct
|
|
201
|
+
<https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/blob/HEAD/CODE_OF_CONDUCT.md>`_
|
|
202
|
+
before contributing to help this project stay welcoming.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
adafruit_lt8912b.py,sha256=fDXGEE7U1vFk2kEIcDQhTCH2-qd9kz-xK5maAkLfGkA,13738
|
|
2
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/LICENSE,sha256=H3817iFnDqZcaLt_SfLc4AZC_IKA1zeOVqGLv_1k26I,1106
|
|
3
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/NOTICE,sha256=sTn3wxq4pRsDgkYWxop72yfsE-gIl57ueakzcPU4P18,1116
|
|
4
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/NOTICE.license,sha256=rNwlROfZy-SwvMkUlgrnuB31BbtjdBA7g409He39PZI,85
|
|
5
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/METADATA,sha256=wpGSBzE2MDdF6pqKBmJ2MCSG-8k9x9a_l300-G0Xt44,7313
|
|
6
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
7
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/top_level.txt,sha256=cnUI3LgdKwFKVQDHHDZhT5Mdve1Rr_ES6-lXJ04Grfk,17
|
|
8
|
+
adafruit_circuitpython_lt8912b-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scott Shawcroft for Adafruit Industries
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Adafruit CircuitPython LT8912B driver
|
|
2
|
+
Copyright (c) 2026 Adafruit Industries
|
|
3
|
+
|
|
4
|
+
This library adapts LT8912B initialization and register programming from
|
|
5
|
+
Espressif's esp_lcd_lt8912b component in esp-bsp:
|
|
6
|
+
https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_lt8912b
|
|
7
|
+
|
|
8
|
+
and from Adafruit's Arduino driver:
|
|
9
|
+
https://github.com/adafruit/Adafruit_LT8912B
|
|
10
|
+
|
|
11
|
+
The source files used carry these notices:
|
|
12
|
+
esp_lcd_lt8912b.c:
|
|
13
|
+
SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
|
14
|
+
esp_lcd_lt8912b.h:
|
|
15
|
+
SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
|
16
|
+
Adafruit_LT8912B:
|
|
17
|
+
SPDX-FileCopyrightText: 2026 Adafruit Industries
|
|
18
|
+
SPDX-License-Identifier: Apache-2.0
|
|
19
|
+
|
|
20
|
+
The register initialization tables in adafruit_lt8912b.py are derived from
|
|
21
|
+
that code and remain subject to the Apache License, Version 2.0, included in
|
|
22
|
+
LICENSES/Apache-2.0.txt. The Python interface is original Adafruit code under
|
|
23
|
+
the MIT license in LICENSE.
|
|
24
|
+
|
|
25
|
+
This attribution notice was added by Adafruit for this port; it is not a
|
|
26
|
+
NOTICE file supplied by Espressif. Dependencies retain their own licenses.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
adafruit_lt8912b
|
adafruit_lt8912b.py
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries
|
|
2
|
+
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
|
3
|
+
# SPDX-FileCopyrightText: 2026 Adafruit Industries
|
|
4
|
+
#
|
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
# Register initialization tables are adapted from Espressif's
|
|
7
|
+
# esp_lcd_lt8912b and Adafruit's Adafruit_LT8912B under the Apache License,
|
|
8
|
+
# Version 2.0. See NOTICE and LICENSES/Apache-2.0.txt.
|
|
9
|
+
"""
|
|
10
|
+
`adafruit_lt8912b`
|
|
11
|
+
================================================================================
|
|
12
|
+
|
|
13
|
+
CircuitPython driver for the LT8912B MIPI DSI to HDMI/DVI bridge.
|
|
14
|
+
|
|
15
|
+
`LT8912B` is a ``mipidsi.Display`` that also configures the bridge over I2C.
|
|
16
|
+
|
|
17
|
+
* Author(s): Scott Shawcroft
|
|
18
|
+
|
|
19
|
+
Implementation Notes
|
|
20
|
+
--------------------
|
|
21
|
+
|
|
22
|
+
**Hardware:**
|
|
23
|
+
|
|
24
|
+
* Adafruit LT8912B MIPI DSI to HDMI/DVI bridge adapter
|
|
25
|
+
(https://github.com/adafruit/Adafruit_LT8912B)
|
|
26
|
+
|
|
27
|
+
**Software and Dependencies:**
|
|
28
|
+
|
|
29
|
+
* Adafruit CircuitPython firmware for the supported boards, with
|
|
30
|
+
``mipidsi.Display.pixel_clock_frequency`` and ``low_power_blanking``, and
|
|
31
|
+
``mipidsi.Bus.minimum_frequency``, ``frequency`` and ``num_lanes``:
|
|
32
|
+
https://circuitpython.org/downloads
|
|
33
|
+
|
|
34
|
+
* Adafruit's Bus Device library:
|
|
35
|
+
https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
import digitalio
|
|
41
|
+
import mipidsi
|
|
42
|
+
from adafruit_bus_device.i2c_device import I2CDevice
|
|
43
|
+
from micropython import const
|
|
44
|
+
|
|
45
|
+
__version__ = "1.0.0"
|
|
46
|
+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LT8912B.git"
|
|
47
|
+
|
|
48
|
+
# The bus treats rates within 1 kHz of its minimum as meeting it.
|
|
49
|
+
_BUS_FREQUENCY_TOLERANCE = const(1_000)
|
|
50
|
+
|
|
51
|
+
# I2C addresses of the bridge's three register banks.
|
|
52
|
+
_I2C_MAIN = const(0x48)
|
|
53
|
+
_I2C_DSI = const(0x49)
|
|
54
|
+
_I2C_AVI = const(0x4A)
|
|
55
|
+
|
|
56
|
+
# MAIN bank registers
|
|
57
|
+
_MAIN_MIPI_RESET = const(0x03)
|
|
58
|
+
_MAIN_DDS_RESET = const(0x05)
|
|
59
|
+
_MAIN_TX_CONTROL = const(0x33)
|
|
60
|
+
_MAIN_SYNC_POLARITY = const(0xAB)
|
|
61
|
+
_MAIN_HDMI_MODE = const(0xB2)
|
|
62
|
+
_MAIN_HPD_STATUS = const(0xC1)
|
|
63
|
+
|
|
64
|
+
# DSI bank registers
|
|
65
|
+
_DSI_LANES = const(0x13)
|
|
66
|
+
_DSI_H_SYNC = const(0x18)
|
|
67
|
+
_DSI_V_SYNC = const(0x19)
|
|
68
|
+
_DSI_H_ACTIVE = const(0x1C)
|
|
69
|
+
_DSI_FIFO_BUFFER_LENGTH = const(0x2F)
|
|
70
|
+
_DSI_H_TOTAL = const(0x34)
|
|
71
|
+
_DSI_V_TOTAL = const(0x36)
|
|
72
|
+
_DSI_V_BACK_PORCH = const(0x38)
|
|
73
|
+
_DSI_V_FRONT_PORCH = const(0x3A)
|
|
74
|
+
_DSI_H_BACK_PORCH = const(0x3C)
|
|
75
|
+
_DSI_H_FRONT_PORCH = const(0x3E)
|
|
76
|
+
_DSI_DDS_FREQUENCY = const(0x4E)
|
|
77
|
+
_DSI_DDS_CONTROL = const(0x51)
|
|
78
|
+
|
|
79
|
+
# AVI bank registers
|
|
80
|
+
_AVI_CHECKSUM = const(0x43)
|
|
81
|
+
|
|
82
|
+
# Vendor initialization tables as flat (register, value) pairs.
|
|
83
|
+
_MAIN_INIT = (
|
|
84
|
+
b"\x02\xf7\x08\xff\x09\xff\x0a\xff\x0b\x7c\x0c\xff" # Digital clock enable
|
|
85
|
+
b"\x31\xe1\x32\xe1\x33\x0c\x37\x00\x38\x22\x60\x82" # TX analog
|
|
86
|
+
b"\x39\x45\x3a\x00\x3b\x00" # CBUS analog
|
|
87
|
+
b"\x44\x31\x55\x44\x57\x01\x5a\x02" # HDMI PLL analog
|
|
88
|
+
b"\x3e\xd6\x3f\xd4\x41\x3c" # MIPI analog: no P/N swap, EQ, control
|
|
89
|
+
)
|
|
90
|
+
_DSI_INIT_START = (
|
|
91
|
+
b"\x10\x01" # Termination enable
|
|
92
|
+
b"\x11\x10" # Settle
|
|
93
|
+
)
|
|
94
|
+
_DSI_INIT_END = (
|
|
95
|
+
b"\x14\x00" # Debug mux
|
|
96
|
+
b"\x15\x00" # No lane swap
|
|
97
|
+
b"\x1a\x03" # H shift
|
|
98
|
+
b"\x1b\x03" # V shift
|
|
99
|
+
# DDS: frequency word, full/empty thresholds, timer and trend coefficients
|
|
100
|
+
b"\x4e\x93\x4f\x3e\x50\x29\x51\x80\x1e\x4f\x1f\x5e\x20\x01\x21\x2c"
|
|
101
|
+
b"\x22\x01\x23\xfa\x24\x00\x25\xc8\x26\x00\x27\x5e\x28\x01\x29\x2c"
|
|
102
|
+
b"\x2a\x01\x2b\xfa\x2c\x00\x2d\xc8\x2e\x00\x42\x64\x43\x00\x44\x04"
|
|
103
|
+
b"\x45\x00\x46\x59\x47\x00\x48\xf2\x49\x06\x4a\x00\x4b\x72\x4c\x45"
|
|
104
|
+
b"\x4d\x00\x52\x08\x53\x00\x54\xb2\x55\x00\x56\xe4\x57\x0d\x58\x00"
|
|
105
|
+
b"\x59\xe4\x5a\x8a\x5b\x00\x5c\x34"
|
|
106
|
+
b"\x51\x00" # Release DDS control
|
|
107
|
+
b"\x70\x00" # Disable the internal test pattern
|
|
108
|
+
)
|
|
109
|
+
_MAIN_LVDS_BYPASS = (
|
|
110
|
+
b"\x44\x30" # LVDS power up
|
|
111
|
+
b"\x51\x05"
|
|
112
|
+
b"\x50\x24" # CP = 50 uA
|
|
113
|
+
b"\x51\x2d" # Pixel clock reference, second-order PLL
|
|
114
|
+
b"\x52\x04"
|
|
115
|
+
b"\x69\x0e"
|
|
116
|
+
b"\x69\x8e"
|
|
117
|
+
b"\x6a\x00"
|
|
118
|
+
b"\x6c\xb8"
|
|
119
|
+
b"\x6b\x51"
|
|
120
|
+
b"\x04\xfb" # Core PLL reset
|
|
121
|
+
b"\x04\xff"
|
|
122
|
+
b"\x7f\x00" # Disable scaler
|
|
123
|
+
b"\xa8\x13" # VESA format
|
|
124
|
+
b"\x44\x31" # LVDS output off
|
|
125
|
+
)
|
|
126
|
+
_AVI_AUDIO_OFF = (
|
|
127
|
+
b"\x06\x00" # Stop audio acquisition
|
|
128
|
+
b"\x07\x00" # Disable audio clock regeneration
|
|
129
|
+
b"\x34\x52" # Disable I2S input
|
|
130
|
+
b"\x3c\x40" # Disable audio/null packets
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class LT8912B(mipidsi.Display):
|
|
135
|
+
"""LT8912B MIPI DSI to HDMI/DVI bridge.
|
|
136
|
+
|
|
137
|
+
This resets the bridge and configures it for the given timing, then starts
|
|
138
|
+
DSI video. Use it with `framebufferio.FramebufferDisplay` like any other
|
|
139
|
+
``mipidsi.Display``. The framebuffer is always RGB888.
|
|
140
|
+
|
|
141
|
+
:param ~mipidsi.Bus bus: DSI bus connected to the bridge. Create it with a
|
|
142
|
+
``minimum_frequency`` of at least 24 times the pixel clock divided by
|
|
143
|
+
the number of lanes. The bridge checks the rate the bus actually runs
|
|
144
|
+
at, which can be higher.
|
|
145
|
+
:param ~busio.I2C i2c: I2C bus connected to the bridge.
|
|
146
|
+
:param int width: Active horizontal pixels.
|
|
147
|
+
:param int height: Active vertical lines.
|
|
148
|
+
:param int hsync_pulse_width: Horizontal sync width, 1 to 255 pixels.
|
|
149
|
+
:param int hsync_back_porch: Horizontal back porch in pixels.
|
|
150
|
+
:param int hsync_front_porch: Horizontal front porch in pixels.
|
|
151
|
+
:param int vsync_pulse_width: Vertical sync width, 1 to 255 lines.
|
|
152
|
+
:param int vsync_back_porch: Vertical back porch in lines.
|
|
153
|
+
:param int vsync_front_porch: Vertical front porch in lines.
|
|
154
|
+
:param int pixel_clock_frequency: Pixel clock in Hz.
|
|
155
|
+
:param ~microcontroller.Pin reset: Optional active-low reset pin.
|
|
156
|
+
:param bool hsync_positive: True for positive horizontal sync.
|
|
157
|
+
:param bool vsync_positive: True for positive vertical sync.
|
|
158
|
+
:param int vic: AVI video code, 0 to 127. 0 for custom timing.
|
|
159
|
+
:param int aspect_ratio: AVI aspect ratio: 0 unspecified, 1 4:3, 2 16:9.
|
|
160
|
+
:param kwargs: Other ``mipidsi.Display`` arguments, such as ``rotation``.
|
|
161
|
+
:raises ValueError: If a parameter is out of range, the bus is too slow for
|
|
162
|
+
the pixel clock, or the bridge does not respond on I2C.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__( # noqa: PLR0913, PLR0917
|
|
166
|
+
self,
|
|
167
|
+
bus,
|
|
168
|
+
i2c,
|
|
169
|
+
*,
|
|
170
|
+
width,
|
|
171
|
+
height,
|
|
172
|
+
hsync_pulse_width,
|
|
173
|
+
hsync_back_porch,
|
|
174
|
+
hsync_front_porch,
|
|
175
|
+
vsync_pulse_width,
|
|
176
|
+
vsync_back_porch,
|
|
177
|
+
vsync_front_porch,
|
|
178
|
+
pixel_clock_frequency,
|
|
179
|
+
reset=None,
|
|
180
|
+
hsync_positive=False,
|
|
181
|
+
vsync_positive=False,
|
|
182
|
+
vic=0,
|
|
183
|
+
aspect_ratio=0,
|
|
184
|
+
**kwargs,
|
|
185
|
+
):
|
|
186
|
+
h_total = width + hsync_front_porch + hsync_pulse_width + hsync_back_porch
|
|
187
|
+
v_total = height + vsync_front_porch + vsync_pulse_width + vsync_back_porch
|
|
188
|
+
if h_total > 0xFFFF or v_total > 0xFFFF:
|
|
189
|
+
raise ValueError("horizontal or vertical total does not fit in 16 bits")
|
|
190
|
+
if not 1 <= hsync_pulse_width <= 255:
|
|
191
|
+
raise ValueError("hsync_pulse_width must be between 1 and 255")
|
|
192
|
+
if not 1 <= vsync_pulse_width <= 255:
|
|
193
|
+
raise ValueError("vsync_pulse_width must be between 1 and 255")
|
|
194
|
+
if not 0 <= vic <= 127:
|
|
195
|
+
raise ValueError("vic must be between 0 and 127")
|
|
196
|
+
if not 0 <= aspect_ratio <= 2:
|
|
197
|
+
raise ValueError("aspect_ratio must be 0, 1, or 2")
|
|
198
|
+
|
|
199
|
+
# Configure the bridge before starting DSI video.
|
|
200
|
+
self._main = I2CDevice(i2c, _I2C_MAIN)
|
|
201
|
+
self._dsi = I2CDevice(i2c, _I2C_DSI)
|
|
202
|
+
self._avi = I2CDevice(i2c, _I2C_AVI)
|
|
203
|
+
|
|
204
|
+
self._reset_pin = None
|
|
205
|
+
if reset is not None:
|
|
206
|
+
self._reset_pin = digitalio.DigitalInOut(reset)
|
|
207
|
+
self._reset_pin.switch_to_output(value=True)
|
|
208
|
+
|
|
209
|
+
self._reset()
|
|
210
|
+
|
|
211
|
+
# Stop the transmitter while reconfiguring.
|
|
212
|
+
self._write_bit(self._main, _MAIN_TX_CONTROL, 1, False)
|
|
213
|
+
|
|
214
|
+
self._write(self._main, _MAIN_INIT)
|
|
215
|
+
self._write(self._dsi, _DSI_INIT_START)
|
|
216
|
+
self._write(self._dsi, bytes((_DSI_LANES, bus.num_lanes % 4))) # 0 means 4 lanes
|
|
217
|
+
self._write(self._dsi, _DSI_INIT_END)
|
|
218
|
+
# The vendor table seeds the DDS at 29 MHz; use the pixel clock.
|
|
219
|
+
self._seed_output_clock(pixel_clock_frequency)
|
|
220
|
+
|
|
221
|
+
timing = (
|
|
222
|
+
(_DSI_H_TOTAL, h_total),
|
|
223
|
+
(_DSI_V_TOTAL, v_total),
|
|
224
|
+
(_DSI_V_BACK_PORCH, vsync_back_porch),
|
|
225
|
+
(_DSI_V_FRONT_PORCH, vsync_front_porch),
|
|
226
|
+
(_DSI_H_BACK_PORCH, hsync_back_porch),
|
|
227
|
+
(_DSI_H_FRONT_PORCH, hsync_front_porch),
|
|
228
|
+
)
|
|
229
|
+
sequence = bytearray(
|
|
230
|
+
(
|
|
231
|
+
_DSI_H_SYNC,
|
|
232
|
+
hsync_pulse_width,
|
|
233
|
+
_DSI_V_SYNC,
|
|
234
|
+
vsync_pulse_width,
|
|
235
|
+
_DSI_H_ACTIVE,
|
|
236
|
+
width & 0xFF,
|
|
237
|
+
_DSI_H_ACTIVE + 1,
|
|
238
|
+
width >> 8,
|
|
239
|
+
_DSI_FIFO_BUFFER_LENGTH,
|
|
240
|
+
0x0C,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
for register, value in timing:
|
|
244
|
+
sequence.extend((register, value & 0xFF, register + 1, value >> 8))
|
|
245
|
+
self._write(self._dsi, sequence)
|
|
246
|
+
|
|
247
|
+
self._write_bit(self._main, _MAIN_SYNC_POLARITY, 1, hsync_positive)
|
|
248
|
+
self._write_bit(self._main, _MAIN_SYNC_POLARITY, 0, vsync_positive)
|
|
249
|
+
|
|
250
|
+
self._write_infoframe(vic, aspect_ratio)
|
|
251
|
+
self._reset_receiver()
|
|
252
|
+
self._write(self._main, _MAIN_LVDS_BYPASS)
|
|
253
|
+
self._write(self._avi, _AVI_AUDIO_OFF)
|
|
254
|
+
self._write_bit(self._main, _MAIN_HDMI_MODE, 0, False) # DVI
|
|
255
|
+
self._write_bit(self._main, _MAIN_TX_CONTROL, 1, True)
|
|
256
|
+
|
|
257
|
+
# Start DSI video only now. The lanes stay in high speed mode, so the
|
|
258
|
+
# bridge's receiver must be ready for the one start of transmission.
|
|
259
|
+
super().__init__(
|
|
260
|
+
bus,
|
|
261
|
+
b"",
|
|
262
|
+
width=width,
|
|
263
|
+
height=height,
|
|
264
|
+
hsync_pulse_width=hsync_pulse_width,
|
|
265
|
+
hsync_back_porch=hsync_back_porch,
|
|
266
|
+
hsync_front_porch=hsync_front_porch,
|
|
267
|
+
vsync_pulse_width=vsync_pulse_width,
|
|
268
|
+
vsync_back_porch=vsync_back_porch,
|
|
269
|
+
vsync_front_porch=vsync_front_porch,
|
|
270
|
+
pixel_clock_frequency=pixel_clock_frequency,
|
|
271
|
+
color_depth=24,
|
|
272
|
+
# The bridge regenerates the pixel clock from the DSI clock and
|
|
273
|
+
# loses lock if the lanes drop to low power during blanking.
|
|
274
|
+
low_power_blanking=False,
|
|
275
|
+
**kwargs,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# RGB888 needs 24 bits per pixel spread across the lanes.
|
|
279
|
+
needed = (self.pixel_clock_frequency * 24 + bus.num_lanes - 1) // bus.num_lanes
|
|
280
|
+
if bus.frequency + _BUS_FREQUENCY_TOLERANCE < needed:
|
|
281
|
+
self.deinit()
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"bus runs at {bus.frequency}, below the {needed} needed for this pixel clock"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Seed again from the real clock now that the DDS has video to track.
|
|
287
|
+
self._seed_output_clock(self.pixel_clock_frequency)
|
|
288
|
+
|
|
289
|
+
def _reset(self):
|
|
290
|
+
"""Pulse the reset pin if there is one, otherwise send a software reset."""
|
|
291
|
+
if self._reset_pin is not None:
|
|
292
|
+
self._reset_pin.value = False
|
|
293
|
+
time.sleep(0.01)
|
|
294
|
+
self._reset_pin.value = True
|
|
295
|
+
time.sleep(0.01)
|
|
296
|
+
return
|
|
297
|
+
with self._main:
|
|
298
|
+
self._main.write(bytes((0x01,)))
|
|
299
|
+
time.sleep(0.02)
|
|
300
|
+
|
|
301
|
+
def deinit(self):
|
|
302
|
+
"""Stop the display and release the reset pin."""
|
|
303
|
+
if self._reset_pin is not None:
|
|
304
|
+
self._reset_pin.deinit()
|
|
305
|
+
self._reset_pin = None
|
|
306
|
+
super().deinit()
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def hot_plug_detected(self):
|
|
310
|
+
"""True when the HDMI hot-plug detect line is high."""
|
|
311
|
+
return bool(self._read_byte(self._main, _MAIN_HPD_STATUS) & 0x80)
|
|
312
|
+
|
|
313
|
+
def _seed_output_clock(self, pixel_clock_frequency):
|
|
314
|
+
"""Start the output clock generator (DDS) at the pixel clock."""
|
|
315
|
+
dds = int(pixel_clock_frequency / 1_000_000 * 0x16C16)
|
|
316
|
+
self._write(
|
|
317
|
+
self._dsi,
|
|
318
|
+
bytes(
|
|
319
|
+
(
|
|
320
|
+
_DSI_DDS_CONTROL,
|
|
321
|
+
0x80,
|
|
322
|
+
_DSI_DDS_FREQUENCY,
|
|
323
|
+
dds & 0xFF,
|
|
324
|
+
_DSI_DDS_FREQUENCY + 1,
|
|
325
|
+
(dds >> 8) & 0xFF,
|
|
326
|
+
_DSI_DDS_FREQUENCY + 2,
|
|
327
|
+
(dds >> 16) & 0xFF,
|
|
328
|
+
_DSI_DDS_CONTROL,
|
|
329
|
+
0x00,
|
|
330
|
+
)
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _write_infoframe(self, vic, aspect_ratio):
|
|
335
|
+
"""Write the AVI infoframe: RGB, picture aspect ratio and VIC."""
|
|
336
|
+
# The checksum also covers the 82 02 0D header.
|
|
337
|
+
aspect = aspect_ratio << 4 | 0x08
|
|
338
|
+
payload = ((0x5F - aspect - vic) & 0xFF, 0x10, aspect, 0, vic) + (0,) * 9
|
|
339
|
+
infoframe = bytearray()
|
|
340
|
+
for index, value in enumerate(payload):
|
|
341
|
+
infoframe.extend((_AVI_CHECKSUM + index, value))
|
|
342
|
+
self._write(self._avi, infoframe)
|
|
343
|
+
|
|
344
|
+
def _reset_receiver(self):
|
|
345
|
+
"""Reset the MIPI receiver and the DDS."""
|
|
346
|
+
self._write(self._main, bytes((_MAIN_MIPI_RESET, 0x7F)))
|
|
347
|
+
time.sleep(0.01)
|
|
348
|
+
self._write(self._main, bytes((_MAIN_MIPI_RESET, 0xFF, _MAIN_DDS_RESET, 0xFB)))
|
|
349
|
+
time.sleep(0.01)
|
|
350
|
+
self._write(self._main, bytes((_MAIN_DDS_RESET, 0xFF)))
|
|
351
|
+
|
|
352
|
+
@staticmethod
|
|
353
|
+
def _write(device, sequence):
|
|
354
|
+
"""Write a flat sequence of (register, value) pairs."""
|
|
355
|
+
with device:
|
|
356
|
+
for index in range(0, len(sequence), 2):
|
|
357
|
+
device.write(sequence, start=index, end=index + 2)
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def _read_byte(device, register):
|
|
361
|
+
buffer = bytearray(1)
|
|
362
|
+
with device:
|
|
363
|
+
device.write_then_readinto(bytes((register,)), buffer)
|
|
364
|
+
return buffer[0]
|
|
365
|
+
|
|
366
|
+
def _write_bit(self, device, register, bit, value):
|
|
367
|
+
mask = 1 << bit
|
|
368
|
+
current = self._read_byte(device, register) & ~mask
|
|
369
|
+
self._write(device, bytes((register, current | (mask if value else 0))))
|