platformio / platform-ststm32

ST STM32: development platform for PlatformIO
https://registry.platformio.org/platforms/platformio/ststm32
Apache License 2.0
394 stars 308 forks source link

product_line deduction from mcu for STM32Cube frameworks #453

Open daniel-starke opened 3 years ago

daniel-starke commented 3 years ago

Up to ststm32 version 9.x there was no need for a product_line field in the board.json. This changed with version 10. The value is needed to get the right linker file and includes. However, the value is redundant. It can be deduced from the given MCU like done in STM32CubeIDE. This would avoid breaking current custom boards and ease the handling for the user as this value if not quite obvious.

The following gawk script can be used to obtain all mappings from an STM32CubeIDE installation:

#!/bin/gawk -f
# @file _mcuParse.awk
#
# execute in e.g. STM32CubeIDE\plugins\com.st.stm32cube.common.mx_6.0.1.202008101643
# unzip -p STM32CubeMX.jar "devices/*" -x devices/stm32boards.db | FAMILIES_FILE=db/mcu/families.xml ./_mcuParse.awk >_mcu.py

function addCoreTarget(core, target) {
    delete _ary
    if ( match(target, /(.*)\((.+)\)(.*)/, _ary) ) {
        # multi-target given
        delete _ary2
        _count = split(_ary[2], _ary2, "-")
        for (_n = 1; _n <= _count; _n++) {
            # handle each single target
            _target = _ary[1] _ary2[_n] _ary[3]
            if (CoreMap[core] == "") CoreMap[core] = ","
            CoreMap[core] = CoreMap[core] _target ","
        }
    } else {
        # single target given
        if (CoreMap[core] == "") CoreMap[core] = ","
        CoreMap[core] = CoreMap[core] target ","
    }
}

function addDistinctItem(list, item) {
    if (list == "") return "\"" item "\""
    if (index(list, "\"" item "\"") != 0) return list
    return list ", \"" item "\""
}

function addDistinctItems(list, items, sep) {
    delete _ary
    _count = split(items, _ary, sep)
    for (_i = 1; _i <= _count; _i++) {
        if (_ary[_i] == "") continue
        list = addDistinctItem(list, _ary[_i])
    }
    return list
}

BEGIN {
    if ( ! "FAMILIES_FILE" in ENVIRON ) {
        printf("Error: Missing environment variable FAMILIES_FILE.\n") > "/dev/stderr"
        exit 1
    }
    delete CoreMap
    oldFS = FS
    FS = "[<>\"]"
    while ((getline < ENVIRON["FAMILIES_FILE"]) > 0) {
        if ($0 ~ /RefName/) target = $3
        if ($2 == "Core" && target != "") {
            gsub(/Arm /, "", $3)
            addCoreTarget(tolower($3), target)
        }
    }
    FS = oldFS
    if (length(CoreMap) < 1) {
        printf("Error: File in FAMILIES_FILE does not exists.\n") > "/dev/stderr"
        exit 1
    }
    printf("# List of possible targets. Each elements contains: Part Numbers, Variants, Define, Cores\n")
    printf("# @see _mcuParse.awk\n")
    printf("mcuList = [")
    FirstMcu = 1
    delete VMap # variant map for wildcard MCU names
    delete DMap # define map for wildcard MCU names
    delete CMap # core map for wildcard MCU names
}

/<device>/ {
    Products = ""
    Variants = ""
    Define = ""
}

/<PN>/ {
    delete ary
    if ( match($0, /<PN>([^<]+)/, ary) ) Products = ary[1]
}

/<variants>/ {
    delete ary
    if ( match($0, /<variants>([^<]+)/, ary) ) Variants = ary[1]
}

/<define>/ {
    delete ary
    if ( match($0, /<define>([^<]+)/, ary) ) Define = ary[1]
}

/<\/device>/ {
    if (Products != "" && Define != "") {
        delete ary
        Count = split(Products, ary, ",")
        POut = ""
        Core = ""
        CoreList = ""
        XProd = ""
        for (i = 1; i <= Count; i++) {
            QProduct = "\"" ary[i] "\""
            if (match(POut, QProduct) || ary[i] ~ /-/) continue
            if (ary[i] ~ /x/) {
                # wildcard MCU name
                VMap[ary[i]] = addDistinctItems(VMap[ary[i]], Variants, ",")
                DMap[ary[i]] = addDistinctItems(DMap[ary[i]], Define, ",")
                XProd = ary[i]
            } else {
                if (POut != "") POut = POut ", "
                POut = POut QProduct
            }
            for (c in CoreMap) {
                if ( match(CoreMap[c], "," ary[i]) ) {
                    Core = addDistinctItem(Core, c)
                    if (CoreList == "") {
                        CoreList = c
                    } else {
                        CoreList = CoreList "," c
                    }
                }
            }
        }
        if (CoreList != "" && XProd != "") {
            CMap[XProd] = addDistinctItems(CMap[XProd], CoreList, ",")
        }
        delete ary
        Count = split(Variants, ary, ",")
        VOut = ""
        for (i = 1; i <= Count; i++) {
            if (i != 1) VOut = VOut ", "
            VOut = VOut "\"" ary[i] "\""
        }
        if (Core == "") {
            printf("Warning: No core architecture found for targets %s. Targets omitted.\n", POut) > "/dev/stderr"
        } else {
            if (FirstMcu == 1) {
                printf("\n")
                FirstMcu = 0
            } else {
                printf(",\n")
            }
            printf("    [[%s], [%s], \"%s\", [%s]]", POut, VOut, Define, Core)
        }
    }
}

END {
    # output wildcard MCU names
    delete McuList
    for (MCU in VMap) {
        McuList[MCU] = MCU
    }
    Count = asort(McuList)
    FirstX = 1
    for (i = 1; i <= Count; i++) {
        if (DMap[McuList[i]] ~ /,/) continue # ignore conflicting definitions
        if (CMap[McuList[i]] == "") continue # ignore incomplete targets
        if (FirstMcu == 1) {
            printf("\n")
            FirstMcu = 0
        } else {
            printf(",\n")
        }
        if (FirstX == 1) {
            printf("    # wildcard MCU list\n")
            FirstX = 0
        }
        printf("    [[\"%s\"], [%s], %s, [%s]]", McuList[i], VMap[McuList[i]], DMap[McuList[i]], CMap[McuList[i]])
    }
    printf("\n]\n")
}

The product_line can be obtained as define field in the result and some validity checks can than be performed using the following python function:

def getMcuVariant(boardId, cpu, mcu):
    """ Get part number, variant, define and core architecture for the current board configuration and perform some validity checks. """
    result = {
        "partNumber": None,
        "variant": None,
        "define": None
    }
    lowerCpu = cpu.lower()
    lowerMcu = mcu.lower()
    selectedTarget = None
    for target in mcuList:
        partNumber = None
        for pn in target[0]:
            if lowerMcu.startswith(pn.lower()) or re.match(pn.replace("x", "."), lowerMcu, re.I):
                partNumber = pn
                break
        if partNumber == None:
            continue
        variant = None
        variants = ""
        for v in target[1]:
            fullName = partNumber.lower() + v.lower()
            if lowerMcu == fullName or re.match(fullName.replace("x", "."), lowerMcu, re.I):
                variant = v
                break
            if variants != "":
                variants += ", "
            variants += v
        if variant == None:
            return {"error": "Unknown variant in MCU field for board %s. Possible variants for %s are: %s." % (boardId, partNumber, variants)}
        selectedTarget = target
        result["partNumber"] = partNumber
        result["variant"] = variant
        result["define"] = target[2]
        break
    if selectedTarget == None:
        return {"error": "Unknown value in MCU field for board %s." % boardId}
    hasCore = False
    cores = ""
    for core in selectedTarget[3]:
        if lowerCpu == core.lower():
            hasCore = True
            break
        if cores != "":
            cores += ", "
        cores += core
    if hasCore == False:
        return {"error": "Invalid value in CPU field for board %s. Possible values for %s are: %s." % (boardId, result.partNumber + result.variant, cores)}
    return result
valeros commented 3 years ago

Hi @daniel-starke, thanks for your research. TBH, I'm a bit reluctant to dig into proprietary Java archives to extract the list of supported MCUs. We introduced that new field to be able to compile the official STM32Cube packages directly from the repositories and upgraded the major version of the platform so user will be aware of possible breaking changes. If this feature is critical for you, please submit a complete PR.

Thanks again!