联系管理员

开通文章发布权限

扫码 添加微信
微信图片
电话:18888888888 QQ:-

K230-pid的可视化调试

我调试 PID 是通过串口 和 Vofa+工具。Vofa+是一款上位机工具,我个人认为目前对这个项目来说最大的作用是显示出数据波形变化。


Vofa+ 上位机界面

 

目前我还不知道K230的单步调试手段,所以只能盲调PID:设置一个P\I\D值,然后看PID输出的数据曲线和目标数据曲线的差别。如果误差很大,则重新设置一个P\I\D值继续调到误差小为止。

当然,如果有时间和精力,也可以自己写一套通过串口调整参数的代码。

硬件连接

图中的这个带金边的模块是CH340的USB转串口模块,是很常用的东西了。这个因为没有找到更好的实物图,就找了这个,现在这个图模块上的RXD和TXD接到一起了,实际连接的时候是不要短接在一起的, 切记。

图片#B #S #R #60% #auto

我们通过下面的代码测试一下k230的串口发送功能 和 vofa+的使用:

K230的串口知识和使用方法,参考立创开发板的庐山派串口章节教程
from machine import UART
from machine import FPIOA

# 配置引脚
fpioa = FPIOA()
fpioa.set_function(5, FPIOA.UART2_TXD)
fpioa.set_function(6, FPIOA.UART2_RXD)

# 初始化UART2,波特率115200,8位数据位,无校验,1位停止位
uart = UART(UART.UART2, baudrate=115200, bits=UART.EIGHTBITS, parity=UART.PARITY_NONE, stop=UART.STOPBITS_ONE)

# 要发送的字符串
message = "Hello,LuShan-Pi!\r\n"

# 通过UART发送数据
uart.write(message)

# 释放UART资源
uart.deinit() 

效果图:
图片#B #S #R #60% #auto

Vofa+的使用

这里就介绍Vofa+的数据接收协议 和 波形的显示方法。

接收协议

比较常用的是 FireWater协议,其协议格式如下:

<any>:ch0,ch1,ch2,...,chN\n

参数说明:

元素描述
<any>表示数据名称,可以自定义
:必须加英文冒号,不然解析不了数据,就不会显示在Vofa+上。
ch0~chNch表示通道数据,0~N表示是第几个通道的数据。比如我要发送两个浮点数据(float)到vofa+显示波形,分别是1.2 和 21.4。那么就可以写为 float:1.2,21.4\n,这样就可以显示两个数据。
,表示每一个数据之间的间隔
\n结尾必须加\n,不然解析不了数据,就不会显示在Vofa+上。

 

示例:

float:1.2,21.4\n
temp:25.3\n
sensor:0.5,1.7,3.2\n
关键注意事项
  1. ​冒号不能省略​​:数据名称后必须立即跟英文冒号(:

  2. ​逗号分隔数据​​:多个数值必须用英文逗号(,)分隔

  3. ​结尾必须有\n​:换行符是数据结束的强制标记

  4. ​通道顺序​​:数据值的顺序对应通道索引(第一个值=ch0,第二个值=ch1,以此类推)


Vofa+ 上关于 FireWater 协议的描述

波形显示

在 Vofa+ 的左侧边栏找控件,将波形图控件拉出到放置区即可。


放置波形图控件

我们通过K230发送一些我们自定义的波形数据看看效果:

发送数据到 Vofa+ 后,还不能马上显示在波形图上,还需要我们配置波形的输入数据。


放大波形画面
设置波形的输入显示数据

 

不出意外的话,你应该会显示下面的画面:

5个通道的数据,不断更新就可以得到波形。
波形显示画面

项目PID的调试


代码:
import time, os, gc, sys, math,utime
from machine import PWM, FPIOA, Pin, UART
from media.sensor import *
from media.display import *
from media.media import *
import _thread
from pid import PID
from filter import LowPassFilter

DETECT_WIDTH = 480
DETECT_HEIGHT = 320

sensor = None
###############################串口配置#####################################################
# 配置引脚
fpioa = FPIOA()
fpioa.set_function(5, FPIOA.UART2_TXD)
fpioa.set_function(6, FPIOA.UART2_RXD)

# 初始化UART2,波特率115200,8位数据位,无校验,1位停止位
uart = UART(UART.UART2, baudrate=115200, bits=UART.EIGHTBITS, parity=UART.PARITY_NONE, stop=UART.STOPBITS_ONE)
##########################################################################################

###############################舵机配置#####################################################
# 2.5 = -90度 7.5 = 0度 12.5 = 90度
min_duty = 2.5      #最小占空比
max_duty = 12.5     #最大占空比
mid_duty = 7.5      # 中间值,对应于0度
pwm_lr = None
# 配置排针引脚号32,芯片引脚号为46的排针复用为PWM通道2输出
pwm_io2 = FPIOA()
pwm_io2.set_function(46, FPIOA.PWM2)
pwm_lr = PWM(2, 50, 50, enable=True)  # 配置PWM2,默认频率50Hz,占空比50% 
pwm_lr.duty(mid_duty)    #左右舵机旋转到中间
##########################################################################################

###############################PID配置#####################################################
#lr_kp = 0.013
#lr_ki = 0.0008
#lr_kd = 0.016
lr_kp = 0.02
lr_ki = 0.005
lr_kd = 0.1
pid_lr = PID(lr_kp, lr_ki, lr_kd,10,12.5)
##########################################################################################

#滤波
alpha = 0.1  # 滤波器系数,您可以根据需要调整这个值
lr_filter = LowPassFilter(alpha)

# 将数值转换为占空比的函数
def input_to_duty_cycle(input_value):
    min_input = -max_duty
    max_input = max_duty
    min_duty_cycle = min_duty
    max_duty_cycle = max_duty
    # 确保输入值在允许的范围内
    if input_value < min_input or input_value > max_input:
        raise ValueError(&#039;输入值超出范围,应为{}到{}&#039;.format(min_input,max_input))
    # 计算输出占空比
    output_value = min_duty_cycle + ((input_value - min_input) / (max_input - min_input)) * (max_duty_cycle - min_duty_cycle)
    return output_value

try:
    # 初始化摄像头
    sensor = Sensor(width = DETECT_WIDTH, height = DETECT_HEIGHT)
    # 传感器复位
    sensor.reset()
    # 开启镜像
    sensor.set_hmirror(False)#False
    # sensor vflip
    sensor.set_vflip(True)#False
    # 设置图像一帧的大小
    sensor.set_framesize(width = DETECT_WIDTH, height = DETECT_HEIGHT)
    # 设置图像输出格式为彩色的RGB565
    sensor.set_pixformat(Sensor.RGB565)
    # 使用IDE显示图像
    Display.init(Display.VIRT, width = DETECT_WIDTH, height = DETECT_HEIGHT, fps = 100)
    # 初始化媒体管理器
    MediaManager.init()
    # 摄像头传感器开启运行
    sensor.run()

    # 定义要识别颜色的阈值,这里需要根据你的具体情况调整
    # 你可以通过尝试不同的阈值来找到最适合你的物体颜色值
    red_threshold = (0, 42, 17, 94, -6, 50)

    while True:
        # 拍摄一张图片
        img = sensor.snapshot()
        # 查找图像中满足红色阈值的区域
        blobs = img.find_blobs([red_threshold], pixels_threshold=200, area_threshold=200, merge=True)

        # 如果找到了至少一个blob
        if blobs:
            # 找到最大的blob
            largest_blob = max(blobs, key=lambda b: b.pixels())

            # 画框
            img.draw_rectangle(largest_blob.rect(), color=(255, 0, 0))
            # 在框内画十字,标记中心点
            img.draw_cross(largest_blob.cx(), largest_blob.cy(), color=(255, 0, 0))

            # 计算相对于屏幕中心的X轴和Y轴的偏移量
            x_offset = largest_blob.cx() - img.width() // 2
            y_offset = largest_blob.cy() - img.height() // 2

            # 屏幕显示位置信息和像素大小,包含正负号
            wz = "x={}, y={}, w={}, h={}".format(x_offset, y_offset, largest_blob.w(), largest_blob.h())
            img.draw_string_advanced(0,0,32,wz)

            # 根据中心偏移量计算PWM的PID
            pid_lr_value = pid_lr.pid_calc(0,x_offset)

            pid_lr_value = -pid_lr_value
            # 转换输出实际的占空比
            duty_lr_value = input_to_duty_cycle(lr_filter.update(pid_lr_value))
            #duty_lr_value = input_to_duty_cycle(pid_lr_value)
            # 根据计算后的占空比控制舵机
            pwm_lr.duty(duty_lr_value)
            print(duty_lr_value)
            #串口输出当前识别物X轴 和 期望目标X轴
            zxc = "data:{},0\n".format(x_offset)
            uart.write(zxc)
    

        # 中心画十字
        img.draw_cross(img.width() // 2, img.height() // 2, color=(0, 255, 0), size=10, thickness=3)
        # IDE显示图片
        Display.show_image(img)

except KeyboardInterrupt as e:
    print(f"user stop")
except BaseException as e:
    print(f"Exception &#039;{e}&#039;")
finally:
    # sensor stop run
    if isinstance(sensor, Sensor):
        sensor.stop()
    # deinit display
    Display.deinit()

    if isinstance(pwm_lr, PWM):
        pwm_lr.deinit()

    # release media buffer
    MediaManager.deinit()

    os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
    time.sleep_ms(100)


 

 

评论

快捷导航

把好文章收藏到微信

打开微信,扫码查看

关闭

还没有账号?立即注册