写好程序后,接上蓝牙模块,打开手机蓝牙助手,小车运行效果:首先进行模式选择,有快速模式、中速模式、慢速模式,按下模式选择,小车接收到串口信号后(串口接收指示灯会闪一下),便可进行不同模式下的前进和后退,在前进和后退期间也可以进行左转和右转的操作,想停止就按停止按钮;切换模式时都要先按下模式选择按钮,再按其他按键
main.c:主函数,调用定时器0、定时器1和串口初始化函数
Motor.c:控制电机旋转,控制小车前进、后退、左转、右转、停止
Delay.c:延时函数
Timer0.c:定时器0初始化,前进的PWM调速
Timer2.c:定时器2初始化,后退的PWM调速
Uart.c:串口初始化,中断处理函数,接收串口指令,根据指令控制小车不同运行模式

为什么会用到定时器2,因为定时器1被用作了串口波特率发生器,定时器0用作前进PWM调速,所以后退的PWM调速就要用到平时不怎么用过的定时器2,手上STC89C52RC型号有定时器2
数据手册有详细介绍,这里截出关键的地方
定时器2中断号

定时器2的中断请求标志位是TF2和EXF2。当定时器寄存器TH2/TL2溢出时,溢出标志位TF2会被置位,定时器中断发生。当单片机转去执行该定时器中断时,定时器的溢出标志位TF2会被硬件清除。当EXEN2=1且T2EX的负跳变产生捕获或重装时,EXF2置位。定时器2中断使能时,EXF2=1也将使CPU从中断向量处执行定时器2中断子程序。

ET2:定时/计数器T2的溢出中断允许位。ET2=1,允许T2中断;ET2=0,禁止T2中断。
TF2:定时器2溢出标志。定时器2溢出时置位,必须由软件清除。当RCLK或TCLK=1时,TF2将不会置位
TR2:定时器2启动/停止控制位。置1时启动定时器
可使用STC-ISP工具生成定时器2配置函数,这里注意,初始化函数中先不着急开启计时,要在接收到串口信号再开启
/**
* @brief 定时器2初始化函数
* @param 无
* @retval无
*/
void Timer2Init(void) //500微秒@11.0592MHz
{
T2MOD = 0; //初始化模式寄存器
T2CON = 0; //初始化控制寄存器
TL2 = 0x33; //设置定时初值
TH2 = 0xFE; //设置定时初值
RCAP2L = 0x33; //设置定时重载值
RCAP2H = 0xFE; //设置定时重载值
TR2 = 0; //定时器2不开始计时,接收到串口信号时开启
ET2 = 1; //开启定时器2中断
EA = 1; //开启总中断
}
然后中断处理函数跟定时器0的内容几乎一样,只是中断号是5,然后定时器0是让小车前进
//Timer1中断处理函数,每隔0.5毫秒进入一次中断
void Timer2_Rountine() interrupt 5
{
count1++;
TF2 = 0; //定时器2溢出标志,软件清零
if(count1 < BackSpeed)
{
//定时器0里是调用GoForward()函数让小车前进
//后退
GoBack();
}
else
{
//停止
Stop();
}
//每次count+1是0.5ms,加了40次,是20ms
if(count1 == 40 ){count1 = 0;}
}
本次的中断处理函数有所更改
1.在判断第一个字母时,加入Q,M,S,分别是快速模式,中速模式和慢速模式
2.定义一个速度选择标志位,当判断串口收到的指令是"Quick",“Middle”,"Slow"中的一种时,相应的让标志位值位
3.然后下一个指令如果是前进或者后退,就判断速度选择标志位,给ForwardSpeed或BackSpeed赋不同的值,达到不同的速度
4.收到停止信号时,前进和后退的speed都要清0,speed一直有数据的话,小车就会一直走,串口这边让停PWM那边又让走,小车会出故障
5.收到指令后开启定时器的原因:
前提:如果定时器0和定时器2的初始化函数中让定时器开始计时,在main函数调用初始化函数,那么单片机一上电后两个定时器便会开始计时,而此时ForwardSpeed(前进速度)和BackSpeed(后退速度)都是0,所以在两者的中断处理函数中,count自增,都不满足count < ForwardSpeed/BackSpeed条件,此时小车会停止;
出现问题:如果此时收到串口前进信号,那么ForwardSpeed会被赋值,必定有段时间满足count < ForwardSpeed条件,所以小车会开始前进,但此时后退的PWM中断处理函数中BackSpeed的值仍然是0,会一直调用Stop()停止函数,定时器0想让小车前进,定时器2想让小车停止,所以两者出现了冲突,出现的现象是小车不动并且会发出吱吱的电流声
解决:解决上诉问题的做法便是两个定时器的初始化函数中不着急着开启计时,而是将TR0 = 1;(开启定时器0计时)放在串口接收到前进指令时开启,此时ForwardSpeed被赋值了,前进PWM调速开始,小车会前进,而定时器2因没有开始计时,所以不会溢出进入中断处理函数,不会进行后退PWM调速,没有调用Stop函数就不会对前进进行干扰;同理,在收到串口后退指令时再开启定时器2;为了避免已经开启的定时器0干扰,先关闭了定时器0计时,再开启定时器2,同理,如果一开始已经开启了定时器2进行后退,再前进的话就要先关闭了定时器2计时,再开启定时器0前进
优化后的现象:手机蓝牙先发送模式,然后按下前进按钮,小车就根据速度模式前进,按下后退按钮,小车也能后退,左转和右转也不受影响
总结:定时器0和定时器2只能有一个在计时,不能同时进行;在收到停止信号后,要将ForwardSpeed和BackSpeed都清零
6.串口最后 if 判断当接收数组为空时让小车停止,加上这句判断的原因是:根据之前的长按控制小车代码进行的优化;
出现问题:之前的长按控制代码中是在main函数的while循环里一直调用Stop函数,这种情况下,左转和右转可以实现长按控制,但前进和后退就不行了,按下前进或者后退按钮小车不动并会发出吱吱电流声,原因跟上面定时器的冲突一样,在PWM调速时,主函数却一直让小车停止;
解决:把main函数中的Stop()函数删掉,在串口最后加上一句 if 判断,当接收的数组为空时,再调用Stop()函数让小车停止,因为每接收到一个有效指令后,都会清空接收数组等待下一次接收,所以串口那边就要一直有信号接收,不然小车就会停止
优化后现象:
所以加上后实现的效果就是:一直按着左转或右转,小车正常转动,松手就停止,模式选择后,点击前进或后退也能正常启动
不足之处:因为前进和后退受PWM控制,是一个前进/后退与停止的不断进行的过程,若外界让小车停止时破坏到了PWM时序,则小车就不会启动并有电流声,而这次加上最后一条 if 语句判断后,虽然左转右转能长按控制,但前进和后退都不能长按控制,按一下按钮便前进或后退,按停止便停止,尝试过在最后的 if 判断中加入清除ForwardSpeed和BackSpeed的语句,但这样前进和后退就会抽搐,原因也简单,ForwardSpeed和BackSpeed在被赋值后很快便又被清0,PWM还没开启让小车启动就又要停止,所以出现轮子抽搐现象
暂且先这样吧,待以后想到好的解决办法再进行优化……
#define SIZE 10
unsigned char rec[SIZE];
extern unsigned char ForwardSpeed; //前进速度
extern unsigned char BackSpeed; //后退速度
//速度选择标志位
unsigned char Speed_Flag = 0;
/**
* @brief 串口中断处理函数,先接收模式选择,再前进和后退
* @param 无
* @retval无
*/
void Uart_Rountine() interrupt 4
{
static unsigned int i = 0;
unsigned char temp;
if(RI)
{
RI = 0;
temp = SBUF;
if(temp == 'F' || temp == 'B' || temp == 'L' || temp == 'R' || temp == 'S'||temp == 'Q'
||temp == 'S'||temp == 'M')
{
i = 0;
}
rec[i++] = temp;
//模式选择
//快速
if(rec[0] == 'Q' && rec[1] == 'u')
{
Speed_Flag = 3;
i = 0;
memset(rec,'\0',SIZE);
}
//中速
if(rec[0] == 'M' && rec[1] == 'i')
{
Speed_Flag = 2;
i = 0;
memset(rec,'\0',SIZE);
}
//慢速
if(rec[0] == 'S' && rec[1] == 'l')
{
Speed_Flag = 1;
i = 0;
memset(rec,'\0',SIZE);
}
//前进
if(rec[0] == 'F' && rec[1] == 'o')
{
TR2 = 0; //关闭定时器2计时
TR0 = 1; //开启定时器0计时
if(Speed_Flag == 3)
{
ForwardSpeed = 35;
}else if(Speed_Flag == 2)
{
ForwardSpeed = 25;
}else if(Speed_Flag == 1)
{
ForwardSpeed = 15;
}
i = 0;
memset(rec,'\0',SIZE);
}
//后退
if(rec[0] == 'B' && rec[1] == 'a')
{
TR0 = 0; //关闭定时器0计时
TR2 = 1; //打开定时器2计时
if(Speed_Flag == 3)
{
BackSpeed = 35;
}else if(Speed_Flag == 2)
{
BackSpeed = 25;
}else if(Speed_Flag == 1)
{
BackSpeed = 15;
}
i = 0;
memset(rec,'\0',SIZE);
}
//左转
if(rec[0] == 'L' && rec[1] == 'e')
{
GoLeft();
/*左转的延时少一点,可通过点动方式一点一点地调方向,
如果延时大的话,按一下就转动很大,不便调方向*/
Delay1ms(200);
i = 0;
memset(rec,'\0',SIZE);
}
//右转
if(rec[0] == 'R' && rec[1] == 'i')
{
GoRight();
Delay1ms(200); //右转的延时跟左转同理
i = 0;
memset(rec,'\0',SIZE);
}
//收到停止信号,speed记得要清零
if(rec[0] == 'S' && rec[1] == 't')
{
ForwardSpeed = 0;
BackSpeed = 0;
Stop();
i = 0;
memset(rec,'\0',SIZE);
}
//如果串口不发送信息,则停止
if(rec[0] == '\0' && rec[1] == '\0')
{
Stop();
}
if(i == SIZE){i = 0;}
}
}