Toggle LED in custom application

I'm trying to do something extremely simple but can't get it to work. Through the dev academy, I successfully loaded up and executed exercise 1. However, when I then try to "Create a New Application" (rather than "Add and existing appliction), running the exact same main.c code, I am not able to toggle the LEDs and none of the GPIOs seem to be configured. I'm guessing this has something to do with the generated files, but I don't know where to start looking. Please help!

BTW - I am able to attach with the debugger and see that the initialization seems to be completing without error and function call to the gpio toggle also returns with no error. main.c code for both applications is the following:

const struct device *dev0;
    const int PIN=29;
    bool led_is_on = true;
    int ret;

    dev0 = DEVICE_DT_GET(DT_NODELABEL(gpio0));

    if (dev0==NULL) {
        printk("device not found");
        return;
    }

    if (!device_is_ready(dev0)) {
        printk("GPIO controller not ready");
        return -ENODEV;
    }
    gpio_pin_configure(dev0, PIN, GPIO_OUTPUT);
    while(1) {
        gpio_pin_toggle(dev0, PIN);
        k_msleep(SLEEP_TIME_MS);
    }
Parents
  • I applied the following modifications to the hello world sample in NCS v2.1.0:

    diff --git a/samples/hello_world/prj.conf b/samples/hello_world/prj.conf
    index b2a4ba5910..d63ffc4391 100644
    --- a/samples/hello_world/prj.conf
    +++ b/samples/hello_world/prj.conf
    @@ -1 +1,2 @@
     # nothing here
    +CONFIG_GPIO=y
    diff --git a/samples/hello_world/src/main.c b/samples/hello_world/src/main.c
    index 8676d8940b..0e61e7a84d 100644
    --- a/samples/hello_world/src/main.c
    +++ b/samples/hello_world/src/main.c
    @@ -5,8 +5,33 @@
      */
     
     #include <zephyr/zephyr.h>
    +#include <zephyr/drivers/gpio.h>
    +
    +#define SLEEP_TIME_MS 500
     
     void main(void)
     {
     	printk("Hello World! %s\n", CONFIG_BOARD);
    +	const struct device *dev0;
    +    const int PIN=29;
    +    int ret;
    +
    +    dev0 = DEVICE_DT_GET(DT_NODELABEL(gpio0));
    +
    +    if (dev0==NULL) {
    +        printk("device not found");
    +    }
    +
    +    if (!device_is_ready(dev0)) {
    +        printk("GPIO controller not ready");
    +    }
    +
    +    ret = gpio_pin_configure(dev0, PIN, GPIO_OUTPUT);
    +    if(ret){
    +        printk("gpio_pin configure failed: %d\n", ret);
    +    }
    +    while(1) {
    +        gpio_pin_toggle(dev0, PIN);
    +        k_msleep(SLEEP_TIME_MS);
    +    }
     }
    

    Then I programmed it onto the board nrf5340dk_nrf5340_cpuapp, and LED2 blinked as expected.

    Here is the complete sample:

    2248.hello_world_blinky.zip

    Best regards,

    Simon

  • I've gotten a little closer to the root of the problem but still need some help. Looking at the registers for the pin, with the application that can successfully toggle the LED, I see this:

    With the application that cannot, I see this:

    So somehow the pin control is being assigned to the network core, not the application core. I verified that for both projects, the build configuration is the same (UBX_EVKNORAB10_NRF5340 Application MCU). So what project settings assign pins to the different cores and how do I set that up? In my application, at least for now, I have no need for the Network MCU to control pins directly, so all should be assigned to the Application core.

    Thanks,

    Jamie

  • I guess P0.29 is forwarded to the netcore using the dts compatible nordic,nrf-gpio-forwarder, like explained in this reply.

    Try to disable forwarding pins to the netcore by doing adding the following to a file nrf5340dk_nrf5340_cpuapp (I assume you're not using cpuapp_ns)

    / {
    	gpio_fwd: nrf-gpio-forwarder {
    		compatible = "nordic,nrf-gpio-forwarder";
    		status = "disabled";
    	};
    };

    If that doesn't work, try to open the file <sample>/build/zephyr/zephyr.dts and search for "nordic,nrf-gpio-forwarder", and see what name the compatible is within. For example if it is within "some_name: nrf-gpio-forwarder" instead, use that in your overlay.

    Best regards,

    Simon

  • Hi Simon
    So I have solved my problem, but it brought up some other questions. First, the solution to what was happening is that the auto-generated main() function for the two non-functional examples had this code hanging around below main() that I hadn't noticed (somehow!):
    -----------------------------------------------------------------------------------------------------
    /** @brief Allow access to specific GPIOs for the network core.
     *
     * Function is executed very early during system initialization to make sure
     * that the network core is not started yet. More pins can be added if the
     * network core needs them.
     */
    static int network_gpio_allow(const struct device *dev)
    {
        ARG_UNUSED(dev);

        /* When the use of the low frequency crystal oscillator (LFXO) is
         * enabled, do not modify the configuration of the pins P0.00 (XL1)
         * and P0.01 (XL2), as they need to stay configured with the value
         * Peripheral.
         */
        uint32_t start_pin = (IS_ENABLED(CONFIG_SOC_ENABLE_LFXO) ? 2 : 0);

        /* Allow the network core to use all GPIOs. */
        for (uint32_t i = start_pin; i < P0_PIN_NUM; i++) {
            NRF_P0_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU <<
                        GPIO_PIN_CNF_MCUSEL_Pos);
        }

        for (uint32_t i = 0; i < P1_PIN_NUM; i++) {
            NRF_P1_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU <<
                        GPIO_PIN_CNF_MCUSEL_Pos);
        }


        return 0;
    }

    SYS_INIT(network_gpio_allow, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
    ------------------------------------------------------------------------------------------------------
    So at start-up, all pins were assigned to the network core via this embedded function call before main even had a chance to execute. Commenting out the last line ("SYS_INIT") fixes the problem and I/Os are under control. However, this brings up the following quesitons:
    1) Why was this code inserted and from where is network_gpio_allow() called since it is clearly not called from main()?
    2) Using STM32 applications as an example, in their main files we get something like the following:
    So each of these HAL functions is called from main which makes it very easy to jump into the generated code and see exactly what registers are being set, etc, to configure a peripheral. In Zephyr code, this seems to be very obfuscated. Where are the initialization calls taking place since?
    3) In STM32 applications, we also get an assembly file - e.g. startup_stm32h743zitx.s - which, upon reset, initializes all the memory then jumps to main. Where does this occur in Zephyr?
    4) Does Zephy code adhere to CMSIS conventions? Again, citing STM32 code (sorry, I've just written a lot of that so it makes comparison easy for me), if I want to change a register in a timer, for example, I can write: TIM3->CNT = 0; which would access the count register of Timer 3 directly. Can I do the same for Zephyr code?
    Thanks,
    Jamie
  • Hi Jamie

    Simon is currently unavailable, so I will help you out instead. 

    jmilliken said:
    1) Why was this code inserted and from where is network_gpio_allow() called since it is clearly not called from main()?

    The only reference to this function I can find is in the main.c file of the empty_app_core example. 

    The assumption is that if you are not running any code in the app core you want all the GPIO's available to the network core, since this is where you're running all your code. 

    In the end it is up to the app core to decide which pins are assigned to which core, which is why the app core has to run this during boot. 

    The SYS_INIT macro allows you to schedule code to be run during the boot process of the Zephyr kernel, before the main thread or any other application code has started. 

    The call to the various SYS_INIT functions will come from the init.c file in the zephyr\kernel folder, more specifically this line.

    jmilliken said:
    Where are the initialization calls taking place since?

    Essentially this is triggered by the z_cstart(void) function in the init.c file I mentioned earlier. 

    The various modules in your system that needs some kind of boot time initialization can define init functions using either the SYS_INIT macro, or one of the DEVICE macros for device drivers, as described here

    jmilliken said:
    3) In STM32 applications, we also get an assembly file - e.g. startup_stm32h743zitx.s - which, upon reset, initializes all the memory then jumps to main. Where does this occur in Zephyr?

    The reset code is implemented in /zephyr/arch/arm/core/aarch32/cortex_m/reset.S, which in turn calls the z_arm_prep_c() function in /zephyr/arch/arm/core/aarch32/prep_c.c, which among other things call the z_cstart() function in init.c that I mentioned earlier. 

    jmilliken said:
    4) Does Zephy code adhere to CMSIS conventions?

    Yes, all the hardware registers have CMSIS definitions available. 

    I believe you need to include <device.h> to get access to them. 

    Please note that all peripheral in the nRF devices use the NRF_ prefix, eg:

    NRF_TIMER0->TASKS_START = 1;

    Best regards
    Torbjørn

Reply
  • Hi Jamie

    Simon is currently unavailable, so I will help you out instead. 

    jmilliken said:
    1) Why was this code inserted and from where is network_gpio_allow() called since it is clearly not called from main()?

    The only reference to this function I can find is in the main.c file of the empty_app_core example. 

    The assumption is that if you are not running any code in the app core you want all the GPIO's available to the network core, since this is where you're running all your code. 

    In the end it is up to the app core to decide which pins are assigned to which core, which is why the app core has to run this during boot. 

    The SYS_INIT macro allows you to schedule code to be run during the boot process of the Zephyr kernel, before the main thread or any other application code has started. 

    The call to the various SYS_INIT functions will come from the init.c file in the zephyr\kernel folder, more specifically this line.

    jmilliken said:
    Where are the initialization calls taking place since?

    Essentially this is triggered by the z_cstart(void) function in the init.c file I mentioned earlier. 

    The various modules in your system that needs some kind of boot time initialization can define init functions using either the SYS_INIT macro, or one of the DEVICE macros for device drivers, as described here

    jmilliken said:
    3) In STM32 applications, we also get an assembly file - e.g. startup_stm32h743zitx.s - which, upon reset, initializes all the memory then jumps to main. Where does this occur in Zephyr?

    The reset code is implemented in /zephyr/arch/arm/core/aarch32/cortex_m/reset.S, which in turn calls the z_arm_prep_c() function in /zephyr/arch/arm/core/aarch32/prep_c.c, which among other things call the z_cstart() function in init.c that I mentioned earlier. 

    jmilliken said:
    4) Does Zephy code adhere to CMSIS conventions?

    Yes, all the hardware registers have CMSIS definitions available. 

    I believe you need to include <device.h> to get access to them. 

    Please note that all peripheral in the nRF devices use the NRF_ prefix, eg:

    NRF_TIMER0->TASKS_START = 1;

    Best regards
    Torbjørn

Children
Related