arduino 伸缩轨道_M5Train 视觉识别轨道小火车头

本帖最后由 沧海笑1122 于 2020-1-13 21:45 编辑

【M5Train 视觉识别轨道小火车头】

【故事】

front_s.jpg (183.22 KB, 下载次数: 4)

2020-1-13 21:10 上传

玩具轨道电动小火车历史悠久,是孩子们常见喜爱的玩具。米兔小火车轨道兼容宜家的轨道,孩子们百玩不厌。但是,毕竟电动小火车头只有一个车速,小火车头拖动着车厢匀速前进,时间长了,难免枯燥。如何增进趣味呢?

M5stack(明栈科技)出品了基于K210的视觉识别core-M5stickV。我们利用http://v-training.m5stack.com/这个友好的训练服务,训练自己的模型并且上传至服务器,将得到返回的训练模型。这样就为小火车装上了一双能识别交通标志的聪明眼睛。

本项目一共训练了七个交通标志。我尝试将其用于米兔轨道小火车,用M5stickC作为执行元件,构成了一个基于视觉识别的,好玩的轨道小火车。

icon1.jpg (124.27 KB, 下载次数: 5)

2020-1-13 21:10 上传

我们先一起看看视频吧,在视频中,小火车头拖着一节车厢,从始发站出发,经过了限速、注意火车、解除限速、亮灯、鸣笛以及停车等七个动作。小火车头变身可以识别交通标志的AI小火车,根据交通标志完成了变速、闪灯、各种音效鸣笛以及起步停车等动作。为儿童玩具增加了更多的趣味。

【硬件】编号内容型号或性能配置备注

1电动火车头配置标称1200MAH的1.2V可充电电池以及管理模块,一套单电机及齿轮传动系统来自闲鱼,这是一个兼容宜家、米兔的可充电电动火车头

2视觉识别装置M5stickV/K210芯片,具有视觉识别、模型自主训练等功能鸣笛及火车音效、亮灯等通过M5stickV完成

3执行装置M5stickC/esp32,接收来自m5stcikV的识别结果并且转换为相应动作,发送至电机驱动板驱动电机的相关动作,有C完成

4电机驱动板L298N双路电机驱动板本项目火车头仅有一个电机,因此仅用一路;

5小火车头动力部分改装锂电池充电控制板、5V/750MA充电器以及14500电池构成。外置的锂电池充电装置,代替了原有小火车内部的1.2V电池以及充电管理模块,14500电池(850MAH/3.7V)具有良好的动力性能,而且可以与L298N模块匹配

6配件自保持开关、铜芯导线若干

vc.jpg (83.63 KB, 下载次数: 6)

2020-1-13 21:10 上传

【软件】编号内容出品及版本备注

1M5sitckV固件M5出品/V1022-beta

2M5StickC固件M5出品/V1.4.3固件基于UIFLOW

3UIFLOW开发环境M5出品/V1.4.3

4Thonny IDEThonny.org /v3.2.6优秀的micropython开发环境

5Vscode+m5stack插件Microsoft出品优秀的通用代码开发环境

【制作过程】

第一步:购买并且改装小火车头

火车头1_s.jpg (83.4 KB, 下载次数: 3)

2020-1-13 21:10 上传

在闲鱼上购买了一只黑红相间的漂亮扎实的小火车头,当时是看中了它具有充电以及车灯功能。到货拆解后,发现其设计比较紧凑合理,做工精良,使用内三角螺丝紧固。小火车头的动力系统包括一节标称1200MAH的1.2V可充电电池+单电机以及齿轮系统+电池充电管理模块。

但是发现存在三个问题:一是动力不足,1200mah的容量几乎可以肯定是虚标;二是电池1.2V的输出电压无法与L298N电机驱动板匹配,也就是难以完成调速、停车、正反转等功能。三是前车灯非常黯淡,可以肯定串接了一个高阻。使用M5sitckC测试后,输出亮度难以满意。

所以我们需要对小火车头进行改装。

(1)改装其动力系统,原有的1.2V电池以及充电管理模块忍痛去除,代之以850MAH/3.7V可充电电池14500,这颗电池的尺寸和原电池一致,所以很容易装配到原位置,我用热熔胶进行了较为严实的固定,将电池的充电系统外置,不再安装在火车内部,把腾出来的位置用于安装L298N电机驱动板。

(2)在车厢顶部开孔(M3)用于安装M5sitckV。

(3)将内部空间里面的原有拨动开关拆除,将内部的塑料结构用电磨进行拆除,空间用于电机驱动板以及布置铜芯导线。

(4)增设一节车厢,用于承载M5sitckC。这样,把小火车的动力部分、电机驱动部分以及视觉识别元件(M5sitckV)放在火车头,而电源开关以及执行元件(M5sitckC)装在一节单独的车厢,两者通过磁性连接件以及排线连接。

old_s.jpg (159.58 KB, 下载次数: 7)

2020-1-13 21:10 上传

第二步:代码设计

代码设计部分其实比较简单,我们一起来看看吧。

(1)M5sitckV部分

一是训练模型

使用M5sitckV的训练程序,将七个交通标志各拍摄100张以上的照片(合计700+照片),发送至http://v-training.m5stack.com/,如果你的训练是符合要求的,计算结果将收敛,这样大约在半小时左右,你会收到一份带有下载链接的邮件。下载后,你将得到一份自主训练的模型库,一个boot.py的demo代码,不要小看这个demo,我们会很容易移植到主程序中。注意:训练时尽可能将模型放入取景框且尝试不同的光线条件。

二是设计代码

这部分主要是将识别的结果发送至M5sitckC,所以uart的发送编程是主要内容,另外,由于M5sitckV带有喇叭,所以我们这个项目的火车音效就靠M5sitckV实现,前文说到的火车头车灯不亮,而M5sitckV本身就带有一个非常亮的全彩LED,这个亮灯的任务也交给M5sitckV吧。

[mw_shl_code=python,true]import audio

import gc

import image

import lcd

import sensor

import sys

import time

import uos

import os

import KPU as kpu

from fpioa_manager import *

from Maix import I2S, GPIO

from machine import I2C

from board import board_info

from pmu import axp192

pmu = axp192()

pmu.enablePMICSleepMode(True)

fm.register(board_info.SPK_SD, fm.fpioa.GPIO0)

spk_sd=GPIO(GPIO.GPIO0, GPIO.OUT)

spk_sd.value(1)

fm.register(board_info.SPK_DIN,fm.fpioa.I2S0_OUT_D1)

fm.register(board_info.SPK_BCLK,fm.fpioa.I2S0_SCLK)

fm.register(board_info.SPK_LRCLK,fm.fpioa.I2S0_WS)

wav_dev = I2S(I2S.DEVICE_0)

from machine import UART

fm.register(board_info.CONNEXT_B,fm.fpioa.UART1_TX)

fm.register(board_info.CONNEXT_A,fm.fpioa.UART1_RX)

uart_A = UART(UART.UART1, 115200, 8, None, 1, timeout=1000, read_buf_len=4096)

fm.register(board_info.LED_W, fm.fpioa.GPIO3)

led_w = GPIO(GPIO.GPIO3, GPIO.OUT)

led_w.value(1)

passlable=''

global v_state

v_state='no'

lcd.init()

lcd.rotation(2)

try:

img = image.Image("/sd/startup.jpg")

lcd.display(img)

except:

lcd.draw_string(lcd.width()//2-100,lcd.height()//2-4, "Error: Cannot find start.jpg", lcd.WHITE, lcd.RED)

task = kpu.load("/sd/3f758421b4b1db32_mbnet10_quant.kmodel")

labels=["1","2","3","4","5","6","7"]

sensor.reset()

sensor.set_pixformat(sensor.RGB565)

sensor.set_framesize(sensor.QVGA)

sensor.set_windowing((224, 224))

sensor.run(1)

lcd.clear()

def play_sound(filename):

try:

player = audio.Audio(path = filename)

player.volume(30)

wav_info = player.play_process(wav_dev)

wav_dev.channel_config(wav_dev.CHANNEL_1, I2S.TRANSMITTER,resolution = I2S.RESOLUTION_16_BIT, align_mode = I2S.STANDARD_MODE)

wav_dev.set_sample_rate(wav_info[1])

spk_sd.value(1)

while True:

ret = player.play()

if ret == None:

break

elif ret==0:

break

player.finish()

spk_sd.value(0)

except:

pass

while(True):

img = sensor.snapshot()

fmap = kpu.forward(task, img)

plist=fmap[:]

pmax=max(plist)

max_index=plist.index(pmax)

a = lcd.display(img)

if pmax>0.99:

lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))

passlable=str(labels[max_index])

if passlable==v_state:

pass

else:

v_state=passlable

uart_A.write(passlable)

if v_state=='1':

led_w.value(0)

time.sleep_ms(2000)

led_w.value(1)

elif v_state=='2':

play_sound("/sd/whistle3.wav")

time.sleep_ms(200)

elif v_state=='3':

play_sound("/sd/train.wav")

time.sleep_ms(200)

else:

pass

else:

pass

a = kpu.deinit(task)

uart_A.deinit()

[/mw_shl_code]

(1)M5sitckC部分

M5sitckC的代码比较简单,从uart接收到识别特征字以后,通过一系列判断语句,将动作行为发送至L298N即可。

(a)在设计M5sitckC的代码时,我用UIFLOW作为UI设计以及框架搭建,然后将生成的代码用thonny进行调试,玩家看到这里可能会问两个问题:一是为什么不是用uiflow继续调试呢?Uiflow毕竟有一定局限性,而thonny在调试esp32时可以得到非常全面的调试和错误信息;二是为什么调试M5sitckC时,不用vscode+m5插件这种方式呢?因为M5sitckC的屏幕很小,在vscode+m5插件调试时,得不到具体的出错信息。所以根据我的体会,在调试M5sitckC时,比较适合我的办法就是UIFLOW+thonny.

(b)在M5sitckC的屏幕上,我设计了三个参数,一是电池的电压(C的续航能力较弱,实时显示电池电压非常重要,否则调试中会走很多弯路),二是显示从v获取的识别特征码,三是现实C的动作行为(如stop/go/......)便于与V联调时,对识别情况的把握。

stop1s.jpg (130.1 KB, 下载次数: 3)

2020-1-13 21:10 上传

[mw_shl_code=python,true]#2020-01-03

#C侧的火车控制程序v0.11

#

from m5stack import *

from m5ui import *

from uiflow import *

import machine

import time

#UI

setScreenColor(0x111111)

title0 = M5Title(title="Train0108", x=3 , fgcolor=0xFFFFFF, bgcolor=0x0000FF)

label0 = M5TextBox(31, 45, "Ready", lcd.FONT_Default,0xFFFFFF, rotate=0)  #显示火车状态

label1 = M5TextBox(33, 89, "Inbox", lcd.FONT_Default,0xFFFFFF, rotate=0) #显示接收到的指令情况

circle0 = M5Circle(17, 52, 3, 0xFFFFFF, 0xFFFFFF)

circle1 = M5Circle(17, 97, 3, 0xFFFFFF, 0xFFFFFF)

#lcd setup

axp.setLDO2Volt(2.7)

title0.setTitle(str(axp.getBatVoltage()))

#setup

#===uart

uart = None

uart = machine.UART(1, tx=32, rx=33)

uart.init(115200, bits=8, parity=None, stop=1)

#===GPI0 SETUP

#pin1 = machine.Pin(5, mode=machine.Pin.OUT, pull=machine.Pin.PULL_UP)

pin0 = machine.Pin(0, mode=machine.Pin.OUT, pull=machine.Pin.PULL_UP)

PWM1 = machine.PWM(26, freq=10000, duty=0, timer=0)

PWM1.resume()

wait(1)

#===Train Action

def go(): #normal

pin0.value(0)

PWM1.duty(60)  #speed=60

label0.setText('Go')

label1.setText('no')

def light(): #light of train

#light on m5stickV

label0.setText('Light')

label1.setText('1')

pin0.value(0)

PWM1.duty(50)  #speed=50

def train(): #Train comming

# Train sound play on m5stickV

label0.setText('Train')

label1.setText('2')

pin0.value(1)

PWM1.duty(100)   #IN1=1   IN2=1  停车3S后正常速度,相当于等待火车通过

time.sleep_ms(3000)

pin0.value(0)

PWM1.duty(50)   #3S为speed=50

def whistle(): #whistle

# whistle play on m5stickV

label0.setText('Whistle')

label1.setText('3')

pin0.value(0)

PWM1.duty(50)  #speed=50

def limit(): #train limit 5KM/h

pin0.value(0)

PWM1.duty(45)   #speed=45

label0.setText('Limit')

label1.setText('4')

def nolimit(): #train no limit 5KM/h

pin0.value(0)

PWM1.duty(75)   #speed=75

label0.setText('NoLmt')

label1.setText('5')

time.sleep_ms(2000)

pin0.value(0)

PWM1.duty(50)   #2S后降速为speed=50

def stop():#train stop

pin0.value(1)

PWM1.duty(100)   #IN1=1   IN2=1

label0.setText('Stop')

label1.setText('6')

def greenlight(): #train Go

pin0.value(0)

PWM1.duty(50)   # speed=50

label0.setText('GLight')

label1.setText('7')

#=====Loop

while True:

title0.setTitle(str(axp.getBatVoltage()))

if uart.any():

bin_data = uart.readline(1)

decode_bin_data=bin_data.decode()

wait_ms(200)

if decode_bin_data=='1': #如果识别到1开灯

light()

time.sleep_ms(200)

elif decode_bin_data=='2':#如果识别到2===火车通过

train()

time.sleep_ms(200)

elif decode_bin_data=='3':#如果识别到3===鸣笛

whistle()

time.sleep_ms(200)

elif decode_bin_data=='4':#如果识别到4===限速

limit()

time.sleep_ms(200)

elif decode_bin_data=='5':#如果识别到5===解除限速

nolimit()

time.sleep_ms(200)

elif decode_bin_data=='6':#如果识别到6===停车

stop()

time.sleep_ms(200)

elif decode_bin_data=='7':#如果识别到7===绿灯

greenlight()

time.sleep_ms(200)

else: #其他数据正常前进即可

go()

else:

pass

[/mw_shl_code]

第三步:L298N电机驱动板调试

L298N电机驱动板的控制真值表:

l298n2.jpg (79.57 KB, 下载次数: 6)

2020-1-13 21:10 上传

联调的接线很简单,见下图:

l298n_s.jpg (142.61 KB, 下载次数: 4)

2020-1-13 21:10 上传

在联调时,我用了一个小技巧,将一个测试用的电机,其输入线端焊接了两个杜邦线的母头,而L298N的输出输入的铜芯导线端部,都焊接了排针并且进行了热缩处理。这样非常方便地讲L298N与电池、电机以及M5sitckC连接起来。而在正式部署时,只需要将铜芯导线截断至合适长度即可。这样的小技巧可以大大缩短调试时间,并且增加调试的可靠性。L298N的调试目的就是测试各个控制行为以及根据电机实际情况得到PWM的参数(这个参数在装配小火车头电机后,还会进行一些修正)。

第四步:组装M5sitckV+M5sitckC以及小火车系统并且联调

将M5sitckV+M5sitckC以及小火车系统安装好,其中M5sitckC用扎带固定在车厢上,M5sitckV用M3螺丝以及M5提供的专用L型支架固定在火车头的车厢顶部。火车头的电机与调试后的L298N连接起来。

联调的过程比较简单,注意把火车头翻过来,车轮朝上,这样就不会在调试中,面对不停动弹的火车头手忙脚乱。然后把七个标志一一由V识别,观察C上面的接收情况。

注意:如果您是第一次调试M5sitckV,您需要增加一个步骤,就是M5sitckV与PC的串口助手要先行进行调试,确保M5sitckV识别到的特征码能够准确地传送至uart,我因为在M5sitckV模拟小超市中已经进行过类似调试,积累了很好的经验,所以不需要此步骤。

charge1_s.jpg (106.03 KB, 下载次数: 5)

2020-1-13 21:10 上传

第五步:组装轨道以及路测

接下来就是比较有趣的部分了,也是亲子活动时间,和孩子一起组装一个环形轨道,然后把七个交通标志布置在你希望出现的地方,就可以开始路测了,路测时注意几个问题:

一是M5sitckC/V需要有足够充电,否则因电压不稳的重启会让你的路测不连续。

二是M5sitckV的摄像头角度需要在路测时不断调整。

三是电机的PWM参数需要在路测中不断进行微调,因为每个人小火车头的电机参数都会略有不同,轨道的弯度也需要进行一些调整,有一些过急的弯道可能会造成脱轨,需要进行一些修正。

allmap_s_1.jpg (137.07 KB, 下载次数: 10)

2020-1-13 21:25 上传

这个调试过程总体是很有趣的,看着小火车头拖动着M5sitckC的车厢,在环形轨道上做出种种识别动作,欢声笑语使得前面的辛苦工作,都显得那么物有所值。

【鸣谢】

感谢m5stack.com以及arduino.cn社区,提供这样好的硬件以及交流平台。

感谢社区多位师兄给我的帮助和鼓励,如“滚筒洗衣机”师兄(抱歉您的ID的确如此)对动力系统的指导,笑笑以及jimmy、小华师兄对我的指导和鼓励。

感谢我的孩子能够欣赏我这个小小的项目,并且和我一起测试。

这个小项目抛砖引玉,希望更多玩家做出更有趣的尝试。

新春将至,春天的脚步更近了,祝福各位师兄新春大吉,诸事顺意。

沧海抱拳。

upload_m5train00.zip

(363.28 KB, 下载次数: 5)

2020-1-13 21:21 上传

点击文件名下载附件

2020-1-13 21:21 上传

点击文件名下载附件

2020-1-13 21:21 上传

点击文件名下载附件

2020-1-13 21:21 上传

点击文件名下载附件

由于论坛附件size限制,请下载四个附件后,进行文件名修改,然后解压,我分享了code\model\wav。

原文件名更改为

upload_m5train00.zipupload_m5train.zip

upload_m5train01.zipupload_m5train.z01

upload_m5train02.zipupload_m5train.z02

upload_m5train03.zipupload_m5train.z03

你可能感兴趣的:(arduino,伸缩轨道)