Konan has asked for the wisdom of the Perl Monks concerning the following question:

Ok. Enough lurking. Time to sign up. Perl amateur here, although using it since the turn of the century when I switched to Linux.

So... I use the GPIO's of Raspberry PI's for... well, lot of things, always using Perl for the code. Works great with the Cpan Device::BCM2835 module.

However, there is no call in the export list that allows for a variable to be used for input except for the $inputcmd. The actual call is...

Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_V2_GPIO_P1_07, $inputcmd);

Thus far I have been hardcoding the call for every pin I need, which is ridiculous, but my efforts to make that 07 into a variable apparently are not letting me see the forest for the trees. ChatGPT comes close, but every example (lots of them) dies on the "strict subs" error or just flat doesn't work.

This should be easy, but again, I am hung up on it. Anybody able to show me what I am overlooking?

Thanks.

Replies are listed 'Best First'.
Re: How to make a variable in hard call.
by kcott (Archbishop) on May 07, 2023 at 17:37 UTC

    G'day Konan,

    Welcome to the Monastery.

    [Disclaimer: I am not a user of Device::BCM2835. The following is based on its source code.]

    gpio_fsel() is not exported; however, you can create an alias so that you don't need to use the fully-qualified subroutine name repeatedly (see "perldata: Typeglobs and Filehandles" for details):

    *gpio_fsel = \&Device::BCM2835::gpio_fsel;

    RPI_V2_GPIO_P1_07 is exported (line 582); it's part of our @EXPORT = qw(...); which starts on line 317.

    Your code could end up looking something like this:

    #!/usr/bin/env perl use strict; use warnings; use Device::BCM2835; ... *gpio_fsel = \&Device::BCM2835::gpio_fsel; ... my $inputcmd = ...; ... gpio_fsel(RPI_V2_GPIO_P1_07, $inputcmd); ...

    To use a variable in place of RPI_V2_GPIO_P1_07, you could do something like the following. Bear in mind, without seeing your code, I'm very much guessing with regards to the context.

    my %rpi_v2_gpio_p1 = ( 3 => RPI_V2_GPIO_P1_03, 5 => RPI_V2_GPIO_P1_05, 7 => RPI_V2_GPIO_P1_07, ..., 40 => RPI_V2_GPIO_P1_40, ); ... gpio_fsel($rpi_v2_gpio_p1{$_}, $inputcmd) for 3, 5, 7, ..., 40;

    In other posts, I see variations using dispatch tables and eval. These may well be more appropriate for your code.

    If none of the suggestions so far are suitable, you'll need to show us more code such that we can see how gpio_fsel() is being used.

    — Ken

      Taken a couple of steps further, we can replace

      my %rpi_v2_gpio_p1 = ( 3 => Device::BCM2835::RPI_V2_GPIO_P1_03, 5 => Device::BCM2835::RPI_V2_GPIO_P1_05, 7 => Device::BCM2835::RPI_V2_GPIO_P1_07, ..., 40 => Device::BCM2835::RPI_V2_GPIO_P1_40, );
      with
      my @rpi_v2_gpio_p1; for ( @Device::BCM2835::EXPORT_OK ) { next if !/^RPI_V2_GPIO_P1_(\d+)\z/; my $pin = 0 + $1; my $val = do { no strict "refs"; "Device::BCM2835::$_"->() }; $rpi_v2_gpio_p1[ $pin ] = $val; }
      or
      my @rpi_v2_gpio_p1; for ( @Device::BCM2835::EXPORT_OK ) { next if !/^RPI_V2_GPIO_P1_(\d+)\z/; my $pin = 0 + $1; my $val = ( \&{"Device::BCM2835::$_"} )->(); $rpi_v2_gpio_p1[ $pin ] = $val; }

        Just a quick note: A hash with named pins might be easier to understand and adapt in the long run. E.g. name pins according to their function in code instead of their pin number. This is especially true if, say, the hardware changes. Pin "40" doesn't say anything, but "RTC_IRQ" tells me it's the IRQ pin for the real time clock.

        Additionally, named pins are much easier to find&replace in code. If you search for "1" you could find a lot of stuff not relevant to the problem at hand, "SPI_CLK" on the other hand would be pretty unique to that pin functionality:

        my %rpi_v2_gpio_p1 = ( SPI_CLK => Device::BCM2835::RPI_V2_GPIO_P1_03, SPI_CS => Device::BCM2835::RPI_V2_GPIO_P1_05, SPI_MOSI => Device::BCM2835::RPI_V2_GPIO_P1_07, SPI_MISO => Device::BCM2835::RPI_V2_GPIO_P1_07, ..., );

        That's the same stuff i do in C for my Arduino projects:

        ... globals.h: #define PIN_RTC_IRQ 2 ...Firmware.ino: #if RADIODUINO_BOARD_REVISION < 2 // RTC PIN uses internal pullup resistor pinMode(PIN_RTC_IRQ, INPUT_PULLUP); #else // REV B and up already have an external pullup pinMode(PIN_RTC_IRQ, INPUT); #endif ... alarms.cpp: attachInterrupt(digitalPinToInterrupt(PIN_RTC_IRQ), RTCIRQ, FALLING);

        If, say, the next hardware revision moves the IRQ pin to PIN 3, all i would need to change is globals.h:

        #if RADIODUINO_BOARD_REVISION < 4 #define PIN_RTC_IRQ 2 #else #define PIN_RTC_IRQ 3 #endif

        And with named pin mappings, it's also way easier to find all parts of the code that touch that specific pin:

        $ fgrep PIN_RTC_IRQ * alarms.cpp: attachInterrupt(digitalPinToInterrupt(PIN_RTC_IRQ), RTC +IRQ, FALLING); Firmware.ino: pinMode(PIN_RTC_IRQ, INPUT_PULLUP); Firmware.ino: pinMode(PIN_RTC_IRQ, INPUT); globals.h:#define PIN_RTC_IRQ 2

        So, in my opinion, don't use "magic numbers", use proper names for everything.

        PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP

      I note that Device::BCM2835 has many functions with fully-qualifed names that aren't exported. You've shown use of one of these:

      Device::BCM2835::gpio_fsel(...);

      and I showed how to create an alias:

      *gpio_fsel = \&Device::BCM2835::gpio_fsel;

      If you're actually using a number of these subroutines, you can make aliases for all in a similar fashion to ++ikegami's "Taken a step further". For example, if you're using Device::BCM2835::gpio_fsel(...), Device::BCM2835::gpio_set(...) and Device::BCM2835::gpio_clr(...), you could write:

      for my $sub (qw{gpio_fsel gpio_set gpio_clr}) { no strict 'refs'; *$sub = \&{"Device::BCM2835::$sub"}; }

      and subsequently call:

      gpio_fsel(...); gpio_set(...); gpio_clr(...);

      Update (typo fix): I had written "(...}" instead of "(...)"; then, with the power of copy-paste, propagated that typo two more times. I've fixed all three instances.

      — Ken

        As a side note: the module claims to export nothing by default, which is contradictory to the source.
Re: How to make a variable in hard call.
by SankoR (Prior) on May 07, 2023 at 13:57 UTC
    I think you're looking for a dispatch table. You could dynamically create the call with an embedded variable name with UNIVERSAL::can(...) but let's not.

    Your code could look something like this:
    my %GPIO = (
        1 => \&Device::BCM2835::RPI_V2_GPIO_P1_01,
        # [...]
        7 => \&Device::BCM2835::RPI_V2_GPIO_P1_07
    );
    
    # [...]
    
    my $pin = 7; # Or however you decide on the correct pin
    Device::BCM2835::gpio_fsel($GPIO{$pin}->(), $inputcmd);
    
Re: How to make a variable in hard call.
by LanX (Saint) on May 07, 2023 at 15:26 UTC
    > Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_V2_GPIO_P1_07, $inputcmd);

    I can't test since I have no RPI, but Device::BCM2835 seems to export all functions and constants by default.

    so please try

    gpio_fsel(RPI_V2_GPIO_P1_07, $inputcmd);

    alternatively preceded by

    use Device::BCM2835 qw/gpio_fsel RPI_V2_GPIO_P1_07 ... etc/;

    or

    use Device::BCM2835 ':all';

    This should shorten your code considerably.

    The rest of your question is a bit fuzzy, please explain.

    probably something like my $pin1 = RPI_V2_GPIO_P1_07 is just what you want?

    Cheers Rolf
    (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
    Wikisyntax for the Monastery

Re: How to make a variable in hard call.
by Bod (Parson) on May 09, 2023 at 23:25 UTC

    I have never used Device::BCM2835 but looking at it, it appears to be a complex way of doing something relatively simple. Please do correct me if there is some advantage of using this module that I have overlooked.

    The way I set the GPIO pins is:

    sub set_gpio { my $pin = shift; if (open my $val, '>', "/sys/class/gpio/gpio$pin/value") { print $val shift; close $val; } else { return "Failed to set pin $pin $!"; } return "SUCCESS"; }
    This way I can set any pin by passing the pin number and the state to the sub. Note that the logic is reversed in that 0 switches on the relay and 1 switches it off again when I have a relay unit attached.

Re: How to make a variable in hard call.
by 1nickt (Canon) on May 07, 2023 at 13:48 UTC

    You could use string eval, if I understand your question correctly.

    use strict; use warnings; use Test::More; sub square { my $num = shift; return $num*$num; } my $subname = 'square'; is( eval "&$subname(4)", 16 ); done_testing;

    Hope this helps!


    The way forward always starts with a minimal test.

      Same without eval:

      do { no strict "refs"; $subname->( 4 ) }
      We can also use some trickery:
      ( \&$subname )->( 4 )
      Designing their code based on this means every time they wanted to write data to a pin there would be a string eval slowing everything down.
Re: How to make a variable in hard call.
by Anonymous Monk on May 09, 2023 at 22:19 UTC
    This is probably the simplest way:
    sub inputPin { my $s = sprintf("Device::BCM2835::RPI_V2_GPIO_P1_%02d", $_[0]); return (\&$s)->(); } inputPin(7); inputPin(6); etc..

      First of all, it would be simpler to avoid misleading the user by using a hack to circumvent strictures.

      sub input_pin { my $name = sprintf( "Device::BCM2835::RPI_V2_GPIO_P1_%02d", $_[0] ) +; no strict "refs"; return $name->(); }

      But consider the following version:

      my @input_pin; for ( @Device::BCM2835::EXPORT_OK ) { next if !/^RPI_V2_GPIO_P1_(\d+)\z/; my $pin = 0 + $1; my $val = do { no strict "refs"; "Device::BCM2835::$_"->() }; $input_pin[ $pin ] = $val; }

      This version allows us to do

      $input_pin[3] $input_pin[5] $input_pin[7] ...

      instead of the slower

      input_pin(3) input_pin(5) input_pin(7)

      So what if it's not the simplest?


      Update: The point of the first half is that using no strict version is simpler, but it originally said the opposite! woops. And sprintf was misspelled.