Skip to content

Instantly share code, notes, and snippets.

@JC3
Last active February 3, 2024 17:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JC3/cd04d9ee9751a48ad8565649e3392eea to your computer and use it in GitHub Desktop.
Save JC3/cd04d9ee9751a48ad8565649e3392eea to your computer and use it in GitHub Desktop.
Crystalfontz CFA-735 Display Example
# This demonstrates how to send/receive packets to a Crystalfontz
# display and also display text on the display. It does not demonstrate
# keypad input or anything like that but the CrystalFontz class can be
# used as a basis for adding whatever.
# -----------------------------------------------------------------------
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <https://unlicense.org>
import serial
from dataclasses import dataclass
from typing import Any
_CRC_LUT = [
0x00000,0x01189,0x02312,0x0329b,0x04624,0x057ad,0x06536,0x074bf,
0x08c48,0x09dc1,0x0af5a,0x0bed3,0x0ca6c,0x0dbe5,0x0e97e,0x0f8f7,
0x01081,0x00108,0x03393,0x0221a,0x056a5,0x0472c,0x075b7,0x0643e,
0x09cc9,0x08d40,0x0bfdb,0x0ae52,0x0daed,0x0cb64,0x0f9ff,0x0e876,
0x02102,0x0308b,0x00210,0x01399,0x06726,0x076af,0x04434,0x055bd,
0x0ad4a,0x0bcc3,0x08e58,0x09fd1,0x0eb6e,0x0fae7,0x0c87c,0x0d9f5,
0x03183,0x0200a,0x01291,0x00318,0x077a7,0x0662e,0x054b5,0x0453c,
0x0bdcb,0x0ac42,0x09ed9,0x08f50,0x0fbef,0x0ea66,0x0d8fd,0x0c974,
0x04204,0x0538d,0x06116,0x0709f,0x00420,0x015a9,0x02732,0x036bb,
0x0ce4c,0x0dfc5,0x0ed5e,0x0fcd7,0x08868,0x099e1,0x0ab7a,0x0baf3,
0x05285,0x0430c,0x07197,0x0601e,0x014a1,0x00528,0x037b3,0x0263a,
0x0decd,0x0cf44,0x0fddf,0x0ec56,0x098e9,0x08960,0x0bbfb,0x0aa72,
0x06306,0x0728f,0x04014,0x0519d,0x02522,0x034ab,0x00630,0x017b9,
0x0ef4e,0x0fec7,0x0cc5c,0x0ddd5,0x0a96a,0x0b8e3,0x08a78,0x09bf1,
0x07387,0x0620e,0x05095,0x0411c,0x035a3,0x0242a,0x016b1,0x00738,
0x0ffcf,0x0ee46,0x0dcdd,0x0cd54,0x0b9eb,0x0a862,0x09af9,0x08b70,
0x08408,0x09581,0x0a71a,0x0b693,0x0c22c,0x0d3a5,0x0e13e,0x0f0b7,
0x00840,0x019c9,0x02b52,0x03adb,0x04e64,0x05fed,0x06d76,0x07cff,
0x09489,0x08500,0x0b79b,0x0a612,0x0d2ad,0x0c324,0x0f1bf,0x0e036,
0x018c1,0x00948,0x03bd3,0x02a5a,0x05ee5,0x04f6c,0x07df7,0x06c7e,
0x0a50a,0x0b483,0x08618,0x09791,0x0e32e,0x0f2a7,0x0c03c,0x0d1b5,
0x02942,0x038cb,0x00a50,0x01bd9,0x06f66,0x07eef,0x04c74,0x05dfd,
0x0b58b,0x0a402,0x09699,0x08710,0x0f3af,0x0e226,0x0d0bd,0x0c134,
0x039c3,0x0284a,0x01ad1,0x00b58,0x07fe7,0x06e6e,0x05cf5,0x04d7c,
0x0c60c,0x0d785,0x0e51e,0x0f497,0x08028,0x091a1,0x0a33a,0x0b2b3,
0x04a44,0x05bcd,0x06956,0x078df,0x00c60,0x01de9,0x02f72,0x03efb,
0x0d68d,0x0c704,0x0f59f,0x0e416,0x090a9,0x08120,0x0b3bb,0x0a232,
0x05ac5,0x04b4c,0x079d7,0x0685e,0x01ce1,0x00d68,0x03ff3,0x02e7a,
0x0e70e,0x0f687,0x0c41c,0x0d595,0x0a12a,0x0b0a3,0x08238,0x093b1,
0x06b46,0x07acf,0x04854,0x059dd,0x02d62,0x03ceb,0x00e70,0x01ff9,
0x0f78f,0x0e606,0x0d49d,0x0c514,0x0b1ab,0x0a022,0x092b9,0x08330,
0x07bc7,0x06a4e,0x058d5,0x0495c,0x03de3,0x02c6a,0x01ef1,0x00f78
]
def _crc16(data, crc=0xffff):
for b in data:
crc = crc >> 8 ^ _CRC_LUT[(crc ^ b) & 0xff]
return ~crc & 0xffff
@dataclass
class CFPacket:
ptype: int = None
command: int = None
data: bytes = None
class CrystalFontz:
def __init__ (self, port: str, baud: int = 115200):
self._ser = serial.Serial(port, baud, timeout=3)
self._cols = 20 # TODO: adjust for other display sizes?
def __enter__ (self):
return self
def __exit__ (self, exc_type, exc_value, exc_tb):
self.close()
def _write_packet (self, command: int, data: Any = None) -> None:
if self._ser is None:
raise RuntimeError("Device has been closed.")
if data is None:
data = bytes()
elif isinstance(data, int):
data = bytes([data])
elif isinstance(data, str):
data = data.encode('latin_1')
elif isinstance(data, list):
data = bytes(data)
elif isinstance(data, bytes) or isinstance(data, bytearray):
pass
else:
raise TypeError("Unsupported command data type.")
data_length = len(data)
if data_length > 255:
raise OverflowError("Data length cannot exceed 255.")
payload = bytearray([command, data_length])
payload = payload + data
crc = _crc16(payload)
crclo = crc & 0xff
crchi = (crc >> 8) & 0xff
payload = payload + bytes([crclo, crchi])
if self._ser.write(payload) != len(payload):
raise RuntimeError("Incomplete serial write.")
def _read_packet (self) -> CFPacket:
if self._ser is None:
raise RuntimeError("Device has been closed.")
header = self._ser.read(2)
data_length = header[1]
response = CFPacket()
response.ptype = (header[0] & 0xC0) >> 6
response.command = header[0] & 0x3F
response.data = self._ser.read(data_length)
self._ser.read(2) # crc ignored
return response
def _send_command (self, command: int, data: Any = None) -> CFPacket:
self._write_packet(command, data)
response = self._read_packet()
if response.command != command:
raise RuntimeError(f"Received unexpected response {response.command} for command {command}.")
elif response.ptype != 1:
print(response)
raise RuntimeError(f"Received error response for command {command}.")
return response
# ------------------------------------------------------------
# public api
# ------------------------------------------------------------
def close (self):
if self._ser is not None:
self._ser.close()
self._ser = None
def ping (self) -> None:
self._send_command(0x00, "PING")
def get_version (self) -> str:
return self._send_command(0x01, 0).data.decode('latin_1')
def get_serial (self) -> str:
return self._send_command(0x01, 1).data.decode('latin_1')
def clear (self) -> None:
self._send_command(0x06)
def print_at (self, row, col, text) -> None:
data = bytearray([col, row])
data = data + text[:20].encode('latin_1')
self._send_command(0x1F, data)
def print_line (self, row, text) -> None:
return self.print_at(row, 0, text.ljust(self._cols))
# ------------------------------------------------------------------------------
# test program
# ------------------------------------------------------------------------------
if __name__ == "__main__":
import argparse
args = argparse.ArgumentParser()
args.add_argument("port")
opts = args.parse_args()
xf = CrystalFontz(opts.port)
xf.ping()
version = xf.get_version()
serialn = xf.get_serial()
xf.clear()
xf.print_line(0, "It works!")
xf.print_line(1, "-" * 20)
xf.print_line(2, version)
xf.print_line(3, serialn)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment