Two individual pwm duty cycle control

Hello, I am fairly new to programing with nRF52832, Segger Embedded Studio and SDK52 V17.0.2.

I am an EE student, and the majorly of my experience comes from Arduino.

I am currently working on creating a small car that will travel very accurate distances. In order to achieve this I will be using 2 DC motors, 1 with a quadrature encoder used for moving back and forth, the other will not have an encoder and will be only for steering. The way to control the car will be by: 2 pin inputs used in a counter (from -5 to 5) for steering left or right and  2 pin inputs that will increase or decrease a counter (from -infinity, infinity) used for setting a travel target, then with a PID the motor will eventually reach the target, thus traveling the set distance. I have implemented the GPIOTE into my code in order to be able to read the quadrature encoder as an interrupt and it works so far. Also in order to control the DC motor H bridge module (motor controller)I have used the Low Power PWM example that allows you to dictate the duty cycle through a variable (m_duty). 

At this point I am stuck in two aspects of my project.

1. Is it possible to have 2 Low Power PWMs in the same code?

I ask because I have looked at other PWM examples in the SDK52 peripheral examples and I have not found an example in which you can control the duty cycle as a variable (m_duty) in the same manner as the Low Power PWM example. If there is an other way to implement the duty cycle control in a variable for the main code, could you please post a code example because I have not been able to decipher the functions (how to code them) in the library descriptions. Also the second PWM duty cycle controlled signal will be used for the DC motor in charge of steering.  

2. In order to get my PID controller working is it best to work with the RTC or with the Driver Timers in order to calculate the derivative and the integrals?   

The timer will be used in the following manner: the elapsed time from the start (turning on the processor) will be recorded in a variable, in the second code iteration (second code loop) the previously stored time value will be recorded in another variable. During the next iterations the current time value will be subtracted from the previous time values and with this value a derivative and an integral can be calculated. the exact accuracy in microseconds is not needed, for example in Arduino (where I have already made this project) the elapsed time for every loop iteration is approximately 7,400 microseconds.

Thank you in advance for answering my question.

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <math.h>
#include "nrf_delay.h"
#include "nrf_gpio.h"
#include "boards.h"
#include "app_error.h"
#include "sdk_errors.h"
#include "app_timer.h"
#include "nrf_drv_clock.h"
#include "app_util_platform.h"
#include "low_power_pwm.h"
#include "nordic_common.h"
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_timer.h"
#include "bsp.h"#define LED 18
#define ENCA 13
#define ENCB 14
#define Positive 16
#define Negative 17
#define MotDir1 1
#define MotDir2 2
#define ex_led PIN_MASK(8) //this will be used to drive the PWM for tge driver motor
#define DEBOUNCE_MS 70
#define Front 3
#define Back 4
//timer variables
   //const nrf_drv_timer_t TIMER_LED = NRF_DRV_TIMER_INSTANCE(0);
  //int prevT = 0;
//Interrupt variables
volatile int posi = 0;
int poss = 0;
float eprev = 0;
float eintegral = 0;
int PWMvalue = 0;
//might change this to a local variable
int target = 0.0;

//#####################  Begin Interrupt Code ############
void input_pin_handle(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
    if(nrf_gpio_pin_read(ENCB))
    {
      posi++;
    }
    else
    {
      posi--;
    }
}void gpio_init()
{
    nrf_gpio_cfg_input(Front, NRF_GPIO_PIN_PULLUP);
    nrf_gpio_cfg_input(Back, NRF_GPIO_PIN_PULLUP);
    nrf_gpio_cfg_input(ENCB, NRF_GPIO_PIN_PULLDOWN);
    nrf_gpio_cfg_output(Positive);
//    nrf_gpio_cfg_output(Negative);
    nrf_gpio_cfg_output(MotDir1);
    nrf_gpio_cfg_output(MotDir2);
    ret_code_t err_code; // hold error value    err_code = nrf_drv_gpiote_init(); // Initialize the GPIOTE
    APP_ERROR_CHECK(err_code); // check for the errors    nrf_gpio_cfg_output(LED); // Initialize the Led
    nrf_gpio_pin_set(LED); // turn off the led    nrf_drv_gpiote_in_config_t  in_config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(true); // Falling edge interrupt detection
    in_config.pull = NRF_GPIO_PIN_PULLDOWN;    err_code = nrf_drv_gpiote_in_init(ENCA, &in_config, input_pin_handle); // Initialize the interrupt pin
    APP_ERROR_CHECK(err_code);    nrf_drv_gpiote_in_event_enable(ENCA, true); // Enable the interrupt events
}

//############################ Start Low Power PWM ########
// create a low power pwm handle which will point to the lfclk(low frequency clock) pwm which is 32.768Khz
static low_power_pwm_t low_power_pwm_0;// Create a variable which will be used to update the duty of the pwm
static volatile uint8_t m_duty = 0;// we have to pass the pin mask to the pwm configurations so we use the followung built in function
// which automatically calculates the pin mask and assigns it to the defined constant.// this handler will be called whenever an intrrupt is occurred
// the pwm will generate interrupts after each pwm wave time period
// we have set this time period to 100 ticks. More info about this in configurations done below.
static void pwm_handler(void * p_context)
{
    uint32_t err_code;
    // we can call this function to change the duty cyle values
// so here we will pass the m_duty and change the values of m_duty variables value in the main function
// this is just to demonstrate the working of interrupts, you can use these interrupts in any way you like
    err_code = low_power_pwm_duty_set(&low_power_pwm_0, m_duty);
    APP_ERROR_CHECK(err_code);
}// create a function which will initialize the pwm with all the configurations
static void pwm_init(void)
{
// a varibale to hold the error values
    uint32_t err_code; // create a struct to hold the configurations
    low_power_pwm_config_t low_power_pwm_config; // Create an application timer handle, if you don't know about application timers watch my application timers tutorial
    APP_TIMER_DEF(lpp_timer_0);    // active high = true means the duty will be changed for logic high, for. e.g duty set to 70 percent means
// the logic high state will remain for 70% of the time period of each pwm wave
// if active_high is set to false then duty will be set according to logic low level. so 70 percent duty means logic low
//  state for 70 percent of the time period
    low_power_pwm_config.active_high   = true;

// 100 means 100 ticks, it uses 32.768 Khz clock which means the minimum tick is 30.51 Micro seconds approx. so 100 ticks = 100 x 30.51 = 3051 Micro
// seconds is the time period of the pwm wave.
    low_power_pwm_config.period        = 100; // this would be the total time period of the wave
    low_power_pwm_config.bit_mask      = ex_led; // pin mask should be passed, so use the function as told above.
    low_power_pwm_config.p_timer_id    = &lpp_timer_0; // pass the application timer handle
    low_power_pwm_config.p_port        = NRF_GPIO; // for nRF52832 use this constant, for nRF52840 use the respective pin port like port 0 or port 1// function to intialize the pwm , pass the handle  of pwm, pass the pwm configurations and pass the handler for the interrupts
    err_code = low_power_pwm_init(&low_power_pwm_0, &low_power_pwm_config, pwm_handler);
    APP_ERROR_CHECK(err_code); // check if any error occured during initialization// after initialization set the duty so that the initial duty is a known duty cycle instead of unknown initial duty.
    err_code = low_power_pwm_duty_set(&low_power_pwm_0, 20);
    APP_ERROR_CHECK(err_code); // check if any error occured during initialization// This function is important so don't miss it otherwise the pwm will not work
// start the pwm by passing the handle and pins bitmask, here we will use the default handle and use the dot operator to access bitmask
// this was configured once we pass the configurations so its same as we have passed for the ex_led in our case so don't get confused
    err_code = low_power_pwm_start(&low_power_pwm_0, low_power_pwm_0.bit_mask);
    APP_ERROR_CHECK(err_code); // check if any error occured during initialization}// we need to initialize the clock if we are not using a softdevice
// in this tutorial we are not using a soft device so we will initialize the lfclk clock
static void lfclk_init(void)
{
    uint32_t err_code;
    err_code = nrf_drv_clock_init();
    APP_ERROR_CHECK(err_code);    nrf_drv_clock_lfclk_request(NULL);
}
void control()
{
        if(!nrf_gpio_pin_read(Front))
        {
          target = target + 25;
        }
        if(!nrf_gpio_pin_read(Back))
        {
          target = target - 25;
        }
}
void motor(int error)
{
 if (error <= 0)
      {
      nrf_gpio_pin_write(Positive, 1);
      }
      else
      {
       nrf_gpio_pin_write(Positive, 0);
      }
}
void PWMincrements()
{
    float carrier = PWMvalue;
    if (carrier >= 95)
    {
    carrier = 95;
    }
    carrier = roundf(carrier);

    PWMvalue = carrier;
    m_duty = carrier; //setting the PWM duty cicle in increments of 10}int main(void)
{

//PWM Begin
    uint32_t err_code;
    gpio_init();//general io function
    lfclk_init();// call the lfclk initialization function
  err_code = app_timer_init();
  APP_ERROR_CHECK(err_code);
  pwm_init();
  m_duty = 0;  
  while (true)
      { 

        float kp = 1;
        float kd = 0.0;
        float ki = 0.0;      
        // ERROR
        int Error = target - posi;
        //Calling motor control function
        motor(Error);
        // Control Signals
        float Control = fabsf(kp*Error);
        PWMvalue = roundf(Control);
        //Call PWM
        PWMincrements();

        printf("Target: %d Position: %d Error: %d PWMvalue: %d \n", target, posi, Error, PWMvalue);
        nrf_delay_us(500);
        }
    }

Parents
  • Hey Joel! Great, then welcome to the world of Nordic and our products!

    Sounds like you have a very interesting project on your hands.

    1. Is it possible to have 2 Low Power PWMs in the same code?

    Yes I think so. I will have to get back to you on this though.

    2. In order to get my PID controller working is it best to work with the RTC or with the Driver Timers in order to calculate the derivative and the integrals?   

    Well if using low power is important you should consider that RTC is running through the Low frequency clock. The LF isn't using much current, about 1uA.

    When using TIMER, SPI/TWI, RADIO etc, the HF clock is used in order to get a better "precision". Though both HFXO and LFXO have an accuracy in the same ball park considering drift in accuracy over time (20-40ppm), you can get a higher resolution using the HF clock. However, the HF uses about 1 mA (a 1000x more than the LF).

    So what you you want to use it for would depend on how accurate/precise you want to be with your sampling. In the context of a PID it doesn't sound like you need to be that precise.

    Best regards,

    Elfving

  • Hello again!

    When it comes to questions one, I don't know if low power PWM is the best option here. It is mainly meant to be used with LEDs. When controlling a motor, you are dependent on a rather high PWM frequency in order to avoid noticeable noise etc. The low power PWM uses a 32 clock, giving you limited frequency and rather bad accuracy.

    So I recommend sticking to the regular PWM library. For an example of how multiple channels can be implemented, take a look .zip shared by Torbjørn here in this thread.

    Best regards,

    Elfving

Reply Children
No Data
Related