哈哈哈,这篇博客想写很久了,但一直拖着没写,毕竟我可是拖延癌晚期患者。OK,话不多说。
这个题是我们学校校赛的题目,当时刚接触stm32才半个月不到吧,刚刚跟完江科大的课程,只做了单纯的一个寻迹小车,哈哈哈。其实对于送药小车这个题目来讲,电控是很简单的,难的是数字识别,你怎么让数字识别的又快又好是这道题的难点(k210这个是我负责视觉模块的队友负责的,但我这里有以前学长的代码,应该也是可以用的,如果有需要可以私信我)。
那么我先说一说电控方向的思路,其实对于第一问来讲,电控真的相当简单,就是让识别到数字1或数字2的时候进入一个你写的函数中(最好别在主函数中写,不然到时候你想改东西的时候可能一不小心改错一个地方就会出事)。然后的代码就是循迹和识别到岔路口转90°了。循迹这个不用说,是最简单的一个东西,不会的朋友可以去看我下一篇博客。至于蓝牙,当时让我另一个队友去搞,其实是很简单的东西,但当时我们课程确实很紧,就没有搞出来。但我后面用了zigbee,就很快搞出来了。但这个题还是没赶得上用,最后我会附上zigbee端的代码(江科大串口收发稍作修改即可)。至于双车,其实有了蓝牙之后双车就很简单了,完全可以自己写出来的。
那么怎么识别到岔路呢?如果你是选择的视觉巡线(用openmv),那你可以就让你视觉模块的队友去星瞳官网上找代码,这些都是有的。直接openmv识别到岔路口转就行了(这种得要延时一下,因为视觉一定是会提前看到的)。如果是灰度循迹,那就直接全部都识别到的时候转就行了。那对于这道题来讲,我个人是推荐视觉的,主要我们就是用视觉做的,哈哈哈。为什么不推荐灰度,因为灰度会有一定概率被数字遮挡导致提前识别,这是一个比较难以避免的问题(选择哪种12路的可能会好一点?)。
然后就是转90°了。这个但是可把我们给害惨了。当时还不太掌握霍尔编码器的使用方法,只能直接给pwm,这就导致了电池的电量也会影响他的转弯,所以我个人是推荐使用编码器的,记录到一遍轮子的一定脉冲数就说明我转过了90,还是相对比较稳定的。当然还可以使用陀螺仪,但一定不要用mpu6050,不然你的程序可能直接就卡死,就会导致循迹也会抽风的情况,对于这道题还是谨慎使用。(这个代码后面没有继续优化了,所以是直接给pwm的)。
至于药品的识别,直接用一个反射式红外传感器调好阈值就可以了。这个代码江科大模数转换那直接拿过来用就可以了。
openmv和k210和stm32通信代码去csdn搜索“Openmv识别Apriltag码并与stm32进行串口通信”,直接拿来用就好了。
下面上代码
主函数:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Serial.h"
#include "encoder.h"
#include "pid.h"
#include "AD.h"
#include "K210.h"
#include "CROSS1.h"
#include "CROSS2.h"
#include "CROSS3.h"
#include "CROSS4.h"
#include "PWM.h"
#include "LED.h"
uint16_t AD_value;
int main(void)
{
OLED_Init();
Motor_Init();
Serial_Init();
Encoder_Init();
pid_init();
K210_Init();
AD_Init();
LED_Init();
PWM_Init();
OLED_ShowString(1,1,"speed:");
OLED_ShowString(2,1,"speed:");
OLED_ShowString(3,1,"x:");
OLED_ShowString(4,1,"type:");
OLED_ShowString(1,10,"AD:");
OLED_ShowString(2,10,"NUM:");
OLED_ShowString(3,10,"TAY:");
while(1)
{
OLED_ShowNum(1,7,num,2);
OLED_ShowNum(3,3,x_translation ,5);
OLED_ShowNum(4,6,tag_id,2);
if(num == 1 | num == 2)
{
CROSS1();
LED_GREEN_ON();
}
else if(num == 3 | num == 4)
{
CROSS2();
LED_GREEN_ON();
}
else if(num == 5 | num == 6)
{
CROSS3();
LED_GREEN_ON();
}
else if(num == 7 | num == 8)
{
CROSS4();
LED_GREEN_ON();
}
}
}
第一问(tag_id就是openmv识别到不同路口类型传不同的参数):
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "motor.h"
#include "Serial.h"
#include "pid.h"
#include "AD.h"
#include "K210.h"
#include "LED.h"
#include "OLED.h"
uint16_t AD_value1 ;
uint16_t see ;//标识符
uint16_t way1;//停下来的次数,第二次停下就不能再转180
uint16_t dir1;//第几次转弯
#define A 2500//阈值
#define B 2000
#define S1 31//左轮速度
#define S2 31//右轮速度
#define V1 32//补转弯时的误差速度
#define V2 29 //转180速度
#define T1 435//小车转弯前直行延时
#define T2 915 //小车左转弯时延时
#define T5 885 //小车右转弯时延时
#define T6 935 //小车右转弯时延时//数字2
#define T7 915 //小车左转弯时延时//数字2
#define T3 900//小车转180延时
#define T4 375//识别到药房继续往前走
void CROSS1(void)
{
K210_Init();
Motor_Init();
Serial_Init();
pid_init();
AD_Init();
LED_Init();
while(see != 3)
{
OLED_ShowNum(1,13,AD_GetValue(),4);
OLED_ShowNum(2,14,num,2);
OLED_ShowNum(3,14,tay,2);
OLED_ShowNum(1,7,left_speed,2);
OLED_ShowNum(2,7,right_speed,2);
OLED_ShowNum(3,3,x_translation ,5);
OLED_ShowNum(4,6,tag_id,2);
OLED_ShowNum(4,10,dir1,2);
if(num == 1)//识别到数字1,要左转
{
STOP();
if(see == 0)
{
STOP();
if(AD_GetValue() > A)//装上药品
{
see = 1;
}
}
else if(see == 1)
{
pid_contorl();
if(dir1 == 0)
{
if(tag_id == 1)//识别到路口了,此时是数字1,要左转
{
ON(V1,V1);
Delay_ms(T1);
ON(0,S2);
Delay_ms(T2);
}
if(tag_id == 3)//送到药房了,要开始卸药
{
ON(S1,S2);
Delay_ms(T4);
uint16_t i = 1;
LED_RED_ON();
Delay_ms(50);
STOP();
Delay_ms(1000);
way1 += 1;//第一次停下
while(i != 0)
{
if(AD_GetValue() < B)//卸下药品了
{
see = 2;
LED_RED_OFF();
i = 0;
}
if( i == 0)
{
break;
}
}
}
}
else if(dir1 == 1)
{
if(tag_id == 1 )//第二次识别到路口了,此时是返回,要右转
{
ON(V1,V1);
Delay_ms(T1);
ON(S1,0);
Delay_ms(T5);
STOP();
Delay_ms(200);
}
else if(tag_id == 3)//回到药房了
{
ON(S1,S2);
Delay_ms(T4);
LED_GREEN_ON();
STOP();
Delay_ms(1500);
see = 3;
}
}
}
else if(see == 2)//向后转180
{
dir1 += 1;
Delay_ms(500);
Turnover(V2,V2);
Delay_ms(T3);
STOP();
Delay_ms(600);
tag_id = 0;
see = 1;
}
else if(see == 3)//回到出发点了
{
ON(S1,S2);
Delay_ms(T4);
LED_GREEN_ON();
STOP();
}
}
if(num == 2)//要右转
{
STOP();
if(see == 0)
{
STOP();
if(AD_GetValue() > A)//装上药品
{
see = 1;
}
}
else if(see == 1)
{
pid_contorl();
if(dir1 == 0)
{
if(tag_id == 1 )//识别到路口了,此时是数字2,要右转
{
ON(V1,V1);
Delay_ms(T1);
ON(S1,0);
Delay_ms(T6);
}
if(tag_id == 3)//送到药房了,要开始卸药
{
uint16_t i = 1;
ON(S1,S2);
Delay_ms(T4);
LED_RED_ON();
Delay_ms(50);
STOP();
Delay_ms(1000);
way1 += 1;
while(i != 0)
{
if(AD_GetValue() < B)//卸下药品了
{
see = 2;
LED_RED_OFF();
i = 0;
}
}
}
}
else if(dir1 == 1)
{
if(tag_id == 1 )//第二次识别到路口了,要左转(返回时)
{
ON(V1,V1);
Delay_ms(T1);
ON(0,S2);
Delay_ms(T7);
}
else if(tag_id == 3)//送到药房了,要开始卸药
{
ON(S1,S2);
Delay_ms(T4);
LED_GREEN_ON();
Delay_ms(50);
STOP();
Delay_ms(1500);
see = 3;
}
}
}
else if(see == 2)//向后转180
{
dir1++;
Delay_ms(500);
Turnover2(V2,V2);
Delay_ms(T3);
STOP();
Delay_ms(600);
tag_id = 0;
see = 1;
}
else if(see == 3)//回到出发点了
{
ON(S1,S2);
Delay_ms(T4);
LED_GREEN_ON();
STOP();
}
}
}
}
第二问和第三问(题目要求3-8随机摆放,所以这里只给出3和4的,5-8其实是一摸一样的。):
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "motor.h"
#include "Serial.h"
#include "pid.h"
#include "AD.h"
#include "K210.h"
#include "LED.h"
#include "OLED.h"
uint16_t see2 ;//标识符
uint16_t way2;//停下来的次数,第二次停下就不能再转180
uint16_t dir2;//第几次转弯
uint16_t far;//远端药房标识符
uint16_t wey1,wey2;//1:数字三返回标识符,2:数字4返回标识符
uint16_t lyq1,lyq2;//中段药房返回时标识符
#define C 2500 //光敏电阻阈值
#define D 1800
#define S3 31//左轮速度
#define S4 31//右轮速度
#define V3 32//补转弯时的误差速度
#define V4 29 //转180速度
#define T4 430//小车转弯前直行延时
#define T6 905//小车转180延时
#define T5 890 //小车左转弯时延时//左端的药房
#define T7 865//小车右转弯延时//左端的药房
#define T8 400//小车识别到药房前进延时
#define T9 965 //小车左转弯时延时//右边的药房
#define T10 960 //小车右转弯时延时//右边的药房
#define T11 300
void CROSS2(void)
{
Motor_Init();
Serial_Init();
pid_init();
AD_Init();
K210_Init();
LED_Init();
while(see2!=6)
{
OLED_ShowNum(1,13,AD_GetValue(),4);
OLED_ShowNum(2,14,num,2);
OLED_ShowNum(3,14,tay,2);
OLED_ShowNum(1,7,left_speed,2);
OLED_ShowNum(2,7,right_speed,2);
OLED_ShowNum(3,3,x_translation ,5);
OLED_ShowNum(4,6,tag_id,2);
// OLED_ShowNum(4,10,dir2,2);
if( num == 3 & tay == 9)//开始时从k210接收到的数字信息
{
if(see2 == 0)
{
STOP();
if(AD_GetValue() > C)//装上药品
{
see2 = 2;
}
}
}
if( num == 4 & tay == 9)//开始时从k210接收到的数字信息
{
if(see2 == 0)
{
OLED_ShowNum(3,14,tay,2);
STOP();
if(AD_GetValue() > C)//装上药品
{
see2 = 3;
}
}
}
if(see2 == 2)//一开始识别数字3
{
pid_contorl();
if (dir2 == 0)//前去药房
{
OLED_ShowNum(3,14,tay,2);
if (tag_id == 1 & tay == 1)//数字3在左边,且是中端药房
{
OLED_ShowNum(3,14,tay,2);
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wey1 = 1;
STOP();
Delay_ms(T11);
OLED_ShowNum(4,10,wey1,2);
}
else if(tag_id == 1 & tay ==2)//数字3在右边,且是中端药房
{
OLED_ShowNum(3,14,tay,2);
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wey1 = 2;
STOP();
Delay_ms(T11);
}
else if (tag_id == 2 & tay ==1)//数字3在左边,且是远端药房
{
OLED_ShowNum(3,14,tay,2);
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
far+=1;
STOP();
Delay_ms(T11);
}
else if(tag_id == 2 & tay == 2)//数字3在右边,且是远端药房
{
OLED_ShowNum(3,14,tay,2);
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
far+=2;
STOP();
Delay_ms(T11);
}
if( far == 1 & tag_id == 2)//远端药房的第二次转弯(第一次向左转)
{
if( tay == 1)//数字三在左边(左下方)第二次识别到数字3且还是在左边
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wey1 = 3;//远端药房
STOP();
Delay_ms(T11);
}
else if ( tay ==2 )//数字3在右端(左上方)第二次识别到数字3且是在右边
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wey1 = 4;//远端药房
STOP();
Delay_ms(T11);
}
}
if(far == 2 & tag_id == 2)//远端药房的第二次转弯(第一次向右转)
{
if(tay == 1)//数字三在左边(右上方)第二次识别到数字3且是在左边
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wey1 = 5;//远端药房
STOP();
Delay_ms(T11);
}
else if (tay == 2)//数字3在右端(右下方)第二次识别到数字3且是在右边
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wey1 = 6;//远端药房
STOP();
Delay_ms(T11);
}
}
if(tag_id == 3)//送到药房了,要开始卸药
{
ON(S3,S4);//因为是视觉巡线,就会导致无法完全到达终点,所以再给一个速度
Delay_ms(T8);
uint16_t i = 1;
LED_RED_ON();
Delay_ms(50);
STOP();
way2 += 1;//第一次停下
while(i!=0)
{
if(AD_GetValue() < D)//卸下药品了
{
LED_RED_OFF();
see2 = 4;//开始转180
i = 0;
}
}
}
}
else if(dir2 == 1)//返回了
{
OLED_ShowNum(4,10,wey1,2);
if(wey1 == 1)//开始3在左边向左转(中端药房)
{
if( tag_id == 1 )//第二次识别到路口了,此时是返回,要右转,因为一开始识别到3在左边
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
STOP();
Delay_ms(T11);
wey1 =0;
}
}
else if(wey1 == 2)//开始3在右边向右转(中端药房)
{
if(tag_id == 1 )//第二次识别到路口了,要左转
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
STOP();
Delay_ms(T11);
wey1 = 0;
}
}
else if(wey1 == 3)//三在远端药房的左下方
{
uint16_t tm3 = 0;//第几次识别到T型路口
if(tag_id == 2)
{
if(tm3 == 0)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
STOP();
Delay_ms(T11);
tm3++;
}
else if(tm3 == 1 )
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
STOP();
Delay_ms(T11);
tm3++;
}
}
}
else if(wey1 == 4)//三在远端药房的左上方
{
uint16_t tn3 = 0; //第几次识别到T型路口
if(tag_id == 2)
{
if(tn3 == 0)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
tn3++;
STOP();
Delay_ms(T11);
}
else if(tn3 == 1 )
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
STOP();
Delay_ms(T11);
tn3++;
}
}
}
else if(wey1 == 5)//三在远端药房的右上方
{
uint16_t tt3 = 0;//第几次识别到T型路口
if( tag_id == 2)
{
if(tt3 == 0)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
tt3++;
STOP();
Delay_ms(T11);
}
else if(tt3 == 1)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
STOP();
Delay_ms(T11);
tt3 ++;
}
}
}
else if(wey1 == 6)//三在远端药房的右下方
{
uint16_t tq3 = 0;//第几次识别到T型路口
if( tag_id == 2)
{
if(tq3 == 0)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
tq3++;
STOP();
Delay_ms(T11);
}
else if(tq3 == 1 )
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
STOP();
Delay_ms(T11);
tq3++;
}
}
}
if(tag_id == 3)//回到出发点
{
ON(S3,S4);
Delay_ms(T8);
LED_GREEN_ON();
STOP();
Delay_ms(1500);
see2 = 6;
}
}
}
else if(see2 == 3)//一开始识别数字4
{
pid_contorl();
if (dir2 == 0)//第一次转弯
{
// if(tay == 1)//左边
// {
// lyq2 += 2;
// }
// if(tay == 2)//右边
// {
// lyq2 += 3;
// }
if (tag_id == 1 & tay == 1)//数字4在左边,且是中端药房
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
tag_id = 0;
Delay_ms(250);
wey2 = 1;
}
else if(tag_id == 1 & tay == 2)//数字4在右边,且是中端药房
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
tag_id = 0;
Delay_ms(250);
wey2 = 2;
}
else if (tag_id == 2 & tay == 1)//数字4在左边,且是远端药房
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
far+=1;
}
else if(tag_id == 2 & tay == 2)//数字4在右边,且是远端药房
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
far+=2;
}
if( far == 1 & tag_id == 2)//远端药房的第二次转弯(第一次向左转)
{
if( tay == 1)//数字4在左边(左下方)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wey2 = 3;//远端药房
Delay_ms(T11);
}
else if ( tay == 2)//数字4在右端(左上方)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wey2 = 4;//远端药房
Delay_ms(T11);
}
}
if( far == 2 & tag_id == 2)//远端药房的第二次转弯(第一次向右转)
{
if( tay == 1)//数字4在左边(右上方)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wey2 = 5;//远端药房
Delay_ms(T11);
}
else if ( tay == 2)//数字3在右端(右下方)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wey2 = 6;//远端药房
Delay_ms(T11);
}
}
if(tag_id == 3)//送到药房了,要开始卸药
{
ON(S3,S4);//因为是视觉巡线,就会导致无法完全到达终点,所以再给一个速度
Delay_ms(T8);
uint16_t i = 1;
LED_RED_ON();
Delay_ms(50);
STOP();
way2 += 1;//第一次停下
while(i!=0)
{
if(AD_GetValue() < D)//卸下药品了
{
LED_RED_OFF();
see2 = 5;//开始转180
i = 0;
}
}
}
}
else if(dir2 == 1)
{
if(wey2 == 1)//开始4在左边向左转(中端药房)
{
if(tag_id == 1 )//第二次识别到路口了,此时是返回,要右转,因为一开始识别到4在左边
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
}
}
else if(wey2 == 2)//开始4在右边向右转(中端药房)
{
if(tag_id == 1 )//第二次识别到路口了,要左转
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
}
}
else if(wey2 == 3)//4在远端药房的左下方
{
uint16_t wm3 = 0;//第几次识别到T型路口
if(wm3 == 0 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wm3++;
}
else if(wm3 == 1 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
}
}
else if(wey2 == 4)//4在远端药房的左上方
{
uint16_t wn3 = 0;//第几次识别到T型路口
if(wn3 == 0 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wn3++;
}
else if(wn3 == 1 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
}
}
else if(wey2 == 5)//4在远端药房的右上方
{
uint16_t wt3 = 0;//第几次识别到T型路口
if(wt3 == 0 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(S3,0);
Delay_ms(T7);
wt3++;
}
else if(wt3 == 1 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
}
}
else if(wey2 == 6)//三在远端药房的右下方
{
uint16_t wq3 = 0;//第几次识别到T型路口
if(wq3 == 0 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
wq3++;
}
else if(wq3 == 1 & tag_id == 2)
{
ON(V3,V3);
Delay_ms(T4);
ON(0,S4);
Delay_ms(T5);
}
}
if(tag_id == 3)//回到出发点
{
ON(S3,S4);
Delay_ms(T8);
LED_GREEN_ON();
STOP();
Delay_ms(1500);
see2 = 6;
}
}
}
else if(see2 == 4)//识别3向后转180
{
Delay_ms(500);
Turnover(V4,V4);
Delay_ms(T6);
STOP();
tag_id = 0;
Delay_ms(800);
see2 = 2;
dir2++;
}
else if(see2 == 5)//识别4向后转180
{
Delay_ms(500);
Turnover(V4,V4);
Delay_ms(T6);
STOP();
tag_id = 0;
Delay_ms(800);
see2 = 3;
dir2++;
}
else if(see2 == 6)//回到出发点了
{
LED_GREEN_ON();
STOP();
}
}
}
zigbee端代码(使用时赋好值Zigbee_TxPacket[4], Zigbee_RxPacket[4],然后使用Zigbee_SendPacket()即可,zigbee配置去b站上搜索zigbee配置,出来第一个跟着做就好了):
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Zigbee_TxPacket[4]; //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
uint8_t Zigbee_RxPacket[4]; //定义接收数据包数组
uint8_t Zigbee_RxFlag; //定义接收数据包标志位
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void Zigbee_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 115200; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART3, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*中断输出配置*/
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART3, ENABLE); //使能USART1,串口开始运行
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Zigbee_SendByte(uint8_t Byte)
{
USART_SendData(USART3, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:串口发送一个数组
* 参 数:Array 要发送数组的首地址
* 参 数:Length 要发送数组的长度
* 返 回 值:无
*/
void Zigbee_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++) //遍历数组
{
Zigbee_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Zigbee_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Zigbee_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Zigbee_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Zigbee_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Zigbee_SendByte(Number / Zigbee_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Zigbee_SendByte(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Zigbee_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Zigbee_SendString(String); //串口发送字符数组(字符串)
}
/**
* 函 数:串口发送数据包
* 参 数:无
* 返 回 值:无
* 说 明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
*/
void Zigbee_SendPacket(void)
{
Zigbee_SendByte(0xFF);
Zigbee_SendArray(Zigbee_TxPacket, 4);
Zigbee_SendByte(0xFE);
}
/**
* 函 数:获取串口接收数据包标志位
* 参 数:无
* 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
*/
uint8_t Zigbee_GetRxFlag(void)
{
if (Zigbee_RxFlag == 1) //如果标志位为1
{
Zigbee_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
/**
* 函 数:USART1中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void USART3_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART3, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART3); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == 0xFF) //如果数据确实是包头
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据*/
else if (RxState == 1)
{
Zigbee_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
if (pRxPacket >= 4) //如果收够4个数据
{
RxState = 2; //置下一个状态
}
}
/*当前状态为2,接收数据包包尾*/
else if (RxState == 2)
{
if (RxData == 0xFE) //如果数据确实是包尾部
{
RxState = 0; //状态归0
Zigbee_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE); //清除标志位
}
}
视觉pid(直接调用pid_control(),这里的ON( ,)其实就是驱动电机,这个代码我就不加了,非常简单):
#include <stdio.h>
#include <stdlib.h>
#include "stm32f10x.h" // Device header
#include "Serial.h"
#include "encoder.h"
#include "PWM.h"
#include "motor.h"
#include "OLED.h"
#define PID_KP 0.2
#define PID_KI 0.01
#define PID_KD 18
#define tar 30 //目标速度
#define MAX_SPEED 60 //限幅速度
#define MIN_SPEED 0
int32_t left_speed ,right_speed , pid_output1;//左轮速度,右轮速度,pid计算后的输出值
struct pid{
int16_t error;
int16_t last_error;
float ki;
float kd;
float kp;
// int16_t ferror;//滤波后的误差
// int16_t flast_error; //上次滤波后的误差
}pid;
void left_xianfu(void)//限制左轮速度
{
if(left_speed <= MIN_SPEED)
{
left_speed = MIN_SPEED;
}
else if( left_speed >= MAX_SPEED )
{
left_speed = MAX_SPEED;
}
}
void right_xianfu(void)//限制右轮速度
{
if( right_speed <= MIN_SPEED )
{
right_speed = MIN_SPEED;
}
else if( right_speed >= MAX_SPEED )
{
right_speed = MAX_SPEED;
}
}
void pid_init( void )
{
pid.error= 0;
pid.last_error = 0;
pid.kp = PID_KP;
pid.ki = PID_KI;
pid.kd = PID_KD;
return;
}
int32_t pid_output( void) //此时计算出的pid误差值
{
pid.error = (x_translation-32);//误差传入,靠左接收到的x_translation减小,要右转,左轮速度大
// float a = 0.3;//滤波
// pid.ferror = a*pid.error + (1-a)* pid.flast_error;//滤波算法
int32_t P;
static int32_t I;
int32_t D;
int32_t pid_output;
P = pid.kp * pid.error;
I +=pid.ki * pid.error;
D = pid.kd * ( pid.error - pid.last_error );
pid.last_error= pid.error;
// pid.flast_error= pid.ferror;
pid_output = P + I + D;
return pid_output;
}
void speed_by_pid( int32_t pid_output )//pid改变速度使小车在直线上走
{
if(!pid.error)
{
left_speed = tar;
right_speed = tar;
ON(tar,tar);
return;
}
left_speed = left_speed + pid_output;
left_xianfu();
right_speed = right_speed - pid_output;
right_xianfu();
ON(left_speed,right_speed);
return ;
}
void pid_contorl()
{
left_speed= 30;
right_speed= 30;
pid_output1=pid_output();
OLED_ShowSignedNum(2,10,pid_output1,4);
speed_by_pid(pid_output1);
}
//void pid_control_motor(void)
//{
// if(pid.error >= 7 && pid.error <= 9){
// Right2();
// return;
// }else if(pid.error >= -9 && pid.error <= -7){
// Left2();
//} return;
// ON2();
// }
//