|
| 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") |
0 commit comments