2020年大三上的时候和同学们一起组队参加了学校举办的机器人大赛,走的是循迹竞速赛道,规则很简单,就是看谁可以以最快的速度跑完全程,经过一个多月的学习与调试,最终我们的小车“德芙”(因为全程跑的十分丝滑)以26s的成绩(领先第二名7s)获得了第一,在此就写一篇博客记录记录自己调车的经历吧。

我们队在比赛中使用的是舵机加双编码电机的机械结构,以芯片主频为72MHz的STM32F103ZET6为核心控制器,赛道的主要元素包括直道,环岛,S弯和连续直角弯; 我们通过一字排列的光电传感器阵列对黑线进行识别,进而检测车身在赛道上的位置;通过编码电机检测智能车的实时速度;使用结合BANGBANG算法的积分分离PI控制算法调节电机的转速,通过基于分段控制思想的舵机转向PD控制舵机的打角,通过基础速度和偏离速度相结合的思想实现针对不同赛道元素的速度调节,实现了智能车在运动过程中速度和方向的闭环控制。

一、路径识别模块的设计
在选择传感器的时候,我们最初选择的是TCRT5000红外反射式开关传感器,但是发现因为赛道和车子结构的原因,其实际的循迹效果不是特别好,经常会误判,故而我们便改用了数字灰度传感器,其灵敏度更高,抗干扰能力强;普通照明灯基本对其无影响,其发光源常用高度良白色聚光LED,接收管对不同的发射光的强弱进行对比处理,只要对白光发射强弱不同即可,差值越大,分辨越好,比普通的红外传感器抗干扰能力要强的多。
其输出为数字输出,即1或0输出,需要根据场地,光线等基本情况来调节基准电压,电压比较器有着两个电压输入,一个为接受管的电压,另一个是电位器输入的基准电压,需要根据接受管在2种色的电压值来调节基准电压,一般将电位器电压调到2种电压的中间值。

我们将七个传感器按一字型非等距排列在超前于车身主体的横杆上,正中间的传感器在正常情况下正对于赛道的黑线,两边的传感器则按间隔距离则由小到大对称分布排列,用以确定小车与中心线的偏差大小,我们根据各个传感器返回的01值来确定小车与中心线偏差的大小,同时我们将偏差的大小分为9级,并由参数error来表征,0代表小车循迹无偏差,不需要进行转弯,正数代表左转,负数代表右转,而数字越大代表小车偏离中心的程度越大。对于十字圆环而言,所有的传感器均会检测到黑线,此时应题目要求是向前走直道,故而将error设定为0。当小车超过终止区域黑线时,所有的传感器都检测不到黑线,此时比赛完成,故而小车要停止,此时令两个电机反转,消除其惯性,使其迅速停下来,当检测到实际速度为 0 时则断开电源,实现制动的同时避免反接制动运行情况发生。
二、舵机
该循迹系统选用的是HS-425BB型舵机,HS-425BB型舵机是一款高性能舵机,扭力大,稳定性好,角度控制准确。由于舵机所需要的电压较大,运行过程中电流也会发生很大变化,还会带来不同程度的干扰,因此,舵机往往采用单独供电,舵机的输入线有三根,其中红色的线用于接电源,黑色的线用于接地,白色的线为信号线,对舵机的控制过程比较简单,提供主控制器产生的PWM控制信号输出至舵机的信号线接口,在输出PWM波频率不变的情况下,通过改变PWM波的占空比,就能相应地改变机器人的转角,从而实现舵机的转角控制。
#include "Servo.h"
#include "sys.h"
//舵机初始化
void TIM3_PWM_Servo_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM5_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//TIM2
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM3, ENABLE);
//TIM3 Channel PWM
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3, ENABLE);
}
void Servo_angle(int pwm)
{
TIM_SetCompare4(TIM3,pwm);
}
三、电机
在该系统的设计中,我们选择了GB37-520编码电机。电机编码器由光电模块和光栅组成,光电模块输出的信号有AB两相。电机的主轴连接着编码器并带动编码器的光栅盘转动,光电模块检测其输出的脉冲数。由于AB两相相差90°,可通过比较A相在前还是B相在前,以判别编码器的正转与反转,通过零位脉冲,可获得编码器的零位参考位;绝大部分的直流电机采用的是开关驱动方式,采用这种方式,半导体功率器件有开启和关闭两种状态,然后用输出可调的PWM电平实现对电机电压的控制,实现调速功能。

extern int Motor_1,Motor_2;
//GPIO 初始化
void MOTOR_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7 |GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//电机PWM定时器输出初始化
//PWM频率= 定时器模块频率/ (Period+1) / (Prescaler+1)
void TIM4_PWM_Motor_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4 , ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM4, ENABLE);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OC3Init(TIM4, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OC4Init(TIM4, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_Cmd(TIM4, ENABLE);
}
//幅值给PWM寄存器,左轮PWM
void Set_Pwm_Motor1(int motor_1)
{
if(motor_1<0)
{
TIM_SetCompare1(TIM4,7200+motor_1);
TIM_SetCompare2(TIM4,7200);
}
else
{
TIM_SetCompare1(TIM4,7200);
TIM_SetCompare2(TIM4,7200-motor_1);
}
}
//轮子反转
void Motor_back(void)
{
Set_Pwm_Motor2(-2000);
Set_Pwm_Motor1(-2000);
}
//测试电机
void MOTOR_TEST(void)
{
Set_Pwm_Motor2(7000);
Set_Pwm_Motor1(-7000);
}
四,PID算法控制
PID控制是工程实际中应用最为广泛的调节器控制规律,单位反馈的PID控制原理框图如图:

e(t)代表理想输入与实际输入的误差,这个误差信号被送到控制器,控制器算出误差信号的积分值和微分值,并将它们与原误差信号进行线性组合,得到输出量。

其中,kp、kd、ki分别为比例系数、积分系数、微分系数。
PID各个参数作用基本介绍:
(1)比例调节(p):是按比例反应系统的偏差,系统一旦出现了偏差,比例调节立即产生调节作用,以减少偏差。比例作用大,可以加快调节,减少误差,但是过大的比例,会使系统稳定性下降,甚至造成系统的不稳定。
(2)积分调节(I):使系统消除稳态误差,提高无差度,因为有误差,积分调节就进行,直至无差,积分调节停止,积分调节输出一个常值。
(3)微分调节(D):微分作用反映系统偏差信号的变化率,具有预见性,能预见偏差变化的趋势,因此能产生超前的控制作用,在偏差还没有形成之前,已被微分调节作用消除。因此,可以改善系统的动态性能。在微分时间选择合适情况下,可以减少超调,减少调节时间。微分作用对噪声干扰有放大作用,因此过强的微分调节,对系统抗干扰不利。此外,微分反应的是变化率,而当输入没有变化时,微分作用输出为零。微分作用不能单独使用,需要与另外两种调节规律相结合,组成 PD 或 PID 控制器。
以 T 作为采样周期,则离散采样时间对应着连续时间,用矩形法数值积分近似代替积分,用一阶向后差分近似代替微分,可得到离散 PID公式为:

位置式 PID 算法具有以下优点:比例部分只与当前的偏差有关,而积分部分则是系统过去所有偏差的累积。故而位置式 PI 调节器的结构清晰,P 和 I 两部分作用分明,参数调整简单明了,编写代码更为简单。
速度策略是影响智能车速度快慢的一个重要因素。首先速度控制必须配合方向控制,即在直道或类似直道时,应该将PWM波提到最高,让通过主电机的电流达到最大,从而让智能车以最快的速度行驶;当进入弯道时,应该根据弯道的曲率大小,适当的较低小车的基础速度,使得小车不容易冲出赛道,对于该基础速度v_set的设定,我们采用PD控制,以小车与中线的偏差大小为输入,以速度的设定量为输出,实现不同赛道类型下的自主速度调整。
我们的小车转弯方式不同于其他万向轮转弯的智能小车,我们采用差速转向和舵机转向方式相配合,相比之下其灵活性提高了很多。小车利用差速电机实现转弯,其原理是利用两个电机的转速不同构成转速差;舵机控制前轮是从动轮,不需要有转速。舵机的位置是在两个前轮的中间,控制两个前轮的转向。
void TIM6_IRQHandler(void) //TIM6中断服务函数
{
if(TIM6->SR&0X0001)//定时器每10ms定时中断一次
{
TIM6->SR&=~(1<<0);
if(backconfig==0)
{
servo_pid_adjust=Servo_Position_PID (Position_error,0); //将PID的输出值赋给舵机
motor_basic_pid=Motor_basic_Position_PD(Basic_Speed_error,0); //将PID控制器的输出赋给基础速度
motor_different_pid=Motor_different_Position_PID(Different_Speed_error,0);//将PID控制器的的输出值赋给偏差速度
Pwm_limit(); //将PWM进行限幅,避免角度或速度过大,超过范围
Poistion_adjust(); //输出PWM给相应的管脚
}
else if(backconfig==1)
{
backconfig=0;
}
}
}
//舵机PID控制器Encoder=Position_error£¬Target=0;
int Servo_Position_PID (int Encoder,int Target)
{
float Position_KP=45,Position_KI=0,Position_KD=35;
//angle=KP*Position_error*0.135
static float Bias,Pwm,Integral_bias,Last_Bias;
Bias=Encoder-Target;
Integral_bias+=Bias;
Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias);
Last_Bias=Bias;
return Pwm;
}
//基础速度PID控制器Encoder=Basic_Speed_error£¬Target=0;
int Motor_basic_Position_PD (int Encoder,int Target)
{
float Position_KP=300 ,Position_KD=0;
static float Bias,Pwm,Last_Bias;
Bias=Encoder-Target; //Æ«²î=ʵ¼ÊÖµ-Ä¿±êÖµ
Pwm=7200-Position_KP*Bias+Position_KD*(Bias-Last_Bias);
Last_Bias=Bias;
return Pwm;
}
//差速PID控制器Encoder=Different_Speed_error£¬Target=0;
int Motor_different_Position_PID (int Encoder,int Target)
{
float Position_KP=650,Position_KI=0,Position_KD=15000;
static float Bias,Pwm,Integral_bias,Last_Bias;
Bias=Encoder-Target;
Integral_bias+=Bias;
Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias);
Last_Bias=Bias;
return Pwm;
}
//输出舵机和电机的PWM
void Poistion_adjust(void)
{
Servo_angle(servo_output);
Set_Pwm_Motor1(motor1_output);
Set_Pwm_Motor2(motor2_output);
}
//限制舵机和电机的PWM
void Pwm_limit(void)
{
servo_output=1500-servo_pid_adjust;
motor2_output=5300-motor_different_pid;
motor1_output=5300+motor_different_pid;
if(servo_output>2000) servo_output=2000;
if(servo_output<1000) servo_output=1000;
if( motor1_output>7200) motor1_output=7200;
if( motor1_output<-7200) motor1_output=-7200;
if( motor2_output>7200) motor2_output=7200;
if( motor2_output<-7200) motor2_output=-7200;
}
整个比赛过程,从最开始的选择车型和主控芯片到后来的编程和现场实际的调车,可以说那段时间一有空,我们就呆在调试赛道旁边,不断改善程序,同时因为我们是舵机和差速控制的方案,故而pid的参数较多,我们需要一个一个的慢慢整定,一遍遍的跑去确定一个合适的参数;当时的我们参数整定全靠自己的判断和车子跑的情况,故而效率其实还是很低的,如果可以的话,其实可以运用无线模块将每一次车子的PWM数据实时的传送到电脑上,画出相应的曲线,这样的话调试会快一点,还有就是最后因为时间等缘故,我们的速度到最后其实没有闭环,即没有用编码电机将实际的速度值传送回来,这样输出的PWM和实际的还是会有一定的区别的,这个以后也可以加上,如果有机会的话,其实我还是想去参加一下飞思卡尔智能车比赛的,再享受一次调车的乐趣,享受看着小车一次又一次以更快的速度跑完全程的那种喜悦!
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L
本文代码使用HAL库。文章目录前言一、MCP4017的重要特性二、MCP4017计算RBW阻值三、MCP4017地址四、MCP4017读写函数五、CubeMX创建工程(利用ADC测量MCP4017电压)、对应代码:总结前言一、MCP4017的重要特性蓝桥杯板子上的是MCP4017T-104ELT,如图1。MCP4017是一个可编程电阻,通过写入的数值可以改变电阻的大小。重点在于6引脚(W),5引脚(B
STM32OTA应用开发——通过USB实现OTA升级目录STM32OTA应用开发——通过USB实现OTA升级前言1环境搭建2功能描述3BootLoader的制作4APP的制作5烧录下载配置6运行测试结束语前言什么是OTA?百度百科:空中下载技术(Over-the-AirTechnology;OTA),是通过移动通信的空中接口实现对移动终端设备及SIM卡数据进行远程管理的技术。经过公网多年的应用与发展,已十分成熟,网络运营商通过OTA技术实现SIM卡远程管理,还能提供移动化的新业务下载功能。实际上,现在我们所说的OTA比百度百科的定义还要更广泛,OTA的形式已经不再局限于手机和SIM卡,只要涉及
文章目录1简介2绪论2.1课题背景与目的3系统设计详细设计描述3.2硬件部分温度测量电路其他电路部分3.3软件部分主程序子系统程序温湿度程序流程键盘显示子程序3.4实现效果3.5部分相关代码4最后1简介Hi,大家好,这里是丹成学长,今天向大家介绍一个单片机项目基于单片机的智能温控农业大棚系统大家可用于课程设计或毕业设计单片机-嵌入式毕设选题大全及项目分享:https://blog.csdn.net/m0_71572576/article/details/1254090522绪论2.1课题背景与目的近年来我国的温室控制取得了长足的进步,首先在温室群控制方面,进行了初步的探索和理论研究,其次在温室
解析数据 进入阿里云的IOTStdio,点击新建项目。 新建项目后点击新建Web应用。名称 应用名称随便填写 创建完成后我们进入应用。 在左侧组件处拖入一个指示灯和一个开关。 点击指示灯组件,点击配置数据源 选择我们的产品、数据、和属性。 我们还可以配置开和关的显示颜色。 点击按钮,配置交互动作。 选择设备和属性,设置值位置点击数据来源,选择组件值 配置完成后进入预览,点击按钮,在esp8266就会收到来自平台的json格式的数据,MCU端需要做的就是解析来自平台的数据,进而达到控制下
目录前言一、定时器部分和按键部分二、PWM调速三、电机驱动部分三、编码器接口部分(测速)四.主函数总结推荐STM32学习课程:[6-8]编码器接口测速_哔哩哔哩_bilibili[6-8]编码器接口测速是STM32入门教程-2022持续更新中的第20集视频,该合集共计30集,视频收藏或关注UP主,及时了解更多相关视频内容。https://www.bilibili.com/video/BV1th411z7sn/?p=20&spm_id_from=pageDriver&vd_source=ed36b2700bbc2bac7746c270bc391540OLED显示屏代码
文章目录1.I2C与SPI通信协议对比2.四脚OLED与六脚OLED3.I2C驱动OLED显示oled.h&oled.c:汉字取模&oledfont.h:main.c显示示例:连线方法:4.SPI驱动OLED显示1.I2C与SPI通信协议对比I2C(Inter-IntegratedCircuit)SPI(SerialPeripheralInterface)传输方式半双工全双工传输速度低速,100Kbps----4Mbps高速,30Mbps以上几线制4线制:VCC,GND,SCL,SDA6/7线制:VCC,GND,SCLK(D0),MOSI(D1/SDA),DC,CS/SS主从模式多主机总线,通
最近玩步进电机时候,发现步进电机驱动种类多;A4988,drv8825,tb6600,lv8731……;tb6600驱动电流可达4A,1600细分,十分强大,但是体积大,用在平衡车上不太合适。drv8825加散热器驱动电流可达2.5A,32细分,还不错。芯片介绍这里的介绍只介绍其引脚连接。如图是它的说明书中对引脚的解释在这里把它翻译成中文。 为了方便大家使用,说明书还给出了一种典型的连接方式 这样初始化好之后,将AOUT1、AOUT2、BOUT1、BOUT2分别连接到步进电机对应信号线,AOUT1与AOUT2同相,BOUT1与BOUT2同相。判断步进电机信号线是否同相的一个方法是将其中两条接
🐱作者:一只大喵咪1201🐱专栏:《STM32学习》🔥格言:你只管努力,剩下的交给时间!LCD显示📺LCD简介📺TFTLCD驱动原理📺ILI9341液晶控制器简介🃏液晶屏的信号线及8080时序📺使用STM32的FSMC模拟8080接口时序📺硬件连接📺代码实现📺字符显示🃏英文字符显示🃏英文字符串的显示📺总结📺LCD简介显示器属于计算机的I/O设备,即输入输出设备。它是一种将特定电子信息输出到屏幕上再反射到人眼的显示工具。常见的有CRT显示器、液晶显示器、LED点阵显示器及OLED显示器。本喵这里使用的是CLD显示器,也就是液晶显示器。液晶显示器,简称LCD(LiquidCrystalDispla