基于STM32F103C8T6最小核心板设计的二轴陀螺仪云台,基于江协科技的中断和OLED例程基础上改进而成的。
省流版:网盘分享的文件:定时器云台.rar
链接: https://pan.baidu.com/s/14EF_TK2dO8duZfw14ER-gg?pwd=s9cg 提取码: s9cg
可以直接去获取。
本项目设计的硬件部分有,MPU6050模块、两个SG90_180度舵机、OLED(IIC协议)屏幕
屏幕主要是输出MPU6050模块的X和Y的倾斜角度,用于观察和调试。
本程序获取MPU6050的以及舵机的驱动,都是在中断里面完成的,不会阻塞主程序,这也方便我们去拓展其他的功能,比如把舵机换成步进电机,添加新的传感器等,方便后续的拓展,当然,程序也采用模块化,方便我们去做移植。
舵机采用PWM驱动,另外说一下,舵机得靠5V驱动这一点注意一下,两个舵机的信号线分别连接了STM32的PA0和PA1引脚,连接舵机的为淘宝购买的舵机打印套件。
首先让我们来看一下时钟开启部分
void Timer_Init(void)
{
//开启TIM2 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//开启 GPIOA 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//nvic
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
开启TIM2 时钟和开启 GPIOA 时钟,当然可以根据自己的需求去更改时钟和引脚,但要提前去查看STM32的引脚分布和复用功能。
void GPIO_Config_PWM(void)
{
//引脚配置
GPIO_InitTypeDef GPIO_InitStructure;
// 配置 A0 引脚(TIM2_CH1)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 A1 引脚(TIM2_CH2)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void TIM2_PWM_Config(void)
{
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置 TIM2_CH1 为 PWM 模式(对应舵机 1 的 A0 引脚)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比为 0
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
// 配置 TIM2_CH2 为 PWM 模式(对应舵机 2 的 A1 引脚)
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
}
这里设置对应的引脚模式以及开启PWM的模式,这样一来,时钟和PWM部分就完成了,接下来让我们来看一下舵机驱动部分
#include "stm32f10x.h" // Device header
#include "Timer.h"
void Servo_Init(void)
{
Timer_Init();
GPIO_Config_PWM();
TIM2_PWM_Config();
}
void Set_Servo_Angles(float angleX, float angleY)
{
uint16_t pulse_widthX,pulse_widthY;
//X舵机计算(A0)
pulse_widthX = (500 + ((2500 - 500) * angleX / 180));
TIM_SetCompare1(TIM2,pulse_widthX);
//Y舵机计算(A1)
pulse_widthY = (500 + ((2500 - 500) * (angleY+90) / 180));
TIM_SetCompare2(TIM2,pulse_widthY);
}
注意,这里要提前引用Timer定时中断的头文件,防止后续编译的报错,第一部分就是初始化,第二部分就是通过传入的float的值来让舵机做出对应的驱动,值得注意的是,我这里的Y舵机,初始让处于中间的位置,所以在angle Y中加入了90的相位,这样可以保证Y舵机的左右转向,然后舵机函数传入的数值,是去捕获陀螺仪获取的,这里因为我们侧重讲的舵机方面,所以陀螺仪部分就先不侧重讲述,后续再看(挖坑中。。。。),这里我们会引用会调用就行,也可去看看江协科技里面讲陀螺仪部分的视频[10-2] MPU6050简介_哔哩哔哩_bilibili,我这里,做了一部分的改进,因为我们是想通过中断的方式去捕获这个数值,所以我在这选择再封装一个函数,
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH, DataL; //定义数据高8位和低8位的变量
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据
*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据
*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据
*AccZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据
*GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据
*GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据
*GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
}
void MPU6050_Tick(void)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
}
通过MPU6050_Tick函数,去获取这个数值,然后让变量的值通过extern的方式,可以让其他文件可以直接调用
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
MPU6050_Tick();
Set_Servo_Angles(AngleX, AngleY);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
主函数中,我们通过中断的方式去掉用陀螺仪的数值以及去驱动舵机,主函数需要去实现显示的功能,这样一来不会阻塞主程序,这里我们通过i++的形式,来观察,主函数的状态
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Key.h"
#include "MPU6050.h"
#include "Servo.h"
uint8_t KeyNum;
uint16_t i;
float AngleX,AngleY;
int main(void)
{
OLED_Init();
Key_Init();
MPU6050_Init();
Servo_Init();
OLED_ShowString(0,0,"i:",OLED_8X16);
OLED_ShowString(0,15,"X:",OLED_8X16);
OLED_ShowString(0,31,"Y:",OLED_8X16);
while (1)
{
OLED_ShowNum(15,0,i++,6,OLED_8X16);
OLED_ShowSignedNum(16, 15,AngleX,5,OLED_8X16);
OLED_ShowSignedNum(16, 31,AngleY,5,OLED_8X16);
OLED_Update();
if(AX<0)AX=0;
if(AY>1800)AY=1800;
if(AY<-900)AY=-900;
AngleX = AX/10;
AngleY = AY/10;//下舵机
}
}
在实际的运行时,我们发现并不会阻塞主函数,这样一来,我们这给例程就可以进行拓展了。
最后,感谢你能看到这里,因为这个项目也是突发奇想的,可能有很多不足的地方以及没完善的地方,希望能多多指正,下面是本套案例的全套代码
网盘分享的文件:定时器云台.rar
链接: https://pan.baidu.com/s/14EF_TK2dO8duZfw14ER-gg?pwd=s9cg 提取码: s9cg