From e0cd78007efc02c9c0bb1ae4edc7c71c275b2502 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Tue, 19 Nov 2024 15:37:14 +0100 Subject: [PATCH] add JuliaC example --- Project.toml | 3 +- README.md | 8 +++++ examples/juliac/Project.toml | 4 +++ examples/juliac/juliac_pid.jl | 50 ++++++++++++++++++++++++++++++ examples/juliac/test_juliac_pid.jl | 30 ++++++++++++++++++ src/DiscretePIDs.jl | 4 +++ test/runtests.jl | 5 +++ 7 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 examples/juliac/Project.toml create mode 100644 examples/juliac/juliac_pid.jl create mode 100644 examples/juliac/test_juliac_pid.jl diff --git a/Project.toml b/Project.toml index 45761e0..f3cabeb 100644 --- a/Project.toml +++ b/Project.toml @@ -13,7 +13,8 @@ julia = "1.7" AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a" ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AllocCheck", "Test", "ControlSystemsBase", "FixedPointNumbers"] +test = ["AllocCheck", "Test", "ControlSystemsBase", "FixedPointNumbers", "JET"] diff --git a/README.md b/README.md index bb7b38b..c60e661 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,14 @@ plot([res, res_fp], plotu=true, lab=["Float64" "" string(T) ""]); ylabel!("u + d The fixed-point controller behaves roughly the same in this case, but artifacts are clearly visible. If the number of bits used for the fractional part is decreased, the controller will start to misbehave. +## Compilation using JuliaC +The file `examples/juliac/juliac_pid.jl` contains a JuliaC-compatible interface that can be compiled into a C-callable shared library using JuliaC. To compile the file, run the following from the `examples/juliac` folder: +```bash +julia +nightly --project /julia/contrib/juliac.jl --output-lib juliac_pid --trim=unsafe-warn --compile-ccallable juliac_pid.jl +``` +where `` should be replaced with the path to the Julia repository on your system. The command will generate a shared library `juliac_pid` that can be called from C. The file `examples/juliac/juliac_pid.h` contains the C-compatible interface to the shared library. + + ## See also - [TrajectoryLimiters.jl](https://github.com/baggepinnen/TrajectoryLimiters.jl) To generate dynamically feasible reference trajectories with bounded velocity and acceleration given an instantaneous reference $r(t)$ which may change abruptly. - [SymbolicControlSystems.jl](https://github.com/JuliaControl/SymbolicControlSystems.jl) For C-code generation of LTI systems. \ No newline at end of file diff --git a/examples/juliac/Project.toml b/examples/juliac/Project.toml new file mode 100644 index 0000000..ddecd7b --- /dev/null +++ b/examples/juliac/Project.toml @@ -0,0 +1,4 @@ +[deps] +ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" +DiscretePIDs = "c1363496-6848-4723-8758-079b737f6baf" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" diff --git a/examples/juliac/juliac_pid.jl b/examples/juliac/juliac_pid.jl new file mode 100644 index 0000000..bb790ad --- /dev/null +++ b/examples/juliac/juliac_pid.jl @@ -0,0 +1,50 @@ +module JuliacPID +import DiscretePIDs +import DiscretePIDs: DiscretePID +import Base.@ccallable + +const T = Float64 # The numeric type used by the controller + +# Set the initial PID parameters here +const pid = DiscretePIDs.DiscretePID(; K = T(1), Ti = 1, Td = false, Ts = 1) + + +@ccallable function calculate_control!(r::T, y::T, uff::T)::T + DiscretePIDs.calculate_control!(pid, r, y, uff)::T +end + +function set_K!(K::T, r::T, y::T)::Cvoid + DiscretePIDs.set_K!(pid, K, r, y) + nothing +end + +function set_Ti!(Ti::T)::Cvoid + DiscretePIDs.set_Ti!(pid, Ti) + nothing +end + +function set_Td!(Td::T)::Cvoid + DiscretePIDs.set_Td!(pid, Td) + nothing +end + +@ccallable function reset_state!()::Cvoid + DiscretePIDs.reset_state!(pid) + nothing +end + +@ccallable function main()::Cint + println(Core.stdout, "I'm alive and well") + u = calculate_control!(0.0, 0.0, 0.0) + println(Core.stdout, u) + + Cint(0) +end + + +end + +# compile using something like +# cd(@__DIR__) +# run(`julia +nightly --project --experimental /home/fredrikb/repos/julia/contrib/juliac.jl --output-lib juliac_pid --trim=unsafe-warn --compile-ccallable juliac_pid.jl`) +# run(`ls -ltrh`) # marvel at the smallness of the binary diff --git a/examples/juliac/test_juliac_pid.jl b/examples/juliac/test_juliac_pid.jl new file mode 100644 index 0000000..b01483d --- /dev/null +++ b/examples/juliac/test_juliac_pid.jl @@ -0,0 +1,30 @@ +# Run this file with the same version of julia that you used to compile the shared library. +cd(@__DIR__) + +const T = Float64 +lib = Libc.Libdl.dlopen("/home/fredrikb/.julia/dev/DiscretePIDs/examples/juliac/juliac_pid.so") +const calc = Libc.Libdl.dlsym(lib, :calculate_control!) + +function pid(r::T, y::T, uff::T) + ccall(calc, T, (T, T, T), r, y, uff) +end + +pid(0.0, 0.0, 0.0) # test + +using ControlSystemsBase, Plots +Tf = 15 # Simulation time +Ts = 0.01 # sample time + +P = c2d(ss(tf(1, [1, 1])), Ts) # Process to be controlled, discretized using zero-order hold + +ctrl = function(x,t) + y = (P.C*x)[] # measurement + d = 1 # disturbance + r = 0 # reference + u = pid(T(r), T(y), T(0)) + u + d # Plant input is control signal + disturbance +end + +res = lsim(P, ctrl, Tf) + +plot(res, plotu=true); ylabel!("u + d", sp=2) \ No newline at end of file diff --git a/src/DiscretePIDs.jl b/src/DiscretePIDs.jl index 06f9ff9..c3bfe6e 100644 --- a/src/DiscretePIDs.jl +++ b/src/DiscretePIDs.jl @@ -124,6 +124,7 @@ function set_K!(pid::DiscretePID, K, r, y) pid.bi = K * pid.Ts / pid.Ti pid.I = pid.I + Kold*(pid.b*r - y) - K*(pid.b*r - y) end + nothing end """ @@ -139,6 +140,7 @@ function set_Ti!(pid::DiscretePID{T}, Ti) where T else pid.bi = zero(T) end + nothing end """ @@ -151,6 +153,7 @@ function set_Td!(pid::DiscretePID, Td) pid.Td = Td pid.ad = Td / (Td + pid.N * pid.Ts) pid.bd = pid.K * pid.N * pid.ad + nothing end @@ -193,6 +196,7 @@ function reset_state!(pid::DiscretePID) pid.I = zero(pid.I) pid.D = zero(pid.D) pid.yold = zero(pid.yold) + nothing end end diff --git a/test/runtests.jl b/test/runtests.jl index 904bb45..9f0c5d5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using DiscretePIDs using Test using ControlSystemsBase using AllocCheck +using JET @testset "DiscretePIDs.jl" begin @@ -95,6 +96,10 @@ reset_state!(pid) res3 = lsim(P, ctrl, Tf) @test res3.y == res2.y +@test_opt pid(1.0, 1.0) +@test_opt pid(1.0, 1.0, 1.0) +# @report_call pid(1.0, 1.0) + ## Test with FixedPointNumbers using FixedPointNumbers T = Fixed{Int16, 10} # 16-bit signed fixed-point with 10 bits for the fractional part