简介
暑假无聊,手头又有一个闲置的单片机一直放着,就想着做个遥控小车出来,复习一下单片机嵌入式编程。该遥控小车项目参考CSDN博主你就叫我李大帅的文章:STM32智能遥控小车,超详细-附下载直接可以用,双电源跑贼快!。自己在原文的基础上添加了电脑端的控制,然后做了一个安卓定制软件来控制小车。 注:
- 本文所有代码均开源,供学习使用。源码在此:百度云盘链接(提取码:uh66)
- 本文是遥控小车的下位机部分,关于PC上位机部分的实现可以参考这里:点击此处
一、硬件总体介绍
最终实物图:
硬件方面与李大帅博主的硬件组成差不多:使用两个L298N电机驱动模块驱动四个电机,STM32开发板用来控制这两个电机驱动模块,并通过JDY-31蓝牙透传模块与手机或电脑通信。整体采用两个独立电源分别为单片机和L298N供电。
1. L298N电机驱动模块
该模块用于驱动电机,一个L298N可以驱动两个电机,有关L298N模块的讲解可以看这个视频:l298n电机驱动模块 电机正反转 电机调速。下图为L298N与单片机的连接示意图。
左侧L298N: 右侧L298N 说明:
- 电机驱动模块L298N在遥控小车两侧分别放置,左边的L298N控制左边的两个电机,右边的L298N控制右边的两个电机。
- 单片机的PD12-PD15以PWM的形式输出控制电机的转速,PE7-PE14以两根为一组分别控制四个电机的转向。
2. JDY-31蓝牙模块
JDY-31这个蓝牙模块属于透明传输模块,意思就是在使用时可以不用关心蓝牙协议的细节,连接好以后可以直接将其当做串口使用。该模块的RXD与TXD连接至单片机的UART接口,连接示意图如下图所示: 说明:
- 连接蓝牙模块时,需要将JDY-31上的RX接口与单片机的TX接口相连接(我这款单片机的RX使用P9复用),同时将JDY-31上的TX接口与单片机的RX接口相连接(我这里的RX使用P10复用)。
- JDY-31上的STATE接口连接到单片机用于检测蓝牙的连接状态,当JDY-31与主机有蓝牙连接时,STATE会置为高电平,否则为低电平。
3. 电源组成
我使用了两块独立电源,4个干电池构成6.5V电压给单片机供电,我所购买的单片机上有DCDC降压芯片,能将6.5V降为3.3V供单片机使用。第二块独立电源由一个12V锂电池提供,为L298N供电。
6.5V电源: 12V电源:
4. 单片机
我采用的单片机型号为STM32F407VET6,这个并不是很重要的,使用其他单片机型号找到对应的固件库也能完成这样的功能。
二、单片机程序介绍
下面部分为程序中主要程序的介绍,详细代码见百度网盘:[下载地址]。
程序结构目录如下:
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();
}
}
}
代码说明:
- 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);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
说明:
- 该模块用于检测蓝牙的连接状态,故Bluetooth_Init函数只需初始化PE0即可。
- 同时在bluetooth.h头文件中定义了一个变量BLUTOOTH_STATE,用来查询蓝牙是否连接,如下所示:
#define BLUTOOTH_STATE GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_0)
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;
GPIO_InitStructure.GPIO_Pin = MTR1_GPIO_PIN;
GPIO_Init(MTR1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = MTR2_GPIO_PIN;
GPIO_Init(MTR2_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = MTR3_GPIO_PIN;
GPIO_Init(MTR3_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = MTR4_GPIO_PIN;
GPIO_Init(MTR4_GPIO_PORT, &GPIO_InitStructure);
MTR_CarBrakeAll();
}
说明:
- 该模块主要通过控制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;
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_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);
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;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
TIM_OCInitStructure.TIM_Pulse = SPEED_LEVEL;
TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
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();
}
说明:
- 该模块主要对PD12-PD15实现PWM输出,从而达到控制小车速度的功能。同时,该模块中包括小车的加减速以及小车的转向功能。
- 在加速函数(减速函数)中,先用switch语句判断小车当前的速度状态,之后将SPEED_LEVEL更改为对应的更大(更小)的速度档位,然后使用RESET_SPEED_LEVEL()函数将更改后的速度档位值赋值到对应的定时器比较值中。
5. uart.c文件
代码部分:
void USART1_IRQHandler(void)
{
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;
}
}
}
说明:
- 在该文件中主要包含uart串口模块的初始化部分以及串口的中断处理程序编写部分。
- 在串口初始化部分中,为了方便,我使用了商家提供的ucos初始化代码,具体可见源代码,本人没能将其搞清楚。
- 在串口中断处理程序中,由于我设定的主机指令以一个字节为单位,故串口检测到一个字节的接收时就立即判断当前指令对应的动作,指令与小车动作的映射见上位机编写部分。
三、总结
- 本文代码可以移植到其他单片机上,源代码在百度云盘中自行领取:点击此处 (提取码:uh66)
- 本文是遥控小车的下位机部分,关于PC上位机部分的实现可以参考这里:点击此处
- 文中解释如有问题可评论留言,我会不定时查看
|