动手做一个简单的智能小车
来到CNDN一年了,看到了许多大佬的杰出作品.也该写点什么来回馈给大家了前不久接触了单片机,想提前进行实践一下所以有想法做一个实体出来,想来想去难的怕自己搞不定,但是还好找到了志同道合的王同学,一起搞一个智能小车.新手上路,多多指教
1.硬件的选取
走过许多坑才知道准备工作的重要性,前期对一个需要开发实体的了解的多少,决定你能走多远
-
四驱的小车(如果对底板有要求可以选择买铝制的)
马达,*,底板都是配套的某宝上可以买到 -
智能的核心:单片机 开始时用正点原子的学习板做的,后来板子烧了
然后直接买了系统板
普中的最小系统板,可扩展引脚很多.便宜好用
尽管说查看引脚手册和基础的芯片手册是基础,但是对于一般新手来说,更多的IO口才是最舒服的
- 电源的选择
我直接买的5号电池盒,因为要达到12v的电压,需要8节5号电池,看起来很累赘,但是续航还是可以的
大家可以买12v的航模电池,既好看还可以充电就是贵点,但是接线麻烦一点
2.模块的选取
你的小车要实现什么功能,这些传感器等模块的选取是很重要的
- 驱动模块 L298N驱动
开始的时候没有意识到一个驱动就可以控制四个*,用了两个驱动.浪费了资源和电量
详细的资料可以看大佬
注意:调适马达的时候不要一股脑全都安装上去,先进行单个马达转动方向,接线的长度的调适,否则你会面临无限的拆卸
Moter.h
#ifndef __MOTER_H
#define __MOTER_H
#include "sys.h"
#define INright1 PEout(6)
#define INright2 PEout(7)
#define INleft1 PEout(8)
#define INleft2 PEout(9)
#define pwmright TIM3->CCR3
#define pwmleft TIM3->CCR4
#define LEFT 0x01
#define RIGHT 0x02
#define ONE 0x01
#define TWO 0x02
#define THREE 0x04
#define FOUR 0x08
#define Car_Right_90 200 //右转90度
#define Car_Right_45 550
#define Car_Left_45 550 //左转45度
#define Car_Left_90 220
void Motor_init(void); //电机初始化
void PWMout(u16 psc,u16 arr); //pwm输出
void Carstart(void); //小车启动
void Carback(void); // 小车倒车
void Carstop(void); //小车停止
void Greadspeed(u8 view); //档速设置
void Carturn(u8 direction,u16 angle); //小车转弯
void Carspeedcontrol(u16 speed); //速度控制
void Carturnall(u8 direction); //原地旋转*/
#endif
Moter.c
#include<motor.h>
#include<sys.h>
#include "delay.h"
#include "stm32f10x.h"
void Motor_init(void)
{
GPIO_InitTypeDef GPIO_InitTypeStructer;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_Out_PP; //全部*
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitTypeStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitTypeStructer);
}
void PWMout(u16 psc,u16 arr)
{
GPIO_InitTypeDef GPIO_InitTypeStructer;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStructer;
TIM_OCInitTypeDef TIM_OCInitTypeStructer;
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_AF_PP; //PWM驱动
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitTypeStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitTypeStructer);
TIM_TimeBaseInitTypeStructer.TIM_ClockDivision=0;
TIM_TimeBaseInitTypeStructer.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitTypeStructer.TIM_Period=arr;
TIM_TimeBaseInitTypeStructer.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStructer);
TIM_OCInitTypeStructer.TIM_OCMode=TIM_OCMode_PWM1; //右驱动
TIM_OCInitTypeStructer.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitTypeStructer.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitTypeStructer.TIM_Pulse=0;
TIM_OC3Init(TIM3,&TIM_OCInitTypeStructer);
TIM_OCInitTypeStructer.TIM_OCMode=TIM_OCMode_PWM1; //左驱动
TIM_OCInitTypeStructer.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitTypeStructer.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitTypeStructer.TIM_Pulse=0;
TIM_OC4Init(TIM3,&TIM_OCInitTypeStructer);
TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM3,TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3,ENABLE); //重装载使能
TIM_Cmd(TIM3,ENABLE); //定时器使能
}
void Carstart(void) //小车启动
{
pwmleft=4000;pwmright=4000;
INleft1=0;INleft2=1;
INright1=1;INright2=0;
}
void Carstop(void) //小车停止
{
INleft1=0;INleft2=0;
INright1=0;INright2=0;
delay_ms(3);
}
void Carback(void)
{
pwmleft=6000;pwmright=6000;
INleft1=1;INleft2=0;
INright1=0;INright2=1;
delay_ms(15);
}
void Greadspeed(u8 view) //挡位设置
{
switch(view)
{
case ONE:pwmleft=4250;pwmright=4250;break; //一档
case TWO:pwmleft=4500;pwmright=4500;break; //二档
case THREE:pwmleft=4700;pwmright=4700;break; //三档
case FOUR:pwmleft=5000;pwmright=5000;break; //四档
}
}
void Carturn(u8 direction,u16 angle) //不完善 需要减速进行转弯
{
if(direction==0x01) //左转
{
Carspeedcontrol(6500);
INleft1=1;INleft2=0;
INright1=1;INright2=0;
if(angle>1800) //根据延迟和转弯速度判断转弯的角度
{
delay_ms(1800);
delay_ms(angle-1800);
}
else
delay_ms(angle);
}
else //右转
{
Carspeedcontrol(6500);
INleft1=0;INleft2=1;
INright1=0;INright2=1;
if(angle>1800)
{
delay_ms(1800);
delay_ms(angle-1800);
}
else
delay_ms(angle);
}
}
void Carspeedcontrol(u16 speed)
{
pwmleft=speed;pwmright=speed;
}
void Carturnall(u8 direction) //原地转圈
{
if(direction==0x01) //左转
{
Carspeedcontrol(6500);
INleft1=1;INleft2=0;
INright1=1;INright2=0;
}
else //右转
{
Carspeedcontrol(6500);
INleft1=0;INleft2=1;
INright1=0;INright2=1;
}
}
- 避障模块(超声波避障)HC-SR04
大佬资料
注意:每一次超声波测距的间隔意味这小车的灵敏度,以为这个模块只有大约15°的测量范围,尽管有了超声波的舵机摆动但是在测量斜体的时候还是有可能会发生碰撞.
解决:1.优化算法,对于舵机转动的角度经行细化,但是增加了计算负担,和测量的时间
2.在小车的两侧加上红外避障模块(原理和循迹模块类似 后面可以看)
3.双舵机,双超声波探测
steering.h
#ifndef __STEERING_H
#define __STEERING_H
#define pwm TIM5->CCR3
#define SG90_Right_90 195 //右转90度
#define SG90_Right_45 190 //右转45度
#define SG90_Front 185 //舵机摆正
#define SG90_Left_45 180 //左转45度
#define SG90_Left_90 175 //左转90度
#include "sys.h"
void Steering_Init(void); //舵机初始化
void Steering_control(u16 angle); //角度转动控制
#endif
steering.c
#include<steering.h>
#include<delay.h>
void Steering_Init(void)
{
GPIO_InitTypeDef GPIO_InitTypeStructer;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStructer;
TIM_OCInitTypeDef TIM_OCInitTypeStructer;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_AF_PP; //舵机pwm驱动
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_2;
GPIO_InitTypeStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitTypeStructer);
TIM_TimeBaseInitTypeStructer.TIM_ClockDivision=0; //定时器5 20ms基础脉冲
TIM_TimeBaseInitTypeStructer.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitTypeStructer.TIM_Period=199;
TIM_TimeBaseInitTypeStructer.TIM_Prescaler=7199;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitTypeStructer);
TIM_OCInitTypeStructer.TIM_OCMode=TIM_OCMode_PWM1; //占空比 = t / T 相关参数如下:t = 0.5ms——————舵机会转动 0 °
//t = 1.0ms——————舵机会转动 45°
//t = 1.5ms——————舵机会转动 90°
//t = 2.0ms——————舵机会转动 135°
//t = 2.5ms——————舵机会转动180
TIM_OCInitTypeStructer.TIM_OCPolarity=TIM_OCPolarity_Low;
TIM_OCInitTypeStructer.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitTypeStructer.TIM_Pulse=0;
TIM_OC3Init(TIM5,&TIM_OCInitTypeStructer);
TIM_OC3PreloadConfig(TIM5,TIM_OCPreload_Enable); //重装载使能
TIM_Cmd(TIM5,ENABLE); //定时器使能
}
void Steering_control(u16 angle)
{
pwm=angle;
delay_ms(500);
pwm=7200; //稳定舵机
}
ultrasonic.h
#ifndef __ULTRASONIC__H
#define __ULTRASONIC__H
#define Trig PFout(3)
#include<sys.h>
void Ultrasonic_Init(void); //超声波避障初始化
u8 Lengthjudge(float len); //距离判断是否规避
void TIM2_IRQHandler(void); //中断函数
#endif
ultrasonic.c
#include<ultrasonic.h>
#include<motor.h>
#include "delay.h"
u16 counter=0;
u8 judge=0;
void Ultrasonic_Init(void)
{
GPIO_InitTypeDef GPIO_InitTypeStructer;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_Out_PP; //PF3:Trig输出 PF4:Echo输入
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_3;
GPIO_InitTypeStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOF,&GPIO_InitTypeStructer);
GPIO_ResetBits(GPIOF,GPIO_Pin_3);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING; //PF3:Trig输出 PF4:Echo输入
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_4;
GPIO_Init(GPIOF,&GPIO_InitTypeStructer);
GPIO_ResetBits(GPIOF,GPIO_Pin_4);
TIM_TimeBaseStructure.TIM_Period = 999; //设置自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =71; //设置时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除更新中断,免得一打开中断立即产生中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占式中断优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应式中断优先级设置为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,DISABLE); //定时器开启
}
u8 Lengthjudge(float len)
{
float length=0,sum=0;
u16 tim;
u16 i=0;
while(i!=5)
{
Trig=1;
delay_us(20);
Trig=0;
while(GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_4)==RESET);
TIM_Cmd(TIM2,ENABLE);//回响信号到来,开启定时器计数
i+=1; //每收到一次回响信号+1,收到5次就计算均值
while(GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_4)==SET);//回响信号消失
TIM_Cmd(TIM2,DISABLE);//关闭定时器
tim=TIM_GetCounter(TIM2);//获取计TIM2数寄存器中的计数值,一边计算回响信号时间
length=(tim+counter*1000)/58.0;//通过回响信号计算距离cm
sum=length+sum;
TIM2->CNT=0; //将TIM4计数寄存器的计数值清零
counter=0; //中断溢出次数清零
delay_ms(5);
}
length=sum/5;
if(length>len)
judge=1;
else
judge=0;
return judge;
}
void TIM2_IRQHandler(void) //TIM2 中断
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查 TIM2 更新中断发生与否
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update ); //清除 TIM2 更新中断标志
counter++;
}
}
-
循迹模块(TCRT5000)
建议买整体的循迹模块,插线方便,连接到小车上也好看.
注意:这个循迹模块测量的距离是5mm-10mm,距离很短但是很灵敏.在连接到小车上面的时候多准备几个六角铜柱和螺母介绍:很简单的原理就不麻烦大佬了
它有4个IO接口,我们只需要用3个即可,就是VCC,GND,D0其中D0就是用来返回信号的,它有两个状态,就是高电平和低电平。
正常情况下,D0返回低电平,当模块检测到黑线的时候,返回高电平
TCRC.h
#ifndef __TCRT_H
#define __TCRT_H
#include<sys.h>
#define TCRT1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_14) //传感器引脚配置
#define TCRT2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_15)
#define TCRT3 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_0)
#define TCRT4 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_1)
#define TCRT5 GPIO_ReadInputDataBit(GPIOF,GPIO_Pin_2)
void TCRT_Init(void); //循迹初始化
void TCRT_Begin(void); //小车开始循迹
#endif
TCRC.c
#include<TCRT.h>
#include<motor.h>
void TCRT_Init(void)
{
GPIO_InitTypeDef GPIO_InitTypeStructer;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE); //初始化所需要的串口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitTypeStructer.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitTypeStructer);
GPIO_InitTypeStructer.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitTypeStructer.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;
GPIO_Init(GPIOF,&GPIO_InitTypeStructer);
}
void TCRT_Begin(void) //循迹开始
{
if(TCRT1==0&&TCRT2==0&&TCRT3==0&&TCRT4==0&&TCRT5==0) //轮胎没有着地
{
Carstop();
}
if(TCRT1==1&&TCRT2==1&&TCRT3==1&&TCRT4==1&&TCRT5==1) //没有在黑线上行驶
{
Carstart();
}
if(TCRT1==1&&TCRT2==1&&TCRT3==0&&TCRT4==1&&TCRT5==1) //中间传感器在黑线上
{
Carstart();
}
if((TCRT1==0&&TCRT2==1&&TCRT3==1&&TCRT4==1&&TCRT5==1)|(TCRT1==1&&TCRT2==0&&TCRT3==1&&TCRT4==1&&TCRT5==1))
{
Carstop();
Carturn(LEFT,50); //速度尽量很小,也不能特别小导致小车无法启动 角度越小越精准
}
if((TCRT1==1&&TCRT2==1&&TCRT3==1&&TCRT4==0&&TCRT5==1)|(TCRT1==1&&TCRT2==1&&TCRT3==1&&TCRT4==1&&TCRT5==0))
{
Carstop();
Carturn(RIGHT,50);
}
if(TCRT1==0&&TCRT2==0&&TCRT3==1&&TCRT4==0&&TCRT5==0) //左转弯
{
Carstop();
Carturn(LEFT,120);
}
if(TCRT1==1&&TCRT2==1&&TCRT3==1&&TCRT4==0&&TCRT5==0) //右转弯
{
Carstop();
Carturn(RIGHT,120);
}
}
**后续的模块还会添加**
那先看一下主函数吧:实现的是循迹和避障
main.c
#include<motor.h>
#include<TCRT.h>
#include<ultrasonic.h>
#include<steering.h>
#include<usart.h>
#include "sys.h"
#include "delay.h"
int main(void)
{
//u16 angle[]={SG90_Right_90,SG90_Left_90}; //舵机角度数组
//u16 angle1[]={Car_Right_90,Car_Left_90}; //小车转动
//u8 direction[]={RIGHT,LEFT};
//u8 i=0;
delay_init();
TCRT_Init();
Steering_Init();
Motor_init();
Ultrasonic_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
PWMout(0,7199);
while(1)
{
TCRT_Begin();
}
/*while(1)
{
Steering_control(SG90_Front);
Carstart();
delay_ms(20); //每隔500ms进行一次扫描 小车停止减小测量误差
while(Lengthjudge(25.0)==0) //进行距离判断 如果达到避障条件进入循环
{
Carstop();
if(i==3)
break;
Steering_control(angle[i]); //根据angle数组的顺序摆动舵机
i++;
}
if(i!=0) //i==0 说明前方无障碍物体
{
if(i==3) //前方180°都为障碍物 掉头
{
Carturn(LEFT,540);
i=0;
}
else //有位置可以走进行转弯
{
if(i==1)
{Carturn(direction[0],angle1[i-1]); //i-1 while循环中多加了一次
i=0;
}
else
{
Carturn(direction[1],angle1[i-1]);
i=0;
}
}
}
}*/
}
3.材料的选择
- 杜邦线:杜邦线长一点比较好,还是要根据你小车的大小进行选择,我的小车30cm就足够了
- 胶枪:很便宜的东西,方便一些东西的固定.日后做别的项目也需要
- 六角铜柱:小车上有许多的口,大部分模块都有这个铜柱的地方,配合螺母和螺丝就可以达到很好的固定
- 螺丝刀
-
黑胶布 小车循迹的时候用到(看模块的间隔买)
别看这几个小小的材料,如果前期准备齐全可以减少很多时间的浪费,让你一心研究代码
总接
- 做一个项目的时候不能头脑一热想做就做,前期一定要做足准备工作和相关的资料也要查询
- 单片机的引脚口的分配看似轻松,但是随着模块的增加,你会发现有需要多引脚口有特定的作用,所以要进行提前的规划,做一个有大局观的人.(关键是看引脚手册 和对于单片机的手册)
- 闭门造车行不通,没有任何经验的你做出来惊世骇俗的东西几乎不可能,所以多多借鉴和学习大佬的知识和想法能让你快速进步.CSND就是一个不错的知识平台.感谢各位博客大佬
- 引用以为大佬的一句话作为结束吧:有了错误先确保代码没有问题,再去查找硬件的问题(真的深有体会)