Imagine software as a snow ball that you want to move forward. As uncle Bob said, if you don't put aside time to clean software, in time its inertia will increase. In the beginning the ball will be light and you will add features, i.e. move the ball easily. With time the code will become messier and changes that took 1 day in the beginning start to take first a couple of days and later weeks. The snow ball gets heavier whenever you move it further. In other engineering disciplines, the more you work on a product, the better it gets, at least it doesn't get much worse. Good luck to software project managers who try to estimate when the project will be done, because the further down the road, the less reliable the estimates are. For any non-trivial project, you can only come up with reasonable time estimates if the code is continuously cleaned up.
C++ and MATLAB Simulink tips for HWIL simulation software engineers
Friday, December 18, 2020
Monday, November 30, 2020
Difference between Simulink model and executable results
When you generate code from a Simulink model, build an executable and compare the results of Simulink model and excutable, you will usually notice that there are differences. To make sure that these differences are normal, do the following:
- Verify that Simulink and executable inputs are exactly (up to 16 digits) the same. Use format long before printing Simulink inputs and outputs. Do the same for executable input and output printing. Most of the time, differences in results are due to differences in significant digits. Pro Tip: If your Simulink model uses constant blocks for all inputs, when you generate C code, those inputs will be embedded into the code. If you build this code and don't touch any of the inputs, you can be sure that Simulink and executable inputs are the same without doing anything extra.
- In the executable main(), use double (64bit floating point) data type for inputs and outputs.
- Run the executable two times and verify that results of these two runs are exactly the same. If they are not, this is possibly due to a random number generator in model. Add random seed to input so that same random numbers will be generated which then guarantees consecutive run results being the same.
- Build executable as 32bit, run and save results. Build as 64bit, run and save results. Compare 32bit and 64bit results. The difference in these results will give you an idea of difference order of magnitude to expect.
- Verify that the difference between Simulink run and executable run is of similar order of magnitude to the difference between 32bit and 64bit runs.
Letters to a novice programmer
When the problem you need to solve has only two states, don't create a monster (e.g. FizzBuzz Enterprise), with state machine builders, polymorphism and templates sprinkled around! Use simple switch statements so that a developer can easily move through code with ctrl+left clicks, without ever needing to use ctrl+F.
Monday, September 14, 2020
Tip when reading data from file
fileInfo = dir(fileName);fprintf("File date: %s\n", fileInfo.date);
Friday, September 11, 2020
Printing all permutations of a string
If you would like to print three character permutations, you have to add another for loop:
As you can see, this is not generalizable because you have to keep adding or deleting for loops manually. Also notice that with each loop, the p[] index is increased by one. We could use this fact to write a recursive function that could handle all character permutations:
This is a nice example of first writing the simple cases, seeing a pattern and reaching the general case, i.e. inductive reasoning.
Friday, August 21, 2020
Clean Code
Software project requirements are often not fully known in advance, requiring teams to learn a significant portion of the necessary information during the development process. This frequently leads to the addition of new features after the initial requirements and design phases, as well as extensive debugging. A key property of software is its ability to improve a system without changing the hardware. To take advantage of this flexibility, software must be designed in such way that it is easily modifiable. Otherwise, it risks becoming as inflexible as hardware. Since most of the development time is spent reading and modifying code, it is imperative to optimize for this fact which means writing clean code, i.e. code that is easy to understand.
Clean Code - Uncle Bob / Lesson 1:
- [28:51] It is not the old people training the new people it is the old code training the new people.
- [31:06] No one writes clean code first because it is just too hard to get the code to work... Cleaning code requires as much time as making it work in the first place. Nobody wants to put that extra effort in. You are not done when it works, you are done when it's right/clean.
- Clean code should read like well written prose.
- [46:30] Every line in a function must be at the same abstraction level.
- [58:45] A function should do one thing, i.e. you should not be able to extract another function from it.
- Don't pass a boolean to a function. Bad: setCentered(true, false). OK: setVisible(true). Using enumeration variables or constants rather than a boolean variable, you make your code more readable, e.g.repaint (PAINT :: immediate).
- Replace switch/if with polymorphism
- Command and query separation: A function returning void must have a side effect [change system state], a function returning a value should have no side effect. With this convention, when you see a function that returns a value, you assume it is safe to call it because it should leave the system in the same state before you called it.
- [31:27] The design and code should get better with time, not get worse [continuous improvement].
- [32:15] If you touch it [the messy code], you will break it. If you break it, it becomes yours(!) Minimize personel risk vs improving a messy system.
- Unit tests results in fearless competence.
- [35:32] Always check it in a little bit better than you found it.
Monday, August 10, 2020
TortoiseSVN: List all externals in a repository
When you have a repository that uses other repos as externals, the easiest way is to right click on your local repo root folder and select Branch/tag. The window will display at the bottom all externals in that repo.
When you tag a repo, don't forget to check all externals so that when at a later date you go back to that tag, the externals will also be in their revision of the tag date. If you do not check, they will come back as head revisions wich might cause problems if your tagged non-external code is not consistent with the latest revision of external repos. The only problem with this scenario is that if you test your code and then let time pass before tagging, another developer might update one of the externals and when you tag, TortoiseSVN uses the revision on the server, not your local.
If you want to be sure that you tag the external revision in your local, you should use svn copy [local path] [server path] --pin-externals -m "this is a test"
Note that you need version 1.9 or later of TortoiseSVN in order to use pin-externals.
Friday, July 17, 2020
Friday, June 19, 2020
Letters to a novice programmer
myAlgo.setInputs(inputStruct);The correct way is to refactor calculate() method as follows:
myAlgo.calculate();
myAlgo.getOutputs(outputStruct);
outputStruct out = calculate(inputStruct)Using this version would save the user of myAlgo from a couple of lines, he would not face the risk of forgetting to set inputs. In the previous version, if you forget to call setInputs(), the compiler will happily build your code and you will waste time finding the bug at run time. In the new version, if you forget to pass inputs to calculate(), it won't build and you will instantly see the bug.
Whenever you have multiple public initialization functions, try to combine them into a constructor. Your design should be such that when your code builds successfully, you should be confident that it has no initialization or finalizations related bugs. Let the compiler help you.
Tuesday, June 16, 2020
From Workspace uses first element as time
The reason is that From Workspace treats the first element of a vector as time and the rest as data.
Tuesday, May 12, 2020
Applying derivative and integral on noisy data
If you have position as input and calculate speed and acceleration, if there is any noise in the signal, it will be amplified. If you have acceleration as input and calculate speed and position via integration, noise will be attenuated. Another advantage of using integration instead of derivation is that we can use linear interpolation since when integrating, we don't need smooth second derivatives. This both decreases computational load and lag due to two points in linear interpolation compared to four points in spline. See below Matlab code for an example:
Thursday, May 7, 2020
Mex only on file change
- Create a hash list file that consists of file names used by s-functions and corresponding hashes.
- Before simulation start, in InitFcn,
- Calculate hashes of files used by s-functions and compare them with the values saved in hash list file. Mark files whose hashes are different as "changed".
- If an existing binary mex file date is earlier than any of the dates of the source files used in its build, mark that binary as "changed".
- Mex files marked as "changed".
From Workspace Sample Time
If you save outputs of plant&controller with to workspace blocks, add from workspace blocks to plant only model and run it with outputs of plant&controller, you might see differences between results. In order to have a close match of results, you shouldn't leave the sample time of from workspace blocks as zero, you have to enter them equal to sample time of plant&controller model.
Tuesday, March 24, 2020
Documenting a real time system
- Introduction: Why does the system exist, what function does it serve?
- Components of the system, how they communicate with each other (serial, ethernet etc.). Photos of the real system and a mind map denoting peripherals (sensors, actuators) would be handy:
- Setup: All software and hardware setup procedures, links to software installation folders.
- Sanity tests to verify that setup works properly. For example, have a test to verify that the system really works in real time by comparing system time with an external time source. A very crude test would be using the timer in your smartphone.
- Development use cases: How to add a new state to the state machine.
- Design:
- Architecture.
- Main workflow, especially external input/output.
- Task priorities and rationale.
- Design decisions and their reasons. Why is the current design the best one under existing constraints (time, budget, experience)? Why is there no simpler way to satisfy requirements? What were the disadvantages of the alternatives. Examples: Why did you write your own file transfer protocol instead of using an existing one? Why have you not used an OS like VxWorks but choose Micrium? Couldn't you have done it without an OS?
- Troubleshooting guide for frequent problems.
Thursday, March 12, 2020
HWIL development workflow
If there are differences between Simulink and Visual Studio runs, most of the time it is due to you forgetting to equalize all inputs. If you are sure about inputs being the same, the remaining sources of difference are details of floating point representations and sometime a block in Simulink generating wrong code. For example Aerospace blockset ECEF2LLA block code results in latitude lagging one time step behind, which does not occur during a Simulink run.
If there are differences between software running on hardware and Visual Studio results, it might be an indication of insufficient stack size allocation or tasks not meeting their deadlines. If you are lucky and have an advanced real time OS like VxWorks, it might throw a segmentation violation if there is too little stack available. If you don't want to count on luck, you have to test thoroughly, preferably using automated tests.
Monday, March 9, 2020
Stack corruption due to different pointer types
When you run it, you will see that the val_f2 is zero (should be 5):
Visual Studio 2015 will only display stack corruption message when you build in debug mode. In release mode, you don't get a message.
If you copy the same code to a cpp file, Visual Studio will use the C++ compiler and it will not build the code, saying that types are incompatible.
Unconventional interfacing
Sunday, March 8, 2020
Simulink: Passing strings to s-functions
Wednesday, March 4, 2020
Verifying that your system runs in real time
For you own sanity, one of the first tests you should run on hardware is to verify that your software runs in real time. The simplest way is to run for 5 minutes by checking with an independent time source (like the stopwatch app on your phone or Windows system time) and track the simulation time. You cannot rely on time values provided by the RTOS. Time between start and stop should be 5*60 = 300s. If not, contact the person responsible for the card.
After you have established that simulation time and real time are the same, you can start to test the system under different loading conditions to verify that all your software tasks meet their deadlines. If not, you update your software, because at this stage you are sure that the hardware is ok in the real time sense.
Tuesday, March 3, 2020
Embedding data into code
When data is large like Geoid Height Data (721*1440 elements), you cannot embed it into a header file. If you do and try to build in Visual Studio, the build might hang. You have to embed data into a cpp file and expose it in header via extern keyword.
To use two dimensional (2D) data in a function you can use the following example:
Debug C++ file
- Compile file: mex -g mySFunction.cpp. This will create mySFunction.mexw64 and mySFunction.mexw64.pdb.
- Open Visual Studio, open mySFunction.cpp
- Click Debug/Attach to process, select Matlab.exe.
- Set breakpoint in Visual Studio
- Run Simulink
- Code will stop in Visual Studio.
Thursday, January 16, 2020
Using workspace variables in C++ Mex functions
When you run mexDriver.m, you will get an output similar to this:
Tuesday, January 7, 2020
Comparing two models
- Open model in Simulink.
- Go to Analysis menu, click Compare Simulink XML Files...
- Select the model file you want to compare with the opened one.
- Simulink will open a window displaying the differences. When you click on a line, it will display both models, highlighting the differences.
Monday, January 6, 2020
File closing bug
After one day of debugging, I saw that I forgot to close a file in another unrelated s-function. I guess after a certain number of unclosed file pointers, fopen() commands start to fail. This reminded me of an old heap corruption bug that took me months to find an fix.