Arduino SimpleFOC library-006-position sensor

The library currently supports the following types of position sensors:

  • encoder:
    • The sensor counts A, B and directs pulses to estimate position.
    • example:
  • Magnetic sensor:
    • A sensor that uses accurate magnetic field measurements to estimate position.
      • They have many different communication standards, such as SPI, SSI, I2C, ABI, UVW, PWM
      • Support communication:( release)
        • SPI, I2C, analog, PWM
        • UVW (using Hall sensor interface)
        • ABI (using encoder interface)
    • example: AS5048AAS5047UAS5600
  • hall sensor :

All types of sensors are implemented in a common way to support as many versions as possible.

1. Instantiate Encoder class

To initialize the encoder, you need to provide encoder A and B channel pins, encoder PPR and optional index pins.

//  Encoder(int encA, int encB , int cpr, int index)
//  - encA, encB    - encoder A and B pins
//  - ppr           - impulses per rotation  (cpr=ppr*4)
//  - index pin     - (optional input)
Encoder encoder = Encoder(2, 3, 8192, A0);

  2. Configuration

When the Encoder class is instantiated, we need to configure it. The first function we can configure is to enable or disable the Quadrature mode. If the Encoder runs in Quadrature mode, PPR detects the number of pulses per rotation by detecting each CHANGE signal A and B -(  ) Quadruple CPR = 4xPPR. In some applications, when the Encoder PPR is very high, Arduino may not be able to handle too much, so it is best not to use the square mode. By default, all encoders use the square mode. If you want to enable or disable this parameter, please perform this operation in the Arduino function before init() call:

// Quadrature mode enabling and disabling
//  Quadrature::ON - CPR = 4xPPR  - default
//  Quadrature::OFF - CPR = PPR
encoder.quadrature = Quadrature::OFF;

CPR, PPR?!

PPR (pulses per revolution) - this is the physical number of pulses per revolution of the encoder. CPR (count per revolution) - this is the number you will have in the counter after the encoder is fully rotated. Now, depending on whether you use orthogonal mode (calculate each edge of the pulse) or not (calculate only the rising edge) , for the same PPR, you will have different CPR. For orthogonal mode, you will get CPR = 4xPPR, if you do not use orthogonal mode, you will get CPR=PPR

In addition, the encoder has a more important parameter, pull-up position. Many encoders need to be pulled up. If you have an encoder that needs to be pulled up and you don't have it, you can use Arduino pull-up. This is set by changing the value of the encoder.pullup variable. The default value is pullup:: use_external, but if you want to change it to use MCU, please execute Do the following:

// check if you need internal pullups
// Pullup::USE_EXTERN - external pullup added  - default
// Pullup::USE_INTERN - needs internal arduino pullup
encoder.pullup = Pullup::USE_INTERN;

Arduino pull-up 20k Ω

Be careful when using internal pull-up. Arduino has a relatively high pull-up value of about 20k Ω, which means you may encounter some problems at higher speeds (short pulse duration). The recommended pull-up value is between 1k Ω and 5k Ω.

3. Encoder interrupt setting

You can run the encoder using the Simple FOC Library in two ways.

Software interrupt

Using hardware external interrupts usually produces better and more reliable performance, but software interrupts work well for lower speeds. Especially on boards without enough hardware interrupt pins, FOC can basically be enabled on these boards.

Hardware external interrupt

Arduino's UNO has two hardware external interrupt pins, pins 2 and 3, Arduino   Mega has six interrupt pins, pins 2, 3, 18, 19, 20 and 2. STM32 boards such as nucleus and bluepill can use all digital pins as interrupt pins, which makes implementation easier. For Arduino Uno, encoder channels A and B must be accurately connected to pisn2 and 3 in order to use hardware interrupt.

The simple FOCEncoder class has implemented initialization and encoder A and B channel callback. All you need to do is define two functions doA()and   doB(), buffer functions of encoder callback function encoder.handleA() and encoder.handleB().

// interrupt routine initialization
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}

These functions are provided to the encoder interrupt init function   encoder.enableInterrupts()

// enable encoder hardware interrupts
encoder.enableInterrupts(doA, doB)

You can name buffer functions as needed. It is important to provide them to the encoder.enableInterrupts() function. This process is a trade-off between scalability and simplicity. This allows you to connect multiple encoders to the same MCU. All you need to do is instantiate the new Encoder class and create a new buffer function. For example:

// encoder 1
Encoder enc1 =  Encoder(...);
void doA1(){enc1.handleA();}
void doB1(){enc1.handleB();}
// encoder 2
Encoder enc2 =  Encoder(...);
void doA2(){enc2.handleA();}
void doB2(){enc2.handleB();}

void setup(){
...
  enc1.init();
  enc1.enableInterrupts(doA1,doB1);
  enc2.init();
  enc2.enableInterrupts(doA2,doB2);
...
}

Index pin configuration

To effectively read index pins, a simple FOC library enables you to use with channels A and B   First, you need to provide the index pin number for the Encoder class:

Encoder encoder = Encoder(pinA, pinB, cpr, index_pin);

If you are using Arduino mega and similar Arduino boards, and you have more than 2 hardware interrupts, you can connect the index pin to the hardware interrupt pin (e.g. Arduino Mega pin 21). Your code will be as follows:

Encoder encoder =  Encoder(2,3,600,21);
// A and B interrupt routine 
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void doIndex(){encoder.handleIndex();}

void setup(){
  ...
  encoder.enableInterrupts(doA,doB,doIndex);
  ...
  }

This function, enableInterrupts, handles all initialization for you.

If you use Arduino UNO to run this algorithm and you do not have enough hardware interrupt pins, you will need to use a software interrupt library, such as PciManager Library . the Arduino UNO code using an indexed encoder can be:

Encoder encoder =  Encoder(2,3,600,A0);
// A and B interrupt routine 
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void doIndex(){encoder.handleIndex();}

// software interrupt listener for index pin
PciListenerImp listenerIndex(encoder.index_pin, doIndex);

void setup(){
  ...
  // hardware interrupts for A and B
  encoder.enableInterrupts(doA,doB);
  // software interrupt for index
  PciManager.registerListener(&listenerIndex);
  ...
  }

If your application causes you to run out of hardware interrupt pins A and B, you can perform the same process on pins A and B. software interrupts are very powerful and can produce results equivalent to hardware interrupts, especially when you have no choice. Indexin generates an interrupt every revolution, which is not important, so software or hardware interrupts will not change much in performance.

To better explore the differences between encoder functions and hardware and software interrupt methods, please check the examples encoder_example.ino and encoder_software_interrupts_example.ino.

Software pin change interrupt

If you cannot access your pins 2 and 3Arduino UNO, or you want to use multiple encoders, you must use the software interrupt method. I recommend using PciManager Library.

The steps to use this library in your code are the same as Hardware interrupt Very similar. The SimpleFOCEncoder class still provides you with all callback A, B and Index channels, but the simple FOC library will not interrupt you.

In order to use the PCIManager library, you need to include it in your code:

#include <PciManager.h>
#include <PciListenerImp.h>

The next step is the same as before, you just need to initialize a new Encoder instance.

Encoder encoder = Encoder(10, 11, 8192);
// A and B interrupt callback buffers
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}

Then you declare the listener PciListenerImp  :

// encoder interrupt init
PciListenerImp listenerA(encoder.pinA, doA);
PciListenerImp listenerB(encoder.pinB, doB);

Finally, after running, encoder.init() calls encoder.enableInterrupts() you skipped and calls the PCIManager library to register interrupts for all encoder channels.

// initialize encoder hardware
encoder.init();
// interrupt initialization
PciManager.registerListener(&listenerA);
PciManager.registerListener(&listenerB);

That's it. It's very simple. If you want multiple encoders, you just need to initialize new class instances, create new A and B callbacks, and initialize new listeners. This is A quick example:

// encoder 1
Encoder enc1 =  Encoder(9, 10, 8192);
void doA1(){enc1.handleA();}
void doB1(){enc1.handleB();}
PciListenerImp listA1(enc1.pinA, doA1);
PciListenerImp listB1(enc1.pinB, doB1);

// encoder 2
Encoder enc2 =  Encoder(13, 12, 8192);
void doA2(){enc2.handleA();}
void doB2(){enc2.handleB();}
PciListenerImp listA2(enc2.pinA, doA2);
PciListenerImp listB2(enc2.pinB, doB2);

void setup(){
...
  // encoder 1
  enc1.init();
  PciManager.registerListener(&listA1);
  PciManager.registerListener(&listB1);
  // encoder 2
  enc2.init();
  PciManager.registerListener(&listA2);
  PciManager.registerListener(&listB2);
...
}

You can view the HMBGC_example.ino example to see the actual effect of this code.

Index pin configuration

Enabling the index pin in case of software interruption is very simple. You just need to provide it Encoder as an additional parameter to class initialization.

Encoder encoder = Encoder(pinA, pinB, cpr, index_pin);

After that, you create a callback buffer function of the same type as the AandB channel, and use the PCIManager tool to initialize and register the listener of the index channel, such as Aand   B. This is a quick example:

// class init
Encoder encoder =  Encoder(9, 10, 8192,11);
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void doIndex(){encoder.handleIndex();}
// listeners init
PciListenerImp listenerA(encoder.pinA, doA);
PciListenerImp listenerB(encoder.pinB, doB);
PciListenerImp listenerIndex(encoder.index_pin, doIndex);

void setup(){
...
  // enable the hardware
  enc1.init();
  // enable interrupt
  PciManager.registerListener(&listenerA);
  PciManager.registerListener(&listenerB);
  PciManager.registerListener(&listenerIndex);
...
}

  4. Use encoder in real time

There are two ways to use the encoder implemented in this library:

  • Motor position sensor as FOC algorithm
  • As an independent position sensor

Position sensor based on FOC algorithm

To use the encoder sensor with the foc algorithm implemented in this library, after initializing encoder.init() and enabling interrupts, encoder.enableInterrupts(...) you only need to link it to the BLDC motor by executing the following command:

motor.linkSensor(&encoder);

Independent sensor

To obtain the encoder angle and speed at any given time, you can use the common method:

class Encoder{
 public:
    // shaft velocity getter
    float getVelocity();
	  // shaft angle getter
    float getAngle();
}
#include <SimpleFOC.h>

Encoder encoder = Encoder(2, 3, 8192);
// interrupt routine initialization
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}

void setup() {
  // monitoring port
  Serial.begin(115200);

  // enable/disable quadrature mode
  encoder.quadrature = Quadrature::ON;

  // check if you need internal pullups
  encoder.pullup = Pullup::USE_EXTERN;
  
  // initialize encoder hardware
  encoder.init();
  // hardware interrupt enable
  encoder.enableInterrupts(doA, doB);

  Serial.println("Encoder ready");
  _delay(1000);
}

void loop() {
  // display the angle and the angular velocity to the terminal
  Serial.print(encoder.getAngle());
  Serial.print("\t");
  Serial.println(encoder.getVelocity());
}

Tags: Single-Chip Microcomputer ARM MCU

Posted on Sun, 03 Oct 2021 18:29:24 -0400 by hdpt00