adafruit / Adafruit_CircuitPython_RFM9x

CircuitPython module for the RFM95/6/7/8 LoRa wireless 433/915mhz packet radios.
MIT License
67 stars 44 forks source link

100% CPU utilization on RPi4 #93

Open Syrex-o opened 1 month ago

Syrex-o commented 1 month ago

Is anyone else also facing the issue, that the CPU utilization reaches 100% on the receiving side?

I use a Pi4 2GB and have a constant 100% cpu utilization on one core. image

jerryneedell commented 1 month ago

Can you post the code you are running?

Syrex-o commented 1 month ago

Sure,

I reduced it to the minimum and tested it. Still 100% on a single core:

#!/usr/bin/python3

import time, struct, sys, board, busio
import digitalio
import adafruit_rfm9x

RADIO_FREQ_MHZ = 434.0
CS = digitalio.DigitalInOut(board.CE1)
RESET = digitalio.DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
radio = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

radio.tx_power = 23
radio.spreading_factor = 7
radio.enable_crc = True
radio.signal_bandwidth = 250000

if __name__ == '__main__':
    try:
        while True:
            packet = radio.receive(timeout=10, with_ack=False, with_header=False)
            if packet is not None:
                print("Received: ", packet)
    except KeyboardInterrupt:
        sys.stdout.flush()
    finally:
        sys.stdout.flush()

Python version is 3.9.2 Iḿ using the SX1278 modules (RA-02)

jerryneedell commented 1 month ago

Thanks, I tried your code on a Pi5 (snce that is what I had handy) and the load gets distributed a bit better, but I'm sure it would be the same as your results on a Pi4. I'm not really surprised since there is nothing in the code that will give the CPU a "break". I just wanted to confirm the behaviour. FYI, I am working on a version of the library that uses asyncio and I will give that a try to see if it make any difference. The new library is still a "Work In Progress", but you are welcome to try it. This library combines the RFM9x and RFM69 libraries and enables using FSK/OOK on the RFM9X. The repository is at : https://github.com/jerryneedell/CircuitPython_RFM.git . the example https://github.com/jerryneedell/CircuitPython_RFM/blob/main/examples/rfm9x_rh_asyncio_listen.py is probably the most relevant. I'll try adapting your example to see how it works but I won't be able to try it until later today. I'll also see if I can set up a Pi4 to were are comparing the same thing.

Syrex-o commented 1 month ago

Thanks for testing :) I´m a little bit confused by this behavior. I work with NRF24 modules and receiving sides with continuous listening and never had such a load.

I already saw your lib and appreciate your efforts :) I will definitely try your new lib and give feedback here.

@jerryneedell litte Update: I even have slight improvements when using asyncio and this library. I tried the pyLoRa library now as well. It acts equal.

Essentially my biggest concern is that a RPi Zero in 24/7 receive mode might have a short lifespan due to constant 100% usage.

jerryneedell commented 1 month ago

I tried this with the new library and the CPU usage appears to be great;y reduced (on my Pi 5) - Note: I did change the frequency to 915 since I am in the USA.

#!/usr/bin/python3

import time, struct, sys, board, busio
import asyncio
import digitalio
from circuitpython_rfm import rfm9x

RADIO_FREQ_MHZ = 915.0
CS = digitalio.DigitalInOut(board.CE1)
RESET = digitalio.DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
radio = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

radio.tx_power = 23
radio.spreading_factor = 7
radio.enable_crc = True
radio.signal_bandwidth = 250000

if __name__ == '__main__':
    try:
        while True:
            packet = radio.receive(timeout=10, with_header=False)
            if packet is not None:
                print("Received: ", packet)
    except KeyboardInterrupt:
        sys.stdout.flush()
    finally:
        sys.stdout.flush()

Now to see if I can find my Pi4...

jerryneedell commented 1 month ago

Looking at the adafruit_rfm9x.py code, it may be possible to reduce the CPU usage by inserting a short sleep - something like "time.sleep(.001)" in the "receive" function -- https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/blob/main/adafruit_rfm9x.py#L844

I don't think this will impact the receiver in list mode, it should just slow down the polling for the packet. As it is now, the polling is continuous -- hence the high CPU usage. I have not tried this -- your mileage may vary ;-)

Edited to add: I am curious if the 100%CPU usage is actually causing you problems or if it is just a curiosity.

Syrex-o commented 1 month ago

Just tested with the new lib on a Pi4 and it works like a charm :) image

Do you have a rough time estimation for the lib to go live on PyPI? As soon as another Pi Zero arrives, I will post test results from it :)

Edit: I don´t really have a problem with the CPU usage. It is just my assumption.

jerryneedell commented 1 month ago

Also FYI -- On a RPi Zero2W using the new library with the example above drops the CPU usage from 100% to about 75% on one core.

jerryneedell commented 1 month ago

Do you have a rough time estimation for the lib to go live on PyPI?

Not really, but since you are the first person to actually express an interest in using it, I will try to get back to my testing. I just need to convince myself it "does no harm". With the combined libraries and new modes, it just takes awhile to create examples and test it on both the Raspberry Pis and other MCUs.
This was a great example of the usefulness of the asyncio addition . I really had not considered the impact on CPU usage. I does also appear to have some positive impact on the failure rate of transmissions and that has taken up a lot of testing time.

I'll post here when I make progress on releasing the new library.

Thanks for the testing.

Syrex-o commented 1 month ago

What exactly do you mean by "harm"?

If you need any testing support, I'm open to assist :)

jerryneedell commented 1 month ago

I just want to make sure it does note break anything that works in the current libraries. If you use it, please let me know if you find any issues and feel free to offer comments on the code.

crichmon762 commented 2 weeks ago

I ran into the same high load issue, which took a while to figure out because I was chasing missed/mangled messages. What I ended up doing was adding a 20ms delay in the receive loop where it waits for self.rx_done. The load went from mid-90% to <5%. A 50ms delay was too long and I was still missing/mangling received messages. I imagine the minimum time could be calculated based on the on-air time of the smallest packet at the fastest data rate. I get 4-5 messages as a block every 5min, and since this change, zero bad messages (actual contents checked with sha1sum on both ends).

jerryneedell commented 2 weeks ago

You may want to try the new library https://github.com/adafruit/Adafruit_CircuitPython_RFM which utilizes asyncio. It has not been released yet, so it is not in the bundle or PyPi, but you can download it from the repository if you want to give it a try. Your code changes should be minimal to use it with your existing code, take a look at the examples. Any comments or suggestions would be welcome.