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: 8
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)    



OutoftheBOTS_
Posts: 847
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: 8
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: 11
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

EDIT1: I've gone full circle and took the code @OutoftheBOTS_ shared a year ago in here and here and am making a micropython 1.12 native module out of it. It will be interesting to compare its speed with the timings above.

Native modules are really cool and relatively easy to make. Main advantage is that they leave the underlying micropython completely untouched. Once you've compiled the mpy module, it's just a matter of copying it to the microsd filesystem and (re-)importing it in micropython after a soft reset.

EDIT2:
I've been playing with native modules for a few days, some things are not as easy as I thought they would be. The biggest drawback is that only a subset of micropython functions can be accessed through the native module interface. This means any functions we need to access need to be added to the mp_fun_table as per this github thread and the linker limations section of the native modules documentation.

So, since I cannot use mp_hal_delay_ms() I am using this alternative instead:

Code: Select all

//https://electronics.stackexchange.com/questions/87241/stm32f4-discovery-board-universal-delay-milisecs-function
#define STM32_CLOCK_HZ 168000000UL
#define STM32_CYCLES_PER_LOOP 6 // This will need tweaking or calculating

void delay_ms(uint32_t ms)
{
    ms *= STM32_CLOCK_HZ / 1000 / STM32_CYCLES_PER_LOOP;
    __asm__ volatile(" mov r0, %[ms] \n\t"
             "1: subs r0, #1 \n\t"
             " bhi 1b \n\t"
             :
             : [ms] "r" (ms)
             : "r0");
}
The other big miss is (I had the same idea as @deshipu) mp_obj_get_array() as some functions need a lot of parameters (e.g. drawRectangle). We can have functions with a couple of parameters (2? 3?) but definitely not 5.

So this is quite a bummer but I guess that for development, using native modules is a viable alternative to recompiling the whole of micropython with an integrated module.

My code isn't stable, but here's what I've been working on (lcdnat.c):

Code: Select all

// Include the header file to get access to the MicroPython API
#include "py/dynruntime.h"
#include "py/obj.h"
#include "stm32f407xx.h"

#define LCD_REG      (*((volatile unsigned short *) 0x60000000)) //address where A15 is low
#define LCD_RAM      (*((volatile unsigned short *) 0x60020000))  //address where A15 is high

/* 
  OutoftheBOTS_ said: https://forum.micropython.org/viewtopic.php?t=3086&start=100#p38189
  I have used the the STM32F407VET6 black with a ILI9431 TFT in 16bit parallel using the
  FSMC peripheral but only in C but 1 could use my library then wrap it up to be called from MP.

  Here is a video of me using a ILI9431 TFT that I brought and soldered down to a breakout board
  then connected to the STM32F407VET6 black via the TFT header but the touch screen I connected
  to the ADC pins rather than uses a XT2046 IC to read the touch screen. https://www.youtube.com/watch?v=H51OzQl3BC8

  Basically it involved me turning on the FSMC clock then setting all the needed pins to AF FSMC
  then setting just 2 FSMC registers then the ILI9431s RAM was addressable like external RAM to the MCU.
  I just simply set a pointer to the RAM the wrote to it.

  I am not very good with using the HAL libraries so tend to just write directly to the registers using the macros in STM32F4xx.h

  First you need to understand how FSMC works with these screens. It utilizes the 8080 ram interface, this
  basically has 16 data lines and 16 address lines then a few control lines(chip select, read, write).
  With the TFT they don't use all the address line to address the RAM but rather uses only 1 address line
  connected to the RS/DC (register select/ data command select)pin.

  FSMC is usually used to attached external RAM to the heap of the MCU with starting address 0x60000000 using
  this 8080 ram interface and allows you to address the RAM like it is part of the internal ram of the MCU.
  So once I have set up the FSMC pins and control registers on the MCU then I can send a command to the control resigister
  of the TFT if I use an address where the address line connected to the RS/DC pin is low and if I want to send data to the
  graphics ram of the TFT I use and address where the addressl line connected to the RS/DC pin is high :)

  So after I have setup all the needed pins in correct AF and setup the 2 FSMC control registers then I can use the screen like this:
  I had the RS/DC pin connected to address line A16 (PD11) (off memory the header connects the RS/DC pin to a different address line
  so you will need to change the address of LCD_REG if using the header)
*/

//https://electronics.stackexchange.com/questions/87241/stm32f4-discovery-board-universal-delay-milisecs-function
#define STM32_CLOCK_HZ 168000000UL
#define STM32_CYCLES_PER_LOOP 6 // This will need tweaking or calculating

void delay_ms(uint32_t ms)
{
    ms *= STM32_CLOCK_HZ / 1000 / STM32_CYCLES_PER_LOOP;
    __asm__ volatile(" mov r0, %[ms] \n\t"
             "1: subs r0, #1 \n\t"
             " bhi 1b \n\t"
             :
             : [ms] "r" (ms)
             : "r0");
}

// This is my code to setup the needed pins in AF and as FSCM.
STATIC mp_obj_t FSMC_Init(void) {
	//setup pins as AF
	GPIOD->MODER |= GPIO_MODER_MODER0_1 | GPIO_MODER_MODER1_1 | GPIO_MODER_MODER4_1 | GPIO_MODER_MODER5_1 | GPIO_MODER_MODER7_1 | GPIO_MODER_MODER8_1 | GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1 | GPIO_MODER_MODER11_1 | GPIO_MODER_MODER14_1 | GPIO_MODER_MODER15_1;
	GPIOE->MODER |= GPIO_MODER_MODER7_1 | GPIO_MODER_MODER8_1 | GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1  | GPIO_MODER_MODER11_1 | GPIO_MODER_MODER12_1 | GPIO_MODER_MODER13_1 | GPIO_MODER_MODER14_1 |  GPIO_MODER_MODER15_1;
	//setup pins as PP
	GPIOD->OTYPER &= 0b0011000001001100;
	GPIOE->OTYPER &= 0b0000000001111111;
	//set speed to 100MHz
	GPIOD->OSPEEDR |= (0b11<<(2*0)) | (0b11<<(2*1)) | (0b11<<(2*4)) | (0b11<<(2*5)) | (0b11<<(2*7)) | (0b11<<(2*8)) | (0b11<<(2*9)) | (0b11<<(2*10)) | (0b11<<(2*11)) | (0b11<<(2*14)) | (0b11<<(2*15));
	GPIOE->OSPEEDR |= (0b11<<(2*7)) | (0b11<<(2*8)) | (0b11<<(2*9)) | (0b11<<(2*10)) | (0b11<<(2*11)) | (0b11<<(2*12)) | (0b11<<(2*13)) | (0b11<<(2*14)) | (0b11<<(2*15));
	//set NO pull-up or pull-down
	GPIOD->PUPDR &= ((0b11<<(2*2)) | (0b11<<(2*3)) | (0b11<<(2*6)) | (0b11<<(2*12)) | (0b11<<(2*13)));
	GPIOE->PUPDR &= ((0b11<<(2*0)) | (0b11<<(2*1)) | (0b11<<(2*2)) | (0b11<<(2*3)) | (0b11<<(2*4)) | (0b11<<(2*5)) | (0b11<<(2*6)));
	//set other pins to AF12 i.e as FSMC pins
	GPIOD->AFR[0] = (GPIOD->AFR[0] & (0b1111<<(4*2) | 0b1111<<(4*3) | 0b1111<<(4*6))) | 0b1100<<(4*0) | 0b1100<<(4*1) | 0b1100<<(4*4) | 0b1100<<(4*5) | 0b1100<<(4*7);
	GPIOD->AFR[1] = (GPIOD->AFR[1] & (0b1111<<(4*(13-8)) | 0b1111<<(4*(12-8)))) | 0b1100<<(4*(8-8)) | 0b1100<<(4*(9-8)) | 0b1100<<(4*(10-8)) | 0b1100<<(4*(11-8)) | 0b1100<<(4*(14-8)) | 0b1100<<(4*(15-8));
	GPIOE->AFR[0] = (GPIOE->AFR[0] & ~(0b1111<<7)) | 0b1100<<(4*7);
	GPIOE->AFR[1] = 0xCCCCCCCC;

	//enable RCC for FSMC 
	RCC->AHB3ENR |= RCC_AHB3ENR_FSMCEN;
	
	//setup FSMC on Bank1 NORSRAM1
	//setup timings of FSCM
	FSMC_Bank1->BTCR[1] = FSMC_BTR1_ADDSET_1 | FSMC_BTR1_DATAST_1;
	// Bank1 NOR/SRAM control register configuration
	FSMC_Bank1->BTCR[0] = FSMC_BCR1_MWID_0 | FSMC_BCR1_WREN | FSMC_BCR1_MBKEN;

	return mp_const_none;
}

STATIC mp_obj_t TFT_Init(void){
	//hardware reset
	GPIOD->ODR &= ~GPIO_ODR_ODR_12;
	delay_ms(100);
	GPIOD->ODR |= GPIO_ODR_ODR_12;
	delay_ms(100);
	//SOFTWARE RESET
	LCD_REG = 0x01;
	delay_ms(100);

	//POWER CONTROL A
	LCD_REG = 0xCB;
	LCD_RAM = 0x39;
	LCD_RAM = 0x2C;
	LCD_RAM = 0x00;
	LCD_RAM = 0x34;
	LCD_RAM = 0x02;

	//POWER CONTROL B
	LCD_REG = 0xCF;
	LCD_RAM = 0x00;
	LCD_RAM = 0xC1;
	LCD_RAM = 0x30;

	//DRIVER TIMING CONTROL A
	LCD_REG = 0xE8;
	LCD_RAM = 0x85;
	LCD_RAM = 0x00;
	LCD_RAM = 0x78;

	//DRIVER TIMING CONTROL B
	LCD_REG = 0xEA;
	LCD_RAM = 0x00;
	LCD_RAM = 0x00;

	//POWER ON SEQUENCE CONTROL
	LCD_REG = 0xED;
	LCD_RAM = 0x64;
	LCD_RAM = 0x03;
	LCD_RAM = 0x12;
	LCD_RAM = 0x81;

	//PUMP RATIO CONTROL
	LCD_REG = 0xF7;
	LCD_RAM = 0x20;

	//POWER CONTROL,VRH[5:0]
	LCD_REG = 0xC0;
	LCD_RAM = 0x23;

	//POWER CONTROL,SAP[2:0];BT[3:0]
	LCD_REG = 0xC1;
	LCD_RAM = 0x10;

	//VCM CONTROL
	LCD_REG = 0xC5;
	LCD_RAM = 0x3E;
	LCD_RAM = 0x28;

	//VCM CONTROL 2
	LCD_REG = 0xC7;
	LCD_RAM = 0x86;

	//MEMORY ACCESS CONTROL
	LCD_REG = 0x36;
	LCD_RAM = 0x48;

	//PIXEL FORMAT
	LCD_REG = 0x3A;
	LCD_RAM = 0x55;

	//FRAME RATIO CONTROL, STANDARD RGB COLOR
	LCD_REG = 0xB1;
	LCD_RAM = 0x00;
	LCD_RAM = 0x18;

	//DISPLAY FUNCTION CONTROL
	LCD_REG = 0xB6;
	LCD_RAM = 0x08;
	LCD_RAM = 0x82;
	LCD_RAM = 0x27;

	//3GAMMA FUNCTION DISABLE
	LCD_REG = 0xF2;
	LCD_RAM = 0x00;

	//GAMMA CURVE SELECTED
	LCD_REG = 0x26;
	LCD_RAM = 0x01;

	//POSITIVE GAMMA CORRECTION
	LCD_REG = 0xE0;
	LCD_RAM = 0x0F;
	LCD_RAM = 0x31;
	LCD_RAM = 0x2B;
	LCD_RAM = 0x0C;
	LCD_RAM = 0x0E;
	LCD_RAM = 0x08;
	LCD_RAM = 0x4E;
	LCD_RAM = 0xF1;
	LCD_RAM = 0x37;
	LCD_RAM = 0x07;
	LCD_RAM = 0x10;
	LCD_RAM = 0x03;
	LCD_RAM = 0x0E;
	LCD_RAM = 0x09;
	LCD_RAM = 0x00;

	//NEGATIVE GAMMA CORRECTION
	LCD_REG = 0xE1;
	LCD_RAM = 0x00;
	LCD_RAM = 0x0E;
	LCD_RAM = 0x14;
	LCD_RAM = 0x03;
	LCD_RAM = 0x11;
	LCD_RAM = 0x07;
	LCD_RAM = 0x31;
	LCD_RAM = 0xC1;
	LCD_RAM = 0x48;
	LCD_RAM = 0x08;
	LCD_RAM = 0x0F;
	LCD_RAM = 0x0C;
	LCD_RAM = 0x31;
	LCD_RAM = 0x36;
	LCD_RAM = 0x0F;

	//EXIT SLEEP
	LCD_REG = 0x11;
	delay_ms(100);

	//TURN ON DISPLAY
	LCD_REG = 0x29;

	//dispaly inversion
	//LCD_REG = 0x21);

	//setup Memory Access Control
	LCD_REG = 0x36;
	LCD_RAM = 0b00111111;

	return mp_const_none;
}

STATIC void ILI9341_Set_Address(uint16_t X1, uint16_t Y1, uint16_t X2, uint16_t Y2){
	//set X min and max
	LCD_REG = 0x2A;
	LCD_RAM = X1>>8;
	LCD_RAM = X1;
	LCD_RAM = X2>>8;
	LCD_RAM = X2;

	//set y min and max
	LCD_REG = 0x2B;
	LCD_RAM = Y1>>8;
	LCD_RAM = Y1;
	LCD_RAM = Y2>>8;
	LCD_RAM = Y2;

	//write data command
	LCD_REG = 0x2C;
}

void draw_rect_helper(uint16_t X1, uint16_t Y1, uint16_t X2, uint16_t Y2, uint16_t colour){
	ILI9341_Set_Address(X1, Y1, X2, Y2);

	for( uint32_t a = 0; a < ((X2-X1+1)*(Y2-Y1+1)); a++ ){
		LCD_RAM = colour;
	}
}

/*
STATIC mp_obj_t draw_rect(mp_obj_t tuple, mp_obj_t col_obj)
{
    size_t len;
    mp_obj_t *elem;

    mp_obj_get_array(tuple, &len, &elem);

    uint16_t x1 = (uint16_t)mp_obj_get_int(elem[0]);
    uint16_t y1 = (uint16_t)mp_obj_get_int(elem[1]);
    uint16_t x2 = (uint16_t)mp_obj_get_int(elem[2]);
    uint16_t y2 = (uint16_t)mp_obj_get_int(elem[3]);
    uint16_t col = (uint16_t)mp_obj_get_int(col_obj);
    draw_rect_helper(x1, y1, x2, y2, col);
	return mp_const_none;
}
*/
STATIC mp_obj_t draw_rect(mp_obj_t col_obj)
{
    uint16_t x1 = 0;
    uint16_t y1 = 0;
    uint16_t x2 = 319;
    uint16_t y2 = 239;
    uint16_t col = (uint16_t)mp_obj_get_int(col_obj);
    draw_rect_helper(x1, y1, x2, y2, col);
	return mp_const_none;
}

// Define a Python reference to the function above
STATIC MP_DEFINE_CONST_FUN_OBJ_0(FSMC_Init_obj, FSMC_Init);
STATIC MP_DEFINE_CONST_FUN_OBJ_0(TFT_Init_obj, TFT_Init);
STATIC MP_DEFINE_CONST_FUN_OBJ_1(draw_rect_obj, draw_rect);

// This is the entry point and is called when the module is imported
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
    // This must be first, it sets up the globals dict and other things
    MP_DYNRUNTIME_INIT_ENTRY

    // Make the function available in the module's namespace
    mp_store_global(MP_QSTR_FSMC_Init, MP_OBJ_FROM_PTR(&FSMC_Init_obj));
    mp_store_global(MP_QSTR_TFT_Init, MP_OBJ_FROM_PTR(&TFT_Init_obj));
    mp_store_global(MP_QSTR_draw_rect, MP_OBJ_FROM_PTR(&draw_rect_obj));

    // This must be last, it restores the globals dict
    MP_DYNRUNTIME_INIT_EXIT
}
and for the Makefile to go with:

Code: Select all

# Location of top-level MicroPython directory
MPY_DIR = ../..

# Name of module
MOD = lcdnat
BOARD = BLACK_F407VE

CFLAGS += -I$(MPY_DIR)/lib/cmsis/inc
CFLAGS += -I$(MPY_DIR)/lib/stm32lib/CMSIS/STM32F4xx/Include
CFLAGS += -I$(MPY_DIR)/ports/stm32
#CFLAGS += -I$(MPY_DIR)/ports/stm32/usbdev/class/inc
#CFLAGS += '-DMP_CONFIGFILE="<mpconfigport.h>"'

# Source files (.c or .py)
SRC = lcdnat.c

# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin)
ARCH = armv7m

# Include to get the rules for compiling and linking the module
include $(MPY_DIR)/py/dynruntime.mk
When filling the screen with a solid color did work, it was only a couple of milliseconds faster than the viper code... 39 instead of 46 :-)

Cheers,
John

bellad
Posts: 96
Joined: Tue May 14, 2019 1:47 pm

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by bellad » Mon Jun 22, 2020 7:26 am

hello,
i have put BLACK_F407VE-20170620-v1.9.1-29-g1686346.dfu on my card with dfusedemo with bt0-3.3v
it's ok
after bt0-gnd , i plug usb and

Code: Select all

unknown device
d2 d3 toogle and after nothing

i have not st-link , it's possible with usb ?
how to switch to repl mode by usb ?
for RPEL mode use usb J4 or rx tx > usb J6 ?
thank
i wiork with windows7

vahithosan
Posts: 19
Joined: Wed Jul 26, 2017 5:15 pm

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by vahithosan » Tue Jun 23, 2020 6:32 am

bellad wrote:
Mon Jun 22, 2020 7:26 am
hello,
i have put BLACK_F407VE-20170620-v1.9.1-29-g1686346.dfu on my card with dfusedemo with bt0-3.3v
it's ok
after bt0-gnd , i plug usb and

Code: Select all

unknown device
d2 d3 toogle and after nothing

i have not st-link , it's possible with usb ?
how to switch to repl mode by usb ?
for RPEL mode use usb J4 or rx tx > usb J6 ?
thank
i wiork with windows7

it happened to me too.

viewtopic.php?t=3313#top

Code: Select all

dfu-util -s :mass-erase:force -a 0 -d 0483:df11 -D path-to-some-dfu-file.dfu
0483:df11 replace your own device

bellad
Posts: 96
Joined: Tue May 14, 2019 1:47 pm

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by bellad » Tue Jun 23, 2020 8:03 am

ok , thank , but it's same
i have put
STM32F4DISC-20200622-unstable-v1.12-557-g3a9d94803.dfu
it's ok , except when i do CTRL-D no reset , he crashes
when
dfu-util -s :mass-erase:force -a 0 -d 0483:df11 -D BLACK_F407VE-20170620-v1.9.1-29-g1686346.dfu
cannot open dfu 0483:df11
ok i have put driver zadig
dfu.jpg
dfu.jpg (70.83 KiB) Viewed 13841 times
after put , with BT0 on 3.3v
dfu-util -s :mass-erase:force -a 0 -d 0483:df11 -D BLACK_F407VE-20170620-v1.9.1-29-g1686346.dfu
cannot open dfu 0483:df11
after BT0 on gnd
it's same , no acces REPL

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by SpotlightKid » Wed Jun 24, 2020 11:41 am

I just received an MCUDev DevEBox STM32F407VGT6 board from AliExpress (only ~$9 € inlc. shippng). This is now marked:

Code: Select all

VER:V3.0
SN:1904
The only visible changes I can see are an additional diode to the left of C1 and two new resistors (R1,R4) above R34.
@mcauser 's board definition works perfectly. Thanks!

SpotlightKid
Posts: 463
Joined: Wed Apr 08, 2015 5:19 am

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by SpotlightKid » Thu Jun 25, 2020 9:44 pm

SpotlightKid wrote:
Wed Jun 24, 2020 11:41 am
MCUDev DevEBox STM32F407VGT6 [...]
@mcauser 's board definition works perfectly.
I spoke too son. I get strange behaviour with this board (firmware compiled from git as of today):

* On every power up or after every reset (even soft reset) I see the following LED blinking pattern 1-2-3. This repeats 4 times and takes about 7 seconds.
* Only then /dev/ttyACM0 appears and I can connect to the REPL with a serial terminal program.
* When I try to connect with rshell, it hangs trying to connect and when I interrupt this with Ctrl-C and then connect with e.g. microterm, the REPL is in RAW mode.

Anybody else experienced this or any ideas what's going on?

bellad
Posts: 96
Joined: Tue May 14, 2019 1:47 pm

Re: MCUDev Black STM32F407VET6 + STM32F407ZET6 dev boards

Post by bellad » Wed Aug 26, 2020 9:06 am

hello,
i want use lcd2004 ( 20 x4 ) with i2c ,
i do

Code: Select all

from machine import I2C, Pin
i2c = I2C(sda=Pin('B10'), scl=Pin('B11'))
print(i2c.scan())
it's ok [39]

but after ??
import ? ( i2c lcd )
I don't know
can you help me
thank you

Post Reply