Showing results for 
Search instead for 
Did you mean: 

Determine GPIO State: PWM or High or Low - changing state

Scott Miller
Associate II

Hi, forgive the newby question, but I'm a newby to STM and micros in general. I'm a hardware engineer and have not really ventured too far into the code world until now.

I need to monitor 4 GPIOs. They could be any of 3 states: PWM (~50% duty, 1 second on and 1s off) or High or Low. The pins will be changing state, (i.e. moving from PWM to on, to off) periodically. I need to monitor the state of all 4, and determine the "mode" that the device I'm monitoring is operating in. After I determine the pin mode, my thought was to assign a variable to each pin mode, and determine the overall mode based on the pin modes.

I'm struggling with how to detect the PWM, and how to detect the pin mode change. I know this is dead easy for a lot of you on here.

My first thought was to set up a "pin_state" variable (0=low DC, 1 = high DC, 2 = PWM), "last_state" variable (0=low DC, 1 = high DC), and a "count" variable for each GPIO. Then set a timer interrupt every 100ms to read each pin. DC is easy. Check the pin polarity. If the input is high, and last_state=high, and pin_state=high, and counter is >10, keep pin_state=high. If the next read of that pin is low, and last_state=high, and pin mode = high, reset counter, set pin_state to low (and set last_state = low). This is not hard to envision the rest of that logic, so I won't continue.

But here's where I'm falling down... when I have a pin_state transition, what do I set the mode to? I can't set it to either mode.  I can set to one mode, and wait another second to see if it transitions again. with PWM, that is where I'm getting foggy. How to differentiate between a pin that is simply transitioning from high to low, versus one that has a PWM that is regularly toggling? That's where the count variable comes in. If the pin is read high, and current_state=high, do nothing. If the pin is high and current_state is PWM, increment the count variable (if the transition occurs when the count <=10 or 11, I know it is toggling).  If the pin goes low, reset the count variable, and set state = low? or PWM? I guess I could say if previous state was PWM, chances are it is still PWM, and if the input doesn't toggle in the next second, I switch it to low.   

Another newby question... where would you put the variable updates and code? Inside the interrupt, the count variable update is real time, vs in the while loop, it isn't. I suppose with very little code in the while loop, it is blazing through and *basically real time.  Would you put the if statements to set the state in the interrupt handler, or just increment a count variable in the interrupt, and do all the work in the main loop?

Am I going about this right? Is there a better way? Since I'm new, I want to keep this simple. I don't want to worry about debouncing GPIO, which is why I'm polling pins rather than looking for edges.  This micro (STM32G01) is doing very little else.  I will blink some LEDs based on state, and change two relays based on state, but that is it.

Code or pseudo code would be super helpful. I don't want to be spoon-fed, but I need to talk in basic terms here as I'm learning to crawl. I'm familiar with CubeMX (built into the IDE now), as I use that to coordinate with the firmware guy.

Associate III

Let's say you read the pins 10 times a second as you said.  Maintain an array of the last 30 samples continuosly.  If they are all low, you have Low DC, if they are all high, High DC.  To determine PWM, there must be say 8 low and 8 high samples in the array.

If, for example, there are 25 low samples and then 5 high samples, you do not have enough information to know what the next state is, so leave the state as Low DC (or have an internal state Change Pending).  A few seconds later, the array will have the information to know if the new state should be High DC or PWM.

Actually, PWM identification will have to be more complicated, something like that the array must have samples in alternating states (low followed by high followed by low, or high, low, high). 

I actually need to respond within a max 2 seconds of state change. Slightly over 1 second is preferable. I need at least slightly over 1 second to see that next edge (or lack thereof). I thought about taking your idea and simplifying it (averaging), or do a fifo, but my need for precision would not allow for that. 

  last_level, is_counting: boolean; 
  result, counter: integer;
  last_level = ReadPin(); 
  result = last_level; 
  counter = 0; 
  is_counting = false;
  delay 100ms; 
  if (last_level == ReadPin()) then it's no edge so begin
    if is_counting then there was edge some time ago so begin
      if counter > 5 then edge was more than 500ms in the past i.e. it's a stable level now, so begin
        result = level, is_counting = false
  end else it's an edge so begin
    last_level = not last_level,
    if is_counting then there was an edge less or equal 500ms ago, so begin
      result = pwm, counter = 0,
    end else there was steady level so begin
      is_counting = true, counter = 0
loop end

Rewrite it to a form intelligible to you (C language, block diagram, whatever works for you). Draw yourself a timing diagram with all possible transitions and manually simulate this algorithm, observing how variables change in time.