Friday, February 11, 2022

0000 0000 1000 1000

PMS150C, ULN2003 and the 28BYJ-48 Stepper Motor (Tiny Dancer)

I have had it in mind to start to know the Padauk microcontrollers a little better, and in particular the PFS154 and the PMS150C. It has been such a journey so far, with the concentration of effort mostly into the making of the open source programmer

In the "still to do" basket are:

  1. Do more than just blink LEDS
  2. Mimic/translate ATTiny/Arduino code to Padauk
  3. Learn more C-Code to better fit into the specs of the little fellas (the Arduino Uno/Nano is very generous and forgiving, and has multiple libraries - many bloated but functional)
  4. Move from C-Code to Padauk assembly for speed, clarity and size.

When a ULN2003 stepper motor controller and 28BYJ-48 combo landed in the mailbag I decided that I might be able to start on points 1 & 2 (and a bit 3) above.

The prospect of making the feature challenged (but cheap!) one-time programmable PMS150C drive a stepper motor reminds me very much of humans driving big machinery - such as here in Australia where tiny figures are dwarfed by Haul Pack trucks.


My plan was to make some stepper code work with Arduino (and appropriate libraries), then transfer the code to the very familiar ATTiny13, and finally the PFS154 (because it's multi-programmable, so testing is less stressful). The ultimate goal of the exercise is to use the one time programmable PMS150C.

In the end I only threw away two fried PMS150C chips, so I'm pretty happy about that!

PMS150C Features (from the website)
1. 1KW OTP program memory
2. 64 Bytes data RAM 
3. One hardware 16-bit timer 
4. One hardware 8-bit timers with PWM generator
5. One general purpose comparator 
6. Support fast wake-up 
7. Every IO pin can be configured to enable wake-up function 
8. 6 IO pins with optional drive/sink current and pull-high resistor 
9. Clock sources: internal high RC oscillator and internal low RC oscillator 
10. Eight levels of LVR reset ~ 4.0V, 3.5V, 3.0V, 2.75V, 2.5V, 2.2V, 2.0V, 1.8V 
11. One external interrupt pin
   
CPU Features
1. One processing unit operating mode 
2. 79 powerful instructions 
3. Most instructions are 1T execution cycle 
4. Programmable stack pointer to provide adjustable stack level 
5. Direct and indirect addressing modes for data access. Data memories are available for use as an index pointer of Indirect addressing mode
6. Separated IO space and memory space 

But first - what's with that four input stepper motor?

Model No: 28BYJ-48
  • Unipolar Stepper with 0.1″ Spaced 5-pin Cable Connector
  • 4096 Steps Per Revolution (5.625 degree step angle – 64 steps per rev + 64:1 gear ratio)
  • 1/64 Geared Down Reduction
  • 5V DC Suggested Operation
  • Weight: 37 g.
  • Dimensions: 28mm diameter, 20mm tall not including 9mm shaft with 5mm diameter
  • 9″ / 23 cm long cable
  • Holding Torque: 150 gram-force*cm, 15 N*mm (2 oz-force*in)
  • Shaft: 5mm diameter flattened
It seems that a common way to control this guy is to use a ULN2003 Darlington Array chip, but somewhat unusually this is wrapped up in a module which allows 5V/12V versions and has blinking lights because, well, who doesn't love blinking lights?? <sigh>


I do have some ULN2003 chips about the place (somewhere...?), but the focus for this project is  programming, so I'll use the OMG AMAZING™ light show version.

Getting an Arduino Uno to light em up and move the stepper was not problem - you simply install this library and then borrow the code from the examples given.

#include <Stepper.h>

const int stepsPerRevolution = 200;

Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);

void setup() {
  myStepper.setSpeed(60);
  Serial.begin(9600);
}

void loop() {
  Serial.println("clockwise");
  myStepper.step(stepsPerRevolution);
  delay(500);

  Serial.println("counterclockwise");
  myStepper.step(-stepsPerRevolution);
  delay(500);
}

The stepper motor datasheet recommends the following sequence to turn the shaft:

Therefore instead of using the stepper library for the ATTiny13 version, I made an array of appropriate data and then stepped through it either backwards or forwards for "random" dancing.

const byte thesteps[] = {

  // ATTiny13 pinout
  //
  //       _________
  //      /         |
  //  1--|RESET  VCC|--8
  //  2--|PB3    PB2|--7
  //  3--|PB4    PB1|--6
  //  4--|GND    PB0|--5
  //     |__________| 

  // IN4,IN3,IN2,IN1
  // PB4,PB3,PB2,PB1
  // pins 3, 2, 7, 6
  0b00011100,
  0b00011000,
  0b00011010,
  0b00010010,
  0b00010110,
  0b00000110,
  0b00001110,
  0b00001100
};

byte numberofsteps = 0;
uint16_t myrand = 2901;  // happy birthday

uint16_t gimmerand(uint16_t small, uint16_t big) {
  myrand ^= (myrand << 13);
  myrand ^= (myrand >> 9);
  myrand ^= (myrand << 7);
  return abs(myrand)%23*(big-small)/23+small;
}

void setup() {
  DDRB = 0b00011110; // drive pins as output
}

void loop() {

  numberofsteps = gimmerand(4, 40);
  delay(10);

  if ((numberofsteps%2) == 0) {
    for (int rotate = 0; rotate < numberofsteps; rotate++) {
      for (byte clocker = 0; clocker < 8; clocker++) {
        PORTB = thesteps[clocker];
        delay(1);
      }
    }
  }
  else {
    for (int rotate = 0; rotate < numberofsteps; rotate++) {
      for (byte clocker = 7; clocker > 0; clocker--) {
        PORTB = thesteps[clocker];
        delay(1);
      }
    }
  }
}

Problems emerged when I "translated" this to the PFS154 and then to the PMS150C. I think the majority of the issues were wrapped up in the variable data types. I eventually simplified most of these to 0-255 by using the uint8_t data type.

The data types "int", "char", "byte" and even "uint16_t" were all too problematic for the PMS150C (although the PFS154 code was pretty much the same as the ATTiny13 code).

The change of data types also meant that I had to "tweak" the code, in particular the "random" number generator, quite a bit! 

The resultant randomness is pretty awful, but that's a problem for another time (perhaps when I learn how to use the chip's comparator as an ADC).

#include <pdk/device.h>
#include <delay.h>
#include <stdint.h>
#include "auto_sysclock.h"
#include <stdlib.h>
#include <stdbool.h>  // important for booleans

char thesteps[] = {

  // PMS150C pinout
  //
  //       _________
  //      /         |
  //  1--|VCC    GND|--8
  //  2--|PA7    PA0|--7
  //  3--|PA6    PA4|--6
  //  4--|PA5    PA3|--5
  //     |__________| 

  // IN 4, IN3, IN 2, IN 1
  // PA6, PA7, PA0, PA4
  // pins 3, 2, 7, 6

  0b11000001,
  0b11000000,
  0b11010000,
  0b01010000,
  0b01010001,
  0b00010001,
  0b10010001,
  0b10000001
};

uint8_t numberofsteps = 0;

// change these as you please, my focus in this
// project was not the "purity" of randomness
uint8_t myrand = 201;
uint8_t xorpoly1 = 0b10001011;
uint8_t xorpoly2 = 0b10111010;
uint8_t throwspanner = 61;

uint8_t spannercount = 0;

uint8_t gimmerand(uint8_t small, uint8_t large) {
  bool carry = false;
  if (spannercount > throwspanner) {
    myrand = myrand ^ xorpoly2;
    myrand--;
    spannercount = 0;
  }
  if (((myrand) >> 7) & 1) {
    carry = true;
  }
  myrand = myrand << 1;
  if (carry) {
    myrand = myrand ^ xorpoly1;
  }
  return (myrand/((255)/(large-small))+small);
  }

bool isEven(uint8_t n) // faster than n%2==0?
{
return (!(n & 1));
}

void main() {

  PAC = 0b11111001; // output pins

while (1) {

  // change these for "speed" of dancing
  numberofsteps = gimmerand(4, 40);
  _delay_ms(10);

  if (isEven(numberofsteps)) {
    for (uint8_t rotate = 0; rotate < numberofsteps; rotate++) {
      for (uint8_t clocker = 0; clocker < 8; clocker++) {
        PA = thesteps[clocker];
        PA = PA | 0b00100000; // turns on LED
        _delay_ms(1);
      }
    }
  }
  else {
    for (uint8_t rotate = 0; rotate < numberofsteps; rotate++) {
      for (uint8_t clocker = 7; clocker > 0; clocker--) {
        PA = thesteps[clocker];
        PA = PA | 0b00001000; // turns on LED
        _delay_ms(1);
      }
    }

}
}
}

Amazingly the PMS150C (and the PFS154) do have an extra two "proper" GPIOs to play with after this program is installed (the ATTiny13's RESET pin can be used as a "weak" GPIO if needed), so you'll see blinking LEDS to indicate clockwise and counterclockwise dancing. 





2 comments:

  1. Hi. I'm Hugues. have a small business and I'm looking for someone who has experience programming Padauk. Would you be available?

    ReplyDelete
  2. What is the nature of the project? Follow then DM me @peckmaths on twitter.

    ReplyDelete