STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!

STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第1张图片

简介

  • 小车的动力部分由4个带减速箱的电机,和两个L298N电机驱动模块组成。
  • 通过STM32核心板控制电机驱动模块,JDY-31蓝牙模块与手机通讯。
  • 总共三块电池,一块专门给单片机供电,另外两块串联在一起同时给电机驱动模块供电(为什么选择分别供电后面的电源硬件部分有说)。
  • 源码下载,提取码:nxo3

硬件

  1. 硬件的基本介绍
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第2张图片
  • 电机的位置很重要,因为电机驱动模块一个可以分别控制两个电机,而且程序里面也是分别控制单个电机的动作。
  • 如果电机的和自己想要的运动方向不一样的话,交换一下接线端的两个线就好了。程序里面小车前进默认是电机1、2CW(顺时针),电机3、4CCW(逆时针)。
  1. L298N电机驱动模块
    电机驱动模块这里不细讲,网上有很多资料,很简单的。
    下面这个是右边的驱动板,控制电机1、2。
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第3张图片
    下面是左边的驱动板,控制电机3、4。
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第4张图片
  • 两个驱动板是并联的,供电时应选择粗一点的导线给驱动板供电,最好就不要使用杜邦线,如果使用杜邦线有可能会电压不够。
  • 有的时候电机可能要用手转一下才能动,那是因为电压太小了,可以串联几个电池。我是用一个小米充电宝和4节干电池,电 压应该在10V左右。
  • 注意不能串联太多充电宝,因为充电宝上面有过电流保护,电流太大充电宝可能会断电,甚至有可能会直接弄坏充电宝。
  • 如果是双电源的话,单片机要和两个驱动板共地。
  • 电机驱动模块上面的电机使能端的跳帽要拔掉,因为程序是用PWM来控制使能引脚的通点时长来让小车转弯的。
  1. 蓝牙模块JDY-31
    我使用的是透传蓝牙模块,简单来说就是手机连接后给蓝牙发送什么数据,然后蓝牙模块再原封不动的通过串口发给单片机,所以只要懂串口的基本通信就好了。
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第5张图片
  • 0V和3.3V是从单片机上取电的,驱动板取电的话有可能会有干扰。
  • PA2、PA3是单片机的串口2引脚。本来我想用串口1通讯的,但是好像串口1只能接受到单片机发出来的数据,不能用手机发送进去给单片机(有可能是因为串口1的时钟频率太高),而且串口1正好留出来下载程序,所以就选择了串口2。
  • 蓝牙模块的默认波特率是9600,为了方便我也将串口的波特率也初始化成了9600。
  • 蓝牙模块的STATE引脚是蓝牙的连接状态引脚,连接成功会变成高电平。该引脚我用来给小车做紧急刹车,当蓝牙没有连接或是断开小车会自动刹车,以免蓝牙突然断开时小车失控的运动。
  1. 电源
    小车采用双电源供电
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第6张图片
  • 这里我选择用两个电源分别给单片机和驱动板供电的原因
    电机启动会有很大的启动电流会在一瞬间将电流全部拉走,使得单片机断电,如果蓝牙模块也是从单片机上取点也会断电。
    直接电机的电刷会产生火花对通讯有干扰,有些质量好的电机可能没有(不太确定是不是这个原因,但同电源时通讯确实有干扰,蓝牙会接收一些奇怪的数据)。
  • 驱动板的两块电源串联连接,然后连接到驱动板的电源接线端。

程序

程序只要按照上面的连接都是可以直接使用,如果硬件有改动我也有定义宏可以直接做更改。
代码里面的注释比较详细,这里只是将重要的代码放上来,稍作讲解,最好是直接下载来看。
下载地址,提取码:z5us

  • 目录结构
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第7张图片
  1. main.c
#include "stm32f10x.h"                  // Device header
#include "MTR_GPIO.h"
#include "USART.h"
#include "LED.h"
#include "blueTooth.h"
#include "PWM_GeneralTim.h"

int main(void){
	USART_Config();//串口
	MTR_GPIOInit();//电机引脚
	GENERAL_TIM_Init();//定时器PWM初始化,用于小车转向
	blueToothInit();//蓝牙初始化
	LEDInit();
	printf("-----指令-----\n\
	0x01:小车后退\n\
	0x02:小车向左\n\
	0x03:小车向右\n\
	0x04:小车前进\n\
	0x05:小车刹车\n\
	0x06:电机停止\n\
	0x07:小车逆时针\n\
	0x08:小车转向停止\n\
	0x09:小车顺时针\n");
	while(1){
		//如果蓝牙断开,小车会一直在刹车状态
		if(BLUE_TOOTH_STATE != Bit_SET){
			MTR_CarBrakeAll();
		}
	}
}
  • 主要是各功能的初始化。
  • 蓝牙状态的判断。
  1. stm32f10x_it.c
void DEBUG_USART_IRQHandler(void){
	uint8_t CMD = 0;//接收的命令
	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET){
		LED1_ON;
		CMD = USART_ReceiveData(DEBUG_USARTx);//读取一个
		switch(CMD){
			case 0x06:
				//电机停止
				MTR_CarBrakeAll();
				printf("0x06");
				break;
			case 0x08:
				//小车转向停止
				PWM_CarMaxGo();
				printf("0x08");
				break;
			case 0x05:
				//小车刹车
				MTR_CarBrakeAll();
				printf("0x05");
				break;
			case 0x02:
				//小车向左
				PWM_CarLeft();
				printf("0x02");
				break;
			case 0x03:
				//小车向右
				PWM_CarRight();
				printf("0x03");
				break;
			case 0x04:
				//小车前进
				MTR_CarGo();
				printf("0x04");
				break;
			case 0x01:
				//小车后退
				MTR_CarBackGo();
				printf("0x01");
				break;
			case 0x07:
				//小车逆时针
				MTR_CarCCW();
				printf("0x07");
				break;
			case 0x09:
				//小车顺时针
				MTR_CarCW();
				printf("0x09");
				break;
		}
		LED1_OFF;//让LED闪烁来表示数据的接收
	}
	USART_ClearFlag(DEBUG_USARTx,USART_FLAG_RXNE);
} 
  • 串口通过接收十六进制的指令来控制驱动板。
  • 接收到特定的指令后,单片机会将小车执行的指令再发送回给蓝牙。
  1. MTR_GPIO.c
#include "MTR_GPIO.h"

//刹车
void MTR_CarBrakeAll(void){
	MTR1_BRAKE;
	MTR2_BRAKE;
	MTR3_BRAKE;
	MTR4_BRAKE;
}

//前进
void MTR_CarGo(void){
	MTR1_CW;
	MTR2_CW;
	MTR3_CCW;
	MTR4_CCW;
}

//后退
void MTR_CarBackGo(void){
	MTR1_CCW;
	MTR2_CCW;
	MTR3_CW;
	MTR4_CW;
}

//顺时针
void MTR_CarCW(void){
	MTR1_CCW;
	MTR2_CCW;
	MTR3_CCW;
	MTR4_CCW;
}

//逆时针
void MTR_CarCCW(void){
	MTR1_CW;
	MTR2_CW;
	MTR3_CW;
	MTR4_CW;
}

void MTR_GPIOInit(void){
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(MTR1_GPIO_CLK|MTR2_GPIO_CLK|MTR3_GPIO_CLK|MTR4_GPIO_CLK,ENABLE);//时钟
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	//电机1
	GPIO_InitStructure.GPIO_Pin = MTR1_GPIO_PIN;
	GPIO_Init(MTR1_GPIO_PORT, &GPIO_InitStructure);
	//电机2
	GPIO_InitStructure.GPIO_Pin = MTR2_GPIO_PIN;
	GPIO_Init(MTR2_GPIO_PORT, &GPIO_InitStructure);
	//电机3
	GPIO_InitStructure.GPIO_Pin = MTR3_GPIO_PIN;
	GPIO_Init(MTR3_GPIO_PORT, &GPIO_InitStructure);
	//电机4
	GPIO_InitStructure.GPIO_Pin = MTR4_GPIO_PIN;
	GPIO_Init(MTR4_GPIO_PORT, &GPIO_InitStructure);
	//小车刹车
	MTR_CarBrakeAll();
}

注意:因为单片机是控制驱动板从而间接的控制电机,为了简单我直接把控制驱动板的引脚看成是控制电机的。

  • 电机的引脚初始化。
  • 小车前进、后退、原地旋转和刹车的函数封装。
  1. MTR_GPIO.h
#ifndef __MTR_GPIO_H
#define __MTR_GPIO_H

#include "stm32f10x.h"

/*
	小车四轮驱动
	电机1:右上
	电机2:右下
	电机3:左上
	电机4:左下
*/
#define MTR1_GPIO_PORT		GPIOB
#define MTR1_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define MTR1_GPIO_PIN		GPIO_Pin_6|GPIO_Pin_7
#define MTR1_CW				{GPIO_ResetBits(MTR1_GPIO_PORT,GPIO_Pin_6);GPIO_SetBits(MTR1_GPIO_PORT,GPIO_Pin_7);}//顺时针
#define MTR1_CCW			{GPIO_SetBits(MTR1_GPIO_PORT,GPIO_Pin_6);GPIO_ResetBits(MTR1_GPIO_PORT,GPIO_Pin_7);}//逆时针
#define MTR1_BRAKE			GPIO_ResetBits(MTR1_GPIO_PORT,MTR1_GPIO_PIN);

#define MTR2_GPIO_PORT    	GPIOB
#define MTR2_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define MTR2_GPIO_PIN		GPIO_Pin_8|GPIO_Pin_9
#define MTR2_CW				{GPIO_ResetBits(MTR2_GPIO_PORT,GPIO_Pin_8);GPIO_SetBits(MTR2_GPIO_PORT,GPIO_Pin_9);}//顺时针
#define MTR2_CCW			{GPIO_SetBits(MTR2_GPIO_PORT,GPIO_Pin_8);GPIO_ResetBits(MTR2_GPIO_PORT,GPIO_Pin_9);}//逆时针
#define MTR2_BRAKE			GPIO_ResetBits(MTR2_GPIO_PORT,MTR2_GPIO_PIN);

#define MTR3_GPIO_PORT    	GPIOB
#define MTR3_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define MTR3_GPIO_PIN		GPIO_Pin_12|GPIO_Pin_13
#define MTR3_CW				{GPIO_ResetBits(MTR3_GPIO_PORT,GPIO_Pin_12);GPIO_SetBits(MTR3_GPIO_PORT,GPIO_Pin_13);}//顺时针
#define MTR3_CCW			{GPIO_SetBits(MTR3_GPIO_PORT,GPIO_Pin_12);GPIO_ResetBits(MTR3_GPIO_PORT,GPIO_Pin_13);}//逆时针
#define MTR3_BRAKE			GPIO_ResetBits(MTR3_GPIO_PORT,MTR3_GPIO_PIN);

#define MTR4_GPIO_PORT    	GPIOB
#define MTR4_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define MTR4_GPIO_PIN		GPIO_Pin_14|GPIO_Pin_15
#define MTR4_CW				{GPIO_ResetBits(MTR4_GPIO_PORT,GPIO_Pin_14);GPIO_SetBits(MTR4_GPIO_PORT,GPIO_Pin_15);}//顺时针
#define MTR4_CCW			{GPIO_SetBits(MTR4_GPIO_PORT,GPIO_Pin_14);GPIO_ResetBits(MTR4_GPIO_PORT,GPIO_Pin_15);}//逆时针
#define MTR4_BRAKE			GPIO_ResetBits(MTR4_GPIO_PORT,MTR4_GPIO_PIN);

void MTR_CarBrakeAll(void);		//小车刹车
void MTR_CarGo(void);			//小车前进
void MTR_CarBackGo(void);		//小车后退
void MTR_CarCW(void);			//小车顺时针转
void MTR_CarCCW(void);			//小车逆时针转
void MTR_GPIOInit(void);

#endif
  • 电机引脚的头文件
  1. PWM_GeneralTim.c
#include "PWM_GeneralTim.h" 

static void GENERAL_TIM_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // 输出比较通道1 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH1_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
	
	// 输出比较通道2 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH2_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
	
	// 输出比较通道3 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH3_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
	
	// 输出比较通道4 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH4_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStructure);
}


///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler            都有
// *	TIM_CounterMode			     TIMx,x[6,7]没有,其他都有
// *  TIM_Period               都有
// *  TIM_ClockDivision        TIMx,x[6,7]没有,其他都有
// *  TIM_RepetitionCounter    TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef; 
// *-----------------------------------------------------------------------------
// */

/* ----------------   PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// 占空比P=CCR/(ARR+1)

static void GENERAL_TIM_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
  // 开启定时器时钟,即内部时钟CK_INT=72M
	GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
	// 配置周期
	// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
	TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;	
	// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;	
	// 时钟分频因子 ,配置死区时间时需要用到
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

	/*--------------------输出比较结构体初始化-------------------*/	
	// 配置为PWM模式
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
	// 输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	// 输出通道电平极性配置	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 2
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 3
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 4
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 使能计数器
	TIM_Cmd(GENERAL_TIM, ENABLE);
}

void GENERAL_TIM_Init(void)
{
	GENERAL_TIM_GPIO_Config();
	GENERAL_TIM_Mode_Config();		
}

//小车左转
void PWM_CarLeft(void){
	TIM_SetCompare1(GENERAL_TIM,0);
	TIM_SetCompare2(GENERAL_TIM,0);
	TIM_SetCompare3(GENERAL_TIM,4000);
	TIM_SetCompare4(GENERAL_TIM,4000);
}

//小车右转
void PWM_CarRight(void){
	TIM_SetCompare1(GENERAL_TIM,4000);
	TIM_SetCompare2(GENERAL_TIM,4000);
	TIM_SetCompare3(GENERAL_TIM,0);
	TIM_SetCompare4(GENERAL_TIM,0);
}

//小车最大速度
void PWM_CarMaxGo(void){
	TIM_SetCompare1(GENERAL_TIM,0);
	TIM_SetCompare2(GENERAL_TIM,0);
	TIM_SetCompare3(GENERAL_TIM,0);
	TIM_SetCompare4(GENERAL_TIM,0);
}

/*********************************************END OF FILE**********************/
  • 小车转向的原理是跟坦克的一样,通过降低转向边的轮子速度来实现。而控制转速就要用到PWM波来减少或增多电机的通断时间(其实是驱动板的电机使能引脚)。
  • 转向函数的封装

PS:完整的程序太多了,最好下载下来慢慢看

蓝牙遥控器

做到这里就剩只蓝牙遥控器的配置了,软件呢随便哪个都行,只要能收发数据就行。
不过我推荐用我这个,因为长得好看。
下载地址,提取码:kvar。此软件为安卓,ios没有

  1. 查看指令
    单片机复位的时候会通过蓝牙发送这些数据,用手机连上蓝牙然后复位单片机就可以看到了。

printf("-----指令-----\n
0x01:小车后退\n
0x02:小车向左\n
0x03:小车向右\n
0x04:小车前进\n
0x05:小车刹车\n
0x06:电机停止\n
0x07:小车逆时针\n
0x08:小车转向停止\n
0x09:小车顺时针\n");

  • 单片机就是通过这些十六进制的数据来控制小车的运动,只要将遥控器上的按键对应这些数据就行了。
  1. 按键说明
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第8张图片
  2. 按键配置
    按键配置简单,只要根据指令来配置就好了,我就举几个例子。
  • 前进
    STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第9张图片
  • 左转STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第10张图片
  • 刹车STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第11张图片
  • 原地顺时针STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!_第12张图片 接下来可以开车上路了
    祝你好运!!!

你可能感兴趣的:(单片机,STM32,智能小车)