1.设计概述
项目旨在设计和实现一个基于51单片机的自动化小车。该小车不仅具备基本的移动功能,还能实现循迹、蓝牙控制和超声避障等智能化功能。
2.功能一概述
循迹行驶:小车能够识别预先设定的轨迹(例如黑线)并沿着轨迹行进。
- 可以按照弧线或者直角进行转弯
- 可以识别交叉线
- 可以识别弯度较大的锐角
2.1 硬件选型
想让小车跑起来,首先要考虑马达、驱动芯片和供电模块选型。这三者中,马达是最核心的部分,只有确定了马达,其他平台才能确定。
马达选型
51单片机小车马达选型一般就是两种:TT马达和N20马达。前者主要是用在各种儿童玩具上,后者的应用就比较广泛,在电子锁,电动牙刷上都有使用。两者之间,主要是在成本和性能之间做权衡。由于我们不仅需要小车动起来,还要精准的操控它,所以这里我们选用精度较高的N20马达。
供电模块选型
当马达型号确定以后,我们去查找N20参数,就可以发现N20有3v、6v、12v三个平台。这里我们选用6v平台,其输入电压是5-9v。我们可以选择两节锂电池串联供电:锂电池的放电电压是4.2v-2.5v,刚好在范围之内。
电压 DCV | 空载转速 rpm/min | 负载转速 rpm/min | 额定力矩 kg.cm | 额定电流 ma | 堵转力矩 kg.cm | 堵转电流 ma | 减速比 1:00 |
6 | 300 | 240 | 0.2 | 160 | 1.6 | 100 | 50 |
6 | 150 | 120 | 0.3 | 160 | 2.4 | 200 | 100 |
6 | 100 | 80 | 0.4 | 160 | 3.2 | 200 | 150 |
从图中我们可以看到,N20额定电流是160mA,我们采用两个电机,同时满载驱动需要320mA,只要电池放电速度高于这个数,就能够供应电机。为了稳妥起见,这里我们采用具有800mA放电速度的电池。
驱动模块选型
驱动模块只要能够满足电机的额定电压和电流都可以。这里选用睿智微的TA6586芯片驱动电机。驱动两个电机我们共需要两片芯片。
2.2 巡线系统
巡线方案采用光电感应模块,共有5组广电感应模块负责巡黑线,同时采用模拟比较器完成模数转换。比较器的型号就选择非常经典的两路比较器XD393。由于有5组光电模块,共需要3片比较器芯片。
巡线系统的架构如下图所示
2.3 PWM波电机驱动方式
TA6586共有两个GPIO输入,两个输出,如下图所示:
其中BI和FI代表PWM波输入,而out3和out4连接N20电机,我们通过调节BI,FI输入的PWM波的占空比,完成电机两端电压控制,从而实现控制转速。
3. 代码实现
Int_Motor.h
#ifndef __INT_MOTOR_H__
#define __INT_MOTOR_H__
#include "Dri_GPIO.h"
#include "Util.h"
#include "Dri_Timer0.h"
/**
* @brief 初始化方法
*/
void Int_Motor_Init();
/**
* @brief 设置左轮速度
* @param speed 左轮速度 [-40, 40]
*/
void Int_Motor_SetLeft(char speed);
/**
* @brief 设置右轮速度
* @param speed 右轮速度 [-40, 40]
*/
void Int_Motor_SetRight(char speed);
/**
* @brief 更新轮子状态的方法,循环调用这个方法,会让轮子的当前速度趋向目标速度
*/
void Int_Motor_UpdateSpeed();
#endif
Int_Motor.c
#include "Int_Motor.h"
#include <STDLIB.H>
#define MAX_SPEED 40
#define SHARP 1
static u8 s_speed_counter;
static u8 s_wheel_status_counter;
typedef struct
{
// 轮子方向,0朝前,1朝后
u8 direction;
// 轮子速度绝对值
u8 absolute_speed;
// 轮子的当前转速
char current_speed;
// 轮子的目标速度
char target_speed;
} Struct_WheelStatus;
// 0是左轮,1是右轮
static Struct_WheelStatus s_st_wheel[2];
/**
* @brief 电机驱动的时钟中断方法,负责发方波
*
*/
static void Int_Motor_TimerCallback()
{
s_speed_counter++;
if (s_speed_counter >= MAX_SPEED)
{
s_speed_counter = 0;
}
// 如果我们不希望轮子转,两个都为0
if (s_speed_counter < s_st_wheel[0].absolute_speed)
{
// 如果我们希望轮子转,拉高其中一个引脚即可
// 如果前进,direction是0,后退direction是1
// 前进的时候 MOTOR_L_PIN1 = 1, MOTOR_L_PIN2 = 0
// 后退时候 MOTOR_L_PIN1 = 0, MOTOR_L_PIN2 = 1
MOTOR_L_PIN1 = !s_st_wheel[0].direction;
MOTOR_L_PIN2 = s_st_wheel[0].direction;
}
else
{
// 如果我们不希望轮子转,两个都为0
MOTOR_L_PIN1 = 0;
MOTOR_L_PIN2 = 0;
}
if (s_speed_counter < s_st_wheel[1].absolute_speed)
{
MOTOR_R_PIN1 = !s_st_wheel[1].direction;
MOTOR_R_PIN2 = s_st_wheel[1].direction;
}
else
{
// 如果我们不希望轮子转,两个都为0
MOTOR_R_PIN1 = 0;
MOTOR_R_PIN2 = 0;
}
}
/**
* @brief 内部方法,给某一个轮子设置目标速度
*
* @param p_st_wheel 轮子指针
* @param target_speed 目标速度
*/
static void Int_Motor_SetTargetSpeed(Struct_WheelStatus *p_st_wheel, char target_speed)
{
if (target_speed < -MAX_SPEED)
{
p_st_wheel->target_speed = -MAX_SPEED;
}
else if (target_speed > MAX_SPEED)
{
p_st_wheel->target_speed = MAX_SPEED;
}
else
{
p_st_wheel->target_speed = target_speed;
}
}
/**
* @brief 内部方法,更新一个轮子的状态,让其当前速度趋近于目标速度
*
* @param p_st_wheel
*/
static void Int_Motor_UpdateWheel(Struct_WheelStatus *p_st_wheel)
{
// 让当前速度趋近于一点点目标速度
if (p_st_wheel->target_speed == p_st_wheel->current_speed)
{
return;
}
if (p_st_wheel->target_speed > p_st_wheel->current_speed)
{
p_st_wheel->current_speed++;
}
else
{
p_st_wheel->current_speed--;
}
// 根据更新的当前速度,更新方向和速度绝对值
p_st_wheel->direction = (p_st_wheel->current_speed < 0);
p_st_wheel->absolute_speed = abs(p_st_wheel->current_speed);
}
void Int_Motor_Init()
{
u8 i;
s_speed_counter = 0;
for (i = 0; i < 2; i++)
{
s_st_wheel[i].target_speed = 0;
s_st_wheel[i].current_speed = 0;
s_st_wheel[i].absolute_speed = 0;
s_st_wheel[i].direction = 0;
}
Dri_Timer0_RegisterCallback(Int_Motor_TimerCallback);
}
void Int_Motor_SetLeft(char speed)
{
Int_Motor_SetTargetSpeed(s_st_wheel, speed);
}
void Int_Motor_SetRight(char speed)
{
Int_Motor_SetTargetSpeed(s_st_wheel + 1, speed);
}
void Int_Motor_UpdateSpeed()
{
s_wheel_status_counter++;
if (s_wheel_status_counter == SHARP)
{
Int_Motor_UpdateWheel(s_st_wheel);
}
else if (s_wheel_status_counter == 2 * SHARP)
{
s_wheel_status_counter = 0;
Int_Motor_UpdateWheel(s_st_wheel + 1);
}
}
4.测试
修改Main.c,写入以下内容
#include "Int_Motor.h"
#include "Dri_Timer0.h"
void main() {
// 初始化定时器
Dri_Timer0_Init();
// 打开中断总开关
EA = 1;
// 初始化电机
Int_Motor_Init();
// 设置电机速度
Int_Motor_SetLeft(10);
Int_Motor_SetRight(10);
while(1) {
// 更新电机速度
Int_Motor_UpdateSpeed();
}
}
重新编译并烧录,可以看到电机转动,更改电机速度,可以看到变化,则测试通过。