implementation of simple class for raspberry pi pwm blink program (Embedded Programming)

Pages: 12
Hi guys,
Please, could you help me with this? I have been working on this simple project to make the LED blink which is hooked onto the GPIO pins of a RPi 4 using PWM signals. The main problem I have with the second snippet of the code (which you will see in a bit) is that it is not capturing the Keyboard Interrupt and cleaning up the GPIO pins and thus, getting it ready for the next time when the program is executed.

One of the inspiration for this personal project came from a snippet of code by M Heidenreich (2021) (https://www.youtube.com/watch?v=gymfmJIrc3g), a version of which I have included below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// soft_pwm_blink_01.cpp
// d20m12y2022
// Blinking led with PWM signal
// NOTE: This implements a cleanup of wiringPi pin used
// and reset of the same at the end of the program, using
// snippet of code courtesy of M Heidenreich (2021), Rasp-
// berry Pi Beginner Guide, YouTube.
//
#include <iostream>
#include <wiringPi.h>
#include <signal.h>
#include <softPwm.h>

using std::cout;
using std::cin;
using std::endl;


class CleanUpGPIOPin
{
    public:
        int getBlink ();               // Member access function
        void setBlink ();    // Member access function
    private:
        int blinkState = 1;            // Member data
};


// Implement class method:
int CleanUpGPIOPin::getBlink()
{
    return blinkState;
}

void CleanUpGPIOPin::setBlink()
{
    blinkState = 0;    // Set blinkState to 0 that is, false

    // Clean up and reset wiringPin
    cout << "\n Commence clean up process..." << endl;
    digitalWrite(15, LOW);     // Ensure LED is turned off
    pinMode(15, INPUT);        // Reset wiringPi pin to INPUT (that is, IN)
    cout << "Clean up process complete!" << endl;
}


// Main program
int main(void)
{
    CleanUpGPIOPin Regular;
    //cleanup = Regular.setBlink(1);

    signal(SIGINT, Regular.setBlink);    // To capture keyboard interrupt

    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    int blink = Regular.getBlink();

    while(blink)
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)

        delay(1000);    // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }

    // Clean up and reset wiringPin (not to be confused with GPIO pin)
    // digitalWrite(15, LOW);    // Ensure LED is turned off
    // pinMode(15, INPUT);       // Reset wiringPi pin to INPUT (that is, IN)

    return 0;
}

Here is the output of the first code:


$ ./soft_pwm_blink_02
^C
cleanup starts...
cleanup ends...


However, even though it worked, I had some reservation about this version of code because it implements a global variable, blink. I also tried to reach out to aforementioned author regarding the working of the global and local functions cleanup() and signal(), but there has been no respond as of the time of this writing.

So, I have been working on a similar version but this time around by implementing a class that way I can make the data members private and secure with a code that is easier to maintain. However for this second version of the program, when I hit Ctrl+C, while the program captures the keyboard interrupt, it doesn't exit the program as expected unlike in the first program (see first snippet above). Here is the code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// soft_pwm_blink_01.cpp
// d20m12y2022
// Blinking led with PWM signal
// NOTE: This implements a cleanup of wiringPi pin used
// and reset of the same at the end of the program, using
// snippet of code courtesy of M Heidenreich (2021), Rasp-
// berry Pi Beginner Guide, YouTube.
//
#include <iostream>
#include <wiringPi.h>
#include <signal.h>
#include <softPwm.h>

using std::cout;
using std::cin;
using std::endl;


class CleanUpGPIOPin
{
    public:
        int getBlink ();               // Member access function
        void setBlink ();    // Member access function
    private:
        int blinkState = 1;            // Member data
};


// Implement class method:
int CleanUpGPIOPin::getBlink()
{
    return blinkState;
}

void CleanUpGPIOPin::setBlink()
{
    blinkState = 0;    // Set blinkState to 0 that is, false

    // Clean up and reset wiringPin
    cout << "\n Commence clean up process..." << endl;
    digitalWrite(15, LOW);     // Ensure LED is turned off
    pinMode(15, INPUT);        // Reset wiringPi pin to INPUT (that is, IN)
    cout << "Clean up process complete!" << endl;
}


// Main program
int main(void)
{
    CleanUpGPIOPin Regular;
    //cleanup = Regular.setBlink(1);

    signal(SIGINT, Regular.setBlink);    // To capture keyboard interrupt

    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    int blink = Regular.getBlink();

    while(blink)
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)

        delay(1000);    // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }

    // Clean up and reset wiringPin (not to be confused with GPIO pin)
    // digitalWrite(15, LOW);    // Ensure LED is turned off
    // pinMode(15, INPUT);       // Reset wiringPi pin to INPUT (that is, IN)

    return 0;
}


Here is a screenshot of the output for the second code:

$ ./soft_pwm_blink_02
0
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^Z
[2]+  Stopped                 ./soft_pwm_blink_02



You may have noticed on the output that each time I hit the Ctrl + C, the program doesn't seem to exit until I hit Ctrl + Z.

Please, does anyone know why the code doesn't seem to parse the keyboard interrupt signal SIGINT through to the member accessor function CleanUpGPIOPin::setBlink() when the instance of this function is called from main?

Thank you in advance.
Your two code examples are exactly the same (checked with diff).
Mind updating to show both?
I don't know what compiler you're using, but line 53 signal(SIGINT, Regular.setBlink); does not compile on my system, g++ says signal can't take a member function that isn't static. Here's some recommendations: https://stackoverflow.com/questions/343219/is-it-possible-to-use-signal-inside-a-c-class

In the code that you show, blink is a local variable. Changing the value of Regular.blinkState does not automatically change the value of blink.
There are several options:

1. Try calling blink = Regular.getBlink() at the end of the main loop.

2. Turn blink into a pointer that points to int * blink = &(Regular.blinkSate) and use while(*blink)

3. Or you could use exit(0); in your setBlink function, but a lot of libraries like to call cleanup functions so an immediate program exit like that is abrasive and rarely desirable.

Finally: Global variables have a bad reputation, but they can be created in a safe manner such as utilizing namespaces or using prefixes. While I might avoid them if I'm writing a shared library, they generally are OK to use in your main.cpp file as long as you know what you're doing. (Consider the usefulness of enums and class enums)
Last edited on
@newbieg
Update:

#1.) Firstly, thank you for pointing out that the two codes are the same. I apologize for my error and any inconveniences caused. Also, I inadvertently posted an old version of what was supposed to be the second program. Anyway, here is the first program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// soft_pwm_blink_01.cpp
// d20m12y2022
// Blinking led with PWM signal
// NOTE: This implements a cleanup of wiringPi pin used
// and reset of the same at the end of the program, using
// snippet of code courtesy of M Heidenreich (2021), Rasp-
// berry Pi Beginner Guide, YouTube.
//
#include <iostream>
#include <wiringPi.h>
#include <signal.h>
#include <softPwm.h>

using std::cout;
using std::cin;
using std::endl;

// Initialize global variable:
int blink = 1;

// Function prototype:
void cleanup(int signal);

// Main program
int main(void)
{
    signal(SIGINT, cleanup);    // To capture keyboard interrupt

    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    while(blink)
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)
        delay(1000);    // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }

    // Clean up and reset wiringPin (not to be confused with GPIO pin)
    digitalWrite(15, LOW);    // Ensure LED is turned off
    pinMode(15, INPUT);       // Reset wiringPi pin to INPUT (that is, IN)

    return 0;
}

// Function definition:
void cleanup(int signal)
{
    blink = 0;
    cout << "\ncleanup starts..." << endl;
    cout << "cleanup ends..." << endl;

}


The output for the first program is:


$ ./soft_pwm_blink_01
^C
cleanup starts...
cleanup ends...


The second program (slightly different to the one in the initial post) is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// soft_pwm_blink_02.cpp
// d20m12y2022
// Blinking led with PWM signal
// NOTE: This implements a cleanup of wiringPi pin used
// and reset of the same at the end of the program, using
// snippet of code courtesy of M Heidenreich (2021), Rasp-
// berry Pi Beginner Guide, YouTube.
//
#include <iostream>
#include <wiringPi.h>
#include <signal.h>
#include <softPwm.h>

using std::cout;
using std::cin;
using std::endl;


class CleanUpGPIOPin
{
    public:
        bool getBlink ();               // Member access function
        void setBlink (int signal);    // Member access function
    private:
        int blinkState;            // Member data
};


// Implement class method (See Horton I (2013) p.305):
bool CleanUpGPIOPin::getBlink()
{
    return blinkState;
}

void CleanUpGPIOPin::setBlink(int b)
{
    blinkState = b;    // Set blinkState to 0 that is, false

    // Clean up and reset wiringPin
    cout << "\n Commence clean up process..." << endl;
    digitalWrite(15, LOW);     // Ensure LED is turned off
    pinMode(15, INPUT);        // Reset wiringPi pin to INPUT (that is, IN)
    cout << "Clean up process complete!" << endl;
}

void cleanup(int signal)
{
    CleanUpGPIOPin Regular;
    Regular.setBlink(0);
}


// Main program
int main(void)
{


    //cleanup = Regular.setBlink(1);

    signal(SIGINT, cleanup);    // To capture keyboard interrupt

    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    CleanUpGPIOPin Regular;
    //Regular.setBlink(1);
    cout << Regular.getBlink() << endl;
    //bool blink = Regular.getBlink();

    while(!Regular.getBlink())
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)

        delay(1000);    // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }

    // Clean up and reset wiringPin (not to be confused with GPIO pin)
    digitalWrite(15, LOW);    // Ensure LED is turned off
    pinMode(15, INPUT);       // Reset wiringPi pin to INPUT (that is, IN)

    return 0;
}


The output for the second program is (similar to the previous post):


$ ./soft_pwm_blink_02
0
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^C
 Commence clean up process...
Clean up process complete!
^Z
[2]+  Stopped                 ./soft_pwm_blink_02



#2.) Secondly, as per the compiler, I also use g++. (Please see details below):

$ g++ --version
g++ (Raspbian 10.2.1-6+rpi1) 10.2.1 20210110


Also, the compilation and build commands which I used are:


g++ -Wall -c soft_pwm_blink_02.cpp -l wiringPi
g++ -Wall -o soft_pwm_blink_02 soft_pwm_blink_02.cpp -l wiringPi



#3.) In regards to the error, that was my bad. Just as previously mentioned, I was due to a problem with the old version of the code that I initially posted. Hopefully, the following error:
non static member function
should be resolved. By the way, here is the initial error message I got at the time:


g++ -Wall -c soft_pwm_blink_01.cpp -l wiringPi
soft_pwm_blink_01.cpp: In function ‘int main()’:
soft_pwm_blink_01.cpp:53:28: error: invalid use of non-static member function ‘void CleanUpGPIOPin::setBlink()’
   53 |     signal(SIGINT, Regular.setBlink);    // To capture keyboard interrupt
      |                    ~~~~~~~~^~~~~~~~
soft_pwm_blink_01.cpp:35:6: note: declared here
   35 | void CleanUpGPIOPin::setBlink()


#4.) Thank you for your comment about considering the usefulness of global variables in so far as one can safely implement them. Your comments are well received. I would also consider enumerated constants as well just as you suggested.

#5.) Please do you understand what signal() function is really doing in main() especially in the first program? The reason I ask is because if I try to call cleanup() by including its parentheses within signal() function header, say for instance,

signal(SIGINT, cleanup()); // To capture keyboard interrupt

I think I get a compilation error. Meanwhile up until now, I wasn't aware one could call cleanup() without including the parentheses following strict c++ (c-11) standard syntax rules considering that cleanup() is also a function itself. Please, any thoughts on this?

Finally, further next comments and contributions are highly welcomed. Thank you so much.

Last edited on
Hey, glad some of that helped.

#5.) Please do you understand what signal() function is really doing in main()

Signal() takes a pointer to a function that will be called when the SIGINT interrupt is detected. That pointer to a function is often called a callback function. In this case "cleanup" must be a void returning function that takes an int value as an argument.

signal(SIGINT, cleanup()); // To capture keyboard interrupt

I think I get a compilation error.

When you provide a pointer to a function it should not have the parenthesis. If you give it parenthesis that is saying to call the function and give the result to the argument to signal rather than passing a pointer of the function... Callback syntax takes a bit to get used to.

Your code is close, but blinkState either set to zero or filled in with garbage value when your object is created (and that is determined by your OS build of G++, I know fedora and ubuntu often have different default settings for g++). Create a constructor to set blinkState to true to make sure it always has an expected result.

And finally, your cleanup function is creating a local object, it is not connected to the state of Regular from main. That would be fixed if Regular were a global object... Sorry, it's xmas and my family's upset that I'm on the computer right now, I'll try to figure out a non-global variable option later tonight if I can.

Sorry if my previous post sounded stiff, I had fun reading your code.
Last edited on
Sure, I'm glad you did. And sorry I had to borrow you for a minute. Please, do extend my regard to your family this holiday season.

In the meantime, I would work on the ideas you have suggested and look forward to more suggestions that you may have in the future.

Thank you!
I couldn't find a way of doing what you want without using a global variable/object. (Except for singleton, but that's not much of an improvement)
I also realized that the function that is handed to signal is going to be called in a separate thread, so you will need to do things in a thread safe manner, see: https://stackoverflow.com/questions/51500257/volatile-stdsig-atomic-t-and-atomic-signal-fence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// soft_pwm_blink_02.cpp
// d20m12y2022
// Blinking led with PWM signal
// NOTE: This implements a cleanup of wiringPi pin used
// and reset of the same at the end of the program, using
// snippet of code courtesy of M Heidenreich (2021), Rasp-
// berry Pi Beginner Guide, YouTube.
//
#include <iostream>
#include <wiringPi.h>
#include <signal.h>
#include <softPwm.h>
#include <mutex>

using std::cout;
using std::cin;
using std::endl;


class CleanUpGPIOPin
{
    public:
	CleanUpGPIOPin();
        bool getBlink ();               // Member access function
        void setBlink (int signal);    // Member access function
    private:
        int blinkState;            // Member data
};

CleanUpGPIOPin::CleanUpGPIOPin()
{
	blinkState = 1;
}

// Implement class method (See Horton I (2013) p.305):
bool CleanUpGPIOPin::getBlink()
{
    return blinkState;
}

void CleanUpGPIOPin::setBlink(int b)
{
    blinkState = b;    // Set blinkState to 0 that is, false

    // Clean up and reset wiringPin
    cout << "\n Commence clean up process..." << endl;
    digitalWrite(15, LOW);     // Ensure LED is turned off
    pinMode(15, INPUT);        // Reset wiringPi pin to INPUT (that is, IN)
    cout << "Clean up process complete!" << endl;
}

CleanUpGPIOPin Regular;

std::mutex mtx;
void cleanup(int signal)
{
    mtx.lock();
    Regular.setBlink(0);
    mtx.unlock();
}


// Main program
int main(void)
{
    signal(SIGINT, cleanup);    // To capture keyboard interrupt

    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    cout << Regular.getBlink() << endl;

    while(Regular.getBlink())
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)

        delay(1000);    // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }
// Note: these following lines will run as well, since we tell SIGINT to just break the above loop.
    // Clean up and reset wiringPin (not to be confused with GPIO pin)
    digitalWrite(15, LOW);    // Ensure LED is turned off
    pinMode(15, INPUT);       // Reset wiringPi pin to INPUT (that is, IN)

    return 0;
}


I'm pretty sure that mutex made things thread safe.
Last edited on
Does anyone have an opinion about atexit()?
I think it might be a better fit for calling cleanup functions, rather than taking over SIGINT. It still gets called if CTRL-C is pressed on my system, I'm assuming that is the standard.

It has an additional feature that multiple functions can be loaded to call; just call it on each function that you want to use. (It says that all platforms must allow for at least 32 atexit functions in a row, though there is no set maximum. So just don't get too crazy with it.)

I did some testing with it, and it looks like atexit runs before global objects are deconstructed, so it might also be useful for debugging purposes.

atexit(): https://cplusplus.com/reference/cstdlib/atexit/
Last edited on
That signal handler can deadlock when reentered. In other words a second signal could arrive on the same thread while the lock is already held. See this thread:
https://cplusplus.com/forum/unices/274079/

The C++ standard seriously limits what can be done in a signal handler, though POSIX is somewhat less restrictive.

I think a "typical" Raspberry Pi runs Debian & complies with POSIX to an extent.
You can call only certain library functions in a signal handler:
https://man7.org/linux/man-pages/man7/signal-safety.7.html

Last edited on
@newbieg:

Thank you for the modification. I really learned a great deal in the process. I will reach back to you as I make further progress.

@mbozzi:

Thank you for weighing in as well. I really appreciate this.

P.S.:
@All:
I have also been taking a refresher on Signal handling in C++ in addition to the links you all have provided. Thanks.
@All:
Update:

Here is the latest version of the code. I have also acknowledged your contribution,
@newbieg.

Thank you. Also, thanks all for current and future contributions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// soft_pwm_blink_03.cpp
// d27m12y2022
// Blinking led with PWM signal
// NOTE: This implements a cleanup of wiringPi pin used
// and reset of the same at the end of the program, using
// snippet of code courtesy of M Heidenreich (2021), Rasp-
// berry Pi Beginner Guide, YouTube.
// Acknowledgment: Contributions by newbieg as well as by
// others on cplusplus.com forum (2022) (See ffg. code:)
//
#include <iostream>
#include <wiringPi.h>
#include <signal.h>
#include <softPwm.h>
#include <mutex>

using std::cout;
using std::cin;
using std::endl;
using std::mutex;        // (See cplusplus.com, 'Mutex class' reference)


class CleanUpGPIOPin
{
    public:
        // (See Teach Yourself C++ in 21 days, www.101.lv/learn/C++/htm/):
        CleanUpGPIOPin();               // Constructor prototype
        ~CleanUpGPIOPin();              // Destructor "
        bool getBlink ();               // Member access fxn prototype
        void setBlink (int signal);     // "
    private:
        int blinkState;                 // Member data
};

// Class constructor declaration (By newbieg):
CleanUpGPIOPin::CleanUpGPIOPin()
{
    blinkState = 1;                 // To properly intialize class member data
}

// Class destructor declaration:
CleanUpGPIOPin::~CleanUpGPIOPin()
{
    // destruction process no action other than its intended purpose
}

// Class public methods declaration (See Horton, I., (2013) p.305):
bool CleanUpGPIOPin::getBlink()
{
    return blinkState;
}

void CleanUpGPIOPin::setBlink(int b)
{
    blinkState = b;    // Set blinkState, in this case, '0' reps 'False'

    // Clean up and reset wiringPin
    cout << "\n Commence clean up process..." << endl;
    digitalWrite(15, LOW);     // Ensure LED is turned off
    pinMode(15, INPUT);        // Reset wiringPi pin to INPUT (that is, IN)
    cout << "Clean up process complete!" << endl;
}

// Declare instance of respective class objects at global scope
// (Although still in search for an alternative to global entities
// that will support easy maintenance of program in future devs):

CleanUpGPIOPin Regular;
mutex mtx;                  // Similarly, instantiate a mutex object
                            // on global scope

// Declare signal handling fxn:
void cleanup(int signalNum)
{
    mtx.lock();             // By newbieg
    Regular.setBlink(0);    // Access class member function through object
                            // and set blinkState to zero (0)
    mtx.unlock();         // By newbieg
}


// Main program begins:
int main(void)
{
    signal(SIGINT, cleanup);    // Call signal() fxn to capture
                                // keyboard interrupt

    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    // Initialize while loop and call class member function
    // which gets blinkState value/condition (which acts as counter)
    while(Regular.getBlink())
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)

        delay(1000);             // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);     // (ditto...)
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }

    return 0;
}


Output for this new program:


$ ./soft_pwm_blink_03
^C
 Commence clean up process...
Clean up process complete!
That program has undefined behavior
https://eel.is/c++draft/support.signal#3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <csignal>
#include <wiringPi.h>
#include <softPwm.h>

volatile sig_atomic_t sigint_raised = 0; 
extern "C" void handle_sigint(int signalNum) { sigint_raised = 1; }

// Main program begins:
int main(void)
{
    signal(SIGINT, handle_sigint);
    wiringPiSetup();    // Call wiringPi library and numbering system
    softPwmCreate(15,0,100);    // Call function to create PWM pin

    while(! sigint_raised)
    {
        softPwmWrite(15, 25);    // Function to write PWM data to output
                                 // device (in this case, LED)
                                 // (where, duty cycle = 25%)

        delay(1000);             // sleep for 1000ms (1 second)
        softPwmWrite(15, 0);     // (ditto...)
        delay(1000);
        softPwmWrite(15, 50);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
        softPwmWrite(15, 100);
        delay(1000);
        softPwmWrite(15, 0);
        delay(1000);
    }
    
    std::cout << "\n Commence clean up process...\n";
    digitalWrite(15, LOW);     // Ensure LED is turned off
    pinMode(15, INPUT);        // Reset wiringPi pin to INPUT (that is, IN)
    std::cout << "Clean up process complete!\n";
}
Last edited on
@mbozzi:

Thanks for pointing this out and thank you for your insights. I modified the program, accordingly, and it ran successfully!
Before then, I also looked up the link you posted and have a question regarding this line:
The function signal is signal-safe if it is invoked with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler.

1.) Just to be clear, please does this mean that a person should strive to adhere to best practices as much as possible so as to avoid ambiguity and undefined/unexpected behavior? That is, '0' should correspond to False while '1' corresponds to True

2.) That way, even if while counter were inadvertently set to Regular.setBlink(2), for instance, either way the condition would still be met in a safe manner?

3.) If otherwise, could you please explain further?

P.S.: I apologize if these questions are verbose. The documentation doesn't give exhaustive detail, as expected.
The statement
signal(SIGINT, cleanup);
Instructs the operating system to call the function cleanup in response to SIGINT.

Signal handlers like cleanup are special because they can be called at nearly any time in the program's execution, even while library-internal data structures are being updated. Since a library data structure might be temporarily invalid while it is being updated, signal handlers are banned from using the standard library, with a few exceptions.

Notably std::mutex::lock/unlock can't be called in a signal handler, because it's a standard library function that isn't explicitly allowed; even if the resulting program appears to work it potentially deadlocks as discussed above.
https://en.wikipedia.org/wiki/Deadlock

The function signal is signal-safe if it is invoked with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler.

This means signal can be called in a signal handler like cleanup if and only if the first argument to signal is the same as the first argument to the signal handler. For example, this is okay:
extern "C" void cleanup(int signalNum) { signal(signalNum, SIG_DFL); }
But this is wrong:
extern "C" void cleanup(int signalNum) { signal(SIGTERM, SIG_DFL); }
unless SIGTERM is the same as signalNum.

Just to be clear, please does this mean that a person should strive to adhere to best practices as much as possible so as to avoid ambiguity and undefined/unexpected behavior?

In general, software development is about making the most correct, performant, and useful software possible within budget. Make choices based on their effect on your deliverables. "Best practice" is merely a guideline.

Finally, "Undefined behavior" has a technical meaning. A C++ program exhibits undefined behavior when it breaks the rules in the standard document, violating the assumptions that the system relies on to operate correctly. A program that exhibits undefined behavior is permitted to do anything.
https://en.cppreference.com/w/cpp/language/ub
cppreference wrote:
Undefined behavior
Renders the entire program meaningless if certain rules of the language are violated.
Last edited on
Wow.
I should have read the actual documentation on the signal function.
I do believe that due to the nature of SIGINT it would be exceedingly unlikely to be called repeatedly close enough to cause a deadlock (as long as we only handled SIGINT in the provided callback and if mutex was allowed).
However the standards are very definite that what I've provided is relying on undefined behavior because it is using a global variable other than one of the allowed data types, and I used a mutex.
I am sorry for providing and propagating bad code. It should be noted that no mention of the danger or of the limited list of available functions are mentioned in our site's description of the header or of the signal function itself.
https://legacy.cplusplus.com/reference/csignal/
https://cplusplus.com/reference/csignal/signal/

Thank you Mbozzi for the education.
Last edited on
@newbieg and @mbozzi


I would like to thank you both for adding to my knowledge. I had very little knowledge about these concept prior to raising this question. But interacting with you both has really been a motivating experience for me.

I was just curious, is there any way to implementing the signal handler function without using a global variable? (I was wondering if that might help avoid running into undefined behavior)
Last edited on
I was just curious, is there any way to implementing the signal handler function without using a global variable? (I was wondering if that might help avoid running into undefined behavior)

You could put the variable into a namespace, or into a class as a static data member. But these are only style changes. The variable will still be used to hold global state. There may also be platform-specific options that I don't know about. It's probably best to use the simplest option for as long as possible.

@newbieg,

I learned yesterday that Windows may call the signal handler in a separate thread, but only for SIGINT. The documentation has this to say:
SIGINT is not supported for any Win32 application. When a CTRL+C interrupt occurs, Win32 operating systems generate a new thread to specifically handle that interrupt. This can cause a single-thread application, such as one in UNIX, to become multithreaded and cause unexpected behavior.

https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/signal?view=msvc-170

I looked at MSVCRT's source code. For SIGINT only, the CRT arranges the signal handler to be called by the system in a new thread using SetConsoleCtrlHandler and the handler eats the signal. On the other hand, raise is always synchronous and the other signals are implemented using SEH, which appears not to make extra threads.
https://learn.microsoft.com/en-us/windows/console/setconsolectrlhandler
https://learn.microsoft.com/en-us/windows/win32/debug/structured-exception-handling

In practice I think
1
2
volatile std::sig_atomic_t sigint_raised = 0; 
extern "C" void handle_sigint(int) { sigint_raised = 1; }

Should be expected to work even on Windows, but I'm not very certain of this. I suggest that Windows programmers who want to catch Ctrl-C should strongly prefer to call SetConsoleCtrlHandler instead.
Last edited on
@mbozzi:

Thank you so much for your suggestions. It seems to corroborate what @newbieg said previously that I would still need to implement some global state either way.

P.S.: Sorry I didn't reply sooner.
@ayoesquire,

In C++ a "global variable" is in a namespace, the global namespace. AKA as global scope.

https://stackoverflow.com/questions/10269012/global-scope-vs-global-namespace

What mbozzi suggested is creating a custom namespace and stuffing your variable into that. Using a custom namespace (kinda, somewhat) narrows the scope.

https://www.learncpp.com/cpp-tutorial/user-defined-namespaces-and-the-scope-resolution-operator/
@George P:

Wow, there's so much to learn, and I have learned quite a lot from interacting with you guys (@mewbieg, @mbozzi) on this thread for which I am so grateful to you guys.

I would do my best to keep what I consider the more important details in mind.

For example, in the stackoverflow and especially learncpp.com resources, I see that both concepts namely, scope (global, class, function, block) and namespaces, are related and, in some cases, interwoven.

I also now understand that part of the reason behind namespaces is to resolve potential situations involving name conflicts and/or collision, which is something that I might need to pay attention to going forward especially working on projects.

Sometimes, keeping up with the C++ ecosystem can be a tad overwhelming albeit in a good way :)

Thank you, @George P; thank you, all.
I first started trying to self-teach myself C++ before C++98 was the standard. ANSI C++ was it. It was more C than what we'd recognized as C++.

C++11 had to be introduced, along with some nice C++ compilers that supported the standard before all the pieces began to fall into place. C++11 was a HUGE change to the language, it defined what is considered "Modern C++". Though ++20 should be the new standard bearer.

A bit of a rant.....

Writing using namespace std; in your code completely blows up the utility of having things encapsulated in namespaces.

https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice

It is NOT wrong, as such, it is just going to bring back problems that were lessened when containing the C++ stdlib in namespaces, namespace std.

Personally I never have using statements in my code, using declarations or directives.*

https://www.learncpp.com/cpp-tutorial/using-declarations-and-using-directives/

Now my fingers automatically type std:: when I use something from the C++ stdlib.

*Well, never say never use. There are a couple of C++ features that work only when having a using declaration. I rarely use those features and can't remember what they are at the moment. Poke around cppreference and you'll find an example or three.

https://en.cppreference.com/w/

Using the 3rd party library Boost kinda requires having using declarations, the library entities are sometimes buried down deep in multiple layers and levels.
Pages: 12