动手做一个简单的智能小车
动手做一个简单的智能小车 来到CNDN一年了,看到了许多大佬的杰出作品.也该写点什么来回馈给大家了前不久接触了单片机,想提前进行实践一下所以有想法做一个实体出来,想来想去难的怕自己搞不定,但是还好找到了志同道合的王同学,一起搞一个智能小车.新手上路,多多指教
1.硬件的选取
走过许多坑才知道准备工作的重要性,前期对一个需要开发实体的了解的多少,决定你能走多远
- 四驱的小车(如果对底板有要求可以选择买铝制的)
马达,轮子,底板都是配套的某宝上可以买到
文章图片
- 智能的核心:单片机 开始时用正点原子的学习板做的,后来板子烧了
然后直接买了系统板
文章图片
普中的最小系统板,可扩展引脚很多.便宜好用
- 电源的选择
我直接买的5号电池盒,因为要达到12v的电压,需要8节5号电池,看起来很累赘,但是续航还是可以的
大家可以买12v的航模电池,既好看还可以充电就是贵点,但是接线麻烦一点
文章图片
你的小车要实现什么功能,这些传感器等模块的选取是很重要的
- 驱动模块 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_90200 //右转90度
#define Car_Right_45550
#define Car_Left_45550//左转45度
#define Car_Left_90220
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
#include
#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
文章图片
大佬资料
解决:1.优化算法,对于舵机转动的角度经行细化,但是增加了计算负担,和测量的时间
2.在小车的两侧加上红外避障模块(原理和循迹模块类似 后面可以看)
文章图片
3.双舵机,双超声波探测
steering.h
#ifndef __STEERING_H
#define __STEERING_H
#define pwm TIM5->CCR3
#defineSG90_Right_90195//右转90度
#defineSG90_Right_45190//右转45度
#defineSG90_Front185//舵机摆正
#defineSG90_Left_45180//左转45度
#defineSG90_Left_90175//左转90度
#include "sys.h"
void Steering_Init(void);
//舵机初始化
void Steering_control(u16 angle);
//角度转动控制
#endif
steering.c
#include
#include
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;
//定时器520ms基础脉冲
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
void Ultrasonic_Init(void);
//超声波避障初始化
u8 Lengthjudge(float len);
//距离判断是否规避
void TIM2_IRQHandler(void);
//中断函数
#endif
ultrasonic.c
#include
#include
#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;
//通过回响信号计算距离cmsum=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
#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
#include
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
#include
#include
#include
#include
#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-1while循环中多加了一次
i=0;
}
else
{
Carturn(direction[1],angle1[i-1]);
i=0;
}
}}
}*/
}
3.材料的选择
- 杜邦线:杜邦线长一点比较好,还是要根据你小车的大小进行选择,我的小车30cm就足够了
- 胶枪:很便宜的东西,方便一些东西的固定.日后做别的项目也需要
- 六角铜柱:小车上有许多的口,大部分模块都有这个铜柱的地方,配合螺母和螺丝就可以达到很好的固定
- 螺丝刀
- 黑胶布 小车循迹的时候用到(看模块的间隔买)
别看这几个小小的材料,如果前期准备齐全可以减少很多时间的浪费,让你一心研究代码
- 做一个项目的时候不能头脑一热想做就做,前期一定要做足准备工作和相关的资料也要查询
- 单片机的引脚口的分配看似轻松,但是随着模块的增加,你会发现有需要多引脚口有特定的作用,所以要进行提前的规划,做一个有大局观的人.(关键是看引脚手册 和对于单片机的手册)
- 闭门造车行不通,没有任何经验的你做出来惊世骇俗的东西几乎不可能,所以多多借鉴和学习大佬的知识和想法能让你快速进步.CSND就是一个不错的知识平台.感谢各位博客大佬
- 引用以为大佬的一句话作为结束吧:有了错误先确保代码没有问题,再去查找硬件的问题(真的深有体会)
推荐阅读
- 我要做大厨
- 一个人的旅行,三亚
- 一个小故事,我的思考。
- 一个人的碎碎念
- 2.6|2.6 Photoshop操作步骤的撤消和重做 [Ps教程]
- 七年之痒之后
- 我从来不做坏事
- 子龙老师语录
- 异地恋中,逐渐适应一个人到底意味着什么()
- 做一件事情的基本原理是什么()