nRF52 PWM / Stepper Motor

The article  RE: nRF52 PWM / Stepper Motor has most of what I need.  The unfortunate thing is that the solution is written for a version of the SDK that I do not know.  I am new to the nRF52* platform, and have come up to speed with using nRFConnect V2.1.0 with VSCode.  The solution does not compile under this newer version of the SDK.  Would someone be able to translate it?  

Specifically, I am trying to drive 2 stepper motors without the use of a secondary MCU to control the steps/acceleration/position/etc that comes with steppers.  I was able to use PWM to drive one motor, but I need 2 motors running at an independent frequency for differing speeds. According to the nRF52840 spec, the same PWM frequency is shared with all the PWM outputs. I would love to try the PPI/counter example to see if it would work for me, but as far as demo code for PPI and Counters with nRFConnect2.1.0, examples are scarce. 

So bottom line is, with nRF.Connect v2.1.0, is there a low-overhead method that would allow the chip to handle 2 stepper motors with minimal overhead?  

Parents Reply Children
  • I misread the spec. I see now that the nRF52840 chip contains Four PWM hardware modules that have 4 PWM outputs with a common frequency per module. I was able to get two motors running, each at a different frequency.   It took me a bit of time to get the overlay file setup correctly, as this is still a bit foreign to me. 

    I modeled the PWM1 and PWM2 after the PWM0/LED setup as described for the nRF52840DK board.  So in the end, I now have 3 PWM modules: PWM0 for LEDs, PWM1 for Motor0, PWM2 for Motor1.   

    The source of what I've created is below.  It only contains the PWM step setup and drive.  The question I have now, is....

    should I be modeling the pwmmotors node as a pwm-leds compatible entity?  or should it be something else?

    //nrf52840_nrf52840.overlay
    
    &pwm1 {
        status = "okay";
        pinctrl-0 = <&pwm1_default>;
        pinctrl-1 = <&pwm1_sleep>;
        pinctrl-names = "default", "sleep";
    };
    
    &pwm2 {
        status = "okay";
        pinctrl-0 = <&pwm2_default>;
        pinctrl-1 = <&pwm2_sleep>;
        pinctrl-names = "default", "sleep";
    };
    
    
    &pinctrl {
        pwm1_sleep: pwm1_sleep {
    		group1 {
    			psels = <NRF_PSEL(PWM_OUT0, 1, 3)>;
    			low-power-enable;
    		};
    	};
        pwm1_default: pwm1_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 1, 3)>;
                nordic,invert;
            };
        };
        pwm2_sleep: pwm2_sleep {
    		group1 {
    			psels = <NRF_PSEL(PWM_OUT0, 1, 4)>;
    			low-power-enable;
    		};
    	};
        pwm2_default: pwm2_default {
            group1 {
                psels = <NRF_PSEL(PWM_OUT0, 1, 4)>;
                nordic,invert;
            };
        };
    };
    
    / {
        pwmmotors {
            compatible = "pwm-leds";
            pwm_motor0: pwm_motor_0 {
                pwms = <&pwm1 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
            };
            pwm_motor1: pwm_motor_1 {
                pwms = <&pwm2 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
            };
        };
        aliases {
            pwm-motor0 = &pwm_motor0;
            pwm-motor1 = &pwm_motor1;
        };
    };
    //main.c
    
    #include <zephyr/zephyr.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/pwm.h>
    
    static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));
    static const struct pwm_dt_spec pwm_motor0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_motor0));
    static const struct pwm_dt_spec pwm_motor1 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_motor1));
    
    #define FREQUENCY_MIN (512U)
    #define FREQUENCY_MAX (16384U)
    #define FREQUENCY_DELTA (512U)
    
    void main(void)
    {
    	int ret;
    
    	printk("PWM1-based motor control\n");
    
    	if (!device_is_ready(pwm_led0.dev)) {
    		printk("Error: PWM0 device %s is not ready\n",
    		       pwm_led0.dev->name);
    		return;
    	}
    	if (!device_is_ready(pwm_motor0.dev)) {
    		printk("Error: PWM1 device %s is not ready\n",
    		       pwm_motor0.dev->name);
    		return;
    	}
    	if (!device_is_ready(pwm_motor1.dev)) {
    		printk("Error: PWM2 device %s is not ready\n",
    		       pwm_motor1.dev->name);
    		return;
    	}
    	while(1) {
    		for(int i = FREQUENCY_MIN; i <= FREQUENCY_MAX; i+=FREQUENCY_DELTA) {
    			printk("%i:",i);
    			if (pwm_set_dt(&pwm_motor0, PWM_HZ(i), PWM_HZ(i) / 2U))
    				printk("Error %d: failed to set pulse width\n", ret);
    			
    			printk("%i:", FREQUENCY_MAX-i+FREQUENCY_MIN);
    			if (pwm_set_dt(&pwm_motor1, PWM_HZ(FREQUENCY_MAX-i+FREQUENCY_MIN),
    				PWM_HZ(FREQUENCY_MAX-i+FREQUENCY_MIN)/2U))
    					printk("Error %d: failed to set motor0 pulse width\n", ret);
    		}
    
    		for(int i = FREQUENCY_MAX; i >= FREQUENCY_MIN; i-=FREQUENCY_DELTA) {
    			printk("%i:",i);
    			if (pwm_set_dt(&pwm_motor0, PWM_HZ(i), PWM_HZ(i) / 2U))
    				printk("Error %d: failed to set pulse width\n", ret);
    
    			printk("%i:", FREQUENCY_MAX-i+FREQUENCY_MIN);
    			if (pwm_set_dt(&pwm_motor1, PWM_HZ(FREQUENCY_MAX-i+FREQUENCY_MIN),
    				PWM_HZ(FREQUENCY_MAX-i+FREQUENCY_MIN)/2U))
    					printk("Error %d: failed to set motor0 pulse width\n", ret);
    		}
    		
    	}
    }
    
  • I edited the above to use pins P1.03 and P1.04.... these are unused on the nRF52840DK board.  Also, my prj.conf file is here:

    CONFIG_STDOUT_CONSOLE=y
    CONFIG_PRINTK=y
    CONFIG_PWM=y
    CONFIG_LOG=y
    CONFIG_LOG_PRINTK=y
    CONFIG_LOG_MODE_IMMEDIATE=y
    CONFIG_PWM_LOG_LEVEL_DBG=y

    The key thing being the CONFIG_LOG_MODE_IMMEDIATE=y

    This slows down the processing enough for you to experience the physical device changing speeds with the different PWM.

  • Hi,

    Did anybody try to use a single PWM (eg. PWM0), and use multiple channels to control multiple PWM outputs?

    I couldn't any relevant answer on how to setup the pinctrl. They always specify pinctrl-0, which is used for the PWM output, but they never expect to use another channel for the same PWM.

    The question is "simple" - how to write pinctr, for a single PWM, using multiple channels?

    Thanks.

Related