用K210和stm32做一只桌面小机器人

        一直想尝试自己动手diy一个像Loona、Vector、Comoz等一样的宠物机器人,毕竟在网上见识过这类产品的人都知道它们的功能是非常神奇的存在,宠物机器人内置了丰富人机交互功能,会有情绪波动,会对人撒娇,会调皮捣蛋搞破坏,让人感觉仿佛这些真的是有生命的小精灵。然而这些产品的售价也不菲,而且网上许多购买过该产品的用户后来也都转二手卖出了,原因之一就是虽然这些宠物机器人有着像真宠物一样的行为,可实际时间久了就会发现其实它的功能来来回回就那几样,这时过了新鲜感的用户逐渐意识到机器人的一个一个表现其实不过是一串又一串预先配置的冷冰冰的代码罢了,因而自然也不会再被其所吸引。由于目前技术所限,宠物机器人还不足以真正做到“有生命”,这就是目前这一类产品的缺陷所在。

        我想自己做一个,其中一方面就是为了能自由开发和更新升级机器的功能,另一方面也是想尝试用自己的想法去仿一个简单的宠物行为,探索其中的奥妙。当然,我目前是刚刚入手,想做到像上述的产品那样的功能还是十分困难的,在这一个项目中,我计划设定的功能都是比较简单的,目前想的是先实现简单的交互后再慢慢升级,探索开发复杂的功能。当然这个小机器人同时也是我给女朋友准备的礼物,女孩子当然是无法拒绝一只乖巧可爱富有活力的小“宠物”的。

硬件说明

        以K210为主控,以迷你版的stm32为协处理器,K210与stm32采用串口通信。K210主要用于处理图像识别等大计算量的任务,stm32则是用于控制底层执行器,这次的执行器布置比较简单,只有两个舵机,一个负责让机器人抬头低头,一个负责左右转。

K210视觉识别模块

         这里K210是在一款名为K210视觉识别模块的产品中,在某橙色软件可以搜到。该产品可以外形像一个小相机,自带了摄像头和LCD屏幕。在购买到手把玩一段时间后我发现恰好拆了亚克力板把摄像头扭向下,整个相机倒过来,恰好就可以做一个小机器人的头,摄像头做眼睛,屏幕做脸,而其中内置的K210芯片,能够跑轻量级视觉算法,恰巧能做机器人的大脑。

K210视觉识别模块

迷你stm32

        这款stm32尺寸只有25.40*22.86mm大小,可以说是非常小巧玲珑了,非常适合用来做小体积的项目,它芯片的具体型号是stm32f103c8t6,其实就是最小系统板常用的那款型号。

mini_stm32

stm32扩展板

        这是自己绘制的用于连接的扩展板,结构非常简单,就两个舵机接口,以及串口通信接口,还有两个电源引脚线。

扩展板

舵机

        用的是MG90s,非常常见的小舵机。(某宝直接截的图,忽略图中的水印哈哈哈哈)

mg90s

软件说明

        程序主要是在K210官方例程的基础上改的,大体的布局就是K210跑视觉算法将相应的参数如识别出的人脸位置以及长度宽度,还有两个舵机的转角通过串口通信传输给stm32,而stm32则是将来自K210的转角值输入两个舵机中,从而是K210这颗大脑能指挥两个关节行动。

表情图像说明

    表情图像用的是B站一位up主何时登陆何时还 开源的oled表情包,详情可点击下面链接。开源代码教程stm32的oled表情显示

      不过我这边不像视频那样做,我直接把表情图像存入SD卡,再让K210从SD卡中读取图片并显示在LCD上,当然本质上都是一样的,就是逐帧显示照片形成动画。

所用表情图像

stm32程序说明

        stm32所用的程序也是在官方例程的基础上改的,比较简单,只是增加了两个舵机函数,让stm32不断地执行从K210读取到转角命令。以下是stm32的主程序代码,其余全部源码将在项目完成后公开。 

#include "AllHeader.h"


char buff_com[50];
msg_k210 k210_msg;//收到k210信息结构体   p0-PB0  p1-PB1
	

int main()
{
	SystemInit();
	Servo_Init1();	//PB0 PB1
	delay_init();
	led_int();//PC13
	USART2_init(115200);//PA2 PA3 	
	LED = 0;	
	while(1)
	{OLED_ShowNum(4, 1, k210_msg.class_n, 3);
		if (k210_msg.class_n != 0)//例程号不为空
		{
			if(k210_msg.class_n == 5)//是人脸特征检测
			{			
				Servo_SetAngle1(k210_msg.p1);			//这是颈部舵机
				Servo_SetAngle0(k210_msg.p0);			//这是底座舵机
				k210_msg.class_n = 0;//清除例程号	
			    delay_ms(10);
			}
			
		}
	}
}

K210程序说明 

        K210使用的编译软件是CanMV,以下代码的主要功能是让机器人醒着的时候盯着人看,当醒着的时间到4000秒(约67分钟)时,它会进入睡眠状态,当睡够3000秒即50分钟时会自然苏醒,当然睡眠状态下也可以通过触碰触摸屏将它唤醒。

import sensor, image, time, lcd
import touchscreen as ts
from maix import KPU
from modules import ybserial
import binascii
import gc
import random
serial = ybserial()
#有时运行失败应该是连接的问题,断开重新连一遍就可以了。
# 定义两种模式的图片列表
file_template = "/sd/EMO/{:04d}.bmp"

# 初始化一个空列表来存储文件路径
img_paths_mode1 = []
sleep =[]
# 定义一个循环,从0000开始,递增到0005
for i in range(8):
    # 将当前的文件名添加到列表中
    img_paths_mode1.append(file_template.format(i))

for i in range(10, 22, 1):
    # 将当前的文件名添加到列表中
    sleep.append(file_template.format(i))

img_paths_mode2 = [
    "/sd/EMO/{:04d}.bmp".format(i) for i in range(0, 38, 5)
]

# 定义一个函数来选择显示模式
def choose_display_mode():
    # 有80%的概率选择模式1,20%的概率选择模式2
    s=random.random()
    if s < 0.7:
        print(s)
        return img_paths_mode1
    else:
        print(s)
        return img_paths_mode2

def str_int(data_str):
    bb = binascii.hexlify(data_str)
    bb = str(bb)[2:-1]
    #print(bb)
    #print(type(bb))
    hex_1 = int(bb[0])*16
    hex_2 = int(bb[1],16)
    return hex_1+hex_2


def send_data(x,y,w,h,p0,p1,msg):
    start = 0x24
    end = 0x23
    length = 5
    class_num = 0x05 #例程编号
    class_group = 0xBB #例程组
    data_num = 0x00 #数据量
    fenge = 0x2c #逗号
    crc = 0 #校验位
    data = [] #数据组


    #x(小端模式)
    low = x & 0xFF #低位
    high = x >> 8& 0xFF #高位
    data.append(low)
    data.append(fenge) #增加","
    data.append(high)
    data.append(fenge) #增加","

    #y(小端模式)
    low = y & 0xFF #低位
    high = y >> 8& 0xFF #高位
    data.append(low)
    data.append(fenge) #增加","
    data.append(high)
    data.append(fenge) #增加","

    #w(小端模式)
    low = w & 0xFF #低位
    high = w >> 8& 0xFF #高位
    data.append(low)
    data.append(fenge) #增加","
    data.append(high)
    data.append(fenge) #增加","

    #h(小端模式)
    low = h & 0xFF #低位
    high = h >> 8& 0xFF #高位
    data.append(low)
    data.append(fenge) #增加","
    data.append(high)
    data.append(fenge) #增加","
     # p0
    low = p0 & 0xFF  # 低位
    high = p0 >> 8 & 0xFF  # 高位
    data.append(low)
    data.append(fenge)  # 增加","
    data.append(high)
    data.append(fenge)  # 增加","

    # p1
    low = p1 & 0xFF  # 低位
    high = p1 >> 8 & 0xFF  # 高位
    data.append(low)
    data.append(fenge)  # 增加","
    data.append(high)
    data.append(fenge)  # 增加","

    if msg !=None:
        #msg
        for i in range(len(msg)):
            hec = str_int(msg[i])
            data.append(hec)
            data.append(fenge) #增加","
        #print(data)

    data_num = len(data)
    length += len(data)
    #print(length)

    send_merr = [length,class_num,class_group,data_num]
    for i in range(data_num):
        send_merr.append(data[i])
    #print(send_merr)

    #不加上CRC位,进行CRC运算
    for i in range(len(send_merr)):
        crc +=send_merr[i]
    crc = crc%256

    send_merr.insert(0,start) #插入头部
    send_merr.append(crc)
    send_merr.append(end)

    #print(send_merr)
    global send_buf
    send_buf = send_merr



last_time = time.time()

send_buf = []
x_ = 0
y_ = 0
w_ = 0
h_ = 0
p0_= 100
p0_0= 100
p1_= 57
p1_1= 57
k0=0.06
k1=0.04
X0=160
Y0=120
i = 0
sleepy =1
current_img_paths = choose_display_mode()

lcd.init()
ts.init()
status_last = ts.STATUS_IDLE

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)

sensor.skip_frames(time = 100)
clock = time.clock()

od_img = image.Image(size=(320,256))

anchor = (0.893, 1.463, 0.245, 0.389, 1.55, 2.58, 0.375, 0.594, 3.099, 5.038, 0.057, 0.090, 0.567, 0.904, 0.101, 0.160, 0.159, 0.255)
kpu = KPU()
kpu.load_kmodel("/sd/KPU/yolo_face_detect/yolo_face_detect.kmodel")
kpu.init_yolo2(anchor, anchor_num=9, img_w=320, img_h=240, net_w=320, net_h=256, layer_w=10, layer_h=8, threshold=0.7, nms_value=0.3, classes=1)


# 创建一个包含所有文件名的列表
img_paths = [
    "/sd/EMO/{:04d}.bmp".format(i) for i in range(0,511,2)
]
# 现在 img_paths 列表包含了从 "0000.bmp" 到 "00511.bmp" 的所有文件名
print(img_paths)


while True:      #  time.sleep(0.5)  # 等待0.2秒
    gc.collect()
    clock.tick()
    img = sensor.snapshot()
    a = od_img.draw_image(img, 0,0)

    od_img.pix_to_ai()
    kpu.run_with_output(od_img)
    dect = kpu.regionlayer_yolo2()
    fps = clock.fps()

        #print("dect:",dect)
    for l in dect :
        a = img.draw_rectangle(l[0],l[1],l[2],l[3], color=(0, 255, 0))
        x_ = l[0]
        y_ = l[1]
        w_ = l[2]
        h_ = l[3]
        p0_=round(p0_-(x_ +(h_/2)-X0)*k0+1)
        p1_=round(p1_+(y_ +(w_/2)-Y0)*k0+1.5)
    #    print(p0_)
        print(p0_)
        print((x_ +(h_/2)-X0)*k0+1)
    if sleepy==0:              #清醒状态下将盯着人看
        if p0_>180:
           p0_0=180
           p0_=p0_0

        elif p0_<50:
             p0_0=50
             p0_=p0_0

        else:
            p0_0=p0_
        if p1_>82:
           p1_1=82
           p1_=p1_1
        elif p1_<57:
             p1_1=57
             p1_=p1_1
        else:
            p1_1=p1_
    else:                       #睡眠状态下低头保持不动
          p0_0=100
          p1_1=82

#    print(p0_)
    print(p0_0)

    send_data(x_,y_,w_,h_,p0_0,p1_1,None)
    serial.send_bytearray(send_buf)

    print(p0_0)

    (status, j, k) = ts.read()
    if status_last != status:
        print(status, j, k)
        status_last = status
    if status == ts.STATUS_MOVE:
        sleepy=0                   #睡眠状态下如果用手轻微摩擦屏幕,那么会将它唤醒。




    if sleepy==1:

        if i <len(sleep):  # 显示睡眠状态的表情
           img_path = sleep[i]
           img_read = image.Image(img_path)
           img2 = img_read.resize(300, 150)
           img2 = img2.rotation_corr(z_rotation=180)
           i=i+1
           lcd.display(img2)
        else:
           i=0
    else:
       if i <len(current_img_paths):  # 显示正常状态的表情
           img_path = current_img_paths[i]
           img_read = image.Image(img_path)
           img2 = img_read.resize(300, 150)
           img2 = img2.rotation_corr(z_rotation=180)
           i=i+1
           lcd.display(img2)
       else:
           i=0
           current_img_paths = choose_display_mode()
    current_time = time.time()
    # 检查是否到了下一秒的开始
    if (current_time - last_time >= 4000)and(sleepy==0):#超过4000秒时自动进入睡眠状态
        last_time = current_time
        sleepy=1
        print("I fall asleep", time.time())
    elif (current_time - last_time >= 3000)and(sleepy==1):#睡够3000秒时自动苏醒
        last_time = current_time
        sleepy=0
        print("I wake up", time.time())



    time.sleep(0.01)  # 短暂休眠以避免过度占用CPU


kpu.deinit()

效果展示

为了突出效果,视频中我把机器人的睡眠时间和清醒时间都调短了,大约每过五六秒就会睡着,视频里看起来像是它要睡觉但我一直骚扰它。

桌面机器人Raboty效果演示

结构说明

        如图是用solidworks建的模型,结构比较简单,仅两个自由度,一个在头部,用于抬头和低头,一个在身体,用于转身。

三维图

        如图为头部剖视图,里面放置了K210模块和舵机,灰色部分是轴承座。

头部剖视图

        如下图为身体的剖视图,ministm32安置在“心部”,可以说是它的心脏了,下边的舵机用于转动整个机身,再往下则是身体通过轴承与底座连接在一起。

身体剖视图

 组装

组装ing-1

组装ing-2

组装ing-3
完成组装~

smile~
关灯扮鬼脸~
最终效果

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值