前言:
这是一个基于STM32的蓝牙小车制作过程,所用到的器件、代码都是超级基础的,只要跟着做,哪怕0基础也能做出一个蓝牙操控的小车。
本项目所用的器件非常简单,两个L298N,四个直流电机,四个轮子和小车板,还有一个HC-05的蓝牙模块
代码放在gitee仓库中:代码在这里
项目原理:
大体上来说,蓝牙控制本质上来说就是串口通信,蓝牙与单片机通过串口连接,使用手机或其他电子产品通过蓝牙为蓝牙模块发送一个16进制的数据包,蓝牙模块再将这个数据包传到单片机上以操控小车。
串口的初始化:
首先根据单片机的参考文档确定串口的引脚,有的引脚它是不能作为串口收发的引脚的,所以得选一个能够支持串口收发的引脚。
这里使用A9和A10作为串口引脚
然后初始化这两个引脚作为串口通信的引脚:
uint8_t Serial_RxData; //定义串口接收的数据变量
uint8_t Serial_RxFlag; //定义串口接收的标志位变量
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Serial_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*中断输出配置*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
引脚的初始化如上,另外还需要写一下串口收发数据包的函数,这里直接照抄江科大的就行,不需要进行修改。
蓝牙发送:
这个部分需要自己去应用商店里随便下载一个蓝牙助手,里面都会有发送特定数据包的功能
接着只需要在单片机的主函数中一直检测接收的数据包并进行相应程序即可。
这里我发送的数据包有:0x41,0x42,0x43,0x44,0x45。都是十六进制,检测到对应的数据包就执行对应的操作。
我的主函数:
int main(void)
{
//timerinit();
Key_Init();
LED_Init();
PWM_Init();
wheel_init();
Serial_Init();
while(1)
{
if(Serial_GetRxFlag()==1)
{
RxDate=Serial_GetRxData();
}
if(RxDate==0x41)
{LED1_ON();
qj();
}
if(RxDate==0x42)
{
LED1_OFF();
left();
}
if(RxDate==0x43)
{LED1_ON();
tz();
}
if(RxDate==0x44)
{LED1_OFF();
right();
}
if(RxDate==0x45)
{LED1_ON();
ht();
}
}
}
电机驱动:
这里我用的驱动是L298N,关于L298N的操控大家可以看这篇文章L298N电机驱动
写的已经很完整了,基本原理就是通过控制PWM波来控制电机转速,这里不再赘述。
这里给出我的电机驱动源代码:
void wheel_init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4| GPIO_Pin_5 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //设置PA1和PA2引脚为高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //设置PA1和PA2引脚为高电平
}
void qj(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_5); //设置PA1和PA2引脚为高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //设置PA1和PA2引脚为高电平
PWM_SetCompare2(80);//左轮
PWM_SetCompare1(80);//右轮
}
void tz(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //设置PA1和PA2引脚为高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //设置PA1和PA2引脚为高电平
PWM_SetCompare2(0);//左轮
PWM_SetCompare1(0);//右轮
}
void ht(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //设置PA1和PA2引脚为高电平
GPIO_SetBits(GPIOA, GPIO_Pin_4); //设置PA1和PA2引脚为高电平
PWM_SetCompare2(80);//左轮
PWM_SetCompare1(80);//右轮
}
void left(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_5); //设置PA1和PA2引脚为高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //设置PA1和PA2引脚为高电平
PWM_SetCompare2(70);//左轮
PWM_SetCompare1(80);//右轮
}
void right(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_5); //设置PA1和PA2引脚为高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //设置PA1和PA2引脚为高电平
PWM_SetCompare2(80);//左轮
PWM_SetCompare1(70);//右轮
}
PWM波的配置:
/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
/*GPIO初始化*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //A1,A2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//TIM3通道1
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
//t=1/f=(1/72*10^6)*(psc+1)*(arr+1)
TIM_TimeBaseInitStructure.TIM_Period = 100-1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 36-1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道1
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare); //设置CCR2的值//定时器2的通道2,引脚是PA1
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR2的值//定时器2的通道三,引脚是PA2
}
总结:
这个项目到这里就完成了,一共就只涉及到了串口通信与电机驱动两个模块的内容,所以蓝牙小车这个项目真的非常适合初学者练习。如果想进一步学习串口通信与电机驱动的原理,可以去b站上找江科大的视频来看一看。