来到CNDN一年了,看到了许多大佬的杰出作品.也该写点什么来回馈给大家了前不久接触了单片机,想提前进行实践一下所以有想法做一个实体出来,想来想去难的怕自己搞不定,但是还好找到了志同道合的王同学,一起搞一个智能小车.新手上路,多多指教
走过许多坑才知道准备工作的重要性,前期对一个需要开发实体的了解的多少,决定你能走多远
尽管说查看引脚手册和基础的芯片手册是基础,但是对于一般新手来说,更多的IO口才是最舒服的
你的小车要实现什么功能,这些传感器等模块的选取是很重要的
详细的资料可以看大佬
注意:调适马达的时候不要一股脑全都安装上去,先进行单个马达转动方向,接线的长度的调适,否则你会面临无限的拆卸
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
#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;
}
}
注意:每一次超声波测距的间隔意味这小车的灵敏度,以为这个模块只有大约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
#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; //定时器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
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;//通过回响信号计算距离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
#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-1 while循环中多加了一次
i=0;
}
else
{
Carturn(direction[1],angle1[i-1]);
i=0;
}
}
}
}*/
}