Lucassifoni

Lucassifoni

Two helper libraries, DtsBuddy and Pwmx

Hello,
I had started to work on two “convenience” libraries some time ago : DtsBuddy and Pwmx. Due to my SAAS starting to get clients, my Nerves work has halted for the time being.

I thought it would be better to still share them, if only because Frank gave me some of his time. Even without leveraging my code, they document the dts loading ceremony and the sysfs pwm interface. The biggest challenge is of course live-testing on real boards.

DtsBuddy

DtsBuddy (GitHub - Lucassifoni/dts-buddy: Research on a device tree source buddy to help people compile & load DTBOs at runtime in Elixir · GitHub) is useful to load device tree overlays at runtime. It provides a few system checks, and a friendly-ish API to load runtime overlays.

iex>DtsBuddy.checks()
 [{:device_tree_compiler_present, true}, {:overlays_enabled, false}]
iex> DtsBuddy.overlays_enabled?()
false
iex> DtsBuddy.enable_overlays()
:ok 
iex> DtsBuddy.overlays_enabled?()
true
iex> import DtsBuddy.Sigil
iex> overlay = ~DTS"""
    /dts-v1/;
    /plugin/;

    /* Example DTS for the MangoPI by Frank Hunleth */

    &{/} {
        gpios_leds {
            compatible = "gpio-leds";
            test_led@36 {
                label = "test-led-gpio36";
                gpios = <&pio 1 4 0>; /* GPIO36/PB4 */

                /* Blink LED at 1 Hz (500 ms on, off) */
                linux,default-trigger = "pattern";
                led-pattern = <1 500 1 0 0 500 0 0>;
            };
        };
    };
  """test
  {:ok, "/data/test.dtbo", "test"}
iex> DtsBuddy.load(overlay)
:ok 
iex> DtsBuddy.status("test")
:applied

There also is a Diagnostic module (dts-buddy/lib/dts_buddy/diagnostic.ex at main · Lucassifoni/dts-buddy · GitHub) based on the Circuits.GPIO diagnostics features, but it currently only works with the above overlay thought for a MangoPI MQ Pro. It would need to be extended to other boards.

 iex> {:ok, gpio} = Circuits.GPIO.open("GPIO12", :input)
      {:ok, %Circuits.GPIO.CDev{}}
      iex> reader = fn () -> Circuits.GPIO.read(gpio)
      #Function<>
      iex> DtsBuddy.Diagnostic.run_diagnostic(reader)
      [DtsBuddy.Diagnostic] Running system checks
      [DtsBuddy.Diagnostic] All system checks passed
      [DtsBuddy.Diagnostic] Loading a sample overlay
      [DtsBuddy.Diagnostic] Sampling GPIO with the user-supplied reader function for ~5 seconds
      [DtsBuddy.Diagnostic] Behavior of the activated GPIO looks correct with 5 LOW samples and 5 HIGH samples
      [DtsBuddy.Diagnostic] Unloading overlay testled
      [DtsBuddy.Diagnostic] Everything looks OK
      :ok 

Pwmx

Pwmx (GitHub - Lucassifoni/pwmx: Pure Elixir interface to Sysfs hardware-pwm on Linux · GitHub) is the simpler one and gives a few convenience helpers to work with Linux PWM :

iex> Pwmx.list_available_outputs()
[
    {"virtualchip0", 0},
    {"virtualchip0", 1},
    {"virtualchip0", 2},
    {"virtualchip0", 3}
]

The idea was to give both a “stateful” API :

iex> import Pwmx.Output
iex> {:ok, pid} = Pwmx.open("pwmchip0", 1) 
pid |> Pwmx.Output.enable |> Pwmx.Output.set_period(1_000_000, :ns) |> Pwmx.Output.set_duty_cycle(500_000)
# or 
pid |> Pwmx.Output.enable |> Pwmx.Output.set_period(1_000_000, :ns) |> Pwmx.Output.set_duty_cycle_normalized(0.5)

As well as a “stateless” API used by the stateful one :

iex> Pwmx.Backend.Sysfs.Ops.get_duty_cycle("pwmchip0", "1")

There’s a virtual backend allowing for unit testing too.
It would need to be tested on live boards too, but I did not manage to get PWM channels to output on the MangoPI MQ Pro’s header after having written a lot of overlays.

I hope something there will be useful to someone !
Best,
Lucas

Most Liked

Lucassifoni

Lucassifoni

I forgot to add : even if in the forseeable future I won’t be able to build on those two, I’m more than open to give push access to someone who would need runtime overlay / pwm helpers.

Otherwise I’m open to anyone forking, vendoring and modifying, and even pushing on hex later. I felt it was a bit too alpha to do so.

nix2intel

nix2intel

This is awesome work and now i’m properly inspired to go play with nerves. Very cool!

Lucassifoni

Lucassifoni

An eternity later I’ve been able to confirm those two helper libraries working on hardware (see Configuring PWM channel outputs on the Allwinner D1 (MangoPi MQ Pro) - #10 by Lucassifoni ) . Well, dts_buddy was already confirmed working, but pwmx was bothering me because of a pinmux issue.

Here is a sample of an iex session with both dts_buddy loading a runtime configfs overlay, and pwmx managing output pwm channels :

iex(livebook@nerves.local)1> import DtsBuddy.Sigil
DtsBuddy.Sigil
iex(livebook@nerves.local)2> DtsBuddy.enable_overlays
:ok
iex(livebook@nerves.local)3> DtsBuddy.overlays_enabled?
true
iex(livebook@nerves.local)4> compiled =
  ~DTS"""
  /dts-v1/;
  /plugin/;
  &{/soc/pinctrl@2000000} {
    pwm0_pb5:  pwm0-pb5  { pins = "PB5";  function = "pwm0"; };
    pwm1_pb6:  pwm1-pb6  { pins = "PB6";  function = "pwm1"; };
    pwm2_pb11: pwm2-pb11 { pins = "PB11"; function = "pwm2"; };
    pwm3_pb0:  pwm3-pb0  { pins = "PB0";  function = "pwm3"; };
    pwm4_pb1:  pwm4-pb1  { pins = "PB1";  function = "pwm4"; };
    pwm5_pd21: pwm5-pd21 { pins = "PD21"; function = "pwm5"; };
    pwm7_pb10: pwm7-pb10 { pins = "PB10"; function = "pwm7"; };
  };
  &{/soc/pwm@2000c00} {
    pinctrl-names = "default";
    pinctrl-0 = <&pwm0_pb5>, <&pwm1_pb6>, <&pwm2_pb11>, <&pwm3_pb0>,
                <&pwm4_pb1>, <&pwm5_pd21>, <&pwm7_pb10>;
    status = "okay";
  };
  """pwm7ch
{:ok, "/data/pwm7ch.dtbo", "pwm7ch"}
iex(livebook@nerves.local)5> DtsBuddy.load(compiled)
:ok
iex(livebook@nerves.local)6> DtsBuddy.status("pwm7ch")
:applied
iex(livebook@nerves.local)7> Pwmx.list_available_outputs()
[
  {"pwmchip0", 0},
  {"pwmchip0", 1},
  {"pwmchip0", 2},
  {"pwmchip0", 3},
  {"pwmchip0", 4},
  {"pwmchip0", 5},
  {"pwmchip0", 6},
  {"pwmchip0", 7}
]
iex(livebook@nerves.local)8>
outs = for ch <- [0, 1, 2, 3, 4, 5, 7], into: %{} 
  {:ok, pid} = Pwmx.Output.start_link({"pwmchip0", ch})
  pid |> Pwmx.Output.set_period(500_000, :us) |> Pwmx.Output.set_duty_cycle_absolute(250_000, :us)  |> Pwmx.Output.enable()
  {ch, pid}
end

I will now take the time to clean them up and publish them.

Where Next?

Popular in Discussions Top

matthias_toepp
I’d love to hear what people think about Wisp, the new Gleam web framework started by Gleam’s primary creator Louis Pilfold. Gleam, alon...
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New
mikl
I wanted to capitalize a string, and tried using String.capitalize(). That generally works well, until you try to capitalize a word like...
New
thojanssens1
It would be nice to be able to define a redirect from one route to another from the router.ex file. E.g.: redirect "/", UserController, ...
New
jer
I’ve been using umbrellas for a while, and generally started off (on greenfield projects at least) by isolating subapps based on clearly ...
New
PragTob
Hey everyone, this has been on my mind for some time and I’d love your input on it! TLDR: I feel like maps are superioer for storing and...
New
und0ck3d
Hello everyone! A few days ago I’ve created a topic here about how people were creating CMSs with Elixir and Phoenix. I’ve been studying...
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39523 209
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement