赞
踩
(纯萌新,学习单片机半年了,这是寒假回家的作品,师从江科大,写博客纪录我实现后的经验)(比较粗略)
因为没买超声波避障模块,只能勉强一下用红外寻迹做一个类似的小车。
问题很多:光线会干扰读取到的AD值,且难以设置详细的距离,只能设置大概距离;碍于萌新我不会PID算法的I算法的编程,以及暂时无法读取电机的转苏,做不出内环,只能勉强做一个只有P,D的PID跟随小车,但实现基本功能是绰绰有余的;本文只用了一个模块,所以只能直线,如果要做到转弯等功能,可以三个模块再用简单的if语句就像寻迹小车一样即可。
能够使用单片机读取红外模块的AD值
写简单的没有I的PID算法,实现比较稳定的简单跟踪
写main函数结合上述目标
首先要解决读取红外模块的数值,红外模块有四根接口,其中AO是读取具体电压值的口,DO是只输出0/1的口。所以接AO。用AD的代码即可读取其值。当其遇见不反光的面时,会输出很高的AD值,反之为低。使用AD的原因是如果用DO口,只能识别有无,但是没法控制距离。
其次是PID的算法,萌新我实力有限,只能写没有I的PID算法博君一笑了。P指的是跟随比例变化电机的转速,D指的是能接收的最终误差。具体可以看其他大佬的PID讲解,很详细,但是我暂时没能将其完全写成自己的代码。
最后就是通过PID算法获知应该输入给电机的转速了,大概思路就是如此。
首先是AD读取,这里建议观看江科大老师的stm32教程,很容易上手的代码(比较重要的部分在注释中已经标明。ps.标注释真是个好习惯)。
- #include "stm32f10x.h" // Device header
- #include "AD.h"
-
- void AD_Init(void)
- {
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //打开ADC
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
-
- RCC_ADCCLKConfig(RCC_PCLK2_Div6); //打开ADCCLK的6分频
-
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //专属ADC的模式捏
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOB, &GPIO_InitStructure);
-
-
-
- //结构体初始化ADC
- ADC_InitTypeDef ADC_InitStructure;
- ADC_InitStructure.ADC_Mode= ADC_Mode_Independent; //ADC1单打模式!
- ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right ; //右对齐就欧克啦
- ADC_InitStructure.ADC_ExternalTrigConv= ADC_ExternalTrigConv_None; //只用软件触发啦
- ADC_InitStructure.ADC_ContinuousConvMode=DISABLE ; //连续转换模式(ENABLE)
- ADC_InitStructure.ADC_NbrOfChannel= 1; //通道数目
- ADC_InitStructure.ADC_ScanConvMode= DISABLE; //扫描模式 看江科大啦
- ADC_Init(ADC1,&ADC_InitStructure);
-
- ADC_Cmd(ADC1,ENABLE); //ADC准备就绪捏
- //但是我们要校准呢
-
- ADC_ResetCalibration(ADC1);
- while(ADC_GetResetCalibrationStatus(ADC1)==SET); //没校准开始给我站在这里
- ADC_StartCalibration(ADC1);
- while(ADC_GetCalibrationStatus(ADC1)==SET); //等待校准完成捏
-
- ADC_SoftwareStartConvCmd(ADC1,ENABLE);
-
- }
-
- uint16_t AD_GetValue_D(void)
- {
- ADC_SoftwareStartConvCmd(ADC1,ENABLE); //连续触发可放在上面,这样也不需要等到转化完成啦↓
- while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET); //转换完成变成1就退出啦
- return ADC_GetConversionValue(ADC1); //因为读取后自动清除标志位,所以不用清除标志位啦
- }
- //若是连续触发模式,可以将软件触发的函数挪到初始化的最后,只需要触发一次即可~
-
- uint16_t AD_GetValue_E(void) //这个就是连续转换用的函数啦
- {
- return ADC_GetConversionValue(ADC1);
- }
-
- uint16_t AD_GetValue(uint8_t ADC_Channel)
- {
- ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
- ADC_SoftwareStartConvCmd(ADC1,ENABLE); //连续触发可放在上面,这样也不需要等到转化完成啦↓
- while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET); //转换完成变成1就退出啦
- return ADC_GetConversionValue(ADC1); //因为读取后自动清除标志位,所以不用清除标志位啦
- }
然后是PID
- #include "stm32f10x.h" // Device header
- #include "AD.h"
-
- void PID_Init(void)
- {
- AD_Init();
- }
-
- float PID(uint16_t Aim,uint16_t D) //Aim是目标,D是误差
- {
- uint16_t Now;
- float Speed=0;
- Now=AD_GetValue(ADC_Channel_8); //假如3500是目标,50是误差,则可以计算具体比例
- if(Now<(Aim-D))
- {
- Speed=((Now-Aim)/70)-50;
- return(Speed); //返回的速度
- }
- else if(Now>(Aim+D))
- {
- Speed=((Now-Aim)/7)+30;
- return(Speed);
- }
- else {return 0;}
- }
最后是main代码
- #include "stm32f10x.h"
- #include "Delay.h"
- #include "Motor.h"
- #include "PID.h"
- #include "OLED.h"
- #include "AD.h" // Device header
-
- int main(void) //电机范围-100~100
- {
- float Speed;
- Motor_Init();
- PID_Init();
- OLED_Init();
- while(1)
- {
- Speed=PID(3500,75);
- Motor_L_SetSpeed(Speed);
- Motor_R_SetSpeed(Speed);
- OLED_ShowNum(1,1,Speed+100,3);
- OLED_ShowNum(2,1,AD_GetValue(ADC_Channel_8),4);
- }
-
- }
本次作品的实现,让我提前适应了超声波避障的思路,便于之后的继续学习和研究。自己编写极其简单的PID算法也让我知道了许多算法的精妙之处,适应算法的难度。这是学习路上一个避不开的坎,努力坚持下去,终会有成果的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。