adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4k stars 1.18k forks source link

Restarting PIO creates phantom 0 inputs #4983

Closed dmcomm closed 3 years ago

dmcomm commented 3 years ago

CircuitPython version

Adafruit CircuitPython 7.0.0-alpha.4-7-g58fdf9e94 on 2021-07-09; Raspberry Pi Pico with rp2040

Code/REPL

import array
import board
import time
import rp2pio

pinIn = board.GP1

program = array.array("H", [0x4001]) #in pins 1
sm = rp2pio.StateMachine(
    program,
    frequency=1_000_000,
    first_in_pin=pinIn,
    auto_push=True,
)
print("frequency", sm.frequency)
time.sleep(3)
buffer=array.array("L", [0] * 4)
zeroCount = 0
oneCount = 0
restartTime = time.monotonic()
print("start")
while True:
    if time.monotonic() - restartTime > 20:
        restartTime = time.monotonic()
        print("restarting")
        sm.restart()
    while sm.in_waiting < 4:
        pass
    sm.readinto(buffer)
    for item in buffer:
        if item == 0:
            zeroCount += 1
            if zeroCount % 100000 == 0:
                print("zeros", zeroCount)
        elif item == 0xFFFFFFFF:
            oneCount += 1
            if oneCount % 100000 == 0:
                print("ones", oneCount)
        else:
            b = bin(item)
            print("0" * (34 - len(b)) + b[2:], end=" ")
            print()

Behavior

When the pin is grounded, as expected:

zeros 100000
zeros 200000
zeros 300000
zeros 400000
restarting
zeros 500000
zeros 600000
zeros 700000
zeros 800000
zeros 900000
restarting
zeros 1000000
zeros 1100000

When the pin is tied to 3V3:

ones 100000
ones 200000
restarting
11111110000000111111111111111111 
ones 300000
ones 400000
ones 500000
restarting
11111111111000011111111111111111 
ones 600000
ones 700000
restarting
11111111111100001111111111111111 
ones 800000
ones 900000

Description

No response

Additional information

This doesn't seem to happen in MicroPython. Edit: Though actually, my program isn't quite doing the same thing, so I should check that a bit more.

dmcomm commented 3 years ago

Reviewing what I did in MicroPython, it was the "wait pin" version. The following program needs to be restarted to return to the waiting state. This program was waiting when it was supposed to in MicroPython and not in CircuitPython.

.program samples
    wait 0 pin 0
loop:
    in pins 1
    jmp loop
dmcomm commented 3 years ago
import array
import board
import time
import rp2pio
import adafruit_pioasm

pinIn = board.GP1

asm = """
    wait 0 pin 0
loop:
    in pins 1
    jmp loop
"""
program = adafruit_pioasm.assemble(asm)

sm = rp2pio.StateMachine(
    program,
    frequency=1_000_000,
    first_in_pin=pinIn,
    auto_push=True,
    pull_in_pin_up=1,
)

buffer=array.array("L", [0] * 4)

while True:
    time.sleep(1)
    sm.restart()
    sm.clear_rxfifo()
    while sm.in_waiting < 4:
        pass
    sm.readinto(buffer)
    for item in buffer:
        b = bin(item)
        print("0" * (34 - len(b)) + b[2:], end=" ")
    print()
DavePutz commented 3 years ago

Hmm. It looks like this issue relates to the functionality of the SDK's pio_sm_restart() call. According to the Pico documentation, "This method clears the ISR, shift counters, clock divider counter pin write flags, delay counter, latched EXEC instruction,and IRQ wait condition." Notably, it does not reset the program counter. Thus it will not execute the "wait 0 pin 0" instruction from the example. We might need to decide whether following the SDK is the right thing to do, or if calling sm.restart() should reset the program counter as well.

dmcomm commented 3 years ago

How bizarre. And I'd just assumed it was the same issue.

Looks like MicroPython noticed this and decided that their restart function will go to the start of the program: https://github.com/micropython/micropython/blob/e3291e1801f27e49ac0e6be275c742777507207f/ports/rp2/rp2_pio.c#L605

Edit: Though I have to wonder if that's actually safe. The PIO program might do something between the restart and exec calls, so the things reset by the restart call might not be clean any more. Edit2: And sounds like this doesn't reset OSR/X/Y either.

DavePutz commented 3 years ago

Added code to reset the State Machine program counter to the initial offset during a restart. There is not a way in the SDK to reset X,Y, or OSR. If those are of concern you might have to put in a PUSH to the register(s) at the start of your PIO code.

dmcomm commented 3 years ago

Right, normally the PIO program would put something into X/Y/OSR.

dmcomm commented 3 years ago

Fixed!