Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Tue Aug 03, 2021 7:52 am

Hello Hippy, I "pushed" your program into my Pico and got an error message:

File "<stdin>", line 28, in <module>
ValueError: duty larger than period

I don't understand where the "Period" is set in the program. In the test section you only define the output and the pulse width for the PWM. I cannot find any definition of the frequency. What does the PWM generator do if only the pulse width is specified?
Is it possible that one and the same pin (GPIO_PIN = 4) in line 7 is used as input and in line 27 as output?

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Tue Aug 03, 2021 8:23 am

Have reduced the duty value. Everything works fine up to 200,000 microseconds!
The maximum pulse duration of the PWM generator has probably been exceeded. I'll just try to set the PWM generator to a ratio of 1: 1 and then simulate the pulse length using the frequency (1000 Hz = 2 ms / 10 Hz = 200 ms high-level).

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Tue Aug 03, 2021 9:07 am

The 1000 Hz was of course a mistake, but this is how it works:

pwm = PWM(Pin(GPIO_PIN, Pin.OUT))
pwm.freq(250)
pwm.duty_u16(32768)

The repeatability of the measurement is impressive!
If only I understood your program: You are counting down during the pulse duration, I also understand the conversion into a positive count. But where is the time base hidden? How long does this "Jmp (x_dec;" next ") last? I suspect that this instruction represents the actual time measurement, because it counts down as long as the pulse at the input lasts.
Why is the x-value then written to a shift register (FIFO) and what does the following "push" instruction do (I understand "block" as a stop when the shift register is full)?
How long is the shift register actually? In the last part of your program the shift register is read out with "sm.get ()". If the shift register has a certain length, the measured values ​​should be offset in time, right?
Overall, I am enthusiastic about the possibilities of PIO programming, even if I can only understand very little about it at the moment.

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

Re: Speed measurement on a Triton router

Tue Aug 03, 2021 11:04 am

The time base is implicit in the counting. The Python equivalent would be -

Code: Select all

def PioFunction():
   X = 0
   while btn.value() != 0:
     pass
   while btn.value() != 1:
     pass
   # Edge detected - Start counting
   while btn.value() == 1:
     X = X - 1
   Push(X)
Which of course made me realise it's measuring the length of a high pulse, not the time between two rising edges. I'll think abut what that should be later. Hopefully some else has figured that out already.

Each PIO instruction takes one clock cycle, and the frequency of that clock is set by the StateMachine 'freq=' parameter, system frequency if not specified.

The 'X' counts how many times it looped while waiting so needs to be adjusted to be an actual time value later.

The queue the 'push' values get put in in is 4 words deep so yes, each subsequent entry will be of a measurement later than the first pulled with sm.get().

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Tue Aug 03, 2021 3:39 pm

You're right! I measured different ratios at the same frequency. The result are incorrect speed measurement values, because (as you say) the high duration of the input pulse is measured. A second edge query would therefore have to be inserted, which ends the counting of the value "x". Not easy for me in any case, but I'm trying to find a solution ...

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

Re: Speed measurement on a Triton router

Tue Aug 03, 2021 7:10 pm

I find that measuring the time between the same-type edges using PIO is surprisingly hard. I can think of two solutions, both using a second, intermediate, State Machine:

1. One SM reads the input signal and outputs a high level between two rising edges on another pin (I called it GATE_PIN). This new signal is then measured as shown before by hippy.

Code: Select all

# INPUT:
#       _____         _____
#      |     |       |     |
# _____|     |_______|     |_________
# Intermediate OUTPUT:
#       _____________
#      |             |
# _____|             |__________
#      ^-------------^ Measure this

from   machine import Pin, PWM
from   rp2     import asm_pio, PIO, StateMachine
import time

GPIO_PIN = 4
GATE_PIN = 15

btn = Pin(GPIO_PIN, Pin.IN, Pin.PULL_UP)
gate = Pin(GATE_PIN)

@asm_pio(sideset_init=PIO.OUT_LOW)
def PulseIn():
    set(x, 0)           # X = 0;
    wait(0, pin, 0)     # Do {} While ( pin == 1 );
    wait(1, pin, 0)     # Do {} While ( pin == 0 );
    label("loop")       # Do
    jmp(x_dec, "next")  #   X--;
    label("next")
    jmp(pin, "loop")    # While ( pin == 1 );
    mov(isr, x)         # Push(X);
    push(isr, block)

@asm_pio(set_init=PIO.OUT_LOW)
def converter():
    wait(0, pin, 0)
    wait(1, pin, 0)
    set(pins, 1)
    wait(0, pin, 0)
    wait(1, pin, 0)
    set(pins, 0)

sm0 = StateMachine(0, converter, in_base=btn, set_base=gate)
sm0.active(1)

sm1 = StateMachine(1, PulseIn, in_base=gate, jmp_pin=gate)
sm1.active(1)

# This is just for testing. We generate a pulse on an output pin using PWM
# which just happens to be the same pin we are measuring

pwm = PWM(Pin(GPIO_PIN, Pin.OUT))
pwm.duty_ns(20_000)

while True:
    # Note the count is decremented in the PIO routine so it will be a
    # negative value. We need to make it a positive value.
    cnt = (1 << 32) - sm1.get()
    tms = cnt / 62500
    rps = 1_000 / tms
    rpm = rps * 60
    print("{} counted, {} ms rev, {} rps, {} rpm".format(cnt, tms, rps, rpm))
    time.sleep(1)
2. One SM generates very short pulses whenever a rising edge is detected and the second measures a high signal between these pulses.

Code: Select all

# INPUT:
#       _____             _____
#      |     |           |     |
# _____|     |___________|     |_________
# Intermediate OUTPUT:
#______   _______________   _____________
#      | |               | |
#      |_|               |_|
#        ^---------------^ Measure this

from   machine import Pin, PWM
from   rp2     import asm_pio, PIO, StateMachine
import time

GPIO_PIN = 4
GATE_PIN = 15

btn = Pin(GPIO_PIN, Pin.IN, Pin.PULL_UP)
gate = Pin(GATE_PIN)

@asm_pio(sideset_init=PIO.OUT_LOW)
def PulseIn():
  set(x, 0)           # X = 0;
  wait(0, pin, 0)     # Do {} While ( pin == 1 );
  wait(1, pin, 0)     # Do {} While ( pin == 0 );
  label("loop")       # Do
  jmp(x_dec, "next")  #   X--;
  label("next")
  jmp(pin, "loop")    # While ( pin == 1 );
  mov(isr, x)         # Push(X);
  push(isr, block)

@asm_pio(set_init=PIO.OUT_HIGH)
def converter():
    wait(0, pin, 0)
    wait(1, pin, 0)
    set(pins, 0) [4]
    set(pins, 1)

sm0 = StateMachine(0, converter, in_base=btn, set_base=gate)
sm0.active(1)

sm1 = StateMachine(1, PulseIn, in_base=gate, jmp_pin=gate)
sm1.active(1)

# This is just for testing. We generate a pulse on an output pin using PWM
# which just happens to be the same pin we are measuring

pwm = PWM(Pin(GPIO_PIN, Pin.OUT))
pwm.freq(500)
pwm.duty_ns(200_000)

while True:
  # Note the count is decremented in the PIO routine so it will be a
  # negative value. We need to make it a positive value.
  cnt = (1 << 32) - sm1.get()
  tms = cnt / 62500
  rps = 1_000 / tms
  rpm = rps * 60
  print("{} counted, {} ms rev, {} rps, {} rpm".format(cnt, tms, rps, rpm))
  time.sleep(1)
The slight advantage of the second method is that it can easily measure every rotation, while the first one measures only every other rotation, because it needs one rotation to "reset". It'll probably not make any difference in this application, but I think it's nice to have an alternative.

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

Re: Speed measurement on a Triton router

Wed Aug 04, 2021 9:43 am

horuable wrote:
Tue Aug 03, 2021 7:10 pm
I find that measuring the time between the same-type edges using PIO is surprisingly hard.
I didn't find it too bad in the end. It would have been nice to have jump on pin low as well as jump on pin high, but it's not so hard to keep the loop times for high and low periods the same number of cycles; now 3 rather than 2. The nop() could be replaced by a [1] elsewhere but this is clearer IMO. The only other change is the fiddle factor to convert count to time -

Code: Select all

from   machine import Pin, PWM
from   rp2     import asm_pio, PIO, StateMachine
import time

GPIO_PIN = 4

btn = Pin(GPIO_PIN, Pin.IN, Pin.PULL_UP)

@asm_pio()
def Period():
  set(x, 0)
  wait(0, pin, 0)
  wait(1, pin, 0) 

  label("highloop")      #   <--.
  jmp(x_dec, "highnext") # 1    |
  label("highnext")      #      |
  nop()                  # 2    |
  jmp(pin, "highloop")   # 3 ---'
  
  label("lowloop")       #   <--.
  jmp(x_dec, "lownext")  # 1    |
  label("lownext")       #      |
  jmp(pin, "done")       # 2    |   This is a nop while pin is still low
  jmp("lowloop")         # 3 ---'

  label("done")
  mov(isr, x)
  push(isr, block)
    
sm = StateMachine(0, Period, in_base=btn, jmp_pin=btn)
sm.active(1)

# This is just for testing. We generate a pulse on an output pin using PWM
# which just happens to be the same pin we are measuring

pwm = PWM(Pin(GPIO_PIN, Pin.OUT))
pwm.freq(1_000)
pwm.duty_u16(0x8000) # 50%

while True:
  # Note the count is decremented in the PIO routine so it will be a
  # negative value. We need to make it a positive value.
  cnt = (1 << 32) - sm.get()
  tms = cnt / 41668
  rps = 1_000 / tms
  rpm = rps * 60
  print("{} counted, {} ms rev, {} rps, {} rpm".format(cnt, tms, rps, rpm))
  time.sleep(1)
  

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

Re: Speed measurement on a Triton router

Wed Aug 04, 2021 11:05 am

Very neat solution. I think it can still be done with 2 cycles per loop by changing the low counting loop to:

Code: Select all

  label("lowloop")
  jmp(pin, "done")	# 1
  jmp(x_dec, "lowloop")	# 2
  label("done")

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

Re: Speed measurement on a Triton router

Wed Aug 04, 2021 11:07 am

Quax wrote:
Tue Aug 03, 2021 7:52 am
In the test section you only define the output and the pulse width for the PWM. I cannot find any definition of the frequency. What does the PWM generator do if only the pulse width is specified?
I forgot to answer that earlier. Fact is I don't know, am not even sure why what I have works. Truth is I did not care if it was right or wrong, what it was actually doing, so long as it produced a pulse I could measure which gave me some sort of meaningful result.
Quax wrote:
Tue Aug 03, 2021 7:52 am
Is it possible that one and the same pin (GPIO_PIN = 4) in line 7 is used as input and in line 27 as output?
Yes, entirely possible as done here. All pins, even outputs, can be read as input. The trick is in figuring out the order of how things should be to make it work.

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

Re: Speed measurement on a Triton router

Wed Aug 04, 2021 11:21 am

horuable wrote:
Wed Aug 04, 2021 11:05 am
Very neat solution. I think it can still be done with 2 cycles per loop by changing the low counting loop to:

Code: Select all

  label("lowloop")
  jmp(pin, "done")	# 1
  jmp(x_dec, "lowloop")	# 2
  label("done")
Even better. Thanks.

I anticipated that might mean one less X counted but seemingly not.

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Sat Aug 07, 2021 3:27 am

Hello hippy, hello hourable,
Do I understand correctly that the "low counting loop" should be replaced by the suggestion from hourable? I tried that, but it got wrong values. How exactly is this part to be integrated?

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

Re: Speed measurement on a Triton router

Sat Aug 07, 2021 10:25 am

This is my full period timing code with horuable's improvement included -

Code: Select all

from   machine import Pin, PWM
from   rp2     import asm_pio, PIO, StateMachine
import time

GPIO_PIN = 4

btn = Pin(GPIO_PIN, Pin.IN, Pin.PULL_UP)

@asm_pio()
def Period():
  set(x, 0)
  wait(0, pin, 0)
  wait(1, pin, 0) 

  label("highloop")      #   <--.
  jmp(x_dec, "highnext") # 1    |
  label("highnext")      #      |
  jmp(pin, "highloop")   # 2 ---'
  
  label("lowloop")       #   <--.
  jmp(pin, "done")       # 1    |
  jmp(x_dec, "lowloop")  # 2 ---'

  label("done")
  mov(isr, x)
  push(isr, block)
    
sm = StateMachine(0, Period, in_base=btn, jmp_pin=btn)
sm.active(1)

# This is just for testing. We generate a pulse on an output pin using PWM
# which just happens to be the same pin we are measuring

pwm = PWM(Pin(GPIO_PIN, Pin.OUT))
pwm.freq(1_000)
pwm.duty_u16(0x8000)

while True:
  # Note the count is decremented in the PIO routine so it will be a
  # negative value. We need to make it a positive value.
  cnt = (1 << 32) - sm.get()
  tms = cnt / 62500
  rps = 1_000 / tms
  rpm = rps * 60
  print("{} counted, {} ms rev, {} rps, {} rpm".format(cnt, tms, rps, rpm))
  time.sleep(1)

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Sun Aug 08, 2021 3:30 am

Hello hippy and hourable,
Your jointly created program works great!
I tested it with a PWM generator (for frequency and ratio setting). The measured period depends only on the frequency, but not on the pulse-pause ratio (ratio).
I would like to thank you most sincerely for your effort and patience (with me) ...
If I now understand the function of the "high loop" and the "low loop" loop blocks, I am completely satisfied. I am working on it...

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Mon Aug 09, 2021 5:42 am

Hello hippy, hello hourable,
would you allow me to publish the solution for the rev counter with period measurement in the German Raspi forum?
It would certainly make sense if you could explain the effect of the two loops to me beforehand. I've been thinking about it for some time now, but I can't cope ...

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

Re: Speed measurement on a Triton router

Mon Aug 09, 2021 10:42 am

Quax wrote:
Mon Aug 09, 2021 5:42 am
Hello hippy, hello hourable,
would you allow me to publish the solution for the rev counter with period measurement in the German Raspi forum?
I would have no problem with that, or whatever anyone does with what I put on the forum.
Quax wrote:
Mon Aug 09, 2021 5:42 am
It would certainly make sense if you could explain the effect of the two loops to me beforehand. I've been thinking about it for some time now, but I can't cope ...
You mean the two 'lowloop' and 'highloop' ?

Those are simply "decrement X while the pin is low and while the pin is high" loops. The equivalent Python code would be ...

Code: Select all

def PioLoop():
  while True:
    X = 0
    # Wait for input to go low
    while btn.value() != 0:
       pass
    # Wait for input to go high, rising edge
    while btn.value() != 1:
       pass
    # Decrement X while pin is high
    while btn.value() == 1:
      X = X - 1
    # Decrement X while pin is low
    while btn.value() == 0:
      X = X - 1
    # Block if the FIFO is full
    while len(fifo) >= 4:
       pass
    # Push the count to the FIFO
    fifo.append(X)

Code: Select all

fifo = []
threading.start(PioLoop)
while True:
  # Wait for something in the FIFO
  while len(fifo) == 0:
     pass
  # Get the count
  cnt = (1 << 32) - fifo.pop(0)
  ...
The X count depends on how quickly the code would loop during the time the pin was high then low. For actual PIO use that is well defined as it is tied to the system clock so, from the number of the count, the time taken to have reached such a count can be determined.

In this case, because the system clock is 125MHz, the PIO takes two cycles to execute per count, there will be a count of 62500 per millisecond high+low period.

millisecond time = count / 62500.

The Python code above isn't an exact representation of how the PIO loops execute. The 'lowloop' can't be represented in Python and we can only check if input value is high. A more accurate representation would be -

Code: Select all

    # Decrement X while pin is high
    repeat                             # lowloop:
      X = X - 1                        #           jmp X--, lownext
    while btn.value() == 1             # lownext:  jmp pin, lowloop
    # Decrement X while pin is low
    while True:                        # hghloop:
       if btn.value() == 1:            #           jmp pin, done
         break;                        #
       X = X - 1                       #           jmp X--, highloop
    # Block if the FIFO is full        # done:

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Wed Aug 11, 2021 8:21 am

Hallo hippy,
danke für die Erklärung. Ich habe Dein Programm und die Erläuterungen ausgedruckt und werde mich "durchkämpfen".
Ich habe zum Programmablauf noch eine Frage: Beim Programmstart werden die "higloop" und "lowloop" Schleifen durchlaufen um die Periodendauer zu messen. Hierzu werden die SchleifenDurchläufe gezählt. Mit dem Ende der "low"- Periode des Eingangssignals erfolgt der Sprung zur Marke "done" und der ermittelte Wert x wird in ein Schieberegister geschrieben. Danach wird eine Testfrequenz erzeugt und es geht in eine "while True"- Schleife. In dieser Schleife erfolgt die Berechnung der Periodendauer und der Drehzahl sowie die Ausgabe dieser Werte.
Müßte das Program nicht endlos in dieser Schleife bleiben (also bis zur Anweisung "time.sleep(1) und wieder mit "while True" beginnen)? Wieso werden die Programmteile oberhalb der "while True" Anweisung offensichlich zyklisch abgearbeitet?
was bedeutet die Programmzeile "cnt = (1 << 32) -sm.get? Wenn ich die Subtraktion weglasse ergibt sich für die Variable cnt ein Wert von 2 EXP 32. Die Zeichenfolge "1 << 32" konnte ich nirgends finden. Warum ziehe ich von diesem Wert sm.get ab? Wie ist sm.get mit der Zählvariable x verbunden?

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

Re: Speed measurement on a Triton router

Wed Aug 11, 2021 12:30 pm

For the cyclic question; it's cyclic because that's how it is, how the code I had to hand was written. It was an example to show how it could be done and how easy it could be.

It can be made single-shot, 'demand driven', I simply hadn't done that. All it needs is a 'pull' at the start of the PIO code, and an sm.put() to trigger it -

Code: Select all

from   machine import Pin, PWM
from   rp2     import asm_pio, PIO, StateMachine
import time

GPIO_PIN = 4

btn = Pin(GPIO_PIN, Pin.IN, Pin.PULL_UP)

@asm_pio()
def Period():
  pull(block)            # Wait to be triggered
  
  set(x, 0)
  wait(0, pin, 0)
  wait(1, pin, 0) 

  label("highloop")      #   <--.
  jmp(x_dec, "highnext") # 1    |
  label("highnext")      #      |
  jmp(pin, "highloop")   # 2 ---'
  
  label("lowloop")       #   <--.
  jmp(pin, "done")       # 1    |
  jmp(x_dec, "lowloop")  # 2 ---'

  label("done")
  mov(isr, x)
  push(isr, block)
    
sm = StateMachine(0, Period, in_base=btn, jmp_pin=btn)
sm.active(1)

# This is just for testing. We generate a pulse on an output pin using PWM
# which just happens to be the same pin we are measuring

pwm = PWM(Pin(GPIO_PIN, Pin.OUT))
pwm.freq(1_000)
pwm.duty_u16(0x8000)

while True:
  # Trigger a reading
  sm.put(0)
  # Note the count is decremented in the PIO routine so it will be a
  # negative value. We need to make it a positive value.
  cnt = (1 << 32) - sm.get()
  tms = cnt / 62500
  rps = 1_000 / tms
  rpm = rps * 60
  print("{} counted, {} ms rev, {} rps, {} rpm".format(cnt, tms, rps, rpm))
  time.sleep(1)
For the "cnt = (1 << 32) - sm.get()" ... That 1 << 32 means shift 1 left 32 times, it is equivalent to a 0x100000000 constant.

Note 9 hex digits in 100000000, 8 hex digits in FFFFFFFF. It's similar to decimal 1000 - 999 = 1

Code: Select all

(1 << 32) - sm.get() => cnt

100000000 - FFFFFFFF => 1
100000000 - FFFFFFFE => 2
100000000 - FFFFFFFD => 3
100000000 - FFFFFFFC => 4
etc
My ...

Code: Select all

  # Note the count is decremented in the PIO routine so it will be a
  # negative value. We need to make it a positive value.
isn't technically correct nor accurate, was meant to convey the feel of what is being done. I am not sure what the best description would be. "Convert an initially large and decreasing value to an initially small and increasing value" perhaps.

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Wed Aug 11, 2021 5:50 pm

Hello hippy,
Please excuse me: although your answer is already there, now the translation again. You will probably not hear from me for a while, I have to "digest" your information ...
Thank you very much again for your support!

Thanks for the explanation. I have printed out your program and the explanations and will "fight my way".
I have one more question about the program sequence: When the program starts, the "higloop" and "lowloop" loops are run through to measure the period duration. For this purpose, the loop runs are counted. At the end of the "low" period of the input signal, there is a jump to the "done" label and the determined value x is written to a shift register. Then a test frequency is generated and a "while True" loop is entered. In this loop, the calculation of the period duration and the speed as well as the output of these values ​​takes place.
Shouldn't the program stay in this loop indefinitely (until the instruction "time.sleep (1) and start again with" while True ")? Why are the program parts above the" while True "instruction obviously processed cyclically?
What does the program line "cnt = (1 << 32) -sm.get" mean? If I omit the subtraction, the value for the variable cnt is 2 EXP 32. I could not find the string "1 << 32" anywhere. Why Do I subtract sm.get from this value? How is sm.get connected to the counter variable x?

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

Re: Speed measurement on a Triton router

Wed Aug 11, 2021 6:28 pm

Quax wrote:
Wed Aug 11, 2021 5:50 pm
Shouldn't the program stay in this loop indefinitely (until the instruction "time.sleep (1) and start again with" while True ")? Why are the program parts above the" while True "instruction obviously processed cyclically?
I forgot to mention that I did struggle with the exact meaning / translation there so apologies if I did not answer the question asked.

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Fri Aug 20, 2021 7:12 am

Hello hippy,
struggled through the program and hope to understand.
I added a PWM generator with adjustment of the frequency and the ratio as well as a two-line LCD.
In preparation for the publication in the German Raspi forum, I added very detailed comments. It would be nice if you could check these comments for accuracy. If you were ready, I would translate the comments with Google's help and provide the complete program for you here.
I still have one question about the following program line:
"sm = StateMachine (0, Period, in_base = btn, jmp_pin = btn)"
"in_base is clear, but I couldn't find" jmp_pin "in the Micropython command description. What is that all about?

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

Re: Speed measurement on a Triton router

Fri Aug 20, 2021 11:21 am

jmp_pin defines the pin used by PIO JMP instruction which is used to branch code depending on the jmp_pin state. In other words, it allows State Machine to behave differently when the selected pin is high than when it is low. In the case of the previously presented code, it's used to control the behaviour of "highloop" and "lowloop".
In "highloop":

Code: Select all

  label("highloop")      #   <--.
  jmp(x_dec, "highnext") # 1    |
  label("highnext")      #      |
  jmp(pin, "highloop")   # 2 ---'
once SM reaches instruction jmp(pin, "highloop") it will jump back to label("highloop") only if jmp_pin is high running this loop as long as jmp_pin is high. When this pin goes low the jump is not executed and program goes to the next instruction, which is the first instruction of "lowloop":

Code: Select all

  label("lowloop")       #   <--.
  jmp(pin, "done")       # 1    |
  jmp(x_dec, "lowloop")  # 2 ---'
This loop is designed to measure how long the input is in a low state, so jmp(pin, "done") is used to break out of this loop once jmp_pin goes high, effectively stopping the measurement.

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

Re: Speed measurement on a Triton router

Fri Aug 20, 2021 12:30 pm

Quax wrote:
Fri Aug 20, 2021 7:12 am
In preparation for the publication in the German Raspi forum, I added very detailed comments. It would be nice if you could check these comments for accuracy. If you were ready, I would translate the comments with Google's help and provide the complete program for you here.
I am quite happy to check those comments.

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Fri Aug 20, 2021 2:30 pm

Hello hippy,
I took something like that from the documentation, but I assumed that the in_base-pin triggers these branches. If the jump- pin triggers the decision on the jump command, what does the in_base pin do?

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

Re: Speed measurement on a Triton router

Fri Aug 20, 2021 2:45 pm

in_base pin is used by wait(0, pin, 0) and wait(1, pin, 0) instructions at the beginning of the program to form a rising edge detection mechanism that starts the measurement. in_base and jmp_pin are set to use the same pin, because the same signal that is measured is also used as a trigger.

Quax
Posts: 26
Joined: Mon Jul 26, 2021 5:51 am

Re: Speed measurement on a Triton router

Fri Aug 20, 2021 3:43 pm

Hello hippy, here the program with hopefully understandable comments:

from machine import Pin, PWM, I2C # Module and library calls
from rp2 import asm_pio, PIO, StateMachine
import time
from pico_i2c_lcd import I2cLcd

i2c = I2C(0, sda = Pin(0), scl = Pin(1), freq = 400000) # Declaration of the I2C port
I2C_ADDR = i2c.scan()[0] # Scan the I2C address of the display
lcd = I2cLcd(i2c, I2C_ADDR, 2, 16) # Link with library, assignment of the scanned address, display with 2 lines of 16 characters each

GPIO_PIN = 4 # Assignment of the first GPIO_PIN (all following pins are mapped: 0-31)
btn = Pin(GPIO_PIN, Pin.IN, Pin.PULL_UP) # Definition of the GPIO_PIN as a digital input with activated pull-up resistor

@asm_pio() # Start of the PIO assembler routine

#Beginning of the "Period" subroutine (period measurement)

def Period():
set(x, 0) # X (and Y) are temporary 32-bit registers (scratch registers) of the state machines. X is set to 0 here.
# The register x is used as a variable for counting the number of times the loop is run.

"""The two wait commands interact to detect a positive edge.
The first wait line waits for a low on pin 0 (corresponds to the mapped, first GPIO).
If there is a low, the first wait line is exited and a high is waited for in the second wait line (= positive edge).
If the positive edge is recognized, the counting of the loop runs begins. """

wait(0, pin, 0)
wait(1, pin, 0)



# Counting loop "highloop"
label("highloop") # <--. Beginning of the "highloop" loop for counting the number of loop cycles during the high duration of the input signal.
jmp(x_dec, "highnext") # 1 | Unconditional jump: Decrement the variable x by 1 and then jump to the label "highnext".
label("highnext") # |
jmp(pin, "highloop") # 2 ---' Conditional jump: Jump back to the label "highloop" as long as pin = high. If the input signal changes to low,
# the loop "highloop" is exited and the loop "lowloop" continues.
# The runs counted up to that point are stored in the variable x.

""" Warning: Due to the limited scope of commands of the PIO assembler routine, only decrementing counting is possible!
Since x was set to 0 at the beginning of the subroutine, the following values ​​are contained in x with a 32-bit word length
(without negative flag):

after the 1st pass: FFFFFFFF (hex)
after the 2nd pass: FFFFFFFE (hex)
after the 3rd pass: FFFFFFFD (hex)
etc. """

# Counting loop "lowloop"
label("lowloop") # <--. Beginning of the "lowloop" loop for counting the number of loop cycles during the low duration of the input signal.
jmp(pin, "done") # 1 | As soon as the input signal becomes high, there is a jump to the "done" label. This ends the counting of the loop runs.
jmp(x_dec, "lowloop") # 2 ---' As long as the input signal is LOW, x is decremented by 1, then a jump is made back to the beginning of the low loop.

""" The variable x was already used in the high loop, so the count value represents the duration of the high level of the measurement signal.
During the low loop, the variable X is simply decremented further, so that at the end of the low level there is an equivalent for
the entire period is stored in x. However, this value still has to be converted (see below) """

label("done") # The counting of the loop runs for a complete period of the measuring signal (high and low component) is completed.
mov(isr, x) # Copies the value of the scratch register x into the "input-shift-register" (ISR)
push(isr, block) # The x values are shifted from the ISR into the RX-FIFO, after which the ISR is deleted. This process is stopped as soon as the FIFO is full.
# Purpose of the exercise: The values contained in the FIFO can be read out for further processing using the sm.get () command.


# "Instantiate" (initialize?) The state machine

sm = StateMachine(0, Period, in_base=btn, jmp_pin=btn) # Parameters: No. of the SM, PIO program to be executed, definition of the input, (definition of the jump input ???)
sm.active(1) # Activate state machine (refers to the previously defined SM 0)


# For test purposes only: The PWM frequency generator simulates an input signal for speed measurement (frequency and ratio values can be set manually).

pwm = PWM(Pin(GPIO_PIN, Pin.OUT)) # The PWM signal is output on the same port that also serves as the input for frequency measurement!
pwm.freq(500) # Frequency setting: 500 Hz corresponds to 30,000 rpm
pwm.duty_u16(1024) # Ratio setting: A change in the pulse / pause ratio must not change the measured value (period remains constant!).

# Evaluation and display section

while True:

cnt = (1 << 32) - sm.get() """ The expression "(1 << 32)" means to shift a binary 1 by 32 places to the left, thus corresponds to 100000000 (hex).
The "sm.get ()" command reads out the values currently contained in the FIFO of the state machine. These values are with everyone
Loop run based on 100000000 (hex) is smaller by the amount 1 (see above).
The necessary conversion occurs through the subtraction.
Starting from 0, the calculated value increases by the amount 1 with each loop pass:

1st pass: 100000000 (hex) - FFFFFFFF (hex) = 1 (dec)
2nd pass: 100000000 (hex) - FFFFFFFE (hex) = 2 (dec)
3rd pass: 100000000 (hex) - FFFFFFFD (hex) = 3 (dec) etc. """


tms = cnt / 62500 """ Calculation of the period duration (time for one revolution). The processor clock is 125 MHz, i.e. 8 ns are required per clock.
Each count of a loop run requires 2 cycles, i.e. 16 ns.
For a period of 1 ms, 62,500 loops would be counted.
This results in the general calculation formula: t [ms] = (number of loop passes / 62,500)"""

rps = 1_000 / tms # Conversion in revolutions / s

rpm = 60*1000/tms # Conversion into revolutions / min

print("{} counted, {} ms rev, {} rps, {} rpm".format(cnt, tms, rps, rpm)) # Output on the terminal (PC)


# Display on LCD display 1602 (16 characters / 2 lines each) with I2C (chip: HD44780)

tms_rounded = int((tms*100) + 0.5) / 100 # Rounding of the period to 2 decimal places
rpm_rounded = int(rpm + 0.5) # Rounding of the speed to integer numbers

tms_string = str(" %8.4f" % (tms_rounded)) # Formatting of the display value for the period duration (3 places before and 4 places after the decimal point)
rpm_string = str("%9d" % (rpm_rounded)) # Formatting of the display value for the speed (9-digit, integer)

# 1st line
lcd.move_to(0,0) # Cursor position: 1st position of the 1st line (Attention: Numbering starts for position and line with 0)
lcd.putstr("T[ms]= "+(tms_string)) # Output on the 1st line of the LCD, formation of the display string

# 2nd line
lcd.move_to(0,1) # Cursor position: 1st position of the 2nd line (Attention: numbering starts for position and line with 0)
lcd.putstr("RPM = " +(rpm_string)) # Output on the 2nd line of the LCD, formation of the display string

time.sleep(1) # Pause for the while true loop, frequency of the display update.]

Return to “MicroPython”