19 Jun / Embedded Code Generation: C/C++ Code from block diagram

IIR Filter for Code Generation

The featured image of this post is the block diagram of the IIR filter used as a testbed. The previous post gives more details about the roadmap of this series. Code generation from a visual description of the signal flow is not a difficult task, but it is complex depending on how far you want to go. In my experience, I noticed the following “intrication factors”:

  • Try to cover different kinds of models (e.g. static models, models with discrete states, continuous states, state machines, conditional execution, et cetera).
  • Develop your own basic search functions.
  • Start coding without a well-defined code model.

Unfortunately, I did the first two and spent a lot of time with it. The third one would be a conceptual flaw, this was not the case. I can reinforce that a well-defined model makes things much much simpler than expected. This is obvious but is always worth to say. There are code generators which do the job at cost of an unreadable code. You could say that once the code is generated by the machine, you would not like to look into the details, but just compile and use it. But experience shows that companies spend a lot of time in software test campaigns which often require manual changes in automatically generated codes. For this reason, a clean code is always a good idea. But… what is a cleanly generated code?

In my point of view, as a control engineer, a clean code is the one which can be easily compared to my block diagram. Just because it (the block diagram) is the closest and fresh reference to what I really want to achieve. Small amount of files, suggestive and readable variables and function names, and useful comments are obvious and subjective characteristics which can be inherited from a good development planning experience. Nowadays, this last aspect is not so difficult to do because there are several references and experiences with Git and Coding Standards (e.g. MISRA and JSF) which would put us in shoulders of giants. But honestly, generate a code which looks like something really extracted from the block diagram is not a thing that you see even in commercial code generators.

In the example shown in this post, we are not going to go into the details of basic search algorithms for block diagrams. Exactly for the reasons cited above. Just have a look on function dependencies that we would a achieve for a stable search, ordering, and classification algorithm:

For sake of simplicity, let us consider the topmost search layer to generate code from a block diagram. This can be seen in figure below:

Topmost code generation search

The algorithm applied to our testbed block diagram generates the dependency structure in table below:

Child 1 
Child 2
filterBD/y Outport filterBD/gainSmall – 
filterBD/gainSmall Gain filterBD/Add – 
filterBD/Add  Sum filterBD/u filterBD/gainBig
filterBD/gainBig Gain filterBD/Unit Delay – 

Note that a proper ordering of the equations does not need so much information initially. We need the name of the block, its children, level in the context of the signal flow, and type of block. Type and name are used for further naming and handling with the help of dictionaries with specific information about each type of block. However, this list as it is allows us to order the equations properly by assuming simple rules (remember the well defined code model):

  1. A block which performs processing will always have the following method: returnOutput(inputSignal).
  2. Inputs and outputs are either scalar or array. Simple method calls can be used for either arrays or scalars as long as the content of these arrays and scalars are populated accordingly.
  3. The current output of a block can be accessed anytime by a method called current Output.

These simple rules applied to the ordered list results in the following code:

outgainBig = gainBig.returnOutput(UnitDelay.currentOutput);
inAdd[0] = ud.u;
inAdd[1] = outgainBig;
outAdd = Add.returnOutput(inAdd);
outgainSmall = gainSmall.returnOutput(outAdd);
y = outgainSmall;

Even extensive pieces of code can be easily read when those essential rules are applied accordingly. We have generated codes for other complex models which can be read easily, mainly if the person uses the block diagram itself as documentation. The next step of this series of articles is about how to change this code into Assembly code prior to actually and finally turn it into a binary code able to be stored in MCU’s program memory.

Post a comment