├── README.md └── gc9a01.py /README.md: -------------------------------------------------------------------------------- 1 | # GC9A01 driver for LVGL micropython bindings on the ESP32 2 | A driver for displays using the GC901 driver for use with LVGL micropython 3 | 4 | This driver is heavily based on the existing LVGL ILI9341 driver and developped for personal use, although it may be of use to others 5 | 6 | An example of a display using this is waveshares round 240x240 IPS LCD: https://www.waveshare.com/wiki/1.28inch_LCD_Module 7 | -------------------------------------------------------------------------------- /gc9a01.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Micropython display driver for GC9A01 designed to work with LVGL 3 | # 4 | # This driver is heavily derived from the ili9XXX driver present in the LVGL 5 | # micropython bindings and as such may not be fully optimised for the GC9A01 6 | # display driver 7 | # 8 | # For GC9A01 display: 9 | # 10 | # Build micropython with 11 | # LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" 12 | # (make parameter) to configure LVGL use the same color format as GC9A01 13 | # and prevent the need to loop over all pixels to translate them. 14 | # 15 | # 16 | # 17 | # Default SPI freq is set to 60MHz. The display is listed to support up to 18 | # 100MHz although mine would not accept more than 60MHz. You can try 19 | # adjusting yours by simply setting "mhz=80", the max esp32 rate. 20 | # 21 | # When hybrid=False driver is pure micropython. 22 | # Pure Micropython could be viable when ESP32 supports Viper code emitter. 23 | # 24 | # **NOTE** Currently "hybrid=True" has not been tested and is not implimented 25 | # Critical function for high FPS are flush and ISR. 26 | # when "hybrid=True", use C implementation for these functions instead of 27 | # pure python implementation. 28 | # 29 | ############################################################################## 30 | 31 | import espidf as esp 32 | import lvgl as lv 33 | import micropython 34 | import ustruct 35 | import gc 36 | 37 | micropython.alloc_emergency_exception_buf(256) 38 | # gc.threshold(0x10000) # leave enough room for SPI master TX DMA buffers 39 | 40 | # Constants 41 | 42 | COLOR_MODE_RGB = const(0x08) 43 | COLOR_MODE_BGR = const(0x00) 44 | 45 | MADCTL_MH = const(0x04) 46 | MADCTL_ML = const(0x10) 47 | MADCTL_MV = const(0x20) 48 | MADCTL_MX = const(0x40) 49 | MADCTL_MY = const(0x80) 50 | 51 | ROTATE = { 52 | 0: 0x18, 53 | 90: 0x28, 54 | 180: 0x48, 55 | 270: 0x88 56 | } 57 | 58 | PORTRAIT = MADCTL_MX 59 | LANDSCAPE = MADCTL_MV 60 | 61 | class GC9A01: 62 | 63 | TRANS_BUFFER_LEN = const(16) 64 | 65 | display_name = 'gc9a01' 66 | 67 | SET_COLUMN = bytearray([0x2A]) # Column address set 68 | SET_PAGE = bytearray([0x2B]) # Page address set 69 | WRITE_RAM = bytearray([0x2C]) # Memory write 70 | 71 | 72 | 73 | # Default values of "power" and "backlight" are reversed logic! 0 means ON. 74 | # You can change this by setting backlight_on and power_on arguments. 75 | 76 | def __init__(self, 77 | miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, 78 | spihost=esp.HSPI_HOST, mhz=60, factor=4, hybrid=False, width=240, height=240, 79 | colormode=COLOR_MODE_RGB, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, 80 | asynchronous=False, initialize=True, rotation=0 81 | ): 82 | 83 | # Initializations 84 | 85 | self.asynchronous = asynchronous 86 | self.initialize = initialize 87 | 88 | self.width = width 89 | self.height = height 90 | 91 | self.miso = miso 92 | self.mosi = mosi 93 | self.clk = clk 94 | self.cs = cs 95 | self.dc = dc 96 | self.rst = rst 97 | self.power = power 98 | self.backlight = backlight 99 | self.backlight_on = backlight_on 100 | self.power_on = power_on 101 | self.spihost = spihost 102 | self.mhz = mhz 103 | self.factor = factor 104 | self.hybrid = hybrid 105 | self.half_duplex = half_duplex 106 | 107 | self.buf_size = (self.width * self.height * lv.color_t.SIZE) // factor 108 | 109 | if invert: 110 | self.init_cmds.append({'cmd': 0x21}) 111 | 112 | # Register display driver 113 | 114 | self.buf1 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) 115 | self.buf2 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) if double_buffer else None 116 | 117 | if self.buf1 and self.buf2: 118 | print("Double buffer") 119 | elif self.buf1: 120 | print("Single buffer") 121 | else: 122 | raise RuntimeError("Not enough DMA-able memory to allocate display buffer") 123 | 124 | self.disp_buf = lv.disp_buf_t() 125 | self.disp_drv = lv.disp_drv_t() 126 | 127 | self.disp_buf.init(self.buf1, self.buf2, self.buf_size // lv.color_t.SIZE) 128 | self.disp_drv.init() 129 | self.disp_spi_init() 130 | 131 | self.disp_drv.user_data = {'dc': self.dc, 'spi': self.spi, 'dt': 0} 132 | self.disp_drv.buffer = self.disp_buf 133 | self.disp_drv.flush_cb = esp.ili9xxx_flush if hybrid and hasattr(esp, 'ili9xxx_flush') else self.flush 134 | self.disp_drv.monitor_cb = self.monitor 135 | self.disp_drv.hor_res = self.width 136 | self.disp_drv.ver_res = self.height 137 | 138 | if rotation not in ROTATE.keys(): 139 | raise RuntimeError('Rotation must be 0, 90, 180 or 270.') 140 | else: 141 | self.rotation = ROTATE[rotation] 142 | 143 | 144 | self.init_cmds = [ 145 | {'cmd': 0xEF, 'data': bytes([0])}, 146 | {'cmd': 0xEB, 'data': bytes([0x14])}, 147 | {'cmd': 0xFE, 'data': bytes([0])}, 148 | {'cmd': 0xEF, 'data': bytes([0])}, 149 | {'cmd': 0xEB, 'data': bytes([0x14])}, 150 | {'cmd': 0x84, 'data': bytes([0x40])}, 151 | {'cmd': 0x85, 'data': bytes([0xFF])}, 152 | {'cmd': 0x86, 'data': bytes([0xFF])}, 153 | {'cmd': 0x87, 'data': bytes([0xFF])}, 154 | {'cmd': 0x88, 'data': bytes([0x0A])}, 155 | {'cmd': 0x89, 'data': bytes([0x21])}, 156 | {'cmd': 0x8A, 'data': bytes([0x00])}, 157 | {'cmd': 0x8B, 'data': bytes([0x80])}, 158 | {'cmd': 0x8C, 'data': bytes([0x01])}, 159 | {'cmd': 0x8D, 'data': bytes([0x01])}, 160 | {'cmd': 0x8E, 'data': bytes([0xFF])}, 161 | {'cmd': 0x8F, 'data': bytes([0xFF])}, 162 | {'cmd': 0xB6, 'data': bytes([0x00, 0x00])}, 163 | {'cmd': 0x36, 'data': bytes([0x48])}, 164 | {'cmd': self.rotation, 'data': bytes([0])}, 165 | {'cmd': 0x3A, 'data': bytes([0x05])}, 166 | {'cmd': 0x90, 'data': bytes([0x08, 0x08, 0x08, 0x08])}, 167 | {'cmd': 0xBD, 'data': bytes([0x06])}, 168 | {'cmd': 0xBC, 'data': bytes([0x00])}, 169 | {'cmd': 0xFF, 'data': bytes([0x60, 0x01, 0x04])}, 170 | {'cmd': 0xC3, 'data': bytes([0x13])}, 171 | {'cmd': 0xC4, 'data': bytes([0x13])}, 172 | {'cmd': 0xC9, 'data': bytes([0x22])}, 173 | {'cmd': 0xBE, 'data': bytes([0x11])}, 174 | {'cmd': 0xE1, 'data': bytes([0x10, 0x0E])}, 175 | {'cmd': 0xDF, 'data': bytes([0x21, 0x0c, 0x02])}, 176 | {'cmd': 0xF0, 'data': bytes([0x45, 0x09, 0x08, 0x08, 0x26, 0x2A])}, 177 | {'cmd': 0xF1, 'data': bytes([0x43, 0x70, 0x72, 0x36, 0x37, 0x6F])}, 178 | {'cmd': 0xF2, 'data': bytes([0x45, 0x09, 0x08, 0x08, 0x26, 0x2A])}, 179 | {'cmd': 0xF3, 'data': bytes([0x43, 0x70, 0x72, 0x36, 0x37, 0x6F])}, 180 | {'cmd': 0xED, 'data': bytes([0x1B, 0x0B])}, 181 | {'cmd': 0xAE, 'data': bytes([0x77])}, 182 | {'cmd': 0xCD, 'data': bytes([0x63])}, 183 | {'cmd': 0x70, 'data': bytes([0x07, 0x07, 0x04, 0x0E, 0x0F, 0x09, 0x07, 0x08, 0x03])}, 184 | {'cmd': 0xE8, 'data': bytes([0x34])}, 185 | {'cmd': 0x62, 'data': bytes([0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70])}, 186 | {'cmd': 0x63, 'data': bytes([0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70])}, 187 | {'cmd': 0x64, 'data': bytes([0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07])}, 188 | {'cmd': 0x66, 'data': bytes([0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00])}, 189 | {'cmd': 0x67, 'data': bytes([0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98])}, 190 | {'cmd': 0x74, 'data': bytes([0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00])}, 191 | {'cmd': 0x98, 'data': bytes([0x3e, 0x07])}, 192 | {'cmd': 0x35, 'data': bytes([0])}, 193 | {'cmd': 0x21, 'data': bytes([0])}, 194 | {'cmd': 0x11, 'data': bytes([0]), 'delay': 20}, 195 | {'cmd': 0x29, 'data': bytes([0]), 'delay': 120} 196 | ] 197 | 198 | if self.initialize: 199 | self.init() 200 | 201 | 202 | 203 | ###################################################### 204 | 205 | def disp_spi_init(self): 206 | 207 | # Register finalizer callback to deinit SPI. 208 | # This would get called on soft reset. 209 | 210 | if not self.asynchronous: 211 | import lvesp32 212 | self.finalizer = lvesp32.cb_finalizer(self.deinit) 213 | lvesp32.init() 214 | 215 | buscfg = esp.spi_bus_config_t({ 216 | "miso_io_num": self.miso, 217 | "mosi_io_num": self.mosi, 218 | "sclk_io_num": self.clk, 219 | "quadwp_io_num": -1, 220 | "quadhd_io_num": -1, 221 | "max_transfer_sz": self.buf_size, 222 | }) 223 | 224 | devcfg_flags = esp.SPI_DEVICE.NO_DUMMY 225 | if self.half_duplex: 226 | devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX 227 | 228 | devcfg = esp.spi_device_interface_config_t({ 229 | "clock_speed_hz": self.mhz*1000*1000, # Clock out at DISP_SPI_MHZ MHz 230 | "mode": 0, # SPI mode 0 231 | "spics_io_num": self.cs, # CS pin 232 | "queue_size": 2, 233 | "flags": devcfg_flags, 234 | "duty_cycle_pos": 128, 235 | }) 236 | 237 | # if self.hybrid and hasattr(esp, 'ili9xxx_post_cb_isr'): 238 | # devcfg.pre_cb = None 239 | # devcfg.post_cb = esp.ili9xxx_post_cb_isr 240 | # else: 241 | # devcfg.pre_cb = esp.ex_spi_pre_cb_isr 242 | # devcfg.post_cb = esp.ex_spi_post_cb_isr 243 | devcfg.pre_cb = esp.ex_spi_pre_cb_isr 244 | devcfg.post_cb = esp.ex_spi_post_cb_isr 245 | 246 | esp.gpio_pad_select_gpio(self.cs) 247 | 248 | # Initialize the SPI bus, if needed. 249 | 250 | if buscfg.miso_io_num >= 0 and \ 251 | buscfg.mosi_io_num >= 0 and \ 252 | buscfg.sclk_io_num >= 0: 253 | 254 | esp.gpio_pad_select_gpio(self.miso) 255 | esp.gpio_pad_select_gpio(self.mosi) 256 | esp.gpio_pad_select_gpio(self.clk) 257 | 258 | esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) 259 | esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) 260 | esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) 261 | esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) 262 | 263 | ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) 264 | if ret != 0: raise RuntimeError("Failed initializing SPI bus") 265 | 266 | self.trans_buffer = esp.heap_caps_malloc(TRANS_BUFFER_LEN, esp.MALLOC_CAP.DMA) 267 | self.cmd_trans_data = self.trans_buffer.__dereference__(1) 268 | self.word_trans_data = self.trans_buffer.__dereference__(4) 269 | 270 | # Attach the LCD to the SPI bus 271 | 272 | ptr_to_spi = esp.C_Pointer() 273 | ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) 274 | if ret != 0: raise RuntimeError("Failed adding SPI device") 275 | self.spi = ptr_to_spi.ptr_val 276 | 277 | self.bytes_transmitted = 0 278 | completed_spi_transaction = esp.spi_transaction_t() 279 | cast_spi_transaction_instance = esp.spi_transaction_t.cast_instance 280 | 281 | def post_isr(arg): 282 | reported_transmitted = self.bytes_transmitted 283 | if reported_transmitted > 0: 284 | print('- Completed DMA of %d bytes (mem_free=0x%X)' % (reported_transmitted , gc.mem_free())) 285 | self.bytes_transmitted -= reported_transmitted 286 | 287 | # Called in ISR context! 288 | def flush_isr(spi_transaction_ptr): 289 | self.disp_drv.flush_ready() 290 | # esp.spi_device_release_bus(self.spi) 291 | esp.get_ccount(self.end_time_ptr) 292 | 293 | # cast_spi_transaction_instance(completed_spi_transaction, spi_transaction_ptr) 294 | # self.bytes_transmitted += completed_spi_transaction.length 295 | # try: 296 | # micropython.schedule(post_isr, None) 297 | # except RuntimeError: 298 | # pass 299 | 300 | self.spi_callbacks = esp.spi_transaction_set_cb(None, flush_isr) 301 | 302 | # 303 | # Deinitialize SPI device and bus, and free memory 304 | # This function is called from finilizer during gc sweep - therefore must not allocate memory! 305 | # 306 | 307 | trans_result_ptr = esp.C_Pointer() 308 | 309 | def deinit(self): 310 | 311 | print('Deinitializing {}..'.format(self.display_name)) 312 | 313 | self.disp_drv.remove() 314 | 315 | # Prevent callbacks to lvgl, which refer to the buffers we are about to delete 316 | 317 | if not self.asynchronous: 318 | import lvesp32 319 | lvesp32.deinit() 320 | 321 | if self.spi: 322 | 323 | # Pop all pending transaction results 324 | 325 | ret = 0 326 | while ret == 0: 327 | ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr , 1) 328 | 329 | # Remove device 330 | 331 | esp.spi_bus_remove_device(self.spi) 332 | self.spi = None 333 | 334 | # Free SPI bus 335 | 336 | esp.spi_bus_free(self.spihost) 337 | self.spihost = None 338 | 339 | # Free RAM 340 | 341 | if self.buf1: 342 | esp.heap_caps_free(self.buf1) 343 | self.buf1 = None 344 | 345 | if self.buf2: 346 | esp.heap_caps_free(self.buf2) 347 | self.buf2 = None 348 | 349 | if self.trans_buffer: 350 | esp.heap_caps_free(self.trans_buffer) 351 | self.trans_buffer = None 352 | 353 | 354 | ###################################################### 355 | 356 | trans = esp.spi_transaction_t() # .cast( 357 | # esp.heap_caps_malloc( 358 | # esp.spi_transaction_t.SIZE, esp.MALLOC_CAP.DMA)) 359 | 360 | def spi_send(self, data): 361 | self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. 362 | self.trans.tx_buffer = data # data should be allocated as DMA-able memory 363 | self.trans.user = None 364 | esp.spi_device_polling_transmit(self.spi, self.trans) 365 | 366 | def spi_send_dma(self, data): 367 | self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. 368 | self.trans.tx_buffer = data # data should be allocated as DMA-able memory 369 | self.trans.user = self.spi_callbacks 370 | esp.spi_device_queue_trans(self.spi, self.trans, -1) 371 | 372 | ###################################################### 373 | ###################################################### 374 | 375 | def send_cmd(self, cmd): 376 | esp.gpio_set_level(self.dc, 0) # Command mode 377 | self.cmd_trans_data[0] = cmd 378 | self.spi_send(self.cmd_trans_data) 379 | 380 | def send_data(self, data): 381 | esp.gpio_set_level(self.dc, 1) # Data mode 382 | if len(data) > TRANS_BUFFER_LEN: raise RuntimeError('Data too long, please use DMA!') 383 | trans_data = self.trans_buffer.__dereference__(len(data)) 384 | trans_data[:] = data[:] 385 | self.spi_send(trans_data) 386 | 387 | def send_trans_word(self): 388 | esp.gpio_set_level(self.dc, 1) # Data mode 389 | self.spi_send(self.word_trans_data) 390 | 391 | def send_data_dma(self, data): # data should be allocated as DMA-able memory 392 | esp.gpio_set_level(self.dc, 1) # Data mode 393 | self.spi_send_dma(data) 394 | 395 | ###################################################### 396 | 397 | async def _init(self, sleep_func): 398 | 399 | # Initialize non-SPI GPIOs 400 | 401 | esp.gpio_pad_select_gpio(self.dc) 402 | if self.rst != -1: esp.gpio_pad_select_gpio(self.rst) 403 | if self.backlight != -1: esp.gpio_pad_select_gpio(self.backlight) 404 | if self.power != -1: esp.gpio_pad_select_gpio(self.power) 405 | 406 | esp.gpio_set_direction(self.dc, esp.GPIO_MODE.OUTPUT) 407 | if self.rst != -1: esp.gpio_set_direction(self.rst, esp.GPIO_MODE.OUTPUT) 408 | if self.backlight != -1: esp.gpio_set_direction(self.backlight, esp.GPIO_MODE.OUTPUT) 409 | if self.power != -1: esp.gpio_set_direction(self.power, esp.GPIO_MODE.OUTPUT) 410 | 411 | # Power the display 412 | 413 | if self.power != -1: 414 | esp.gpio_set_level(self.power, self.power_on) 415 | await sleep_func(100) 416 | 417 | # Reset the display 418 | 419 | if self.rst != -1: 420 | esp.gpio_set_level(self.rst, 0) 421 | await sleep_func(100) 422 | esp.gpio_set_level(self.rst, 1) 423 | await sleep_func(100) 424 | 425 | # Send all the commands 426 | 427 | for cmd in self.init_cmds: 428 | self.send_cmd(cmd['cmd']) 429 | if 'data' in cmd: 430 | self.send_data(cmd['data']) 431 | if 'delay' in cmd: 432 | await sleep_func(cmd['delay']) 433 | 434 | print("{} initialization completed".format(self.display_name)) 435 | 436 | # Enable backlight 437 | 438 | if self.backlight != -1: 439 | print("Enable backlight") 440 | esp.gpio_set_level(self.backlight, self.backlight_on) 441 | 442 | # Register the driver 443 | self.disp_drv.register() 444 | 445 | 446 | def init(self): 447 | import utime 448 | generator = self._init(lambda ms:(yield ms)) 449 | try: 450 | while True: 451 | ms = next(generator) 452 | utime.sleep_ms(ms) 453 | except StopIteration: 454 | pass 455 | 456 | async def init_async(self): 457 | import uasyncio 458 | await self._init(uasyncio.sleep_ms) 459 | 460 | def power_down(self): 461 | 462 | if self.power != -1: 463 | esp.gpio_set_level(self.power, 1 - self.power_on) 464 | 465 | if self.backlight != -1: 466 | esp.gpio_set_level(self.backlight, 1 - self.backlight_on) 467 | 468 | 469 | ###################################################### 470 | 471 | start_time_ptr = esp.C_Pointer() 472 | end_time_ptr = esp.C_Pointer() 473 | flush_acc_setup_cycles = 0 474 | flush_acc_dma_cycles = 0 475 | 476 | def flush(self, disp_drv, area, color_p): 477 | 478 | if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: 479 | self.flush_acc_dma_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val 480 | 481 | esp.get_ccount(self.start_time_ptr) 482 | 483 | # esp.spi_device_acquire_bus(self.spi, esp.ESP.MAX_DELAY) 484 | 485 | # Column addresses 486 | 487 | self.send_cmd(0x2A); 488 | 489 | self.word_trans_data[0] = (area.x1 >> 8) & 0xFF 490 | self.word_trans_data[1] = area.x1 & 0xFF 491 | self.word_trans_data[2] = (area.x2 >> 8) & 0xFF 492 | self.word_trans_data[3] = area.x2 & 0xFF 493 | self.send_trans_word() 494 | 495 | # Page addresses 496 | 497 | self.send_cmd(0x2B); 498 | 499 | self.word_trans_data[0] = (area.y1 >> 8) & 0xFF 500 | self.word_trans_data[1] = area.y1 & 0xFF 501 | self.word_trans_data[2] = (area.y2 >> 8) & 0xFF 502 | self.word_trans_data[3] = area.y2 & 0xFF 503 | self.send_trans_word() 504 | 505 | # Memory write by DMA, disp_flush_ready when finished 506 | 507 | self.send_cmd(0x2C) 508 | 509 | size = (area.x2 - area.x1 + 1) * (area.y2 - area.y1 + 1) 510 | data_view = color_p.__dereference__(size * lv.color_t.SIZE) 511 | 512 | esp.get_ccount(self.end_time_ptr) 513 | if self.end_time_ptr.int_val > self.start_time_ptr.int_val: 514 | self.flush_acc_setup_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val 515 | esp.get_ccount(self.start_time_ptr) 516 | 517 | self.send_data_dma(data_view) 518 | 519 | ###################################################### 520 | 521 | monitor_acc_time = 0 522 | monitor_acc_px = 0 523 | monitor_count = 0 524 | 525 | cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 526 | 527 | def monitor(self, disp_drv, time, px): 528 | self.monitor_acc_time += time 529 | self.monitor_acc_px += px 530 | self.monitor_count += 1 531 | 532 | def stat(self): 533 | if self.monitor_count == 0: 534 | return None 535 | 536 | time = self.monitor_acc_time // self.monitor_count 537 | setup = self.flush_acc_setup_cycles // (self.monitor_count * self.cycles_in_ms) 538 | dma = self.flush_acc_dma_cycles // (self.monitor_count * self.cycles_in_ms) 539 | px = self.monitor_acc_px // self.monitor_count 540 | 541 | self.monitor_acc_time = 0 542 | self.monitor_acc_px = 0 543 | self.monitor_count = 0 544 | self.flush_acc_setup_cycles = 0 545 | self.flush_acc_dma_cycles = 0 546 | 547 | return time, setup, dma, px 548 | --------------------------------------------------------------------------------