STM32
直播中

张明

7年用户 920经验值
私信 关注
[问答]

如何去实现一种基于STM32F103ZET6和L298N的智能车设计

如何去实现一种基于STM32F103ZET6和L298N的智能车设计?

回帖(1)

杨静

2021-12-22 15:55:37
简介:

大学第一学期就希望自己能够制造出一辆智能车,但是前期经验不足,踩了很多坑,但是我没有放弃,继续动手制作一辆智能车。
硬件:

1个STM32F103ZET6开发板,1个L298N模块,智能车框架,1个18650电池盒三节带粗线,3节18650充电锂电池,两个TT直流减速电机,一个四路红外循迹模块,一个超声波支架,一个HC-SR04超声波模块,一个红外接收头和红外遥控器。
STM32F103ZET6开发板

stm32开发板是整车的核心,控制车辆的运动状态。功能非常强大,有定时器通道可以输出PWM控制电机转速,也有输入捕获功能解码红外信号。不过建议使用stm32f1系列的最小系统,因为智能车不需要用到开发板那么多功能,开发板太占空间了。
L298N电机驱动模块

单片机的电流太小,无法驱动电机转动,需要用到这个功放模块。

使用GPIOB的端口驱动电机,左电机连接GPIOB6,GPIOB12,右电机连接GPIOB7,GPIOB_13。注意电池和L298N和单片机要共地。其中GPIOB6,GPIOB7是PWM控制,GPIOB12,GPIOB13是直流控制。
通道A使能端和通道B使能端的跳线帽不要拔掉,才能控制电机。L298N上面有四个I/O口与单片机相连控制电机。in1和in2控制电机1,in3和in4控制电机2,控制原理是一端高电平,一端低电平,有点压差就能使电机转动。
电源模块

电源正极与L298N模块的12V输入连接,负极与L298N的GND连接。L298N的驱动电压至少6V,电源电量减少时会导致电压下降,可能会导致由于L298N的供电不足,L298N上面的5V输出无法驱动单片机正常工作,表现为单片机上电几秒钟之后就断电了。
各个模块很消耗电能,建议买个18650充电器,每次玩车之后就充电,充电的时间很长。
TT直流减速电机

这款电机带插头,只需要用杜邦线就能连接电机和单片机。如果电机没有带插头的话,就需要在电机的引脚那里打胶。有电压差,电机就能转动。但是,虽然单片机也能输出5V,但是由于单片机的电流太小,达不到电机转动的最小功率,所以只靠单片机的5V输出是无法使电机转动的,需要配合L298N模块使用。
一个四路红外循迹模块

这里我只用了两路红外循迹,正常高电平,红灯亮起。检测到黑线变成低电平,红灯熄灭。左边的红外对管连接GPIOA4,右边接GPIOA1。根据红外线输入信号情况,判断路面上黑线的方向,进而循着黑线前进。

超声波支架和HC-SR04超声波模块

超声波模块有四个引脚分别为:VCC、GND、TRIG、ECHO。
超声波支架是用来固定超声波的,为了增加车车的趣味性和可靠性,可以把支架换成步进电机,以后再换上。
TRIG引脚用来产生超声波,而ECHO是用来接收超声波回响。当超声波不工作时,ECHO为低电平,TRIG也为低电平。当TRIG变成高电平时,发出超声波,开启定时器计算时间,当接收到反射回来的超声波时,ECHO变成高电平,定时器计时结束。根据波速,时间计算出距离。
红外接收头和红外遥控器

红外接收头有三个引脚,分别为VCC、GND、I/O。这款红外遥控设备采用NEC协议(即根据高低电平的情况解码),红外接收头捕获红外遥控器发射的信号并根据NEC协议进行解码,即可判断出是遥控器上面哪个按键按下。
软件

程序上面采用定时器通道输出PWM控制电机,另外使用定时器通道的输入捕获功能解码红外遥控,具体什么是PWM,输入捕获这里就不详细介绍,不过会讲解PWM和输入捕获的代码。
采用PWM控制电机

一个电机上面有两个引脚,其中一个引脚连接PWM通道,另一个引脚连接直流控制I/O口。
在配置PWM之前,要先设置定时器的溢出时间,即设置定时器的定时周期。在一个定时周期中,可以控制高低电平的比例,高电平在一个周期中所占的比例叫做占空比,占空比越大,则电机转动得越快,占空比越小,电机转动得越慢。由此可以实现电机调速,也能通过左右电机转速不同实现转弯。
配置PWM.c

//定义初始化GPIO用到的结构体变量//
GPIO_InitTypeDef GPIO_InitStructure;


//定义初始化定时器4基本设置用到的结构体变量//
TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

//定义配置定时器通道输出PWM用到的结构体变量//
TIM_OCInitTypeDef  TIM_OCInitStructure;


//开启定时器4和GPIOB的时钟//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);  
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);


//选择GPIOB6和GPIOB7,这两个是PWM接口,分别连接左右电机//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
//设置为复用推挽输出模式//
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//选择速度//
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//初始化GPIOB//
GPIO_Init(GPIOB, &GPIO_InitStructure);

//这两个GPIO口分别连接左右电机//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13;
//设置为输出模式//
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);


//设置自动重装载值//
IM_TimeBaseStructure.TIM_Period = 1000;
//设置分频系数//
TIM_TimeBaseStructure.TIM_Prescaler =72-1;
//不分频//
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//向上计数模式//
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
//初始化定时器4//
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);


//TIM4_CHANNEL1//
//选择定时器通道输出PWM//
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//输出比较使能//
IM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//CCR上的值,占空比先设置为0//
TIM_OCInitStructure.TIM_Pulse = 0;
//极性高//
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//通道1输出比较初始化//
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
//使能CCR上的预装载寄存器//
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);


//TIM4_CHANNEL2//
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM4, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);


//使能TIM4//
TIM_ARRPreloadConfig(TIM4, ENABLE);
TIM_Cmd(TIM4, ENABLE);


配置电机.c


//前进//
void gostraight(void)
{
        TIM_SetCompare1(TIM4,600);        //右
        PBout(12)=0;        //右
        TIM_SetCompare2(TIM4,600);        //左
        PBout(13)=0;        //左
}


//向左转,只需要右电机转速比左电机快//
void goleft(void)
{
        //右电机//
        TIM_SetCompare1(TIM4,600);
        PBout(12)=0;
        //左电机//
        TIM_SetCompare2(TIM4,250);
        PBour(13);
}


//向右转,只需要左电机转速比右电机快//
void goright(void)
{
        //右电机//
        TIM_SetCompare1(TIM4,250)
        PBout(12)=0;
        //左电机//
        TIM_SetComare2(TIM4,600);
        PBout(13);
}


//向后走,电机I/O口电平相反即可
void goback(void)
{
        //右电机//
        TIM_SetCompare1(TIM4,400);
        PBout(12)=1;
        //左电机//
        TIM_SetCompare2(TIM4,400);
        PBout(13);
}


//停止//
void gostop(void)
{
        //右电机//
        TIM_SetCompare1(TIM4,0);
        PBout(12)=0;
        //左电机//
        TIM_SetCompare2(TIM4,0);
        PBout(12)=0;
}


输入捕获解码红外信号

小车上面的红外接收头遵循NEC协议,即根据红外信号中高低电平的脉宽值进行判断在红外遥控上是哪个按键被按下。因此需要对遥控器发出的红外信号中的高低电平脉宽进行计算,我们就要捕获高低电平,可以用定时器输入捕获功能实现。这里的TIM3_CH2与GPIAOA7相连,我们选择PA7与红外接收头的信号线I/O口相连。因为要用到定时器通道的输入捕获功能,因此红外接收头的信号线I/O口一定要和定时器通道相连。
配置定时器输入捕获remote.c

//初始化GPIO用到的结构体变量//
GPIO_InitTypeDef GPIO_InitStructure;
//初始化定时器基本设置用到的结构体变量//
TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
//初始化定时器输入捕获用到的结构体变量//
TIM_ICInitTypeDef  TIM_ICInitStructure;


//开启定时器和GPIOA的时钟//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);


//配置GPIOA7//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
//设置为输入模式//
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
//I/O口的速度/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//初始化GPIOA/
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOA7上拉//
GPIO_SetBits(GPIOA,GPIO_Pin_7);


//配置定时器基本设置//
//设定定时器自动重装载值,最大10ms溢出//
TIM_TimeBaseStructure.TIM_Period = 10000;
//设置预分频器//
TIM_TimeBaseStructure.TIM_Prescaler =(72-1);
//不分频//
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//向上计数模式//
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//初始化定时器3//
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);


//选择通道2//
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
//上升沿捕获//
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
//不反向,通道2对应输入捕获2//
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
//不分频//
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
//配置滤波器//
TIM_ICInitStructure.TIM_ICFilter = 0x03;
//定时器输入捕获初始化//
TIM_ICInit(TIM3, &TIM_ICInitStructure);


//使能定时器3//
TIM_Cmd(TIM3,ENABLE );
//允许定时器3更新中断和定时器3通道2输入捕获中断//
TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC2,ENABLE);


//遥控器状态
//[7]:收到引导码标志
//[6]:得到了一个按键所有信息
//[5]:保留
//[4]:标记上升沿已经捕获的
//[3:0]:溢出计时器
u8  RmtSta=0;
u16 Dval;
u32 RmtRec=0;
u8  RmtCnt=0;


//定时器3中断服务函数//
void TIM3_IRQHandler(void)
{
        if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET)
        {
                if(RmtSta&0x80)
                {
                        RmtSta&=~0X10;
                        if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;
                        if((RmtSta&0X0F)<14)RmtSta++;
                        else
                        {
                                   RmtSta&=~(1<<7);
                                    RmtSta&=0XF0;
                        }
                }
        }
        if(TIM_GetITStatus(TIM3,TIM_IT_CC2)!=RESET)
        {
                if(PAin(7)==1)
                {
                        TIM_OC2PolarityConfig(TIM3,TIM_ICPolarity_Falling);                  
                        TIM_SetCounter(TIM3,0);
                        RmtSta|=0X10;
                }else
                {
                        Dval=TIM_GetCapture2(TIM3);
                        TIM_OC2PolarityConfig(TIM3,TIM_ICPolarity_Rising);
                        if(RmtSta&0X10)
                        {
                                if(RmtSta&0X80)
                                {
                                        if(Dval>300&&Dval<800)
                                        {
                                                RmtRec<<=1;
                                                RmtRec|=0;
                                        }else if(Dval>1400&&Dval<1800)
                                        {
                                                RmtRec<<=1;
                                                RmtRec|=1;
                                        }else if(Dval>2200&&Dval<2600)
                                        {
                                                RmtCnt++;
                                                RmtSta&=0XF0;
                                        }
                                }
                                else if(Dval>4200&&Dval<4700)
                                {
                                        RmtSta|=1<<7;
                                        RmtCnt=0;
                                }
                        }
                        RmtSta&=~(1<<4);
                 }
        }
                 TIM_ClearITPendingBit(TIM3,TIM_IT_Update|TIM_IT_CC2);  
}


//该函数用于把得到的红外信息进行解码,并传递到程序中//
u8 Remote_Scan(void)
{
        u8 sta=0;      
            u8 t1,t2;
            if(RmtSta&(1<<6))
            {
                    t1=RmtRec>>24;
                    t2=(RmtRec>>16)&0xff;
                    if((t1==(u8)~t2)&&t1==0)
                    {
                            t1=RmtRec>>8;
                         t2=RmtRec;  
                         if(t1==(u8)~t2)
                                 sta=t1;
                 }
                if((sta==0)||((RmtSta&0X80)==0))
                 {
                         RmtSta&=~(1<<6);
                         RmtCnt=0;
                 }
         }
         return sta;
}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分