I^2R (Customer) asked a question.

Debouncing P1AM-100 Discrete inputs

Hi,

I need to debounce some Discrete inputs. I came up with the following code, based on a modified Adafruit routine.

I'm an old PLC guy, but pretty new to C++. The code appears to work, but I wanted to know if anyone sees something I'm doing that's silly or could be optimized.

I'm hoping to use this as a generic debounce function for future projects. Feel free to use and modify.

I'd like to expand this to accommodate more than one discrete input module. What would be the best approach? I'm thinking 2D arrays?

 

Thanks.

  1. #include <P1AM.h>
  2.  
  3. // This debouncing routine is a modified version of code posted by Adafruit:
  4. // https://blog.adafruit.com/2009/10/20/example-code-for-multi-button-checker-with-debouncing/
  5.  
  6. #define DEBOUNCE 10 // input debouncer, how many ms to debounce, 5+ ms is usually plenty
  7.  
  8. // Here is where we define the inputs that we'll debounce.
  9. // This is for a digital input module in A SINGLE SLOT ONLY as set by SLOT below. For multiple modules the code will require modification (2D array perhaps?).
  10. // QUANTITY sets the number of consecutive inputs to debounce, beginning at input #1.
  11. #define SLOT 2 //in this example we have a digital input module in slot #2
  12. #define QUANTITY 6 //in this example, we are debouncing inputs 1-6
  13.  
  14. // Define an array to represent the inputs. As array is zero indexed, but inputs start at #1, we create the array one element larger than the number of inputs.
  15. // We will ignore the array element at index zero.
  16.  
  17. #define NUMINPUTS QUANTITY + 1 // Gets the size of the array
  18. //byte inputs[NUMINPUTS];
  19.  
  20.  
  21. // we will track if a input is on, just had a rising or falling edge.
  22. byte inputIsOn[NUMINPUTS], inputRisingEdge[NUMINPUTS], inputFallingEdge[NUMINPUTS];
  23.  
  24. // Array to track output states. Not part of debouncing logic. Used for example code in main loop
  25. byte outstate[NUMINPUTS];
  26.  
  27. void setup() {
  28.  
  29. // set up serial port
  30. //Serial.begin(9600);
  31. Serial.begin(115200);
  32.  
  33.  
  34. while (!P1.init()) {
  35. ; //Wait for Modules to Sign on
  36. }
  37. }
  38.  
  39. void loop() {
  40.  
  41. /*
  42. Upon calling the check_inputs function, it will debounce the inputs and update the three arrays where n = the input number.
  43. the inputs start at 1, but the arrays are 0 indexed: The 0 index element of the arrays are ignored.
  44. inputIsOn[n]
  45. shows the current debounced state of each input.
  46.  
  47. inputRisingEdge[n] and inputFallingEdge[n]
  48. Will be true after an input state change for ONE pass through the main loop.
  49. The next call to check_inputs will clear the array.
  50.  
  51. */
  52. check_inputs();
  53.  
  54.  
  55.  
  56. /*
  57. The following example code is not part of the debounce logic. This demonstrates a P1-16CDR module in slot #2, this is a module with 8 inputs and 8 outputs.
  58. The code toggles the outputs (and the LED for each) corresponding to each input. The output will toggle state on each rising edge.
  59. The state of each input and the rising and falling edges are printed to the terminal.
  60. */
  61.  
  62. for (byte i = 1; i < NUMINPUTS; i++) {
  63. if (inputRisingEdge[i]) { //If we see a rising edge
  64. Serial.print(i, DEC);
  65. Serial.println(" Rising edge");
  66. if (!outstate[i]) { //If corresponding output is off...
  67. P1.writeDiscrete(HIGH, SLOT, i); //Turn corresponding output on
  68. outstate[i] = 1; //update array to track output state
  69. }
  70. else { //Else corresponding output is on...
  71. P1.writeDiscrete(LOW, SLOT, i); //Turn corresponding output off
  72. outstate[i] = 0; //update array to track output state
  73. }
  74. }
  75. if (inputFallingEdge[i]) {
  76. Serial.print(i, DEC);
  77. Serial.println(" Falling edge");
  78. }
  79. if (inputIsOn[i]) {
  80. Serial.print("Input #");
  81. Serial.print(i, DEC);
  82. Serial.println(" is on");
  83. // is the input on at this moment?
  84. }
  85. if (!inputIsOn[i]) {
  86. Serial.print("Input #");
  87. Serial.print(i, DEC);
  88. Serial.println(" is off");
  89. // is the input off at this moment?
  90. }
  91. }
  92. // Delay not required, just here to slow the serial display down.
  93. // CAUTION: Beware of long delays in the main loop. A legitimate input pulse longer than the debouce time, but shorter than the loop cylic time could be missed.
  94. delay(300);
  95. }
  96.  
  97.  
  98. void check_inputs() {
  99.  
  100. // Create arrays to hold comparisons. These are static so the persist between function calls...
  101. static byte previousstate[NUMINPUTS]; //
  102. static byte currentstate[NUMINPUTS];
  103. static long lasttime;
  104.  
  105.  
  106. // Clear the rising / falling arrays...
  107. memset(inputRisingEdge, 0, NUMINPUTS);
  108. memset(inputFallingEdge, 0, NUMINPUTS);
  109.  
  110. if (millis() < lasttime) {
  111. // we wrapped around, lets just try again
  112. lasttime = millis();
  113. }
  114.  
  115. if ((lasttime + DEBOUNCE) > millis()) {
  116. // not enough time has passed to debounce
  117. return;
  118. }
  119. // ok, we have waited DEBOUNCE milliseconds, lets reset the timer
  120. lasttime = millis();
  121.  
  122.  
  123. for (byte index = 1; index < NUMINPUTS; index++) {
  124.  
  125. currentstate[index] = P1.readDiscrete(SLOT, index); // read the input
  126.  
  127. if (currentstate[index] == previousstate[index]) { //Same reading two times in a row, must be the real deal
  128. if ((inputIsOn[index] == 0) && (currentstate[index] == 1)) { // rising edge
  129. inputRisingEdge[index] = 1; //Update the array
  130. } else if ((inputIsOn[index] == 1) && (currentstate[index] == 0)) { // falling edge
  131. inputFallingEdge[index] = 1; //Update the array
  132. }
  133. inputIsOn[index] = currentstate[index]; //Update the array
  134. }
  135. previousstate[index] = currentstate[index]; // Save state to compare next time through
  136. }
  137. }

 


  • FACTS_AdamC (AutomationDirect)

    Hey @I^2R (Customer)​ , If you're looking to use multiple modules and optimize things a bit, I would recommend using the readBlockData function. It'll let you query all of your discrete input modules in a single call. With your current approach there is some overhead due to readDiscrete being called for each channel individually.

    Selected as Best
  • FACTS_AdamC (AutomationDirect)

    Hey @I^2R (Customer)​ , If you're looking to use multiple modules and optimize things a bit, I would recommend using the readBlockData function. It'll let you query all of your discrete input modules in a single call. With your current approach there is some overhead due to readDiscrete being called for each channel individually.

    Selected as Best
  • I^2R (Customer)

    Hi Adam,

    I just played with the readBlockData function, thanks, I was not aware of that.

    Using that, I suppose I would call readBlockData once at the beginning of the check_inputs function, then loop through the array element by element, as I did before, looking for state changes.

    It seems like it would be efficient to do a bitwise XOR on the entire array to pull out the changed inputs, but I'm not aware of a way to do that on an array all in one shot.

     

    Expand Post
  • I^2R (Customer)

    Thinking this through a bit (no pun intended) further...

    This gets me an array of bytes. If I want to get the states of the individual inputs to debounce, i'll need to pull out the individual bits (bitread() I assume?). It seems that i'm saving "overhead" on the module reads, but will have more code to process the resulting byte arrays.

    Is the call to read the hardware modules similar to a serial.write call where it is very slow compared to looping through code, so the net result will be faster execution?

     

    Thanks.

     

    Expand Post
    • FACTS_AdamC (AutomationDirect)

      You are right that the calls to the modules would have the same considerations as a serial.write call, albeit the module calls are much faster than writing serial. I would expect the time to process the code and compare the inputs to be faster than if you did consecutive single channel reads.

       

       

       

       

      Expand Post
  • I^2R (Customer)

    <deleted>

    Posted some new code, but it was not ready.

    stay tuned.

  • I^2R (Customer)

    Updated version using the readBlockData function.

    Seems to be working fine. I only have one input module to test against. If anyone has multiple input modules to prove it out on, that would be great.

    1. /*
    2. Discrete input module debouncing routine with rising & falling edge detection for Automation Direct
    3. P1AM-100 using block data transfer method (P1.readBlockData) The debouncing logic is based on code posted by Adafruit:
    4. https://blog.adafruit.com/2009/10/20/example-code-for-multi-button-checker-with-debouncing/
    5.  
    6. */
    7. #include <P1AM.h>
    8.  
    9. // Adjust DEBOUNCE to set how many ms to debounce, 5+ ms is usually plenty
    10. #define DEBOUNCE 10
    11.  
    12. // Adjust MODULEQTY to set the number of 8-channel input modules being used
    13. // **16 channel input modules (currently P1-16ND3 & P1-16NE3) count as two**
    14. #define MODULEQTY 1
    15.  
    16. /*
    17. ********** NO NEED TO MODIFY BELOW THIS LINE ***********
    18. */
    19.  
    20. // The following DEFINES are used to calculate different array and FOR loop sizes.
    21. // As array is zero indexed, but physical inputs start at #1, we create the array one element larger than the number of inputs.
    22. // We will ignore the array element at index zero.
    23.  
    24. #define NUMINPUTS MODULEQTY * 8 // 8 inputs per module
    25. #define BYTEARRAYSIZE NUMINPUTS + 1 // One byte larger as we discard index 0
    26. #define BITARRAYSIZE (NUMINPUTS * 8) + 1 //Array size once we break out the bytes into bits
    27.  
    28. // Arrays to track if an input is on, just had a rising or falling edge.
    29. byte inputIsOn[BITARRAYSIZE], inputRisingEdge[BITARRAYSIZE], inputFallingEdge[BITARRAYSIZE];
    30.  
    31. // Array to track output states. Not part of debouncing logic, used for example code in main loop
    32. byte outstate[BITARRAYSIZE];
    33.  
    34.  
    35. void setup() {
    36.  
    37. // set up serial port
    38. Serial.begin(115200);
    39.  
    40. //initialize modules
    41. while (!P1.init()) {
    42. ; //Wait for Modules to Sign on
    43. }
    44. }
    45.  
    46. void loop() {
    47.  
    48. /*
    49. Upon calling the check_inputs function, it will debounce the inputs and update the three arrays where n = the input number.
    50. the inputs start at 1, but the arrays are 0 indexed: The 0 index element of the arrays are ignored.
    51. inputIsOn[n]
    52. shows the current debounced state of each input.
    53.  
    54. inputRisingEdge[n] and inputFallingEdge[n]
    55. Will be true after an input state change for ONE pass through the main loop.
    56. The next call to check_inputs will clear the array.
    57.  
    58. */
    59. check_inputs(); //Function to debounce inputs and update arrays
    60.  
    61.  
    62. // The following is not part of the debounce logic, just a demo of the functionality.
    63.  
    64. for (byte i = 1; i < BYTEARRAYSIZE; i++) {
    65.  
    66. if (inputRisingEdge[i]) { //If we see a rising edge
    67. Serial.print("Input #");
    68. Serial.print(i, DEC);
    69. Serial.println(" Rising edge");
    70. }
    71. if (inputFallingEdge[i]) { //If we see a falling edge
    72. Serial.print("Input #");
    73. Serial.print(i, DEC);
    74. Serial.println(" Falling edge");
    75. }
    76. if (inputIsOn[i]) { //If the input is on
    77. Serial.print("Input #");
    78. Serial.print(i, DEC);
    79. Serial.println(" is on");
    80. }
    81. if (!inputIsOn[i]) { //If the input is off
    82. Serial.print("Input #");
    83. Serial.print(i, DEC);
    84. Serial.println(" is off");
    85. }
    86. }
    87.  
    88. Serial.println(); // Print blank line
    89.  
    90. // Delay not required or recommended, just here to slow the serial display down.
    91. // CAUTION: Beware of long delays in the main loop. A legitimate input pulse longer than the debouce time, but shorter than the loop cylic time could be missed.
    92. delay(300);
    93. }
    94.  
    95.  
    96. void check_inputs() {
    97.  
    98. // Create array to hold block of digital input data
    99. char dataArrayIn[MODULEQTY];
    100.  
    101. // Create arrays to hold comparisons. These are static so they persist between function calls...
    102. static byte previousState[BYTEARRAYSIZE];
    103. static byte currentState[BYTEARRAYSIZE];
    104.  
    105. // Create long to hold last scan time
    106. static long lasttime;
    107.  
    108. // Clear the rising / falling arrays...
    109. memset(inputRisingEdge, 0, BITARRAYSIZE);
    110. memset(inputFallingEdge, 0, BITARRAYSIZE);
    111.  
    112. if (millis() < lasttime) {
    113. // we wrapped around, lets just try again
    114. lasttime = millis();
    115.  
    116. // DO WE NEED A RETURN HERE?
    117. // IT'S NOT IN THE ORIGINAL ADAFRUIT CODE, BUT WITHOUT IT, THIS IF STATEMENT SEEMS POINTLESS?
    118. // return;
    119. }
    120.  
    121. if ((lasttime + DEBOUNCE) > millis()) {
    122. // not enough time has passed to debounce
    123. return;
    124. }
    125. // ok, we have waited DEBOUNCE milliseconds, lets reset the timer
    126. lasttime = millis();
    127.  
    128. P1.readBlockData(dataArrayIn, MODULEQTY, 0, DISCRETE_IN_BLOCK); //Reads in all the discrete input states in one shot to dataArrayIn
    129.  
    130. /*
    131. The inputs states are encoded in the bits of the dataArrayIn[] array, one byte (8 bits) per 8 input block (bit 0 = input #1, bit 7 = input #8, etc.).
    132. The following code loops through the array, pulling each bit out.
    133. The index of the output arrays (inputIsOn[n], inputRisingEdge[n] and inputFallingEdge[n]) is designed to ignore index 0
    134. so the index will align with the physical numbering (1-8) printed on the face of the actual hardware modules.
    135. */
    136. for (byte byteindex = 0; byteindex < MODULEQTY; byteindex++) { // Loop through each byte of the array
    137.  
    138. for (byte bitindex = 0; bitindex < 8; bitindex++) { // Loop through each bit of each byte
    139.  
    140. byte inputID = ((byteindex * 8) + bitindex + 1); //input ID is the
    141.  
    142. currentState[inputID] = bitRead(dataArrayIn[byteindex], bitindex); // read the input
    143.  
    144. if (currentState[inputID] == previousState[inputID]) { //Same reading two times in a row, must be the real deal
    145.  
    146. // At this point, inputIsOn has not been updated yet, so it equals the last debounced input state. currentState equals the current input
    147. // state, so we can compare the two to find rising and faling edges.
    148.  
    149. if ((inputIsOn[inputID] == 0) && (currentState[inputID] == 1)) { // rising edge
    150. inputRisingEdge[inputID] = 1; //Update the array
    151. //Serial.print(inputID);
    152. //Serial.println("rising");
    153.  
    154. } else if ((inputIsOn[inputID] == 1) && (currentState[inputID] == 0)) { // falling edge
    155. inputFallingEdge[inputID] = 1; //Update the array
    156. // Serial.print(inputID);
    157. //Serial.println("falling");
    158. }
    159. inputIsOn[inputID] = currentState[inputID]; //Update the array
    160. }
    161. previousState[inputID] = currentState[inputID]; // Save state to compare next time through
    162. }
    163. }
    164. }

     

    Expand Post