STM32F103C8 Fast Input Rate Counter

What are you developing?
ozcar
Posts: 149
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32F103C8 Fast Input Rate Counter

Post by ozcar »

I took your last test program and modified it to alter the timer to count external signal, instead of using the internal clock. It did not take much to do that, but it is far from being a generalised solution. It does not make any assumption about which timer will be used, but does assume that the input will be on channel 1 of the timer. That is valid in your case using PA8 as the input, as that has TIM1 channel 1 on it. I changed the loop() processing to show difference in count from the last time, with delay of one second, so it behaves like a (not spectacularly accurate) frequency counter:

Code: Select all

#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION  < 0x01090000)
#error "Due to API change, this sketch is compatible with STM32_CORE_VERSION  >= 0x01090000"
#endif

#define pin  PA8

uint32_t channel;
volatile uint16_t TimerCountOverflow=0;
HardwareTimer *MyTim;
TIM_HandleTypeDef *MyTimHandle; 

void Rollover_IT_callback(void)
{
  TimerCountOverflow++;
}

void setup()
{
  Serial.begin(115200);
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
  channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
  MyTim = new HardwareTimer(Instance);
  MyTim->setMode(channel, TIMER_INPUT_CAPTURE_RISING, pin);
  uint32_t PrescalerFactor = 1;
  MyTim->setPrescaleFactor(PrescalerFactor);
  MyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
  MyTim->attachInterrupt(Rollover_IT_callback);

  MyTimHandle = MyTim->getHandle();                             // HAL handle address 
  MyTimHandle->Instance->SMCR |= TIM_SMCR_TS_0 | TIM_SMCR_TS_2  // Filtered Timer Input 1 (TI1FP1)
           | TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2;  // External Clock Mode 1 

  MyTim->resume();
}

void loop()
{
  static uint32_t lastcount; 
  uint32_t counter;
  uint16_t overflow;
  do
    {
      overflow = TimerCountOverflow; 
      counter = MyTim->getCount();
    }
  while ( overflow != TimerCountOverflow );  // repeat if overflow changed
  counter |= overflow << 16;                 // combine
  
  Serial.println((String)"    counter-lastcount = " + (counter-lastcount) );
  lastcount = counter;
  delay(1000);
}
I tested it on the F401 (which happens to also have TIM1 CH1 on PA8), and it worked OK up to the 10MHz limit of the signal generator I was using:

Code: Select all

14:45:40.417 ->     counter-lastcount = 10000672
14:45:41.420 ->     counter-lastcount = 10000675
14:45:42.423 ->     counter-lastcount = 10000675
14:45:43.425 ->     counter-lastcount = 10000673
14:45:44.428 ->     counter-lastcount = 10000673
14:45:45.431 ->     counter-lastcount = 10000674
You can see from the timestamps that the loop is taking just over 1 second due to the Serial.Println(), which will be part of the reason why the result is a bit out.

It is using a 16-bit overflow counter, but that could be changed if necessary.

Give it a try anyway. If it does not work on F103, I have some of those here I can dig out if necessary.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

Im not 100% sure if this is the right approach, I basically copied this code "https://web.archive.org/web/20190316154 ... =18&t=3804"
and added a second Timer, please correct me if this is wrong

Code: Select all

#include "Streaming.h"


volatile uint32_t counter = 0;  // global counter variable

void counter_increment(void)
{
counter += timer_get_count(TIMER2); // increment counter by value of timer2
timer_set_count(TIMER2, 0); // reset timer2 counter
}
void setup()
{
  Serial.begin(115200);
  Serial << "Serial Started" << endl;
    pinMode(PA0, INPUT);
    pinMode(PA1, INPUT);

    timer_init(TIMER2);
    // as this mode is not supported by the core lib, we have to set up the registers manually.
    (TIMER2->regs).gen->CR1 = 0; // stop the timer
    (TIMER2->regs).gen->CR2 = 0;
    (TIMER2->regs).gen->SMCR = (TIMER_SMCR_ECE | TIMER_SMCR_SMS_GATED | TIMER_SMCR_TS_TI2FP2);
    (TIMER2->regs).gen->DIER = 0;
    (TIMER2->regs).gen->SR = 0;
    (TIMER2->regs).gen->EGR = 0;
    (TIMER2->regs).gen->CCMR1 = TIMER_CCMR1_CC2S_INPUT_TI2;
    (TIMER2->regs).gen->CCMR2 = 0;
    (TIMER2->regs).gen->CCER = 0;
    timer_resume(TIMER2); // start timer
    Serial << "Timer 2 Started" << endl;
    timer_init(TIMER3);
    (TIMER3->regs).gen->CR1 = 0; // stop the timer
    (TIMER3->regs).gen->CR2 = 0;
    //(TIMER3->regs).gen->SMCR = (TIMER_SMCR_ECE | TIMER_SMCR_SMS_GATED | TIMER_SMCR_TS_TI2FP2);
    (TIMER3->regs).gen->DIER = 1;
    (TIMER3->regs).gen->SR = 0;
    (TIMER3->regs).gen->EGR = 0;
    (TIMER3->regs).gen->CCMR1 = TIMER_CCMR1_CC2S_INPUT_TI2;
    (TIMER3->regs).gen->CCMR2 = 0;
    (TIMER3->regs).gen->CCER = 0;
    (TIMER3->regs).gen->PSC = 50; // set prescaler to 100 to achieve a 5ms interval
    timer_attach_interrupt(TIMER3, 512,&counter_increment);
    timer_resume(TIMER3); // start timer


}

void loop ()
{

    Serial << "Counter = " << counter << endl;  // print counter value
    Serial << "timer Count = " << timer_get_count(TIMER2) << endl;

}
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

damn the other stuff doesn't work any more with the board manager I set this up...
Last edited by willywonka on Sat Dec 24, 2022 8:25 am, edited 1 time in total.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

ozcar wrote: Fri Dec 23, 2022 4:02 am I took your last test program and modified it to alter the timer to count external signal, instead of using the internal clock. It did not take much to do that, but it is far from being a generalised solution. It does not make any assumption about which timer will be used, but does assume that the input will be on channel 1 of the timer. That is valid in your case using PA8 as the input, as that has TIM1 channel 1 on it. I changed the loop() processing to show difference in count from the last time, with delay of one second, so it behaves like a (not spectacularly accurate) frequency counter:

Code: Select all

#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION  < 0x01090000)
#error "Due to API change, this sketch is compatible with STM32_CORE_VERSION  >= 0x01090000"
#endif

#define pin  PA8

uint32_t channel;
volatile uint16_t TimerCountOverflow=0;
HardwareTimer *MyTim;
TIM_HandleTypeDef *MyTimHandle; 

void Rollover_IT_callback(void)
{
  TimerCountOverflow++;
}

void setup()
{
  Serial.begin(115200);
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
  channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
  MyTim = new HardwareTimer(Instance);
  MyTim->setMode(channel, TIMER_INPUT_CAPTURE_RISING, pin);
  uint32_t PrescalerFactor = 1;
  MyTim->setPrescaleFactor(PrescalerFactor);
  MyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
  MyTim->attachInterrupt(Rollover_IT_callback);

  MyTimHandle = MyTim->getHandle();                             // HAL handle address 
  MyTimHandle->Instance->SMCR |= TIM_SMCR_TS_0 | TIM_SMCR_TS_2  // Filtered Timer Input 1 (TI1FP1)
           | TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2;  // External Clock Mode 1 

  MyTim->resume();
}

void loop()
{
  static uint32_t lastcount; 
  uint32_t counter;
  uint16_t overflow;
  do
    {
      overflow = TimerCountOverflow; 
      counter = MyTim->getCount();
    }
  while ( overflow != TimerCountOverflow );  // repeat if overflow changed
  counter |= overflow << 16;                 // combine
  
  Serial.println((String)"    counter-lastcount = " + (counter-lastcount) );
  lastcount = counter;
  delay(1000);
}
I tested it on the F401 (which happens to also have TIM1 CH1 on PA8), and it worked OK up to the 10MHz limit of the signal generator I was using:

Code: Select all

14:45:40.417 ->     counter-lastcount = 10000672
14:45:41.420 ->     counter-lastcount = 10000675
14:45:42.423 ->     counter-lastcount = 10000675
14:45:43.425 ->     counter-lastcount = 10000673
14:45:44.428 ->     counter-lastcount = 10000673
14:45:45.431 ->     counter-lastcount = 10000674
You can see from the timestamps that the loop is taking just over 1 second due to the Serial.Println(), which will be part of the reason why the result is a bit out.

It is using a 16-bit overflow counter, but that could be changed if necessary.

Give it a try anyway. If it does not work on F103, I have some of those here I can dig out if necessary.
oh sorry i overlooked your post.
10Mhz is more than enough
<3 thank so much
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

so here is the full code (without Correction of the stepper position)
A Big Thanks @ozcar

Code: Select all

#include <STM32FreeRTOS.h>
#include <Arduino.h>
#include <TMCStepper.h>
#include <Wire.h>
#include <AS5600.h>
#include <STM32FreeRTOS.h>

#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION  < 0x01090000)
#error "Due to API change, this sketch is compatible with STM32_CORE_VERSION  >= 0x01090000"
#endif

#define EX_STEP_PIN         PA8
#define EX_DIR_PIN          PB3
#define EX_EN_PIN           PB13 
#define STEP_PIN            PA11
#define DIR_PIN             PA12
#define EN_PIN              PB9 
#define SW_TX               PB4  
#define SW_RX               PB4  
#define DRIVER_ADDRESS      0b00  
#define R_SENSE             0.11f  
#define SERIAL              Serial

uint32_t channel;
volatile uint16_t TimerCountOverflow=0;
volatile int32_t counter = 0;

float degAngle;
int quadrantNumber, previousquadrantNumber; 
float numberofTurns = 0; 
float correctedAngle = 0; 
float startAngle = 0; 
float startPoint = 0; 
bool started = false; 
float totalAngle = 0; 
float FullStepsperRev = 200; 
int startStepsC = 0;

uint16_t RMS_Current = 600;
uint8_t MStepping = 16;        
float RawToStep = 4096/(FullStepsperRev*MStepping);
int totalAngleInt = 0;

HardwareTimer *MyTim;
HardwareTimer *MyTim2;
TIM_HandleTypeDef *MyTimHandle; 

TMC2209Stepper driver(SW_RX, SW_TX, R_SENSE, DRIVER_ADDRESS);
AS5600 as5600;

void Rollover_IT_callback(void)
{
  TimerCountOverflow++;
}

void ISR_callback(void)
{
  if (digitalReadFast(digitalPinToPinName(EX_DIR_PIN)) == HIGH)
  {
    counter = counter + MyTim->getCount(); // store the current count value in the count variable
  }
  else {
  counter = counter - MyTim->getCount(); // store the current count value in the count variable
  }
  MyTim->setCount(0);
}

void setup()
{
  pinMode(EX_DIR_PIN, INPUT);
  Serial.begin(115200);
  SERIAL.begin(115200);
  Wire.begin();

  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(EX_STEP_PIN), PinMap_PWM);
  channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(EX_STEP_PIN), PinMap_PWM));
  MyTim = new HardwareTimer(Instance);
  MyTim->setMode(channel, TIMER_INPUT_CAPTURE_RISING, EX_STEP_PIN);
  MyTim->setPrescaleFactor(1);
  MyTim->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
  MyTim->attachInterrupt(Rollover_IT_callback);
  
  MyTimHandle = MyTim->getHandle();                             // HAL handle address 
  MyTimHandle->Instance->SMCR |= TIM_SMCR_TS_0 | TIM_SMCR_TS_2  // Filtered Timer Input 1 (TI1FP1)
           | TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2;  // External Clock Mode 1 

  MyTim->resume();
  Serial.println("MyTim Started");
  TIM_HandleTypeDef *MyTim2Handle;
  MyTim2 = new HardwareTimer(TIM2);
  MyTim2->setPrescaleFactor(48000); // Set prescaler to 48000 to achieve a period of 5 ms
  MyTim2->setOverflow(500); // Set overflow to 1000 to achieve a period of 5 ms
  MyTim2->attachInterrupt(ISR_callback); // Attach the ISR callback function
  MyTim2Handle = MyTim2->getHandle(); // Get the HAL handle for the new timer
  MyTim2->resume();

  xTaskCreate(
    EnableDriver
    ,  (const portCHAR *)"EnableDriver"  
    ,  128  
    ,  NULL
    ,  -1  
    ,  NULL );
  xTaskCreate(
  PrintCounter
  ,  (const portCHAR *)"EnableDriver"  
  ,  128  
  ,  NULL
  ,  0  
  ,  NULL );
    xTaskCreate(
    correctAngle
    ,  (const portCHAR *) "correctAngle"
    ,  128  
    ,  NULL
    ,  2  
    ,  NULL );
  xTaskCreate(
    checkQuadrant
    ,  (const portCHAR *) "checkQuadrant"
    ,  128  
    ,  NULL
    ,  1  
    ,  NULL );
 xTaskCreate(
    CorrectStartPos
    ,  (const portCHAR *) "CorrectStartPos"
    ,  128  
    ,  NULL
    ,  0 
    ,  NULL );
  
  vTaskStartScheduler();
}
void EnableDriver(void  *pvParameters __attribute__((unused))){
  driver.beginSerial(11520);  
  driver.pdn_disable(false);    
  driver.I_scale_analog(false); 
  driver.rms_current(600, 0.5); 
  driver.mstep_reg_select(true);
  driver.mres(8);               
  driver.microsteps(MStepping); 
  driver.toff(2);               
  digitalWrite(EN_PIN, LOW);    
  driver.en_spreadCycle(true);  
  driver.pwm_autoscale(true);   

  SERIAL.println("begin driver conf");
  SERIAL.println("driver Status:");
  SERIAL.println(driver.DRV_STATUS());
  SERIAL.println("GCONF:");
  SERIAL.println(driver.GCONF());
  SERIAL.println("GSTAT:");
  SERIAL.println(driver.GSTAT());
  SERIAL.println("drv_err:");
  SERIAL.println(driver.drv_err());
  SERIAL.println("microsteps:");
  SERIAL.println(driver.microsteps());
  SERIAL.println("rms_current:");
  SERIAL.println(driver.rms_current());
  Wire.begin();
  if (as5600.detectMagnet() == 0){
    Serial.println(" NO Magnet ");
  }
  if (as5600.detectMagnet() == 1){
    Serial.println("Magnet detected ");
  }
  vTaskSuspend(NULL);
}
void correctAngle(void  *pvParameters __attribute__((unused))){
  for (;;) // A Task shall never return or exit.
  {
    degAngle = as5600.rawAngle();
    correctedAngle = degAngle - startAngle; 
    if (correctedAngle < 0){
      correctedAngle = correctedAngle + 4096; 
    }
    else {}
    /*TaskHandle_t taskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(taskHandle);
    Serial.println(String("stackHighWaterMark of : correctAngle ")+stackHighWaterMark);*/
    vTaskDelay(12);
  }
}
void checkQuadrant(void  *pvParameters __attribute__((unused))){
  for (;;) // A Task shall never return or exit.
  {
    if (correctedAngle >= 0 && correctedAngle <= 4096/3){quadrantNumber = 1; }    
    if (correctedAngle > 4096/3 && correctedAngle <= 4096/3*2){quadrantNumber = 2;}    
    if (correctedAngle > 4096/3*2 && correctedAngle <= 4096){quadrantNumber = 3;}    
    if (quadrantNumber != previousquadrantNumber) //if we changed quadrant
    {
    if (quadrantNumber == 1 && previousquadrantNumber == 3){numberofTurns++;} // 4 --> 1 transition: CW rotation    
      if (quadrantNumber == 3 && previousquadrantNumber == 1){ numberofTurns--;} // 1 --> 4 transition: CCW rotation      
      //this could be done between every quadrants so one can count every 1/4th of transition
      previousquadrantNumber = quadrantNumber;  //update to the current quadrant      
      }

    totalAngle = (numberofTurns * 4096/RawToStep) + (correctedAngle/RawToStep); 
    totalAngle = totalAngle + startPoint;
    totalAngleInt = (int)totalAngle;
    while (started == false){
      startPoint = startPoint - totalAngle;
      started = true;
    }
    /*TaskHandle_t taskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(taskHandle);
    Serial.println(String("stackHighWaterMark of : checkQuadrant ")+stackHighWaterMark);*/
    vTaskDelay(10);
  }
}
void CorrectStartPos(void  *pvParameters __attribute__((unused))){
    vTaskDelay(1000);
    startStepsC = totalAngleInt;
    vTaskSuspend(NULL);
}
void PrintCounter(void *pvParameters __attribute__((unused))){
  for (;;)
  {
  Serial.println(String("Total Steps Done: ") + (totalAngleInt-startStepsC)+(String)"    counter = " + (counter) );
  vTaskDelay(100);
  }
}
void loop()
{}
Last edited by willywonka on Sat Dec 24, 2022 8:26 am, edited 1 time in total.
ozcar
Posts: 149
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32F103C8 Fast Input Rate Counter

Post by ozcar »

I notice you are not using the overflow counter there. Maybe that is OK because you periodically reset the TIM1 counter in the TIM2 ISR, but then if you are not using the overflow counter you might as well take out the TIM1 overflow ISR. I'm also not sure how often you really want to call the TIM2 ISR, because you mention 5ms in the comments, but actually set it up for way longer than that.

Presumably there would be some setup and hold constraints on the step and direction signals, but still what you have now does worry me a bit. I am concerned that you could sometimes lose some counts, because there does not appear to be anything that synchronises TIM2 to the step and direction signals.

Another way would be to forget about using the second timer, and instead just check every time the direction pin level changes. That way the ISR is at least synchronised with the direction signal, and it would potentially have lower overhead. You could do that by dropping the TIM2 setup and instead use something like:

Code: Select all

  pinMode(EX_DIR_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(EX_DIR_PIN), ISR_callback, CHANGE);
If you did go that way, you might have to retain the overflow ISR and make use of the overflow counter though, in case there are more than 65k steps without a change of direction.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

Yes, you are completely right, I was a bit hasty yesterday and didn't think things through properly.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

Hello everyone, Merry Christmas and thank you so much for your help and especially for your patience with me. I have revised the code with your recommendations and hope it fits now. My test with some random high speed movements looked promising, the positions at the and was always correct.

Code: Select all

#include <STM32FreeRTOS.h> // includes the STM32 FreeRTOS library
#include <Arduino.h> // includes the Arduino library
#include <TMCStepper.h> // includes the TMCStepper library for control of TMC2209 stepper motor drivers
#include <Wire.h> // includes the Wire library for I2C communication
#include <AS5600.h> // includes the AS5600 library for control of AS5600 magnetic encoder
#include <STM32FreeRTOS.h> // includes the STM32 FreeRTOS library

// check if the STM32 core version is compatible with this sketch
#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION < 0x01090000)
#error "Due to API change, this sketch is compatible with STM32_CORE_VERSION >= 0x01090000"
#endif

// define pin constants
#define EX_STEP_PIN PA8 // external step pin
#define EX_DIR_PIN PB3 // external direction pin
#define EX_EN_PIN PB13 // external enable pin
#define STEP_PIN PA11 // step pin
#define DIR_PIN PA12 // direction pin
#define EN_PIN PB9 // enable pin
#define SW_TX PB4 // serial wire transmit pin
#define SW_RX PB4 // serial wire receive pin
#define DRIVER_ADDRESS 0b00 // I2C address of TMC2209 stepper motor driver
#define R_SENSE 0.11f // sense resistor value
#define SERIAL Serial // serial communication object

// declare variables
uint32_t channel; // channel of the timer
volatile uint16_t TimerCountOverflow = 0; // overflow counter for timer
volatile int32_t counter = 0; // counter for external steps

float degAngle; // angle in degrees
int quadrantNumber, previousquadrantNumber; // quadrant number and previous quadrant number
float numberofTurns = 0; // number of turns
float correctedAngle = 0; // corrected angle
float startAngle = 0; // start angle
float totalAngle = 0; // total angle
float FullStepsperRev = 200; // full steps per revolution of the motor
uint16_t RMS_Current = 600; // RMS current for the motor
uint8_t MStepping = 16; // microstepping for the motor
float RawToStep = 4096/(FullStepsperRev*MStepping); // conversion factor for raw encoder values to steps
int totalAngleInt = 0; // total angle as an integer
int8_t Direction = 1; // direction of rotation

HardwareTimer *MyTim; // hardware timer object
TIM_HandleTypeDef *MyTimHandle; // timer handle

// create objects for TMC2209 stepper motor driver and AS5600 magnetic encoder
TMC2209Stepper driver(SW_RX, SW_TX, R_SENSE, DRIVER_ADDRESS);
AS5600 as5600; // object for AS5600 magnetic encoder

// callback function for timer rollover interrupt
void Rollover_IT_callback(void){
  int32_t tmp =0; 
  tmp |= 1 << 16; // shift the overflow counter to the correct position
  counter += (tmp * Direction ); // store the current count value in the counter variable, taking into account the direction of rotation
  
  //TimerCountOverflow ++; // increment overflow counter
  //int32_t tmp = MyTim->getCount(); // get the current count value of the timer
  //tmp |= TimerCountOverflow << 16; // shift the overflow counter to the correct position
  //counter += (tmp * Direction ); // store the current count value in the counter variable, taking into account the direction of rotation
  //TimerCountOverflow --; // decrement the overflow counter
}

// interrupt service routine for direction change
void ISR_Dir_Change(void){
  int tmp = MyTim->getCount(); // get the current count value of the timer
  if (digitalReadFast(digitalPinToPinName(EX_DIR_PIN)) == HIGH){ // if the direction pin is high
    MyTim->setCount(0); // reset the timer count
    counter += (tmp * Direction ); // store the current count value in the counter variable, taking into account the direction of rotation
    Direction = 1; // set the direction to clockwise
  }
  if (digitalReadFast(digitalPinToPinName(EX_DIR_PIN)) == LOW){ // if the direction pin is low
    MyTim->setCount(0); // reset the timer count
    counter += (tmp * Direction ); // store the current count value in the counter variable, taking into account the direction of rotation
    Direction = -1; // set the direction to counterclockwise
  }
}

// setup function
void setup(){
  pinMode(EX_DIR_PIN, INPUT_PULLUP); // set the external direction pin as an input with a pull-up resistor
  Serial.begin(115200); // start serial communication at 115200 baud
  SERIAL.begin(115200); // start serial communication at 115200 baud
  Wire.begin(); // start I2C communication

  // configure the timer for input capture of external steps
  TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(EX_STEP_PIN), PinMap_PWM);
  channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(EX_STEP_PIN), PinMap_PWM));
  MyTim = new HardwareTimer(Instance);
  MyTim->setMode(channel, TIMER_INPUT_CAPTURE_RISING, EX_STEP_PIN);
  MyTim->setPrescaleFactor(1);
  MyTim->setOverflow(0x10000); // set the overflow value to the maximum to allow for detection of rising edges and avoid timer rollover
  MyTim->attachInterrupt(Rollover_IT_callback); // attach the rollover callback function to the timer interrupt
  attachInterrupt(digitalPinToInterrupt(PB3), ISR_Dir_Change, CHANGE); // attach the direction change ISR to the external direction pin interrupt

  // configure the timer for external clock mode
  MyTimHandle = MyTim->getHandle();                             // HAL handle address 
  MyTimHandle->Instance->SMCR |= TIM_SMCR_TS_0 | TIM_SMCR_TS_2  // Filtered Timer Input 1 (TI1FP1)
           | TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2;  // External Clock Mode 1 
  MyTim->resume(); // Resume Timer MyTim
  // create tasks for enabling the driver, printing the counter, correcting the angle, checking the quadrant, and correcting the start position
  xTaskCreate(
    EnableDriver
    , (const portCHAR *)"EnableDriver"
    , 128
    , NULL
    , -1
    , NULL ); // task to enable the driver
  xTaskCreate(
    PrintCounter
    , (const portCHAR *)"PrintCounter"
    , 128
    , NULL
    , 0
    , NULL ); // task to print the counter value
  xTaskCreate(
    correctAngle
    , (const portCHAR *) "correctAngle"
    , 128
    , NULL
    , 2
    , NULL ); // task to correct the angle measurement
  xTaskCreate(
    checkQuadrant
    , (const portCHAR *) "checkQuadrant"
    , 128
    , NULL
    , 1
    , NULL ); // task to check the quadrant of the angle measurement
  vTaskStartScheduler(); // start the task scheduler
}

void EnableDriver(void  *pvParameters __attribute__((unused))){
  // Initialize the driver and set various configurations
  driver.beginSerial(11520);  
  driver.pdn_disable(false);                        // Enable Driver 
  driver.I_scale_analog(false);                     // Disable analog current scaling.
  driver.rms_current(RMS_Current, (RMS_Current/2)); // set RMS Current according to the defined Val and use half of it as hold current 
  driver.mstep_reg_select(true);                    // Selects the microstep register (default) 
  //driver.mres(8);                                 // Set Mres to 8  should be ignored when using .microsteps()
  driver.microsteps(MStepping);                     // Set Microstepping to defined Var 
  driver.toff(2);                                   // Sets the driver's off-time. Time duration of the Mosfet between Microsteps: higher more torque and accuracy but more power drawn and hotter Motor    
  digitalWrite(EN_PIN, LOW);                        // Enable Driver via EN Pin 
  driver.en_spreadCycle(true);                      // Enables or disables spreadCycle mode.
  driver.pwm_autoscale(true);                       // Enables or disables PWM autoscale. needed for Silentstepstick
  // Print Driver Settings 
  SERIAL.println("begin driver conf");
  SERIAL.println("driver Status:");
  SERIAL.println(driver.DRV_STATUS());
  SERIAL.println("GCONF:");
  SERIAL.println(driver.GCONF());
  SERIAL.println("GSTAT:");
  SERIAL.println(driver.GSTAT());
  SERIAL.println("drv_err:");
  SERIAL.println(driver.drv_err());
  SERIAL.println("microsteps:");
  SERIAL.println(driver.microsteps());
  SERIAL.println("rms_current:");
  SERIAL.println(driver.rms_current());
  Wire.begin();                                     // Start I2C Communication for AS5600 Encoder 
  while (as5600.detectMagnet() == 0){               // check if Magnet is present 
    Serial.println(" NO Magnet ");
  }
  if (as5600.detectMagnet() == 1){                  // check if Magnet is present 
    Serial.println("Magnet detected ");
    startAngle = as5600.rawAngle();
  }
    /*TaskHandle_t taskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(taskHandle);
    Serial.println(String("stackHighWaterMark of : EnableDriver ")+stackHighWaterMark);*/
  vTaskSuspend(NULL);                               // 
}
void correctAngle(void  *pvParameters __attribute__((unused))){
  for (;;) // A Task shall never return or exit.
  {
    degAngle = as5600.rawAngle();
    correctedAngle = degAngle - startAngle; 
    if (correctedAngle < 0){
      correctedAngle = correctedAngle + 4096; 
    }
    else {}
    vTaskDelay(12);
  }
}
void checkQuadrant(void  *pvParameters __attribute__((unused))){
  for (;;) // A Task shall never return or exit.
  {
    if (correctedAngle >= 0 && correctedAngle <= 4096/3){quadrantNumber = 1; }    
    if (correctedAngle > 4096/3 && correctedAngle <= 4096/3*2){quadrantNumber = 2;}    
    if (correctedAngle > 4096/3*2 && correctedAngle <= 4096){quadrantNumber = 3;}    
    if (quadrantNumber != previousquadrantNumber) //if we changed quadrant
    {
    if (quadrantNumber == 1 && previousquadrantNumber == 3){numberofTurns++;} // 4 --> 1 transition: CW rotation    
      if (quadrantNumber == 3 && previousquadrantNumber == 1){ numberofTurns--;} // 1 --> 4 transition: CCW rotation      
      //this could be done between every quadrants so one can count every 1/4th of transition
      previousquadrantNumber = quadrantNumber;  //update to the current quadrant      
      }
    totalAngle = (numberofTurns * 4096/RawToStep) + (correctedAngle/RawToStep); 
    vTaskDelay(10);
  }
}


void PrintCounter(void *pvParameters __attribute__((unused))){
  int lastCounter = 0; // initialize lastCounter to 
  for (;;){

    int CurrentCount  = MyTim->getCount();
    float LastPos       = (counter/FullStepsperRev/MStepping*40 );
    float CurrentPos    = ((counter + (Direction*CurrentCount))/FullStepsperRev/MStepping*40);
    Serial.println(
    String("    counter  = ")           + counter           + 
    String("    Last Position = ")      + LastPos           + 
    String("    Current getCount = ")   + MyTim->getCount() +
    String("    Current Count = ")      + CurrentCount      +
    String("    Current Position = ")   + CurrentPos        +
    String("    Direction = ")          + Direction         +    
    String("    totalAngleInt: ")       + ((int)totalAngle)           
    );

  vTaskDelay(100);
  }
}

void loop()
{}
ozcar
Posts: 149
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32F103C8 Fast Input Rate Counter

Post by ozcar »

Good to hear that you are making progress. A few comments:

Like "counter", "Direction" should be declared as volatile.

It may be reasonable to assume, or maybe you know for sure, that there will not be a step pulse very close to a change of direction, so it might be "safe" to reset the timer counter in the direction change ISR without losing a count. However, in the PrintCounter() code, or anywhere else where you need to access the counter, be careful not to assume that "Direction", "counter", and the timer counter as returned by MyTim->getCount() will remain the same as your code executes. If you refer to them multiple times, you could get a different value each time, and they may not be in a consistent state. Have a look at the code I posted a few days ago, where I at least made an attempt to handle that sort of problem.

Now that you have added some comments to say what the pins are used for, I notice that there is also an "enable" line. Is it OK to ignore that as you are doing now?

FYI the hardware timer can count up or down, depending on a flag in the CR1 register, and you could probably make use of that. I'm not going to suggest that you start all over again though.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

Hey :)
Like "counter", "Direction" should be declared as volatile.
the counter was already declared as "volatile", I changed the direction to volatile just now.

It's probably better not to reset the "counter" when the direction changes, but it probably doesn't matter for the current use case. The 3D printing software also reduces the speed at the end of a movement, so it's probably not critical, but for other use cases it would probably be better not to reset the counter. The overflow should also not occur too frequently, unless you have a huge print bed or microsteps higher than 32, typically it is set to 16. The enable pin can probably be omitted, as the stepper can also be activated via UART, but I would have to check more carefully whether this works reliably. I left it like this for testing. Increasing or decreasing the counter through the CR1 register flag could be useful, but I would have to be careful if the values become negative (e.g. at homing).

The function PrintCounter() is currently only used for debugging and has no actual use and will probably be replaced at the end by a display output or completely omitted.

Currently I am still having problems with the encoder, but that is probably due to hardware issues, either the magnet is not properly positioned or the resolution of the encoder is not high enough. I have tested two As5600 that work differently well. It reliably detects full rotations, but for example it has problems with half a rotation, then it suddenly lacks values or it has too many (+-20).
Maybe I need to create a lookup table, since the encoder read errors are pretty constant.
Post Reply

Return to “Projects”