当前位置: 首页 > news >正文

【STM32+HAL】杆球控制系统

一、前言

2017年电赛出了道板球控制系统题目,现写一个简化版本——杆球控制系统,以此记录电赛集训生活。

二、题目分析

最终采取的方案是:OpenMV读取小球的当前位置,并将坐标值传给STM32端,再由32通过电机改变杆的位置来改变乒乓球位置,由此实现闭环控制。模式间切换通过按键实现。

三、 所用工具

1、电机:DengFOC的2208无刷云台电机(无刷电机控制精度更高,算法较舵机难)

2、芯片:STM32F407ZGT6

3、机械结构:建议3D打印,提高精度,减轻算法压力。

四、CubeMX配置

1、控制+计时定时器

频率 frequency = 84MHz / 84 / 1000 = 1000Hz,即周期为1ms

2、三路PWM输出定时器

FOC控制需三路PWM波,频率设为25kHz

有关DengFOC的更多内容,请移步B站搜索DengFOC。

3、LCD屏显示配置

详见【STM32+HAL】LCD显示及触摸初始化配置,这里不再赘述。

4、读取AS5600编码器

配置IIC模式读取编码器值,获取当前电机转动角度。

5、串口配置

串口一通过DMA与OpenMV通信,有关串口DMA传输的内容,详见【STM32+HAL】DMA应用

至此,CuebMX配置完毕。

五、OpenMV识别

代码不难,大家自行理解吧。想了解更多OpenMV技术细节详见【OPENMV】学习记录 (持续更新)

import sensor, image, time, ustruct
from pyb import UARTuart = UART(3, 115200, timeout_char=200)
uart.init(115200, bits=8, parity=None, stop=1)  # init with given parameters
roi = (30, 50, 126, 22)# 初始化摄像头
sensor.reset()                        # 初始化感应器
sensor.set_pixformat(sensor.RGB565)   # 设置像素格式为RGB565
sensor.set_framesize(sensor.QQVGA)     # 设置帧大小为320x240
sensor.set_brightness(-3)
sensor.set_contrast(-3)
sensor.set_saturation(-3)
sensor.set_windowing(roi)
sensor.skip_frames(time = 2000)       # 跳过前2秒帧,用于摄像头设置稳定
sensor.set_auto_gain(False)           # 必须关闭才能进行颜色跟踪
sensor.set_auto_whitebal(False)       # 必须关闭才能进行颜色跟踪
clock = time.clock()                  # 初始化时钟对象def send_data(x):global uart;uart.write(str(x))uart.write(bytearray([0x20]))# 设置颜色阈值,识别黄色
yellow_threshold = (80, 100, -35, 29, 32, 100)
white_threshole = (90,100,-2,2,-2,2)while(True):clock.tick()                      # 开始新帧计时sensor.set_hmirror(True)img = sensor.snapshot().lens_corr(1.9).mean(1)           # 捕获图像blobs = img.find_blobs([yellow_threshold])if blobs: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))send_data(largest_blob.cx())

六、Keil填写代码

1、电机控制

此文件包含两个函数:

PID_Control:控制乒乓球运动到指定位置

PID_Sin:控制乒乓球以杆中心做正弦运动

有关无刷电机驱动的内容,详见[STM32+HAL]DengFOC移植之闭环速度控制

#include "Control.h"
#include "tim.h"
#include "FOC2.h"#define KP 	0.21			// 比例系数
#define KI 	0.000022		// 积分系数
#define KD  13       		// 微分系数double Current_position;			//小球当前位置
double Target_position;			//小球目标位置/* 定点PID控制函数 */
float PID_Control(float Current,float Target)
{double Error, Integral, Derivative, LastError;/*PID算法*/Error = Target - Current;								// 当前误差Integral += Error;Derivative = Error - LastError;							// E[k]-E[k-1]项/*更新输出值*/float temp = KP * Error + (KI) * Integral + KD * Derivative;temp = (temp > 20 ) ? 20 : (temp < -20) ? -20 :temp;	//限幅LastError = Error;										// 存储误差,用于下次计算return temp;
}/* Sin运动PID控制函数 */
float PID_Sin(float Current,float Target)
{double Error1, Integral1, Derivative1, LastError1;	/*Sin波的PID算法*/float P = 0.5;float D = 3;Error1 = Target - Current;								// 当前误差Derivative1 = Error1 - LastError1;			/*更新输出值*/float temp = P * Error1 + D * Derivative1;temp = (temp > 20 ) ? 20 : (temp < -20) ? -20 :temp;	//限幅LastError1 = Error1;return temp;
}

2、LCD显示
/* 界面一: 显示小球目标与当前位置曲线 */
void Show_1(uint8_t flag)
{LCD_ShowString(20,160,100,16,16,"Distance:");LCD_ShowString(160,160,100,16,16,"target_cx:");LCD_ShowString(20,210,100,16,16,"Overshoot:");LCD_ShowString(160,210,100,16,16,"Time:");if(fabs(target_cx - cx) < 2 && flag_Overshoot == 0)		//第一次经过目标值时,重置超调量,改变标志位{flag_Overshoot = 1;Max_Overshoot = 0;Overshoot = 0;}if(flag_Overshoot == 1)									//判断已经过了一次目标值后的最大偏移{Overshoot = fabs(target_cx - cx);Max_Overshoot = (Max_Overshoot <= Overshoot) ? Overshoot :Max_Overshoot;	//取最大值}LCD_ShowNum(95,160,distance,3,16);LCD_ShowNum(240,160,distance_target,3,16);LCD_ShowNum(103,210,(Max_Overshoot - 8.057) / 3.4615,3,16);	//距离值与像素转换LCD_ShowNum(240,210,time,5,16);draw_distance_wave1(distance,distance_target);			//绘制小球位置曲线
}/* 界面二:两种模式之间切换界面 */
void Show_2(uint8_t flag)
{LCD_ShowString(20,160,100,16,16,"Left point:");LCD_ShowNum(105,160,2,3,16);LCD_ShowString(175,160,100,16,16,"Right point:");LCD_ShowNum(270,160,30,3,16);LCD_ShowString(20,30,100,16,16,"Set Cycle:");LCD_ShowNum(115,30,3,2,16);
}/* 界面三:Sin运动曲线 0 - 30cm */
void Show_3(uint8_t flag)
{LCD_ShowString(20,210,100,16,16,"Left point:");LCD_ShowString(160,210,100,16,16,"Right point:");LCD_ShowString(160,30,100,16,16,"Fact Cycle:");LCD_ShowNum(115,210,2,3,16);LCD_ShowNum(255,210,30,3,16);LCD_ShowNum(255,30,3,3,16);draw_distance_wave(distance);			//绘制小球位置曲线
}/* 显示的总菜单 */
void Show(uint8_t flag)
{if(flag != last_flag) LCD_Clear(WHITE);switch (flag){case 1:Show_1(flag),HAL_TIM_Base_Start_IT(&htim2);	//界面一,定点控制break;case 2:Show_2(flag),HAL_TIM_Base_Stop_IT(&htim2);	//界面二,两种模式停顿切换界面break;case 3:Show_3(flag),HAL_TIM_Base_Start_IT(&htim2);	//界面三,Sin运动曲线控制break;default:break;}last_flag = flag;
}

3、mian.c

为缩减篇幅,下附代码删减了部分冗余的代码,相信以大家的聪明才智一定看得懂!

思路:初始化后,当按键按下切换模式后,显示对应的界面,并在定时器回调函数中进行一系列的控制与计时。

int main(void)
{/* USER CODE BEGIN 2 *//* OpenMV接收数据初始化 */__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);HAL_UART_Receive_DMA(&huart1,rx_buffer,RXBUFFERSIZE);/* FOC初始化 */DFOC_Vbus(VMax);DFOC_alignSensor(PP,DIR);HAL_Delay(200);/* LCD初始化 */Show_Init();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){Show(flag);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)		    //1ms定时器
{	/* 电机定时控制 */if( htim -> Instance == TIM2){/* flag == 1时进入闭环位置控制 */if(flag == 1){if(flag_time_loop == 0) time_loop++;					//若不为目标位置,时间周期++time = 1.5 * time_loop;									//时间转换target_cx = distance_target * 3.4615 + 8.057;DFOC_M0_set_Force_Angle(PID_Control(cx,target_cx));		//设置位置}/* 当flag == 3时进入曲线控制 */else if(flag == 3){DFOC_M0_set_Force_Angle(PID_Sin(cx,60));				//设置位置}else{time_loop = 0;flag_time_loop = 0;}}
}/* 按键消抖 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET){HAL_Delay(20); //延时消抖if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET){/* flag切换模式 */flag = (flag + 1) % 4;}}else if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET){HAL_Delay(20); //延时消抖if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET){/* distance = 30 切换距离 */distance_target = (distance_target + 1) % 31;}}
}
/* USER CODE END 4 */

七、源码提供

夸克:我用夸克网盘分享了「Ball_Control」,点击链接即可保存。

百度:通过百度网盘分享的文件 提取码:6666

Gitee:Ball_Control

CSDN:Ball_Control

八、成果欣赏

Ball_Control

九、结语

本人能力有限,代码未必是最优解,若有可改进之处望在评论区留言。

如有小伙伴想交流学习心得,欢迎加入群聊751950234,群内不定期更新代码,以及提供本人博客所有源码

相关文章:

【STM32+HAL】杆球控制系统

一、前言 2017年电赛出了道板球控制系统题目&#xff0c;现写一个简化版本——杆球控制系统&#xff0c;以此记录电赛集训生活。 二、题目分析 最终采取的方案是&#xff1a;OpenMV读取小球的当前位置&#xff0c;并将坐标值传给STM32端&#xff0c;再由32通过电机改变杆的位置…...

用Python实现9大回归算法详解——04. 多项式回归算法

多项式回归 是线性回归的一种扩展&#xff0c;它通过将输入特征的多项式项&#xff08;如平方、立方等&#xff09;引入模型中&#xff0c;以捕捉数据中非线性的关系。虽然多项式回归属于线性模型的范畴&#xff0c;但它通过增加特征的多项式形式&#xff0c;使得模型能够拟合非…...

vue打包更新packge.json版本号

VUE项目打包自动更新版本号 此方法只针对 Vue 如果使用其他框架&#xff0c;可以此参照作为参考 一、先看效果 二、创建 buildVersion.js 文件 文件内容 目前只针对3位版本号 递增规则是 每次更新 加一次小版本&#xff0c;10次小版本向前递增一个版本。如&#xff1a;1.0.9 递…...

计算机视觉技术解析:从基础到前沿

第一部分&#xff1a;计算机视觉基础与基本原理 计算机视觉是人工智能领域的一个重要分支&#xff0c;旨在使计算机能够理解和处理图像和视频数据。随着深度学习技术的飞速发展&#xff0c;计算机视觉已经在许多实际应用场景中取得了显著的成果&#xff0c;如图像识别、目标检…...

unity游戏开发003:深入理解Unity中的坐标系

Unity游戏开发 “好读书&#xff0c;不求甚解&#xff1b;每有会意&#xff0c;便欣然忘食。” 本文目录&#xff1a; Unity游戏开发 Unity游戏开发深入理解Unity中的坐标系前言1. 坐标轴2. 左手坐标系3. 世界坐标系 vs. 局部坐标系4. 坐标变换5. 注意事项 总结 深入理解Unity中…...

伊索寓言两则

马和驴 马为自己精美的马具感到骄傲&#xff0c;在大马路上遇见了驴子子正驮着重担挪着步子&#xff0c;挡了路&#xff0c;马儿没法过去&#xff0c;就不耐烦叫道&#xff1a;真想踢你两脚&#xff0c;好让你走快点。驴子沉默不语&#xff0c;但没忘马儿的傲慢。不久后马儿患…...

嵌入式硬件产品开发:编码文件规则

目录 简介 文件内容的一般规则 文件名命名的规则 简介 一个工程是往往由多个文件组成。 这些文件怎么管理、怎么命名都是非常重要的。 文件内容的一般规则 【规则1】每个头文件和源文件的头部必须包含文件头部说明和修改记录。 源文件和头文件的头部说明必须包含的内容和次…...

设计模式 - 组合模式

💝💝💝首先,欢迎各位来到我的博客!本文深入理解设计模式原理、应用技巧、强调实战操作,提供代码示例和解决方案,适合有一定编程基础并希望提升设计能力的开发者,帮助读者快速掌握并灵活运用设计模式。 💝💝💝如有需要请大家订阅我的专栏【设计模式】哟!我会定…...

打靶记录11——Billu_b0x

靶机&#xff1a; https://download.vulnhub.com/billu/Billu_b0x.zip难度&#xff1a; 中&#xff08;两种攻击路线&#xff09; 目标&#xff1a; 取得root权限 涉及的攻击方法&#xff1a; 主机发现端口扫描Web信息收集SQL注入&#xff08;Sqlmap跑不出来&#xff09;…...

一、在cubemx上配置sd和fatfs示例演示

一、sd和fatfs的配置流程界面 1、选择sd4bits 根据自己的sd卡的硬件插槽进行选择。 2、fatfs配置由于使用的是sd卡所以直接选择sd选项 3、程序中对sd卡的初始化需要进行改动&#xff0c;直接使用默认的参数sd卡是挂载不上的。 4、在sd卡挂载好后&#xff0c;就可以使用文件系统…...

C++ 语言特性02 - 命名空间

一&#xff1a;概述 现代C中的命名空间是什么&#xff1f; C中的命名空间允许用户在命名空间范围内对类、方法、变量和函数等实体进行分组&#xff0c;而不是在全局范围内使用。这可以防止大型项目中的类、方法、函数和变量之间发生命名冲突。命名空间将开发人员编写的代码组织…...

drools规则引擎 规则配置文件drl语法使用案例

前提&#xff1a;环境搭建&#xff0c;参考博文springboot整合drools规则引擎 示例入门-CSDN博客案例1&#xff0c;商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分订单pojo编写 package cn.beijing.model;import lom…...

C++编程:高性能通信组件Capnproto与Protobuf的对比分析

文章目录 0. 概要1. 测试环境2. 测试方法3. 测试结果及分析3.1 延迟测试3.2 吞吐量测试3.3 稳定性测试3.4 一对二测试记录3.5 二对一测试记录3.6 Inter-process 单个点开销分析 4. CapnProto 与 Protobuf 的对比测试总结 0. 概要 本文主要探讨了两种高性能通信组件&#xff1a…...

【Python读书数据,并计算数据的相关系数、方差,均方根误差】

为了处理Python中的读书数据&#xff08;假设这里指的是一系列关于书籍阅读量或评分的数据&#xff09;&#xff0c;并计算这些数据的相关系数、方差以及均方根误差&#xff08;RMSE&#xff09;&#xff0c;我们首先需要明确数据的结构。这里&#xff0c;我将假设我们有一组关…...

垃圾收集器G1ZGC详解

G1收集器(-XX:UseG1GC) G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征. G1将Java堆划分为多个大小相等的独立区域&#xff08;Region&#xff09;&#xff0c;JVM目…...

AI芯片:高性能卷积计算中的数据复用

随着深度学习的飞速发展&#xff0c;对处理器的性能要求也变得越来越高&#xff0c;随之涌现出了很多针对神经网络加速设计的AI芯片。卷积计算是神经网络中最重要的一类计算&#xff0c;本文分析了高性能卷积计算中的数据复用&#xff0c;这是AI芯片设计中需要优化的重点之一&a…...

gitlab修改默认访问端口

GitLab 自带了一个 Nginx 服务器实例&#xff0c;用于处理 HTTP 和 HTTPS 请求。这个内置的 Nginx 服务器被配置为与 GitLab 应用程序实例一起工作&#xff0c;并且它负责处理所有前端的网络通信。 通过yum或者apt安装Gitlab时&#xff0c;nginx通常是被自带安装并配置好的。 …...

python——异常

Python 中的异常及继承关系 在 Python 中&#xff0c;异常用于表示程序在运行过程中遇到的错误&#xff0c;所有异常类最终都继承自 BaseException。通过异常处理&#xff0c;我们可以捕获和处理这些错误&#xff0c;避免程序崩溃。 Python 异常继承关系图 BaseException-- …...

【人工智能】利用TensorFlow.js在浏览器中实现一个基本的情感分析系统

使用TensorFlow.js在浏览器中进行情感分析是一个非常实用的应用场景。TensorFlow.js 是一个用于在JavaScript环境中训练和部署机器学习模型的库&#xff0c;使得开发者能够在客户端直接运行复杂的机器学习任务。对于情感分析&#xff0c;我们可以使用预先训练好的模型来识别文本…...

Python——扩展数据类型

Python 的扩展数据类型是对内置数据类型的增强&#xff0c;旨在解决特定需求&#xff0c;提供更高级的功能。我们来看一些常见的扩展数据类型及其原理、用途&#xff0c;并通过示例逐步讲解。 1. collections.namedtuple namedtuple 是增强的元组&#xff0c;允许用名称访问元…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...