基于江科大的超声波避障和红外寻迹小车

注意:这里是参考的这个UP主【智能小车制作教程(基于stm32)-哔哩哔哩】 https://b23.tv/JLEpIPL

前言

我做的这个与UP主的有点不同,他那个是四驱的,我这个是后驱的,因此我这个在使用电机时只需要两个电机来驱动后轮,前轮使用转向前桥即可。

在本工程中,各个模块都只介绍部分代码和接线图,但会在最后给出总工程和总接线图。

小车移动

器件准备

小车底板

这个小车底板和电机可以直接买一体的,拼多多上有,这是链接https://mobile.yangkeduo.com/goods1.html?ps=gi29BlrXVk

具体根据你的实际需求,这里我没有按那个UP主那样买的四轮,我只买了两轮的作为后轮,前轮我是准备用转向前桥的,如图:

d3d78491c4f941f28808d04c3e4f6118.jpg

电机

电机购买是和小车底板一起的,如果另买那还是要根据你买的型号来决定后续器件购买以及电路设计。

对于这个电机,驱动方式和江科大的那个是一样的,在电机购买来后先测试一下,看能不能转?怎样是正传?怎样是反转?

注意:如果遇到没有接线的电机,就要使用电洛铁将其焊接上,我买到的就是没有接线的,尽量还是备好电洛铁,对于电烙铁我也不太熟悉,不过也可以买和我一样的,下面是链接:【淘宝】https://m.tb.cn/h.g1LtLu8?tk=IXuLWtT3OBW CZ0012 「德力西电烙铁家用小型电焊笔维修焊接神器套装专业级电洛铁焊锡枪」

这个:

697360302a234327b941eee4d4b07c9b.jpg

 

电机驱动芯片

c234c267098a4de0adcbf3a1d9d79063.jpg

 

这个芯片呢,使用的是tb6612就是和江科大教程里的那个一样。这个我就不多讲了,具体的去看江科大的就行,这里是链接:【STM32入门教程-2023版 细致讲解 中文字幕-哔哩哔哩】 https://b23.tv/8cttsnB。

这里由于是使用的两个电机,那么把它们的两个端分别共接一起就行。

这里说一下为什么共接就行,首先这两个电机是对着的吧,对着的就意味着这两个电机的正负极是相反的,然后又因为是对着的吧,那么它们的旋转方向也是相反的,正所谓负负得正嘛,那么在接入相同正极和相同负极时它们的旋转方向就是一样的了。

转向前桥

对于转向前桥呢,这个是用来当作前轮的,用来控制方向,就跟汽车的后驱车类似。

转向前桥是基于阿克曼转向模型来设计的,这是参考(也可以不看,会用就行):【阿克曼转向模型介绍 - CSDN App】http://t.csdnimg.cn/z2sEk

采购链接:【淘宝】https://m.tb.cn/h.g1GTCdK?tk=HHiTWtQn053 HU9046 「转向前桥ZX2舵机型 电子智能小车前后轮转向总成手工配件转向系统」

这个:bdfd5f49b3d14bac929f1ed7f5e5a20e.jpg

电路连接

驱动芯片

194ced23e44b48daa89b06034e1efbc4.jpeg

转向前桥

因为转向前桥只有一个舵机,所以这里就只用了一个舵机。

f1cc5b2cfd88493f9b7d41610ceae5a0.jpeg

代码实现

在本代码中仅用于测试。

基本配置

可以参考江科大的,就是和他一样的

电机初始化

PWM.c

#include "stm32f10x.h" // Device header



//电机模块底层



/**

  * 函 数:PWM初始化

  * 参 数:无

  * 返 回 值:无

  */

void PWM_Init(void)

{

    /*开启时钟*/

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟

    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_2;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出    

                                                                    //受外设控制的引脚,均需要配置为复用模式

    

    /*配置时钟源*/

    TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟

    

    /*时基单元初始化*/

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量

    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能

    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数

    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_OC3Init,配置TIM2的输出比较通道3

    

    /*TIM使能*/

    TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行

}



/**

  * 函 数:PWM设置CCR

  * 参 数:Compare 要写入的CCR的值,范围:0~100

  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare3(uint16_t Compare)
{
    TIM_SetCompare3(TIM2, Compare);        //设置CCR3的值
}

PWM.h

#ifndef __PWM_H

#define __PWM_H



void PWM_Init(void);

void PWM_SetCompare3(uint16_t Compare);



#endif

转向前桥初始化

PWM_Auto_Serve.c

#include "stm32f10x.h" // Device header



//转向前桥底层



/**

  * 函 数:PWM初始化

  * 参 数:无

  * 返 回 值:无

  */

void PWM_Auto_Serve_Init(void)

{

    /*开启时钟*/

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //开启TIM4的时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟

    

    /*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(GPIOB, &GPIO_InitStructure); //将PB9引脚初始化为复用推挽输出    

                                                                    //受外设控制的引脚,均需要配置为复用模式

    

    /*配置时钟源*/

    TIM_InternalClockConfig(TIM4); //选择TIM4为内部时钟,若不调用此函数,TIM默认也为内部时钟

    

    /*时基单元初始化*/

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量

    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能

    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数

    TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值

    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值

    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到

    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM4的时基单元

    

    /*输出比较初始化*/ 

    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_OC4Init(TIM4, &TIM_OCInitStructure); //将结构体变量交给TIM_OC4Init,配置TIM4的输出比较通道4

    

    /*TIM使能*/

    TIM_Cmd(TIM4, ENABLE); //使能TIM2,定时器开始运行

}



/**

  * 函 数:PWM设置CCR

  * 参 数:Compare 要写入的CCR的值,范围:0~100

  * 返 回 值:无

  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比

  * 占空比Duty = CCR / (ARR + 1)

  */

void PWM_SetCompare4(uint16_t Compare)

{

    TIM_SetCompare4(TIM4, Compare); //设置CCR3的值

}

 

PWM_Auto_Serve.h

注意:这里具体的电机方向设置和转向前桥舵机角度需要根据实际的连接来决定。

小车移动

前进

GPIO_SetBits(GPIOA, GPIO_Pin_4);

//PA4置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5);    

//PA5置低电平,设置方向为正转

PWM_SetCompare3(50);  

//PWM设置为速度值

后退

GPIO_SetBits(GPIOA, GPIO_Pin_5);

//PA5置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_4);    

//PA4置低电平,设置方向为反转

PWM_SetCompare3(50);  

//PWM设置为速度值

这里的舵机旋转度数以90°为正向

左转

PWM_SetCompare4(45);

右转

PWM_SetCompare4(135);

正向

PWM_SetCompare4(90);

 

蓝牙连接

蓝牙APP

使用这个蓝牙调试器,在首页的up主链接哪儿有。

d7c8b551c09f4380b5a876f1a7974184.jpg

 

c0af066697714934b9c5ec6e4f7cfaf4.jpg

 

蓝牙模块

器件选择

这里选择的是HC_06

就是下面这个

b828a72b2f3f4e1381dbad5a8bebe943.jpg

 这是链接:

https://mobile.yangkeduo.com/goods2.html?ps=y4BpVoGLDB

电路连接

341e1912b831433e92e397c7fc75d144.jpeg

代码

这里直接照搬江科大的代码,主要是串口的基本配置,以及中断。

C文件

//蓝牙模块
#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];                //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4];                //定义接收数据包数组
uint8_t Serial_RxFlag;                    //定义接收数据包标志位

void Blue_Usart_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,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
    USART_SendData(USART1, Byte);        //将字节数据写入数据寄存器,写入后USART自动生成时序波形
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);    //等待发送完成
    /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    uint16_t i;
    for (i = 0; i < Length; i ++)        //遍历数组
    {
        Serial_SendByte(Array[i]);        //依次调用Serial_SendByte发送每个字节数据
    }
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
    {
        Serial_SendByte(String[i]);        //依次调用Serial_SendByte发送每个字节数据
    }
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;    //设置结果初值为1
    while (Y --)            //执行Y次
    {
        Result *= X;        //将X累乘到结果
    }
    return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i ++)        //根据数字长度遍历数字的每一位
    {
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');    //依次调用Serial_SendByte发送每位数字
    }
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);            //将printf的底层重定向到自己的发送字节函数
    return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
    char String[100];                //定义字符数组
    va_list arg;                    //定义可变参数列表数据类型的变量arg
    va_start(arg, format);            //从format开始,接收参数列表到arg变量
    vsprintf(String, format, arg);    //使用vsprintf打印格式化字符串和参数列表到字符数组中
    va_end(arg);                    //结束变量arg
    Serial_SendString(String);        //串口发送字符数组(字符串)
}

/**
  * 函    数:串口发送数据包
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
  */
void Serial_SendPacket(void)
{
    Serial_SendByte(0xFF);
    Serial_SendArray(Serial_TxPacket, 4);
    Serial_SendByte(0xFE);
}

/**
  * 函    数:获取串口接收数据包标志位
  * 参    数:无
  * 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
    if (Serial_RxFlag == 1)            //如果标志位为1
    {
        Serial_RxFlag = 0;
        return 1;                    //则返回1,并自动清零标志位
    }
    return 0;                        //如果标志位为0,则返回0
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
//void USART1_IRQHandler(void)
//{
//    static uint8_t RxState = 0;        //定义表示当前状态机状态的静态变量
//    static uint8_t pRxPacket = 0;    //定义表示当前接收数据位置的静态变量
//    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)        //判断是否是USART1的接收事件触发的中断
//    {
//        uint8_t RxData = USART_ReceiveData(USART1);                //读取数据寄存器,存放在接收的数据变量
//        
//        /*使用状态机的思路,依次处理数据包的不同部分*/
//        
//        /*当前状态为0,接收数据包包头*/
//        if (RxState == 0)
//        {
//            if (RxData == 0xFF)            //如果数据确实是包头
//            {
//                RxState = 1;            //置下一个状态
//                pRxPacket = 0;            //数据包的位置归零
//            }
//        }
//        /*当前状态为1,接收数据包数据*/
//        else if (RxState == 1)
//        {
//            Serial_RxPacket[pRxPacket] = RxData;    //将数据存入数据包数组的指定位置
//            pRxPacket ++;                //数据包的位置自增
//            if (pRxPacket >= 4)            //如果收够4个数据
//            {
//                RxState = 2;            //置下一个状态
//            }
//        }
//        /*当前状态为2,接收数据包包尾*/
//        else if (RxState == 2)
//        {
//            if (RxData == 0xFE)            //如果数据确实是包尾部
//            {
//                RxState = 0;            //状态归0
//                Serial_RxFlag = 1;        //接收数据包标志位置1,成功接收一个数据包
//            }
//        }
//        
//        USART_ClearITPendingBit(USART1, USART_IT_RXNE);        //清除标志位
//    }
//}

 

H文件

#ifndef BLUE_USART_H
#define BLUE_USART_H

extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;

void Blue_Usart_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

#endif


 

电源供给

5v供电就行。

器件选择

65f56083244a4625ab08a325f4c50727.jpg

 我试了一下,这个也行。

电路连接

这里先把电池和充电模块接上,然后直接通过Type-C把stlink和充电模块连起来就行。

注意:电池和充电模块的连接可以使用焊接和热熔胶。

自动避障

超声波模块

器件选择

这里选择的是HC-SR04

c4de1444c8b24264a92555e477ca771a.jpg

 选新版本的(3.3v-5v),注意:这个是不包含云台的。

链接:https://mobile.yangkeduo.com/goods2.html?ps=U6gJij5gg2

超声波原理分析

参考:

【51单片机---超声波测距(HC-SR04) - CSDN App】http://t.csdnimg.cn/Hlewf

f3ea61ac3f924d0186dd5b3d98dc122b.jpg

总结就是通过给该模块发送一个40us的高电平信号,使其发送一个较为稳定的方波,然后我们将发送端口置低电平,开启定时器,等待返回信号,返回信号后,关闭定时器,获取定时器计数值,清除定时器计数值,最后通过计算公式计算距离。

具体如下:

float Super_Test_Distance(void){
	GPIO_SetBits(GPIOB,GPIO_Pin_15);
	Delay_us(30);
	GPIO_ResetBits(GPIOB,GPIO_Pin_15);
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==RESET){
	};
	TIM_Cmd(TIM1, ENABLE);
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==SET){
	};
	TIM_Cmd(TIM1, DISABLE);
	Cnt=TIM_GetCounter(TIM1);
	float distance=(Cnt*1.0/10*0.34)/2;
	TIM1->CNT=0;
	Delay_ms(100);
	return distance;
}

电路连接

超声波

b4cdf22cca344fa297eada1204700ead.jpeg

云台舵机

c5eb533629f94e7a8c1450e38fb7e48d.jpeg

代码

超声波

C文件

#include "stm32f10x.h"                  // Device header

#include "Delay.h"

uint16_t Cnt;
uint16_t OverCnt;
void Auto_Super_Init(void)
	{
		
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//trig
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//echo
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM1);
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 60000 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);

}
float Super_Test_Distance(void){
	GPIO_SetBits(GPIOB,GPIO_Pin_15);
	Delay_us(30);
	GPIO_ResetBits(GPIOB,GPIO_Pin_15);
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==RESET){
	};
	TIM_Cmd(TIM1, ENABLE);
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==SET){
	};
	TIM_Cmd(TIM1, DISABLE);
	Cnt=TIM_GetCounter(TIM1);
	float distance=(Cnt*1.0/10*0.34)/2;
	TIM1->CNT=0;
	Delay_ms(100);
	return distance;
}

H文件

#ifndef __AUTO_SUPER_H
#define __AUTO_SUPER_H

void Auto_Super_Init(void);
float Super_Test_Distance(void);

#endif

舵机

.C文件

#include "stm32f10x.h"                  // Device header

//超声波舵机底层

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Super_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);			//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);							//将PB0引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;                 //计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
	
	/*输出比较初始化*/ 
	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(TIM3, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC3Init,配置TIM3的输出比较通道3
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_Super_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM3, Compare);		//设置CCR3的值
}

.H文件

#ifndef __PWM_SUPER_H
#define __PWM_SUPER_H

void PWM_Super_Init(void);
void PWM_Super_SetCompare3(uint16_t Compare);


#endif

避障原理分析

我们在主程序的while语句中,首先通过超声波去获取到超声波模块前方的距离信息,然后去判断这个距离是否小于设定值,如果小于,那么使小车停止,等待一段时间,然后再次获取距离信息。判断是否还小于设定值,如果还小于,那么就进行相应的避障操作。具体流程参考这个流程图:

4de9ff94d52646ee890fd30c9bfaa90c.jpeg

代码

这里说一下,为什么在程序里要进行延时,你可以尝试一下不延时一直获取测量值,你会发现是有错误值的。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Auto_Serve.h"
#include "Auto_Super.h"
#include "Auto_Super_Serve.h"
#include "Auto_Action.h"


uint16_t a;
int Speed = 30;


int main(void)
{
    Delay_ms(200);
    Auto_Action_Init();
    Auto_Serve_Init();
    Auto_Super_Serve_Init();
    Auto_Super_Init();


      Delay_ms(500);

   while(1)
     {
         
//超声波模块
         a = Super_Test_Distance();
         Serial_SendNumber(a,3);
        if(a < 40)
            {
                Auto_Speed_Set(0);
                Delay_ms(500);
                a= Super_Test_Distance();
                Serial_SendNumber(a,3);
                if(a<40)
                {
                    Auto_Super_Serve_Right();
                    Delay_ms(500);
                    a= Super_Test_Distance();
                    Serial_SendNumber(a,3);
                    if(a >= 40)
                    {
                        Auto_Serve_Right();
                        Auto_Speed_Set(-Speed);
                        Delay_ms(700);
                        Auto_Super_Serve_UP();
                        Auto_Serve_UP();    
                    }
                    else if( a < 40)
                    {
                        Auto_Super_Serve_Left();
                        Delay_ms(500);
                        a= Super_Test_Distance();
                        Serial_SendNumber(a,3);
                        if(a >= 40)
                      {
                        Auto_Serve_Left();
                        Auto_Speed_Set(-Speed);
                        Delay_ms(700);
                        Auto_Super_Serve_UP();
                         Auto_Serve_UP();    
                      }
                      else if( a < 40)
                      {
                          Auto_Super_Serve_UP();
                          Auto_Serve_UP();
                          Auto_Speed_Set(Speed);
                      }                          
                    }
                  }
        }
        else if(a >= 40)
        {
            Auto_Serve_UP();
            Auto_Speed_Set(-Speed);
        }
        Delay_ms(200);

}

 

红外寻迹

红外模块

器件选择

这里选择的是TCRT5000

8ab943c554904e0892c389a1438e9dca.jpg

 

这是链接:

https://mobile.yangkeduo.com/goods1.html?ps=8A8u4lDASO

红外原理分析

2408e0dfa34947b7a35961e85a39b10d.jpg

 具体的就是这个,他说的意思就是没有返回红外线,就输出高电平,返回红外线,就输出低电平。我们知道黑色能吸收红外线,因此当tcrt5000检测到黑色时就返回不了红外线,然后输出高电平,因此我们只需在程序中不断检测模块的输出电平即可。

电路连接

d0c56494f0e44b6c9c7d53cda624bf1c.jpeg

代码

.C

#include "stm32f10x.h"                  // Device header


void Track_Init(void)
{
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 |GPIO_Pin_7 | GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_ResetBits(GPIOB, GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8);
	
}

.H

#ifndef __TRACK_H
#define __TRACK_H

void Track_Init(void);

#endif

红外寻迹原理分析

在本次工程中一个使用了4个红外传感,因此这里就4个红外传感进行分析,首先我们将红外传感从左到右(安装位置)命名为7,5,6,8,这里是根据我自己的端口分配来的,我们将种间两个(即5和6)作为小车直行识别,7和8作为小车转向识别,这里并未将全部情况列出,只是谈下思路。

小车直行:当只有5和6识别到黑线时认为此时小车处于正向道路,因此执行前进程序。

if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 1 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0)
			 {		
				 Auto_Serve_UP();
                 Auto_Speed_Set(60);
			 }

小车左转向:当7识别到黑线,8没有识别到黑线,认为此时小车处于偏离道路向右,因此执行小车左转程序。

 else if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0)
			 {
				Auto_Serve_Left();
                Auto_Speed_Set(60);
			 }

小车右转向:当8识别到黑线,7没有识别到黑线,认为此时小车处于偏离道路向左,因此执行小车右转程序。

 else if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 1)
			 {
				Auto_Serve_Right();
                Auto_Speed_Set(60);
			 }

小车完全偏离道路:当所有传感器均没有识别到黑线时,认为此时小车完全偏离道路,因此执行小车停止程序。

else if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0)
			 {
				 Auto_Speed_Set(0);
       			 }

代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Auto_Serve.h"
#include "Auto_Super.h"
#include "Auto_Super_Serve.h"
#include "Auto_Action.h"
#include "Blue_Usart.h"
#include "track.h"


int Speed = 60;
int main(void)
{
    Delay_ms(200);
	Auto_Action_Init();
	Auto_Serve_Init();
	Blue_Usart_Init();
	Auto_Super_Serve_Init();
	Auto_Super_Init();
	Track_Init();
	
	  Delay_ms(500);

   while(1)
     {
			 
			 if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 1 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0)
			 {		
				 Auto_Serve_UP();
				 Auto_Speed_Set(-Speed);
			 }
			 else if((GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0 )||
				 (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0))
			 {
				Auto_Serve_Left();
				 Auto_Speed_Set(-Speed);
			 }
			 else if((GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 1) || 
				 (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 1 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0))
			 {
				Auto_Serve_Right();
				 Auto_Speed_Set(-Speed);
			 }
			else if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 0)
			 {
				 Auto_Speed_Set(0);
       			 }
			 else if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 1 &&
				GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 1 && GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == 1)
			 {
				Auto_Serve_Right();
				 Auto_Speed_Set(-Speed);
       			 }


}

工程文件

在我的主页可以看到上传的文件

电路图

这个是我在UP主下的评论区内找的,可以照着这个接

90e0ea43f8f24850bc1408d562ee7381.jpg

也可以按我自己画的这个图接,我自己画的是和工程里代码写的端口是一样的。 

a70a3f4d54c7481e817b103a1cb5d1df.jpeg

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值