STM32读取MPU6050数据并通过角度值控制舵机运动(STM32、GY-521 MPU6050、SG90舵机、MG946舵机)
通过STM32F103C8T6读取MPU6050数据控制舵机运动(STM32、GY-521 MPU6050、SG90舵机、MG946舵机)
- 最终现象
- 一、MPU6050数据读取
- 二、舵机控制原理
- ①什么是PWM?
- ②STM32F103C8T6如何生成PWM?
- ③控制舵机需要什么样的PWM波?
- 三、代码分析
- 四、完整工程代码
最终现象
STM32F103读取MPU6050数据控制舵机运动
一、MPU6050数据读取
使用软件IIC与MPU6050通信,这里可以直接参照之前的一篇博客:
https://blog.csdn.net/m0_71523511/article/details/135831042
二、舵机控制原理
通过输出占空比不同的PWM波就可以控制舵机转不同的角度。
①什么是PWM?
PWM全称脉冲宽度调制。通过对一系列脉冲的宽度进行调制来获得所需要的模拟参量,参用于电机控速等领域。
规定周期为Ts则频率为1/Ts,占空比为Ton/Ts(Ton为高电平时间)。如果频率为50Hz ,也就是说一个周期是20ms,那么一秒钟就有 50次PWM周期。
假设高电平为5V、低电平则为0V,那么要输出不同的模拟电压就要用到PWM。通过改变IO口输出的方波的占空比,从而获得使用数字信号模拟成的模拟电压信号。占空比为50%那就是高电平时间一半,低电平时间一半。在一定的频率下,就可以得到模拟的2.5V输出电压。那么75%的占空比,得到的电压就是3.75V,以此类推,如下图所示。
②STM32F103C8T6如何生成PWM?
想知道这部分原理直接看视频,视频最好理解:https://www.bilibili.com/video/BV1th411z7sn/?p=15&spm_id_from=pageDriver&vd_source=2a10d30b8351190ea06d85c5d0bfcb2a
③控制舵机需要什么样的PWM波?
舵机的控制需要MCU产生一个周期为20ms的脉冲信号,以0.5ms到2.5ms的高电平来控制舵机转动的角度。这里的角度根据自己的需求定,也可以是-90°-90°。
那么要产生周期为20ms的脉冲信号要怎么配置呢?
上图中ARR指的是定时器重载的周期,PSC指的是分频系数,CCR指的是输出比较寄存器的值。计算公式如下:
上面的CK_PSC为72Mhz,不同开发板的这个值是不一样的,也可以自己配置。比如要输出频率为1Khz,占空比为50%,分辨率为1%的PWM波,将这些值带入上面的公式可以得到ARR=100、PSC=720、CCR=50。
那么现在就可以来求舵机所需要的参数了,周期为20ms对应频率为1/0.02 = 50hz,在这里PSC和ARR的参数是不固定的,只要能满足第一个公式就可以,参考江协科技设置PSC为72-1,ARR为20000-1,这样设置的目的是此时CCR设置为500,那么占空比就是500/20000=0.025,即占空比为2.5%(看上面的控制参数);CCR设置为2500,那么占空比就是2500/20000=0.125,即占空比为12.5%(脉冲高电平时间2.5ms,转到180°),这样就很直观。
三、代码分析
1、main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
#include "usart.h"
#include <math.h>
#include "Servo.h"
#include "PWM.h"uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;
int16_t accel_agleX;
int16_t accel_agleY; int main(void)
{OLED_Init();MPU6050_Init();uart_init(115200);Servo_Init();OLED_ShowString(1, 1, "ID:");ID = MPU6050_GetID();OLED_ShowHexNum(1, 4, ID, 2);while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);accel_agleX = (AX + 300)*1.2*1800/3.14/15384; // 这里我的转换是不标准的,因为我发现使用别人的公式舵机的位置无法达到预期,所以我就观察oled上采集到的数值进行缩放,只要能到达平的时候是0就好,但是其实不用改16384为15384也行,改前面那个300,但是我偷懒了,这样改的快,但是只要能达到目的公式不是很重要,accel_agleX = accel_agleX -18;//修改完上面的参数发现还是有偏差,所以在测试之后,这里减去偏差值,基本就能确保角度是在-90-90之间。OLED_ShowSignedNum(2, 1, accel_agleX, 5);accel_agleY = (AY + 300)*1.2*1800/3.14/15384;accel_agleY = accel_agleY -13;OLED_ShowSignedNum(3, 1, accel_agleY, 5);if(accel_agleY >= 0){Servo_SetAngle(98-accel_agleY);}else if(accel_agleY <0){int16_t a = ~accel_agleY;Servo_SetAngle(a+90);}Delay_ms(100);if(accel_agleX >= 0){Servo_SetAngle2(98-accel_agleX);}else if(accel_agleX <0){int16_t a = ~accel_agleX;Servo_SetAngle2(a+90);}Delay_ms(100);}
}
2、PWM.c
#include "stm32f10x.h" // Device header/*** 函 数:PWM初始化* 参 数:无* 返 回 值:无*/
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式/*配置时钟源*/TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/ TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC2Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}void PWM2_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式/*配置时钟源*/TIM_InternalClockConfig(TIM3); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/ TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC1Init(TIM3, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2/*TIM使能*/TIM_Cmd(TIM3, ENABLE); //使能TIM2,定时器开始运行
}/*** 函 数:PWM设置CCR* 参 数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比* 占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare2(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare); //设置CCR2的值
}void PWM_SetCompare22(uint16_t Compare)
{TIM_SetCompare1(TIM3, Compare); //设置CCR2的值
}
实际上想驱动多个电机用一个定时器的多个输出通道就好了,但是我想试试用两个定时器,上面就是用两定时器不同输出通道的代码。如果是想用一个定时器的话之间加一个代码就行:
想要通道几就加通道几的代码。STM32F103C8T6的引脚图如下,想要使用定时器的输出通道,在对gpio进行初始化的时候需要选择复用推挽输出才行。
3、servo.c
驱动舵机直接调用前面封装好的底层代码即可。
#include "stm32f10x.h" // Device header
#include "PWM.h"/*** 函 数:舵机初始化* 参 数:无* 返 回 值:无*/
void Servo_Init(void)
{PWM_Init(); //初始化舵机的底层PWMPWM2_Init();
}/*** 函 数:舵机设置角度* 参 数:Angle 要设置的舵机角度,范围:0~180* 返 回 值:无*/
void Servo_SetAngle(float Angle)
{PWM_SetCompare2(Angle / 180 * 2000 + 500); //设置占空比//将角度线性变换,对应到舵机要求的占空比范围上
}
void Servo_SetAngle2(float Angle)
{PWM_SetCompare22(Angle / 180 * 2000 + 500); //设置占空比//将角度线性变换,对应到舵机要求的占空比范围上
}
四、完整工程代码
我用夸克网盘分享了「MPU6050控制舵机.rar」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
链接:https://pan.quark.cn/s/a07f1cbd8320
提取码:PV24
相关文章:

STM32读取MPU6050数据并通过角度值控制舵机运动(STM32、GY-521 MPU6050、SG90舵机、MG946舵机)
通过STM32F103C8T6读取MPU6050数据控制舵机运动(STM32、GY-521 MPU6050、SG90舵机、MG946舵机) 最终现象一、MPU6050数据读取二、舵机控制原理①什么是PWM?②STM32F103C8T6如何生成PWM?③控制舵机需要什么样的PWM波? 三…...
Unity_Playable工具使用
Unity_Playable工具使用 目录 Unity_Playable工具使用 1. Default Playables(Timeline扩展) 2. PlayableGraph Visualizer&#x...

Flutter canvas 画一条波浪线 进度条
之前用 Flutter Canvas 画过一个三角三角形,html 的 Canvas 也画过一次类似的, 今天用 Flutter Canvas 试了下 感觉差不多: html 版本 大致效果如下: 思路和 html 实现的类似: 也就是找出点的位置,使用二阶…...

Java技术栈 —— Spring MVC 与 Spring Boot
参考文章或视频链接[1] Spring vs. Spring Boot vs. Spring MVC[2] Key Differences Between Spring vs Spring Boot vs Spring MVC...

跟着cherno手搓游戏引擎【16】Camera和Uniform变量的封装
相机封装: OrthographicCamera.h: #pragma once #include <glm/glm.hpp> namespace YOTO {class OrthographicCamera{public:OrthographicCamera(float left,float right , float bottom,float top);const glm::vec3& GetPosition()const { return m_Pos…...

微服务中间件 RabbitMq学习
1、为什么需要Mq 例如在用户注册业务中,用户注册成功后 需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式;2.并行的方式 ; 假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间10…...
3D Gaussian Splatting-实时辐射场渲染技术
引用自:https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/3d_gaussian_splatting_high.pdf 概述: 该论文介绍了一种用于实时辐射场渲染的3D高斯点渲染技术。 其基本原理是: 一:首先从SfM校准的图像及其对应的稀疏点云…...
vue中nextTick()
在 Vue.js 中,nextTick() 是一个非常有用的方法,用于在下一个 DOM 更新循环结束后执行延迟回调。这在你需要读取或写入刚刚更新的 DOM 时非常有用。 下面是一个简单的示例代码,用于解析 nextTick() 的用法: <template> &…...

Redis 布隆过滤器
布隆过滤器 这一篇文章主要是记录布隆过滤器的使用和认识 主要参考了如下的blog https://blog.csdn.net/weixin_42972832/article/details/131211665 他讲的还不错 简单的来说,布隆过滤器,实际上就像是一个集合,拿redis的key来举例来说,布隆过滤器的设置就是去过滤不属于redi…...

中国的茶文化:现代生活中的茶文化
中国的茶文化:现代生活中的茶文化 引言 在现代社会的快节奏生活中,茶文化并未随时间流逝而褪色,反而以其独特的方式融入了全球各地人们的日常生活。它超越了饮品本身的范畴,成为一种连接历史、人文与现代生活方式的艺术形式。本文…...

Python正则表达式语法
正则表达式是一种强大的文本处理工具,它可以用来搜索、匹配和替换特定的字符模式。在Python中,正则表达式常常被用于处理字符串数据,例如文本搜索、数据提取、格式验证等任务。本文将深入介绍Python中正则表达式的语法,帮助读者全…...
C++核心编程:文件操作 笔记
5.文件操作 程序运行时产生的数据都属于临时数据,程序一旦允许结束都会被释放。通过文件可以将数据持久化 C中对文件操作需要包含头文件<fstream> 文件类型分为两种: 文本文件 - 文件以文本的ASCII码形式存储在计算机中二进制文件 - 文件以文本…...

ElementUI组件:Button 按钮
ElementUI安装与使用指南 Button按钮 点击下载learnelementuispringboot项目源码 效果图 el-button.vue页面效果图 项目里el-button.vue代码 <script> export default {name: "el_button",// 注意这里的名称不能和 router inex.js里的name一样methods: {s…...

#RAG|NLP|Jieba|PDF2WORD# pdf转word-换行问题
文档在生成PDF时,文宁都发生了什么。本文讲解了配置对象、resources对象和content对象的作用,以及字体、宇号、坐标、文本摆放等过程。同时,还解释了为什么PDF转word或转文字都是一行一行的以及为什么页眉页脚的问题会加大识别难度。最后提到了文本的编码和PDF中缺少文档结构标…...

solr的原理是什么
1 Java程序里如果有无限for循环的代码导致CPU负载超高,如何排查? 排查Java程序中由于无限循环导致的CPU负载过高的问题,可以按照以下步骤进行: 资源监控: 使用系统命令行工具(如Linux上的top或htop…...

【安装指南】nodejs下载、安装与配置详细教程
目录 🌼一、概述 🍀二、下载node.js 🌷三、安装node.js 🍁四、配置node.js 🌼一、概述 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,用于构建可扩展的网络应用程序。Node.js 使用事件驱动、…...

Mobileye CES 2024 自动驾驶新技术新方向
Mobileye亮相2024年国际消费类电子产品展览会推出什么自动驾驶新技术? Mobileye再次亮相CES,展示了我们的最新技术,并推出了Mobileye DXP--我们全新的驾驶体验平台。 与往年一样,Mobileye是拉斯维加斯展会现场的一大亮点,让参观者有机会见证我们对自主未来的愿景。 在…...
【Linux】网络基本配置及网络测试、测试工具
一、网络基本配置 查看网络信息: ifconfigc / ip addr停止网卡eth0: ifconfig eth0 down在本地启动网卡eth0: ifconfig eth0 up改变网卡ip: ifconfig eth0 192.168. .修改子网掩码: ifconfig eth0 (I…...

pnpm : 无法加载文件 D:\tool\nvm\nvm\node_global\pnpm.ps1,因为在此系统上禁止运行脚本
你们好,我是金金金。 场景 新创建的项目,在vscode编辑器终端输入 pnpm i,显示报错如上 解决 在终端输入get-ExecutionPolicy(查看执行策略/权限) 输出Restricted(受限的) 终端再次输入Set-ExecutionPolicy -Scope CurrentUser命令给用户赋予…...
Python 类与实例
在面向对象编程中,类(Class)是一种抽象的概念,它描述了对象的属性和行为。类可以看作是创建对象的蓝图或模板,它定义了一组属性和方法,并提供了创建对象的规范。 类包含了对象的属性和方法的定义ÿ…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...