STM32超级蓝牙小车——基于STM32F103C8T6的多功能蓝牙小车(PID循迹、跟踪、有源蜂鸣器播放音乐、蓝牙遥控、AD采集+DMA转运等超多元素小车)

这篇具有很好参考价值的文章主要介绍了STM32超级蓝牙小车——基于STM32F103C8T6的多功能蓝牙小车(PID循迹、跟踪、有源蜂鸣器播放音乐、蓝牙遥控、AD采集+DMA转运等超多元素小车)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、项目时间:2023.7.24~11.26

二、实现效果:通过蓝牙控制小车运动与模式转换

                        模式一:循迹模式

                        模式二:跟踪模式

                        模式三:音乐模式

                        模式四:控制运动模式

三、使用模块:

  1. STM32F103C8T6核心板 * 1
  2. L298N电机驱动模块 * 1
  3. TCRT5000L五路红外循迹传感器模块 * 1
  4. DC3V-6V黄色直流减速电机-TT * 4
  5. 锂电池组电源 6V  * 1
  6. OLED屏幕-四针 * 1
  7. DC - DC 12V装3.3v 5v 12v 电源模块
  8. HC-SR04超声波模块
  9. 光敏模块+热敏模块
  10. 八个灯
  11. 蓝牙模块

下面是超级蓝牙小车实物图:

stm32f103c8t6蓝牙模块,stm32,嵌入式硬件,单片机

 需要用到的资源如下:

1,车轮:TIM2-CH1,CH2  => PA0,PA1 + PB0,PB1,PB2,PB10,PB11

2,蓝牙:USART-TX,RX => PA9 PA10 

3,超声波:TIM-CH1,CH2 => PA6 PA7

4,OLED:  PB8,PB9

5,ADC+DMA:  PA3,PA4(采集两个数据:温度与光强)

6,舵机:TIM4-CH2  => PB7

7,循迹模块: PA15,PB3~6

8,炫灯

9:蜂鸣器

下面的表格是引脚使用的直观表格

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
PA 1 1 X 5 5 9 3 3 8 2 2 8 8 / / 7
PB 1 1 / 7 7 7 7 6 4 4 1 1 8 8 8

8

四、代码

因为使用到的模块很多,一些基础配置的代码就不放出来了,下面是一些重要部分的代码

1,循迹模块代码 Trailing.c

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "Trailing.h"
#include "Motor.h"
#include "PID.h"

int Speed_Begin = 50; //extern 后面需要加上->变量以及变量的类型


/*====================================
函数	:五路循迹模块加权函数
参数	:无
返回值	:返回五路加权的结果
描述	:通过对五路循迹情况进行加权,输出
		 到PID函数作为误差参数,即将数字情
		 况转变为误差。
====================================*/
int Trailing_Speed(void)
{
//	int Flag;
	int Sum,Speed_1;
	Sum = 0;
//	Flag = 0;
	if(LED1==1) Sum=Sum-20;
	if(LED2==1) Sum=Sum-10;
	if(LED3==1) Sum=Sum  ;
	if(LED4==1) Sum=Sum+10;
	if(LED5==1) Sum=Sum+20;
	Speed_1 = (int)Sum;
	return(Speed_1);
//	if(LED1==0) Sum=Sum-20,Flag++;
//	if(LED2==0) Sum=Sum-10,Flag++;
//	if(LED3==0) Sum=Sum   ,Flag++;
//	if(LED4==0) Sum=Sum+10,Flag++;
//	if(LED5==0) Sum=Sum+20,Flag++;
//	Speed_1 = (int)Sum / Flag + Speed_Begin;
//	return(Speed_1);
}

/*====================================
函数	:运动轨迹调整函数
参数	:无
返回值	:无
描述	:先通过pid计算出现在的速度值
		 如,速度值等于初始值,直走
			 速度值小于初始值,左转
		  	 速度值大于初始值,右转
====================================*/
void Trailing_Adjust(void)
{
	int Speed;
	Speed = Speed_Begin + Position_PID(Trailing_Speed());
	
	if(Speed == Speed_Begin) //做一个速度的大小判断,进而判断此时对应的转向
	{
		delay_ms(1);
		Motor_Straight(Speed);
	}
	else if	(Speed < Speed_Begin) 	Motor_Left(Speed - 2 *Position_PID(Trailing_Speed()));
	else 							Motor_Right(Speed);
}

/*====================================
函数	:五路循迹模块情况汇总函数
参数	:无
返回值	:五路循迹情况
描述	:通过对五路循迹的情况进行计算得到
		 一个五位数的数字,便于输出在OLED
		 显示屏上,例:00001—最右边一个
		 被遮挡
====================================*/
int Track_State(void)
{
	u16 LED;
	LED = (LED1 * 10000) + (LED2 * 1000)+ (LED3 * 100) + (LED4 * 10) + LED5;
	return (LED);
}


//u8 Trailing_Filter_One(u8 Value,u8 Get_Io)   //滤波1
//{
//	u8 Count=0;
//	u8 New_Value;
//	New_Value=Get_Io;
//	while(Value!=New_Value)
//	{
//		Count++;
//		if(Count>=5) return New_Value;
//		Delay_us(1);
//		New_Value=Get_Io;
//	}
//	return Value;
//}

//int Trailing_Filter_Two(void)	//滤波2+角度结算
//{
//	int Speed_ori;
//	for(i=0;i<16;i++)
//	{
//		RAY[i]=1;
//		RAY_try[i]=1;		
//	}
//	for(i=0;i<200;i++) //800
//	{
//		RAY_try[0]=Trailing_Filter_One(1,LED1);//滤波
//		RAY_try[1]=Trailing_Filter_One(1,LED2);
//		RAY_try[2]=Trailing_Filter_One(1,LED3);
//		RAY_try[3]=Trailing_Filter_One(1,LED4);
//		RAY_try[4]=Trailing_Filter_One(1,LED5);
//		
//		RAY[0]&=RAY_try[0];
//		RAY[1]&=RAY_try[1];
//		RAY[2]&=RAY_try[2];
//		RAY[3]&=RAY_try[3];
//		RAY[4]&=RAY_try[4];
//		delay_us(100);             //增加稳定性
//	}
//	Speed_ori=Trailing_Speed();
//	return (Speed_ori);
//}

Trailing.h

#ifndef __TRAILING_H
#define __TRAILING_H

#include "sys.h"

#define LED1 PAin(15)
#define LED2 PBin(3)
#define LED3 PBin(4)
#define LED4 PBin(5)
#define LED5 PBin(6)

#define LED2_1(a) 		if(a) \
							  GPIO_SetBits(TRAILING_PORT_2,IO4); \
						else  GPIO_ResetBits(TRAILING_PORT_2,IO4);


#define TRAILING_PORT_1   GPIOA
#define TRAILING_PORT_2   GPIOB
#define TRAILING_CLK_1    RCC_APB2Periph_GPIOA
#define TRAILING_CLK_2    RCC_APB2Periph_GPIOB

#define IO5 GPIO_Pin_15
#define IO4 GPIO_Pin_3
#define IO3 GPIO_Pin_4
#define IO2 GPIO_Pin_5
#define IO1 GPIO_Pin_6

//起始速度值
extern int Speed_Begin;

//#define Ray[0] PAin(8)
//#define Ray[1] PAin(9)
//#define Ray[2] PAin(10)
//#define Ray[3] PAin(11)
//#define Ray[4] PAin(12)

//#define LED1 PAin(8)
//#define LED2 PAin(9)
//#define LED3 PAin(10)
//#define LED4 PAin(11)
//#define LED5 PAin(12)

//循迹模块初始化
void Trailing_Init(void);

//五路循迹模块加权函数
int Trailing_Speed(void);

//五路循迹模块情况汇总函数
int Track_State(void);

//u8 Trailing_Filter_One(u8 Value,u8 Get_Io);
//int Trailing_Filter_Two(void);

//运动轨迹调整函数
void Trailing_Adjust(void);

#endif



//OLED_ShowString(1,1,"NUM:");	
//int NUM;
//while(1)
//{
//	Trailing_Speed();
//	Trailing_Adjust();
//	NUM = Track_State();
//	OLED_ShowNum(1,6,NUM,5);
//}



2、跟踪模块代码 HCSR04.c

#include "stm32f10x.h"                  // Device header
#include "sys.h"
#include "delay.h"
#include "PID.h"
#include "Motor.h"
#include "HCSR04.h"	
#include "OLED.h"

extern int Speed_Begin;
int Location_Begin = 30;

//超声波计数
u16 msHcCount = 0;


/*====================================
函数    :打开定时器函数
参数    :无
返回值    :无
描述    :通过使能TIMx
====================================*/
static void HCSR04_TimerOpen()  
{
   TIM_SetCounter(HCSR04_TIM,0);
   msHcCount = 0;
   TIM_Cmd(HCSR04_TIM, ENABLE); 
}

/*====================================
函数    :关闭定时器函数
参数    :无
返回值    :无
描述    :通过失能TIMx
====================================*/
static void HCSR04_TimerClose()
{
   TIM_Cmd(HCSR04_TIM, DISABLE); 
}    

//定时器中断
void TIM3_IRQHandler(void)  
{
   if (TIM_GetITStatus(HCSR04_TIM, TIM_IT_Update) != RESET)  
   {
       TIM_ClearITPendingBit(HCSR04_TIM, TIM_IT_Update); 
       msHcCount++;
   }
}

/*====================================
函数    :获取定时器计数器值函数
参数    :无
返回值    :获取高电平时间
描述    :通过获取定时器计数器值,
         通过计算得到高电平时间
====================================*/
u32 HCSR04_GetEchoTimer()
{
   u32 t = 0;
   t = msHcCount * 1000;
   t += TIM_GetCounter(HCSR04_TIM);
   HCSR04_TIM -> CNT = 0;  
   delay_ms(50);
//   msHcCount = 0;
   return t;
}

/*====================================
函数    :通过定时器3计数器值推算距离函数
参数    :无
返回值    :超声波测距的距离
描述    :通过控制io口的开关与超声波模块的使用
         ,通过计算值得到最终模块的测距
====================================*/
float Hcsr04_GetLength(void )
{
   u32 t = 0;
   int i = 0;
   float lengthTemp = 0;
   float sum = 0;
   while(i!=2)
   {
      TRIG_Send = 1;      
      delay_us(20);
      TRIG_Send = 0;
      while(ECHO_Reci == 0);      
      HCSR04_TimerOpen();
      i = i + 1;
      while(ECHO_Reci == 1);
      HCSR04_TimerClose();        
      t = HCSR04_GetEchoTimer();        
      lengthTemp = ((float)t / 58.0);//cm
      sum = lengthTemp + sum ;
   }
    lengthTemp = sum/2.0;
    return lengthTemp;
}


/*====================================
函数    :位置调整函数
参数    :无
返回值    :无
描述    :先通得出现在的距离值(上限~下限),
          然后进行PID计算得出PID距离值 
         如,距离值大于上限值,前进
             距离值小于下限值,后退
               距离值在上下限之间,停止     
====================================*/
void Location_Adjust(void)
{
    int Speed;
    float Speed_1,Speed_2;
    Speed_1 = Location_PID(Hcsr04_GetLength());
    Speed_2 = (int)Speed_1;
    Speed = Speed_Begin + Speed_2;
    if (Location_Begin >= Hcsr04_GetLength())    Motor_Retreat(Speed - 2 * Speed_2);
    else if(Location_Begin + 5 < Hcsr04_GetLength())    Motor_Straight(Speed);
}

HCSR04.h

#ifndef  __HCSR04_H
#define  __HCSR04_H

#include "sys.h"

//超声波硬件接口定义
#define HCSR04_TIM     	TIM3
#define HCSR04_TIMCLK   RCC_APB1Periph_TIM3

#define HCSR04_PORT     GPIOA
#define HCSR04_CLK      RCC_APB2Periph_GPIOA
#define HCSR04_TRIG     GPIO_Pin_7		 //输出
#define HCSR04_ECHO     GPIO_Pin_6       //输入

#define ECHO_Reci  PAin(6)
#define TRIG_Send  PAout(7)

extern int Speed_Begin;
extern int Location_Begin;

//起始速度值
extern int Speed_Begin;
//起始距离值
extern int Location_Begin;

//配置超声波模块定时器
void HCSR04_NVIC(void);

//超声波模块初始化
void HCSR04_Init(void);

//打开定时器
static void HCSR04_TimerOpen(void);

//关闭定时器
static void HCSR04_TimerClose(void);

//获取定时器计数器值
u32 HCSR04_GetEchoTimer(void);

//推算距离
float Hcsr04_GetLength(void );

//位置调整
void Location_Adjust(void);


#endif



//	 while(1) 
//	 {		
	 Motor_Retreat(50);
//		 Location_Adjust();
//		 delay_ms(10);
//	 }			


3,蜂鸣器音乐模块 Beep.c

#include "stm32f10x.h"                  // Device header
#include "beep.h"


int melody[] = {50, 50, 50, 50, 200, 200, 200, 400, 400, 500, 500, 500};

/*====================================
函数	:发声1基本函数
参数	:
返回值	:
描述	:
====================================*/
void Sound(u16 frq)
{
	u32 time;
	if(frq != 1000)
	{
//		time = 500000/((u32)frq);
		time = 100000/((u32)frq);
		PBeep = 1;
		delay_us(time);
		PBeep = 0;
		delay_us(time);
	}
	else
		delay_us(1000);
}

/*====================================
函数	:发声2基本函数
参数	:
返回值	:
描述	:
====================================*/
void Sound2(u16 time)
{
    PBeep = 1;
    delay_ms(time);
    PBeep = 0;
    delay_ms(time);
}

/*====================================
函数	:成功音效(先快后慢)
参数	:
返回值	:
描述	:得得得  得  得  得
====================================*/
void play_successful(void)
{
    int id=0;
    for(id = 0 ;id < 5 ;id++)
    {
        Sound2(melody[id]);
    }
}

/*====================================
函数	:失败音效(先快后慢)
参数	:
返回值	:
描述	:得  得  得   得得得
====================================*/
void play_failed(void)
{
    int id=0;
    for(id = 5 ;id >=0 ;id--)
    {
        Sound2(melody[id]);
    }
}


/*====================================
函数	:播放音乐
参数	:
返回值	:
描述	:音乐库中有:红尘情歌
					 小燕子
		(注意:music和time前面两版含
		有13这个音,但由于不能正常发音,
		后面的为删除这个音节的版本)
====================================*/
void play_music(void)
{
	//              低7  1   2   3   4   5   6   7  高1 高2 高3 高4 高5 不发音
	uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表
//	//红尘情歌
//	u8 music[]={5,5,6,8,7,6,5,6,13,13,//音调
//                5,5,6,8,7,6,5,3,13,13,
//                2,2,3,5,3,5,6,3,2,1,
//                6,6,5,6,5,3,6,5,13,13,

//                5,5,6,8,7,6,5,6,13,13,
//                5,5,6,8,7,6,5,3,13,13,
//                2,2,3,5,3,5,6,3,2,1,
//                6,6,5,6,5,3,6,1,	

//                13,8,9,10,10,9,8,10,9,8,6,
//                13,6,8,9,9,8,6,9,8,6,5,
//                13,2,3,5,5,3,5,5,6,8,7,6,
//                6,10,9,9,8,6,5,6,8};	
//	u8 time[] = {2,4,2,2,2,2,2,8,4, 4, //时间
//                2,4,2,2,2,2,2,8,4, 4, 
//                2,4,2,4,2,2,4,2,2,8,
//                2,4,2,2,2,2,2,8,4 ,4, 

//                2,4,2,2,2,2,2,8,4, 4, 
//                2,4,2,2,2,2,2,8,4, 4, 
//                2,4,2,4,2,2,4,2,2,8,
//                2,4,2,2,2,2,2,8,

//                4, 2,2,2, 4, 2,2,2, 2,2,8,
//                4, 2,2,2,4,2,2,2,2,2,8,
//                4, 2,2,2,4,2,2,5,2,6,2,4,
//                2,2 ,2,4,2,4,2,2,12};	

//	//小燕子
//		u8 music[]={3,5,8,6,5,13,//音调
//	                3,5,6,8,5,13,
//	                8,10,9,8,9,8,6,8,5,13,
//					3,5,6,5,6,8,9,5,6,13,
//					3,2,1,2,13,
//					2,2,3,5,5,8,2,3,5,13};
//		u8 time[] ={2,2,2,2,6,4,//时间  
//				2,2,2,2,6,4,
//                6,2,4,4,2,2,2,2,6,4,
//				6,2,4,2,2,4,2,2,6,4,
//				2,2,4,6,4,
//				4,2,2,4,4,4,2,2,6,4};

	//删去音节13的两首歌版本
	//红尘情歌
	u8 music[]={5,5,6,8,7,6,5,6,//音调
                5,5,6,8,7,6,5,3,
                2,2,3,5,3,5,6,3,2,1,
                6,6,5,6,5,3,6,5,

                5,5,6,8,7,6,5,6,
                5,5,6,8,7,6,5,3,
                2,2,3,5,3,5,6,3,2,1,
                6,6,5,6,5,3,6,1,	

                8,9,10,10,9,8,10,9,8,6,
                6,8,9,9,8,6,9,8,6,5,
                2,3,5,5,3,5,5,6,8,7,6,
                6,10,9,9,8,6,5,6,8};	
	u8 time[] = {2,4,2,2,2,2,2,8, //时间
                2,4,2,2,2,2,2,8, 
                2,4,2,4,2,2,4,2,2,8,
                2,4,2,2,2,2,2,8, 

                2,4,2,2,2,2,2,8,
                2,4,2,2,2,2,2,8,
                2,4,2,4,2,2,4,2,2,8,
                2,4,2,2,2,2,2,8,

                2,2,2, 4, 2,2,2, 2,2,8,
                2,2,2,4,2,2,2,2,2,8,
                2,2,2,4,2,2,5,2,6,2,4,
                2,2 ,2,4,2,4,2,2,12};					
				
//	//小燕子
//		u8 music[]={3,5,8,6,5,//音调
//	                3,5,6,8,5,
//	                8,10,9,8,9,8,6,8,5,
//					3,5,6,5,6,8,9,5,6,
//					3,2,1,2,
//					2,2,3,5,5,8,2,3,5};
//		u8 time[] ={2,2,2,2,6,//时间  
//				2,2,2,2,6,
//                6,2,4,4,2,2,2,2,6,
//				6,2,4,2,2,4,2,2,6,
//				2,2,4,6,
//				4,2,2,4,4,4,2,2,6};

//	u8 music[]={13,1,2,3,4,5,6,7,8};//测试基础音
//	u8 time[] ={4, 4,4,4,4,4,4,4,4};
				
	u32 yanshi;
	u16 i,e;
	yanshi = 2;//10	;	4;	2;
	for(i=0;i<sizeof(music)/sizeof(music[0]);i++)
	{
		for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++)
		{
			Sound((u32)tone[music[i]]);
		}	
	}
	GPIO_ResetBits(BEEP_PORT,BEEP_IO);
	
}

void play_music2(void)
{
	//              低7  1   2   3   4   5   6   7  高1 高2 高3 高4 高5 不发音
	uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表
	//小燕子
		u8 music[]={3,5,8,6,5,//音调
	                3,5,6,8,5,
	                8,10,9,8,9,8,6,8,5,
					3,5,6,5,6,8,9,5,6,
					3,2,1,2,
					2,2,3,5,5,8,2,3,5};
		u8 time[] ={2,2,2,2,6,//时间  
				2,2,2,2,6,
                6,2,4,4,2,2,2,2,6,
				6,2,4,2,2,4,2,2,6,
				2,2,4,6,
				4,2,2,4,4,4,2,2,6};

	u32 yanshi;
	u16 i,e;
	yanshi = 2;//10	;	4;	2;
	for(i=0;i<sizeof(music)/sizeof(music[0]);i++)
	{
		for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++)
		{
			Sound((u32)tone[music[i]]);
		}	
	}
	GPIO_ResetBits(BEEP_PORT,BEEP_IO);
}

音乐模块是学习博客其他优秀博主的写法

Beep.h

#ifndef __BEEP_H
#define __BEEP_H

#include "sys.h"
#include "stdlib.h"	  
#include "delay.h"

#define BEEP_PORT    GPIOA
#define BEEP_CLK     RCC_APB2Periph_GPIOA
#define BEEP_IO    	 GPIO_Pin_5	 //输出

//定义GPIOB的位地址变量宏,位输入宏,输出宏
#define PBeep PAout(5)

//Beep蜂鸣器初始化
void Beep_Init(void);

//发出声音1
void Sound(u16 frq);

//发出声音2
void Sound2(u16 time);

//放音乐
void play_music(void);

//放音乐2
void play_music2(void);


//播放成功
void play_successful(void);

//播放失败
void play_failed(void);

#endif

4.串口模块代码  Serial.c

这部分代码最多,即讲主函数操作都封装起来了,因为我们是蓝牙小车对吧

#include "stm32f10x.h"                  // Device header
#include "Serial.h"

uint8_t Serial_RxPacket[4];
uint8_t Serial_TxPacket[4];

char Serial_RxPacket_W[100];
uint8_t Serial_RxFlag;
uint8_t RxData;


int NUM;//A -> 循迹模式:代表此时遮挡情况
float length;//B -> 跟踪模式:代表此时相对遮挡物的距离

void Serial_Pattern(void)
{
//	Serial_SendString("OPEN_OK\r\n");
//	 printf("正在为您播放“程序开始”\r\n");
	OLED_ShowString(1,1,"OK1");
	if(Serial_RxFlag == 1)
	{
		OLED_Clear();//OLED清屏
		OLED_ShowString(1,1,"OK1");
		if (strcmp(Serial_RxPacket_W, "A") == 0)
		{
			//先停下我们的车
			Serial_Stop();
			//做好进入模式的准备
			Serial_Pattern_into(1);
			OLED_ShowString(1,1,"OK  12");	
			Serial_SendString("Pattern_A_OK\r\n");
			//在没有新的指令到来一直保持循迹模式
			while(Serial_RxFlag == 0) Serial_PAT_Trailing();
		}
		else if (strcmp(Serial_RxPacket_W, "B") == 0)
		{
			Serial_Stop();
			Serial_Pattern_into(2);
			Serial_SendString("Pattern_B_OK\r\n");
			while(Serial_RxFlag == 0) Serial_PAT_Track();
		}
		else if (strcmp(Serial_RxPacket_W, "C") == 0)
		{
			Serial_Stop();
//			OLED_ShowString(1,1,"OK  10");	
			Serial_Pattern_into(3);
			Serial_SendString("Pattern_C_OK\r\n");
			while(Serial_RxFlag == 0) Serial_PAT_Music();
		}
		else if (strcmp(Serial_RxPacket_W, "w") == 0)
		{
			Serial_RxFlag = 0;//清空蓝牙接收标志位
			Serial_Straight(60);
		}
		else if (strcmp(Serial_RxPacket_W, "s") == 0)
		{
			Serial_RxFlag = 0;//清空蓝牙接收标志位
			Serial_Retreat(60);
		}
		else if (strcmp(Serial_RxPacket_W, "a") == 0)
		{
			Serial_RxFlag = 0;//清空蓝牙接收标志位
			Serial_Left(60);
		}
		else if (strcmp(Serial_RxPacket_W, "d") == 0)
		{
			Serial_RxFlag = 0;//清空蓝牙接收标志位
			Serial_Right(60);
		}
		else
		{
			Serial_Stop();
			Serial_PAT_XX();
		}

	}
}


/*====================================
函数	:进入模式准备
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_Pattern_into(u8 x)
{
//	int i;
	
	Serial_RxFlag = 0;//清空蓝牙接收标志位
	
//	LED_Pattern_1();//灯光效果一 -> 进入模式一循迹模式
//	LED_Pattern_1();
//	OLED_ShowString(1,1,"Trailing_Mode_OK");//显示正在做的功能
		
	Serial_LEDPx(x);
	
	OLED_ShowString(3,1,"LI:");
	OLED_ShowString(4,1,"TI:");
	
//	for(i = 0; i <= 0; i++)
//	{
//		Servo_SP();
//		delay_ms(5);
//	}
	
//	i = 0;
	
	play_successful();//成功效果音
	GPIO_SetBits(BEEP_PORT,BEEP_IO);//因为蜂鸣器不会自己关
}

/*====================================
函数	:进入模式的灯光与OLED准备
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_LEDPx(u8 x)
{
	switch(x)
	{
		case 1:	LED_Pattern_1(); LED_Pattern_1();  break;
		case 2:	LED_Pattern_2(); LED_Pattern_2();  break;
		case 3:	LED_Pattern_3(); LED_Pattern_3();  break;
	}
	
	switch(x)
	{
		case 1:	OLED_ShowString(1,1,"Trailing_Mode_OK"); break;
		case 2:	OLED_ShowString(1,1,"Track_Mode_OK");	 break;	
		case 3:	OLED_ShowString(1,1,"Music_Mode_OK");    break;
	}
}	


/*====================================
函数	:循迹模式操作(A)
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_PAT_Trailing(void)
{
	Trailing_Adjust();
	NUM = Track_State();
	
	OLED_ShowString(2,1,"NUM:00000");
	OLED_ShowNum(2,6,NUM,5);
	Serial_PAT_TL();//每次操作的同时都显示一下此时的温度与光强
	
//	printf("遮挡情况:%dcm\n",NUM);
}
/*====================================
函数	:跟踪模式操作(B)
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_PAT_Track(void)
{
	 Location_Adjust();
	 length = Hcsr04_GetLength();
	
	 OLED_ShowString(2,1,"Lo:");
	 OLED_ShowNum(2,4,length,3);
	 Serial_PAT_TL();//每次操作的同时都显示一下此时的温度与光强
	
//	 printf("距离:%.3fcm\n",length);
}
/*====================================
函数	:音乐模式操作(C)
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_PAT_Music(void)
{
	 int i;
	 Serial_PAT_TL();//每次操作的同时都显示一下此时的温度与光强
//	 printf("正在为您播放“红尘情歌”");
	 OLED_ShowString(2,1,"open'hongchenqingge'");
	 play_music();
	
	for(i = 0; i <= 0; i++)
	{
		Servo_SP();//舵机来回转动函数
		delay_ms(5);
	}
	i = 0;
	 delay_ms(2);
	 Serial_PAT_TL();//实时显示温度与光强
	
	 Serial_PAT_TL();//每次操作的同时都显示一下此时的温度与光强
//	 printf("正在为您播放“小燕子”");
	 OLED_ShowString(2,1,"open'xiaoyanzi'");
	 play_music2();
	
	for(i = 0; i <= 0; i++)//由于串口与定时器的尴尬关系,这里加一个循环可以使舵机转动更加稳定
	{
		Servo_SP();
		delay_ms(5);
	}
	i = 0;
	 delay_ms(2);
	 Serial_PAT_TL();//实时显示温度与光强

}

/*====================================
函数	:错误指令模式操作(D)
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_PAT_XX(void)
{
	Serial_RxFlag = 0;//清空蓝牙接收标志位
	OLED_Clear();//OLED清屏
	
	Serial_SendString("ERROR_COMMAND\r\n");
	OLED_ShowString(1, 1, "ERROR_RX");
	OLED_ShowString(2, 1, "ERROR_COMMAND");
	OLED_ShowString(3,1,"LI:");
	OLED_ShowString(4,1,"TI:");
				
	
	play_failed();//失败效果音
	GPIO_SetBits(BEEP_PORT,BEEP_IO);//因为蜂鸣器不会自己关
	while(Serial_RxFlag == 0)
	{
		Servo_SP();//舵机来回摆动一次
		delay_ms(2);
		Serial_PAT_TL();//实时显示温度与光强
	}
}

/*====================================
函数	:判断此时温度与光强
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_PAT_TL(void)
{
	if(ADValue[0] >= 2000)
	{
//		OLED_ShowString(3,4,"SO Light");
		OLED_ShowNum(3,4,ADValue[0],4);//发送数据包储存数组第一位数据
	}
	else 
	{
//		OLED_ShowString(3,4,"Not Light");
		OLED_ShowNum(3,4,ADValue[0],4);
	}
	
	if(ADValue[1] >= 2100)
	{
//		OLED_ShowString(4,4,"SO hot");
		OLED_ShowNum(4,4,ADValue[1],4);//发送数据包储存数组第二位数据
	}
	else
	{
//		OLED_ShowString(4,4,"Not hot");
		OLED_ShowNum(4,4,ADValue[1],4);
	}
}

/*====================================
函数	:运动控制指令——前进,后退,左转,右转
参数	:无
返回值	:无
描述	:
====================================*/
void Serial_Straight(u8 x)
{
	while(Serial_RxFlag == 0)
	{
		Motor_Straight(x);
	}
}
void Serial_Retreat(u8 x)
{
	while(Serial_RxFlag == 0)
	{
		Motor_Retreat(x);
	}
}
void Serial_Right(u8 x)
{
	Motor_Right(x);
	delay_ms(1500);
	
	while(Serial_RxFlag == 0) 
	{
		Motor_Straight(x);
	}
}
void Serial_Left(u8 x)
{
	Motor_Left(x);
	delay_ms(1200);
	while(Serial_RxFlag == 0) 
	{
		Motor_Straight(x);
	}
}
void Serial_Stop(void)
{
	Motor_Stop();
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>
#include <stdarg.h>
#include "Key.h"
#include "string.h"

#include "OLED.h"
#include "Motor.h"
#include "Trailing.h"
#include "PID.h"
#include "HCSR04.h"
#include "AD.h"
#include "PWM.h"
#include "led.h"
#include "Beep.h"
#include "Servo.h"


extern uint8_t Serial_RxPacket[4];
extern uint8_t Serial_TxPacket[4];
extern char Serial_RxPacket_W[100];
extern uint8_t Serial_RxFlag;//传输数据标志位
extern uint8_t RxData;

//发送HEX数据包函数
void Serial_SendPacket(uint8_t *ABC,uint8_t Length);

//清除标志位Serial_RxFlag
uint8_t Serial_GetRxFlag(void);

封装数据函数(接收数据)
//uint8_t Serial_GetRxData(void);

//串口中断
//void USART1_IRQHandler(void);	
void USART1_IRQHandler(void);	

//串口初始化
void Serial_Init(void);

//串口发送函数
void Serial_SendByte(uint8_t Byte);//一个字节
void Serial_SendArray(uint8_t *Array, uint16_t Length);//一个数组
void Serial_SendString(char *String);//一串字符串(文本)
uint32_t Serial_Pow(uint32_t X,uint32_t Y);//次方函数(与发送数函数结合使用)
void Serial_SendNumber(uint32_t Number,uint8_t Length);//一个十六进制数

//printf的底层函数
int fputc(int ch, FILE *f);

//sprintf封装函数
void Serial_Printf(char *format, ...);


/*====================================
printf打印函数经常乱码解决方案

1-UTF8不乱码方案

工程选项(魔术棒) -> C/C++ ->	Misc Controles ->填上: --no-multibyte-chars 


2-GB2312编码

设置 -> Encoding -> 删掉汉字 -> 关闭小文件再打开 -> 再编辑文字 -> 串口选择GBK编码
====================================*/



extern int NUM;//A -> 循迹模式:代表此时遮挡情况
extern float length;//B -> 跟踪模式:代表此时相对遮挡物的距离

//模式选择
void Serial_Pattern(void);

//进入模式准备
void Serial_Pattern_into(u8 x);

//准备灯光与OLED模式
void Serial_LEDPx(u8 x);

//判断此时温度与光强
void Serial_PAT_TL(void);

//循迹模式操作——A	B	C	D
void Serial_PAT_Trailing(void);//A-循迹模式
void Serial_PAT_Track(void);//B-跟踪模式
void Serial_PAT_Music(void);//C-音乐模式
void Serial_PAT_XX(void);//D-错误指令模式

//运动控制指令——前进,后退,左转,右转
void Serial_Straight(u8 x);//前进
void Serial_Retreat(u8 x);//后退
void Serial_Right(u8 x);//右转
void Serial_Left(u8 x);//左转
void Serial_Stop(void);//停下

#endif

5,PID模块代码 PID.c

#include "stm32f10x.h"                  // Device header
#include "PID.h"
#include "Trailing.h"
#include "Motor.h"
#include "Delay.h"
#include "HCSR04.h"	
#include "usart.h"	 
#include "OLED.h"

//结构体声明
PIDPara Location;
PIDPara Speed;

extern int Speed_Begin;
extern int Location_Begin;

/*====================================
函数	:循迹PID函数(位置式)
参数	:误差
返回值	:经过PID计算后的量
描述	:通过pid计算误差,将其加到原有的
		 计算中,得到需要的新速度量
====================================*/
float Position_KP = 1.8;//例:速度80 -> 1.8 大概
//	  Position_KI = 0.1,
//	  Position_KD = 0;

int Position_PID(int target)
{
	static float Pwm ,
				 Bias ;
//				 Interqral_Bias ; 
//				 Last_Bias;
	
	Bias = target;
//	Interqral_Bias += Bias;
//	
//	if(Interqral_Bias > + 6)	Interqral_Bias = + 6;
//	if(Interqral_Bias < - 6)	Interqral_Bias = - 6;
	
	Pwm = Position_KP * Bias ;
//		  Position_KI * Interqral_Bias ; 
//		  Position_KD * (Bias - Last_Bias);
	
//	Last_Bias = Bias;
	
	return Pwm;
}



/*====================================
函数	:跟踪PID函数(位置环+速度环)
参数	:误差
返回值	:经过PID计算后的量
描述	:通过pid计算误差,将其加到原有的
		 计算中,得到需要的新速度量
====================================*/
void PID_Init(void)
{
	//pid 参数初始化
	Location.Kp = 1;			
	Location.Ki = 0;	        
	Location.Kd = 0;	        
	                        
    Location.C0 = 0;	
	Location.C1 = 0;  
	Location.Cmin = 0;
	Location.Cmax = 0;
	
	Location.E0 = 0;
	Location.E1 = 0;
	Location.E2 = 0;
	
	Speed.Kp = 1.5;					
	Speed.Ki = 0;	                
	Speed.Kd = 0;	                
	                        	
	Speed.C0 = 0;	
    Speed.C1 = 0;  
    Speed.Cmin = 0;
    Speed.Cmax = 0;

	Speed.E0 = 0;
    Speed.E1 = 0;
    Speed.E2 = 0;
}

/*====================================
函数	:跟踪PID函数(位置式)
参数	:误差
返回值	:经过PID计算后的量
描述	:通过pid计算误差,将其加到原有的
		 计算中,得到需要的新速度量
====================================*/
int Location_PID(int target)
{
	static float Pwm ,
				 Bias;
//				 Last_Bias;
	
	Bias = target - Location_Begin;
	Pwm = 1 * Bias ;
//		  0.3 * (Bias - Last_Bias);

	//	Last_Bias = Bias;
	
	return Pwm;
}


//int Speed_PID(void)
//{
//	
//	static float Speed_Pwm ,
//				 Bias ,
//				 Last_Bias;
//	
//	Bias = Location_PID(Hcsr04_GetLength());

//	Speed_Pwm = Speed.Kp * Bias +
//		  Speed.Kd * (Bias - Last_Bias);
//	
//	Last_Bias = Bias;
//	
//	return Speed_Pwm;
//}

PID.h

#ifndef __PID_H
#define __PID_H

#include "sys.h"

extern int Speed_Begin;
extern int Location_Begin;

int Position_PID(int target);

//结构体
typedef struct
{
	//PID 参数系数K
	float Kp;
	float Ki;
	float Kd;

	//PID 控制量C:当前: C0  上一次:C1 范围控制量:Cmin Cmax 
	float C0;
	float C1;
	float C2;
	float Cmin;
	float Cmax;

//	//PID 变量:当前: C0  上一次:C1 范围控制量:Cmin Cmax 
//	static float Pwm;
//	static float Bias ;
//	static float Interqral_Bias;
//	static float Last_Bias;
	
    //PID 误差量E 当前: E0  上一次: E1 上上一次: E2
	float E0;
	float E1;
	float E2;	

}PIDPara; //PID 参数

//PID初始化
void PID_Init(void);

//循迹模式PID函数
int Speed_PID(void);

//跟踪模式PID函数
int Location_PID(int target);

#endif

其他还有AD+DMA转运代码,大家在下载中查看吧

五、制作小车需要注意的地方

1,要提早分配好io口,这次制作的过程中就出现了使用PB3的尴尬情况,还要增加释放函数

2,串口与定时器互相影响,我当时原测试模块时初始化互相影响,但是后面将它们初始化放在上下靠近,看教程是定时器见初始化再初始化串口,一次解决,不过在使用串口的同时使用舵机容易造成舵机的抖动,这边偶然增加一个for循环以及delay可以解决这个影响问题,还算是运气很好哈哈

下面是实现的效果展示,我把它做成视频发布在b站:

STM32超级蓝牙小车——基于STM32F103C8T6的多功能蓝牙小车(PID循迹、跟踪、有源蜂鸣器播放音乐、蓝牙遥控、AD采集+DMA转运等超多元素小车)

六、代码源文件

链接:https://pan.baidu.com/s/1WZF10LWIbbE5Mubth__VEQ 
提取码:grcc

这次的程序写了蛮多注释的,也是花了三天时间做的,感觉最大的问题就是串口与定时器关系的问题,程序上可能还存在错误,希望大家多多指正!!!文章来源地址https://www.toymoban.com/news/detail-763889.html

到了这里,关于STM32超级蓝牙小车——基于STM32F103C8T6的多功能蓝牙小车(PID循迹、跟踪、有源蜂鸣器播放音乐、蓝牙遥控、AD采集+DMA转运等超多元素小车)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于STM32F103C8T6与ESP8266的物联网智能温度采集与蓝牙OLED数字钟的设计与实现

    作者: 颜孙炜 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wC12xZrc-1673843611066)(C:UsersadminAppDataRoamingTyporatypora-user-imagesimage-20230110223524043.png)] 用STM32F103C8T6自有的RTC功能实现一款数字钟的设计,包括温度输入检测和显示模块、数字钟显示模块

    2024年02月02日
    浏览(60)
  • Qt实现安卓手机蓝牙通信并控制stm32f103c8t6驱动VFD屏

    Qt具有跨平台的特性所以非常适合写通信的demo,但是在这个例程中Qt蓝牙部分不支持Windows平台,安卓平台使用没问题。 Qt蓝牙主要涉及到三个类的使用: QBluetoothDeviceDiscoveryAgent //扫描周围蓝牙设备 QBluetoothLocalDevice //扫描本地蓝牙 QBluetoothSocket //建立蓝牙的socket读写 安卓不支

    2024年02月08日
    浏览(55)
  • 【基于STM32F103C8T6的智能风扇控制】

    本次分享的是基于STM32F103的智能风扇的制作,相关的硬件和部分源码可看下面哦,需要成品的可私(创作不易,拒绝白嫖) 喜欢就给小殷一个关注和点赞吧 演示视频链接 1、按键模式下通过按键进行风扇档数改变一共设置为4档 2、自动模式下根据环境温度而改变温度的转速和

    2024年02月12日
    浏览(88)
  • STM32项目设计:基于stm32f103c8t6智能电梯系统

    资料链接:待更新~ 哔哩哔哩视频链接:https://www.bilibili.com/video/BV17D4y1V7HG/?vd_source=e5082ef80535e952b2a4301746491be0(bilibili:化作尘my) 资料链接:https://pan.baidu.com/s/1qObK7KBl50RmFrAtoX12kw 提取码:iih4 实物链接:https://m.tb.cn/h.5h5mDXI?tk=MdxLWYHgKnz CZ0001 1、OLED显示当前楼层、目标楼层等信息

    2024年02月03日
    浏览(57)
  • 基于stm32f103c8t6连接阿里云上传信息

    很抱歉,作为一个AI助手,我无法提供一篇基于STM32F103C8T6连接阿里云上传信息的完整博客文章。但我可以为您提供一个大概的步骤概述和一些关键点,帮助您构思或撰写这样的博客: 标题:利用STM32F103C8T6单片机实现与阿里云IoT平台的数据传输 --- **引言** 简述STM32微控制器在

    2024年01月16日
    浏览(59)
  • 基于STM32F103C8T6ADC检测交流电压

    上篇文章写了硬件部分的实现思路,通过采样电阻的到小电压后经过二级放大电路得到单片机可处理的交流电压,此文介绍了如何采用单片机采集交流电压以及stm32ADC外设的使用。首先是硬件电路部分。  电路没有采用核心板,而是直接将芯片焊接到主板上,采用type-c接口供

    2024年02月12日
    浏览(56)
  • STM32f103c8t6模板的搭建-基于正点例程

           笔者认为正点编写的官方例程结构较为整洁,可以便于后期的例程开发,如果开发者对于项目开发中芯片要求较高的话,有很多人会选择正点的开发板,但是通常大多数是stm32初学者会选择用价格更为便宜的c8t6来进行学习,而正点选用的教程开发板大多都是些RC、ZE、

    2024年02月06日
    浏览(68)
  • 基于STM32F103C8T6的超声波测距应用

    #一、超声波HC_SR04简介 #二、超声波工作原理 #三、超声波测距步骤 #四、硬件接线 #五、项目代码 一、超声波HC_SR04简介 超声波传感器模块上面通常有两个超声波元器件,一个用于发射,一个用于接收。电路板上有四个引脚:VCC、GND、Trig(触发)、Echo(回应) 工作电压与电流

    2024年02月03日
    浏览(47)
  • 基于stm32f103c8t6的fft频率计

    之前项目中需要用到正弦信号的频率测量,也参考了几个大佬的博客(链接如下),但可能是由于stm32的型号不匹配,虽然也在网上查了一些需要修改的地方,但结果一直不太对,后来经过自己摸索结果终于对了,在这里给大家分享下,具体原理不在赘述。 参考的部分大佬博

    2024年02月14日
    浏览(74)
  • HX711压力传感器(基于STM32F103C8T6)

    HX711模块是我们目前比较常见的压力传感器模块,主要的作用是用来做压力检测,重量监测等等。博主的这篇博文主要实现功能为,在对重量或者压力进行监测的同时,可以累加或者清零数值,在此基础上就可以对比如饮水量进行统计等等。 HX711模块是市面上比较常见的模块

    2024年02月11日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包