STM32F103C8 Fast Input Rate Counter

What are you developing?
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

STM32F103C8 Fast Input Rate Counter

Post by willywonka »

Hi I'm pretty new to the STM32 boards.

I'm currently working on a closed loop stepper, based on a AS5600.
I need to read external input signals at high frequency and have no clue how to get it running.
if I use interrupts the MCU does not count accuratly if the speed of the external board increases.
I tried an example with a modified PWM counter but if i add the Code for the encoder the counter reads are not fast enough.

Code: Select all

#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 EN_PIN              PB9   
#define DIR_PIN             PA12   
#define STEP_PIN            PA11   
#define SW_TX               PB4  
#define SW_RX               PB4  
#define DRIVER_ADDRESS      0b00  
#define EX_EN_PIN           PB13  
#define R_SENSE             0.11f  
#define SERIAL              Serial
#define EX_DIR_PIN          PB3
#define REGISTER_MEM_LOCATION 0x20000200

uint32_t channel;
volatile int Counter;
uint32_t input_freq = 0;

int32_t absolutAngle = 0;
int magnetStatus = 0; 
int lowbyte;
word highbyte; 
int rawAngle; 
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;
TMC2209Stepper driver(SW_RX, SW_TX, R_SENSE, DRIVER_ADDRESS);
AS5600 as5600;



//SemaphoreHandle_t mutexHandle;
SemaphoreHandle_t xSemaphore;

void ReadRawAngle( void *pvParameters );
void correctAngle( void *pvParameters );
void checkQuadrant( void *pvParameters );
void PrintOutput( void *pvParameters );

void InputCapture_IT_callback(void)
{
/*
    // Increment the counter using direct register access
    __asm__("ldr r0, =Counter\n\t"  // Load the address of "counter" into R0
            "ldr r1, [r0]\n\t"     // Load the value of "counter" into R1
            "add r1, r1, #1\n\t"   // Increment the value of R1 by 1
            "str r1, [r0]\n\t");   // Store the value of R1 back into "counter"
}
// Enter critical section
/*///taskENTER_CRITICAL();
if (xSemaphoreTakeFromISR(xSemaphore, NULL) == pdTRUE){
// Mutex acquired, we can access the "Counter" variable safely
if (digitalReadFast(digitalPinToPinName(EX_DIR_PIN)) == HIGH){(*((volatile uint32_t *)REGISTER_MEM_LOCATION)) ++;}
if (digitalReadFast(digitalPinToPinName(EX_DIR_PIN)) == LOW){(*((volatile uint32_t *)REGISTER_MEM_LOCATION)) --;}
xSemaphoreGiveFromISR(xSemaphore, NULL);
//taskEXIT_CRITICAL();
}
}


void setup()
{
  Serial.begin(115200);
  SERIAL.begin(115200);
  SERIAL.println("Serial Started ");
  Wire.begin();
  pinMode(EN_PIN, OUTPUT);
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(EX_DIR_PIN, INPUT);
  xSemaphore = xSemaphoreCreateMutex();
  *((volatile uint32_t *)REGISTER_MEM_LOCATION) = 0;
  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);


  uint32_t PrescalerFactor = 1;
  MyTim->setPrescaleFactor(PrescalerFactor);
  MyTim->setOverflow(0x100000); 
  MyTim->attachInterrupt(channel, InputCapture_IT_callback);

  MyTim->resume();
  //input_freq = MyTim->getTimerClkFreq() / MyTim->getPrescaleFactor();
  xTaskCreate(
    EnableDriver
    ,  (const portCHAR *)"EnableDriver"  
    ,  128  
    ,  NULL
    ,  -1  
    ,  NULL );
  /*xTaskCreate(
    ReadRawAngle
    ,  (const portCHAR *)"ReadRawAngle"  
    ,  56  
    ,  NULL
    ,  2  
    ,  NULL );
*/
  xTaskCreate(
    correctAngle
    ,  (const portCHAR *) "correctAngle"
    ,  128  
    ,  NULL
    ,  2  
    ,  NULL );
  xTaskCreate(
    checkQuadrant
    ,  (const portCHAR *) "checkQuadrant"
    ,  72  
    ,  NULL
    ,  1  
    ,  NULL );
 xTaskCreate(
    CorrectStartPos
    ,  (const portCHAR *) "CorrectStartPos"
    ,  128  
    ,  NULL
    ,  0 
    ,  NULL );
  xTaskCreate(
    PrintOutput
    ,  (const portCHAR *) "PrintOutput"
    ,  72
    ,  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 ReadRawAngle(void  *pvParameters __attribute__((unused))){
  for (;;) 
  {
    /*Wire.beginTransmission(0x36); //connect to the sensor
    Wire.write(0x0D); //figure 21 - register map: Raw angle (7:0)
    Wire.endTransmission(); //end transmission
    Wire.requestFrom(0x36, 1); //request from the sensor
    while (Wire.available() == 0); //wait until it becomes available
    lowbyte = Wire.read(); //Reading the data after the request
    Wire.beginTransmission(0x36);
    Wire.write(0x0C); //figure 21 - register map: Raw angle (11:8)
    Wire.endTransmission();
    Wire.requestFrom(0x36, 1);
    while (Wire.available() == 0);
    highbyte = Wire.read();
    highbyte = highbyte << 8; //shifting to left
    rawAngle = highbyte | lowbyte; //int is 16 bits (as well as the word)
    degAngle = rawAngle;
    */
    
    //Serial.println("ReadRawAngle");
    //delay(1);
    vTaskDelay(10);
  }
}
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 PrintOutput(void  *pvParameters __attribute__((unused))){
  for (;;) // A Task shall never return or exit.
  {
    Counter = *((volatile uint32_t *)REGISTER_MEM_LOCATION);
    Serial.println(String("Total Steps Done: ") + (totalAngleInt-startStepsC) +String("    startStepsC Counter: ") + startStepsC +String("    Step Counter: ") + Counter );
    //Serial.println(String("    Step Counter: ") + Counter );
    /*TaskHandle_t taskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(taskHandle);
    Serial.println(String("stackHighWaterMark of : PrintOutput ")+stackHighWaterMark);*/
    vTaskDelay(1000);
  }
}
void loop() {}
is there a better way of doing it?
Last edited by willywonka on Thu Dec 22, 2022 11:09 am, edited 1 time in total.
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32F103C8 High Frequency Input Counter

Post by ozcar »

There is a frequency measurement example at https://github.com/stm32duino/STM32Exam ... rement.ino

Maybe that is what you started with, but I do not follow what you are trying to do with that counter at a fixed location. Are you just trying to measure the frequency, or do you have to do something else?

If you only need to measure the frequency, how high is the frequency? Comments in the example suggest it could be good up to around 100kHz on a 80MHz L476. I have tested it on an 84MHz F401 and it was OK up to 140kHz, but by that point it is spending practically 100% of the time processing interrupts with no time left over to do anything else.

The example measures duty cycle as well as frequency. If you only need to measure frequency you don't need to generate interrupts for both rising and falling edges. If I modify the example to only interrupt on the rising edge, then it is OK to about 185kHz max. Obviously maximum will be less on F103 at probably 72MHz, but maybe this will give you some idea of what difference you could expect triggering on only one edge per cycle.

If "high frequency" means up in MHz range, then you would be better off setting up a timer clocked by the external signal, and then sample or gate that at a fixed rate, say every 10ms or 100ms. Just recently there was another thread talking about ways to do that. However that might require you to venture below the stm32duino level.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 High Frequency Input Counter

Post by willywonka »

Hi thank you for your reply.

Im not 100% sure if the source is a pwm signal. But i assume that it is not. The source is a regular 3D-Printer Board, so i just need to count the pulses that is sent from the board.
is there a way of counting those pulses (just the rising edges) in hardware and read those with a timer every 10ms or so ?
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 High Frequency Input Counter

Post by willywonka »

the title is a bit misleading I maybe should have named it "high Input Rate"
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32F103C8 High Frequency Input Counter

Post by ozcar »

Is it a signal from the AS5600 that you have connected to EX_STEP_PIN / PA8? I'm not familiar with AS5600, but from the data sheet that I turned up now, that can produce either an analog output or PWM output. If that is set to generate PWM output, and that is what you have connected to EX_STEP_PIN / PA8, then I would think that code based on the frequency / duty cycle example should be able to work, because according to the data sheet the PWM frequency is not high enough to be a problem (selectable between 115Hz to 920Hz). In fact the frequency might be so low that you would have to set PrescalerFactor > 1 to prevent the timer counter from overflowing, as mentioned in the example.

However, from the AS5600 data sheet, it appears that the angle can be read over the I2C interface as well, so does the library that you are using for the AS5600 not give you what you need anyway?

If it is something else connected to that input, what is the frequency?
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

the PA8 is connected to the 3D-Printer Boards Step Pin
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: STM32F103C8 Fast Input Rate Counter

Post by dannyf »

the title is a bit misleading I maybe should have named it "high Input Rate"
the answer depends on how high is your "high".

a frequency counter should work but they often rely on the pulse being on a timer input pin. if not, pulse counting can be challenging.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

40,000-80,000 inputs per second if that is possible. That would translate to a max speed of 500mm/s at 16-32 micristeps

the current code is able to process 8000 inputs per second, which is just around 100 mm/s
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32F103C8 Fast Input Rate Counter

Post by ozcar »

willywonka wrote: Thu Dec 22, 2022 5:48 pm 40,000-80,000 inputs per second if that is possible. That would translate to a max speed of 500mm/s at 16-32 micristeps

the current code is able to process 8000 inputs per second, which is just around 100 mm/s
I took the frequency / duty cycle example, and changed it to only interrupt on rising edge, and made the interrupt routine just increment a counter:

Code: Select all

void TIMINPUT_Capture_Rising_IT_callback(void)
{
  Counter++;
}
I did not try it on a F103, but the 84MHz F401 that I have lying here, was able to count at over 200kHz. A 74MHz F103 won't be able to go so fast, but maximum of only 8000 counts per second seems very low. From my test 40,000 or maybe 80,000 counts per second does not sound impossible, but also depending on what other processing you have to do. Maybe try the modified example without anything else going on and find the upper bound.

If the interrupt-based counting is really not viable, then count rates of many millions of times per second are possible by setting up a timer to count the pulses all by itself - clocked by the external signal. With only 16-bit timers available, you'd have to keep track of timer overflows in software, but that would be a very tiny overhead. As far as I know, stm32duino does not provide a way to do this, but by going down to a lower level like the HAL routines, if should not be difficult. You could probably get sym32duino to do most of the timer set-up work, and then just make a few adjustments using HAL or even direct timer register access.
willywonka
Posts: 14
Joined: Wed Dec 21, 2022 11:28 am

Re: STM32F103C8 Fast Input Rate Counter

Post by willywonka »

ozcar wrote: Thu Dec 22, 2022 7:37 pm
willywonka wrote: Thu Dec 22, 2022 5:48 pm 40,000-80,000 inputs per second if that is possible. That would translate to a max speed of 500mm/s at 16-32 micristeps

the current code is able to process 8000 inputs per second, which is just around 100 mm/s
I took the frequency / duty cycle example, and changed it to only interrupt on rising edge, and made the interrupt routine just increment a counter:

Code: Select all

void TIMINPUT_Capture_Rising_IT_callback(void)
{
  Counter++;
}
I did not try it on a F103, but the 84MHz F401 that I have lying here, was able to count at over 200kHz. A 74MHz F103 won't be able to go so fast, but maximum of only 8000 counts per second seems very low. From my test 40,000 or maybe 80,000 counts per second does not sound impossible, but also depending on what other processing you have to do. Maybe try the modified example without anything else going on and find the upper bound.

If the interrupt-based counting is really not viable, then count rates of many millions of times per second are possible by setting up a timer to count the pulses all by itself - clocked by the external signal. With only 16-bit timers available, you'd have to keep track of timer overflows in software, but that would be a very tiny overhead. As far as I know, stm32duino does not provide a way to do this, but by going down to a lower level like the HAL routines, if should not be difficult. You could probably get sym32duino to do most of the timer set-up work, and then just make a few adjustments using HAL or even direct timer register access.
oh okey that sounds exactly what I am looking for.
But I have basically zero knowledge of the HAL stuff :lol:

without the encoder stuff the code is able to count faster than the board is able to generate the signals. 4000 mm/s at 16 microsteps without issue.

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;
uint32_t counter;
HardwareTimer *MyTim;

void InputCapture_IT_callback(void)
{
  counter++;
}

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(channel, InputCapture_IT_callback);
  MyTim->resume();
}

void loop()
{
  Serial.println(counter);
  delay(500);
}
Post Reply

Return to “Projects”