基于STM32F1的自动追光云台(代码开源)

2024-02-19 12:30

本文主要是介绍基于STM32F1的自动追光云台(代码开源),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前一段时间做了一个自动追光云台(大家感兴趣的也可以自己DIY一个呀),用来自动捕捉阳光供太阳板发电提高太阳板的发电效率,我用了一款STM32f103c8t6为主控来控制云台舵机的旋转。感光元器件使用的是光敏传感器(淘宝随便买一款啊很便宜)来感知光强,所以用ADC进行模拟量采集光强信息。下面就来介绍介绍它吧!

  1. 舵机旋转的控制

舵机的主要组成部分为伺服电机,伺服就是服从信号的要求从而做出相应动作。在信号发出之前,电机停止不动;信号发出之后,电机立即做旋转运动。因此我们就可以给舵机输入不同的信号,来控制其旋转到不同的角度。舵机接收的是PWM信号,当信号进入内部电路产生一个偏置电压,触发电机通过减速齿轮带动电位器移动,使电压差为零时,电机停转,从而达到伺服的效果。简单来说就是给舵机一个特定的PWM信号,舵机就可以旋转到指定的位置。

舵机上有三根线,分别是GND、VCC和SIG,也就是地线、电源线和信号线,其中的PWM波就是从信号线输入给舵机的。一般来说,舵机接收的PWM信号频率为50HZ,即周期为20ms。当高电平的脉宽在0.5ms-2.5ms之间时舵机就可以对应旋转到不同的角度。

舵机是通过高电平的占空比来调节输出轴的角度,舵机的驱动信号是50Hz的方波信号,每个周期为20ms,其中高电平的占比在0.5ms-2.5ms之间。

在这Keil里编写PWM程序,通过使用TIM_SetComparex(TIMx, Compare)函数来改变PWM的输出占空比。ok下面就是PWM初始化程序

void PWM2_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  //开启定时器2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //设置PA1引脚为复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);TIM_InternalClockConfig(TIM2);  //定时器2内部时钟配置TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;   //ARR自动重装值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;   //PSC时钟预分频,这里我们使用的是f103的72MHz所以除以72进行分频得到1MHz的的时钟频率TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;             //输出为PWM模式2的向下计数模式TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;      //设置问低电平有效TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出通道使能TIM_OCInitStructure.TIM_Pulse = 0;                            //CCR这里设置为0,下面使用函数对其在运行时改变他的他的它大小TIM_OC2Init(TIM2, &TIM_OCInitStructure);TIM_Cmd(TIM2, ENABLE);   //开启定时器2 
}void PWM_SetCompare2(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare);   //设置PWM占空比还能输函数,通过改变Compare的值来PWM占空比,其范围为0~ARR
}/*定时器3的初始化和定时器2是一样的了*/
void PWM3_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);TIM_InternalClockConfig(TIM3);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;        //ARRTIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;        //PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCStructInit(&TIM_OCInitStructure);TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = 0;        //CCRTIM_OC4Init(TIM3, &TIM_OCInitStructure);//    TIM_OC4PreloadConfig(TIM3,TIM_OCPreload_Enable);
//    TIM_ARRPreloadConfig(TIM3,ENABLE);TIM_Cmd(TIM3, ENABLE);
}void PWM_SetCompare3(uint16_t Compare)
{TIM_SetCompare4(TIM3, Compare);
}

2.光敏传感器的模拟量采集

光敏传感器在上电后受到不同强度的光照射后会通过传感器上信号线反馈出一个连续的模拟信号,但是单片机在处理信息的时候都是数字量的,所以我们需要把模拟量转化为数字量。stm32f103上有自带的ADC采样的功能,分为8位和12位的不同方式,在这里我使用了12位ADC的模式。在这个追光云台上有4个光敏传感器模块,要对4个模拟量都进行采集,所以使用ADC的扫描模式,并且涉及到多通道数据采集为了不给CPU产生负担,准备使用DMA数据转运的方式进行数据的储存。

1.ADC简单介绍

ADC电路就是指模数转换电路。也就是将模拟信号变为数字信号。一般用在数据采集方面。ADC,Analog-to-Digital Converter的缩写,指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。STM32 将 ADC 的转换分为 2 个通道组:规则通道组注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。

2.DMA简单介绍

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。

(这个图是参考江科大自动化协的)

好了直接上代码吧!

/*AD*/
uint16_t AD_Value[4]; //AD数据存放的位置
void AD_Init(void)   
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  //开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //开启DMA1的时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6);  //ADC挂载的时钟6分频GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_55Cycles5);   //规则组通道的配置ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 3, ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 4, ADC_SampleTime_55Cycles5);ADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  //数据右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;ADC_InitStructure.ADC_ScanConvMode = ENABLE;ADC_InitStructure.ADC_NbrOfChannel = 4;ADC_Init(ADC1, &ADC_InitStructure);DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  //数据搬运的地址DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;       //数据将要搬到的地址DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_BufferSize = 4;DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;DMA_Init(DMA1_Channel1, &DMA_InitStructure);DMA_Cmd(DMA1_Channel1, ENABLE);ADC_DMACmd(ADC1, ENABLE);ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);while (ADC_GetResetCalibrationStatus(ADC1) == SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1) == SET);ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

3.云台自动追光的实现方法

在图中可以看到,四个光敏传感器安装在四个角上,因为它们感受到的光强都是不同的,这里我处理的方法是avt=左上和右上之和除2、avb=左下和右下之和除2、avl=左上和左下之和除2、avr=右上和右下之和除2,这样就得到了avt,avb,abl,avr这4个值。在对这4个值进行对比判断时有一个特别重要的量不得不说,那就是tol它就是决定着我们这个云台对光强的灵敏度了,它就相当于一个阈值把要判断的结果卡在这个范围内,只有在这个范围内,才能进行接下来的上面得到的4个值的比较。

  1. 上下差值dvert=avt-avb 上-下 ,左右差值dhoriz=avl-avr 左-右;

  1. 如果上下差值dvert在tol(这就体现了上面说到的灵敏度这个概念了,tol越小就说明上下差值很容易满足在tol范围外)之外就可以比较avt和avb的大小了,每次判断增加或减速上下旋转舵机的占空比大小了最终使得上下差值进入tol范围内,舵机不在旋转达到我们的目标;

  1. 那么判断左右就和上下一样的方法啦。

这里就是主函数程序了

Servo1_SetAngle(50);             //控制上下旋转,PA1
Servo2_SetAngle(100);       //控制左右旋转,PB1
uint8_t tol=2;               //照度的相应范围,越小越灵敏while (1){zs = (float)AD_Value[0]/ 4095 * 100;   //PA4zx = (float)AD_Value[1]/ 4095 * 100;   //PA5ys = (float)AD_Value[2]/ 4095 * 100;   //PA6yx = (float)AD_Value[3]/ 4095 * 100;   //PA7avt=(zs+ys)/2;   //上avb=(zx+yx)/2;   //下avl=(zs+zx)/2;   //左avr=(ys+yx)/2;   //右dvert=avt-avb;   //上-下dhoriz=avl-avr;  //左-右if (-1*tol > dvert || dvert > tol) {if (avt > avb){servov++;if (servov > servovLimitHigh) { servov = servovLimitHigh;}}else if (avt < avb){servov--;if (servov < servovLimitHigh){servov = servovLimitLow;}}Servo1_SetAngle(50+servov);     //PA1}//检查差异是否在公差范围内,否则改变水平角度  if (-1*tol > dhoriz || dhoriz > tol) {if (avl > avr)         //左>右{servoh++; if (servoh > servohLimitHigh){servoh = servohLimitHigh;}}else if (avl < avr)    //右<左{servoh--;if (servoh < servohLimitLow){servoh = servohLimitLow;}}Servo2_SetAngle(100+servoh);     //PB1}Delay_ms(1);}
}

原工程代码:

链接:https://pan.baidu.com/s/1ZhUwJNlQqWLXtNjR9RtSdw

提取码:m5w6

创作不易在这里如有不对的地方还请大家可以指出!!

这篇关于基于STM32F1的自动追光云台(代码开源)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/724655

相关文章

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

Python如何去除图片干扰代码示例

《Python如何去除图片干扰代码示例》图片降噪是一个广泛应用于图像处理的技术,可以提高图像质量和相关应用的效果,:本文主要介绍Python如何去除图片干扰的相关资料,文中通过代码介绍的非常详细,... 目录一、噪声去除1. 高斯噪声(像素值正态分布扰动)2. 椒盐噪声(随机黑白像素点)3. 复杂噪声(如伪