Skip to content

Commit 67eccd1

Browse files
authored
Merge pull request #2839 from jedgarpark/servo-commander
first commit servo commander code
2 parents bc5742c + 749d43e commit 67eccd1

File tree

1 file changed

+212
-0
lines changed

1 file changed

+212
-0
lines changed

Servo_Commander/code.py

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# SPDX-FileCopyrightText: 2024 johnpark for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
'''
5+
Servo Commander
6+
- Feather Reverse TFT ESP32-S3 + two servos + two push encoders
7+
- test servo ranges with encoder rotation
8+
- store saved positions with enc button + D0, D1, D2 buttons
9+
- recall saved positions with D0, D1, D2
10+
- playback animation by pressing both push encoders
11+
'''
12+
13+
import time
14+
import board
15+
import displayio
16+
import terminalio
17+
from adafruit_display_text import label
18+
from adafruit_display_shapes.rect import Rect
19+
import rotaryio
20+
import pwmio
21+
from adafruit_motor import servo
22+
import keypad
23+
24+
# Define custom servo pulse range variables
25+
s_cfgs = [
26+
{'min_pulse': 600, 'max_pulse': 2400, 'min_ang': 10, 'max_ang': 170},
27+
{'min_pulse': 600, 'max_pulse': 2300, 'min_ang': 10, 'max_ang': 170}
28+
]
29+
30+
# Setup the PWM output for the servos
31+
pwm_pins = [board.D10, board.D11]
32+
servo_motors = []
33+
34+
for i, config in enumerate(s_cfgs):
35+
pwm = pwmio.PWMOut(pwm_pins[i], duty_cycle=2**15, frequency=50)
36+
servo_motor = servo.Servo(pwm, min_pulse=config['min_pulse'], max_pulse=config['max_pulse'])
37+
servo_motors.append(servo_motor)
38+
servo_motor.angle = 90
39+
40+
s_saves = [
41+
[s_cfgs[0]['min_ang'], 90, s_cfgs[0]['max_ang']],
42+
[s_cfgs[1]['min_ang'], 90, s_cfgs[1]['max_ang']]
43+
]
44+
45+
# Setup the rotary encs
46+
encs = [
47+
rotaryio.IncrementalEncoder(board.A2, board.A1),
48+
rotaryio.IncrementalEncoder(board.A5, board.A4)
49+
]
50+
51+
for enc in encs:
52+
enc.position = 90
53+
54+
last_positions = [enc.position for enc in encs]
55+
56+
# Setup the buttons
57+
enc_buttons = keypad.Keys((board.D13, board.D12), value_when_pressed=False, pull=True)
58+
tft_d0_button = keypad.Keys((board.D0,), value_when_pressed=False, pull=True)
59+
tft_buttons = keypad.Keys((board.D1, board.D2), value_when_pressed=True, pull=True)
60+
61+
def set_servo_angle(s_servo_motor, s_angle, s_enc, min_ang, max_ang):
62+
s_angle = min(max(s_angle, min_ang), max_ang)
63+
s_servo_motor.angle = s_angle
64+
s_enc.position = s_angle
65+
66+
def playback(mode, speed, steps):
67+
for k in range(3): # Loop through each save position (assuming 3 saves per motor)
68+
for m in range(len(servo_motors)):
69+
p_servo_motor = servo_motors[m]
70+
p_enc = encs[m]
71+
save = s_saves[m][k] # Get the k-th save position for the m-th motor
72+
73+
if mode:
74+
direction = 1 if save > enc.position else -1
75+
for p_angle in range(enc.position, save, direction * steps):
76+
p_servo_motor.angle = p_angle
77+
time.sleep(speed)
78+
p_enc.position = save
79+
else:
80+
servo_motor.angle = save
81+
time.sleep(0.75)
82+
83+
84+
# Setup the display
85+
display = board.DISPLAY
86+
group = displayio.Group()
87+
background_rect = Rect(0, 10, display.width, display.height - 10, fill=0x000010)
88+
group.append(background_rect)
89+
mid_bar = Rect(116, 0, 3, display.height, fill=0x00000)
90+
group.append(mid_bar)
91+
top_bar = Rect(0, 0, display.width, 20, fill=0x000000)
92+
group.append(top_bar)
93+
94+
FONT = terminalio.FONT
95+
TXTCOL = 0xFFFF00
96+
97+
# Create labels
98+
labels = []
99+
for i, config in enumerate(s_cfgs):
100+
lbl = label.Label(FONT, text=f"Pulse: {config['min_pulse']}-{config['max_pulse']}",color=TXTCOL,
101+
scale=1, anchor_point=(0, 0), anchored_position=(5 + 125 * i, 5))
102+
labels.append(lbl)
103+
group.append(lbl)
104+
105+
for i in range(2):
106+
lbl = label.Label(FONT, text="Angle:-", color=TXTCOL, scale=2, anchor_point=(0, 0),
107+
anchored_position=(4 + 126 * i, 24))
108+
labels.append(lbl)
109+
group.append(lbl)
110+
111+
for i in range(2):
112+
for j in range(3):
113+
lbl = label.Label(FONT, text=f"D{j}:{s_saves[i][j]}", color=TXTCOL, scale=2,
114+
anchor_point=(0, 0), anchored_position=(4 + i * 126, 48 + 24 * j))
115+
labels.append(lbl)
116+
group.append(lbl)
117+
118+
display.root_group = group
119+
120+
modifier1 = False
121+
modifier2 = False
122+
123+
print("[]-Servo Commander READY-[]")
124+
125+
while True:
126+
enc_button_event = enc_buttons.events.get()
127+
if enc_button_event:
128+
if enc_button_event.pressed:
129+
if enc_button_event.key_number == 0:
130+
modifier1 = True
131+
elif enc_button_event.key_number == 1:
132+
modifier2 = True
133+
134+
if enc_button_event.released:
135+
if enc_button_event.key_number == 0:
136+
modifier1 = False
137+
elif enc_button_event.key_number == 1:
138+
modifier2 = False
139+
140+
if modifier1 and modifier2:
141+
print("Playback")
142+
playback(True, 0.006, 2)
143+
144+
tft_d0_button_event = tft_d0_button.events.get()
145+
if tft_d0_button_event and tft_d0_button_event.pressed:
146+
if modifier1:
147+
s_saves[0][0] = min(max(encs[0].position, s_cfgs[0]['min_ang']), s_cfgs[0]['max_ang'])
148+
print("D0 save motor1:", s_saves[0][0])
149+
labels[4].text = f"D0:{s_saves[0][0]}"
150+
elif modifier2:
151+
s_saves[1][0] = min(max(encs[1].position, s_cfgs[1]['min_ang']), s_cfgs[1]['max_ang'])
152+
print("D0 save motor2:", s_saves[1][0])
153+
labels[7].text = f"D0:{s_saves[1][0]}"
154+
else:
155+
for i in range(len(servo_motors)):
156+
servo_motor = servo_motors[i]
157+
enc = encs[i]
158+
s_save = s_saves[i][0]
159+
config = s_cfgs[i]
160+
print(f"D0 recalled motor{i+1}:", s_save)
161+
set_servo_angle(servo_motor, s_save, enc, config['min_ang'], config['max_ang'])
162+
labels[2 + i].text = f"Angle:{s_save}"
163+
164+
tft_buttons_event = tft_buttons.events.get()
165+
if tft_buttons_event and tft_buttons_event.pressed:
166+
if tft_buttons_event.key_number == 0:
167+
if modifier1:
168+
s_saves[0][1] = min(max(encs[0].position,s_cfgs[0]['min_ang']),s_cfgs[0]['max_ang'])
169+
print("D1 save motor1:", s_saves[0][1])
170+
labels[5].text = f"D1:{s_saves[0][1]}"
171+
elif modifier2:
172+
s_saves[1][1] = min(max(encs[1].position,s_cfgs[1]['min_ang']),s_cfgs[1]['max_ang'])
173+
print("D1 save motor2:", s_saves[1][1])
174+
labels[8].text = f"D1:{s_saves[1][1]}"
175+
else:
176+
for i in range(len(servo_motors)):
177+
servo_motor = servo_motors[i]
178+
enc = encs[i]
179+
s_save = s_saves[i][1]
180+
config = s_cfgs[i]
181+
print(f"D1 recalled motor{i+1}:", s_save)
182+
set_servo_angle(servo_motor, s_save, enc, config['min_ang'], config['max_ang'])
183+
labels[2 + i].text = f"Angle:{s_save}"
184+
elif tft_buttons_event.key_number == 1:
185+
if modifier1:
186+
s_saves[0][2] = min(max(encs[0].position,s_cfgs[0]['min_ang']),s_cfgs[0]['max_ang'])
187+
print("D2 save motor1:", s_saves[0][2])
188+
labels[6].text = f"D2:{s_saves[0][2]}"
189+
elif modifier2:
190+
s_saves[1][2] = min(max(encs[1].position,s_cfgs[1]['min_ang']),s_cfgs[1]['max_ang'])
191+
print("D2 save motor2:", s_saves[1][2])
192+
labels[9].text = f"D2:{s_saves[1][2]}"
193+
else:
194+
for i in range(len(servo_motors)):
195+
servo_motor = servo_motors[i]
196+
enc = encs[i]
197+
s_save = s_saves[i][2]
198+
config = s_cfgs[i]
199+
print(f"D2 recalled motor{i+1}:", s_save)
200+
set_servo_angle(servo_motor, s_save, enc, config['min_ang'], config['max_ang'])
201+
labels[2 + i].text = f"Angle:{s_save}"
202+
203+
for i in range(len(servo_motors)):
204+
current_position = encs[i].position
205+
if current_position != last_positions[i]:
206+
config = s_cfgs[i]
207+
angle = min(max(current_position, config['min_ang']), config['max_ang'])
208+
servo_motor = servo_motors[i]
209+
enc = encs[i]
210+
set_servo_angle(servo_motor, angle, enc, config['min_ang'], config['max_ang'])
211+
labels[2 + i].text = f"Angle:{angle}"
212+
last_positions[i] = current_position

0 commit comments

Comments
 (0)