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

我们可以用一块废滑板做些什么   

如今,越来越多的人选择电动滑板作为代步工具或娱乐方式,市场上也涌现出越来越多的电动滑板产品。

(图片来源: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

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

接下来测试一下起步。

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

最后试一下刹车。

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

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

本项目已经开源,资料可在下方链接处下载。如果您对本项目有任何建议,欢迎留言。

附:Robodyno Pro一体化可编程电机技术文档

附:Robodyno Pro一体化可编程电机购买链接

附:主要零件清单

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

TFT3.5通信转接板

1安装在ESP32控制器外壳中

附:开源资料

资料清单

序号

内容
1

【A004】-程序源代码

2

【A004】-电路原理图

3

【A004】-控制器电路板生产文件

4

【A004】-控制器外壳3D打印文件

5

控制器固件

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/88041.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Linux系统上安装和配置Redis数据库,无需公网IP即可实现远程连接的详细解析

文章目录 1. Linux(centos8)安装redis数据库2. 配置redis数据库3. 内网穿透3.1 安装cpolar内网穿透3.2 创建隧道映射本地端口 4. 配置固定TCP端口地址4.1 保留一个固定tcp地址4.2 配置固定TCP地址4.3 使用固定的tcp地址连接 Redis作为一款高速缓存的key value键值对的数据库,在…

学会Mybatis框架:一文掌握MyBatis与GitHub插件分页的完美结合【三.分页】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Mybatis的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Mybatis分页 1. Mybatis自带分页 2…

springboot小知识:配置feign服务超时时间

背景&#xff1a;当前项目通过feign服务调用了其他两个项目的接口&#xff0c;但是由于特殊需求&#xff0c;需要调整某一个项目的feign服务的默认超时时间&#xff1a; 默认连接超时10秒&#xff0c;默认读取超时时间 60秒 1.找到定义的FeignClient 2.根据FeignClient定义的名…

Node基础--Node简介以及安装教程

1.Node简介 Node.js发布于2009年5月&#xff0c;由Ryan Dahl开发&#xff0c;是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c;让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Pe…

对CSV格式的数据文件进行插值处理

使用Python程序&#xff0c;实现对一个较短的csv文件&#xff0c;进行差值处理&#xff0c;并绘制GUI界面&#xff1b; 这个程序是一个使用Python的Tkinter库构建的GUI应用程序&#xff0c;用于对CSV格式的数据文件进行插值处理。下面我会逐步解释程序的各个部分和功能&#x…

消息队列前世今生 字节跳动 Kafka #创作活动

消息队列前世今生 1.1 案例一&#xff1a; 系统崩溃 首先大家跟着我想象一下下面的这个的场景&#xff0c; 看到新出的游戏机&#xff0c;太贵了买不起&#xff0c;这个时候你突然想到&#xff0c;今天抖音直播搞活动&#xff0c;打开抖音搜索&#xff0c;找到直播间以后&am…

边缘计算节点BEC典型实践:如何快速上手PC-Farm服务器?

百度智能云边缘计算节点BEC&#xff08;Baidu Edge Computing&#xff09;基于运营商边缘节点和网络构建&#xff0c;一站式提供靠近终端用户的弹性计算资源。边缘计算节点在海外覆盖五大洲&#xff0c;在国内覆盖全国七大区、三大运营商。BEC通过就近计算和处理&#xff0c;大…

并发编程系列-分而治之思想Forkjoin

我们介绍过一些有关并发编程的工具和概念&#xff0c;包括线程池、Future、CompletableFuture和CompletionService。如果仔细观察&#xff0c;你会发现这些工具实际上是帮助我们从任务的角度来解决并发问题的&#xff0c;而不是让我们陷入线程之间如何协作的繁琐细节&#xff0…

一网打尽java注解-克隆-面向对象设计原则-设计模式

文章目录 注解内置注解元注解 对象克隆为什么要克隆&#xff1f;如何克隆浅克隆深克隆 Java设计模式什么是设计模式&#xff1f;为什么要学习设计模式&#xff1f; 建模语言类接口类之间的关系依赖关系关联关系聚合关系组合关系继承关系实现关系 面向对象设计原则单一职责开闭原…

仓库使用综合练习

目录 1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 2、安装搭建私有仓库 Harbor 3、编写Dockerfile制作Web应用系统nginx镜像&#xff0c;生成镜像nginx:v1.1&#xff0c;并推送其到私有仓库。 4、Dockerfile快速搭建自己专属的LAMP环境&#xff0c;生…

跨平台图表:ChartDirector for .NET 7.1 Crack

什么是新的 ChartDirector for .NET 7.0 支持跨平台使用&#xff0c;但仅限于 .NET 6。这是因为在 .NET 7 中&#xff0c;Microsoft 停止了用于非 Windows 使用的 .NET 图形库 System.Drawing.Common。由于 ChartDirector for .NET 7.0 依赖于该库&#xff0c;因此它不再支持 .…

小白到运维工程师自学之路 第七十九集 (基于Jenkins自动打包并部署Tomcat环境)1

一、传统的流程 1、传统网站部署的流程 在运维过程中&#xff0c;网站部署是运维的工作之一。传统的网站部署的流程大致分为:需求分 析-->原型设计-->开发代码-->提交代码-->内网部署-->内网测试-->确认上线-->备份数据-->外网更新-->外网测试--&g…

【ES6】—数组的扩展

一、类数组/ 伪数组 1. 类/伪数组: 并不是真正意义的数组&#xff0c;有长度的属性&#xff0c;但无法使用Array原型上的方法 let divs document.getElementsByTagName(div) console.log(divs) // HTMLCollection []let divs2 document.getElementsByClassName("xxx&q…

《操作系统真象还原》学习笔记:第七章 中断

由于 CPU 获知了计算机中发生的某些事&#xff0c;CPU 暂停正在执行的程序&#xff0c;转而去执行处理该事件的程序&#xff0c;当这段程序执行完毕后&#xff0c;CPU 继续执行刚才的程序。整个过程称为中断处理&#xff0c;也称为中断。 把中断按事件来源分类&#xff0c;来自…

食品制造行业云MES系统解决方案

食品饮料行业大致可以分为初级产品加工、二次加工、食品制造、食品分装、调味品和饲料加工等几大类。由于处于产业链不同的位置&#xff0c;其管理存在一定的差异&#xff0c;那么食品行业的MES应该怎么建设呢&#xff1f; 食品饮料行业生产管理特点&#xff1a; 食品饮料行业…

leetcode 122. 买卖股票的最佳时机 II

2023.8.21 和买卖股票的最佳时机相比&#xff0c;本题的股票可以买卖多次了&#xff0c;直接用贪心解决&#xff0c;计算所有涨价的股票相加。代码如下&#xff1a; 贪心&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {int ans 0;for…

Ubuntu18.04 交叉编译curl-7.61.0

下载 官方网址是&#xff1a;curl 安装依赖库 如果需要curl支持https协议&#xff0c;需要先交叉编译 openssl,编译流程如下&#xff1a; Ubuntu18.04 交叉编译openssl-1.1.1_我是谁&#xff1f;&#xff1f;的博客-CSDN博客 解压 # 解压&#xff1a; $tar -xzvf curl-7.61.…

村口的人家排放污水,污水浸染了整个村子,怎么办

从前有一个很不错的村子里&#xff0c;村子里有很多户人家&#xff0c;随着生活水平越来越好&#xff0c;房子也修起来了&#xff0c;柏油马路也宽敞了&#xff0c;大家进出村子&#xff0c;都要走那条马路&#xff0c;要不就出不去。 目录 1. 修厕所 2. 村口的日家 3. 告诉…

Android 系统桌面 App —— Launcher 开发(1)

Android 系统桌面 App —— Launcher 开发&#xff08;1&#xff09; Launcher简介 Launcher就是Android系统的桌面&#xff0c;俗称“HomeScreen”也就是我们开机后看到的第一个App。launcher其实就是一个app&#xff0c;它的作用是显示和管理手机上其他App。目前市场上有很…

商城的TPS与并发用户数是如何换算的?

商城的TPS与并发用户数的换算关系可以通过以下公式计算&#xff1a; TPS 并发用户数 / 平均事务响应时间 其中&#xff0c;平均事务响应时间是指系统处理一个事务所需的平均时间。 下面是商城性能测试的一些用例示例&#xff1a; 用户登录&#xff1a; 目标&#xff1a;测…