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:
- Do more than just blink LEDS
- Mimic/translate ATTiny/Arduino code to Padauk
- 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)
- 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!
1. 1KW OTP program memory2. 64 Bytes data RAM3. One hardware 16-bit timer4. One hardware 8-bit timers with PWM generator5. One general purpose comparator6. Support fast wake-up7. Every IO pin can be configured to enable wake-up function8. 6 IO pins with optional drive/sink current and pull-high resistor9. Clock sources: internal high RC oscillator and internal low RC oscillator10. Eight levels of LVR reset ~ 4.0V, 3.5V, 3.0V, 2.75V, 2.5V, 2.2V, 2.0V, 1.8V11. One external interrupt pin
CPU Features
1. One processing unit operating mode2. 79 powerful instructions3. Most instructions are 1T execution cycle4. Programmable stack pointer to provide adjustable stack level5. Direct and indirect addressing modes for data access. Data memories are available for use as an index pointer of Indirect addressing mode6. Separated IO space and memory space
But first - what's with that four input stepper motor?
- 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
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.
Hi. I'm Hugues. have a small business and I'm looking for someone who has experience programming Padauk. Would you be available?
ReplyDeleteWhat is the nature of the project? Follow then DM me @peckmaths on twitter.
ReplyDelete