最近参见了学校的电子设计大赛(三流财经类学校比较水其中的题目大多数是这几年点电赛的题目)当时看到了这个题,几个人商量之后,就决定做这个题目,题目如下在这里插入图片描述(用的板子是原子哥的STM32 mini版)
初步想的是用两个超声波,分别放在X、Y轴,分别测量物体距离超声波轴的值,然后减去超声波距离X、Y轴的距离作为机械误差,最后得到坐标,然后在LCD上显示出来,但是在测量中发现,一个超声波模块其左右两侧最大测量范围只有4CM,所有就只能选择用6个超声波对10CM的范围全覆盖,X,Y各三个。(用的最普通HC_SR_04超声波模块)
最后的成品也如上图所示,单个的超声波测距恐怕是对在座的大佬们一如反掌(文尾也会附带单个超声波的例程哟!),但这个同时控制六个超声波其实也不难,因为要避免各个发出的超声波存在干扰,所以要在一个在测距是其他超声波是关闭的,依次轮流打开,有点类似于动态数码管的显示。
因为要用到6个超声波所以要用到6个定时器的通道口,这里我用的是T2 的123通道和T4的123通道,进行一般的初始化使能就可以了
这里我只写定时器2的三个通道的初始化,定时器4的初始化是一样的 HCSR04_TIM2_Cap_Init(0XFFFF,72-1); //以1MHZ的频率计数,1us记一次数
void HCSR04_TIM2_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO初始化的结构体
TIM_ICInitTypeDef TIM2_ICInitStructure;//定义通道的初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定义定时器的初始化结构体
NVIC_InitTypeDef NVIC_InitStructure;//中断初始化结构体
//使能相关时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
//三个超声波模块的ECHO引脚初始化
//ECHO1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 ECH0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0输入无需设置速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
//ECHO2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //PA1 ECH0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA1
GPIO_Init(GPIOA, &GPIO_InitStructure);
//ECHO3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2 ECH0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA2
GPIO_Init(GPIOA, &GPIO_InitStructure);
// //三个超声波模块的TRIF引脚初始化 (引脚是随机选的不是很有序)用的C13 C0 C12
//控制端一
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //PC13
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//控制端二
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PC0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure); //³õʼ»¯ÍâÉèGPIO
//控制端三
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PC2½ÓTRIG
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure); //³õʼ»¯ÍâÉèGPIO
//拉低电平等待高电平
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0下拉
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA1下拉
GPIO_ResetBits(GPIOA,GPIO_Pin_2); //PA2下拉
//定时器二的初始化
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//三个通道的初始化
//oc1
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_1;//通道1
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//上升沿触发
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //时钟不分频
TIM2_ICInitStructure.TIM_ICFilter = 0x00;//不滤波
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
//TIM2 OC2
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM2_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
//TIM2 OC3
TIM2_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM2_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM2_ICInitStructure);
//因为要用到中断所以要初始化中断
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure); ÷
//使能三个通道和更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_CC1,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_CC2,ENABLE);
TIM_ITConfig(TIM2,TIM_IT_CC3,ENABLE);
TIM_Cmd(TIM2,ENABLE );使能定时器二
}
这里是中断处理函数,设置了两个变量,用了一个协议来记录高电平的时间,
u8 TIM2CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH1_CAPTURE_VAL; //输入捕获值
u8 TIM2CH2_CAPTURE_STA=0;//输入捕获状态
u16 TIM2CH2_CAPTURE_VAL; //输入捕获值
u8 TIM2CH3_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH3_CAPTURE_VAL; //输入捕获值
//定时器二中断处理函数
void TIM2_IRQHandler(void)
{
//通道1捕获中断函数
if((TIM2CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //开始捕获但还未捕获完成
{
if(TIM2CH1_CAPTURE_STA&0X40)//已经捕获到了高电平
{
if((TIM2CH1_CAPTURE_STA&0X3F)==0X3F)//高电平的时间太长了
{
TIM2CH1_CAPTURE_STA|=0X80;//标记成功捕获一次
TIM2CH1_CAPTURE_VAL=0XFFFF;
}else TIM2CH1_CAPTURE_STA++; =//一次溢出的时间
}
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)//捕获以发生捕获事件
{
if(TIM2CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM2CH1_CAPTURE_STA|=0X80; //标记成功捕获到一个下降沿
TIM2CH1_CAPTURE_VAL=TIM_GetCapture1(TIM2);
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Rising); //设为上升沿捕获
}else //未开始 第一次捕获到上升沿
{
TIM2CH1_CAPTURE_STA=0; //清空
TIM2CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM2,0); //计数器的值设为0
TIM2CH1_CAPTURE_STA|=0X40; /捕获到了上升沿
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC1P=1设为下降沿捕获
}
}
}
//IC2中断捕获函数 内容大体与OC1函数一致,要细心把相关位换成通道2的标志字
if((TIM2CH2_CAPTURE_STA&0X80)==0)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if(TIM2CH2_CAPTURE_STA&0X40)
{
if((TIM2CH2_CAPTURE_STA&0X3F)==0X3F)
{
TIM2CH2_CAPTURE_STA|=0X80;
TIM2CH2_CAPTURE_VAL=0XFFFF;
}else TIM2CH2_CAPTURE_STA++;
}
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
{
if(TIM2CH2_CAPTURE_STA&0X40)
{
TIM2CH2_CAPTURE_STA|=0X80;
TIM2CH2_CAPTURE_VAL=TIM_GetCapture2(TIM2);
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising);
}else
{
TIM2CH2_CAPTURE_STA=0;
TIM2CH2_CAPTURE_VAL=0;
TIM_SetCounter(TIM2,0);
TIM2CH2_CAPTURE_STA|=0X40;
TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling);
}
}
}
//IC3通道捕获函数
if((TIM2CH3_CAPTURE_STA&0X80)==0)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if(TIM2CH3_CAPTURE_STA&0X40)
{
if((TIM2CH3_CAPTURE_STA&0X3F)==0X3F)
{
TIM2CH3_CAPTURE_STA|=0X80;
TIM2CH3_CAPTURE_VAL=0XFFFF;
}else TIM2CH3_CAPTURE_STA++;
}
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET)
{
if(TIM2CH3_CAPTURE_STA&0X40)
{
TIM2CH3_CAPTURE_STA|=0X80;
TIM2CH3_CAPTURE_VAL=TIM_GetCapture3(TIM2);
TIM_OC3PolarityConfig(TIM2,TIM_ICPolarity_Rising);
}else
{
TIM2CH3_CAPTURE_STA=0;
TIM2CH3_CAPTURE_VAL=0;
TIM_SetCounter(TIM2,0);
TIM2CH3_CAPTURE_STA|=0X40;
TIM_OC3PolarityConfig(TIM2,TIM_ICPolarity_Falling);
}
}
}
//清除中断标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
}
以上函数写完之后就可以使用超声波模块了,但要用以下函数将高电平时间转换成距离
double HCSR_T2Length_1; //长度
double HCSR_T2_Length_1(void)
{
u32 temp=0;
delay_ms(10);
if(TIM2CH1_CAPTURE_STA&0X80)//已经成功捕获到一次上升沿
{
temp=TIM2CH1_CAPTURE_STA&0X3F;
temp*=65536;//溢出时间的总和(最大值65535)
temp+=TIM2CH1_CAPTURE_VAL;//得到总的高电平时间us
//距离公式为 长度=高电平时间*声速/2
HCSR_T2Length_1 = temp*340/2000; //单位mm
TIM2CH1_CAPTURE_STA=0;//开启下一次捕获
}
return HCSR_T2Length_1;
}
//超声二的距离换算函数
double HCSR_T2Length_2;
double HCSR_T2_Length_2(void)
{
u32 temp=0;
delay_ms(10);
if(TIM2CH2_CAPTURE_STA&0X80)
{
temp=TIM2CH2_CAPTURE_STA&0X3F;
temp*=65536;
temp+=TIM2CH2_CAPTURE_VAL;
HCSR_T2Length_2 = temp*340/2000;
TIM2CH2_CAPTURE_STA=0;
}
return HCSR_T2Length_2 ;//50Ϊ»úеÎó²î
}
//超声三的距离换算函数
double HCSR_T2Length_3;
double HCSR_T2_Length_3(void)
{
u32 temp=0;
delay_ms(10);
if(TIM2CH3_CAPTURE_STA&0X80)//³É¹¦²¶»ñµ½ÁËÒ»´ÎÉÏÉýÑØ
{
temp=TIM2CH3_CAPTURE_STA&0X3F;
temp*=65536;//Òç³öʱ¼ä×ܺͣ¨Òç³öÒ»´Î¼ÆÊý65536£©
temp+=TIM2CH3_CAPTURE_VAL;//µÃµ½×ܵĸߵçƽʱ¼ä us
//¹«Ê½Îª ³¤¶È=¸ßµçƽʱ¼ä*ÉùËÙ/2
HCSR_T2Length_3 = temp*340/2000; //µ¥Î»Îªmm
// printf("HIGH:%d us\r\n",temp);//´òÓ¡×ܵĸߵãƽʱ¼ä
TIM2CH3_CAPTURE_STA=0;//¿ªÆôÏÂÒ»´Î²¶»ñ
}
return HCSR_T2Length_3;//54Ϊ»úеÎó²î
}
最后时三个的开启信号(避免各个超声波之间干扰延时5ms开启下一个超声波)
void HCSR04_TIM2_StartMeasure_1(void)
{
GPIO_SetBits(GPIOC,GPIO_Pin_0);
delay_us(20); //给20us的高电平时间
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
delay_ms(5);
}
void HCSR04_TIM2_StartMeasure_2(void)
{
GPIO_SetBits(GPIOC,GPIO_Pin_13);
delay_us(20);
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
delay_ms(5);
}
void HCSR04_TIM2_StartMeasure_3(void)
{
GPIO_SetBits(GPIOC,GPIO_Pin_12);
delay_us(20);
GPIO_ResetBits(GPIOC,GPIO_Pin_12);
delay_ms(5);
}
以下是主函数,可以用串口显示超声波返回的距离
int main(void)
{
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200); /
LED_Init();
HCSR04_TIM2_Cap_Init(0XFFFF,72-1); //以1us的时间计数
HCSR04_TIM4_Cap_Init(0XFFFF,72-1); //以1us的时间计数
while(1)
{
HCSR04_TIM2_StartMeasure_1();
printf("Ultrasonic measure the length is TIM2_OC1 %f mm\r\n",HCSR_T2_Length_1());
HCSR04_TIM2_StartMeasure_2();
printf("Ultrasonic measure the length is TIM2_OC2 %f mm\r\n",HCSR_T2_Length_2());
HCSR04_TIM2_StartMeasure_3();
printf("Ultrasonic measure the length is TIM2_OC3 %f mm\r\n",HCSR_T2_Length_3());
HCSR04_TIM4_StartMeasure_1();
printf("Ultrasonic measure the length is TIM4_OC1 %f mm\r\n",HCSR_T4_Length_1());
HCSR04_TIM4_StartMeasure_2();
printf("Ultrasonic measure the length is TIM4_OC2 %f mm\r\n",HCSR_T4_Length_2());
HCSR04_TIM4_StartMeasure_3();
printf("Ultrasonic measure the length is TIM4_OC3 %f mm\r\n\r\n",HCSR_T4_Length_3());
delay_ms(1000);
}
}
最后的其实代码最后不要放在一个函数内,这样容易出现各个超声波之间发生混乱!!!(我也不知道为甚麽,我猜是因为进入中断的时间发生了冲突,在中断函数里面反转极性不恰当导致),但分开写的话是等待一个一个测试完成之后才开启下一个,不会发生混乱的现象。
最后组装完成之后经过测试,对于长宽5cm的物体也就是大于超声波模块的宽度的物体,精度大约在2-1mm之内,但对于测小的物体,小于超声波模块的宽度的物体,误差会越来越大,差20-30 mm也是正常的,原因就是HC_SR_04超声波是两个大眼睛发射超声波,物体小于两个大眼睛的宽度时,只会返回一部分的超声波,会导致超声波强度不够,计算距离偏远。例如你用一个尺子和一只笔在同一个位置用超声波侧距离,用笔(竖直放)测的的距离大约比用尺子大2cm。不同的位置,误差尽不相同,不会太大,也就几厘米,但是在以mm为精度的话,2—3CM误差太大了。
这也是最艰难的地方,不同的物体返回的误差不同,这也就是说,你用不同的物体测得的距离是不同的。滤波算法是没用的,这是硬性误差。
最后我们是决定用小的物体,把每个地方的误差算出来,直接减去,然后分别设置了测大物体和小物体两种模式。大物体精度在3mm内,小物体大约在5mm左右。
如果哪位大佬有好的算法,还请评论一下,小弟感激不尽。
最后附上三套源码
1是单个的超声波输入捕获输入捕获超声波
2是六路超声波同时测距然后串口发送六路超声波
3是完全版在OLED屏幕上显示坐标OLED屏幕上显示坐标