MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Discussion and questions about boards that can run MicroPython but don't have a dedicated forum.
Target audience: Everyone interested in running MicroPython on other hardware.
holberg
Posts: 6
Joined: Mon Apr 20, 2020 9:01 am

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by holberg » Sun Apr 26, 2020 11:14 am

OutoftheBOTS_ wrote:
Fri Apr 24, 2020 8:26 am
holberg wrote:
Thu Apr 23, 2020 9:51 am
OutoftheBOTS_ wrote:
Tue Apr 21, 2020 11:43 pm
I wrote the code quite a while ago so without sitting down and reading the reference manual and looking at the code to remember what I did I couldn't be certain but that looks about right :)
I got it running. ILIDriver now in micropython.

But when I want to implement a ILI9341 grafic class, I' run out of RAM. If I check the availabel Memory

MicroPython v1.12-387-g1b1ceb67b on 2020-04-19; MCUDEV STM32F407VE with STM32F407VE
Type "help()" for more information.
>>> import gc
>>> gc.collect()
>>> gc.mem_free()
33280

its not this much, any Idea ?
Low RAM is the big factor in using this MCU for graphic. The work around is that you have very fast access to the TFT RAM so you need to program in a way that makes use of the TFT RAM and only having super minimal stuff in MCU RAM
Thank you for your reply, I did not use MCU Ram for the TFT. Just some small bytearray for a 5x8 font,

My code (as of my experimental design), working but not yet as class, and yes I'm a "C" Programmer :-)

Code: Select all

import stm
import time
import machine
from math import trunc
import glcdfont


def FSMC_Init():
    #constants of register
    RCC_AHB3ENR_FSMCEN = 0x1 #bit to enable FSMC clock
    RCC_AHB1ENR_GPIODEN = 0x00000008 #bit to enable D port clock
    RCC_AHB1ENR_GPIOEEN = 0x00000010 #bit to enable E port clock
    FSMC_Bank1_base = 0xA0000000 # 0xA0000000 FSMC registers base address
    FSMC_BCR_bank1_offset = 0x0
    FSMC_BTR_bank1_offset = 0x4
	
    FSMC_BTR1_ADDSET_0 = 0x00000001
    FSMC_BTR1_ADDSET_1 = 0x00000002
    FSMC_BTR1_ADDSET_2 = 0x00000004
    FSMC_BTR1_ADDSET_3 = 0x00000008
    FSMC_BTR1_ADDSET_T = 0x0000000F			  # ILI9341, this is equivalent to RD high duration, 90ns, we set ADDSET to 15,is 15*6=90ns
    FSMC_BTR1_DATAST   = 0x0000FF00           #<DATAST [3:0] bits (Data-phase duration) */
    FSMC_BTR1_DATAST_0 = 0x00000100
    FSMC_BTR1_DATAST_1 = 0x00000200
    FSMC_BTR1_DATAST_2 = 0x00000400
    FSMC_BTR1_DATAST_3 = 0x00000800
    FSMC_BTR1_DATAST_T = 0x00003C00          # DATAST to 60, which is 60 HCLK cycles, and the time is approximately 360ns.
    FSMC_BTR1_BUSTURN   = 0x000F0000         #<BUSTURN[3:0] bits (Bus turnaround phase duration) */
    FSMC_BTR1_BUSTURN_0 = 0x00010000
    FSMC_BTR1_BUSTURN_1 = 0x00020000
    FSMC_BTR1_BUSTURN_2 = 0x00040000
    FSMC_BTR1_BUSTURN_3 = 0x00080000
    FSMC_BTR1_ACCMOD    = 0x03000000
    #FSMC_BTR1_ADDSET_1 = 0x2 
    #FSMC_BTR1_DATAST_1 = 0x00000200
    FSMC_BCR1_MWID_0 = 0x00000010
    FSMC_BCR1_WREN = 0x00001000			
    FSMC_BCR1_MBKEN = 0x1
    
    machine.mem32[stm.RCC + stm.RCC_AHB3ENR] |= RCC_AHB3ENR_FSMCEN
    machine.mem32[stm.RCC + stm.RCC_AHB1ENR] |= RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOEEN
    
    # MODER allows a programmer to define the functionality of a GPIO pin
    # Each pin has2 bitsthat permits the following configurations:
    #  00: Input
    #  01: Output
    #  10: Alternate Function
    #  11: Analog
    # MODER15(1:0)... MODER0(1:0)
	
    machine.mem32[stm.GPIOD + stm.GPIO_MODER] = 0b10101000001010101000101000101010 #10 10 10 00 00 10 10 10 10 00 10 10 00 10 10 10 
    machine.mem32[stm.GPIOE + stm.GPIO_MODER] = 0xAAAA8000

    # OTYPER allows a programmer to configure the output stage of anoutput GPIO pin
    # Each pin has1 bits that permits the following configurations:
    #  0: Push-pull
    #  1: Open Drain
	
    machine.mem32[stm.GPIOD + stm.GPIO_OSPEEDR] = 0xFFFFFFFF  #0x54154525
    machine.mem32[stm.GPIOE + stm.GPIO_OSPEEDR] = 0xFFFFFFFF  #0x55554000

    machine.mem32[stm.GPIOD + stm.GPIO_AFR0] =  ( (0b1100<<(4*0)) | (0b1100<<(4*1)) | (0b1100<<(4*2)) | (0b1100<<(4*4)) | (0b1100<<(4*5)) | (0b1100<<(4*7)) )
    machine.mem32[stm.GPIOD + stm.GPIO_AFR1] =  ( (0b1100<<(4*(8-8))) | (0b1100<<(4*(9-8))) | (0b1100<<(4*(10-8))) | (0b1100<<(4*(13-8))) | (0b1100<<(4*(14-8))) | (0b1100<<(4*(15-8))))
  
    machine.mem32[stm.GPIOE + stm.GPIO_AFR0] =  0b1100<<(4*7)
    machine.mem32[stm.GPIOE + stm.GPIO_AFR1] = 0xCCCCCCCC
  
    FSMC_BTR1_ADDHLD_0  = 0x00000010        #<Bit 0 */
    FSMC_BTR1_CLKDIV_0 = 0x00100000        # <Bit 0 */
    FSMC_BTR1_CLKDIV_1 = 0x00200000        #<Bit 1 */
    FSMC_BCR1_FACCEN = 0x00000040        #<Flash access enable

    machine.mem32[FSMC_Bank1_base + FSMC_BTR_bank1_offset] = FSMC_BTR1_ADDSET_T | FSMC_BTR1_DATAST_T | FSMC_BTR1_ADDHLD_0 # | FSMC_BTR1_CLKDIV_1 | FSMC_BTR1_ACCMOD

    machine.mem32[FSMC_Bank1_base + FSMC_BCR_bank1_offset] = FSMC_BCR1_MWID_0 | FSMC_BCR1_WREN | FSMC_BCR1_MBKEN 
    	
def TFT_Init():

    #SOFTWARE RESET
    machine.mem8[LCD_REG] = 0x01
    time.sleep_ms(1000)
    machine.mem8[LCD_REG] = 0x01
    time.sleep_ms(1000)

    #POWER CONTROL A
    machine.mem8[LCD_REG] = 0xCB
    machine.mem8[LCD_RAM] = 0x39
    machine.mem8[LCD_RAM] = 0x2C
    machine.mem8[LCD_RAM] = 0x00
    machine.mem8[LCD_RAM] = 0x34
    machine.mem8[LCD_RAM] = 0x02
    
    #POWER CONTROL B
    machine.mem8[LCD_REG] = 0xCF
    machine.mem8[LCD_RAM] = 0x00
    machine.mem8[LCD_RAM] = 0xC1
    machine.mem8[LCD_RAM] = 0x30
	
    #DRIVER TIMING CONTROL A
    machine.mem8[LCD_REG] = 0xE8
    machine.mem8[LCD_RAM] = 0x85
    machine.mem8[LCD_RAM] = 0x00
    machine.mem8[LCD_RAM] = 0x78
    
    #DRIVER TIMING CONTROL B
    machine.mem8[LCD_REG] = 0xEA
    machine.mem8[LCD_RAM] = 0x00
    machine.mem8[LCD_RAM] = 0x00
    
    #POWER ON SEQUENCE CONTROL
    machine.mem8[LCD_REG] = 0xED
    machine.mem8[LCD_RAM] = 0x64
    machine.mem8[LCD_RAM] = 0x03
    machine.mem8[LCD_RAM] = 0x12
    machine.mem8[LCD_RAM] = 0x81

    #PUMP RATIO CONTROL
    machine.mem8[LCD_REG] = 0xF7
    machine.mem8[LCD_RAM] = 0x20

    #POWER CONTROL,VRH[5:0]
    machine.mem8[LCD_REG] = 0xC0
    machine.mem8[LCD_RAM] = 0x23

    #POWER CONTROL,SAP[2:0]BT[3:0]
    machine.mem8[LCD_REG] = 0xC1
    machine.mem8[LCD_RAM] = 0x10

    #VCM CONTROL
    machine.mem8[LCD_REG] = 0xC5
    machine.mem8[LCD_RAM] = 0x3E
    machine.mem8[LCD_RAM] = 0x28

    #VCM CONTROL 2
    machine.mem8[LCD_REG] = 0xC7
    machine.mem8[LCD_RAM] = 0x86

    #MEMORY ACCESS CONTROL
    machine.mem8[LCD_REG] = 0x36
    machine.mem8[LCD_RAM] = 0x48
 
    #PIXEL FORMAT 16bit
    machine.mem8[LCD_REG] = 0x3A
    machine.mem8[LCD_RAM] = 0x55

    #FRAME RATIO CONTROL, STANDARD RGB COLOR
    machine.mem8[LCD_REG] = 0xB1
    machine.mem8[LCD_RAM] = 0x00
    machine.mem8[LCD_RAM] = 0x18

    #DISPLAY FUNCTION CONTROL
    machine.mem8[LCD_REG] = 0xB6
    machine.mem8[LCD_RAM] = 0x08
    machine.mem8[LCD_RAM] = 0x82
    machine.mem8[LCD_RAM] = 0x27

    #3GAMMA FUNCTION DISABLE
    #machine.mem8[LCD_REG] = 0xF2
    #machine.mem8[LCD_RAM] = 0x00

    #GAMMA CURVE SELECTED
    #machine.mem8[LCD_REG] = 0x26
    #machine.mem8[LCD_RAM] = 0x01

    #POSITIVE GAMMA CORRECTION
    #machine.mem8[LCD_REG] = 0xE0
    #machine.mem8[LCD_RAM] = 0x0F
    #machine.mem8[LCD_RAM] = 0x31
    #machine.mem8[LCD_RAM] = 0x2B
    #machine.mem8[LCD_RAM] = 0x0C
    #machine.mem8[LCD_RAM] = 0x0E
    #machine.mem8[LCD_RAM] = 0x08
    #machine.mem8[LCD_RAM] = 0x4E
    #machine.mem8[LCD_RAM] = 0xF1
    #machine.mem8[LCD_RAM] = 0x37
    #machine.mem8[LCD_RAM] = 0x07
    #machine.mem8[LCD_RAM] = 0x10
    #machine.mem8[LCD_RAM] = 0x03
    #machine.mem8[LCD_RAM] = 0x0E
    #machine.mem8[LCD_RAM] = 0x09
    #machine.mem8[LCD_RAM] = 0x00

    #NEGATIVE GAMMA CORRECTION
    #machine.mem8[LCD_REG] = 0xE1
    #machine.mem8[LCD_RAM] = 0x00
    #machine.mem8[LCD_RAM] = 0x0E
    #machine.mem8[LCD_RAM] = 0x14
    #machine.mem8[LCD_RAM] = 0x03
    #machine.mem8[LCD_RAM] = 0x11
    #machine.mem8[LCD_RAM] = 0x07
    #machine.mem8[LCD_RAM] = 0x31
    #machine.mem8[LCD_RAM] = 0xC1
    #machine.mem8[LCD_RAM] = 0x48
    #machine.mem8[LCD_RAM] = 0x08
    #machine.mem8[LCD_RAM] = 0x0F
    #machine.mem8[LCD_RAM] = 0x0C
    #machine.mem8[LCD_RAM] = 0x31
    #machine.mem8[LCD_RAM] = 0x36
    #machine.mem8[LCD_RAM] = 0x0F

    #EXIT SLEEP
    machine.mem8[LCD_REG] = 0x11
    time.sleep_ms(1200)

    #TURN ON DISPLAY
    machine.mem8[LCD_REG] = 0x29

    #display inversion
    #machine.mem8[LCD_REG] = 0x21

    #setup Memory Access Control
    machine.mem8[LCD_REG] = 0x36
    machine.mem8[LCD_RAM] = 0b00111111
    
def ILI9341_Set_Address(X1, Y1, X2, Y2):
    #set X min and max
    machine.mem8[LCD_REG] = 0x2A
    machine.mem16[LCD_RAM] = X1>>8
    machine.mem16[LCD_RAM] = X1
    machine.mem16[LCD_RAM] = X2>>8
    machine.mem16[LCD_RAM] = X2

    #set y min and max
    machine.mem8[LCD_REG] = 0x2B
    machine.mem16[LCD_RAM] = Y1>>8
    machine.mem16[LCD_RAM] = Y1
    machine.mem16[LCD_RAM] = Y2>>8
    machine.mem16[LCD_RAM] = Y2

    #write data command
    machine.mem8[LCD_REG] = 0x2C
     
def drawPixel(X, Y, color):
    ILI9341_Set_Address(X, Y, X, Y)
    machine.mem16[LCD_RAM] = color

def fill(x = 0, y = 0, width = 320, height = 240, color = 0):
    ILI9341_Set_Address(x, y, x + width, x + height)
    for x in range(width):
        for y in range(height):
            machine.mem16[LCD_RAM] = color

def _set_ortho_line(width, length, color):
    pixels = width * (length + 1)
    for i in range(pixels):
        machine.mem16[LCD_RAM] = color

curWidth  = 240                                                           # Current TFT width
curHeight = 320                                                           # Current TFT height

def drawVline(x, y, length, color, width = 1):
    if length > curHeight: length = curHeight
    if width > 10: width = 10
    ILI9341_Set_Address(x, y, x + (width - 1), y + length - 1)
    _set_ortho_line(width, length, color)

def drawHline(x, y, length, color, width = 1):
    if length > curWidth: length = curWidth
    if width > 10: width = 10
    ILI9341_Set_Address(x, y, x + length - 1, y + (width - 1))
    _set_ortho_line(width, length, color)
	
def drawLine(x, y, x1, y1, color):
    if x == x1:
        drawVline( x, y if y <= y1 else y1, abs(y1 - y), color )
    elif y==y1:
        drawHline( x if x <= x1 else x1, y, abs(x-x1), color )
    else:
        # keep positive range for x
        if x1 < x:
            x, x1 = x1, x
            y, y1 = y1, y
        r = (y1 - y) / (x1 - x)
        # select ratio > 1 for fast drawing (and thin line)
        if abs(r) >= 1:
            for i in range(x1 - x + 1):
                drawPixel(x + i, trunc(y + (r * i)), color)
        else:
            # keep positive range for y
            if y1 < y:
               x, x1 = x1, x
               y, y1 = y1, y
            # invert the ratio (should be close of r = 1/r)
            r = (x1 - x) / (y1 - y)
            for i in range(y1 - y + 1):
                drawPixel(trunc(x + (r * i)), y + i, color)

def drawRect(x, y, width, height, color, border = 1, infill = None):
    if border is None:
        border = 0
    border = 10 if border > 10 else border
    if width > curWidth:
        width = curWidth
    if height > curHeight:
        height = curHeight
    #height = 2 if height < 2 else height
    #width  = 2 if width  < 2 else width
    #self._graph_orientation()
    if border > 0:
        if border > width // 2:
            border = width // 2 - 1
        X, Y = x, y
        for i in range(2):
            if i == 1:
                Y = y + height - (border - 1)
            else:
                Y = y
            drawHline(X, Y, width, color, border)
            print(X, Y, width)
            if border > 1:
                Y = y + 1
                H = height
            else:
                Y = y
                H = height + 1
            if i == 1:
                X = x + width - (border - 1)
            else:
                X = x
            drawVline(X, Y, H, color, border)
            print(X, Y, H)            
    else:
        infill = color

    if infill:
        fill(x, y, width, height, infill)

def char(x, y, color):
    cb, l = glcdfont.get_ch('0')
    new = 0
#   rotate pixel by 90 degree
    buf = bytearray(8)
    for j in range(8):
        for i in range(l):
            c = cb[i]   # erste Reihe
            c = c >> j
            if (c & 0x01) == 0x01:
                new |= 1 << i
         #print (hex(new))
        buf[j] = new
        new = 0
 #  print pixel of char
    ILI9341_Set_Address(x, y, x + 5, y + 8)
    for i in range(8):
        c = buf[i]
        for j in range(l):
            #print(j, (c & 0x01))
            if (c & 0x01) == 0x01:
                machine.mem16[LCD_RAM] = color
            else:
                machine.mem16[LCD_RAM] = 0
            c = c >> 1

def show_bits(first, second, len = 8):
    out =""
    for i in range(len):
        f = (first << i) & 0x80
        #print(hex(f))
        if f:
            out = out + "1"
        else:
            out = out + "0"
    for i in range(len):
        f = (second << i) & 0x80
        if f:
            out = out + "1"
        else:
            out = out + "0"
    print (out)

def big_char(x, y, char, fgcolor=0xffff, bgcolor=0):
    k = 0
    cb, l = glcdfont.get_ch(char)
    pix = bytearray(24)
    for i in range(6):
    # first Byte
        a = cb[i] 
        pix[k] = 0
        if (a&0x10): pix[k]|=0x03
        if (a&0x20): pix[k]|=0x0c
        if (a&0x40): pix[k]|=0x30
        if (a&0x80): pix[k]|=0xc0
        k += 1
        pix[k] = 0
        if (a&0x01): pix[k]|=0x03		# die vier doppelten punkte setzen
        if (a&0x02): pix[k]|=0x0c
        if (a&0x04): pix[k]|=0x30
        if (a&0x08): pix[k]|=0xc0
        k += 1
        pix[k] = 0
        if (a&0x10): pix[k]|=0x03
        if (a&0x20): pix[k]|=0x0c
        if (a&0x40): pix[k]|=0x30
        if (a&0x80): pix[k]|=0xc0
        k += 1
        pix[k] = 0
        if (a&0x01): pix[k]|=0x03		# die vier doppelten punkte setzen
        if (a&0x02): pix[k]|=0x0c
        if (a&0x04): pix[k]|=0x30
        if (a&0x08): pix[k]|=0xc0
        k += 1
    new = 0
#   rotate pixel by 90 degree
    buf = bytearray(32)
    for j in range(8):
        for i in range(8):
            c = pix[(i * 2) + 1] # (High) Low
            c = c >> j
            if (c & 0x01) == 0x01:
                new |= 0x80 >> i
        buf[(j * 2)] = new
        new = 0
        for i in range(4):
            c = pix[(i * 2) + 17] # zweite Reihe
            c = c >> j
            if (c & 0x01) == 0x01:
                new |= 0x80 >> i
        buf[(j * 2)+1] = new
        #show_bits(buf[j*2], buf[(j*2)+1])
        new = 0
    for j in range(8):
        for i in range(8):
            c = pix[i * 2] # erste Reihe
            c = c >> j
            if (c & 0x01) == 0x01:
                new |= 0x80 >> i
        buf[(j * 2) + 16] = new
        new = 0
        for i in range(4):
            c = pix[(i * 2) + 16] # zweite Reihe
            c = c >> j
            if (c & 0x01) == 0x01:
                new |= 0x80 >> i
        buf[(j * 2) + 17] = new
        new = 0
        #show_bits(buf[(j*2) + 16], buf[(j*2)+17])
 #  print pixel of char
    ILI9341_Set_Address(x, y, x + 11, y + 15)
    for i in range(16):
        #show_bits(buf[i*2], buf[(i*2)+1])
        c = buf[i * 2]
        for j in range(8):
            if (c & 0x80) == 0x80:
                machine.mem16[LCD_RAM] = fgcolor
            else:
                machine.mem16[LCD_RAM] = bgcolor
            c = c << 1
        c = buf[(i * 2) + 1]
        for j in range(4):
            if (c & 0x80) == 0x80:
                machine.mem16[LCD_RAM] = fgcolor
            else:
                machine.mem16[LCD_RAM] = bgcolor
            c = c << 1

def string_big(x, y, string, fgcolor=0xffff, bgcolor=0):
    for i in range(len(string)):
        big_char(x + (i*11), y, string[i], fgcolor, bgcolor)
	
LCD_REG = const(0x60000002)
LCD_RAM = const(0x60080002)    



Online
OutoftheBOTS_
Posts: 768
Joined: Mon Nov 20, 2017 10:18 am

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by OutoftheBOTS_ » Sun Apr 26, 2020 11:26 am

Ok 2 things

1. if you use CONST() for the values of the registers then it doesn't use ram, it works much like the #define in C

2. there is a viper mode that will run much faster than using the machine.mem8[] function

holberg
Posts: 6
Joined: Mon Apr 20, 2020 9:01 am

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by holberg » Sun Apr 26, 2020 12:48 pm

OutoftheBOTS_ wrote:
Sun Apr 26, 2020 11:26 am
Ok 2 things

1. if you use CONST() for the values of the registers then it doesn't use ram, it works much like the #define in C

2. there is a viper mode that will run much faster than using the machine.mem8[] function
Ah, thank you, I will try.

User avatar
JohnLittle
Posts: 10
Joined: Mon Oct 08, 2018 7:10 pm

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by JohnLittle » Fri May 29, 2020 10:13 pm

@holberg, thank you for your efforts, this is definitely going in the right direction!

Just a note about viper magic,

I tried this for fill() instead of your code:

Code: Select all

import utime
import machine
LCD_REG = const(0x60000002)
LCD_RAM = const(0x60080002)    

@micropython.viper
def fill(n: int):
    width=320
    height=240
    odr = ptr16(LCD_RAM)
    for _ in range(width*height):
        odr[0] = n

t1 = utime.ticks_ms()        
fill(255*255)
print(utime.ticks_ms()-t1)
838ms for yours, 46ms for mine. Pretty sweet, huh? :D

I'm looking at potentially replicating this: https://blog.lvgl.io/2019-08-05/micropy ... lay-driver

Cheers,
John

Post Reply