一、项目时间:2023.7.24~11.26
二、实现效果:通过蓝牙控制小车运动与模式转换
模式一:循迹模式
模式二:跟踪模式
模式三:音乐模式
模式四:控制运动模式
三、使用模块:
- STM32F103C8T6核心板 * 1
- L298N电机驱动模块 * 1
- TCRT5000L五路红外循迹传感器模块 * 1
- DC3V-6V黄色直流减速电机-TT * 4
- 锂电池组电源 6V * 1
- OLED屏幕-四针 * 1
- DC - DC 12V装3.3v 5v 12v 电源模块
- HC-SR04超声波模块
- 光敏模块+热敏模块
- 八个灯
- 蓝牙模块
下面是超级蓝牙小车实物图:
需要用到的资源如下:
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
这次的程序写了蛮多注释的,也是花了三天时间做的,感觉最大的问题就是串口与定时器关系的问题,程序上可能还存在错误,希望大家多多指正!!!文章来源地址https://www.toymoban.com/news/detail-763889.html
到了这里,关于STM32超级蓝牙小车——基于STM32F103C8T6的多功能蓝牙小车(PID循迹、跟踪、有源蜂鸣器播放音乐、蓝牙遥控、AD采集+DMA转运等超多元素小车)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!