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. 蓝牙模块

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

 需要用到的资源如下:

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

这次的程序写了蛮多注释的,也是花了三天时间做的,感觉最大的问题就是串口与定时器关系的问题,程序上可能还存在错误,希望大家多多指正!!!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32超级蓝牙小车——多功能蓝牙小车基于STM32F103C8T6,具备PID循迹、跟踪、有源蜂鸣器播放音乐、蓝牙遥控和AD采集DMA转运等多种功能

发表评论