Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

micropython #2

Open
kjm1102 opened this issue Oct 6, 2021 · 31 comments
Open

micropython #2

kjm1102 opened this issue Oct 6, 2021 · 31 comments

Comments

@kjm1102
Copy link

kjm1102 commented Oct 6, 2021

My guess is that, like me, the majority of RP2040 users are coding newbies who've had a splash in the micropython pool but haven't ventured into the murky water that is C++.

A micropython version of a 1.3mA sleep mode that wakes itself from an internal timer would broaden the scope & appeal of this ingenious bit of detective work considerably.

@ghubcoder
Copy link
Owner

ghubcoder commented Oct 17, 2021

Hi @kjm1102 thanks for having a look at the code.

I've had a quick look at this, I couldn't find anything for micopython around this after an initial look ( for waking from the real time clock, see the last part of this post for GPIO wakeup), that's not to say it hasn't been implemented. I did stumble upon this however:

https://github.com/adafruit/circuitpython/pull/4816/files

It appears that someone has done the work to get deep sleeping working for circuit python. It's fairly similar to micropython in that you just upload a uf2 file to your pico and then drop python files onto it, which it will then run for you.

Some other links around this are:
https://circuitpython.readthedocs.io/en/latest/shared-bindings/alarm/index.html#alarm.exit_and_deep_sleep_until_alarms

https://learn.adafruit.com/deep-sleep-with-circuitpython/alarms-and-sleep#timealarm-deep-sleep-3078700-7

I tried with this code:

import alarm
import board
import time
import digitalio

print("Waking up")

led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT

# Flash the led
led.value = True
time.sleep(2)
led.value = False
# Set an alarm for 60 seconds from now.
time_alarm = alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 60)

print("Sleeping")
# Deep sleep until the alarm goes off. Then restart the program.
alarm.exit_and_deep_sleep_until_alarms(time_alarm)

You can then upload this as code.py onto the pico board and it will run it. At least for me it seems to work. However I have not measured the power consumption. Certainly from a brief look at the PR above they are using dormant mode to sleep when no alarm is set (wake from GPIO), and a light sleep mode otherwise. With the example above I expect it should use around 1.3mA ("light" sleep mode).

Unfortunately there is no main loop here, so each time the program restarts. However if you want to accomplish some work and then sleep, after which the program wakes up again from the beginning, this is certainly a way to do that.

I also found this https://github.com/tomjorquera/pico-micropython-lowpower-workaround

Which appears to be a way for the pico to sleep and be woken from a GPIO pin using micropython. Very similar to this post https://ghubcoder.github.io/posts/waking-the-pico-external-trigger/

So by using an external RTC you could wake the pico by listening for a pin to go high. I haven't tested this however.

@ghubcoder
Copy link
Owner

@kjm1102

I've had a look at recompiling micropython to support this, please see here

@kjm1102
Copy link
Author

kjm1102 commented Oct 27, 2021 via email

@kjm1102
Copy link
Author

kjm1102 commented Oct 28, 2021 via email

@kjm1102
Copy link
Author

kjm1102 commented Oct 28, 2021 via email

@kjm1102
Copy link
Author

kjm1102 commented Oct 28, 2021 via email

@ghubcoder
Copy link
Owner

Hi @kjm1102 glad you got it working!

Yep as you found with this you need to put it inside a loop. machine.deepsleep() as the docs state must reset the device after sleeping, causing it to then restart and once again running your main.py file. With this you have more control of that main event loop. Perhaps though, it should be written to behave in the same way.

There might be some consequences with regards to the python environment on the pico that manifest after deep sleeping and waking up. I've only tested it with simple scripts up until this point though and it seems to work ok.

With regards to the repl, whilst the machine is deep sleeping the repl prompt won't respond, another downside of how this works, but it does show the device is indeed in a deep sleep state.

If I run this script however as main.py:

from machine import Pin
import picosleep
import time

# I can connect via the repl from this point
led = Pin(25, Pin.OUT)
led.toggle()
time.sleep(5)
led.toggle()
# until this point.

# Cannot connect from this point
picosleep.seconds(5)
# Once this point is reached, the script exits, the pico does not 
# restart and I can connect to the repl once more.

I can connect at the points described above. Also it's interesting to note that if I connect during the first time.sleep(5), (with Thonny I should add, with the stop/restart button), this will stop the script from executing preventing it from going into the deepsleep. It's useful to put that step at the start of you program to allow you to connect to your device without it getting into a loop where it's unreachable.

If it does get into that state, there is a factory reset uf2 file you can download onto the pico by holding the bootsel button at power on. File is at the bottom of this page.

Interesting that you seen different current draw with circuitpython. Might need to give that another test.

Happy coding! 🙂

@kjm1102
Copy link
Author

kjm1102 commented Oct 28, 2021

One advantage of picosleep in a loop is we loose the reboot overhead. With a 50k micropython file on the ESP32 it takes 5-6s for the program to run again after a machine.deepsleep, a significant penalty when the program execution (read a few sensors, upload to a server) is only 10-15s long. This idea of going dormant then picking up where we left off in the loop should eliminate that reboot time each cycle.

I still don't understand why atom/thonny repl doesn't resume displaying print statements once the dormancy is finished?

@capnf
Copy link

capnf commented Nov 6, 2021

Hi and thanks for creating this code. I ran it to execute calls to a Cricket Wifi module and it certainly slashes the power consumption on the Pico.
I did find though that if the timer interval is set to values above 1024 the picosleep call seems to lose the expected interval and begins to cycle at shorter intervals than expected. For example using an interval of 3600 seconds starts a sleep that appears to last about 20 seconds. I wonder if there might be an overflow condition occurring on the input parameter?

@ghubcoder
Copy link
Owner

@capnf which code are you getting that with? Is that with the micropython uf2 file?

@capnf
Copy link

capnf commented Nov 6, 2021 via email

@ghubcoder
Copy link
Owner

Great thanks @capnf I will take a look at what might be going on there.

@ghubcoder
Copy link
Owner

ghubcoder commented Nov 6, 2021

@capnf I've uploaded a new release which should fix that here

If you could give it a test when you get a moment that would be much appreciated.

@capnf
Copy link

capnf commented Nov 6, 2021

That's terrific - thank you. I've downloaded and am testing now. Will get back with results later today.

@capnf
Copy link

capnf commented Nov 6, 2021

I've run tests at various timer intervals from 60 seconds up to 1500 secs and all worked correctly, whereas previously it began to misbehave at the upper end of that range. Obviously it will take longer to check what happens when much longer intervals are selected, but I'll keep running the tests and let you know. Looks as though it's fixed though - good work and super fast response :)

@capnf
Copy link

capnf commented Nov 6, 2021

It works at 3600secs also, so looks like the fix is good.

@ghubcoder
Copy link
Owner

Brilliant, thanks for checking.

You were right with your hunch, it was overflowing. The type used was int8_t, max value 127.

Now it's using uint32_t, max value 4,294,967,295 which I hope should be plenty.

@capnf
Copy link

capnf commented Nov 6, 2021

haha!. Debugging by hunch 👍 - I was 35 years in the IT business, and have never coded in C or Python, but some issues smell the same no matter what the language. I calculate that your new counter will be good for intervals up to the year 2157, and as I've already been around quite a lot of years I doubt I'll have to worry about what happens from then on :)

I'm planning to use your new module in a remote monitoring system for my boat. I want to rig up a system that sleeps on minimum power, then fires up a mifi router periodically, sniffs the temperature and the service battery voltages, and then reports back via a Cricket wifi module and a Pipedream workflow, before going to sleep again. I can't use the main service batteries to power this because the boatyards insist that everything is disconnected in Winter due to the fire risk. Your code will, I hope, let me use a pretty small standalone battery that will last all winter - I wouldn't have been able to do this without your help.

@ghubcoder
Copy link
Owner

That's really good to hear you know 🙂 I'm glad this little experiment has been useful for someone else and will be put to good use.

Thanks for raising the issue!

@kjm1102
Copy link
Author

kjm1102 commented Dec 2, 2021

I'm a tad off topic with this question but I'm going to ask it anyway because, after a month of trying stuff, I've run out of ideas. Your uf2 works great & my test program with picosleep.seconds(300) works the same as time.sleep(300), the only difference being the greatly reduced power consumption during the 300s of dormancy in the case of the former.

So next I connected the pico to to a simcom7020 4G modem via uart0. Yes, this is the off topic part. This modem 4G network
attaches automatically, but you have to tell it to 4G network connect. Here's the rub! If the pico has slept I can command the modem to connect no probs, but if the pico has been dormant no dice. It's bizarre, like the modem somehow knows which sleep mode I've used. The only way I can get the modem to connect is if I do a machine.reset() after the dormancy is finished. This is not the end of the world but it bugs me because I loose all my variables in RAM. Initially I thought the dormancy must have subtly effected uart0 but the modem responds to all other AT cmds normally, just won't 4g connect.

Since the 2 uart0 lines & 0v are the only 3 wires the modem & pico share I can't figure out how the modem knows when I've used dormancy Vs sleep. From your understanding of what happens to the various timers when dormant do you have any thoughts on what machine.reset() is fixing that allows the modem to carry out the connect command.

@kjm1102
Copy link
Author

kjm1102 commented Dec 11, 2021

I finally got to the bottom of this. The dormancy leaves the pico with a uart unable to send or receive more than 32 chrs. So when the modem returns
b'at+cgcontrdp\r\r\n+CGCONTRDP: 1,5,"telstra.internet","10.108.59.111.255.255.255.0",,"101.168.244.106","10.4.130.164",,,,,1500 for the connect cmd the pico sees only
b'at+cgcontrdp\r\r\n+CGCONTRDP: 1,5,

@ghubcoder
Copy link
Owner

ghubcoder commented Jan 2, 2022

Very strange @kjm1102.

I've tested with two picos connected together via UART 0. Pico 1 sends a long string every 5 seconds. Pico 2 runs the micropython deep sleep uf2 with the following:

import machine
uart = machine.UART(0)
from machine import Pin
led = Pin(25, Pin.OUT)
import time
import picosleep

def flash(times,sleep):
    num = 0
    while num < times:
        led.toggle()
        time.sleep_ms(sleep)
        num=num+1

while True:
    try:
        if uart.any():
            rxData = uart.read()
            string = rxData.decode("ascii")
            if "UART0 with debug with very very long string to see if it is cut" in string:
                flash(4,500)
                picosleep.seconds(2)
                
    except UnicodeError as e:
        continue

I added in the UnicodeError exception handler as sometimes when it starts up it fails with that error.

I also had to remove all print() statements as this seems to hang the pico after a while when sleeping. This is whilst connected via usb. There are definitely issues with usb communication after deep sleeping, I haven't been able to get to the bottom of that. Another issue was raised here #1

However with the above code it will loop and flash indicating it has received the long string that is being sent after sleeping. That is more than 32 chars.......

Seems like there was someone else with your issue though a while back:
https://forum.micropython.org/viewtopic.php?t=9913

@kjm1102
Copy link
Author

kjm1102 commented Jan 3, 2022 via email

@ghubcoder
Copy link
Owner

ghubcoder commented Jan 3, 2022

I rewrote my code so that pico2 would send data to pico1, and then pico1 would reply.

Using your code I was able to reproduce the issue where it would only ever receive 32 chars after sleeping, by checking the contents of the log file.

The fix for me is to reinitialise uart0 immediately once we wake up:

picosleep.seconds(2)
uart = machine.UART(0, 115200)

Full code looks like:

import machine
uart = machine.UART(0)
from machine import Pin
led = Pin(25, Pin.OUT)
import time
import picosleep

def flash(times,sleep):
    num = 0
    while num < times:
        led.toggle()
        time.sleep_ms(sleep)
        num=num+1

while True:
    time.sleep(2)
    uart.write('send\n')
    try:
        while uart.any():
            rxData = uart.read()
            string = rxData.decode("ascii")
            if "UART0 with debug with very very long string to see if it is cut" in string:
                flash(4,500)
                picosleep.seconds(2)
                uart = machine.UART(0, 115200)
            else:
                f=open('log', 'a'); f.write(repr(string)+'\n'); f.close()
       
    except UnicodeError as e:
        continue

This isn't great, sleeping must reset something which is breaking this. But for me this does appear to work around the issue.

Hopefully this will work for you as well 🤞

@kjm1102
Copy link
Author

kjm1102 commented Jan 4, 2022 via email

@simarjeet75
Copy link

simarjeet75 commented Apr 7, 2023

I was trying to use the deep sleep function. I uploaded the new picosleep firmware to my board. I have a dc motor which is being powered with the help of a transistor. My objective is to toggle the motor at an interval of 5 seconds. In between the intervals the pico should go in deepsleep mode. The problem is that nothing happens. This is my code

from machine import Pin
import picosleep

motor = Pin(1, Pin.OUT)

while True:
    motor.value(1)
    picosleep.seconds(5)
    motor.value(0)
    picosleep.seconds(5)

If I replace the picosleep.seconds() with the utime.sleep() (and of course import the utime module as well) then everything works as expected. What is the issue here?

@kjm1102
Copy link
Author

kjm1102 commented Apr 7, 2023

Define 'nothing happens', the motor fails to start before the first dormancy? Have you tried;

  1. Using the pico led instead of gpio1 so you can eyeball what's happening
  2. putting a utime.sleep(.1) before the picosleep in case the picosleep fires before the motor gpio has finished toggling?

@simarjeet75
Copy link

simarjeet75 commented Apr 7, 2023

I did exactly what you suggested and the led indeed toggled at an interval of 5 seconds.
After this I disconnected my pico, attached a multimeter and reconnected it, I couldn't however, measure the current consumption as the pico wouldn't connect. Everytime I connected it with the IDE, It logs this error in loop

Unable to connect to COM8: Cannot configure port, something went wrong. Original message: PermissionError(13, 'A device attached to the system is not functioning.', None, 31)

If you have serial connection to the device from another program, then disconnect it there first.

Process ended with exit code 1.

I noticed that this error went away when I boot the pico and upload the standard micropython uf2 firmware. But it comes back again after reuploading the pico sleep micropython uf2 firmware. Any possible solution to fix this? And thank you for helping me out so far

@simarjeet75
Copy link

simarjeet75 commented Apr 7, 2023

Okay so I uploaded a flash nuke to the pico which I got online while looking for a solution for this error that is mentioned above. I found out that I need to do this everytime I save the code in pico. It temporarily fixes the issue.

As I said earlier that this code (given below) works fine

from machine import Pin
import picosleep
import utime

led= Pin(25, Pin.OUT)

while True:
    led.value(1)
    utime.sleep(1)
    picosleep.seconds(5)
    led.value(0)
    picosleep.seconds(5)

However, as soon as I change the pin number to GPIO 1, To which the motor is connected, things start to go south. The motor starts but it stops running after 1-2 seconds. And from then nothing happens. No motor movement or anything.

from machine import Pin
import picosleep
import utime

motor = Pin(1, Pin.OUT)

while True:
    motor.value(1)
    utime.sleep(1)
    # Motor stops whenever pico reaches the next line, and from then onwards nothing happens, no motor movement at all 
    picosleep.seconds(5)
    motor.value(0) # Motor should stop at this line after the 5 second deep sleep
    picosleep.seconds(5)

@kjm1102
Copy link
Author

kjm1102 commented Apr 8, 2023

ghubcoder did well to get dormancy working but it's fragile, print statements break it & it seems to have issues with the uart0 pins (gpio 0 & 1). gpio2 seems to work OK as a motor driver with picosleep.

Not sure what's wrong with your IDE, I've found Atom & Thonny work OK but are sensitive to other programs that might be running on your PC, Cura for example will hog the serial ports on a Windows machine & stop them connecting to the pico.

Maybe try a 'for i in range (5)' instead of 'while True' while testing so the program can quit with the IDE connection still working.

@simarjeet75
Copy link

I have fixed the issue with the help of some workarounds. Thank you for your help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants