036 - MicroPython TechNotes: Stepper Motor

Introduction

In this article, I will talk about the STEPPER MOTOR with ESP32 using MicroPython.

Bill Of Materials

  1. ESP32 development board.
  2. Gorillacell ESP32 shield.
  3. Stepper motor set which is consist of (1) Stepper Motor Driver module, (2) generic stepper motor, (3) 9V external power supply, and the (4) wire connectors between the driver module and the stepper motor.
  4. 16×2 LCD module
  5. Rotary Encoder module
  6. and of course dupont wires for attaching the said modules.

Pinout

Pinout of stepper module.

  1. GND – for the ground pin.
  2. VCC – for the supply voltage for the A4988 driver chip.
  3. DIR – for setting the direction of rotation. A value of logic 0, sets a clockwise direction while a value of logic 1, sets a counter-clockwise direction of rotation.
  4. STEP – for enabling / disabling the power applied to the motor.
  5. Microstep Slide Switch:
  6. Full-step: it takes 200 steps to complete 1 revolution.
  7. Half-step: it takes 400 steps to complete 1 revolution.

Hardware Instruction

  1. Attach the ESP32 board on top of the ESP32 shield and make sure that the USB port are on the same side.
  2. Attach the dupont wires to the modules by following the color coding which is black for the ground, red for the VCC, and yellow and the following colors for the signal pins.
  3. Attach the other end of the dupont wires to the ESP32 shield by matching the colors of the wires to the colors of the pin headers that is black to black, red to red, yellow and the following colors to the yellow pin headers.
  4. Power the ESP32 shield with an external power supply through a type-C USB cable and make sure that the slide switch is set to ON state.
  5. Connect the ESP32 to the computer by attaching a micro-USB cable.

Software Instruction

  1. Copy the source code provided below.
  2. Please feel free to modify it according to your liking.
  3. Enjoy and please let me know of your progress.

Video Demonstration

Call To Action

For any concern, write your message in the comment section.

You might also like to support my journey on Youtube by Subscribing. Click this to Subscribe to TechToTinker.

Thank you and have a good days ahead.

See you,

– George Bantique | tech.to.tinker@gmail.com

Source Code

1. Example # 1, exploring the basics:

 1# More details can be found in TechToTinker.blogspot.com 
 2# George Bantique | tech.to.tinker@gmail.com
 3
 4from machine import Pin
 5from time import sleep_ms
 6from time import sleep_us
 7
 8
 9def map(x, in_min, in_max, out_min, out_max): 
10    return int((x - in_min) * (out_max - out_min) /
11               (in_max - in_min) + out_min)
12    
13class GORILLACELL_STEPMOTOR:
14    def __init__(self, step_pin, dir_pin):
15        self.step = Pin(step_pin, Pin.OUT)
16        self.dir = Pin(dir_pin, Pin.OUT)
17
18    def rotate(self, angle=0, rotation='cw'):
19        num_of_steps = map(angle, 0, 360, 0, 200)
20        if rotation=='cw':
21            self.dir.value(0)
22            for i in range(0,num_of_steps,1):
23               self.step.value(1)
24               sleep_us(500)
25               self.step.value(0)
26               sleep_us(500)
27        if rotation=='ccw':
28            self.dir.value(1)
29            for i in range(num_of_steps-1,-1,-1):
30               self.step.value(1)
31               sleep_us(500)
32               self.step.value(0)
33               sleep_us(500)       
34
35stepper = GORILLACELL_STEPMOTOR(step_pin=19, dir_pin=18)
36
37
38# The following lines of codes can be tested using the REPL:
39# 1. To rotate the stepper motor in clockwise direction:
40# stepper.rotate(360, 'cw')
41# The first parameter, sets the angle of rotation
42# The second parameter, sets the direction
43# 2. To rotate it in counter clockwise direction:
44# stepper.rotate(360, 'ccw')

2. Example # 2, controlling the step motor through a menu system displayed in the 16×2 and a rotary encoder for navigating the menu:

  1# More details can be found in TechToTinker.blogspot.com 
  2# George Bantique | tech.to.tinker@gmail.com
  3
  4from machine import Pin
  5from time import ticks_ms
  6from time import sleep_ms
  7from time import sleep_us
  8from machine import SoftI2C
  9from i2c_lcd import I2cLcd
 10from rotary_irq import RotaryIRQ
 11
 12
 13def map(x, in_min, in_max, out_min, out_max): 
 14    return int((x - in_min) * (out_max - out_min) /
 15               (in_max - in_min) + out_min)
 16    
 17class GORILLACELL_STEPMOTOR:
 18    def __init__(self, step_pin, dir_pin):
 19        self.step = Pin(step_pin, Pin.OUT)
 20        self.dir = Pin(dir_pin, Pin.OUT)
 21
 22    def rotate(self, angle=0, rotation='cw'):
 23        num_of_steps = map(angle, 0, 360, 0, 200)
 24        if rotation=='cw':
 25            self.dir.value(0)
 26            for i in range(0,num_of_steps,1):
 27               self.step.value(1)
 28               sleep_us(500)
 29               self.step.value(0)
 30               sleep_us(500)
 31        if rotation=='ccw':
 32            self.dir.value(1)
 33            for i in range(num_of_steps-1,-1,-1):
 34               self.step.value(1)
 35               sleep_us(500)
 36               self.step.value(0)
 37               sleep_us(500)       
 38
 39led = Pin(2, Pin.OUT)
 40stepper = GORILLACELL_STEPMOTOR(step_pin=19, dir_pin=18)
 41i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=400000) 
 42lcd = I2cLcd(i2c, 0x20, 2, 16)
 43r = RotaryIRQ(pin_num_clk=32, 
 44              pin_num_dt=33, 
 45              min_val=0, 
 46              max_val=19, 
 47              reverse=True, 
 48              range_mode=RotaryIRQ.RANGE_WRAP)
 49rsw = Pin(34, Pin.IN)              
 50val_old = r.value()
 51isRotaryEncoder = False
 52isPress = False
 53working_idx = 0 # holds the menu index
 54curr_direction = 0 # 0-cw, 1-ccw
 55curr_angle = 360
 56curr_action = 0 # 0-do not execute, 1-execute
 57
 58# get the current time
 59prev_time = ticks_ms()
 60
 61menus = ['Edit Direction',
 62         'Edit Angle',
 63         'Set, Move!',
 64         'Freestyle']
 65
 66# Prints a string of characters in the LCD
 67#    text - the string you want to print
 68#    x - x position
 69#    y - y position
 70def print_line(text, x, y):
 71    # Calculate the start of character display
 72    start = 8-len(text)//2
 73    # Calculate the end of character display
 74    end = start + len(text)
 75    
 76    # Clears characters before the start position
 77    lcd.move_to(x,y)
 78    for i in range(x,start,1):
 79        lcd.putchar(' ')
 80    
 81    # Clears characters after the end position
 82    lcd.move_to(end,y)
 83    for i in range(end,15,1):
 84        lcd.putchar(' ')
 85    
 86    # Print the desired display
 87    lcd.move_to(start,y)
 88    lcd.putstr(text)
 89    
 90# Performs updating the menu display
 91# according to the rotary encoder value
 92def process_menu(rotary_dir=0):
 93    # Global variables here if needed to be edited,
 94    # else, no need to declare as global here.
 95    global working_idx
 96    global curr_direction
 97    global curr_angle
 98    global curr_action
 99
100    
101    # If the rotary value is less than -1 ie:-2,-3,etc:
102    #    rotary value = -1
103    # If the rotary value is more than 1:
104    #    rotary value = 1
105    # Else
106    #    rotary value is not modified
107    if rotary_dir < -1:
108        rotary_dir = -1
109    elif rotary_dir > 1:
110        rotary_dir = 1
111    
112    # DISPLAY ONLY MODE
113    if isPress==False:
114        # Calculate the working index
115        # based on rotary encoder value.
116        working_idx += rotary_dir
117        if working_idx < 0:
118            working_idx = 0
119        elif working_idx > len(menus) - 1:
120            working_idx = len(menus) - 1
121
122        # Check if there is menu available in left
123        # If true, display < character
124        # If false, display none
125        lcd.move_to(0,0)
126        if working_idx==0:
127            lcd.putchar(' ')
128        else:
129            lcd.putchar('<')
130
131        # Print the menu based on working index value
132        print_line(menus[working_idx],1,0)
133        
134        # Checks if there is menu available in right
135        # If true, display > character
136        # If false, display none
137        lcd.move_to(15,0)
138        if working_idx==len(menus)-1:
139            lcd.putchar(' ')
140        else:
141            lcd.putchar('>')
142        
143        # ------------------------
144        # Process menu selections:
145        if working_idx==0:
146            # Display current direction
147            curr_dir = ""
148            if curr_direction==0:
149                curr_dir = "Clockwise"
150            else: # curr_direction==1:
151                curr_dir = "Anti-clockwise"
152            print_line(curr_dir,1,1)
153        elif working_idx==1:
154            # Display current angle
155            print_line(str(curr_angle),1,1)
156        elif working_idx==2:
157            curr_act = ""
158            if curr_action==0:
159                curr_act = "Status: Stop"
160            else:
161                curr_act = "Status: Go"
162            print_line(curr_act,1,1)
163        elif working_idx==3:
164            print_line("Press it",1,1)
165        else:
166            print('where is this?')
167    # CONFIGURATION MODE
168    elif isPress==True:
169        if working_idx==0:
170            # Edit Direction
171            if rotary_dir!=0:
172                curr_direction = not curr_direction
173            curr_dir = ""
174            if curr_direction==0:
175                curr_dir = "Clockwise"
176            else: # curr_direction==1:
177                curr_dir = "Anti-clockwise"
178            print_line(curr_dir,1,1)
179        elif working_idx==1:
180            # Display current angle
181            if rotary_dir!=0:
182                curr_angle+=rotary_dir
183            print_line(str(curr_angle),1,1) 
184        elif working_idx==2:
185            if rotary_dir!=0:
186                curr_action = not curr_action
187            curr_act = ""
188            if curr_action==0:
189                curr_act = "Status: Stop"
190            else:
191                curr_act = "Status: Go"
192            print_line(curr_act,1,1)
193        elif working_idx==3:
194            if rotary_dir < 0:
195                stepper.rotate(rotary_dir * -10, 'ccw')
196            elif rotary_dir > 0:
197                stepper.rotate(rotary_dir * 10, 'cw')  
198        else:
199            print('where is this?')
200# Prints the initial menus
201process_menu()
202
203while True:
204    if curr_action==1 and isSaved==True: # status: go
205        curr_dir = ""
206        if curr_direction==0: # cw
207            curr_dir = "cw"
208        else:
209            curr_dir = "ccw"
210        stepper.rotate(curr_angle, curr_dir)
211        curr_action=0 # status: stop
212        process_menu()    
213    
214    # Creates 200 ms interval
215    if ticks_ms() - prev_time  >= 200:
216        # Checks only for the switch when index is more than 2
217        # If switch is press, toggle the state of isPress variable
218        # If isPress is True, config for editing
219        # If isPress is False back again, save the configs.
220        if rsw.value()==1:
221            isPress = not isPress
222
223            if isPress==True:
224                process_menu()
225                isSaved=False
226            if isPress==False and isSaved==False:
227                isSaved=True
228        
229        # Read the rotary encoder values for processing
230        val_new = r.value()
231        if val_old != val_new:
232            if val_old == 0 and val_new == 19:
233                val_dif = -1
234            elif val_old == 19 and val_new == 0:
235                val_dif = 1
236            else:
237                val_dif = val_new - val_old
238            print(val_dif)
239            process_menu(val_dif)
240            val_old = val_new
241        
242        # Blink the onboard LED during config mode
243        if isPress:
244            led.value(not led.value())
245        else:
246            led.value(0)
247        
248        # Save the current timer counter
249        prev_time = ticks_ms()

3. Copy the lcd_api.py from:

https://techtotinker.com/008-micropython-technotes-16×2-lcd/

4. Copy the i2c_lcd.py from:

https://techtotinker.com/008-micropython-technotes-16×2-lcd/

5. Copy the rotary.py from:

https://techtotinker.com/027-micropython-technotes-rotary-encoder/

6. Copy the rotary_irq.py from:

https://techtotinker.com/027-micropython-technotes-rotary-encoder/

References And Credits

  1. Purchase your Gorillacell ESP32 development kit at: https://gorillacell.kr


Posts in this series



No comments yet!

GitHub-flavored Markdown & a sane subset of HTML is supported.