基于stm32f103c8t6的机械四爪

本文介绍了如何使用STM32F103C8T6开发板,配合8个SG90舵机和一块IICOLED屏,通过TIM2和TIM4实现机械四足的简单控制。作者分享了代码示例和安装步骤,强调这是一个适合初学者的项目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在刷b站时,偶然看到了一个up主制作的机械四足,于是突发奇想就做了一个简易版的,做的非常简单见谅。

效果展示 

id="rJ7RQFdV-1701252315254" frameborder="0" src="https://live.csdn.net/v/embed/347393" allowfullscreen="true" data-mediaembed="csdn">

机械四爪展示视频

所需材料

1.一块stm32f103c8t6开发板

2.8个蓝色SG90经典180度舵机

3.一块iic通信的oled屏(大屏小屏都可以)

4.一块3.7v锂电池+一块5v锂电池  or  用我的懒蛋方法直接使用stlink供电

原理图和PCB设计说明

使用软件为嘉立创EDA(专业版)

已在嘉立创开源平台开源:基于stm32的机械四爪 - 嘉立创EDA开源硬件平台

这里就主要对代码进行说明。

代码部分说明

使用的keil5来进行编程,这里其实只需要编写一下PWM的文件,oled直接抄网上整理好的头文件就好了。

所用头文件总览

是的没错,只需要知道舵机和oled屏如何控制就可以进行制作,因为没有加其他蓝牙等模块,所以极其简单,适合我这种萌新做着玩。 

PWM控制舵机部分

需要先查看stm32的TIM时钟表

我这里选用了TIM2和TIM4,所以要对PA0、PA1、PA2、PA3和PB6、PB7、PB8、PB9这几个IO口进行定义:

舵机控制的.h文件

#ifndef __DUOJI_H
#define __DUOJI_H	

void Pwm_Init_TIM2(void);
void Pwm_Init_TIM4(void);

#endif

舵机控制的.c文件 

#include "stm32f10x.h"                  // Device header
#include "duoji.h"
 
void Pwm_Init_TIM2(void)
{
	GPIO_InitTypeDef GPIO_InitTypeStruce;    //这里的名称我自己是喜欢定义很短的
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruce;    //这里是看大佬们的定义方式定义的
	TIM_OCInitTypeDef TIM_OCInitTypeStruce;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    //使能GPIOA口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);    //使能复用时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);    //使能TIM2定时器时钟
	
	GPIO_InitTypeStruce.GPIO_Mode=GPIO_Mode_AF_PP;    //复用推挽输出
	GPIO_InitTypeStruce.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIO_InitTypeStruce.GPIO_Speed=GPIO_Speed_50MHz;
	
	TIM_TimeBaseInitTypeStruce.TIM_Period=1999;    //2000-1    arr
	TIM_TimeBaseInitTypeStruce.TIM_Prescaler=719;    //720-1    psc
    //这里是算一个 20ms = (720*2000)/72000000=0.02

    //TIM_TimeBaseInitTypeStruce.TIM_Period=199;    //200-1
	//TIM_TimeBaseInitTypeStruce.TIM_Prescaler=7199;    //7200-1
    //这样算也是 20ms = (7200*200)/72000000=0.02
    
	TIM_TimeBaseInitTypeStruce.TIM_ClockDivision=0;    
	TIM_TimeBaseInitTypeStruce.TIM_CounterMode=TIM_CounterMode_Up;
	
	TIM_OCInitTypeStruce.TIM_OCMode=TIM_OCMode_PWM1;    //选择PWM模式1
	TIM_OCInitTypeStruce.TIM_OutputState=TIM_OutputState_Enable;    //比较输出使能
	TIM_OCInitTypeStruce.TIM_OCPolarity=TIM_OCPolarity_High;    
	
	GPIO_Init(GPIOA,&GPIO_InitTypeStruce);    //这里对所有的TIM2时钟的通道进行初始化
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitTypeStruce);
	TIM_OC1Init(TIM2,&TIM_OCInitTypeStruce);
	TIM_OC2Init(TIM2,&TIM_OCInitTypeStruce);
	TIM_OC3Init(TIM2,&TIM_OCInitTypeStruce);
	TIM_OC4Init(TIM2,&TIM_OCInitTypeStruce);
	
	TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable);
	TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);
	TIM_OC3PreloadConfig(TIM2,TIM_OCPreload_Enable);
	TIM_OC4PreloadConfig(TIM2,TIM_OCPreload_Enable);
	
	TIM_Cmd(TIM2,ENABLE);
	
}

void Pwm_Init_TIM4(void)
{
	GPIO_InitTypeDef GPIO_InitTypeStruce;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruce;
	TIM_OCInitTypeDef TIM_OCInitTypeStruce;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);    //使能GPIOB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);    //使能复用时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);    //使能TIM4定时器时钟
	
	GPIO_InitTypeStruce.GPIO_Mode=GPIO_Mode_AF_PP;    //复用推挽输出
	GPIO_InitTypeStruce.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
	GPIO_InitTypeStruce.GPIO_Speed=GPIO_Speed_50MHz;
	
	TIM_TimeBaseInitTypeStruce.TIM_Period=1999;    //arr
	TIM_TimeBaseInitTypeStruce.TIM_Prescaler=719;    //psc
	TIM_TimeBaseInitTypeStruce.TIM_ClockDivision=0;    
	TIM_TimeBaseInitTypeStruce.TIM_CounterMode=TIM_CounterMode_Up;
	
	TIM_OCInitTypeStruce.TIM_OCMode=TIM_OCMode_PWM1;
	TIM_OCInitTypeStruce.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OCInitTypeStruce.TIM_OCPolarity=TIM_OCPolarity_High;
	
	GPIO_Init(GPIOB,&GPIO_InitTypeStruce);    //对TIM4时钟对应的四个通道初始化
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitTypeStruce);
	TIM_OC1Init(TIM4,&TIM_OCInitTypeStruce);
	TIM_OC2Init(TIM4,&TIM_OCInitTypeStruce);
	TIM_OC3Init(TIM4,&TIM_OCInitTypeStruce);
	TIM_OC4Init(TIM4,&TIM_OCInitTypeStruce);
	
	TIM_OC1PreloadConfig(TIM4,TIM_OCPreload_Enable);
	TIM_OC2PreloadConfig(TIM4,TIM_OCPreload_Enable);
	TIM_OC3PreloadConfig(TIM4,TIM_OCPreload_Enable);
	TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable);
	
	TIM_Cmd(TIM4,ENABLE);
	
}

对舵机控制时,我们就需要使用淘宝之力,搜出来舵机参数,下图就是淘宝里搜SG90,180度舵机的数据手册:

 我们可以看出需要20ms的脉冲,对TIMx计数器产生的PWM计算中

    TIM_TimeBaseInitTypeStruce.TIM_Period=arr;
	TIM_TimeBaseInitTypeStruce.TIM_Prescaler=psc;
	TIM_TimeBaseInitTypeStruce.TIM_ClockDivision=0;
TIM_TimeBaseInitTypeStruce.TIM_ClockDivision=0;

是重复计数器,高级定时器才有,不需要用所以直接给0关闭就好了。

    TIM_TimeBaseInitTypeStruce.TIM_Period=arr;
	TIM_TimeBaseInitTypeStruce.TIM_Prescaler=psc;

arr和psc这两个参数决定我们的定时时间。

arr是计数器的自动重装值,也就是执行中断的总次数,可以确定定时器溢出的时间间隔

psc是预分频器,将系统时钟分频后作为定时器的时钟源,TIM时钟的总线时钟最大为72MHz(CK_psc)。

时间计算公式

time=\frac{1}{f}=\frac{(arr+1)(psc+1)}{CKpsc}

这里的CK_psc就是72MHz,我们就可以算出很多组时间等于20ms的数据

arr==200-1;psc==7200-1;        //这样就是以比较低的频率计比较少的数

arr==2000-1;psc==720-1;        //这样就是以比较高的频率计比较多的数

这些数据算出来,理论上都可以直接使用,只是arr和psc的值会影响TIM的计数速度和计数周期。

arr的值还会影响我们接下来的ccr占空比控制。

但是要注意arr和psc取值都要在0~65535之间。

编写完PWM初始化的文件后我们就可以使用ccr来控制占空比,控制旋转的范围。

TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

这几个函数进行占空比控制,这里1234对应内部选择的TIMx时钟的第几个通道。

同样我们还是离不开TIM时钟表

eg:

TIM_SetCompare3(TIM2,50);//这里意味着TIM2时钟上的第三个串口PA2,给ccr值50

TIM_SetCompare2(TIM4,250);//这里意味着TIM4时钟上的第二个串口PB7,给ccr值250

 用到了PWM占空比公式

Duty = \frac{ccr}{arr+1 }

因为我选用的arr为1999+1,所以:

这里的50对应的占空比就是50/2000,20ms的1/40对应的就是0.5ms舵机转到0°

而这里的250对应占空比就是250/2000,20ms的1/8对应2.5ms舵机转到180°。

那么舵机旋转角度就是50~250对应0°~180°。

到这里,最关键的东西已经结束了,剩下的就是对每个舵机进行控制即可。

(新手教程结束,你可以对线theshy、做掉克列了)

比如我下面主函数中定义的各种动作:

eg:

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Delay.h"
#include "duoji.h"

void move(void);
void move2(void);
void move3(void);
void move4(void);
void zuoyou(void);
void zhaoshou(void);
void zhanli(void);
void bailan(void);

int main()
{
	Pwm_Init_TIM2();
	Pwm_Init_TIM4();
	OLED_Init();     //初始化OLED
	OLED_ShowString(46,20,(u8*)"0.0",24);
	OLED_Refresh();
	Delay_ms(50);
	while(1)
	{
		
				zhanli();
				Delay_ms(500);
				zhaoshou();
				zhanli();
				Delay_ms(500);
				zuoyou();
				zhanli();
				Delay_ms(500);
				bailan();
				
		}
}


void move(void)
{
				TIM_SetCompare3(TIM2,130);

				TIM_SetCompare4(TIM2,130);

				TIM_SetCompare3(TIM4,240);

				TIM_SetCompare4(TIM4,240);
}

void move2(void)
{
				TIM_SetCompare3(TIM2,60);
	
				TIM_SetCompare4(TIM2,60);

				TIM_SetCompare3(TIM4,170);

				TIM_SetCompare4(TIM4,170);
}

void move3(void)
{
						TIM_SetCompare1(TIM2,120);
						
						TIM_SetCompare2(TIM2,180);
						
						TIM_SetCompare1(TIM4,180);
						
						TIM_SetCompare2(TIM4,120);
}

void move4(void)
{
						TIM_SetCompare1(TIM2,180);
						
						TIM_SetCompare2(TIM2,120);
						
						TIM_SetCompare1(TIM4,120);
						
						TIM_SetCompare2(TIM4,180);
}

void zuoyou(void)
{
			u8 i;
			for(i=0;i<5;i++)
	{
			OLED_ShowString(46,20,(u8*)"0.0",24);
			OLED_Refresh();
			Delay_ms(50);
			move();
			Delay_ms(500);
			OLED_ShowString(46,20,(u8*)">.<",24);
			OLED_Refresh();
			Delay_ms(50);
			move2();
			Delay_ms(500);
	}
}

void zhaoshou(void)
{
		u8 i;
		OLED_ShowString(46,20,(u8*)"^v^",24);
		OLED_Refresh();
    Delay_ms(50);
		TIM_SetCompare1(TIM2,100);
		Delay_ms(100);
		TIM_SetCompare2(TIM2,190);
		Delay_ms(100);
		TIM_SetCompare3(TIM2,150);
		Delay_ms(100);
		TIM_SetCompare4(TIM2,80);
		Delay_ms(100);
		TIM_SetCompare2(TIM4,120);
		Delay_ms(100);
		for(i=0;i<3;i++)
	{
		TIM_SetCompare4(TIM4,150);
		Delay_ms(300);
		TIM_SetCompare4(TIM4,80);
		Delay_ms(300);
	}
}

void zhanli(void)
{
		TIM_SetCompare1(TIM2,140);
		Delay_ms(100);
		TIM_SetCompare2(TIM2,150);
		Delay_ms(100);
		TIM_SetCompare1(TIM4,160);
		Delay_ms(100);
		TIM_SetCompare2(TIM4,150);
		Delay_ms(100);
		
		TIM_SetCompare3(TIM2,70);
		Delay_ms(100);
		TIM_SetCompare4(TIM2,70);
		Delay_ms(100);
		TIM_SetCompare3(TIM4,230);
		Delay_ms(100);
		TIM_SetCompare4(TIM4,230);
		Delay_ms(100);
	
		TIM_Cmd(TIM2,DISABLE);
		Delay_ms(250);
		TIM_Cmd(TIM2,ENABLE);
		Delay_ms(250);
		
}

void bailan(void)
{
		u8 i;
		OLED_ShowString(46,20,(u8*)"X.X",24);
		OLED_Refresh();
    Delay_ms(50);
		
				TIM_SetCompare3(TIM2,150);
				Delay_ms(100);
				TIM_SetCompare4(TIM2,150);
				Delay_ms(100);
				TIM_SetCompare3(TIM4,150);
				Delay_ms(100);
				TIM_SetCompare4(TIM4,150);
				Delay_ms(100);
					
				for(i=0;i<5;i++)
				{
						move3();
						Delay_ms(300);
					
						move4();
						Delay_ms(300);
					
				}
		
}


OLED部分的使用

请大家去CSDN的那些大佬哪里学一下使用方法吧

吃透OLED显示原理——玩转OLED模块各种使用方法_oled使用方法-CSDN博客

建模部分说明和参数

我使用的是solidworks 2022版本

舵机留的孔大小:

经过3d打印后的材料挤压,可以直接用2mm螺丝

脚留的孔大小:

同样用2mm螺丝即可

腿部连接:

因为用了镜像,在自己切片时需要进行一下切割。

安装注意事项

需要知道舵机的三条线:

黄线 or 橙线是信号线

红线是电源线

黑线 or 棕线是地线

首先需要知道舵机是如何旋转的:

然后在安装之前需要用代码将舵机都旋转到90°后再进行脚的固定,可以增大脚左右移动的自由度:

先用这几个代码

TIM_SetCompare1(TIM2,150);

TIM_SetCompare2(TIM2,150);

TIM_SetCompare3(TIM2,150);

TIM_SetCompare4(TIM2,150);



TIM_SetCompare1(TIM4,150);

TIM_SetCompare2(TIM4,150);

TIM_SetCompare3(TIM4,150);

TIM_SetCompare4(TIM4,150);

将舵机调至90°后,按下图连接腿部。

如果要用我的代码进行运行,需要如下图连接舵机线:

总结

源码、sw源文件和3d打印文件都在嘉立创的开源平台。

基于stm32的机械四爪 - 嘉立创EDA开源硬件平台

因为我也还是一个大学的萌新,所以做的很简单,有问题也请大家指出,谢谢。

基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放,基于stm32的六轴机械臂控制+open mv颜色识别,识别不同的物块分放 *2 控制部分为MDK工程,主控为STM32F103C8T6,使用HAL库开发,可快速移植至不同芯片平台 *3 视觉部分为OpenMV程序 硬件部分为飞控底板,但是兼容机械臂控制,输入电压为5V,在外接电源后可直接驱动6路MG996R舵机 机械部分为3D打印,无开源计划,此方案经过简单的参数调整后,适用于市面上各种常见6机械臂。 基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+openmv颜色识别,识别不同的物块分放基于stm32的六轴机械臂控制+o
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值