adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
MIT License
3.96k stars 1.16k forks source link

nRF52840 - SPIM3 not working if VBUS not powered when reset #5233

Open jerryneedell opened 2 years ago

jerryneedell commented 2 years ago

See this Forum thread for the details: https://forums.adafruit.com/viewtopic.php?f=57&t=182604&p=887344#p887344 Summary: with an RFM9x featherwing mounted on a nRF52840, all works normally when powered vis USB, but when powered via a LiPo battery, the rfm9x fails its version check.

The OP found a workaround: create a dummy SPI instance before creating the real SPI instance that is fed to the rfm9x init. This appears to force the nRF52840 to use a different SPIM and it works. It is not clear why....

This problem is not present on M4 or esp32S2 boards.

jerryneedell commented 2 years ago

Just as an exercise, I verified that the same issue occurs with an rfm9x breakout board instead of the featherwing.

jerryneedell commented 2 years ago

I hooked up a logic analyzer: under USB power using SPIM3 (default) it airs and looks normal: rfm9x_usb under battery power, it fails and there is no activity on the SPI lines other than the chip select! rfm9x_battery_fail

If I then created the "dummy" SPI device to bypass SPIM3 both USB and Batter powered work and look like the first screen shot.

edited to add: I also confirmed with an oscilloscope that there is no signal on the SPI clock when using SPIM3 and battery power....The CS goes low, but there is no activity on the SPI lines.

mrdalgaard commented 2 years ago

I just tested this on another SPI device (BMP390) to see if this is a general issue.

I used the following bit of code:

import time
import board
import adafruit_bmp3xx
import digitalio
spi = board.SPI()
cs = digitalio.DigitalInOut(board.RX)
bmp = adafruit_bmp3xx.BMP3XX_SPI(spi, cs)

while True:
    print(bmp.pressure)
    time.sleep(0.5)

It works fine on USB power, but on battery it also fails:

>>> supervisor.get_previous_traceback()
'Traceback (most recent call last):\r\n  File "code.py", line 9, in <module>\r\n  File "/lib/adafruit_bmp3xx.py", line 323, in __init__\r\n  File "/lib/adafruit_bmp3xx.py", line 68, in __init__\r\nRuntimeError: Failed to find BMP3XX! Chip ID 0x0\n'
>>> 

Hence it seems to not be related to the RFM9x at all.

Tested it on 7.0.0rc1

jerryneedell commented 2 years ago

@mrdalgaard Thanks -- That was next on my todo list!
I also reproduced the same behavior using a Particle ARGON board (nrf52840) Something very odd about SPIM3 Googling nrf52840 spim3 produced some interesting discussions but none that seemed relevant...yet.

mrdalgaard commented 2 years ago

This issue might be related: https://devzone.nordicsemi.com/f/nordic-q-a/74754/nrf52840-application-won-t-completely-run-without-power-applied-to-vbus Since the nRF chip has VBUS directly connected to it, it might actually change behaviour based on whether USB power is connected.

This could also be related, but have not tested with a capacitor yet: https://devzone.nordicsemi.com/f/nordic-q-a/65475/spim-3-mosi-must-be-tied-to-ground-via-capacitor-to-work/268076#268076

SPIM3 does look to have a history of issues when searching for solutions.

jerryneedell commented 2 years ago

Interesting -- the first link does appear to be the same issue

amaul
amaul 4 months ago
SPI signals are obviously coming through with VBUS powered.  With VBUS disconnected the only signal that I have a heartbeat on is Chip Select. MISO, MOSI and CLK are low and do not even trip a trigger acquisition.  Signal frequency doesn't really match the working configuration, but I supposed that would be expected.  Logic level and voltage level looks correct.  I'll need to check with the BSP on APP_USBD_EVT_POWER_READY or APP_USBD_EVT_POWER_DETECTED.  I don't specifically call those.
jerryneedell commented 2 years ago

An additional observation -- If I power it up via USB, then disconnect the USB and leave it on battery power, it continues to operate normally. The issue only occurs if it is powered up or RESET when in battery power.

mrdalgaard commented 2 years ago

An additional observation -- If I power it up via USB, then disconnect the USB and leave it on battery power, it continues to operate normally. The issue only occurs if it is powered up or RESET when in battery power.

Yes, and since the person in the first link does not seem to be using CircuitPython, it would seem to be a problem with the hardware, or the bootloader, which is in common.

dhalbert commented 2 years ago

SPIM3 has a number of errata. We have implemented a RAM workaround to get it to work reliably. But I had not heard of this VBUS issue until now. I will look at this further in a few days and probably ask Nordic about it.

hathach commented 2 years ago

It is a bit weird and sound strange. I have no idea though. Maybe it is the power regulator or something. There is configurable DCDCEN0, DCDCEN register which is mentioned in 5.3.1.4 Regulator configuration examples with 4 illustrated. To be honest, I haven't tried it out.

image

hathach commented 2 years ago

I just remembered one thing that may help, when vbus is detected, usb will auto enable the hfclk() https://github.com/hathach/tinyusb/blob/master/src/portable/nordic/nrf5x/dcd_nrf5x.c#L1001

I am not sure it could help, maybe just try to plainly enable hfclk() to see if it has any impact.

dhalbert commented 2 years ago

@hathach That is very interesting, I'll try that! I note the examples above all don't use BLE, which would turn on HFCLK for radio purposes.

dhalbert commented 2 years ago

I partly reproduced this, but with the latest build (main, past rc.1), I cannot reproduce the problem. Here is my test program, run on a Feather nRF52840, powered either with USB or with a LiPO. I am monitoring the SCK and MOSI pins with a Saleae.

import board
#import _bleio

spi = board.SPI()
spi.try_lock()

data = bytes([0x79] * 100)
while True:
    spi.write(data)

I was originally testing (by mistake) with some random earlier 7.0.0 build, but when I tried the latest main build I did not see the problem Interestingly, with the unknown build, I did see SPI stop working, but only after the program had run for about 16 seconds or so, which was extremely odd. If I added import _bleio (which enables the SoftDevice), then the drop after 16 seconds did not occur.

I tried physically unplugging the battery to make sure it would work from a cold start as well as a reset.

Could you folks try again with an "Absolute Newest" build? If you don't have an oscilloscope or logic analyzer, you can just monitor the SCK pin with an LED and it should be dimly lit. You can increase the size of data to increase the duty cycle.

jerryneedell commented 2 years ago

Just to clarify, did you start from power off when trying with the LiPo?

It will keep running if initially powered via USB then unplugged.

I'll try it in an hour or so.

mrdalgaard commented 2 years ago

I tried to reproduce the problem, using my currently running code, by just commenting out the workaround. Plugged in the battery, and..... it didn't fail.. I narrowed it down to setting up the watchdog.

This bit of code will run ok:

import digitalio
import busio
import board
from microcontroller import watchdog as w
from watchdog import WatchDogMode
import adafruit_rfm9x

RADIO_FREQ_MHZ = 868.0
CS = digitalio.DigitalInOut(board.D10)
RESET = digitalio.DigitalInOut(board.D11)
w.timeout = 1000
w.mode = WatchDogMode.RAISE

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

While this will fail:

import digitalio
import busio
import board
from microcontroller import watchdog as w
from watchdog import WatchDogMode
import adafruit_rfm9x

RADIO_FREQ_MHZ = 868.0
CS = digitalio.DigitalInOut(board.D10)
RESET = digitalio.DigitalInOut(board.D11)
w.timeout = 1000
#w.mode = WatchDogMode.RAISE

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

What does the watchdog change that makes this work?

Otherwise i'm seeing the same issue as on 7.0.0b0 - Currently testing with: Adafruit CircuitPython 7.0.0-rc.1-99-g3cc4f25a1 on 2021-09-15; Adafruit Feather nRF52840 Express with nRF52840

mrdalgaard commented 2 years ago

Adding import _bleio to the code does not make a difference

dhalbert commented 2 years ago

Just to clarify, did you start from power off when trying with the LiPo?

Yes, both from a reset-button push, and from plugging the battery into an unpowered board.

jerryneedell commented 2 years ago

I just tried my previous code and it behaves the same as before -- work on USB, not on LiPo

Press any key to enter the REPL. Use CTRL-D to reload.

Adafruit CircuitPython 7.0.0-rc.1-99-g3cc4f25a1 on 2021-09-15; Adafruit Feather nRF52840 Express with nRF52840
>>> import supervisor
>>> supervisor.get_previous_traceback()
'Traceback (most recent call last):\r\n  File "code.py", line 33, in <module>\r\n  File "adafruit_rfm9x.py", line 252, in __init__\r\nRuntimeError: Failed to find rfm9x with expected version -- check wiring\n'
>>> 

code.py

# Simple demo of sending and recieving data with the RFM95 LoRa radio.
# Author: Tony DiCola
import board
import busio
import digitalio
import adafruit_rfm9x
import time

# Define radio parameters.
RADIO_FREQ_MHZ = 915.0  # Frequency of the radio in Mhz. Must match your
# module! Can be a value like 915.0, 433.0, etc.

# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
CS = digitalio.DigitalInOut(board.D10)
RESET = digitalio.DigitalInOut(board.D11)
# Or uncomment and instead use these if using a Feather M0 RFM9x board and the appropriate
# CircuitPython build:
# CS = digitalio.DigitalInOut(board.RFM9X_CS)
# RESET = digitalio.DigitalInOut(board.RFM9X_RST)

# Define the onboard LED
LED = digitalio.DigitalInOut(board.D13)
LED.direction = digitalio.Direction.OUTPUT

# Initialize SPI bus.
#spix = busio.SPI(board.A0, board.A1, board.A2)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Initialze RFM radio
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)

# Note that the radio is configured in LoRa mode so you can't control sync
# word, encryption, frequency deviation, or other settings!

# You can however adjust the transmit power (in dB).  The default is 13 dB but
# high power radios like the RFM95 can go up to 23 dB:
rfm9x.tx_power = 23

# Send a packet.  Note you can only send a packet up to 252 bytes in length.
# This is a limitation of the radio packet size, so if you need to send larger
# amounts of data you will need to break it into smaller send calls.  Each send
# call will wait for the previous one to finish before continuing.
rfm9x.send(bytes("Hello world!\r\n", "utf-8"))
print("Sent Hello World message!")

# Wait to receive packets.  Note that this library can't receive data at a fast
# rate, in fact it can only receive and process one 252 byte packet at a time.
# This means you should only use this for low bandwidth scenarios, like sending
# and receiving a single message at a time.
print("Waiting for packets...")
last_xmit = time.monotonic()
xmit_interval = 10.
while True:
    packet = rfm9x.receive()
    # Optionally change the receive timeout from its default of 0.5 seconds:
    # packet = rfm9x.receive(timeout=5.0)
    # If no packet was received during the timeout then None is returned.
    if packet is None:
        if time.monotonic() - last_xmit > xmit_interval:
            rfm9x.send(bytes("Hello world!\r\n", "utf-8"))
            print("Sent Hello World message!")
            last_xmit = time.monotonic()
    else:
        # Received a packet!
        LED.value = True
        # Print out the raw bytes of the packet:
        print("Received (raw bytes): {0}".format(packet))
        # And decode to ASCII text and print it too.  Note that you always
        # receive raw bytes and need to convert to a text format like ASCII
        # if you intend to do string processing on your data.  Make sure the
        # sending side is sending ASCII data before you try to decode!
        packet_text = str(packet, "ascii")
        print("Received (ASCII): {0}".format(packet_text))
        # Also read the RSSI (signal strength) of the last received message and
        # print it.
        rssi = rfm9x.last_rssi
        print("Received signal strength: {0} dB".format(rssi))
dhalbert commented 2 years ago

Can you try my simple example as well?

jerryneedell commented 2 years ago

@dhalbert I tried your code and it shows SPI Activity (with Salea) under both LiPo and USB I tried it with and without the rfm9x board attached. Same results.

jerryneedell commented 2 years ago

but switching to my code -- I see nothing under LiPo Power....

dhalbert commented 2 years ago

More mentions of apparently the same problem: https://devzone.nordicsemi.com/f/nordic-q-a/83266/spim3-not-work-standalone/350598 (quite recent -- I commented)

https://forums.adafruit.com/viewtopic.php?f=62&t=188798 (user encountering this)

amowry commented 1 year ago

I'm still seeing this issue with recent NRF52840 boards using the Adafruit core. Could someone give me an example of Arduino code that would implement the workaround, or is there a better workaround at this point? Thanks!