LCDGFX LCD display driver  1.2.0
Lightweight graphics library for SSD1306, SSD1325, SSD1327, SSD1331, SSD1351, SH1106, SH1107, IL9163, ST7735, ST7789, ILI9341, PCD8544 displays over I2C/SPI
lcd_code_generator.py
1 #!/usr/bin/python3
2 # -*- coding: UTF-8 -*-
3 # MIT License
4 #
5 # Copyright (c) 2019, Alexey Dynda
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in all
15 # copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 # SOFTWARE.
24 #
25 
26 
27 import re
28 import sys
29 import os
30 import shutil
31 import json
32 from collections import OrderedDict
33 
34 def print_help_and_exit():
35  print("Usage: lcd_code_generator.py [args]")
36  print("args:")
37  print(" -c name controller name")
38  print(" -b bits bits per pixel")
39  print(" -B Do not add bits to screen class name")
40  print(" -r WxH resolution")
41  print(" -j json Use json data from file")
42  print(" -t template path source templates, templates by default")
43  print("")
44  print("examples:")
45  print(" ./lcd_code_generator.py -c all")
46  exit(1)
47 
48 if len(sys.argv) < 2:
49  print_help_and_exit()
50 
51 g_voc={}
52 
53 controller=""
54 templates="templates"
55 resolution_db = []
56 g_voc = {
57  "options":
58  {
59  "no_bits_in_name": False,
60  "args_in_cmd_mode": False,
61  "rowcol_bits": 8,
62  "col_cmd": "0x15",
63  "row_cmd": "0x75",
64  "use_paging": False,
65  },
66  "interfaces": {},
67  "bits": {},
68  "CONTROLLER": "",
69  "controller": "",
70  "resolution": "",
71  "exbits": "",
72  "_bits": "",
73  "width": "",
74  "height": "",
75  "init_data": "" }
76 
77 # parse args
78 idx = 1
79 while idx < len(sys.argv):
80  opt = sys.argv[idx]
81  if opt == "-c":
82  idx += 1
83  controller = sys.argv[idx]
84  elif opt == "-b":
85  idx += 1
86  bits = int(sys.argv[idx])
87  g_voc["bits"].append({"bits": zip( resolution_db, [{}] * len(resolution_db) )})
88  resolution_db = []
89  elif opt == "-t":
90  idx += 1
91  templates = sys.argv[idx]
92  elif opt == "-j":
93  idx += 1
94  elif opt == "-r":
95  idx += 1
96  resolution = sys.argv[idx]
97  resolution_db.append( resolution )
98  for t in g_voc["bits"]:
99  t.append( { resolution: {} } )
100  else:
101  print("Unknown option: ", opt)
102  print_help_and_exit()
103  idx += 1
104 
105 def get_val_by_path(path, default):
106  nodes = path.split('/')
107  data = g_voc;
108  for i in range(0, len(nodes) - 1):
109  data = data.get(nodes[i],{})
110  # check if file exists
111  ctl_path = templates + "/lcd/" + g_voc["controller"] + "/" + path
112  base_path = templates + path
113  if len( data ) == 0 and os.path.exists(ctl_path) and os.path.isfile(ctl_path):
114  with open(ctl_path, 'r') as myfile: data = myfile.read().splitlines()
115  elif len( data ) == 0 and os.path.exists(base_path) and os.path.isfile(base_path):
116  with open(base_path, 'r') as myfile:
117  data = myfile.read().splitlines()
118  else:
119  data = data.get( nodes[-1], default )
120  return data
121 
122 def read_template(fname):
123  if not os.path.isfile(templates + fname):
124  return ""
125  with open(templates + fname, 'r') as myfile:
126  data=myfile.read()
127  return data
128 
129 def fill_template(temp):
130  temp = temp.replace('~FUNCS_DECL~', get_val_by_path("FUNCS_DECL", ""))
131  temp = temp.replace('~FIELDS_DECL~', get_val_by_path("FIELDS_DECL", ""))
132  temp = temp.replace('~SERIAL_INTERFACE_ARGS~', get_val_by_path("serial_interface_args", ""))
133  temp = temp.replace('~CUSTOM_SERIAL_INTERFACE_ARGS~', get_val_by_path("custom_serial_interface_args", ""))
134  temp = temp.replace('~CUSTOM_INTERFACE_ARGS~', get_val_by_path("custom_interface_args", ""))
135  temp = temp.replace('~CONTROLLER~', get_val_by_path("CONTROLLER",""))
136  temp = temp.replace('~controller~', get_val_by_path("controller",""))
137  temp = temp.replace('~RESOLUTION~', get_val_by_path("resolution",""))
138  temp = temp.replace('~EXBITS~', get_val_by_path("exbits",""))
139  temp = temp.replace('~BITS~', get_val_by_path("_bits",""))
140  temp = temp.replace('~WIDTH~', get_val_by_path("width",""))
141  temp = temp.replace('~HEIGHT~',get_val_by_path("height",""))
142  temp = temp.replace('~INIT~', get_val_by_path("init_data",""))
143  temp = temp.replace('~OPTIONAL_CONFIG~', get_val_by_path("optional_config",""))
144  temp = temp.replace('~CONFIG_FUNC~', get_val_by_path("options/config_func","_configureSpiDisplay"))
145  temp = temp.replace('~SET_BLOCK~', get_val_by_path("_set_block",""))
146  temp = temp.replace('~END_BLOCK~', get_val_by_path("_end_block", " this->stop();"))
147  temp = temp.replace('~FREQUENCY~', get_val_by_path("_frequency", "4400000"))
148  temp = temp.replace('~I2C_ADDR~', get_val_by_path("interfaces/i2c/addr", "0x3C"))
149  temp = temp.replace('~FUNCS_DEF~', get_val_by_path("FUNCS_DEF", ""))
150  temp = temp.replace('~RESET_DURATION~', str(get_val_by_path("options/reset_duration", 20)))
151  temp = temp.replace('~RESET_DELAY~', str(get_val_by_path("options/reset_delay", 100)))
152  return temp
153 
154 def get_file_data(fname):
155  temp = read_template(fname)
156  temp = fill_template(temp)
157  return temp
158 
159 def get_json_controller_list(fname):
160  with open(templates + 'lcd/' + fname + "/" + fname + ".json") as json_file:
161  data = json.load(json_file, object_pairs_hook=OrderedDict)
162  return data.keys()
163 
164 def load_data_from_json(fname, ctl):
165  with open(templates + 'lcd/' + fname) as json_file:
166  data = json.load(json_file, object_pairs_hook=OrderedDict)
167  g_voc["bits"] = data[ctl]["bits"];
168  g_voc["options"] = data[ctl]["options"]
169  g_voc["interfaces"] = data[ctl]["interfaces"]
170  g_voc["functions"] = data[ctl].get("functions", {})
171 
172 def load_init_data_from_json(fname, ctl, bits, resolution):
173  with open(templates + 'lcd/' + fname) as json_file:
174  data = json.load(json_file, object_pairs_hook=OrderedDict)
175  g_voc["init_data"] = "\n".join( data[ctl]["bits"][bits][resolution]["init"] )
176  if "begin" in data[ctl]["bits"][bits][resolution]:
177  g_voc["optional_config"] = "\n".join( data[ctl]["bits"][bits][resolution]["begin"] )
178  else:
179  g_voc["optional_config"] = ""
180 
181 templates = templates + "/"
182 
183 def generate_custom_decl(name, doc=[" /**", " * DOCUMENT"," */"], type=["void",""]):
184  lines = get_val_by_path("functions/" + name + "/doc", doc)
185  decl = get_val_by_path("functions/" + name + "/decl", type)
186  init = get_val_by_path("functions/" + name + "/init", None)
187  if init is not None and len(init) > 0:
188  lines.append( ' '*4 + decl[0])
189  lines.append( ' '*4 + name + "(" + ', '.join(decl[1:]) +")")
190  lines.append( ' '*8 + ": " + init[0])
191  for i in init[1:]:
192  lines.append(' '*8 + ", " + i)
193  lines.extend([' '*4 + "{", ' '*4 + "}"])
194  else:
195  lines.append( " " + decl[0] + " " + name + "(" + ', '.join(decl[1:]) +");")
196  return fill_template('\n'.join(lines))
197 
198 def generate_custom_def(name, type=["void",""], code=[]):
199  init = get_val_by_path("functions/" + name + "/init", None)
200  if init is not None:
201  return None
202  delc = get_val_by_path("functions/" + name + "/decl", type)
203  lines = [ "template <class I> " + delc[0] + \
204  " Interface~CONTROLLER~<I>::" + name + "(" + \
205  ', '.join(delc[1:]) +")", "{" ]
206  code = get_val_by_path("functions/" + name + "/code",code)
207  if len(code) == 0:
208  if name == "startBlock":
209  code = [generate_set_block_content()]
210  lines.append( '\n'.join(code) )
211  lines.extend( ["}", ""] )
212  return fill_template('\n'.join(lines))
213 
214 def generate_set_block_content():
215  lines = [" lcduint_t rx = w ? (x + w - 1) : (m_base.width() - 1);",
216  " commandStart();" ]
217  lines.append(" this->send({0});".format(get_val_by_path("options/col_cmd", "0x22")))
218  if not get_val_by_path("options/args_in_cmd_mode", False):
219  lines.append(" setDataMode(1); // According to datasheet all args must be passed in data mode")
220  if get_val_by_path("options/rowcol_bits",8) != 8:
221  lines.append(" this->send(0);")
222  if get_val_by_path("options/column_div", 1) > 1:
223  lines.append( " this->send(x / {0});".format(get_val_by_path("options/column_div", 1)) )
224  else:
225  lines.append( " this->send(x);" )
226  if get_val_by_path("options/rowcol_bits",8) != 8:
227  lines.append(" this->send(0);")
228  if get_val_by_path("options/column_div", 1) > 1:
229  lines.append( " this->send( (rx < m_base.width() ? rx : (m_base.width() - 1)) / {0} );".format(
230  get_val_by_path("options/column_div", 1)) )
231  else:
232  lines.append( " this->send( rx < m_base.width() ? rx : (m_base.width() - 1) );" )
233 
234  if not get_val_by_path("options/args_in_cmd_mode", False):
235  lines.append(" setDataMode(0);")
236  lines.append(" this->send({0});".format(get_val_by_path("options/row_cmd", "0x72")))
237  if not get_val_by_path("options/args_in_cmd_mode", False):
238  lines.append(" setDataMode(1); // According to datasheet all args must be passed in data mode")
239  if get_val_by_path("options/rowcol_bits",8) != 8:
240  lines.append(" this->send(0);")
241  lines.append(" this->send(y);")
242  if get_val_by_path("options/rowcol_bits",8) != 8:
243  lines.append(" this->send(0);")
244  lines.append(" this->send(m_base.height() - 1);")
245  if not get_val_by_path("options/args_in_cmd_mode", False):
246  lines.append(" setDataMode(0);")
247  if get_val_by_path("options/exit_cmd_mode_command", None) is not None:
248  lines.append(" this->send({0});".format( get_val_by_path("options/exit_cmd_mode_command", None) ) )
249  lines.extend( [ " if ( m_dc >= 0 )",
250  " {",
251  " setDataMode(1);",
252  " }",
253  " else",
254  " {",
255  " this->stop();",
256  " this->start();",
257  " this->send(0x40);",
258  " }" ] )
259  return "\n".join(lines)
260 
261 def generate_controller_data(jsonfile, ctl):
262  controller = ctl.lower();
263  g_voc["CONTROLLER"] = controller.upper()
264  g_voc["controller"] = controller.lower()
265 
266  if jsonfile is not None:
267  load_data_from_json( jsonfile, ctl )
268  func_list = ["Interface~CONTROLLER~", "startBlock", "nextBlock", "endBlock", "setDataMode", "commandStart"]
269  for f in get_val_by_path( "functions/interface_list",[] ):
270  if f not in func_list:
271  func_list.append( f )
272  g_voc["FUNCS_DECL"] = ""
273  for f in func_list:
274  g_voc["FUNCS_DECL"] += generate_custom_decl(f) + '\n\n'
275  g_voc["FUNCS_DEF"] = ""
276  for f in func_list:
277  custom_def = generate_custom_def(f)
278  if custom_def is not None:
279  g_voc["FUNCS_DEF"] += custom_def + '\n'
280  g_voc["FIELDS_DECL"] = '\n'.join(get_val_by_path("fields/Interface~CONTROLLER~",[]))
281 
282  location = "../src/v2/lcd/" + controller
283  shutil.rmtree(location,True)
284  os.mkdir(location)
285  header = open(location + "/lcd_" + controller + ".h", "w" )
286  inl = open(location + "/lcd_" + controller + ".inl", "w" )
287  cpp = open(location + "/lcd_" + controller + ".cpp", "w" )
288 
289  header.write( get_file_data('copyright.txt') )
290  inl.write( get_file_data('copyright.txt') )
291  cpp.write( get_file_data('copyright.txt') )
292 
293  header.write( get_file_data('header.h') )
294  inl.write( get_file_data('header.inl') )
295  cpp.write( get_file_data('header.cpp') )
296 
297  header.write( get_file_data('interface_spi.h') )
298 
299  for _bits in g_voc["bits"].keys():
300  g_voc["_bits"] = _bits;
301  if not get_val_by_path("options/no_bits_in_name", False):
302  g_voc["exbits"] = "x" + g_voc["_bits"]
303  else:
304  g_voc["exbits"] = ""
305  header.write( get_file_data( 'display.h' ) )
306  inl.write( get_file_data( 'display.inl' ) )
307  for res in g_voc["bits"][_bits].keys():
308  g_voc["resolution"] = res + g_voc["exbits"]
309  g_voc["init_data"] = get_file_data('init_data.inl')
310  if jsonfile is not None:
311  load_init_data_from_json(jsonfile, ctl, _bits, res)
312  g_voc["width"] = res.split('x')[0]
313  g_voc["height"] = res.split('x')[1]
314  header.write( get_file_data('resolution.h') )
315  inl.write( get_file_data('resolution.inl') )
316  for intf in g_voc["interfaces"].keys():
317  g_voc["serial_interface_args"] = get_val_by_path("bits/" + _bits + "/" + res + "/serial_interface_args",\
318  "*this, -1" if intf == "i2c" else "*this, config.dc")
319  g_voc["custom_serial_interface_args"] = get_val_by_path("bits/" + _bits + "/" + res + "/custom_serial_interface_args",\
320  "*this, -1" if intf == "i2c" else "*this, dcPin")
321  g_voc["custom_interface_args"] = get_val_by_path("bits/" + _bits + "/" + res + "/custom_interface_args",\
322  "*this, dcPin, frequency = frequency ? frequency : ~FREQUENCY~")
323  g_voc["_frequency"] = str(get_val_by_path( "interfaces/" + intf + "/frequency", 4400000 ))
324  header.write( get_file_data('display_' + intf + '.h') )
325  cpp.write( get_file_data('display_' + intf + '.cpp') )
326 
327  header.write( get_file_data('footer.h') )
328 
329  header.close()
330  inl.close()
331  cpp.close()
332 
333 display_list = [f for f in os.listdir(templates + "lcd") if len(f) > 3]
334 for display in display_list:
335  _ctl_list = get_json_controller_list(display)
336  for _ctl in _ctl_list:
337  generate_controller_data( _ctl + "/" + _ctl + ".json", _ctl)
338