STM32蓝牙小车、红外循迹小车、超声波避障小车项目设计

news2024/11/17 11:54:38

一、前言

本文旨在分享我学习STM32的过程中,为了强化学习成果,试着制作一些实训项目。最开始做的就是STM32蓝牙小车、STM32红外循迹小车、STM32超声波避障小车。

相信看完本文的你,一定可以亲手制作一辆属于自己的智能小车!

注:文末附源码工程,需要的读者可以至文末下载

如果你还想进阶的话,可以尝试制作基于PID算法的两轮平衡小车——这是它的相关源码工程。STM32两轮平衡小车原理详解(开源)_stm32平衡车原理-CSDN博客


二、实训项目 

(一)、项目概述

1、共同需要的驱动模块

关于以上三种功能的智能小车,都需要用到一些共同的驱动模块,这里我先把他们共同用到的模块罗列出来。

A、模块一:TB6612电机驱动模块

关于该模块的使用方式,请看以下文章:

B、模块二:两轮或四轮车模

为了满足大家的需求,我会在讲解电机驱动时分别讲两种电机:

(1)一种时大家看到的下图的直流电机

(2)另一种是大家熟悉的编码电机

C、模块三:电源 

 D、模块四:电源稳压模块(降压模块)

E、模块四:stm32f103c8t6最小系统板


2、共同的驱动代码

为什么我这里要先讲述以上三个功能的共同驱动代码呢?因为以上三个功能都是基于同一个功能下实现的,这个功能就是——

首先车必须能动!!!

那么车动的驱动代码是如何实现的呢?不了解TB6612电机驱动模块的小伙伴建议先去了解一下该模块的使用的方法!

 这里先讲一下直流电机的驱动:

 (1)直流电机
单片机驱动引脚配置:
void CarGo(void)
{
	TIM_SetCompare4(TIM1 , 170);
	TIM_SetCompare1(TIM1 , 170);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
    GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}
PWM配置: 
#include "PWM1.h"                  // Device header




void PWM1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &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 = 1000 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
	
	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(TIM1, &TIM_OCInitStructure);
	TIM_OC4Init(TIM1, &TIM_OCInitStructure);
	TIM_Cmd(TIM1, ENABLE);
    
  TIM_CtrlPWMOutputs(TIM1,ENABLE);
}

//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
//TIM1_PWM_Init(7199,0);//PWM频率=72000/(7199+1)=10Khz
//void TIM1_PWM_Init(u16 arr,u16 psc)
//{  
//	GPIO_InitTypeDef GPIO_InitStructure;
//	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
//	TIM_OCInitTypeDef  TIM_OCInitStructure;
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);// 
// 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);  //使能GPIO外设时钟使能
//   //设置该引脚为复用输出功能,输出TIM1 CH1 CH4的PWM脉冲波形
//	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM_CH1 //TIM_CH4
//	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
//	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//	GPIO_Init(GPIOA, &GPIO_InitStructure);
//	
//	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
//	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
//	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
//	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
//	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

// 
//	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
//	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
//	TIM_OCInitStructure.TIM_Pulse = 0;                            //设置待装入捕获比较寄存器的脉冲值
//	TIM_OCInitStructure.TIM_Pulse = arr >> 1;
//	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     //输出极性:TIM输出比较极性高
//	TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//	TIM_OC4Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx

//    TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能	

//	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 
//	TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH4预装载使能	 
//	
//	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
//	
//	TIM_Cmd(TIM1, ENABLE);  //使能TIM1
//}


#ifndef __PWM1_H
#define __PWM1_H

#include "sys.h"
#include "stm32f10x_tim.h" 

void TIM1_PWM_Init(u16 arr,u16 psc);

#endif

 直流电机驱动:
#include "stm32f10x.h"                  // Device header
#include "PWM1.h"
#include "motor.h"
/*

一个端口接PWM,还有一个接高低电平,无论接哪个电平输出的PWM波形不变,若PWM输出30
IO接负:PWM的上半段输出电压,则给电机30的电压,正转
IO接正;PWM的上半段输出电压,则给电机70的电压,负转

*/
void CarGo(void)
{
	TIM_SetCompare4(TIM1 , 170);
	TIM_SetCompare1(TIM1 , 170);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
    GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}

void CarStop(void)
{
  TIM_SetCompare4(TIM1 , 0);
	TIM_SetCompare1(TIM1 , 0);
	GPIO_SetBits(GPIOB,GPIO_Pin_13);   //控制左边轮胎 
	GPIO_SetBits(GPIOB,GPIO_Pin_12);	
  GPIO_SetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}
void CarLeft(void)
{
	TIM_SetCompare4(TIM1 , 100);
	TIM_SetCompare1(TIM1 , 200);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
  GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}
void CarBigLeft(void)
{
	TIM_SetCompare4(TIM1 , 100);
	TIM_SetCompare1(TIM1 , 250);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
  GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);

}
void CarLeftAround(void)
{
	TIM_SetCompare4(TIM1 , 0);	
	TIM_SetCompare1(TIM1 , 0);
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
  GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}

void CarRight(void)
{
	TIM_SetCompare4(TIM1 , 200);
	TIM_SetCompare1(TIM1 , 100);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
  GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}

void CarBigRight(void)
{
	TIM_SetCompare4(TIM1 , 250);
	TIM_SetCompare1(TIM1 , 100);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
   GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}

void CarRightAround(void)
{
	TIM_SetCompare4(TIM1 , 400);	
	TIM_SetCompare1(TIM1 , 400);	
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制左边轮胎
   GPIO_ResetBits(GPIOB,GPIO_Pin_12);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_1);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}

void CarBack(void)
{
	TIM_SetCompare4(TIM1 , 100);
	TIM_SetCompare1(TIM1 , 100);
	GPIO_ResetBits(GPIOB,GPIO_Pin_13);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_12);
	GPIO_SetBits(GPIOB,GPIO_Pin_1);    //控制左边轮胎
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);
}

void GPIO_init_Init(void)
{
	 GPIO_InitTypeDef  GPIO_InitStructure;	
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能GPIO的外设时钟

     GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13|GPIO_Pin_12|GPIO_Pin_1|GPIO_Pin_0;//选择要用的GPIO引脚
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置引脚模式为推挽输出模式						 
	 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置引脚速度为50MHZ         
	 GPIO_Init(GPIOB,&GPIO_InitStructure);//调用库函数,初始化GPIO
	
}



/*-------------------
函数功能:
提供电机PWM接口函数,将PID计算好的PWM直接赋值给电机
--------------------*/
void ZhengZhuan()
{
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制右边轮胎
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);

	GPIO_SetBits(GPIOB,GPIO_Pin_1);    //控制左边轮胎
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);
}

void FanZhuan()
{
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_13);

	GPIO_ResetBits(GPIOB,GPIO_Pin_0);    //控制左边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_1);
}

void Stop()
{
	GPIO_SetBits(GPIOB,GPIO_Pin_13);    //控制右边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_12);

	GPIO_SetBits(GPIOB,GPIO_Pin_1);    //控制左边轮胎
	GPIO_SetBits(GPIOB,GPIO_Pin_0);
}


void motor_pwm(float duty)     //0 -- 7200
{
	if(duty>=0 && duty <= 5000 )
	{
		ZhengZhuan();//====电机正转,逆时针					
		TIM_SetCompare1(TIM1, duty);
		TIM_SetCompare4(TIM1, duty);
	}
	
	else if(duty > 5000)
	{
		Stop();
	}
	
	else
	{
		FanZhuan();
		TIM_SetCompare2(TIM1, duty);
		TIM_SetCompare1(TIM1, duty);
	}
}

(2)、编码电机

编码电机实质和直流电机一样,只不过其本身自带一个编码测速盘,所以我在这里详细讲述一下编码器的使用方式。

Encoder.c
#include "stm32f10x.h"                  // Device header
#include "Encoder.h"
#include "sys.h"

//电机编码器线数大约位263
void Encoder2_Init()
{
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
  TIM_ICInitTypeDef TIM_ICInitStructure;  
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//使能定时器4的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB端口时钟
	
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;	//端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOB, &GPIO_InitStructure);					      //根据设定参数初始化GPIOB
  
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 
  TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数  
  TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
  TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
  TIM_ICStructInit(&TIM_ICInitStructure);
  TIM_ICInitStructure.TIM_ICFilter = 10;
  TIM_ICInit(TIM4, &TIM_ICInitStructure);
  TIM_ClearFlag(TIM4, TIM_FLAG_Update);//清除TIM的更新标志位
  TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
  
  //Reset counter
  TIM_SetCounter(TIM4,0);
  TIM_Cmd(TIM4, ENABLE);  
}



void Encoder1_Init()
{
    GPIO_InitTypeDef GPIO_InitStructure;
	//开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);      
    //输入比较用的TIM2,所以输入捕获用TIM3,不能用一个,TIM3也在APB1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
    //配置GPIO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;   //TIM3的通道一的引脚在PA6上
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
    //配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR,设大,防止溢出
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
	TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICStructInit(&TIM_ICInitStructure);    //给默认的初始值
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;         //通道一
	TIM_ICInitStructure.TIM_ICFilter = 0xF;                //滤波大小 
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
    
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;         //通道一
	TIM_ICInitStructure.TIM_ICFilter = 0xF;                //滤波大小  
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
    
    TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
    
	//配置编码器接口   
    TIM_Cmd(TIM3,ENABLE);
}

int Read_Encoder(u8 TIMX)
{
    int Encoder_TIM;    
   switch(TIMX)
	 {
	   case 4:  
		 Encoder_TIM= (short)TIM4 -> CNT; 
		 TIM4 -> CNT=0;
		 break;
	   case 3:  
		 Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=0;
	     break;	
		 default: Encoder_TIM=0;
	 }
		return Encoder_TIM;
}

 Encoder.h
#ifndef __ENCODER_H
#define __ENCODER_H

#define ENCODER_TIM_PERIOD (u16)(65535)   //103的定时器是16位 2的16次方最大是65536

//uint16_t Encoder1_get(void);

void Encoder1_Init(void);
void Encoder2_Init(void);
int Read_Encoder(u8 TIMX);

#endif

 提示:编码电机的驱动和直流电机一样,所以不再赘述。

(二)、实训项目——蓝牙小车

Step1:器材准备

在以上共同的器材上添加一个蓝牙模块

 Step2:核心代码分析

设计蓝牙小车,由于蓝牙是串口通信,所以关键就是如何配置串口,保证数据收发正常。

对于单片机的串口配置,请详细看下面的代码: 

Usart.c
#include "sys.h"
#include "usart1.h"	  
// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//串口1初始化		   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/8/18
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明 
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//V1.4修改说明
//1,修改串口初始化IO的bug
//2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
//3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改说明
//1,增加了对UCOSII的支持
// 	  
 

//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

 
 
#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart1_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

	USART_Init(USART1, &USART_InitStructure); //初始化串口1
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
	USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
#endif	


Usart.h
#ifndef __USART1_H
#define __USART1_H
#include "stdio.h"	
#include "sys.h" 
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//串口1初始化		   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/8/18
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明 
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//V1.4修改说明
//1,修改串口初始化IO的bug
//2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
//3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改说明
//1,增加了对UCOSII的支持
#define USART_REC_LEN  			200  	//定义最大接收字节数 200
#define EN_USART1_RX 			1		//使能(1)/禁止(0)串口1接收

//#define EN_USART2_RX 			1		//使能(1)/禁止(0)串口1接收
	  	
extern u8  USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 
extern u16 USART_RX_STA;         		//接收状态标记	
//如果想串口中断接收,请不要注释以下宏定义
void uart1_init(u32 bound);
//void uart_init2(u32 bound);

#endif




(三)、实训项目——超声波避障小车

超声波避障,关键就是如何驱动超声波模块,以达到测距避障功能

超声波驱动核心代码

这里需要注意一个问题就是,超声波测距应该放在中断里实现!!!!

CS.c

#include "cs.h"

#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"

/*记录定时器溢出次数*/
uint overcount=0;

/*设置中断优先级*/
void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructer;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	NVIC_InitStructer.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructer.NVIC_IRQChannelSubPriority=0;
	NVIC_InitStructer.NVIC_IRQChannel=TIM4_IRQn;
	NVIC_InitStructer.NVIC_IRQChannelCmd=ENABLE;

	NVIC_Init(&NVIC_InitStructer);
}

/*初始化模块的GPIO以及初始化定时器TIM2*/
void CH_SR04_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructer;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructer;

	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

	/*TRIG触发信号*/
	GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;				//原来接PB 8、PB 9不能用,后改为PA 11,PA 12就能用
	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_11;
	GPIO_Init(GPIOA, &GPIO_InitStructer);

	/*ECHO回响信号*/
	GPIO_InitStructer.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructer.GPIO_Pin=GPIO_Pin_12;
	GPIO_Init(GPIOA, & GPIO_InitStructer);
	
	GPIO_ResetBits(GPIOA,GPIO_Pin_4);

	/*定时器TIM2初始化*/
	TIM_DeInit(TIM4);
	TIM_TimeBaseInitStructer.TIM_Period=999;//定时周期为1000
	TIM_TimeBaseInitStructer.TIM_Prescaler=71; //分频系数72
	TIM_TimeBaseInitStructer.TIM_ClockDivision=TIM_CKD_DIV1;//不分频
	TIM_TimeBaseInitStructer.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructer);

	TIM_ClearFlag(TIM4,TIM_FLAG_Update);
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//开启更新中断
	NVIC_Config();
	TIM_Cmd(TIM4,DISABLE);//关闭定时器使能

}



float Senor_Using(void) //测距函数
{
	float length=0,sum=0;
	u16 tim;
	uint i=0;
	/*测5次数据计算一次平均值*/
	while(i!=5)
	{
		PAout(11)=1; //拉高信号,作为触发信号
		delay_us(20); //高电平信号超过10us
		PAout(11)=0;
		/*等待回响信号*/
		while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12)==RESET);
		TIM_Cmd(TIM4,ENABLE);//回响信号到来,开启定时器计数

		i+=1; //每收到一次回响信号+1,收到5次就计算均值
		while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_12)==SET);//回响信号消失
		TIM_Cmd(TIM4,DISABLE);//关闭定时器

		tim=TIM_GetCounter(TIM4);//获取计TIM4数寄存器中的计数值,一边计算回响信号时间

		length=(tim+overcount*1000)/58.0;//通过回响信号计算距离

		sum=length+sum;
		TIM4->CNT=0; //将TIM4计数寄存器的计数值清零
		overcount=0; //中断溢出次数清零
		delay_ms(10);
	}
	length=sum/5;
	return length;//距离作为函数返回值
}



void TIM4_IRQHandler(void) //中断,当回响信号很长是,计数值溢出后重复计数,用中断来保存溢出次数
{
	if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
	 {
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//清除中断标志
	overcount++;

	 }
}

Cs.h

#ifndef __CS_H
#define __CS_H 	

#include "stm32f10x.h"
#include "delay.h"

#include "sys.h"
#define uint unsigned int
#define TRIG_Send PBout(8)
#define ECHO_Reci PBin(9)

void CH_SR04_Init(void);  //超声波模块相关配置初始化
float Senor_Using(void);  //测距函数,返回值即为距离
void NVIC_Config(void);   //中断配置


#endif

通过以上驱动代码,就能实现超声波测距了。

 (四)、实训项目——红外循迹

红外循迹相对简单,只需明白红外传感器在检测到黑色物质时和未检测到黑色物质时,传感器信号口传回的电平是不同的。通过单片机端口来判断传回的高低电平,不同位置的传感器传回的信号不同,从而可以根据不同位置传回的高低电平来控制车的运动。

定义几个单片机检测端口来判断红外 传感器传回的电平信号即可。

#include "xunji.h"

	
	
void xunji_config(void)	
{
  GPIO_InitTypeDef GPIO_InitStructure;	
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PB端口时钟
	
	//配置电机引脚,不知道什么原因要在这里配置成上拉才能实现pwm调速
  GPIO_InitStructure.GPIO_Pin =   GPIO_Pin_6 | GPIO_Pin_7|GPIO_Pin_14|GPIO_Pin_15;	//选择对应的引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//配置GPIO模式,输入上拉       
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PB端口
	

  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE); // 使能PA端口时钟
	
	//配置红外引脚
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_1 | GPIO_Pin_2  | GPIO_Pin_3 | GPIO_Pin_4| GPIO_Pin_5;;	//选择对应的引脚
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//配置GPIO模式,浮空输入       
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PC端口

}

//读取红外的信号
void Read_xunji_Date(void)
{
	L1;
	L2;
	M;
	R2;
	R1;
}

#ifndef __XUNJI_H
#define	__XUNJI_H

#include "stm32f10x.h"

#define L1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)
#define L2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2)
#define M  GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3)
#define R2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)
#define R1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5) // L1 L2 M R2 R1 

void xunji_config(void);
void Read_xunji_Date(void);  //读循迹模块返回的值

#endif

三、总结

以上就是STM32红外循迹、蓝牙、超声波避障智能小车的所有关键代码和注意事项的详细分享。希望我的分享对你有所帮助。

最后,我将完整的代码工程分享给大家,白嫖的时候可不能忘了点赞,你的点赞,是我保持分享欲的无尽动力!!!加油,冲冲冲……

1、https://download.csdn.net/download/m0_73931287/88726542

2、https://download.csdn.net/download/m0_73931287/88726547?spm=1001.2014.3001.5503

如果以上资源不能下载,请读者下方留言或者私信我,收到后我会第一时间回复。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1390498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML--表单

睡不着就看书之------------------------ 表单 作用&#xff1a;嗯~~动态页面需要借助表单实现 表单标签&#xff1a; 主要分五种&#xff1a; form&#xff0c;input&#xff0c;textarea&#xff0c;select&#xff0c;option 从外观来看&#xff0c;表单就包含以下几种&…

【已解决】丨Details: An error occurred while executing command: “host-status

Author&#xff1a;AXYZdong 硕士在读 工科男 有一点思考&#xff0c;有一点想法&#xff0c;有一点理性&#xff01; 定个小小目标&#xff0c;努力成为习惯&#xff01;在最美的年华遇见更好的自己&#xff01; CSDNAXYZdong&#xff0c;CSDN首发&#xff0c;AXYZdong原创 唯…

代码随想录 Leetcode18. 四数之和

题目&#xff1a; 代码&#xff08;首刷看解析 2024年1月15日&#xff09;&#xff1a; class Solution { public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> result;sort(nums.begin(), nums.end(…

谷粒商城-商品服务-品牌管理-阿里云云存储+JSR303数字校验+统一异常处理

阿里云云存储OSS 分布式系统上传文件 分布式系统上传文件 单体应用上传&#xff1a;上传文件到服务器&#xff0c;想获取文件时再向服务器发请求获取文件。 分布式系统上传&#xff1a; 因为有多台服务器&#xff0c;为防止负载均衡导致获取文件时没找到对应的服务器&#xf…

stack,queue和prioriy_queue

MySTL stack和queue template <class T, class Container deque<T> > class queue;template <class T, class Container deque<T> > class stack;选择适配器的宗旨是要能达到预想的功能 queue——只能使用list和deque stack——可以使用vector和…

019、错误处理:不可恢复错误与panic!

鉴于上一篇文章过长&#xff0c;不方便大家阅读和理解&#xff0c;因此关于Rust中的错误处理&#xff0c; 我将分以下3篇来讲。 另外&#xff0c;随着我们学习的不断深入&#xff0c;难度也会越来越大&#xff0c;但不用担心。接下来只需要让自己的脚步慢一些&#xff0c;认真搞…

微信商家转账到零钱怎么开通?场景模板

商家转账到零钱是什么&#xff1f; 使用商家转账到零钱这个功能&#xff0c;可以让商户同时向多个用户的零钱转账。商户可以使用这个功能用于费用报销、员工福利发放、合作伙伴货款或分销返佣等场景&#xff0c;提高效率。 商家转账到零钱的使用场景有哪些&#xff1f; 商家…

数据结构之bool类

bool类 bool 是布尔类。它是最简单的一个类&#xff0c;其取值有两种&#xff0c;1和O&#xff0c;即 True 和 False。可以这样简单地理解&#xff0c;除了1和0以及 True 和 False 的情况之外&#xff0c;但凡有值&#xff08;非空&#xff09;即为真&#xff0c;但凡无值&…

C#编程-属性和反射

属性和反射 属性是将元数据信息和行为添加到应用程序代码中的简单技术。属性是允许您将声明信息添加到程序的元素。此声明信息在运行时用途广泛,可使用应用程序开发工具在设计时使用。 介绍属性 对象是由其属性值描述的。例如,汽车可以使用它的构造、型号或颜色来描述。类似…

遇到问题不要慌,轻松搞定内存泄露

当一个系统在发生 OOM 的时候&#xff0c;行为可能会让你感到非常困惑。因为 JVM 是运行在操作系统之上的&#xff0c;操作系统的一些限制&#xff0c;会严重影响 JVM 的行为。故障排查是一个综合性的技术问题&#xff0c;在日常工作中要增加自己的知识广度。多总结、多思考、多…

PVE虚拟机配置文件恢复(qm list不显示虚拟机,web控制台看不到虚拟机)

本文章的目的是故障后复盘&#xff1a; 故障现象在命令行执行qm list不显示虚拟机&#xff0c;web控制台看不到虚拟机&#xff0c;网上查不到相关现象的处理办法。 处理思路&#xff1a;虚拟机还在正常工作&#xff0c;通过查看kvm进程ps aux | grep kvm&#xff0c;百度查看…

【RTOS】快速体验FreeRTOS所有常用API(11)打印空闲栈、CPU占用比

目录 十一、调试11.1 打印任务空闲栈11.2 打印所有任务栈信息11.3 CPU占用比11.4 空闲任务和钩子函数 十一、调试 该部分在上份代码基础上修改得来&#xff0c;代码下载链接&#xff1a; https://wwzr.lanzout.com/in63o1lauwwh 密码:9bhf 该代码尽量做到最简&#xff0c;不添加…

怎么安装IK分词器

.安装IK分词器 1.在线安装ik插件&#xff08;较慢&#xff09; # 进入容器内部 docker exec -it elasticsearch /bin/bash ​ # 在线下载并安装 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elastics…

一个程序员“玩”出来的网站:每月成本仅 350 元,如今赚了 16.4 万元

很难想象&#xff1a;一个每月运行成本不到 50 美元&#xff08;约人民币 358 元&#xff09;的网站. 是如何做到收入 2.3 万美元&#xff08;约人民币 16.4 万元&#xff09;的&#xff1f; ** 如果你也对网站开发感兴趣&#xff1f; ** 首先&#xff0c;这个网站只有创始…

​LeetCode解法汇总82. 删除排序链表中的重复元素 II

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给定一个已排序的链表的头 head &#xf…

年终总结各类表格模板Excel,大屏可视化,PPT总结等

马上就要进行年底总结,很多职场人找不到模板而浪费很多时间 今天就给大家分享一些常用的模板,报表,可视化,大屏,PPT汇报,表格等。 AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作 PowerBI 商业智能 68集 数据库Mysql 8.0 54集 数据库Oracle 21C 142集 Office 2021实战应…

[Docker] Docker为什么出现

Docker为什么出现 一款产品&#xff1a; 开发–上线 -->两套环境 | 应用配置 开发即运维&#xff01; 环境配置十分麻烦&#xff0c;每一个机器都要部署环境&#xff08;Redis, ES, Hadoop&#xff09; 费时费力 项目带上配置环境安装打包。 传统&#xff1a; 开发jar&…

Python——VScode安装

⼀、下载安装 [root192 ~]# rpm --import https://packages.microsoft.com/keys/microsoft.asc[root192 ~]# sh -c echo -e "[code]\nnameVisualStudio Code\nbaseurlhttps://packages.microsoft.com/yumrepos/vscode\nenabled1\ngpgcheck1\ngpgkeyhttps://packages.mi…

华清远见作业第二十八天——网络编程(第三天)

思维导图&#xff1a; 向服务器发送数据实现下载 代码&#xff1a; #include <a.h> void menu(); int download(int cfd, struct sockaddr_in sin); #define SER_PORT 69 #define SER_IP "192.168.125.4" int main(int argc, const char *argv[]) {//1创建用…

基于ssm的线上旅游体验系统+vue论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…