The library currently supports the following types of position sensors:
- encoder:
- The sensor counts A, B and directs pulses to estimate position.
- example:
- Optics: Omron 1000P
- capacitance: AMT103 CUI
- Magnetism: AS5047U - Use ABI
- 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: AS5048A, AS5047U, AS5600
- A sensor that uses accurate magnetic field measurements to estimate position.
- hall sensor :
- A sensor that estimates the rotor position by reading the magnet position on the rotor.
- Example: 49E hall detector, 105 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.
- use Hardware external interrupt
- Arduino UNO(Atmega328) pins 2 and 3
- STM32 board any pin
- ESP32 any pin
- through the use of PciManager Library etc. library use Software pin change interrupt
- Only for Arduino devices (Atmga328 and Atmage2560)
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()); }