51智能循迹车(一)

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();
    }
}

 ​​​​​​​重新编译并烧录​​​​​​​,可以看到电机转动,更改电机速度,可以看到变化,则测试通过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值