{
  "id": "mattytrentini/micropython-jd9853",
  "name": "micropython-jd9853",
  "prettyName": "micropython-jd9853",
  "description": "",
  "version": "",
  "author": "mattytrentini",
  "license": "MIT",
  "tags": [],
  "status": "VALID",
  "official": false,
  "source": "https://github.com/mattytrentini/micropython-jd9853",
  "updatedAt": "2026-09-08T06:47:49.768Z",
  "page": "https://checkmim.com/packages/mattytrentini+micropython-jd9853",
  "createdAt": "2026-09-08T06:47:48.651Z",
  "submittedBy": "Matt",
  "install": {
    "mpremote": "mpremote mip install github:mattytrentini/micropython-jd9853",
    "mip": "import mip; mip.install(\"github:mattytrentini/micropython-jd9853\")"
  },
  "readme": "# MicroPython JD9853 driver\n\nA pure-Python, four-wire SPI driver for the **Waveshare ESP32-C6-Touch-LCD-1.47**\n172×320 LCD. `JD9853` subclasses `framebuf.FrameBuffer` and uses standard RGB565\ncolors. No custom firmware or native extension is required.\n\n**Status:** host tests pass using real `framebuf` in the `micropython/unix`\nDocker image. Text/RGB output and all four orientations have also been visually\nconfirmed with Viper acceleration at 40 MHz on the Waveshare board running\nMicroPython 1.29.0. See\n[hardware validation](HARDWARE_TEST.md) for results and remaining checks.\n\n## Install and draw\n\nInstall both the driver and its optional Viper helper from GitHub:\n\n```python\nimport mip\nmip.install(\"github:mattytrentini/micropython-jd9853\")\n```\n\nOr, from the host with an existing `mpremote` installation:\n\n```sh\nmpremote connect auto mip install github:mattytrentini/micropython-jd9853\n```\n\n`mip` installs `jd9853.py` and `jd9853_viper.py` into the first `/lib` path on\nthe board. To install a specific release after tags are published, append its\ntag: `github:mattytrentini/micropython-jd9853@v0.1.0`.\n\nThis example uses the board's LCD pin assignments. The touch controller is a\nseparate device and is not handled by this driver.\n\n```python\nfrom machine import Pin, SPI\nfrom jd9853 import JD9853, rgb565\n\nPin(4, Pin.OUT, value=1)  # Keep the SD card deselected on the shared bus.\nbacklight = Pin(23, Pin.OUT, value=0)\nspi = SPI(1, baudrate=40_000_000, polarity=0, phase=0,\n          bits=8, firstbit=SPI.MSB, sck=Pin(1), mosi=Pin(2), miso=Pin(3))\ndisplay = JD9853(spi,\n                 cs=Pin(14, Pin.OUT, value=1),\n                 dc=Pin(15, Pin.OUT, value=0),\n                 reset=Pin(22, Pin.OUT, value=1),\n                 rotation=0)\n\ndisplay.fill(0)\ndisplay.text(\"MicroPython\", 10, 10, 0xFFFF)\ndisplay.rect(10, 30, 80, 40, rgb565(255, 0, 0), True)\ndisplay.show()\nbacklight(1)\n```\n\nSPI must use mode 0, eight-bit words, and MSB first. The example uses 40 MHz;\n20 MHz is also supported. The\ndriver uses the supplied SPI configuration without reinitializing it; callers\nsharing the bus must serialize access, select the appropriate SPI settings,\nand deselect other devices. LCD reads/MISO are not needed, but explicitly set\n`miso=Pin(3)` (the board's SD-card input). The ESP32-C6 build tested here defaults\nMISO to GPIO2 when omitted, conflicting with the LCD's MOSI on GPIO2.\n\n## Interface\n\n`JD9853(spi, cs, dc, reset, *, rotation=0)` accepts an initialized SPI object\nand output Pin objects. Construction resets and initializes the panel, clears\nits visible memory to black, and enables display output. Backlight control is\nseparate. Initialization includes a 10 ms reset pulse and 120 ms waits after\nreset release and sleep exit.\n\n| Rotation | `width` × `height` | Controller offset (x, y) |\n| --- | --- | --- |\n| 0 | 172 × 320 | 34, 0 |\n| 90 | 320 × 172 | 0, 34 |\n| 180 | 172 × 320 | 34, 0 |\n| 270 | 320 × 172 | 0, 34 |\n\nRotation follows the vendor's native orientation and MADCTL mapping. Select\nit at construction; to change orientation, discard the old instance, collect\ngarbage, and create another. The logical origin is the top-left corner of the\nselected orientation. Hardware testing must confirm the physical orientation.\n\n| Method or attribute | Behavior |\n| --- | --- |\n| Inherited `fill`, `pixel`, `line`, `rect`, `text`, `blit`, etc. | Draw into the framebuffer using MicroPython's normal semantics. |\n| `show()` | Send the entire framebuffer synchronously. Returns `None`. |\n| `show_rect(x, y, width, height)` | Send just one in-bounds framebuffer rectangle. Redraw all changed pixels in that rectangle first; useful for small animations. |\n| `invert(value)` | Enable/disable panel inversion. Enabled by default for normal colors on this panel. |\n| `poweroff()`, `poweron()` | Disable/enable display output without changing the Python framebuffer or backlight. These do not enter/exit sleep. |\n| `sleep(value)` | Enter/exit controller sleep; wait 120 ms before returning. After waking, call `show()`; if previously powered off, also call `poweron()`. |\n| `width`, `height` | Logical dimensions, to be treated as read-only. |\n| `buffer` | Native RGB565 backing `bytearray`. Edit its contents if needed; do not resize or replace it. |\n| `rgb565(r, g, b)` | Module function converting components in the range 0–255 to an RGB565 integer. |\n\nColors are conventional RGB565 integers: red `0xF800`, green `0x07E0`, blue\n`0x001F`, white `0xFFFF`, and black `0x0000`. Blit directly from other\n`framebuf.RGB565` framebuffers. When loading raw assets into `buffer`, their\n16-bit pixels must use the host's native byte order (little-endian on ESP32-C6).\nThe driver handles high-byte-first SPI transmission separately.\n\nMemory use is **110,080 bytes** for pixels, plus a reusable eight-row buffer\n(2,752 bytes in portrait or 5,120 in landscape), and Python/native code overhead.\n`show()` does not allocate another full framebuffer or modify the original\npixels. Avoid concurrent drawing or bus access during a transfer. SPI errors\npropagate after releasing CS; retrying `show()` reprograms the full window.\n\nThe optional `jd9853_viper.py` module accelerates conversion using Viper.\nIf it is absent or the build rejects Viper compilation, the driver falls back\nto Python conversion. The public RGB565 interface is identical in both cases.\nEight-row transfers reduce SPI call overhead; the final landscape transfer\ncontains only the remaining four rows, using a preallocated memoryview.\n\nBenchmarks on this board reduced portrait refresh time from about 715 ms\n(original Python loop, 20 MHz) to 68 ms (Viper and eight-row transfers, 20 MHz),\nor 46 ms at 40 MHz—roughly 22 full-frame updates per second. There is no\npromised minimum refresh rate. Transfers remain blocking, and updates are not\nsynchronized to the panel's refresh. Partial updates, runtime rotation, other panel configurations,\nhardware scrolling, and non-SPI interfaces are outside this version.\n\nThe datasheet's four-wire SPI write timing specifies a minimum 16 ns clock\nperiod (62.5 MHz), with minimum high/low durations of 7 ns (section 10.3.3,\npage 160). The example's 40 MHz is below that limit. The vendor demo's 80 MHz\nsetting exceeds the published timing limit.\n\n### Backlight brightness\n\nGPIO23 is application-controlled and independent of LCD display/sleep commands.\nFor PWM brightness, use this in place of the plain backlight Pin above:\n\n```python\nfrom machine import PWM\n\nbacklight = PWM(Pin(23), freq=5000, duty_u16=0)\n# After display initialization and show():\nbacklight.duty_u16(32768)  # Approximately 50% duty.\n# Turn off before disposal:\nbacklight.duty_u16(0)\nbacklight.deinit()\nPin(23, Pin.OUT, value=0)\n```\n\n## Testing\n\nRun the host tests in the Unix container; building the Unix port is unnecessary:\n\n```sh\ndocker run --rm -v \"$PWD:/work:ro\" -w /work micropython/unix tests/run.py\n```\n\nThe image already uses MicroPython as its entrypoint. The tests print the\nruntime version and exercise real framebuffer construction, drawing, scrolling,\nRGB565 blitting, and readback. Recording SPI/Pin doubles verify initialization,\ndelays, rotation windows, full transfer length, color byte order, repeated\nupdates, power/sleep controls, invalid rotation, and recovery from SPI errors.\nThese checks cannot establish electrical or visual correctness.\n\nThe suite also checks all 65,536 RGB565 values, conversion bounds, transfer\nboundaries, and error recovery on the short final landscape transfer. To test\nwithout the optional Viper module, mount only the core driver and tests:\n\n```sh\ndocker run --rm -v \"$PWD/jd9853.py:/work/jd9853.py:ro\" \\\n  -v \"$PWD/tests:/work/tests:ro\" -w /work micropython/unix tests/run.py\n```\n\nWith the board connected and both modules installed, run the manual test without\ninstalling it as `main.py`:\n\n```sh\nmpremote connect auto run examples/board_test.py\n```\n\nIt cycles through all four orientations, draws RGB bars, corner labels, a\none-pixel border, text and shapes, and exercises output, sleep, inversion and\nbrightness controls. It prints firmware information, free heap, and mean\n`show()` duration over ten transfers. At completion (or interruption), it\nturns the backlight off and releases peripherals.\n\nRecord these results during hardware validation:\n\n- Red/green/blue appear in order, with a black background and white markings.\n- All corner labels and the complete border are visible in every orientation;\n  there is no clipping, wrapping, mirroring, or displacement.\n- Repeated refreshes are stable; display off/on and sleep/wake restore the image.\n- Disabling inversion changes the colors; re-enabling it restores them.\n- PWM brightness works; cold power-up followed by the example also works.\n- Record the MicroPython build, SPI frequency, free heap, and refresh duration.\n\n## Sources and license\n\n- [JD9853 datasheet](https://files.waveshare.com/wiki/common/Jd9853_datasheet.pdf):\n  serial protocol, RGB565 format, address windows, MADCTL and timing.\n- [Waveshare board documentation](https://docs.waveshare.com/ESP32-C6-Touch-LCD-1.47):\n  panel dimensions, GPIO assignments and shared SPI wiring.\n- [Waveshare demo archive](https://files.waveshare.com/wiki/ESP32-C6-Touch-LCD-1.47/ESP32-C6-Touch-LCD-1.47-Demo.zip):\n  vendor initialization from\n  `ESP-IDF/01_factory/components/esp_lcd_jd9853/esp_lcd_jd9853.c`, rotation\n  mapping from `main/main.c`, and RGB/inversion defaults from `bsp_display.c`.\n- [MicroPython framebuf documentation](https://docs.micropython.org/en/latest/library/framebuf.html)\n  and [SSD1306 driver](https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/display/ssd1306/ssd1306.py):\n  framebuffer interface and subclassing style.\n\nThe driver implementation is licensed under MIT; see [LICENSE](LICENSE). The\npanel register data adapted from the Waveshare/Espressif demo remains subject\nto Apache-2.0; its attribution and license information are in [NOTICE](NOTICE).\nThe vendor register data retains its original order, including repeated\nregister-page writes. Standard commands are applied separately: one sleep exit,\nTE output disabled, explicit RGB565 and rotation, inversion enabled, a full\nblack frame transferred, then display enabled. Panel-specific power/gamma\nsettings have not been generalized to other JD9853 LCDs.\n"
}