Bad accuracy with 28-BYJ despite seemingly correct code

Hi!

I used the ubiquitous 28-BYJ-48 roughly 2 years ago with a Raspberry Pi (which one would assume is more problematic) without issue. Having studied Computer Science for two years and worked with low level systems programming, I felt comfortable delving into MCUs and having them interface with the afformentioned stepper motor. Unfortunately, over 100 rations, the achieved inaccuracy is roughly 1/4 rotation.

To have more granular control and due to the fact that I only plan to do simple projects with it, I chose to manually interface with the motor instead of using an overkill library. I also avoided using any abstractions and accessed the DDR-/PORT registers directly.

The setup:

Board: Arduino Nano (clone)
Software: Arduino IDE version 1.8.10
Motor: 28-BYJ-48
Driver: ULN2003A (ST) on the driver board which is always supplied with the stepper.

Following my code:

Initialization:

char incr = 0;
char mask = 0;
volatile char rdy = 1;
volatile int rev = 0;

void setup() {

  DDRB = 15; // 4 lowest pins of B are 1, i.e. digital pins 8, 9, 10, 11
  PORTB = 0;
  Serial.begin(9600);
  Serial.write("Initialized!\n");

}

Caller code of the actual rotation procedure. It reads an integer over serial input uses it for rotation count:

void serialEvent()
{
  if(rdy && Serial.available()) // If motor not running AND new serial input.
                                              // Not sure if Serial.available() is needed because serialEvent() only gets called if Serial bus 
                                              // reg-/FIFO queue was written by device
  {

    rev = Serial.parseInt(); // reads # of rotations from serial port
    rdy = 0; // rdy == 0 => any further inputs over serial ignored

    rotate(); // Rotate for 

    rdy = 1;
  }
}

Wave-stepped:

void rotate()
{
  
    while(rev-- > 0)
    {
       Serial.print("Revolution: ");
       Serial.println(rev);
       for(int i = 0; i < 512; ++i)
        {
    
            Serial.println(i);
             mask = 1 << incr;
            PORTB = mask;
            incr = (incr + 1 ) % 4;
            delay(10);
        }
    }
  
    // Finishes always after LAST iteration,
    // i.e. where binary mask goes from 0001 --> 0010 --> 0100 --> 1000, i.e. the last stage finishes
}

Half-stepped (ugly code, but I want to get it right before rewriting it in in a nicer way):

void rotate()
{
  
  while(rev-- > 0)
  {
    Serial.print("Revolution: ");
    Serial.println(rev);
    for(int i = 0; i < 128; ++i)
    {
      PORTB = 1;  
      delay(10);

      PORTB = 3;
      delay(10);

      PORTB = 2;
      delay(10);

      PORTB = 6;
      delay(10);

      PORTB = 4;
      delay(10);

      PORTB = 12;
      delay(10);

      PORTB = 8;
      delay(10);

      PORTB = 9;
      delay(10);
      
    }
  }
}

Following is the half-stepping sequence:

Unfortunately, I have no idea where I went wrong. I would very much appreciate help in finding my error!

Please describe your exact expectations, and state the actual results.

Some, if not all of those motors, have a non-integer number of steps per shaft rotation.

Sorry if I was inaccurate:

Specific problem:
Having used the motor before (as stated, 2 years ago on an RPi), I expect much better accuracy than what is achieved with my current Arduino Nano setup.

Exact expectations:
Getting higher accuracy. With my RPi setup from 2 years ago, I managed to get negligible error after ~500 iterations.

Hardware clarifications:
I understand that there exist multiple versions of the stepper. It specifically says: 1:16 (it's from Adafruit, the stamping matches the 1/16th gearing noted on the Adafruit website). Unfortunately, Adafruit themselves are very intransparent, as in one example they state 512 steps per revolution, in another 768. With 512 is close, but I also tried +/- 2 steps without success.

Specific questions:
Is there an error in my code causing the problem (assuming step count is correct).
How can I determine the actual step count needed without disassembling (and thereby destroying) the stepper?

To determine the number of steps per shaft revolution, I use a full step, two coil drive sequence that drives forward slowly, so that it is easy to see if any steps are skipped..

Fiddle with the program step count until a shaft position marker returns exactly to its starting position after some number of steps.

With some of those motors, it is not possible to return to the starting position. One of mine, for example, has slightly less than 2038 steps/rev, in agreement with this article

These motors come in several different versions from different manufacturers, they are a standard component for vehicle air conditioner airflow vane control, they are not precision components, but made down to minimum price.

Their specification has slack - nominally they are 2048 full steps/rotation, in practice the gear ratios used are different, so expect 2050 +/- 50.

Their actual intended application calls for a total rotation of 90 degrees (vane closed, vane open), so there's no need for any great accuracy over multiple rotations.