C51单线循迹小车
硬件材料
C51最小系统板,L298N电机驱动模块,TCRT5000五路循迹传感器,USB转TTL模块,小车模组,电池
制作思路
红外传感器检测黑线(黑色低电平,白色高电平),对检测结果进行分析判断,并控制轮子的转速来达到转弯等的控制.
流程
- 选择需要使用的IO口,然后将对应的管脚连接对应的模块.(注意各模块和系统板的相对位置,不然连线会比较乱)
- 写程序,实地测试,不断调整差速修改程序.
代码
#include <reg52.h>
#include <stdio.h>
#define TrackPort P2 //循迹模块接口
//电机驱动接口
sbit M2PWM = P1^0;
sbit M2B = P1^2;
sbit M2A = P1^1;
sbit M1B = P1^3;
sbit M1A = P1^4;
sbit M1PWM = P1^5;
unsigned char timeCnt; //周期计数值
unsigned char DutyRatio_L,DutyRatio_R; //占空比0-100
char Motor_L_e = 0; //机械误差
char Motor_R_e = 0;
bit enableMotor = 1; //电机启动开关
void PWM_Config();
void setPWM(char set_L,char set_R);
unsigned char ScanLine();
void PWM_Config() //定时器初始化
{
TH2=0xFE;
TL2=0x0c;
ET2=1;
EA=1;
M2PWM=0;
M1PWM =0;
TR2=1;
}
void Timer2_PWM(void) interrupt 5 //定时器中断函数
{
TF2 =0;
TH2=0xFE;
TL2=0x0c;
timeCnt++; //计数
if(timeCnt>100)
{
timeCnt=0; //计数周期100归零
}
if(enableMotor) //电机启动开关
{
//启动
M2PWM = (timeCnt<DutyRatio_L)? 1 : 0;
M1PWM = (timeCnt<DutyRatio_R)? 1 : 0;
}
else
{
//关闭
M2PWM = 0;
M1PWM = 0;
}
}
void setPWM(char set_L,char set_R)
{
if(set_L>=0)
{
//正转
M2B=1;
M2A=0;
//设置占空比 Motor_L_e是机械误差校正
DutyRatio_L = set_L + Motor_L_e;
}
else
{
//反转
M2B=0;
M2A=1;
//设置占空比(负号代表取绝对值) Motor_L_e是机械误差校正
DutyRatio_L = -set_L + Motor_L_e;
}
if(set_R>=0)
{
M1B=0;
M1A=1;
DutyRatio_R = set_R + Motor_R_e;
}
else
{
M1B=1;
M1A=0;
DutyRatio_R = -set_R + Motor_R_e;
}
//限幅
if(DutyRatio_L > 100)
DutyRatio_L = 100;
if(DutyRatio_L < 0)
DutyRatio_L = 0;
if(DutyRatio_R > 100)
DutyRatio_R = 100;
if(DutyRatio_R < 0)
DutyRatio_R = 0;
}
unsigned char ScanLine()
{
unsigned char scanTemp;
int scanResult; //返回值
scanTemp = TrackPort; //读取TrackPort
scanTemp = TrackPort & 0x1F; //在TrackPort中提取Px.0-Px.4
switch(scanTemp)
{
case 0x1e: scanResult = 1;break; //11110 左偏3级
case 0x1d: scanResult = 2;break; //11101 左偏1级
case 0x19: scanResult = 2;break; //11001 左偏1级
case 0x1c: scanResult = 2;break; //11100 左偏2级
case 0x18: scanResult = 2;break; //11000 左偏2级(new)
case 0x1b: scanResult = 3;break; //11011 √
case 0x17: scanResult = 4;break; //10111 右偏1级
case 0x13: scanResult = 4;break; //10011 右偏1级(new)
case 0x0f: scanResult = 5;break; //01111 右偏3级
case 0x07: scanResult = 5;break; //00111 右偏2级
case 0x03: scanResult = 5;break; //00011 右偏2级(new)
case 0x00: scanResult = 6;break; //00000 后退
default: scanResult = 0; break; //其他情况
}
return scanResult; //返回最后结果
}
void main()
{
PWM_Config(); //PWM定时器初始化
while(1)
{
switch (ScanLine())
{
case(1):setPWM(50,-50);break; //右转
case(2):setPWM(60,30);break; //小幅右转
case(3):setPWM(60,60);break; //前进
case(4):setPWM(30,60);break; //小幅左转
case(5):setPWM(-50,50);break; //左转
case(6):setPWM(-50,-50);break; //后退
case(0):setPWM(30,30);break; //小幅度前进
}
}
}
PID算法
P(比例),I(积分),D(微分),理学堂上讲了PID算法,听得一知半解,我在网上搜了一些PID算法的解析,总算是更加理解了算法核心。
以下称为闭环控制系统,就是让输入指令执行得到的结果反馈给我们,再做出一系列判断实现对我们原始指令的调整,来达到我们需要的输出目的。
e(k)为输出值与设定值之间的差值,
第一项为比例控制,每次对差值进行比例调整,相当于每次的e(k)都成比例变化,逐渐到0.
第二项为积分控制,为了防止某些因素导致的影响使输出结果永远达不到设定值(稳态误差),于是采用积分控制来不断对每次的e(k)进行积分,当长时间未达到设定值,第二项的比重会越来越大,知道影响u(k),从而系统不会存在稳态误差。
第三项为微分控制,对于这个的解释,我看了一个例子讲的比较好
考虑刹车情况。平稳的驾驶车辆,当发现前面有红灯时,为了使得行车平稳,基本上提前几十米就放松油门并踩刹车了。当车辆离停车线非常近的时候,则使劲踩刹车,使车辆停下来。整个过程可以看做一个加入微分的控制策略。
微分,说白了在离散情况下,就是error的差值,就是t时刻和t-1时刻error的差,即u=kd*(error(t)-error(t-1)),其中的kd是一个系数项。可以看到,在刹车过程中,因为error是越来越小的,所以这个微分控制项一定是负数,在控制中加入一个负数项,他存在的作用就是为了防止汽车由于刹车不及时而闯过了线。从常识上可以理解,越是靠近停车线,越是应该注意踩刹车,不能让车过线,所以这个微分项的作用,就可以理解为刹车,当车离停车线很近并且车速还很快时,这个微分项的绝对值(实际上是一个负数)就会很大,从而表示应该用力踩刹车才能让车停下来。
切换到上面给水缸加水的例子,就是当发现水缸里的水快要接近1的时候,加入微分项,可以防止给水缸里的水加到超过1米的高度,说白了就是减少控制过程中的震荡。
STM32GPIO初学
GPIO称为通用输入输出。
STM32的GPIO分为GPIOA–>GPIOG共七组,每组端口又被分为0-15共计16个不同的引脚。每个GPIO可被寄存器配置成8种工作模式(4种输入模式4种输出模式)。
GPIO点亮LED灯实验
跟着B站视频学习主要是从STM32CubeMX建立工程配置GPIO模式,之后的实验是自己查资料学习的。自己使用的是STM32F103C8T6最小系统板。
- 建立工程
在STMCubeMX将PC13配置为GPIO_OUTPUT,然后再建立工程在keil中打开,在main.c文件中的main函数中写LED代码。
- 烧录程序
烧录程序与51不同的是,STM32有几种烧录方式。
- 通过ST-LINK烧录,因为这个东西比较贵,我就没有用。
- 通过USB转TTL进行烧录。
我这种烧录方式还需要手动调一下BOOT。
一般使用的好像都是将BOOT0设置为1,BOOT1为0,通过mcuisp进行下载。步骤如下:
- 手动调节跳线帽将BOOT0设置为1,BOOT1设置为0
- 将USB转TTL的3V3和GND与系统板上的对应位置连线,再讲RXD接PA9(TXD),将TXD接系统板的PA10(RXD).
- 打开mcuisp进行下载
- 下载完后将跳线帽调节BOOT0至0,按RESET进行启动运行
可以看到开发板上面的灯在闪烁。