展示视频
摇杆+舵机+STM32F103=云台:构建一个简易的云台控制
1.材料准备
- STM32F103C8T6最小系统板*1 作为系统的核心控制单元
- SG90舵机(180°)*2 用于驱动云台的旋转
- 摇杆按键*1 用于输入控制信号,分别对应X轴和Y轴
- 舵机支架*1 用于组合两个舵机
- 面包板*1(非必须) 快速搭建和测试电路
- 杜邦线若干 用于连接,模块元件和面包版
- 总约:40元
2. 系统架构
系统的整体架构如下:
- 摇杆模块:通过ADC(模数转换器)将摇杆的模拟信号转换为数字信号。
- STM32F103微控制器:接收摇杆的输入信号,并根据输入信号计算舵机的控制脉冲宽度。
- 舵机:根据STM32F103输出的PWM信号调整角度,实现云台的水平和垂直运动。
3. 软件实现
3.1 初始化
首先,我们需要初始化STM32F103的ADC和PWM模块。以下是初始化代码示例:
adc.c 文件
#include "adc.h"
uint16_t AD_Value[5];
void MY_ADC1_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
ADC_InitTypeDef ADC_InitStruct;
DMA_InitTypeDef DMA_InitStructure;
//1、使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1时钟使能
//2、选择ADC分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//3、初始化GPIO
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
//4、选择ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
//ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
//ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
//ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 5, ADC_SampleTime_55Cycles5);
//5、初始化ADC
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//ADC工作模式。独立工作模式
ADC_InitStruct.ADC_ScanConvMode = ENABLE; //扫描模式(多个通道)
ADC_InitStruct.ADC_ContinuousConvMode =ENABLE;//连续转换
//不使用外部触发,使用内部软件触发
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
ADC_InitStruct.ADC_NbrOfChannel =5;//需要扫描的通道数
ADC_Init(ADC1, &ADC_InitStruct);//ADC初始化
//6、初始化DMA
//DMA外设地址,ADC数据储存地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
//外设非增量模式,只读取ADC储存地址里面的
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//外设数据长度:16位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//存储器地址
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
//接收到数据长度:16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设到储存器(数据传输方向)
DMA_InitStructure.DMA_BufferSize = 5;//数据传输量
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 循环扫描
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化DMA
//6、使能DMA
DMA_Cmd(DMA1_Channel1,ENABLE);
//7、使能ADC触发DMA
ADC_DMACmd(ADC1,ENABLE);
//8、使能ADC
ADC_Cmd(ADC1, ENABLE);
//9、校准ADC数据
ADC_ResetCalibration(ADC1);//开始复位校准
while(ADC_GetResetCalibrationStatus(ADC1) == SET);//获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1); //开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1)); //获取指定ADC的校准状态
//使能指定的ADC1的软件转换启动功能 (连续转换时,初始化一次就行了)
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
pwm.c
#include "pwm.h"
//TIM_SetCompare1(TIM3,500); //发送占空比// Time 通道PA6通道1 PA7通道2
void PWM_INIT(unsigned int Per, unsigned int Pre)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructre;
TIM_OCInitTypeDef TIM_OCInitStructre; // 定时器通道结构体// 使能TIM3和GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 复用通道// 配置GPIOA的引脚为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // PA6和PA7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化IO// 配置TIM3的基本参数
TIM_TimeBaseInitStructre.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频
TIM_TimeBaseInitStructre.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStructre.TIM_Period = Per; // 自动重装值
TIM_TimeBaseInitStructre.TIM_Prescaler = Pre; // 预分频系数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructre); // 初始化定时器// 配置TIM3的输出比较通道
TIM_OCInitStructre.TIM_OCMode = TIM_OCMode_PWM1; // 设置PWM模式1
TIM_OCInitStructre.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高
TIM_OCInitStructre.TIM_OCNPolarity = TIM_OCNPolarity_High; // 互补输出极性为高
TIM_OCInitStructre.TIM_OutputState = TIM_OutputState_Enable; // 使能输出
TIM_OCInitStructre.TIM_OutputNState = TIM_OutputNState_Disable; // 禁用互补输出// 初始化通道1和通道2
TIM_OC1Init(TIM3, &TIM_OCInitStructre);
TIM_OC2Init(TIM3, &TIM_OCInitStructre);// 使能定时器通道1和通道2的输出比较预装载寄存器
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);// 使能定时器
TIM_Cmd(TIM3, ENABLE);
}void get_pwm_DJ(unsigned int Angle1,unsigned int Angle2)
{
u16 position1 = 500 + (2000 /180 * Angle1);
u16 position2 = 500 + (2000 /180 * Angle2);
TIM_SetCompare1(TIM3,position1);
TIM_SetCompare2(TIM3,position2);
}
3.2 主循环
在主循环中,我们读取摇杆的ADC值,并根据这些值计算舵机的PWM脉冲宽度。以下是主循环代码示例:
#include "pwm.h"
#include "adc.h"extern uint16_t AD_Value[5];
u8 Angle1 = 90;
u8 Angle2 = 90;
u8 AngleSpeed = 10; // 每次改变舵机的角度步长#define DEAD_ZONE 100 // 死区范围
int main(void)
{
MY_ADC1_INIT();
PWM_INIT(20000-1, 72-1); // (Per+1)*(Pre+1)/时钟 20mswhile(1)
{
// 处理X轴摇杆数据
if (AD_Value[0] > (2048 + DEAD_ZONE)) // 左
{
if (Angle1 <= (180 - AngleSpeed))
Angle1 += AngleSpeed;
}
else if (AD_Value[0] < (2048 - DEAD_ZONE)) // 右
{
if (Angle1 >= AngleSpeed)
Angle1 -= AngleSpeed;
}// 处理Y轴摇杆数据
if (AD_Value[1] > (2048 + DEAD_ZONE)) // 上
{
if (Angle2 <= (180 - AngleSpeed))
Angle2 += AngleSpeed;
}
else if (AD_Value[1] < (2048 - DEAD_ZONE)) // 下
{
if (Angle2 >= AngleSpeed)
Angle2 -= AngleSpeed;
}// 更新舵机位置
get_pwm_DJ(Angle1, Angle2);}
}
4. 调试与优化
在实际应用中,可能需要根据具体情况进行调试和优化。以下是一些建议:
- 死区处理:在摇杆的中心位置设置一个死区,避免不必要的抖动。
- 角度步长控制:根据实际需求调整角度变化的步长,以控制舵机的响应速度。
- PWM频率调整:根据舵机的规格调整PWM的频率和占空比。
5. 总结
通过结合摇杆、舵机和STM32F103微控制器,我们可以构建一个简易的云台控制系统。该系统能够根据摇杆的输入信号控制云台的水平和垂直运动,具有较高的灵活性和可扩展性。通过进一步的优化和调试,可以实现更加精确和稳定的控制效果。
希望本文能够帮助你理解如何使用STM32F103微控制器、摇杆和舵机来构建一个云台控制系统。如果你有任何问题或建议,欢迎在评论区留言讨论。