{
"cells": [
{
"cell_type": "markdown",
"id": "6680972c-1589-4b55-a8f2-1edaecee6215",
"metadata": {},
"source": [
"# Stepper Motors\n",
"\n",
"* [How to use a stepper motor with the Raspberry Pi Pico](https://www.youngwonks.com/blog/How-to-use-a-stepper-motor-with-the-Raspberry-Pi-Pico)\n",
"* [Control 28BYJ-48 Stepper Motor with ULN2003 Driver & Arduino](https://lastminuteengineers.com/28byj48-stepper-motor-arduino-tutorial/) Description of the 27BYJ-48 stepper motor, ULN2003 driver, and Arduino code.\n",
"* [28BYJ-48 stepper motor and ULN2003 Arduino (Quick tutorial for beginners)](https://www.youtube.com/watch?v=avrdDZD7qEQ) Video description.\n",
"* [Stepper Motor - Wikipedia](https://en.wikipedia.org/wiki/Stepper_motor)\n",
"\n",
"
\n",
"\n",
"Link"
]
},
{
"cell_type": "markdown",
"id": "4cba9e2b-1fce-4cb7-818a-bbd52072e086",
"metadata": {},
"source": [
"## Stepper Motors\n",
"\n",
"\n",
"[Adafruit](https://learn.adafruit.com/all-about-stepper-motors/types-of-steppers)\n",
"\n",
"\n",
"[Adafruit](https://learn.adafruit.com/all-about-stepper-motors/types-of-steppers)\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"id": "c12a5bbc-a613-4dba-bf84-aa9df4ce816a",
"metadata": {
"tags": []
},
"source": [
"## Unipolar Stepper Motors\n",
"\n",
"The ubiquitous 28BYJ-48 stepper motor with reduction gears that is manufactured by the millions and widely available at very low cost. [Elegoo, for example, sells kits of 5 motors with ULN2003 5V driver boards](https://www.elegoo.com/products/elegoo-uln2003-5v-stepper-motor-uln2003-driver-board) for less than $15/kit. The [UNL2003](https://en.wikipedia.org/wiki/ULN2003A) is a package of seven NPN Darlington transistors capable of 500ma output at 50 volts, with flyback diodes to drive inductive loads.\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"The 28BJY-48 has 32 teeth thus each full step corresponds to 360/32 = 11.25 degrees of rotation. A set of four reduction gears yields a 63.68395:1 gear reduction, or 2037.8864 steps per rotation. The maximum speed is 500 steps per second. If half steps are used, then there are 4075.7728 half steps per revolution at a maximum speed of 1000 half steps per second.\n",
"\n",
"(See https://youtu.be/15K9N1yVnhc for a teardown of the 28BYJ-48 motor.)"
]
},
{
"cell_type": "markdown",
"id": "21e102bf-2ff1-4337-a132-8c5d77bc23b5",
"metadata": {},
"source": [
"## Driving the 28BYJ-48 Stepper Motor\n",
"\n",
"(Also see https://www.youtube.com/watch?v=UJ4JjeCLuaI&ab_channel=TinkerTechTrove)\n",
"\n",
"The following code assigns four GPIO pins to the four coils. For this code, the pins don't need to be contiguous or in order, but keeping that discipline may help later when we attempt to implement a driver using the PIO state machines of the Raspberry Pi Pico.\n",
"\n",
"Note that the Stepper class maintains an internal parameter corresponding to the current rotor position. This is used to index into the sequence data using modular arithmetic.\n",
"\n",
"See []() for ideas on a Stepper class."
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "39ed2bc5-00d4-423d-812b-f390ca5b7c1b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found serial ports: /dev/cu.usbmodem14401, /dev/cu.Bluetooth-Incoming-Port \n",
"\u001b[34mConnecting to --port=/dev/cu.usbmodem14401 --baud=115200 \u001b[0m\n",
"\u001b[34mReady.\n",
"\u001b[0m.0\n"
]
}
],
"source": [
"%serialconnect\n",
"\n",
"from machine import Pin\n",
"import time\n",
"\n",
"class Stepper(object):\n",
" \n",
" step_seq = [[1, 0, 0, 0], \n",
" [1, 1, 0, 0], \n",
" [0, 1, 0, 0],\n",
" [0, 1, 1, 0],\n",
" [0, 0, 1, 0],\n",
" [0, 0, 1, 1], \n",
" [0, 0, 0, 1], \n",
" [1, 0, 0, 1]]\n",
" \n",
" def __init__(self, gpio_pins):\n",
" self.pins = [Pin(pin, Pin.OUT) for pin in gpio_pins]\n",
" self.motor_position = 0\n",
" \n",
" def rotate(self, degrees=360):\n",
" n_steps = abs(int(4075.7728*degrees/360))\n",
" d = 1 if degrees > 0 else -1\n",
" for _ in range(n_steps):\n",
" self.motor_position += d\n",
" phase = self.motor_position % len(self.step_seq)\n",
" for i, value in enumerate(self.step_seq[phase]):\n",
" self.pins[i].value(value)\n",
" time.sleep(0.001) \n",
" \n",
"stepper = Stepper([2, 3, 4, 5])\n",
"stepper.rotate(360)\n",
"stepper.rotate(-360)\n",
"print(stepper.motor_position)"
]
},
{
"cell_type": "markdown",
"id": "f9db2e53-01ba-4b73-b9b3-6141c65f51c2",
"metadata": {},
"source": [
"Discussion:\n",
"\n",
"* What class methods should we build to support the syringe pump project?\n",
"* Should we simplify and stick with half-step sequence?\n",
"* How will be integrate motor operation with UI buttons and other controls?"
]
},
{
"cell_type": "markdown",
"id": "f3567eb5-ca0b-4759-a844-e8729b2c4b96",
"metadata": {},
"source": [
"## Programmable Input/Ouput (PIO)\n",
"\n",
"\n",
"* MicroPython (https://datasheets.raspberrypi.org/pico/raspberry-pi-pico-python-sdk.pdf)\n",
"* TinkerTechTrove [[github]](https://github.com/tinkertechtrove/pico-pi-playinghttps://github.com/tinkertechtrove/pico-pi-playing) [[youtube]](https://www.youtube.com/channel/UCnoBIijHK7NnCBVpUojYFTA/videoshttps://www.youtube.com/channel/UCnoBIijHK7NnCBVpUojYFTA/videos)\n",
"* [Raspberry Pi Pico PIO - Ep. 1 - Overview with Pull, Out, and Parallel Port](https://youtu.be/YafifJLNr6I)"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "edd1033c-afc8-43af-81b3-ed2aceb80689",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found serial ports: /dev/cu.usbmodem14201, /dev/cu.Bluetooth-Incoming-Port \n",
"\u001b[34mConnecting to --port=/dev/cu.usbmodem14201 --baud=115200 \u001b[0m\n",
"\u001b[34mReady.\n",
"\u001b[0m.."
]
}
],
"source": [
"%serialconnect\n",
"\n",
"from machine import Pin\n",
"from rp2 import PIO, StateMachine, asm_pio\n",
"from time import sleep\n",
"import sys\n",
"\n",
"@asm_pio(set_init=(PIO.OUT_LOW,) * 4)\n",
"def prog():\n",
" wrap_target()\n",
" set(pins, 8) [31] # 8\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" set(pins, 4) [31] # 4\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" set(pins, 2) [31] # 2\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" set(pins, 1) [31] # 1\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" wrap()\n",
" \n",
"\n",
"sm = StateMachine(0, prog, freq=100000, set_base=Pin(14))\n",
"\n",
"\n",
"sm.active(1)\n",
"sleep(10)\n",
"sm.active(0)\n",
"sm.exec(\"set(pins,0)\")"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "444ee411-1f2d-40fc-9fe3-d2e8332636f3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found serial ports: /dev/cu.usbmodem14301, /dev/cu.Bluetooth-Incoming-Port \n",
"\u001b[34mConnecting to --port=/dev/cu.usbmodem14301 --baud=115200 \u001b[0m\n",
"\u001b[34mReady.\n",
"\u001b[0m.."
]
}
],
"source": [
"%serialconnect\n",
"\n",
"from machine import Pin\n",
"from rp2 import PIO, StateMachine, asm_pio\n",
"from time import sleep\n",
"import sys\n",
"\n",
"@asm_pio(set_init=(PIO.OUT_LOW,) * 4,\n",
" out_init=(PIO.OUT_HIGH,) * 4,\n",
" out_shiftdir=PIO.SHIFT_LEFT)\n",
"def prog():\n",
" pull()\n",
" mov(y, osr) # step pattern\n",
" \n",
" pull()\n",
" mov(x, osr) # num steps\n",
" \n",
" jmp(not_x, \"end\")\n",
" \n",
" label(\"loop\")\n",
" jmp(not_osre, \"step\") # loop pattern if exhausted\n",
" mov(osr, y)\n",
" \n",
" label(\"step\")\n",
" out(pins, 4) [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
"\n",
" jmp(x_dec,\"loop\")\n",
" label(\"end\")\n",
" set(pins, 8) [31] # 8\n",
"\n",
"sm = StateMachine(0, prog, freq=10000, set_base=Pin(14), out_base=Pin(14))\n",
"\n",
"sm.active(1)\n",
"sm.put(2216789025) #1000 0100 0010 0001 1000010000100001\n",
"sm.put(1000)\n",
"sleep(10)\n",
"sm.active(0)\n",
"sm.exec(\"set(pins,0)\")\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "619d22f1-26aa-4b53-bedd-014ef7560fed",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"serial exception on close write failed: [Errno 6] Device not configured\n",
"Found serial ports: /dev/cu.usbmodem14301, /dev/cu.Bluetooth-Incoming-Port \n",
"\u001b[34mConnecting to --port=/dev/cu.usbmodem14301 --baud=115200 \u001b[0m\n",
"\u001b[34mReady.\n",
"\u001b[0m......"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"\", line 56, in \n",
"KeyboardInterrupt: \n"
]
}
],
"source": [
"%serialconnect\n",
"\n",
"from machine import Pin\n",
"from rp2 import PIO, StateMachine, asm_pio\n",
"from time import sleep\n",
"import sys\n",
"\n",
"@asm_pio(set_init=(PIO.OUT_LOW,) * 4,\n",
" out_init=(PIO.OUT_LOW,) * 4,\n",
" out_shiftdir=PIO.SHIFT_RIGHT,\n",
" in_shiftdir=PIO.SHIFT_LEFT)\n",
"def prog():\n",
" pull()\n",
" mov(x, osr) # num steps\n",
" \n",
" pull()\n",
" mov(y, osr) # step pattern\n",
" \n",
" jmp(not_x, \"end\")\n",
" \n",
" label(\"loop\")\n",
" jmp(not_osre, \"step\") # loop pattern if exhausted\n",
" mov(osr, y)\n",
" \n",
" label(\"step\")\n",
" out(pins, 4) [31]\n",
" \n",
" jmp(x_dec,\"loop\")\n",
" label(\"end\")\n",
" \n",
" irq(rel(0))\n",
"\n",
"\n",
"sm = StateMachine(0, prog, freq=10000, set_base=Pin(14), out_base=Pin(14))\n",
"data = [(1,2,4,8),(2,4,8,1),(4,8,1,2),(8,1,2,4)]\n",
"steps = 0\n",
"\n",
"def turn(sm):\n",
" global steps\n",
" global data\n",
" \n",
" idx = steps % 4\n",
" a = data[idx][0] | (data[idx][1] << 4) | (data[idx][2] << 8) | (data[idx][3] << 12)\n",
" a = a << 16 | a\n",
" \n",
" #print(\"{0:b}\".format(a))\n",
" sleep(1)\n",
" \n",
" sm.put(500)\n",
" sm.put(a)\n",
" \n",
" steps += 500\n",
"\n",
"sm.irq(turn)\n",
"sm.active(1)\n",
"turn(sm)\n",
"\n",
"sleep(50)\n",
"print(\"done\")\n",
"sm.active(0)\n",
"sm.exec(\"set(pins,0)\")\n"
]
},
{
"cell_type": "code",
"execution_count": 58,
"id": "3e1f661e-c2c2-4849-b373-4f4bad4dbb9d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found serial ports: /dev/cu.usbmodem14201, /dev/cu.Bluetooth-Incoming-Port \n",
"\u001b[34mConnecting to --port=/dev/cu.usbmodem14201 --baud=115200 \u001b[0m\n",
"\u001b[34mReady.\n",
"\u001b[0m256\n",
"512\n",
"256\n",
"512\n",
"256\n",
"512\n",
"256\n",
"512\n",
"256\n",
"512\n",
"256\n",
"512\n",
"256\n",
"512\n",
"256\n"
]
}
],
"source": [
"%serialconnect\n",
" \n",
"import time\n",
"import rp2\n",
"\n",
"@rp2.asm_pio()\n",
"def irq_test():\n",
" wrap_target()\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" irq(0)\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" nop() [31]\n",
" irq(1)\n",
" wrap()\n",
"\n",
"\n",
"rp2.PIO(0).irq(lambda pio: print(pio.irq().flags()))\n",
"#rp2.PIO(1).irq(lambda pio: print(\"1\"))\n",
"\n",
"sm = rp2.StateMachine(0, irq_test, freq=2000)\n",
"sm1 = rp2.StateMachine(1, irq_test, freq=2000)\n",
"sm.active(1)\n",
"#sm1.active(1)\n",
"time.sleep(1)\n",
"sm.active(0)\n",
"sm1.active(0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59bf4a10-a689-4eb4-9b0c-aa7697a5a4c9",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "MicroPython - USB",
"language": "micropython",
"name": "micropython"
},
"language_info": {
"codemirror_mode": "python",
"file_extension": ".py",
"mimetype": "text/python",
"name": "micropython"
}
},
"nbformat": 4,
"nbformat_minor": 5
}