硬件介绍
小车:
面包板:
用于扩展接线,由于小车可以扩展许多模块,所以使用面包板增加容错性
L9110s:
电机的驱动模块,接通VCC,GND模块电源指示灯亮;下图中集成了两个L9110s。
B-1A -> P3.2; B-1B -> P3.3; A-1A -> P3.4; A-1B -> P3.5;
正转时:B_1A = 0; B_1B = 1; A_1A = 0; A_1B = 1;
反转时:B_1A = 1; B_1B = 0; A_1A = 1; A_1B = 0;
初步接线图:
PS:面包板,开关和单片机均采用热熔胶固定,电机及其驱动模块均由电池供电,除了上面提到L9110s和单片机的控制接线和电源之外,注意要有一根杜邦线让单片机和L9110s共地。
让小车动起来
将小车组装完成后,首先就是要让小车动起来,由刚刚对于电机驱动模块的讲解可以很容易实现让电机正反转,基于此,前进后退,左转右转的代码就都能写出来了:
void move_backward() //后退
{
B_1A_le = 1;
B_1B_le = 0;
A_1A_ri = 1;
A_1B_ri = 0;
}
void move_forward() //前进
{
B_1A_le = 0;
B_1B_le = 1;
A_1A_ri = 0;
A_1B_ri = 1;
}
void move_leftturn() //左转
{
B_1A_le = 0; //左轮不动
B_1B_le = 0;
A_1A_ri = 0; //右轮往前
A_1B_ri = 1;
}
void move_rightturn() //右转
{
B_1A_le = 0; //左轮往前
B_1B_le = 1;
A_1A_ri = 0; //右轮不动
A_1B_ri = 0;
}
void move_stop() //停止
{
B_1A_le = 0;
B_1B_le = 0;
A_1A_ri = 0;
A_1B_ri = 0;
}
让小车前进的 实现效果:
代码封装
由于小车代码肯定会巨长无比,所以养成良好的习惯,习惯性的完成一个模块之后就封装,方法和上节DHT11的末尾方法一样, 创建motor的c和h文件。
小车的控制
在熟悉了小车如何进行移动之后,就要开始学习如何控制小车,在之前已经学习过各种模块了,其中就有很多可以用来控制小车:
1. 串口/蓝牙控制小车
依然采用封装函数的方法:
UART.c:
#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include <string.h>
sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
static int i = 0; //此时这句命令只会被执行一次。避免每次发生中断i都会清0
char cmd[12];
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void printSTR(char *msg)
{
while(*msg != '\0'){
SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
TI = 0;
msg++;
}
}
void Dealstr()
{
if(cmd[0] == 'M' && cmd[1] == '1'){ //前进
move_forward();
memset(cmd,'\0',12); //将字符串清空
}
if(cmd[0] == 'M' && cmd[1] == '2'){ //后退
move_backward();
memset(cmd,'\0',12); //将字符串清空
}
if(cmd[0] == 'M' && cmd[1] == '3'){ //左转
move_leftturn();
memset(cmd,'\0',12); //将字符串清空
}
if(cmd[0] == 'M' && cmd[1] == '4'){ //右转
move_rightturn();
memset(cmd,'\0',12); //将字符串清空
}
if(cmd[0] == 'M' && cmd[1] == '5'){ //停止
move_stop();
memset(cmd,'\0',12); //将字符串清空
}
}
void UARTinter() interrupt 4 //由于不管TI还是RI置1时,中断都会发生,所以为了逻辑严谨,可以在中断处理函数中添加判断
{
if(RI == 1){ //如果是RI引起的中断
char tmp;
tmp = SBUF;
if(tmp == 'M'|| i == 12){ //M1=前进;M2=后退;M3=左转;M4=右转; M5=停止
i = 0;
}
cmd[i] = tmp; //从SBUF里面读发来的数据
i++;
Dealstr();
RI = 0;//软件复位
}
}
main.c:
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include "motor.h"
#include "delay.h"
#include "UART.h"
sbit D5 = P3^7;
void main()
{
UartInit();
ES = 1;
EA = 1; //打开中断!
while(1){
printSTR("mjm");
Delay1000ms();
}
}
实现效果:
可以不断接受心跳指令的同时,写下M1,M2,M3,M4,M5电机会做出对应的动作。
实现了串口,就相当于实现了蓝牙,因为蓝牙模块也使用了串口:
同样, 可以不断接受心跳指令的同时,写下M1,M2,M3,M4,M5电机会做出对应的动作。
PS: 连一根杜邦线接面包板的正极和单片机的VCC,加上之前连的共地线,就可以实现电池给单片机的供电,从而实现小车的无线控制。并且,这种方式不是点动,比如左转一点点我要输入M3,然后再输入M5停下,但是现实生活中的遥控车,按下左转的时候左转,不按就不会左转,即现实中的遥控车是点动的,而我以上实现的并不是。
同时注意到虽然我输入的是M1是前进的意思,但是车子并没有笔直往前,说明小车两边的电机输出可能存在一定的差异。
2.使用无线遥控器遥控
我同样尝试了使用做电瓶车遥控器时用过的433M无线收发模块:
433M.c:
#include "reg52.h"
#include "motor.h"
#include "delay.h"
sbit D0 = P0^0;
sbit D1 = P0^1;
sbit D2 = P0^2;
sbit D3 = P0^3;
void statusCheck()
{
if(D0 == 1){ //D0(A)为前进
move_forward();
}else if(D1 == 1){ //D1(B)为右转
move_rightturn();
}else if(D2 == 1){ //D2(C)为左转
move_leftturn();
}else if(D3 == 1){ //D3(D)为后退
move_backward();
}else{
move_stop();
}
}
main.c:
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include "motor.h"
#include "delay.h"
#include "433M.h"
void main()
{
while(1){
statusCheck();
}
}
PS:这种实现方式虽然可以实现点动,但是太“点”了,即我按一下按钮,电机只会微弱的转一下,而如果我长按,电机也只会不停的一抽一抽的微弱转动没法连续转动。。。
小车的PWM调速
在刚刚的控制中,实现了上下左右的控制,但是小车的速度无法控制,所以需要用PWM波来对小车的速度来进行控制:已知,对于电机,写1就是全速前进,那么比如在20ms内,如果一半的时间写1,一半的时间写0,那就会比20ms一直是1来的速度慢一半,基于这种思路就可以用PWM波,通过调整占空比来控制小车的速度:
timer0.c:
#include "reg52.h"
#include "motor.h"
sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
int cnt = 0;
char speed;
void Timer0Init(void) //0.5毫秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x33; //设置定时初值
TH0 = 0xFE; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1; //打开中断!
}
//timer0的中断处理程序 //中断程序一般写在main函数的后面 //定时器0溢出时将触发这个中断函数
void timer0_inter() interrupt 1
{
cnt++;
TL0 = 0x33; //重新给初值!!
TH0 = 0xFE;
if(cnt < speed){ //cnt =1 时,爆表了一次,过了0.5ms,speed的值越大,属于动的代码就占比越大,速度就越快
//动
move_forward();
}else{
//停止
move_stop();
}
if(cnt == 39){//每经过(40*0.5毫秒 = )20毫秒,PWM波经过一个周期
cnt = 0;
}
}
main.c:
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include "motor.h"
#include "delay.h"
#include "UART.h"
#include "433M.h"
#include "timer0.h"
extern char speed;
void main()
{
UartInit();
Timer0Init();
while(1){
speed = 10; //10/40 = 1/4的时间全速前进
Delay1000ms();
Delay1000ms();
speed = 30; //30/40 = 3/4的时间全速前进
Delay1000ms();
Delay1000ms();
}
}
实现效果:
可以看到,小车每隔两秒就会在慢速和快速之间切换:
左右轮的分别调速:
要实现这个效果,首先要在motor.c中添加单独控制左右轮前进后退的代码:
void move_backward_left() //左轮后退
{
B_1A_le = 1;
B_1B_le = 0;
}
void move_backward_right() //右轮后退
{
A_1A_ri = 1;
A_1B_ri = 0;
}
void move_forward_left() //左轮前进
{
B_1A_le = 0;
B_1B_le = 1;
}
void move_forward_right() //右轮前进
{
A_1A_ri = 0;
A_1B_ri = 1;
}
void move_stop_left() //左轮停止
B_1A_le = 0;
B_1B_le = 0;
}
void move_stop_right() //右轮停止
{
A_1A_ri = 0;
A_1B_ri = 0;
}
然后在timer0.c中进行修改,我名字起的不太好,要是要实现左右轮的控制,就需要两个timer,一个控制左轮一个控制右轮:
#include "reg52.h"
#include "motor.h"
sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
int cnt1 = 0;
int cnt2 = 0;
char speed_left;
char speed_right;
void Timer0Init(void) //timer0控制左轮
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x33; //设置定时初值
TH0 = 0xFE; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1; //打开中断!
}
void Timer1Init(void) //timer1控制右轮
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x33; //设置定时初值
TH1 = 0xFE; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1;
EA = 1; //打开中断!
}
//timer0的中断处理程序 //中断程序一般写在main函数的后面 //定时器0溢出时将触发这个中断函数
void timer0_inter() interrupt 1
{
cnt1++;
TL0 = 0x33; //重新给初值!!
TH0 = 0xFE;
if(cnt1 < speed_left){ //cnt =1 时,爆表了一次,过了0.5ms,speed的值越大,属于动的代码就占比越大,速度就越快
//动
move_forward_left();
}else{
//停止
move_stop_left();
}
if(cnt1 == 39){//每经过(40*0.5毫秒 = )20毫秒,PWM波经过一个周期
cnt1 = 0;
}
}
//timer1的中断处理程序 //中断程序一般写在main函数的后面 //定时器1溢出时将触发这个中断函数
void timer1_inter() interrupt 3
{
cnt2++;
TL1 = 0x33; //重新给初值!!
TH1 = 0xFE;
if(cnt2 < speed_right){ //cnt =1 时,爆表了一次,过了0.5ms,speed的值越大,属于动的代码就占比越大,速度就越快
//动
move_forward_right();
}else{
//停止
move_stop_right();
}
if(cnt2 == 39){//每经过(40*0.5毫秒 = )20毫秒,PWM波经过一个周期
cnt2 = 0;
}
}