nRF52832: how to toggle two synced pulsing GPIO pins between input (high impedance) and output

Description:

I have two GPIO pins whose polarity is toggled via PPI signal. These pins are pulsing in sync: both are simultaneously high, and simultaneously low. I'd like to toggle between these two pins so that only one is in use at a time (output mode) while the other is inactive (input / high impedance). There's always a falling edge following a rising edge and vice versa, no matter which pin is active. The input pin is not supposed to read anything, the main purpose is to be disconnected.

GPIOTE initialization:

static uint8_t gpiote_channel1;
static nrfx_gpiote_pin_t           gpiote_pin1 = DT_GPIO_PIN(DT_NODELABEL(m_gpio1), gpios);
static nrfx_gpiote_output_config_t pin1_out_config;
static nrfx_gpiote_task_config_t   pin1_task_config;

static uint8_t gpiote_channel2;
static nrfx_gpiote_pin_t           gpiote_pin2 = DT_GPIO_PIN(DT_NODELABEL(m_gpio2), gpios);
static nrfx_gpiote_output_config_t pin2_out_config;
static nrfx_gpiote_task_config_t   pin2_task_config;

...
...
...

void gpiote_init() {
    /* pin 1 init */
    
    nrfx_gpiote_channel_alloc(&gpiote_channel1);

    pin1_out_config.drive = NRF_GPIO_PIN_S0S1;
    pin1_out_config.input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT;
    pin1_out_config.pull = NRF_GPIO_PIN_NOPULL;

    pin1_task_config.task_ch = gpiote_channel1;
    pin1_task_config.polarity = NRF_GPIOTE_POLARITY_TOGGLE;
    pin1_task_config.init_val = NRF_GPIOTE_INITIAL_VALUE_LOW;

    nrfx_gpiote_output_configure(gpiote_pin1, &pin1_out_config, &pin1_task_config);
    nrfx_gpiote_out_task_enable(gpiote_pin1);
    
    /* pin 2 init */
    
    nrfx_gpiote_channel_alloc(&gpiote_channel2);

    pin2_out_config.drive = NRF_GPIO_PIN_S0S1;
    pin2_out_config.input_connect = NRF_GPIO_PIN_INPUT_DISCONNECT;
    pin2_out_config.pull = NRF_GPIO_PIN_NOPULL;

    pin2_task_config.task_ch = gpiote_channel2;
    pin2_task_config.polarity = NRF_GPIOTE_POLARITY_TOGGLE;
    pin2_task_config.init_val = NRF_GPIOTE_INITIAL_VALUE_LOW;

    nrfx_gpiote_output_configure(gpiote_pin2, &pin2_out_config, &pin2_task_config);
    nrfx_gpiote_out_task_enable(gpiote_pin2);
}
    

PPI setup:

void mm_ppi_set() 
{
    nrfx_ppi_channel_alloc(&mm_ppi_ch1);

    nrfx_ppi_channel_assign(mm_ppi_ch1, mm_rtc_event(), mm_gpiote_toggle1());
    rfx_ppi_channel_fork_assign(mm_ppi_ch1, mm_gpiote_toggle2());

    err = nrfx_ppi_channel_enable(mm_ppi_ch1);
}

1st approach:

By using nrfx_gpiote.h-library I tried to affect the pin state by using functions nrfx_gpiote_output_configure() and nrfx_gpiote_input_configure(). I never managed to get the expected result, the closest I got was to toggle between the pins, but on the oscilloscope I'd sometimes see two rising edges following each other from different pins, or two falling edges. This would mean that they are no longer in sync. The aforementioned functions had some rules to apply to ensure the correct behaviour, but either I missed something or this is not the way to go.

Input configuration:

void callback(void)
{
...
...
    nrfx_gpiote_input_config_t p_input_config = {
        .pull = NRF_GPIO_PIN_NOPULL,
    };
    nrfx_gpiote_trigger_config_t p_trigger_config = {
        .trigger = NRFX_GPIOTE_TRIGGER_NONE,
        .p_in_channel = NULL,                 // <--- if NULL, the sensing mechanism is used.
    };
    nrfx_gpiote_pin_uninit(nonactive);  // <--- without this the input configuration returns 'NRFX_ERROR_INVALID_PARAM'
    nrf_gpiote_task_configure(NRF_GPIOTE, nonactive_task->task_ch, nonactive,
                              NRF_GPIOTE_POLARITY_NONE, NRF_GPIOTE_INITIAL_VALUE_LOW);  // another way to set POLARITY_NONE?
    nrfx_gpiote_input_configure(nonactive, &p_input_config, &p_trigger_config, NULL);
...
...
}

I understand that the p_input_config cannot be NULL, if I want to configure the pin from output to input. For this I need to disable the task by configuring NRF_GPIOTE_POLARITY_NONE. There is a mention of this in the comments of the code snippet. Also configuring trigger shouldn't be done, but instead use sensing. This is done by setting .p_in_channel to NULL, correct?

Output configuration:

void callback(void)
{
...
...
    //nrfx_gpiote_trigger_disable(active);  <--- is this needed if the trigger was configured as NONE?
    nrfx_gpiote_output_configure(active, &pin1_out_config, active_task);  <--- active task points to the init task conf of the active pin
    nrfx_gpiote_out_task_enable(active);
...
...
}

With the pin reconfiguration to output I'm a bit lost: how should I go about it?

1st conclusion:

The pins remain on low state (or high-z, I wasn't able to measure the exact state) and doesn't produce any rising edges, so this approach is either missing something or is definitely wrong. It is still the theory on what I should do.

2nd approach:

I write directly on the pin configuration drive registers. I understand that this way I can hold on to the output state, which doesn't cause any accidental misbehaviour. It would also explain why NRF_GPIOTE_POLARITY_TOGGLE in the configuration now works flawlessly. S0S1 means "standard'0', standard '1'" and S0D1 is "standard '0', disconnect '1'".

void callback(void)
{
...
...
    NRF_GPIO->PIN_CNF[nonactive] = (GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos);
    NRF_GPIO->PIN_CNF[active]    = (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos);
...
...
}

2nd conclusion:

The oscilloscope shows that the pins are switching between each other, and are in sync. This is exactly the behaviour I was after. However the deactivated pin is disconnected only half the time. It's either grounded (0) or disconnected (1), but instead I'd like to have it disconnected altogether. Since there is no D0D1-option, I'm not all the way there with my goal.

So now I'd like to hear your opinions and suggestions on how to improve the codes and ideas I share here, or maybe you have an alternative solution to this issue.

Thank you for your help, and let me know if I can provide you with some information I overlooked so far.

Parents
  • Hi,

    Have you tried to set the PIN_CNF->DIR to "Input" and PIN_CNF->INPUT to "Disconnect" for the nonactive pin?

    Best regards,
    Jørgen

  • Hi,

    thank you for the suggestion.

    I tried as such:

    NRF_GPIO->PIN_CNF[nonactive] = (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
    NRF_GPIO->PIN_CNF[nonactive] = (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);
    //NRF_GPIO->PIN_CNF[active] = (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);

    I tried both with and without the output configuration, hoping to see any kind of effect like a flatline. However these configurations seem to do nothing as is. Might this be the reason (from the documents):

    ...

    As long as a SET[n], CLR[n] and OUT[n] task or an IN[n] event is configured to control a pin n, the pin's output value will only be updated by the GPIOTE module. The pin's output value as specified in the GPIO will therefore be ignored as long as the pin is controlled by GPIOTE. Attempting to write a pin as a normal GPIO pin will have no effect. When the GPIOTE is disconnected from a pin, see MODE field in CONFIG[n] register, the associated pin will get the output and configuration values specified in the GPIO module.

    ...

    Or maybe I have other registers to toggle that I have overlooked? I haven't worked like this with registers too much yet.

    There is a mention of disconnecting GPIOTE from the GPIO before writing. Would this be a solution? I don't know how to do this other than using something like nrfx_gpiote_pin_uninit(). This seems to lead a reconfiguration cycle, since I would need to configure the pin as output everytime a switch is done. This seems a bit messy way to do it.

    Do you have any further thoughts or suggestions?

Reply
  • Hi,

    thank you for the suggestion.

    I tried as such:

    NRF_GPIO->PIN_CNF[nonactive] = (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
    NRF_GPIO->PIN_CNF[nonactive] = (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);
    //NRF_GPIO->PIN_CNF[active] = (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);

    I tried both with and without the output configuration, hoping to see any kind of effect like a flatline. However these configurations seem to do nothing as is. Might this be the reason (from the documents):

    ...

    As long as a SET[n], CLR[n] and OUT[n] task or an IN[n] event is configured to control a pin n, the pin's output value will only be updated by the GPIOTE module. The pin's output value as specified in the GPIO will therefore be ignored as long as the pin is controlled by GPIOTE. Attempting to write a pin as a normal GPIO pin will have no effect. When the GPIOTE is disconnected from a pin, see MODE field in CONFIG[n] register, the associated pin will get the output and configuration values specified in the GPIO module.

    ...

    Or maybe I have other registers to toggle that I have overlooked? I haven't worked like this with registers too much yet.

    There is a mention of disconnecting GPIOTE from the GPIO before writing. Would this be a solution? I don't know how to do this other than using something like nrfx_gpiote_pin_uninit(). This seems to lead a reconfiguration cycle, since I would need to configure the pin as output everytime a switch is done. This seems a bit messy way to do it.

    Do you have any further thoughts or suggestions?

Children
No Data
Related