如今,越来越多的人选择电动滑板作为代步工具或娱乐方式,市场上也涌现出越来越多的电动滑板产品。
(图片来源:https://www.backfireboards.com/products/backfire-zealot-x-belt-drive-electric-skateboard)
(图片来源:https://boostedusa.com/collections/evolve-skateboards)
那么,这是不是就意味着传统的"原始"滑板会被冷落呢?
我们团队里有几个滑板爱好者,他们认为可以发明一种滑板助力器,让原始滑板焕发新生命,摆脱对人体能量的依赖。安装滑板助力器之后,我们的原始滑板就可以得到电动助力,延长滑行距离,提高速度,并且不再依赖个人体力续航。同时也要保留传统滑板的操作方式,尽量不改变滑板玩家的操作习惯(比如上下板的动作),这样既能享受电动滑板的便利和效率,又能保留一些“原始情结”。
既然要做电动的助力器,那么选择一个合适的电机是少不了的。经过对比,我们选择了robodyno一体化可编程电机Pro-P12型。该电机采用驱控一体设计,全钢齿轮组1:12.45行星减速器,14位绝对位置磁编码器,IIC总线接口,额定功率35W,最高转速310rpm,堵转扭矩2.79N·m。借助一块小小的通信模块与电脑连接后,即可使用python语言直接编程,非常适合开发。
曾经用于开发机械臂、机器狗、智能车等开发项目。
电机选定后,接下来的关键就是轮子,我们从网上挑选了一款搅拌机用的摩擦轮,改造后便可作为助力器的轮子。
接下来准备好全套零部件及装配工具。
去掉摩擦轮原先的轮毂并安装上Robodyno Pro-P12电机配套的轮毂零件,以及3D打印的轮毂外圈,轮子部分就完成了。
然后给电机装上轮架侧板,再和轮子装配在一起。
将轴承、轮架顶板、轮架外板装上(在上图中,轮架顶板和轮架外板已经装成了L型,即图中右侧第一个零件),助力轮便装配完成了。
最后,在滑板板尾和合适位置上打孔,并将助力轮安装在滑板上面。
装配完成。
下面是我们选择的控制器,采用ESP32-Pico-D4芯片,安装了一块OLED屏幕(用于显示操作界面)以及一个旋钮按键(用于操作)。这个控制器里已经集成一个电机通信模块,因此不需要额外安装电机通信模块了。
我们编写了一套固件,让它可以用于控制滑板。
通过拨动旋钮,可以控制助力轮的输出力矩,数字越大,力矩越大,旋转速度也就越快,使用者可以根据自身体重和期望的起步速度来选择合适的数值。
点击下方的正方形按钮,就可以控制助力轮启停。
然后接上11.1V动力电池,并将电池和控制器用轧带固定在滑板上。
我们最期待的功能是要让滑板像普通滑板一样,蹬一脚就能走,用脚摩擦地面就能停。换句话说,就是开机后,电机在未受力的情况下静止,受到一定的推力后开始转动,受到一定的阻力后停止转动。Robodyno Pro电机使用python即可编程,非常方便。
程序源代码如下:
main.py
Python import lvgl as lv from ili9XXX import ili9488, LANDSCAPE SCR_WIDTH = 480 SCR_HEIGHT = 320 disp = ili9488(mosi=4, miso=18, clk=5, cs=14, dc=13, rst=12, \ power=-1, backlight=15, backlight_on=1, factor=32, \ width=SCR_WIDTH, height=SCR_HEIGHT, rot=LANDSCAPE) from ft6x36 import ft6x36 touch_rst = Pin(2, Pin.OUT) touch_rst.on() touch = ft6x36(sda=27, scl=26, width=SCR_HEIGHT, height=SCR_WIDTH, inv_x=True, swap_xy=True) style = lv.style_t() style.init() style.set_radius(0) style.set_bg_color(lv.color_hex(0x2E3234)) style.set_text_color(lv.color_hex(0xC2C1C1)) style.set_border_width(0) screen = lv.scr_act() screen.add_style(style, 0) from motor_controller import MotorController controller = MotorController(screen) import time while True: controller.update() time.sleep(0.01)boot.py
Python # This file is executed on every boot (including wake-boot from deepsleep) #import esp #esp.osdebug(None) #import webrepl #webrepl.start() from machine import Pin import time power = Pin(32, Pin.OUT) _roller_btn = Pin(36, Pin.IN) _enc_a = Pin(37, Pin.IN) _enc_b = Pin(38, Pin.IN) _a0 = 1 _b0 = 1 _ab0 = 1 enc_pos = 0 def on_enc_change(pin): global _a0, _b0, _ab0, enc_pos a = _enc_a.value() b = _enc_b.value() if not a == _a0: _a0 = a if not b == _b0: _b0 = b enc_pos += 1 if a == b else -1 if not (a == b) == _ab0: enc_pos += 1 if a == b else -1 _ab0 = (a == b) _enc_a.irq(handler = on_enc_change) time.sleep(2) power.on()encoder.py
Python from machine import Pin class Encoder: def __init__(self): self.btn_pin = Pin(36, Pin.IN) self.enc_a = Pin(37, Pin.IN) self.enc_b = Pin(38, Pin.IN) self.a0 = 1 self.b0 = 1 self.ab0 = 1 self.pos = 0 self.enc_a.irq(handler = self.on_change) def on_change(self, pin): a = self.enc_a.value() b = self.enc_b.value() if not a == self.a0: self.a0 = a if not b == self.b0: self.b0 = b self.pos += 1 if a == b else -1 if not (a == b) == self.ab0: self.pos += 1 if a == b else -1 self.ab0 = (a == b)motor_controller.py
Python from encoder import Encoder import lvgl as lv from machine import Pin import time from robodyno import * class MotorController: def __init__(self, container): self.running_flag = False self.encoder = Encoder() self.container = container self.power = Pin(32, Pin.OUT) self.can = CanBus() self.motor = Motor(self.can, 0x10, ROBODYNO_PRO_12) self.motor.torque_mode() self.max_torque = 3 self.draw_power_btn() self.draw_bar() self.draw_btn() def draw_bar(self): self.label = lv.label(self.container) self.label.set_text(str(self.encoder.pos)) self.label.set_style_text_font(lv.font_montserrat_24, 0) self.label.align(lv.ALIGN.TOP_MID, 0, 50) self.bar = lv.bar(self.container) self.bar.set_size(200, 20) self.bar.align(lv.ALIGN.TOP_MID, 0, 90) self.bar.set_value(self.encoder.pos, lv.ANIM.OFF) def draw_btn(self): self.btn = lv.btn(self.container) self.btn.align(lv.ALIGN.TOP_MID, 0, 150) self.btn.set_size(100,100) self.btn.set_style_bg_color(lv.color_hex(0x4EB181), 0) self.btn_label = lv.label(self.btn) self.btn_label.set_style_text_font(lv.font_montserrat_24, 0) self.btn_label.set_text(lv.SYMBOL.PLAY) self.btn_label.center() self.btn.add_event_cb(self.on_btn_click, lv.EVENT.CLICKED, None) def on_btn_click(self, e): btn = e.get_target() label = btn.get_child(0) if self.running_flag: self.motor.set_torque(0) self.motor.disable() self.running_flag = False btn.set_style_bg_color(lv.color_hex(0x4EB181), 0) label.set_text(lv.SYMBOL.PLAY) else: self.motor.set_torque(0) self.motor.enable() self.running_flag = True btn.set_style_bg_color(lv.color_hex(0xF44336), 0) label.set_text(lv.SYMBOL.STOP) def draw_power_btn(self): self.power_btn = lv.btn(self.container) self.power_btn.align(lv.ALIGN.TOP_RIGHT, -40, 40) self.power_btn.set_size(40,40) self.power_btn.set_style_bg_color(lv.color_hex(0xF44336), 0) self.power_btn_label = lv.label(self.power_btn) self.power_btn_label.set_style_text_font(lv.font_montserrat_24, 0) self.power_btn_label.set_text(lv.SYMBOL.POWER) self.power_btn_label.center() self.power_btn.add_event_cb(self.on_power_off, lv.EVENT.CLICKED, None) def on_power_off(self, e): self.power.off() def update(self): if self.encoder.pos > 100: self.encoder.pos = 100 if self.encoder.pos < 0: self.encoder.pos = 0 self.bar.set_value(self.encoder.pos, lv.ANIM.OFF) self.label.set_text(str(self.encoder.pos)) if self.running_flag: b = 0.002 + self.encoder.pos / 100 * 0.3 vel = self.motor.get_vel() if vel is not None: vel = -vel torque = 0 if vel > 3: torque = max(-0.107 - b * vel, -self.max_torque) elif vel < -3: torque = min(0.1 - b * vel, self.max_torque) self.motor.set_torque(torque)
将以上4个程序文件拷贝到控制器的内存中,即可被固件调用。
接下来测试一下起步
然后再试一下转弯,表现效果还不错。
最后试一下刹车。
这些稳如狗的表现自然是通过一次又一次的调试和数不清的翻车换来的。
现在,我们的报废滑板又获得赛博新生啦~(可以踩着它出去浪了)
本项目已经开源,资料可在下方链接处下载。如果您对本项目有任何建议,欢迎留言。
更多资料详见 【A004】滑板助力器项目