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
  • According to the nRF52840 spec, the same PWM frequency is shared with all the PWM outputs.

    All the outputs of a single PWM peripheral share a frequency. But there are several PWM peripherals in the nRF52840. You should be able to use two PWM peripherals independently.

  • 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);
    		}
    		
    	}
    }
    
Reply
  • 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);
    		}
    		
    	}
    }
    
Children
  • 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.

Related