机器人制作开源方案 | 滑板助力器

我们可以用一块废滑板做些什么
如今,越来越多的人选择电动滑板作为代步工具或娱乐方式,市场上也涌现出越来越多的电动滑板产品。

(图片来源:Backfire Zealot X Belt Drive Electric Skateboard-- Backfire Boards


(图片来源:Electric Skateboards - Boosted USA
那么,这是不是就意味着传统的"原始"滑板会被冷落呢?

我们团队里有几个滑板爱好者,他们认为可以发明一种滑板助力器,让原始滑板焕发新生命,摆脱对人体能量的依赖。安装滑板助力器之后,我们的原始滑板就可以得到电动助力,延长滑行距离,提高速度,并且不再依赖个人体力续航。同时也要保留传统滑板的操作方式,尽量不改变滑板玩家的操作习惯(比如上下板的动作),这样既能享受电动滑板的便利和效率,又能保留一些"原始情结"。

既然要做电动的助力器,那么选择一个合适的电机是少不了的。经过对比,我们选择了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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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个程序文件拷贝到控制器的内存中,即可被固件调用。

接下来测试一下起步。

然后再试一下转弯,表现效果还不错。

最后试一下刹车。

这些稳如狗的表现自然是通过一次又一次的调试和数不清的翻车换来的。

现在,我们的报废滑板又获得赛博新生啦~(可以踩着它出去浪了)

本项目已经开源,资料可在下方链接处下载。如果您对本项目有任何建议,欢迎留言。
附:Robodyno Pro一体化可编程电机技术文档
附:Robodyno Pro一体化可编程电机购买链接

附:主要零件清单

|--------|--------------------------|--------|-----------------|
| 序号 | 零件名称 | 数量 | 备注 |
| 1 | 普通滑板 | 1 | |
| 2 | Robodyno Pro-P12一体化可编程电机 | 1 | |
| 3 | 搅拌机摩擦轮轮皮 | 1 | |
| 4 | Robodyno Pro轮毂零件 | 1 | |
| 5 | 3D打印轮毂外圈 | 1 | PLA |
| 6 | Robodyno Pro轮架外板 | 1 | 铝 |
| 7 | Robodyno Pro轮架侧板 | 1 | 铝 |
| 8 | Robodyno Pro轮架顶板 | 1 | 铝 |
| 9 | M2.5*15螺丝 | 9 | 不锈钢 |
| 10 | Robodyno Pro电机线 | 1 | |
| 11 | ESP32控制器 | 1 | ESP32-Pico-D4芯片 |
| 12 | TFT3.5通信转接板 | 1 | 安装在ESP32控制器外壳中 |

附:开源资料

|--------|--------------------|
| 资料清单 ||
| 序号 | 内容 |
| 1 | 【A004】-程序源代码 |
| 2 | 【A004】-电路原理图 |
| 3 | 【A004】-控制器电路板生产文件 |
| 4 | 【A004】-控制器外壳3D打印文件 |
| 5 | 控制器固件 |

更多资料内容下载详见 【A004】滑板助力器项目

相关推荐
SeaTunnel4 小时前
如何将SeaTunnel MySQL-CDC与Databend 高效整合?格式与方案全解析
数据库·mysql·开源·数据集成·seatunnel·databend
算家计算5 小时前
Meta开源V-JEPA 2:杨立昆颠覆生成式AI,世界模型性能碾压英伟达30倍
人工智能·开源
FIT2CLOUD飞致云6 小时前
干货分享|JumpServer PAM特权账号管理功能详解
开源
时序数据说6 小时前
时序数据库IoTDB数据模型建模实例详解
大数据·数据库·开源·时序数据库·iotdb
ajassi20006 小时前
开源 java android app 开发(十二)封库.aar
android·java·linux·开源
时序数据说6 小时前
时序数据库IoTDB结合SeaTunnel实现高效数据同步
大数据·数据库·开源·时序数据库·iotdb
天天打码6 小时前
Bootstrap Table开源的企业级数据表格集成
前端·开源·bootstrap
竹6688 小时前
群晖NAS如何使用docker安装雷池防火墙?
架构·开源
不念霉运8 小时前
开源生态新势能: 驱动国产 DevSecOps 与 AI 工程新进展
人工智能·开源·github·devops