一、一些闲话

    抱歉了各位许久不见了,我是lemon。不知不觉又过了两个月了,为什么托更这么久,单纯因为我把这里忘了而已。当初信誓旦旦的立下一年要写100篇文章的flag,但是lemon转头就忘了。说实话这两个月过的也不算顺利,各种小问题不断。我很确定问题出在我自己身上,一切都是我咎由自取、忘了初心。我想了很多,也许我还有回头的机会。从这篇博文开始我会再把博客捡起来。先做人后做事,慢点就慢点吧。先把自己调整回正常状态先。
    本篇教程,也不能说是教程吧。旨在记录下使用maix nano遇到的相关问题,为自己以后会来找问题提供一点参考。如果能对你如后使用maixpy有所帮助的话就好了。
    让我们正式开始

二、项目背景介绍:

最近接了一个社会上的单子,希望我用maixpy做一个基于机器视觉的网球回收小车,以提供给学习相关知识的小朋友们参考。

三、使用材料:

  1. maix nano开发板(主控K210)
  2. GC0328摄像头*1
  3. L9110S驱动器模块*3
  4. 直流减速电机*2
  5. 直流升降电机*1
  6. 3.7V锂电池*1

四、一些参考材料

  1. maix nano开发板固件库
  2. maixpy官方支持文档(wiki)
  3. maix nano主控K210技术手册
  4. maix nano pcb布局图
  5. L9110S驱动器引脚定义图
  6. TB6612NFG驱动器引脚图/使用示例
  7. 寻找小球代码参考Openmv官方历程

五、构想的工作流程:

使用maixpy的openMV固件,参考官方历程写一个程序配合相应算法驱动相机进行图像识别,配合PID算法驱动直流电机。完成回收网球的功能。自动回归原位暂定。
PWM驱动电机带动轮胎旋转
直流减速电机通过操作GPIO电平高低来进行伸缩带动结构旋转。

六、涉及关键知识:

  1. 定时器实现PWM(定时器,占空比,相关函数)
  2. 引脚GPIO控制(
  3. PID算法配合驱动电机
  4. 使用FPIOA预先映射IO
  5. 正确调节色彩阈值
  6. openMV算法实现
  7. 电机的旋转控制运动算法(转速、转向)【当前使用的L9110S和示例使用的TB6612NFG控制逻辑上存在差异,具体表现为连接关系上的差异。TB6612NFG通过两路信号控制,一路信号通过输入信号的占空比控制速度、另一路信号通过高低电平控制电动机运行方向。而L9110虽然也是通过两路信号输入控制直流电机运动,但是与上面不同的是,电动机的运行方向是通过两个信号的大小进行比较确定的,速度由两个信号的之间的差值(数值的绝对值)控制。关于电机与直流电机的控制方式之后可以再开一篇文章专门介绍下(又开新坑)。】

七、我的自述

事先说明本人不懂python,我之前主C语言。本篇博文的核心就在于,在不懂python的基础上完成所有工作,完成这个程序。我本人称之为不懂编程也能做程序员(就和搬砖一样,如果想要在很有限的时间内搬完大量的砖块,搬砖的人最好就只要想着做搬砖这件事就好,去想砖是什么怎么做的只会分心无法在短时间内完成工作罢了)。
当然这样并不好,希望你们在学习嵌入式开发的过程中千万不要学我。我其实应该叫冒牌程序员更合适。大概我太小看编程这件事了。

八、我的方法

如果你想最短时间内做成这件事,那么有几点你一定要知道的。我称之为面向问题(完全实用主义,完全为解决问题而生)的思考方式(申论)。

  1. 这是什么?(是什么)
  2. 要做什么?(为什么)
  3. 要怎么做?(怎么做)
    假设每个问题都可以分成三个问题,那么问题的问题大概也可以分为这三个·问题,当你分到不能再分时那么对于要解决这个问题该做什么你大概了就已经思考清楚了。接着去查资料往前冲就好。

九、操作过程(正确)

下面让我们正式开始吧:

9.1 配置开发环境

9.1.1 配置开发环境

https://dl.sipeed.com/shareURL/MAIX/MaixPy/release/master/maixpy_v0.6.2_72_g22a8555b5
首先进入这个网页,会发现很多.bin后缀的文件
我们挑出其中一个文件名为:maixpy_v0.6.2_72_g22a8555b5_openmv_kmodel_v4_with_ide_support.bin的文件
后缀有几个单词值得关注:
openmv表示支持openmv库
ide_support表示支持ide进行开发
kemodel_v4表示版本为v4
想使用机器视觉的话我们最好选择后缀有openmv后缀的bin文件,以使用openmv库
可以下载最新版本的最好下载最新版本的
官方固件库
官方固件库

9.1.2 安装maixpy IDE

开发工具可以在以下网站下载:https://dl.sipeed.com/shareURL/MAIX/MaixPy/ide/v0.2.5
下载页面
下载页面
后缀为.exe适用于windows系统的
.run后缀的文件适用于linux系统
.dmg后缀的文件适用于macos系统
根据自己的需求下载即可
有最新的下载最新的最好

9.1.3 驱动安装:

首先附一张maixpy官网的USB芯片与maixpy板对应关系表
官方USB芯片与maixpy板的对应关系表
官方USB芯片与maixpy板的对应关系表
安装驱动时只要寻找对应的芯片的驱动就好。
其他一些有关于USB的问题都可以在官方支持页面找到答案。
https://wiki.sipeed.com/soft/maixpy/zh/get_started/env_install_driver.html

9.2在开始之前有一些需要知道的事情

9.2.1 miax nano的FPIOA

K210 支持每个外设随意映射到任意引脚, 使用 FPIOA 功能来实现。这句话摘自官方FPIOA支持页面。
上句话的意思是虽然MCU芯片的连接关系在设计时就已经决定好,焊的时候就已经焊死了,但是实际上我们还可以通过调用特定函数将特定外设与引脚进行映射,从而使IO具有多种不同功能,可以大大提升了硬件使用的灵活性。

9.2.2 关于maix nano使用的Micro Python3

MicroPython是Python3语言的精简高效实现,包括Python标准库的一小部分,经过优化可在微控制器和受限环境中运行。
Micro Pyhton基于python3版本开发,其语法和python语法接近,但是还是有些许差别。在不同单片机平台受限于编译器和硬件还有对应函数库编写的差别,相同的代码在不同的平台实现效果不完全相同。当然python的代码在移植性上还是不错的。Micro Python的编译器是使用C语言编写的。日常使用的时候当成Python使用就好。
这里有一篇学习教程附上:https://blog.csdn.net/c1063891514/article/details/88362162

9.2.3 关于maixpy的Board文件

Board文件

这是一个 MaixPy 板级配置模块,它可以在用户层统一 Python 代码,从而屏蔽许多硬件的引脚差异。

如果是使用maix nano初次上手,会发现maixpy的我入门教程中有一段内容是描述Board的。但是对于Maix Nano来说因为没有外设所以并不需要这个文件,无视即可。当然如果你查maixpy其他系列的板子可能会发现他们一般会配置Board文件。我想对于新手来说如果是初上手在查看看教程可能会有疑惑。所以在在这里说明。

9.2.4 OpenMv是什么?如何使用OpenMv?

简单的来说,它是一个可编程的摄像头,通过MicroPython语言,可以实现你的逻辑。而且摄像头本身内置了一些图像处理算法,很容易使用。

上句话摘自OpenMv官网,但是我觉得虽然说得对但是还不够明确。
I:OpenMv它是什么?
OpenMv是一套开源函数库
II:OpenMv能做什么?
通过使用MicroPython编写对应逻辑,调用OpenMv库内的对应函数使用对应功能。完成任务。
III:要使用OpenMv需要做什么?
如果你的时间比较多的话可以通过Openmv官方的教程一个章节一个章节的进行学习,最终可以完全掌握OpenMv的使用方法。

9.3 代码及常用函数:

9.3.1 首先简短的介绍一下本程序

本程序基于OpenMv官方历程开发,程序的原理是,通过主函数调用OpenMv的函数库完成小球的识别,同时同时联合PID算法完成小车的闭环控制,控制小车的运动。PID函数将PID函数封装在里面用于计算OpenMv函数取得数值,并将该值输入直流电动机控制函数。直流电动机控制函数负责执行PID算法计算的值。当摄像头视野中没有出现对应的小球时,会默认控制小车向某一方向旋转以达到寻找小球的目的。

9.3.2 如何调用相关函数:

python和之前C语言的编程类似,虽然思想并不相同。需要使用其他头文件的函数时就需要在程序的开头进行声明。当然Python是面向对象的,两者在原理上并不相同,但是效果类似。
示例:

import sensor, image, time        #
from Maix import GPIO
from PID import PID
import mars
from fpioa_manager import fm

9.3.3 首先通过FPIOA映射GPIO

因为Maix nano的可供我们使用的GPIO并不多,同时对应IO的初始映射可能并不符合我们要求,所以要先映射GPIO。
要想使用FPIOA函数需要调用Maix模块中的FPIOA函数块。
对应官网页面:https://wiki.sipeed.com/soft/maixpy/zh/api_reference/Maix/fpioa.html
基本语法:

fm.register(10, fm.fpioa.GPIO2)        #本句话的意思是将IO_10映射为GPIO2
fm.register(9, fm.fpioa.GPIO1)         #本句话的意思是将IO_9映射为GPIO1

本语法适用于你要连接的IO和你要是用的外设不同时。修改IO映射使用。
在开头需要引用对用的库:

from fpioa_manager import fm

9.3.4 控制引脚输入输出类型及电平大小

使用该函数需要在开头调用函数:

from Maix import GPIO

函数代码:

D3S=GPIO(GPIO.GPIO2,GPIO.OUT)
D3J=GPIO(GPIO.GPIO1,GPIO.OUT)
GPIO.GPIO2代表你想操作的GPIO代号,GPIO.OUT代表是输出模式,其他的还有输入模式,是否上拉等等。

这里就简单介绍下,具体可查阅官方GPIO支持页面:https://wiki.sipeed.com/soft/maixpy/zh/api_reference/Maix/gpio.html
如何拉高或者拉低GPIO:

D3S.value(0)
D3J.value(0)

在你想要操作的引脚号后加上后缀value()即可操作GPIO,括号内的值可以是0或者1。这样就可以改变GPIO电压了。

十、调用OpenMv函数库移植openmv官方历程(捡球小车)

10.1 main函数代码

import sensor, image, time
from Maix import GPIO
from PID import PID
import mars
from fpioa_manager import fm


sensor.reset() # 初始化摄像头
sensor.set_pixformat(sensor.RGB565) # 格式为 RGB565.
sensor.set_framesize(sensor.VGA) # 使用 QQVGA 速度快一些
size_threshold = 2000                   #控制摄像头和小球之间的距离
sensor.skip_frames(time = 2000) # 跳过2000s,使新设置生效,并自动调节白平衡
#sensor.set_auto_whitebal(False)
clock = time.clock()                # Create a clock object to track the FPS.
fm.register(10, fm.fpioa.GPIO2)
fm.register(11, fm.fpioa.GPIO1)

green_threshold   = (45, 77, 32, 42, 0, 23)  #颜色阈值调整
sc = 1
x_pid = PID(p=1, i=1, imax=100)       #转弯半径过大调整
h_pid = PID(p=1.3, i=0.1, imax=50)     #速度过慢调整数值

D3S=GPIO(GPIO.GPIO1,GPIO.OUT)
D3J=GPIO(GPIO.GPIO2,GPIO.OUT)
D3S.value(0)
D3J.value(0)

def find_max(blobs):
    max_size=0
    max_blob=None
    for blob in blobs:
        if blob[2]*blob[3] > max_size:
            max_blob=blob
            max_size = blob[2]*blob[3]
    return max_blob


while(True):
    clock.tick() # Track elapsed milliseconds between snapshots().
    img = sensor.snapshot() # Take a picture and return the image.

    blobs = img.find_blobs([green_threshold])
    if blobs:
        max_blob = find_max(blobs)

        x_error = max_blob[5]-img.width()/2
        h_error = max_blob[2]*max_blob[3]-size_threshold
        print("x error: ", x_error)
        '''
        for b in blobs:
            # Draw a rect around the blob.
            img.draw_rectangle(b[0:4]) # rect
            img.draw_cross(b[5], b[6]) # cx, cy
        '''
        img.draw_rectangle(max_blob[0:4]) # rect
        img.draw_cross(max_blob[5], max_blob[6]) # cx, cy
        x_output=x_pid.get_pid(x_error,1)
        h_output=h_pid.get_pid(h_error,1)
        #print("h_output",h_output)
        #print("-h_output-x_output",-h_output-x_output)
        #print("-h_output+x_output",-h_output+x_output)
        mars.run((-h_output-x_output)/10,(-h_output+x_output/10))
        x_output=0
        h_output=0
        #mars.run(80,80)
        #time.sleep_ms(2000)
    else:
        mars.run(80,-70)
        #mars.run(80,-70)

10.2 PID函数代码

import time
from math import pi,isnan


class PID:
    _kp = _ki = _kd = _integrator = _imax = 0
    _last_error = _last_derivative = _last_t = 0
    _RC = 1/(2 * pi * 20)
    def __init__(self, p=0, i=0, d=0, imax=0):
        self._kp = float(p)
        self._ki = float(i)
        self._kd = float(d)
        self._imax = abs(imax)
        self._last_derivative = float('nan')

    def get_pid(self, error, scaler):
        tnow = time.ticks_ms()
        dt = tnow - self._last_t
        output = 0
        if self._last_t == 0 or dt > 1000:
            dt = 0
            self.reset_I()
        self._last_t = tnow
        delta_time = float(dt) / float(1000)
        output += error * self._kp
        if abs(self._kd) > 0 and dt > 0:
            if isnan(self._last_derivative):
                derivative = 0
                self._last_derivative = 0
            else:
                derivative = (error - self._last_error) / delta_time
            derivative = self._last_derivative + \
                                     ((delta_time / (self._RC + delta_time)) * \
                                        (derivative - self._last_derivative))
            self._last_error = error
            self._last_derivative = derivative
            output += self._kd * derivative
        output *= scaler
        if abs(self._ki) > 0 and dt > 0:
            self._integrator += (error * self._ki) * scaler * delta_time
            if self._integrator < -self._imax: self._integrator = -self._imax
            elif self._integrator > self._imax: self._integrator = self._imax
            output += self._integrator
        return output
    def reset_I(self):
        self._integrator = 0
        self._last_derivative = float('nan')

10.3 GOIO控制昱初始化代码

import time, sys
from machine import Timer,PWM
import time
from Maix import GPIO
from fpioa_manager import fm


#使用FPIOA启动引脚
#fm.register(17, fm.fpioa.GPIO4, force=True)
#fm.register(13, fm.fpioa.GPIO7, force=True)
#fm.register(11, fm.fpioa.GPIO3, force=True)
#fm.register(14, fm.fpioa.GPIO0, force=True)


#变量定义
inverse_left=False  #change it to True to inverse left wheel
inverse_right=False #change it to True to inverse right wheel


#启用引脚(定义引脚模式,引脚编号),将此动作的结果设置为一个个对象
#D1L=GPIO(GPIO.GPIOHS1,GPIO.OUT)
#D1R=GPIO(GPIO.GPIO5,GPIO.OUT)
#D2L=GPIO(GPIO.GPIO3,GPIO.OUT)
#D2R=GPIO(GPIO.GPIO6,GPIO.OUT)


#开启定时器,开启定时器并设置通道,将此动作的结果其设置为一个个对象
tim01 = Timer(Timer.TIMER0,Timer.CHANNEL0,mode=Timer.MODE_PWM)
D1L = PWM(tim01,freq=1000,duty=0,pin=13)
tim02 = Timer(Timer.TIMER0,Timer.CHANNEL1,mode=Timer.MODE_PWM)
D1R = PWM(tim02,freq=1000,duty=0,pin=17)
tim11 = Timer(Timer.TIMER0,Timer.CHANNEL2,mode=Timer.MODE_PWM)
D2L = PWM(tim11,freq=1000,duty=0,pin=14)
tim12 = Timer(Timer.TIMER0,Timer.CHANNEL3,mode=Timer.MODE_PWM)
D2R = PWM(tim12,freq=1000,duty=0,pin=11)


#开启PWM,并把此动作的结果再分为一个个对象

D1L.enable()
D1R.enable()
D2L.enable()
D2R.enable()

#直流减速电机基本控制函数
def run(left_speed, right_speed):
    if inverse_left==True:
        left_speed=(-left_speed)
    if inverse_right==True:
        right_speed=(-right_speed)

    if left_speed < 0:
        D1L.duty(0)
        #print("int(abs(left_speed))",int(abs(left_speed)))
        if 0<int(abs(left_speed))<100:
            D1R.duty(int(abs(left_speed)))
        elif int(abs(left_speed))>=100:
            D1R.duty(98)
            print("98")
        elif int(abs(left_speed))<=0:
            D1R.duty(1)
            print("1")
    else:
        D1R.duty(0)
        #print("int(abs(left_speed))",int(abs(left_speed)))
        if 0<int(abs(left_speed))<100:
            D1L.duty(int(abs(left_speed)))
        elif int(abs(left_speed))>=100:
            D1L.duty(98)
            print("98")
        elif int(abs(left_speed))<=0:
            D1L.duty(1)
            print("1")

    if right_speed < 0:
        D2L.duty(0)
        #print("int(abs(right_speed))",int(abs(right_speed)))
        if 0<int(abs(right_speed))<100:
            D2R.duty(int(abs(left_speed)))
        elif int(abs(right_speed))>=100:
            D2R.duty(98)
            print("98")
        elif int(abs(right_speed))<=0:
            D2R.duty(1)
            print("1")
    else:
        D2R.duty(0)
        #print("int(abs(right_speed))",int(abs(right_speed)))
        if 0<int(abs(right_speed))<100:
            D2L.duty(int(abs(left_speed)))
        elif int(abs(right_speed))>=100:
            D2L.duty(98)
            print("98")
        elif int(abs(right_speed))<=0:
            D2L.duty(1)
            print("1")

历程参考星瞳科技教程(追小球的小车)

https://book.openmv.cc/project/zhui-xiao-qiu-de-xiao-8f665d28-project-pan-tilt-md.html