PWM + Encoder = Please Help :-)

Post here first, or if you can't find a relevant section!
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: PWM + Encoder = Please Help :-)

Post by ag123 »

GVisser wrote: Sat Jun 13, 2020 5:23 pm Hi @ag123 ,

Thank you for your reply and yes I will look further into the setPWM function.

As for the rest, the PWM is working properly and the reading of the decoder is working properly, the issue is that I cannot find a way to alter the PWM signal with values from the main loop. Once setup is done, the PWM signal runs regardless and I am way too new to programming to know how to fix it :-)

My aim is to be able to starts and stop the PWM output from within the main loop and, use the encoders (or potentiometers) to set and vary the PWM frequency and duty cycle (or preferably, the actual +pulse time). 8-)
my guess is you can try
something like this

Code: Select all

void setPeriod(uint32 microseconds) {
    int CYCLES_PER_MICROSECOND = 72; //i.e. stm32f103 72mhz
    int MAX_RELOAD = 65535;
    uint32 period_cyc = microseconds * CYCLES_PER_MICROSECOND;
    uint16 prescaler = (uint16)(period_cyc / MAX_RELOAD + 1);
    uint16 overflow = (uint16)((period_cyc + (prescaler / 2)) / prescaler);
    MyTim->setPrescaleFactor(prescaler);
    MyTim->setOverflow(overflow);
}

void loop() {
   ...
   microsconds = 100;
   setPeriod(microseconds);

}
the above code is derived from the libmaple core hence it may not be correct in the STM's core case.
review how

Code: Select all

void setPWM(uint32_t channel, PinName pin, uint32_t frequency,  ...
works in STM core.
and duty cycle needs to be separately set in the capture compare registers, cross reference the manuals the hardware timers for more info
the duty cycle is the fraction of that overflow i.e. capture_compare = duty_cycle x overflow / 100
assuming that duty_cycle is a number between 0 .. 100
Last edited by ag123 on Sat Jun 13, 2020 8:33 pm, edited 4 times in total.
GVisser
Posts: 19
Joined: Thu Jun 11, 2020 5:17 pm
Answers: 1
Location: Hatfield, UK

Re: PWM + Encoder = Please Help :-)

Post by GVisser »

ag123 wrote: Sat Jun 13, 2020 8:16 pm my guess is you can try
something like this

Code: Select all

void setPeriod(uint32 microseconds) {
    CYCLES_PER_MICROSECOND = 72; //i.e. stm32f103 72mhz
    uint32 period_cyc = microseconds * CYCLES_PER_MICROSECOND;
    uint16 prescaler = (uint16)(period_cyc / MAX_RELOAD + 1);
    uint16 overflow = (uint16)((period_cyc + (prescaler / 2)) / prescaler);
    MyTim->->setPrescaleFactor(prescaler);
    MyTim->->setOverflow(overflow);
}

void loop() {
   ...
   microsconds = 100;
   setPeriod(microseconds);

}
the above code is derived from the libmaple core hence it may not be correct in the STM's core case.
review how

Code: Select all

void setPWM(uint32_t channel, PinName pin, uint32_t frequency,  ...
works in STM core.
and duty cycle needs to be separately set in the capture compare registers, cross reference the manuals the hardware timers for more info
Thanks @ag123 , I'll give that a try and report back! :-)
fredbox
Posts: 125
Joined: Thu Dec 19, 2019 3:05 am
Answers: 2

Re: PWM + Encoder = Please Help :-)

Post by fredbox »

Before setup include these lines:

Code: Select all

#define pin PA6

TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
HardwareTimer *MyTim = new HardwareTimer(Instance);
Inside setup() include this line:

Code: Select all

MyTim->setPWM(channel, pin, 1000, 50); // 1000 Hertz, 50% dutycycle
Inside loop() include it again but use a variable to set the duty cycle:

Code: Select all

MyTim->setPWM(channel, pin, 1000, count); // 1000 Hertz, count should be between 1 and 99
This sets up a 1000 hertz PWM signal on pin PA6. You can connect an LED/resistor to PA6 and watch the brightness of the LED change as you adjust the encoder.
GVisser
Posts: 19
Joined: Thu Jun 11, 2020 5:17 pm
Answers: 1
Location: Hatfield, UK

Re: PWM + Encoder = Please Help :-)

Post by GVisser »

Morning all :-)

@fredbox @ag123 @ABOSTM THANK YOU all for your help :D

The code is now running as I had originally intended and is included below in case it helps someone else in the future.

Note: I did have to move the 'setPWM' out of the main loop as it was 'resetting' the output on every tick even if there was no change, and thus messing up the output. Only executing this IF there was movement in the encoder solved the problem.

As my plan, for the later version, was to have 5 encoders (all on seperate timers), I am rather going to simplify my life (and probably the code overhead) by moving to linear potentiometers.

Code: Select all

#include <LiquidCrystal.h> // include the LCD library
 
const int rs = PB_10, en = PB_2, d4 = PB_1, d5 = PB_0, d6 = PA_7, d7 = PA_6; //STM32 Pins to which LCD is connected
LiquidCrystal lcd(rs, en, d4, d5, d6, d7); //Initialize the LCD

#define STM32DUINO_CORE
#define PWMPIN PA0 // Main Output
#define RUN_FREQ_ENC_CLK PB8 //Encoder1
#define RUN_FREQ_ENC_DATA PB9
#define RUN_PW_SW PA5
#define RUN_PW_ENC_CLK PB4 //Encoder2
#define RUN_PW_ENC_DATA PB5
#define RUN_PW_SW PA6

#define LEDPIN PC13

volatile uint32_t RUN_FREQ = 100;
volatile uint32_t RUN_PW = 10;
// volatile uint32_t BURST_FREQ = 10;
// volatile uint32_t BURST_PW = 50;
// volatile uint32_t ONESHOT_PW = 50;

// Automatically retrieve TIM instance and channel associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(PWMPIN), PinMap_PWM);
uint32_t channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(PWMPIN), PinMap_PWM));

// Instantiate HardwareTimer object. Thanks to 'new' instantiation, HardwareTimer is not destructed when setup() function is finished.
HardwareTimer *MyTim = new HardwareTimer(Instance);


void run_freq_enc_read(void)
{
  volatile static uint8_t F_ABs = 0;
  F_ABs = (F_ABs << 2) & 0x0f; //left 2 bits now contain the previous AB key read-out;
  F_ABs |= (digitalRead(RUN_FREQ_ENC_CLK) << 1) | digitalRead(RUN_FREQ_ENC_DATA);
  switch (F_ABs)
  {
    case 0x0d:
      RUN_FREQ++;
      break;
    case 0x0e:
      RUN_FREQ--;
      break;
  }
}

void run_pw_enc_read(void)
{
  volatile static uint8_t P_ABs = 0;
  P_ABs = (P_ABs << 2) & 0x0f; //left 2 bits now contain the previous AB key read-out;
  P_ABs |= (digitalRead(RUN_PW_ENC_CLK) << 1) | digitalRead(RUN_PW_ENC_DATA);
  switch (P_ABs)
  {
    case 0x0d:
      RUN_PW++;
      break;
    case 0x0e:
      RUN_PW--;
      break;
  }
}

void led_blink()
{
  static uint8_t led_state = 0;
  led_state = 1 - led_state;
  digitalWrite(LEDPIN, led_state);
}
 
void setup() {
  // LCD setup
  lcd.begin(16, 2);//Defining 16*2 LCD
  lcd.setCursor(0, 0); //LCD Row 0 and Column 0
  lcd.print("Freaky ISSTC"); //Print this Line
  lcd.setCursor(0, 1); //LCD Row 0 and Column 1
  lcd.print("Interrupter V1.0"); //Print this Line
 
  delay(4000); //wait for four secounds
  lcd.clear(); //Clear the screen

  // Encoder setup
  Serial.begin(9600);
  pinMode(RUN_FREQ_ENC_CLK, INPUT);
  pinMode(RUN_FREQ_ENC_DATA, INPUT);
  pinMode(RUN_PW_ENC_CLK, INPUT);
  pinMode(RUN_PW_ENC_DATA, INPUT);
  pinMode(LEDPIN, OUTPUT);

  // Configure and start PWM
  MyTim->setPWM(channel, PWMPIN, RUN_FREQ, RUN_PW); // RUN_FREQ Hertz, RUN_PW dutycycle
}
 
void loop() {
  static uint32_t freq_count;
  static uint32_t freq_prevCount;
  freq_count = RUN_FREQ;
  if (freq_count != freq_prevCount)
  {
    freq_prevCount = freq_count;
    led_blink();
    MyTim->setPWM(channel, PWMPIN, RUN_FREQ, RUN_PW); // RUN_FREQ Hertz, RUN_PW dutycycle
  }
  static uint32_t pw_count;
  static uint32_t pw_prevCount;
  pw_count = RUN_PW;
  if (pw_count != pw_prevCount)
  {
    pw_prevCount = pw_count;
    led_blink();
    MyTim->setPWM(channel, PWMPIN, RUN_FREQ, RUN_PW); // RUN_FREQ Hertz, RUN_PW dutycycle
  }
  
lcd.print("BPS: ");
lcd.print(RUN_FREQ); // display the beats per second
lcd.setCursor(0,1); // move cursor to next line
lcd.print("PW: ");
lcd.print(RUN_PW); // display the pulse width
delay(200);
lcd.clear();

}

#ifdef STM32DUINO_CORE
void HAL_SYSTICK_Callback()
{
  run_freq_enc_read();
  run_pw_enc_read();
}
#endif
pic_7_1.png
pic_7_1.png (23.16 KiB) Viewed 2882 times
Post Reply

Return to “General discussion”