Strategies for robust handling of a shutdown signal pattern - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 18, 2022, 08:47:17 pm
content: I'm trying to come up with the best strategy to handle a certain type of input signal pattern in a robust and fail-safe way. I'm wondering if anyone has any suggestions.Basically, the signal is given to indicate that it is safe to shut off power to the subsystem that my microcontroller is managing. The signal idles in the low state until such time as shut-down is needed, upon which it will go high for 100 ms (tACTIVE), low for another 100 ms (tINACTIVE), then finally high again, where it will remain until power is cut. However, because of the need for voltage-level translation, I have the input signal driving the gate of a MOSFET, so the logic levels at my MCU pin are actually inverted. See diagram below.What I'm trying to aim for is a robust way of avoiding accidental triggering (due to noise, random signal toggling, or whatever).My first thought was to handle it like this:- A pin-change edge interrupt (falling or rising) is triggered at each change of state.- When the ISR is called, set a timeout (say, 110 ms). Next time the ISR runs, examine if the timeout has expired; if not, increment a counter; if so, decrement that counter.- If the counter reaches a value of 3, flag that shutdown is required.However, I realise while this handles the signal randomly toggling at lengthy intervals, it doesn't handle erroneous chatter faster than the specified timing.Is there any better way to do this? To only respond to the specified signal pattern with perhaps a +/-10 ms tolerance on timing?I have a basic 16-bit CAPCOM timer/counter available to use if it can be done that way. There is also an RC low-pass filter on the incoming signal line that should filter out any high-frequency stuff, so I don't need to worry about that kind of noise.

author: langwadt
author_rank: Super Contributor
timestamp: September 18, 2022, 09:00:36 pm
content: if you have some timer running is seems slow enough that you can just make a state machine that samples the signal and advances through the states as it fits the pattern

author: fourfathom
author_rank: Super Contributor
timestamp: September 18, 2022, 09:48:40 pm
content: To be robust you want to test the received signal against the defined pattern. How much variation in the valid signal can you tolerate before declaring a match? Anyway, think about timestamping the input transitions and looking for a strong-enough correlation to the defined pattern.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 19, 2022, 10:22:10 pm
content: I've been looking into the use of input capture on a timer/counter to do this. I think it might work, but there's one loose end that I can't quite figure out how to handle.I can set up two input capture units on the timer, both driven by the same input pin: one to capture falling edges, one rising edges. Also configure the timer to tick at 3.9 kHz, making the resolution of periods I can measure approx. 256 usec, which I think should be adequate for this task. With a 16-bit counter, it should only overflow every 168 seconds.Then, in the timer capture ISR, I do the following:Upon a falling edge capture:If in state '0' (first falling edge), read the counter value and store as 'A'. Advance to state '1'.If in state '2' (second falling edge), read the counter value and store as 'A'. Calculate the absolute difference between 'B' and 'A'. Based on timer frequency, if that value is again within expected range for 100 ms +/- 10, then flag that we have a valid 'inactive' period length. Advance to state '3'.Upon a rising edge capture:If in state '1', read the counter and store as 'B'. Calculate the absolute difference between 'A' and 'B'. Based on timer frequency, if that value is within expected range for 100 ms +/- 10, then flag that we have a valid 'active' period length. Advance to state '2'.If in state '3', do nothing and go back to state '0'.Whenever we have both valid 'active' and 'inactive' periods, disable the timer and flag that a power shut-down is needed.However, I'm not sure how to properly handle the situation where the timer counter has overflowed in the middle of the 'active' or 'inactive' periods. I could probably configure an overflow ISR and count the number of overflows, then if there were none or just one overflow during a period, we're still good - any more than that indicates that period has been way too long anyway. I would reset the overflow count each time an edge is captured.However, I then have the remaining loose end of what to do when the overflow counter itself overflows. I mean, imagine the following situation: a rogue random falling edge occurs, and is captured, then nothing happens for minutes or even hours, but then another random rogue rising edge occurs, but happens at the exact time to coincide with the overflow counter having just rolling over to zero. My logic above would detect that as an 'valid' period. How do I mitigate that?

author: fourfathom
author_rank: Super Contributor
timestamp: September 20, 2022, 06:02:48 am
content: You need a higher-level timeout that resets the whole thing, perhaps triggered by the timer overflow.Also, is it possible to reset or pre-load the timer as part of the edge-detect routine? This way you don't have to worry about overflow, and you don't even have to calculate differences. Of course you have to compensate for the time elapsed during the reset action.

author: mikerj
author_rank: Super Contributor
timestamp: September 20, 2022, 09:25:15 am
content: Is there a reason for the 100ms high going pulse after the initial falling edge rather than just having a single high->low transition i.e. are there other signal patterns that could be present on this pin?If not a very simple method would just be to poll the pin with a suitable slow timer interrupt (e.g. 5ms) and shut down when it has been held low for greater than some threshold (e.g. 120ms), but this would delay the shutdown until that period after the final falling edge. Almost as simple would be to keep this slow poling scheme and implement a simple state machine that requires this pattern to step through, but you'd need to define all possible cases e.g. what if the reset pin goes low and stays low (no 100ms pulse)?

author: cv007
author_rank: Frequent Contributor
timestamp: September 20, 2022, 08:21:13 pm
content: The capture is not necessary for the times in use, as I would guess you are not blocking in some other interrupt for extended periods. So just use the pin interrupts to manually deal with the timer.Something like-Code://falling/rising edge pin interupt set,//timer enabled (initially set counter to 0xFFFF so first edge is invalid)isr(){bool falling = isFallingEdge(); //0=rising, 1=fallingbool ovf = isTimerOvfFlag(); //ovf=1=timeouttimerClearOvfFlag();uint16_t cnt = timerCount(); //current countbool valid = !ovf && cnt>=90_ms && cnt<=110_ms; //only valid if all true// falling|valid|set counter// 0 0 0xFFFF rising/invalid, 0xFFFF will make next falling invalid// 0 1 0 rising/valid, counter = 0// 1 0 0 falling/invalid, counter = 0// 1 1 0 falling/valid, counter = 0, shutdowntimerSetCount( !falling && !valid ? 0xFFFF : 0 );if( falling && valid ) shutdownDevice();}

author: nfmax
author_rank: Super Contributor
timestamp: September 20, 2022, 08:52:05 pm
content: You can use a UART, if you have one spare. The Rx input is your signal (I think with another inverter, I'm not sure I follow your description). Set the UART clock so that the bit period is 20ms, and program it to 8 data bits, no parity, 1 stop bit. Then the low-going 100ms pulse sends the start bit followed by four 0's; the high-going 100ms pulse is four 1's followed by the stop bit. Your receive interrupt handler can then check for a 0x0F character (and no framing errors).

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 20, 2022, 10:31:32 pm
content: Quote from: HwAoRrDk on September 19, 2022, 10:22:10 pmI just use a pin change interrupt and ARM_DWT_CYCCNT or whatever other timer I'd be using, with a hook to an "idle" function that gets called at irregular intervals, but at least once per quarter of the timer wraparound interval. One thing the idle function does, is check if the latest transition on the signal pattern state machine has expired, setting a suitable flag in that case. Such "idle" or timeout checks are very common, so it is generally a good idea to make sure you can do them efficiently. I like the "at least once per quarter timer wraparound" (which is not exactly the same as at least four times per timer wraparound, because the former says that the interval between consecutive calls is at most a quarter of the expiry interval, whereas the latter technically allows almost a full wraparound interval between calls).So, to recap: you have a simple state machine, with a state indicator (number or pointer or whatever), and at least one timestamp, plus an expiry flag on that timestamp, and some kind of idle/cleanup/yield function that is called often enough. In the simplest case, you set that timestamp to now plus the time after which the current state expires. The idle function only compares the current time to that timestamp, and sets the expiry flag when the timer exceeds that timestamp. The state machine examines the expiry flag.If your timer and timestamp are 32-bit, then usinguint32_tand unsigned integer arithmetic (uint32_t timestamp, timer), the check is trivial (but odd-looking):if ((int32_t)(timestamp-timer) <= 0) expired_flag = 1;This works, becauseuintN_tarithmetic is modular (wraps around), andintN_tuses two's complement format. It isNOTthe same asif (timestamp<=timer) expired_flag = 1;, because this latter fails whentimestampis very large andtimervery small (i.e., when the wraparound happens between the two); the former works even in that case.In the state machine code, the expiry flag is another datum you use to determine the proper transition. Also, you clear theexpired_flagexactly after setting thetimestamp, always, and only then. Both should bevolatile, so the compiler knows their values can be changed unexpectedly by e.g. code running in an interrupt.In other words, we're very much along the same lines, just tiny differences in the practical implementation details.There are many different ways to ensure the idle function (or function set, since you probably will have more than one such timer expiry handlers) is called often enough, varying from timer interrupts to rate-limited "very often called" functions – similar toyield()in the Arduino environment (which is also used in various OSes to yield running time to other tasks/processes), so I haven't bothered to talk about that, since something like it is always needed whenever you have timers that can wrap around.

author: mushroom
author_rank: Regular Contributor
timestamp: September 20, 2022, 10:54:09 pm
content: Quote from: mikerj on September 20, 2022, 09:25:15 amThe Raspberry Pi has this exact same behaviour whith this exact same timing when dtoverlay=gpio-poweroff is activated. See line 1323 :https://github.com/raspberrypi/firmware/blob/master/boot/overlays/READMEA RasPi related project ? Curious to know...I could be off-topic... This caused me a problem while using a relay to auto-turn off a Pi (relay clicking). Problem solved with a capacitor.

author: nfmax
author_rank: Super Contributor
timestamp: September 21, 2022, 06:48:32 am
content: Quote from: nfmax on September 20, 2022, 08:52:05 pmI made a mistake - didn't read the OP properlyAfter detecting the 0x0F, you need to look for a line break (i.e. 10 bit periods with no stop bit) to confirm the input goes low and stays low for > 100ms. Most UARTs will do this automatically,but not all.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 22, 2022, 05:31:04 am
content: Quote from: nfmax on September 20, 2022, 08:52:05 pmI do not.Quote from: Nominal Animal on September 20, 2022, 10:31:32 pmFunny you should say that, because I do have some general-purpose code elsewhere for taking a system timestamp (incremented on a 1 kHz timer interrupt), adding to it to make an expiry timestamp, and to check if that expiry has expired. I considered what it would take to use that, but I'm hesitant to do that as it's a little too heavyweight, because a 'timestamp' for that isn't just a straight integer - it's a uint32_t tick count plus a uint8_t epoch count (i.e. tick overflow count). Needed that because I will have up-times of more than 49 days, but a uint64_t was overkill. This is an 8-bit micro, so throwing uint32_t's around isn't trivial business.Quote from: mushroom on September 20, 2022, 10:54:09 pmYes, it is a Raspberry Pi. Well, the subsystem has a Pi as part of it, at least. The Pi is essentially the arbiter of whether power should be shut off to the whole lot.I've been playing more with the idea of using timer capture, and I think that method will actually be fairly simple - a variation on what I talked about earlier, with state machine, etc.If I configure the timer to tick at 500 kHz, it will overflow in 131 ms - a little over the desired valid period length. Whenever an input capture takes place (on either rising or falling edge), I simply have to - depending on state machine state - use the captured counter value to determine if the period length is valid. The key is to reset the counter to zero each time. That way, I'm not needing to subtract one counter value from another and deal with overflow ISRs, etc. All I need to do is check whether the captured counter value is within the desired range, and whether or not an overflow occurred during that period. Within range and no overflow? A valid period. Otherwise not valid.The preliminary code I've come up with for this is as follows. BTW, if I didn't mention it before, this is for STM8.Code:#define PWR_OFF_IN_ACTIVE_PERIOD_MS 100#define PWR_OFF_IN_ACTIVE_MARGIN_MS 10#define PWR_OFF_IN_INACTIVE_PERIOD_MS 100#define PWR_OFF_IN_INACTIVE_MARGIN_MS 10#define PWR_OFF_TIMER_PRESCALER_POWER 5#define PWR_OFF_TIMER_RESOLUTION_US (1000000 / (F_CPU / (1 << PWR_OFF_TIMER_PRESCALER_POWER)))#define PWR_OFF_TIMER_COUNT_ACTIVE_MIN (((PWR_OFF_IN_ACTIVE_PERIOD_MS - PWR_OFF_IN_ACTIVE_MARGIN_MS) * 1000UL) / PWR_OFF_TIMER_RESOLUTION_US)#define PWR_OFF_TIMER_COUNT_ACTIVE_MAX (((PWR_OFF_IN_ACTIVE_PERIOD_MS + PWR_OFF_IN_ACTIVE_MARGIN_MS) * 1000UL) / PWR_OFF_TIMER_RESOLUTION_US)#define PWR_OFF_TIMER_COUNT_INACTIVE_MIN (((PWR_OFF_IN_INACTIVE_PERIOD_MS - PWR_OFF_IN_INACTIVE_MARGIN_MS) * 1000UL) / PWR_OFF_TIMER_RESOLUTION_US)#define PWR_OFF_TIMER_COUNT_INACTIVE_MAX (((PWR_OFF_IN_INACTIVE_PERIOD_MS + PWR_OFF_IN_INACTIVE_MARGIN_MS) * 1000UL) / PWR_OFF_TIMER_RESOLUTION_US)typedef enum {PWR_OFF_IN_STATE_IDLE_PRE,PWR_OFF_IN_STATE_ACTIVE,PWR_OFF_IN_STATE_INACTIVE,PWR_OFF_IN_STATE_IDLE_POST,} pwr_off_in_state_enum_t;static void tim2_init(void) {TIM2_CR1 = 0;TIM2_IER = _BV(TIM2_IER_CC2IE) | _BV(TIM2_IER_CC1IE);TIM2_CCMR1 = ((uint8_t)0xF << TIM2_CCMR1_IC1F0) | _BV(TIM2_CCMR1_CC1S1);TIM2_CCMR2 = ((uint8_t)0xF << TIM2_CCMR2_IC2F0) | _BV(TIM2_CCMR2_CC2S0);TIM2_CCMR3 = 0;TIM2_CCER1 = _BV(TIM2_CCER1_CC2E) | _BV(TIM2_CCER1_CC1P) | _BV(TIM2_CCER1_CC1E);TIM2_CCER2 = 0;TIM2_PSCR = (PWR_OFF_TIMER_PRESCALER_POWER << TIM2_PSCR_PSC0);TIM2_ARRH = 0xFF;TIM2_ARRL = 0xFF;TIM2_SR1 = 0;TIM2_SR2 = 0;}void tim2_cap_isr(void) __interrupt(ISR_TIM2_CAP_COM) {static pwr_off_in_state_enum_t state = PWR_OFF_IN_STATE_IDLE_PRE;static bool valid_active = false, valid_inactive = false;const uint8_t sr1 = TIM2_SR1;const uint8_t sr2 = TIM2_SR2;if(bit_is_set(sr1, TIM2_SR1_CC1IF)) { // FALLING edgeconst uint16_t count = (TIM2_CCR1H << 8) | TIM2_CCR1L; // Reading L auto-clears CCnIF.switch(state) {case PWR_OFF_IN_STATE_IDLE_PRE:valid_active = false;valid_inactive = false;state = PWR_OFF_IN_STATE_ACTIVE;break;case PWR_OFF_IN_STATE_INACTIVE:valid_inactive = (count >= PWR_OFF_TIMER_COUNT_INACTIVE_MIN &&count <= PWR_OFF_TIMER_COUNT_INACTIVE_MAX &&bit_is_clear(sr1, TIM2_SR1_UIF));if(valid_active && valid_inactive) {// flag power shut-off here...}state = PWR_OFF_IN_STATE_IDLE_POST;break;}}if(bit_is_set(sr1, TIM2_SR1_CC2IF)) { // RISING edgeconst uint16_t count = (TIM2_CCR2H << 8) | TIM2_CCR2L; // Reading L auto-clears CCnIF.switch(state) {case PWR_OFF_IN_STATE_ACTIVE:valid_active = (count >= PWR_OFF_TIMER_COUNT_ACTIVE_MIN &&count <= PWR_OFF_TIMER_COUNT_ACTIVE_MAX &&bit_is_clear(sr1, TIM2_SR1_UIF));state = PWR_OFF_IN_STATE_INACTIVE;break;case PWR_OFF_IN_STATE_IDLE_POST:valid_active = false;valid_inactive = false;state = PWR_OFF_IN_STATE_IDLE_PRE;break;}}TIM2_EGR |= _BV(TIM2_EGR_UG); // Reset counterTIM2_SR1 &= ~_BV(TIM2_SR1_UIF); // Clear overflow flag}

author: mikerj
author_rank: Super Contributor
timestamp: September 23, 2022, 07:01:41 am
content: You mentioned you wanted this to be "fail safe" so what is the safe state for this system? e.g. if the reset signal goes low and stays low, would you want your sub-system remain powered up indefinitely?

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 23, 2022, 07:36:44 am
content: If by "reset" signal, you mean the power-off signal, then yes, if it goes low once and stays that way, that won't be a valid power-off signal pattern, so no shut-down should occur.