展示视频
摇杆+舵机+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)/时钟 20ms
while(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微控制器、摇杆和舵机来构建一个云台控制系统。如果你有任何问题或建议,欢迎在评论区留言讨论。