Skip to content

Commit 429d73c

Browse files
icyqwqlbuque
authored andcommitted
lib/module: Add GRBL module.
Signed-off-by: icyqwq <[email protected]>
1 parent f0f0e37 commit 429d73c

File tree

3 files changed

+277
-0
lines changed

3 files changed

+277
-0
lines changed

m5stack/libs/module/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"DisplayModule": "display",
1010
"DualKmeterModule": "dual_kmeter",
1111
"Encoder4MotorModule": "encoder4_motor",
12+
"GRBLModule": "grbl",
1213
"HMIModule": "hmi",
1314
"IotBaseCatmModule": "iot_base_catm",
1415
"LoraModule": "lora",

m5stack/libs/module/grbl.py

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
from .mbus import i2c1
6+
from .module_helper import ModuleError
7+
import struct
8+
from micropython import const
9+
import re, math
10+
import time
11+
12+
13+
class GRBLModule:
14+
"""
15+
16+
note:
17+
cn: GRBL 13.2 是 M5Stack 堆叠模块系列中的三轴步进电机驱动器模块。它采用 ATmega328P-AU 控制器,带有三组 DRV8825PWPR 步进电机驱动芯片控制方式,可以同时驱动三个双极步进电机。
18+
en: GRBL 13.2 is a three-axis stepper motor driver module in the M5Stack stacking module series. It uses an ATmega328P-AU controller with three sets of DRV8825PWPR stepper motor driver chip control ways, which can drive three bipolar steppers at the same time.
19+
20+
details:
21+
color: "#0FE6D7"
22+
link: https://docs.m5stack.com/en/module/grbl13.2
23+
image: https://static-cdn.m5stack.com/resource/docs/products/module/grbl13.2/grbl13.2_01.webp
24+
category: Module
25+
26+
example: |
27+
from module import GRBLModule
28+
motor = GRBLModule()
29+
30+
31+
"""
32+
33+
"""
34+
constant: Motor mode
35+
"""
36+
MODE_ABSOLUTE = const(0)
37+
MODE_RELATIVE = const(1)
38+
39+
def __init__(
40+
self,
41+
address: int = 0x70,
42+
):
43+
"""
44+
note:
45+
en: Initialize the GRBLModule.
46+
cn: 初始化GRBL模块。
47+
48+
params:
49+
address:
50+
note: The I2C address of the device.
51+
"""
52+
53+
self.i2c = i2c1
54+
self.addr = address
55+
56+
# Check if the devices are connected and accessible
57+
if self.addr not in self.i2c.scan():
58+
raise ModuleError("GRBLModule not found at I2C address 0x%02X" % self.addr)
59+
60+
self.mode = self.MODE_ABSOLUTE
61+
self.last_speed = 300
62+
63+
def g_code(self, command):
64+
"""
65+
note:
66+
en: Send the G-code command.
67+
cn: 发送G代码命令。
68+
69+
params:
70+
command:
71+
note: The G-code command.
72+
"""
73+
self.i2c.writeto(self.addr, command + "\n")
74+
return self.get_code_time(command)
75+
76+
def get_code_time(self, code) -> int:
77+
"""
78+
note:
79+
en: Get the time of the code.
80+
cn: 获取代码的时间。
81+
82+
params:
83+
code:
84+
note: The G-code command
85+
86+
return:
87+
note: The estimated time of the command.
88+
89+
"""
90+
x_value = re.search(r"X-*\d+", code)
91+
y_value = re.search(r"Y-*\d+", code)
92+
z_value = re.search(r"Z-*\d+", code)
93+
speed = re.search(r"F\d+", code)
94+
hold = re.search(r"P\d+", code)
95+
hold = float(hold.group(0)[1:]) * 1000 if hold else 0
96+
x_value = int(x_value.group(0)[1:]) if x_value else 0
97+
y_value = int(y_value.group(0)[1:]) if y_value else 0
98+
z_value = int(z_value.group(0)[1:]) if z_value else 0
99+
self.last_speed = int(speed.group(0)[1:]) if speed else self.last_speed
100+
time = (
101+
math.sqrt(x_value * x_value + y_value * y_value + z_value * z_value)
102+
* 60
103+
* 1000
104+
/ self.last_speed
105+
)
106+
time += hold
107+
return int(time)
108+
109+
def turn(self, x=None, y=None, z=None, speed=None) -> int:
110+
"""
111+
note:
112+
en: Turn the motor to a specific position.
113+
cn: 将电机转到特定位置。
114+
115+
params:
116+
x:
117+
note: The position of the X motor, 1.6=360°.
118+
y:
119+
note: The position of the Y motor, 1.6=360°.
120+
z:
121+
note: The position of the Z motor, 1.6=360°.
122+
speed:
123+
note: The speed of the motor.
124+
"""
125+
command = "G1"
126+
command += " X{}".format(x) if x is not None else ""
127+
command += " Y{}".format(y) if y is not None else ""
128+
command += " Z{}".format(z) if z is not None else ""
129+
command += " F{}".format(speed) if speed else ""
130+
self.last_speed = speed if speed else self.last_speed
131+
return self.g_code(command)
132+
133+
def set_mode(self, mode):
134+
"""
135+
note:
136+
en: Set the mode of the motor.
137+
cn: 设置电机的模式。
138+
139+
params:
140+
mode:
141+
note: The mode of the motor.
142+
field: dropdown
143+
options:
144+
Absolute: GRBLModule.MODE_ABSOLUTE
145+
Relative: GRBLModule.MODE_RELATIVE
146+
"""
147+
self.mode = mode
148+
if mode == self.MODE_ABSOLUTE:
149+
return self.g_code("G90")
150+
else:
151+
return self.g_code("G91")
152+
153+
def init(self, x_step=None, y_step=None, z_step=None, acc=None):
154+
"""
155+
note:
156+
en: Initialize the motor.
157+
cn: 初始化电机。
158+
159+
params:
160+
x_step:
161+
note: The step of the X motor.
162+
y_step:
163+
note: The step of the Y motor.
164+
z_step:
165+
note: The step of the Z motor.
166+
acc:
167+
note: The acceleration of the motor.
168+
"""
169+
if x_step:
170+
self.i2c.writeto(self.addr, "$0={}\n".format(x_step))
171+
time.sleep(0.01)
172+
if y_step:
173+
self.i2c.writeto(self.addr, "$1={}\n".format(y_step))
174+
time.sleep(0.01)
175+
if z_step:
176+
self.i2c.writeto(self.addr, "$2={}\n".format(z_step))
177+
time.sleep(0.01)
178+
if acc:
179+
self.i2c.writeto(self.addr, "$8={}\n".format(acc))
180+
181+
def flush(self):
182+
"""
183+
note:
184+
en: Flush the buffer.
185+
cn: 清空缓冲区。
186+
"""
187+
while True:
188+
data = self.i2c.readfrom(self.addr, 10)
189+
if data[-1] == 255:
190+
break
191+
192+
def get_message(self) -> str:
193+
"""
194+
note:
195+
en: Get the message.
196+
cn: 获取消息。
197+
return:
198+
note: The message string.
199+
"""
200+
i2c_data = ""
201+
while True:
202+
data = self.i2c.readfrom(self.addr, 10)
203+
if data[-1] == 255:
204+
i2c_data += data[: data.find(b"\x00")].decode()
205+
break
206+
i2c_data += data.decode()
207+
return i2c_data
208+
209+
def get_status(self) -> str:
210+
"""
211+
note:
212+
en: Get the status.
213+
cn: 获取状态。
214+
return:
215+
note: The status string.
216+
"""
217+
self.flush()
218+
self.i2c.writeto(self.addr, "@")
219+
return self.get_message()
220+
221+
def get_idle_state(self) -> bool:
222+
"""
223+
note:
224+
en: Get the idle state.
225+
cn: 获取空闲状态。
226+
return:
227+
note: The idle state.
228+
"""
229+
return self.get_status()[0] == "I"
230+
231+
def get_lock_state(self) -> bool:
232+
"""
233+
note:
234+
en: Get the lock state.
235+
cn: 获取锁定状态。
236+
return:
237+
note: The lock state.
238+
"""
239+
return self.get_status()[0] == "A"
240+
241+
def wait_idle(self):
242+
"""
243+
note:
244+
en: Wait until the motor is idle.
245+
cn: 等待电机空闲。
246+
"""
247+
self.flush()
248+
while not self.get_idle_state():
249+
time.sleep(0.1)
250+
251+
def unlock_alarm_state(self):
252+
"""
253+
note:
254+
en: Unlock the alarm state.
255+
cn: 解锁报警状态。
256+
"""
257+
self.i2c.writeto(self.addr, b"\x18")
258+
time.sleep(0.01)
259+
self.i2c.writeto(self.addr, "$X\r\n")
260+
261+
def lock(self):
262+
"""
263+
note:
264+
en: Lock the motor.
265+
cn: 锁定电机。
266+
"""
267+
self.g_code("$7=255")
268+
269+
def unlock(self):
270+
"""
271+
note:
272+
en: Unlock the motor.
273+
cn: 解锁电机。
274+
"""
275+
self.g_code("$7=25")

m5stack/libs/module/manifest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"display.py",
1111
"dual_kmeter.py",
1212
"encoder4_motor.py",
13+
"grbl.py",
1314
"hmi.py",
1415
"iot_base_catm.py",
1516
"lora.py",

0 commit comments

Comments
 (0)