embassy icon indicating copy to clipboard operation
embassy copied to clipboard

nRF52 - Implement a way to handle using NFC pins and Reset pin as GPIO

Open Ardelean-Calin opened this issue 3 years ago • 3 comments

On the Nordic line of microcontrollers it is possible to use the NFC pins as GPIO. This behavior needs to be enabled as described below. A similar situation exists for the nRESET pin, which by default acts as a GPIO and needs to be reconfigured in order to work as a true Reset pin (for more details see Peter's comment below).

However, in order to change their functionalities to GPIO, a write to one of the User Information Configuration Registers(UICR) is needed.

For the NFC pins, the NFCPINS register of the UICR needs to be set from 0xFFFFFFFF to 0xFFFFFFFE in order to enable GPIO functionality. For the nRESET pin, something similar needs to be done with the PSELRESET[0] and PSELRESET[1] registers. (See here)

This can be done by using, for example, ‘nrfjprog’ but the values will be reset to default after a chip erase: nfrjprog —memwr 0x1000120C —val 0xFFFFFFFE

Nordic does this by the use of C preprocessor defines. See here in the Nordic SDK how they enable GPIO on nRESET and NFCPINS.

We could also do something similar but using cargo features instead of preprocessor defines.

Peter Hansen on the embassy Matrix channel was kind enough to supply an early version on how to implement this UICR write in Rust using the nRF52 PAC. I have used these snippets as a temporary workaround.

nRESET to GPIO:

// Reconfigure UICR to enable reset pin if required (resets if changed).
pub fn configure_reset_pin() {
    let uicr = unsafe { &*pac::UICR::ptr() };
    let nvmc = unsafe { &*pac::NVMC::ptr() };

    #[cfg(feature = "52840")]
    const RESET_PIN: u8 = 18;
    #[cfg(feature = "52832")]
    const RESET_PIN: u8 = 21;

    // Sequence copied from Nordic SDK components/toolchain/system_nrf52.c
    if uicr.pselreset[0].read().connect().is_disconnected() ||
        uicr.pselreset[1].read().connect().is_disconnected()
        {
        nvmc.config.write(|w| w.wen().wen());
        while nvmc.ready.read().ready().is_busy() {}

        for i in 0..=1 {
            uicr.pselreset[i].write(|w| {
                unsafe { w.pin().bits(RESET_PIN); }           // should be 21 for 52832

                #[cfg(feature = "52840")]
                w.port().clear_bit();       // not present on 52832

                w.connect().connected();
                w
                });
            while nvmc.ready.read().ready().is_busy() {}
        }

        nvmc.config.write(|w| w.wen().ren());
        while nvmc.ready.read().ready().is_busy() {}

        cortex_m::peripheral::SCB::sys_reset();
    }
} 

NFCPINS to GPIO:

// Reconfigure NFC pins to be regular GPIO pins (resets if changed).
pub fn configure_nfc_pins_as_gpio() {
    let uicr = unsafe { &*pac::UICR::ptr() };
    let nvmc = unsafe { &*pac::NVMC::ptr() };

    // Sequence copied from Nordic SDK components/toolchain/system_nrf52.c
    if uicr.nfcpins.read().protect().is_nfc() {
        nvmc.config.write(|w| w.wen().wen());
        while nvmc.ready.read().ready().is_busy() {}

        uicr.nfcpins.write(|w| w.protect().disabled());
        while nvmc.ready.read().ready().is_busy() {}

        nvmc.config.write(|w| w.wen().ren());
        while nvmc.ready.read().ready().is_busy() {}

        cortex_m::peripheral::SCB::sys_reset();
    }
}

These code snippets may still need to be generalized for other nRF chips, but they represent a good start.

I personally think cargo features would be the most straightforward and least confusing approach from a newcomers perspective, but I understand the appeal of having an explicit API function to call in case such pin remapping is needed.

Let’s discuss the best approach in this issue!

Ardelean-Calin avatar Sep 24 '22 11:09 Ardelean-Calin

A correction to the above: by default the reset pin is just a GPIO, and in this case the write to UICR is required to make it available for the reset functionality. Without doing that, the chip has no physical reset pin available, just soft reset (via code) or power cycle or watchdog reset. That might have been clearer if I'd named the function enable_reset_pin() instead of configure_reset_pin()...

Also, though the code works properly, I would think that nfrjprog snippet would not, as the value should be 0xfffffffe (with 'e', not 'd'). I believe... if it does work as-is, we might want to investigate why since it seems it should not.

peter9477 avatar Sep 24 '22 18:09 peter9477

And to capture some of the Matrix discussion: I'd wondered if it might be reasonable to put these into both of the embassy_exec::main macro and the config struct passed to embassy_nrf::init() to allow greater user control over when this is done. Dario pointed out that if not done with a feature flag, it wouldn't be possible to remove the two NFC pins from the Peripheral struct statically. Doing that statically has a big benefit, since then you'd get a compiler error if you tried accessing those as regular GPIO pins when they're not actually available. The same logic doesn't apply in the PINRESET case (since the pin is available as a GPIO in either case), but there's a benefit in keeping the two things consistent in how they're specified, so using a feature for PINRESET as well makes sense.

Someone needs to review the datasheets or PACs for the other chips in the family and make a matrix of which have the same features. Note that the actual physical pin available as the PINRESET is different on the various chips, and there's a specific value that must be written there (i.e. you can't make just any GPIO the nRESET pin). The above code handles only the 52832 and 52840 for now.

peter9477 avatar Sep 24 '22 18:09 peter9477

I corrected the issue description regarding the reset pin. Also it should have been 0xFFFFFFFE, that was I typo on my part, sorry. Thanks, Peter!

Ardelean-Calin avatar Sep 24 '22 18:09 Ardelean-Calin

It seems this issue is handled in #1170

Ardelean-Calin avatar Jan 25 '23 09:01 Ardelean-Calin

Fixed in #1170

Dirbaio avatar Feb 20 '23 01:02 Dirbaio