STM32遥控小车下位机及硬件连接部分(Keil MDK5平台的C++编程)

简介

暑假无聊,手头又有一个闲置的单片机一直放着,就想着做个遥控小车出来,复习一下单片机嵌入式编程。该遥控小车项目参考CSDN博主你就叫我李大帅的文章:STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!。自己在原文的基础上添加了电脑端的控制,然后做了一个安卓定制软件来控制小车。
注:

  1. 本文所有代码均开源,供学习使用。源码在此:百度云盘链接(提取码:uh66)
  2. 本文是遥控小车的下位机部分,关于PC上位机部分的实现可以参考这里:点击此处

一、硬件总体介绍

最终实物图:
整体图
请添加图片描述

硬件方面与李大帅博主的硬件组成差不多:使用两个L298N电机驱动模块驱动四个电机,STM32开发板用来控制这两个电机驱动模块,并通过JDY-31蓝牙透传模块与手机或电脑通信。整体采用两个独立电源分别为单片机和L298N供电。

1. L298N电机驱动模块

该模块用于驱动电机,一个L298N可以驱动两个电机,有关L298N模块的讲解可以看这个视频:l298n电机驱动模块 电机正反转 电机调速。下图为L298N与单片机的连接示意图。

左侧L298N:
左侧L298N
右侧L298N
右侧L298N
说明:

  1. 电机驱动模块L298N在遥控小车两侧分别放置,左边的L298N控制左边的两个电机,右边的L298N控制右边的两个电机。
  2. 单片机的PD12-PD15以PWM的形式输出控制电机的转速,PE7-PE14以两根为一组分别控制四个电机的转向。

2. JDY-31蓝牙模块

JDY-31这个蓝牙模块属于透明传输模块,意思就是在使用时可以不用关心蓝牙协议的细节,连接好以后可以直接将其当做串口使用。该模块的RXD与TXD连接至单片机的UART接口,连接示意图如下图所示:
JDY-31蓝牙模块
说明:

  1. 连接蓝牙模块时,需要将JDY-31上的RX接口与单片机的TX接口相连接(我这款单片机的RX使用P9复用),同时将JDY-31上的TX接口与单片机的RX接口相连接(我这里的RX使用P10复用)。
  2. JDY-31上的STATE接口连接到单片机用于检测蓝牙的连接状态,当JDY-31与主机有蓝牙连接时,STATE会置为高电平,否则为低电平。

3. 电源组成

我使用了两块独立电源,4个干电池构成6.5V电压给单片机供电,我所购买的单片机上有DCDC降压芯片,能将6.5V降为3.3V供单片机使用。第二块独立电源由一个12V锂电池提供,为L298N供电。

6.5V电源:
请添加图片描述
12V电源:
请添加图片描述

4. 单片机

我采用的单片机型号为STM32F407VET6,这个并不是很重要的,使用其他单片机型号找到对应的固件库也能完成这样的功能。
stm32

二、单片机程序介绍

下面部分为程序中主要程序的介绍,详细代码见百度网盘:[下载地址]。

程序结构目录如下:
请添加图片描述

1. main.c文件

代码片段:

int main(void)
{ 
	//设置外部中断优先组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//各部分的初始化
	uart_init(9600);
	delay_init(168);
	LED_Init();			 
	Bluetooth_Init();  
	SPEEDER_Init();
	MTR_GPIOInit();
	LED0 = 1;
	//主程序
	while(1)
	{		
		if(BLUTOOTH_STATE)
		{
				LED0 = 0;
				delay_ms(100);  	
				LED0 = 1;
				delay_ms(100);     
		}
		else 
		{
			LED0 = 0;
			MTR_CarBrakeAll();
		}
	}
}

代码说明:

  1. main函数主要执行各个硬件部分的初始化,然后在主程序中判断JDY-31蓝牙与主机的连接的状态,如果与主机正常连接,则LED0以闪烁的形式不停跳动;如果未连接主机,则LED0保持常亮。

2. bluetooth.c文件

代码片段:

#include "bluetooth.h"
#include "delay.h"  
void Bluetooth_Init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOE时钟
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //STATE连接的引脚PE0
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//down pull
  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE
} 

说明:

  1. 该模块用于检测蓝牙的连接状态,故Bluetooth_Init函数只需初始化PE0即可。
  2. 同时在bluetooth.h头文件中定义了一个变量BLUTOOTH_STATE,用来查询蓝牙是否连接,如下所示:
    #define BLUTOOTH_STATE 	GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_0)	//PE0
    

3. motor.c文件

代码片段:

#include "motor.h"

//刹车
void MTR_CarBrakeAll(void){
	MTR1_BRAKE;
	MTR2_BRAKE;
	MTR3_BRAKE;
	MTR4_BRAKE;
}
//前进
void MTR_CarGo(void){
	MTR1_ROTA_F;
	MTR2_ROTA_F;
	MTR3_ROTA_F;
	MTR4_ROTA_F;
}
//后退
void MTR_CarBack(void){
	MTR1_ROTA_B;
	MTR2_ROTA_B;
	MTR3_ROTA_B;
	MTR4_ROTA_B;
}
//顺时针转
void MTR_CarCW(void){
	MTR1_ROTA_F;
	MTR2_ROTA_F;
	MTR3_ROTA_B;
	MTR4_ROTA_B;
}
//逆时针转
void MTR_CarCCW(void){
	MTR1_ROTA_B;
	MTR2_ROTA_B;
	MTR3_ROTA_F;
	MTR4_ROTA_F;
}
//电机驱动控制初始化
void MTR_GPIOInit(void){
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(MTR1_GPIO_CLK|MTR2_GPIO_CLK|MTR3_GPIO_CLK|MTR4_GPIO_CLK,ENABLE);//时钟
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//推挽输出
	//电机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. 该模块主要通过控制PE7-PE14从而实现刹车前进后退以及自转的功能,例如MTR1_ROTA_F是对第一个电机的两个引脚分别赋值为1和0,而MTR1_BRAKE则是将这两个引脚都赋值为0。

4. speeder.c文件

代码片段:

#include "sys.h"
#include "speeder.h"

u16 SPEED_LEVEL = SPEED_LEVEL1;
u8 CAR_STATE = STRAIGHT_STATE;
//GPIO初始化
static void SPEEDER_GPIO_Init(void){
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(SPEEDER_GPIO_CLK,ENABLE);//时钟
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//推挽输出
	//初始化GPIO 将复用的引脚与对应的定时器绑定在一起。
	GPIO_InitStructure.GPIO_Pin = SPEEDER_GPIO_PIN;
	GPIO_Init(SPEEDER_GPIO_PORT, &GPIO_InitStructure);
	GPIO_PinAFConfig(SPEEDER_GPIO_PORT,GPIO_PinSource12,GPIO_AF_TIM4);
	GPIO_PinAFConfig(SPEEDER_GPIO_PORT,GPIO_PinSource13,GPIO_AF_TIM4);
	GPIO_PinAFConfig(SPEEDER_GPIO_PORT,GPIO_PinSource14,GPIO_AF_TIM4);
	GPIO_PinAFConfig(SPEEDER_GPIO_PORT,GPIO_PinSource15,GPIO_AF_TIM4);
}
//定时器初始化
static void SPEEDER_TIM_Init(void){
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK,ENABLE);
	/*--------------------TIME BASE 结构体初始化-------------------------*/
	TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;	//自动重装载的值
	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);
	/*--------------------输出比较结构体初始化-------------------*/	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;	// 配置为PWM2模式   计数器值大于occr时输出有效信号
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	// 输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;	// 输出通道电平极性配置	   有效信号为高电平
	TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;									//比较的值:0
	TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);	// 输出比较通道 1
	TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

	TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
	TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);	// 输出比较通道 2
	TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

	TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
	TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);	// 输出比较通道 3
	TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);

	TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
	TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);	// 输出比较通道 4
	TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	TIM_ARRPreloadConfig(GENERAL_TIM,ENABLE);
	TIM_Cmd(GENERAL_TIM, ENABLE);	// 使能定时器
}
//整体的使能
void SPEEDER_Init(void){
	SPEEDER_GPIO_Init();
	SPEEDER_TIM_Init();
	CAR_STATE = STRAIGHT_STATE;
}
//向右转
void SET_RIGHT_TURN(void){
	CAR_STATE = RIGHT_STATE;
	TIM_SetCompare1(GENERAL_TIM,SPEED_LEVEL);
	TIM_SetCompare2(GENERAL_TIM,SPEED_LEVEL);
	TIM_SetCompare3(GENERAL_TIM,0);
	TIM_SetCompare4(GENERAL_TIM,0);
}
//向左转
void SET_LEFT_TURN(void){
	CAR_STATE = LEFT_STATE;
	TIM_SetCompare1(GENERAL_TIM,0);
	TIM_SetCompare2(GENERAL_TIM,0);
	TIM_SetCompare3(GENERAL_TIM,SPEED_LEVEL);
	TIM_SetCompare4(GENERAL_TIM,SPEED_LEVEL);
}
//变直道
void RESET_DIRECTION(void){
	CAR_STATE = STRAIGHT_STATE;
	TIM_SetCompare1(GENERAL_TIM,SPEED_LEVEL);
	TIM_SetCompare2(GENERAL_TIM,SPEED_LEVEL);
	TIM_SetCompare3(GENERAL_TIM,SPEED_LEVEL);
	TIM_SetCompare4(GENERAL_TIM,SPEED_LEVEL);
}
//根据当前的小车状态将当前的速度等级赋值到对应的定时器中。
static void RESET_SPEED_LEVEL(void){
	switch(CAR_STATE){
		case STRAIGHT_STATE:
			RESET_DIRECTION();
			break;
		case LEFT_STATE:
			SET_LEFT_TURN();
			break;
		case RIGHT_STATE:
			SET_RIGHT_TURN();
			break;
	}
}
//变速:加速
void SPEED_UP(void){
	switch(SPEED_LEVEL){
		case SPEED_LEVEL0:
			SPEED_LEVEL = SPEED_LEVEL1;
			break;
		case SPEED_LEVEL1:
			SPEED_LEVEL = SPEED_LEVEL2;
			break;
		case SPEED_LEVEL2:
			SPEED_LEVEL = SPEED_LEVEL3;
			break;
		case SPEED_LEVEL3:
			SPEED_LEVEL = SPEED_LEVEL3;
			break;
	}
	RESET_SPEED_LEVEL();
}
//变速:减速
void SPEED_DOWN(void){
	switch(SPEED_LEVEL){
		case SPEED_LEVEL0:
			SPEED_LEVEL = SPEED_LEVEL0;
			break;
		case SPEED_LEVEL1:
			SPEED_LEVEL = SPEED_LEVEL0;
			break;
		case SPEED_LEVEL2:
			SPEED_LEVEL = SPEED_LEVEL1;
			break;
		case SPEED_LEVEL3:
			SPEED_LEVEL = SPEED_LEVEL2;
			break;
	}
	RESET_SPEED_LEVEL();
}

说明:

  1. 该模块主要对PD12-PD15实现PWM输出,从而达到控制小车速度的功能。同时,该模块中包括小车的加减速以及小车的转向功能。
  2. 在加速函数(减速函数)中,先用switch语句判断小车当前的速度状态,之后将SPEED_LEVEL更改为对应的更大(更小)的速度档位,然后使用RESET_SPEED_LEVEL()函数将更改后的速度档位值赋值到对应的定时器比较值中。

5. uart.c文件

代码部分:

void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	uint8_t CMD = 0;//接收的命令
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
		USART_ClearFlag(USART1,USART_FLAG_RXNE);
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
		CMD = USART_ReceiveData(USART1);//读取一个字节
		switch(CMD){
			case 0x00:
				RESET_DIRECTION();
				MTR_CarGo();
				printf("forward\r\n");
				break;
			case 0x01:
				RESET_DIRECTION();
				MTR_CarBack();
				printf("back\r\n");
				break;
			case 0x02:
				SET_RIGHT_TURN();
				printf("right turn\r\n");
				break;
			case 0x03:
				SET_LEFT_TURN();
				printf("left turn\r\n");
				break;
			case 0x04:
				MTR_CarCW();
				printf("right CW\r\n");
				break;
			case 0x05:
				MTR_CarCCW();
				printf("left CW\r\n");
				break;
			case 0x06://==================加速
				SPEED_UP();
				printf("speed up\r\n");
				break;
			case 0x07://===================减速
				SPEED_DOWN();
				printf("speed down\r\n");
				break;
			case 0x0a:
				RESET_DIRECTION();
				printf("reset straight\r\n");
				break;
			case 0xff:
				MTR_CarBrakeAll();
				printf("stop\r\n");
				break;
			case 0x1f:	//================ 测试连接
				printf("connect successfully");
				break;
		}
	}
} 

说明:

  1. 在该文件中主要包含uart串口模块的初始化部分以及串口的中断处理程序编写部分。
  2. 在串口初始化部分中,为了方便,我使用了商家提供的ucos初始化代码,具体可见源代码,本人没能将其搞清楚。
  3. 在串口中断处理程序中,由于我设定的主机指令以一个字节为单位,故串口检测到一个字节的接收时就立即判断当前指令对应的动作,指令与小车动作的映射见上位机编写部分。

三、总结

  1. 本文代码可以移植到其他单片机上,源代码在百度云盘中自行领取:点击此处 (提取码:uh66)
  2. 本文是遥控小车的下位机部分,关于PC上位机部分的实现可以参考这里:点击此处
  3. 文中解释如有问题可评论留言,我会不定时查看
  • 12
    点赞
  • 114
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值