大一萌新の作品:蓝牙主从模式摇杆控制小车和副产品手机遥控小车

(纯萌新,学习单片机半年了,这是寒假回家的作品,师从江科大,写博客纪录我实现后的经验)(比较粗略)

蓝牙主从模式遥杆控制小车,我将其分为了三部分:1.让小车动 2.蓝牙主从连接与信号的发送接收 3.获取摇杆的状态并控制小车转向

首先是让小车动起来

目标:内部电源,调节参数转向。

原理:让小车动只需上电即可。通过程序控制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的使用方法,该作品适合初学者。

通过以上的大概步骤,能够做出比较粗糙的摇杆小车,具体文件可以@我私发哦。

猜你喜欢

转载自blog.csdn.net/ChiShangying/article/details/128780550