How to Initialize a Microcontroller [часть 2]

As is known, a program is an implementation of an algorithm. And an algorithm is an ordered sequence of actions. Therefore, the correct order of program execution is of great importance.

If you choose the wrong program initialization order, the firmware will get stuck at run-time.

In kitchen language. There is no point in initializing UART if GPIO is not initialized. This is obvious. However, this is not always the case. When a program is built from a huge number of software components (SWC) (tens and hundreds) and each software component has dependencies on several more software components, then it is simply unrealistic to mentally identify the correct sequence of initialization of the entire firmware, sorry…

The program consists of software components. Software components are related to each other by a dependency relationship.

In the previous text https://habr.com/ru/articles/818917/ I showed how the make utility can formally perform topological sorting of a graph. However, this is not convenient, since it requires manually writing a separate make file just to sort the graph. Formally, yes, everything works, but it is terribly inconvenient.

The thing is that there has long been a cult language for graph labeling GraphVizWe already use it to generate documentation.

At the same time, our assembly system automatically composes a graph of dependencies between software components. Here is the text about it https://habr.com/ru/articles/765424/

To identify the correct program loading sequence, it is necessary to perform a topological sorting of the program dependency graph.

The good news is that it turns out there is a separate free utility for topological sorting. tsort.exe. It is part of the CygWin package and is located in the file system at

C:\cygwin64\bin\tsort.exe

You just need to somehow connect the Graphviz file with the tsort utility and the problem will solve itself.

What's the plan?

1–Write a tree of software component dependencies in the Graphviz language (*.gv file). It doesn't matter how: manually or automatically.

2–Perform automatic syntactic parsing of Graphviz *.gv file and create a config file (tsort.txt) with an adjacency list specifically for the utility tsort.exe

3–Remove duplicates in tsort.txt with utility uniq.exe or sort.exe with option -u

4–Perform topological sorting of the dependency graph of software components using the utility tsort.exe. Get the *.init file

5–Create an initialization array for flashing into the microcontroller

This plan can be shown schematically like this.

The only difficulty here is that you need to form an adjacency list from the GraphViz code for the tsort utility.

Essentially, we need to make some kind of compiler for the GraphViz language.

Here is an example of a typical firmware software component dependency tree in GraphViz language
strict digraph graphname {
    rankdir=LR;
    splines=ortho
    node [shape="box"];
CORTEX_M4->NVIC
FPU->CORTEX_M4
THUMB->CORTEX_M4
NVIC->CORTEX_M4
SYSTICK->CORTEX_M4
subgraph cluster_mcal{
    label = "MCAL";
    style=filled;
    color=oldlace;
    ADC [shape = note][fillcolor = aquamarine][style="filled"]
    DMA [shape = note][fillcolor = aquamarine][style="filled"]
    FLASH [shape = note][fillcolor = aquamarine][style="filled"]
    GPIO [shape = note][fillcolor = aquamarine][style="filled"]
    I2C [shape = note][fillcolor = aquamarine][style="filled"]
    I2S [shape = note][fillcolor = aquamarine][style="filled"]
    PWM [shape = note][fillcolor = aquamarine][style="filled"]
    SPI [shape = note][fillcolor = aquamarine][style="filled"]
    TIMER [shape = note][fillcolor = aquamarine][style="filled"]
    UART [shape = note][fillcolor = aquamarine][style="filled"]
}
REG->ADC
GPIO->ADC
NVIC->ADC
REG->DMA
TIME->DMA
ARRAY->FLASH
REG->FLASH
TIME->GPIO
REG->GPIO
REG->I2C
GPIO->I2C
NVIC->I2C
DFT->I2S
SW_DAC->I2S
DMA->I2S
CRC->NVS
FLASH->NVS
INTERVALS->NVS
TIMER->PWM
TIME->SPI
GPIO->SPI
NVIC->TIMER
REG->TIMER
MATH->TIMER
TIME->UART
FIFO->UART
GPIO->ADC
TIME->ADC
PLL->CLOCK
NVIC->TIMER
CLOCK->TIMER
DMA->UART [color = red]
FIFO->UART [color = blue]
UART->LOG
subgraph cluster_adc{
    label = "ADT";
    style=filled;
    color=oldlace;
        ARRAY [shape = note][fillcolor = aqua][style="filled"]
        FIFO [shape = note][fillcolor = aqua][style="filled"]
}
RAM->ARRAY [color=blue]
RAM->FIFO
subgraph cluster_Connectivity{
    label = "Connectivity";
    style=filled;
    color=oldlace;
    STREAM
    LOG
subgraph cluster_Interfaces{
    label = "Interfaces";
    color=red
GPIO->CAN
REG->CAN
NVIC->CAN
ARRAY->RS485
FIFO->RS485
GPIO->RS485
UART->RS485
UART->RS232
FIFO->RS232
ARRAY->RS232
}
subgraph cluster_protocols{
    label = "Protocols";
    style=filled;
    color=oldlace;
        CLI;
        ISO_TP;
        UDS;
}
    UART->STREAM [color=chartreuse]
    RS232->STREAM [color=chartreuse]
    FIFO->STRING_READER [color=aqua]
    STRING->CLI [color=aqua]
    STRING_READER->CLI [color=aqua]
    STREAM->CLI [color=aqua]
    LOG->CLI [color=aqua]
    CSV->CLI [color=aqua]
CAN->ISO_TP [color="green"]
ISO_TP->UDS [color="blue"]
NVRAM->UDS [color="red"]
ARRAY->UDS [color="yellow"]
}
TIME->LOG
STREAM->LOG
FIFO->LOG
UART->LOG
subgraph cluster_control{
    label = "Control";
    style=filled;
    color=oldlace;
BOOT
DEBUGGER
LED
RELAY
SUPER_CYCLE
SYSTEM
TASK
}
PARAM->BOOT
FLASH->BOOT
FLASH->DEBUGGER
GPIO->LED
GPIO->LED_MONO
TIME->LED_MONO
MATH->LED_MONO
TIME->LOG
STREAM->LOG
FIFO->LOG
UART->LOG
GPIO->RELAY
TIME->RELAY
TASK->SUPER_CYCLE
TASK [label="Scheduler"]
LIMITER->TASK[color="green"]
TIME->TASK[color="red"]
FLASH->TASK[color="blue"]
subgraph cluster_Computing{
    label = "Computing";
    style=filled;
    color=oldlace;
 COMPLEX
    CRC32
    CRC8
    CRC16
    DFT
    LIMITER
    INTERVAL
    MATH
    SOLVER
    SW_DAC
}
MATH->COMPLEX
VECTOR->COMPLEX
RAM->CRC8
RAM->CRC16
RAM->CRC32
TIME->LIMITER
FLASH->LIMITER
RAM->INTERVAL
ASM->MATH
FLOAT->MATH
    MATH->VECTOR
LIFO->SOLVER
STRING->SOLVER
ARRAY->SW_DAC
MATH->SW_DAC
TIME->SW_DAC
HEAP->SW_DAC
subgraph cluster_storage{
    label = "Storage";
    style=filled;
    color=oldlace;
    RAM
    REG
    ALLOCATOR
    NVS
    FLASH
FLASH_FS
    PARAM
}
RAM->ALLOCATOR
FLASH_FS->PARAM
PARAM->NVRAM
NVS->FLASH_FS
CRC8->FLASH_FS
FLASH->NVS

I2C->NAU8814
I2S->NAU8814
SW_DAC->I2S
LOG->NAU8814
PWM->NAU8814
}

Why was Graphviz chosen to describe the dependency graph?

The answer is simple. For the Graphviz language, there is a utility called render dot.exe, which itself composes and traces a graph in various topologies. This is very convenient for data visualization and saves a ton of time and effort.

Graphviz Compiler

The task of the Graphviz compiler is to build an adjacency list. It is assumed that in the original *.gv file all dependencies have the simplest form.

SOURCE->DESTINATION\n
SOURCE -> DESTINATION \n
SOURCE->DESTINATION   some text \n

As is known, text patterns are perfectly recognized by finite automata. Here is such a finite automaton that can catch the specified syntax.

I wrote a C program mix that processes a graphviz file and thus produces a view file as output.

ADC GPIO
ADC NVIC
ADC REG
...
...
UART GPIO
UART TIME
UDS ARRAY
UDS ISO_TP
UDS NVRAM
VECTOR MATH

In front of you is the graph adjacency list. This is the raw material, the metadata, for the standard tsort.exe utility.

Launching the tsort.exe program

It is important that the file that is input to the tsort utility does not contain the \r (carriage return) symbol. Otherwise, an emergency startup will occur and the utility will produce an incorrect result. The symbol must be used as a line separator. \n (line break) Like this.

Debugging

Here I launched my utility graphviz_to_tsort.exe

The utility is easily integrated into the general build script using this make script

$(info auto_init_script)

$(info WORKSPACE_LOC=$(WORKSPACE_LOC))
WORKSPACE_LOC := $(subst /cygdrive/c/,C:/, $(WORKSPACE_LOC))
$(info WORKSPACE_LOC=$(WORKSPACE_LOC))
#$(error WORKSPACE_LOC=$(WORKSPACE_LOC))

GRAPHVIZ_TO_TSORT_TOOL=$(WORKSPACE_LOC)../tool/graphviz_to_tsort.exe
$(info GRAPHVIZ_TO_TSORT_TOOL=$(GRAPHVIZ_TO_TSORT_TOOL))

.PHONY: auto_init
auto_init: $(SOURCES_DOT_RES)
	$(info RunAutoInit...)
	cd $(MK_PATH_WIN) && $(GRAPHVIZ_TO_TSORT_TOOL) gts $(SOURCES_DOT_RES)

After execution do everything, on top of that, I also automatically got this recommended initialization sequence for the build at_start_f437_generic_monolithic_m

REG FPU, NVIC, SYSTICK, THUMB, CORTEX_M4, PLL, ASM, FLOAT, 
CLOCK, MATH TIMER, TIME RAM DMA, ARRAY MCU, CRC, FLASH, 
INTERVALS, FIFO, GPIO, CRC8, NVS, UART, FLASH_FS, ALLOCATOR, HEAP, 
RS232, CAN, PARAM, LIMITER, DFT, SW_DAC, STREAM, ISO_TP, NVRAM, TASK, 
LIFO, STRING, I2C, I2S, LOG, PWM, VECTOR, CSV, STRING_READER, UDS, 
SUPER_CYCLE, SPI, SOLVER, RS485, RELAY, NAU8814, LED_MONO, LED, INTERVAL,
DEBUGGER, CRC32, CRC16, CLI, BOOT, ADC

Seems quite reasonable.

Results

The issue of designing the initialization function can be considered closed. It turns out to be pure mechanics and there is absolutely no room for improvisation. The graphviz_to_tsort utility, like a litmus test, will show the true sequence of launching the entire system.

By using GraphViz for the dependency graph we kill three birds with one stone:

1–We get the ability to synthesize documentation automatically using the cpp.exe preprocessor utility

2–We get the ability to visualize the graph using the dot.exe utility

3–We get the ability to perform topological sorting to identify the true procedure for initializing the entire system using the tsort.exe utility

If you need the graphviz_to_tsort.exe utility, I can send you the distribution. I hope that this text will help other microcontroller programmers to solve this eternal problem of incorrect initialization in huge assemblies.

Links

No.

Name of the text

URL

1

How to Compile a Microcontroller Initialization Function (Topological Graph Sorting with Make Utility)

https://habr.com/ru/articles/818917/

2

Generating dependencies within a program

https://habr.com/ru/articles/765424/

3

Topological sort

https://www.manniwood.com/2019_09_08/tsort.html

4

Graphviz Visual Editor

http://magjac.com/graphviz-visual-editor/

5

Remove a carriage return with sed

https://unix.stackexchange.com/questions/170665/remove-a-carriage-return-with-sed

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *