# # The MIT License (MIT) # # Copyright (c) 2016 Robert Hammelrath # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # 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 OR COPYRIGHT HOLDERS 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. # # Class supporting TFT LC-displays with a parallel Interface # First example: Controller SSD1963 # It uses X1..X8 for data and Y3, Y9, Y10, Y11 and Y12 for control signals. # The minimal connection just for writes is X1..X8 for data, Y9 for /Reset. Y11 for /WR and Y12 for /RS # Then LED and /CS must be hard tied to Vcc and GND, and /RD is not used. # # Some parts of the software are a port of code provided by Rinky-Dink Electronics, Henning Karlsen, # with the following copyright notice: ## Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved ## This library is free software; you can redistribute it and/or ## modify it under the terms of the CC BY-NC-SA 3.0 license. ## Please see the included documents for further information. # import pyb, stm from uctypes import addressof # define constants # RESET = const(1 << 10) ## Y9 RD = const(1 << 11) ## Y10 WR = const(0x01) ## Y11 D_C = const(0x02) ## Y12 LED = const(1 << 8) ## Y3 POWER = const(1 << 9) ## Y4 ## CS is not used and must be hard tied to GND PORTRAIT = const(1) LANDSCAPE = const(0) class TFT_io: # # display bitmap # @staticmethod @micropython.viper def displaySCR_bitmap(bits: ptr8, size: int, control: ptr8, bg_buf: ptr8): gpioa = ptr8(stm.GPIOA) gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL) gpioam = ptr16(stm.GPIOA + stm.GPIO_MODER) # transparency = control[6] bm_ptr = 0 bg_ptr = 0 mask = 0x80 # rd_command = 0x2e ## start read while size: # if False: # transparency: # read back data # gpioa[stm.GPIO_ODR] = rd_command # start/continue read command # gpiob[1] = D_C | WR # set C/D and WR low # gpiob[0] = D_C | WR # set C/D and WR high # gpioam[0] = 0 # configure X1..X8 as Input # gpiob[1] = RD # set RD low. C/D still high # rd_command = 0x3e # continue read # bg_red = gpioa[stm.GPIO_IDR] # get data from port A # gpiob[0] = RD # set RD high again # gpiob[1] = RD # set RD low. C/D still high # delay = 1 # bg_green = gpioa[stm.GPIO_IDR] # get data from port A # gpiob[0] = RD # set RD high again # gpiob[1] = RD # set RD low. C/D still high # delay = 1 # bg_blue = gpioa[stm.GPIO_IDR] # get data from port A # gpiob[0] = RD # set RD high again # gpioam[0] = 0x5555 # configure X1..X8 as Output # gpioa[stm.GPIO_ODR] = 0x3c # continue write command # gpiob[1] = D_C | WR # set C/D and WR low # gpiob[0] = D_C | WR # set C/D and WR high if bits[bm_ptr] & mask: if transparency & 8: # Invert bg color as foreground gpioa[stm.GPIO_ODR] = 255 - bg_buf[bg_ptr] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = 255 - bg_buf[bg_ptr + 1] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = 255 - bg_buf[bg_ptr + 2] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again else: # not invert gpioa[stm.GPIO_ODR] = control[3] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = control[4] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = control[5] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again else: if transparency & 1: # Dim background gpioa[stm.GPIO_ODR] = bg_buf[bg_ptr] >> 1 # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = bg_buf[bg_ptr + 1] >> 1 # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = bg_buf[bg_ptr + 2] >> 1 # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again elif transparency & 2: # keep Background gpioa[stm.GPIO_ODR] = bg_buf[bg_ptr] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = bg_buf[bg_ptr + 1] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = bg_buf[bg_ptr + 2] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again elif transparency & 4: # invert Background gpioa[stm.GPIO_ODR] = 255 - bg_buf[bg_ptr] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = 255 - bg_buf[bg_ptr + 1] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = 255 - bg_buf[bg_ptr + 2] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again else: # not transparent gpioa[stm.GPIO_ODR] = control[0] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = control[1] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[stm.GPIO_ODR] = control[2] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again mask >>= 1 if mask == 0: # map ptr advance on byte exhaust mask = 0x80 bm_ptr += 1 size -= 1 bg_ptr += 3 # # Set the address range for various draw commands and set the TFT for expecting data # @staticmethod @micropython.viper def setXY_P(x1: int, y1: int, x2: int, y2: int): ## set the adress range, Portrait # set column address gpioa = ptr8(stm.GPIOA + stm.GPIO_ODR) gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL) gpioa[0] = 0x2b # command gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high gpioa[0] = x1 >> 8 # high byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = x1 & 0xff# low byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = x2 >> 8 # high byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = x2 & 0xff# low byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again # set row address gpioa[0] = 0x2a # command gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high gpioa[0] = y1 >> 8 # high byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = y1 & 0xff# low byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = y2 >> 8 # high byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = y2 & 0xff# low byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = 0x2c # Start data entry gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high @staticmethod @micropython.viper def setXY_L(x1: int, y1: int, x2: int, y2: int): ## set the adress range, Landscape # set column address gpioa = ptr8(stm.GPIOA + stm.GPIO_ODR) gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL) gpioa[0] = 0x2a # command gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high gpioa[0] = x1 >> 8 # high byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = x1 & 0xff# low byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = x2 >> 8 # high byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = x2 & 0xff# low byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again # set row address gpioa[0] = 0x2b # command gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high gpioa[0] = y1 >> 8 # high byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = y1 & 0xff# low byte of x1 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = y2 >> 8 # high byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = y2 & 0xff# low byte of x2 gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again gpioa[0] = 0x2c # Start data entry gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high # # Assembler version of # Fill screen by writing size pixels with the color given in data # data must be 3 bytes of red, green, blue # The area to be filled has to be set in advance by setXY # The speed is about 214 ns/pixel # @staticmethod @micropython.asm_thumb def fillSCR_AS(r0, r1): # r0: ptr to data, r1: number of pixels (3 bytes/pixel) # set up pointers to GPIO # r5: bit mask for control lines # r6: GPIOA OODR register ptr # r7: GPIOB BSSRL register ptr mov(r5, WR) movwt(r6, stm.GPIOA) # target add (r6, stm.GPIO_ODR) movwt(r7, stm.GPIOB) add (r7, stm.GPIO_BSRRL) ldrb(r2, [r0, 0]) # red ldrb(r3, [r0, 1]) # green ldrb(r4, [r0, 2]) # blue b(loopend) label(loopstart) strb(r2, [r6, 0]) # Store red strb(r5, [r7, 2]) # WR low # nop() strb(r5, [r7, 0]) # WR high strb(r3, [r6, 0]) # store blue strb(r5, [r7, 2]) # WR low nop() strb(r5, [r7, 0]) # WR high strb(r4, [r6, 0]) # store blue strb(r5, [r7, 2]) # WR low # nop() strb(r5, [r7, 0]) # WR high label(loopend) sub (r1, 1) # End of loop? bpl(loopstart) # # Assembler version of: # Fill screen by writing size pixels with the data # data must contains size triplets of red, green and blue data values # The area to be filled has to be set in advance by setXY # the speed is 266 ns for a byte triple # @staticmethod @micropython.asm_thumb def displaySCR_AS(r0, r1): # r0: ptr to data, r1: is number of pixels (3 bytes/pixel) # set up pointers to GPIO # r5: bit mask for control lines # r6: GPIOA OODR register ptr # r7: GPIOB BSSRL register ptr mov(r5, WR) movwt(r6, stm.GPIOA) # target add (r6, stm.GPIO_ODR) movwt(r7, stm.GPIOB) add (r7, stm.GPIO_BSRRL) b(loopend) label(loopstart) ldrb(r2, [r0, 0]) # red strb(r2, [r6, 0]) # Store red strb(r5, [r7, 2]) # WR low strb(r5, [r7, 0]) # WR high ldrb(r2, [r0, 1]) # pre green strb(r2, [r6, 0]) # store greem strb(r5, [r7, 2]) # WR low strb(r5, [r7, 0]) # WR high ldrb(r2, [r0, 2]) # blue strb(r2, [r6, 0]) # store blue strb(r5, [r7, 2]) # WR low strb(r5, [r7, 0]) # WR high add (r0, 3) # advance data ptr label(loopend) sub (r1, 1) # End of loop? bpl(loopstart) # Assembler version of: # Fill screen by writing size pixels with the data # data must contains size packed duplets of red, green and blue data values # The area to be filled has to be set in advance by setXY # the speed is 266 ns for a byte pixel # @staticmethod @micropython.asm_thumb def displaySCR565_AS(r0, r1): # r0: ptr to data, r1: is number of pixels (3 bytes/pixel) # set up pointers to GPIO # r5: bit mask for control lines # r6: GPIOA OODR register ptr # r7: GPIOB BSSRL register ptr mov(r5, WR) movwt(r6, stm.GPIOA) # target add (r6, stm.GPIO_ODR) movwt(r7, stm.GPIOB) add (r7, stm.GPIO_BSRRL) b(loopend) label(loopstart) ldrb(r2, [r0, 0]) # red mov (r3, 0xf8) # mask out lower 3 bits and_(r2, r3) strb(r2, [r6, 0]) # Store red strb(r5, [r7, 2]) # WR low strb(r5, [r7, 0]) # WR high ldrb(r2, [r0, 0]) # pre green mov (r3, 5) # shift 5 bits up to lsl(r2, r3) ldrb(r4, [r0, 1]) # get the next 3 bits mov (r3, 3) # shift 3 to the right lsr(r4, r3) orr(r2, r4) # add them to the first bits mov(r3, 0xfc) # mask off the lower two bits and_(r2, r3) strb(r2, [r6, 0]) # store green strb(r5, [r7, 2]) # WR low strb(r5, [r7, 0]) # WR high ldrb(r2, [r0, 1]) # blue mov (r3, 3) lsl(r2, r3) strb(r2, [r6, 0]) # store blue strb(r5, [r7, 2]) # WR low strb(r5, [r7, 0]) # WR high add (r0, 2) # advance data ptr label(loopend) sub (r1, 1) # End of loop? bpl(loopstart) # # Send a command and data to the TFT controller # cmd is the command byte, data must be a bytearray object with the command payload, # int is the size of the data # For the startup-phase use this function. # @staticmethod @micropython.viper def tft_cmd_data(cmd: int, data: ptr8, size: int): gpioa = ptr8(stm.GPIOA + stm.GPIO_ODR) gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL) gpioa[0] = cmd # set data on port A gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high for i in range(size): gpioa[0] = data[i] # set data on port A gpiob[1] = WR # set WR low. C/D still high gpiob[0] = WR # set WR high again # # Assembler version of send command & data to the TFT controller # data must be a bytearray object, int is the size of the data. # The speed is about 120 ns/byte # @staticmethod @micropython.asm_thumb def tft_cmd_data_AS(r0, r1, r2): # r0: command, r1: ptr to data, r2 is size in bytes # set up pointers to GPIO # r5: bit mask for control lines # r6: GPIOA OODR register ptr # r7: GPIOB BSSRL register ptr movwt(r6, stm.GPIOA) # target add (r6, stm.GPIO_ODR) movwt(r7, stm.GPIOB) add (r7, stm.GPIO_BSRRL) # Emit command byte mov(r5, WR | D_C) strb(r0, [r6, 0]) # set command byte strh(r5, [r7, 2]) # WR and D_C low strh(r5, [r7, 0]) # WR and D_C high # now loop though data mov(r5, WR) b(loopend) label(loopstart) ldrb(r4, [r1, 0]) # load data strb(r4, [r6, 0]) # Store data strh(r5, [r7, 2]) # WR low strh(r5, [r7, 0]) # WR high add (r1, 1) # advance data ptr label(loopend) sub (r2, 1) # End of loop? bpl(loopstart) # # Send a command to the TFT controller # @staticmethod @micropython.viper def tft_cmd(cmd: int): gpioa = ptr8(stm.GPIOA + stm.GPIO_ODR) gpiob = ptr16(stm.GPIOB + stm.GPIO_BSRRL) gpioa[0] = cmd # set data on port A gpiob[1] = D_C | WR # set C/D and WR low gpiob[0] = D_C | WR # set C/D and WR high # # Assembler version of send data to the TFT controller # data must be a bytearray object, int is the size of the data. # The speed is about 120 ns/byte # @staticmethod @micropython.asm_thumb def tft_write_data_AS(r0, r1): # r0: ptr to data, r1: is size in Bytes # set up pointers to GPIO # r5: bit mask for control lines # r6: GPIOA OODR register ptr # r7: GPIOB BSSRL register ptr movwt(r6, stm.GPIOA) # target add (r6, stm.GPIO_ODR) movwt(r7, stm.GPIOB) add (r7, stm.GPIO_BSRRL) mov(r5, WR) # and go, first test size for 0 b(loopend) label(loopstart) ldrb(r3, [r0, 0]) # load data strb(r3, [r6, 0]) # Store data strh(r5, [r7, 2]) # WR low strh(r5, [r7, 0]) # WR high add (r0, 1) # advance data ptr label(loopend) sub (r1, 1) # End of loop? bpl(loopstart) # # Assembler version of send a command byte and read data from to the TFT controller # data must be a bytearray object, int is the size of the data. # The speed is about 130 ns/byte # @staticmethod @micropython.asm_thumb def tft_read_cmd_data_AS(r0, r1, r2): # r0: command, r1: ptr to data buffer, r2 is expected size in bytes # set up pointers to GPIO # r5: bit mask for control lines # r6: GPIOA base register ptr # r7: GPIOB BSSRL register ptr movwt(r6, stm.GPIOA) # target movwt(r7, stm.GPIOB) add (r7, stm.GPIO_BSRRL) # Emit command byte movw(r5, WR | D_C) strb(r0, [r6, stm.GPIO_ODR]) # set command byte strh(r5, [r7, 2]) # WR and D_C low strh(r5, [r7, 0]) # WR and D_C high # now switch gpioaa to input movw(r0, 0) strh(r0, [r6, stm.GPIO_MODER]) # now loop though data movw(r5, RD) b(loopend) label(loopstart) strh(r5, [r7, 2]) # RD low nop() # short delay nop() ldrb(r4, [r6, stm.GPIO_IDR]) # load data strh(r5, [r7, 0]) # RD high strb(r4, [r1, 0]) # Store data add (r1, 1) # advance data ptr label(loopend) sub (r2, 1) # End of loop? bpl(loopstart) # now switch gpioaa back to input movw(r0, 0x5555) strh(r0, [r6, stm.GPIO_MODER]) # # swap byte pairs in a buffer # sometimes needed for picture data # @staticmethod @micropython.asm_thumb def swapbytes(r0, r1): # bytearray, len(bytearray) mov(r2, 1) # divide loop count by 2 lsr(r1, r2) # to avoid odd valued counter b(loopend) label(loopstart) ldrb(r2, [r0, 0]) ldrb(r3, [r0, 1]) strb(r3, [r0, 0]) strb(r2, [r0, 1]) add(r0, 2) label(loopend) sub (r1, 1) # End of loop? bpl(loopstart) # # swap colors red/blue in the buffer # @staticmethod @micropython.asm_thumb def swapcolors(r0, r1): # bytearray, len(bytearray) mov(r2, 3) udiv(r1, r1, r2) # 3 bytes per triple b(loopend) label(loopstart) ldrb(r2, [r0, 0]) ldrb(r3, [r0, 2]) strb(r3, [r0, 0]) strb(r2, [r0, 2]) add(r0, 3) label(loopend) sub (r1, 1) # End of loop? bpl(loopstart)