csim2 is a very simple time domain simulation tool. It is designed to enable implementing dynamic systems as block diagrams (similar to simulink). Individual blocks can be written and then be composed together to create more complex systems.
Before this tool will make sense you'll need to know something about differential equations. I'll attempt to put it in a nutshell here but you may want to study up yourself. If you are comfortable with differential equations and/or state space you can probably skip this section.
A differential equation is just an eqation that is a function of a variable and its derivatives. The classic (mechanical) example is a mass spring damper. Int this case the variable is position and its derivatives are velocity and acceleration. The system looks like this:
|----> +x
_______
>|----K----| |
>| | M |---->F
>|----C----|_______|
This is supposed to look like a wall on the left connected to a weight on the right via a spring and damper.
Where:
- M is the mass (force due to acceleration = mass * acceleration)
- C is the damping coefficient (damping force = damping coefficient * velocity)
- K is the spring constant (spring force = spring constant * position)
- F is an externally applied force
- +x indicates the positive x direction
Summing the forces results in the following differential equation:
M*xdotdot + C*xdot + K*x = F
I've used dot
to indcate a time derivative so xdot
is the first derivative of position
dx/dt (velocity) and xdotdot
is the second derivative of position d^2x/dt^2 (acceleration).
In csim2 the differential equation is defined by the physics_function
for a block (see src/block.h
).
The physics function calculates the state derivative as a function of state, input, and time.
What are the state derivative and state? Start by identifying the highest and lowest
order derivatives of x that appear in the differential equation. In our case the highest
is xdotdot
and the lowest is x
. That means that xdotdot
is definitely a state derivative
and x
is definitely a state. Everything in-between are both state
derivatives and states. In our case that's xdot
.
state
derivative state
[ xdotdot ] -> [ xdot ]
[ xdot ] -> [ x ]
Notice how xdot
appears in both the state and state derivative. This isn't a problem but
calling them the same thing can get messy. I fix this by prefixing the state derivatives with
a d
instead of a suffix of dot
. They have the same meaning but it allows me to easily
identify the state derivative versus the state.
state
derivative state
[ dxdot ] -> [ xdot ]
[ dx ] -> [ x ]
Now back to the example. In the physics function we need to calculate the state derivative as a function of state:
[ dxdot ] = [ (F - C*xdot - K*x)/M ]
[ dx ] = [ xdot ]
See examples/mass_spring_damper_example.c
for a working demo of this example.
For the purposes of this library a block is basically just the physics function for a given system.
See src/block.h
for the definition of physics_function
and struct block
. The physics function
is the differential equation that converts input and state into state derivative. The library includes
the definition of some basic blocks (see src/block_basic.c
) but the more interesting blocks will
be the ones that you implement. The blocks that implement the physics that you care about.
Solvers only ever deal with a single block (although the block may be composed of several
other blocks by using a block_system
). Their job is to integrate the state derivative and
produce the next state. See src/block_solver.c
.
src/block_system.c
is intended to simplify the composition of multiple blocks into a new block.
Unlike a standard block, block systems do not have a physics function (at least not one that you need
to worry about) or state. Instead the state is made up of all of the child block states and you have to implement
an update_child_inputs_function
(see src/block_system.h
). The update child inputs function is where
the wiring between blocks happens. The following block diagram is implemented in example/block_system.c
:
_____________________________________________________
| block system |
| _______ __________ __________ |
system | | | | | | | |
input ---|-->| scale |--->| block[0] |--->| block[1] | |
| |_______| |__________| |__________| |
| | | _____ |
| | | | | |
| ---------------)--->|dx[0]| |
| | | | | block system
| --->|dx[1]|-->|----> dx
| |dx[2]| |
| |_____| |
|_____________________________________________________|
Note: that block[0] has a single input and a single state while block[1] has a single input and two states.
The block system itself has a single input. If you were to inspect the num_states
and num_inputs
properties
of the struct block representing this block system you would find that it has 3 states (sum of the number of
child states) and 3 inputs (sum of the number of child inputs plus the number of system inputs). This is an
important detail when it comes time to allocate memory for the solver
In this example the system input is scaled (multiplied by some constant) before being sent on to block[0].
Then the first (and only) state of block[0] is used as the input into block[1]. Hopefully this is fairly
obvious after looking at the example update_c_inputs
implementation in example/block_system.c
.
The rest of the diagram is showing what the block system is doing for you. Essentially each blocks
physics function is called in turn and the results are stacked in the block system output.