大一萌新の作品:红外寻迹模块做跟随小车之没有I的极简PID算法

(纯萌新,学习单片机半年了,这是寒假回家的作品,师从江科大,写博客纪录我实现后的经验)(比较粗略)

前言:

因为没买超声波避障模块,只能勉强一下用红外寻迹做一个类似的小车。

问题很多:光线会干扰读取到的AD值,且难以设置详细的距离,只能设置大概距离;碍于萌新我不会PID算法的I算法的编程,以及暂时无法读取电机的转苏,做不出内环,只能勉强做一个只有P,D的PID跟随小车,但实现基本功能是绰绰有余的;本文只用了一个模块,所以只能直线,如果要做到转弯等功能,可以三个模块再用简单的if语句就像寻迹小车一样即可。

目标:

  1. 能够使用单片机读取红外模块的AD值

  1. 写简单的没有I的PID算法,实现比较稳定的简单跟踪

  1. 写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算法也让我知道了许多算法的精妙之处,适应算法的难度。这是学习路上一个避不开的坎,努力坚持下去,终会有成果的。

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据引用\[1\]和引用\[3\]的内容,寻迹模块可以使用PID算法PID算法是一种常用的控制算法,由比例(P)、积分(I)和微分(D)三个部分组成。在寻迹模块中,由于作者只使用了P和D两个部分,所以只能实现基本的功能。 P部分指的是根据跟随比例变化电机的转速。通过测量寻迹模块与轨道之间的距离,可以根据距离的变化来调整电机的转速,使小车能够保持在轨道上。 D部分指的是能接收的最终误差。通过测量小车当前的位置与目标位置之间的误差,可以根据误差的变化率来调整电机的转速,以实现更加稳定的跟随效果。 虽然作者在引用\[1\]中提到自己只使用了P和D两个部分,但是PID算法的完整形式还包括积分(I)部分。积分部分可以用来消除系统的稳态误差,提高系统的稳定性和精度。如果需要更加精确的寻迹功能,可以考虑添加积分部分。 总结来说,寻迹模块可以使用PID算法,通过调整电机的转速来实现对轨道的跟随。P部分根据距离的变化调整转速,D部分根据误差的变化率调整转速。如果需要更加精确的功能,可以考虑添加积分部分。 #### 引用[.reference_title] - *1* *2* *3* [大一萌新作品红外寻迹模块跟随小车没有I的PID算法](https://blog.csdn.net/ChiShangying/article/details/128858013)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西南胶带の池上桜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值