stream.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. #!/usr/bin/env python3
  2. import argparse
  3. import struct
  4. import time
  5. from pycrc.algorithms import Crc
  6. import control
  7. # ---------------------------------------------------------------------------
  8. # DSI utilities
  9. # ---------------------------------------------------------------------------
  10. EOTP = bytearray([ 0x08, 0x0f, 0x0f, 0x01 ])
  11. DSI_CRC = Crc(width=16, poly=0x1021, xor_in=0xffff, xor_out=0x0000, reflect_in=True, reflect_out=True)
  12. def parity(x):
  13. p = 0
  14. while x:
  15. p ^= x & 1
  16. x >>= 1
  17. return p
  18. def dsi_header(*data):
  19. cmd = (data[2] << 16) | (data[1] << 8) | data[0]
  20. ecc = 0
  21. if parity(cmd & 0b111100010010110010110111): ecc |= 0x01;
  22. if parity(cmd & 0b111100100101010101011011): ecc |= 0x02;
  23. if parity(cmd & 0b011101001001101001101101): ecc |= 0x04;
  24. if parity(cmd & 0b101110001110001110001110): ecc |= 0x08;
  25. if parity(cmd & 0b110111110000001111110000): ecc |= 0x10;
  26. if parity(cmd & 0b111011111111110000000000): ecc |= 0x20;
  27. return bytearray(data) + bytearray([ecc])
  28. def dsi_crc(payload):
  29. crc = DSI_CRC.bit_by_bit(bytes(payload))
  30. return bytearray([ crc & 0xff, (crc >> 8) & 0xff ])
  31. def dcs_short_write(cmd, val=None):
  32. if val is None:
  33. return dsi_header(0x05, cmd, 0x00)
  34. else:
  35. return dsi_header(0x15, cmd, val)
  36. def dcs_long_write(cmd, data):
  37. pl = bytearray([ cmd ]) + data
  38. l = len(pl)
  39. return dsi_header(0x39, l & 0xff, l >> 8) + pl + dsi_crc(pl)
  40. def generic_short_write(cmd, val=None):
  41. if val is None:
  42. return dsi_header(0x13, cmd, 0x00)
  43. else:
  44. return dsi_header(0x23, cmd, val)
  45. def generic_long_write(cmd, data):
  46. pl = bytearray([ cmd ]) + data
  47. l = len(pl)
  48. return dsi_header(0x29, l & 0xff, l >> 8) + pl + dsi_crc(pl)
  49. # ---------------------------------------------------------------------------
  50. # nanoPMOD Board control
  51. # ---------------------------------------------------------------------------
  52. class DSIControl(control.BoardControlBase):
  53. REG_LCD_CTRL = 0x00
  54. REG_DSI_HS_PREP = 0x10
  55. REG_DSI_HS_ZERO = 0x11
  56. REG_DSI_HS_TRAIL = 0x12
  57. REG_PKT_WR_DATA_RAW = 0x20
  58. REG_PKT_WR_DATA_U8 = 0x21
  59. TRANSPOSE_NONE = 0
  60. TRANSPOSE_DCS = 1
  61. TRANSPOSE_MANUAL = 2
  62. def __init__(self, n_col=240, n_page=240, flip_col=False, flip_page=False, transpose=TRANSPOSE_NONE, **kwargs):
  63. # Super call
  64. super().__init__(**kwargs)
  65. # Save params
  66. self.n_col = n_col
  67. self.n_page = n_page
  68. self.flip_col = flip_col
  69. self.flip_page = flip_page
  70. self.transpose = transpose
  71. # Init the LCD
  72. self.init()
  73. def init(self):
  74. # Default values
  75. self.backlight = 0x100
  76. # Turn off Back Light / HS clock and assert reset
  77. self.reg_w16(self.REG_LCD_CTRL, 0x8000)
  78. # Wait a bit
  79. time.sleep(0.1)
  80. # Configure backlight and release reset
  81. self.reg_w16(self.REG_LCD_CTRL, self.backlight)
  82. # Configure DSI timings
  83. self.reg_w8(self.REG_DSI_HS_PREP, 0x10)
  84. self.reg_w8(self.REG_DSI_HS_ZERO, 0x18)
  85. self.reg_w8(self.REG_DSI_HS_TRAIL, 0x18)
  86. # Enable HS clock
  87. self.reg_w16(self.REG_LCD_CTRL, 0x4000 | self.backlight)
  88. # Wait a bit
  89. time.sleep(0.1)
  90. # Send DSI packets
  91. self.send_dsi_pkt(
  92. dcs_short_write(0x11) + # Exist sleep
  93. EOTP # EoTp
  94. )
  95. self.send_dsi_pkt(
  96. dcs_short_write(0x29) + # Exist sleep
  97. EOTP # EoTp
  98. )
  99. mode = (
  100. (0x80 if self.flip_page else 0) |
  101. (0x40 if self.flip_col else 0) |
  102. (0x20 if self.transpose == DSIControl.TRANSPOSE_DCS else 0)
  103. )
  104. # Note. According to the DCS spec, mode.B3=0 should be RGB ... but
  105. # the nano display driver IC has an errata and it's actually
  106. # BGR order.
  107. self.send_dsi_pkt(
  108. dcs_short_write(0x11) + # Exist sleep
  109. dcs_short_write(0x29) + # Display on
  110. dcs_short_write(0x36, mode) + # Set address mode
  111. dcs_short_write(0x3a, 0x55) + # Set pixel format
  112. EOTP # EoTp
  113. )
  114. def set_backlight(self, backlight):
  115. self.backlight = backlight
  116. self.reg_w16(self.REG_LCD_CTRL, 0x4000 | self.backlight)
  117. def send_dsi_pkt(self, data):
  118. self.reg_burst(self.REG_PKT_WR_DATA_RAW, data)
  119. def set_column_address(self, sc, ec):
  120. self.send_dsi_pkt(dcs_long_write(0x2a, struct.pack('>HH', sc, ec)))
  121. def set_page_address(self, sp, ep):
  122. self.send_dsi_pkt(dcs_long_write(0x2b, struct.pack('>HH', sp, ep)))
  123. def _send_frame_normal(self, frame, bpp):
  124. # Max packet size
  125. mtu = 1024 - 4 - 1 - 2
  126. psz = (mtu // (2 * self.n_col)) * (2 * self.n_col)
  127. pcnt = (self.n_col * self.n_page * 2 + psz - 1) // psz
  128. if bpp == 16:
  129. for i in range(pcnt):
  130. self.send_dsi_pkt(
  131. dsi_header(0x39, (psz + 1) & 0xff, (psz + 1) >> 8) +
  132. (b'\x2c' if i == 0 else b'\x3c') +
  133. frame[i*psz:(i+1)*psz] +
  134. b'\x00\x00'
  135. )
  136. else:
  137. for i in range(pcnt):
  138. self.reg_burst(self.REG_PKT_WR_DATA_U8,
  139. dsi_header(0x39, (psz + 1) & 0xff, (psz + 1) >> 8) +
  140. (b'\x2c' if i == 0 else b'\x3c') +
  141. frame[i*(psz//2):(i+1)*(psz//2)] +
  142. b'\x00'
  143. )
  144. def _send_frame_transpose_16b(self, frame):
  145. # Packet size for each line
  146. mtu = 1024
  147. psz = len(self._line_sel_cmd[0]) + 4 + 1 + self.n_page * 2 + 2
  148. ppb = mtu // psz
  149. # Scan each line
  150. lsz = self.n_page * 2
  151. burst = []
  152. bpc = 0
  153. for y in range(self.n_col):
  154. burst.append(self._line_sel_cmd[y])
  155. burst.append(dsi_header(0x39, (lsz + 1) & 0xff, (lsz + 1) >> 8))
  156. burst.append(b'\x2c')
  157. burst.append(frame[y*lsz:(y+1)*lsz])
  158. burst.append(b'\x00\x00')
  159. bpc += 1
  160. if (bpc == ppb) or (y == (self.n_col-1)):
  161. self.send_dsi_pkt(b''.join(burst))
  162. bpc = 0
  163. burst = []
  164. def _send_frame_transpose_8b(self, frame):
  165. # No choice but to scan each line independently
  166. for y in range(self.n_col):
  167. # Select line
  168. self.send_dsi_pkt(self._line_sel_cmd[y])
  169. # Send data with special 8bit expand command
  170. lsz = self.n_page
  171. self.reg_burst(self.REG_PKT_WR_DATA_U8,
  172. dsi_header(0x39, (lsz*2 + 1) & 0xff, (lsz*2 + 1) >> 8) +
  173. b'\x2c' +
  174. frame[y*lsz:(y+1)*lsz] +
  175. b'\x00'
  176. )
  177. def send_frame(self, frame, bpp=16):
  178. # Delegate depending on config
  179. if self.transpose == DSIControl.TRANSPOSE_MANUAL:
  180. # Init the command tables
  181. if not hasattr(self, '_line_sel_cmd'):
  182. self._line_sel_cmd = []
  183. for y in range(self.n_col):
  184. self._line_sel_cmd.append(
  185. dcs_long_write(0x2a, struct.pack('>HH', y, y))
  186. )
  187. # In 8 bit mode, we can't combine packets, so do it all the slow way
  188. if bpp == 8:
  189. self._send_frame_transpose_8b(frame)
  190. else:
  191. self._send_frame_transpose_16b(frame)
  192. else:
  193. self._send_frame_normal(frame, bpp)
  194. # ---------------------------------------------------------------------------
  195. # Main
  196. # ---------------------------------------------------------------------------
  197. def load_bgr888_as_bgr565(filename):
  198. img = open(filename,'rb').read()
  199. dat = []
  200. for i in range(len(img) // 4):
  201. b = img[4*i + 0]
  202. g = img[4*i + 1]
  203. r = img[4*i + 2]
  204. c = ((r >> 3) & 0x1f) << 11;
  205. c |= ((g >> 2) & 0x3f) << 5;
  206. c |= ((b >> 3) & 0x1f) << 0;
  207. dat.append( ((c >> 0) & 0xff) )
  208. dat.append( ((c >> 8) & 0xff) )
  209. return bytearray(dat)
  210. def main():
  211. # Parse options
  212. parser = argparse.ArgumentParser(
  213. formatter_class=argparse.ArgumentDefaultsHelpFormatter
  214. )
  215. g_input = parser.add_argument_group('input', 'Input options')
  216. g_display = parser.add_argument_group('display', 'Display configuation options')
  217. g_brd = parser.add_argument_group('board', 'Board configuration options')
  218. g_input.add_argument('--input', type=argparse.FileType('rb'), metavar='FILE', help='Input file', required=True)
  219. g_input.add_argument('--fps', type=float, help='Target FPS to regulate to (None=no regulation)')
  220. g_input.add_argument('--loop', help='Play in a loop', action='store_true', default=False)
  221. g_input.add_argument('--bgr8', help='Input is BGR8 instead of BGR565', action='store_true', default=False)
  222. g_display.add_argument('--n_col', type=int, metavar='N', help='Number of columns', default=240)
  223. g_display.add_argument('--n_page', type=int, metavar='N', help='Number of pages', default=240)
  224. g_display.add_argument('--flip_col', help='Flip column order', action='store_true', default=False)
  225. g_display.add_argument('--flip_page', help='Flip page order', action='store_true', default=False)
  226. g_display.add_argument('--transpose', help='Transpose mode', choices=['none', 'dcs', 'manual'], default='none')
  227. control.arg_group_setup(g_brd)
  228. args = parser.parse_args()
  229. # Build the actual panel control object with those params
  230. kwargs = control.arg_to_kwargs(args)
  231. kwargs['n_col'] = args.n_col
  232. kwargs['n_page'] = args.n_page
  233. kwargs['flip_col'] = args.flip_col
  234. kwargs['flip_page'] = args.flip_page
  235. kwargs['transpose'] = {
  236. 'none' : DSIControl.TRANSPOSE_NONE,
  237. 'dcs' : DSIControl.TRANSPOSE_DCS,
  238. 'manual' : DSIControl.TRANSPOSE_MANUAL,
  239. }[args.transpose]
  240. ctrl = DSIControl(**kwargs)
  241. # Streaming loop
  242. if args.fps:
  243. tpf = 1.0 / args.fps
  244. tt = time.time() + tpf
  245. fsize = args.n_col * args.n_page * (1 if args.bgr8 else 2)
  246. while True:
  247. # Send one frame
  248. data = args.input.read(fsize)
  249. if len(data) == fsize:
  250. ctrl.send_frame(data, bpp=(8 if args.bgr8 else 16))
  251. # Loop ?
  252. else:
  253. if args.loop:
  254. args.input.seek(0)
  255. continue
  256. else:
  257. break
  258. # FPS regulation
  259. if args.fps:
  260. w = tt - time.time()
  261. if w > 0:
  262. time.sleep(w)
  263. tt += tpf
  264. if __name__ == '__main__':
  265. main()