BetaRavener / uPyLoader

File transfer and communication tool for MicroPython boards
MIT License
355 stars 76 forks source link

Mac won't connect to Pico #81

Open mlewus opened 3 years ago

mlewus commented 3 years ago

I compiled uPyLoader from source after installing dependencies using install_osx_dependencies.sh as a guide. I am testing uPyLoader on a MacBook Pro 2019 with macOS Catalina 10.15.7. Micropython (not CircuitPython) has been flashed to the Pico. I am trying to connect via USB. It works OK with Thonny so I think the hardware side and port is OK.

uPyLoader starts and detects a usb tty port at /dev/tty.usbmodem0000000000001 which is the correct port for the Pico. I set the baud rate to 115200 which is correct for this board. When I try to connect, the program beach-balls (hangs) for 10-15 seconds, then the status changes to "Status: Connecting..." for a second and then displays "Status:Error". uPyLoader does not hang, I can do this multiple times. I tried checking "issue reset", no change.

I did the same test using the interpreted version of the program. Other than a button (back?) appearing between the connection and baud rate boxes that was a blank rectangle in the compiled version, the behavior was the same.

Can you give me some hints how to proceed with debugging?

BetaRavener commented 3 years ago

Hi @mlewus, I'm sorry you are having issues. Thanks for describing in detail what you tried so far and trying to debug the issue. Looking at the code that leads to the mentioned error in your case (USB/UART), there are two things that might go wrong:

  1. The serial connection fails to open
  2. The connection is not considered valid (which would happen if we fail to list files)

Since you already checked out source code, you could use debugger or simply put print statements at these places in code to be sure which of them is causing this. To make sure the port could be opened, you could also put a print directly after this command, but as you said you can open the port with another tool, I don't think that's the cause. In both cases it would then boil down to communication failure, when the board won't reply in expected way. To check what (if anything) the board sends back, you can put a print to read_to_next_prompt function and check the value of ret variable.

Let me know what you find out or if you need additional help.

mlewus commented 3 years ago

I have had some health problems so have not been able to debug yet. But I thought you'd like to know that the Pico also failed on a Linux machine, so I'm thinking it may not be a Mac problem but a Pico problem. I'll let you know what info I can glean from debugging.

mlewus commented 3 years ago

I fixed the issue. DTR must be asserted True in serial_connected.py in order for the Pico to connect. The program currently has it set to False. This issue is caused by the Raspberry Pi Pico, and is not related to the host OS. I tested the fix on MacOS and Mint (Debian) Linux. I have not tested it on Windows. I have also not tested that asserting RTS will reboot the Pico board. The change on line 26, along with a minor comment typo fix on line 25, are listed below.

This is the affected function in the original file:

class SerialConnection(Connection):
    def __init__(self, port, baud_rate, terminal=None, reset=False):
        Connection.__init__(self, terminal)

        self._port = port
        self._baud_rate = baud_rate

        try:
            self._serial = serial.Serial(None, self._baud_rate, timeout=0, write_timeout=0.2)
# dtr rts aere both false
            self._serial.dtr = False
            self._serial.rts = False
            self._serial.port = port
            self._serial.open()

            kill_and_wait = not reset
            if reset:
                self._serial.rts = True
                time.sleep(0.1)
                self._serial.rts = False
                try:
                    # Give board time to reset (10 seconds should be enough)
                    self.read_to_next_prompt(10)
                except TimeoutError:
                    # If we still get time out, a script was probably
                    # started automatically and needs to be killed
                    kill_and_wait = True
            if kill_and_wait:
                self.send_kill()
                self.read_to_next_prompt()

        except (OSError, serial.SerialException) as e:
            self._serial = None
            return
        except Exception as e:
            return

To resolve this issue, change line 25 & 26 in serial_connected.py from # dtr rts aere both false self._serial.dtr = False to # DTR must be True for Raspberry Pi Pico, False for other boards. RTS is False self._serial.dtr = True

BetaRavener commented 3 years ago

Sorry I have been rather busy myself. Hope you are doing better now. Thanks for great report, glad you managed to figure it out. I'd probably include a drop-box or something in serial mode to choose between boards and set the levels accordingly. Could you point me towards document that explains that Pico's DTR should be high (True) when connecting?

mlewus commented 3 years ago

I determined that DTR needed to be set empirically. I'll see if I can find a document and I'll let you know.

On Mon, Feb 22, 2021, 15:24 BetaRavener notifications@github.com wrote:

Sorry I have been rather busy myself. Hope you are doing better now. Thanks for great report, glad you managed to figure it out. I'd probably include a drop-box or something in serial mode to choose between boards and set the levels accordingly. Could you point me towards document that explains that Pico's DTR should be high (True) when connecting?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/BetaRavener/uPyLoader/issues/81#issuecomment-783651041, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABKP5UNBVPDFOVPAYEGCZWTTAK4PNANCNFSM4XB3PAHQ .

mlewus commented 3 years ago

I reviewed the RP2040 datasheet, the C/C++ SDK and the Python SDK, and I was not able to find this documented anywhere. I posted a request for more information on the new Raspberry Pi Pico forum. I'll let you know if I learn anything.

mlewus commented 3 years ago

I have not heard back from the pi forum. This leads me to believe there are no Pi Foundation engineers there.

RTS /CTS/DTR etc. are control signal leftovers from the days of analog modems. Every modern board implements them differently, or not at all. For instance ESP boards use DTR for reset and DTR + CTS for reset to bootloader. Probably the only way to determine how that works on the Pico is empirically. While I have determined that DTR = True is required to communicate, I have not checked that DTR plus CTS cause a reset which is what your code expects.

If you'd like me to do some additional testing I would be happy to do so. If you feel like you need to wait to get written documentation to make these changes, I think you're going to be waiting a long time :)

BetaRavener commented 3 years ago

Thanks so much for spending time on this. No I don't need a hard proof to make changes, lot of the other things were anyway based on trying empirically. The reason was just to have reference as to why this is needed and looking if there are other deviations (like you said with the reset).

Would be great, if you could verify that reset happens when the "Issue Reset" checkbox is checked. I think easiest would be to define some global variable via terminal, then disconnecting, checking the box, connecting again and trying to read value of the variable. If the reset happened, the variable will be gone.

mlewus commented 3 years ago

Sure, I'll get it done in the next couple days. I did locate some information on terminal commands ie. how to do a soft reset if CTS does not work.

I'll test the control signals and I'll also post the list of terminal commands.

On Wed, Feb 24, 2021, 05:42 BetaRavener notifications@github.com wrote:

Thanks so much for spending time on this. No I don't need a hard proof to make changes, lot of the other things were anyway based on trying empirically. The reason was just to have reference as to why this is needed and looking if there are other deviations (like you said with the reset).

Would be great, if you could verify that reset happens when the "Issue Reset" checkbox is checked. I think easiest would be to define some global variable via terminal, then disconnecting, checking the box, connecting again and trying to read value of the variable. If the reset happened, the variable will be gone.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/BetaRavener/uPyLoader/issues/81#issuecomment-784983767, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABKP5UO25S7WDHGVYYKGWVLTATJYVANCNFSM4XB3PAHQ .

mlewus commented 3 years ago

I determined that the Pico does not respond to toggling CTS as a means of reset. Here is the information I referred to previously, posted by @DWiskow on the Raspberry Pi forum:

Re: USB serial communication using second processor/thread of the RD2040
Sun Feb 07, 2021 3:08 am

There are a number of useful shortcuts for interacting with the MicroPython REPL. See below for the key combinations;

Ctrl-A on a blank line will enter raw REPL mode. This is similar to permanent paste mode, except that characters are not echoed back.
Ctrl-B on a blank like goes to normal REPL mode.
Ctrl-C cancels any input, or interrupts the currently running code.
Ctrl-D on a blank line will do a soft reset (and will auto run anything defined in main.py).
Ctrl-E enters ‘paste mode’ that allows you to copy and paste chunks of text. Exit this mode using Ctrl-D.
Ctrl-F performs a “safe-boot” of the device that prevents boot.py and main.py from executing

So, the “ Ctrl-C, Ctrl-C, Ctrl-D then Ctrl-B” sequence 

interrupts the currently running code [twice to make sure]
Executes a soft reset
and goes to normal REPL mode

The entire thread is here: https://www.raspberrypi.org/forums/viewtopic.php?t=302889

I think a pulldown to set the Pico as a target would work well, setting DTR = True on port open, and then using the "Ctrl-C Ctrl-C Ctrl-D Ctrl-B" sequence for reboot as suggested. I tested that as well and it does work. You might also add Ctrl-F as a button on the terminal pane to enable easy safe boot.

Let me know if there is any other testing I can help with.

BetaRavener commented 3 years ago

Hi, thanks again for the write up. 👍 I was aware of the soft-reset option, in fact all of these are exposed if you look at right side of terminal window. They are also used by uPyLoader itself to operate in serial mode, for example https://github.com/BetaRavener/uPyLoader/blob/2c004ea190ec73f494efef709da61952c38580a0/src/connection/connection.py#L107.

Adding a help as to what they do like what you pasted might be nice though, will do that when adding the checkbox.

mlewus commented 3 years ago

I did notice the Ctrl buttons on the terminal window ranging from Ctrl-A to Ctrl-E. Very nice, btw. My suggestion to add Ctrl-F was to take advantage of the Pico’s single button “safe boot”. I’m not sure if any other boards respond to Ctrl-F so you might / might not want to add it to the terminal GUI.