It all started when we ordered an actuator, with an encoder, from China for a project. But something went wrong, and a month later, we received actuators without an encoder.
It was necessary to complete the project code urgently, but it would not be possible to control the actuators without encoders. For the project, you need to know the rod extension length, and for this, you need to know the number of engine rotations. So we decided to spend one day and make our encoder for actuators. At first, we thought to put a multi-turn potentiometer.
But there can be a lot of rotations, and we didn’t have such an encoder at hand.
Then we wanted to use an encoder like EC11,
but such encoders have a very small resource of work. And in the end, we chose the option: of two SS443A digital hall sensors and magnets.
The SS443A hall sensor outputs a logic HIGH on one polarity of magnets and a logic LOW on the other and retains that value even when the magnet is removed. If briefly: We take two hall sensors and put them at an angle of 45 degrees to each other. We glue two magnets to the motor axis, with different poles, and when the motor is spinning, each sensor turns on and off in turn.
If each sensor turned on and off one time, then there was one turn of the engine. With two sensors, we can count a quarter of a turn.
And knowing the sequence of turning on and off the sensors, we can determine the direction of rotation.
We dismantled the actuator to find a place to mount the magnets.
No place was there, and magnets could not be glued because everything was oily with grease. Then we decided to throw away the original cap of the actuator, which will have a built-in gear with magnets and two hall sensors. The gear will be driven, and we will place the gear on a separate axis. And the gear will spin without load from the drive gear of the engine. We took two magnets with a diameter of 5mm and a thickness of 4mm. In SolidWorks, with the help of the “GearTrax” plugin, we drew the gear of the required size and drew the case cap for the actuator with a 6-pin GX16 connector.
We printed the case on a 3D printer from PETG plastic, and on the 3rd attempt, we got a working mechanism. It took about 11 hours for all these, along with 3D printing.
All that’s left is to write the code to calculate the length of the rod extension. For the test, we took the Arduino MEGA 2560 board; there are six pins with hardware interrupts. You can make the code without interruptions, but if at least one pulse (a quarter of a turn) is skipped, the error will be 1.6mm, and this is already a lot for our task. Tests have shown that without interruptions, the error can accumulate to large values with active use. Therefore, we use two interrupt pins for each actuator. 1 pin = 1 hall sensor.
The code, in the end, turned out to be quite simple, but I decided to leave it here as a cheat sheet for the future, or maybe it will come in handy for someone else:
const byte M1_HALL1_pin = 20; //connect pintout Hall sensor 1
const byte M1_HALL2_pin = 21; //connect pintout Hall sensor 2
const byte M1_HALL1_pin_interupt = 3; //Interrupt pin3 match Pin20 Arduino Mega
const byte M1_HALL2_pin_interupt = 2; //Interrupt pin2 match Pin21 Arduino Mega
volatile byte M1_HALL_cur_state = B00; //Current state hall sensors. If Hall1 and Hall2 is LOW then variable value is B00
volatile byte M1_HALL_last_state = B00; //Last state hall sensors. If Hall1 is HIGH and Hall2 is LOW then variable value is B10
volatile long countPulsesM1 = 0; //Counter
/*
Arrays dirUpM1 and dirDownM1 have directions of rotation.
*/
byte dirUpM1[] = {1,3,0,2}; //01 11 00 10
byte dirDownM1[] = {2,0,3,1}; //10 00 11 01
void setup(void){
attachInterrupt(M1_HALL1_pin_interupt, countRotationInteruptsM1, CHANGE);
attachInterrupt(M1_HALL2_pin_interupt, countRotationInteruptsM1, CHANGE);
//Be sure to pullup the pins to avoid noises
pinMode(M1_HALL1_pin, INPUT_PULLUP);
pinMode(M1_HALL2_pin, INPUT_PULLUP);
Serial.begin(9600);
}
void loop(void){
Serial.println(countPulsesM1);
}
void countRotationInteruptsM1(){
//Present cur status of hall sensor as binary number
M1_HALL_cur_state = digitalRead(M1_HALL1_pin); //Read HALL1 state (for example: if HALL1 is HIGH then value is B01)
M1_HALL_cur_state = M1_HALL_cur_state << 1; //Shift the bit to make room for the second value (for example: value is B10)
M1_HALL_cur_state += digitalRead(M1_HALL2_pin); //Read HALL2 state and ADD it to HALL1 value (for example: if HALL2 is HIGH too, then value is B11)
//If in an array dirUpM1 of sequences, the previous value is followed by the current, means the actuator moves up
if(dirUpM1[M1_HALL_cur_state] == M1_HALL_last_state){
countPulsesM1++;
//else the actuator moves up
} else if(dirDownM1[M1_HALL_cur_state] == M1_HALL_last_state){
countPulsesM1--;
if(countPulsesM1 < 0){
countPulsesM1 = 0;
}
}
}
Arrays dirUpM1 and dirDownM1 have directions of rotation. As the gear turns, the hall sensors will turn on and off one after the other.
For example:
at first, HALL1 and HALL2 is LOW. This can be written as binary B00
then HALL1 is LOW, and HALL2 is HIGH. This can be written as binary B01
then HALL1 and HALL2 are HIGH. This can be written as binary B11
then HALL1 is HIGH, and HALL2 is LOW. This can be written as binary B10
and then the cycle repeats
This can be represented as a combination: 00 - 01 01 - 11 11 - 10 10 - 00
If you convert binary numbers to decimals, it will look like this: 0 - 1 1 - 3 3 - 2 2 - 0
Now let's represent it as an array:
arrD[0] = 1;
arrD[1] = 3;
arrD[3] = 2;
arrD[2] = 0;
And finally: byte dirUpM1[] = {1,3,0,2};
If suddenly the connection of the hall sensors changes, the array with the direction can be “trained”:
void setDerectionUp(){
if(M1_HALL_cur_state != M1_HALL_last_state){
dirUpM1[M1_HALL_last_state] = M1_HALL_cur_state;
M1_HALL_last_state=M1_HALL_cur_state;
}
if(M2_HALL_cur_state != M2_HALL_last_state){
dirUpM2[M2_HALL_last_state] = M2_HALL_cur_state;
M2_HALL_last_state=M2_HALL_cur_state;
}
}
PS: The factory actuator with an encoder costs: $41 Actuator without encoder + homemade encoder costs: $26 + $4 = $30