@@ -293,3 +293,102 @@ savefig("plot4_LinMPC.svg"); nothing # hide
293293Note that measured disturbances are assumed constant in the future by default but custom
294294`` \mathbf{D̂} `` predictions are possible. The same applies for the setpoint predictions
295295`` \mathbf{R̂_y} `` .
296+
297+ ## Generating C code
298+
299+ The [ ` LinearMPC.jl ` ] (@extref LinearMPC) package extension provides code generation
300+ capabilities to export the controller as optimized and standalone C code. First, install the
301+ package with:
302+
303+ ``` test
304+ using Pkg; Pkg.add("LinearMPC")
305+ ```
306+
307+ The feedforward MPC controller can be converted to a [ ` LinearMPC.MPC ` ] ( @ref ) object using:
308+
309+ ``` @example 1
310+ import LinearMPC
311+ c_mpc_d = LinearMPC.MPC(mpc_d);
312+ nothing # hide
313+ ```
314+
315+ We test the converted controller in closed-loop to verify that it behaves identically to the
316+ original one, notably because of the two different solvers:
317+
318+ ``` @example 1
319+ function test_c_mpc_d(c_mpc_d, model)
320+ N = 200
321+ ry, ul = [50, 30], 0
322+ dop = 20
323+ u = model.uop
324+ u_data, y_data, ry_data = zeros(model.nu, N), zeros(model.ny, N), zeros(model.ny, N)
325+ for i = 1:N
326+ i == 51 && (ry = [50, 35])
327+ i == 101 && (ry = [54, 30])
328+ i == 151 && (ul = -20)
329+ d = [ul .+ dop]
330+ y = model()
331+ x̂ = LinearMPC.correct_state!(c_mpc_d, y, d)
332+ u = LinearMPC.compute_control(c_mpc_d, x̂; r=ry, d=d, uprev=u)
333+ u_data[:,i], y_data[:,i], ry_data[:,i] = u, y, ry
334+ LinearMPC.predict_state!(c_mpc_d, u, d)
335+ updatestate!(model, u + [0; ul])
336+ end
337+ return u_data, y_data, ry_data
338+ end
339+ setstate!(model, zeros(model.nx))
340+ LinearMPC.set_state!(c_mpc_d, zeros(c_mpc_d.model.nx))
341+ u_data, y_data, ry_data = test_c_mpc_d(c_mpc_d, model)
342+ plot_data(t_data, u_data, y_data, ry_data)
343+ savefig("plot5_LinMPC.svg"); nothing # hide
344+ ```
345+
346+ ![ plot5_LinMPC] ( plot5_LinMPC.svg )
347+
348+ The closed-loop simulation matches the results of the previous section, as expected. We
349+ can now generate the C code using:
350+
351+ ``` julia
352+ LinearMPC. codegen (c_mpc_d; dir= " codegen" , fname= " mpc_funcs" )
353+ ```
354+
355+ The three C functions to call at each control period are declared in the generated
356+ ` codegen/mpc_funcs.h ` file, and they receive pointers of ` c_float ` arrays:
357+
358+ ``` C
359+ void mpc_correct_state (c_float* state, c_float* measurement, c_float* disturbance);
360+ int mpc_compute_control(c_float* control, c_float* state, c_float* reference, c_float* disturbance);
361+ void mpc_predict_state(c_float* state, c_float* control, c_float* disturbance);
362+ ```
363+
364+ For example, on Linux, you can add the following code in a new `main.c` file:
365+
366+ ```C
367+ #include "mpc_funcs.h"
368+ #include <stdio.h>
369+ int main(){
370+ // initialize arrays:
371+ c_float u[2] = {20, 20};
372+ c_float x[6] = {0, 0, 0, 0, 0, 0};
373+ c_float r[2] = {50, 35};
374+ c_float y[2] = {50, 30};
375+ c_float d[1] = {20};
376+ // execute one control period:
377+ mpc_correct_state(x, y, d);
378+ mpc_compute_control(u, x, r, d);
379+ mpc_predict_state(x, u, d);
380+ // print the computed control:
381+ printf("The computed u value is: [%f, %f]\n", u[0], u[1]);
382+ return 0;
383+ }
384+ ```
385+
386+ compile with using ` gcc *.c -o main.bin ` and run it with ` ./main.bin ` . The printed ` u ` value
387+ should be identical to:
388+
389+ ``` @example 1
390+ LinearMPC.set_state!(c_mpc_d, zeros(c_mpc_d.model.nx))
391+ x̂ = LinearMPC.correct_state!(c_mpc_d, [50, 30], [20])
392+ u = LinearMPC.compute_control(c_mpc_d, x̂; r=[50, 35], d=[20], uprev=[20, 20])
393+ println("The computed u value is: $(round.(u, digits=6))")
394+ ```
0 commit comments