## Evaluate the PWM signal

Nitro_fpv
Posts: 89
Joined: Tue Mar 30, 2021 11:56 am
Location: Switzerland

### Re: Evaluate the PWM signal

Sorry horuable , you posted something in front of me at the same time that I have to read it first.

Nitro_fpv
Posts: 89
Joined: Tue Mar 30, 2021 11:56 am
Location: Switzerland

### Re: Evaluate the PWM signal

horuable wrote:
Sun Apr 11, 2021 1:02 pm

While doing this I've also slightly modified the code because there's no need to make all the calculations and mapping on every loop, even if no new data was read. Also map shouldn't be used for function name because there's already a built-in function called map.

Code: Select all

``````from machine import Pin, PWM
from time import ticks_us, ticks_diff
from PWMCounter import PWMCounter

pwm  = PWM(Pin(14))
pwm.freq(200)

def interpoliert(x, i_m, i_M, o_m, o_M):
return max(min(o_M, (x - i_m) * (o_M - o_m) // (i_M - i_m) + o_m), o_m)

#             __input___   __Output__
mapping = [
1500  , # Failsafe - For any input value not in any range
[  500, 1000,  1000, 1000 ],
[ 1000, 1485,  1000, 1400 ],
[ 1515, 2000,  1600, 2000 ],
[ 2000, 2500,  2000, 2000 ],
]

def map_(n, mapping):
for idx in range(1, len(mapping)):
inmin, inmax, outmin, outmax = mapping[idx]
if n >= inmin and n <= inmax:
i = (n - inmin) / (inmax - inmin)
o = ((outmax - outmin) * i ) + outmin
return int(o)
return mapping[0]

# We'll use counter pin for triggering, so set it up.
in_pin = Pin(19, Pin.IN)
# Configure counter to count rising edges on GP15
counter = PWMCounter(19, PWMCounter.LEVEL_HIGH)
# Set divisor to 16 (helps avoid counter overflow)
counter.set_div(16)
# Start counter
counter.start()

last_state = 0
pwmout = 0

while True:
if ~(x := in_pin.value()) & last_state:
pwmout = ((counter.read_and_reset() * 16) // 125)
convertoutput = (interpoliert(deadzone,500, 2500, 6555, 32777 ))
pwm.duty_u16(convertoutput)
last_state = x
``````

Your new code now works perfectly without jerks!

What was my mistake?
The failsave is still missing, can I incorporate that from your first example?

I am so happy

hippy
Posts: 9990
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

### Re: Evaluate the PWM signal

Nitro_fpv wrote:
Sun Apr 11, 2021 1:11 pm
I found the cause!
I now know what is causing the twitches.
I am not sure you do, though I will accept you appear to have it working, That however appears to me to be due to dropping the duty to 0%, turning the output pulses off. That causes the servo to remain at its last position.

My interpretation is that whatever input you are providing is sometimes too low or too high, causes the 1500 fail-safe default to be used occasionally which causes the servo to jerk before it recovers to a more legitimate value.

Though more likely that jerking occurs when it occasionally moves into the dead-zone before moving out.

By using a fail-safe of zero you are effectively telling the servo to 'ignore the dead-zone, stay where you last were'.

That provides hysteresis which may remove the problem you have but causes larger jumps, doesn't retain any fail-safe functionality -

Code: Select all

``````2000 :     :     :     :     ______________
:     :     :     :    /:     :     :
:     :     :     :   / :     :     :
:     :     :   _____/  :     :     :
1500 :     :     :  |  :     :     :     :
:     :     :   _____|  :     :     :
:     :     :  /  :     :     :     :
:     :     : /   :     :     :     :
1000 :____________/    :     :     :     :
:     :     :     :     :     :     :
0    500   1000  1500  2000  2500  3000
Input

``````
I was never convinced the mapping should be as you specified it with steps in to and out of the dead-zone -

Code: Select all

``````2000 :     :     :     :     ______:     :
:     :     :     :    /:     |     :
:     :     :     :   / :     |     :
:     :     :     :  /  :     |     :
1500 :_____:     :    ___|   :     |_____:__
:     |     :   | :     :     :     :
:     |     :  /  :     :     :     :
:     |     : /   :     :     :     :
1000 :     |______/    :     :     :     :
:     :     :     :     :     :     :
0    500   1000  1500  2000  2500  3000
Input
``````
I thought you would be better off without those steps around the dead-zone -

Code: Select all

``````2000 :     :     :     :     ______:     :
:     :     :     :    /:     |     :
:     :     :     :   / :     |     :
:     :     :     :  /  :     |     :
1500 :_____:     :    ___/   :     |_____:__
:     |     :   / :     :     :     :
:     |     :  /  :     :     :     :
:     |     : /   :     :     :     :
1000 :     |______/    :     :     :     :
:     :     :     :     :     :     :
0    500   1000  1500  2000  2500  3000
Input
``````
That would be -

Code: Select all

``````#             __input___   __Output__
mapping = [                      1500  , # Failsafe - For any input value not in any range
[  500, 1000,  1000, 1000 ],
[ 1000, 1485,  1000, 1500 ],
[ 1515, 2000,  1500, 2000 ],
[ 2000, 2500,  2000, 2000 ],
]
``````
There's nothing I can see wrong with the mapping function I provided so it looks to me to be inappropriate mapping which is causing a result you did not actually desire, did not anticipate.

Nitro_fpv
Posts: 89
Joined: Tue Mar 30, 2021 11:56 am
Location: Switzerland

### Re: Evaluate the PWM signal

Hello hippy

1: I did not say that the mapping is to blame, but together with reading the WPM there are (were) jerks.
Probably the whole thing was too time-critical.
2: I really need the dead zone that I wrote so that the BL regulators don't stutter.
from 1500us to 1600us the motors stutter, as well as from 1500us to 1400us.
The motors must not stutter, therefore the starting value of 1600us / 1400us.

Attached is a small video of the application that is involved, because you can see the sense of this special dead zone:

hippy
Posts: 9990
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

### Re: Evaluate the PWM signal

horuable wrote:
Sun Apr 11, 2021 1:02 pm
hippy wrote: just that it's not necessarily reading only one pulse worth of counts.
That was my concern as well, but I've dismissed it because ... that gives anywhere from 2800 to 4200 us to read and clear the counter which should be more than enough.
I would have generally accepted that, argued the same myself, but my own testing has shown there can be unanticipated timing variations, substantially large ones -

viewtopic.php?p=1848943#p1848943

That was repeatedly timing a simple "dummy = 1 * 2 / 3" calculation. That showed 15us time on average, but there were times it showed 7200us, over 7ms. And that is repeatable.

Try this and you will see that same calculation takes over 5ms every half second or so -

Code: Select all

``````import time
epoch = time.ticks_us()
while True:
start = time.ticks_us()
dummy = 1 * 2 / 3
end = time.ticks_us()
elapsed = end - start
if elapsed > 5000:
print((time.ticks_us() - epoch) / 1000000)
``````
There's nothing in that code which would suddenly take more than 5ms, and the same is observed with 'minicom' so not something caused by Thonny.

That has to be something MicoPython is doing itself, handling an interrupt, or clock, garbage collection, or something. I suspect garbage collection because adding 'gc.collect()' before 'start=' massively improves things, though elapsed time does still increase by tens of us at times.

hippy
Posts: 9990
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

### Re: Evaluate the PWM signal

Nitro_fpv wrote:
Sun Apr 11, 2021 3:08 pm
2: I really need the dead zone that I wrote so that the BL regulators don't stutter.
from 1500us to 1600us the motors stutter, as well as from 1500us to 1400us.
The motors must not stutter, therefore the starting value of 1600us / 1400us.
I understand that. What I am saying is that the mapping you originally chose will cause stutter more than avoid it.

Let's consider an input of 1485; that gives 1400 output. Because of your step into the dead-zone, an input of 1486 will give 1500.

So a single input variance will cause a 100 jump in output.

If the input is stable at 1485, jumps to a single 1486, before returning to a stable 1485, there will be a jerk as the servo begins to moves from 1400 to 1500 as instructed then back to 1400. It won't be a huge jerk as it won't be heading towards 1500 for long enough to be a big jerk, but it will have been on its way and that will appear as a stutter.

It probably isn't doing exactly that but it looks very much like something similar. It may be actual input variance, it may be incorrectly reading the input, sometimes, if far too large, pushing the input beyond its limit, into fail-safe which is indistinguishable from the dead-zone.

I can understand what you are trying to do but that cannot work using a single mapping with stepped dead-zone without the associated problems described above being observed. You need something to track the input value, determine if it's rising or falling, to prevent undesired jumping forwards and backwards across the dead-zone steps to prevent unwanted stutter. Simply smoothing, filtering or averaging won't work because you will still get stutter as the step is briefly crossed. You need some hysteresis to prevent that.

Nitro_fpv
Posts: 89
Joined: Tue Mar 30, 2021 11:56 am
Location: Switzerland

### Re: Evaluate the PWM signal

......
Here is the final program.
.....
All modules now work perfectly together!
I also included the failsave from the first version.

Digital precision servo, logic analyzer, all deliver precise values, and all without jerks!

I thank everyone who helped me.
You don't know how happy I am.

Code: Select all

``````from machine import Pin, PWM
from time import ticks_us, ticks_diff
from PWMCounter import PWMCounter

pwm  = PWM(Pin(14))
pwm.freq(200)

def interpoliert(x, i_m, i_M, o_m, o_M):
return max(min(o_M, (x - i_m) * (o_M - o_m) // (i_M - i_m) + o_m), o_m)

#             __input___   __Output__
mapping = [
1500  , # Failsafe - For any input value not in any range
[  500, 1000,  1000, 1000 ],
[ 1000, 1485,  1000, 1400 ],
[ 1515, 2000,  1600, 2000 ],
[ 2000, 2500,  2000, 2000 ],
]

def map_(n, mapping):
for idx in range(1, len(mapping)):
inmin, inmax, outmin, outmax = mapping[idx]
if n >= inmin and n <= inmax:
i = (n - inmin) / (inmax - inmin)
o = ((outmax - outmin) * i ) + outmin
return int(o)
return mapping[0]

# We'll use counter pin for triggering, so set it up.
in_pin = Pin(19, Pin.IN)
# Configure counter to count rising edges on GP15
counter = PWMCounter(19, PWMCounter.LEVEL_HIGH)
# Set divisor to 16 (helps avoid counter overflow)
counter.set_div(16)
# Start counter
counter.start()

last_update = ticks_us()
timeout_period = 20000
timeout = False
last_state = 0
convertoutput = 0

while True:
if ~(x := in_pin.value()) & last_state:
pwmout = ((counter.read_and_reset() * 16) // 125)
last_update = ticks_us()
timeout = False
convertoutput = (interpoliert(deadzone,500, 2500, 6555, 32777 ))
if ticks_diff(ticks_us(), last_update) > timeout_period and timeout is not True:
print("Timeout")
timeout = True
counter.reset()
convertoutput = 19666
last_state = x
pwm.duty_u16(convertoutput)
``````

horuable
Posts: 121
Joined: Sat Mar 06, 2021 12:35 am

### Re: Evaluate the PWM signal

hippy wrote:That showed 15us time on average, but there were times it showed 7200us, over 7ms. And that is repeatable.
Wow! I wasn't expecting this much difference. Thanks for bringing it to my attention, I definitely must remember that in the future.
Just a small tip though: when calculating time differences you should use time.ticks_diff() to avoid problems with timer wrapping.

That being said I wouldn't be myself if I hadn't checked the timing of a loop in my previous program. To do that I set the GP2 to high at the beginning of the loop and then to low at the end and feed this signal to the scope with the trigger set to fire only when a pulse longer than 400 us and shorter than 1 s is detected. Then I've left it to run for over 30 min and it turns out it didn't fire once, so I've started digging around a bit in search of a possible explanation for these huge differences in timing. It turns out that doing a division is a culprit here. In my latest version I have:

Code: Select all

``pwmout = ((counter.read_and_reset() * 16) // 125)``
which uses floor division, since we want to get an integer anyway. This is the version that doesn't have any strange timing problems.
But when I use an older version with normal division and then cast to int:

Code: Select all

``````pwmout = ((counter.read_and_reset() * 16) / 125)
pwmout = int(pwmout)
``````
I can see the occasional 7 ms loop similar to your test code. This of course throws pulse width measurement off since it measures 2 cycles.

If you change the calculation in your test code to:

Code: Select all

``dummy = 1 * 2 // 3``
it will almost never show output even if you'll lower elapsed threshold to 10 us.
And you can do any calculation as long as it returns an integer. Try:

Code: Select all

``(2**12 - 7 * 3) % 10``
and it still works with 10 us thresold.

hippy
Posts: 9990
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

### Re: Evaluate the PWM signal

horuable wrote:
Sun Apr 11, 2021 6:30 pm
Just a small tip though: when calculating time differences you should use time.ticks_diff() to avoid problems with timer wrapping.
Yes, that is indeed a habit I should get into. It's not been a problem with the proof of concept, test and example, code I have been writing but it will eventually bite me or, worse, someone else. Thanks.

Nitro_fpv
Posts: 89
Joined: Tue Mar 30, 2021 11:56 am
Location: Switzerland

### Re: Evaluate the PWM signal

For everyone here, my thanks: