🌈个人主页:羽晨同学

💫个人格言:“成为自己未来的主人~” 

在我们的这篇文章当中,我们主要想要实现的功能的是电机调速功能。在我们的这篇文章中,主要实现的是开环的功能,而非闭环,也就是不加入PID对电机速度进行控制。

首先,我们需要将STBY引脚设置为高电平,因为STBY为休眠引脚,当STBY为低电平的时候,电机处于休眠状态,此时电机是不会进行运动的。

由电路图可知,STBY引脚,也就是PA1引脚,所以,我们需要将PA1设置为高电平。

在这个之前,我们先实现按键的功能,通过按键控制电机速度。

static Button_TypeDef userKey;//用户按钮
static void OnUserKey_Clicked(uint8_t clicks);
//
// @简介: 按键任务的初始化函数
//
void App_Button_Init(void)
{Button_InitTypeDef Button_InitStruct = {0};Button_InitStruct.GPIOx = GPIOA;Button_InitStruct.GPIO_Pin = GPIO_Pin_11;My_Button_Init(&userKey,&Button_InitStruct);My_Button_SetClickCb(&userKey,OnUserKey_Clicked);
}

通过回调函数来控制按键按下之后的操作。

//
// @简介: 按键回调函数
//
static void OnUserKey_Clicked(uint8_t clicks)
{}

所以,接下来,我们通过回调函数来实现对应的功能

首先,我们先设置STBY,来控制是休眠状态还是启动状态,也就是说,我们需要先配置PA1引脚。

//
// @简介: 初始化STBY引脚 PA1 -- Out -PP
//
static void STBY_Pin_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);}

然后,我们设置一个函数来控制STBY的状态

//
// @简介: 控制TB6612进入休眠状态或者活动状态
// @参数: on  0 - 休眠状态,向STBY写L
// 					 非零 - 活动状态,向STBY写H
//
void App_Pwm_Cmd(uint8_t on)
{if(on == 0){GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);//休眠}else{GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);//觉醒}
}

这样的话,我们就可以在按键回调函数当中,通过按下按键来改变对应的状态

//
// @简介: 按键回调函数
//
static void OnUserKey_Clicked(uint8_t clicks)
{if(clicks == 1){pwm_on^=1;App_Pwm_Cmd(pwm_on);}
}

所以,这个时候来说,我们就实现了,只要按下按键,就可以控制STBY的状态。

然后,我们在main中对PWM进行初始化

#include "stm32f10x.h"
#include "delay.h"
#include "app_bat.h"
#include "app_button.h"
int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);App_Bat_Init();App_Button_Init();App_Pwm_Init();while(1){App_Bat_Proc();App_Button_Proc();}
}

这样,就可以实现通过按键来控制STBY的状态了。

接下来,我们就来实现电机调速的具体功能。

这个是电机控制的具体的引脚图,我们可以看到在这个H桥的结构当中,AIN1为正,AIN2为负的时候,是正转,否则反转,所以,我们可以通过控制AIN1和AIN2来控制电机转动的方向,通过PWMA和PWMB来控制速度,而对应的AIN1和AIN2为PA10和PA9,同理,BIN1和BIN2为PB5和PB7,所以,首先,我们需要对这些引脚进行初始化。

//
// @简介: 左电机的初始化
//
static void Motor_L_Init(void)
{//初始化 PA9 PA10为Out_PPRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);
}
//
// @简介: 右电机初始化
//
static void Motor_R_Init(void)
{//初始化PB5和PB7RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);
}

这样,我们就对对应的引脚进行了初始化

接下来,我们需要对对应的PWM进行初始化,一个是PA8一个是PB6,我们应该设置为AF_PP模式,因为PA8和PB6均被定时器所占用,所以为复用模式,又因为要输出高低电平,所以为推挽模式。

//
// @简介: 左电机的初始化
//
static void Motor_L_Init(void)
{//初始化 PA9 PA10为Out_PPRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//对PWM进行初始化,PA8 - AF_PPGPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);
}
//
// @简介: 右电机初始化
//
static void Motor_R_Init(void)
{//初始化PB5和PB7RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);//对PWM进行初始化,PB6 - AF_PPGPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);}

初始化完成之后,接下来,让我们完成对应的PWM的操作

这个是定时器内部的结构框图。

我们先来完成时基单元的参数设置,我们想要实现的效果为1000级可调,我们输入的是72MHz,为了让周期尽可能的小,所以我们的PSC为0,为了实现1000级可调,PWM是由CCR寄存器实现的,它的范围是0-ARR寄存器,所以,ARR应该为999,重复计数器RCR为0就好了,接下来,我们在代码中实现这个功能:

所以,我们对定时器进行初始化

	//对定时器1进行初始化RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);//设置时基单元的参数TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period = 999;TIM_TimeBaseInitStruct.TIM_Prescaler = 0;TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct);
	//设置时基单元的参数TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period = 999;TIM_TimeBaseInitStruct.TIM_Prescaler = 0;TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct);	

初始化时钟之后,我们对PWM进行设置,PWM是在CCR中进行设置,通过输出比较产生PWM波

	//配置输出比较TIM_OCInitTypeDef TIM_OCInitStruct = {0};TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStruct.TIM_OutputState = ENABLE;TIM_OCInitStruct.TIM_Pulse = 0;TIM_OC1Init(TIM1,&TIM_OCInitStruct);//配置MOE的开关TIM_CtrlPWMOutputs(TIM1,ENABLE);//闭合定时器的总开关TIM_Cmd(TIM1,ENABLE);
	//配置输出比较TIM_OCInitTypeDef TIM_OCInitStruct = {0};TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStruct.TIM_OutputState = ENABLE;TIM_OCInitStruct.TIM_Pulse = 0;TIM_OC1Init(TIM4,&TIM_OCInitStruct);//配置MOE的开关TIM_CtrlPWMOutputs(TIM4,ENABLE);//闭合定时器的总开关TIM_Cmd(TIM4,ENABLE);

接下来,我们来设置左右电机的占空比

//
// @简介: 控制左电机的PWM
// @参数: duty 占空比的具体指,-100.0f - 100.0f
//
void App_Pwm_Set_L(float duty)
{float sign;//符号,正数 - +1,负数, - 1if(duty >= 0) sign = 1;else sign = -1;duty = fabs(duty);if(sign >= 0)//正传{GPIO_WriteBit(GPIOA,GPIO_Pin_9,Bit_SET);// AIN1 - 高GPIO_WriteBit(GPIOA,GPIO_Pin_10,Bit_RESET);//AIN2 - 低}else//反转{GPIO_WriteBit(GPIOA,GPIO_Pin_10,Bit_SET);// AIN1 - 高GPIO_WriteBit(GPIOA,GPIO_Pin_9,Bit_RESET);//AIN2 - 低	}uint16_t ccr = duty/100.0f*999;TIM_SetCompare1(TIM1,ccr);
}
//
// @简介: 控制右电机的PWM
// @参数: duty 占空比的具体指,-100.0f - 100.0f
//
void App_Pwm_Set_R(float duty)
{float sign;//符号,正数 - +1,负数, - 1if(duty >= 0) sign = 1;else sign = -1;duty = fabs(duty);if(sign >= 0)//正传{GPIO_WriteBit(GPIOB,GPIO_Pin_5,Bit_SET);// AIN1 - 高GPIO_WriteBit(GPIOB,GPIO_Pin_7,Bit_RESET);//AIN2 - 低}else//反转{GPIO_WriteBit(GPIOB,GPIO_Pin_7,Bit_SET);// AIN1 - 高GPIO_WriteBit(GPIOB,GPIO_Pin_5,Bit_RESET);//AIN2 - 低	}uint16_t ccr = duty/100.0f*999;TIM_SetCompare1(TIM4,ccr);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97857.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97857.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97857.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从利润率看价值:哪些公司值得长期持有?

💡 为什么盯紧利润率? 投资者常常盯着营收增长,却忽略了一个更关键的指标——利润率。 收入可以靠规模“堆”出来,但利润率却是企业护城河的真实体现。心理学研究表明:当一个产品或服务被消费者认定为“不可替代”&a…

小迪web自用笔记25

传统文件上传:上传至服务器本身硬盘。云存储:借助云存储oss对象存储(只能被访问,不可解析)Oss云存储Access key与Access ID:有了这两个东西之后就可以操作云存储,可以向里面发数据了。这玩意儿泄…

分发饼干——很好的解释模板

好的,孩子,我们来玩一个“喂饼干”的游戏。 0. 问题的本质是什么? 想象一下,你就是个超棒的家长,手里有几块大小不一的饼干,而面前有几个饿着肚子的小朋友。每个小朋友都有一个最小的“胃口”值&#xff0c…

场景题:如果一个大型项目,某一个时间所有的CPU的已经被占用了,导致服务不可用,我们开发人员应该如何使服务器尽快恢复正常

问:如果一个大型项目,某一个时间所有的CPU的 已经被占用了,导致服务不可用,我们开发人员 应该如何使服务器尽快恢复正常答:应对CPU 100%导致服务不可用的紧急恢复流程面试官,如果遇到这种情况,我会立即按照…

Docker 安装 RAGFlow保姆教程

前提条件 Ubuntu 服务器(20.04 或 22.04 LTS 推荐) 已安装 Docker 和 Docker Compose 如果尚未安装,请先运行以下命令:# 安装 Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # 将当前用户加入 docker 组,避免每次都要 sudo sudo user…

为什么实际工程里 C++ 部署深度学习模型更常见?为什么大家更爱用 TensorRT?

很多人刚接触深度学习模型部署的时候,都会习惯用 Python,因为训练的时候就是 PyTorch、TensorFlow 啊,写起来方便。但一到 实际工程,特别是工业设备、医疗影像、上位机系统这种场景,你会发现大多数人都转向了 C 部署。…

深入理解 Java 集合框架:底层原理与实战应用

在日常开发中,集合是 Java 中使用频率最高的工具之一。从最常见的 ArrayList、HashMap 到更复杂的并发集合,几乎每一个 Java 程序员都离不开集合框架。集合框架不仅提供了丰富的数据结构实现,还封装了底层复杂的逻辑,让开发者能够…

爬取m3u8视频完整教程

爬取步骤:1.先找到网页源代码2.从网页源代码中拿到m3u83.下载m3u84.读取m3u8文件,下载视频5.合并视频首先我们来爬取一个星辰影院的电影:下面我以这个为例:我们需要在源代码中找到m3u8这个url:紧接着我们利用下面的方法…

Python爬虫实战: 基于Scrapy的Amazon跨境电商选品数据爬虫方案

概述与设计思路 利用Python的Scrapy框架进行大规模页面抓取和结构化数据提取,配合aiohttp实现高并发请求,从而高效获取Amazon平台上的商品列表、详情、评论等公开信息。通过对这些数据进行清洗与分析,可以识别出有潜力的商品,评估市场竞争程度,并跟踪竞争对手的动态,为跨…

稳定版IM即时通讯 仿默往APP即时通讯im源码聊天社交源码支持二开原生开发独立部署 含搭建教程

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 技术开发语言: 后台管理端:Java GO Mysql数据库 安卓端:Java iOS端:ob PC端:c 功能简单介绍: 单聊&#xff…

封装一个redis获取并解析数据的工具类

redis获取并解析数据工具类实现代码使用示例实现代码 import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import lom…

23种设计模式——策略模式 (Strategy Pattern)​详解

✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。 🍎个人主页:Meteors.的博客 💞当前专栏:设计模式 ✨特色专栏:知识分享 &#x…

CI(持续集成)、CD(持续交付/部署)、CT(持续测试)、CICD、CICT

目录 **CI、CD、CT 详解与关系** **1. CI(Continuous Integration,持续集成)** **2. CD(Continuous Delivery/Deployment,持续交付/部署)** **持续交付(Continuous Delivery)** **持续部署(Continuous Deployment)** **3. CT(Continuous Testing,持续测试)** **4.…

【音视频】WebRTC ICE 模块深度剖析

原文链接: https://mp.weixin.qq.com/s?__bizMzIzMjY3MjYyOA&mid2247498075&idx2&sn6021a2f60b1e7c71ce4d7af6df0b9b89&chksme893e540dfe46c56323322e780d41aec1f851925cfce8b76b3f4d5cfddaa9c7cbb03a7ae4c25&scene178&cur_album_id314699…

linux0.12 head.s代码解析

重新设置IDT和GDT,为256个中断门设置默认的中断处理函数检查A20地址线是否启用设置数学协处理器将main函数相关的参数压栈设置分页机制,将页表映射到0~16MB的物理内存上返回main函数执行 源码详细注释如下: /** linux/boot/head.s** (C) 1991 Linus T…

Maven动态控制版本号秘籍:高效发包部署,版本管理不再头疼!

作者:唐叔在学习 专栏:唐叔的Java实践 关键词:Maven版本控制、versions插件、动态版本号、持续集成、自动化部署、Java项目管理 摘要:本文介绍如何使用Maven Versions插件动态控制项目版本号和依赖组件版本号,实现无需…

简述:普瑞时空数据建库软件(国土变更建库)之一(变更预检查部分规则)

简述:普瑞时空数据建库软件(国土变更建库)之一(变更预检查部分规则) 主要包括三种类型:常规检查、行政区范围检查、20X异常灭失检查 本blog地址:https://blog.csdn.net/hsg77

shell中命令小工具:cut、sort、uniq,tr的使用方式

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、cut —— 按列或字符截取1. 常用选项2. 示例二、sort —— 排序(默认按行首字符升序)1. 常用选项常用 sort 命令选项三、uniq —— 去…

【Linux】Linux开发必备:Git版本控制与GDB调试全指南

前言:在Linux开发流程中,版本控制与程序调试是保障项目稳定性和开发效率的两大核心环节。Git作为当前最主流的分布式版本控制系统,能高效管理代码迭代、追踪修改记录并支持多人协同开发;GDB(GNU调试器)是Li…

实现 TypeScript 内置工具类型(源码解析与实现)

目标读者:已经熟悉 TypeScript 基础语法、泛型、条件类型的同学。本文按常见工具类型的分类与顺序实现并解释 Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、NonNullable、ReturnType、Parameters、ConstructorParameters、InstanceType、Th…