How to detect If a digital pin is blinking (from high to low) in C?

123 Views Asked by At

I am writing a firmware application. I must read a GPIO pin connected to a smart battery, which communicates the state of the battery. A high value in the pin means the battery is in an unknow state, a low value means the battery is disconnected, and an error is indicated by a 2.5 Hz blink. So far I have managed to detect the disconnection and the unknown state with the following function:

void Check_Bat_Connected(void)
{ 
static int Low_Level_debounce = 0
if(IOPORT_LEVEL_LOW == Pin_Level)
    {
        if(25 == Low_Level_debounce)
        {
            BAT_Status = BAT_Not_Connect;
        }
        else if(Low_Level_debounce < 25)
        {
            Low_Level_debounce++;
        }
        else
        {
            /**/
        }
    }
    else
    {
        Low_Level_debounce = 0;
        BAT_Status = BAT_Unknown;
    }
}

This function is being called in a loop by a battery handler which has the role of getting info about the battery. I added a debounce variable to increase robustness againt false disconnects of the battery.

My question is now, how do I go around detecting if the LED is blinking at 2.5 Hz? I dont have any information on how much time passes between two calls of the Check_Bat_Connected function, even if I assume that the frequency is much higher than 2.5 Hz. How can I implement this last check in a robust way?

2

There are 2 best solutions below

0
Fe2O3 On BEST ANSWER

Presuming the hardware can provide an interrupt on a fairly reasonable interval, there's a way to debounce and detect that 2.5Hz signal.

Use an static unsigned 32 bit variable to accumulate signal samples (0 or 1) by left shifting and OR'ing:

sample = (sample << 1) | !!currentVal; // shift and store one more bit until 32 gathered

Imagine a boucey 2.5Hz square wave sampling at about 15ms (you want 32 samples to capture slightly more than one period of the input signal):

00000000000011111111111111000000 // 50% clean duty cycle (not to scale)
T           T             T
01010000000010101111111111010100 // bouncing samples gathered

Shifting the 32 samples right until a stable value is detected. Here "stable" is 5 consecutive samples all high:

                           vvvvv // 5 lowest bits with iterative shifting
01010000000010101111111111010100 // fails 0x14 
00101000000001010111111111101010 // fails 0x0A 
00010100000000101011111111110101 // fails 0x05
00001010000000010101111111111010 // fails 0x02
00000101000000001010111111111101 // fails 0x01
00000101000000001011001011111111 // Succeeds 0x1F
            ^----1/2 period----^    XOR of 1 indicates signal is toggling

This example data has captured a recent transition from hi to low. This works equally well if the transition were from low to hi. Convince yourself the following while() loop detects either 5 set bits or 5 reset bits as the low order bits when shifting:

int range = 0;
while( range++ < 10 && (sample & 0x1F) < 0x1F || (sample & 0x1F) > 0 ) )
    sample >>= 1;

toggling = range < 10 && ((sample & 0x1) ^ ((sample >> 20) & 0x1));

Once you have a stable value in the low order bits, XOR'ing with a bit to the left at 1/2 the period of the signal will compare stable values.
If the result of the XOR is 1, then the signal is flashing.
If the result of the XOR is 0, the signal is stable (may be high, may be low.)

The range counter is necessary to prevent a signal that is ON from shifting far enough to cause the prepended zeros of shifting to be considered.

If this detection is critical, one could involve more bits being XOR'd to form a majority decision as to whether or not the input signal is alternating high/low.

0
Lundin On

If you need a high accuracy reading of the time elapsed, then an edge-triggered input capture interrupt is the only way to go. But having interrupts on GPIO signals with potential noise in them is problematic, so in that case maybe consider an external RC lowpass filter to remove spikes.

Otherwise, if the edge-triggered interrupt isn't feasible (the signal is just too noisy etc), the most robust way would be to have a cyclic timer interrupt triggering lets say 10 times more frequent than the expected frequency. So 1/2.5 = 400ms, lets have the interrupt trigger once every 40ms.

From inside the ISR store results in a bit field. For example we can use a unsigned int to store a bit field of samples. Conceptual pseudo code:

static volatile pin_status = UNKNOWN; 

void isr (void)
{
  static unsigned int samples = 0;

  volatile bool pin_level = some_gpio_pin;
  samples <<= 1;
  samples |= pin_level;

  static const ten_samples = 0x3FFu;
  pin_status = lookup_table [samples & ten_samples]; 
}

(A look-up table would chew up lots of flash, but there are many other ways to do this. You could also store samples in a RAM byte array and use memcmp() to look for certain patterns - slower and RAM consuming but doesn't take up any flash.)

samples will end up a bit stream of samples taken each 40ms, so a binary sequence of 1 1 1... (10 times) corresponds to a high and a sequence with 0 0 0... (10 times) corresponds to a low. You can build in a tolerance of 10 samples +/-1 or so, depending on how picky you are. Since this timer will be running so slowly, we get debouncing implicitly... LEDs don't bounce, so we are essentially just doing some manner of software EMI filtering here.

Keep your system clock inaccuracy in mind, especially if you are running on an internal oscillator instead of external quartz.