(纯萌新,学习单片机半年了,这是寒假回家的作品,师从江科大,写博客纪录我实现后的经验)(比较粗略)
蓝牙主从模式遥杆控制小车,我将其分为了三部分:1.让小车动 2.蓝牙主从连接与信号的发送接收 3.获取摇杆的状态并控制小车转向
![](https://i-blog.csdnimg.cn/blog_migrate/8b5518d84d1bfa4489d41915e8616f19.jpeg)
首先是让小车动起来
目标:内部电源,调节参数转向。
原理:让小车动只需上电即可。通过程序控制PWM的CCR而调节占空比,可以向电机输出频率大小不同的电流,可以调节小车的转动速度。当两个轮子转速不同时,小车就会转向。内部电源就是弄了电池和L298n,电池输出7-12v的电压,接在L298n电机驱动模块上可以输出5v电压,再转化为3.3v给单片机供电即可。我使用了淘宝商家的小车底盘和电路板,这个电路的唯一作用就是可以把5v转变为3.3v供电,此外也可以用电源模块在面包板上将5v转变为3.3v。
过程:对于我来说难在电源,通过两次转换让电源由两节3.7v转变为3.3v。
具体PWM代码和电机代码(大部分为江科大内容,没什么好说的)
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_0 ; //用的通道1PA0和通道2PA1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitSturcture;
TIM_TimeBaseInitSturcture.TIM_ClockDivision= TIM_CKD_DIV1;
TIM_TimeBaseInitSturcture.TIM_CounterMode= TIM_CounterMode_Up;
TIM_TimeBaseInitSturcture.TIM_Period= 100-1; //ARR
TIM_TimeBaseInitSturcture.TIM_Prescaler= 720-1; //PSC 电机的参数
TIM_TimeBaseInitSturcture.TIM_RepetitionCounter= 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitSturcture);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse=0 ; //CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
TIM_OC2Init(TIM2,&TIM_OCInitStructure); //此处通道二PA1,所以用OC2,可以用多个通道控制多个舵机
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare); //通道二的SetCompare2
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2,Compare); //通道二的SetCompare2
}
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure; //方向GPIO口初始化
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_4 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PWM_Init(); //初始化PWM
}
void Motor_TwoSetSpeed(int8_t Speed)
{
if(Speed>=0)
{
PWM_SetCompare1(Speed);
PWM_SetCompare2(Speed);
}
if(Speed<=0)
{
PWM_SetCompare1(-Speed);
PWM_SetCompare2(-Speed);
}
}
void Motor_L_SetSpeed(int8_t Speed)
{
if(Speed>=0)
{
PWM_SetCompare1(Speed);
}
if(Speed<=0)
{
PWM_SetCompare1(-Speed);
}
}
void Motor_R_SetSpeed(int8_t Speed)
{
if(Speed>=0)
{
PWM_SetCompare2(Speed);
}
if(Speed<=0)
{
PWM_SetCompare2(-Speed);
}
}
void Motor_Stop(void)
{
PWM_SetCompare1(0);
PWM_SetCompare2(0);
}
void Motor_Retreat()
{
GPIO_SetBits(GPIOA,GPIO_Pin_8);
GPIO_SetBits(GPIOA,GPIO_Pin_6);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
}
void Motor_Go()
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_6);
GPIO_ResetBits(GPIOA,GPIO_Pin_8);
}
让两片蓝牙主从通信
目标:在实现主从模式前,我的第一目标是让手机能连上蓝牙并向小车发送信息
过程:蓝牙我使用的是HC-05,可配置主从,HC-05出厂为从模式。首先通过AT模式改变HC-05的波特率为9600即可。然后将HC-05连接在面包板上,使用usart2作为通信媒介,对照引脚定义表连接Rx,Tx,把接收数据的代码写好,通过手机app连接蓝牙发送即可。
下面是蓝牙的从模式代码(用到了中断)
#include "stm32f10x.h" // Device header
uint8_t LanYa_RxData;
uint8_t LanYa_RxFlag;
void LanYa_Init(void) //初始化
{
//第一步,开启时钟(APB1——USART2)(GPIO引脚)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //注意,USART1是APB2的外设,USART2是APB1的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP; //TX是输出脚用复用推挽,RX是输入脚用输入模式(不分模式)一般浮空
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_2; //TX
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IPU; //Tx是输出脚用复用推挽,RX是输入脚用输入模式(不分模式)一般浮空或上拉
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3; //Rx
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//第二步,初始化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; //一位停止位
USART_InitStructure.USART_WordLength= USART_WordLength_8b; //8位字长
USART_Init(USART2,&USART_InitStructure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //可选择中断判断是否有数据录入
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel= USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd= ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority= 1; //响应优先级
NVIC_Init(&NVIC_InitStructure);
//第三步,打开即可
USART_Cmd(USART2, ENABLE);
}
uint8_t LanYa_GetRxFlag(void) //判断是否有数据输入,有的话,使其复位为0
{
if(LanYa_RxFlag==1)
{
LanYa_RxFlag=0;
return 1;
}
return 0;
}
uint8_t LanYa_GetRxData(void) //获得输入的数据
{
return LanYa_RxData; //返回输入的数据
}
void USART2_IRQHandler(void) //中断函数主体(本蓝牙函数是利用中断)
{
if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //如果是低电平
{
LanYa_RxData = USART_ReceiveData(USART2); //使得Data成为输入的数据
LanYa_RxFlag=1; //置一,我有数据来了
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除标志位
}
}
然后就是主模式的配置了,主从配置比较麻烦,我通过查阅b站配置主从,大概就是把主模式,传输地址,波特率,密码等内容,具体可以从b站找。
一下是主模式的蓝牙代码
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t LanYa_RxData;
uint8_t LanYa_RxFlag;
void LanSend_Init(void) //初始化
{
//第一步,开启时钟(APB1——USART2)(GPIO引脚)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //注意,USART1是APB2的外设,USART2是APB1的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP; //TX是输出脚用复用推挽,RX是输入脚用输入模式(不分模式)一般浮空
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_2; //TX
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IPU; //Tx是输出脚用复用推挽,RX是输入脚用输入模式(不分模式)一般浮空或上拉
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3; //Rx
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//第二步,初始化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; //一位停止位
USART_InitStructure.USART_WordLength= USART_WordLength_8b; //8位字长
USART_Init(USART2,&USART_InitStructure);
//第三步,打开即可
USART_Cmd(USART2, ENABLE);
}
void LanYa_SendByte(uint8_t Byte)
{
USART_SendData(USART2, Byte);
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
void LanYa_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
LanYa_SendByte(Array[i]);
}
}
void LanYa_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
LanYa_SendByte(String[i]);
}
}
uint32_t LanYa_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void LanYa_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
LanYa_SendByte(Number / LanYa_Pow(10, Length - i - 1) % 10 + '0');
}
}
int fputc(int ch, FILE *f)
{
LanYa_SendByte(ch);
return ch;
}
void LanYa_Printf(char *format, ...)
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
LanYa_SendString(String);
}
获取摇杆的状态
原理:我不知道各位的摇杆是什么样的,我是通过AD来获取摇杆的x,y数据,不断读取获得状态,然后通过蓝牙输出即可。
原理简单,所以直接上AD的代码
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //打开ADC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //打开ADCCLK的6分频
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //专属ADC的模式捏
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //专属ADC的模式捏
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//结构体初始化ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode= ADC_Mode_Independent; //ADC1单打模式!
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right ; //右对齐就欧克啦
ADC_InitStructure.ADC_ExternalTrigConv= ADC_ExternalTrigConv_None; //只用软件触发啦
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE ; //连续转换模式(ENABLE)
ADC_InitStructure.ADC_NbrOfChannel= 1; //通道数目
ADC_InitStructure.ADC_ScanConvMode= DISABLE; //扫描模式 看江科大啦
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE); //ADC准备就绪捏
//但是我们要校准呢
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET); //没校准开始给我站在这里
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET); //等待校准完成捏
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
uint16_t AD_GetValue_D(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //连续触发可放在上面,这样也不需要等到转化完成啦↓
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET); //转换完成变成1就退出啦
return ADC_GetConversionValue(ADC1); //因为读取后自动清除标志位,所以不用清除标志位啦
}
//若是连续触发模式,可以将软件触发的函数挪到初始化的最后,只需要触发一次即可~
uint16_t AD_GetValue_E(void) //这个就是连续转换用的函数啦
{
return ADC_GetConversionValue(ADC1);
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //连续触发可放在上面,这样也不需要等到转化完成啦↓
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET); //转换完成变成1就退出啦
return ADC_GetConversionValue(ADC1); //因为读取后自动清除标志位,所以不用清除标志位啦
}
在设置状态时,可以用OLED显示AD参数,比较方便。
尾声:
作为我的第一个比较完整的学习作品,我还是比较满意的。
它让我巩固了PWM,串口,AD的知识,多次练习了接线,深入学习熟练掌握HC-05的使用方法,该作品适合初学者。
通过以上的大概步骤,能够做出比较粗糙的摇杆小车,具体文件可以@我私发哦。